commit 162bbde1fe114fb7d023d1a43582e8a00f735d40 Author: Tim Evans Date: Wed Mar 4 14:21:21 2026 +0000 Initial Ringworld system publish with companion content module and starter world data diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e97a76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.vite/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..0de04f6 --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# Ringworld Foundry System (v12-v13 MVP) + +Ringworld is a custom Foundry VTT game system for BRP-like percentile play with hit locations, location armor, and energy-tracked weapons. + +## Online Install (GitHub) + +Repository: +- https://github.com/slaguru666/Ringworld + +System manifest URL: +- `https://raw.githubusercontent.com/slaguru666/Ringworld/main/system.json` + +Companion content module manifest URL: +- `https://raw.githubusercontent.com/slaguru666/Ringworld/main/ringworld-content/module.json` + +Starter world data archive: +- `https://raw.githubusercontent.com/slaguru666/Ringworld/main/dist/ringworld-starter-world.zip` + +## Status + +MVP implemented: +- Actor types: `explorer`, `creature`, `vehicle` (stub) +- Item types: `skill`, `weapon`, `armor`, `gear`, `species` +- Percentile checks with `Special Success` and `Fumble` +- 1d20 hit location roll with Ringworld 7-location mapping +- Weapon workflow with target selection, hit location, armor subtraction, and damage to both location HP and general HP +- Auto conditions at location 0 or less (`prone`, `unconscious`, limb unusable flags) +- Armor overlap stacking across all equipped armor items covering a location +- Energy use from actor pool or linked power pack item +- World catalog seeding for Ringworld equipment, armor, weapons, gear, and species +- Character tools: roll all characteristics and random explorer generation + +## Install / Build + +From this project folder: + +```bash +cd /Users/timevans/FoundryVTT/foundryvtt-ringworld +npm install +npm run build +``` + +Build output: +- `module/ringworld.mjs` +- `styles/ringworld.css` + +## Foundry placement + +Foundry loads systems from `.../data/systems` (or `.../Data/systems` on case-insensitive macOS paths). + +Recommended live-dev symlink: + +```bash +ln -s /Users/timevans/FoundryVTT/foundryvtt-ringworld /Users/timevans/FoundryVTT/data/systems/ringworld +``` + +If `ringworld` already exists, remove or rename it first. + +## Using the system + +1. In Foundry, create a world using **Ringworld** system. +2. Create an **Explorer** actor. +3. In the **Skills** tab, click **Populate Ringworld Base Skills** to add all canonical Ringworld skills. +4. Add extra custom skills or weapon items from the actor sheet as needed. +5. For energy weapons: + - Create a `gear` item with `Is Power Pack` checked. + - Set weapon `Power Source` to `Power Pack Item` and select the pack. +6. Target a token, then click **Use** on a weapon. +7. Choose hit location (or random 1d20) and confirm damage application in chat. +8. Use header tools on Explorer sheet: + - **Roll Characteristics**: rolls all characteristic values from gravity profile + - **Random Character**: random species + gravity + rolled stats + starter kit + +## Catalog and Generators + +- On first GM world load, the system seeds world Items with Ringworld catalog entries. +- You can reseed manually from **Items Directory** using `Seed Ringworld Items`. +- You can create a random explorer from **Actors Directory** using `Create Random Explorer`. +- If you install the companion module **Ringworld Content**, you can import everything with one button: + - **Items Directory** -> `Import Ringworld Content` + +## Rules implemented for v1 + +- Roll-under percentile checks (`1d100 <= target`) +- Special Success: roll `<= floor(target / 5)` +- Fumble: + - target `< 50`: roll `96-100` + - target `>= 50`: roll `100` +- Hit locations (1d20): + - `01-03 Right Leg` + - `04-06 Left Leg` + - `07-10 Abdomen` + - `11-15 Chest` + - `16-17 Right Arm` + - `18-19 Left Arm` + - `20 Head` +- Locational damage also reduces general HP (same final damage) +- Armor subtracts 1:1 damage and stacks across overlapping covered armor pieces + +## Seeds + +`seeds/` contains starter data: +- `base-skills.json` full base skill list +- `example-items.json` sample skill/weapon/armor/power pack +- `example-explorer.json` sample explorer with items + +These are JSON templates for manual import or scripted pack generation. + +## Notes + +- Content is numeric/stat-driven only. No long rulebook text is embedded. +- Vehicle combat and computer subsystems remain backlog. +- `world-data/` contains a starter world shell and packaging files for server transfer. diff --git a/assets/icons/armor.svg b/assets/icons/armor.svg new file mode 100644 index 0000000..ce635f8 --- /dev/null +++ b/assets/icons/armor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/armor/arm-bracers.svg b/assets/icons/armor/arm-bracers.svg new file mode 100644 index 0000000..dff35b7 --- /dev/null +++ b/assets/icons/armor/arm-bracers.svg @@ -0,0 +1,7 @@ + + + + + + AB + diff --git a/assets/icons/armor/armored-coat.svg b/assets/icons/armor/armored-coat.svg new file mode 100644 index 0000000..5bc44b1 --- /dev/null +++ b/assets/icons/armor/armored-coat.svg @@ -0,0 +1,7 @@ + + + + + + AC + diff --git a/assets/icons/armor/cloth-vest.svg b/assets/icons/armor/cloth-vest.svg new file mode 100644 index 0000000..a2c1ee0 --- /dev/null +++ b/assets/icons/armor/cloth-vest.svg @@ -0,0 +1,7 @@ + + + + + + CV + diff --git a/assets/icons/armor/combat-harness.svg b/assets/icons/armor/combat-harness.svg new file mode 100644 index 0000000..46809d6 --- /dev/null +++ b/assets/icons/armor/combat-harness.svg @@ -0,0 +1,7 @@ + + + + + + CH + diff --git a/assets/icons/armor/combat-helmet.svg b/assets/icons/armor/combat-helmet.svg new file mode 100644 index 0000000..e0104b0 --- /dev/null +++ b/assets/icons/armor/combat-helmet.svg @@ -0,0 +1,7 @@ + + + + + + HM + diff --git a/assets/icons/armor/full-combat-suit.svg b/assets/icons/armor/full-combat-suit.svg new file mode 100644 index 0000000..4f5a439 --- /dev/null +++ b/assets/icons/armor/full-combat-suit.svg @@ -0,0 +1,7 @@ + + + + + + FS + diff --git a/assets/icons/armor/impact-suit.svg b/assets/icons/armor/impact-suit.svg new file mode 100644 index 0000000..4fc7be8 --- /dev/null +++ b/assets/icons/armor/impact-suit.svg @@ -0,0 +1,7 @@ + + + + + + IS + diff --git a/assets/icons/armor/leg-guards.svg b/assets/icons/armor/leg-guards.svg new file mode 100644 index 0000000..071bdcf --- /dev/null +++ b/assets/icons/armor/leg-guards.svg @@ -0,0 +1,7 @@ + + + + + + LG + diff --git a/assets/icons/armor/soft-combat-vest.svg b/assets/icons/armor/soft-combat-vest.svg new file mode 100644 index 0000000..b0da820 --- /dev/null +++ b/assets/icons/armor/soft-combat-vest.svg @@ -0,0 +1,7 @@ + + + + + + SV + diff --git a/assets/icons/gear.svg b/assets/icons/gear.svg new file mode 100644 index 0000000..7d0c552 --- /dev/null +++ b/assets/icons/gear.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/gear/air-reprocessor.svg b/assets/icons/gear/air-reprocessor.svg new file mode 100644 index 0000000..f9edee7 --- /dev/null +++ b/assets/icons/gear/air-reprocessor.svg @@ -0,0 +1,7 @@ + + + + + + AR + diff --git a/assets/icons/gear/autodoc-portable.svg b/assets/icons/gear/autodoc-portable.svg new file mode 100644 index 0000000..09fa561 --- /dev/null +++ b/assets/icons/gear/autodoc-portable.svg @@ -0,0 +1,7 @@ + + + + + + AD + diff --git a/assets/icons/gear/bioscanner.svg b/assets/icons/gear/bioscanner.svg new file mode 100644 index 0000000..9f55533 --- /dev/null +++ b/assets/icons/gear/bioscanner.svg @@ -0,0 +1,7 @@ + + + + + + BS + diff --git a/assets/icons/gear/field-toolkit.svg b/assets/icons/gear/field-toolkit.svg new file mode 100644 index 0000000..e651840 --- /dev/null +++ b/assets/icons/gear/field-toolkit.svg @@ -0,0 +1,7 @@ + + + + + + FT + diff --git a/assets/icons/gear/holo-projector.svg b/assets/icons/gear/holo-projector.svg new file mode 100644 index 0000000..b847075 --- /dev/null +++ b/assets/icons/gear/holo-projector.svg @@ -0,0 +1,7 @@ + + + + + + HP + diff --git a/assets/icons/gear/medkit.svg b/assets/icons/gear/medkit.svg new file mode 100644 index 0000000..80c73e2 --- /dev/null +++ b/assets/icons/gear/medkit.svg @@ -0,0 +1,7 @@ + + + + + + MK + diff --git a/assets/icons/gear/memory-bubble-reader.svg b/assets/icons/gear/memory-bubble-reader.svg new file mode 100644 index 0000000..3cdedd1 --- /dev/null +++ b/assets/icons/gear/memory-bubble-reader.svg @@ -0,0 +1,7 @@ + + + + + + MB + diff --git a/assets/icons/gear/micromind-unit.svg b/assets/icons/gear/micromind-unit.svg new file mode 100644 index 0000000..ad8ff6f --- /dev/null +++ b/assets/icons/gear/micromind-unit.svg @@ -0,0 +1,7 @@ + + + + + + MU + diff --git a/assets/icons/gear/portable-generator.svg b/assets/icons/gear/portable-generator.svg new file mode 100644 index 0000000..445b3bf --- /dev/null +++ b/assets/icons/gear/portable-generator.svg @@ -0,0 +1,7 @@ + + + + + + PG + diff --git a/assets/icons/gear/power-pack-r100.svg b/assets/icons/gear/power-pack-r100.svg new file mode 100644 index 0000000..6e3be85 --- /dev/null +++ b/assets/icons/gear/power-pack-r100.svg @@ -0,0 +1,7 @@ + + + + + + 100 + diff --git a/assets/icons/gear/power-pack-r20.svg b/assets/icons/gear/power-pack-r20.svg new file mode 100644 index 0000000..717e8a9 --- /dev/null +++ b/assets/icons/gear/power-pack-r20.svg @@ -0,0 +1,7 @@ + + + + + + 20 + diff --git a/assets/icons/gear/power-pack-r50.svg b/assets/icons/gear/power-pack-r50.svg new file mode 100644 index 0000000..c0b4b89 --- /dev/null +++ b/assets/icons/gear/power-pack-r50.svg @@ -0,0 +1,7 @@ + + + + + + 50 + diff --git a/assets/icons/gear/reactionless-flycycle.svg b/assets/icons/gear/reactionless-flycycle.svg new file mode 100644 index 0000000..2019b5c --- /dev/null +++ b/assets/icons/gear/reactionless-flycycle.svg @@ -0,0 +1,7 @@ + + + + + + RF + diff --git a/assets/icons/gear/signal-beacon.svg b/assets/icons/gear/signal-beacon.svg new file mode 100644 index 0000000..1bffd62 --- /dev/null +++ b/assets/icons/gear/signal-beacon.svg @@ -0,0 +1,7 @@ + + + + + + SB + diff --git a/assets/icons/gear/stasis-box.svg b/assets/icons/gear/stasis-box.svg new file mode 100644 index 0000000..883c4f5 --- /dev/null +++ b/assets/icons/gear/stasis-box.svg @@ -0,0 +1,7 @@ + + + + + + ST + diff --git a/assets/icons/gear/survey-scanner.svg b/assets/icons/gear/survey-scanner.svg new file mode 100644 index 0000000..b218271 --- /dev/null +++ b/assets/icons/gear/survey-scanner.svg @@ -0,0 +1,7 @@ + + + + + + SS + diff --git a/assets/icons/gear/translator-module.svg b/assets/icons/gear/translator-module.svg new file mode 100644 index 0000000..d17b1d0 --- /dev/null +++ b/assets/icons/gear/translator-module.svg @@ -0,0 +1,7 @@ + + + + + + TM + diff --git a/assets/icons/gear/vac-suit.svg b/assets/icons/gear/vac-suit.svg new file mode 100644 index 0000000..25a2b0e --- /dev/null +++ b/assets/icons/gear/vac-suit.svg @@ -0,0 +1,7 @@ + + + + + + VS + diff --git a/assets/icons/power-pack.svg b/assets/icons/power-pack.svg new file mode 100644 index 0000000..f50277a --- /dev/null +++ b/assets/icons/power-pack.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/pursuit.svg b/assets/icons/pursuit.svg new file mode 100644 index 0000000..0370a22 --- /dev/null +++ b/assets/icons/pursuit.svg @@ -0,0 +1,7 @@ + + + + + PU + PURSUIT + diff --git a/assets/icons/pursuit/artist.svg b/assets/icons/pursuit/artist.svg new file mode 100644 index 0000000..a924eb3 --- /dev/null +++ b/assets/icons/pursuit/artist.svg @@ -0,0 +1,8 @@ + + + + + + AR + PURSUIT + diff --git a/assets/icons/pursuit/colonist.svg b/assets/icons/pursuit/colonist.svg new file mode 100644 index 0000000..e5b33b6 --- /dev/null +++ b/assets/icons/pursuit/colonist.svg @@ -0,0 +1,8 @@ + + + + + + CO + PURSUIT + diff --git a/assets/icons/pursuit/criminal.svg b/assets/icons/pursuit/criminal.svg new file mode 100644 index 0000000..c8bc9e1 --- /dev/null +++ b/assets/icons/pursuit/criminal.svg @@ -0,0 +1,8 @@ + + + + + + CR + PURSUIT + diff --git a/assets/icons/pursuit/dilettante.svg b/assets/icons/pursuit/dilettante.svg new file mode 100644 index 0000000..57e4038 --- /dev/null +++ b/assets/icons/pursuit/dilettante.svg @@ -0,0 +1,8 @@ + + + + + + DI + PURSUIT + diff --git a/assets/icons/pursuit/engineer.svg b/assets/icons/pursuit/engineer.svg new file mode 100644 index 0000000..cd51272 --- /dev/null +++ b/assets/icons/pursuit/engineer.svg @@ -0,0 +1,8 @@ + + + + + + EN + PURSUIT + diff --git a/assets/icons/pursuit/entertainer.svg b/assets/icons/pursuit/entertainer.svg new file mode 100644 index 0000000..f1b6219 --- /dev/null +++ b/assets/icons/pursuit/entertainer.svg @@ -0,0 +1,8 @@ + + + + + + EN + PURSUIT + diff --git a/assets/icons/pursuit/explorer.svg b/assets/icons/pursuit/explorer.svg new file mode 100644 index 0000000..dcad02f --- /dev/null +++ b/assets/icons/pursuit/explorer.svg @@ -0,0 +1,8 @@ + + + + + + EX + PURSUIT + diff --git a/assets/icons/pursuit/functionary.svg b/assets/icons/pursuit/functionary.svg new file mode 100644 index 0000000..c8d0ae6 --- /dev/null +++ b/assets/icons/pursuit/functionary.svg @@ -0,0 +1,8 @@ + + + + + + FU + PURSUIT + diff --git a/assets/icons/pursuit/journalist-writer.svg b/assets/icons/pursuit/journalist-writer.svg new file mode 100644 index 0000000..83dcc75 --- /dev/null +++ b/assets/icons/pursuit/journalist-writer.svg @@ -0,0 +1,8 @@ + + + + + + JW + PURSUIT + diff --git a/assets/icons/pursuit/miner.svg b/assets/icons/pursuit/miner.svg new file mode 100644 index 0000000..eb68e76 --- /dev/null +++ b/assets/icons/pursuit/miner.svg @@ -0,0 +1,8 @@ + + + + + + MI + PURSUIT + diff --git a/assets/icons/pursuit/pilot.svg b/assets/icons/pursuit/pilot.svg new file mode 100644 index 0000000..8ecbe4d --- /dev/null +++ b/assets/icons/pursuit/pilot.svg @@ -0,0 +1,8 @@ + + + + + + PI + PURSUIT + diff --git a/assets/icons/pursuit/police-officer.svg b/assets/icons/pursuit/police-officer.svg new file mode 100644 index 0000000..5c835e7 --- /dev/null +++ b/assets/icons/pursuit/police-officer.svg @@ -0,0 +1,8 @@ + + + + + + PO + PURSUIT + diff --git a/assets/icons/pursuit/politician.svg b/assets/icons/pursuit/politician.svg new file mode 100644 index 0000000..058bc65 --- /dev/null +++ b/assets/icons/pursuit/politician.svg @@ -0,0 +1,8 @@ + + + + + + PO + PURSUIT + diff --git a/assets/icons/pursuit/prisoner.svg b/assets/icons/pursuit/prisoner.svg new file mode 100644 index 0000000..77f0c2f --- /dev/null +++ b/assets/icons/pursuit/prisoner.svg @@ -0,0 +1,8 @@ + + + + + + PR + PURSUIT + diff --git a/assets/icons/pursuit/scholar.svg b/assets/icons/pursuit/scholar.svg new file mode 100644 index 0000000..c8b63c9 --- /dev/null +++ b/assets/icons/pursuit/scholar.svg @@ -0,0 +1,8 @@ + + + + + + SC + PURSUIT + diff --git a/assets/icons/pursuit/scientific-researcher.svg b/assets/icons/pursuit/scientific-researcher.svg new file mode 100644 index 0000000..93dfbab --- /dev/null +++ b/assets/icons/pursuit/scientific-researcher.svg @@ -0,0 +1,8 @@ + + + + + + SR + PURSUIT + diff --git a/assets/icons/pursuit/soldier.svg b/assets/icons/pursuit/soldier.svg new file mode 100644 index 0000000..f2583ec --- /dev/null +++ b/assets/icons/pursuit/soldier.svg @@ -0,0 +1,8 @@ + + + + + + SO + PURSUIT + diff --git a/assets/icons/pursuit/zealot.svg b/assets/icons/pursuit/zealot.svg new file mode 100644 index 0000000..b6cd63b --- /dev/null +++ b/assets/icons/pursuit/zealot.svg @@ -0,0 +1,8 @@ + + + + + + ZE + PURSUIT + diff --git a/assets/icons/skill.svg b/assets/icons/skill.svg new file mode 100644 index 0000000..c6bad04 --- /dev/null +++ b/assets/icons/skill.svg @@ -0,0 +1,7 @@ + + + + + SK + RINGWORLD + diff --git a/assets/icons/skill/anthropology.svg b/assets/icons/skill/anthropology.svg new file mode 100644 index 0000000..4ea0f6f --- /dev/null +++ b/assets/icons/skill/anthropology.svg @@ -0,0 +1,8 @@ + + + + + + AN + KNO + diff --git a/assets/icons/skill/aquatic-vehicle.svg b/assets/icons/skill/aquatic-vehicle.svg new file mode 100644 index 0000000..27f3400 --- /dev/null +++ b/assets/icons/skill/aquatic-vehicle.svg @@ -0,0 +1,8 @@ + + + + + + AV + TEC + diff --git a/assets/icons/skill/archaic-melee-weapon.svg b/assets/icons/skill/archaic-melee-weapon.svg new file mode 100644 index 0000000..695fcf3 --- /dev/null +++ b/assets/icons/skill/archaic-melee-weapon.svg @@ -0,0 +1,8 @@ + + + + + + AM + AGI + diff --git a/assets/icons/skill/archaic-ranged-weapons.svg b/assets/icons/skill/archaic-ranged-weapons.svg new file mode 100644 index 0000000..4a8c43c --- /dev/null +++ b/assets/icons/skill/archaic-ranged-weapons.svg @@ -0,0 +1,8 @@ + + + + + + AR + AGI + diff --git a/assets/icons/skill/astronomy.svg b/assets/icons/skill/astronomy.svg new file mode 100644 index 0000000..90a29ba --- /dev/null +++ b/assets/icons/skill/astronomy.svg @@ -0,0 +1,8 @@ + + + + + + AS + KNO + diff --git a/assets/icons/skill/athletics.svg b/assets/icons/skill/athletics.svg new file mode 100644 index 0000000..499a0a9 --- /dev/null +++ b/assets/icons/skill/athletics.svg @@ -0,0 +1,8 @@ + + + + + + AT + AGI + diff --git a/assets/icons/skill/atmospheric-craft.svg b/assets/icons/skill/atmospheric-craft.svg new file mode 100644 index 0000000..48cdc2c --- /dev/null +++ b/assets/icons/skill/atmospheric-craft.svg @@ -0,0 +1,8 @@ + + + + + + AC + TEC + diff --git a/assets/icons/skill/bargain.svg b/assets/icons/skill/bargain.svg new file mode 100644 index 0000000..84331bc --- /dev/null +++ b/assets/icons/skill/bargain.svg @@ -0,0 +1,8 @@ + + + + + + BA + COM + diff --git a/assets/icons/skill/biology.svg b/assets/icons/skill/biology.svg new file mode 100644 index 0000000..5ea51c0 --- /dev/null +++ b/assets/icons/skill/biology.svg @@ -0,0 +1,8 @@ + + + + + + BI + KNO + diff --git a/assets/icons/skill/botany.svg b/assets/icons/skill/botany.svg new file mode 100644 index 0000000..ea23726 --- /dev/null +++ b/assets/icons/skill/botany.svg @@ -0,0 +1,8 @@ + + + + + + BO + KNO + diff --git a/assets/icons/skill/chemistry.svg b/assets/icons/skill/chemistry.svg new file mode 100644 index 0000000..97aea39 --- /dev/null +++ b/assets/icons/skill/chemistry.svg @@ -0,0 +1,8 @@ + + + + + + CH + KNO + diff --git a/assets/icons/skill/computers.svg b/assets/icons/skill/computers.svg new file mode 100644 index 0000000..add1eee --- /dev/null +++ b/assets/icons/skill/computers.svg @@ -0,0 +1,8 @@ + + + + + + CO + KNO + diff --git a/assets/icons/skill/debate.svg b/assets/icons/skill/debate.svg new file mode 100644 index 0000000..f61846e --- /dev/null +++ b/assets/icons/skill/debate.svg @@ -0,0 +1,8 @@ + + + + + + DE + COM + diff --git a/assets/icons/skill/emergency-treatment.svg b/assets/icons/skill/emergency-treatment.svg new file mode 100644 index 0000000..f766e48 --- /dev/null +++ b/assets/icons/skill/emergency-treatment.svg @@ -0,0 +1,8 @@ + + + + + + ET + KNO + diff --git a/assets/icons/skill/engineering.svg b/assets/icons/skill/engineering.svg new file mode 100644 index 0000000..8363ef7 --- /dev/null +++ b/assets/icons/skill/engineering.svg @@ -0,0 +1,8 @@ + + + + + + EN + KNO + diff --git a/assets/icons/skill/farming.svg b/assets/icons/skill/farming.svg new file mode 100644 index 0000000..f12609e --- /dev/null +++ b/assets/icons/skill/farming.svg @@ -0,0 +1,8 @@ + + + + + + FA + KNO + diff --git a/assets/icons/skill/fast-talk.svg b/assets/icons/skill/fast-talk.svg new file mode 100644 index 0000000..a17a160 --- /dev/null +++ b/assets/icons/skill/fast-talk.svg @@ -0,0 +1,8 @@ + + + + + + FT + COM + diff --git a/assets/icons/skill/fine-arts.svg b/assets/icons/skill/fine-arts.svg new file mode 100644 index 0000000..20480f4 --- /dev/null +++ b/assets/icons/skill/fine-arts.svg @@ -0,0 +1,8 @@ + + + + + + FA + COM + diff --git a/assets/icons/skill/ground-vehicle.svg b/assets/icons/skill/ground-vehicle.svg new file mode 100644 index 0000000..269c3f2 --- /dev/null +++ b/assets/icons/skill/ground-vehicle.svg @@ -0,0 +1,8 @@ + + + + + + GV + TEC + diff --git a/assets/icons/skill/handgun-energy.svg b/assets/icons/skill/handgun-energy.svg new file mode 100644 index 0000000..3241ab5 --- /dev/null +++ b/assets/icons/skill/handgun-energy.svg @@ -0,0 +1,8 @@ + + + + + + HE + PER + diff --git a/assets/icons/skill/handgun-projectile.svg b/assets/icons/skill/handgun-projectile.svg new file mode 100644 index 0000000..395c6d9 --- /dev/null +++ b/assets/icons/skill/handgun-projectile.svg @@ -0,0 +1,8 @@ + + + + + + HP + PER + diff --git a/assets/icons/skill/heavy-weapon-energy.svg b/assets/icons/skill/heavy-weapon-energy.svg new file mode 100644 index 0000000..b7705dc --- /dev/null +++ b/assets/icons/skill/heavy-weapon-energy.svg @@ -0,0 +1,8 @@ + + + + + + HW + PER + diff --git a/assets/icons/skill/heavy-weapon-projectile.svg b/assets/icons/skill/heavy-weapon-projectile.svg new file mode 100644 index 0000000..b7705dc --- /dev/null +++ b/assets/icons/skill/heavy-weapon-projectile.svg @@ -0,0 +1,8 @@ + + + + + + HW + PER + diff --git a/assets/icons/skill/hide.svg b/assets/icons/skill/hide.svg new file mode 100644 index 0000000..e5df3e3 --- /dev/null +++ b/assets/icons/skill/hide.svg @@ -0,0 +1,8 @@ + + + + + + HI + AGI + diff --git a/assets/icons/skill/history.svg b/assets/icons/skill/history.svg new file mode 100644 index 0000000..2926be7 --- /dev/null +++ b/assets/icons/skill/history.svg @@ -0,0 +1,8 @@ + + + + + + HI + KNO + diff --git a/assets/icons/skill/hyperdrive.svg b/assets/icons/skill/hyperdrive.svg new file mode 100644 index 0000000..44939f6 --- /dev/null +++ b/assets/icons/skill/hyperdrive.svg @@ -0,0 +1,8 @@ + + + + + + HY + TEC + diff --git a/assets/icons/skill/law.svg b/assets/icons/skill/law.svg new file mode 100644 index 0000000..bdd2c3c --- /dev/null +++ b/assets/icons/skill/law.svg @@ -0,0 +1,8 @@ + + + + + + LA + KNO + diff --git a/assets/icons/skill/listen.svg b/assets/icons/skill/listen.svg new file mode 100644 index 0000000..2ac7e43 --- /dev/null +++ b/assets/icons/skill/listen.svg @@ -0,0 +1,8 @@ + + + + + + LI + PER + diff --git a/assets/icons/skill/mathematics.svg b/assets/icons/skill/mathematics.svg new file mode 100644 index 0000000..ac9d8d1 --- /dev/null +++ b/assets/icons/skill/mathematics.svg @@ -0,0 +1,8 @@ + + + + + + MA + KNO + diff --git a/assets/icons/skill/musicianship.svg b/assets/icons/skill/musicianship.svg new file mode 100644 index 0000000..a8ff2b1 --- /dev/null +++ b/assets/icons/skill/musicianship.svg @@ -0,0 +1,8 @@ + + + + + + MU + COM + diff --git a/assets/icons/skill/observe.svg b/assets/icons/skill/observe.svg new file mode 100644 index 0000000..75de7fe --- /dev/null +++ b/assets/icons/skill/observe.svg @@ -0,0 +1,8 @@ + + + + + + OB + PER + diff --git a/assets/icons/skill/orate.svg b/assets/icons/skill/orate.svg new file mode 100644 index 0000000..a93ad98 --- /dev/null +++ b/assets/icons/skill/orate.svg @@ -0,0 +1,8 @@ + + + + + + OR + COM + diff --git a/assets/icons/skill/own-language.svg b/assets/icons/skill/own-language.svg new file mode 100644 index 0000000..4b60973 --- /dev/null +++ b/assets/icons/skill/own-language.svg @@ -0,0 +1,8 @@ + + + + + + OL + COM + diff --git a/assets/icons/skill/perform.svg b/assets/icons/skill/perform.svg new file mode 100644 index 0000000..999b253 --- /dev/null +++ b/assets/icons/skill/perform.svg @@ -0,0 +1,8 @@ + + + + + + PE + COM + diff --git a/assets/icons/skill/personal-flyer.svg b/assets/icons/skill/personal-flyer.svg new file mode 100644 index 0000000..2ed1e3a --- /dev/null +++ b/assets/icons/skill/personal-flyer.svg @@ -0,0 +1,8 @@ + + + + + + PF + TEC + diff --git a/assets/icons/skill/physics.svg b/assets/icons/skill/physics.svg new file mode 100644 index 0000000..45c47bb --- /dev/null +++ b/assets/icons/skill/physics.svg @@ -0,0 +1,8 @@ + + + + + + PH + KNO + diff --git a/assets/icons/skill/planetology.svg b/assets/icons/skill/planetology.svg new file mode 100644 index 0000000..629296b --- /dev/null +++ b/assets/icons/skill/planetology.svg @@ -0,0 +1,8 @@ + + + + + + PL + KNO + diff --git a/assets/icons/skill/psychology.svg b/assets/icons/skill/psychology.svg new file mode 100644 index 0000000..92c5259 --- /dev/null +++ b/assets/icons/skill/psychology.svg @@ -0,0 +1,8 @@ + + + + + + PS + COM + diff --git a/assets/icons/skill/reaction-drive.svg b/assets/icons/skill/reaction-drive.svg new file mode 100644 index 0000000..4cc006c --- /dev/null +++ b/assets/icons/skill/reaction-drive.svg @@ -0,0 +1,8 @@ + + + + + + RD + TEC + diff --git a/assets/icons/skill/reactionless-drive.svg b/assets/icons/skill/reactionless-drive.svg new file mode 100644 index 0000000..4cc006c --- /dev/null +++ b/assets/icons/skill/reactionless-drive.svg @@ -0,0 +1,8 @@ + + + + + + RD + TEC + diff --git a/assets/icons/skill/repair.svg b/assets/icons/skill/repair.svg new file mode 100644 index 0000000..0ff162c --- /dev/null +++ b/assets/icons/skill/repair.svg @@ -0,0 +1,8 @@ + + + + + + RE + TEC + diff --git a/assets/icons/skill/ringworld.svg b/assets/icons/skill/ringworld.svg new file mode 100644 index 0000000..e5fee11 --- /dev/null +++ b/assets/icons/skill/ringworld.svg @@ -0,0 +1,8 @@ + + + + + + RI + TEC + diff --git a/assets/icons/skill/scent.svg b/assets/icons/skill/scent.svg new file mode 100644 index 0000000..664f2ba --- /dev/null +++ b/assets/icons/skill/scent.svg @@ -0,0 +1,8 @@ + + + + + + SC + PER + diff --git a/assets/icons/skill/search.svg b/assets/icons/skill/search.svg new file mode 100644 index 0000000..5ffe219 --- /dev/null +++ b/assets/icons/skill/search.svg @@ -0,0 +1,8 @@ + + + + + + SE + PER + diff --git a/assets/icons/skill/second-languages.svg b/assets/icons/skill/second-languages.svg new file mode 100644 index 0000000..2c8de58 --- /dev/null +++ b/assets/icons/skill/second-languages.svg @@ -0,0 +1,8 @@ + + + + + + SL + KNO + diff --git a/assets/icons/skill/sneak.svg b/assets/icons/skill/sneak.svg new file mode 100644 index 0000000..3b68098 --- /dev/null +++ b/assets/icons/skill/sneak.svg @@ -0,0 +1,8 @@ + + + + + + SN + AGI + diff --git a/assets/icons/skill/strategy.svg b/assets/icons/skill/strategy.svg new file mode 100644 index 0000000..dd5857d --- /dev/null +++ b/assets/icons/skill/strategy.svg @@ -0,0 +1,8 @@ + + + + + + ST + KNO + diff --git a/assets/icons/skill/theology.svg b/assets/icons/skill/theology.svg new file mode 100644 index 0000000..93b89cb --- /dev/null +++ b/assets/icons/skill/theology.svg @@ -0,0 +1,8 @@ + + + + + + TH + KNO + diff --git a/assets/icons/skill/track.svg b/assets/icons/skill/track.svg new file mode 100644 index 0000000..2c00c2a --- /dev/null +++ b/assets/icons/skill/track.svg @@ -0,0 +1,8 @@ + + + + + + TR + PER + diff --git a/assets/icons/skill/unarmed-combat.svg b/assets/icons/skill/unarmed-combat.svg new file mode 100644 index 0000000..d107789 --- /dev/null +++ b/assets/icons/skill/unarmed-combat.svg @@ -0,0 +1,8 @@ + + + + + + UC + AGI + diff --git a/assets/icons/skill/v-sword-f-laser.svg b/assets/icons/skill/v-sword-f-laser.svg new file mode 100644 index 0000000..6775be6 --- /dev/null +++ b/assets/icons/skill/v-sword-f-laser.svg @@ -0,0 +1,8 @@ + + + + + + VS + AGI + diff --git a/assets/icons/skill/weapons-system.svg b/assets/icons/skill/weapons-system.svg new file mode 100644 index 0000000..8e72c1f --- /dev/null +++ b/assets/icons/skill/weapons-system.svg @@ -0,0 +1,8 @@ + + + + + + WS + TEC + diff --git a/assets/icons/skill/zoology.svg b/assets/icons/skill/zoology.svg new file mode 100644 index 0000000..e48cc18 --- /dev/null +++ b/assets/icons/skill/zoology.svg @@ -0,0 +1,8 @@ + + + + + + ZO + KNO + diff --git a/assets/icons/species.svg b/assets/icons/species.svg new file mode 100644 index 0000000..40c8b1d --- /dev/null +++ b/assets/icons/species.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/species/bandersnatchi.svg b/assets/icons/species/bandersnatchi.svg new file mode 100644 index 0000000..3bcb3a7 --- /dev/null +++ b/assets/icons/species/bandersnatchi.svg @@ -0,0 +1,7 @@ + + + + + + BD + diff --git a/assets/icons/species/boat-people.svg b/assets/icons/species/boat-people.svg new file mode 100644 index 0000000..999bfeb --- /dev/null +++ b/assets/icons/species/boat-people.svg @@ -0,0 +1,7 @@ + + + + + + BP + diff --git a/assets/icons/species/builder.svg b/assets/icons/species/builder.svg new file mode 100644 index 0000000..4731126 --- /dev/null +++ b/assets/icons/species/builder.svg @@ -0,0 +1,7 @@ + + + + + + BL + diff --git a/assets/icons/species/dolphin.svg b/assets/icons/species/dolphin.svg new file mode 100644 index 0000000..c31d959 --- /dev/null +++ b/assets/icons/species/dolphin.svg @@ -0,0 +1,7 @@ + + + + + + DL + diff --git a/assets/icons/species/earth-human.svg b/assets/icons/species/earth-human.svg new file mode 100644 index 0000000..4d91f09 --- /dev/null +++ b/assets/icons/species/earth-human.svg @@ -0,0 +1,7 @@ + + + + + + EH + diff --git a/assets/icons/species/ghoul.svg b/assets/icons/species/ghoul.svg new file mode 100644 index 0000000..9a8cd58 --- /dev/null +++ b/assets/icons/species/ghoul.svg @@ -0,0 +1,7 @@ + + + + + + GH + diff --git a/assets/icons/species/human.svg b/assets/icons/species/human.svg new file mode 100644 index 0000000..7532b4b --- /dev/null +++ b/assets/icons/species/human.svg @@ -0,0 +1,7 @@ + + + + + + HM + diff --git a/assets/icons/species/kzinti.svg b/assets/icons/species/kzinti.svg new file mode 100644 index 0000000..49db6e2 --- /dev/null +++ b/assets/icons/species/kzinti.svg @@ -0,0 +1,7 @@ + + + + + + KZ + diff --git a/assets/icons/species/martian.svg b/assets/icons/species/martian.svg new file mode 100644 index 0000000..5cb291b --- /dev/null +++ b/assets/icons/species/martian.svg @@ -0,0 +1,7 @@ + + + + + + MR + diff --git a/assets/icons/species/pierin.svg b/assets/icons/species/pierin.svg new file mode 100644 index 0000000..41906a7 --- /dev/null +++ b/assets/icons/species/pierin.svg @@ -0,0 +1,7 @@ + + + + + + PI + diff --git a/assets/icons/species/pierson-s-puppeteer.svg b/assets/icons/species/pierson-s-puppeteer.svg new file mode 100644 index 0000000..d3e95a2 --- /dev/null +++ b/assets/icons/species/pierson-s-puppeteer.svg @@ -0,0 +1,7 @@ + + + + + + PP + diff --git a/assets/icons/species/trinoc.svg b/assets/icons/species/trinoc.svg new file mode 100644 index 0000000..8afdcf3 --- /dev/null +++ b/assets/icons/species/trinoc.svg @@ -0,0 +1,7 @@ + + + + + + TR + diff --git a/assets/icons/weapon-melee.svg b/assets/icons/weapon-melee.svg new file mode 100644 index 0000000..96474d5 --- /dev/null +++ b/assets/icons/weapon-melee.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/weapon-ranged.svg b/assets/icons/weapon-ranged.svg new file mode 100644 index 0000000..37016bf --- /dev/null +++ b/assets/icons/weapon-ranged.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/weapon/flashlight-laser.svg b/assets/icons/weapon/flashlight-laser.svg new file mode 100644 index 0000000..a2c6e2d --- /dev/null +++ b/assets/icons/weapon/flashlight-laser.svg @@ -0,0 +1,7 @@ + + + + + + FL + diff --git a/assets/icons/weapon/handgun-projectile.svg b/assets/icons/weapon/handgun-projectile.svg new file mode 100644 index 0000000..2c9399e --- /dev/null +++ b/assets/icons/weapon/handgun-projectile.svg @@ -0,0 +1,7 @@ + + + + + + HP + diff --git a/assets/icons/weapon/laser-pistol.svg b/assets/icons/weapon/laser-pistol.svg new file mode 100644 index 0000000..b584b23 --- /dev/null +++ b/assets/icons/weapon/laser-pistol.svg @@ -0,0 +1,7 @@ + + + + + + LP + diff --git a/assets/icons/weapon/laser-rifle.svg b/assets/icons/weapon/laser-rifle.svg new file mode 100644 index 0000000..ca42544 --- /dev/null +++ b/assets/icons/weapon/laser-rifle.svg @@ -0,0 +1,7 @@ + + + + + + LR + diff --git a/assets/icons/weapon/monofilament-knife.svg b/assets/icons/weapon/monofilament-knife.svg new file mode 100644 index 0000000..074bbaf --- /dev/null +++ b/assets/icons/weapon/monofilament-knife.svg @@ -0,0 +1,7 @@ + + + + + + MK + diff --git a/assets/icons/weapon/rifle-projectile.svg b/assets/icons/weapon/rifle-projectile.svg new file mode 100644 index 0000000..fb8a0d4 --- /dev/null +++ b/assets/icons/weapon/rifle-projectile.svg @@ -0,0 +1,7 @@ + + + + + + RP + diff --git a/assets/icons/weapon/shock-baton.svg b/assets/icons/weapon/shock-baton.svg new file mode 100644 index 0000000..4e4c637 --- /dev/null +++ b/assets/icons/weapon/shock-baton.svg @@ -0,0 +1,7 @@ + + + + + + SB + diff --git a/assets/icons/weapon/slug-thrower-carbine.svg b/assets/icons/weapon/slug-thrower-carbine.svg new file mode 100644 index 0000000..c5b32e6 --- /dev/null +++ b/assets/icons/weapon/slug-thrower-carbine.svg @@ -0,0 +1,7 @@ + + + + + + SC + diff --git a/assets/icons/weapon/stunner.svg b/assets/icons/weapon/stunner.svg new file mode 100644 index 0000000..a11771f --- /dev/null +++ b/assets/icons/weapon/stunner.svg @@ -0,0 +1,7 @@ + + + + + + ST + diff --git a/assets/icons/weapon/variable-sword.svg b/assets/icons/weapon/variable-sword.svg new file mode 100644 index 0000000..c3bee3e --- /dev/null +++ b/assets/icons/weapon/variable-sword.svg @@ -0,0 +1,7 @@ + + + + + + VS + diff --git a/dist/ringworld-content.zip b/dist/ringworld-content.zip new file mode 100644 index 0000000..f43b2bd Binary files /dev/null and b/dist/ringworld-content.zip differ diff --git a/dist/ringworld-starter-world.zip b/dist/ringworld-starter-world.zip new file mode 100644 index 0000000..47dcd8b Binary files /dev/null and b/dist/ringworld-starter-world.zip differ diff --git a/dist/ringworld-system.zip b/dist/ringworld-system.zip new file mode 100644 index 0000000..2f9d51d Binary files /dev/null and b/dist/ringworld-system.zip differ diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..5126b94 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,5 @@ +{ + "RINGWORLD.RollSkill": "Roll Skill", + "RINGWORLD.RollHitLocation": "Roll Hit Location", + "RINGWORLD.UseWeapon": "Use Weapon" +} diff --git a/module/ringworld.mjs b/module/ringworld.mjs new file mode 100644 index 0000000..d2e16a4 --- /dev/null +++ b/module/ringworld.mjs @@ -0,0 +1,3335 @@ +async function Tt(e) { + const t = game == null ? void 0 : game.dice3d; + if (t != null && t.showForRoll) + try { + await t.showForRoll(e, game.user, !0, null, !1); + } catch { + } +} +async function Me(e) { + const t = await new Roll(e).evaluate(); + return await Tt(t), t; +} +async function Ue(e) { + const t = await Me("1d100"); + return Dt(t.total ?? 0, e); +} +function Dt(e, t) { + const a = Math.max(0, Math.floor(Number(t) || 0)), n = Math.max(1, Math.floor(a / 5)), o = a >= 50 ? 100 : 96, s = e <= a, i = s && e <= n, l = !s && e >= o; + let r = "failure"; + return l ? r = "fumble" : i ? r = "special" : s && (r = "success"), { + roll: e, + target: a, + outcome: r, + isSuccess: s, + isSpecial: i, + isFumble: l + }; +} +function Ae(e) { + switch (e) { + case "special": + return "Special Success"; + case "success": + return "Success"; + case "fumble": + return "Fumble"; + default: + return "Failure"; + } +} +const x = "ringworld", Ce = [ + { slug: "rLeg", label: "Right Leg", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Leg", min: 4, max: 6 }, + { slug: "abdomen", label: "Abdomen", min: 7, max: 10 }, + { slug: "chest", label: "Chest", min: 11, max: 15 }, + { slug: "rArm", label: "Right Arm", min: 16, max: 17 }, + { slug: "lArm", label: "Left Arm", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } +], Ht = ["rArm", "lArm", "rLeg", "lLeg"], Nt = ["rLeg", "lLeg"]; +function Ot(e) { + return Ht.includes(e); +} +function it(e) { + return String(e ?? "").toLowerCase().replace(/[^a-z0-9]+/g, ""); +} +function oe(e, t) { + return e.map((a) => ({ + ...a, + hpFactor: (t == null ? void 0 : t[a.slug]) ?? (a.slug === "chest" ? 0.35 : a.slug === "rArm" || a.slug === "lArm" ? 0.25 : 0.3) + })); +} +const rt = { + id: "humanoid", + label: "Humanoid", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: oe(Ce) +}, Et = { + id: "dolphin", + label: "Dolphin", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: oe([ + { slug: "rLeg", label: "Right Fluke", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Fluke", min: 4, max: 6 }, + { slug: "abdomen", label: "Lower Body", min: 7, max: 9 }, + { slug: "chest", label: "Main Body", min: 10, max: 15 }, + { slug: "rArm", label: "Right Fin", min: 16, max: 17 }, + { slug: "lArm", label: "Left Fin", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } + ], { + head: 0.3, + rArm: 0.2, + lArm: 0.2, + chest: 0.4, + abdomen: 0.35, + rLeg: 0.25, + lLeg: 0.25 + }) +}, Bt = { + id: "puppeteer", + label: "Puppeteer", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: oe([ + { slug: "rLeg", label: "Right Rear Leg", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Rear Leg", min: 4, max: 6 }, + { slug: "rArm", label: "Right Foreleg", min: 7, max: 9 }, + { slug: "lArm", label: "Left Foreleg", min: 10, max: 12 }, + { slug: "chest", label: "Fore Body", min: 13, max: 15 }, + { slug: "abdomen", label: "Hind Body", min: 16, max: 18 }, + { slug: "head", label: "Twin Heads", min: 19, max: 20 } + ], { + head: 0.32, + chest: 0.35, + abdomen: 0.35, + rArm: 0.28, + lArm: 0.28, + rLeg: 0.28, + lLeg: 0.28 + }) +}, Ft = { + id: "trinoc", + label: "Trinoc", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: oe([ + { slug: "rArm", label: "Right Wing/Arm", min: 1, max: 3 }, + { slug: "lArm", label: "Left Wing/Arm", min: 4, max: 6 }, + { slug: "chest", label: "Upper Torso", min: 7, max: 11 }, + { slug: "abdomen", label: "Lower Torso", min: 12, max: 14 }, + { slug: "rLeg", label: "Right Leg", min: 15, max: 17 }, + { slug: "lLeg", label: "Left Leg", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } + ], { + head: 0.3, + rArm: 0.27, + lArm: 0.27, + chest: 0.36, + abdomen: 0.3, + rLeg: 0.27, + lLeg: 0.27 + }) +}, Ut = { + id: "bandersnatchi", + label: "Bandersnatchi", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: oe([ + { slug: "rLeg", label: "Right Rear Mass", min: 1, max: 2 }, + { slug: "lLeg", label: "Left Rear Mass", min: 3, max: 4 }, + { slug: "abdomen", label: "Lower Body Mass", min: 5, max: 11 }, + { slug: "chest", label: "Upper Body Mass", min: 12, max: 17 }, + { slug: "rArm", label: "Right Manipulator", min: 18, max: 18 }, + { slug: "lArm", label: "Left Manipulator", min: 19, max: 19 }, + { slug: "head", label: "Head Cluster", min: 20, max: 20 } + ], { + head: 0.28, + chest: 0.42, + abdomen: 0.42, + rArm: 0.2, + lArm: 0.2, + rLeg: 0.24, + lLeg: 0.24 + }) +}, qt = { + id: "kzinti", + label: "Kzinti", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: oe([ + { slug: "rLeg", label: "Right Hind Leg", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Hind Leg", min: 4, max: 6 }, + { slug: "abdomen", label: "Abdomen", min: 7, max: 10 }, + { slug: "chest", label: "Chest", min: 11, max: 15 }, + { slug: "rArm", label: "Right Foreleg", min: 16, max: 17 }, + { slug: "lArm", label: "Left Foreleg", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } + ]) +}, lt = { + humanoid: rt, + dolphin: Et, + puppeteer: Bt, + trinoc: Ft, + bandersnatchi: Ut, + kzinti: qt +}; +function Kt() { + return Object.values(lt); +} +function Vt(e) { + const t = it(e); + return t ? t.includes("dolphin") ? "dolphin" : t.includes("puppeteer") ? "puppeteer" : t.includes("trinoc") ? "trinoc" : t.includes("bandersnatch") ? "bandersnatchi" : t.includes("kzinti") ? "kzinti" : "humanoid" : "humanoid"; +} +function Ne(e) { + const t = it(e); + return lt[t] ?? rt; +} +function se(e) { + var n, o, s; + const t = String(((n = e == null ? void 0 : e.system) == null ? void 0 : n.hitLocationSchema) || "auto").trim().toLowerCase(); + if (t && t !== "auto") + return Ne(t); + const a = ((s = (o = e == null ? void 0 : e.system) == null ? void 0 : o.profile) == null ? void 0 : s.species) ?? ""; + return Ne(Vt(a)); +} +function Gt(e) { + return e ? se(e).entries : Ce; +} +function jt(e, t) { + return t.find((n) => e >= n.min && e <= n.max) ?? t[t.length - 1]; +} +async function ct(e) { + const a = (await Me("1d20")).total ?? 20, n = Gt(e); + return { + roll: a, + location: jt(a, n) + }; +} +async function _t(e) { + const t = e ? se(e) : Ne("humanoid"), a = t.entries, n = new Map(a.map((r) => [r.slug, r])), o = (r, c) => { + var u; + return ((u = n.get(r)) == null ? void 0 : u.label) ?? c; + }, s = ` +
+ +
+ +
+
+ + + + + + + +
+
+ `, i = await new Promise((r) => { + let c = !1; + const u = (g) => { + c || (c = !0, r(g)); + }; + new Dialog({ + title: "Choose Hit Location", + content: s, + buttons: { + apply: { + label: "Apply", + callback: (g) => { + const m = String(g.find("input[name='location']").val() || "random"); + u(m); + } + }, + cancel: { + label: "Cancel", + callback: () => u(null) + } + }, + default: "apply", + render: (g) => { + const m = g.find(".ringworld-hit-picker"); + m.find(".rw-hit-picker-choice").on("click", (f) => { + f.preventDefault(); + const y = f.currentTarget, b = String(y.dataset.location || "random"); + m.find(".rw-hit-picker-choice").removeClass("active"), $(y).addClass("active"), m.find("input[name='location']").val(b); + }); + }, + close: () => u(null) + }, { + classes: ["ringworld-hit-location-dialog"], + width: 500, + resizable: !1 + }).render(!0); + }); + if (!i) return null; + if (i === "random") { + const r = await ct(e); + return { + slug: r.location.slug, + label: r.location.label, + rolled: r.roll + }; + } + const l = a.find((r) => r.slug === i); + return l ? { + slug: l.slug, + label: l.label, + rolled: null + } : null; +} +const Wt = [ + "Agility", + "Communication", + "Knowledge", + "Perception", + "Technical" +], we = [ + { name: "Archaic Melee Weapon", category: "Agility", base: 5 }, + { name: "Archaic Ranged Weapons", category: "Agility", base: 3 }, + { name: "Athletics", category: "Agility", base: 15 }, + { name: "Hide", category: "Agility", base: 10 }, + { name: "Sneak", category: "Agility", base: 5 }, + { name: "Unarmed Combat", category: "Agility", base: 0 }, + { name: "V. Sword, F. Laser", category: "Agility", base: 15 }, + { name: "Bargain", category: "Communication", base: 10 }, + { name: "Debate", category: "Communication", base: 5 }, + { name: "Fast Talk", category: "Communication", base: 10 }, + { name: "Fine Arts", category: "Communication", base: 5 }, + { name: "Musicianship", category: "Communication", base: 5 }, + { name: "Orate", category: "Communication", base: 5 }, + { name: "Own Language", category: "Communication", base: 0, specialization: "INTx5 base" }, + { name: "Perform", category: "Communication", base: 5 }, + { name: "Psychology", category: "Communication", base: 0 }, + { name: "Anthropology", category: "Knowledge", base: 0 }, + { name: "Astronomy", category: "Knowledge", base: 0 }, + { name: "Biology", category: "Knowledge", base: 0 }, + { name: "Botany", category: "Knowledge", base: 0 }, + { name: "Chemistry", category: "Knowledge", base: 0 }, + { name: "Computers", category: "Knowledge", base: 0 }, + { name: "Emergency Treatment", category: "Knowledge", base: 1 }, + { name: "Engineering", category: "Knowledge", base: 0 }, + { name: "Farming", category: "Knowledge", base: 0 }, + { name: "History", category: "Knowledge", base: 5 }, + { name: "Law", category: "Knowledge", base: 0 }, + { name: "Mathematics", category: "Knowledge", base: 0 }, + { name: "Physics", category: "Knowledge", base: 0 }, + { name: "Planetology", category: "Knowledge", base: 0 }, + { name: "Second Languages", category: "Knowledge", base: 0 }, + { name: "Strategy", category: "Knowledge", base: 0 }, + { name: "Theology", category: "Knowledge", base: 0 }, + { name: "Zoology", category: "Knowledge", base: 0 }, + { name: "Handgun, Energy", category: "Perception", base: 5 }, + { name: "Handgun, Projectile", category: "Perception", base: 3 }, + { name: "Heavy Weapon, Energy", category: "Perception", base: 5 }, + { name: "Heavy Weapon, Projectile", category: "Perception", base: 3 }, + { name: "Listen", category: "Perception", base: 5 }, + { name: "Observe", category: "Perception", base: 5 }, + { name: "Scent", category: "Perception", base: 0 }, + { name: "Search", category: "Perception", base: 5 }, + { name: "Track", category: "Perception", base: 5 }, + { name: "Aquatic Vehicle", category: "Technical", base: 0 }, + { name: "Atmospheric Craft", category: "Technical", base: 0 }, + { name: "Ground Vehicle", category: "Technical", base: 0 }, + { name: "Hyperdrive", category: "Technical", base: 0 }, + { name: "Personal Flyer", category: "Technical", base: 15 }, + { name: "Reaction Drive", category: "Technical", base: 0 }, + { name: "Reactionless Drive", category: "Technical", base: 0 }, + { name: "Repair", category: "Technical", base: 0 }, + { name: "Ringworld", category: "Technical", base: 0 }, + { name: "Weapons System", category: "Technical", base: 0 } +]; +function R(e) { + return String(e || "").toLowerCase().replace(/[^a-z0-9]+/g, "").trim(); +} +function Yt() { + return new Set(we.map((e) => R(e.name))); +} +const W = "systems/ringworld/assets/icons/skill.svg"; +function zt(e) { + return String(e).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); +} +function ye(e) { + return `systems/ringworld/assets/icons/skill/${zt(e)}.svg`; +} +function $e(e) { + const t = R(e); + return we.find((a) => R(a.name) === t); +} +function Oe(e, t) { + const a = String(t || "").trim(); + return a && a !== "icons/svg/item-bag.svg" ? a : $e(e) ? ye(e) : W; +} +async function ut(e) { + var a; + const t = e.actor ? ChatMessage.getSpeaker({ actor: e.actor }) : ChatMessage.getSpeaker(); + await ChatMessage.create({ + user: (a = game.user) == null ? void 0 : a.id, + speaker: t, + content: e.content + }); +} +async function mt(e) { + var n; + const t = { + ...e, + actorImg: e.actorImg || ((n = e.actor) == null ? void 0 : n.img) || "icons/svg/mystery-man.svg", + skillImg: e.skillImg || W + }, a = await renderTemplate(`systems/${x}/templates/chat/skill-roll-card.hbs`, t); + await ut({ actor: e.actor, content: a }); +} +async function Xe(e) { + var n; + const t = { + ...e, + actorImg: e.actorImg || ((n = e.actor) == null ? void 0 : n.img) || "icons/svg/mystery-man.svg", + weaponImg: e.weaponImg || "icons/svg/sword.svg", + skillImg: e.skillImg || W, + hasTarget: e.hasTarget ?? !0 + }, a = await renderTemplate(`systems/${x}/templates/chat/attack-card.hbs`, t); + await ut({ actor: e.actor, content: a }); +} +function P(e, t = 0) { + const a = Number(e); + return Number.isFinite(a) ? a : t; +} +function Qt() { + var t, a; + const e = Array.from(((t = game.user) == null ? void 0 : t.targets) ?? []); + return e.length ? ((a = e[0]) == null ? void 0 : a.actor) ?? null : null; +} +async function Xt(e) { + var o; + const t = await ct(e), a = e ? ChatMessage.getSpeaker({ actor: e }) : ChatMessage.getSpeaker(), n = ` +
+

Hit Location

+

Roll: ${t.roll}

+

Location: ${t.location.label}

+
+ `; + await ChatMessage.create({ + user: (o = game.user) == null ? void 0 : o.id, + speaker: a, + content: n + }); +} +async function Ee(e, t, a) { + const n = await Ue(a), o = e.items.find( + (s) => s.type === "skill" && s.name.toLowerCase() === String(t).toLowerCase() + ); + await mt({ + actor: e, + actorName: e.name, + actorImg: e.img, + skillName: t, + skillImg: o == null ? void 0 : o.img, + roll: n.roll, + target: n.target, + outcomeLabel: Ae(n.outcome), + isSuccess: n.isSuccess, + isSpecial: n.isSpecial, + isFumble: n.isFumble + }); +} +function Zt(e, t) { + const a = t.system.linkedSkillId, n = a ? e.items.get(a) : null; + if ((n == null ? void 0 : n.type) === "skill") + return { name: n.name, target: P(n.system.value), img: n.img }; + const o = String(t.system.linkedSkill || "").trim(); + if (o) { + const s = e.items.find( + (i) => i.type === "skill" && i.name.toLowerCase() === o.toLowerCase() + ); + if (s) + return { name: s.name, target: P(s.system.value), img: s.img }; + } + return { + name: o || "Unlinked Skill", + target: 0 + }; +} +function Jt(e) { + return Math.max(0, Math.min(99, Math.floor(e))); +} +async function ea(e, t, a) { + var d; + const o = String(((d = t.system) == null ? void 0 : d.attackType) || "ranged") === "ranged", s = "full", i = foundry.utils.escapeHTML(String((e == null ? void 0 : e.name) || "Attacker")), l = foundry.utils.escapeHTML(String((t == null ? void 0 : t.name) || "Weapon")), r = foundry.utils.escapeHTML(String((e == null ? void 0 : e.img) || "icons/svg/mystery-man.svg")), c = foundry.utils.escapeHTML(String((t == null ? void 0 : t.img) || "icons/svg/sword.svg")), u = ` +
+
+ ${i} + ${l} +
+

${l}

+

${i} Attack Setup

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Ringworld rule support: unaimed ranged attacks use quarter skill, partial aim uses half skill, full aim uses full skill.

+
+ `; + return new Promise((g) => { + let m = !1; + const f = (y) => { + m || (m = !0, g(y)); + }; + new Dialog({ + title: `${t.name} - Attack Modifiers`, + content: u, + buttons: { + roll: { + label: "Roll Attack", + callback: (y) => { + var le, ce, ue, me, de, ge, pe; + const b = y[0], w = (ee) => b == null ? void 0 : b.querySelector(`[name="${ee}"]`), p = String(((le = w("aimMode")) == null ? void 0 : le.value) || s), h = p === "unaimed" || p === "partial" || p === "full" ? p : s, A = o ? { unaimed: 0.25, partial: 0.5, full: 1 }[h] : 1, S = P((ce = w("coverMod")) == null ? void 0 : ce.value, 0), I = P((ue = w("movementMod")) == null ? void 0 : ue.value, 0), M = P((me = w("rangeMod")) == null ? void 0 : me.value, 0), D = P((de = w("situationalMod")) == null ? void 0 : de.value, 0), T = P((ge = w("customMod")) == null ? void 0 : ge.value, 0), H = String(((pe = w("customLabel")) == null ? void 0 : pe.value) || "").trim(), L = S + I + M + D + T, F = a + L, X = Math.floor(F * A), J = Jt(X), N = [`Base ${a}`]; + if (S && N.push(`Cover ${S >= 0 ? "+" : ""}${S}`), I && N.push(`Movement ${I >= 0 ? "+" : ""}${I}`), M && N.push(`Range ${M >= 0 ? "+" : ""}${M}`), D && N.push(`Situational ${D >= 0 ? "+" : ""}${D}`), T && N.push(`${H || "Custom"} ${T >= 0 ? "+" : ""}${T}`), N.push(`Pre-Aim ${F}`), o) { + const ee = h === "unaimed" ? "Quarter" : h === "partial" ? "Half" : "Full"; + N.push(`Aim ${ee} x${A}`); + } + N.push(`Final ${J}`), f({ + finalTarget: J, + summary: N.join(" | "), + aimMode: h, + aimMultiplier: A, + flatModifierTotal: L + }); + } + }, + cancel: { + label: "Cancel", + callback: () => f(null) + } + }, + default: "roll", + close: () => f(null) + }, { + classes: ["ringworld-attack-dialog"], + width: 510, + resizable: !1 + }).render(!0); + }); +} +async function ta(e, t) { + var i, l, r, c, u, d; + const a = Math.max(0, P(t.system.energyUsedPerAttack)); + if (a <= 0) + return { sourceLabel: "None", remaining: 0, cost: 0, insufficient: !1 }; + if (String(t.system.powerSource || "actor") === "powerPack") { + const g = String(t.system.powerPackId || ""), m = g ? e.items.get(g) : null; + if (!m || m.type !== "gear" || !m.system.isPowerPack) + return (i = ui.notifications) == null || i.warn("Weapon is configured for a power pack but no valid power pack is linked."), { sourceLabel: "Power Pack", remaining: 0, cost: a, insufficient: !0 }; + const f = P((l = m.system.energy) == null ? void 0 : l.value); + if (f < a) + return (r = ui.notifications) == null || r.warn(`${m.name} does not have enough energy.`), { sourceLabel: m.name, remaining: f, cost: a, insufficient: !0 }; + const y = f - a; + return await e.updateEmbeddedDocuments("Item", [{ _id: m.id, "system.energy.value": y }]), { sourceLabel: m.name, remaining: y, cost: a, insufficient: !1 }; + } + const o = P((u = (c = e.system.resources) == null ? void 0 : c.energy) == null ? void 0 : u.value); + if (o < a) + return (d = ui.notifications) == null || d.warn(`${e.name} does not have enough actor energy.`), { sourceLabel: "Actor Energy", remaining: o, cost: a, insufficient: !0 }; + const s = o - a; + return await e.update({ "system.resources.energy.value": s }), { sourceLabel: "Actor Energy", remaining: s, cost: a, insufficient: !1 }; +} +function aa(e, t) { + var n, o; + if (e.type !== "armor" || !e.system.equipped || e.system.stowed || !((n = e.system.coverage) != null && n[t])) return 0; + const a = P((o = e.system.perLocation) == null ? void 0 : o[t]); + return a > 0 ? a : P(e.system.value); +} +function na(e, t) { + var o, s; + const a = P((s = (o = e.system.hitLocations) == null ? void 0 : o[t]) == null ? void 0 : s.armor), n = e.items.filter((i) => i.type === "armor").reduce((i, l) => i + aa(l, t), 0); + return a + n; +} +function oa(e, t, a, n, o) { + const s = foundry.utils.deepClone(e.system.conditions ?? {}); + return s.unusableLimbs = s.unusableLimbs ?? { rArm: !1, lArm: !1, rLeg: !1, lLeg: !1 }, a <= 0 && ((t === "head" || t === "chest") && (s.unconscious = !0, s.prone = !0), t === "abdomen" && (s.prone = !0, s.unusableLimbs.rLeg = !0, s.unusableLimbs.lLeg = !0), (t === "rArm" || t === "lArm") && (s.unusableLimbs[t] = !0), Nt.includes(t) && (s.unusableLimbs[t] = !0, s.prone = !0)), o <= 0 && (s.dying = !0), (t === "head" || t === "chest" || t === "abdomen") && a <= -n && (s.dying = !0), t === "head" && a <= -n && (s.dead = !0, s.unconscious = !0), s; +} +async function sa(e, t) { + const a = e.toggleStatusEffect; + if (typeof a == "function") + try { + await a.call(e, "prone", { active: !!t.prone }), await a.call(e, "unconscious", { active: !!t.unconscious }), await a.call(e, "dead", { active: !!t.dead }); + } catch { + } +} +async function ia(e, t, a) { + var f, y, b, w; + const n = (f = e.system.hitLocations) == null ? void 0 : f[t], o = P((y = n == null ? void 0 : n.hp) == null ? void 0 : y.value), s = Math.max(1, P((b = n == null ? void 0 : n.hp) == null ? void 0 : b.max, 1)), i = na(e, t), l = Math.max(0, a - i), r = o - l, c = Ot(t) ? Math.max(r, -s) : r, d = P((w = e.system.generalHp) == null ? void 0 : w.value) - l, g = oa(e, t, c, s, d), m = { + [`system.hitLocations.${t}.hp.value`]: c, + "system.generalHp.value": d, + "system.conditions": g + }; + return await e.update(m), await sa(e, g), { + armor: i, + finalDamage: l, + locationValue: c, + generalHpValue: d + }; +} +async function dt(e, t) { + var f; + const a = Qt(), n = !!a, o = (a == null ? void 0 : a.name) ?? "No Target", s = Zt(e, t), i = await ea(e, t, s.target); + if (!i) return; + const l = await ta(e, t); + if (l.insufficient) return; + const r = await Ue(i.finalTarget); + if (!r.isSuccess) { + const y = a ? P((f = a.system.generalHp) == null ? void 0 : f.value, 0) : 0; + await Xe({ + actor: e, + actorName: e.name, + actorImg: e.img, + targetName: o, + hasTarget: n, + weaponName: t.name, + weaponImg: t.img, + skillName: s.name, + skillImg: s.img, + attackRoll: r.roll, + attackTarget: r.target, + attackOutcomeLabel: Ae(r.outcome), + isSuccess: !1, + isSpecial: r.isSpecial, + isFumble: r.isFumble, + hitLocationLabel: "-", + hitLocationRoll: null, + rawDamage: 0, + armor: 0, + finalDamage: 0, + remainingLocationHp: 0, + remainingGeneralHp: y, + energySourceLabel: l.sourceLabel, + energyCost: l.cost, + energyRemaining: l.remaining, + notes: `Attack failed before damage was rolled. ${i.summary}` + }); + return; + } + const c = await _t(a ?? e); + if (!c) return; + const u = String(t.system.damageFormula || "0").trim() || "0", d = await Me(u), g = Math.max(0, P(d.total)), m = a ? await ia(a, c.slug, g) : { + armor: 0, + finalDamage: g, + locationValue: 0, + generalHpValue: 0 + }; + await Xe({ + actor: e, + actorName: e.name, + actorImg: e.img, + targetName: o, + hasTarget: n, + weaponName: t.name, + weaponImg: t.img, + skillName: s.name, + skillImg: s.img, + attackRoll: r.roll, + attackTarget: r.target, + attackOutcomeLabel: Ae(r.outcome), + isSuccess: r.isSuccess, + isSpecial: r.isSpecial, + isFumble: r.isFumble, + hitLocationLabel: c.label, + hitLocationRoll: c.rolled, + rawDamage: g, + armor: m.armor, + finalDamage: m.finalDamage, + remainingLocationHp: m.locationValue, + remainingGeneralHp: m.generalHpValue, + energySourceLabel: l.sourceLabel, + energyCost: l.cost, + energyRemaining: l.remaining, + notes: a ? `Location and general HP were both reduced by final damage, per Ringworld rules. ${i.summary}` : `No target selected: roll resolved with no damage application. ${i.summary}` + }); +} +function ra(e) { + var m, f, y, b, w, p, h, A; + if (e.type !== "explorer" && e.type !== "creature") return; + const t = P((f = (m = e.system) == null ? void 0 : m.characteristics) == null ? void 0 : f.con, 10), a = P((b = (y = e.system) == null ? void 0 : y.characteristics) == null ? void 0 : b.mas, 10), n = P((w = e.system.generalHp) == null ? void 0 : w.max, 0), o = P((p = e.system.generalHp) == null ? void 0 : p.value, 0), s = Math.max(1, Math.floor(t + a)); + e.system.generalHp = e.system.generalHp ?? { value: s, max: s }, e.system.generalHp.max = s; + const i = n > 0, l = i && o >= n, r = n <= 1 && o <= 1, c = i ? Math.max(0, n - o) : 0, u = !i || l || r ? s : Math.max(0, s - c); + e.system.generalHp.value = Math.min(Math.max(0, u), s); + const d = P(e.system.generalHp.value, s) >= s; + e.system.hitLocations = e.system.hitLocations ?? {}, e.system.hitLocationSchema = e.system.hitLocationSchema ?? "auto"; + const g = se(e); + for (const S of g.entries) { + const I = S.slug, M = e.system.hitLocations[I] ?? { + label: I, + hp: { value: 1, max: 1 }, + armor: 0 + }; + M.label = S.label; + const D = Math.max(0.05, P(S.hpFactor, 0.3)), T = Math.max(1, Math.ceil(s * D)), H = P((h = M.hp) == null ? void 0 : h.max, 1), L = P((A = M.hp) == null ? void 0 : A.value, H), F = H <= 1 && L <= 1, X = d && L <= 1 && T > 1; + M.hp.max = T, M.hp.value = F || X ? T : Math.min(L, T), e.system.hitLocations[I] = M; + } +} +const De = 9, _ = { + speciesDefault: "systems/ringworld/assets/icons/species.svg", + skillDefault: "systems/ringworld/assets/icons/skill.svg", + pursuitDefault: "systems/ringworld/assets/icons/pursuit.svg", + weaponMelee: "systems/ringworld/assets/icons/weapon-melee.svg", + weaponRanged: "systems/ringworld/assets/icons/weapon-ranged.svg", + armor: "systems/ringworld/assets/icons/armor.svg", + gearDefault: "systems/ringworld/assets/icons/gear.svg", + powerPackDefault: "systems/ringworld/assets/icons/power-pack.svg" +}; +function be(e) { + return String(e).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); +} +function la(e) { + return `systems/ringworld/assets/icons/species/${be(e)}.svg`; +} +function ca(e) { + return `systems/ringworld/assets/icons/weapon/${be(e)}.svg`; +} +function ua(e) { + return `systems/ringworld/assets/icons/armor/${be(e)}.svg`; +} +function ma(e) { + return `systems/ringworld/assets/icons/gear/${be(e)}.svg`; +} +function gt(e) { + return `systems/ringworld/assets/icons/pursuit/${be(e)}.svg`; +} +const Ze = { + rLeg: !0, + lLeg: !0, + abdomen: !0, + chest: !0, + rArm: !0, + lArm: !0, + head: !0 +}, Se = { + rLeg: !1, + lLeg: !1, + abdomen: !0, + chest: !0, + rArm: !1, + lArm: !1, + head: !1 +}, da = { + rLeg: !1, + lLeg: !1, + abdomen: !1, + chest: !1, + rArm: !1, + lArm: !1, + head: !0 +}, ga = { + rLeg: !1, + lLeg: !1, + abdomen: !1, + chest: !1, + rArm: !0, + lArm: !0, + head: !1 +}, pa = { + rLeg: !0, + lLeg: !0, + abdomen: !1, + chest: !1, + rArm: !1, + lArm: !1, + head: !1 +}, pt = [ + { + name: "Human", + playable: !0, + gravity: "normal", + homeworld: "Known Space", + modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 }, + notes: "Baseline human explorer profile." + }, + { + name: "Earth Human", + playable: !0, + gravity: "normal", + homeworld: "Earth", + modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 1, dex: 0, app: 0, edu: 0, age: 0 }, + notes: "Earth-line humans with stronger latent psionic potential." + }, + { + name: "Kzinti", + playable: !0, + gravity: "heavy", + homeworld: "Kzin", + modifiers: { str: 2, mas: 2, con: 1, int: -1, pow: 0, dex: 0, app: -2, edu: 0, age: 0 }, + notes: "Predatory felinoids with high physical strength." + }, + { + name: "Pierson's Puppeteer", + playable: !0, + gravity: "light", + homeworld: "Fleet of Worlds", + modifiers: { str: -2, mas: -2, con: -1, int: 2, pow: 1, dex: 1, app: 0, edu: 1, age: 0 }, + notes: "Highly intelligent, cautious engineers." + }, + { + name: "Dolphin", + playable: !0, + gravity: "normal", + homeworld: "Human Space Oceans", + modifiers: { str: -2, mas: 1, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 }, + notes: "Aquatic specialists with enhanced endurance." + }, + { + name: "Trinoc", + playable: !0, + gravity: "light", + homeworld: "Trinoc Worlds", + modifiers: { str: -1, mas: -1, con: 0, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 }, + notes: "Three-eyed flyers with excellent perception." + }, + { + name: "Martian", + playable: !0, + gravity: "light", + homeworld: "Mars", + modifiers: { str: -1, mas: -1, con: -1, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 }, + notes: "Light-gravity adapted intellect-focused profile." + }, + { + name: "Pierin", + playable: !0, + gravity: "normal", + homeworld: "Pierin Space", + modifiers: { str: 0, mas: 0, con: 0, int: 1, pow: 0, dex: 0, app: 1, edu: 0, age: 0 }, + notes: "Socially adept, technically curious explorers." + }, + { + name: "Boat People", + playable: !0, + gravity: "normal", + homeworld: "Ringworld", + modifiers: { str: 0, mas: 0, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: -1, age: 0 }, + notes: "Ringworld hominids adapted to river and sea cultures." + }, + { + name: "Builder", + playable: !0, + gravity: "normal", + homeworld: "Ringworld", + modifiers: { str: 1, mas: 1, con: 0, int: 0, pow: 0, dex: 0, app: -1, edu: 0, age: 0 }, + notes: "Ringworld labor stock with robust build." + }, + { + name: "Ghoul", + playable: !0, + gravity: "normal", + homeworld: "Ringworld", + modifiers: { str: 0, mas: 0, con: 1, int: -1, pow: 0, dex: 1, app: -2, edu: -1, age: 0 }, + notes: "Scavenger subspecies with strong survival instincts." + }, + { + name: "Bandersnatchi", + playable: !1, + gravity: "heavy", + homeworld: "Known Space", + modifiers: { str: 4, mas: 4, con: 3, int: 0, pow: 0, dex: -2, app: -3, edu: 0, age: 10 }, + notes: "Massive engineered beings; typically GM creature profile." + } +], fa = [ + { + name: "Variable Sword", + equipped: !0, + weight: 1.5, + linkedSkill: "V. Sword, F. Laser", + attackType: "melee", + damageFormula: "1d10+1", + range: { close: "Melee", medium: "-", long: "-" }, + energyUsedPerAttack: 1, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "High-energy blade weapon." + }, + { + name: "Monofilament Knife", + equipped: !0, + weight: 0.2, + linkedSkill: "Archaic Melee Weapon", + attackType: "melee", + damageFormula: "1d6+1", + range: { close: "Melee", medium: "-", long: "-" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 0, + powerSource: "actor", + notes: "Fine-edge close combat blade." + }, + { + name: "Shock Baton", + equipped: !0, + weight: 1.2, + linkedSkill: "Unarmed Combat", + attackType: "melee", + damageFormula: "1d8+1", + range: { close: "Melee", medium: "-", long: "-" }, + energyUsedPerAttack: 1, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Close-quarters stun baton." + }, + { + name: "Flashlight Laser", + equipped: !0, + weight: 0.8, + linkedSkill: "V. Sword, F. Laser", + attackType: "ranged", + damageFormula: "1d10", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 2, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Compact laser unit." + }, + { + name: "Laser Pistol", + equipped: !0, + weight: 1.4, + linkedSkill: "Handgun, Energy", + attackType: "ranged", + damageFormula: "1d10+1", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 2, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Common energy sidearm." + }, + { + name: "Laser Rifle", + equipped: !0, + weight: 4.5, + linkedSkill: "Heavy Weapon, Energy", + attackType: "ranged", + damageFormula: "2d6+2", + range: { close: "Short", medium: "Long", long: "Extreme" }, + energyUsedPerAttack: 3, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Standard long-arm laser weapon." + }, + { + name: "Stunner", + equipped: !0, + weight: 1, + linkedSkill: "Handgun, Energy", + attackType: "ranged", + damageFormula: "1d6", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 1, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Low-lethality disruption sidearm." + }, + { + name: "Handgun (Projectile)", + equipped: !0, + weight: 1.2, + linkedSkill: "Handgun, Projectile", + attackType: "ranged", + damageFormula: "1d10", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 1, + powerSource: "actor", + notes: "Conventional projectile sidearm." + }, + { + name: "Rifle (Projectile)", + equipped: !0, + weight: 5, + linkedSkill: "Heavy Weapon, Projectile", + attackType: "ranged", + damageFormula: "2d6", + range: { close: "Short", medium: "Long", long: "Extreme" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 1, + powerSource: "actor", + notes: "Conventional projectile rifle." + }, + { + name: "Slug Thrower Carbine", + equipped: !0, + weight: 3.8, + linkedSkill: "Heavy Weapon, Projectile", + attackType: "ranged", + damageFormula: "2d6+1", + range: { close: "Short", medium: "Long", long: "Extreme" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 1, + powerSource: "actor", + notes: "Compact projectile carbine." + } +], ya = [ + { name: "Cloth Vest", equipped: !0, weight: 2, value: 1, coverage: Se, notes: "Basic torso protection." }, + { name: "Soft Combat Vest", equipped: !0, weight: 4, value: 2, coverage: Se, notes: "Flexible tactical vest." }, + { name: "Combat Harness", equipped: !0, weight: 5, value: 2, coverage: { ...Se, rArm: !0, lArm: !0 }, notes: "Torso + upper arms." }, + { name: "Armored Coat", equipped: !0, weight: 7, value: 3, coverage: { ...Se, rArm: !0, lArm: !0 }, notes: "Heavy coat with arm plating." }, + { name: "Leg Guards", equipped: !0, weight: 4, value: 2, coverage: pa, notes: "Lower-body armor." }, + { name: "Arm Bracers", equipped: !0, weight: 2, value: 2, coverage: ga, notes: "Forearm and upper arm guards." }, + { name: "Combat Helmet", equipped: !0, weight: 1.5, value: 2, coverage: da, notes: "Cranial protection." }, + { name: "Full Combat Suit", equipped: !0, weight: 12, value: 3, coverage: Ze, notes: "Full-body armored suit." }, + { name: "Impact Suit", equipped: !0, weight: 16, value: 4, coverage: Ze, notes: "High-end impact and hazard suit." } +], ha = [ + { + name: "Power Pack R20", + weight: 1, + cost: 100, + energyUse: 0, + powerSupplyNotes: "Rechargeable power pack", + isPowerPack: !0, + energy: { value: 20, max: 20 }, + notes: "Standard personal battery pack." + }, + { + name: "Power Pack R50", + weight: 2, + cost: 220, + energyUse: 0, + powerSupplyNotes: "Rechargeable power pack", + isPowerPack: !0, + energy: { value: 50, max: 50 }, + notes: "Extended personal battery pack." + }, + { + name: "Power Pack R100", + weight: 4, + cost: 400, + energyUse: 0, + powerSupplyNotes: "Rechargeable power pack", + isPowerPack: !0, + energy: { value: 100, max: 100 }, + notes: "High-capacity field battery." + }, + { name: "Portable Generator", weight: 8, cost: 700, energyUse: 0, powerSupplyNotes: "Provides charging capacity", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Portable recharge generator." }, + { name: "MicroMind Unit", weight: 0.5, cost: 900, energyUse: 1, powerSupplyNotes: "Requires external power", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Portable advanced computation module." }, + { name: "Memory Bubble Reader", weight: 0.5, cost: 450, energyUse: 1, powerSupplyNotes: "Standard battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Reads and writes memory bubbles." }, + { name: "Autodoc (Portable)", weight: 18, cost: 3500, energyUse: 6, powerSupplyNotes: "High draw medical station", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Field medical support unit." }, + { name: "Medkit", weight: 1, cost: 120, energyUse: 0, powerSupplyNotes: "None", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Manual emergency treatment supplies." }, + { name: "Bioscanner", weight: 1, cost: 300, energyUse: 1, powerSupplyNotes: "Battery or generator", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Vitals and hazard scanning tool." }, + { name: "Survey Scanner", weight: 2, cost: 500, energyUse: 2, powerSupplyNotes: "Battery or generator", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Terrain and structure analysis scanner." }, + { name: "Translator Module", weight: 0.5, cost: 650, energyUse: 1, powerSupplyNotes: "Battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Linguistic conversion and support." }, + { name: "Holo Projector", weight: 1.5, cost: 800, energyUse: 2, powerSupplyNotes: "Battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Short-range holographic projection." }, + { name: "Stasis Box", weight: 12, cost: 2600, energyUse: 5, powerSupplyNotes: "Continuous power required", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Suspended animation container." }, + { name: "Reactionless Flycycle", weight: 35, cost: 4200, energyUse: 4, powerSupplyNotes: "Vehicle power core", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Single-rider reactionless vehicle." }, + { name: "Field Toolkit", weight: 2, cost: 180, energyUse: 0, powerSupplyNotes: "None", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Mechanical and electronics repair kit." }, + { name: "Vac Suit", weight: 7, cost: 1400, energyUse: 1, powerSupplyNotes: "Suit battery required", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Pressure and vacuum survival suit." }, + { name: "Air Reprocessor", weight: 3, cost: 950, energyUse: 2, powerSupplyNotes: "Continuous power", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Atmosphere recycling support unit." }, + { name: "Signal Beacon", weight: 1, cost: 140, energyUse: 1, powerSupplyNotes: "Battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Emergency and navigation beacon." } +], he = [ + { + name: "Artist", + rollRange: "01-07", + wealthValue: 0, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Computers", "Fast Talk", "Fine Arts", "Hide", "Musicianship", "Observe", "Perform", "Psychology", "Scent", "Theology"], + notes: "Creative specialist pursuit." + }, + { + name: "Colonist", + rollRange: "08-27", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Atmospheric Craft", "Bargain", "Ground Vehicle", "Farming", "Handgun, Energy", "Personal Flyer", "Listen", "Heavy Weapon, Energy", "Observe", "Planetology", "Search", "Track"], + notes: "Settlement and frontier operations pursuit." + }, + { + name: "Criminal", + rollRange: "28-29", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Computers", "Fast Talk", "Hide", "Personal Flyer", "Handgun, Energy", "Handgun, Projectile", "Sneak", "Unarmed Combat"], + notes: "Underground survival pursuit." + }, + { + name: "Dilettante", + rollRange: "30", + wealthValue: 5, + occupationPointsPerYear: 15, + fixedSkills: [], + notes: "Any skills allowed; uses 15 occupation points per year." + }, + { + name: "Engineer", + rollRange: "31-36", + wealthValue: 2, + occupationPointsPerYear: 20, + fixedSkills: ["Astronomy", "Chemistry", "Computers", "Engineering", "Ground Vehicle", "Mathematics", "Personal Flyer", "Physics", "Planetology", "Reaction Drive", "Reactionless Drive", "Repair"], + notes: "Technical design and maintenance pursuit." + }, + { + name: "Entertainer", + rollRange: "37-38", + wealthValue: 2, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Bargain", "Own Language", "Musicianship", "Observe", "Orate", "Perform", "Psychology", "Search", "Sneak"], + notes: "Performance and social influence pursuit." + }, + { + name: "Explorer", + rollRange: "39-52", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Atmospheric Craft", "Bargain", "Biology", "Botany", "Emergency Treatment", "Engineering", "Ground Vehicle", "Handgun, Energy", "Heavy Weapon, Energy", "Hyperdrive", "Observe", "Planetology", "Reactionless Drive", "Repair", "Search", "Track", "Zoology"], + notes: "Survey and expedition pursuit." + }, + { + name: "Functionary", + rollRange: "53-56", + wealthValue: 3, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Debate", "Fast Talk", "History", "Law", "Observe", "Orate", "Psychology", "Strategy", "Theology"], + notes: "Administrative and governance pursuit." + }, + { + name: "Journalist/Writer", + rollRange: "57-59", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Anthropology", "Bargain", "Computers", "Debate", "History", "Fine Arts", "Own Language", "Law", "Observe", "Psychology", "Search", "Sneak", "Unarmed Combat", "Weapons System"], + notes: "Investigation and reporting pursuit." + }, + { + name: "Miner", + rollRange: "60-65", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Atmospheric Craft", "Aquatic Vehicle", "Bargain", "Climb (Custom)", "Computers", "Emergency Treatment", "Engineering", "Handgun, Energy", "Heavy Weapon, Energy", "Observe", "Personal Flyer", "Planetology", "Reaction Drive", "Reactionless Drive", "Repair", "Search"], + notes: "Resource extraction frontier pursuit." + }, + { + name: "Pilot", + rollRange: "66-68", + wealthValue: 3, + occupationPointsPerYear: 20, + fixedSkills: ["Astronomy", "Atmospheric Craft", "Computers", "Engineering", "Hyperdrive", "History", "Law", "Mathematics", "Physics", "Reaction Drive", "Reactionless Drive", "Repair", "Strategy", "Weapons System"], + notes: "Flight and spacecraft operations pursuit." + }, + { + name: "Police Officer", + rollRange: "69-76", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Atmospheric Craft", "Bargain", "Computers", "Fast Talk", "Emergency Treatment", "Handgun, Energy", "Handgun, Projectile", "Heavy Weapon, Energy", "Heavy Weapon, Projectile", "Law", "Observe", "Personal Flyer", "Psychology", "Search", "Unarmed Combat"], + notes: "Law enforcement and response pursuit." + }, + { + name: "Politician", + rollRange: "77-78", + wealthValue: 3, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Computers", "Debate", "Fast Talk", "History", "Orate", "Own Language", "Perform", "Psychology"], + optionalSkillGroups: [ + { + choose: 1, + skills: ["Engineering", "Farming", "Second Languages", "Strategy", "Theology"] + } + ], + notes: "Public office and influence pursuit." + }, + { + name: "Prisoner", + rollRange: "79-80", + wealthValue: 0, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Fast Talk", "Hide", "Observe", "Psychology", "Search", "Sneak", "Unarmed Combat"], + notes: "Carceral survival pursuit." + }, + { + name: "Scholar", + rollRange: "81-84", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: [], + optionalSkillGroups: [ + { + choose: 2, + skills: ["Anthropology", "Astronomy", "Biology", "Botany", "Chemistry", "Computers", "Engineering", "Fine Arts", "History", "Law", "Mathematics", "Own Language", "Physics", "Planetology", "Second Languages", "Theology", "Zoology"] + } + ], + notes: "Academic pursuit; pick up to two listed skills." + }, + { + name: "Scientific Researcher", + rollRange: "85-94", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: [], + optionalSkillGroups: [ + { + choose: 3, + skills: ["Anthropology", "Astronomy", "Bargain", "Biology", "Botany", "Chemistry", "Computers", "Engineering", "History", "Mathematics", "Observe", "Personal Flyer", "Physics", "Planetology", "Psychology", "Repair", "Search", "Zoology"] + } + ], + notes: "Research pursuit; pick up to three listed skills." + }, + { + name: "Soldier", + rollRange: "95-98", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Atmospheric Craft", "Aquatic Vehicle", "Computers", "Emergency Treatment", "Fast Talk", "Ground Vehicle", "Handgun, Energy", "Handgun, Projectile", "Heavy Weapon, Energy", "Heavy Weapon, Projectile"], + notes: "Military operations pursuit." + }, + { + name: "Zealot", + rollRange: "99-00", + wealthValue: 0, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Debate", "Fast Talk", "Fine Arts", "Handgun, Energy", "Handgun, Projectile", "Hide", "Own Language", "Law", "Strategy", "Orate", "Psychology", "Theology"], + notes: "Ideological dedication pursuit." + } +]; +function ft() { + return pt.filter((e) => e.playable); +} +function Q(e, t) { + return `${e}:${R(t)}`; +} +function wa(e) { + var s, i, l, r, c; + const t = (s = getDocumentClass("Item")) == null ? void 0 : s.TYPES; + if (Array.isArray(t)) return t.includes(e); + const a = (l = (i = game == null ? void 0 : game.system) == null ? void 0 : i.documentTypes) == null ? void 0 : l.Item; + if (Array.isArray(a)) return a.includes(e); + if (a && typeof a == "object") return Object.prototype.hasOwnProperty.call(a, e); + const n = (r = CONFIG == null ? void 0 : CONFIG.Item) == null ? void 0 : r.dataModels; + if (n && typeof n == "object" && Object.prototype.hasOwnProperty.call(n, e)) + return !0; + const o = (c = game == null ? void 0 : game.model) == null ? void 0 : c.Item; + return !!(o && typeof o == "object" && Object.prototype.hasOwnProperty.call(o, e)); +} +function ba(e) { + return { + key: Q("skill", e.name), + folderKey: "skills", + img: ye(e.name) || _.skillDefault, + data: { + name: e.name, + type: "skill", + system: { + value: e.base, + category: e.category, + specialization: e.specialization ?? "" + } + } + }; +} +function ka(e) { + const t = la(e.name) || _.speciesDefault; + return { + key: Q("species", e.name), + folderKey: "species", + img: t, + data: { + name: e.name, + type: "species", + system: { + playable: e.playable, + gravity: e.gravity, + homeworld: e.homeworld, + modifiers: e.modifiers, + notes: e.notes + } + } + }; +} +function va(e) { + const t = e.attackType === "melee" ? "weaponMelee" : "weaponRanged", a = ca(e.name) || (e.attackType === "melee" ? _.weaponMelee : _.weaponRanged); + return { + key: Q("weapon", e.name), + folderKey: t, + img: a, + data: { + name: e.name, + type: "weapon", + system: { + equipped: e.equipped ?? !0, + stowed: !1, + weight: e.weight ?? 0, + linkedSkill: e.linkedSkill, + linkedSkillId: "", + attackType: e.attackType, + damageFormula: e.damageFormula, + range: e.range, + energyUsedPerAttack: e.energyUsedPerAttack, + ammoUsedPerAttack: e.ammoUsedPerAttack, + powerSource: e.powerSource, + powerPackId: "", + notes: e.notes + } + } + }; +} +function Sa(e) { + const t = ua(e.name) || _.armor; + return { + key: Q("armor", e.name), + folderKey: "armor", + img: t, + data: { + name: e.name, + type: "armor", + system: { + equipped: e.equipped, + stowed: !1, + weight: e.weight ?? 0, + value: e.value, + coverage: e.coverage, + perLocation: { + rLeg: 0, + lLeg: 0, + abdomen: 0, + chest: 0, + rArm: 0, + lArm: 0, + head: 0 + }, + notes: e.notes + } + } + }; +} +function Aa(e) { + const t = ma(e.name) || (e.isPowerPack ? _.powerPackDefault : _.gearDefault); + return { + key: Q("gear", e.name), + folderKey: "gear", + img: t, + data: { + name: e.name, + type: "gear", + system: { + equipped: e.equipped ?? !0, + stowed: !1, + weight: e.weight, + cost: e.cost, + energyUse: e.energyUse, + powerSupplyNotes: e.powerSupplyNotes, + isPowerPack: e.isPowerPack, + energy: e.energy, + notes: e.notes + } + } + }; +} +function xa(e) { + const t = gt(e.name) || _.pursuitDefault; + return { + key: Q("pursuit", e.name), + folderKey: "pursuits", + img: t, + data: { + name: e.name, + type: "pursuit", + system: { + wealthValue: e.wealthValue, + occupationPointsPerYear: e.occupationPointsPerYear, + rollRange: e.rollRange, + fixedSkills: e.fixedSkills, + optionalSkillGroups: e.optionalSkillGroups ?? [], + notes: e.notes + } + } + }; +} +function Pa(e) { + const t = e.fixedSkills.length ? `Fixed skills: ${e.fixedSkills.join(", ")}` : "Fixed skills: (none)", a = (e.optionalSkillGroups ?? []).map((o) => `Choose ${o.choose}: ${o.skills.join(", ")}`).join(" | "), n = a ? ` Optional: ${a}.` : ""; + return `${e.notes} ${t}.${n} Wealth ${e.wealthValue}. Occupation points/year ${e.occupationPointsPerYear}.`; +} +function Ia(e) { + const t = gt(e.name) || _.pursuitDefault, a = `Pursuit: ${e.name}`; + return { + key: Q("gear", a), + folderKey: "pursuits", + img: t, + data: { + name: a, + type: "gear", + system: { + equipped: !1, + stowed: !0, + weight: 0, + cost: 0, + energyUse: 0, + powerSupplyNotes: "Pursuit reference", + isPowerPack: !1, + energy: { value: 0, max: 0 }, + notes: Pa(e) + }, + flags: { + ringworld: { + pursuitData: { + pursuitName: e.name, + rollRange: e.rollRange, + wealthValue: e.wealthValue, + occupationPointsPerYear: e.occupationPointsPerYear, + fixedSkills: e.fixedSkills, + optionalSkillGroups: e.optionalSkillGroups ?? [] + } + } + } + } + }; +} +async function G(e, t) { + const a = game.folders.find( + (n) => { + var o; + return n.type === "Item" && n.name === e && (((o = n.folder) == null ? void 0 : o.id) ?? null) === t; + } + ); + return a || Folder.create({ + name: e, + type: "Item", + folder: t, + sorting: "a" + }); +} +async function La() { + const e = await G("Ringworld Catalog", null), t = await G("Species", e.id), a = await G("Skills", e.id), n = await G("Pursuits", e.id), o = await G("Gear", e.id), s = await G("Armor", o.id), i = await G("Weapons", e.id), l = await G("Melee Weapons", i.id), r = await G("Ranged Weapons", i.id); + return { + species: t.id, + skills: a.id, + pursuits: n.id, + gear: o.id, + armor: s.id, + weaponMelee: l.id, + weaponRanged: r.id + }; +} +function Ma() { + const e = wa("pursuit"); + return [ + ...pt.map(ka), + ...we.map(ba), + ...e ? he.map(xa) : he.map(Ia), + ...fa.map(va), + ...ya.map(Sa), + ...ha.map(Aa) + ]; +} +async function Be(e) { + var i, l, r, c, u, d, g; + if (!((i = game.user) != null && i.isGM)) return { added: 0, updated: 0 }; + const t = await La(), a = Ma(), n = new Map( + game.items.map((m) => [Q(m.type, m.name), m]) + ), o = [], s = []; + for (const m of a) { + const f = t[m.folderKey], y = n.get(m.key); + if (!y) { + o.push({ + ...m.data, + img: m.img, + folder: f, + flags: { + ringworld: { + catalog: !0, + catalogKey: m.key + } + } + }); + continue; + } + const b = { _id: y.id }; + (((l = y.folder) == null ? void 0 : l.id) ?? null) !== f && (b.folder = f), String(y.img || "") !== m.img && (b.img = m.img), ((c = (r = y.flags) == null ? void 0 : r.ringworld) == null ? void 0 : c.catalog) !== !0 && (b["flags.ringworld.catalog"] = !0), ((d = (u = y.flags) == null ? void 0 : u.ringworld) == null ? void 0 : d.catalogKey) !== m.key && (b["flags.ringworld.catalogKey"] = m.key); + const w = foundry.utils.deepClone(y.system ?? {}), p = foundry.utils.mergeObject(w, m.data.system ?? {}, { + inplace: !1, + insertKeys: !0, + insertValues: !0, + overwrite: !1 + }), h = foundry.utils.diffObject(w, p); + Object.keys(h).length > 0 && (b.system = p), Object.keys(b).length > 1 && s.push(b); + } + return o.length && await Item.createDocuments(o), s.length && await Item.updateDocuments(s), e != null && e.notify && ((g = ui.notifications) == null || g.info(`Ringworld catalog synced: +${o.length} new, ${s.length} updated.`)), { added: o.length, updated: s.length }; +} +function yt() { + return game.items.filter((e) => e.type === "species" && e.system.playable); +} +function K(e) { + return Math.max(1, Math.min(99, Math.floor(e))); +} +async function j(e) { + const t = await Me(e); + return Number(t.total ?? 0); +} +async function Ca() { + let e = 10; + for (; ; ) { + const t = await j("1d6"); + if (e += t, t <= 4) break; + } + return K(e); +} +async function $a(e) { + const t = { + light: { str: "2d6+3", mas: "2d6+3", con: "2d6+6", dex: "2d6+9" }, + normal: { str: "2d6+6", mas: "2d6+6", con: "2d6+6", dex: "2d6+6" }, + heavy: { str: "2d6+9", mas: "2d6+9", con: "2d6+3", dex: "2d6+6" } + }[e]; + return { + str: await j(t.str), + mas: await j(t.mas), + con: await j(t.con), + int: await j("2d6+6"), + pow: await j("2d6+6"), + dex: await j(t.dex), + app: await j("2d6+6"), + edu: await Ca(), + age: await j("1d20+16") + }; +} +function xe(e) { + return e[Math.floor(Math.random() * e.length)]; +} +function Ra(e) { + const t = ["Ar", "Bel", "Cor", "Den", "Ela", "Far", "Gal", "Hel", "Ira", "Jun", "Kor", "Lys", "Mor", "Nyr", "Or", "Pra", "Qua", "Rin", "Sol", "Tor", "Uri", "Val", "Wen", "Xel", "Yor", "Zen"], a = ["an", "el", "is", "or", "us", "a", "en", "ion", "eth", "al", "ir", "os"]; + return `${xe(t)}${xe(a)} (${e})`; +} +function Ta(e, t) { + const a = (t == null ? void 0 : t.modifiers) ?? {}; + return { + str: K(e.str + Number(a.str || 0)), + mas: K(e.mas + Number(a.mas || 0)), + con: K(e.con + Number(a.con || 0)), + int: K(e.int + Number(a.int || 0)), + pow: K(e.pow + Number(a.pow || 0)), + dex: K(e.dex + Number(a.dex || 0)), + app: K(e.app + Number(a.app || 0)), + edu: K(e.edu + Number(a.edu || 0)), + age: K(e.age + Number(a.age || 0)) + }; +} +async function ht(e) { + var d, g, m, f; + const t = String(((g = (d = e.system) == null ? void 0 : d.profile) == null ? void 0 : g.gravity) || "normal"), a = t === "light" || t === "heavy" ? t : "normal", n = String(((f = (m = e.system) == null ? void 0 : m.profile) == null ? void 0 : f.species) || ""), s = yt().find((y) => y.name === n), i = ft().find((y) => y.name === n), l = (s == null ? void 0 : s.system) ?? i ?? null, r = await $a(a), c = Ta(r, l); + await e.update({ + "system.characteristics.str": c.str, + "system.characteristics.mas": c.mas, + "system.characteristics.con": c.con, + "system.characteristics.int": c.int, + "system.characteristics.pow": c.pow, + "system.characteristics.dex": c.dex, + "system.characteristics.app": c.app, + "system.characteristics.edu": c.edu, + "system.characteristics.age": c.age, + "system.profile.chronologicalAge": c.age, + "system.profile.physiologicalAge": c.age + }); + const u = e.items.find((y) => y.type === "skill" && y.name.toLowerCase() === "own language"); + u && await e.updateEmbeddedDocuments("Item", [{ + _id: u.id, + "system.value": Math.min(99, c.int * 5) + }]); +} +async function Da(e) { + const t = [ + ["weapon", "Laser Pistol"], + ["weapon", "Variable Sword"], + ["armor", "Soft Combat Vest"], + ["gear", "Power Pack R20"], + ["gear", "Medkit"], + ["gear", "Survey Scanner"] + ], a = game.items, n = new Set(e.items.map((s) => `${s.type}:${s.name.toLowerCase()}`)), o = []; + for (const [s, i] of t) { + if (n.has(`${s}:${String(i).toLowerCase()}`)) continue; + const l = a.find((r) => r.type === s && r.name === i); + l && o.push(l.toObject()); + } + o.length && await e.createEmbeddedDocuments("Item", o); +} +async function wt(e) { + var c; + const t = yt(), a = ft(), n = t.length ? xe(t) : null, o = n ? null : xe(a), s = (n == null ? void 0 : n.system) ?? o, i = (n == null ? void 0 : n.name) ?? (o == null ? void 0 : o.name) ?? "Human", l = String(s.gravity || "normal"), r = String(s.homeworld || "Ringworld"); + await e.update({ + name: Ra(i), + "system.profile.species": i, + "system.profile.gravity": l, + "system.profile.homeworld": r + }), await ht(e), await ((c = e.ensureBaseSkills) == null ? void 0 : c.call(e)), await Da(e); +} +async function bt() { + const t = await getDocumentClass("Actor").create({ + name: "Random Explorer", + type: "explorer" + }); + return await wt(t), t; +} +const Ha = ["str", "mas", "con", "int", "pow", "dex", "app", "edu", "age"]; +function v(e, t = 0) { + const a = Number(e); + return Number.isFinite(a) ? a : t; +} +function Na(e) { + return e != null && String(e).trim() !== ""; +} +function He(e) { + return Math.max(1, Math.min(99, Math.floor(e))); +} +function Oa(e, t) { + const a = e + t; + return a <= 12 ? "-1d6" : a <= 24 ? "-1d4" : a <= 32 ? "0" : a <= 40 ? "+1d4" : a <= 56 ? "+1d6" : a <= 72 ? "+2d6" : "+3d6"; +} +function Je(e) { + const t = v(e == null ? void 0 : e.con, 10), a = v(e == null ? void 0 : e.int, 10), n = v(e == null ? void 0 : e.pow, 10), o = v(e == null ? void 0 : e.dex, 10); + return { + health: Math.max(0, Math.min(99, t * 5)), + reasoning: Math.max(0, Math.min(99, a * 5)), + luck: Math.max(0, Math.min(99, n * 5)), + dodge: Math.max(0, Math.min(99, o * 5)) + }; +} +function Ea(e) { + const t = v(e == null ? void 0 : e.str, 10), a = v(e == null ? void 0 : e.dex, 10), n = v(e == null ? void 0 : e.app, 10), o = v(e == null ? void 0 : e.edu, 10), s = v(e == null ? void 0 : e.int, 10), i = v(e == null ? void 0 : e.pow, 10), l = v(e == null ? void 0 : e.con, 10); + return { + agility: Math.max(0, t + a), + communication: Math.max(0, s + n), + knowledge: Math.max(0, s + o), + perception: Math.max(0, i + l), + technical: Math.max(0, a + s) + }; +} +function Ba(e) { + return e <= 4 ? 7 : e <= 8 ? 6 : e <= 12 ? 5 : e <= 16 ? 4 : e <= 20 ? 3 : e <= 24 ? 2 : 1; +} +function Fa(e) { + return e === "light" ? 0.5 : e === "heavy" ? 2 : 1; +} +function Ua(e) { + var t; + return e.type !== "weapon" && e.type !== "armor" && e.type !== "gear" ? !1 : ((t = e.system) == null ? void 0 : t.stowed) !== !0; +} +function qa(e) { + var t; + return Math.max(0, v((t = e.system) == null ? void 0 : t.weight, 0)); +} +function Ka(e, t) { + var y, b, w, p; + const a = String(((y = e == null ? void 0 : e.profile) == null ? void 0 : y.gravity) || "normal"), n = Fa(a), o = t.filter((h) => h.type === "weapon" || h.type === "armor" || h.type === "gear").filter(Ua).reduce((h, A) => h + qa(A), 0), s = Math.max(0, o), i = s * n, l = Math.max(0, v((b = e == null ? void 0 : e.characteristics) == null ? void 0 : b.str, 0)), r = Math.max(0, i - l), c = r > 0 ? Math.ceil(r / 10) : 0, u = r > 0, d = Math.max(1, v((w = e == null ? void 0 : e.derived) == null ? void 0 : w.move, 3)), g = Math.max(1, v((p = e == null ? void 0 : e.derived) == null ? void 0 : p.run, d * 2)); + return { + rawLoadKg: s, + effectiveLoadKg: i, + limitKg: l, + overLimitKg: r, + actionRankingPenalty: c, + overLimit: u, + currentMove: u ? 2 : d, + currentRun: u ? 4 : g, + gravityMultiplier: n + }; +} +class Va extends Actor { + prepareDerivedData() { + if (super.prepareDerivedData(), ra(this), this.type !== "explorer" && this.type !== "creature") return; + const t = this.system ?? {}, a = t.characteristics ?? {}, n = t.profile ?? {}; + n.chronologicalAge = v(n.chronologicalAge, v(a.age, 25)), n.physiologicalAge = v(n.physiologicalAge, n.chronologicalAge), t.profile = n, t.combat = t.combat ?? {}, Na(t.combat.damageMod) || (t.combat.damageMod = Oa(v(a.str, 10), v(a.mas, 10))), t.derivedRolls = t.derivedRolls ?? {}; + const o = Je(a); + for (const l of Object.keys(o)) + t.derivedRolls[l] = v(t.derivedRolls[l], o[l]); + t.skillRootMax = t.skillRootMax ?? {}; + const s = Ea(a); + for (const [l, r] of Object.entries(s)) + t.skillRootMax[l] = r; + t.derived = t.derived ?? {}, t.derived.actionRanking = Ba(v(a.dex, 10)), t.derived.move = v(t.derived.move, 3), t.derived.run = v(t.derived.run, Math.max(1, v(t.derived.move, 3) * 2)), t.encumbrance = t.encumbrance ?? {}; + const i = Ka(t, this.items); + t.encumbrance.rawLoadKg = i.rawLoadKg, t.encumbrance.effectiveLoadKg = i.effectiveLoadKg, t.encumbrance.limitKg = i.limitKg, t.encumbrance.overLimitKg = i.overLimitKg, t.encumbrance.actionRankingPenalty = i.actionRankingPenalty, t.encumbrance.overLimit = i.overLimit, t.encumbrance.gravityMultiplier = i.gravityMultiplier, t.derived.currentMove = i.currentMove, t.derived.currentRun = i.currentRun, t.finance = t.finance ?? { money: 0, inBank: 0 }, t.finance.money = v(t.finance.money, 0), t.finance.inBank = v(t.finance.inBank, 0), t.notes = t.notes ?? { defects: "", habits: "" }, typeof t.sheetToolsUnlocked != "boolean" && (t.sheetToolsUnlocked = !1), t.characterBuilder = t.characterBuilder ?? { + pursuits: [], + allocations: {}, + baseBySkillId: {} + }, t.unconsciousLevel = v(t.unconsciousLevel, 0); + } + get skills() { + return this.items.filter((t) => t.type === "skill"); + } + get weapons() { + return this.items.filter((t) => t.type === "weapon"); + } + get armors() { + return this.items.filter((t) => t.type === "armor"); + } + get gears() { + return this.items.filter((t) => t.type === "gear"); + } + async rollSkillById(t) { + const a = this.items.get(t); + !a || a.type !== "skill" || await Ee(this, a.name, Number(a.system.value ?? 0)); + } + async rollHitLocation() { + await Xt(this); + } + async useWeaponById(t) { + const a = this.items.get(t); + !a || a.type !== "weapon" || await dt(this, a); + } + async ensureBaseSkills() { + if (this.type !== "explorer" && this.type !== "creature") return 0; + const t = new Set( + this.items.filter((n) => n.type === "skill").map((n) => R(n.name)) + ), a = we.filter((n) => !t.has(R(n.name))).map((n) => ({ + name: n.name, + type: "skill", + img: ye(n.name), + system: { + value: n.base, + category: n.category, + specialization: n.specialization ?? "" + } + })); + return a.length ? (await this.createEmbeddedDocuments("Item", a), a.length) : 0; + } + async rollCharacteristics() { + this.type === "explorer" && await ht(this); + } + async randomizeCharacter() { + this.type === "explorer" && await wt(this); + } + async applySpeciesItem(t) { + var y, b, w; + if (this.type !== "explorer" && this.type !== "creature") return; + const a = String((t == null ? void 0 : t.name) || "").trim(), n = (t == null ? void 0 : t.system) ?? {}, o = (n == null ? void 0 : n.modifiers) ?? {}, s = ((y = this.system) == null ? void 0 : y.speciesApplied) ?? {}, i = (s == null ? void 0 : s.modifiers) ?? {}, l = ((b = this.system) == null ? void 0 : b.characteristics) ?? {}, r = ((w = this.system) == null ? void 0 : w.profile) ?? {}, c = {}, u = { + str: 0, + mas: 0, + con: 0, + int: 0, + pow: 0, + dex: 0, + app: 0, + edu: 0, + age: 0 + }; + for (const p of Ha) { + const h = v(i == null ? void 0 : i[p], 0), A = v(o == null ? void 0 : o[p], 0); + u[p] = A; + const I = v(l == null ? void 0 : l[p], p === "age" ? 25 : 10) - h; + c[`system.characteristics.${p}`] = He(I + A); + } + const d = v(i == null ? void 0 : i.age, 0), g = u.age, m = v(r == null ? void 0 : r.chronologicalAge, v(l == null ? void 0 : l.age, 25)), f = v(r == null ? void 0 : r.physiologicalAge, m); + c["system.profile.chronologicalAge"] = He(m - d + g), c["system.profile.physiologicalAge"] = He(f - d + g), a && (c["system.profile.species"] = a), n != null && n.gravity && (c["system.profile.gravity"] = String(n.gravity)), (n == null ? void 0 : n.homeworld) !== void 0 && (c["system.profile.homeworld"] = String(n.homeworld || "")), c["system.speciesApplied"] = { + name: a, + modifiers: u + }, await this.update(c); + } + async rollDerivedCheck(t) { + var s, i, l; + if (this.type !== "explorer" && this.type !== "creature") return; + const a = { + health: "Health Roll", + reasoning: "Reasoning Roll", + luck: "Luck Roll", + dodge: "Dodge Roll" + }, n = Je(((s = this.system) == null ? void 0 : s.characteristics) ?? {}), o = v((l = (i = this.system) == null ? void 0 : i.derivedRolls) == null ? void 0 : l[t], n[t]); + await Ee(this, a[t], o); + } +} +class Ga extends Item { + async rollSkill() { + var a; + if (this.type !== "skill") return; + const t = this.actor; + if (!t) { + (a = ui.notifications) == null || a.warn("Skill must be owned by an Actor to roll."); + return; + } + await Ee(t, this.name, Number(this.system.value ?? 0)); + } + async useWeapon() { + var a; + if (this.type !== "weapon") return; + const t = this.actor; + if (!t) { + (a = ui.notifications) == null || a.warn("Weapon must be owned by an Actor to use."); + return; + } + await dt(t, this); + } +} +const ja = foundry.appv1.sheets.ActorSheet, Fe = { + Agility: "agility", + Communication: "communication", + Knowledge: "knowledge", + Perception: "perception", + Technical: "technical" +}, _a = { + Agility: "STR + DEX", + Communication: "INT + APP", + Knowledge: "INT + EDU", + Perception: "POW + CON", + Technical: "DEX + INT" +}, Wa = /* @__PURE__ */ new Set([ + R("Archaic Melee Weapon"), + R("Archaic Ranged Weapons") +]), Ya = R("Hyperdrive"); +function fe(e, t) { + return e.name.localeCompare(t.name); +} +function C(e, t = 0) { + const a = Number(e); + return Number.isFinite(a) ? a : t; +} +function B(e, t = 0) { + return Math.max(0, Math.floor(C(e, t))); +} +function Pe(e) { + return { + education: B(e == null ? void 0 : e.education, 0), + pursuits: B(e == null ? void 0 : e.pursuits, 0), + special: B(e == null ? void 0 : e.special, 0) + }; +} +function za(e) { + const t = String((e == null ? void 0 : e.name) || "").trim(), a = B(e == null ? void 0 : e.years, 0); + return !t || a <= 0 ? null : { name: t, years: a }; +} +function kt(e) { + const t = Array.isArray(e == null ? void 0 : e.pursuits) ? e.pursuits.map((o) => za(o)).filter((o) => !!o) : [], a = {}; + if (e != null && e.allocations && typeof e.allocations == "object") + for (const [o, s] of Object.entries(e.allocations)) { + if (!o) continue; + const i = Pe(s); + i.education + i.pursuits + i.special <= 0 || (a[o] = i); + } + const n = {}; + if (e != null && e.baseBySkillId && typeof e.baseBySkillId == "object") + for (const [o, s] of Object.entries(e.baseBySkillId)) + o && (n[o] = Math.max(0, B(s, 0))); + return { + pursuits: t, + allocations: a, + baseBySkillId: n + }; +} +function Qa(e) { + return !Wa.has(R(e)); +} +function Xa(e) { + return R(e) !== Ya; +} +function Za(e) { + const t = new Map( + he.map((n) => [R(n.name), n]) + ), a = /* @__PURE__ */ new Set(); + for (const n of e) { + const o = t.get(R(n.name)); + if (o) { + for (const s of o.fixedSkills || []) + a.add(R(s)); + for (const s of o.optionalSkillGroups || []) + for (const i of s.skills || []) + a.add(R(i)); + } + } + return a; +} +function Ja(e) { + const t = B(e == null ? void 0 : e.age, 0), a = B(e == null ? void 0 : e.edu, 0), n = B(e == null ? void 0 : e.int, 0), o = Math.max(0, (a - 10) * 20), s = Math.max(0, t - a - 6), i = s * 20, l = Math.max(0, n * 10); + return { + educationTotal: o, + pursuitsTotal: i, + specialTotal: l, + pursuitYearsTotal: s + }; +} +function Z(e, t) { + var d, g, m, f, y, b, w; + const a = Ja(((d = e.system) == null ? void 0 : d.characteristics) ?? {}), n = kt(((g = e.system) == null ? void 0 : g.characterBuilder) ?? {}), o = he.map((p) => p.name).sort((p, h) => p.localeCompare(h)), s = Za(n.pursuits), i = /* @__PURE__ */ new Map(); + let l = 0, r = 0, c = 0; + for (const p of t) { + const h = Pe(n.allocations[p.id]), A = h.education + h.pursuits + h.special, S = n.baseBySkillId[p.id], I = (m = $e(p.name)) == null ? void 0 : m.base, M = Math.max(0, C((f = p.system) == null ? void 0 : f.value, 0) - A), D = Math.max( + 0, + Number.isFinite(S) ? S : I ?? M + ), T = Fe[String(((y = p.system) == null ? void 0 : y.category) || "")] ?? "", H = T ? Math.max(0, B((w = (b = e.system) == null ? void 0 : b.skillRootMax) == null ? void 0 : w[T], 0)) : 99, L = Math.max(0, H - D), F = R(p.name), X = Qa(p.name), J = s.has(F), N = Xa(p.name); + i.set(p.id, { + allocation: h, + totalAllocated: A, + baseValue: D, + rootCap: H, + maxAllocation: L, + allowsEducation: X, + allowsPursuits: J, + allowsSpecial: N + }), l += h.education, r += h.pursuits, c += h.special; + } + const u = n.pursuits.reduce((p, h) => p + h.years, 0); + return { + state: n, + education: { + total: a.educationTotal, + spent: l, + remaining: a.educationTotal - l + }, + pursuits: { + total: a.pursuitsTotal, + spent: r, + remaining: a.pursuitsTotal - r + }, + special: { + total: a.specialTotal, + spent: c, + remaining: a.specialTotal - c + }, + pursuitYears: { + total: a.pursuitYearsTotal, + spent: u, + remaining: a.pursuitYearsTotal - u + }, + pursuitOptions: o, + pursuitAllowedSkills: s, + bySkillId: i + }; +} +function en(e, t) { + var n, o, s, i, l, r, c; + if (e.type !== "armor" || !((n = e.system) != null && n.equipped) || (o = e.system) != null && o.stowed || !((i = (s = e.system) == null ? void 0 : s.coverage) != null && i[t])) return 0; + const a = C((r = (l = e.system) == null ? void 0 : l.perLocation) == null ? void 0 : r[t], 0); + return a > 0 ? a : C((c = e.system) == null ? void 0 : c.value, 0); +} +function tn(e, t) { + var o, s, i; + const a = C((i = (s = (o = e.system) == null ? void 0 : o.hitLocations) == null ? void 0 : s[t]) == null ? void 0 : i.armor, 0), n = e.items.filter((l) => l.type === "armor").reduce((l, r) => l + en(r, t), 0); + return a + n; +} +function et(e, t, a) { + var l, r; + const n = se(e).entries.find((c) => c.slug === t), o = ((r = (l = e.system) == null ? void 0 : l.hitLocations) == null ? void 0 : r[t]) ?? {}, s = o.hp ?? {}, i = String(o.label ?? "").trim(); + return { + slug: t, + rollRange: a, + label: i || String((n == null ? void 0 : n.label) || t), + armor: C(o.armor, 0), + totalArmor: tn(e, t), + hp: { + value: C(s.value, 0), + max: C(s.max, 0) + } + }; +} +function tt(e) { + return e[Math.floor(Math.random() * e.length)]; +} +function an(e) { + return Math.max(0, Math.min(99, Math.floor(e))); +} +class nn extends ja { + isToolsUnlocked() { + var t, a; + return !!((a = (t = this.actor) == null ? void 0 : t.system) != null && a.sheetToolsUnlocked); + } + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "actor", "explorer"], + width: 930, + height: 1460, + tabs: [{ navSelector: ".tabs", contentSelector: ".sheet-body", initial: "overview" }] + }); + } + get template() { + return "systems/ringworld/templates/actor/explorer-sheet.hbs"; + } + async getData(t) { + var N, le, ce, ue, me, de, ge, pe, ee, Ve, Ge, je, _e, We, Ye, ze; + const a = await super.getData(t), n = this.actor; + (N = game.user) != null && N.isGM && n.isOwner && await ((le = n.ensureBaseSkills) == null ? void 0 : le.call(n)); + const o = n.items.filter((k) => k.type === "skill").sort(fe), s = Z(n, o), i = n.items.filter((k) => k.type === "weapon").sort(fe), l = n.items.filter((k) => k.type === "armor").sort(fe), r = n.items.filter((k) => k.type === "gear").sort(fe), c = game.items.filter((k) => k.type === "species" && k.system.playable).map((k) => k.name).sort(), u = Yt(), d = /* @__PURE__ */ new Map(); + for (const k of o) { + const O = R(k.name); + d.has(O) || d.set(O, k); + } + const g = n.system.skillRootMax ?? {}, m = Wt.map((k) => { + const O = Fe[k] ?? k.toLowerCase(), Te = we.filter((V) => V.category === k).map((V) => { + var Qe; + const U = d.get(R(V.name)), te = U ? s.bySkillId.get(U.id) : null; + return { + name: V.name, + base: V.base, + category: V.category, + specialization: V.specialization ?? "", + itemId: (U == null ? void 0 : U.id) ?? null, + icon: (U == null ? void 0 : U.img) || ye(V.name), + value: ((Qe = U == null ? void 0 : U.system) == null ? void 0 : Qe.value) ?? V.base, + missing: !U, + allocationTotal: (te == null ? void 0 : te.totalAllocated) ?? 0, + allocation: (te == null ? void 0 : te.allocation) ?? { education: 0, pursuits: 0, special: 0 } + }; + }); + return { + category: k, + rootKey: O, + rootFormula: _a[k] ?? "", + rootValue: C(g[O], 0), + rows: Te + }; + }), f = o.filter((k) => !u.has(R(k.name))).map((k) => { + const O = s.bySkillId.get(k.id); + return { + id: k.id, + name: k.name, + system: k.system, + img: Oe(k.name, k.img || W), + allocationTotal: (O == null ? void 0 : O.totalAllocated) ?? 0, + allocation: (O == null ? void 0 : O.allocation) ?? { education: 0, pursuits: 0, special: 0 } + }; + }), y = m.reduce( + (k, O) => k + O.rows.filter((Te) => Te.missing).length, + 0 + ), b = se(n), w = new Map(b.entries.map((k) => [k.slug, `${k.min}-${k.max}`])), h = b.entries.map((k) => k.slug).map( + (k) => et(n, k, w.get(k) ?? "") + ), S = b.bodyOrder.map( + (k) => et(n, k, w.get(k) ?? "") + ), I = ((ce = n.system) == null ? void 0 : ce.encumbrance) ?? {}, M = C(I.rawLoadKg, 0), D = C(I.effectiveLoadKg, M), T = C(I.limitKg, C((me = (ue = n.system) == null ? void 0 : ue.characteristics) == null ? void 0 : me.str, 0)), H = C(I.actionRankingPenalty, 0), L = C((ge = (de = n.system) == null ? void 0 : de.derived) == null ? void 0 : ge.actionRanking, 0), F = Math.max(1, L + H), X = `${M.toFixed(1)}kg on-person | ${D.toFixed(1)}kg effective / ${T.toFixed(1)}kg limit | AR +${H} (stowed ignored)`, J = `${C((ee = (pe = n.system) == null ? void 0 : pe.derived) == null ? void 0 : ee.currentMove, C((Ge = (Ve = n.system) == null ? void 0 : Ve.derived) == null ? void 0 : Ge.move, 3))}m / ${C((_e = (je = n.system) == null ? void 0 : je.derived) == null ? void 0 : _e.currentRun, C((Ye = (We = n.system) == null ? void 0 : We.derived) == null ? void 0 : Ye.run, 6))}m`; + return { + ...a, + system: n.system, + skills: o, + skillsByCategory: m, + customSkills: f, + missingBaseSkillsCount: y, + weapons: i, + armors: l, + gear: r, + speciesOptions: c, + hitLocations: h, + bodyLocations: S, + hitLocationSchemaOptions: Kt(), + currentHitLocationSchema: String(n.system.hitLocationSchema || "auto"), + activeHitLocationSchemaId: b.id, + activeHitLocationSchemaLabel: b.label, + sheetToolsUnlocked: !!((ze = n.system) != null && ze.sheetToolsUnlocked), + baseActionRanking: L, + effectiveActionRanking: F, + encumbranceSummary: X, + movementSummary: J, + builder: { + education: s.education, + pursuits: s.pursuits, + special: s.special, + pursuitYears: s.pursuitYears, + pursuitsList: s.state.pursuits, + pursuitOptions: s.pursuitOptions + } + }; + } + async updateBuilderState(t) { + await this.actor.update({ + "system.characterBuilder": { + pursuits: t.pursuits, + allocations: t.allocations, + baseBySkillId: t.baseBySkillId + } + }); + } + async syncSkillsFromBuilderState(t) { + var o, s, i, l, r, c; + const a = this.actor.items.filter((u) => u.type === "skill"), n = []; + for (const u of a) { + const d = Pe(t.allocations[u.id]), g = d.education + d.pursuits + d.special, m = Fe[String(((o = u.system) == null ? void 0 : o.category) || "")] ?? "", f = m ? Math.max(0, B((i = (s = this.actor.system) == null ? void 0 : s.skillRootMax) == null ? void 0 : i[m], 0)) : 99; + let y = t.baseBySkillId[u.id]; + if (!Number.isFinite(y)) { + const w = (l = $e(u.name)) == null ? void 0 : l.base; + y = Math.max(0, w ?? C((r = u.system) == null ? void 0 : r.value, 0) - g), t.baseBySkillId[u.id] = y; + } + const b = Math.max(0, Math.min(99, Math.min(f, y + g))); + C((c = u.system) == null ? void 0 : c.value, 0) !== b && n.push({ _id: u.id, "system.value": b }); + } + n.length && await this.actor.updateEmbeddedDocuments("Item", n); + } + async addOrUpdatePursuit(t, a) { + var c; + if (!this.isToolsUnlocked()) return; + const n = String(t || "").trim(), o = Math.max(0, Math.floor(Number(a) || 0)); + if (!n || o <= 0) { + (c = ui.notifications) == null || c.warn("Choose a pursuit and set years above 0."); + return; + } + const s = this.actor.items.filter((u) => u.type === "skill"), i = Z(this.actor, s), l = foundry.utils.deepClone(i.state), r = l.pursuits.findIndex((u) => R(u.name) === R(n)); + r >= 0 ? l.pursuits[r].years = o : l.pursuits.push({ name: n, years: o }), await this.updateBuilderState(l); + } + async removePursuit(t) { + if (!this.isToolsUnlocked()) return; + const a = this.actor.items.filter((s) => s.type === "skill"), n = Z(this.actor, a), o = foundry.utils.deepClone(n.state); + t < 0 || t >= o.pursuits.length || (o.pursuits.splice(t, 1), await this.updateBuilderState(o)); + } + async resetBuilderAllocations() { + if (!this.isToolsUnlocked()) return; + const t = this.actor.items.filter((o) => o.type === "skill"), a = Z(this.actor, t), n = foundry.utils.deepClone(a.state); + n.allocations = {}, await this.updateBuilderState(n), await this.syncSkillsFromBuilderState(n); + } + async randomizeBuilderAllocations() { + var f, y, b, w; + if (!this.isToolsUnlocked()) return; + await ((y = (f = this.actor).ensureBaseSkills) == null ? void 0 : y.call(f)); + const t = this.actor.items.filter((p) => p.type === "skill").sort(fe), a = Z(this.actor, t), n = foundry.utils.deepClone(a.state), o = Math.max(0, a.pursuitYears.total), s = he.map((p) => p.name); + if (n.pursuits = [], o > 0 && s.length > 0) { + const p = Math.min( + s.length, + Math.max(1, Math.min(3, Math.floor(o / 2) || 1)) + ), h = [...s]; + for (let S = 0; S < p && h.length; S += 1) { + const I = Math.floor(Math.random() * h.length); + n.pursuits.push({ name: h[I], years: 1 }), h.splice(I, 1); + } + let A = o - n.pursuits.length; + for (; A > 0 && n.pursuits.length; ) { + const S = tt(n.pursuits); + S.years += 1, A -= 1; + } + } + const i = kt(n); + i.allocations = {}; + const l = Z( + { ...this.actor, system: { ...this.actor.system, characterBuilder: i } }, + t + ); + for (const p of t) { + const h = l.bySkillId.get(p.id); + h && (i.baseBySkillId[p.id] = h.baseValue, i.allocations[p.id] = { education: 0, pursuits: 0, special: 0 }); + } + const r = /* @__PURE__ */ new Map(); + for (const p of t) + r.set(p.id, 0); + const c = (p) => { + const h = l.bySkillId.get(p); + return h ? Math.max(0, h.maxAllocation) : 0; + }, u = (p, h, A) => { + let S = Math.max(0, p), I = 0; + for (; S > 0 && I < 3e5; ) { + I += 1; + const M = t.filter((H) => { + const L = l.bySkillId.get(H.id); + return !L || !A(H, L) ? !1 : (r.get(H.id) ?? 0) < c(H.id); + }); + if (!M.length) break; + const D = tt(M), T = i.allocations[D.id] ?? { education: 0, pursuits: 0, special: 0 }; + T[h] += 1, i.allocations[D.id] = T, r.set(D.id, (r.get(D.id) ?? 0) + 1), S -= 1; + } + return S; + }, d = u( + Math.max(0, l.education.total), + "education", + (p, h) => h.allowsEducation + ), g = u( + Math.max(0, l.pursuits.total), + "pursuits", + (p, h) => h.allowsPursuits + ), m = u( + Math.max(0, l.special.total), + "special", + (p, h) => h.allowsSpecial + ); + for (const p of t) { + const h = Pe(i.allocations[p.id]); + h.education + h.pursuits + h.special <= 0 && delete i.allocations[p.id]; + } + await this.updateBuilderState(i), await this.syncSkillsFromBuilderState(i), d > 0 || g > 0 || m > 0 ? (b = ui.notifications) == null || b.info( + `Random build complete. Unspent points: EDU ${d}, Pursuits ${g}, Special ${m}.` + ) : (w = ui.notifications) == null || w.info("Random build complete. All available points allocated."); + } + async promptSkillAllocation(t) { + var y, b, w; + if (!this.isToolsUnlocked()) return; + const a = this.actor.items.get(t); + if (!a || a.type !== "skill") return; + const n = this.actor.items.filter((p) => p.type === "skill"), o = Z(this.actor, n), s = o.bySkillId.get(t); + if (!s) return; + const i = s.allocation, l = Math.max(0, o.education.remaining + i.education), r = Math.max(0, o.pursuits.remaining + i.pursuits), c = Math.max(0, o.special.remaining + i.special), u = Math.max(0, s.maxAllocation), d = ` +
+
+ +
+

${a.name}

+

Base ${s.baseValue}% | Root cap ${s.rootCap}% | Current ${C((y = a.system) == null ? void 0 : y.value, 0)}%

+
+
+ + + + + + + +

Max additional from root cap: ${u}. Pools: EDU ${o.education.remaining}, Pursuits ${o.pursuits.remaining}, Special ${o.special.remaining}.

+
+ `, g = await new Promise((p) => { + new Dialog({ + title: `${a.name} - Allocate Character Points`, + content: d, + buttons: { + save: { + label: "Apply", + callback: (h) => { + const A = h.find("form")[0]; + if (!A) { + p(null); + return; + } + const S = new FormData(A); + p({ + education: s.allowsEducation ? B(S.get("education"), i.education) : i.education, + pursuits: s.allowsPursuits ? B(S.get("pursuits"), i.pursuits) : i.pursuits, + special: s.allowsSpecial ? B(S.get("special"), i.special) : i.special + }); + } + }, + cancel: { + label: "Cancel", + callback: () => p(null) + } + }, + default: "save", + close: () => p(null) + }).render(!0); + }); + if (!g) return; + const m = g.education + g.pursuits + g.special; + if (m > u) { + (b = ui.notifications) == null || b.warn(`Allocation exceeds this skill's root-cap allowance (${u}).`); + return; + } + if (g.education > l || g.pursuits > r || g.special > c) { + (w = ui.notifications) == null || w.warn("Allocation exceeds remaining pool points."); + return; + } + const f = foundry.utils.deepClone(o.state); + f.baseBySkillId[a.id] = s.baseValue, m <= 0 ? delete f.allocations[a.id] : f.allocations[a.id] = g, await this.updateBuilderState(f), await this.syncSkillsFromBuilderState(f); + } + async rollCharacteristicCheck(t, a) { + var c, u; + if (!(/* @__PURE__ */ new Set(["str", "mas", "con", "int", "pow", "dex", "app", "edu", "age"])).has(t)) return; + const o = Math.max(0, B((u = (c = this.actor.system) == null ? void 0 : c.characteristics) == null ? void 0 : u[t], 0)), s = ` +
+
+ + +
+
+ + +
+
+ + +
+

Roll d100 against Characteristic x Multiplier.

+
+ `, i = await new Promise((d) => { + let g = !1; + const m = (f) => { + g || (g = !0, d(f)); + }; + new Dialog({ + title: `${a} Check`, + content: s, + buttons: { + roll: { + label: "Roll", + callback: (f) => { + var w, p; + const y = Number(((p = (w = f[0]) == null ? void 0 : w.querySelector("select[name='multiplier']")) == null ? void 0 : p.value) || 1), b = Math.max(1, Math.min(7, Math.floor(y || 1))); + m(b); + } + }, + cancel: { + label: "Cancel", + callback: () => m(null) + } + }, + default: "roll", + close: () => m(null) + }).render(!0); + }); + if (!i) return; + const l = an(o * i), r = await Ue(l); + await mt({ + actor: this.actor, + actorName: this.actor.name, + actorImg: this.actor.img, + skillName: `${a} x${i}`, + skillImg: W, + roll: r.roll, + target: r.target, + outcomeLabel: Ae(r.outcome), + isSuccess: r.isSuccess, + isSpecial: r.isSpecial, + isFumble: r.isFumble + }); + } + activateListeners(t) { + super.activateListeners(t), t.find(".toggle-sheet-lock").on("click", async (a) => { + var o; + a.preventDefault(); + const n = !this.isToolsUnlocked(); + await this.actor.update({ "system.sheetToolsUnlocked": n }), (o = ui.notifications) == null || o.info(n ? "Sheet tools unlocked." : "Sheet tools locked."); + }), t.find(".roll-characteristic").on("click", async (a) => { + a.preventDefault(); + const n = a.currentTarget, o = String(n.dataset.char || "").trim().toLowerCase(), s = String(n.dataset.label || o.toUpperCase()); + o && await this.rollCharacteristicCheck(o, s); + }), t.find(".item-create").on("click", async (a) => { + a.preventDefault(); + const n = String(a.currentTarget.dataset.type || "gear"), o = { name: `New ${n}`, type: n }; + n === "skill" && (o.img = W, o.system = { value: 0, category: "Technical", specialization: "" }), await this.actor.createEmbeddedDocuments("Item", [o]); + }), t.find(".item-edit").on("click", (a) => { + var s; + a.preventDefault(); + const n = a.currentTarget.closest(".item"), o = n ? this.actor.items.get(n.dataset.itemId ?? "") : null; + (s = o == null ? void 0 : o.sheet) == null || s.render(!0); + }), t.find(".item-delete").on("click", async (a) => { + a.preventDefault(); + const n = a.currentTarget.closest(".item"); + n != null && n.dataset.itemId && await this.actor.deleteEmbeddedDocuments("Item", [n.dataset.itemId]); + }), t.find(".item-roll-skill").on("click", async (a) => { + a.preventDefault(); + const n = a.currentTarget.closest(".item"); + n != null && n.dataset.itemId && await this.actor.rollSkillById(n.dataset.itemId); + }), t.find(".roll-derived").on("click", async (a) => { + a.preventDefault(); + const n = String(a.currentTarget.dataset.check || ""); + n && await this.actor.rollDerivedCheck(n); + }), t.find(".item-use-weapon").on("click", async (a) => { + a.preventDefault(); + const n = a.currentTarget.closest(".item"); + n != null && n.dataset.itemId && await this.actor.useWeaponById(n.dataset.itemId); + }), t.find(".item-toggle-equipped").on("click", async (a) => { + var l; + a.preventDefault(); + const n = a.currentTarget.closest(".item"); + if (!(n != null && n.dataset.itemId)) return; + const o = this.actor.items.get(n.dataset.itemId); + if (!o) return; + const s = !((l = o.system) != null && l.equipped), i = { + _id: o.id, + "system.equipped": s + }; + s && (i["system.stowed"] = !1), await this.actor.updateEmbeddedDocuments("Item", [i]); + }), t.find(".item-toggle-stowed").on("click", async (a) => { + var l; + a.preventDefault(); + const n = a.currentTarget.closest(".item"); + if (!(n != null && n.dataset.itemId)) return; + const o = this.actor.items.get(n.dataset.itemId); + if (!o) return; + const s = !((l = o.system) != null && l.stowed), i = { + _id: o.id, + "system.stowed": s + }; + s && (i["system.equipped"] = !1), await this.actor.updateEmbeddedDocuments("Item", [i]); + }), t.find(".roll-hit-location").on("click", async (a) => { + a.preventDefault(), await this.actor.rollHitLocation(); + }), t.find(".reset-conditions").on("click", async (a) => { + a.preventDefault(), await this.actor.update({ + "system.conditions.prone": !1, + "system.conditions.unconscious": !1, + "system.conditions.dead": !1, + "system.conditions.dying": !1, + "system.conditions.unusableLimbs.rArm": !1, + "system.conditions.unusableLimbs.lArm": !1, + "system.conditions.unusableLimbs.rLeg": !1, + "system.conditions.unusableLimbs.lLeg": !1 + }); + }), t.find(".populate-base-skills").on("click", async (a) => { + var o, s; + a.preventDefault(); + const n = await this.actor.ensureBaseSkills(); + n > 0 ? (o = ui.notifications) == null || o.info(`Added ${n} Ringworld base skills.`) : (s = ui.notifications) == null || s.info("All Ringworld base skills are already present."); + }), t.find(".add-base-skill").on("click", async (a) => { + a.preventDefault(); + const n = a.currentTarget, o = String(n.dataset.skillName || ""), s = String(n.dataset.skillCategory || ""), i = Number(n.dataset.skillBase || 0), l = String(n.dataset.skillSpecialization || ""); + o && await this.actor.createEmbeddedDocuments("Item", [{ + name: o, + type: "skill", + img: ye(o), + system: { value: i, category: s, specialization: l } + }]); + }), t.find(".builder-add-pursuit").on("click", async (a) => { + var i, l; + a.preventDefault(); + const n = a.currentTarget.closest(".rw-builder-pursuits"); + if (!n) return; + const o = String(((i = n.querySelector(".builder-new-pursuit")) == null ? void 0 : i.value) || ""), s = Number(((l = n.querySelector(".builder-new-years")) == null ? void 0 : l.value) || 0); + await this.addOrUpdatePursuit(o, s); + }), t.find(".builder-update-pursuit").on("click", async (a) => { + var i, l; + a.preventDefault(); + const n = a.currentTarget.closest(".builder-pursuit-row"); + if (!n) return; + const o = String(((i = n.querySelector(".builder-pursuit-name")) == null ? void 0 : i.value) || ""), s = Number(((l = n.querySelector(".builder-pursuit-years")) == null ? void 0 : l.value) || 0); + await this.addOrUpdatePursuit(o, s); + }), t.find(".builder-remove-pursuit").on("click", async (a) => { + a.preventDefault(); + const n = Number(a.currentTarget.dataset.index ?? -1); + !Number.isFinite(n) || n < 0 || await this.removePursuit(n); + }), t.find(".builder-reset-points").on("click", async (a) => { + var n; + a.preventDefault(), await this.resetBuilderAllocations(), (n = ui.notifications) == null || n.info("Character-builder allocations reset."); + }), t.find(".roll-characteristics").on("click", async (a) => { + var n, o, s; + a.preventDefault(), this.isToolsUnlocked() && (await ((o = (n = this.actor).rollCharacteristics) == null ? void 0 : o.call(n)), (s = ui.notifications) == null || s.info("Characteristics rolled.")); + }), t.find(".builder-auto-allocate").on("click", async (a) => { + var n, o; + a.preventDefault(), this.isToolsUnlocked() && (await ((o = (n = this.actor).ensureBaseSkills) == null ? void 0 : o.call(n)), await this.randomizeBuilderAllocations()); + }), t.find(".builder-skill-allocate").on("click", async (a) => { + a.preventDefault(); + const n = a.currentTarget.closest(".item"), o = String((n == null ? void 0 : n.dataset.itemId) || ""); + o && await this.promptSkillAllocation(o); + }); + } + async _onDropItemCreate(t) { + var i, l, r; + const a = Array.isArray(t) ? t : [t], n = a.filter((c) => (c == null ? void 0 : c.type) === "species"), o = a.filter((c) => (c == null ? void 0 : c.type) !== "species"); + for (const c of n) + await ((l = (i = this.actor).applySpeciesItem) == null ? void 0 : l.call(i, c)); + if (n.length > 0) { + const c = n.map((u) => String((u == null ? void 0 : u.name) || "Species")).join(", "); + (r = ui.notifications) == null || r.info(`Applied species to actor: ${c}`); + } + if (o.length === 0) return []; + const s = o.map((c) => (c == null ? void 0 : c.type) !== "skill" ? c : { + ...c, + img: Oe(String((c == null ? void 0 : c.name) || "Skill"), String((c == null ? void 0 : c.img) || "")) + }); + return super._onDropItemCreate(Array.isArray(t) ? s : s[0]); + } +} +const on = foundry.appv1.sheets.ActorSheet; +class sn extends on { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "actor", "creature"], + width: 760, + height: 700 + }); + } + get template() { + return "systems/ringworld/templates/actor/creature-sheet.hbs"; + } + async getData(t) { + const a = await super.getData(t), n = this.actor; + return { + ...a, + system: n.system, + skills: n.items.filter((o) => o.type === "skill"), + weapons: n.items.filter((o) => o.type === "weapon"), + hitLocations: se(n).entries.map((o) => { + var s; + return { + slug: o.slug, + rollRange: `${o.min}-${o.max}`, + label: o.label, + ...((s = n.system.hitLocations) == null ? void 0 : s[o.slug]) ?? {} + }; + }) + }; + } + activateListeners(t) { + super.activateListeners(t), t.find(".item-create").on("click", async (a) => { + a.preventDefault(); + const n = String(a.currentTarget.dataset.type || "skill"); + await this.actor.createEmbeddedDocuments("Item", [{ name: `New ${n}`, type: n }]); + }), t.find(".item-edit").on("click", (a) => { + var s; + const n = a.currentTarget.closest(".item"), o = n ? this.actor.items.get(n.dataset.itemId ?? "") : null; + (s = o == null ? void 0 : o.sheet) == null || s.render(!0); + }), t.find(".item-delete").on("click", async (a) => { + const n = a.currentTarget.closest(".item"); + n != null && n.dataset.itemId && await this.actor.deleteEmbeddedDocuments("Item", [n.dataset.itemId]); + }), t.find(".item-roll-skill").on("click", async (a) => { + const n = a.currentTarget.closest(".item"); + n != null && n.dataset.itemId && await this.actor.rollSkillById(n.dataset.itemId); + }), t.find(".item-use-weapon").on("click", async (a) => { + const n = a.currentTarget.closest(".item"); + n != null && n.dataset.itemId && await this.actor.useWeaponById(n.dataset.itemId); + }), t.find(".roll-hit-location").on("click", async () => { + await this.actor.rollHitLocation(); + }); + } +} +const rn = foundry.appv1.sheets.ActorSheet; +class ln extends rn { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "actor", "vehicle"], + width: 680, + height: 560 + }); + } + get template() { + return "systems/ringworld/templates/actor/vehicle-sheet.hbs"; + } + async getData(t) { + return { + ...await super.getData(t), + system: this.actor.system + }; + } +} +const cn = foundry.appv1.sheets.ItemSheet; +class un extends cn { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "skill"], + width: 520, + height: 420 + }); + } + get template() { + return "systems/ringworld/templates/item/skill-sheet.hbs"; + } + async getData(t) { + return { + ...await super.getData(t), + system: this.item.system + }; + } + activateListeners(t) { + super.activateListeners(t), t.find(".roll-skill").on("click", async (a) => { + a.preventDefault(), await this.item.rollSkill(); + }); + } +} +const mn = foundry.appv1.sheets.ItemSheet; +class dn extends mn { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "weapon"], + width: 620, + height: 620 + }); + } + get template() { + return "systems/ringworld/templates/item/weapon-sheet.hbs"; + } + async getData(t) { + const a = await super.getData(t), n = this.item.actor; + return { + ...a, + system: this.item.system, + skills: n ? n.items.filter((o) => o.type === "skill") : [], + powerPacks: n ? n.items.filter((o) => o.type === "gear" && o.system.isPowerPack) : [] + }; + } + activateListeners(t) { + super.activateListeners(t), t.find(".use-weapon").on("click", async (a) => { + a.preventDefault(), await this.item.useWeapon(); + }); + } +} +const gn = foundry.appv1.sheets.ItemSheet; +class pn extends gn { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "armor"], + width: 620, + height: 560 + }); + } + get template() { + return "systems/ringworld/templates/item/armor-sheet.hbs"; + } + async getData(t) { + return { + ...await super.getData(t), + system: this.item.system + }; + } +} +const fn = foundry.appv1.sheets.ItemSheet; +class yn extends fn { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "gear"], + width: 620, + height: 520 + }); + } + get template() { + return "systems/ringworld/templates/item/gear-sheet.hbs"; + } + async getData(t) { + return { + ...await super.getData(t), + system: this.item.system + }; + } +} +const hn = foundry.appv1.sheets.ItemSheet; +class wn extends hn { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "species"], + width: 620, + height: 560 + }); + } + get template() { + return "systems/ringworld/templates/item/species-sheet.hbs"; + } + async getData(t) { + return { + ...await super.getData(t), + system: this.item.system + }; + } +} +const bn = foundry.appv1.sheets.ItemSheet; +function at(e) { + return Array.isArray(e) ? e.map((t) => String(t)) : []; +} +class kn extends bn { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "pursuit"], + width: 680, + height: 620 + }); + } + get template() { + return "systems/ringworld/templates/item/pursuit-sheet.hbs"; + } + async getData(t) { + const a = await super.getData(t), n = this.item.system ?? {}, o = at(n.fixedSkills), s = Array.isArray(n.optionalSkillGroups) ? n.optionalSkillGroups.map((i) => ({ + choose: Number((i == null ? void 0 : i.choose) ?? 1), + skills: at(i == null ? void 0 : i.skills) + })) : []; + return { + ...a, + system: n, + fixedSkills: o, + optionalGroups: s + }; + } +} +const vt = "impulseState", qe = `${x}.impulseHudPos`, vn = [ + { value: "draw", label: "Draw / Stow Weapon" }, + { value: "aim", label: "Aim Ranged Weapon" }, + { value: "melee", label: "Melee Attack" }, + { value: "stand", label: "Stand / Recover" }, + { value: "perception", label: "Perception Skill" }, + { value: "unarmed", label: "Unarmed Combat" } +], Sn = [ + { value: "fire", label: "Fire Ranged Weapon" }, + { value: "fall", label: "Fall" }, + { value: "rise", label: "Rise (Kneeling to Stand/Run)" }, + { value: "minorCustom", label: "Other Minor Action" } +]; +function q(e, t = 0) { + const a = Number(e); + return Number.isFinite(a) ? a : t; +} +function Y(e) { + return String(e ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """); +} +function Ke(e) { + return e <= 4 ? 7 : e <= 8 ? 6 : e <= 12 ? 5 : e <= 16 ? 4 : e <= 20 ? 3 : e <= 24 ? 2 : 1; +} +function Re(e) { + switch (e) { + case "draw": + return "Draw / Stow"; + case "aim": + return "Aim"; + case "melee": + return "Melee Attack"; + case "stand": + return "Stand / Recover"; + case "perception": + return "Perception"; + case "unarmed": + return "Unarmed Combat"; + case "fire": + return "Fire Ranged Weapon"; + case "fall": + return "Fall"; + case "rise": + return "Rise"; + case "minorCustom": + return "Minor Action"; + default: + return String(e); + } +} +function z(e) { + var t, a, n; + return Math.max(1, q((n = (a = (t = e == null ? void 0 : e.actor) == null ? void 0 : t.system) == null ? void 0 : a.characteristics) == null ? void 0 : n.dex, 10)); +} +function St(e) { + var a, n; + const t = q((n = (a = e == null ? void 0 : e.flags) == null ? void 0 : a[x]) == null ? void 0 : n.actionRanking, 0); + return t > 0 ? t : Ke(z(e)); +} +function At(e) { + var a, n, o; + const t = q((o = (n = (a = e == null ? void 0 : e.actor) == null ? void 0 : a.system) == null ? void 0 : n.encumbrance) == null ? void 0 : o.actionRankingPenalty, 0); + return Math.max(0, Math.trunc(t)); +} +async function xt(e) { + var n, o; + const t = Ke(z(e)); + return q((o = (n = e == null ? void 0 : e.flags) == null ? void 0 : n[x]) == null ? void 0 : o.actionRanking, 0) !== t && await e.update({ [`flags.${x}.actionRanking`]: t }), t; +} +function ie(e) { + const t = foundry.utils.deepClone(e.getFlag(x, vt) ?? {}), a = Array.isArray(t.queue) ? t.queue : []; + return { + currentImpulse: Math.max(1, q(t.currentImpulse, 1)), + queue: a + }; +} +async function re(e, t) { + await e.setFlag(x, vt, { + currentImpulse: Math.max(1, q(t.currentImpulse, 1)), + queue: Array.isArray(t.queue) ? t.queue : [] + }); +} +function ae(e, t) { + return [...e].sort((a, n) => { + if (a.completeImpulse !== n.completeImpulse) return a.completeImpulse - n.completeImpulse; + const o = z(t.combatants.get(a.combatantId)); + return z(t.combatants.get(n.combatantId)) - o; + }); +} +function ke(e, t) { + const a = new Set(t.combatants.map((n) => n.id)); + return e.filter((n) => a.has(n.combatantId)); +} +function An() { + try { + const e = localStorage.getItem(qe); + if (!e) return null; + const t = JSON.parse(e), a = q(t == null ? void 0 : t.left, NaN), n = q(t == null ? void 0 : t.top, NaN); + return !Number.isFinite(a) || !Number.isFinite(n) ? null : { left: a, top: n }; + } catch { + return null; + } +} +function xn(e) { + try { + localStorage.setItem( + qe, + JSON.stringify({ left: Math.round(e.left), top: Math.round(e.top) }) + ); + } catch { + } +} +function Pn() { + try { + localStorage.removeItem(qe); + } catch { + } +} +function In(e, t) { + const n = t.offsetWidth || 300, o = t.offsetHeight || 220, s = Math.max(8, window.innerWidth - n - 8), i = Math.max(8, window.innerHeight - o - 8); + return { + left: Math.min(Math.max(8, e.left), s), + top: Math.min(Math.max(8, e.top), i) + }; +} +function nt(e, t) { + const a = e.get(0); + if (!a) return; + const n = In(t, a); + e.css({ + left: `${n.left}px`, + top: `${n.top}px`, + right: "auto", + bottom: "auto" + }); +} +function Ln(e) { + const t = e.get(0); + if (!t) return; + const a = e.find(".rw-imp-mini-head"); + if (!a.length) return; + const n = An(); + n && nt(e, n); + let o = !1, s = 0, i = 0, l = 0, r = 0; + a.on("mousedown", (c) => { + var g; + const u = c; + if (u.button !== 0 || (g = u.target) != null && g.closest("button")) return; + u.preventDefault(); + const d = t.getBoundingClientRect(); + s = u.clientX, i = u.clientY, l = d.left, r = d.top, o = !0, e.addClass("dragging"), e.css({ left: `${l}px`, top: `${r}px`, right: "auto", bottom: "auto" }), $(document).on("mousemove.ringworldImpulseHudDrag", (m) => { + if (!o) return; + const f = m, y = f.clientX - s, b = f.clientY - i; + nt(e, { left: l + y, top: r + b }); + }), $(document).one("mouseup.ringworldImpulseHudDrag", () => { + o = !1, e.removeClass("dragging"), $(document).off("mousemove.ringworldImpulseHudDrag"); + const m = t.getBoundingClientRect(); + xn({ left: m.left, top: m.top }); + }); + }), a.on("dblclick", () => { + Pn(), e.css({ left: "", top: "", right: "", bottom: "" }); + }); +} +function Pt(e, t) { + return ae(ke(t, e), e).map((a) => { + const n = e.combatants.get(a.combatantId); + return { + combatantId: a.combatantId, + name: String((n == null ? void 0 : n.name) || "Unknown"), + label: Re(a.kind), + completeImpulse: a.completeImpulse, + dex: z(n) + }; + }); +} +function It(e, t = 8) { + return e.length ? e.slice(0, t).map((a) => ` +
+ ${Y(a.name)} + ${Y(a.label)} @ ${a.completeImpulse} +
+ `).join("") : '
No actions queued.
'; +} +function Lt(e, t) { + var n; + const a = !((n = game.user) != null && n.isGM); + e.find(".rw-mini-next").prop("disabled", a).on("click", async (o) => { + o.preventDefault(), await Ie(t, "next"); + }), e.find(".rw-mini-step").prop("disabled", a).on("click", async (o) => { + o.preventDefault(), await Ie(t, "step"); + }), e.find(".rw-mini-reset").prop("disabled", a).on("click", async (o) => { + o.preventDefault(), await Mt(t); + }); +} +function Mn(e) { + var s; + const t = (s = ui == null ? void 0 : ui.chat) == null ? void 0 : s.element; + if (!(t != null && t.length) || (t.find(".ringworld-impulse-chat-marker").remove(), !e)) return; + const a = ie(e), n = Pt(e, a.queue), o = $(` +
+
+ Impulse ${a.currentImpulse} + Queued ${n.length} +
+
${It(n, 5)}
+
+ + + +
+
+ `); + Lt(o, e), t.prepend(o); +} +function Cn(e) { + if ($("#ringworld-impulse-hud").remove(), !e) return; + const a = ie(e), n = Pt(e, a.queue), o = $(` +
+
+ Impulse ${a.currentImpulse} + Queued ${n.length} +
+
${It(n, 8)}
+
+ + + +
+
+ `); + Lt(o, e), $("body").append(o), Ln(o); +} +function E(e) { + Mn(e), Cn(e); +} +async function ve() { + var e, t; + return (e = game.user) != null && e.isGM ? !0 : ((t = ui.notifications) == null || t.warn("Only the GM can modify Ringworld impulse sequencing."), !1); +} +async function ot(e, t, a, n) { + var w, p, h, A; + if (!await ve()) return; + const o = e.combatants.get(t); + if (!o) return; + const s = ie(e), i = s.currentImpulse, l = await xt(o), r = At(o), c = q((p = (w = o.flags) == null ? void 0 : w[x]) == null ? void 0 : p.impulseModifier, 0), u = Math.max(1, l + r), g = Math.max(1, (n ? u : 1) + c), m = i + g, f = { + id: foundry.utils.randomID(), + actorId: ((h = o.actor) == null ? void 0 : h.id) ?? null, + combatantId: t, + kind: a, + major: n, + startImpulse: i, + completeImpulse: m + }, y = ke( + s.queue.filter((S) => S.combatantId !== t), + e + ); + y.push(f), await re(e, { + currentImpulse: i, + queue: ae(y, e) + }), await o.update({ + [`flags.${x}.pendingEventId`]: f.id, + [`flags.${x}.pendingKind`]: a, + [`flags.${x}.actionRanking`]: l, + [`flags.${x}.effectiveActionRanking`]: u + }); + const b = n ? `AR ${l} + Enc ${r}${c ? ` ${c > 0 ? "+" : ""} Imp ${c}` : ""}` : `Minor 1${c ? ` ${c > 0 ? "+" : ""} Imp ${c}` : ""}`; + (A = ui.notifications) == null || A.info( + `${o.name}: ${Re(a)} completes on impulse ${m} (${b} => ${g}).` + ), E(e); +} +async function $n(e, t) { + if (!await ve()) return; + const a = e.combatants.get(t); + if (!a) return; + const n = ie(e), o = ke( + n.queue.filter((s) => s.combatantId !== t), + e + ); + await re(e, { + currentImpulse: n.currentImpulse, + queue: ae(o, e) + }), await a.update({ + [`flags.${x}.pendingEventId`]: null, + [`flags.${x}.pendingKind`]: "", + [`flags.${x}.aimState`]: { + active: !1, + targetId: null, + sinceImpulse: null + } + }), E(e); +} +async function Rn(e, t, a) { + var i; + const n = e.combatants.get(t.combatantId); + if (!n) return; + const o = { + [`flags.${x}.pendingEventId`]: null, + [`flags.${x}.pendingKind`]: "" + }; + t.kind === "aim" && (o[`flags.${x}.aimState`] = { + active: !0, + targetId: t.targetTokenId ?? null, + sinceImpulse: a + }), await n.update(o); + const s = ` +
+

Impulse ${a} Completion

+

${Y(n.name)} completes ${Y(Re(t.kind))}.

+

DEX ${z(n)} | AR ${St(n)}

+
+ `; + await ChatMessage.create({ + user: (i = game.user) == null ? void 0 : i.id, + speaker: ChatMessage.getSpeaker({ actor: n.actor ?? void 0 }), + content: s + }); +} +async function Ie(e, t) { + var c; + if (!await ve()) return; + const a = ie(e), n = ae(ke(a.queue, e), e), o = a.currentImpulse; + let s = o + 1; + if (t === "next") { + const u = n.map((d) => d.completeImpulse).filter((d) => d >= o).sort((d, g) => d - g)[0]; + Number.isFinite(u) && (s = u); + } + const i = n.filter((u) => u.completeImpulse === s), l = n.filter((u) => u.completeImpulse !== s); + await re(e, { + currentImpulse: s, + queue: l + }); + const r = ae(i, e); + for (const u of r) + await Rn(e, u, s); + r.length || (c = ui.notifications) == null || c.info(`Impulse advanced to ${s}.`), E(e); +} +async function Mt(e) { + if (await ve()) { + await re(e, { currentImpulse: 1, queue: [] }); + for (const t of e.combatants) + await t.update({ + [`flags.${x}.pendingEventId`]: null, + [`flags.${x}.pendingKind`]: "", + [`flags.${x}.aimState`]: { + active: !1, + targetId: null, + sinceImpulse: null + }, + [`flags.${x}.actionRanking`]: Ke(z(t)), + [`flags.${x}.effectiveActionRanking`]: null, + [`flags.${x}.impulseModifier`]: 0 + }); + E(e); + } +} +function Tn(e, t) { + var u; + const a = "find" in t ? t : $(t); + if (!(a != null && a.length)) return; + a.addClass("ringworld-impulse-mode ringworld-impulse-only"); + const n = (e == null ? void 0 : e.viewed) ?? game.combat; + if (!n) return; + const o = ie(n), s = ae(ke(o.queue, n), n); + s.length !== o.queue.length && re(n, { currentImpulse: o.currentImpulse, queue: s }), a.find( + ".token-initiative, .combatant-initiative, .initiative, [data-action='rollInitiative'], [data-control='rollInitiative'], .combatant .fa-dice-d20" + ).addClass("rw-hide-initiative"), a.find(".ringworld-impulse-panel").remove(); + const i = (u = game.user) != null && u.isGM ? "" : "disabled", l = n.combatants.map((d) => { + var M, D, T, H; + const g = St(d), m = At(d), f = q((D = (M = d.flags) == null ? void 0 : M[x]) == null ? void 0 : D.impulseModifier, 0), y = Math.max(1, g + m), b = z(d), w = s.find((L) => L.combatantId === d.id), p = w ? `${Re(w.kind)} @ ${w.completeImpulse}` : "Ready", h = (H = (T = d.flags) == null ? void 0 : T[x]) == null ? void 0 : H.aimState, A = h != null && h.active ? `Aiming since ${h.sinceImpulse}` : "Not aiming", S = vn.map((L) => { + const F = w != null && w.major && (w == null ? void 0 : w.kind) === L.value ? "selected" : ""; + return ``; + }).join(""), I = Sn.map((L) => { + const F = !(w != null && w.major) && (w == null ? void 0 : w.kind) === L.value ? "selected" : ""; + return ``; + }).join(""); + return ` +
+
+ ${Y(d.name)} + DEX ${b} | AR ${g} + Enc ${m} = ${y} | Imp Mod ${f >= 0 ? `+${f}` : f} | ${Y(p)} | ${Y(A)} +
+
+ + + + + + + +
+
+ `; + }).join(""), r = $(` +
+
+
Impulse ${o.currentImpulse}
+
Queued: ${s.length}
+
+ + + +
+
+
${l}
+
+ `); + a.prepend(r); + const c = a.get(0); + if (c) + for (const d of Array.from(c.children)) + d.classList.contains("ringworld-impulse-panel") || d.classList.add("rw-hide-core-tracker"); + r.find(".rw-advance-next").on("click", async (d) => { + d.preventDefault(), await Ie(n, "next"); + }), r.find(".rw-advance-step").on("click", async (d) => { + d.preventDefault(), await Ie(n, "step"); + }), r.find(".rw-reset-sequence").on("click", async (d) => { + d.preventDefault(), await Mt(n); + }), r.find(".rw-major-start").on("click", async (d) => { + d.preventDefault(); + const g = d.currentTarget.closest(".rw-imp-row"); + if (!g) return; + const m = String(g.getAttribute("data-combatant-id") || ""), f = g.querySelector(".rw-major-kind"), y = String((f == null ? void 0 : f.value) || "draw"); + await ot(n, m, y, !0); + }), r.find(".rw-minor-start").on("click", async (d) => { + d.preventDefault(); + const g = d.currentTarget.closest(".rw-imp-row"); + if (!g) return; + const m = String(g.getAttribute("data-combatant-id") || ""), f = g.querySelector(".rw-minor-kind"), y = String((f == null ? void 0 : f.value) || "minorCustom"); + await ot(n, m, y, !1); + }), r.find(".rw-cancel-start").on("click", async (d) => { + d.preventDefault(); + const g = d.currentTarget.closest(".rw-imp-row"); + if (!g) return; + const m = String(g.getAttribute("data-combatant-id") || ""); + await $n(n, m); + }), r.find(".rw-save-mods").on("click", async (d) => { + if (d.preventDefault(), !await ve()) return; + const g = d.currentTarget.closest(".rw-imp-row"); + if (!g) return; + const m = String(g.getAttribute("data-combatant-id") || ""), f = n.combatants.get(m); + if (!f) return; + const y = g.querySelector(".rw-imp-mod"), b = Math.trunc(q(y == null ? void 0 : y.value, 0)); + await f.update({ + [`flags.${x}.impulseModifier`]: b + }); + }), E(n); +} +function Dn() { + Hooks.on("renderCombatTracker", (e, t) => { + Tn(e, t); + }), Hooks.on("createCombat", async (e) => { + await re(e, { currentImpulse: 1, queue: [] }), E(e); + }), Hooks.on("createCombatant", async (e) => { + await xt(e), E(game.combat ?? null); + }), Hooks.on("deleteCombatant", async () => { + E(game.combat ?? null); + }), Hooks.on("updateCombat", (e) => { + E(e ?? game.combat ?? null); + }), Hooks.on("deleteCombat", () => { + E(null); + }), Hooks.on("renderChatLog", () => { + E(game.combat ?? null); + }), Hooks.on("canvasReady", () => { + E(game.combat ?? null); + }), Hooks.on("updateActor", (e) => { + var n; + const t = game.combat; + if (!t) return; + t.combatants.some((o) => { + var s; + return ((s = o.actor) == null ? void 0 : s.id) === e.id; + }) && ((n = ui.combat) == null || n.render(!0), E(t)); + }), Hooks.on("updateItem", (e) => { + var o; + const t = e == null ? void 0 : e.parent; + if (!t) return; + const a = game.combat; + if (!a) return; + a.combatants.some((s) => { + var i; + return ((i = s.actor) == null ? void 0 : i.id) === t.id; + }) && ((o = ui.combat) == null || o.render(!0), E(a)); + }); +} +const st = 2; +function Ct(e) { + return String(e ?? "").trim().toLowerCase(); +} +function Hn() { + var t; + const e = /* @__PURE__ */ new Map(); + for (const a of game.items ?? []) { + if (a.type !== "weapon" && a.type !== "armor" && a.type !== "gear") continue; + const n = `${a.type}:${Ct(a.name)}`; + e.set(n, Number(((t = a.system) == null ? void 0 : t.weight) ?? 0) || 0); + } + return e; +} +async function Nn() { + var a, n, o, s; + if (!((a = game.user) != null && a.isGM)) return 0; + const e = Hn(); + let t = 0; + for (const i of game.actors ?? []) { + const l = []; + for (const r of i.items ?? []) { + if (r.type !== "weapon" && r.type !== "armor" && r.type !== "gear") continue; + const c = { _id: r.id }; + let u = !1; + if (((n = r.system) == null ? void 0 : n.equipped) === void 0 && (c["system.equipped"] = !0, u = !0), ((o = r.system) == null ? void 0 : o.weight) === void 0) { + const d = `${r.type}:${Ct(r.name)}`; + c["system.weight"] = Number(e.get(d) ?? 0), u = !0; + } + ((s = r.system) == null ? void 0 : s.stowed) === void 0 && (c["system.stowed"] = !1, u = !0), u && l.push(c); + } + l.length && (await i.updateEmbeddedDocuments("Item", l), t += l.length); + } + return t; +} +function Le(e, t, a, n) { + if (!e) return; + const o = "find" in e ? e : $(e); + if (!o.length || o.find(`.${t}`).length) return; + const s = $(``); + s.on("click", async () => { + await n(); + }); + const l = [ + o.find(".directory-footer"), + o.find("footer.directory-footer"), + o.find("footer"), + o.find(".footer-actions"), + o.find(".header-actions"), + o.find(".action-buttons") + ].find((c) => c.length); + if (l) { + l.first().append(s); + return; + } + let r = o.find(".ringworld-directory-controls"); + r.length || (r = $('
'), o.append(r)), r.first().append(s); +} +async function $t() { + await Be({ notify: !0 }); +} +async function Rt() { + var t; + const e = await bt(); + (t = e == null ? void 0 : e.sheet) == null || t.render(!0); +} +function ne() { + var a, n, o; + if (!((a = game.user) != null && a.isGM)) return; + const e = (n = ui.items) == null ? void 0 : n.element; + Le(e, "ringworld-seed-items", "Seed Ringworld Items", $t); + const t = (o = ui.actors) == null ? void 0 : o.element; + Le(t, "ringworld-random-actor", "Create Random Explorer", Rt); +} +function On(e) { + var o, s, i, l; + const t = (o = getDocumentClass("Item")) == null ? void 0 : o.TYPES; + if (Array.isArray(t)) return t.includes(e); + const a = (i = (s = game == null ? void 0 : game.system) == null ? void 0 : s.documentTypes) == null ? void 0 : i.Item; + if (Array.isArray(a)) return a.includes(e); + if (a && typeof a == "object") return Object.prototype.hasOwnProperty.call(a, e); + const n = (l = CONFIG == null ? void 0 : CONFIG.Item) == null ? void 0 : l.dataModels; + return !!(n && typeof n == "object" && Object.prototype.hasOwnProperty.call(n, e)); +} +Hooks.once("init", async () => { + var o, s, i, l, r, c, u, d; + console.log("ringworld | Initializing Ringworld system"), Dn(); + const e = (s = (o = foundry == null ? void 0 : foundry.documents) == null ? void 0 : o.collections) == null ? void 0 : s.Actors, t = (l = (i = foundry == null ? void 0 : foundry.documents) == null ? void 0 : i.collections) == null ? void 0 : l.Items, a = (c = (r = foundry == null ? void 0 : foundry.appv1) == null ? void 0 : r.sheets) == null ? void 0 : c.ActorSheet, n = (d = (u = foundry == null ? void 0 : foundry.appv1) == null ? void 0 : u.sheets) == null ? void 0 : d.ItemSheet; + if (CONFIG.ringworld = { + hitLocations: Ce + }, CONFIG.Combat.initiative = foundry.utils.mergeObject(CONFIG.Combat.initiative ?? {}, { + formula: "1d100", + decimals: 0 + }, { inplace: !1 }), CONFIG.Actor.documentClass = Va, CONFIG.Item.documentClass = Ga, !e || !t || !a || !n) { + console.error("ringworld | Foundry sheet registries were not available during init."); + return; + } + e.unregisterSheet("core", a), e.registerSheet("ringworld", nn, { + types: ["explorer"], + makeDefault: !0 + }), e.registerSheet("ringworld", sn, { + types: ["creature"], + makeDefault: !0 + }), e.registerSheet("ringworld", ln, { + types: ["vehicle"], + makeDefault: !0 + }), t.unregisterSheet("core", n), t.registerSheet("ringworld", un, { + types: ["skill"], + makeDefault: !0 + }), t.registerSheet("ringworld", dn, { + types: ["weapon"], + makeDefault: !0 + }), t.registerSheet("ringworld", pn, { + types: ["armor"], + makeDefault: !0 + }), t.registerSheet("ringworld", yn, { + types: ["gear"], + makeDefault: !0 + }), t.registerSheet("ringworld", wn, { + types: ["species"], + makeDefault: !0 + }), On("pursuit") && t.registerSheet("ringworld", kn, { + types: ["pursuit"], + makeDefault: !0 + }), game.settings.register("ringworld", "catalogSeedVersion", { + scope: "world", + config: !1, + type: Number, + default: 0 + }), game.settings.register("ringworld", "encumbranceDataMigrationVersion", { + scope: "world", + config: !1, + type: Number, + default: 0 + }), Handlebars.registerHelper("eq", (g, m) => g === m), Handlebars.registerHelper("or", (g, m) => !!(g || m)); +}); +Hooks.once("ready", async () => { + var t, a, n, o, s; + const e = "systems/ringworld/resources/hit-locations/humanoid.json"; + try { + const i = await fetch(e); + if (i.ok) { + const l = await i.json(); + CONFIG.ringworld.hitLocations = l.entries ?? Ce; + } + } catch { + console.warn(`ringworld | Could not load ${e}, using built-in table`); + } + if ((t = game.user) != null && t.isGM) { + for (const r of game.combats ?? []) { + const c = String(((n = (a = r == null ? void 0 : r.settings) == null ? void 0 : a.initiative) == null ? void 0 : n.formula) ?? "").trim(); + (!c || c.toLowerCase() === "undefined") && await r.update({ + "settings.initiative.formula": "1d100", + "settings.initiative.decimals": 0 + }); + } + if (Number(game.settings.get("ringworld", "catalogSeedVersion") ?? 0) < De && (await Be({ notify: !0 })).added >= 0 && await game.settings.set("ringworld", "catalogSeedVersion", De), Number(game.settings.get("ringworld", "encumbranceDataMigrationVersion") ?? 0) < st) { + const r = await Nn(); + await game.settings.set("ringworld", "encumbranceDataMigrationVersion", st), r > 0 && ((o = ui.notifications) == null || o.info(`Ringworld encumbrance migration updated ${r} actor items.`)); + } + } + ne(), window.setTimeout(() => ne(), 300), game.ringworld = { + ...game.ringworld ?? {}, + seedWorldCatalog: async (i) => Be(i), + createRandomExplorer: async () => bt(), + catalogVersion: De, + systemId: "ringworld", + systemVersion: String(((s = game.system) == null ? void 0 : s.version) ?? "0.0.0") + }, Hooks.callAll("ringworldReady", game.ringworld); +}); +Hooks.on("createActor", async (e, t, a) => { + var n, o; + if ((n = game.user) != null && n.isGM && a === game.user.id && !(!e || e.type !== "explorer" && e.type !== "creature")) + try { + const s = await ((o = e.ensureBaseSkills) == null ? void 0 : o.call(e)); + Number(s) > 0 && console.log(`ringworld | Added ${s} base skills to ${e.name}`); + } catch (s) { + console.error("ringworld | Failed to auto-populate base skills", s); + } +}); +Hooks.on("preCreateItem", (e) => { + if (e.type !== "skill") return; + const t = {}, a = String(e.name || "Skill"), n = String(e.img || ""); + t.img = Oe(a, n); + const o = $e(a), s = e.system ?? {}, i = Number((s == null ? void 0 : s.value) ?? NaN), l = String((s == null ? void 0 : s.category) ?? "").trim(); + t.system = { + value: Number.isFinite(i) ? i : (o == null ? void 0 : o.base) ?? 0, + category: l || ((o == null ? void 0 : o.category) ?? "Technical"), + specialization: String((s == null ? void 0 : s.specialization) ?? (o == null ? void 0 : o.specialization) ?? "") + }, t.img || (t.img = W), e.updateSource(t); +}); +Hooks.on("renderItemDirectory", (e, t) => { + var a; + (a = game.user) != null && a.isGM && (Le(t, "ringworld-seed-items", "Seed Ringworld Items", $t), ne()); +}); +Hooks.on("renderActorDirectory", (e, t) => { + var a; + (a = game.user) != null && a.isGM && (Le(t, "ringworld-random-actor", "Create Random Explorer", Rt), ne()); +}); +Hooks.on("renderSidebar", () => { + ne(); +}); +Hooks.on("changeSidebarTab", (e) => { + var a; + const t = String((e == null ? void 0 : e.tabName) ?? (e == null ? void 0 : e.id) ?? ((a = e == null ? void 0 : e.options) == null ? void 0 : a.id) ?? ""); + (t === "items" || t === "actors") && ne(); +}); +//# sourceMappingURL=ringworld.mjs.map diff --git a/module/ringworld.mjs.map b/module/ringworld.mjs.map new file mode 100644 index 0000000..858c679 --- /dev/null +++ b/module/ringworld.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ringworld.mjs","sources":["../src/module/utils/dice.ts","../src/module/utils/percentile.ts","../src/module/constants.ts","../src/module/hit-location-schemas.ts","../src/module/utils/hit-locations.ts","../src/module/data-base-skills.ts","../src/module/skill-icons.ts","../src/module/chat.ts","../src/module/mechanics.ts","../src/module/world-catalog.ts","../src/module/character-tools.ts","../src/module/actor/RingworldActor.ts","../src/module/item/RingworldItem.ts","../src/module/sheets/actors/ExplorerSheet.ts","../src/module/sheets/actors/CreatureSheet.ts","../src/module/sheets/actors/VehicleSheet.ts","../src/module/sheets/items/SkillSheet.ts","../src/module/sheets/items/WeaponSheet.ts","../src/module/sheets/items/ArmorSheet.ts","../src/module/sheets/items/GearSheet.ts","../src/module/sheets/items/SpeciesSheet.ts","../src/module/sheets/items/PursuitSheet.ts","../src/module/impulse-tracker.ts","../src/ringworld.ts"],"sourcesContent":["export async function showDiceSoNice(roll: Roll): Promise {\n const dice3d = (game as any)?.dice3d;\n if (!dice3d?.showForRoll) return;\n\n try {\n await dice3d.showForRoll(roll, game.user, true, null, false);\n } catch (_error) {\n // Ignore Dice So Nice integration failures and continue normal flow.\n }\n}\n\nexport async function evaluateRollWithDice(formula: string): Promise {\n const roll = await new Roll(formula).evaluate();\n await showDiceSoNice(roll as any);\n return roll as any;\n}\n","import { evaluateRollWithDice } from \"./dice\";\n\nexport type PercentileOutcome = \"special\" | \"success\" | \"failure\" | \"fumble\";\n\nexport interface PercentileResult {\n roll: number;\n target: number;\n outcome: PercentileOutcome;\n isSuccess: boolean;\n isSpecial: boolean;\n isFumble: boolean;\n}\n\nexport async function rollPercentile(target: number): Promise {\n const roll = await evaluateRollWithDice(\"1d100\");\n return classifyPercentile(roll.total ?? 0, target);\n}\n\nexport function classifyPercentile(roll: number, target: number): PercentileResult {\n const cleanTarget = Math.max(0, Math.floor(Number(target) || 0));\n const specialThreshold = Math.max(1, Math.floor(cleanTarget / 5));\n const fumbleThreshold = cleanTarget >= 50 ? 100 : 96;\n\n const isSuccess = roll <= cleanTarget;\n const isSpecial = isSuccess && roll <= specialThreshold;\n const isFumble = !isSuccess && roll >= fumbleThreshold;\n\n let outcome: PercentileOutcome = \"failure\";\n if (isFumble) outcome = \"fumble\";\n else if (isSpecial) outcome = \"special\";\n else if (isSuccess) outcome = \"success\";\n\n return {\n roll,\n target: cleanTarget,\n outcome,\n isSuccess,\n isSpecial,\n isFumble\n };\n}\n\nexport function outcomeLabel(outcome: PercentileOutcome): string {\n switch (outcome) {\n case \"special\":\n return \"Special Success\";\n case \"success\":\n return \"Success\";\n case \"fumble\":\n return \"Fumble\";\n default:\n return \"Failure\";\n }\n}\n","export type HitLocationSlug = \"rLeg\" | \"lLeg\" | \"abdomen\" | \"chest\" | \"rArm\" | \"lArm\" | \"head\";\n\nexport interface HitLocationEntry {\n slug: HitLocationSlug;\n label: string;\n min: number;\n max: number;\n}\n\nexport const SYSTEM_ID = \"ringworld\";\n\nexport const HIT_LOCATION_TABLE: HitLocationEntry[] = [\n { slug: \"rLeg\", label: \"Right Leg\", min: 1, max: 3 },\n { slug: \"lLeg\", label: \"Left Leg\", min: 4, max: 6 },\n { slug: \"abdomen\", label: \"Abdomen\", min: 7, max: 10 },\n { slug: \"chest\", label: \"Chest\", min: 11, max: 15 },\n { slug: \"rArm\", label: \"Right Arm\", min: 16, max: 17 },\n { slug: \"lArm\", label: \"Left Arm\", min: 18, max: 19 },\n { slug: \"head\", label: \"Head\", min: 20, max: 20 }\n];\n\nexport const HIT_LOCATION_ORDER: HitLocationSlug[] = [\n \"rLeg\",\n \"lLeg\",\n \"abdomen\",\n \"chest\",\n \"rArm\",\n \"lArm\",\n \"head\"\n];\n\nexport const LIMB_LOCATIONS: HitLocationSlug[] = [\"rArm\", \"lArm\", \"rLeg\", \"lLeg\"];\nexport const LEG_LOCATIONS: HitLocationSlug[] = [\"rLeg\", \"lLeg\"];\n\nexport function isLimb(slug: HitLocationSlug): boolean {\n return LIMB_LOCATIONS.includes(slug);\n}\n","import { HIT_LOCATION_ORDER, HIT_LOCATION_TABLE, type HitLocationEntry, type HitLocationSlug } from \"./constants\";\n\nexport type HitLocationSchemaId = \"humanoid\" | \"dolphin\" | \"puppeteer\" | \"trinoc\" | \"bandersnatchi\" | \"kzinti\";\n\nexport interface HitLocationSchemaEntry extends HitLocationEntry {\n hpFactor: number;\n}\n\nexport interface HitLocationSchema {\n id: HitLocationSchemaId;\n label: string;\n bodyOrder: HitLocationSlug[];\n entries: HitLocationSchemaEntry[];\n}\n\nfunction normalize(value: unknown): string {\n return String(value ?? \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"\");\n}\n\nfunction withFactors(entries: HitLocationEntry[], hpFactors?: Partial>): HitLocationSchemaEntry[] {\n return entries.map((entry) => ({\n ...entry,\n hpFactor: hpFactors?.[entry.slug] ?? (entry.slug === \"chest\" ? 0.35 : (entry.slug === \"rArm\" || entry.slug === \"lArm\") ? 0.25 : 0.3)\n }));\n}\n\nconst HUMANOID_SCHEMA: HitLocationSchema = {\n id: \"humanoid\",\n label: \"Humanoid\",\n bodyOrder: [\"head\", \"lArm\", \"chest\", \"rArm\", \"abdomen\", \"lLeg\", \"rLeg\"],\n entries: withFactors(HIT_LOCATION_TABLE)\n};\n\nconst DOLPHIN_SCHEMA: HitLocationSchema = {\n id: \"dolphin\",\n label: \"Dolphin\",\n bodyOrder: [\"head\", \"lArm\", \"chest\", \"rArm\", \"abdomen\", \"lLeg\", \"rLeg\"],\n entries: withFactors([\n { slug: \"rLeg\", label: \"Right Fluke\", min: 1, max: 3 },\n { slug: \"lLeg\", label: \"Left Fluke\", min: 4, max: 6 },\n { slug: \"abdomen\", label: \"Lower Body\", min: 7, max: 9 },\n { slug: \"chest\", label: \"Main Body\", min: 10, max: 15 },\n { slug: \"rArm\", label: \"Right Fin\", min: 16, max: 17 },\n { slug: \"lArm\", label: \"Left Fin\", min: 18, max: 19 },\n { slug: \"head\", label: \"Head\", min: 20, max: 20 }\n ], {\n head: 0.3,\n rArm: 0.2,\n lArm: 0.2,\n chest: 0.4,\n abdomen: 0.35,\n rLeg: 0.25,\n lLeg: 0.25\n })\n};\n\nconst PUPPETEER_SCHEMA: HitLocationSchema = {\n id: \"puppeteer\",\n label: \"Puppeteer\",\n bodyOrder: [\"head\", \"lArm\", \"chest\", \"rArm\", \"abdomen\", \"lLeg\", \"rLeg\"],\n entries: withFactors([\n { slug: \"rLeg\", label: \"Right Rear Leg\", min: 1, max: 3 },\n { slug: \"lLeg\", label: \"Left Rear Leg\", min: 4, max: 6 },\n { slug: \"rArm\", label: \"Right Foreleg\", min: 7, max: 9 },\n { slug: \"lArm\", label: \"Left Foreleg\", min: 10, max: 12 },\n { slug: \"chest\", label: \"Fore Body\", min: 13, max: 15 },\n { slug: \"abdomen\", label: \"Hind Body\", min: 16, max: 18 },\n { slug: \"head\", label: \"Twin Heads\", min: 19, max: 20 }\n ], {\n head: 0.32,\n chest: 0.35,\n abdomen: 0.35,\n rArm: 0.28,\n lArm: 0.28,\n rLeg: 0.28,\n lLeg: 0.28\n })\n};\n\nconst TRINOC_SCHEMA: HitLocationSchema = {\n id: \"trinoc\",\n label: \"Trinoc\",\n bodyOrder: [\"head\", \"lArm\", \"chest\", \"rArm\", \"abdomen\", \"lLeg\", \"rLeg\"],\n entries: withFactors([\n { slug: \"rArm\", label: \"Right Wing/Arm\", min: 1, max: 3 },\n { slug: \"lArm\", label: \"Left Wing/Arm\", min: 4, max: 6 },\n { slug: \"chest\", label: \"Upper Torso\", min: 7, max: 11 },\n { slug: \"abdomen\", label: \"Lower Torso\", min: 12, max: 14 },\n { slug: \"rLeg\", label: \"Right Leg\", min: 15, max: 17 },\n { slug: \"lLeg\", label: \"Left Leg\", min: 18, max: 19 },\n { slug: \"head\", label: \"Head\", min: 20, max: 20 }\n ], {\n head: 0.3,\n rArm: 0.27,\n lArm: 0.27,\n chest: 0.36,\n abdomen: 0.3,\n rLeg: 0.27,\n lLeg: 0.27\n })\n};\n\nconst BANDERSNATCHI_SCHEMA: HitLocationSchema = {\n id: \"bandersnatchi\",\n label: \"Bandersnatchi\",\n bodyOrder: [\"head\", \"lArm\", \"chest\", \"rArm\", \"abdomen\", \"lLeg\", \"rLeg\"],\n entries: withFactors([\n { slug: \"rLeg\", label: \"Right Rear Mass\", min: 1, max: 2 },\n { slug: \"lLeg\", label: \"Left Rear Mass\", min: 3, max: 4 },\n { slug: \"abdomen\", label: \"Lower Body Mass\", min: 5, max: 11 },\n { slug: \"chest\", label: \"Upper Body Mass\", min: 12, max: 17 },\n { slug: \"rArm\", label: \"Right Manipulator\", min: 18, max: 18 },\n { slug: \"lArm\", label: \"Left Manipulator\", min: 19, max: 19 },\n { slug: \"head\", label: \"Head Cluster\", min: 20, max: 20 }\n ], {\n head: 0.28,\n chest: 0.42,\n abdomen: 0.42,\n rArm: 0.2,\n lArm: 0.2,\n rLeg: 0.24,\n lLeg: 0.24\n })\n};\n\nconst KZINTI_SCHEMA: HitLocationSchema = {\n id: \"kzinti\",\n label: \"Kzinti\",\n bodyOrder: [\"head\", \"lArm\", \"chest\", \"rArm\", \"abdomen\", \"lLeg\", \"rLeg\"],\n entries: withFactors([\n { slug: \"rLeg\", label: \"Right Hind Leg\", min: 1, max: 3 },\n { slug: \"lLeg\", label: \"Left Hind Leg\", min: 4, max: 6 },\n { slug: \"abdomen\", label: \"Abdomen\", min: 7, max: 10 },\n { slug: \"chest\", label: \"Chest\", min: 11, max: 15 },\n { slug: \"rArm\", label: \"Right Foreleg\", min: 16, max: 17 },\n { slug: \"lArm\", label: \"Left Foreleg\", min: 18, max: 19 },\n { slug: \"head\", label: \"Head\", min: 20, max: 20 }\n ])\n};\n\nexport const HIT_LOCATION_SCHEMAS: Record = {\n humanoid: HUMANOID_SCHEMA,\n dolphin: DOLPHIN_SCHEMA,\n puppeteer: PUPPETEER_SCHEMA,\n trinoc: TRINOC_SCHEMA,\n bandersnatchi: BANDERSNATCHI_SCHEMA,\n kzinti: KZINTI_SCHEMA\n};\n\nexport function listHitLocationSchemas(): HitLocationSchema[] {\n return Object.values(HIT_LOCATION_SCHEMAS);\n}\n\nexport function inferHitLocationSchemaId(speciesValue: unknown): HitLocationSchemaId {\n const species = normalize(speciesValue);\n if (!species) return \"humanoid\";\n if (species.includes(\"dolphin\")) return \"dolphin\";\n if (species.includes(\"puppeteer\")) return \"puppeteer\";\n if (species.includes(\"trinoc\")) return \"trinoc\";\n if (species.includes(\"bandersnatch\")) return \"bandersnatchi\";\n if (species.includes(\"kzinti\")) return \"kzinti\";\n return \"humanoid\";\n}\n\nexport function getHitLocationSchemaById(schemaId: unknown): HitLocationSchema {\n const key = normalize(schemaId) as HitLocationSchemaId;\n return HIT_LOCATION_SCHEMAS[key] ?? HUMANOID_SCHEMA;\n}\n\nexport function getActorHitLocationSchema(actor: any): HitLocationSchema {\n const mode = String(actor?.system?.hitLocationSchema || \"auto\").trim().toLowerCase();\n if (mode && mode !== \"auto\") {\n return getHitLocationSchemaById(mode);\n }\n const species = actor?.system?.profile?.species ?? \"\";\n return getHitLocationSchemaById(inferHitLocationSchemaId(species));\n}\n\nexport function getSchemaLocationEntry(schema: HitLocationSchema, slug: HitLocationSlug): HitLocationSchemaEntry {\n return schema.entries.find((entry) => entry.slug === slug) ?? withFactors(HIT_LOCATION_TABLE).find((entry) => entry.slug === slug)!;\n}\n\nexport function getSchemaOrder(schema: HitLocationSchema): HitLocationSlug[] {\n const set = new Set(schema.entries.map((entry) => entry.slug));\n const ordered = schema.bodyOrder.filter((slug) => set.has(slug));\n if (ordered.length === schema.entries.length) return ordered;\n return [...HIT_LOCATION_ORDER];\n}\n\n","import { HIT_LOCATION_TABLE, type HitLocationEntry, type HitLocationSlug } from \"../constants\";\nimport { evaluateRollWithDice } from \"./dice\";\nimport { getActorHitLocationSchema, getHitLocationSchemaById } from \"../hit-location-schemas\";\n\nexport interface HitLocationRollResult {\n roll: number;\n location: HitLocationEntry;\n}\n\nexport function getHitLocationFromRoll(roll: number): HitLocationEntry {\n const found = HIT_LOCATION_TABLE.find((entry) => roll >= entry.min && roll <= entry.max);\n return found ?? HIT_LOCATION_TABLE[HIT_LOCATION_TABLE.length - 1];\n}\n\nfunction getTableForActor(actor?: any): HitLocationEntry[] {\n if (!actor) return HIT_LOCATION_TABLE;\n const schema = getActorHitLocationSchema(actor);\n return schema.entries;\n}\n\nfunction getHitLocationFromRollForTable(roll: number, table: HitLocationEntry[]): HitLocationEntry {\n const found = table.find((entry) => roll >= entry.min && roll <= entry.max);\n return found ?? table[table.length - 1];\n}\n\nexport async function rollHitLocation(actor?: any): Promise {\n const roll = await evaluateRollWithDice(\"1d20\");\n const total = roll.total ?? 20;\n const table = getTableForActor(actor);\n return {\n roll: total,\n location: getHitLocationFromRollForTable(total, table)\n };\n}\n\nexport async function chooseOrRollHitLocation(actor?: any): Promise<{\n slug: HitLocationSlug;\n label: string;\n rolled: number | null;\n} | null> {\n const schema = actor ? getActorHitLocationSchema(actor) : getHitLocationSchemaById(\"humanoid\");\n const table = schema.entries;\n const entriesBySlug = new Map(table.map((entry) => [entry.slug, entry]));\n const labelFor = (slug: HitLocationSlug, fallback: string) => entriesBySlug.get(slug)?.label ?? fallback;\n\n const html = `\n
\n \n
\n \n
\n
\n \n \n \n \n \n \n \n
\n
\n `;\n\n const result = await new Promise((resolve) => {\n let settled = false;\n const done = (value: string | null) => {\n if (settled) return;\n settled = true;\n resolve(value);\n };\n\n const dialog = new Dialog({\n title: \"Choose Hit Location\",\n content: html,\n buttons: {\n apply: {\n label: \"Apply\",\n callback: (dlgHtml: JQuery) => {\n const value = String(dlgHtml.find(\"input[name='location']\").val() || \"random\");\n done(value);\n }\n },\n cancel: {\n label: \"Cancel\",\n callback: () => done(null)\n }\n },\n default: \"apply\",\n render: (dlgHtml: JQuery) => {\n const picker = dlgHtml.find(\".ringworld-hit-picker\");\n picker.find(\".rw-hit-picker-choice\").on(\"click\", (event) => {\n event.preventDefault();\n const button = event.currentTarget as HTMLElement;\n const location = String(button.dataset.location || \"random\");\n picker.find(\".rw-hit-picker-choice\").removeClass(\"active\");\n $(button).addClass(\"active\");\n picker.find(\"input[name='location']\").val(location);\n });\n },\n close: () => done(null)\n }, {\n classes: [\"ringworld-hit-location-dialog\"],\n width: 500,\n resizable: false\n });\n dialog.render(true);\n });\n\n if (!result) return null;\n\n if (result === \"random\") {\n const rolled = await rollHitLocation(actor);\n return {\n slug: rolled.location.slug,\n label: rolled.location.label,\n rolled: rolled.roll\n };\n }\n\n const entry = table.find((loc) => loc.slug === result);\n if (!entry) return null;\n\n return {\n slug: entry.slug,\n label: entry.label,\n rolled: null\n };\n}\n","export interface BaseSkillDef {\n name: string;\n category: \"Agility\" | \"Communication\" | \"Knowledge\" | \"Perception\" | \"Technical\";\n base: number;\n specialization?: string;\n}\n\nexport const BASE_SKILL_CATEGORIES: Array = [\n \"Agility\",\n \"Communication\",\n \"Knowledge\",\n \"Perception\",\n \"Technical\"\n];\n\nexport const RINGWORLD_BASE_SKILLS: BaseSkillDef[] = [\n { name: \"Archaic Melee Weapon\", category: \"Agility\", base: 5 },\n { name: \"Archaic Ranged Weapons\", category: \"Agility\", base: 3 },\n { name: \"Athletics\", category: \"Agility\", base: 15 },\n { name: \"Hide\", category: \"Agility\", base: 10 },\n { name: \"Sneak\", category: \"Agility\", base: 5 },\n { name: \"Unarmed Combat\", category: \"Agility\", base: 0 },\n { name: \"V. Sword, F. Laser\", category: \"Agility\", base: 15 },\n\n { name: \"Bargain\", category: \"Communication\", base: 10 },\n { name: \"Debate\", category: \"Communication\", base: 5 },\n { name: \"Fast Talk\", category: \"Communication\", base: 10 },\n { name: \"Fine Arts\", category: \"Communication\", base: 5 },\n { name: \"Musicianship\", category: \"Communication\", base: 5 },\n { name: \"Orate\", category: \"Communication\", base: 5 },\n { name: \"Own Language\", category: \"Communication\", base: 0, specialization: \"INTx5 base\" },\n { name: \"Perform\", category: \"Communication\", base: 5 },\n { name: \"Psychology\", category: \"Communication\", base: 0 },\n\n { name: \"Anthropology\", category: \"Knowledge\", base: 0 },\n { name: \"Astronomy\", category: \"Knowledge\", base: 0 },\n { name: \"Biology\", category: \"Knowledge\", base: 0 },\n { name: \"Botany\", category: \"Knowledge\", base: 0 },\n { name: \"Chemistry\", category: \"Knowledge\", base: 0 },\n { name: \"Computers\", category: \"Knowledge\", base: 0 },\n { name: \"Emergency Treatment\", category: \"Knowledge\", base: 1 },\n { name: \"Engineering\", category: \"Knowledge\", base: 0 },\n { name: \"Farming\", category: \"Knowledge\", base: 0 },\n { name: \"History\", category: \"Knowledge\", base: 5 },\n { name: \"Law\", category: \"Knowledge\", base: 0 },\n { name: \"Mathematics\", category: \"Knowledge\", base: 0 },\n { name: \"Physics\", category: \"Knowledge\", base: 0 },\n { name: \"Planetology\", category: \"Knowledge\", base: 0 },\n { name: \"Second Languages\", category: \"Knowledge\", base: 0 },\n { name: \"Strategy\", category: \"Knowledge\", base: 0 },\n { name: \"Theology\", category: \"Knowledge\", base: 0 },\n { name: \"Zoology\", category: \"Knowledge\", base: 0 },\n\n { name: \"Handgun, Energy\", category: \"Perception\", base: 5 },\n { name: \"Handgun, Projectile\", category: \"Perception\", base: 3 },\n { name: \"Heavy Weapon, Energy\", category: \"Perception\", base: 5 },\n { name: \"Heavy Weapon, Projectile\", category: \"Perception\", base: 3 },\n { name: \"Listen\", category: \"Perception\", base: 5 },\n { name: \"Observe\", category: \"Perception\", base: 5 },\n { name: \"Scent\", category: \"Perception\", base: 0 },\n { name: \"Search\", category: \"Perception\", base: 5 },\n { name: \"Track\", category: \"Perception\", base: 5 },\n\n { name: \"Aquatic Vehicle\", category: \"Technical\", base: 0 },\n { name: \"Atmospheric Craft\", category: \"Technical\", base: 0 },\n { name: \"Ground Vehicle\", category: \"Technical\", base: 0 },\n { name: \"Hyperdrive\", category: \"Technical\", base: 0 },\n { name: \"Personal Flyer\", category: \"Technical\", base: 15 },\n { name: \"Reaction Drive\", category: \"Technical\", base: 0 },\n { name: \"Reactionless Drive\", category: \"Technical\", base: 0 },\n { name: \"Repair\", category: \"Technical\", base: 0 },\n { name: \"Ringworld\", category: \"Technical\", base: 0 },\n { name: \"Weapons System\", category: \"Technical\", base: 0 }\n];\n\nexport function normalizeSkillName(name: string): string {\n return String(name || \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"\")\n .trim();\n}\n\nexport function baseSkillNameSet(): Set {\n return new Set(RINGWORLD_BASE_SKILLS.map((skill) => normalizeSkillName(skill.name)));\n}\n","import { normalizeSkillName, RINGWORLD_BASE_SKILLS } from \"./data-base-skills\";\n\nexport const DEFAULT_SKILL_ICON = \"systems/ringworld/assets/icons/skill.svg\";\n\nfunction iconSlug(name: string): string {\n return String(name)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nexport function skillIconPath(name: string): string {\n return `systems/ringworld/assets/icons/skill/${iconSlug(name)}.svg`;\n}\n\nexport function baseSkillByName(name: string): (typeof RINGWORLD_BASE_SKILLS)[number] | undefined {\n const key = normalizeSkillName(name);\n return RINGWORLD_BASE_SKILLS.find((skill) => normalizeSkillName(skill.name) === key);\n}\n\nexport function resolveSkillIcon(name: string, currentImg?: string): string {\n const img = String(currentImg || \"\").trim();\n if (img && img !== \"icons/svg/item-bag.svg\") return img;\n if (baseSkillByName(name)) return skillIconPath(name);\n return DEFAULT_SKILL_ICON;\n}\n\n","import { SYSTEM_ID } from \"./constants\";\nimport { DEFAULT_SKILL_ICON } from \"./skill-icons\";\n\ninterface CreateChatOptions {\n actor?: any;\n content: string;\n}\n\nasync function createChatMessage(options: CreateChatOptions): Promise {\n const speaker = options.actor ? ChatMessage.getSpeaker({ actor: options.actor }) : ChatMessage.getSpeaker();\n await ChatMessage.create({\n user: game.user?.id,\n speaker,\n content: options.content\n });\n}\n\nexport async function postSkillRollCard(data: {\n actor: any;\n actorName: string;\n actorImg?: string;\n skillName: string;\n skillImg?: string;\n roll: number;\n target: number;\n outcomeLabel: string;\n isSuccess: boolean;\n isSpecial: boolean;\n isFumble: boolean;\n}): Promise {\n const payload = {\n ...data,\n actorImg: data.actorImg || data.actor?.img || \"icons/svg/mystery-man.svg\",\n skillImg: data.skillImg || DEFAULT_SKILL_ICON\n };\n const content = await renderTemplate(`systems/${SYSTEM_ID}/templates/chat/skill-roll-card.hbs`, payload);\n await createChatMessage({ actor: data.actor, content });\n}\n\nexport async function postAttackCard(data: {\n actor: any;\n actorName: string;\n actorImg?: string;\n targetName: string;\n hasTarget?: boolean;\n weaponName: string;\n weaponImg?: string;\n skillName: string;\n skillImg?: string;\n attackRoll: number;\n attackTarget: number;\n attackOutcomeLabel: string;\n isSuccess: boolean;\n isSpecial: boolean;\n isFumble: boolean;\n hitLocationLabel: string;\n hitLocationRoll: number | null;\n rawDamage: number;\n armor: number;\n finalDamage: number;\n remainingLocationHp: number;\n remainingGeneralHp: number;\n energySourceLabel: string;\n energyCost: number;\n energyRemaining: number;\n notes: string;\n}): Promise {\n const payload = {\n ...data,\n actorImg: data.actorImg || data.actor?.img || \"icons/svg/mystery-man.svg\",\n weaponImg: data.weaponImg || \"icons/svg/sword.svg\",\n skillImg: data.skillImg || DEFAULT_SKILL_ICON,\n hasTarget: data.hasTarget ?? true\n };\n const content = await renderTemplate(`systems/${SYSTEM_ID}/templates/chat/attack-card.hbs`, payload);\n await createChatMessage({ actor: data.actor, content });\n}\n","import { outcomeLabel, rollPercentile } from \"./utils/percentile\";\nimport { chooseOrRollHitLocation, rollHitLocation } from \"./utils/hit-locations\";\nimport { LEG_LOCATIONS, SYSTEM_ID, isLimb, type HitLocationSlug } from \"./constants\";\nimport { postAttackCard, postSkillRollCard } from \"./chat\";\nimport { evaluateRollWithDice } from \"./utils/dice\";\nimport { getActorHitLocationSchema } from \"./hit-location-schemas\";\n\nfunction num(value: unknown, fallback = 0): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : fallback;\n}\n\nfunction getSingleTargetActor(): any | null {\n const targets = Array.from(game.user?.targets ?? []);\n if (!targets.length) return null;\n return (targets[0] as any)?.actor ?? null;\n}\n\nexport async function rollStandaloneHitLocation(actor?: any): Promise {\n const result = await rollHitLocation(actor);\n const speaker = actor ? ChatMessage.getSpeaker({ actor }) : ChatMessage.getSpeaker();\n const content = `\n
\n

Hit Location

\n

Roll: ${result.roll}

\n

Location: ${result.location.label}

\n
\n `;\n\n await ChatMessage.create({\n user: game.user?.id,\n speaker,\n content\n });\n}\n\nexport async function rollSkillForActor(actor: any, skillName: string, target: number): Promise {\n const result = await rollPercentile(target);\n const skillItem = actor.items.find(\n (item: any) => item.type === \"skill\" && item.name.toLowerCase() === String(skillName).toLowerCase()\n );\n\n await postSkillRollCard({\n actor,\n actorName: actor.name,\n actorImg: actor.img,\n skillName,\n skillImg: skillItem?.img,\n roll: result.roll,\n target: result.target,\n outcomeLabel: outcomeLabel(result.outcome),\n isSuccess: result.isSuccess,\n isSpecial: result.isSpecial,\n isFumble: result.isFumble\n });\n}\n\nfunction resolveLinkedSkill(actor: any, weapon: any): { name: string; target: number; img?: string } {\n const skillId = weapon.system.linkedSkillId;\n const byId = skillId ? actor.items.get(skillId) : null;\n if (byId?.type === \"skill\") {\n return { name: byId.name, target: num(byId.system.value), img: byId.img };\n }\n\n const linkedSkillName = String(weapon.system.linkedSkill || \"\").trim();\n if (linkedSkillName) {\n const byName = actor.items.find(\n (item: any) => item.type === \"skill\" && item.name.toLowerCase() === linkedSkillName.toLowerCase()\n );\n if (byName) {\n return { name: byName.name, target: num(byName.system.value), img: byName.img };\n }\n }\n\n return {\n name: linkedSkillName || \"Unlinked Skill\",\n target: 0\n };\n}\n\ntype AimMode = \"unaimed\" | \"partial\" | \"full\";\n\ninterface AttackRollModifierResult {\n finalTarget: number;\n summary: string;\n aimMode: AimMode;\n aimMultiplier: number;\n flatModifierTotal: number;\n}\n\nfunction clampPercentTarget(value: number): number {\n return Math.max(0, Math.min(99, Math.floor(value)));\n}\n\nasync function promptAttackModifiers(attacker: any, weapon: any, baseSkillTarget: number): Promise {\n const attackType = String(weapon.system?.attackType || \"ranged\");\n const isRanged = attackType === \"ranged\";\n const defaultAim: AimMode = \"full\";\n const safeActorName = foundry.utils.escapeHTML(String(attacker?.name || \"Attacker\"));\n const safeWeaponName = foundry.utils.escapeHTML(String(weapon?.name || \"Weapon\"));\n const safeActorImg = foundry.utils.escapeHTML(String(attacker?.img || \"icons/svg/mystery-man.svg\"));\n const safeWeaponImg = foundry.utils.escapeHTML(String(weapon?.img || \"icons/svg/sword.svg\"));\n\n const content = `\n
\n
\n \"${safeActorName}\"\n \"${safeWeaponName}\"\n
\n

${safeWeaponName}

\n

${safeActorName} Attack Setup

\n
\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n

Ringworld rule support: unaimed ranged attacks use quarter skill, partial aim uses half skill, full aim uses full skill.

\n
\n `;\n\n return new Promise((resolve) => {\n let settled = false;\n const done = (value: AttackRollModifierResult | null) => {\n if (settled) return;\n settled = true;\n resolve(value);\n };\n\n new Dialog({\n title: `${weapon.name} - Attack Modifiers`,\n content,\n buttons: {\n roll: {\n label: \"Roll Attack\",\n callback: (dlgHtml: JQuery) => {\n const root = dlgHtml[0] as HTMLElement | undefined;\n const query = (name: string) => (root?.querySelector(`[name=\"${name}\"]`) as HTMLInputElement | HTMLSelectElement | null);\n const aimModeRaw = String(query(\"aimMode\")?.value || defaultAim) as AimMode;\n const aimMode: AimMode = (aimModeRaw === \"unaimed\" || aimModeRaw === \"partial\" || aimModeRaw === \"full\")\n ? aimModeRaw\n : defaultAim;\n const aimMultiplier = isRanged ? ({ unaimed: 0.25, partial: 0.5, full: 1 } as const)[aimMode] : 1;\n const coverMod = num(query(\"coverMod\")?.value, 0);\n const movementMod = num(query(\"movementMod\")?.value, 0);\n const rangeMod = num(query(\"rangeMod\")?.value, 0);\n const situationalMod = num(query(\"situationalMod\")?.value, 0);\n const customMod = num(query(\"customMod\")?.value, 0);\n const customLabel = String(query(\"customLabel\")?.value || \"\").trim();\n\n const flatModifierTotal = coverMod + movementMod + rangeMod + situationalMod + customMod;\n const preAimTarget = baseSkillTarget + flatModifierTotal;\n const adjustedByAim = Math.floor(preAimTarget * aimMultiplier);\n const finalTarget = clampPercentTarget(adjustedByAim);\n\n const parts: string[] = [`Base ${baseSkillTarget}`];\n if (coverMod) parts.push(`Cover ${coverMod >= 0 ? \"+\" : \"\"}${coverMod}`);\n if (movementMod) parts.push(`Movement ${movementMod >= 0 ? \"+\" : \"\"}${movementMod}`);\n if (rangeMod) parts.push(`Range ${rangeMod >= 0 ? \"+\" : \"\"}${rangeMod}`);\n if (situationalMod) parts.push(`Situational ${situationalMod >= 0 ? \"+\" : \"\"}${situationalMod}`);\n if (customMod) parts.push(`${customLabel || \"Custom\"} ${customMod >= 0 ? \"+\" : \"\"}${customMod}`);\n parts.push(`Pre-Aim ${preAimTarget}`);\n if (isRanged) {\n const aimLabel = aimMode === \"unaimed\" ? \"Quarter\" : aimMode === \"partial\" ? \"Half\" : \"Full\";\n parts.push(`Aim ${aimLabel} x${aimMultiplier}`);\n }\n parts.push(`Final ${finalTarget}`);\n\n done({\n finalTarget,\n summary: parts.join(\" | \"),\n aimMode,\n aimMultiplier,\n flatModifierTotal\n });\n }\n },\n cancel: {\n label: \"Cancel\",\n callback: () => done(null)\n }\n },\n default: \"roll\",\n close: () => done(null)\n }, {\n classes: [\"ringworld-attack-dialog\"],\n width: 510,\n resizable: false\n }).render(true);\n });\n}\n\nasync function consumeWeaponEnergy(actor: any, weapon: any): Promise<{\n sourceLabel: string;\n remaining: number;\n cost: number;\n insufficient: boolean;\n}> {\n const cost = Math.max(0, num(weapon.system.energyUsedPerAttack));\n if (cost <= 0) {\n return { sourceLabel: \"None\", remaining: 0, cost: 0, insufficient: false };\n }\n\n const source = String(weapon.system.powerSource || \"actor\");\n\n if (source === \"powerPack\") {\n const packId = String(weapon.system.powerPackId || \"\");\n const pack = packId ? actor.items.get(packId) : null;\n\n if (!pack || pack.type !== \"gear\" || !pack.system.isPowerPack) {\n ui.notifications?.warn(\"Weapon is configured for a power pack but no valid power pack is linked.\");\n return { sourceLabel: \"Power Pack\", remaining: 0, cost, insufficient: true };\n }\n\n const current = num(pack.system.energy?.value);\n if (current < cost) {\n ui.notifications?.warn(`${pack.name} does not have enough energy.`);\n return { sourceLabel: pack.name, remaining: current, cost, insufficient: true };\n }\n\n const next = current - cost;\n await actor.updateEmbeddedDocuments(\"Item\", [{ _id: pack.id, \"system.energy.value\": next }]);\n return { sourceLabel: pack.name, remaining: next, cost, insufficient: false };\n }\n\n const current = num(actor.system.resources?.energy?.value);\n if (current < cost) {\n ui.notifications?.warn(`${actor.name} does not have enough actor energy.`);\n return { sourceLabel: \"Actor Energy\", remaining: current, cost, insufficient: true };\n }\n\n const next = current - cost;\n await actor.update({ \"system.resources.energy.value\": next });\n return { sourceLabel: \"Actor Energy\", remaining: next, cost, insufficient: false };\n}\n\nfunction getItemArmorAtLocation(item: any, slug: HitLocationSlug): number {\n if (item.type !== \"armor\" || !item.system.equipped || item.system.stowed) return 0;\n if (!item.system.coverage?.[slug]) return 0;\n\n const specific = num(item.system.perLocation?.[slug]);\n if (specific > 0) return specific;\n return num(item.system.value);\n}\n\nfunction getTotalArmorAtLocation(targetActor: any, slug: HitLocationSlug): number {\n const locArmor = num(targetActor.system.hitLocations?.[slug]?.armor);\n const stackedItemsArmor = targetActor.items\n .filter((item: any) => item.type === \"armor\")\n .reduce((sum: number, item: any) => sum + getItemArmorAtLocation(item, slug), 0);\n\n return locArmor + stackedItemsArmor;\n}\n\nfunction buildConditionUpdates(targetActor: any, slug: HitLocationSlug, locationValue: number, locationMax: number, generalHp: number) {\n const current = foundry.utils.deepClone(targetActor.system.conditions ?? {});\n current.unusableLimbs = current.unusableLimbs ?? { rArm: false, lArm: false, rLeg: false, lLeg: false };\n\n if (locationValue <= 0) {\n if (slug === \"head\" || slug === \"chest\") {\n current.unconscious = true;\n current.prone = true;\n }\n\n if (slug === \"abdomen\") {\n current.prone = true;\n current.unusableLimbs.rLeg = true;\n current.unusableLimbs.lLeg = true;\n }\n\n if (slug === \"rArm\" || slug === \"lArm\") {\n current.unusableLimbs[slug] = true;\n }\n\n if (LEG_LOCATIONS.includes(slug)) {\n current.unusableLimbs[slug] = true;\n current.prone = true;\n }\n }\n\n if (generalHp <= 0) {\n current.dying = true;\n }\n\n if ((slug === \"head\" || slug === \"chest\" || slug === \"abdomen\") && locationValue <= -locationMax) {\n current.dying = true;\n }\n\n if (slug === \"head\" && locationValue <= -locationMax) {\n current.dead = true;\n current.unconscious = true;\n }\n\n return current;\n}\n\nasync function syncCoreStatusEffects(targetActor: any, conditions: any): Promise {\n const toggler = (targetActor as any).toggleStatusEffect;\n if (typeof toggler !== \"function\") return;\n\n try {\n await toggler.call(targetActor, \"prone\", { active: !!conditions.prone });\n await toggler.call(targetActor, \"unconscious\", { active: !!conditions.unconscious });\n await toggler.call(targetActor, \"dead\", { active: !!conditions.dead });\n } catch (_err) {\n // Some worlds lock actor-level status manipulation; system conditions still update.\n }\n}\n\nasync function applyLocationDamage(targetActor: any, slug: HitLocationSlug, rawDamage: number): Promise<{\n armor: number;\n finalDamage: number;\n locationValue: number;\n generalHpValue: number;\n}> {\n const location = targetActor.system.hitLocations?.[slug];\n const currentLoc = num(location?.hp?.value);\n const locMax = Math.max(1, num(location?.hp?.max, 1));\n const armor = getTotalArmorAtLocation(targetActor, slug);\n const finalDamage = Math.max(0, rawDamage - armor);\n\n const unclampedLoc = currentLoc - finalDamage;\n const nextLoc = isLimb(slug) ? Math.max(unclampedLoc, -locMax) : unclampedLoc;\n\n const currentGeneral = num(targetActor.system.generalHp?.value);\n const nextGeneral = currentGeneral - finalDamage;\n\n const conditions = buildConditionUpdates(targetActor, slug, nextLoc, locMax, nextGeneral);\n\n const updates: Record = {\n [`system.hitLocations.${slug}.hp.value`]: nextLoc,\n \"system.generalHp.value\": nextGeneral,\n \"system.conditions\": conditions\n };\n\n await targetActor.update(updates);\n await syncCoreStatusEffects(targetActor, conditions);\n\n return {\n armor,\n finalDamage,\n locationValue: nextLoc,\n generalHpValue: nextGeneral\n };\n}\n\nexport async function useWeaponWorkflow(attacker: any, weapon: any): Promise {\n const targetActor = getSingleTargetActor();\n const hasTarget = Boolean(targetActor);\n const targetName = targetActor?.name ?? \"No Target\";\n\n const linkedSkill = resolveLinkedSkill(attacker, weapon);\n const modifierResult = await promptAttackModifiers(attacker, weapon, linkedSkill.target);\n if (!modifierResult) return;\n\n const energy = await consumeWeaponEnergy(attacker, weapon);\n if (energy.insufficient) return;\n\n const attack = await rollPercentile(modifierResult.finalTarget);\n\n if (!attack.isSuccess) {\n const remainingGeneralHp = targetActor ? num(targetActor.system.generalHp?.value, 0) : 0;\n await postAttackCard({\n actor: attacker,\n actorName: attacker.name,\n actorImg: attacker.img,\n targetName,\n hasTarget,\n weaponName: weapon.name,\n weaponImg: weapon.img,\n skillName: linkedSkill.name,\n skillImg: linkedSkill.img,\n attackRoll: attack.roll,\n attackTarget: attack.target,\n attackOutcomeLabel: outcomeLabel(attack.outcome),\n isSuccess: false,\n isSpecial: attack.isSpecial,\n isFumble: attack.isFumble,\n hitLocationLabel: \"-\",\n hitLocationRoll: null,\n rawDamage: 0,\n armor: 0,\n finalDamage: 0,\n remainingLocationHp: 0,\n remainingGeneralHp,\n energySourceLabel: energy.sourceLabel,\n energyCost: energy.cost,\n energyRemaining: energy.remaining,\n notes: `Attack failed before damage was rolled. ${modifierResult.summary}`\n });\n return;\n }\n\n const hitSelection = await chooseOrRollHitLocation(targetActor ?? attacker);\n if (!hitSelection) return;\n\n const damageFormula = String(weapon.system.damageFormula || \"0\").trim() || \"0\";\n const damageRoll = await evaluateRollWithDice(damageFormula);\n const rawDamage = Math.max(0, num(damageRoll.total));\n\n const application = targetActor\n ? await applyLocationDamage(targetActor, hitSelection.slug, rawDamage)\n : {\n armor: 0,\n finalDamage: rawDamage,\n locationValue: 0,\n generalHpValue: 0\n };\n\n await postAttackCard({\n actor: attacker,\n actorName: attacker.name,\n actorImg: attacker.img,\n targetName,\n hasTarget,\n weaponName: weapon.name,\n weaponImg: weapon.img,\n skillName: linkedSkill.name,\n skillImg: linkedSkill.img,\n attackRoll: attack.roll,\n attackTarget: attack.target,\n attackOutcomeLabel: outcomeLabel(attack.outcome),\n isSuccess: attack.isSuccess,\n isSpecial: attack.isSpecial,\n isFumble: attack.isFumble,\n hitLocationLabel: hitSelection.label,\n hitLocationRoll: hitSelection.rolled,\n rawDamage,\n armor: application.armor,\n finalDamage: application.finalDamage,\n remainingLocationHp: application.locationValue,\n remainingGeneralHp: application.generalHpValue,\n energySourceLabel: energy.sourceLabel,\n energyCost: energy.cost,\n energyRemaining: energy.remaining,\n notes: targetActor\n ? `Location and general HP were both reduced by final damage, per Ringworld rules. ${modifierResult.summary}`\n : `No target selected: roll resolved with no damage application. ${modifierResult.summary}`\n });\n}\n\nexport function recomputeHitPointMaximums(actor: any): void {\n if (actor.type !== \"explorer\" && actor.type !== \"creature\") return;\n\n // Ringworld general HP is CON + MAS. Location HP then scales from that general HP.\n const con = num(actor.system?.characteristics?.con, 10);\n const mas = num(actor.system?.characteristics?.mas, 10);\n const existingMax = num(actor.system.generalHp?.max, 0);\n const existingValue = num(actor.system.generalHp?.value, 0);\n const generalMax = Math.max(1, Math.floor(con + mas));\n\n actor.system.generalHp = actor.system.generalHp ?? { value: generalMax, max: generalMax };\n actor.system.generalHp.max = generalMax;\n const hadExisting = existingMax > 0;\n const wasFull = hadExisting && existingValue >= existingMax;\n const looksPlaceholder = existingMax <= 1 && existingValue <= 1;\n const preservedDamage = hadExisting ? Math.max(0, existingMax - existingValue) : 0;\n const recomputedValue = (!hadExisting || wasFull || looksPlaceholder)\n ? generalMax\n : Math.max(0, generalMax - preservedDamage);\n\n actor.system.generalHp.value = Math.min(Math.max(0, recomputedValue), generalMax);\n const generalAtFull = num(actor.system.generalHp.value, generalMax) >= generalMax;\n\n actor.system.hitLocations = actor.system.hitLocations ?? {};\n actor.system.hitLocationSchema = actor.system.hitLocationSchema ?? \"auto\";\n const schema = getActorHitLocationSchema(actor);\n\n for (const entry of schema.entries) {\n const slug = entry.slug;\n const loc = actor.system.hitLocations[slug] ?? {\n label: slug,\n hp: { value: 1, max: 1 },\n armor: 0\n };\n loc.label = entry.label;\n const hpFactor = Math.max(0.05, num((entry as any).hpFactor, 0.3));\n const max = Math.max(1, Math.ceil(generalMax * hpFactor));\n\n const previousMax = num(loc.hp?.max, 1);\n const previousValue = num(loc.hp?.value, previousMax);\n const isPlaceholder = previousMax <= 1 && previousValue <= 1;\n const shouldBackfillToFull = generalAtFull && previousValue <= 1 && max > 1;\n\n loc.hp.max = max;\n loc.hp.value = (isPlaceholder || shouldBackfillToFull) ? max : Math.min(previousValue, max);\n actor.system.hitLocations[slug] = loc;\n }\n}\n\nexport function getTemplatePath(path: string): string {\n return `systems/${SYSTEM_ID}/templates/${path}`;\n}\n","import { SYSTEM_ID } from \"./constants\";\nimport { normalizeSkillName, RINGWORLD_BASE_SKILLS } from \"./data-base-skills\";\nimport { skillIconPath } from \"./skill-icons\";\n\nexport type GravityType = \"light\" | \"normal\" | \"heavy\";\n\ninterface SpeciesCatalogEntry {\n name: string;\n playable: boolean;\n gravity: GravityType;\n homeworld: string;\n modifiers: {\n str: number;\n mas: number;\n con: number;\n int: number;\n pow: number;\n dex: number;\n app: number;\n edu: number;\n age: number;\n };\n notes: string;\n}\n\ninterface WeaponCatalogEntry {\n name: string;\n equipped?: boolean;\n weight?: number;\n linkedSkill: string;\n attackType: \"melee\" | \"ranged\";\n damageFormula: string;\n range: { close: string; medium: string; long: string };\n energyUsedPerAttack: number;\n ammoUsedPerAttack: number;\n powerSource: \"actor\" | \"powerPack\";\n notes: string;\n}\n\ninterface ArmorCatalogEntry {\n name: string;\n equipped: boolean;\n weight?: number;\n value: number;\n coverage: Record;\n notes: string;\n}\n\ninterface GearCatalogEntry {\n name: string;\n equipped?: boolean;\n weight: number;\n cost: number;\n energyUse: number;\n powerSupplyNotes: string;\n isPowerPack: boolean;\n energy: { value: number; max: number };\n notes: string;\n}\n\ninterface PursuitCatalogEntry {\n name: string;\n rollRange: string;\n wealthValue: number;\n occupationPointsPerYear: number;\n fixedSkills: string[];\n optionalSkillGroups?: Array<{\n choose: number;\n skills: string[];\n }>;\n notes: string;\n}\n\ninterface CatalogSeed {\n key: string;\n data: any;\n folderKey: \"species\" | \"skills\" | \"pursuits\" | \"gear\" | \"armor\" | \"weaponMelee\" | \"weaponRanged\";\n img: string;\n}\n\nexport const RINGWORLD_CATALOG_VERSION = 9;\n\nconst ICONS = {\n speciesDefault: \"systems/ringworld/assets/icons/species.svg\",\n skillDefault: \"systems/ringworld/assets/icons/skill.svg\",\n pursuitDefault: \"systems/ringworld/assets/icons/pursuit.svg\",\n weaponMelee: \"systems/ringworld/assets/icons/weapon-melee.svg\",\n weaponRanged: \"systems/ringworld/assets/icons/weapon-ranged.svg\",\n armor: \"systems/ringworld/assets/icons/armor.svg\",\n gearDefault: \"systems/ringworld/assets/icons/gear.svg\",\n powerPackDefault: \"systems/ringworld/assets/icons/power-pack.svg\"\n};\n\nfunction iconSlug(name: string): string {\n return String(name)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nfunction speciesIconPath(name: string): string {\n return `systems/ringworld/assets/icons/species/${iconSlug(name)}.svg`;\n}\n\nfunction weaponIconPath(name: string): string {\n return `systems/ringworld/assets/icons/weapon/${iconSlug(name)}.svg`;\n}\n\nfunction armorIconPath(name: string): string {\n return `systems/ringworld/assets/icons/armor/${iconSlug(name)}.svg`;\n}\n\nfunction gearIconPath(name: string): string {\n return `systems/ringworld/assets/icons/gear/${iconSlug(name)}.svg`;\n}\n\nfunction pursuitIconPath(name: string): string {\n return `systems/ringworld/assets/icons/pursuit/${iconSlug(name)}.svg`;\n}\n\nconst allCoverage = {\n rLeg: true,\n lLeg: true,\n abdomen: true,\n chest: true,\n rArm: true,\n lArm: true,\n head: true\n};\n\nconst torsoCoverage = {\n rLeg: false,\n lLeg: false,\n abdomen: true,\n chest: true,\n rArm: false,\n lArm: false,\n head: false\n};\n\nconst headCoverage = {\n rLeg: false,\n lLeg: false,\n abdomen: false,\n chest: false,\n rArm: false,\n lArm: false,\n head: true\n};\n\nconst armCoverage = {\n rLeg: false,\n lLeg: false,\n abdomen: false,\n chest: false,\n rArm: true,\n lArm: true,\n head: false\n};\n\nconst legCoverage = {\n rLeg: true,\n lLeg: true,\n abdomen: false,\n chest: false,\n rArm: false,\n lArm: false,\n head: false\n};\n\nexport const SPECIES_CATALOG: SpeciesCatalogEntry[] = [\n {\n name: \"Human\",\n playable: true,\n gravity: \"normal\",\n homeworld: \"Known Space\",\n modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 },\n notes: \"Baseline human explorer profile.\"\n },\n {\n name: \"Earth Human\",\n playable: true,\n gravity: \"normal\",\n homeworld: \"Earth\",\n modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 1, dex: 0, app: 0, edu: 0, age: 0 },\n notes: \"Earth-line humans with stronger latent psionic potential.\"\n },\n {\n name: \"Kzinti\",\n playable: true,\n gravity: \"heavy\",\n homeworld: \"Kzin\",\n modifiers: { str: 2, mas: 2, con: 1, int: -1, pow: 0, dex: 0, app: -2, edu: 0, age: 0 },\n notes: \"Predatory felinoids with high physical strength.\"\n },\n {\n name: \"Pierson's Puppeteer\",\n playable: true,\n gravity: \"light\",\n homeworld: \"Fleet of Worlds\",\n modifiers: { str: -2, mas: -2, con: -1, int: 2, pow: 1, dex: 1, app: 0, edu: 1, age: 0 },\n notes: \"Highly intelligent, cautious engineers.\"\n },\n {\n name: \"Dolphin\",\n playable: true,\n gravity: \"normal\",\n homeworld: \"Human Space Oceans\",\n modifiers: { str: -2, mas: 1, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 },\n notes: \"Aquatic specialists with enhanced endurance.\"\n },\n {\n name: \"Trinoc\",\n playable: true,\n gravity: \"light\",\n homeworld: \"Trinoc Worlds\",\n modifiers: { str: -1, mas: -1, con: 0, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 },\n notes: \"Three-eyed flyers with excellent perception.\"\n },\n {\n name: \"Martian\",\n playable: true,\n gravity: \"light\",\n homeworld: \"Mars\",\n modifiers: { str: -1, mas: -1, con: -1, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 },\n notes: \"Light-gravity adapted intellect-focused profile.\"\n },\n {\n name: \"Pierin\",\n playable: true,\n gravity: \"normal\",\n homeworld: \"Pierin Space\",\n modifiers: { str: 0, mas: 0, con: 0, int: 1, pow: 0, dex: 0, app: 1, edu: 0, age: 0 },\n notes: \"Socially adept, technically curious explorers.\"\n },\n {\n name: \"Boat People\",\n playable: true,\n gravity: \"normal\",\n homeworld: \"Ringworld\",\n modifiers: { str: 0, mas: 0, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: -1, age: 0 },\n notes: \"Ringworld hominids adapted to river and sea cultures.\"\n },\n {\n name: \"Builder\",\n playable: true,\n gravity: \"normal\",\n homeworld: \"Ringworld\",\n modifiers: { str: 1, mas: 1, con: 0, int: 0, pow: 0, dex: 0, app: -1, edu: 0, age: 0 },\n notes: \"Ringworld labor stock with robust build.\"\n },\n {\n name: \"Ghoul\",\n playable: true,\n gravity: \"normal\",\n homeworld: \"Ringworld\",\n modifiers: { str: 0, mas: 0, con: 1, int: -1, pow: 0, dex: 1, app: -2, edu: -1, age: 0 },\n notes: \"Scavenger subspecies with strong survival instincts.\"\n },\n {\n name: \"Bandersnatchi\",\n playable: false,\n gravity: \"heavy\",\n homeworld: \"Known Space\",\n modifiers: { str: 4, mas: 4, con: 3, int: 0, pow: 0, dex: -2, app: -3, edu: 0, age: 10 },\n notes: \"Massive engineered beings; typically GM creature profile.\"\n }\n];\n\nexport const WEAPON_CATALOG: WeaponCatalogEntry[] = [\n {\n name: \"Variable Sword\",\n equipped: true,\n weight: 1.5,\n linkedSkill: \"V. Sword, F. Laser\",\n attackType: \"melee\",\n damageFormula: \"1d10+1\",\n range: { close: \"Melee\", medium: \"-\", long: \"-\" },\n energyUsedPerAttack: 1,\n ammoUsedPerAttack: 0,\n powerSource: \"powerPack\",\n notes: \"High-energy blade weapon.\"\n },\n {\n name: \"Monofilament Knife\",\n equipped: true,\n weight: 0.2,\n linkedSkill: \"Archaic Melee Weapon\",\n attackType: \"melee\",\n damageFormula: \"1d6+1\",\n range: { close: \"Melee\", medium: \"-\", long: \"-\" },\n energyUsedPerAttack: 0,\n ammoUsedPerAttack: 0,\n powerSource: \"actor\",\n notes: \"Fine-edge close combat blade.\"\n },\n {\n name: \"Shock Baton\",\n equipped: true,\n weight: 1.2,\n linkedSkill: \"Unarmed Combat\",\n attackType: \"melee\",\n damageFormula: \"1d8+1\",\n range: { close: \"Melee\", medium: \"-\", long: \"-\" },\n energyUsedPerAttack: 1,\n ammoUsedPerAttack: 0,\n powerSource: \"powerPack\",\n notes: \"Close-quarters stun baton.\"\n },\n {\n name: \"Flashlight Laser\",\n equipped: true,\n weight: 0.8,\n linkedSkill: \"V. Sword, F. Laser\",\n attackType: \"ranged\",\n damageFormula: \"1d10\",\n range: { close: \"Short\", medium: \"Medium\", long: \"Long\" },\n energyUsedPerAttack: 2,\n ammoUsedPerAttack: 0,\n powerSource: \"powerPack\",\n notes: \"Compact laser unit.\"\n },\n {\n name: \"Laser Pistol\",\n equipped: true,\n weight: 1.4,\n linkedSkill: \"Handgun, Energy\",\n attackType: \"ranged\",\n damageFormula: \"1d10+1\",\n range: { close: \"Short\", medium: \"Medium\", long: \"Long\" },\n energyUsedPerAttack: 2,\n ammoUsedPerAttack: 0,\n powerSource: \"powerPack\",\n notes: \"Common energy sidearm.\"\n },\n {\n name: \"Laser Rifle\",\n equipped: true,\n weight: 4.5,\n linkedSkill: \"Heavy Weapon, Energy\",\n attackType: \"ranged\",\n damageFormula: \"2d6+2\",\n range: { close: \"Short\", medium: \"Long\", long: \"Extreme\" },\n energyUsedPerAttack: 3,\n ammoUsedPerAttack: 0,\n powerSource: \"powerPack\",\n notes: \"Standard long-arm laser weapon.\"\n },\n {\n name: \"Stunner\",\n equipped: true,\n weight: 1.0,\n linkedSkill: \"Handgun, Energy\",\n attackType: \"ranged\",\n damageFormula: \"1d6\",\n range: { close: \"Short\", medium: \"Medium\", long: \"Long\" },\n energyUsedPerAttack: 1,\n ammoUsedPerAttack: 0,\n powerSource: \"powerPack\",\n notes: \"Low-lethality disruption sidearm.\"\n },\n {\n name: \"Handgun (Projectile)\",\n equipped: true,\n weight: 1.2,\n linkedSkill: \"Handgun, Projectile\",\n attackType: \"ranged\",\n damageFormula: \"1d10\",\n range: { close: \"Short\", medium: \"Medium\", long: \"Long\" },\n energyUsedPerAttack: 0,\n ammoUsedPerAttack: 1,\n powerSource: \"actor\",\n notes: \"Conventional projectile sidearm.\"\n },\n {\n name: \"Rifle (Projectile)\",\n equipped: true,\n weight: 5.0,\n linkedSkill: \"Heavy Weapon, Projectile\",\n attackType: \"ranged\",\n damageFormula: \"2d6\",\n range: { close: \"Short\", medium: \"Long\", long: \"Extreme\" },\n energyUsedPerAttack: 0,\n ammoUsedPerAttack: 1,\n powerSource: \"actor\",\n notes: \"Conventional projectile rifle.\"\n },\n {\n name: \"Slug Thrower Carbine\",\n equipped: true,\n weight: 3.8,\n linkedSkill: \"Heavy Weapon, Projectile\",\n attackType: \"ranged\",\n damageFormula: \"2d6+1\",\n range: { close: \"Short\", medium: \"Long\", long: \"Extreme\" },\n energyUsedPerAttack: 0,\n ammoUsedPerAttack: 1,\n powerSource: \"actor\",\n notes: \"Compact projectile carbine.\"\n }\n];\n\nexport const ARMOR_CATALOG: ArmorCatalogEntry[] = [\n { name: \"Cloth Vest\", equipped: true, weight: 2, value: 1, coverage: torsoCoverage, notes: \"Basic torso protection.\" },\n { name: \"Soft Combat Vest\", equipped: true, weight: 4, value: 2, coverage: torsoCoverage, notes: \"Flexible tactical vest.\" },\n { name: \"Combat Harness\", equipped: true, weight: 5, value: 2, coverage: { ...torsoCoverage, rArm: true, lArm: true }, notes: \"Torso + upper arms.\" },\n { name: \"Armored Coat\", equipped: true, weight: 7, value: 3, coverage: { ...torsoCoverage, rArm: true, lArm: true }, notes: \"Heavy coat with arm plating.\" },\n { name: \"Leg Guards\", equipped: true, weight: 4, value: 2, coverage: legCoverage, notes: \"Lower-body armor.\" },\n { name: \"Arm Bracers\", equipped: true, weight: 2, value: 2, coverage: armCoverage, notes: \"Forearm and upper arm guards.\" },\n { name: \"Combat Helmet\", equipped: true, weight: 1.5, value: 2, coverage: headCoverage, notes: \"Cranial protection.\" },\n { name: \"Full Combat Suit\", equipped: true, weight: 12, value: 3, coverage: allCoverage, notes: \"Full-body armored suit.\" },\n { name: \"Impact Suit\", equipped: true, weight: 16, value: 4, coverage: allCoverage, notes: \"High-end impact and hazard suit.\" }\n];\n\nexport const GEAR_CATALOG: GearCatalogEntry[] = [\n {\n name: \"Power Pack R20\",\n weight: 1,\n cost: 100,\n energyUse: 0,\n powerSupplyNotes: \"Rechargeable power pack\",\n isPowerPack: true,\n energy: { value: 20, max: 20 },\n notes: \"Standard personal battery pack.\"\n },\n {\n name: \"Power Pack R50\",\n weight: 2,\n cost: 220,\n energyUse: 0,\n powerSupplyNotes: \"Rechargeable power pack\",\n isPowerPack: true,\n energy: { value: 50, max: 50 },\n notes: \"Extended personal battery pack.\"\n },\n {\n name: \"Power Pack R100\",\n weight: 4,\n cost: 400,\n energyUse: 0,\n powerSupplyNotes: \"Rechargeable power pack\",\n isPowerPack: true,\n energy: { value: 100, max: 100 },\n notes: \"High-capacity field battery.\"\n },\n { name: \"Portable Generator\", weight: 8, cost: 700, energyUse: 0, powerSupplyNotes: \"Provides charging capacity\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Portable recharge generator.\" },\n { name: \"MicroMind Unit\", weight: 0.5, cost: 900, energyUse: 1, powerSupplyNotes: \"Requires external power\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Portable advanced computation module.\" },\n { name: \"Memory Bubble Reader\", weight: 0.5, cost: 450, energyUse: 1, powerSupplyNotes: \"Standard battery compatible\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Reads and writes memory bubbles.\" },\n { name: \"Autodoc (Portable)\", weight: 18, cost: 3500, energyUse: 6, powerSupplyNotes: \"High draw medical station\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Field medical support unit.\" },\n { name: \"Medkit\", weight: 1, cost: 120, energyUse: 0, powerSupplyNotes: \"None\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Manual emergency treatment supplies.\" },\n { name: \"Bioscanner\", weight: 1, cost: 300, energyUse: 1, powerSupplyNotes: \"Battery or generator\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Vitals and hazard scanning tool.\" },\n { name: \"Survey Scanner\", weight: 2, cost: 500, energyUse: 2, powerSupplyNotes: \"Battery or generator\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Terrain and structure analysis scanner.\" },\n { name: \"Translator Module\", weight: 0.5, cost: 650, energyUse: 1, powerSupplyNotes: \"Battery compatible\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Linguistic conversion and support.\" },\n { name: \"Holo Projector\", weight: 1.5, cost: 800, energyUse: 2, powerSupplyNotes: \"Battery compatible\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Short-range holographic projection.\" },\n { name: \"Stasis Box\", weight: 12, cost: 2600, energyUse: 5, powerSupplyNotes: \"Continuous power required\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Suspended animation container.\" },\n { name: \"Reactionless Flycycle\", weight: 35, cost: 4200, energyUse: 4, powerSupplyNotes: \"Vehicle power core\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Single-rider reactionless vehicle.\" },\n { name: \"Field Toolkit\", weight: 2, cost: 180, energyUse: 0, powerSupplyNotes: \"None\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Mechanical and electronics repair kit.\" },\n { name: \"Vac Suit\", weight: 7, cost: 1400, energyUse: 1, powerSupplyNotes: \"Suit battery required\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Pressure and vacuum survival suit.\" },\n { name: \"Air Reprocessor\", weight: 3, cost: 950, energyUse: 2, powerSupplyNotes: \"Continuous power\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Atmosphere recycling support unit.\" },\n { name: \"Signal Beacon\", weight: 1, cost: 140, energyUse: 1, powerSupplyNotes: \"Battery compatible\", isPowerPack: false, energy: { value: 0, max: 0 }, notes: \"Emergency and navigation beacon.\" }\n];\n\nexport const PURSUIT_CATALOG: PursuitCatalogEntry[] = [\n {\n name: \"Artist\",\n rollRange: \"01-07\",\n wealthValue: 0,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Athletics\", \"Computers\", \"Fast Talk\", \"Fine Arts\", \"Hide\", \"Musicianship\", \"Observe\", \"Perform\", \"Psychology\", \"Scent\", \"Theology\"],\n notes: \"Creative specialist pursuit.\"\n },\n {\n name: \"Colonist\",\n rollRange: \"08-27\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Atmospheric Craft\", \"Bargain\", \"Ground Vehicle\", \"Farming\", \"Handgun, Energy\", \"Personal Flyer\", \"Listen\", \"Heavy Weapon, Energy\", \"Observe\", \"Planetology\", \"Search\", \"Track\"],\n notes: \"Settlement and frontier operations pursuit.\"\n },\n {\n name: \"Criminal\",\n rollRange: \"28-29\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Bargain\", \"Computers\", \"Fast Talk\", \"Hide\", \"Personal Flyer\", \"Handgun, Energy\", \"Handgun, Projectile\", \"Sneak\", \"Unarmed Combat\"],\n notes: \"Underground survival pursuit.\"\n },\n {\n name: \"Dilettante\",\n rollRange: \"30\",\n wealthValue: 5,\n occupationPointsPerYear: 15,\n fixedSkills: [],\n notes: \"Any skills allowed; uses 15 occupation points per year.\"\n },\n {\n name: \"Engineer\",\n rollRange: \"31-36\",\n wealthValue: 2,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Astronomy\", \"Chemistry\", \"Computers\", \"Engineering\", \"Ground Vehicle\", \"Mathematics\", \"Personal Flyer\", \"Physics\", \"Planetology\", \"Reaction Drive\", \"Reactionless Drive\", \"Repair\"],\n notes: \"Technical design and maintenance pursuit.\"\n },\n {\n name: \"Entertainer\",\n rollRange: \"37-38\",\n wealthValue: 2,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Athletics\", \"Bargain\", \"Own Language\", \"Musicianship\", \"Observe\", \"Orate\", \"Perform\", \"Psychology\", \"Search\", \"Sneak\"],\n notes: \"Performance and social influence pursuit.\"\n },\n {\n name: \"Explorer\",\n rollRange: \"39-52\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Athletics\", \"Atmospheric Craft\", \"Bargain\", \"Biology\", \"Botany\", \"Emergency Treatment\", \"Engineering\", \"Ground Vehicle\", \"Handgun, Energy\", \"Heavy Weapon, Energy\", \"Hyperdrive\", \"Observe\", \"Planetology\", \"Reactionless Drive\", \"Repair\", \"Search\", \"Track\", \"Zoology\"],\n notes: \"Survey and expedition pursuit.\"\n },\n {\n name: \"Functionary\",\n rollRange: \"53-56\",\n wealthValue: 3,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Bargain\", \"Debate\", \"Fast Talk\", \"History\", \"Law\", \"Observe\", \"Orate\", \"Psychology\", \"Strategy\", \"Theology\"],\n notes: \"Administrative and governance pursuit.\"\n },\n {\n name: \"Journalist/Writer\",\n rollRange: \"57-59\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Anthropology\", \"Bargain\", \"Computers\", \"Debate\", \"History\", \"Fine Arts\", \"Own Language\", \"Law\", \"Observe\", \"Psychology\", \"Search\", \"Sneak\", \"Unarmed Combat\", \"Weapons System\"],\n notes: \"Investigation and reporting pursuit.\"\n },\n {\n name: \"Miner\",\n rollRange: \"60-65\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Atmospheric Craft\", \"Aquatic Vehicle\", \"Bargain\", \"Climb (Custom)\", \"Computers\", \"Emergency Treatment\", \"Engineering\", \"Handgun, Energy\", \"Heavy Weapon, Energy\", \"Observe\", \"Personal Flyer\", \"Planetology\", \"Reaction Drive\", \"Reactionless Drive\", \"Repair\", \"Search\"],\n notes: \"Resource extraction frontier pursuit.\"\n },\n {\n name: \"Pilot\",\n rollRange: \"66-68\",\n wealthValue: 3,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Astronomy\", \"Atmospheric Craft\", \"Computers\", \"Engineering\", \"Hyperdrive\", \"History\", \"Law\", \"Mathematics\", \"Physics\", \"Reaction Drive\", \"Reactionless Drive\", \"Repair\", \"Strategy\", \"Weapons System\"],\n notes: \"Flight and spacecraft operations pursuit.\"\n },\n {\n name: \"Police Officer\",\n rollRange: \"69-76\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Atmospheric Craft\", \"Bargain\", \"Computers\", \"Fast Talk\", \"Emergency Treatment\", \"Handgun, Energy\", \"Handgun, Projectile\", \"Heavy Weapon, Energy\", \"Heavy Weapon, Projectile\", \"Law\", \"Observe\", \"Personal Flyer\", \"Psychology\", \"Search\", \"Unarmed Combat\"],\n notes: \"Law enforcement and response pursuit.\"\n },\n {\n name: \"Politician\",\n rollRange: \"77-78\",\n wealthValue: 3,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Bargain\", \"Computers\", \"Debate\", \"Fast Talk\", \"History\", \"Orate\", \"Own Language\", \"Perform\", \"Psychology\"],\n optionalSkillGroups: [\n {\n choose: 1,\n skills: [\"Engineering\", \"Farming\", \"Second Languages\", \"Strategy\", \"Theology\"]\n }\n ],\n notes: \"Public office and influence pursuit.\"\n },\n {\n name: \"Prisoner\",\n rollRange: \"79-80\",\n wealthValue: 0,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Bargain\", \"Fast Talk\", \"Hide\", \"Observe\", \"Psychology\", \"Search\", \"Sneak\", \"Unarmed Combat\"],\n notes: \"Carceral survival pursuit.\"\n },\n {\n name: \"Scholar\",\n rollRange: \"81-84\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [],\n optionalSkillGroups: [\n {\n choose: 2,\n skills: [\"Anthropology\", \"Astronomy\", \"Biology\", \"Botany\", \"Chemistry\", \"Computers\", \"Engineering\", \"Fine Arts\", \"History\", \"Law\", \"Mathematics\", \"Own Language\", \"Physics\", \"Planetology\", \"Second Languages\", \"Theology\", \"Zoology\"]\n }\n ],\n notes: \"Academic pursuit; pick up to two listed skills.\"\n },\n {\n name: \"Scientific Researcher\",\n rollRange: \"85-94\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [],\n optionalSkillGroups: [\n {\n choose: 3,\n skills: [\"Anthropology\", \"Astronomy\", \"Bargain\", \"Biology\", \"Botany\", \"Chemistry\", \"Computers\", \"Engineering\", \"History\", \"Mathematics\", \"Observe\", \"Personal Flyer\", \"Physics\", \"Planetology\", \"Psychology\", \"Repair\", \"Search\", \"Zoology\"]\n }\n ],\n notes: \"Research pursuit; pick up to three listed skills.\"\n },\n {\n name: \"Soldier\",\n rollRange: \"95-98\",\n wealthValue: 1,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Athletics\", \"Atmospheric Craft\", \"Aquatic Vehicle\", \"Computers\", \"Emergency Treatment\", \"Fast Talk\", \"Ground Vehicle\", \"Handgun, Energy\", \"Handgun, Projectile\", \"Heavy Weapon, Energy\", \"Heavy Weapon, Projectile\"],\n notes: \"Military operations pursuit.\"\n },\n {\n name: \"Zealot\",\n rollRange: \"99-00\",\n wealthValue: 0,\n occupationPointsPerYear: 20,\n fixedSkills: [\"Bargain\", \"Debate\", \"Fast Talk\", \"Fine Arts\", \"Handgun, Energy\", \"Handgun, Projectile\", \"Hide\", \"Own Language\", \"Law\", \"Strategy\", \"Orate\", \"Psychology\", \"Theology\"],\n notes: \"Ideological dedication pursuit.\"\n }\n];\n\nexport function speciesCatalogForGeneration(): SpeciesCatalogEntry[] {\n return SPECIES_CATALOG.filter((entry) => entry.playable);\n}\n\nfunction itemKey(type: string, name: string): string {\n return `${type}:${normalizeSkillName(name)}`;\n}\n\nfunction itemTypeAvailable(type: string): boolean {\n const directTypes = (getDocumentClass(\"Item\") as any)?.TYPES;\n if (Array.isArray(directTypes)) return directTypes.includes(type);\n\n const docTypes = (game as any)?.system?.documentTypes?.Item;\n if (Array.isArray(docTypes)) return docTypes.includes(type);\n if (docTypes && typeof docTypes === \"object\") return Object.prototype.hasOwnProperty.call(docTypes, type);\n\n const configModels = (CONFIG as any)?.Item?.dataModels;\n if (configModels && typeof configModels === \"object\" && Object.prototype.hasOwnProperty.call(configModels, type)) {\n return true;\n }\n\n const gameModels = (game as any)?.model?.Item;\n if (gameModels && typeof gameModels === \"object\" && Object.prototype.hasOwnProperty.call(gameModels, type)) {\n return true;\n }\n\n return false;\n}\n\nfunction toSkillItem(entry: (typeof RINGWORLD_BASE_SKILLS)[number]): CatalogSeed {\n return {\n key: itemKey(\"skill\", entry.name),\n folderKey: \"skills\",\n img: skillIconPath(entry.name) || ICONS.skillDefault,\n data: {\n name: entry.name,\n type: \"skill\",\n system: {\n value: entry.base,\n category: entry.category,\n specialization: entry.specialization ?? \"\"\n }\n }\n };\n}\n\nfunction toSpeciesItem(entry: SpeciesCatalogEntry): CatalogSeed {\n const img = speciesIconPath(entry.name) || ICONS.speciesDefault;\n return {\n key: itemKey(\"species\", entry.name),\n folderKey: \"species\",\n img,\n data: {\n name: entry.name,\n type: \"species\",\n system: {\n playable: entry.playable,\n gravity: entry.gravity,\n homeworld: entry.homeworld,\n modifiers: entry.modifiers,\n notes: entry.notes\n }\n }\n };\n}\n\nfunction toWeaponItem(entry: WeaponCatalogEntry): CatalogSeed {\n const folderKey = entry.attackType === \"melee\" ? \"weaponMelee\" : \"weaponRanged\";\n const img = weaponIconPath(entry.name) || (entry.attackType === \"melee\" ? ICONS.weaponMelee : ICONS.weaponRanged);\n return {\n key: itemKey(\"weapon\", entry.name),\n folderKey,\n img,\n data: {\n name: entry.name,\n type: \"weapon\",\n system: {\n equipped: entry.equipped ?? true,\n stowed: false,\n weight: entry.weight ?? 0,\n linkedSkill: entry.linkedSkill,\n linkedSkillId: \"\",\n attackType: entry.attackType,\n damageFormula: entry.damageFormula,\n range: entry.range,\n energyUsedPerAttack: entry.energyUsedPerAttack,\n ammoUsedPerAttack: entry.ammoUsedPerAttack,\n powerSource: entry.powerSource,\n powerPackId: \"\",\n notes: entry.notes\n }\n }\n };\n}\n\nfunction toArmorItem(entry: ArmorCatalogEntry): CatalogSeed {\n const img = armorIconPath(entry.name) || ICONS.armor;\n return {\n key: itemKey(\"armor\", entry.name),\n folderKey: \"armor\",\n img,\n data: {\n name: entry.name,\n type: \"armor\",\n system: {\n equipped: entry.equipped,\n stowed: false,\n weight: entry.weight ?? 0,\n value: entry.value,\n coverage: entry.coverage,\n perLocation: {\n rLeg: 0,\n lLeg: 0,\n abdomen: 0,\n chest: 0,\n rArm: 0,\n lArm: 0,\n head: 0\n },\n notes: entry.notes\n }\n }\n };\n}\n\nfunction toGearItem(entry: GearCatalogEntry): CatalogSeed {\n const img = gearIconPath(entry.name) || (entry.isPowerPack ? ICONS.powerPackDefault : ICONS.gearDefault);\n return {\n key: itemKey(\"gear\", entry.name),\n folderKey: \"gear\",\n img,\n data: {\n name: entry.name,\n type: \"gear\",\n system: {\n equipped: entry.equipped ?? true,\n stowed: false,\n weight: entry.weight,\n cost: entry.cost,\n energyUse: entry.energyUse,\n powerSupplyNotes: entry.powerSupplyNotes,\n isPowerPack: entry.isPowerPack,\n energy: entry.energy,\n notes: entry.notes\n }\n }\n };\n}\n\nfunction toPursuitItem(entry: PursuitCatalogEntry): CatalogSeed {\n const img = pursuitIconPath(entry.name) || ICONS.pursuitDefault;\n return {\n key: itemKey(\"pursuit\", entry.name),\n folderKey: \"pursuits\",\n img,\n data: {\n name: entry.name,\n type: \"pursuit\",\n system: {\n wealthValue: entry.wealthValue,\n occupationPointsPerYear: entry.occupationPointsPerYear,\n rollRange: entry.rollRange,\n fixedSkills: entry.fixedSkills,\n optionalSkillGroups: entry.optionalSkillGroups ?? [],\n notes: entry.notes\n }\n }\n };\n}\n\nfunction pursuitAsGearNotes(entry: PursuitCatalogEntry): string {\n const fixed = entry.fixedSkills.length ? `Fixed skills: ${entry.fixedSkills.join(\", \")}` : \"Fixed skills: (none)\";\n const optional = (entry.optionalSkillGroups ?? []).map((group) => `Choose ${group.choose}: ${group.skills.join(\", \")}`).join(\" | \");\n const optionalText = optional ? ` Optional: ${optional}.` : \"\";\n return `${entry.notes} ${fixed}.${optionalText} Wealth ${entry.wealthValue}. Occupation points/year ${entry.occupationPointsPerYear}.`;\n}\n\nfunction toPursuitFallbackGearItem(entry: PursuitCatalogEntry): CatalogSeed {\n const img = pursuitIconPath(entry.name) || ICONS.pursuitDefault;\n const itemName = `Pursuit: ${entry.name}`;\n return {\n key: itemKey(\"gear\", itemName),\n folderKey: \"pursuits\",\n img,\n data: {\n name: itemName,\n type: \"gear\",\n system: {\n equipped: false,\n stowed: true,\n weight: 0,\n cost: 0,\n energyUse: 0,\n powerSupplyNotes: \"Pursuit reference\",\n isPowerPack: false,\n energy: { value: 0, max: 0 },\n notes: pursuitAsGearNotes(entry)\n },\n flags: {\n ringworld: {\n pursuitData: {\n pursuitName: entry.name,\n rollRange: entry.rollRange,\n wealthValue: entry.wealthValue,\n occupationPointsPerYear: entry.occupationPointsPerYear,\n fixedSkills: entry.fixedSkills,\n optionalSkillGroups: entry.optionalSkillGroups ?? []\n }\n }\n }\n }\n };\n}\n\nasync function getOrCreateItemFolder(name: string, parentId: string | null): Promise {\n const existing = game.folders.find(\n (folder: any) => folder.type === \"Item\" && folder.name === name && (folder.folder?.id ?? null) === parentId\n );\n if (existing) return existing;\n\n return Folder.create({\n name,\n type: \"Item\",\n folder: parentId,\n sorting: \"a\"\n });\n}\n\nasync function ensureCatalogFolders(): Promise> {\n const root = await getOrCreateItemFolder(\"Ringworld Catalog\", null);\n const species = await getOrCreateItemFolder(\"Species\", root.id);\n const skills = await getOrCreateItemFolder(\"Skills\", root.id);\n const pursuits = await getOrCreateItemFolder(\"Pursuits\", root.id);\n const gear = await getOrCreateItemFolder(\"Gear\", root.id);\n const armor = await getOrCreateItemFolder(\"Armor\", gear.id);\n const weapons = await getOrCreateItemFolder(\"Weapons\", root.id);\n const weaponMelee = await getOrCreateItemFolder(\"Melee Weapons\", weapons.id);\n const weaponRanged = await getOrCreateItemFolder(\"Ranged Weapons\", weapons.id);\n\n return {\n species: species.id,\n skills: skills.id,\n pursuits: pursuits.id,\n gear: gear.id,\n armor: armor.id,\n weaponMelee: weaponMelee.id,\n weaponRanged: weaponRanged.id\n };\n}\n\nfunction buildAllSeeds(): CatalogSeed[] {\n const canUsePursuitType = itemTypeAvailable(\"pursuit\");\n return [\n ...SPECIES_CATALOG.map(toSpeciesItem),\n ...RINGWORLD_BASE_SKILLS.map(toSkillItem),\n ...(canUsePursuitType ? PURSUIT_CATALOG.map(toPursuitItem) : PURSUIT_CATALOG.map(toPursuitFallbackGearItem)),\n ...WEAPON_CATALOG.map(toWeaponItem),\n ...ARMOR_CATALOG.map(toArmorItem),\n ...GEAR_CATALOG.map(toGearItem)\n ];\n}\n\nexport async function seedWorldCatalog(options?: { notify?: boolean }): Promise<{ added: number; updated: number }> {\n if (!game.user?.isGM) return { added: 0, updated: 0 };\n\n const folderIds = await ensureCatalogFolders();\n const seeds = buildAllSeeds();\n\n const existing = new Map(\n game.items.map((item: any) => [itemKey(item.type, item.name), item])\n );\n\n const createPayload: any[] = [];\n const updatePayload: any[] = [];\n\n for (const seed of seeds) {\n const folderId = folderIds[seed.folderKey];\n const existingItem = existing.get(seed.key);\n\n if (!existingItem) {\n createPayload.push({\n ...seed.data,\n img: seed.img,\n folder: folderId,\n flags: {\n ringworld: {\n catalog: true,\n catalogKey: seed.key\n }\n }\n });\n continue;\n }\n\n const update: Record = { _id: existingItem.id };\n\n if ((existingItem.folder?.id ?? null) !== folderId) {\n update.folder = folderId;\n }\n\n if (String(existingItem.img || \"\") !== seed.img) {\n update.img = seed.img;\n }\n\n if (existingItem.flags?.ringworld?.catalog !== true) {\n update[\"flags.ringworld.catalog\"] = true;\n }\n\n if (existingItem.flags?.ringworld?.catalogKey !== seed.key) {\n update[\"flags.ringworld.catalogKey\"] = seed.key;\n }\n\n const existingSystem = foundry.utils.deepClone(existingItem.system ?? {});\n const mergedSystem = foundry.utils.mergeObject(existingSystem, seed.data.system ?? {}, {\n inplace: false,\n insertKeys: true,\n insertValues: true,\n overwrite: false\n });\n const systemDiff = foundry.utils.diffObject(existingSystem, mergedSystem);\n if (Object.keys(systemDiff).length > 0) {\n update.system = mergedSystem;\n }\n\n if (Object.keys(update).length > 1) {\n updatePayload.push(update);\n }\n }\n\n if (createPayload.length) {\n await Item.createDocuments(createPayload as any[]);\n }\n\n if (updatePayload.length) {\n await Item.updateDocuments(updatePayload as any[]);\n }\n\n if (options?.notify) {\n ui.notifications?.info(`Ringworld catalog synced: +${createPayload.length} new, ${updatePayload.length} updated.`);\n }\n\n return { added: createPayload.length, updated: updatePayload.length };\n}\n\nexport function getPlayableSpeciesFromWorld(): any[] {\n return game.items.filter((item: any) => item.type === \"species\" && item.system.playable);\n}\n\nexport function ringworldSystemSettingKey(key: string): string {\n return `${SYSTEM_ID}.${key}`;\n}\n","import { speciesCatalogForGeneration, getPlayableSpeciesFromWorld } from \"./world-catalog\";\nimport { evaluateRollWithDice } from \"./utils/dice\";\n\ntype GravityType = \"light\" | \"normal\" | \"heavy\";\n\ninterface CharacteristicSet {\n str: number;\n mas: number;\n con: number;\n int: number;\n pow: number;\n dex: number;\n app: number;\n edu: number;\n age: number;\n}\n\nfunction clampScore(value: number): number {\n return Math.max(1, Math.min(99, Math.floor(value)));\n}\n\nasync function rollTotal(formula: string): Promise {\n const roll = await evaluateRollWithDice(formula);\n return Number(roll.total ?? 0);\n}\n\nasync function rollEdu(): Promise {\n let edu = 10;\n while (true) {\n const die = await rollTotal(\"1d6\");\n edu += die;\n if (die <= 4) break;\n }\n return clampScore(edu);\n}\n\nasync function rollByGravity(gravity: GravityType): Promise {\n const gravityFormulas = {\n light: { str: \"2d6+3\", mas: \"2d6+3\", con: \"2d6+6\", dex: \"2d6+9\" },\n normal: { str: \"2d6+6\", mas: \"2d6+6\", con: \"2d6+6\", dex: \"2d6+6\" },\n heavy: { str: \"2d6+9\", mas: \"2d6+9\", con: \"2d6+3\", dex: \"2d6+6\" }\n }[gravity];\n\n return {\n str: await rollTotal(gravityFormulas.str),\n mas: await rollTotal(gravityFormulas.mas),\n con: await rollTotal(gravityFormulas.con),\n int: await rollTotal(\"2d6+6\"),\n pow: await rollTotal(\"2d6+6\"),\n dex: await rollTotal(gravityFormulas.dex),\n app: await rollTotal(\"2d6+6\"),\n edu: await rollEdu(),\n age: await rollTotal(\"1d20+16\")\n };\n}\n\nfunction randomPick(arr: T[]): T {\n return arr[Math.floor(Math.random() * arr.length)];\n}\n\nfunction randomExplorerName(speciesName: string): string {\n const starts = [\"Ar\", \"Bel\", \"Cor\", \"Den\", \"Ela\", \"Far\", \"Gal\", \"Hel\", \"Ira\", \"Jun\", \"Kor\", \"Lys\", \"Mor\", \"Nyr\", \"Or\", \"Pra\", \"Qua\", \"Rin\", \"Sol\", \"Tor\", \"Uri\", \"Val\", \"Wen\", \"Xel\", \"Yor\", \"Zen\"];\n const ends = [\"an\", \"el\", \"is\", \"or\", \"us\", \"a\", \"en\", \"ion\", \"eth\", \"al\", \"ir\", \"os\"];\n return `${randomPick(starts)}${randomPick(ends)} (${speciesName})`;\n}\n\nfunction applySpeciesModifiers(base: CharacteristicSet, speciesSystem: any): CharacteristicSet {\n const mods = speciesSystem?.modifiers ?? {};\n return {\n str: clampScore(base.str + Number(mods.str || 0)),\n mas: clampScore(base.mas + Number(mods.mas || 0)),\n con: clampScore(base.con + Number(mods.con || 0)),\n int: clampScore(base.int + Number(mods.int || 0)),\n pow: clampScore(base.pow + Number(mods.pow || 0)),\n dex: clampScore(base.dex + Number(mods.dex || 0)),\n app: clampScore(base.app + Number(mods.app || 0)),\n edu: clampScore(base.edu + Number(mods.edu || 0)),\n age: clampScore(base.age + Number(mods.age || 0))\n };\n}\n\nexport async function rollCharacteristicsForActor(actor: any): Promise {\n const gravity = String(actor.system?.profile?.gravity || \"normal\") as GravityType;\n const gravityType: GravityType = gravity === \"light\" || gravity === \"heavy\" ? gravity : \"normal\";\n\n const currentSpeciesName = String(actor.system?.profile?.species || \"\");\n const worldSpecies = getPlayableSpeciesFromWorld();\n const speciesItem = worldSpecies.find((item: any) => item.name === currentSpeciesName);\n const builtIn = speciesCatalogForGeneration().find((entry) => entry.name === currentSpeciesName);\n const speciesSystem = speciesItem?.system ?? builtIn ?? null;\n\n const rolled = await rollByGravity(gravityType);\n const finalValues = applySpeciesModifiers(rolled, speciesSystem);\n\n await actor.update({\n \"system.characteristics.str\": finalValues.str,\n \"system.characteristics.mas\": finalValues.mas,\n \"system.characteristics.con\": finalValues.con,\n \"system.characteristics.int\": finalValues.int,\n \"system.characteristics.pow\": finalValues.pow,\n \"system.characteristics.dex\": finalValues.dex,\n \"system.characteristics.app\": finalValues.app,\n \"system.characteristics.edu\": finalValues.edu,\n \"system.characteristics.age\": finalValues.age,\n \"system.profile.chronologicalAge\": finalValues.age,\n \"system.profile.physiologicalAge\": finalValues.age\n });\n\n const ownLanguage = actor.items.find((item: any) => item.type === \"skill\" && item.name.toLowerCase() === \"own language\");\n if (ownLanguage) {\n await actor.updateEmbeddedDocuments(\"Item\", [{\n _id: ownLanguage.id,\n \"system.value\": Math.min(99, finalValues.int * 5)\n }]);\n }\n}\n\nexport async function applyStarterKit(actor: any): Promise {\n const starterNames = [\n [\"weapon\", \"Laser Pistol\"],\n [\"weapon\", \"Variable Sword\"],\n [\"armor\", \"Soft Combat Vest\"],\n [\"gear\", \"Power Pack R20\"],\n [\"gear\", \"Medkit\"],\n [\"gear\", \"Survey Scanner\"]\n ];\n\n const worldItems = game.items;\n const actorExisting = new Set(actor.items.map((item: any) => `${item.type}:${item.name.toLowerCase()}`));\n const createPayload: any[] = [];\n\n for (const [type, name] of starterNames) {\n if (actorExisting.has(`${type}:${String(name).toLowerCase()}`)) continue;\n const worldItem = worldItems.find((item: any) => item.type === type && item.name === name);\n if (!worldItem) continue;\n createPayload.push(worldItem.toObject());\n }\n\n if (createPayload.length) {\n await actor.createEmbeddedDocuments(\"Item\", createPayload);\n }\n}\n\nexport async function randomizeExplorerActor(actor: any): Promise {\n const worldSpecies = getPlayableSpeciesFromWorld();\n const builtInSpecies = speciesCatalogForGeneration();\n\n const pickedWorldSpecies = worldSpecies.length ? randomPick(worldSpecies) : null;\n const pickedBuiltInSpecies = !pickedWorldSpecies ? randomPick(builtInSpecies) : null;\n\n const speciesChoice = pickedWorldSpecies?.system ?? pickedBuiltInSpecies;\n const speciesName = pickedWorldSpecies?.name ?? pickedBuiltInSpecies?.name ?? \"Human\";\n\n const gravity = String(speciesChoice.gravity || \"normal\") as GravityType;\n const homeworld = String(speciesChoice.homeworld || \"Ringworld\");\n\n await actor.update({\n name: randomExplorerName(speciesName),\n \"system.profile.species\": speciesName,\n \"system.profile.gravity\": gravity,\n \"system.profile.homeworld\": homeworld\n });\n\n await rollCharacteristicsForActor(actor);\n await actor.ensureBaseSkills?.();\n await applyStarterKit(actor);\n}\n\nexport async function createRandomExplorer(): Promise {\n const ActorClass = getDocumentClass(\"Actor\");\n const actor = await ActorClass.create({\n name: \"Random Explorer\",\n type: \"explorer\"\n });\n\n await randomizeExplorerActor(actor);\n return actor;\n}\n","import { recomputeHitPointMaximums, rollSkillForActor, rollStandaloneHitLocation, useWeaponWorkflow } from \"../mechanics\";\nimport { normalizeSkillName, RINGWORLD_BASE_SKILLS } from \"../data-base-skills\";\nimport { randomizeExplorerActor, rollCharacteristicsForActor } from \"../character-tools\";\nimport { skillIconPath } from \"../skill-icons\";\n\ntype DerivedCheckKey = \"health\" | \"reasoning\" | \"luck\" | \"dodge\";\ntype SpeciesModifierKey = \"str\" | \"mas\" | \"con\" | \"int\" | \"pow\" | \"dex\" | \"app\" | \"edu\" | \"age\";\nconst SPECIES_MODIFIER_KEYS: SpeciesModifierKey[] = [\"str\", \"mas\", \"con\", \"int\", \"pow\", \"dex\", \"app\", \"edu\", \"age\"];\n\nfunction num(value: unknown, fallback = 0): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : fallback;\n}\n\nfunction hasValue(value: unknown): boolean {\n return value !== null && value !== undefined && String(value).trim() !== \"\";\n}\n\nfunction clampCharacteristic(value: number): number {\n return Math.max(1, Math.min(99, Math.floor(value)));\n}\n\nfunction computeDamageMod(str: number, mas: number): string {\n const total = str + mas;\n if (total <= 12) return \"-1d6\";\n if (total <= 24) return \"-1d4\";\n if (total <= 32) return \"0\";\n if (total <= 40) return \"+1d4\";\n if (total <= 56) return \"+1d6\";\n if (total <= 72) return \"+2d6\";\n return \"+3d6\";\n}\n\nfunction computeDerivedRolls(characteristics: any): Record {\n const con = num(characteristics?.con, 10);\n const int = num(characteristics?.int, 10);\n const pow = num(characteristics?.pow, 10);\n const dex = num(characteristics?.dex, 10);\n\n return {\n health: Math.max(0, Math.min(99, con * 5)),\n reasoning: Math.max(0, Math.min(99, int * 5)),\n luck: Math.max(0, Math.min(99, pow * 5)),\n dodge: Math.max(0, Math.min(99, dex * 5))\n };\n}\n\nfunction computeRootMaxes(characteristics: any): Record {\n const str = num(characteristics?.str, 10);\n const dex = num(characteristics?.dex, 10);\n const app = num(characteristics?.app, 10);\n const edu = num(characteristics?.edu, 10);\n const int = num(characteristics?.int, 10);\n const pow = num(characteristics?.pow, 10);\n const con = num(characteristics?.con, 10);\n\n return {\n agility: Math.max(0, str + dex),\n communication: Math.max(0, int + app),\n knowledge: Math.max(0, int + edu),\n perception: Math.max(0, pow + con),\n technical: Math.max(0, dex + int)\n };\n}\n\nfunction computeActionRanking(dex: number): number {\n if (dex <= 4) return 7;\n if (dex <= 8) return 6;\n if (dex <= 12) return 5;\n if (dex <= 16) return 4;\n if (dex <= 20) return 3;\n if (dex <= 24) return 2;\n return 1;\n}\n\nfunction gravityLoadMultiplier(gravity: string): number {\n if (gravity === \"light\") return 0.5;\n if (gravity === \"heavy\") return 2;\n return 1;\n}\n\nfunction contributesToEncumbrance(item: any): boolean {\n if (item.type !== \"weapon\" && item.type !== \"armor\" && item.type !== \"gear\") return false;\n return item.system?.stowed !== true;\n}\n\nfunction itemWeightKg(item: any): number {\n return Math.max(0, num(item.system?.weight, 0));\n}\n\nfunction computeEncumbrance(system: any, items: any[]): {\n rawLoadKg: number;\n effectiveLoadKg: number;\n limitKg: number;\n overLimitKg: number;\n actionRankingPenalty: number;\n overLimit: boolean;\n currentMove: number;\n currentRun: number;\n gravityMultiplier: number;\n} {\n const gravity = String(system?.profile?.gravity || \"normal\");\n const gravityMultiplier = gravityLoadMultiplier(gravity);\n const equippedLoadKg = items\n .filter((item) => item.type === \"weapon\" || item.type === \"armor\" || item.type === \"gear\")\n .filter(contributesToEncumbrance)\n .reduce((sum, item) => sum + itemWeightKg(item), 0);\n\n const rawLoadKg = Math.max(0, equippedLoadKg);\n const effectiveLoadKg = rawLoadKg * gravityMultiplier;\n const limitKg = Math.max(0, num(system?.characteristics?.str, 0));\n const overLimitKg = Math.max(0, effectiveLoadKg - limitKg);\n const actionRankingPenalty = overLimitKg > 0 ? Math.ceil(overLimitKg / 10) : 0;\n const overLimit = overLimitKg > 0;\n\n const baseMove = Math.max(1, num(system?.derived?.move, 3));\n const baseRun = Math.max(1, num(system?.derived?.run, baseMove * 2));\n const currentMove = overLimit ? 2 : baseMove;\n const currentRun = overLimit ? 4 : baseRun;\n\n return {\n rawLoadKg,\n effectiveLoadKg,\n limitKg,\n overLimitKg,\n actionRankingPenalty,\n overLimit,\n currentMove,\n currentRun,\n gravityMultiplier\n };\n}\n\nexport class RingworldActor extends Actor {\n prepareDerivedData(): void {\n super.prepareDerivedData();\n recomputeHitPointMaximums(this as any);\n\n if (this.type !== \"explorer\" && this.type !== \"creature\") return;\n\n const system = (this as any).system ?? {};\n const characteristics = system.characteristics ?? {};\n const profile = system.profile ?? {};\n\n profile.chronologicalAge = num(profile.chronologicalAge, num(characteristics.age, 25));\n profile.physiologicalAge = num(profile.physiologicalAge, profile.chronologicalAge);\n system.profile = profile;\n\n system.combat = system.combat ?? {};\n if (!hasValue(system.combat.damageMod)) {\n system.combat.damageMod = computeDamageMod(num(characteristics.str, 10), num(characteristics.mas, 10));\n }\n\n system.derivedRolls = system.derivedRolls ?? {};\n const derivedRollDefaults = computeDerivedRolls(characteristics);\n for (const key of Object.keys(derivedRollDefaults) as DerivedCheckKey[]) {\n system.derivedRolls[key] = num(system.derivedRolls[key], derivedRollDefaults[key]);\n }\n\n system.skillRootMax = system.skillRootMax ?? {};\n const rootMaxDefaults = computeRootMaxes(characteristics);\n for (const [key, value] of Object.entries(rootMaxDefaults)) {\n system.skillRootMax[key] = value;\n }\n\n system.derived = system.derived ?? {};\n system.derived.actionRanking = computeActionRanking(num(characteristics.dex, 10));\n system.derived.move = num(system.derived.move, 3);\n system.derived.run = num(system.derived.run, Math.max(1, num(system.derived.move, 3) * 2));\n\n system.encumbrance = system.encumbrance ?? {};\n const encumbrance = computeEncumbrance(system, this.items as any[]);\n system.encumbrance.rawLoadKg = encumbrance.rawLoadKg;\n system.encumbrance.effectiveLoadKg = encumbrance.effectiveLoadKg;\n system.encumbrance.limitKg = encumbrance.limitKg;\n system.encumbrance.overLimitKg = encumbrance.overLimitKg;\n system.encumbrance.actionRankingPenalty = encumbrance.actionRankingPenalty;\n system.encumbrance.overLimit = encumbrance.overLimit;\n system.encumbrance.gravityMultiplier = encumbrance.gravityMultiplier;\n system.derived.currentMove = encumbrance.currentMove;\n system.derived.currentRun = encumbrance.currentRun;\n\n system.finance = system.finance ?? { money: 0, inBank: 0 };\n system.finance.money = num(system.finance.money, 0);\n system.finance.inBank = num(system.finance.inBank, 0);\n\n system.notes = system.notes ?? { defects: \"\", habits: \"\" };\n if (typeof system.sheetToolsUnlocked !== \"boolean\") {\n system.sheetToolsUnlocked = false;\n }\n system.characterBuilder = system.characterBuilder ?? {\n pursuits: [],\n allocations: {},\n baseBySkillId: {}\n };\n system.unconsciousLevel = num(system.unconsciousLevel, 0);\n }\n\n get skills(): any[] {\n return this.items.filter((item) => item.type === \"skill\");\n }\n\n get weapons(): any[] {\n return this.items.filter((item) => item.type === \"weapon\");\n }\n\n get armors(): any[] {\n return this.items.filter((item) => item.type === \"armor\");\n }\n\n get gears(): any[] {\n return this.items.filter((item) => item.type === \"gear\");\n }\n\n async rollSkillById(skillId: string): Promise {\n const skill = this.items.get(skillId);\n if (!skill || skill.type !== \"skill\") return;\n await rollSkillForActor(this, skill.name, Number((skill as any).system.value ?? 0));\n }\n\n async rollHitLocation(): Promise {\n await rollStandaloneHitLocation(this);\n }\n\n async useWeaponById(weaponId: string): Promise {\n const weapon = this.items.get(weaponId);\n if (!weapon || weapon.type !== \"weapon\") return;\n await useWeaponWorkflow(this, weapon);\n }\n\n async ensureBaseSkills(): Promise {\n if (this.type !== \"explorer\" && this.type !== \"creature\") return 0;\n\n const existing = new Set(\n this.items\n .filter((item) => item.type === \"skill\")\n .map((item) => normalizeSkillName(item.name))\n );\n\n const toCreate = RINGWORLD_BASE_SKILLS\n .filter((skill) => !existing.has(normalizeSkillName(skill.name)))\n .map((skill) => ({\n name: skill.name,\n type: \"skill\",\n img: skillIconPath(skill.name),\n system: {\n value: skill.base,\n category: skill.category,\n specialization: skill.specialization ?? \"\"\n }\n }));\n\n if (!toCreate.length) return 0;\n await this.createEmbeddedDocuments(\"Item\", toCreate as any[]);\n return toCreate.length;\n }\n\n async rollCharacteristics(): Promise {\n if (this.type !== \"explorer\") return;\n await rollCharacteristicsForActor(this as any);\n }\n\n async randomizeCharacter(): Promise {\n if (this.type !== \"explorer\") return;\n await randomizeExplorerActor(this as any);\n }\n\n async applySpeciesItem(speciesData: any): Promise {\n if (this.type !== \"explorer\" && this.type !== \"creature\") return;\n\n const speciesName = String(speciesData?.name || \"\").trim();\n const speciesSystem = speciesData?.system ?? {};\n const incomingMods = speciesSystem?.modifiers ?? {};\n const appliedState = (this as any).system?.speciesApplied ?? {};\n const previousMods = appliedState?.modifiers ?? {};\n\n const currentCharacteristics = (this as any).system?.characteristics ?? {};\n const currentProfile = (this as any).system?.profile ?? {};\n const update: Record = {};\n const normalizedMods: Record = {\n str: 0,\n mas: 0,\n con: 0,\n int: 0,\n pow: 0,\n dex: 0,\n app: 0,\n edu: 0,\n age: 0\n };\n\n for (const key of SPECIES_MODIFIER_KEYS) {\n const prev = num(previousMods?.[key], 0);\n const next = num(incomingMods?.[key], 0);\n normalizedMods[key] = next;\n\n const current = num(currentCharacteristics?.[key], key === \"age\" ? 25 : 10);\n const base = current - prev;\n update[`system.characteristics.${key}`] = clampCharacteristic(base + next);\n }\n\n const agePrev = num(previousMods?.age, 0);\n const ageNext = normalizedMods.age;\n const chronoCurrent = num(currentProfile?.chronologicalAge, num(currentCharacteristics?.age, 25));\n const physCurrent = num(currentProfile?.physiologicalAge, chronoCurrent);\n update[\"system.profile.chronologicalAge\"] = clampCharacteristic((chronoCurrent - agePrev) + ageNext);\n update[\"system.profile.physiologicalAge\"] = clampCharacteristic((physCurrent - agePrev) + ageNext);\n\n if (speciesName) update[\"system.profile.species\"] = speciesName;\n if (speciesSystem?.gravity) update[\"system.profile.gravity\"] = String(speciesSystem.gravity);\n if (speciesSystem?.homeworld !== undefined) update[\"system.profile.homeworld\"] = String(speciesSystem.homeworld || \"\");\n\n update[\"system.speciesApplied\"] = {\n name: speciesName,\n modifiers: normalizedMods\n };\n\n await this.update(update);\n }\n\n async rollDerivedCheck(key: DerivedCheckKey): Promise {\n if (this.type !== \"explorer\" && this.type !== \"creature\") return;\n\n const labels: Record = {\n health: \"Health Roll\",\n reasoning: \"Reasoning Roll\",\n luck: \"Luck Roll\",\n dodge: \"Dodge Roll\"\n };\n\n const defaults = computeDerivedRolls((this as any).system?.characteristics ?? {});\n const target = num((this as any).system?.derivedRolls?.[key], defaults[key]);\n await rollSkillForActor(this, labels[key], target);\n }\n}\n","import { rollSkillForActor, useWeaponWorkflow } from \"../mechanics\";\n\nexport class RingworldItem extends Item {\n async rollSkill(): Promise {\n if (this.type !== \"skill\") return;\n\n const actor = this.actor;\n if (!actor) {\n ui.notifications?.warn(\"Skill must be owned by an Actor to roll.\");\n return;\n }\n\n await rollSkillForActor(actor, this.name, Number((this as any).system.value ?? 0));\n }\n\n async useWeapon(): Promise {\n if (this.type !== \"weapon\") return;\n\n const actor = this.actor;\n if (!actor) {\n ui.notifications?.warn(\"Weapon must be owned by an Actor to use.\");\n return;\n }\n\n await useWeaponWorkflow(actor, this);\n }\n}\n","import { type HitLocationSlug } from \"../../constants\";\nimport {\n BASE_SKILL_CATEGORIES,\n baseSkillNameSet,\n normalizeSkillName,\n RINGWORLD_BASE_SKILLS\n} from \"../../data-base-skills\";\nimport { baseSkillByName, DEFAULT_SKILL_ICON, resolveSkillIcon, skillIconPath } from \"../../skill-icons\";\nimport { getActorHitLocationSchema, listHitLocationSchemas } from \"../../hit-location-schemas\";\nimport { PURSUIT_CATALOG } from \"../../world-catalog\";\nimport { outcomeLabel, rollPercentile } from \"../../utils/percentile\";\nimport { postSkillRollCard } from \"../../chat\";\n\nconst BaseActorSheet = (foundry as any).appv1.sheets.ActorSheet;\n\nconst ROOT_KEY_BY_CATEGORY: Record = {\n Agility: \"agility\",\n Communication: \"communication\",\n Knowledge: \"knowledge\",\n Perception: \"perception\",\n Technical: \"technical\"\n};\n\nconst ROOT_FORMULA_BY_CATEGORY: Record = {\n Agility: \"STR + DEX\",\n Communication: \"INT + APP\",\n Knowledge: \"INT + EDU\",\n Perception: \"POW + CON\",\n Technical: \"DEX + INT\"\n};\n\nconst EXCLUDED_EDUCATION_SKILLS = new Set([\n normalizeSkillName(\"Archaic Melee Weapon\"),\n normalizeSkillName(\"Archaic Ranged Weapons\")\n]);\n\nconst HYPERDRIVE_KEY = normalizeSkillName(\"Hyperdrive\");\n\ninterface BuilderAllocation {\n education: number;\n pursuits: number;\n special: number;\n}\n\ninterface BuilderPursuitSelection {\n name: string;\n years: number;\n}\n\ninterface BuilderState {\n pursuits: BuilderPursuitSelection[];\n allocations: Record;\n baseBySkillId: Record;\n}\n\ninterface BuilderPool {\n total: number;\n spent: number;\n remaining: number;\n}\n\ninterface BuilderSkillMeta {\n allocation: BuilderAllocation;\n totalAllocated: number;\n baseValue: number;\n rootCap: number;\n maxAllocation: number;\n allowsEducation: boolean;\n allowsPursuits: boolean;\n allowsSpecial: boolean;\n}\n\ninterface BuilderContext {\n state: BuilderState;\n education: BuilderPool;\n pursuits: BuilderPool;\n special: BuilderPool;\n pursuitYears: BuilderPool;\n pursuitOptions: string[];\n pursuitAllowedSkills: Set;\n bySkillId: Map;\n}\n\nfunction byName(a: any, b: any): number {\n return a.name.localeCompare(b.name);\n}\n\nfunction num(value: unknown, fallback = 0): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : fallback;\n}\n\nfunction toInt(value: unknown, fallback = 0): number {\n return Math.max(0, Math.floor(num(value, fallback)));\n}\n\nfunction normalizeAllocation(raw: any): BuilderAllocation {\n return {\n education: toInt(raw?.education, 0),\n pursuits: toInt(raw?.pursuits, 0),\n special: toInt(raw?.special, 0)\n };\n}\n\nfunction normalizePursuitSelection(raw: any): BuilderPursuitSelection | null {\n const name = String(raw?.name || \"\").trim();\n const years = toInt(raw?.years, 0);\n if (!name || years <= 0) return null;\n return { name, years };\n}\n\nfunction normalizeBuilderState(raw: any): BuilderState {\n const pursuits = Array.isArray(raw?.pursuits)\n ? raw.pursuits.map((entry: any) => normalizePursuitSelection(entry)).filter((entry: any) => Boolean(entry))\n : [];\n\n const allocations: Record = {};\n if (raw?.allocations && typeof raw.allocations === \"object\") {\n for (const [skillId, allocation] of Object.entries(raw.allocations)) {\n if (!skillId) continue;\n const normalized = normalizeAllocation(allocation);\n if (normalized.education + normalized.pursuits + normalized.special <= 0) continue;\n allocations[skillId] = normalized;\n }\n }\n\n const baseBySkillId: Record = {};\n if (raw?.baseBySkillId && typeof raw.baseBySkillId === \"object\") {\n for (const [skillId, value] of Object.entries(raw.baseBySkillId)) {\n if (!skillId) continue;\n baseBySkillId[skillId] = Math.max(0, toInt(value, 0));\n }\n }\n\n return {\n pursuits,\n allocations,\n baseBySkillId\n };\n}\n\nfunction isEducationEligible(skillName: string): boolean {\n return !EXCLUDED_EDUCATION_SKILLS.has(normalizeSkillName(skillName));\n}\n\nfunction isSpecialEligible(skillName: string): boolean {\n return normalizeSkillName(skillName) !== HYPERDRIVE_KEY;\n}\n\nfunction buildPursuitAllowedSkillSet(pursuits: BuilderPursuitSelection[]): Set {\n const entriesByName = new Map(\n PURSUIT_CATALOG.map((entry) => [normalizeSkillName(entry.name), entry])\n );\n const allowed = new Set();\n\n for (const pursuit of pursuits) {\n const entry = entriesByName.get(normalizeSkillName(pursuit.name));\n if (!entry) continue;\n\n for (const skillName of entry.fixedSkills || []) {\n allowed.add(normalizeSkillName(skillName));\n }\n\n for (const group of entry.optionalSkillGroups || []) {\n for (const skillName of group.skills || []) {\n allowed.add(normalizeSkillName(skillName));\n }\n }\n }\n\n return allowed;\n}\n\nfunction computeBuilderPools(characteristics: any): {\n educationTotal: number;\n pursuitsTotal: number;\n specialTotal: number;\n pursuitYearsTotal: number;\n} {\n const age = toInt(characteristics?.age, 0);\n const edu = toInt(characteristics?.edu, 0);\n const int = toInt(characteristics?.int, 0);\n\n const educationTotal = Math.max(0, (edu - 10) * 20);\n const pursuitYearsTotal = Math.max(0, age - edu - 6);\n const pursuitsTotal = pursuitYearsTotal * 20;\n const specialTotal = Math.max(0, int * 10);\n\n return {\n educationTotal,\n pursuitsTotal,\n specialTotal,\n pursuitYearsTotal\n };\n}\n\nfunction buildBuilderContext(actor: any, skills: any[]): BuilderContext {\n const pools = computeBuilderPools(actor.system?.characteristics ?? {});\n const state = normalizeBuilderState(actor.system?.characterBuilder ?? {});\n\n const pursuitOptions = PURSUIT_CATALOG.map((entry) => entry.name).sort((a, b) => a.localeCompare(b));\n const pursuitAllowedSkills = buildPursuitAllowedSkillSet(state.pursuits);\n\n const bySkillId = new Map();\n\n let educationSpent = 0;\n let pursuitsSpent = 0;\n let specialSpent = 0;\n\n for (const skill of skills) {\n const allocation = normalizeAllocation(state.allocations[skill.id]);\n const totalAllocated = allocation.education + allocation.pursuits + allocation.special;\n\n const baseFromState = state.baseBySkillId[skill.id];\n const baseFromDefinition = baseSkillByName(skill.name)?.base;\n const inferredBase = Math.max(0, num(skill.system?.value, 0) - totalAllocated);\n const baseValue = Math.max(\n 0,\n Number.isFinite(baseFromState)\n ? baseFromState\n : (baseFromDefinition ?? inferredBase)\n );\n\n const rootKey = ROOT_KEY_BY_CATEGORY[String(skill.system?.category || \"\")] ?? \"\";\n const rootCap = rootKey ? Math.max(0, toInt(actor.system?.skillRootMax?.[rootKey], 0)) : 99;\n const maxAllocation = Math.max(0, rootCap - baseValue);\n\n const normalizedSkillName = normalizeSkillName(skill.name);\n const allowsEducation = isEducationEligible(skill.name);\n const allowsPursuits = pursuitAllowedSkills.has(normalizedSkillName);\n const allowsSpecial = isSpecialEligible(skill.name);\n\n bySkillId.set(skill.id, {\n allocation,\n totalAllocated,\n baseValue,\n rootCap,\n maxAllocation,\n allowsEducation,\n allowsPursuits,\n allowsSpecial\n });\n\n educationSpent += allocation.education;\n pursuitsSpent += allocation.pursuits;\n specialSpent += allocation.special;\n }\n\n const pursuitYearsAllocated = state.pursuits.reduce((sum, row) => sum + row.years, 0);\n\n return {\n state,\n education: {\n total: pools.educationTotal,\n spent: educationSpent,\n remaining: pools.educationTotal - educationSpent\n },\n pursuits: {\n total: pools.pursuitsTotal,\n spent: pursuitsSpent,\n remaining: pools.pursuitsTotal - pursuitsSpent\n },\n special: {\n total: pools.specialTotal,\n spent: specialSpent,\n remaining: pools.specialTotal - specialSpent\n },\n pursuitYears: {\n total: pools.pursuitYearsTotal,\n spent: pursuitYearsAllocated,\n remaining: pools.pursuitYearsTotal - pursuitYearsAllocated\n },\n pursuitOptions,\n pursuitAllowedSkills,\n bySkillId\n };\n}\n\nfunction getItemArmorAtLocation(item: any, slug: string): number {\n if (item.type !== \"armor\" || !item.system?.equipped || item.system?.stowed) return 0;\n if (!item.system?.coverage?.[slug]) return 0;\n\n const specific = num(item.system?.perLocation?.[slug], 0);\n if (specific > 0) return specific;\n return num(item.system?.value, 0);\n}\n\nfunction getTotalArmorAtLocation(actor: any, slug: string): number {\n const base = num(actor.system?.hitLocations?.[slug]?.armor, 0);\n const fromItems = actor.items\n .filter((item: any) => item.type === \"armor\")\n .reduce((sum: number, item: any) => sum + getItemArmorAtLocation(item, slug), 0);\n return base + fromItems;\n}\n\nfunction getPreparedHitLocation(actor: any, slug: string, rollRange: string): any {\n const locationConfig = getActorHitLocationSchema(actor).entries.find((entry) => entry.slug === slug);\n const source = actor.system?.hitLocations?.[slug] ?? {};\n const hp = source.hp ?? {};\n const rawLabel = String(source.label ?? \"\").trim();\n return {\n slug,\n rollRange,\n label: rawLabel || String(locationConfig?.label || slug),\n armor: num(source.armor, 0),\n totalArmor: getTotalArmorAtLocation(actor, slug),\n hp: {\n value: num(hp.value, 0),\n max: num(hp.max, 0)\n }\n };\n}\n\nfunction chooseRandom(items: T[]): T {\n return items[Math.floor(Math.random() * items.length)];\n}\n\nfunction clampPercent(value: number): number {\n return Math.max(0, Math.min(99, Math.floor(value)));\n}\n\nexport class RingworldExplorerSheet extends BaseActorSheet {\n private isToolsUnlocked(): boolean {\n return Boolean((this.actor as any)?.system?.sheetToolsUnlocked);\n }\n\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"actor\", \"explorer\"],\n width: 930,\n height: 1460,\n tabs: [{ navSelector: \".tabs\", contentSelector: \".sheet-body\", initial: \"overview\" }]\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/actor/explorer-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n const actor = this.actor as any;\n\n if (game.user?.isGM && actor.isOwner) {\n await actor.ensureBaseSkills?.();\n }\n\n const skills = actor.items.filter((item: any) => item.type === \"skill\").sort(byName);\n const builderContext = buildBuilderContext(actor, skills);\n\n const weapons = actor.items.filter((item: any) => item.type === \"weapon\").sort(byName);\n const armors = actor.items.filter((item: any) => item.type === \"armor\").sort(byName);\n const gear = actor.items.filter((item: any) => item.type === \"gear\").sort(byName);\n const speciesOptions = game.items\n .filter((item: any) => item.type === \"species\" && item.system.playable)\n .map((item: any) => item.name)\n .sort();\n const baseSet = baseSkillNameSet();\n\n const skillByNormalized = new Map();\n for (const skill of skills) {\n const key = normalizeSkillName(skill.name);\n if (!skillByNormalized.has(key)) {\n skillByNormalized.set(key, skill);\n }\n }\n\n const skillRootMax = actor.system.skillRootMax ?? {};\n const skillsByCategory = BASE_SKILL_CATEGORIES.map((category) => {\n const rootKey = ROOT_KEY_BY_CATEGORY[category] ?? category.toLowerCase();\n const rows = RINGWORLD_BASE_SKILLS\n .filter((skill) => skill.category === category)\n .map((skill) => {\n const existing = skillByNormalized.get(normalizeSkillName(skill.name));\n const meta = existing ? builderContext.bySkillId.get(existing.id) : null;\n return {\n name: skill.name,\n base: skill.base,\n category: skill.category,\n specialization: skill.specialization ?? \"\",\n itemId: existing?.id ?? null,\n icon: existing?.img || skillIconPath(skill.name),\n value: existing?.system?.value ?? skill.base,\n missing: !existing,\n allocationTotal: meta?.totalAllocated ?? 0,\n allocation: meta?.allocation ?? { education: 0, pursuits: 0, special: 0 }\n };\n });\n\n return {\n category,\n rootKey,\n rootFormula: ROOT_FORMULA_BY_CATEGORY[category] ?? \"\",\n rootValue: num(skillRootMax[rootKey], 0),\n rows\n };\n });\n\n const customSkills = skills\n .filter((item: any) => !baseSet.has(normalizeSkillName(item.name)))\n .map((item: any) => {\n const meta = builderContext.bySkillId.get(item.id);\n return {\n id: item.id,\n name: item.name,\n system: item.system,\n img: resolveSkillIcon(item.name, item.img || DEFAULT_SKILL_ICON),\n allocationTotal: meta?.totalAllocated ?? 0,\n allocation: meta?.allocation ?? { education: 0, pursuits: 0, special: 0 }\n };\n });\n\n const missingBaseSkillsCount = skillsByCategory.reduce(\n (total, group) => total + group.rows.filter((row) => row.missing).length,\n 0\n );\n\n const schema = getActorHitLocationSchema(actor);\n const rangesFromSchema = new Map(schema.entries.map((entry) => [entry.slug, `${entry.min}-${entry.max}`]));\n const order = schema.entries.map((entry) => entry.slug as HitLocationSlug);\n const hitLocations = order.map((slug) =>\n getPreparedHitLocation(actor, slug, rangesFromSchema.get(slug) ?? \"\")\n );\n\n const bodyLayoutOrder = schema.bodyOrder;\n const bodyLocations = bodyLayoutOrder.map((slug) =>\n getPreparedHitLocation(actor, slug, rangesFromSchema.get(slug) ?? \"\")\n );\n\n const enc = actor.system?.encumbrance ?? {};\n const encRaw = num(enc.rawLoadKg, 0);\n const encEffective = num(enc.effectiveLoadKg, encRaw);\n const encLimit = num(enc.limitKg, num(actor.system?.characteristics?.str, 0));\n const encPenalty = num(enc.actionRankingPenalty, 0);\n const baseActionRanking = num(actor.system?.derived?.actionRanking, 0);\n const effectiveActionRanking = Math.max(1, baseActionRanking + encPenalty);\n const encumbranceSummary = `${encRaw.toFixed(1)}kg on-person | ${encEffective.toFixed(1)}kg effective / ${encLimit.toFixed(1)}kg limit | AR +${encPenalty} (stowed ignored)`;\n const movementSummary = `${num(actor.system?.derived?.currentMove, num(actor.system?.derived?.move, 3))}m / ${num(actor.system?.derived?.currentRun, num(actor.system?.derived?.run, 6))}m`;\n\n return {\n ...data,\n system: actor.system,\n skills,\n skillsByCategory,\n customSkills,\n missingBaseSkillsCount,\n weapons,\n armors,\n gear,\n speciesOptions,\n hitLocations,\n bodyLocations,\n hitLocationSchemaOptions: listHitLocationSchemas(),\n currentHitLocationSchema: String(actor.system.hitLocationSchema || \"auto\"),\n activeHitLocationSchemaId: schema.id,\n activeHitLocationSchemaLabel: schema.label,\n sheetToolsUnlocked: Boolean(actor.system?.sheetToolsUnlocked),\n baseActionRanking,\n effectiveActionRanking,\n encumbranceSummary,\n movementSummary,\n builder: {\n education: builderContext.education,\n pursuits: builderContext.pursuits,\n special: builderContext.special,\n pursuitYears: builderContext.pursuitYears,\n pursuitsList: builderContext.state.pursuits,\n pursuitOptions: builderContext.pursuitOptions\n }\n };\n }\n\n private async updateBuilderState(state: BuilderState): Promise {\n await this.actor.update({\n \"system.characterBuilder\": {\n pursuits: state.pursuits,\n allocations: state.allocations,\n baseBySkillId: state.baseBySkillId\n }\n });\n }\n\n private async syncSkillsFromBuilderState(state: BuilderState): Promise {\n const skills = this.actor.items.filter((item: any) => item.type === \"skill\");\n const updates: any[] = [];\n\n for (const skill of skills) {\n const allocation = normalizeAllocation(state.allocations[skill.id]);\n const total = allocation.education + allocation.pursuits + allocation.special;\n\n const rootKey = ROOT_KEY_BY_CATEGORY[String(skill.system?.category || \"\")] ?? \"\";\n const rootCap = rootKey ? Math.max(0, toInt(this.actor.system?.skillRootMax?.[rootKey], 0)) : 99;\n\n let base = state.baseBySkillId[skill.id];\n if (!Number.isFinite(base)) {\n const fromBase = baseSkillByName(skill.name)?.base;\n base = Math.max(0, fromBase ?? (num(skill.system?.value, 0) - total));\n state.baseBySkillId[skill.id] = base;\n }\n\n const value = Math.max(0, Math.min(99, Math.min(rootCap, base + total)));\n if (num(skill.system?.value, 0) !== value) {\n updates.push({ _id: skill.id, \"system.value\": value });\n }\n }\n\n if (updates.length) {\n await this.actor.updateEmbeddedDocuments(\"Item\", updates);\n }\n }\n\n private async addOrUpdatePursuit(name: string, years: number): Promise {\n if (!this.isToolsUnlocked()) return;\n const trimmed = String(name || \"\").trim();\n const cleanYears = Math.max(0, Math.floor(Number(years) || 0));\n if (!trimmed || cleanYears <= 0) {\n ui.notifications?.warn(\"Choose a pursuit and set years above 0.\");\n return;\n }\n\n const skills = this.actor.items.filter((item: any) => item.type === \"skill\");\n const context = buildBuilderContext(this.actor, skills);\n const nextState = foundry.utils.deepClone(context.state) as BuilderState;\n\n const existingIndex = nextState.pursuits.findIndex((entry) => normalizeSkillName(entry.name) === normalizeSkillName(trimmed));\n if (existingIndex >= 0) {\n nextState.pursuits[existingIndex].years = cleanYears;\n } else {\n nextState.pursuits.push({ name: trimmed, years: cleanYears });\n }\n\n await this.updateBuilderState(nextState);\n }\n\n private async removePursuit(index: number): Promise {\n if (!this.isToolsUnlocked()) return;\n const skills = this.actor.items.filter((item: any) => item.type === \"skill\");\n const context = buildBuilderContext(this.actor, skills);\n const nextState = foundry.utils.deepClone(context.state) as BuilderState;\n if (index < 0 || index >= nextState.pursuits.length) return;\n\n nextState.pursuits.splice(index, 1);\n await this.updateBuilderState(nextState);\n }\n\n private async resetBuilderAllocations(): Promise {\n if (!this.isToolsUnlocked()) return;\n const skills = this.actor.items.filter((item: any) => item.type === \"skill\");\n const context = buildBuilderContext(this.actor, skills);\n const nextState = foundry.utils.deepClone(context.state) as BuilderState;\n\n nextState.allocations = {};\n await this.updateBuilderState(nextState);\n await this.syncSkillsFromBuilderState(nextState);\n }\n\n private async randomizeBuilderAllocations(): Promise {\n if (!this.isToolsUnlocked()) return;\n await (this.actor as any).ensureBaseSkills?.();\n\n const skills = this.actor.items.filter((item: any) => item.type === \"skill\").sort(byName);\n const context = buildBuilderContext(this.actor, skills);\n const nextState = foundry.utils.deepClone(context.state) as BuilderState;\n\n const totalYears = Math.max(0, context.pursuitYears.total);\n const pursuits = PURSUIT_CATALOG.map((entry) => entry.name);\n nextState.pursuits = [];\n\n if (totalYears > 0 && pursuits.length > 0) {\n const desiredCount = Math.min(\n pursuits.length,\n Math.max(1, Math.min(3, Math.floor(totalYears / 2) || 1))\n );\n\n const available = [...pursuits];\n for (let i = 0; i < desiredCount && available.length; i += 1) {\n const idx = Math.floor(Math.random() * available.length);\n nextState.pursuits.push({ name: available[idx], years: 1 });\n available.splice(idx, 1);\n }\n\n let remainingYears = totalYears - nextState.pursuits.length;\n while (remainingYears > 0 && nextState.pursuits.length) {\n const row = chooseRandom(nextState.pursuits);\n row.years += 1;\n remainingYears -= 1;\n }\n }\n\n const seededState = normalizeBuilderState(nextState);\n seededState.allocations = {};\n\n const freshContext = buildBuilderContext(\n { ...this.actor, system: { ...this.actor.system, characterBuilder: seededState } },\n skills\n );\n\n for (const skill of skills) {\n const meta = freshContext.bySkillId.get(skill.id);\n if (!meta) continue;\n seededState.baseBySkillId[skill.id] = meta.baseValue;\n seededState.allocations[skill.id] = { education: 0, pursuits: 0, special: 0 };\n }\n\n const totalBySkill = new Map();\n for (const skill of skills) {\n totalBySkill.set(skill.id, 0);\n }\n\n const maxForSkill = (skillId: string): number => {\n const meta = freshContext.bySkillId.get(skillId);\n return meta ? Math.max(0, meta.maxAllocation) : 0;\n };\n\n const allocatePool = (\n points: number,\n poolKey: keyof BuilderAllocation,\n eligible: (skill: any, meta: BuilderSkillMeta) => boolean\n ): number => {\n let remaining = Math.max(0, points);\n let guard = 0;\n\n while (remaining > 0 && guard < 300000) {\n guard += 1;\n const candidates = skills.filter((skill) => {\n const meta = freshContext.bySkillId.get(skill.id);\n if (!meta || !eligible(skill, meta)) return false;\n const allocated = totalBySkill.get(skill.id) ?? 0;\n return allocated < maxForSkill(skill.id);\n });\n\n if (!candidates.length) break;\n const picked = chooseRandom(candidates);\n const current = seededState.allocations[picked.id] ?? { education: 0, pursuits: 0, special: 0 };\n current[poolKey] += 1;\n seededState.allocations[picked.id] = current;\n totalBySkill.set(picked.id, (totalBySkill.get(picked.id) ?? 0) + 1);\n remaining -= 1;\n }\n\n return remaining;\n };\n\n const educationUnspent = allocatePool(\n Math.max(0, freshContext.education.total),\n \"education\",\n (skill, meta) => meta.allowsEducation\n );\n\n const pursuitsUnspent = allocatePool(\n Math.max(0, freshContext.pursuits.total),\n \"pursuits\",\n (skill, meta) => meta.allowsPursuits\n );\n\n const specialUnspent = allocatePool(\n Math.max(0, freshContext.special.total),\n \"special\",\n (skill, meta) => meta.allowsSpecial\n );\n\n for (const skill of skills) {\n const allocation = normalizeAllocation(seededState.allocations[skill.id]);\n if (allocation.education + allocation.pursuits + allocation.special <= 0) {\n delete seededState.allocations[skill.id];\n }\n }\n\n await this.updateBuilderState(seededState);\n await this.syncSkillsFromBuilderState(seededState);\n\n if (educationUnspent > 0 || pursuitsUnspent > 0 || specialUnspent > 0) {\n ui.notifications?.info(\n `Random build complete. Unspent points: EDU ${educationUnspent}, Pursuits ${pursuitsUnspent}, Special ${specialUnspent}.`\n );\n } else {\n ui.notifications?.info(\"Random build complete. All available points allocated.\");\n }\n }\n\n private async promptSkillAllocation(skillId: string): Promise {\n if (!this.isToolsUnlocked()) return;\n const skill = this.actor.items.get(skillId);\n if (!skill || skill.type !== \"skill\") return;\n\n const skills = this.actor.items.filter((item: any) => item.type === \"skill\");\n const context = buildBuilderContext(this.actor, skills);\n const meta = context.bySkillId.get(skillId);\n if (!meta) return;\n\n const current = meta.allocation;\n const maxEducation = Math.max(0, context.education.remaining + current.education);\n const maxPursuits = Math.max(0, context.pursuits.remaining + current.pursuits);\n const maxSpecial = Math.max(0, context.special.remaining + current.special);\n\n const maxByCap = Math.max(0, meta.maxAllocation);\n\n const content = `\n
\n
\n \"\"\n
\n

${skill.name}

\n

Base ${meta.baseValue}% | Root cap ${meta.rootCap}% | Current ${num(skill.system?.value, 0)}%

\n
\n
\n\n \n\n \n\n \n\n

Max additional from root cap: ${maxByCap}. Pools: EDU ${context.education.remaining}, Pursuits ${context.pursuits.remaining}, Special ${context.special.remaining}.

\n
\n `;\n\n const result = await new Promise((resolve) => {\n new Dialog({\n title: `${skill.name} - Allocate Character Points`,\n content,\n buttons: {\n save: {\n label: \"Apply\",\n callback: (html: JQuery) => {\n const form = html.find(\"form\")[0] as HTMLFormElement | undefined;\n if (!form) {\n resolve(null);\n return;\n }\n const data = new FormData(form);\n resolve({\n education: meta.allowsEducation ? toInt(data.get(\"education\"), current.education) : current.education,\n pursuits: meta.allowsPursuits ? toInt(data.get(\"pursuits\"), current.pursuits) : current.pursuits,\n special: meta.allowsSpecial ? toInt(data.get(\"special\"), current.special) : current.special\n });\n }\n },\n cancel: {\n label: \"Cancel\",\n callback: () => resolve(null)\n }\n },\n default: \"save\",\n close: () => resolve(null)\n }).render(true);\n });\n\n if (!result) return;\n\n const requestedTotal = result.education + result.pursuits + result.special;\n if (requestedTotal > maxByCap) {\n ui.notifications?.warn(`Allocation exceeds this skill's root-cap allowance (${maxByCap}).`);\n return;\n }\n\n if (result.education > maxEducation || result.pursuits > maxPursuits || result.special > maxSpecial) {\n ui.notifications?.warn(\"Allocation exceeds remaining pool points.\");\n return;\n }\n\n const nextState = foundry.utils.deepClone(context.state) as BuilderState;\n nextState.baseBySkillId[skill.id] = meta.baseValue;\n if (requestedTotal <= 0) {\n delete nextState.allocations[skill.id];\n } else {\n nextState.allocations[skill.id] = result;\n }\n\n await this.updateBuilderState(nextState);\n await this.syncSkillsFromBuilderState(nextState);\n }\n\n private async rollCharacteristicCheck(characterKey: string, label: string): Promise {\n const validKeys = new Set([\"str\", \"mas\", \"con\", \"int\", \"pow\", \"dex\", \"app\", \"edu\", \"age\"]);\n if (!validKeys.has(characterKey)) return;\n\n const base = Math.max(0, toInt((this.actor as any).system?.characteristics?.[characterKey], 0));\n const content = `\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n

Roll d100 against Characteristic x Multiplier.

\n
\n `;\n\n const multiplier = await new Promise((resolve) => {\n let settled = false;\n const done = (value: number | null) => {\n if (settled) return;\n settled = true;\n resolve(value);\n };\n\n new Dialog({\n title: `${label} Check`,\n content,\n buttons: {\n roll: {\n label: \"Roll\",\n callback: (dlgHtml: JQuery) => {\n const selected = Number((dlgHtml[0]?.querySelector(\"select[name='multiplier']\") as HTMLSelectElement | null)?.value || 1);\n const safeMultiplier = Math.max(1, Math.min(7, Math.floor(selected || 1)));\n done(safeMultiplier);\n }\n },\n cancel: {\n label: \"Cancel\",\n callback: () => done(null)\n }\n },\n default: \"roll\",\n close: () => done(null)\n }).render(true);\n });\n\n if (!multiplier) return;\n const target = clampPercent(base * multiplier);\n const result = await rollPercentile(target);\n\n await postSkillRollCard({\n actor: this.actor,\n actorName: this.actor.name,\n actorImg: this.actor.img,\n skillName: `${label} x${multiplier}`,\n skillImg: DEFAULT_SKILL_ICON,\n roll: result.roll,\n target: result.target,\n outcomeLabel: outcomeLabel(result.outcome),\n isSuccess: result.isSuccess,\n isSpecial: result.isSpecial,\n isFumble: result.isFumble\n });\n }\n\n activateListeners(html: JQuery): void {\n super.activateListeners(html);\n\n html.find(\".toggle-sheet-lock\").on(\"click\", async (event) => {\n event.preventDefault();\n const next = !this.isToolsUnlocked();\n await this.actor.update({ \"system.sheetToolsUnlocked\": next });\n ui.notifications?.info(next ? \"Sheet tools unlocked.\" : \"Sheet tools locked.\");\n });\n\n html.find(\".roll-characteristic\").on(\"click\", async (event) => {\n event.preventDefault();\n const button = event.currentTarget as HTMLElement;\n const key = String(button.dataset.char || \"\").trim().toLowerCase();\n const label = String(button.dataset.label || key.toUpperCase());\n if (!key) return;\n await this.rollCharacteristicCheck(key, label);\n });\n\n html.find(\".item-create\").on(\"click\", async (event) => {\n event.preventDefault();\n const type = String((event.currentTarget as HTMLElement).dataset.type || \"gear\");\n const doc: any = { name: `New ${type}`, type };\n if (type === \"skill\") {\n doc.img = DEFAULT_SKILL_ICON;\n doc.system = { value: 0, category: \"Technical\", specialization: \"\" };\n }\n await this.actor.createEmbeddedDocuments(\"Item\", [doc]);\n });\n\n html.find(\".item-edit\").on(\"click\", (event) => {\n event.preventDefault();\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n const item = li ? this.actor.items.get(li.dataset.itemId ?? \"\") : null;\n item?.sheet?.render(true);\n });\n\n html.find(\".item-delete\").on(\"click\", async (event) => {\n event.preventDefault();\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n await this.actor.deleteEmbeddedDocuments(\"Item\", [li.dataset.itemId]);\n });\n\n html.find(\".item-roll-skill\").on(\"click\", async (event) => {\n event.preventDefault();\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n await (this.actor as any).rollSkillById(li.dataset.itemId);\n });\n\n html.find(\".roll-derived\").on(\"click\", async (event) => {\n event.preventDefault();\n const key = String((event.currentTarget as HTMLElement).dataset.check || \"\");\n if (!key) return;\n await (this.actor as any).rollDerivedCheck(key);\n });\n\n html.find(\".item-use-weapon\").on(\"click\", async (event) => {\n event.preventDefault();\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n await (this.actor as any).useWeaponById(li.dataset.itemId);\n });\n\n html.find(\".item-toggle-equipped\").on(\"click\", async (event) => {\n event.preventDefault();\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n const item = this.actor.items.get(li.dataset.itemId);\n if (!item) return;\n\n const nextEquipped = !Boolean(item.system?.equipped);\n const update: Record = {\n _id: item.id,\n \"system.equipped\": nextEquipped\n };\n if (nextEquipped) {\n update[\"system.stowed\"] = false;\n }\n await this.actor.updateEmbeddedDocuments(\"Item\", [update]);\n });\n\n html.find(\".item-toggle-stowed\").on(\"click\", async (event) => {\n event.preventDefault();\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n const item = this.actor.items.get(li.dataset.itemId);\n if (!item) return;\n\n const nextStowed = !Boolean(item.system?.stowed);\n const update: Record = {\n _id: item.id,\n \"system.stowed\": nextStowed\n };\n if (nextStowed) {\n update[\"system.equipped\"] = false;\n }\n await this.actor.updateEmbeddedDocuments(\"Item\", [update]);\n });\n\n html.find(\".roll-hit-location\").on(\"click\", async (event) => {\n event.preventDefault();\n await (this.actor as any).rollHitLocation();\n });\n\n html.find(\".reset-conditions\").on(\"click\", async (event) => {\n event.preventDefault();\n await this.actor.update({\n \"system.conditions.prone\": false,\n \"system.conditions.unconscious\": false,\n \"system.conditions.dead\": false,\n \"system.conditions.dying\": false,\n \"system.conditions.unusableLimbs.rArm\": false,\n \"system.conditions.unusableLimbs.lArm\": false,\n \"system.conditions.unusableLimbs.rLeg\": false,\n \"system.conditions.unusableLimbs.lLeg\": false\n });\n });\n\n html.find(\".populate-base-skills\").on(\"click\", async (event) => {\n event.preventDefault();\n const added = await (this.actor as any).ensureBaseSkills();\n if (added > 0) ui.notifications?.info(`Added ${added} Ringworld base skills.`);\n else ui.notifications?.info(\"All Ringworld base skills are already present.\");\n });\n\n html.find(\".add-base-skill\").on(\"click\", async (event) => {\n event.preventDefault();\n const button = event.currentTarget as HTMLElement;\n const name = String(button.dataset.skillName || \"\");\n const category = String(button.dataset.skillCategory || \"\");\n const base = Number(button.dataset.skillBase || 0);\n const specialization = String(button.dataset.skillSpecialization || \"\");\n if (!name) return;\n\n await this.actor.createEmbeddedDocuments(\"Item\", [{\n name,\n type: \"skill\",\n img: skillIconPath(name),\n system: { value: base, category, specialization }\n }]);\n });\n\n html.find(\".builder-add-pursuit\").on(\"click\", async (event) => {\n event.preventDefault();\n const root = (event.currentTarget as HTMLElement).closest(\".rw-builder-pursuits\");\n if (!root) return;\n const name = String((root.querySelector(\".builder-new-pursuit\") as HTMLSelectElement | null)?.value || \"\");\n const years = Number((root.querySelector(\".builder-new-years\") as HTMLInputElement | null)?.value || 0);\n await this.addOrUpdatePursuit(name, years);\n });\n\n html.find(\".builder-update-pursuit\").on(\"click\", async (event) => {\n event.preventDefault();\n const row = (event.currentTarget as HTMLElement).closest(\".builder-pursuit-row\");\n if (!row) return;\n const name = String((row.querySelector(\".builder-pursuit-name\") as HTMLSelectElement | null)?.value || \"\");\n const years = Number((row.querySelector(\".builder-pursuit-years\") as HTMLInputElement | null)?.value || 0);\n await this.addOrUpdatePursuit(name, years);\n });\n\n html.find(\".builder-remove-pursuit\").on(\"click\", async (event) => {\n event.preventDefault();\n const index = Number((event.currentTarget as HTMLElement).dataset.index ?? -1);\n if (!Number.isFinite(index) || index < 0) return;\n await this.removePursuit(index);\n });\n\n html.find(\".builder-reset-points\").on(\"click\", async (event) => {\n event.preventDefault();\n await this.resetBuilderAllocations();\n ui.notifications?.info(\"Character-builder allocations reset.\");\n });\n\n html.find(\".roll-characteristics\").on(\"click\", async (event) => {\n event.preventDefault();\n if (!this.isToolsUnlocked()) return;\n await (this.actor as any).rollCharacteristics?.();\n ui.notifications?.info(\"Characteristics rolled.\");\n });\n\n html.find(\".builder-auto-allocate\").on(\"click\", async (event) => {\n event.preventDefault();\n if (!this.isToolsUnlocked()) return;\n await (this.actor as any).ensureBaseSkills?.();\n await this.randomizeBuilderAllocations();\n });\n\n html.find(\".builder-skill-allocate\").on(\"click\", async (event) => {\n event.preventDefault();\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n const skillId = String(li?.dataset.itemId || \"\");\n if (!skillId) return;\n await this.promptSkillAllocation(skillId);\n });\n }\n\n protected async _onDropItemCreate(itemData: any): Promise {\n const items = Array.isArray(itemData) ? itemData : [itemData];\n const speciesItems = items.filter((entry: any) => entry?.type === \"species\");\n const createItems = items.filter((entry: any) => entry?.type !== \"species\");\n\n for (const species of speciesItems) {\n await (this.actor as any).applySpeciesItem?.(species);\n }\n if (speciesItems.length > 0) {\n const names = speciesItems.map((entry: any) => String(entry?.name || \"Species\")).join(\", \");\n ui.notifications?.info(`Applied species to actor: ${names}`);\n }\n\n if (createItems.length === 0) return [];\n\n const patched = createItems.map((entry: any) => {\n if (entry?.type !== \"skill\") return entry;\n return {\n ...entry,\n img: resolveSkillIcon(String(entry?.name || \"Skill\"), String(entry?.img || \"\"))\n };\n });\n return super._onDropItemCreate(Array.isArray(itemData) ? patched : patched[0]);\n }\n}\n","import { getActorHitLocationSchema } from \"../../hit-location-schemas\";\n\nconst BaseActorSheet = (foundry as any).appv1.sheets.ActorSheet;\n\nexport class RingworldCreatureSheet extends BaseActorSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"actor\", \"creature\"],\n width: 760,\n height: 700\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/actor/creature-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n const actor = this.actor as any;\n\n return {\n ...data,\n system: actor.system,\n skills: actor.items.filter((item: any) => item.type === \"skill\"),\n weapons: actor.items.filter((item: any) => item.type === \"weapon\"),\n hitLocations: getActorHitLocationSchema(actor).entries.map((entry) => ({\n slug: entry.slug,\n rollRange: `${entry.min}-${entry.max}`,\n label: entry.label,\n ...(actor.system.hitLocations?.[entry.slug] ?? {})\n }))\n };\n }\n\n activateListeners(html: JQuery): void {\n super.activateListeners(html);\n\n html.find(\".item-create\").on(\"click\", async (event) => {\n event.preventDefault();\n const type = String((event.currentTarget as HTMLElement).dataset.type || \"skill\");\n await this.actor.createEmbeddedDocuments(\"Item\", [{ name: `New ${type}`, type }]);\n });\n\n html.find(\".item-edit\").on(\"click\", (event) => {\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n const item = li ? this.actor.items.get(li.dataset.itemId ?? \"\") : null;\n item?.sheet?.render(true);\n });\n\n html.find(\".item-delete\").on(\"click\", async (event) => {\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n await this.actor.deleteEmbeddedDocuments(\"Item\", [li.dataset.itemId]);\n });\n\n html.find(\".item-roll-skill\").on(\"click\", async (event) => {\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n await (this.actor as any).rollSkillById(li.dataset.itemId);\n });\n\n html.find(\".item-use-weapon\").on(\"click\", async (event) => {\n const li = (event.currentTarget as HTMLElement).closest(\".item\");\n if (!li?.dataset.itemId) return;\n await (this.actor as any).useWeaponById(li.dataset.itemId);\n });\n\n html.find(\".roll-hit-location\").on(\"click\", async () => {\n await (this.actor as any).rollHitLocation();\n });\n }\n}\n","const BaseActorSheet = (foundry as any).appv1.sheets.ActorSheet;\n\nexport class RingworldVehicleSheet extends BaseActorSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"actor\", \"vehicle\"],\n width: 680,\n height: 560\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/actor/vehicle-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n return {\n ...data,\n system: (this.actor as any).system\n };\n }\n}\n","const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet;\n\nexport class RingworldSkillSheet extends BaseItemSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"item\", \"skill\"],\n width: 520,\n height: 420\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/item/skill-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n return {\n ...data,\n system: (this.item as any).system\n };\n }\n\n activateListeners(html: JQuery): void {\n super.activateListeners(html);\n html.find(\".roll-skill\").on(\"click\", async (event) => {\n event.preventDefault();\n await (this.item as any).rollSkill();\n });\n }\n}\n","const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet;\n\nexport class RingworldWeaponSheet extends BaseItemSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"item\", \"weapon\"],\n width: 620,\n height: 620\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/item/weapon-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n const actor = this.item.actor as any;\n\n return {\n ...data,\n system: (this.item as any).system,\n skills: actor ? actor.items.filter((item: any) => item.type === \"skill\") : [],\n powerPacks: actor ? actor.items.filter((item: any) => item.type === \"gear\" && item.system.isPowerPack) : []\n };\n }\n\n activateListeners(html: JQuery): void {\n super.activateListeners(html);\n html.find(\".use-weapon\").on(\"click\", async (event) => {\n event.preventDefault();\n await (this.item as any).useWeapon();\n });\n }\n}\n","const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet;\n\nexport class RingworldArmorSheet extends BaseItemSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"item\", \"armor\"],\n width: 620,\n height: 560\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/item/armor-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n return {\n ...data,\n system: (this.item as any).system\n };\n }\n}\n","const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet;\n\nexport class RingworldGearSheet extends BaseItemSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"item\", \"gear\"],\n width: 620,\n height: 520\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/item/gear-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n return {\n ...data,\n system: (this.item as any).system\n };\n }\n}\n","const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet;\n\nexport class RingworldSpeciesSheet extends BaseItemSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"item\", \"species\"],\n width: 620,\n height: 560\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/item/species-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n return {\n ...data,\n system: (this.item as any).system\n };\n }\n}\n","const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet;\n\nfunction asArray(value: unknown): string[] {\n return Array.isArray(value) ? value.map((entry) => String(entry)) : [];\n}\n\nexport class RingworldPursuitSheet extends BaseItemSheet {\n static get defaultOptions(): any {\n return foundry.utils.mergeObject(super.defaultOptions, {\n classes: [\"ringworld\", \"sheet\", \"item\", \"pursuit\"],\n width: 680,\n height: 620\n });\n }\n\n get template(): string {\n return \"systems/ringworld/templates/item/pursuit-sheet.hbs\";\n }\n\n async getData(options?: any): Promise {\n const data = await super.getData(options);\n const system = (this.item as any).system ?? {};\n const fixedSkills = asArray(system.fixedSkills);\n const optionalGroups = Array.isArray(system.optionalSkillGroups)\n ? system.optionalSkillGroups.map((group: any) => ({\n choose: Number(group?.choose ?? 1),\n skills: asArray(group?.skills)\n }))\n : [];\n\n return {\n ...data,\n system,\n fixedSkills,\n optionalGroups\n };\n }\n}\n\n","import { SYSTEM_ID } from \"./constants\";\n\ntype MajorActionKind = \"draw\" | \"aim\" | \"melee\" | \"stand\" | \"perception\" | \"unarmed\";\ntype MinorActionKind = \"fire\" | \"fall\" | \"rise\" | \"minorCustom\";\ntype ActionKind = MajorActionKind | MinorActionKind;\n\ninterface ActionEvent {\n id: string;\n actorId: string | null;\n combatantId: string;\n kind: ActionKind;\n major: boolean;\n startImpulse: number;\n completeImpulse: number;\n itemId?: string;\n skillId?: string;\n targetTokenId?: string;\n}\n\ninterface ImpulseState {\n currentImpulse: number;\n queue: ActionEvent[];\n}\n\nconst IMPULSE_FLAG = \"impulseState\";\nconst IMPULSE_HUD_POS_KEY = `${SYSTEM_ID}.impulseHudPos`;\nconst MAJOR_ACTION_OPTIONS: { value: MajorActionKind; label: string }[] = [\n { value: \"draw\", label: \"Draw / Stow Weapon\" },\n { value: \"aim\", label: \"Aim Ranged Weapon\" },\n { value: \"melee\", label: \"Melee Attack\" },\n { value: \"stand\", label: \"Stand / Recover\" },\n { value: \"perception\", label: \"Perception Skill\" },\n { value: \"unarmed\", label: \"Unarmed Combat\" }\n];\nconst MINOR_ACTION_OPTIONS: { value: MinorActionKind; label: string }[] = [\n { value: \"fire\", label: \"Fire Ranged Weapon\" },\n { value: \"fall\", label: \"Fall\" },\n { value: \"rise\", label: \"Rise (Kneeling to Stand/Run)\" },\n { value: \"minorCustom\", label: \"Other Minor Action\" }\n];\n\nfunction num(value: unknown, fallback = 0): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : fallback;\n}\n\nfunction esc(value: unknown): string {\n return String(value ?? \"\")\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll(\"\\\"\", \""\");\n}\n\nexport function actionRankingFromDex(dex: number): number {\n if (dex <= 4) return 7;\n if (dex <= 8) return 6;\n if (dex <= 12) return 5;\n if (dex <= 16) return 4;\n if (dex <= 20) return 3;\n if (dex <= 24) return 2;\n return 1;\n}\n\nfunction actionKindLabel(kind: ActionKind): string {\n switch (kind) {\n case \"draw\":\n return \"Draw / Stow\";\n case \"aim\":\n return \"Aim\";\n case \"melee\":\n return \"Melee Attack\";\n case \"stand\":\n return \"Stand / Recover\";\n case \"perception\":\n return \"Perception\";\n case \"unarmed\":\n return \"Unarmed Combat\";\n case \"fire\":\n return \"Fire Ranged Weapon\";\n case \"fall\":\n return \"Fall\";\n case \"rise\":\n return \"Rise\";\n case \"minorCustom\":\n return \"Minor Action\";\n default:\n return String(kind);\n }\n}\n\nfunction getDexForCombatant(combatant: any): number {\n return Math.max(1, num(combatant?.actor?.system?.characteristics?.dex, 10));\n}\n\nfunction getCombatantActionRanking(combatant: any): number {\n const fromFlag = num(combatant?.flags?.[SYSTEM_ID]?.actionRanking, 0);\n if (fromFlag > 0) return fromFlag;\n return actionRankingFromDex(getDexForCombatant(combatant));\n}\n\nfunction getEncumbranceArPenalty(combatant: any): number {\n const fromActor = num(combatant?.actor?.system?.encumbrance?.actionRankingPenalty, 0);\n return Math.max(0, Math.trunc(fromActor));\n}\n\nasync function syncCombatantActionRanking(combatant: any): Promise {\n const ranking = actionRankingFromDex(getDexForCombatant(combatant));\n const existing = num(combatant?.flags?.[SYSTEM_ID]?.actionRanking, 0);\n if (existing !== ranking) {\n await combatant.update({ [`flags.${SYSTEM_ID}.actionRanking`]: ranking });\n }\n return ranking;\n}\n\nfunction getImpulseState(combat: any): ImpulseState {\n const raw = foundry.utils.deepClone(combat.getFlag(SYSTEM_ID, IMPULSE_FLAG) ?? {});\n const queue = Array.isArray(raw.queue) ? raw.queue : [];\n return {\n currentImpulse: Math.max(1, num(raw.currentImpulse, 1)),\n queue\n };\n}\n\nasync function setImpulseState(combat: any, state: ImpulseState): Promise {\n await combat.setFlag(SYSTEM_ID, IMPULSE_FLAG, {\n currentImpulse: Math.max(1, num(state.currentImpulse, 1)),\n queue: Array.isArray(state.queue) ? state.queue : []\n });\n}\n\nfunction sortQueueByCompletion(queue: ActionEvent[], combat: any): ActionEvent[] {\n return [...queue].sort((a, b) => {\n if (a.completeImpulse !== b.completeImpulse) return a.completeImpulse - b.completeImpulse;\n const aDex = getDexForCombatant(combat.combatants.get(a.combatantId));\n const bDex = getDexForCombatant(combat.combatants.get(b.combatantId));\n return bDex - aDex;\n });\n}\n\nfunction pruneQueueToCombatants(queue: ActionEvent[], combat: any): ActionEvent[] {\n const validIds = new Set(combat.combatants.map((c: any) => c.id));\n return queue.filter((entry) => validIds.has(entry.combatantId));\n}\n\nfunction loadHudPosition(): { left: number; top: number } | null {\n try {\n const raw = localStorage.getItem(IMPULSE_HUD_POS_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw);\n const left = num(parsed?.left, NaN);\n const top = num(parsed?.top, NaN);\n if (!Number.isFinite(left) || !Number.isFinite(top)) return null;\n return { left, top };\n } catch (_err) {\n return null;\n }\n}\n\nfunction saveHudPosition(position: { left: number; top: number }): void {\n try {\n localStorage.setItem(\n IMPULSE_HUD_POS_KEY,\n JSON.stringify({ left: Math.round(position.left), top: Math.round(position.top) })\n );\n } catch (_err) {\n // Ignore storage issues.\n }\n}\n\nfunction clearHudPosition(): void {\n try {\n localStorage.removeItem(IMPULSE_HUD_POS_KEY);\n } catch (_err) {\n // Ignore storage issues.\n }\n}\n\nfunction clampHudPosition(position: { left: number; top: number }, element: HTMLElement): { left: number; top: number } {\n const margin = 8;\n const width = element.offsetWidth || 300;\n const height = element.offsetHeight || 220;\n const maxLeft = Math.max(margin, window.innerWidth - width - margin);\n const maxTop = Math.max(margin, window.innerHeight - height - margin);\n return {\n left: Math.min(Math.max(margin, position.left), maxLeft),\n top: Math.min(Math.max(margin, position.top), maxTop)\n };\n}\n\nfunction applyHudAbsolutePosition(hud: JQuery, position: { left: number; top: number }): void {\n const el = hud.get(0) as HTMLElement | undefined;\n if (!el) return;\n const clamped = clampHudPosition(position, el);\n hud.css({\n left: `${clamped.left}px`,\n top: `${clamped.top}px`,\n right: \"auto\",\n bottom: \"auto\"\n });\n}\n\nfunction makeHudDraggable(hud: JQuery): void {\n const el = hud.get(0) as HTMLElement | undefined;\n if (!el) return;\n\n const handle = hud.find(\".rw-imp-mini-head\");\n if (!handle.length) return;\n\n const saved = loadHudPosition();\n if (saved) {\n applyHudAbsolutePosition(hud, saved);\n }\n\n let dragging = false;\n let startX = 0;\n let startY = 0;\n let startLeft = 0;\n let startTop = 0;\n\n handle.on(\"mousedown\", (event) => {\n const mouseEvent = event as JQuery.MouseDownEvent;\n if (mouseEvent.button !== 0) return;\n if ((mouseEvent.target as HTMLElement)?.closest(\"button\")) return;\n mouseEvent.preventDefault();\n\n const rect = el.getBoundingClientRect();\n startX = mouseEvent.clientX;\n startY = mouseEvent.clientY;\n startLeft = rect.left;\n startTop = rect.top;\n dragging = true;\n\n hud.addClass(\"dragging\");\n hud.css({ left: `${startLeft}px`, top: `${startTop}px`, right: \"auto\", bottom: \"auto\" });\n\n $(document).on(\"mousemove.ringworldImpulseHudDrag\", (moveEvent) => {\n if (!dragging) return;\n const evt = moveEvent as JQuery.MouseMoveEvent;\n const deltaX = evt.clientX - startX;\n const deltaY = evt.clientY - startY;\n applyHudAbsolutePosition(hud, { left: startLeft + deltaX, top: startTop + deltaY });\n });\n\n $(document).one(\"mouseup.ringworldImpulseHudDrag\", () => {\n dragging = false;\n hud.removeClass(\"dragging\");\n $(document).off(\"mousemove.ringworldImpulseHudDrag\");\n const latestRect = el.getBoundingClientRect();\n saveHudPosition({ left: latestRect.left, top: latestRect.top });\n });\n });\n\n handle.on(\"dblclick\", () => {\n clearHudPosition();\n hud.css({ left: \"\", top: \"\", right: \"\", bottom: \"\" });\n });\n}\n\nfunction getQueueWithNames(combat: any, queue: ActionEvent[]): Array<{\n combatantId: string;\n name: string;\n label: string;\n completeImpulse: number;\n dex: number;\n}> {\n return sortQueueByCompletion(pruneQueueToCombatants(queue, combat), combat).map((entry) => {\n const combatant = combat.combatants.get(entry.combatantId);\n return {\n combatantId: entry.combatantId,\n name: String(combatant?.name || \"Unknown\"),\n label: actionKindLabel(entry.kind),\n completeImpulse: entry.completeImpulse,\n dex: getDexForCombatant(combatant)\n };\n });\n}\n\nfunction renderQueueRowsMarkup(rows: ReturnType, limit = 8): string {\n if (!rows.length) return `
No actions queued.
`;\n return rows.slice(0, limit).map((row) => `\n
\n ${esc(row.name)}\n ${esc(row.label)} @ ${row.completeImpulse}\n
\n `).join(\"\");\n}\n\nfunction attachOverlayControls(root: JQuery, combat: any): void {\n const disabled = game.user?.isGM ? false : true;\n root.find(\".rw-mini-next\").prop(\"disabled\", disabled).on(\"click\", async (event) => {\n event.preventDefault();\n await advanceImpulse(combat, \"next\");\n });\n root.find(\".rw-mini-step\").prop(\"disabled\", disabled).on(\"click\", async (event) => {\n event.preventDefault();\n await advanceImpulse(combat, \"step\");\n });\n root.find(\".rw-mini-reset\").prop(\"disabled\", disabled).on(\"click\", async (event) => {\n event.preventDefault();\n await resetImpulseSequence(combat);\n });\n}\n\nfunction renderChatImpulseMarker(combat: any | null): void {\n const chatEl = (ui as any)?.chat?.element as JQuery | undefined;\n if (!chatEl?.length) return;\n chatEl.find(\".ringworld-impulse-chat-marker\").remove();\n if (!combat) return;\n\n const state = getImpulseState(combat);\n const rows = getQueueWithNames(combat, state.queue);\n const marker = $(`\n
\n
\n Impulse ${state.currentImpulse}\n Queued ${rows.length}\n
\n
${renderQueueRowsMarkup(rows, 5)}
\n
\n \n \n \n
\n
\n `);\n attachOverlayControls(marker, combat);\n chatEl.prepend(marker);\n}\n\nfunction renderFloatingImpulseHud(combat: any | null): void {\n const existing = $(\"#ringworld-impulse-hud\");\n existing.remove();\n if (!combat) return;\n\n const state = getImpulseState(combat);\n const rows = getQueueWithNames(combat, state.queue);\n const hud = $(`\n
\n
\n Impulse ${state.currentImpulse}\n Queued ${rows.length}\n
\n
${renderQueueRowsMarkup(rows, 8)}
\n
\n \n \n \n
\n
\n `);\n attachOverlayControls(hud, combat);\n $(\"body\").append(hud);\n makeHudDraggable(hud);\n}\n\nfunction refreshImpulseOverlays(combat: any | null): void {\n renderChatImpulseMarker(combat);\n renderFloatingImpulseHud(combat);\n}\n\nasync function ensureGmControl(): Promise {\n if (game.user?.isGM) return true;\n ui.notifications?.warn(\"Only the GM can modify Ringworld impulse sequencing.\");\n return false;\n}\n\nasync function scheduleAction(\n combat: any,\n combatantId: string,\n kind: ActionKind,\n major: boolean\n): Promise {\n if (!(await ensureGmControl())) return;\n\n const combatant = combat.combatants.get(combatantId);\n if (!combatant) return;\n\n const state = getImpulseState(combat);\n const currentImpulse = state.currentImpulse;\n const actionRanking = await syncCombatantActionRanking(combatant);\n const encPenalty = getEncumbranceArPenalty(combatant);\n const impulseMod = num(combatant.flags?.[SYSTEM_ID]?.impulseModifier, 0);\n const effectiveAr = Math.max(1, actionRanking + encPenalty);\n const baseDuration = major ? effectiveAr : 1;\n const duration = Math.max(1, baseDuration + impulseMod);\n const completeImpulse = currentImpulse + duration;\n\n const event: ActionEvent = {\n id: foundry.utils.randomID(),\n actorId: combatant.actor?.id ?? null,\n combatantId,\n kind,\n major,\n startImpulse: currentImpulse,\n completeImpulse\n };\n\n const queueWithoutCombatant = pruneQueueToCombatants(\n state.queue.filter((entry) => entry.combatantId !== combatantId),\n combat\n );\n queueWithoutCombatant.push(event);\n\n await setImpulseState(combat, {\n currentImpulse,\n queue: sortQueueByCompletion(queueWithoutCombatant, combat)\n });\n\n await combatant.update({\n [`flags.${SYSTEM_ID}.pendingEventId`]: event.id,\n [`flags.${SYSTEM_ID}.pendingKind`]: kind,\n [`flags.${SYSTEM_ID}.actionRanking`]: actionRanking,\n [`flags.${SYSTEM_ID}.effectiveActionRanking`]: effectiveAr\n });\n\n const timingLabel = major\n ? `AR ${actionRanking} + Enc ${encPenalty}${impulseMod ? ` ${impulseMod > 0 ? \"+\" : \"\"} Imp ${impulseMod}` : \"\"}`\n : `Minor 1${impulseMod ? ` ${impulseMod > 0 ? \"+\" : \"\"} Imp ${impulseMod}` : \"\"}`;\n ui.notifications?.info(\n `${combatant.name}: ${actionKindLabel(kind)} completes on impulse ${completeImpulse} (${timingLabel} => ${duration}).`\n );\n refreshImpulseOverlays(combat);\n}\n\nasync function cancelAction(combat: any, combatantId: string): Promise {\n if (!(await ensureGmControl())) return;\n\n const combatant = combat.combatants.get(combatantId);\n if (!combatant) return;\n\n const state = getImpulseState(combat);\n const nextQueue = pruneQueueToCombatants(\n state.queue.filter((entry) => entry.combatantId !== combatantId),\n combat\n );\n\n await setImpulseState(combat, {\n currentImpulse: state.currentImpulse,\n queue: sortQueueByCompletion(nextQueue, combat)\n });\n\n await combatant.update({\n [`flags.${SYSTEM_ID}.pendingEventId`]: null,\n [`flags.${SYSTEM_ID}.pendingKind`]: \"\",\n [`flags.${SYSTEM_ID}.aimState`]: {\n active: false,\n targetId: null,\n sinceImpulse: null\n }\n });\n refreshImpulseOverlays(combat);\n}\n\nasync function resolveActionEvent(combat: any, event: ActionEvent, impulse: number): Promise {\n const combatant = combat.combatants.get(event.combatantId);\n if (!combatant) return;\n\n const updates: Record = {\n [`flags.${SYSTEM_ID}.pendingEventId`]: null,\n [`flags.${SYSTEM_ID}.pendingKind`]: \"\"\n };\n\n if (event.kind === \"aim\") {\n updates[`flags.${SYSTEM_ID}.aimState`] = {\n active: true,\n targetId: event.targetTokenId ?? null,\n sinceImpulse: impulse\n };\n }\n\n await combatant.update(updates);\n\n const content = `\n
\n

Impulse ${impulse} Completion

\n

${esc(combatant.name)} completes ${esc(actionKindLabel(event.kind))}.

\n

DEX ${getDexForCombatant(combatant)} | AR ${getCombatantActionRanking(combatant)}

\n
\n `;\n await ChatMessage.create({\n user: game.user?.id,\n speaker: ChatMessage.getSpeaker({ actor: combatant.actor ?? undefined }),\n content\n });\n}\n\nasync function advanceImpulse(combat: any, mode: \"next\" | \"step\"): Promise {\n if (!(await ensureGmControl())) return;\n\n const state = getImpulseState(combat);\n const queue = sortQueueByCompletion(pruneQueueToCombatants(state.queue, combat), combat);\n const current = state.currentImpulse;\n\n let targetImpulse = current + 1;\n if (mode === \"next\") {\n const next = queue\n .map((event) => event.completeImpulse)\n .filter((impulse) => impulse >= current)\n .sort((a, b) => a - b)[0];\n if (Number.isFinite(next)) {\n targetImpulse = next;\n }\n }\n\n const completing = queue.filter((event) => event.completeImpulse === targetImpulse);\n const remaining = queue.filter((event) => event.completeImpulse !== targetImpulse);\n\n await setImpulseState(combat, {\n currentImpulse: targetImpulse,\n queue: remaining\n });\n\n const ordered = sortQueueByCompletion(completing, combat);\n for (const event of ordered) {\n await resolveActionEvent(combat, event, targetImpulse);\n }\n\n if (!ordered.length) {\n ui.notifications?.info(`Impulse advanced to ${targetImpulse}.`);\n }\n refreshImpulseOverlays(combat);\n}\n\nasync function resetImpulseSequence(combat: any): Promise {\n if (!(await ensureGmControl())) return;\n\n await setImpulseState(combat, { currentImpulse: 1, queue: [] });\n\n for (const combatant of combat.combatants) {\n await combatant.update({\n [`flags.${SYSTEM_ID}.pendingEventId`]: null,\n [`flags.${SYSTEM_ID}.pendingKind`]: \"\",\n [`flags.${SYSTEM_ID}.aimState`]: {\n active: false,\n targetId: null,\n sinceImpulse: null\n },\n [`flags.${SYSTEM_ID}.actionRanking`]: actionRankingFromDex(getDexForCombatant(combatant)),\n [`flags.${SYSTEM_ID}.effectiveActionRanking`]: null,\n [`flags.${SYSTEM_ID}.impulseModifier`]: 0\n });\n }\n refreshImpulseOverlays(combat);\n}\n\nfunction renderImpulsePanel(app: any, htmlLike: JQuery | HTMLElement): void {\n const html = \"find\" in (htmlLike as any) ? (htmlLike as JQuery) : $(htmlLike);\n if (!html?.length) return;\n html.addClass(\"ringworld-impulse-mode ringworld-impulse-only\");\n\n const combat = app?.viewed ?? game.combat;\n if (!combat) return;\n\n const state = getImpulseState(combat);\n const queue = sortQueueByCompletion(pruneQueueToCombatants(state.queue, combat), combat);\n if (queue.length !== state.queue.length) {\n void setImpulseState(combat, { currentImpulse: state.currentImpulse, queue });\n }\n\n html\n .find(\n \".token-initiative, .combatant-initiative, .initiative, [data-action='rollInitiative'], [data-control='rollInitiative'], .combatant .fa-dice-d20\"\n )\n .addClass(\"rw-hide-initiative\");\n\n html.find(\".ringworld-impulse-panel\").remove();\n\n const disabled = game.user?.isGM ? \"\" : \"disabled\";\n const rows = combat.combatants.map((combatant: any) => {\n const ar = getCombatantActionRanking(combatant);\n const encPenalty = getEncumbranceArPenalty(combatant);\n const impMod = num(combatant.flags?.[SYSTEM_ID]?.impulseModifier, 0);\n const effAr = Math.max(1, ar + encPenalty);\n const dex = getDexForCombatant(combatant);\n const pending = queue.find((event) => event.combatantId === combatant.id);\n const pendingLabel = pending\n ? `${actionKindLabel(pending.kind)} @ ${pending.completeImpulse}`\n : \"Ready\";\n const aimState = combatant.flags?.[SYSTEM_ID]?.aimState;\n const aimLabel = aimState?.active ? `Aiming since ${aimState.sinceImpulse}` : \"Not aiming\";\n\n const options = MAJOR_ACTION_OPTIONS.map((opt) => {\n const selected = pending?.major && pending?.kind === opt.value ? \"selected\" : \"\";\n return ``;\n }).join(\"\");\n const minorOptions = MINOR_ACTION_OPTIONS.map((opt) => {\n const selected = !pending?.major && pending?.kind === opt.value ? \"selected\" : \"\";\n return ``;\n }).join(\"\");\n\n return `\n
\n
\n ${esc(combatant.name)}\n DEX ${dex} | AR ${ar} + Enc ${encPenalty} = ${effAr} | Imp Mod ${impMod >= 0 ? `+${impMod}` : impMod} | ${esc(pendingLabel)} | ${esc(aimLabel)}\n
\n
\n \n \n \n \n \n \n \n
\n
\n `;\n }).join(\"\");\n\n const panel = $(`\n
\n
\n
Impulse ${state.currentImpulse}
\n
Queued: ${queue.length}
\n
\n \n \n \n
\n
\n
${rows}
\n
\n `);\n\n html.prepend(panel);\n\n // Hide all native combat tracker blocks so only Ringworld impulse UI is shown.\n const root = html.get(0);\n if (root) {\n for (const child of Array.from(root.children)) {\n if (!(child as HTMLElement).classList.contains(\"ringworld-impulse-panel\")) {\n (child as HTMLElement).classList.add(\"rw-hide-core-tracker\");\n }\n }\n }\n\n panel.find(\".rw-advance-next\").on(\"click\", async (event) => {\n event.preventDefault();\n await advanceImpulse(combat, \"next\");\n });\n\n panel.find(\".rw-advance-step\").on(\"click\", async (event) => {\n event.preventDefault();\n await advanceImpulse(combat, \"step\");\n });\n\n panel.find(\".rw-reset-sequence\").on(\"click\", async (event) => {\n event.preventDefault();\n await resetImpulseSequence(combat);\n });\n\n panel.find(\".rw-major-start\").on(\"click\", async (event) => {\n event.preventDefault();\n const row = (event.currentTarget as HTMLElement).closest(\".rw-imp-row\");\n if (!row) return;\n const combatantId = String(row.getAttribute(\"data-combatant-id\") || \"\");\n const select = row.querySelector(\".rw-major-kind\") as HTMLSelectElement | null;\n const kind = String(select?.value || \"draw\") as MajorActionKind;\n await scheduleAction(combat, combatantId, kind, true);\n });\n\n panel.find(\".rw-minor-start\").on(\"click\", async (event) => {\n event.preventDefault();\n const row = (event.currentTarget as HTMLElement).closest(\".rw-imp-row\");\n if (!row) return;\n const combatantId = String(row.getAttribute(\"data-combatant-id\") || \"\");\n const select = row.querySelector(\".rw-minor-kind\") as HTMLSelectElement | null;\n const kind = String(select?.value || \"minorCustom\") as MinorActionKind;\n await scheduleAction(combat, combatantId, kind, false);\n });\n\n panel.find(\".rw-cancel-start\").on(\"click\", async (event) => {\n event.preventDefault();\n const row = (event.currentTarget as HTMLElement).closest(\".rw-imp-row\");\n if (!row) return;\n const combatantId = String(row.getAttribute(\"data-combatant-id\") || \"\");\n await cancelAction(combat, combatantId);\n });\n\n panel.find(\".rw-save-mods\").on(\"click\", async (event) => {\n event.preventDefault();\n if (!(await ensureGmControl())) return;\n const row = (event.currentTarget as HTMLElement).closest(\".rw-imp-row\");\n if (!row) return;\n const combatantId = String(row.getAttribute(\"data-combatant-id\") || \"\");\n const combatant = combat.combatants.get(combatantId);\n if (!combatant) return;\n\n const impModInput = row.querySelector(\".rw-imp-mod\") as HTMLInputElement | null;\n const impMod = Math.trunc(num(impModInput?.value, 0));\n await combatant.update({\n [`flags.${SYSTEM_ID}.impulseModifier`]: impMod\n });\n });\n\n refreshImpulseOverlays(combat);\n}\n\nexport function initializeImpulseTracker(): void {\n Hooks.on(\"renderCombatTracker\", (app: any, htmlLike: JQuery | HTMLElement) => {\n renderImpulsePanel(app, htmlLike);\n });\n\n Hooks.on(\"createCombat\", async (combat: any) => {\n await setImpulseState(combat, { currentImpulse: 1, queue: [] });\n refreshImpulseOverlays(combat);\n });\n\n Hooks.on(\"createCombatant\", async (combatant: any) => {\n await syncCombatantActionRanking(combatant);\n refreshImpulseOverlays(game.combat ?? null);\n });\n\n Hooks.on(\"deleteCombatant\", async () => {\n refreshImpulseOverlays(game.combat ?? null);\n });\n\n Hooks.on(\"updateCombat\", (combat: any) => {\n refreshImpulseOverlays(combat ?? game.combat ?? null);\n });\n\n Hooks.on(\"deleteCombat\", () => {\n refreshImpulseOverlays(null);\n });\n\n Hooks.on(\"renderChatLog\", () => {\n refreshImpulseOverlays(game.combat ?? null);\n });\n\n Hooks.on(\"canvasReady\", () => {\n refreshImpulseOverlays(game.combat ?? null);\n });\n\n Hooks.on(\"updateActor\", (actor: any) => {\n const combat = game.combat;\n if (!combat) return;\n const inCombat = combat.combatants.some((c: any) => c.actor?.id === actor.id);\n if (inCombat) {\n ui.combat?.render(true);\n refreshImpulseOverlays(combat);\n }\n });\n\n Hooks.on(\"updateItem\", (item: any) => {\n const actor = item?.parent;\n if (!actor) return;\n const combat = game.combat;\n if (!combat) return;\n const inCombat = combat.combatants.some((c: any) => c.actor?.id === actor.id);\n if (inCombat) {\n ui.combat?.render(true);\n refreshImpulseOverlays(combat);\n }\n });\n}\n","import \"./styles/ringworld.scss\";\n\nimport { RingworldActor } from \"./module/actor/RingworldActor\";\nimport { RingworldItem } from \"./module/item/RingworldItem\";\nimport { RingworldExplorerSheet } from \"./module/sheets/actors/ExplorerSheet\";\nimport { RingworldCreatureSheet } from \"./module/sheets/actors/CreatureSheet\";\nimport { RingworldVehicleSheet } from \"./module/sheets/actors/VehicleSheet\";\nimport { RingworldSkillSheet } from \"./module/sheets/items/SkillSheet\";\nimport { RingworldWeaponSheet } from \"./module/sheets/items/WeaponSheet\";\nimport { RingworldArmorSheet } from \"./module/sheets/items/ArmorSheet\";\nimport { RingworldGearSheet } from \"./module/sheets/items/GearSheet\";\nimport { RingworldSpeciesSheet } from \"./module/sheets/items/SpeciesSheet\";\nimport { RingworldPursuitSheet } from \"./module/sheets/items/PursuitSheet\";\nimport { HIT_LOCATION_TABLE } from \"./module/constants\";\nimport { createRandomExplorer } from \"./module/character-tools\";\nimport { RINGWORLD_CATALOG_VERSION, seedWorldCatalog } from \"./module/world-catalog\";\nimport { initializeImpulseTracker } from \"./module/impulse-tracker\";\nimport { DEFAULT_SKILL_ICON, baseSkillByName, resolveSkillIcon } from \"./module/skill-icons\";\n\nconst ENCUMBRANCE_DATA_MIGRATION_VERSION = 2;\n\nfunction normName(value: unknown): string {\n return String(value ?? \"\").trim().toLowerCase();\n}\n\nfunction worldItemWeightMap(): Map {\n const map = new Map();\n for (const item of game.items ?? []) {\n if (item.type !== \"weapon\" && item.type !== \"armor\" && item.type !== \"gear\") continue;\n const key = `${item.type}:${normName(item.name)}`;\n map.set(key, Number((item as any).system?.weight ?? 0) || 0);\n }\n return map;\n}\n\nasync function migrateActorItemEncumbranceData(): Promise {\n if (!game.user?.isGM) return 0;\n const weightMap = worldItemWeightMap();\n let touched = 0;\n\n for (const actor of game.actors ?? []) {\n const updates: any[] = [];\n\n for (const item of actor.items ?? []) {\n if (item.type !== \"weapon\" && item.type !== \"armor\" && item.type !== \"gear\") continue;\n const patch: Record = { _id: item.id };\n let changed = false;\n\n if ((item as any).system?.equipped === undefined) {\n patch[\"system.equipped\"] = true;\n changed = true;\n }\n\n if ((item as any).system?.weight === undefined) {\n const key = `${item.type}:${normName(item.name)}`;\n patch[\"system.weight\"] = Number(weightMap.get(key) ?? 0);\n changed = true;\n }\n\n if ((item as any).system?.stowed === undefined) {\n patch[\"system.stowed\"] = false;\n changed = true;\n }\n\n if (changed) {\n updates.push(patch);\n }\n }\n\n if (updates.length) {\n await actor.updateEmbeddedDocuments(\"Item\", updates);\n touched += updates.length;\n }\n }\n\n return touched;\n}\n\nfunction injectDirectoryButton(htmlLike: JQuery | HTMLElement | null | undefined, className: string, label: string, onClick: () => Promise | void): void {\n if (!htmlLike) return;\n const html = \"find\" in (htmlLike as any) ? (htmlLike as JQuery) : $(htmlLike);\n if (!html.length || html.find(`.${className}`).length) return;\n\n const button = $(``);\n button.on(\"click\", async () => {\n await onClick();\n });\n\n const containers = [\n html.find(\".directory-footer\"),\n html.find(\"footer.directory-footer\"),\n html.find(\"footer\"),\n html.find(\".footer-actions\"),\n html.find(\".header-actions\"),\n html.find(\".action-buttons\")\n ];\n\n const container = containers.find((candidate) => candidate.length);\n if (container) {\n container.first().append(button);\n return;\n }\n\n let fallback = html.find(\".ringworld-directory-controls\");\n if (!fallback.length) {\n fallback = $('
');\n html.append(fallback);\n }\n fallback.first().append(button);\n}\n\nasync function handleSeedCatalogClick(): Promise {\n await seedWorldCatalog({ notify: true });\n}\n\nasync function handleCreateRandomExplorerClick(): Promise {\n const actor = await createRandomExplorer();\n actor?.sheet?.render(true);\n}\n\nfunction ensureSidebarDirectoryButtons(): void {\n if (!game.user?.isGM) return;\n\n const itemElement = (ui as any).items?.element;\n injectDirectoryButton(itemElement, \"ringworld-seed-items\", \"Seed Ringworld Items\", handleSeedCatalogClick);\n\n const actorElement = (ui as any).actors?.element;\n injectDirectoryButton(actorElement, \"ringworld-random-actor\", \"Create Random Explorer\", handleCreateRandomExplorerClick);\n}\n\nfunction itemTypeAvailable(type: string): boolean {\n const directTypes = (getDocumentClass(\"Item\") as any)?.TYPES;\n if (Array.isArray(directTypes)) return directTypes.includes(type);\n\n const docTypes = (game as any)?.system?.documentTypes?.Item;\n if (Array.isArray(docTypes)) return docTypes.includes(type);\n if (docTypes && typeof docTypes === \"object\") return Object.prototype.hasOwnProperty.call(docTypes, type);\n\n const configModels = (CONFIG as any)?.Item?.dataModels;\n if (configModels && typeof configModels === \"object\" && Object.prototype.hasOwnProperty.call(configModels, type)) {\n return true;\n }\n return false;\n}\n\nHooks.once(\"init\", async () => {\n console.log(\"ringworld | Initializing Ringworld system\");\n initializeImpulseTracker();\n\n const ActorRegistry = (foundry as any)?.documents?.collections?.Actors;\n const ItemRegistry = (foundry as any)?.documents?.collections?.Items;\n const BaseActorSheet = (foundry as any)?.appv1?.sheets?.ActorSheet;\n const BaseItemSheet = (foundry as any)?.appv1?.sheets?.ItemSheet;\n\n (CONFIG as any).ringworld = {\n hitLocations: HIT_LOCATION_TABLE\n };\n CONFIG.Combat.initiative = foundry.utils.mergeObject(CONFIG.Combat.initiative ?? {}, {\n formula: \"1d100\",\n decimals: 0\n }, { inplace: false });\n\n CONFIG.Actor.documentClass = RingworldActor as any;\n CONFIG.Item.documentClass = RingworldItem as any;\n\n if (!ActorRegistry || !ItemRegistry || !BaseActorSheet || !BaseItemSheet) {\n console.error(\"ringworld | Foundry sheet registries were not available during init.\");\n return;\n }\n\n ActorRegistry.unregisterSheet(\"core\", BaseActorSheet);\n ActorRegistry.registerSheet(\"ringworld\", RingworldExplorerSheet as any, {\n types: [\"explorer\"],\n makeDefault: true\n });\n ActorRegistry.registerSheet(\"ringworld\", RingworldCreatureSheet as any, {\n types: [\"creature\"],\n makeDefault: true\n });\n ActorRegistry.registerSheet(\"ringworld\", RingworldVehicleSheet as any, {\n types: [\"vehicle\"],\n makeDefault: true\n });\n\n ItemRegistry.unregisterSheet(\"core\", BaseItemSheet);\n ItemRegistry.registerSheet(\"ringworld\", RingworldSkillSheet as any, {\n types: [\"skill\"],\n makeDefault: true\n });\n ItemRegistry.registerSheet(\"ringworld\", RingworldWeaponSheet as any, {\n types: [\"weapon\"],\n makeDefault: true\n });\n ItemRegistry.registerSheet(\"ringworld\", RingworldArmorSheet as any, {\n types: [\"armor\"],\n makeDefault: true\n });\n ItemRegistry.registerSheet(\"ringworld\", RingworldGearSheet as any, {\n types: [\"gear\"],\n makeDefault: true\n });\n ItemRegistry.registerSheet(\"ringworld\", RingworldSpeciesSheet as any, {\n types: [\"species\"],\n makeDefault: true\n });\n if (itemTypeAvailable(\"pursuit\")) {\n ItemRegistry.registerSheet(\"ringworld\", RingworldPursuitSheet as any, {\n types: [\"pursuit\"],\n makeDefault: true\n });\n }\n\n game.settings.register(\"ringworld\", \"catalogSeedVersion\", {\n scope: \"world\",\n config: false,\n type: Number,\n default: 0\n });\n game.settings.register(\"ringworld\", \"encumbranceDataMigrationVersion\", {\n scope: \"world\",\n config: false,\n type: Number,\n default: 0\n });\n\n Handlebars.registerHelper(\"eq\", (a: unknown, b: unknown) => a === b);\n Handlebars.registerHelper(\"or\", (a: unknown, b: unknown) => Boolean(a || b));\n});\n\nHooks.once(\"ready\", async () => {\n const path = \"systems/ringworld/resources/hit-locations/humanoid.json\";\n try {\n const response = await fetch(path);\n if (response.ok) {\n const table = await response.json();\n (CONFIG as any).ringworld.hitLocations = table.entries ?? HIT_LOCATION_TABLE;\n }\n } catch (_error) {\n console.warn(`ringworld | Could not load ${path}, using built-in table`);\n }\n\n if (game.user?.isGM) {\n for (const combat of game.combats ?? []) {\n const formula = String(combat?.settings?.initiative?.formula ?? \"\").trim();\n if (!formula || formula.toLowerCase() === \"undefined\") {\n await combat.update({\n \"settings.initiative.formula\": \"1d100\",\n \"settings.initiative.decimals\": 0\n });\n }\n }\n\n const seededVersion = Number(game.settings.get(\"ringworld\", \"catalogSeedVersion\") ?? 0);\n if (seededVersion < RINGWORLD_CATALOG_VERSION) {\n const result = await seedWorldCatalog({ notify: true });\n if (result.added >= 0) {\n await game.settings.set(\"ringworld\", \"catalogSeedVersion\", RINGWORLD_CATALOG_VERSION);\n }\n }\n\n const migratedVersion = Number(game.settings.get(\"ringworld\", \"encumbranceDataMigrationVersion\") ?? 0);\n if (migratedVersion < ENCUMBRANCE_DATA_MIGRATION_VERSION) {\n const touched = await migrateActorItemEncumbranceData();\n await game.settings.set(\"ringworld\", \"encumbranceDataMigrationVersion\", ENCUMBRANCE_DATA_MIGRATION_VERSION);\n if (touched > 0) {\n ui.notifications?.info(`Ringworld encumbrance migration updated ${touched} actor items.`);\n }\n }\n }\n\n ensureSidebarDirectoryButtons();\n window.setTimeout(() => ensureSidebarDirectoryButtons(), 300);\n\n (game as any).ringworld = {\n ...((game as any).ringworld ?? {}),\n seedWorldCatalog: async (options?: { notify?: boolean }) => seedWorldCatalog(options),\n createRandomExplorer: async () => createRandomExplorer(),\n catalogVersion: RINGWORLD_CATALOG_VERSION,\n systemId: \"ringworld\",\n systemVersion: String(game.system?.version ?? \"0.0.0\")\n };\n Hooks.callAll(\"ringworldReady\", (game as any).ringworld);\n});\n\nHooks.on(\"createActor\", async (actor: any, _options: any, userId: string) => {\n if (!game.user?.isGM) return;\n if (userId !== game.user.id) return;\n if (!actor || (actor.type !== \"explorer\" && actor.type !== \"creature\")) return;\n\n try {\n const added = await actor.ensureBaseSkills?.();\n if (Number(added) > 0) {\n console.log(`ringworld | Added ${added} base skills to ${actor.name}`);\n }\n } catch (error) {\n console.error(\"ringworld | Failed to auto-populate base skills\", error);\n }\n});\n\nHooks.on(\"preCreateItem\", (item: any) => {\n if (item.type !== \"skill\") return;\n\n const updates: Record = {};\n const name = String(item.name || \"Skill\");\n const currentImg = String(item.img || \"\");\n updates.img = resolveSkillIcon(name, currentImg);\n\n const base = baseSkillByName(name);\n const currentSystem = (item as any).system ?? {};\n const currentValue = Number(currentSystem?.value ?? NaN);\n const currentCategory = String(currentSystem?.category ?? \"\").trim();\n\n updates.system = {\n value: Number.isFinite(currentValue) ? currentValue : (base?.base ?? 0),\n category: currentCategory || (base?.category ?? \"Technical\"),\n specialization: String(currentSystem?.specialization ?? base?.specialization ?? \"\")\n };\n\n if (!updates.img) updates.img = DEFAULT_SKILL_ICON;\n item.updateSource(updates);\n});\n\nHooks.on(\"renderItemDirectory\", (_app: any, html: JQuery) => {\n if (!game.user?.isGM) return;\n injectDirectoryButton(html, \"ringworld-seed-items\", \"Seed Ringworld Items\", handleSeedCatalogClick);\n ensureSidebarDirectoryButtons();\n});\n\nHooks.on(\"renderActorDirectory\", (_app: any, html: JQuery) => {\n if (!game.user?.isGM) return;\n injectDirectoryButton(html, \"ringworld-random-actor\", \"Create Random Explorer\", handleCreateRandomExplorerClick);\n ensureSidebarDirectoryButtons();\n});\n\nHooks.on(\"renderSidebar\", () => {\n ensureSidebarDirectoryButtons();\n});\n\nHooks.on(\"changeSidebarTab\", (app: any) => {\n const tabName = String(app?.tabName ?? app?.id ?? app?.options?.id ?? \"\");\n if (tabName === \"items\" || tabName === \"actors\") {\n ensureSidebarDirectoryButtons();\n }\n});\n"],"names":["showDiceSoNice","roll","dice3d","evaluateRollWithDice","formula","rollPercentile","target","classifyPercentile","cleanTarget","specialThreshold","fumbleThreshold","isSuccess","isSpecial","isFumble","outcome","outcomeLabel","SYSTEM_ID","HIT_LOCATION_TABLE","LIMB_LOCATIONS","LEG_LOCATIONS","isLimb","slug","normalize","value","withFactors","entries","hpFactors","entry","HUMANOID_SCHEMA","DOLPHIN_SCHEMA","PUPPETEER_SCHEMA","TRINOC_SCHEMA","BANDERSNATCHI_SCHEMA","KZINTI_SCHEMA","HIT_LOCATION_SCHEMAS","listHitLocationSchemas","inferHitLocationSchemaId","speciesValue","species","getHitLocationSchemaById","schemaId","key","getActorHitLocationSchema","actor","_a","_b","_c","mode","getTableForActor","getHitLocationFromRollForTable","table","rollHitLocation","total","chooseOrRollHitLocation","schema","entriesBySlug","labelFor","fallback","html","result","resolve","settled","done","dlgHtml","picker","event","button","location","rolled","loc","BASE_SKILL_CATEGORIES","RINGWORLD_BASE_SKILLS","normalizeSkillName","name","baseSkillNameSet","skill","DEFAULT_SKILL_ICON","iconSlug","skillIconPath","baseSkillByName","resolveSkillIcon","currentImg","img","createChatMessage","options","speaker","postSkillRollCard","data","payload","content","postAttackCard","num","n","getSingleTargetActor","targets","rollStandaloneHitLocation","rollSkillForActor","skillName","skillItem","item","resolveLinkedSkill","weapon","skillId","byId","linkedSkillName","byName","clampPercentTarget","promptAttackModifiers","attacker","baseSkillTarget","isRanged","defaultAim","safeActorName","safeWeaponName","safeActorImg","safeWeaponImg","_d","_e","_f","_g","root","query","aimModeRaw","aimMode","aimMultiplier","coverMod","movementMod","rangeMod","situationalMod","customMod","customLabel","flatModifierTotal","preAimTarget","adjustedByAim","finalTarget","parts","aimLabel","consumeWeaponEnergy","cost","packId","pack","current","next","getItemArmorAtLocation","specific","getTotalArmorAtLocation","targetActor","locArmor","stackedItemsArmor","sum","buildConditionUpdates","locationValue","locationMax","generalHp","syncCoreStatusEffects","conditions","toggler","applyLocationDamage","rawDamage","currentLoc","locMax","armor","finalDamage","unclampedLoc","nextLoc","nextGeneral","updates","useWeaponWorkflow","hasTarget","targetName","linkedSkill","modifierResult","energy","attack","remainingGeneralHp","hitSelection","damageFormula","damageRoll","application","recomputeHitPointMaximums","_h","con","mas","existingMax","existingValue","generalMax","hadExisting","wasFull","looksPlaceholder","preservedDamage","recomputedValue","generalAtFull","hpFactor","max","previousMax","previousValue","isPlaceholder","shouldBackfillToFull","RINGWORLD_CATALOG_VERSION","ICONS","speciesIconPath","weaponIconPath","armorIconPath","gearIconPath","pursuitIconPath","allCoverage","torsoCoverage","headCoverage","armCoverage","legCoverage","SPECIES_CATALOG","WEAPON_CATALOG","ARMOR_CATALOG","GEAR_CATALOG","PURSUIT_CATALOG","speciesCatalogForGeneration","itemKey","type","itemTypeAvailable","directTypes","docTypes","configModels","gameModels","toSkillItem","toSpeciesItem","toWeaponItem","folderKey","toArmorItem","toGearItem","toPursuitItem","pursuitAsGearNotes","fixed","optional","group","optionalText","toPursuitFallbackGearItem","itemName","getOrCreateItemFolder","parentId","existing","folder","ensureCatalogFolders","skills","pursuits","gear","weapons","weaponMelee","weaponRanged","buildAllSeeds","canUsePursuitType","seedWorldCatalog","folderIds","seeds","createPayload","updatePayload","seed","folderId","existingItem","update","existingSystem","mergedSystem","systemDiff","getPlayableSpeciesFromWorld","clampScore","rollTotal","rollEdu","edu","die","rollByGravity","gravity","gravityFormulas","randomPick","arr","randomExplorerName","speciesName","starts","ends","applySpeciesModifiers","base","speciesSystem","mods","rollCharacteristicsForActor","gravityType","currentSpeciesName","speciesItem","builtIn","finalValues","ownLanguage","applyStarterKit","starterNames","worldItems","actorExisting","worldItem","randomizeExplorerActor","worldSpecies","builtInSpecies","pickedWorldSpecies","pickedBuiltInSpecies","speciesChoice","homeworld","createRandomExplorer","SPECIES_MODIFIER_KEYS","hasValue","clampCharacteristic","computeDamageMod","str","computeDerivedRolls","characteristics","int","pow","dex","computeRootMaxes","app","computeActionRanking","gravityLoadMultiplier","contributesToEncumbrance","itemWeightKg","computeEncumbrance","system","items","gravityMultiplier","equippedLoadKg","rawLoadKg","effectiveLoadKg","limitKg","overLimitKg","actionRankingPenalty","overLimit","baseMove","baseRun","RingworldActor","profile","derivedRollDefaults","rootMaxDefaults","encumbrance","weaponId","toCreate","speciesData","incomingMods","appliedState","previousMods","currentCharacteristics","currentProfile","normalizedMods","prev","agePrev","ageNext","chronoCurrent","physCurrent","labels","defaults","RingworldItem","BaseActorSheet","ROOT_KEY_BY_CATEGORY","ROOT_FORMULA_BY_CATEGORY","EXCLUDED_EDUCATION_SKILLS","HYPERDRIVE_KEY","a","b","toInt","normalizeAllocation","raw","normalizePursuitSelection","years","normalizeBuilderState","allocations","allocation","normalized","baseBySkillId","isEducationEligible","isSpecialEligible","buildPursuitAllowedSkillSet","entriesByName","allowed","pursuit","computeBuilderPools","age","educationTotal","pursuitYearsTotal","pursuitsTotal","specialTotal","buildBuilderContext","pools","state","pursuitOptions","pursuitAllowedSkills","bySkillId","educationSpent","pursuitsSpent","specialSpent","totalAllocated","baseFromState","baseFromDefinition","inferredBase","baseValue","rootKey","rootCap","maxAllocation","normalizedSkillName","allowsEducation","allowsPursuits","allowsSpecial","pursuitYearsAllocated","row","fromItems","getPreparedHitLocation","rollRange","locationConfig","source","hp","rawLabel","chooseRandom","clampPercent","RingworldExplorerSheet","_i","_j","_k","_l","_m","_n","_o","_p","builderContext","armors","speciesOptions","baseSet","skillByNormalized","skillRootMax","skillsByCategory","category","rows","meta","customSkills","missingBaseSkillsCount","rangesFromSchema","hitLocations","bodyLocations","enc","encRaw","encEffective","encLimit","encPenalty","baseActionRanking","effectiveActionRanking","encumbranceSummary","movementSummary","fromBase","trimmed","cleanYears","context","nextState","existingIndex","index","totalYears","desiredCount","available","i","idx","remainingYears","seededState","freshContext","totalBySkill","maxForSkill","allocatePool","points","poolKey","eligible","remaining","guard","candidates","picked","educationUnspent","pursuitsUnspent","specialUnspent","maxEducation","maxPursuits","maxSpecial","maxByCap","form","requestedTotal","characterKey","label","multiplier","selected","safeMultiplier","doc","li","nextEquipped","nextStowed","added","specialization","itemData","speciesItems","createItems","names","patched","RingworldCreatureSheet","RingworldVehicleSheet","BaseItemSheet","RingworldSkillSheet","RingworldWeaponSheet","RingworldArmorSheet","RingworldGearSheet","RingworldSpeciesSheet","asArray","RingworldPursuitSheet","fixedSkills","optionalGroups","IMPULSE_FLAG","IMPULSE_HUD_POS_KEY","MAJOR_ACTION_OPTIONS","MINOR_ACTION_OPTIONS","esc","actionRankingFromDex","actionKindLabel","kind","getDexForCombatant","combatant","getCombatantActionRanking","fromFlag","getEncumbranceArPenalty","fromActor","syncCombatantActionRanking","ranking","getImpulseState","combat","queue","setImpulseState","sortQueueByCompletion","aDex","pruneQueueToCombatants","validIds","c","loadHudPosition","parsed","left","top","saveHudPosition","position","clearHudPosition","clampHudPosition","element","width","height","maxLeft","maxTop","applyHudAbsolutePosition","hud","el","clamped","makeHudDraggable","handle","saved","dragging","startX","startY","startLeft","startTop","mouseEvent","rect","moveEvent","evt","deltaX","deltaY","latestRect","getQueueWithNames","renderQueueRowsMarkup","limit","attachOverlayControls","disabled","advanceImpulse","resetImpulseSequence","renderChatImpulseMarker","chatEl","marker","renderFloatingImpulseHud","refreshImpulseOverlays","ensureGmControl","scheduleAction","combatantId","major","currentImpulse","actionRanking","impulseMod","effectiveAr","duration","completeImpulse","queueWithoutCombatant","timingLabel","cancelAction","nextQueue","resolveActionEvent","impulse","targetImpulse","completing","ordered","renderImpulsePanel","htmlLike","ar","impMod","effAr","pending","pendingLabel","aimState","opt","minorOptions","panel","child","select","impModInput","initializeImpulseTracker","ENCUMBRANCE_DATA_MIGRATION_VERSION","normName","worldItemWeightMap","map","migrateActorItemEncumbranceData","weightMap","touched","patch","changed","injectDirectoryButton","className","onClick","container","candidate","handleSeedCatalogClick","handleCreateRandomExplorerClick","ensureSidebarDirectoryButtons","itemElement","actorElement","ActorRegistry","ItemRegistry","path","response","_options","userId","error","currentSystem","currentValue","currentCategory","_app","tabName"],"mappings":"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"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9ecddf3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2755 @@ +{ + "name": "foundryvtt-ringworld", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "foundryvtt-ringworld", + "version": "0.1.0", + "devDependencies": { + "@league-of-foundry-developers/foundry-vtt-types": "^12.331.3", + "sass": "^1.93.2", + "typescript": "^5.9.3", + "vite": "^5.4.21" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@league-of-foundry-developers/foundry-vtt-types": { + "version": "12.331.5", + "resolved": "https://registry.npmjs.org/@league-of-foundry-developers/foundry-vtt-types/-/foundry-vtt-types-12.331.5.tgz", + "integrity": "sha512-Ncyv5W5dtE8LdVxjYLfrx2ujQIuBPch5bDcnHaqKTL4wVEE8jgCE7amPkGHltFLilLTZ7Ki3veuFDURzpDIgbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pixi/basis": "^7.4.2", + "@pixi/graphics-smooth": "^1.1.0", + "@pixi/particle-emitter": "^5.0.8", + "@types/earcut": "^3.0.0", + "@types/jquery": "~3.5.22", + "@types/showdown": "~2.0.6", + "@types/simple-peer": "~9.11.1", + "@types/youtube": "~0.0.48", + "earcut": "^3.0.1", + "handlebars": "^4.7.8", + "pixi.js": "^7.4.2", + "postinstall-postinstall": "^2.1.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.5.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.0", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.21.0", + "prosemirror-schema-list": "^1.3.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.3.7", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.6", + "socket.io-client": "^4.7.5", + "tinymce": "^6.8.3" + }, + "peerDependencies": { + "typescript": "^5.4" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pixi/accessibility": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.4.3.tgz", + "integrity": "sha512-tCr0yeWpMe0yucFvEPidy5a7gVJGpTjqGrDpSEBYT/kbScfUwcoX49RrckCCCiXDlyO4WRh9lVVuHXTvqRLIMg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/events": "7.4.3" + } + }, + "node_modules/@pixi/app": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.4.3.tgz", + "integrity": "sha512-opyWMuO0Ir8pf1DYUR++wAA6ZfNU+nIX2z95R2OD172HbcdhB4/HD7leLIIAny/LciEdMqlWEBhXK7N93YWbdg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/assets": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.4.3.tgz", + "integrity": "sha512-StvjiJBSp/j9hHkGu8AFHNvwYUazXq64WhyhytztyDMRkg/l/cL7EcttY5T0qZNWlIpccdr60LUKrWDOuMpkiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/css-font-loading-module": "^0.0.12" + }, + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/basis": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/basis/-/basis-7.4.3.tgz", + "integrity": "sha512-dnRk8fEP4Y4g7EYUXtRX+Xrgu6C/tvqOdCNvfHAazYoDQ4MT484gp/1jEEabsLcH9tFgftXu/+ZVcUbwzdd8qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/compressed-textures": "7.4.3", + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/color": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz", + "integrity": "sha512-a6R+bXKeXMDcRmjYQoBIK+v2EYqxSX49wcjAY579EYM/WrFKS98nSees6lqVUcLKrcQh2DT9srJHX7XMny3voQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pixi/colord": "^2.9.6" + } + }, + "node_modules/@pixi/colord": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", + "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pixi/compressed-textures": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.4.3.tgz", + "integrity": "sha512-uJ3CC+lNX4HIxs6IxEESO50/0A1KxSVm6CO9UlkXzTsNj9ynmdy5BkJ1dzii7LCdqGcHIXHO01yvKuUbJBBQtw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/constants": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.3.tgz", + "integrity": "sha512-QGmwJUNQy/vVEHzL6VGQvnwawLZ1wceZMI8HwJAT4/I2uAzbBeFDdmCS8WsTpSWLZjF/DszDc1D8BFp4pVJ5UQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pixi/core": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.4.3.tgz", + "integrity": "sha512-5YDs11faWgVVTL8VZtLU05/Fl47vaP5Tnsbf+y/WRR0VSW3KhRRGTBU1J3Gdc2xEWbJhUK07KGP7eSZpvtPVgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pixi/color": "7.4.3", + "@pixi/constants": "7.4.3", + "@pixi/extensions": "7.4.3", + "@pixi/math": "7.4.3", + "@pixi/runner": "7.4.3", + "@pixi/settings": "7.4.3", + "@pixi/ticker": "7.4.3", + "@pixi/utils": "7.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/@pixi/display": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.4.3.tgz", + "integrity": "sha512-b5m2dAaoNAVdxz1oDaxl3XZ059NEOcNtGkxTOZ4EYCw/jcp9sZXkgSROHRzsGn4k+NugH7+9MP4Id2Z0kkdUhw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/events": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.4.3.tgz", + "integrity": "sha512-o3j/5Dxq6WDVS6eHfURB/cf/MP+NcsF/eC5PnbSHjXxJmDE7PoTVwLvxexm5uuvNRpFh/6/Fn0V8Vl4gV8sc8w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/extensions": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.3.tgz", + "integrity": "sha512-FhoiYkHQEDYHUE7wXhqfsTRz6KxLXjuMbSiAwnLb9uG1vAgp6q6qd6HEsf4X30YaZbLFY8a4KY6hFZWjF+4Fdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pixi/extract": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.4.3.tgz", + "integrity": "sha512-HNvGNrEVaeVsbcnIO1MsHpjZbTwo9nIlaOEBzDGcL6JWwzuB1RnzUke7WUCndCUt91sGUdvPnvgCvy9/NNFg3w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-alpha": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.4.3.tgz", + "integrity": "sha512-YFdUB1I53USQb+9TEhS849dV2KZhbnNGIoBbOSThUJfXQc4pDguIFWMagVToAQYgmZ4C4AtYfVjaSEELrMcCdA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-blur": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.4.3.tgz", + "integrity": "sha512-ZFzS9L/whdRbs5A/EUgF3yQaBcxNarmbuwaMgrfnpQ84mRczkGByqDLGToadiufyals07ufTrXBGRle9lbtEDA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-color-matrix": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.4.3.tgz", + "integrity": "sha512-TNu0h20SrzjUWIb5v19dAp1vPpqtG0w2XF9kIHN91bMNaf3R1jzhpWG6TtaVO9eo1IolWcEJLw38jIohyC+KNw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-displacement": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.4.3.tgz", + "integrity": "sha512-ax+cFA2mEnKgqf9F8qInpv09GNWzjwnASLETpwPXzWBtlAlNCeHV2tCv3+SlMdEKUkwG9sA7AmjjjC2JBUyt+Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-fxaa": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.4.3.tgz", + "integrity": "sha512-y9jhho5cCflhEsPtNqqsd+XJHsb+/ysht4rG/VHQ8Z6pScHYpbgiEpowryGq8uSMQQwx6zKNS2DPiXdiOHPZsg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-noise": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.4.3.tgz", + "integrity": "sha512-rwgSO3BKe1jW/P5CaOcfLKjfpl674aBEo/igi/3QLxA3ORhILNqWRsKkOwP8xF/ejI5NE4rMEkdv0LScbdGFhA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/graphics": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.4.3.tgz", + "integrity": "sha512-wWLivD8/URb8A7X4TqCZGG39C91IE+aOuWY/z9NCz5Z6WvA/VWnsc5fLTlO+ggjGHgKF0cSucCXZfUe1wm0AOQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/graphics-smooth": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@pixi/graphics-smooth/-/graphics-smooth-1.1.1.tgz", + "integrity": "sha512-9xIFWZhHGEb6KCnyWL6TVPYG/QkF0YDM/yDU5EvjTQbaj/1cITrXtI5P3tBkB5H0DQi+8J8/QS38MjfqNEJAYQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "^7.2.0", + "@pixi/display": "^7.2.0", + "@pixi/graphics": "^7.2.0" + } + }, + "node_modules/@pixi/math": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.3.tgz", + "integrity": "sha512-/uJOVhR2DOZ+zgdI6Bs/CwcXT4bNRKsS+TqX3ekRIxPCwaLra+Qdm7aDxT5cTToDzdxbKL5+rwiLu3Y1egILDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pixi/mesh": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.4.3.tgz", + "integrity": "sha512-CikqFPtKvU3Zj986/MSoC8X39CWv5CEpiEW/tYp47p4tgQNDSkNWYnDiNYgb+4VX6pNsBrgX4DALLdTR17SlSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/mesh-extras": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.4.3.tgz", + "integrity": "sha512-EqpxpVZoTObyupxMSzuUsCGmWPQioW84n9EO9Ajawkk/HYA+qKFZ5viKiEThIUBYgv4Apn/7c0U3Feg7Ez4uQQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/mesh": "7.4.3" + } + }, + "node_modules/@pixi/mixin-cache-as-bitmap": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.4.3.tgz", + "integrity": "sha512-NgvDdgSgd2tfcTSc+SWF12JJjVVz5ZrkSlhX0idSp/LSako82AiFJlD2xqH9GUsEcA6sqBBlnu7nrGkPTHQdhA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/mixin-get-child-by-name": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.4.3.tgz", + "integrity": "sha512-HLhDxHwafQT+CxbqQx9w9ivJIyAOg9JJ/6m4fNymVuDWeuMGcxDxBD7DukdUYIieT+RD/RlxdPEmq8YoromlFA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/mixin-get-global-position": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.4.3.tgz", + "integrity": "sha512-k09kvkS379EypCIWgXMY7uiXtWk1BsaJyTYlV16Co0AsmNPdFd+wUozMx1xV6rxcGiWXsxr/1k9fbETuYkcXCQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/particle-container": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.4.3.tgz", + "integrity": "sha512-0DfJF5C0XTfuI2FsLYvMKCOtqWjXWGOWfA6m4l0W/Ke/qw5zKIOEhgjPLw4qNRtOhmEfkVKJUGp66Ap/ya2YzA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/particle-emitter": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@pixi/particle-emitter/-/particle-emitter-5.0.10.tgz", + "integrity": "sha512-GF8AvL2KjVAE+n3FFRjCvE6cmn+rBCQayp9NR8rw5oJ95cx5inKx6DwNyOqSLHyPqNt/tfb9XGS+6P1Wun6yQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/constants": ">=6.0.4 <8.0.0", + "@pixi/core": ">=6.0.4 <8.0.0", + "@pixi/display": ">=6.0.4 <8.0.0", + "@pixi/math": ">=6.0.4 <8.0.0", + "@pixi/sprite": ">=6.0.4 <8.0.0", + "@pixi/ticker": ">=6.0.4 <8.0.0" + } + }, + "node_modules/@pixi/prepare": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.4.3.tgz", + "integrity": "sha512-OjJHGKXPzwP5OLKxBnTBnKMOktHynLvO0TQPqTYgNtmGQzY109mypCqM4M+s/V+uYmBo/T+sXvBahj98q/f1tA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/graphics": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/runner": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.3.tgz", + "integrity": "sha512-TJyfp7y23u5vvRAyYhVSa7ytq0PdKSvPLXu4G3meoFh1oxTLHH6g/RIzLuxUAThPG2z7ftthuW3qWq6dRV+dhw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pixi/settings": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.3.tgz", + "integrity": "sha512-SmGK8smc0PxRB9nr0UJioEtE9hl4gvj9OedCvZx3bxBwA3omA5BmP3CyhQfN8XJ29+o2OUL01r3zAPVol4l4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pixi/constants": "7.4.3", + "@types/css-font-loading-module": "^0.0.12", + "ismobilejs": "^1.1.0" + } + }, + "node_modules/@pixi/sprite": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.4.3.tgz", + "integrity": "sha512-iNBrpOFF9nXDT6m2jcyYy6l/sRzklLDDck1eFHprHZwvNquY2nzRfh+RGBCecxhBcijiLJ3fsZN33fP0LDXkvw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/sprite-animated": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.4.3.tgz", + "integrity": "sha512-mw5YIec8KfO1Jv9qrDNvGoD7Dlmcgww5YlMtd+ARi7Zzo+6ziNw899LXtoaKX1+3BXdZbYNyJAx3C5r30NtwXA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/sprite-tiling": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.4.3.tgz", + "integrity": "sha512-kUa9cEcMsGXSIZoXA7LhW4oo0eWa30w0KYd7mZ0bqalBMfOcvsGZMN701Lc5lpE8URw+8yu5bnyGLbrxhWBTuw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/spritesheet": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.4.3.tgz", + "integrity": "sha512-Ce4xZzUxUSKfiROUjjVCBYNLuCcDEWKJ822bSV9rkgVHItu3q04VnEww0DXO+9K0hKv4Ukjjk8aP6Pz0LgPm7A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/text": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.4.3.tgz", + "integrity": "sha512-IAF0iu04rPg3oiL0HZsEZI44fpJxq3UZ4xTmx8l1RyhhSXiElLvvSlSH57vt/BKMQZtCs+AqEit7yn8heK2+nQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/text-bitmap": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.4.3.tgz", + "integrity": "sha512-TnBocJm7f5nMAYwYcsojc62uCrOYauAGH26o3pNrlqmHDRDQ7FzPOGvkYZGYFREbUycloLSRlYpSy0FB9ZdV4Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/mesh": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/text-html": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.4.3.tgz", + "integrity": "sha512-nm9K9gjSZAU8ETwQZBE3kMGNdO1IzyghxoRTcJCWKhekiGDpUQhopfNhqieNZ7reVJpvhpFQWjbyaHDehndUaQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/ticker": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.3.tgz", + "integrity": "sha512-tHsAD0iOUb6QSGGw+c8cyRBvxsq/NlfzIFBZLEHhWZ+Bx4a0MmXup6I/yJDGmyPCYE+ctCcAfY13wKAzdiVFgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pixi/extensions": "7.4.3", + "@pixi/settings": "7.4.3", + "@pixi/utils": "7.4.3" + } + }, + "node_modules/@pixi/utils": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.3.tgz", + "integrity": "sha512-NO3Y9HAn2UKS1YdxffqsPp+kDpVm8XWvkZcS/E+rBzY9VTLnNOI7cawSRm+dacdET3a8Jad3aDKEDZ0HmAqAFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pixi/color": "7.4.3", + "@pixi/constants": "7.4.3", + "@pixi/settings": "7.4.3", + "@types/earcut": "^2.1.0", + "earcut": "^2.2.4", + "eventemitter3": "^4.0.0", + "url": "^0.11.0" + } + }, + "node_modules/@pixi/utils/node_modules/@types/earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pixi/utils/node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", + "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/earcut": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-3.0.0.tgz", + "integrity": "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jquery": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.34.tgz", + "integrity": "sha512-3m3939S3erqmTLJANS/uy0B6V7BorKx7RorcGZVjZ62dF5PAGbKEDZK1CuLtKombJkFA2T1jl8LAIIs7IV6gBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sizzle": "*" + } + }, + "node_modules/@types/node": { + "version": "25.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", + "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/showdown": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", + "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/simple-peer": { + "version": "9.11.9", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.9.tgz", + "integrity": "sha512-6Gdl7TSS5oh9nuwKD4Pl8cSmaxWycYeZz9HLnJBNvIwWjZuGVsmHe9RwW3+9RxfhC1aIR9Z83DvaJoMw6rhkbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/youtube": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/youtube/-/youtube-0.0.50.tgz", + "integrity": "sha512-d4GpH4uPYp9W07kc487tiq6V/EUHl18vZWFMbQoe4Sk9LXEWzFi/BMf9x7TI4m7/j7gU3KeX8H6M8aPBgykeLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ismobilejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", + "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pixi.js": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.4.3.tgz", + "integrity": "sha512-uIWdH0EI2dVgNoqN9aFaHCmR0V65OEhMkXs2sek3c/QP2ItV6UoM+ouX9esSv3ibo20F+J5D1XwnQhUZI6wqeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pixi/accessibility": "7.4.3", + "@pixi/app": "7.4.3", + "@pixi/assets": "7.4.3", + "@pixi/compressed-textures": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/events": "7.4.3", + "@pixi/extensions": "7.4.3", + "@pixi/extract": "7.4.3", + "@pixi/filter-alpha": "7.4.3", + "@pixi/filter-blur": "7.4.3", + "@pixi/filter-color-matrix": "7.4.3", + "@pixi/filter-displacement": "7.4.3", + "@pixi/filter-fxaa": "7.4.3", + "@pixi/filter-noise": "7.4.3", + "@pixi/graphics": "7.4.3", + "@pixi/mesh": "7.4.3", + "@pixi/mesh-extras": "7.4.3", + "@pixi/mixin-cache-as-bitmap": "7.4.3", + "@pixi/mixin-get-child-by-name": "7.4.3", + "@pixi/mixin-get-global-position": "7.4.3", + "@pixi/particle-container": "7.4.3", + "@pixi/prepare": "7.4.3", + "@pixi/sprite": "7.4.3", + "@pixi/sprite-animated": "7.4.3", + "@pixi/sprite-tiling": "7.4.3", + "@pixi/spritesheet": "7.4.3", + "@pixi/text": "7.4.3", + "@pixi/text-bitmap": "7.4.3", + "@pixi/text-html": "7.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postinstall-postinstall": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz", + "integrity": "sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", + "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz", + "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.6", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz", + "integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinymce": { + "version": "6.8.6", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.6.tgz", + "integrity": "sha512-++XYEs8lKWvZxDCjrr8Baiw7KiikraZ5JkLMg6EdnUVNKJui0IsrAADj5MsyUeFkcEryfn2jd3p09H7REvewyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3b294c2 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "foundryvtt-ringworld", + "version": "0.1.0", + "description": "Ringworld Foundry VTT system (v12)", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite build --watch", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@league-of-foundry-developers/foundry-vtt-types": "^12.331.3", + "sass": "^1.93.2", + "typescript": "^5.9.3", + "vite": "^5.4.21" + } +} diff --git a/resources/hit-locations/humanoid.json b/resources/hit-locations/humanoid.json new file mode 100644 index 0000000..2a965bf --- /dev/null +++ b/resources/hit-locations/humanoid.json @@ -0,0 +1,14 @@ +{ + "id": "humanoid", + "label": "Humanoid (Ringworld)", + "die": "1d20", + "entries": [ + { "slug": "rLeg", "label": "Right Leg", "min": 1, "max": 3 }, + { "slug": "lLeg", "label": "Left Leg", "min": 4, "max": 6 }, + { "slug": "abdomen", "label": "Abdomen", "min": 7, "max": 10 }, + { "slug": "chest", "label": "Chest", "min": 11, "max": 15 }, + { "slug": "rArm", "label": "Right Arm", "min": 16, "max": 17 }, + { "slug": "lArm", "label": "Left Arm", "min": 18, "max": 19 }, + { "slug": "head", "label": "Head", "min": 20, "max": 20 } + ] +} diff --git a/ringworld-content/README.md b/ringworld-content/README.md new file mode 100644 index 0000000..ac40dae --- /dev/null +++ b/ringworld-content/README.md @@ -0,0 +1,22 @@ +# Ringworld Content Module + +Companion module for the Ringworld Foundry system. + +## What it does + +- Adds a GM-only button to the Items directory: **Import Ringworld Content** +- One click imports/updates: + - skills + - species + - pursuits + - weapons + - armor + - gear + +It uses the Ringworld system API (`game.ringworld.seedWorldCatalog`) and keeps content synced. + +## Requires + +- Foundry world running the `ringworld` system +- Ringworld system version `0.1.0` or later + diff --git a/ringworld-content/module.json b/ringworld-content/module.json new file mode 100644 index 0000000..aaf44c0 --- /dev/null +++ b/ringworld-content/module.json @@ -0,0 +1,33 @@ +{ + "id": "ringworld-content", + "title": "Ringworld Content", + "description": "Companion content module for Ringworld: one-click import for skills, species, pursuits, weapons, armor, and gear.", + "version": "0.1.0", + "url": "https://github.com/slaguru666/Ringworld", + "manifest": "https://raw.githubusercontent.com/slaguru666/Ringworld/main/ringworld-content/module.json", + "download": "https://raw.githubusercontent.com/slaguru666/Ringworld/main/dist/ringworld-content.zip", + "compatibility": { + "minimum": "12", + "verified": "13.351", + "maximum": "13" + }, + "authors": [ + { + "name": "Codex" + } + ], + "esmodules": [ + "module/ringworld-content.mjs" + ], + "relationships": { + "systems": [ + { + "id": "ringworld", + "type": "system", + "compatibility": { + "minimum": "0.1.0" + } + } + ] + } +} diff --git a/ringworld-content/module/ringworld-content.mjs b/ringworld-content/module/ringworld-content.mjs new file mode 100644 index 0000000..7ec0ba9 --- /dev/null +++ b/ringworld-content/module/ringworld-content.mjs @@ -0,0 +1,75 @@ +const MODULE_ID = "ringworld-content"; + +function isRingworldWorld() { + return String(game.system?.id || "") === "ringworld"; +} + +function canShowControls() { + return isRingworldWorld() && Boolean(game.user?.isGM); +} + +function getRingworldApi() { + return game.ringworld || null; +} + +async function importRingworldContent() { + if (!canShowControls()) return; + const api = getRingworldApi(); + if (!api?.seedWorldCatalog) { + ui.notifications?.error("Ringworld API unavailable. Ensure Ringworld system is active."); + return; + } + + const result = await api.seedWorldCatalog({ notify: true }); + if (result && typeof result.added === "number") { + ui.notifications?.info(`Ringworld Content import complete: +${result.added}, updated ${result.updated || 0}.`); + } +} + +function injectDirectoryButton(htmlLike) { + if (!canShowControls()) return; + if (!htmlLike) return; + const html = ("find" in htmlLike) ? htmlLike : $(htmlLike); + if (!html?.length || html.find(".ringworld-content-import").length) return; + + const button = $( + `` + ); + button.on("click", async () => { + await importRingworldContent(); + }); + + const candidates = [ + html.find(".directory-footer"), + html.find("footer.directory-footer"), + html.find("footer"), + html.find(".footer-actions"), + html.find(".header-actions"), + html.find(".action-buttons") + ]; + const container = candidates.find((candidate) => candidate.length); + if (container) { + container.first().append(button); + return; + } + + html.append($('
').append(button)); +} + +Hooks.once("ready", () => { + if (!canShowControls()) return; + game.ringworldContent = { + importCatalog: importRingworldContent + }; +}); + +Hooks.on("renderItemDirectory", (_app, html) => { + injectDirectoryButton(html); +}); + +Hooks.on("renderSidebar", () => { + if (!canShowControls()) return; + const itemsElement = ui.items?.element; + injectDirectoryButton(itemsElement); +}); + diff --git a/seeds/base-skills.json b/seeds/base-skills.json new file mode 100644 index 0000000..3fec4fa --- /dev/null +++ b/seeds/base-skills.json @@ -0,0 +1,59 @@ +[ + { "name": "Archaic Melee Weapon", "type": "skill", "system": { "value": 5, "category": "Agility", "specialization": "" } }, + { "name": "Archaic Ranged Weapons", "type": "skill", "system": { "value": 3, "category": "Agility", "specialization": "" } }, + { "name": "Athletics", "type": "skill", "system": { "value": 15, "category": "Agility", "specialization": "" } }, + { "name": "Hide", "type": "skill", "system": { "value": 10, "category": "Agility", "specialization": "" } }, + { "name": "Sneak", "type": "skill", "system": { "value": 5, "category": "Agility", "specialization": "" } }, + { "name": "Unarmed Combat", "type": "skill", "system": { "value": 0, "category": "Agility", "specialization": "" } }, + { "name": "V. Sword, F. Laser", "type": "skill", "system": { "value": 15, "category": "Agility", "specialization": "" } }, + + { "name": "Bargain", "type": "skill", "system": { "value": 10, "category": "Communication", "specialization": "" } }, + { "name": "Debate", "type": "skill", "system": { "value": 5, "category": "Communication", "specialization": "" } }, + { "name": "Fast Talk", "type": "skill", "system": { "value": 10, "category": "Communication", "specialization": "" } }, + { "name": "Fine Arts", "type": "skill", "system": { "value": 5, "category": "Communication", "specialization": "" } }, + { "name": "Musicianship", "type": "skill", "system": { "value": 5, "category": "Communication", "specialization": "" } }, + { "name": "Orate", "type": "skill", "system": { "value": 5, "category": "Communication", "specialization": "" } }, + { "name": "Own Language", "type": "skill", "system": { "value": 0, "category": "Communication", "specialization": "INTx5 base" } }, + { "name": "Perform", "type": "skill", "system": { "value": 5, "category": "Communication", "specialization": "" } }, + { "name": "Psychology", "type": "skill", "system": { "value": 0, "category": "Communication", "specialization": "" } }, + + { "name": "Anthropology", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Astronomy", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Biology", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Botany", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Chemistry", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Computers", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Emergency Treatment", "type": "skill", "system": { "value": 1, "category": "Knowledge", "specialization": "" } }, + { "name": "Engineering", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Farming", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "History", "type": "skill", "system": { "value": 5, "category": "Knowledge", "specialization": "" } }, + { "name": "Law", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Mathematics", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Physics", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Planetology", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Second Languages", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Strategy", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Theology", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + { "name": "Zoology", "type": "skill", "system": { "value": 0, "category": "Knowledge", "specialization": "" } }, + + { "name": "Handgun, Energy", "type": "skill", "system": { "value": 5, "category": "Perception", "specialization": "" } }, + { "name": "Handgun, Projectile", "type": "skill", "system": { "value": 3, "category": "Perception", "specialization": "" } }, + { "name": "Heavy Weapon, Energy", "type": "skill", "system": { "value": 5, "category": "Perception", "specialization": "" } }, + { "name": "Heavy Weapon, Projectile", "type": "skill", "system": { "value": 3, "category": "Perception", "specialization": "" } }, + { "name": "Listen", "type": "skill", "system": { "value": 5, "category": "Perception", "specialization": "" } }, + { "name": "Observe", "type": "skill", "system": { "value": 5, "category": "Perception", "specialization": "" } }, + { "name": "Scent", "type": "skill", "system": { "value": 0, "category": "Perception", "specialization": "" } }, + { "name": "Search", "type": "skill", "system": { "value": 5, "category": "Perception", "specialization": "" } }, + { "name": "Track", "type": "skill", "system": { "value": 5, "category": "Perception", "specialization": "" } }, + + { "name": "Aquatic Vehicle", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Atmospheric Craft", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Ground Vehicle", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Hyperdrive", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Personal Flyer", "type": "skill", "system": { "value": 15, "category": "Technical", "specialization": "" } }, + { "name": "Reaction Drive", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Reactionless Drive", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Repair", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Ringworld", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } }, + { "name": "Weapons System", "type": "skill", "system": { "value": 0, "category": "Technical", "specialization": "" } } +] diff --git a/seeds/example-explorer.json b/seeds/example-explorer.json new file mode 100644 index 0000000..f435c5c --- /dev/null +++ b/seeds/example-explorer.json @@ -0,0 +1,87 @@ +{ + "name": "Sample Explorer", + "type": "explorer", + "system": { + "characteristics": { + "str": 12, + "mas": 11, + "con": 13, + "int": 12, + "pow": 11, + "dex": 14, + "app": 10, + "edu": 12, + "age": 28 + }, + "derived": { + "actionRanking": 2, + "move": 3 + }, + "generalHp": { + "value": 24, + "max": 24 + }, + "resources": { + "energy": { + "value": 12, + "max": 12 + } + } + }, + "items": [ + { + "name": "Handgun, Energy", + "type": "skill", + "system": { + "value": 52, + "category": "Perception", + "specialization": "" + } + }, + { + "name": "Observe", + "type": "skill", + "system": { + "value": 45, + "category": "Perception", + "specialization": "" + } + }, + { + "name": "Laser Pistol", + "type": "weapon", + "system": { + "linkedSkill": "Handgun, Energy", + "linkedSkillId": "", + "attackType": "ranged", + "damageFormula": "1d10+1", + "range": { + "close": "Short", + "medium": "Medium", + "long": "Long" + }, + "energyUsedPerAttack": 2, + "ammoUsedPerAttack": 0, + "powerSource": "powerPack", + "powerPackId": "", + "notes": "Link to Power Pack A after import" + } + }, + { + "name": "Power Pack A", + "type": "gear", + "system": { + "weight": 1, + "cost": 100, + "energyUse": 0, + "powerSupplyNotes": "Standard power pack", + "isPowerPack": true, + "energy": { + "value": 20, + "max": 20 + }, + "notes": "" + } + } + ] +} diff --git a/seeds/example-items.json b/seeds/example-items.json new file mode 100644 index 0000000..ae06a67 --- /dev/null +++ b/seeds/example-items.json @@ -0,0 +1,74 @@ +[ + { + "name": "Handgun, Energy", + "type": "skill", + "system": { + "value": 45, + "category": "Perception", + "specialization": "" + } + }, + { + "name": "Laser Pistol", + "type": "weapon", + "system": { + "linkedSkill": "Handgun, Energy", + "linkedSkillId": "", + "attackType": "ranged", + "damageFormula": "1d10+1", + "range": { + "close": "Short", + "medium": "Medium", + "long": "Long" + }, + "energyUsedPerAttack": 2, + "ammoUsedPerAttack": 0, + "powerSource": "powerPack", + "powerPackId": "", + "notes": "Sample energy sidearm" + } + }, + { + "name": "Soft Combat Vest", + "type": "armor", + "system": { + "equipped": true, + "value": 2, + "coverage": { + "rLeg": false, + "lLeg": false, + "abdomen": true, + "chest": true, + "rArm": false, + "lArm": false, + "head": false + }, + "perLocation": { + "rLeg": 0, + "lLeg": 0, + "abdomen": 0, + "chest": 0, + "rArm": 0, + "lArm": 0, + "head": 0 + }, + "notes": "Sample armor" + } + }, + { + "name": "Power Pack A", + "type": "gear", + "system": { + "weight": 1, + "cost": 100, + "energyUse": 0, + "powerSupplyNotes": "Standard power pack", + "isPowerPack": true, + "energy": { + "value": 20, + "max": 20 + }, + "notes": "Sample pack for energy weapons" + } + } +] diff --git a/src/module/actor/RingworldActor.ts b/src/module/actor/RingworldActor.ts new file mode 100644 index 0000000..4d0e845 --- /dev/null +++ b/src/module/actor/RingworldActor.ts @@ -0,0 +1,335 @@ +import { recomputeHitPointMaximums, rollSkillForActor, rollStandaloneHitLocation, useWeaponWorkflow } from "../mechanics"; +import { normalizeSkillName, RINGWORLD_BASE_SKILLS } from "../data-base-skills"; +import { randomizeExplorerActor, rollCharacteristicsForActor } from "../character-tools"; +import { skillIconPath } from "../skill-icons"; + +type DerivedCheckKey = "health" | "reasoning" | "luck" | "dodge"; +type SpeciesModifierKey = "str" | "mas" | "con" | "int" | "pow" | "dex" | "app" | "edu" | "age"; +const SPECIES_MODIFIER_KEYS: SpeciesModifierKey[] = ["str", "mas", "con", "int", "pow", "dex", "app", "edu", "age"]; + +function num(value: unknown, fallback = 0): number { + const n = Number(value); + return Number.isFinite(n) ? n : fallback; +} + +function hasValue(value: unknown): boolean { + return value !== null && value !== undefined && String(value).trim() !== ""; +} + +function clampCharacteristic(value: number): number { + return Math.max(1, Math.min(99, Math.floor(value))); +} + +function computeDamageMod(str: number, mas: number): string { + const total = str + mas; + if (total <= 12) return "-1d6"; + if (total <= 24) return "-1d4"; + if (total <= 32) return "0"; + if (total <= 40) return "+1d4"; + if (total <= 56) return "+1d6"; + if (total <= 72) return "+2d6"; + return "+3d6"; +} + +function computeDerivedRolls(characteristics: any): Record { + const con = num(characteristics?.con, 10); + const int = num(characteristics?.int, 10); + const pow = num(characteristics?.pow, 10); + const dex = num(characteristics?.dex, 10); + + return { + health: Math.max(0, Math.min(99, con * 5)), + reasoning: Math.max(0, Math.min(99, int * 5)), + luck: Math.max(0, Math.min(99, pow * 5)), + dodge: Math.max(0, Math.min(99, dex * 5)) + }; +} + +function computeRootMaxes(characteristics: any): Record { + const str = num(characteristics?.str, 10); + const dex = num(characteristics?.dex, 10); + const app = num(characteristics?.app, 10); + const edu = num(characteristics?.edu, 10); + const int = num(characteristics?.int, 10); + const pow = num(characteristics?.pow, 10); + const con = num(characteristics?.con, 10); + + return { + agility: Math.max(0, str + dex), + communication: Math.max(0, int + app), + knowledge: Math.max(0, int + edu), + perception: Math.max(0, pow + con), + technical: Math.max(0, dex + int) + }; +} + +function computeActionRanking(dex: number): number { + if (dex <= 4) return 7; + if (dex <= 8) return 6; + if (dex <= 12) return 5; + if (dex <= 16) return 4; + if (dex <= 20) return 3; + if (dex <= 24) return 2; + return 1; +} + +function gravityLoadMultiplier(gravity: string): number { + if (gravity === "light") return 0.5; + if (gravity === "heavy") return 2; + return 1; +} + +function contributesToEncumbrance(item: any): boolean { + if (item.type !== "weapon" && item.type !== "armor" && item.type !== "gear") return false; + return item.system?.stowed !== true; +} + +function itemWeightKg(item: any): number { + return Math.max(0, num(item.system?.weight, 0)); +} + +function computeEncumbrance(system: any, items: any[]): { + rawLoadKg: number; + effectiveLoadKg: number; + limitKg: number; + overLimitKg: number; + actionRankingPenalty: number; + overLimit: boolean; + currentMove: number; + currentRun: number; + gravityMultiplier: number; +} { + const gravity = String(system?.profile?.gravity || "normal"); + const gravityMultiplier = gravityLoadMultiplier(gravity); + const equippedLoadKg = items + .filter((item) => item.type === "weapon" || item.type === "armor" || item.type === "gear") + .filter(contributesToEncumbrance) + .reduce((sum, item) => sum + itemWeightKg(item), 0); + + const rawLoadKg = Math.max(0, equippedLoadKg); + const effectiveLoadKg = rawLoadKg * gravityMultiplier; + const limitKg = Math.max(0, num(system?.characteristics?.str, 0)); + const overLimitKg = Math.max(0, effectiveLoadKg - limitKg); + const actionRankingPenalty = overLimitKg > 0 ? Math.ceil(overLimitKg / 10) : 0; + const overLimit = overLimitKg > 0; + + const baseMove = Math.max(1, num(system?.derived?.move, 3)); + const baseRun = Math.max(1, num(system?.derived?.run, baseMove * 2)); + const currentMove = overLimit ? 2 : baseMove; + const currentRun = overLimit ? 4 : baseRun; + + return { + rawLoadKg, + effectiveLoadKg, + limitKg, + overLimitKg, + actionRankingPenalty, + overLimit, + currentMove, + currentRun, + gravityMultiplier + }; +} + +export class RingworldActor extends Actor { + prepareDerivedData(): void { + super.prepareDerivedData(); + recomputeHitPointMaximums(this as any); + + if (this.type !== "explorer" && this.type !== "creature") return; + + const system = (this as any).system ?? {}; + const characteristics = system.characteristics ?? {}; + const profile = system.profile ?? {}; + + profile.chronologicalAge = num(profile.chronologicalAge, num(characteristics.age, 25)); + profile.physiologicalAge = num(profile.physiologicalAge, profile.chronologicalAge); + system.profile = profile; + + system.combat = system.combat ?? {}; + if (!hasValue(system.combat.damageMod)) { + system.combat.damageMod = computeDamageMod(num(characteristics.str, 10), num(characteristics.mas, 10)); + } + + system.derivedRolls = system.derivedRolls ?? {}; + const derivedRollDefaults = computeDerivedRolls(characteristics); + for (const key of Object.keys(derivedRollDefaults) as DerivedCheckKey[]) { + system.derivedRolls[key] = num(system.derivedRolls[key], derivedRollDefaults[key]); + } + + system.skillRootMax = system.skillRootMax ?? {}; + const rootMaxDefaults = computeRootMaxes(characteristics); + for (const [key, value] of Object.entries(rootMaxDefaults)) { + system.skillRootMax[key] = value; + } + + system.derived = system.derived ?? {}; + system.derived.actionRanking = computeActionRanking(num(characteristics.dex, 10)); + system.derived.move = num(system.derived.move, 3); + system.derived.run = num(system.derived.run, Math.max(1, num(system.derived.move, 3) * 2)); + + system.encumbrance = system.encumbrance ?? {}; + const encumbrance = computeEncumbrance(system, this.items as any[]); + system.encumbrance.rawLoadKg = encumbrance.rawLoadKg; + system.encumbrance.effectiveLoadKg = encumbrance.effectiveLoadKg; + system.encumbrance.limitKg = encumbrance.limitKg; + system.encumbrance.overLimitKg = encumbrance.overLimitKg; + system.encumbrance.actionRankingPenalty = encumbrance.actionRankingPenalty; + system.encumbrance.overLimit = encumbrance.overLimit; + system.encumbrance.gravityMultiplier = encumbrance.gravityMultiplier; + system.derived.currentMove = encumbrance.currentMove; + system.derived.currentRun = encumbrance.currentRun; + + system.finance = system.finance ?? { money: 0, inBank: 0 }; + system.finance.money = num(system.finance.money, 0); + system.finance.inBank = num(system.finance.inBank, 0); + + system.notes = system.notes ?? { defects: "", habits: "" }; + if (typeof system.sheetToolsUnlocked !== "boolean") { + system.sheetToolsUnlocked = false; + } + system.characterBuilder = system.characterBuilder ?? { + pursuits: [], + allocations: {}, + baseBySkillId: {} + }; + system.unconsciousLevel = num(system.unconsciousLevel, 0); + } + + get skills(): any[] { + return this.items.filter((item) => item.type === "skill"); + } + + get weapons(): any[] { + return this.items.filter((item) => item.type === "weapon"); + } + + get armors(): any[] { + return this.items.filter((item) => item.type === "armor"); + } + + get gears(): any[] { + return this.items.filter((item) => item.type === "gear"); + } + + async rollSkillById(skillId: string): Promise { + const skill = this.items.get(skillId); + if (!skill || skill.type !== "skill") return; + await rollSkillForActor(this, skill.name, Number((skill as any).system.value ?? 0)); + } + + async rollHitLocation(): Promise { + await rollStandaloneHitLocation(this); + } + + async useWeaponById(weaponId: string): Promise { + const weapon = this.items.get(weaponId); + if (!weapon || weapon.type !== "weapon") return; + await useWeaponWorkflow(this, weapon); + } + + async ensureBaseSkills(): Promise { + if (this.type !== "explorer" && this.type !== "creature") return 0; + + const existing = new Set( + this.items + .filter((item) => item.type === "skill") + .map((item) => normalizeSkillName(item.name)) + ); + + const toCreate = RINGWORLD_BASE_SKILLS + .filter((skill) => !existing.has(normalizeSkillName(skill.name))) + .map((skill) => ({ + name: skill.name, + type: "skill", + img: skillIconPath(skill.name), + system: { + value: skill.base, + category: skill.category, + specialization: skill.specialization ?? "" + } + })); + + if (!toCreate.length) return 0; + await this.createEmbeddedDocuments("Item", toCreate as any[]); + return toCreate.length; + } + + async rollCharacteristics(): Promise { + if (this.type !== "explorer") return; + await rollCharacteristicsForActor(this as any); + } + + async randomizeCharacter(): Promise { + if (this.type !== "explorer") return; + await randomizeExplorerActor(this as any); + } + + async applySpeciesItem(speciesData: any): Promise { + if (this.type !== "explorer" && this.type !== "creature") return; + + const speciesName = String(speciesData?.name || "").trim(); + const speciesSystem = speciesData?.system ?? {}; + const incomingMods = speciesSystem?.modifiers ?? {}; + const appliedState = (this as any).system?.speciesApplied ?? {}; + const previousMods = appliedState?.modifiers ?? {}; + + const currentCharacteristics = (this as any).system?.characteristics ?? {}; + const currentProfile = (this as any).system?.profile ?? {}; + const update: Record = {}; + const normalizedMods: Record = { + str: 0, + mas: 0, + con: 0, + int: 0, + pow: 0, + dex: 0, + app: 0, + edu: 0, + age: 0 + }; + + for (const key of SPECIES_MODIFIER_KEYS) { + const prev = num(previousMods?.[key], 0); + const next = num(incomingMods?.[key], 0); + normalizedMods[key] = next; + + const current = num(currentCharacteristics?.[key], key === "age" ? 25 : 10); + const base = current - prev; + update[`system.characteristics.${key}`] = clampCharacteristic(base + next); + } + + const agePrev = num(previousMods?.age, 0); + const ageNext = normalizedMods.age; + const chronoCurrent = num(currentProfile?.chronologicalAge, num(currentCharacteristics?.age, 25)); + const physCurrent = num(currentProfile?.physiologicalAge, chronoCurrent); + update["system.profile.chronologicalAge"] = clampCharacteristic((chronoCurrent - agePrev) + ageNext); + update["system.profile.physiologicalAge"] = clampCharacteristic((physCurrent - agePrev) + ageNext); + + if (speciesName) update["system.profile.species"] = speciesName; + if (speciesSystem?.gravity) update["system.profile.gravity"] = String(speciesSystem.gravity); + if (speciesSystem?.homeworld !== undefined) update["system.profile.homeworld"] = String(speciesSystem.homeworld || ""); + + update["system.speciesApplied"] = { + name: speciesName, + modifiers: normalizedMods + }; + + await this.update(update); + } + + async rollDerivedCheck(key: DerivedCheckKey): Promise { + if (this.type !== "explorer" && this.type !== "creature") return; + + const labels: Record = { + health: "Health Roll", + reasoning: "Reasoning Roll", + luck: "Luck Roll", + dodge: "Dodge Roll" + }; + + const defaults = computeDerivedRolls((this as any).system?.characteristics ?? {}); + const target = num((this as any).system?.derivedRolls?.[key], defaults[key]); + await rollSkillForActor(this, labels[key], target); + } +} diff --git a/src/module/character-tools.ts b/src/module/character-tools.ts new file mode 100644 index 0000000..1e6b7c7 --- /dev/null +++ b/src/module/character-tools.ts @@ -0,0 +1,178 @@ +import { speciesCatalogForGeneration, getPlayableSpeciesFromWorld } from "./world-catalog"; +import { evaluateRollWithDice } from "./utils/dice"; + +type GravityType = "light" | "normal" | "heavy"; + +interface CharacteristicSet { + str: number; + mas: number; + con: number; + int: number; + pow: number; + dex: number; + app: number; + edu: number; + age: number; +} + +function clampScore(value: number): number { + return Math.max(1, Math.min(99, Math.floor(value))); +} + +async function rollTotal(formula: string): Promise { + const roll = await evaluateRollWithDice(formula); + return Number(roll.total ?? 0); +} + +async function rollEdu(): Promise { + let edu = 10; + while (true) { + const die = await rollTotal("1d6"); + edu += die; + if (die <= 4) break; + } + return clampScore(edu); +} + +async function rollByGravity(gravity: GravityType): Promise { + const gravityFormulas = { + light: { str: "2d6+3", mas: "2d6+3", con: "2d6+6", dex: "2d6+9" }, + normal: { str: "2d6+6", mas: "2d6+6", con: "2d6+6", dex: "2d6+6" }, + heavy: { str: "2d6+9", mas: "2d6+9", con: "2d6+3", dex: "2d6+6" } + }[gravity]; + + return { + str: await rollTotal(gravityFormulas.str), + mas: await rollTotal(gravityFormulas.mas), + con: await rollTotal(gravityFormulas.con), + int: await rollTotal("2d6+6"), + pow: await rollTotal("2d6+6"), + dex: await rollTotal(gravityFormulas.dex), + app: await rollTotal("2d6+6"), + edu: await rollEdu(), + age: await rollTotal("1d20+16") + }; +} + +function randomPick(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +function randomExplorerName(speciesName: string): string { + const starts = ["Ar", "Bel", "Cor", "Den", "Ela", "Far", "Gal", "Hel", "Ira", "Jun", "Kor", "Lys", "Mor", "Nyr", "Or", "Pra", "Qua", "Rin", "Sol", "Tor", "Uri", "Val", "Wen", "Xel", "Yor", "Zen"]; + const ends = ["an", "el", "is", "or", "us", "a", "en", "ion", "eth", "al", "ir", "os"]; + return `${randomPick(starts)}${randomPick(ends)} (${speciesName})`; +} + +function applySpeciesModifiers(base: CharacteristicSet, speciesSystem: any): CharacteristicSet { + const mods = speciesSystem?.modifiers ?? {}; + return { + str: clampScore(base.str + Number(mods.str || 0)), + mas: clampScore(base.mas + Number(mods.mas || 0)), + con: clampScore(base.con + Number(mods.con || 0)), + int: clampScore(base.int + Number(mods.int || 0)), + pow: clampScore(base.pow + Number(mods.pow || 0)), + dex: clampScore(base.dex + Number(mods.dex || 0)), + app: clampScore(base.app + Number(mods.app || 0)), + edu: clampScore(base.edu + Number(mods.edu || 0)), + age: clampScore(base.age + Number(mods.age || 0)) + }; +} + +export async function rollCharacteristicsForActor(actor: any): Promise { + const gravity = String(actor.system?.profile?.gravity || "normal") as GravityType; + const gravityType: GravityType = gravity === "light" || gravity === "heavy" ? gravity : "normal"; + + const currentSpeciesName = String(actor.system?.profile?.species || ""); + const worldSpecies = getPlayableSpeciesFromWorld(); + const speciesItem = worldSpecies.find((item: any) => item.name === currentSpeciesName); + const builtIn = speciesCatalogForGeneration().find((entry) => entry.name === currentSpeciesName); + const speciesSystem = speciesItem?.system ?? builtIn ?? null; + + const rolled = await rollByGravity(gravityType); + const finalValues = applySpeciesModifiers(rolled, speciesSystem); + + await actor.update({ + "system.characteristics.str": finalValues.str, + "system.characteristics.mas": finalValues.mas, + "system.characteristics.con": finalValues.con, + "system.characteristics.int": finalValues.int, + "system.characteristics.pow": finalValues.pow, + "system.characteristics.dex": finalValues.dex, + "system.characteristics.app": finalValues.app, + "system.characteristics.edu": finalValues.edu, + "system.characteristics.age": finalValues.age, + "system.profile.chronologicalAge": finalValues.age, + "system.profile.physiologicalAge": finalValues.age + }); + + const ownLanguage = actor.items.find((item: any) => item.type === "skill" && item.name.toLowerCase() === "own language"); + if (ownLanguage) { + await actor.updateEmbeddedDocuments("Item", [{ + _id: ownLanguage.id, + "system.value": Math.min(99, finalValues.int * 5) + }]); + } +} + +export async function applyStarterKit(actor: any): Promise { + const starterNames = [ + ["weapon", "Laser Pistol"], + ["weapon", "Variable Sword"], + ["armor", "Soft Combat Vest"], + ["gear", "Power Pack R20"], + ["gear", "Medkit"], + ["gear", "Survey Scanner"] + ]; + + const worldItems = game.items; + const actorExisting = new Set(actor.items.map((item: any) => `${item.type}:${item.name.toLowerCase()}`)); + const createPayload: any[] = []; + + for (const [type, name] of starterNames) { + if (actorExisting.has(`${type}:${String(name).toLowerCase()}`)) continue; + const worldItem = worldItems.find((item: any) => item.type === type && item.name === name); + if (!worldItem) continue; + createPayload.push(worldItem.toObject()); + } + + if (createPayload.length) { + await actor.createEmbeddedDocuments("Item", createPayload); + } +} + +export async function randomizeExplorerActor(actor: any): Promise { + const worldSpecies = getPlayableSpeciesFromWorld(); + const builtInSpecies = speciesCatalogForGeneration(); + + const pickedWorldSpecies = worldSpecies.length ? randomPick(worldSpecies) : null; + const pickedBuiltInSpecies = !pickedWorldSpecies ? randomPick(builtInSpecies) : null; + + const speciesChoice = pickedWorldSpecies?.system ?? pickedBuiltInSpecies; + const speciesName = pickedWorldSpecies?.name ?? pickedBuiltInSpecies?.name ?? "Human"; + + const gravity = String(speciesChoice.gravity || "normal") as GravityType; + const homeworld = String(speciesChoice.homeworld || "Ringworld"); + + await actor.update({ + name: randomExplorerName(speciesName), + "system.profile.species": speciesName, + "system.profile.gravity": gravity, + "system.profile.homeworld": homeworld + }); + + await rollCharacteristicsForActor(actor); + await actor.ensureBaseSkills?.(); + await applyStarterKit(actor); +} + +export async function createRandomExplorer(): Promise { + const ActorClass = getDocumentClass("Actor"); + const actor = await ActorClass.create({ + name: "Random Explorer", + type: "explorer" + }); + + await randomizeExplorerActor(actor); + return actor; +} diff --git a/src/module/chat.ts b/src/module/chat.ts new file mode 100644 index 0000000..0f8bc7e --- /dev/null +++ b/src/module/chat.ts @@ -0,0 +1,77 @@ +import { SYSTEM_ID } from "./constants"; +import { DEFAULT_SKILL_ICON } from "./skill-icons"; + +interface CreateChatOptions { + actor?: any; + content: string; +} + +async function createChatMessage(options: CreateChatOptions): Promise { + const speaker = options.actor ? ChatMessage.getSpeaker({ actor: options.actor }) : ChatMessage.getSpeaker(); + await ChatMessage.create({ + user: game.user?.id, + speaker, + content: options.content + }); +} + +export async function postSkillRollCard(data: { + actor: any; + actorName: string; + actorImg?: string; + skillName: string; + skillImg?: string; + roll: number; + target: number; + outcomeLabel: string; + isSuccess: boolean; + isSpecial: boolean; + isFumble: boolean; +}): Promise { + const payload = { + ...data, + actorImg: data.actorImg || data.actor?.img || "icons/svg/mystery-man.svg", + skillImg: data.skillImg || DEFAULT_SKILL_ICON + }; + const content = await renderTemplate(`systems/${SYSTEM_ID}/templates/chat/skill-roll-card.hbs`, payload); + await createChatMessage({ actor: data.actor, content }); +} + +export async function postAttackCard(data: { + actor: any; + actorName: string; + actorImg?: string; + targetName: string; + hasTarget?: boolean; + weaponName: string; + weaponImg?: string; + skillName: string; + skillImg?: string; + attackRoll: number; + attackTarget: number; + attackOutcomeLabel: string; + isSuccess: boolean; + isSpecial: boolean; + isFumble: boolean; + hitLocationLabel: string; + hitLocationRoll: number | null; + rawDamage: number; + armor: number; + finalDamage: number; + remainingLocationHp: number; + remainingGeneralHp: number; + energySourceLabel: string; + energyCost: number; + energyRemaining: number; + notes: string; +}): Promise { + const payload = { + ...data, + actorImg: data.actorImg || data.actor?.img || "icons/svg/mystery-man.svg", + weaponImg: data.weaponImg || "icons/svg/sword.svg", + skillImg: data.skillImg || DEFAULT_SKILL_ICON, + hasTarget: data.hasTarget ?? true + }; + const content = await renderTemplate(`systems/${SYSTEM_ID}/templates/chat/attack-card.hbs`, payload); + await createChatMessage({ actor: data.actor, content }); +} diff --git a/src/module/constants.ts b/src/module/constants.ts new file mode 100644 index 0000000..e70780e --- /dev/null +++ b/src/module/constants.ts @@ -0,0 +1,37 @@ +export type HitLocationSlug = "rLeg" | "lLeg" | "abdomen" | "chest" | "rArm" | "lArm" | "head"; + +export interface HitLocationEntry { + slug: HitLocationSlug; + label: string; + min: number; + max: number; +} + +export const SYSTEM_ID = "ringworld"; + +export const HIT_LOCATION_TABLE: HitLocationEntry[] = [ + { slug: "rLeg", label: "Right Leg", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Leg", min: 4, max: 6 }, + { slug: "abdomen", label: "Abdomen", min: 7, max: 10 }, + { slug: "chest", label: "Chest", min: 11, max: 15 }, + { slug: "rArm", label: "Right Arm", min: 16, max: 17 }, + { slug: "lArm", label: "Left Arm", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } +]; + +export const HIT_LOCATION_ORDER: HitLocationSlug[] = [ + "rLeg", + "lLeg", + "abdomen", + "chest", + "rArm", + "lArm", + "head" +]; + +export const LIMB_LOCATIONS: HitLocationSlug[] = ["rArm", "lArm", "rLeg", "lLeg"]; +export const LEG_LOCATIONS: HitLocationSlug[] = ["rLeg", "lLeg"]; + +export function isLimb(slug: HitLocationSlug): boolean { + return LIMB_LOCATIONS.includes(slug); +} diff --git a/src/module/data-base-skills.ts b/src/module/data-base-skills.ts new file mode 100644 index 0000000..08724b3 --- /dev/null +++ b/src/module/data-base-skills.ts @@ -0,0 +1,85 @@ +export interface BaseSkillDef { + name: string; + category: "Agility" | "Communication" | "Knowledge" | "Perception" | "Technical"; + base: number; + specialization?: string; +} + +export const BASE_SKILL_CATEGORIES: Array = [ + "Agility", + "Communication", + "Knowledge", + "Perception", + "Technical" +]; + +export const RINGWORLD_BASE_SKILLS: BaseSkillDef[] = [ + { name: "Archaic Melee Weapon", category: "Agility", base: 5 }, + { name: "Archaic Ranged Weapons", category: "Agility", base: 3 }, + { name: "Athletics", category: "Agility", base: 15 }, + { name: "Hide", category: "Agility", base: 10 }, + { name: "Sneak", category: "Agility", base: 5 }, + { name: "Unarmed Combat", category: "Agility", base: 0 }, + { name: "V. Sword, F. Laser", category: "Agility", base: 15 }, + + { name: "Bargain", category: "Communication", base: 10 }, + { name: "Debate", category: "Communication", base: 5 }, + { name: "Fast Talk", category: "Communication", base: 10 }, + { name: "Fine Arts", category: "Communication", base: 5 }, + { name: "Musicianship", category: "Communication", base: 5 }, + { name: "Orate", category: "Communication", base: 5 }, + { name: "Own Language", category: "Communication", base: 0, specialization: "INTx5 base" }, + { name: "Perform", category: "Communication", base: 5 }, + { name: "Psychology", category: "Communication", base: 0 }, + + { name: "Anthropology", category: "Knowledge", base: 0 }, + { name: "Astronomy", category: "Knowledge", base: 0 }, + { name: "Biology", category: "Knowledge", base: 0 }, + { name: "Botany", category: "Knowledge", base: 0 }, + { name: "Chemistry", category: "Knowledge", base: 0 }, + { name: "Computers", category: "Knowledge", base: 0 }, + { name: "Emergency Treatment", category: "Knowledge", base: 1 }, + { name: "Engineering", category: "Knowledge", base: 0 }, + { name: "Farming", category: "Knowledge", base: 0 }, + { name: "History", category: "Knowledge", base: 5 }, + { name: "Law", category: "Knowledge", base: 0 }, + { name: "Mathematics", category: "Knowledge", base: 0 }, + { name: "Physics", category: "Knowledge", base: 0 }, + { name: "Planetology", category: "Knowledge", base: 0 }, + { name: "Second Languages", category: "Knowledge", base: 0 }, + { name: "Strategy", category: "Knowledge", base: 0 }, + { name: "Theology", category: "Knowledge", base: 0 }, + { name: "Zoology", category: "Knowledge", base: 0 }, + + { name: "Handgun, Energy", category: "Perception", base: 5 }, + { name: "Handgun, Projectile", category: "Perception", base: 3 }, + { name: "Heavy Weapon, Energy", category: "Perception", base: 5 }, + { name: "Heavy Weapon, Projectile", category: "Perception", base: 3 }, + { name: "Listen", category: "Perception", base: 5 }, + { name: "Observe", category: "Perception", base: 5 }, + { name: "Scent", category: "Perception", base: 0 }, + { name: "Search", category: "Perception", base: 5 }, + { name: "Track", category: "Perception", base: 5 }, + + { name: "Aquatic Vehicle", category: "Technical", base: 0 }, + { name: "Atmospheric Craft", category: "Technical", base: 0 }, + { name: "Ground Vehicle", category: "Technical", base: 0 }, + { name: "Hyperdrive", category: "Technical", base: 0 }, + { name: "Personal Flyer", category: "Technical", base: 15 }, + { name: "Reaction Drive", category: "Technical", base: 0 }, + { name: "Reactionless Drive", category: "Technical", base: 0 }, + { name: "Repair", category: "Technical", base: 0 }, + { name: "Ringworld", category: "Technical", base: 0 }, + { name: "Weapons System", category: "Technical", base: 0 } +]; + +export function normalizeSkillName(name: string): string { + return String(name || "") + .toLowerCase() + .replace(/[^a-z0-9]+/g, "") + .trim(); +} + +export function baseSkillNameSet(): Set { + return new Set(RINGWORLD_BASE_SKILLS.map((skill) => normalizeSkillName(skill.name))); +} diff --git a/src/module/hit-location-schemas.ts b/src/module/hit-location-schemas.ts new file mode 100644 index 0000000..3ee0af8 --- /dev/null +++ b/src/module/hit-location-schemas.ts @@ -0,0 +1,191 @@ +import { HIT_LOCATION_ORDER, HIT_LOCATION_TABLE, type HitLocationEntry, type HitLocationSlug } from "./constants"; + +export type HitLocationSchemaId = "humanoid" | "dolphin" | "puppeteer" | "trinoc" | "bandersnatchi" | "kzinti"; + +export interface HitLocationSchemaEntry extends HitLocationEntry { + hpFactor: number; +} + +export interface HitLocationSchema { + id: HitLocationSchemaId; + label: string; + bodyOrder: HitLocationSlug[]; + entries: HitLocationSchemaEntry[]; +} + +function normalize(value: unknown): string { + return String(value ?? "") + .toLowerCase() + .replace(/[^a-z0-9]+/g, ""); +} + +function withFactors(entries: HitLocationEntry[], hpFactors?: Partial>): HitLocationSchemaEntry[] { + return entries.map((entry) => ({ + ...entry, + hpFactor: hpFactors?.[entry.slug] ?? (entry.slug === "chest" ? 0.35 : (entry.slug === "rArm" || entry.slug === "lArm") ? 0.25 : 0.3) + })); +} + +const HUMANOID_SCHEMA: HitLocationSchema = { + id: "humanoid", + label: "Humanoid", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: withFactors(HIT_LOCATION_TABLE) +}; + +const DOLPHIN_SCHEMA: HitLocationSchema = { + id: "dolphin", + label: "Dolphin", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: withFactors([ + { slug: "rLeg", label: "Right Fluke", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Fluke", min: 4, max: 6 }, + { slug: "abdomen", label: "Lower Body", min: 7, max: 9 }, + { slug: "chest", label: "Main Body", min: 10, max: 15 }, + { slug: "rArm", label: "Right Fin", min: 16, max: 17 }, + { slug: "lArm", label: "Left Fin", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } + ], { + head: 0.3, + rArm: 0.2, + lArm: 0.2, + chest: 0.4, + abdomen: 0.35, + rLeg: 0.25, + lLeg: 0.25 + }) +}; + +const PUPPETEER_SCHEMA: HitLocationSchema = { + id: "puppeteer", + label: "Puppeteer", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: withFactors([ + { slug: "rLeg", label: "Right Rear Leg", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Rear Leg", min: 4, max: 6 }, + { slug: "rArm", label: "Right Foreleg", min: 7, max: 9 }, + { slug: "lArm", label: "Left Foreleg", min: 10, max: 12 }, + { slug: "chest", label: "Fore Body", min: 13, max: 15 }, + { slug: "abdomen", label: "Hind Body", min: 16, max: 18 }, + { slug: "head", label: "Twin Heads", min: 19, max: 20 } + ], { + head: 0.32, + chest: 0.35, + abdomen: 0.35, + rArm: 0.28, + lArm: 0.28, + rLeg: 0.28, + lLeg: 0.28 + }) +}; + +const TRINOC_SCHEMA: HitLocationSchema = { + id: "trinoc", + label: "Trinoc", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: withFactors([ + { slug: "rArm", label: "Right Wing/Arm", min: 1, max: 3 }, + { slug: "lArm", label: "Left Wing/Arm", min: 4, max: 6 }, + { slug: "chest", label: "Upper Torso", min: 7, max: 11 }, + { slug: "abdomen", label: "Lower Torso", min: 12, max: 14 }, + { slug: "rLeg", label: "Right Leg", min: 15, max: 17 }, + { slug: "lLeg", label: "Left Leg", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } + ], { + head: 0.3, + rArm: 0.27, + lArm: 0.27, + chest: 0.36, + abdomen: 0.3, + rLeg: 0.27, + lLeg: 0.27 + }) +}; + +const BANDERSNATCHI_SCHEMA: HitLocationSchema = { + id: "bandersnatchi", + label: "Bandersnatchi", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: withFactors([ + { slug: "rLeg", label: "Right Rear Mass", min: 1, max: 2 }, + { slug: "lLeg", label: "Left Rear Mass", min: 3, max: 4 }, + { slug: "abdomen", label: "Lower Body Mass", min: 5, max: 11 }, + { slug: "chest", label: "Upper Body Mass", min: 12, max: 17 }, + { slug: "rArm", label: "Right Manipulator", min: 18, max: 18 }, + { slug: "lArm", label: "Left Manipulator", min: 19, max: 19 }, + { slug: "head", label: "Head Cluster", min: 20, max: 20 } + ], { + head: 0.28, + chest: 0.42, + abdomen: 0.42, + rArm: 0.2, + lArm: 0.2, + rLeg: 0.24, + lLeg: 0.24 + }) +}; + +const KZINTI_SCHEMA: HitLocationSchema = { + id: "kzinti", + label: "Kzinti", + bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"], + entries: withFactors([ + { slug: "rLeg", label: "Right Hind Leg", min: 1, max: 3 }, + { slug: "lLeg", label: "Left Hind Leg", min: 4, max: 6 }, + { slug: "abdomen", label: "Abdomen", min: 7, max: 10 }, + { slug: "chest", label: "Chest", min: 11, max: 15 }, + { slug: "rArm", label: "Right Foreleg", min: 16, max: 17 }, + { slug: "lArm", label: "Left Foreleg", min: 18, max: 19 }, + { slug: "head", label: "Head", min: 20, max: 20 } + ]) +}; + +export const HIT_LOCATION_SCHEMAS: Record = { + humanoid: HUMANOID_SCHEMA, + dolphin: DOLPHIN_SCHEMA, + puppeteer: PUPPETEER_SCHEMA, + trinoc: TRINOC_SCHEMA, + bandersnatchi: BANDERSNATCHI_SCHEMA, + kzinti: KZINTI_SCHEMA +}; + +export function listHitLocationSchemas(): HitLocationSchema[] { + return Object.values(HIT_LOCATION_SCHEMAS); +} + +export function inferHitLocationSchemaId(speciesValue: unknown): HitLocationSchemaId { + const species = normalize(speciesValue); + if (!species) return "humanoid"; + if (species.includes("dolphin")) return "dolphin"; + if (species.includes("puppeteer")) return "puppeteer"; + if (species.includes("trinoc")) return "trinoc"; + if (species.includes("bandersnatch")) return "bandersnatchi"; + if (species.includes("kzinti")) return "kzinti"; + return "humanoid"; +} + +export function getHitLocationSchemaById(schemaId: unknown): HitLocationSchema { + const key = normalize(schemaId) as HitLocationSchemaId; + return HIT_LOCATION_SCHEMAS[key] ?? HUMANOID_SCHEMA; +} + +export function getActorHitLocationSchema(actor: any): HitLocationSchema { + const mode = String(actor?.system?.hitLocationSchema || "auto").trim().toLowerCase(); + if (mode && mode !== "auto") { + return getHitLocationSchemaById(mode); + } + const species = actor?.system?.profile?.species ?? ""; + return getHitLocationSchemaById(inferHitLocationSchemaId(species)); +} + +export function getSchemaLocationEntry(schema: HitLocationSchema, slug: HitLocationSlug): HitLocationSchemaEntry { + return schema.entries.find((entry) => entry.slug === slug) ?? withFactors(HIT_LOCATION_TABLE).find((entry) => entry.slug === slug)!; +} + +export function getSchemaOrder(schema: HitLocationSchema): HitLocationSlug[] { + const set = new Set(schema.entries.map((entry) => entry.slug)); + const ordered = schema.bodyOrder.filter((slug) => set.has(slug)); + if (ordered.length === schema.entries.length) return ordered; + return [...HIT_LOCATION_ORDER]; +} + diff --git a/src/module/impulse-tracker.ts b/src/module/impulse-tracker.ts new file mode 100644 index 0000000..d8b86a0 --- /dev/null +++ b/src/module/impulse-tracker.ts @@ -0,0 +1,756 @@ +import { SYSTEM_ID } from "./constants"; + +type MajorActionKind = "draw" | "aim" | "melee" | "stand" | "perception" | "unarmed"; +type MinorActionKind = "fire" | "fall" | "rise" | "minorCustom"; +type ActionKind = MajorActionKind | MinorActionKind; + +interface ActionEvent { + id: string; + actorId: string | null; + combatantId: string; + kind: ActionKind; + major: boolean; + startImpulse: number; + completeImpulse: number; + itemId?: string; + skillId?: string; + targetTokenId?: string; +} + +interface ImpulseState { + currentImpulse: number; + queue: ActionEvent[]; +} + +const IMPULSE_FLAG = "impulseState"; +const IMPULSE_HUD_POS_KEY = `${SYSTEM_ID}.impulseHudPos`; +const MAJOR_ACTION_OPTIONS: { value: MajorActionKind; label: string }[] = [ + { value: "draw", label: "Draw / Stow Weapon" }, + { value: "aim", label: "Aim Ranged Weapon" }, + { value: "melee", label: "Melee Attack" }, + { value: "stand", label: "Stand / Recover" }, + { value: "perception", label: "Perception Skill" }, + { value: "unarmed", label: "Unarmed Combat" } +]; +const MINOR_ACTION_OPTIONS: { value: MinorActionKind; label: string }[] = [ + { value: "fire", label: "Fire Ranged Weapon" }, + { value: "fall", label: "Fall" }, + { value: "rise", label: "Rise (Kneeling to Stand/Run)" }, + { value: "minorCustom", label: "Other Minor Action" } +]; + +function num(value: unknown, fallback = 0): number { + const n = Number(value); + return Number.isFinite(n) ? n : fallback; +} + +function esc(value: unknown): string { + return String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll("\"", """); +} + +export function actionRankingFromDex(dex: number): number { + if (dex <= 4) return 7; + if (dex <= 8) return 6; + if (dex <= 12) return 5; + if (dex <= 16) return 4; + if (dex <= 20) return 3; + if (dex <= 24) return 2; + return 1; +} + +function actionKindLabel(kind: ActionKind): string { + switch (kind) { + case "draw": + return "Draw / Stow"; + case "aim": + return "Aim"; + case "melee": + return "Melee Attack"; + case "stand": + return "Stand / Recover"; + case "perception": + return "Perception"; + case "unarmed": + return "Unarmed Combat"; + case "fire": + return "Fire Ranged Weapon"; + case "fall": + return "Fall"; + case "rise": + return "Rise"; + case "minorCustom": + return "Minor Action"; + default: + return String(kind); + } +} + +function getDexForCombatant(combatant: any): number { + return Math.max(1, num(combatant?.actor?.system?.characteristics?.dex, 10)); +} + +function getCombatantActionRanking(combatant: any): number { + const fromFlag = num(combatant?.flags?.[SYSTEM_ID]?.actionRanking, 0); + if (fromFlag > 0) return fromFlag; + return actionRankingFromDex(getDexForCombatant(combatant)); +} + +function getEncumbranceArPenalty(combatant: any): number { + const fromActor = num(combatant?.actor?.system?.encumbrance?.actionRankingPenalty, 0); + return Math.max(0, Math.trunc(fromActor)); +} + +async function syncCombatantActionRanking(combatant: any): Promise { + const ranking = actionRankingFromDex(getDexForCombatant(combatant)); + const existing = num(combatant?.flags?.[SYSTEM_ID]?.actionRanking, 0); + if (existing !== ranking) { + await combatant.update({ [`flags.${SYSTEM_ID}.actionRanking`]: ranking }); + } + return ranking; +} + +function getImpulseState(combat: any): ImpulseState { + const raw = foundry.utils.deepClone(combat.getFlag(SYSTEM_ID, IMPULSE_FLAG) ?? {}); + const queue = Array.isArray(raw.queue) ? raw.queue : []; + return { + currentImpulse: Math.max(1, num(raw.currentImpulse, 1)), + queue + }; +} + +async function setImpulseState(combat: any, state: ImpulseState): Promise { + await combat.setFlag(SYSTEM_ID, IMPULSE_FLAG, { + currentImpulse: Math.max(1, num(state.currentImpulse, 1)), + queue: Array.isArray(state.queue) ? state.queue : [] + }); +} + +function sortQueueByCompletion(queue: ActionEvent[], combat: any): ActionEvent[] { + return [...queue].sort((a, b) => { + if (a.completeImpulse !== b.completeImpulse) return a.completeImpulse - b.completeImpulse; + const aDex = getDexForCombatant(combat.combatants.get(a.combatantId)); + const bDex = getDexForCombatant(combat.combatants.get(b.combatantId)); + return bDex - aDex; + }); +} + +function pruneQueueToCombatants(queue: ActionEvent[], combat: any): ActionEvent[] { + const validIds = new Set(combat.combatants.map((c: any) => c.id)); + return queue.filter((entry) => validIds.has(entry.combatantId)); +} + +function loadHudPosition(): { left: number; top: number } | null { + try { + const raw = localStorage.getItem(IMPULSE_HUD_POS_KEY); + if (!raw) return null; + const parsed = JSON.parse(raw); + const left = num(parsed?.left, NaN); + const top = num(parsed?.top, NaN); + if (!Number.isFinite(left) || !Number.isFinite(top)) return null; + return { left, top }; + } catch (_err) { + return null; + } +} + +function saveHudPosition(position: { left: number; top: number }): void { + try { + localStorage.setItem( + IMPULSE_HUD_POS_KEY, + JSON.stringify({ left: Math.round(position.left), top: Math.round(position.top) }) + ); + } catch (_err) { + // Ignore storage issues. + } +} + +function clearHudPosition(): void { + try { + localStorage.removeItem(IMPULSE_HUD_POS_KEY); + } catch (_err) { + // Ignore storage issues. + } +} + +function clampHudPosition(position: { left: number; top: number }, element: HTMLElement): { left: number; top: number } { + const margin = 8; + const width = element.offsetWidth || 300; + const height = element.offsetHeight || 220; + const maxLeft = Math.max(margin, window.innerWidth - width - margin); + const maxTop = Math.max(margin, window.innerHeight - height - margin); + return { + left: Math.min(Math.max(margin, position.left), maxLeft), + top: Math.min(Math.max(margin, position.top), maxTop) + }; +} + +function applyHudAbsolutePosition(hud: JQuery, position: { left: number; top: number }): void { + const el = hud.get(0) as HTMLElement | undefined; + if (!el) return; + const clamped = clampHudPosition(position, el); + hud.css({ + left: `${clamped.left}px`, + top: `${clamped.top}px`, + right: "auto", + bottom: "auto" + }); +} + +function makeHudDraggable(hud: JQuery): void { + const el = hud.get(0) as HTMLElement | undefined; + if (!el) return; + + const handle = hud.find(".rw-imp-mini-head"); + if (!handle.length) return; + + const saved = loadHudPosition(); + if (saved) { + applyHudAbsolutePosition(hud, saved); + } + + let dragging = false; + let startX = 0; + let startY = 0; + let startLeft = 0; + let startTop = 0; + + handle.on("mousedown", (event) => { + const mouseEvent = event as JQuery.MouseDownEvent; + if (mouseEvent.button !== 0) return; + if ((mouseEvent.target as HTMLElement)?.closest("button")) return; + mouseEvent.preventDefault(); + + const rect = el.getBoundingClientRect(); + startX = mouseEvent.clientX; + startY = mouseEvent.clientY; + startLeft = rect.left; + startTop = rect.top; + dragging = true; + + hud.addClass("dragging"); + hud.css({ left: `${startLeft}px`, top: `${startTop}px`, right: "auto", bottom: "auto" }); + + $(document).on("mousemove.ringworldImpulseHudDrag", (moveEvent) => { + if (!dragging) return; + const evt = moveEvent as JQuery.MouseMoveEvent; + const deltaX = evt.clientX - startX; + const deltaY = evt.clientY - startY; + applyHudAbsolutePosition(hud, { left: startLeft + deltaX, top: startTop + deltaY }); + }); + + $(document).one("mouseup.ringworldImpulseHudDrag", () => { + dragging = false; + hud.removeClass("dragging"); + $(document).off("mousemove.ringworldImpulseHudDrag"); + const latestRect = el.getBoundingClientRect(); + saveHudPosition({ left: latestRect.left, top: latestRect.top }); + }); + }); + + handle.on("dblclick", () => { + clearHudPosition(); + hud.css({ left: "", top: "", right: "", bottom: "" }); + }); +} + +function getQueueWithNames(combat: any, queue: ActionEvent[]): Array<{ + combatantId: string; + name: string; + label: string; + completeImpulse: number; + dex: number; +}> { + return sortQueueByCompletion(pruneQueueToCombatants(queue, combat), combat).map((entry) => { + const combatant = combat.combatants.get(entry.combatantId); + return { + combatantId: entry.combatantId, + name: String(combatant?.name || "Unknown"), + label: actionKindLabel(entry.kind), + completeImpulse: entry.completeImpulse, + dex: getDexForCombatant(combatant) + }; + }); +} + +function renderQueueRowsMarkup(rows: ReturnType, limit = 8): string { + if (!rows.length) return `
No actions queued.
`; + return rows.slice(0, limit).map((row) => ` +
+ ${esc(row.name)} + ${esc(row.label)} @ ${row.completeImpulse} +
+ `).join(""); +} + +function attachOverlayControls(root: JQuery, combat: any): void { + const disabled = game.user?.isGM ? false : true; + root.find(".rw-mini-next").prop("disabled", disabled).on("click", async (event) => { + event.preventDefault(); + await advanceImpulse(combat, "next"); + }); + root.find(".rw-mini-step").prop("disabled", disabled).on("click", async (event) => { + event.preventDefault(); + await advanceImpulse(combat, "step"); + }); + root.find(".rw-mini-reset").prop("disabled", disabled).on("click", async (event) => { + event.preventDefault(); + await resetImpulseSequence(combat); + }); +} + +function renderChatImpulseMarker(combat: any | null): void { + const chatEl = (ui as any)?.chat?.element as JQuery | undefined; + if (!chatEl?.length) return; + chatEl.find(".ringworld-impulse-chat-marker").remove(); + if (!combat) return; + + const state = getImpulseState(combat); + const rows = getQueueWithNames(combat, state.queue); + const marker = $(` +
+
+ Impulse ${state.currentImpulse} + Queued ${rows.length} +
+
${renderQueueRowsMarkup(rows, 5)}
+
+ + + +
+
+ `); + attachOverlayControls(marker, combat); + chatEl.prepend(marker); +} + +function renderFloatingImpulseHud(combat: any | null): void { + const existing = $("#ringworld-impulse-hud"); + existing.remove(); + if (!combat) return; + + const state = getImpulseState(combat); + const rows = getQueueWithNames(combat, state.queue); + const hud = $(` +
+
+ Impulse ${state.currentImpulse} + Queued ${rows.length} +
+
${renderQueueRowsMarkup(rows, 8)}
+
+ + + +
+
+ `); + attachOverlayControls(hud, combat); + $("body").append(hud); + makeHudDraggable(hud); +} + +function refreshImpulseOverlays(combat: any | null): void { + renderChatImpulseMarker(combat); + renderFloatingImpulseHud(combat); +} + +async function ensureGmControl(): Promise { + if (game.user?.isGM) return true; + ui.notifications?.warn("Only the GM can modify Ringworld impulse sequencing."); + return false; +} + +async function scheduleAction( + combat: any, + combatantId: string, + kind: ActionKind, + major: boolean +): Promise { + if (!(await ensureGmControl())) return; + + const combatant = combat.combatants.get(combatantId); + if (!combatant) return; + + const state = getImpulseState(combat); + const currentImpulse = state.currentImpulse; + const actionRanking = await syncCombatantActionRanking(combatant); + const encPenalty = getEncumbranceArPenalty(combatant); + const impulseMod = num(combatant.flags?.[SYSTEM_ID]?.impulseModifier, 0); + const effectiveAr = Math.max(1, actionRanking + encPenalty); + const baseDuration = major ? effectiveAr : 1; + const duration = Math.max(1, baseDuration + impulseMod); + const completeImpulse = currentImpulse + duration; + + const event: ActionEvent = { + id: foundry.utils.randomID(), + actorId: combatant.actor?.id ?? null, + combatantId, + kind, + major, + startImpulse: currentImpulse, + completeImpulse + }; + + const queueWithoutCombatant = pruneQueueToCombatants( + state.queue.filter((entry) => entry.combatantId !== combatantId), + combat + ); + queueWithoutCombatant.push(event); + + await setImpulseState(combat, { + currentImpulse, + queue: sortQueueByCompletion(queueWithoutCombatant, combat) + }); + + await combatant.update({ + [`flags.${SYSTEM_ID}.pendingEventId`]: event.id, + [`flags.${SYSTEM_ID}.pendingKind`]: kind, + [`flags.${SYSTEM_ID}.actionRanking`]: actionRanking, + [`flags.${SYSTEM_ID}.effectiveActionRanking`]: effectiveAr + }); + + const timingLabel = major + ? `AR ${actionRanking} + Enc ${encPenalty}${impulseMod ? ` ${impulseMod > 0 ? "+" : ""} Imp ${impulseMod}` : ""}` + : `Minor 1${impulseMod ? ` ${impulseMod > 0 ? "+" : ""} Imp ${impulseMod}` : ""}`; + ui.notifications?.info( + `${combatant.name}: ${actionKindLabel(kind)} completes on impulse ${completeImpulse} (${timingLabel} => ${duration}).` + ); + refreshImpulseOverlays(combat); +} + +async function cancelAction(combat: any, combatantId: string): Promise { + if (!(await ensureGmControl())) return; + + const combatant = combat.combatants.get(combatantId); + if (!combatant) return; + + const state = getImpulseState(combat); + const nextQueue = pruneQueueToCombatants( + state.queue.filter((entry) => entry.combatantId !== combatantId), + combat + ); + + await setImpulseState(combat, { + currentImpulse: state.currentImpulse, + queue: sortQueueByCompletion(nextQueue, combat) + }); + + await combatant.update({ + [`flags.${SYSTEM_ID}.pendingEventId`]: null, + [`flags.${SYSTEM_ID}.pendingKind`]: "", + [`flags.${SYSTEM_ID}.aimState`]: { + active: false, + targetId: null, + sinceImpulse: null + } + }); + refreshImpulseOverlays(combat); +} + +async function resolveActionEvent(combat: any, event: ActionEvent, impulse: number): Promise { + const combatant = combat.combatants.get(event.combatantId); + if (!combatant) return; + + const updates: Record = { + [`flags.${SYSTEM_ID}.pendingEventId`]: null, + [`flags.${SYSTEM_ID}.pendingKind`]: "" + }; + + if (event.kind === "aim") { + updates[`flags.${SYSTEM_ID}.aimState`] = { + active: true, + targetId: event.targetTokenId ?? null, + sinceImpulse: impulse + }; + } + + await combatant.update(updates); + + const content = ` +
+

Impulse ${impulse} Completion

+

${esc(combatant.name)} completes ${esc(actionKindLabel(event.kind))}.

+

DEX ${getDexForCombatant(combatant)} | AR ${getCombatantActionRanking(combatant)}

+
+ `; + await ChatMessage.create({ + user: game.user?.id, + speaker: ChatMessage.getSpeaker({ actor: combatant.actor ?? undefined }), + content + }); +} + +async function advanceImpulse(combat: any, mode: "next" | "step"): Promise { + if (!(await ensureGmControl())) return; + + const state = getImpulseState(combat); + const queue = sortQueueByCompletion(pruneQueueToCombatants(state.queue, combat), combat); + const current = state.currentImpulse; + + let targetImpulse = current + 1; + if (mode === "next") { + const next = queue + .map((event) => event.completeImpulse) + .filter((impulse) => impulse >= current) + .sort((a, b) => a - b)[0]; + if (Number.isFinite(next)) { + targetImpulse = next; + } + } + + const completing = queue.filter((event) => event.completeImpulse === targetImpulse); + const remaining = queue.filter((event) => event.completeImpulse !== targetImpulse); + + await setImpulseState(combat, { + currentImpulse: targetImpulse, + queue: remaining + }); + + const ordered = sortQueueByCompletion(completing, combat); + for (const event of ordered) { + await resolveActionEvent(combat, event, targetImpulse); + } + + if (!ordered.length) { + ui.notifications?.info(`Impulse advanced to ${targetImpulse}.`); + } + refreshImpulseOverlays(combat); +} + +async function resetImpulseSequence(combat: any): Promise { + if (!(await ensureGmControl())) return; + + await setImpulseState(combat, { currentImpulse: 1, queue: [] }); + + for (const combatant of combat.combatants) { + await combatant.update({ + [`flags.${SYSTEM_ID}.pendingEventId`]: null, + [`flags.${SYSTEM_ID}.pendingKind`]: "", + [`flags.${SYSTEM_ID}.aimState`]: { + active: false, + targetId: null, + sinceImpulse: null + }, + [`flags.${SYSTEM_ID}.actionRanking`]: actionRankingFromDex(getDexForCombatant(combatant)), + [`flags.${SYSTEM_ID}.effectiveActionRanking`]: null, + [`flags.${SYSTEM_ID}.impulseModifier`]: 0 + }); + } + refreshImpulseOverlays(combat); +} + +function renderImpulsePanel(app: any, htmlLike: JQuery | HTMLElement): void { + const html = "find" in (htmlLike as any) ? (htmlLike as JQuery) : $(htmlLike); + if (!html?.length) return; + html.addClass("ringworld-impulse-mode ringworld-impulse-only"); + + const combat = app?.viewed ?? game.combat; + if (!combat) return; + + const state = getImpulseState(combat); + const queue = sortQueueByCompletion(pruneQueueToCombatants(state.queue, combat), combat); + if (queue.length !== state.queue.length) { + void setImpulseState(combat, { currentImpulse: state.currentImpulse, queue }); + } + + html + .find( + ".token-initiative, .combatant-initiative, .initiative, [data-action='rollInitiative'], [data-control='rollInitiative'], .combatant .fa-dice-d20" + ) + .addClass("rw-hide-initiative"); + + html.find(".ringworld-impulse-panel").remove(); + + const disabled = game.user?.isGM ? "" : "disabled"; + const rows = combat.combatants.map((combatant: any) => { + const ar = getCombatantActionRanking(combatant); + const encPenalty = getEncumbranceArPenalty(combatant); + const impMod = num(combatant.flags?.[SYSTEM_ID]?.impulseModifier, 0); + const effAr = Math.max(1, ar + encPenalty); + const dex = getDexForCombatant(combatant); + const pending = queue.find((event) => event.combatantId === combatant.id); + const pendingLabel = pending + ? `${actionKindLabel(pending.kind)} @ ${pending.completeImpulse}` + : "Ready"; + const aimState = combatant.flags?.[SYSTEM_ID]?.aimState; + const aimLabel = aimState?.active ? `Aiming since ${aimState.sinceImpulse}` : "Not aiming"; + + const options = MAJOR_ACTION_OPTIONS.map((opt) => { + const selected = pending?.major && pending?.kind === opt.value ? "selected" : ""; + return ``; + }).join(""); + const minorOptions = MINOR_ACTION_OPTIONS.map((opt) => { + const selected = !pending?.major && pending?.kind === opt.value ? "selected" : ""; + return ``; + }).join(""); + + return ` +
+
+ ${esc(combatant.name)} + DEX ${dex} | AR ${ar} + Enc ${encPenalty} = ${effAr} | Imp Mod ${impMod >= 0 ? `+${impMod}` : impMod} | ${esc(pendingLabel)} | ${esc(aimLabel)} +
+
+ + + + + + + +
+
+ `; + }).join(""); + + const panel = $(` +
+
+
Impulse ${state.currentImpulse}
+
Queued: ${queue.length}
+
+ + + +
+
+
${rows}
+
+ `); + + html.prepend(panel); + + // Hide all native combat tracker blocks so only Ringworld impulse UI is shown. + const root = html.get(0); + if (root) { + for (const child of Array.from(root.children)) { + if (!(child as HTMLElement).classList.contains("ringworld-impulse-panel")) { + (child as HTMLElement).classList.add("rw-hide-core-tracker"); + } + } + } + + panel.find(".rw-advance-next").on("click", async (event) => { + event.preventDefault(); + await advanceImpulse(combat, "next"); + }); + + panel.find(".rw-advance-step").on("click", async (event) => { + event.preventDefault(); + await advanceImpulse(combat, "step"); + }); + + panel.find(".rw-reset-sequence").on("click", async (event) => { + event.preventDefault(); + await resetImpulseSequence(combat); + }); + + panel.find(".rw-major-start").on("click", async (event) => { + event.preventDefault(); + const row = (event.currentTarget as HTMLElement).closest(".rw-imp-row"); + if (!row) return; + const combatantId = String(row.getAttribute("data-combatant-id") || ""); + const select = row.querySelector(".rw-major-kind") as HTMLSelectElement | null; + const kind = String(select?.value || "draw") as MajorActionKind; + await scheduleAction(combat, combatantId, kind, true); + }); + + panel.find(".rw-minor-start").on("click", async (event) => { + event.preventDefault(); + const row = (event.currentTarget as HTMLElement).closest(".rw-imp-row"); + if (!row) return; + const combatantId = String(row.getAttribute("data-combatant-id") || ""); + const select = row.querySelector(".rw-minor-kind") as HTMLSelectElement | null; + const kind = String(select?.value || "minorCustom") as MinorActionKind; + await scheduleAction(combat, combatantId, kind, false); + }); + + panel.find(".rw-cancel-start").on("click", async (event) => { + event.preventDefault(); + const row = (event.currentTarget as HTMLElement).closest(".rw-imp-row"); + if (!row) return; + const combatantId = String(row.getAttribute("data-combatant-id") || ""); + await cancelAction(combat, combatantId); + }); + + panel.find(".rw-save-mods").on("click", async (event) => { + event.preventDefault(); + if (!(await ensureGmControl())) return; + const row = (event.currentTarget as HTMLElement).closest(".rw-imp-row"); + if (!row) return; + const combatantId = String(row.getAttribute("data-combatant-id") || ""); + const combatant = combat.combatants.get(combatantId); + if (!combatant) return; + + const impModInput = row.querySelector(".rw-imp-mod") as HTMLInputElement | null; + const impMod = Math.trunc(num(impModInput?.value, 0)); + await combatant.update({ + [`flags.${SYSTEM_ID}.impulseModifier`]: impMod + }); + }); + + refreshImpulseOverlays(combat); +} + +export function initializeImpulseTracker(): void { + Hooks.on("renderCombatTracker", (app: any, htmlLike: JQuery | HTMLElement) => { + renderImpulsePanel(app, htmlLike); + }); + + Hooks.on("createCombat", async (combat: any) => { + await setImpulseState(combat, { currentImpulse: 1, queue: [] }); + refreshImpulseOverlays(combat); + }); + + Hooks.on("createCombatant", async (combatant: any) => { + await syncCombatantActionRanking(combatant); + refreshImpulseOverlays(game.combat ?? null); + }); + + Hooks.on("deleteCombatant", async () => { + refreshImpulseOverlays(game.combat ?? null); + }); + + Hooks.on("updateCombat", (combat: any) => { + refreshImpulseOverlays(combat ?? game.combat ?? null); + }); + + Hooks.on("deleteCombat", () => { + refreshImpulseOverlays(null); + }); + + Hooks.on("renderChatLog", () => { + refreshImpulseOverlays(game.combat ?? null); + }); + + Hooks.on("canvasReady", () => { + refreshImpulseOverlays(game.combat ?? null); + }); + + Hooks.on("updateActor", (actor: any) => { + const combat = game.combat; + if (!combat) return; + const inCombat = combat.combatants.some((c: any) => c.actor?.id === actor.id); + if (inCombat) { + ui.combat?.render(true); + refreshImpulseOverlays(combat); + } + }); + + Hooks.on("updateItem", (item: any) => { + const actor = item?.parent; + if (!actor) return; + const combat = game.combat; + if (!combat) return; + const inCombat = combat.combatants.some((c: any) => c.actor?.id === actor.id); + if (inCombat) { + ui.combat?.render(true); + refreshImpulseOverlays(combat); + } + }); +} diff --git a/src/module/item/RingworldItem.ts b/src/module/item/RingworldItem.ts new file mode 100644 index 0000000..e65855e --- /dev/null +++ b/src/module/item/RingworldItem.ts @@ -0,0 +1,27 @@ +import { rollSkillForActor, useWeaponWorkflow } from "../mechanics"; + +export class RingworldItem extends Item { + async rollSkill(): Promise { + if (this.type !== "skill") return; + + const actor = this.actor; + if (!actor) { + ui.notifications?.warn("Skill must be owned by an Actor to roll."); + return; + } + + await rollSkillForActor(actor, this.name, Number((this as any).system.value ?? 0)); + } + + async useWeapon(): Promise { + if (this.type !== "weapon") return; + + const actor = this.actor; + if (!actor) { + ui.notifications?.warn("Weapon must be owned by an Actor to use."); + return; + } + + await useWeaponWorkflow(actor, this); + } +} diff --git a/src/module/mechanics.ts b/src/module/mechanics.ts new file mode 100644 index 0000000..d63cc8d --- /dev/null +++ b/src/module/mechanics.ts @@ -0,0 +1,525 @@ +import { outcomeLabel, rollPercentile } from "./utils/percentile"; +import { chooseOrRollHitLocation, rollHitLocation } from "./utils/hit-locations"; +import { LEG_LOCATIONS, SYSTEM_ID, isLimb, type HitLocationSlug } from "./constants"; +import { postAttackCard, postSkillRollCard } from "./chat"; +import { evaluateRollWithDice } from "./utils/dice"; +import { getActorHitLocationSchema } from "./hit-location-schemas"; + +function num(value: unknown, fallback = 0): number { + const n = Number(value); + return Number.isFinite(n) ? n : fallback; +} + +function getSingleTargetActor(): any | null { + const targets = Array.from(game.user?.targets ?? []); + if (!targets.length) return null; + return (targets[0] as any)?.actor ?? null; +} + +export async function rollStandaloneHitLocation(actor?: any): Promise { + const result = await rollHitLocation(actor); + const speaker = actor ? ChatMessage.getSpeaker({ actor }) : ChatMessage.getSpeaker(); + const content = ` +
+

Hit Location

+

Roll: ${result.roll}

+

Location: ${result.location.label}

+
+ `; + + await ChatMessage.create({ + user: game.user?.id, + speaker, + content + }); +} + +export async function rollSkillForActor(actor: any, skillName: string, target: number): Promise { + const result = await rollPercentile(target); + const skillItem = actor.items.find( + (item: any) => item.type === "skill" && item.name.toLowerCase() === String(skillName).toLowerCase() + ); + + await postSkillRollCard({ + actor, + actorName: actor.name, + actorImg: actor.img, + skillName, + skillImg: skillItem?.img, + roll: result.roll, + target: result.target, + outcomeLabel: outcomeLabel(result.outcome), + isSuccess: result.isSuccess, + isSpecial: result.isSpecial, + isFumble: result.isFumble + }); +} + +function resolveLinkedSkill(actor: any, weapon: any): { name: string; target: number; img?: string } { + const skillId = weapon.system.linkedSkillId; + const byId = skillId ? actor.items.get(skillId) : null; + if (byId?.type === "skill") { + return { name: byId.name, target: num(byId.system.value), img: byId.img }; + } + + const linkedSkillName = String(weapon.system.linkedSkill || "").trim(); + if (linkedSkillName) { + const byName = actor.items.find( + (item: any) => item.type === "skill" && item.name.toLowerCase() === linkedSkillName.toLowerCase() + ); + if (byName) { + return { name: byName.name, target: num(byName.system.value), img: byName.img }; + } + } + + return { + name: linkedSkillName || "Unlinked Skill", + target: 0 + }; +} + +type AimMode = "unaimed" | "partial" | "full"; + +interface AttackRollModifierResult { + finalTarget: number; + summary: string; + aimMode: AimMode; + aimMultiplier: number; + flatModifierTotal: number; +} + +function clampPercentTarget(value: number): number { + return Math.max(0, Math.min(99, Math.floor(value))); +} + +async function promptAttackModifiers(attacker: any, weapon: any, baseSkillTarget: number): Promise { + const attackType = String(weapon.system?.attackType || "ranged"); + const isRanged = attackType === "ranged"; + const defaultAim: AimMode = "full"; + const safeActorName = foundry.utils.escapeHTML(String(attacker?.name || "Attacker")); + const safeWeaponName = foundry.utils.escapeHTML(String(weapon?.name || "Weapon")); + const safeActorImg = foundry.utils.escapeHTML(String(attacker?.img || "icons/svg/mystery-man.svg")); + const safeWeaponImg = foundry.utils.escapeHTML(String(weapon?.img || "icons/svg/sword.svg")); + + const content = ` +
+
+ ${safeActorName} + ${safeWeaponName} +
+

${safeWeaponName}

+

${safeActorName} Attack Setup

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Ringworld rule support: unaimed ranged attacks use quarter skill, partial aim uses half skill, full aim uses full skill.

+
+ `; + + return new Promise((resolve) => { + let settled = false; + const done = (value: AttackRollModifierResult | null) => { + if (settled) return; + settled = true; + resolve(value); + }; + + new Dialog({ + title: `${weapon.name} - Attack Modifiers`, + content, + buttons: { + roll: { + label: "Roll Attack", + callback: (dlgHtml: JQuery) => { + const root = dlgHtml[0] as HTMLElement | undefined; + const query = (name: string) => (root?.querySelector(`[name="${name}"]`) as HTMLInputElement | HTMLSelectElement | null); + const aimModeRaw = String(query("aimMode")?.value || defaultAim) as AimMode; + const aimMode: AimMode = (aimModeRaw === "unaimed" || aimModeRaw === "partial" || aimModeRaw === "full") + ? aimModeRaw + : defaultAim; + const aimMultiplier = isRanged ? ({ unaimed: 0.25, partial: 0.5, full: 1 } as const)[aimMode] : 1; + const coverMod = num(query("coverMod")?.value, 0); + const movementMod = num(query("movementMod")?.value, 0); + const rangeMod = num(query("rangeMod")?.value, 0); + const situationalMod = num(query("situationalMod")?.value, 0); + const customMod = num(query("customMod")?.value, 0); + const customLabel = String(query("customLabel")?.value || "").trim(); + + const flatModifierTotal = coverMod + movementMod + rangeMod + situationalMod + customMod; + const preAimTarget = baseSkillTarget + flatModifierTotal; + const adjustedByAim = Math.floor(preAimTarget * aimMultiplier); + const finalTarget = clampPercentTarget(adjustedByAim); + + const parts: string[] = [`Base ${baseSkillTarget}`]; + if (coverMod) parts.push(`Cover ${coverMod >= 0 ? "+" : ""}${coverMod}`); + if (movementMod) parts.push(`Movement ${movementMod >= 0 ? "+" : ""}${movementMod}`); + if (rangeMod) parts.push(`Range ${rangeMod >= 0 ? "+" : ""}${rangeMod}`); + if (situationalMod) parts.push(`Situational ${situationalMod >= 0 ? "+" : ""}${situationalMod}`); + if (customMod) parts.push(`${customLabel || "Custom"} ${customMod >= 0 ? "+" : ""}${customMod}`); + parts.push(`Pre-Aim ${preAimTarget}`); + if (isRanged) { + const aimLabel = aimMode === "unaimed" ? "Quarter" : aimMode === "partial" ? "Half" : "Full"; + parts.push(`Aim ${aimLabel} x${aimMultiplier}`); + } + parts.push(`Final ${finalTarget}`); + + done({ + finalTarget, + summary: parts.join(" | "), + aimMode, + aimMultiplier, + flatModifierTotal + }); + } + }, + cancel: { + label: "Cancel", + callback: () => done(null) + } + }, + default: "roll", + close: () => done(null) + }, { + classes: ["ringworld-attack-dialog"], + width: 510, + resizable: false + }).render(true); + }); +} + +async function consumeWeaponEnergy(actor: any, weapon: any): Promise<{ + sourceLabel: string; + remaining: number; + cost: number; + insufficient: boolean; +}> { + const cost = Math.max(0, num(weapon.system.energyUsedPerAttack)); + if (cost <= 0) { + return { sourceLabel: "None", remaining: 0, cost: 0, insufficient: false }; + } + + const source = String(weapon.system.powerSource || "actor"); + + if (source === "powerPack") { + const packId = String(weapon.system.powerPackId || ""); + const pack = packId ? actor.items.get(packId) : null; + + if (!pack || pack.type !== "gear" || !pack.system.isPowerPack) { + ui.notifications?.warn("Weapon is configured for a power pack but no valid power pack is linked."); + return { sourceLabel: "Power Pack", remaining: 0, cost, insufficient: true }; + } + + const current = num(pack.system.energy?.value); + if (current < cost) { + ui.notifications?.warn(`${pack.name} does not have enough energy.`); + return { sourceLabel: pack.name, remaining: current, cost, insufficient: true }; + } + + const next = current - cost; + await actor.updateEmbeddedDocuments("Item", [{ _id: pack.id, "system.energy.value": next }]); + return { sourceLabel: pack.name, remaining: next, cost, insufficient: false }; + } + + const current = num(actor.system.resources?.energy?.value); + if (current < cost) { + ui.notifications?.warn(`${actor.name} does not have enough actor energy.`); + return { sourceLabel: "Actor Energy", remaining: current, cost, insufficient: true }; + } + + const next = current - cost; + await actor.update({ "system.resources.energy.value": next }); + return { sourceLabel: "Actor Energy", remaining: next, cost, insufficient: false }; +} + +function getItemArmorAtLocation(item: any, slug: HitLocationSlug): number { + if (item.type !== "armor" || !item.system.equipped || item.system.stowed) return 0; + if (!item.system.coverage?.[slug]) return 0; + + const specific = num(item.system.perLocation?.[slug]); + if (specific > 0) return specific; + return num(item.system.value); +} + +function getTotalArmorAtLocation(targetActor: any, slug: HitLocationSlug): number { + const locArmor = num(targetActor.system.hitLocations?.[slug]?.armor); + const stackedItemsArmor = targetActor.items + .filter((item: any) => item.type === "armor") + .reduce((sum: number, item: any) => sum + getItemArmorAtLocation(item, slug), 0); + + return locArmor + stackedItemsArmor; +} + +function buildConditionUpdates(targetActor: any, slug: HitLocationSlug, locationValue: number, locationMax: number, generalHp: number) { + const current = foundry.utils.deepClone(targetActor.system.conditions ?? {}); + current.unusableLimbs = current.unusableLimbs ?? { rArm: false, lArm: false, rLeg: false, lLeg: false }; + + if (locationValue <= 0) { + if (slug === "head" || slug === "chest") { + current.unconscious = true; + current.prone = true; + } + + if (slug === "abdomen") { + current.prone = true; + current.unusableLimbs.rLeg = true; + current.unusableLimbs.lLeg = true; + } + + if (slug === "rArm" || slug === "lArm") { + current.unusableLimbs[slug] = true; + } + + if (LEG_LOCATIONS.includes(slug)) { + current.unusableLimbs[slug] = true; + current.prone = true; + } + } + + if (generalHp <= 0) { + current.dying = true; + } + + if ((slug === "head" || slug === "chest" || slug === "abdomen") && locationValue <= -locationMax) { + current.dying = true; + } + + if (slug === "head" && locationValue <= -locationMax) { + current.dead = true; + current.unconscious = true; + } + + return current; +} + +async function syncCoreStatusEffects(targetActor: any, conditions: any): Promise { + const toggler = (targetActor as any).toggleStatusEffect; + if (typeof toggler !== "function") return; + + try { + await toggler.call(targetActor, "prone", { active: !!conditions.prone }); + await toggler.call(targetActor, "unconscious", { active: !!conditions.unconscious }); + await toggler.call(targetActor, "dead", { active: !!conditions.dead }); + } catch (_err) { + // Some worlds lock actor-level status manipulation; system conditions still update. + } +} + +async function applyLocationDamage(targetActor: any, slug: HitLocationSlug, rawDamage: number): Promise<{ + armor: number; + finalDamage: number; + locationValue: number; + generalHpValue: number; +}> { + const location = targetActor.system.hitLocations?.[slug]; + const currentLoc = num(location?.hp?.value); + const locMax = Math.max(1, num(location?.hp?.max, 1)); + const armor = getTotalArmorAtLocation(targetActor, slug); + const finalDamage = Math.max(0, rawDamage - armor); + + const unclampedLoc = currentLoc - finalDamage; + const nextLoc = isLimb(slug) ? Math.max(unclampedLoc, -locMax) : unclampedLoc; + + const currentGeneral = num(targetActor.system.generalHp?.value); + const nextGeneral = currentGeneral - finalDamage; + + const conditions = buildConditionUpdates(targetActor, slug, nextLoc, locMax, nextGeneral); + + const updates: Record = { + [`system.hitLocations.${slug}.hp.value`]: nextLoc, + "system.generalHp.value": nextGeneral, + "system.conditions": conditions + }; + + await targetActor.update(updates); + await syncCoreStatusEffects(targetActor, conditions); + + return { + armor, + finalDamage, + locationValue: nextLoc, + generalHpValue: nextGeneral + }; +} + +export async function useWeaponWorkflow(attacker: any, weapon: any): Promise { + const targetActor = getSingleTargetActor(); + const hasTarget = Boolean(targetActor); + const targetName = targetActor?.name ?? "No Target"; + + const linkedSkill = resolveLinkedSkill(attacker, weapon); + const modifierResult = await promptAttackModifiers(attacker, weapon, linkedSkill.target); + if (!modifierResult) return; + + const energy = await consumeWeaponEnergy(attacker, weapon); + if (energy.insufficient) return; + + const attack = await rollPercentile(modifierResult.finalTarget); + + if (!attack.isSuccess) { + const remainingGeneralHp = targetActor ? num(targetActor.system.generalHp?.value, 0) : 0; + await postAttackCard({ + actor: attacker, + actorName: attacker.name, + actorImg: attacker.img, + targetName, + hasTarget, + weaponName: weapon.name, + weaponImg: weapon.img, + skillName: linkedSkill.name, + skillImg: linkedSkill.img, + attackRoll: attack.roll, + attackTarget: attack.target, + attackOutcomeLabel: outcomeLabel(attack.outcome), + isSuccess: false, + isSpecial: attack.isSpecial, + isFumble: attack.isFumble, + hitLocationLabel: "-", + hitLocationRoll: null, + rawDamage: 0, + armor: 0, + finalDamage: 0, + remainingLocationHp: 0, + remainingGeneralHp, + energySourceLabel: energy.sourceLabel, + energyCost: energy.cost, + energyRemaining: energy.remaining, + notes: `Attack failed before damage was rolled. ${modifierResult.summary}` + }); + return; + } + + const hitSelection = await chooseOrRollHitLocation(targetActor ?? attacker); + if (!hitSelection) return; + + const damageFormula = String(weapon.system.damageFormula || "0").trim() || "0"; + const damageRoll = await evaluateRollWithDice(damageFormula); + const rawDamage = Math.max(0, num(damageRoll.total)); + + const application = targetActor + ? await applyLocationDamage(targetActor, hitSelection.slug, rawDamage) + : { + armor: 0, + finalDamage: rawDamage, + locationValue: 0, + generalHpValue: 0 + }; + + await postAttackCard({ + actor: attacker, + actorName: attacker.name, + actorImg: attacker.img, + targetName, + hasTarget, + weaponName: weapon.name, + weaponImg: weapon.img, + skillName: linkedSkill.name, + skillImg: linkedSkill.img, + attackRoll: attack.roll, + attackTarget: attack.target, + attackOutcomeLabel: outcomeLabel(attack.outcome), + isSuccess: attack.isSuccess, + isSpecial: attack.isSpecial, + isFumble: attack.isFumble, + hitLocationLabel: hitSelection.label, + hitLocationRoll: hitSelection.rolled, + rawDamage, + armor: application.armor, + finalDamage: application.finalDamage, + remainingLocationHp: application.locationValue, + remainingGeneralHp: application.generalHpValue, + energySourceLabel: energy.sourceLabel, + energyCost: energy.cost, + energyRemaining: energy.remaining, + notes: targetActor + ? `Location and general HP were both reduced by final damage, per Ringworld rules. ${modifierResult.summary}` + : `No target selected: roll resolved with no damage application. ${modifierResult.summary}` + }); +} + +export function recomputeHitPointMaximums(actor: any): void { + if (actor.type !== "explorer" && actor.type !== "creature") return; + + // Ringworld general HP is CON + MAS. Location HP then scales from that general HP. + const con = num(actor.system?.characteristics?.con, 10); + const mas = num(actor.system?.characteristics?.mas, 10); + const existingMax = num(actor.system.generalHp?.max, 0); + const existingValue = num(actor.system.generalHp?.value, 0); + const generalMax = Math.max(1, Math.floor(con + mas)); + + actor.system.generalHp = actor.system.generalHp ?? { value: generalMax, max: generalMax }; + actor.system.generalHp.max = generalMax; + const hadExisting = existingMax > 0; + const wasFull = hadExisting && existingValue >= existingMax; + const looksPlaceholder = existingMax <= 1 && existingValue <= 1; + const preservedDamage = hadExisting ? Math.max(0, existingMax - existingValue) : 0; + const recomputedValue = (!hadExisting || wasFull || looksPlaceholder) + ? generalMax + : Math.max(0, generalMax - preservedDamage); + + actor.system.generalHp.value = Math.min(Math.max(0, recomputedValue), generalMax); + const generalAtFull = num(actor.system.generalHp.value, generalMax) >= generalMax; + + actor.system.hitLocations = actor.system.hitLocations ?? {}; + actor.system.hitLocationSchema = actor.system.hitLocationSchema ?? "auto"; + const schema = getActorHitLocationSchema(actor); + + for (const entry of schema.entries) { + const slug = entry.slug; + const loc = actor.system.hitLocations[slug] ?? { + label: slug, + hp: { value: 1, max: 1 }, + armor: 0 + }; + loc.label = entry.label; + const hpFactor = Math.max(0.05, num((entry as any).hpFactor, 0.3)); + const max = Math.max(1, Math.ceil(generalMax * hpFactor)); + + const previousMax = num(loc.hp?.max, 1); + const previousValue = num(loc.hp?.value, previousMax); + const isPlaceholder = previousMax <= 1 && previousValue <= 1; + const shouldBackfillToFull = generalAtFull && previousValue <= 1 && max > 1; + + loc.hp.max = max; + loc.hp.value = (isPlaceholder || shouldBackfillToFull) ? max : Math.min(previousValue, max); + actor.system.hitLocations[slug] = loc; + } +} + +export function getTemplatePath(path: string): string { + return `systems/${SYSTEM_ID}/templates/${path}`; +} diff --git a/src/module/sheets/actors/CreatureSheet.ts b/src/module/sheets/actors/CreatureSheet.ts new file mode 100644 index 0000000..2e8f230 --- /dev/null +++ b/src/module/sheets/actors/CreatureSheet.ts @@ -0,0 +1,73 @@ +import { getActorHitLocationSchema } from "../../hit-location-schemas"; + +const BaseActorSheet = (foundry as any).appv1.sheets.ActorSheet; + +export class RingworldCreatureSheet extends BaseActorSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "actor", "creature"], + width: 760, + height: 700 + }); + } + + get template(): string { + return "systems/ringworld/templates/actor/creature-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + const actor = this.actor as any; + + return { + ...data, + system: actor.system, + skills: actor.items.filter((item: any) => item.type === "skill"), + weapons: actor.items.filter((item: any) => item.type === "weapon"), + hitLocations: getActorHitLocationSchema(actor).entries.map((entry) => ({ + slug: entry.slug, + rollRange: `${entry.min}-${entry.max}`, + label: entry.label, + ...(actor.system.hitLocations?.[entry.slug] ?? {}) + })) + }; + } + + activateListeners(html: JQuery): void { + super.activateListeners(html); + + html.find(".item-create").on("click", async (event) => { + event.preventDefault(); + const type = String((event.currentTarget as HTMLElement).dataset.type || "skill"); + await this.actor.createEmbeddedDocuments("Item", [{ name: `New ${type}`, type }]); + }); + + html.find(".item-edit").on("click", (event) => { + const li = (event.currentTarget as HTMLElement).closest(".item"); + const item = li ? this.actor.items.get(li.dataset.itemId ?? "") : null; + item?.sheet?.render(true); + }); + + html.find(".item-delete").on("click", async (event) => { + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + await this.actor.deleteEmbeddedDocuments("Item", [li.dataset.itemId]); + }); + + html.find(".item-roll-skill").on("click", async (event) => { + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + await (this.actor as any).rollSkillById(li.dataset.itemId); + }); + + html.find(".item-use-weapon").on("click", async (event) => { + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + await (this.actor as any).useWeaponById(li.dataset.itemId); + }); + + html.find(".roll-hit-location").on("click", async () => { + await (this.actor as any).rollHitLocation(); + }); + } +} diff --git a/src/module/sheets/actors/ExplorerSheet.ts b/src/module/sheets/actors/ExplorerSheet.ts new file mode 100644 index 0000000..fd00bba --- /dev/null +++ b/src/module/sheets/actors/ExplorerSheet.ts @@ -0,0 +1,1082 @@ +import { type HitLocationSlug } from "../../constants"; +import { + BASE_SKILL_CATEGORIES, + baseSkillNameSet, + normalizeSkillName, + RINGWORLD_BASE_SKILLS +} from "../../data-base-skills"; +import { baseSkillByName, DEFAULT_SKILL_ICON, resolveSkillIcon, skillIconPath } from "../../skill-icons"; +import { getActorHitLocationSchema, listHitLocationSchemas } from "../../hit-location-schemas"; +import { PURSUIT_CATALOG } from "../../world-catalog"; +import { outcomeLabel, rollPercentile } from "../../utils/percentile"; +import { postSkillRollCard } from "../../chat"; + +const BaseActorSheet = (foundry as any).appv1.sheets.ActorSheet; + +const ROOT_KEY_BY_CATEGORY: Record = { + Agility: "agility", + Communication: "communication", + Knowledge: "knowledge", + Perception: "perception", + Technical: "technical" +}; + +const ROOT_FORMULA_BY_CATEGORY: Record = { + Agility: "STR + DEX", + Communication: "INT + APP", + Knowledge: "INT + EDU", + Perception: "POW + CON", + Technical: "DEX + INT" +}; + +const EXCLUDED_EDUCATION_SKILLS = new Set([ + normalizeSkillName("Archaic Melee Weapon"), + normalizeSkillName("Archaic Ranged Weapons") +]); + +const HYPERDRIVE_KEY = normalizeSkillName("Hyperdrive"); + +interface BuilderAllocation { + education: number; + pursuits: number; + special: number; +} + +interface BuilderPursuitSelection { + name: string; + years: number; +} + +interface BuilderState { + pursuits: BuilderPursuitSelection[]; + allocations: Record; + baseBySkillId: Record; +} + +interface BuilderPool { + total: number; + spent: number; + remaining: number; +} + +interface BuilderSkillMeta { + allocation: BuilderAllocation; + totalAllocated: number; + baseValue: number; + rootCap: number; + maxAllocation: number; + allowsEducation: boolean; + allowsPursuits: boolean; + allowsSpecial: boolean; +} + +interface BuilderContext { + state: BuilderState; + education: BuilderPool; + pursuits: BuilderPool; + special: BuilderPool; + pursuitYears: BuilderPool; + pursuitOptions: string[]; + pursuitAllowedSkills: Set; + bySkillId: Map; +} + +function byName(a: any, b: any): number { + return a.name.localeCompare(b.name); +} + +function num(value: unknown, fallback = 0): number { + const n = Number(value); + return Number.isFinite(n) ? n : fallback; +} + +function toInt(value: unknown, fallback = 0): number { + return Math.max(0, Math.floor(num(value, fallback))); +} + +function normalizeAllocation(raw: any): BuilderAllocation { + return { + education: toInt(raw?.education, 0), + pursuits: toInt(raw?.pursuits, 0), + special: toInt(raw?.special, 0) + }; +} + +function normalizePursuitSelection(raw: any): BuilderPursuitSelection | null { + const name = String(raw?.name || "").trim(); + const years = toInt(raw?.years, 0); + if (!name || years <= 0) return null; + return { name, years }; +} + +function normalizeBuilderState(raw: any): BuilderState { + const pursuits = Array.isArray(raw?.pursuits) + ? raw.pursuits.map((entry: any) => normalizePursuitSelection(entry)).filter((entry: any) => Boolean(entry)) + : []; + + const allocations: Record = {}; + if (raw?.allocations && typeof raw.allocations === "object") { + for (const [skillId, allocation] of Object.entries(raw.allocations)) { + if (!skillId) continue; + const normalized = normalizeAllocation(allocation); + if (normalized.education + normalized.pursuits + normalized.special <= 0) continue; + allocations[skillId] = normalized; + } + } + + const baseBySkillId: Record = {}; + if (raw?.baseBySkillId && typeof raw.baseBySkillId === "object") { + for (const [skillId, value] of Object.entries(raw.baseBySkillId)) { + if (!skillId) continue; + baseBySkillId[skillId] = Math.max(0, toInt(value, 0)); + } + } + + return { + pursuits, + allocations, + baseBySkillId + }; +} + +function isEducationEligible(skillName: string): boolean { + return !EXCLUDED_EDUCATION_SKILLS.has(normalizeSkillName(skillName)); +} + +function isSpecialEligible(skillName: string): boolean { + return normalizeSkillName(skillName) !== HYPERDRIVE_KEY; +} + +function buildPursuitAllowedSkillSet(pursuits: BuilderPursuitSelection[]): Set { + const entriesByName = new Map( + PURSUIT_CATALOG.map((entry) => [normalizeSkillName(entry.name), entry]) + ); + const allowed = new Set(); + + for (const pursuit of pursuits) { + const entry = entriesByName.get(normalizeSkillName(pursuit.name)); + if (!entry) continue; + + for (const skillName of entry.fixedSkills || []) { + allowed.add(normalizeSkillName(skillName)); + } + + for (const group of entry.optionalSkillGroups || []) { + for (const skillName of group.skills || []) { + allowed.add(normalizeSkillName(skillName)); + } + } + } + + return allowed; +} + +function computeBuilderPools(characteristics: any): { + educationTotal: number; + pursuitsTotal: number; + specialTotal: number; + pursuitYearsTotal: number; +} { + const age = toInt(characteristics?.age, 0); + const edu = toInt(characteristics?.edu, 0); + const int = toInt(characteristics?.int, 0); + + const educationTotal = Math.max(0, (edu - 10) * 20); + const pursuitYearsTotal = Math.max(0, age - edu - 6); + const pursuitsTotal = pursuitYearsTotal * 20; + const specialTotal = Math.max(0, int * 10); + + return { + educationTotal, + pursuitsTotal, + specialTotal, + pursuitYearsTotal + }; +} + +function buildBuilderContext(actor: any, skills: any[]): BuilderContext { + const pools = computeBuilderPools(actor.system?.characteristics ?? {}); + const state = normalizeBuilderState(actor.system?.characterBuilder ?? {}); + + const pursuitOptions = PURSUIT_CATALOG.map((entry) => entry.name).sort((a, b) => a.localeCompare(b)); + const pursuitAllowedSkills = buildPursuitAllowedSkillSet(state.pursuits); + + const bySkillId = new Map(); + + let educationSpent = 0; + let pursuitsSpent = 0; + let specialSpent = 0; + + for (const skill of skills) { + const allocation = normalizeAllocation(state.allocations[skill.id]); + const totalAllocated = allocation.education + allocation.pursuits + allocation.special; + + const baseFromState = state.baseBySkillId[skill.id]; + const baseFromDefinition = baseSkillByName(skill.name)?.base; + const inferredBase = Math.max(0, num(skill.system?.value, 0) - totalAllocated); + const baseValue = Math.max( + 0, + Number.isFinite(baseFromState) + ? baseFromState + : (baseFromDefinition ?? inferredBase) + ); + + const rootKey = ROOT_KEY_BY_CATEGORY[String(skill.system?.category || "")] ?? ""; + const rootCap = rootKey ? Math.max(0, toInt(actor.system?.skillRootMax?.[rootKey], 0)) : 99; + const maxAllocation = Math.max(0, rootCap - baseValue); + + const normalizedSkillName = normalizeSkillName(skill.name); + const allowsEducation = isEducationEligible(skill.name); + const allowsPursuits = pursuitAllowedSkills.has(normalizedSkillName); + const allowsSpecial = isSpecialEligible(skill.name); + + bySkillId.set(skill.id, { + allocation, + totalAllocated, + baseValue, + rootCap, + maxAllocation, + allowsEducation, + allowsPursuits, + allowsSpecial + }); + + educationSpent += allocation.education; + pursuitsSpent += allocation.pursuits; + specialSpent += allocation.special; + } + + const pursuitYearsAllocated = state.pursuits.reduce((sum, row) => sum + row.years, 0); + + return { + state, + education: { + total: pools.educationTotal, + spent: educationSpent, + remaining: pools.educationTotal - educationSpent + }, + pursuits: { + total: pools.pursuitsTotal, + spent: pursuitsSpent, + remaining: pools.pursuitsTotal - pursuitsSpent + }, + special: { + total: pools.specialTotal, + spent: specialSpent, + remaining: pools.specialTotal - specialSpent + }, + pursuitYears: { + total: pools.pursuitYearsTotal, + spent: pursuitYearsAllocated, + remaining: pools.pursuitYearsTotal - pursuitYearsAllocated + }, + pursuitOptions, + pursuitAllowedSkills, + bySkillId + }; +} + +function getItemArmorAtLocation(item: any, slug: string): number { + if (item.type !== "armor" || !item.system?.equipped || item.system?.stowed) return 0; + if (!item.system?.coverage?.[slug]) return 0; + + const specific = num(item.system?.perLocation?.[slug], 0); + if (specific > 0) return specific; + return num(item.system?.value, 0); +} + +function getTotalArmorAtLocation(actor: any, slug: string): number { + const base = num(actor.system?.hitLocations?.[slug]?.armor, 0); + const fromItems = actor.items + .filter((item: any) => item.type === "armor") + .reduce((sum: number, item: any) => sum + getItemArmorAtLocation(item, slug), 0); + return base + fromItems; +} + +function getPreparedHitLocation(actor: any, slug: string, rollRange: string): any { + const locationConfig = getActorHitLocationSchema(actor).entries.find((entry) => entry.slug === slug); + const source = actor.system?.hitLocations?.[slug] ?? {}; + const hp = source.hp ?? {}; + const rawLabel = String(source.label ?? "").trim(); + return { + slug, + rollRange, + label: rawLabel || String(locationConfig?.label || slug), + armor: num(source.armor, 0), + totalArmor: getTotalArmorAtLocation(actor, slug), + hp: { + value: num(hp.value, 0), + max: num(hp.max, 0) + } + }; +} + +function chooseRandom(items: T[]): T { + return items[Math.floor(Math.random() * items.length)]; +} + +function clampPercent(value: number): number { + return Math.max(0, Math.min(99, Math.floor(value))); +} + +export class RingworldExplorerSheet extends BaseActorSheet { + private isToolsUnlocked(): boolean { + return Boolean((this.actor as any)?.system?.sheetToolsUnlocked); + } + + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "actor", "explorer"], + width: 930, + height: 1460, + tabs: [{ navSelector: ".tabs", contentSelector: ".sheet-body", initial: "overview" }] + }); + } + + get template(): string { + return "systems/ringworld/templates/actor/explorer-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + const actor = this.actor as any; + + if (game.user?.isGM && actor.isOwner) { + await actor.ensureBaseSkills?.(); + } + + const skills = actor.items.filter((item: any) => item.type === "skill").sort(byName); + const builderContext = buildBuilderContext(actor, skills); + + const weapons = actor.items.filter((item: any) => item.type === "weapon").sort(byName); + const armors = actor.items.filter((item: any) => item.type === "armor").sort(byName); + const gear = actor.items.filter((item: any) => item.type === "gear").sort(byName); + const speciesOptions = game.items + .filter((item: any) => item.type === "species" && item.system.playable) + .map((item: any) => item.name) + .sort(); + const baseSet = baseSkillNameSet(); + + const skillByNormalized = new Map(); + for (const skill of skills) { + const key = normalizeSkillName(skill.name); + if (!skillByNormalized.has(key)) { + skillByNormalized.set(key, skill); + } + } + + const skillRootMax = actor.system.skillRootMax ?? {}; + const skillsByCategory = BASE_SKILL_CATEGORIES.map((category) => { + const rootKey = ROOT_KEY_BY_CATEGORY[category] ?? category.toLowerCase(); + const rows = RINGWORLD_BASE_SKILLS + .filter((skill) => skill.category === category) + .map((skill) => { + const existing = skillByNormalized.get(normalizeSkillName(skill.name)); + const meta = existing ? builderContext.bySkillId.get(existing.id) : null; + return { + name: skill.name, + base: skill.base, + category: skill.category, + specialization: skill.specialization ?? "", + itemId: existing?.id ?? null, + icon: existing?.img || skillIconPath(skill.name), + value: existing?.system?.value ?? skill.base, + missing: !existing, + allocationTotal: meta?.totalAllocated ?? 0, + allocation: meta?.allocation ?? { education: 0, pursuits: 0, special: 0 } + }; + }); + + return { + category, + rootKey, + rootFormula: ROOT_FORMULA_BY_CATEGORY[category] ?? "", + rootValue: num(skillRootMax[rootKey], 0), + rows + }; + }); + + const customSkills = skills + .filter((item: any) => !baseSet.has(normalizeSkillName(item.name))) + .map((item: any) => { + const meta = builderContext.bySkillId.get(item.id); + return { + id: item.id, + name: item.name, + system: item.system, + img: resolveSkillIcon(item.name, item.img || DEFAULT_SKILL_ICON), + allocationTotal: meta?.totalAllocated ?? 0, + allocation: meta?.allocation ?? { education: 0, pursuits: 0, special: 0 } + }; + }); + + const missingBaseSkillsCount = skillsByCategory.reduce( + (total, group) => total + group.rows.filter((row) => row.missing).length, + 0 + ); + + const schema = getActorHitLocationSchema(actor); + const rangesFromSchema = new Map(schema.entries.map((entry) => [entry.slug, `${entry.min}-${entry.max}`])); + const order = schema.entries.map((entry) => entry.slug as HitLocationSlug); + const hitLocations = order.map((slug) => + getPreparedHitLocation(actor, slug, rangesFromSchema.get(slug) ?? "") + ); + + const bodyLayoutOrder = schema.bodyOrder; + const bodyLocations = bodyLayoutOrder.map((slug) => + getPreparedHitLocation(actor, slug, rangesFromSchema.get(slug) ?? "") + ); + + const enc = actor.system?.encumbrance ?? {}; + const encRaw = num(enc.rawLoadKg, 0); + const encEffective = num(enc.effectiveLoadKg, encRaw); + const encLimit = num(enc.limitKg, num(actor.system?.characteristics?.str, 0)); + const encPenalty = num(enc.actionRankingPenalty, 0); + const baseActionRanking = num(actor.system?.derived?.actionRanking, 0); + const effectiveActionRanking = Math.max(1, baseActionRanking + encPenalty); + const encumbranceSummary = `${encRaw.toFixed(1)}kg on-person | ${encEffective.toFixed(1)}kg effective / ${encLimit.toFixed(1)}kg limit | AR +${encPenalty} (stowed ignored)`; + const movementSummary = `${num(actor.system?.derived?.currentMove, num(actor.system?.derived?.move, 3))}m / ${num(actor.system?.derived?.currentRun, num(actor.system?.derived?.run, 6))}m`; + + return { + ...data, + system: actor.system, + skills, + skillsByCategory, + customSkills, + missingBaseSkillsCount, + weapons, + armors, + gear, + speciesOptions, + hitLocations, + bodyLocations, + hitLocationSchemaOptions: listHitLocationSchemas(), + currentHitLocationSchema: String(actor.system.hitLocationSchema || "auto"), + activeHitLocationSchemaId: schema.id, + activeHitLocationSchemaLabel: schema.label, + sheetToolsUnlocked: Boolean(actor.system?.sheetToolsUnlocked), + baseActionRanking, + effectiveActionRanking, + encumbranceSummary, + movementSummary, + builder: { + education: builderContext.education, + pursuits: builderContext.pursuits, + special: builderContext.special, + pursuitYears: builderContext.pursuitYears, + pursuitsList: builderContext.state.pursuits, + pursuitOptions: builderContext.pursuitOptions + } + }; + } + + private async updateBuilderState(state: BuilderState): Promise { + await this.actor.update({ + "system.characterBuilder": { + pursuits: state.pursuits, + allocations: state.allocations, + baseBySkillId: state.baseBySkillId + } + }); + } + + private async syncSkillsFromBuilderState(state: BuilderState): Promise { + const skills = this.actor.items.filter((item: any) => item.type === "skill"); + const updates: any[] = []; + + for (const skill of skills) { + const allocation = normalizeAllocation(state.allocations[skill.id]); + const total = allocation.education + allocation.pursuits + allocation.special; + + const rootKey = ROOT_KEY_BY_CATEGORY[String(skill.system?.category || "")] ?? ""; + const rootCap = rootKey ? Math.max(0, toInt(this.actor.system?.skillRootMax?.[rootKey], 0)) : 99; + + let base = state.baseBySkillId[skill.id]; + if (!Number.isFinite(base)) { + const fromBase = baseSkillByName(skill.name)?.base; + base = Math.max(0, fromBase ?? (num(skill.system?.value, 0) - total)); + state.baseBySkillId[skill.id] = base; + } + + const value = Math.max(0, Math.min(99, Math.min(rootCap, base + total))); + if (num(skill.system?.value, 0) !== value) { + updates.push({ _id: skill.id, "system.value": value }); + } + } + + if (updates.length) { + await this.actor.updateEmbeddedDocuments("Item", updates); + } + } + + private async addOrUpdatePursuit(name: string, years: number): Promise { + if (!this.isToolsUnlocked()) return; + const trimmed = String(name || "").trim(); + const cleanYears = Math.max(0, Math.floor(Number(years) || 0)); + if (!trimmed || cleanYears <= 0) { + ui.notifications?.warn("Choose a pursuit and set years above 0."); + return; + } + + const skills = this.actor.items.filter((item: any) => item.type === "skill"); + const context = buildBuilderContext(this.actor, skills); + const nextState = foundry.utils.deepClone(context.state) as BuilderState; + + const existingIndex = nextState.pursuits.findIndex((entry) => normalizeSkillName(entry.name) === normalizeSkillName(trimmed)); + if (existingIndex >= 0) { + nextState.pursuits[existingIndex].years = cleanYears; + } else { + nextState.pursuits.push({ name: trimmed, years: cleanYears }); + } + + await this.updateBuilderState(nextState); + } + + private async removePursuit(index: number): Promise { + if (!this.isToolsUnlocked()) return; + const skills = this.actor.items.filter((item: any) => item.type === "skill"); + const context = buildBuilderContext(this.actor, skills); + const nextState = foundry.utils.deepClone(context.state) as BuilderState; + if (index < 0 || index >= nextState.pursuits.length) return; + + nextState.pursuits.splice(index, 1); + await this.updateBuilderState(nextState); + } + + private async resetBuilderAllocations(): Promise { + if (!this.isToolsUnlocked()) return; + const skills = this.actor.items.filter((item: any) => item.type === "skill"); + const context = buildBuilderContext(this.actor, skills); + const nextState = foundry.utils.deepClone(context.state) as BuilderState; + + nextState.allocations = {}; + await this.updateBuilderState(nextState); + await this.syncSkillsFromBuilderState(nextState); + } + + private async randomizeBuilderAllocations(): Promise { + if (!this.isToolsUnlocked()) return; + await (this.actor as any).ensureBaseSkills?.(); + + const skills = this.actor.items.filter((item: any) => item.type === "skill").sort(byName); + const context = buildBuilderContext(this.actor, skills); + const nextState = foundry.utils.deepClone(context.state) as BuilderState; + + const totalYears = Math.max(0, context.pursuitYears.total); + const pursuits = PURSUIT_CATALOG.map((entry) => entry.name); + nextState.pursuits = []; + + if (totalYears > 0 && pursuits.length > 0) { + const desiredCount = Math.min( + pursuits.length, + Math.max(1, Math.min(3, Math.floor(totalYears / 2) || 1)) + ); + + const available = [...pursuits]; + for (let i = 0; i < desiredCount && available.length; i += 1) { + const idx = Math.floor(Math.random() * available.length); + nextState.pursuits.push({ name: available[idx], years: 1 }); + available.splice(idx, 1); + } + + let remainingYears = totalYears - nextState.pursuits.length; + while (remainingYears > 0 && nextState.pursuits.length) { + const row = chooseRandom(nextState.pursuits); + row.years += 1; + remainingYears -= 1; + } + } + + const seededState = normalizeBuilderState(nextState); + seededState.allocations = {}; + + const freshContext = buildBuilderContext( + { ...this.actor, system: { ...this.actor.system, characterBuilder: seededState } }, + skills + ); + + for (const skill of skills) { + const meta = freshContext.bySkillId.get(skill.id); + if (!meta) continue; + seededState.baseBySkillId[skill.id] = meta.baseValue; + seededState.allocations[skill.id] = { education: 0, pursuits: 0, special: 0 }; + } + + const totalBySkill = new Map(); + for (const skill of skills) { + totalBySkill.set(skill.id, 0); + } + + const maxForSkill = (skillId: string): number => { + const meta = freshContext.bySkillId.get(skillId); + return meta ? Math.max(0, meta.maxAllocation) : 0; + }; + + const allocatePool = ( + points: number, + poolKey: keyof BuilderAllocation, + eligible: (skill: any, meta: BuilderSkillMeta) => boolean + ): number => { + let remaining = Math.max(0, points); + let guard = 0; + + while (remaining > 0 && guard < 300000) { + guard += 1; + const candidates = skills.filter((skill) => { + const meta = freshContext.bySkillId.get(skill.id); + if (!meta || !eligible(skill, meta)) return false; + const allocated = totalBySkill.get(skill.id) ?? 0; + return allocated < maxForSkill(skill.id); + }); + + if (!candidates.length) break; + const picked = chooseRandom(candidates); + const current = seededState.allocations[picked.id] ?? { education: 0, pursuits: 0, special: 0 }; + current[poolKey] += 1; + seededState.allocations[picked.id] = current; + totalBySkill.set(picked.id, (totalBySkill.get(picked.id) ?? 0) + 1); + remaining -= 1; + } + + return remaining; + }; + + const educationUnspent = allocatePool( + Math.max(0, freshContext.education.total), + "education", + (skill, meta) => meta.allowsEducation + ); + + const pursuitsUnspent = allocatePool( + Math.max(0, freshContext.pursuits.total), + "pursuits", + (skill, meta) => meta.allowsPursuits + ); + + const specialUnspent = allocatePool( + Math.max(0, freshContext.special.total), + "special", + (skill, meta) => meta.allowsSpecial + ); + + for (const skill of skills) { + const allocation = normalizeAllocation(seededState.allocations[skill.id]); + if (allocation.education + allocation.pursuits + allocation.special <= 0) { + delete seededState.allocations[skill.id]; + } + } + + await this.updateBuilderState(seededState); + await this.syncSkillsFromBuilderState(seededState); + + if (educationUnspent > 0 || pursuitsUnspent > 0 || specialUnspent > 0) { + ui.notifications?.info( + `Random build complete. Unspent points: EDU ${educationUnspent}, Pursuits ${pursuitsUnspent}, Special ${specialUnspent}.` + ); + } else { + ui.notifications?.info("Random build complete. All available points allocated."); + } + } + + private async promptSkillAllocation(skillId: string): Promise { + if (!this.isToolsUnlocked()) return; + const skill = this.actor.items.get(skillId); + if (!skill || skill.type !== "skill") return; + + const skills = this.actor.items.filter((item: any) => item.type === "skill"); + const context = buildBuilderContext(this.actor, skills); + const meta = context.bySkillId.get(skillId); + if (!meta) return; + + const current = meta.allocation; + const maxEducation = Math.max(0, context.education.remaining + current.education); + const maxPursuits = Math.max(0, context.pursuits.remaining + current.pursuits); + const maxSpecial = Math.max(0, context.special.remaining + current.special); + + const maxByCap = Math.max(0, meta.maxAllocation); + + const content = ` +
+
+ +
+

${skill.name}

+

Base ${meta.baseValue}% | Root cap ${meta.rootCap}% | Current ${num(skill.system?.value, 0)}%

+
+
+ + + + + + + +

Max additional from root cap: ${maxByCap}. Pools: EDU ${context.education.remaining}, Pursuits ${context.pursuits.remaining}, Special ${context.special.remaining}.

+
+ `; + + const result = await new Promise((resolve) => { + new Dialog({ + title: `${skill.name} - Allocate Character Points`, + content, + buttons: { + save: { + label: "Apply", + callback: (html: JQuery) => { + const form = html.find("form")[0] as HTMLFormElement | undefined; + if (!form) { + resolve(null); + return; + } + const data = new FormData(form); + resolve({ + education: meta.allowsEducation ? toInt(data.get("education"), current.education) : current.education, + pursuits: meta.allowsPursuits ? toInt(data.get("pursuits"), current.pursuits) : current.pursuits, + special: meta.allowsSpecial ? toInt(data.get("special"), current.special) : current.special + }); + } + }, + cancel: { + label: "Cancel", + callback: () => resolve(null) + } + }, + default: "save", + close: () => resolve(null) + }).render(true); + }); + + if (!result) return; + + const requestedTotal = result.education + result.pursuits + result.special; + if (requestedTotal > maxByCap) { + ui.notifications?.warn(`Allocation exceeds this skill's root-cap allowance (${maxByCap}).`); + return; + } + + if (result.education > maxEducation || result.pursuits > maxPursuits || result.special > maxSpecial) { + ui.notifications?.warn("Allocation exceeds remaining pool points."); + return; + } + + const nextState = foundry.utils.deepClone(context.state) as BuilderState; + nextState.baseBySkillId[skill.id] = meta.baseValue; + if (requestedTotal <= 0) { + delete nextState.allocations[skill.id]; + } else { + nextState.allocations[skill.id] = result; + } + + await this.updateBuilderState(nextState); + await this.syncSkillsFromBuilderState(nextState); + } + + private async rollCharacteristicCheck(characterKey: string, label: string): Promise { + const validKeys = new Set(["str", "mas", "con", "int", "pow", "dex", "app", "edu", "age"]); + if (!validKeys.has(characterKey)) return; + + const base = Math.max(0, toInt((this.actor as any).system?.characteristics?.[characterKey], 0)); + const content = ` +
+
+ + +
+
+ + +
+
+ + +
+

Roll d100 against Characteristic x Multiplier.

+
+ `; + + const multiplier = await new Promise((resolve) => { + let settled = false; + const done = (value: number | null) => { + if (settled) return; + settled = true; + resolve(value); + }; + + new Dialog({ + title: `${label} Check`, + content, + buttons: { + roll: { + label: "Roll", + callback: (dlgHtml: JQuery) => { + const selected = Number((dlgHtml[0]?.querySelector("select[name='multiplier']") as HTMLSelectElement | null)?.value || 1); + const safeMultiplier = Math.max(1, Math.min(7, Math.floor(selected || 1))); + done(safeMultiplier); + } + }, + cancel: { + label: "Cancel", + callback: () => done(null) + } + }, + default: "roll", + close: () => done(null) + }).render(true); + }); + + if (!multiplier) return; + const target = clampPercent(base * multiplier); + const result = await rollPercentile(target); + + await postSkillRollCard({ + actor: this.actor, + actorName: this.actor.name, + actorImg: this.actor.img, + skillName: `${label} x${multiplier}`, + skillImg: DEFAULT_SKILL_ICON, + roll: result.roll, + target: result.target, + outcomeLabel: outcomeLabel(result.outcome), + isSuccess: result.isSuccess, + isSpecial: result.isSpecial, + isFumble: result.isFumble + }); + } + + activateListeners(html: JQuery): void { + super.activateListeners(html); + + html.find(".toggle-sheet-lock").on("click", async (event) => { + event.preventDefault(); + const next = !this.isToolsUnlocked(); + await this.actor.update({ "system.sheetToolsUnlocked": next }); + ui.notifications?.info(next ? "Sheet tools unlocked." : "Sheet tools locked."); + }); + + html.find(".roll-characteristic").on("click", async (event) => { + event.preventDefault(); + const button = event.currentTarget as HTMLElement; + const key = String(button.dataset.char || "").trim().toLowerCase(); + const label = String(button.dataset.label || key.toUpperCase()); + if (!key) return; + await this.rollCharacteristicCheck(key, label); + }); + + html.find(".item-create").on("click", async (event) => { + event.preventDefault(); + const type = String((event.currentTarget as HTMLElement).dataset.type || "gear"); + const doc: any = { name: `New ${type}`, type }; + if (type === "skill") { + doc.img = DEFAULT_SKILL_ICON; + doc.system = { value: 0, category: "Technical", specialization: "" }; + } + await this.actor.createEmbeddedDocuments("Item", [doc]); + }); + + html.find(".item-edit").on("click", (event) => { + event.preventDefault(); + const li = (event.currentTarget as HTMLElement).closest(".item"); + const item = li ? this.actor.items.get(li.dataset.itemId ?? "") : null; + item?.sheet?.render(true); + }); + + html.find(".item-delete").on("click", async (event) => { + event.preventDefault(); + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + await this.actor.deleteEmbeddedDocuments("Item", [li.dataset.itemId]); + }); + + html.find(".item-roll-skill").on("click", async (event) => { + event.preventDefault(); + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + await (this.actor as any).rollSkillById(li.dataset.itemId); + }); + + html.find(".roll-derived").on("click", async (event) => { + event.preventDefault(); + const key = String((event.currentTarget as HTMLElement).dataset.check || ""); + if (!key) return; + await (this.actor as any).rollDerivedCheck(key); + }); + + html.find(".item-use-weapon").on("click", async (event) => { + event.preventDefault(); + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + await (this.actor as any).useWeaponById(li.dataset.itemId); + }); + + html.find(".item-toggle-equipped").on("click", async (event) => { + event.preventDefault(); + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + const item = this.actor.items.get(li.dataset.itemId); + if (!item) return; + + const nextEquipped = !Boolean(item.system?.equipped); + const update: Record = { + _id: item.id, + "system.equipped": nextEquipped + }; + if (nextEquipped) { + update["system.stowed"] = false; + } + await this.actor.updateEmbeddedDocuments("Item", [update]); + }); + + html.find(".item-toggle-stowed").on("click", async (event) => { + event.preventDefault(); + const li = (event.currentTarget as HTMLElement).closest(".item"); + if (!li?.dataset.itemId) return; + const item = this.actor.items.get(li.dataset.itemId); + if (!item) return; + + const nextStowed = !Boolean(item.system?.stowed); + const update: Record = { + _id: item.id, + "system.stowed": nextStowed + }; + if (nextStowed) { + update["system.equipped"] = false; + } + await this.actor.updateEmbeddedDocuments("Item", [update]); + }); + + html.find(".roll-hit-location").on("click", async (event) => { + event.preventDefault(); + await (this.actor as any).rollHitLocation(); + }); + + html.find(".reset-conditions").on("click", async (event) => { + event.preventDefault(); + await this.actor.update({ + "system.conditions.prone": false, + "system.conditions.unconscious": false, + "system.conditions.dead": false, + "system.conditions.dying": false, + "system.conditions.unusableLimbs.rArm": false, + "system.conditions.unusableLimbs.lArm": false, + "system.conditions.unusableLimbs.rLeg": false, + "system.conditions.unusableLimbs.lLeg": false + }); + }); + + html.find(".populate-base-skills").on("click", async (event) => { + event.preventDefault(); + const added = await (this.actor as any).ensureBaseSkills(); + if (added > 0) ui.notifications?.info(`Added ${added} Ringworld base skills.`); + else ui.notifications?.info("All Ringworld base skills are already present."); + }); + + html.find(".add-base-skill").on("click", async (event) => { + event.preventDefault(); + const button = event.currentTarget as HTMLElement; + const name = String(button.dataset.skillName || ""); + const category = String(button.dataset.skillCategory || ""); + const base = Number(button.dataset.skillBase || 0); + const specialization = String(button.dataset.skillSpecialization || ""); + if (!name) return; + + await this.actor.createEmbeddedDocuments("Item", [{ + name, + type: "skill", + img: skillIconPath(name), + system: { value: base, category, specialization } + }]); + }); + + html.find(".builder-add-pursuit").on("click", async (event) => { + event.preventDefault(); + const root = (event.currentTarget as HTMLElement).closest(".rw-builder-pursuits"); + if (!root) return; + const name = String((root.querySelector(".builder-new-pursuit") as HTMLSelectElement | null)?.value || ""); + const years = Number((root.querySelector(".builder-new-years") as HTMLInputElement | null)?.value || 0); + await this.addOrUpdatePursuit(name, years); + }); + + html.find(".builder-update-pursuit").on("click", async (event) => { + event.preventDefault(); + const row = (event.currentTarget as HTMLElement).closest(".builder-pursuit-row"); + if (!row) return; + const name = String((row.querySelector(".builder-pursuit-name") as HTMLSelectElement | null)?.value || ""); + const years = Number((row.querySelector(".builder-pursuit-years") as HTMLInputElement | null)?.value || 0); + await this.addOrUpdatePursuit(name, years); + }); + + html.find(".builder-remove-pursuit").on("click", async (event) => { + event.preventDefault(); + const index = Number((event.currentTarget as HTMLElement).dataset.index ?? -1); + if (!Number.isFinite(index) || index < 0) return; + await this.removePursuit(index); + }); + + html.find(".builder-reset-points").on("click", async (event) => { + event.preventDefault(); + await this.resetBuilderAllocations(); + ui.notifications?.info("Character-builder allocations reset."); + }); + + html.find(".roll-characteristics").on("click", async (event) => { + event.preventDefault(); + if (!this.isToolsUnlocked()) return; + await (this.actor as any).rollCharacteristics?.(); + ui.notifications?.info("Characteristics rolled."); + }); + + html.find(".builder-auto-allocate").on("click", async (event) => { + event.preventDefault(); + if (!this.isToolsUnlocked()) return; + await (this.actor as any).ensureBaseSkills?.(); + await this.randomizeBuilderAllocations(); + }); + + html.find(".builder-skill-allocate").on("click", async (event) => { + event.preventDefault(); + const li = (event.currentTarget as HTMLElement).closest(".item"); + const skillId = String(li?.dataset.itemId || ""); + if (!skillId) return; + await this.promptSkillAllocation(skillId); + }); + } + + protected async _onDropItemCreate(itemData: any): Promise { + const items = Array.isArray(itemData) ? itemData : [itemData]; + const speciesItems = items.filter((entry: any) => entry?.type === "species"); + const createItems = items.filter((entry: any) => entry?.type !== "species"); + + for (const species of speciesItems) { + await (this.actor as any).applySpeciesItem?.(species); + } + if (speciesItems.length > 0) { + const names = speciesItems.map((entry: any) => String(entry?.name || "Species")).join(", "); + ui.notifications?.info(`Applied species to actor: ${names}`); + } + + if (createItems.length === 0) return []; + + const patched = createItems.map((entry: any) => { + if (entry?.type !== "skill") return entry; + return { + ...entry, + img: resolveSkillIcon(String(entry?.name || "Skill"), String(entry?.img || "")) + }; + }); + return super._onDropItemCreate(Array.isArray(itemData) ? patched : patched[0]); + } +} diff --git a/src/module/sheets/actors/VehicleSheet.ts b/src/module/sheets/actors/VehicleSheet.ts new file mode 100644 index 0000000..369d0a8 --- /dev/null +++ b/src/module/sheets/actors/VehicleSheet.ts @@ -0,0 +1,23 @@ +const BaseActorSheet = (foundry as any).appv1.sheets.ActorSheet; + +export class RingworldVehicleSheet extends BaseActorSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "actor", "vehicle"], + width: 680, + height: 560 + }); + } + + get template(): string { + return "systems/ringworld/templates/actor/vehicle-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + return { + ...data, + system: (this.actor as any).system + }; + } +} diff --git a/src/module/sheets/items/ArmorSheet.ts b/src/module/sheets/items/ArmorSheet.ts new file mode 100644 index 0000000..17c6c97 --- /dev/null +++ b/src/module/sheets/items/ArmorSheet.ts @@ -0,0 +1,23 @@ +const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet; + +export class RingworldArmorSheet extends BaseItemSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "armor"], + width: 620, + height: 560 + }); + } + + get template(): string { + return "systems/ringworld/templates/item/armor-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + return { + ...data, + system: (this.item as any).system + }; + } +} diff --git a/src/module/sheets/items/GearSheet.ts b/src/module/sheets/items/GearSheet.ts new file mode 100644 index 0000000..ac5e329 --- /dev/null +++ b/src/module/sheets/items/GearSheet.ts @@ -0,0 +1,23 @@ +const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet; + +export class RingworldGearSheet extends BaseItemSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "gear"], + width: 620, + height: 520 + }); + } + + get template(): string { + return "systems/ringworld/templates/item/gear-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + return { + ...data, + system: (this.item as any).system + }; + } +} diff --git a/src/module/sheets/items/PursuitSheet.ts b/src/module/sheets/items/PursuitSheet.ts new file mode 100644 index 0000000..b7e1f54 --- /dev/null +++ b/src/module/sheets/items/PursuitSheet.ts @@ -0,0 +1,39 @@ +const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet; + +function asArray(value: unknown): string[] { + return Array.isArray(value) ? value.map((entry) => String(entry)) : []; +} + +export class RingworldPursuitSheet extends BaseItemSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "pursuit"], + width: 680, + height: 620 + }); + } + + get template(): string { + return "systems/ringworld/templates/item/pursuit-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + const system = (this.item as any).system ?? {}; + const fixedSkills = asArray(system.fixedSkills); + const optionalGroups = Array.isArray(system.optionalSkillGroups) + ? system.optionalSkillGroups.map((group: any) => ({ + choose: Number(group?.choose ?? 1), + skills: asArray(group?.skills) + })) + : []; + + return { + ...data, + system, + fixedSkills, + optionalGroups + }; + } +} + diff --git a/src/module/sheets/items/SkillSheet.ts b/src/module/sheets/items/SkillSheet.ts new file mode 100644 index 0000000..8e802d9 --- /dev/null +++ b/src/module/sheets/items/SkillSheet.ts @@ -0,0 +1,31 @@ +const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet; + +export class RingworldSkillSheet extends BaseItemSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "skill"], + width: 520, + height: 420 + }); + } + + get template(): string { + return "systems/ringworld/templates/item/skill-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + return { + ...data, + system: (this.item as any).system + }; + } + + activateListeners(html: JQuery): void { + super.activateListeners(html); + html.find(".roll-skill").on("click", async (event) => { + event.preventDefault(); + await (this.item as any).rollSkill(); + }); + } +} diff --git a/src/module/sheets/items/SpeciesSheet.ts b/src/module/sheets/items/SpeciesSheet.ts new file mode 100644 index 0000000..48d69dc --- /dev/null +++ b/src/module/sheets/items/SpeciesSheet.ts @@ -0,0 +1,23 @@ +const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet; + +export class RingworldSpeciesSheet extends BaseItemSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "species"], + width: 620, + height: 560 + }); + } + + get template(): string { + return "systems/ringworld/templates/item/species-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + return { + ...data, + system: (this.item as any).system + }; + } +} diff --git a/src/module/sheets/items/WeaponSheet.ts b/src/module/sheets/items/WeaponSheet.ts new file mode 100644 index 0000000..e41634b --- /dev/null +++ b/src/module/sheets/items/WeaponSheet.ts @@ -0,0 +1,35 @@ +const BaseItemSheet = (foundry as any).appv1.sheets.ItemSheet; + +export class RingworldWeaponSheet extends BaseItemSheet { + static get defaultOptions(): any { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ringworld", "sheet", "item", "weapon"], + width: 620, + height: 620 + }); + } + + get template(): string { + return "systems/ringworld/templates/item/weapon-sheet.hbs"; + } + + async getData(options?: any): Promise { + const data = await super.getData(options); + const actor = this.item.actor as any; + + return { + ...data, + system: (this.item as any).system, + skills: actor ? actor.items.filter((item: any) => item.type === "skill") : [], + powerPacks: actor ? actor.items.filter((item: any) => item.type === "gear" && item.system.isPowerPack) : [] + }; + } + + activateListeners(html: JQuery): void { + super.activateListeners(html); + html.find(".use-weapon").on("click", async (event) => { + event.preventDefault(); + await (this.item as any).useWeapon(); + }); + } +} diff --git a/src/module/skill-icons.ts b/src/module/skill-icons.ts new file mode 100644 index 0000000..9c347bf --- /dev/null +++ b/src/module/skill-icons.ts @@ -0,0 +1,27 @@ +import { normalizeSkillName, RINGWORLD_BASE_SKILLS } from "./data-base-skills"; + +export const DEFAULT_SKILL_ICON = "systems/ringworld/assets/icons/skill.svg"; + +function iconSlug(name: string): string { + return String(name) + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +export function skillIconPath(name: string): string { + return `systems/ringworld/assets/icons/skill/${iconSlug(name)}.svg`; +} + +export function baseSkillByName(name: string): (typeof RINGWORLD_BASE_SKILLS)[number] | undefined { + const key = normalizeSkillName(name); + return RINGWORLD_BASE_SKILLS.find((skill) => normalizeSkillName(skill.name) === key); +} + +export function resolveSkillIcon(name: string, currentImg?: string): string { + const img = String(currentImg || "").trim(); + if (img && img !== "icons/svg/item-bag.svg") return img; + if (baseSkillByName(name)) return skillIconPath(name); + return DEFAULT_SKILL_ICON; +} + diff --git a/src/module/utils/dice.ts b/src/module/utils/dice.ts new file mode 100644 index 0000000..4a7f8e5 --- /dev/null +++ b/src/module/utils/dice.ts @@ -0,0 +1,16 @@ +export async function showDiceSoNice(roll: Roll): Promise { + const dice3d = (game as any)?.dice3d; + if (!dice3d?.showForRoll) return; + + try { + await dice3d.showForRoll(roll, game.user, true, null, false); + } catch (_error) { + // Ignore Dice So Nice integration failures and continue normal flow. + } +} + +export async function evaluateRollWithDice(formula: string): Promise { + const roll = await new Roll(formula).evaluate(); + await showDiceSoNice(roll as any); + return roll as any; +} diff --git a/src/module/utils/hit-locations.ts b/src/module/utils/hit-locations.ts new file mode 100644 index 0000000..c749194 --- /dev/null +++ b/src/module/utils/hit-locations.ts @@ -0,0 +1,130 @@ +import { HIT_LOCATION_TABLE, type HitLocationEntry, type HitLocationSlug } from "../constants"; +import { evaluateRollWithDice } from "./dice"; +import { getActorHitLocationSchema, getHitLocationSchemaById } from "../hit-location-schemas"; + +export interface HitLocationRollResult { + roll: number; + location: HitLocationEntry; +} + +export function getHitLocationFromRoll(roll: number): HitLocationEntry { + const found = HIT_LOCATION_TABLE.find((entry) => roll >= entry.min && roll <= entry.max); + return found ?? HIT_LOCATION_TABLE[HIT_LOCATION_TABLE.length - 1]; +} + +function getTableForActor(actor?: any): HitLocationEntry[] { + if (!actor) return HIT_LOCATION_TABLE; + const schema = getActorHitLocationSchema(actor); + return schema.entries; +} + +function getHitLocationFromRollForTable(roll: number, table: HitLocationEntry[]): HitLocationEntry { + const found = table.find((entry) => roll >= entry.min && roll <= entry.max); + return found ?? table[table.length - 1]; +} + +export async function rollHitLocation(actor?: any): Promise { + const roll = await evaluateRollWithDice("1d20"); + const total = roll.total ?? 20; + const table = getTableForActor(actor); + return { + roll: total, + location: getHitLocationFromRollForTable(total, table) + }; +} + +export async function chooseOrRollHitLocation(actor?: any): Promise<{ + slug: HitLocationSlug; + label: string; + rolled: number | null; +} | null> { + const schema = actor ? getActorHitLocationSchema(actor) : getHitLocationSchemaById("humanoid"); + const table = schema.entries; + const entriesBySlug = new Map(table.map((entry) => [entry.slug, entry])); + const labelFor = (slug: HitLocationSlug, fallback: string) => entriesBySlug.get(slug)?.label ?? fallback; + + const html = ` +
+ +
+ +
+
+ + + + + + + +
+
+ `; + + const result = await new Promise((resolve) => { + let settled = false; + const done = (value: string | null) => { + if (settled) return; + settled = true; + resolve(value); + }; + + const dialog = new Dialog({ + title: "Choose Hit Location", + content: html, + buttons: { + apply: { + label: "Apply", + callback: (dlgHtml: JQuery) => { + const value = String(dlgHtml.find("input[name='location']").val() || "random"); + done(value); + } + }, + cancel: { + label: "Cancel", + callback: () => done(null) + } + }, + default: "apply", + render: (dlgHtml: JQuery) => { + const picker = dlgHtml.find(".ringworld-hit-picker"); + picker.find(".rw-hit-picker-choice").on("click", (event) => { + event.preventDefault(); + const button = event.currentTarget as HTMLElement; + const location = String(button.dataset.location || "random"); + picker.find(".rw-hit-picker-choice").removeClass("active"); + $(button).addClass("active"); + picker.find("input[name='location']").val(location); + }); + }, + close: () => done(null) + }, { + classes: ["ringworld-hit-location-dialog"], + width: 500, + resizable: false + }); + dialog.render(true); + }); + + if (!result) return null; + + if (result === "random") { + const rolled = await rollHitLocation(actor); + return { + slug: rolled.location.slug, + label: rolled.location.label, + rolled: rolled.roll + }; + } + + const entry = table.find((loc) => loc.slug === result); + if (!entry) return null; + + return { + slug: entry.slug, + label: entry.label, + rolled: null + }; +} diff --git a/src/module/utils/percentile.ts b/src/module/utils/percentile.ts new file mode 100644 index 0000000..b5062ab --- /dev/null +++ b/src/module/utils/percentile.ts @@ -0,0 +1,54 @@ +import { evaluateRollWithDice } from "./dice"; + +export type PercentileOutcome = "special" | "success" | "failure" | "fumble"; + +export interface PercentileResult { + roll: number; + target: number; + outcome: PercentileOutcome; + isSuccess: boolean; + isSpecial: boolean; + isFumble: boolean; +} + +export async function rollPercentile(target: number): Promise { + const roll = await evaluateRollWithDice("1d100"); + return classifyPercentile(roll.total ?? 0, target); +} + +export function classifyPercentile(roll: number, target: number): PercentileResult { + const cleanTarget = Math.max(0, Math.floor(Number(target) || 0)); + const specialThreshold = Math.max(1, Math.floor(cleanTarget / 5)); + const fumbleThreshold = cleanTarget >= 50 ? 100 : 96; + + const isSuccess = roll <= cleanTarget; + const isSpecial = isSuccess && roll <= specialThreshold; + const isFumble = !isSuccess && roll >= fumbleThreshold; + + let outcome: PercentileOutcome = "failure"; + if (isFumble) outcome = "fumble"; + else if (isSpecial) outcome = "special"; + else if (isSuccess) outcome = "success"; + + return { + roll, + target: cleanTarget, + outcome, + isSuccess, + isSpecial, + isFumble + }; +} + +export function outcomeLabel(outcome: PercentileOutcome): string { + switch (outcome) { + case "special": + return "Special Success"; + case "success": + return "Success"; + case "fumble": + return "Fumble"; + default: + return "Failure"; + } +} diff --git a/src/module/world-catalog.ts b/src/module/world-catalog.ts new file mode 100644 index 0000000..bbe2310 --- /dev/null +++ b/src/module/world-catalog.ts @@ -0,0 +1,978 @@ +import { SYSTEM_ID } from "./constants"; +import { normalizeSkillName, RINGWORLD_BASE_SKILLS } from "./data-base-skills"; +import { skillIconPath } from "./skill-icons"; + +export type GravityType = "light" | "normal" | "heavy"; + +interface SpeciesCatalogEntry { + name: string; + playable: boolean; + gravity: GravityType; + homeworld: string; + modifiers: { + str: number; + mas: number; + con: number; + int: number; + pow: number; + dex: number; + app: number; + edu: number; + age: number; + }; + notes: string; +} + +interface WeaponCatalogEntry { + name: string; + equipped?: boolean; + weight?: number; + linkedSkill: string; + attackType: "melee" | "ranged"; + damageFormula: string; + range: { close: string; medium: string; long: string }; + energyUsedPerAttack: number; + ammoUsedPerAttack: number; + powerSource: "actor" | "powerPack"; + notes: string; +} + +interface ArmorCatalogEntry { + name: string; + equipped: boolean; + weight?: number; + value: number; + coverage: Record; + notes: string; +} + +interface GearCatalogEntry { + name: string; + equipped?: boolean; + weight: number; + cost: number; + energyUse: number; + powerSupplyNotes: string; + isPowerPack: boolean; + energy: { value: number; max: number }; + notes: string; +} + +interface PursuitCatalogEntry { + name: string; + rollRange: string; + wealthValue: number; + occupationPointsPerYear: number; + fixedSkills: string[]; + optionalSkillGroups?: Array<{ + choose: number; + skills: string[]; + }>; + notes: string; +} + +interface CatalogSeed { + key: string; + data: any; + folderKey: "species" | "skills" | "pursuits" | "gear" | "armor" | "weaponMelee" | "weaponRanged"; + img: string; +} + +export const RINGWORLD_CATALOG_VERSION = 9; + +const ICONS = { + speciesDefault: "systems/ringworld/assets/icons/species.svg", + skillDefault: "systems/ringworld/assets/icons/skill.svg", + pursuitDefault: "systems/ringworld/assets/icons/pursuit.svg", + weaponMelee: "systems/ringworld/assets/icons/weapon-melee.svg", + weaponRanged: "systems/ringworld/assets/icons/weapon-ranged.svg", + armor: "systems/ringworld/assets/icons/armor.svg", + gearDefault: "systems/ringworld/assets/icons/gear.svg", + powerPackDefault: "systems/ringworld/assets/icons/power-pack.svg" +}; + +function iconSlug(name: string): string { + return String(name) + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +function speciesIconPath(name: string): string { + return `systems/ringworld/assets/icons/species/${iconSlug(name)}.svg`; +} + +function weaponIconPath(name: string): string { + return `systems/ringworld/assets/icons/weapon/${iconSlug(name)}.svg`; +} + +function armorIconPath(name: string): string { + return `systems/ringworld/assets/icons/armor/${iconSlug(name)}.svg`; +} + +function gearIconPath(name: string): string { + return `systems/ringworld/assets/icons/gear/${iconSlug(name)}.svg`; +} + +function pursuitIconPath(name: string): string { + return `systems/ringworld/assets/icons/pursuit/${iconSlug(name)}.svg`; +} + +const allCoverage = { + rLeg: true, + lLeg: true, + abdomen: true, + chest: true, + rArm: true, + lArm: true, + head: true +}; + +const torsoCoverage = { + rLeg: false, + lLeg: false, + abdomen: true, + chest: true, + rArm: false, + lArm: false, + head: false +}; + +const headCoverage = { + rLeg: false, + lLeg: false, + abdomen: false, + chest: false, + rArm: false, + lArm: false, + head: true +}; + +const armCoverage = { + rLeg: false, + lLeg: false, + abdomen: false, + chest: false, + rArm: true, + lArm: true, + head: false +}; + +const legCoverage = { + rLeg: true, + lLeg: true, + abdomen: false, + chest: false, + rArm: false, + lArm: false, + head: false +}; + +export const SPECIES_CATALOG: SpeciesCatalogEntry[] = [ + { + name: "Human", + playable: true, + gravity: "normal", + homeworld: "Known Space", + modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 }, + notes: "Baseline human explorer profile." + }, + { + name: "Earth Human", + playable: true, + gravity: "normal", + homeworld: "Earth", + modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 1, dex: 0, app: 0, edu: 0, age: 0 }, + notes: "Earth-line humans with stronger latent psionic potential." + }, + { + name: "Kzinti", + playable: true, + gravity: "heavy", + homeworld: "Kzin", + modifiers: { str: 2, mas: 2, con: 1, int: -1, pow: 0, dex: 0, app: -2, edu: 0, age: 0 }, + notes: "Predatory felinoids with high physical strength." + }, + { + name: "Pierson's Puppeteer", + playable: true, + gravity: "light", + homeworld: "Fleet of Worlds", + modifiers: { str: -2, mas: -2, con: -1, int: 2, pow: 1, dex: 1, app: 0, edu: 1, age: 0 }, + notes: "Highly intelligent, cautious engineers." + }, + { + name: "Dolphin", + playable: true, + gravity: "normal", + homeworld: "Human Space Oceans", + modifiers: { str: -2, mas: 1, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 }, + notes: "Aquatic specialists with enhanced endurance." + }, + { + name: "Trinoc", + playable: true, + gravity: "light", + homeworld: "Trinoc Worlds", + modifiers: { str: -1, mas: -1, con: 0, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 }, + notes: "Three-eyed flyers with excellent perception." + }, + { + name: "Martian", + playable: true, + gravity: "light", + homeworld: "Mars", + modifiers: { str: -1, mas: -1, con: -1, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 }, + notes: "Light-gravity adapted intellect-focused profile." + }, + { + name: "Pierin", + playable: true, + gravity: "normal", + homeworld: "Pierin Space", + modifiers: { str: 0, mas: 0, con: 0, int: 1, pow: 0, dex: 0, app: 1, edu: 0, age: 0 }, + notes: "Socially adept, technically curious explorers." + }, + { + name: "Boat People", + playable: true, + gravity: "normal", + homeworld: "Ringworld", + modifiers: { str: 0, mas: 0, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: -1, age: 0 }, + notes: "Ringworld hominids adapted to river and sea cultures." + }, + { + name: "Builder", + playable: true, + gravity: "normal", + homeworld: "Ringworld", + modifiers: { str: 1, mas: 1, con: 0, int: 0, pow: 0, dex: 0, app: -1, edu: 0, age: 0 }, + notes: "Ringworld labor stock with robust build." + }, + { + name: "Ghoul", + playable: true, + gravity: "normal", + homeworld: "Ringworld", + modifiers: { str: 0, mas: 0, con: 1, int: -1, pow: 0, dex: 1, app: -2, edu: -1, age: 0 }, + notes: "Scavenger subspecies with strong survival instincts." + }, + { + name: "Bandersnatchi", + playable: false, + gravity: "heavy", + homeworld: "Known Space", + modifiers: { str: 4, mas: 4, con: 3, int: 0, pow: 0, dex: -2, app: -3, edu: 0, age: 10 }, + notes: "Massive engineered beings; typically GM creature profile." + } +]; + +export const WEAPON_CATALOG: WeaponCatalogEntry[] = [ + { + name: "Variable Sword", + equipped: true, + weight: 1.5, + linkedSkill: "V. Sword, F. Laser", + attackType: "melee", + damageFormula: "1d10+1", + range: { close: "Melee", medium: "-", long: "-" }, + energyUsedPerAttack: 1, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "High-energy blade weapon." + }, + { + name: "Monofilament Knife", + equipped: true, + weight: 0.2, + linkedSkill: "Archaic Melee Weapon", + attackType: "melee", + damageFormula: "1d6+1", + range: { close: "Melee", medium: "-", long: "-" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 0, + powerSource: "actor", + notes: "Fine-edge close combat blade." + }, + { + name: "Shock Baton", + equipped: true, + weight: 1.2, + linkedSkill: "Unarmed Combat", + attackType: "melee", + damageFormula: "1d8+1", + range: { close: "Melee", medium: "-", long: "-" }, + energyUsedPerAttack: 1, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Close-quarters stun baton." + }, + { + name: "Flashlight Laser", + equipped: true, + weight: 0.8, + linkedSkill: "V. Sword, F. Laser", + attackType: "ranged", + damageFormula: "1d10", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 2, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Compact laser unit." + }, + { + name: "Laser Pistol", + equipped: true, + weight: 1.4, + linkedSkill: "Handgun, Energy", + attackType: "ranged", + damageFormula: "1d10+1", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 2, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Common energy sidearm." + }, + { + name: "Laser Rifle", + equipped: true, + weight: 4.5, + linkedSkill: "Heavy Weapon, Energy", + attackType: "ranged", + damageFormula: "2d6+2", + range: { close: "Short", medium: "Long", long: "Extreme" }, + energyUsedPerAttack: 3, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Standard long-arm laser weapon." + }, + { + name: "Stunner", + equipped: true, + weight: 1.0, + linkedSkill: "Handgun, Energy", + attackType: "ranged", + damageFormula: "1d6", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 1, + ammoUsedPerAttack: 0, + powerSource: "powerPack", + notes: "Low-lethality disruption sidearm." + }, + { + name: "Handgun (Projectile)", + equipped: true, + weight: 1.2, + linkedSkill: "Handgun, Projectile", + attackType: "ranged", + damageFormula: "1d10", + range: { close: "Short", medium: "Medium", long: "Long" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 1, + powerSource: "actor", + notes: "Conventional projectile sidearm." + }, + { + name: "Rifle (Projectile)", + equipped: true, + weight: 5.0, + linkedSkill: "Heavy Weapon, Projectile", + attackType: "ranged", + damageFormula: "2d6", + range: { close: "Short", medium: "Long", long: "Extreme" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 1, + powerSource: "actor", + notes: "Conventional projectile rifle." + }, + { + name: "Slug Thrower Carbine", + equipped: true, + weight: 3.8, + linkedSkill: "Heavy Weapon, Projectile", + attackType: "ranged", + damageFormula: "2d6+1", + range: { close: "Short", medium: "Long", long: "Extreme" }, + energyUsedPerAttack: 0, + ammoUsedPerAttack: 1, + powerSource: "actor", + notes: "Compact projectile carbine." + } +]; + +export const ARMOR_CATALOG: ArmorCatalogEntry[] = [ + { name: "Cloth Vest", equipped: true, weight: 2, value: 1, coverage: torsoCoverage, notes: "Basic torso protection." }, + { name: "Soft Combat Vest", equipped: true, weight: 4, value: 2, coverage: torsoCoverage, notes: "Flexible tactical vest." }, + { name: "Combat Harness", equipped: true, weight: 5, value: 2, coverage: { ...torsoCoverage, rArm: true, lArm: true }, notes: "Torso + upper arms." }, + { name: "Armored Coat", equipped: true, weight: 7, value: 3, coverage: { ...torsoCoverage, rArm: true, lArm: true }, notes: "Heavy coat with arm plating." }, + { name: "Leg Guards", equipped: true, weight: 4, value: 2, coverage: legCoverage, notes: "Lower-body armor." }, + { name: "Arm Bracers", equipped: true, weight: 2, value: 2, coverage: armCoverage, notes: "Forearm and upper arm guards." }, + { name: "Combat Helmet", equipped: true, weight: 1.5, value: 2, coverage: headCoverage, notes: "Cranial protection." }, + { name: "Full Combat Suit", equipped: true, weight: 12, value: 3, coverage: allCoverage, notes: "Full-body armored suit." }, + { name: "Impact Suit", equipped: true, weight: 16, value: 4, coverage: allCoverage, notes: "High-end impact and hazard suit." } +]; + +export const GEAR_CATALOG: GearCatalogEntry[] = [ + { + name: "Power Pack R20", + weight: 1, + cost: 100, + energyUse: 0, + powerSupplyNotes: "Rechargeable power pack", + isPowerPack: true, + energy: { value: 20, max: 20 }, + notes: "Standard personal battery pack." + }, + { + name: "Power Pack R50", + weight: 2, + cost: 220, + energyUse: 0, + powerSupplyNotes: "Rechargeable power pack", + isPowerPack: true, + energy: { value: 50, max: 50 }, + notes: "Extended personal battery pack." + }, + { + name: "Power Pack R100", + weight: 4, + cost: 400, + energyUse: 0, + powerSupplyNotes: "Rechargeable power pack", + isPowerPack: true, + energy: { value: 100, max: 100 }, + notes: "High-capacity field battery." + }, + { name: "Portable Generator", weight: 8, cost: 700, energyUse: 0, powerSupplyNotes: "Provides charging capacity", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Portable recharge generator." }, + { name: "MicroMind Unit", weight: 0.5, cost: 900, energyUse: 1, powerSupplyNotes: "Requires external power", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Portable advanced computation module." }, + { name: "Memory Bubble Reader", weight: 0.5, cost: 450, energyUse: 1, powerSupplyNotes: "Standard battery compatible", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Reads and writes memory bubbles." }, + { name: "Autodoc (Portable)", weight: 18, cost: 3500, energyUse: 6, powerSupplyNotes: "High draw medical station", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Field medical support unit." }, + { name: "Medkit", weight: 1, cost: 120, energyUse: 0, powerSupplyNotes: "None", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Manual emergency treatment supplies." }, + { name: "Bioscanner", weight: 1, cost: 300, energyUse: 1, powerSupplyNotes: "Battery or generator", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Vitals and hazard scanning tool." }, + { name: "Survey Scanner", weight: 2, cost: 500, energyUse: 2, powerSupplyNotes: "Battery or generator", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Terrain and structure analysis scanner." }, + { name: "Translator Module", weight: 0.5, cost: 650, energyUse: 1, powerSupplyNotes: "Battery compatible", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Linguistic conversion and support." }, + { name: "Holo Projector", weight: 1.5, cost: 800, energyUse: 2, powerSupplyNotes: "Battery compatible", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Short-range holographic projection." }, + { name: "Stasis Box", weight: 12, cost: 2600, energyUse: 5, powerSupplyNotes: "Continuous power required", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Suspended animation container." }, + { name: "Reactionless Flycycle", weight: 35, cost: 4200, energyUse: 4, powerSupplyNotes: "Vehicle power core", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Single-rider reactionless vehicle." }, + { name: "Field Toolkit", weight: 2, cost: 180, energyUse: 0, powerSupplyNotes: "None", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Mechanical and electronics repair kit." }, + { name: "Vac Suit", weight: 7, cost: 1400, energyUse: 1, powerSupplyNotes: "Suit battery required", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Pressure and vacuum survival suit." }, + { name: "Air Reprocessor", weight: 3, cost: 950, energyUse: 2, powerSupplyNotes: "Continuous power", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Atmosphere recycling support unit." }, + { name: "Signal Beacon", weight: 1, cost: 140, energyUse: 1, powerSupplyNotes: "Battery compatible", isPowerPack: false, energy: { value: 0, max: 0 }, notes: "Emergency and navigation beacon." } +]; + +export const PURSUIT_CATALOG: PursuitCatalogEntry[] = [ + { + name: "Artist", + rollRange: "01-07", + wealthValue: 0, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Computers", "Fast Talk", "Fine Arts", "Hide", "Musicianship", "Observe", "Perform", "Psychology", "Scent", "Theology"], + notes: "Creative specialist pursuit." + }, + { + name: "Colonist", + rollRange: "08-27", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Atmospheric Craft", "Bargain", "Ground Vehicle", "Farming", "Handgun, Energy", "Personal Flyer", "Listen", "Heavy Weapon, Energy", "Observe", "Planetology", "Search", "Track"], + notes: "Settlement and frontier operations pursuit." + }, + { + name: "Criminal", + rollRange: "28-29", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Computers", "Fast Talk", "Hide", "Personal Flyer", "Handgun, Energy", "Handgun, Projectile", "Sneak", "Unarmed Combat"], + notes: "Underground survival pursuit." + }, + { + name: "Dilettante", + rollRange: "30", + wealthValue: 5, + occupationPointsPerYear: 15, + fixedSkills: [], + notes: "Any skills allowed; uses 15 occupation points per year." + }, + { + name: "Engineer", + rollRange: "31-36", + wealthValue: 2, + occupationPointsPerYear: 20, + fixedSkills: ["Astronomy", "Chemistry", "Computers", "Engineering", "Ground Vehicle", "Mathematics", "Personal Flyer", "Physics", "Planetology", "Reaction Drive", "Reactionless Drive", "Repair"], + notes: "Technical design and maintenance pursuit." + }, + { + name: "Entertainer", + rollRange: "37-38", + wealthValue: 2, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Bargain", "Own Language", "Musicianship", "Observe", "Orate", "Perform", "Psychology", "Search", "Sneak"], + notes: "Performance and social influence pursuit." + }, + { + name: "Explorer", + rollRange: "39-52", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Atmospheric Craft", "Bargain", "Biology", "Botany", "Emergency Treatment", "Engineering", "Ground Vehicle", "Handgun, Energy", "Heavy Weapon, Energy", "Hyperdrive", "Observe", "Planetology", "Reactionless Drive", "Repair", "Search", "Track", "Zoology"], + notes: "Survey and expedition pursuit." + }, + { + name: "Functionary", + rollRange: "53-56", + wealthValue: 3, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Debate", "Fast Talk", "History", "Law", "Observe", "Orate", "Psychology", "Strategy", "Theology"], + notes: "Administrative and governance pursuit." + }, + { + name: "Journalist/Writer", + rollRange: "57-59", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Anthropology", "Bargain", "Computers", "Debate", "History", "Fine Arts", "Own Language", "Law", "Observe", "Psychology", "Search", "Sneak", "Unarmed Combat", "Weapons System"], + notes: "Investigation and reporting pursuit." + }, + { + name: "Miner", + rollRange: "60-65", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Atmospheric Craft", "Aquatic Vehicle", "Bargain", "Climb (Custom)", "Computers", "Emergency Treatment", "Engineering", "Handgun, Energy", "Heavy Weapon, Energy", "Observe", "Personal Flyer", "Planetology", "Reaction Drive", "Reactionless Drive", "Repair", "Search"], + notes: "Resource extraction frontier pursuit." + }, + { + name: "Pilot", + rollRange: "66-68", + wealthValue: 3, + occupationPointsPerYear: 20, + fixedSkills: ["Astronomy", "Atmospheric Craft", "Computers", "Engineering", "Hyperdrive", "History", "Law", "Mathematics", "Physics", "Reaction Drive", "Reactionless Drive", "Repair", "Strategy", "Weapons System"], + notes: "Flight and spacecraft operations pursuit." + }, + { + name: "Police Officer", + rollRange: "69-76", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Atmospheric Craft", "Bargain", "Computers", "Fast Talk", "Emergency Treatment", "Handgun, Energy", "Handgun, Projectile", "Heavy Weapon, Energy", "Heavy Weapon, Projectile", "Law", "Observe", "Personal Flyer", "Psychology", "Search", "Unarmed Combat"], + notes: "Law enforcement and response pursuit." + }, + { + name: "Politician", + rollRange: "77-78", + wealthValue: 3, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Computers", "Debate", "Fast Talk", "History", "Orate", "Own Language", "Perform", "Psychology"], + optionalSkillGroups: [ + { + choose: 1, + skills: ["Engineering", "Farming", "Second Languages", "Strategy", "Theology"] + } + ], + notes: "Public office and influence pursuit." + }, + { + name: "Prisoner", + rollRange: "79-80", + wealthValue: 0, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Fast Talk", "Hide", "Observe", "Psychology", "Search", "Sneak", "Unarmed Combat"], + notes: "Carceral survival pursuit." + }, + { + name: "Scholar", + rollRange: "81-84", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: [], + optionalSkillGroups: [ + { + choose: 2, + skills: ["Anthropology", "Astronomy", "Biology", "Botany", "Chemistry", "Computers", "Engineering", "Fine Arts", "History", "Law", "Mathematics", "Own Language", "Physics", "Planetology", "Second Languages", "Theology", "Zoology"] + } + ], + notes: "Academic pursuit; pick up to two listed skills." + }, + { + name: "Scientific Researcher", + rollRange: "85-94", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: [], + optionalSkillGroups: [ + { + choose: 3, + skills: ["Anthropology", "Astronomy", "Bargain", "Biology", "Botany", "Chemistry", "Computers", "Engineering", "History", "Mathematics", "Observe", "Personal Flyer", "Physics", "Planetology", "Psychology", "Repair", "Search", "Zoology"] + } + ], + notes: "Research pursuit; pick up to three listed skills." + }, + { + name: "Soldier", + rollRange: "95-98", + wealthValue: 1, + occupationPointsPerYear: 20, + fixedSkills: ["Athletics", "Atmospheric Craft", "Aquatic Vehicle", "Computers", "Emergency Treatment", "Fast Talk", "Ground Vehicle", "Handgun, Energy", "Handgun, Projectile", "Heavy Weapon, Energy", "Heavy Weapon, Projectile"], + notes: "Military operations pursuit." + }, + { + name: "Zealot", + rollRange: "99-00", + wealthValue: 0, + occupationPointsPerYear: 20, + fixedSkills: ["Bargain", "Debate", "Fast Talk", "Fine Arts", "Handgun, Energy", "Handgun, Projectile", "Hide", "Own Language", "Law", "Strategy", "Orate", "Psychology", "Theology"], + notes: "Ideological dedication pursuit." + } +]; + +export function speciesCatalogForGeneration(): SpeciesCatalogEntry[] { + return SPECIES_CATALOG.filter((entry) => entry.playable); +} + +function itemKey(type: string, name: string): string { + return `${type}:${normalizeSkillName(name)}`; +} + +function itemTypeAvailable(type: string): boolean { + const directTypes = (getDocumentClass("Item") as any)?.TYPES; + if (Array.isArray(directTypes)) return directTypes.includes(type); + + const docTypes = (game as any)?.system?.documentTypes?.Item; + if (Array.isArray(docTypes)) return docTypes.includes(type); + if (docTypes && typeof docTypes === "object") return Object.prototype.hasOwnProperty.call(docTypes, type); + + const configModels = (CONFIG as any)?.Item?.dataModels; + if (configModels && typeof configModels === "object" && Object.prototype.hasOwnProperty.call(configModels, type)) { + return true; + } + + const gameModels = (game as any)?.model?.Item; + if (gameModels && typeof gameModels === "object" && Object.prototype.hasOwnProperty.call(gameModels, type)) { + return true; + } + + return false; +} + +function toSkillItem(entry: (typeof RINGWORLD_BASE_SKILLS)[number]): CatalogSeed { + return { + key: itemKey("skill", entry.name), + folderKey: "skills", + img: skillIconPath(entry.name) || ICONS.skillDefault, + data: { + name: entry.name, + type: "skill", + system: { + value: entry.base, + category: entry.category, + specialization: entry.specialization ?? "" + } + } + }; +} + +function toSpeciesItem(entry: SpeciesCatalogEntry): CatalogSeed { + const img = speciesIconPath(entry.name) || ICONS.speciesDefault; + return { + key: itemKey("species", entry.name), + folderKey: "species", + img, + data: { + name: entry.name, + type: "species", + system: { + playable: entry.playable, + gravity: entry.gravity, + homeworld: entry.homeworld, + modifiers: entry.modifiers, + notes: entry.notes + } + } + }; +} + +function toWeaponItem(entry: WeaponCatalogEntry): CatalogSeed { + const folderKey = entry.attackType === "melee" ? "weaponMelee" : "weaponRanged"; + const img = weaponIconPath(entry.name) || (entry.attackType === "melee" ? ICONS.weaponMelee : ICONS.weaponRanged); + return { + key: itemKey("weapon", entry.name), + folderKey, + img, + data: { + name: entry.name, + type: "weapon", + system: { + equipped: entry.equipped ?? true, + stowed: false, + weight: entry.weight ?? 0, + linkedSkill: entry.linkedSkill, + linkedSkillId: "", + attackType: entry.attackType, + damageFormula: entry.damageFormula, + range: entry.range, + energyUsedPerAttack: entry.energyUsedPerAttack, + ammoUsedPerAttack: entry.ammoUsedPerAttack, + powerSource: entry.powerSource, + powerPackId: "", + notes: entry.notes + } + } + }; +} + +function toArmorItem(entry: ArmorCatalogEntry): CatalogSeed { + const img = armorIconPath(entry.name) || ICONS.armor; + return { + key: itemKey("armor", entry.name), + folderKey: "armor", + img, + data: { + name: entry.name, + type: "armor", + system: { + equipped: entry.equipped, + stowed: false, + weight: entry.weight ?? 0, + value: entry.value, + coverage: entry.coverage, + perLocation: { + rLeg: 0, + lLeg: 0, + abdomen: 0, + chest: 0, + rArm: 0, + lArm: 0, + head: 0 + }, + notes: entry.notes + } + } + }; +} + +function toGearItem(entry: GearCatalogEntry): CatalogSeed { + const img = gearIconPath(entry.name) || (entry.isPowerPack ? ICONS.powerPackDefault : ICONS.gearDefault); + return { + key: itemKey("gear", entry.name), + folderKey: "gear", + img, + data: { + name: entry.name, + type: "gear", + system: { + equipped: entry.equipped ?? true, + stowed: false, + weight: entry.weight, + cost: entry.cost, + energyUse: entry.energyUse, + powerSupplyNotes: entry.powerSupplyNotes, + isPowerPack: entry.isPowerPack, + energy: entry.energy, + notes: entry.notes + } + } + }; +} + +function toPursuitItem(entry: PursuitCatalogEntry): CatalogSeed { + const img = pursuitIconPath(entry.name) || ICONS.pursuitDefault; + return { + key: itemKey("pursuit", entry.name), + folderKey: "pursuits", + img, + data: { + name: entry.name, + type: "pursuit", + system: { + wealthValue: entry.wealthValue, + occupationPointsPerYear: entry.occupationPointsPerYear, + rollRange: entry.rollRange, + fixedSkills: entry.fixedSkills, + optionalSkillGroups: entry.optionalSkillGroups ?? [], + notes: entry.notes + } + } + }; +} + +function pursuitAsGearNotes(entry: PursuitCatalogEntry): string { + const fixed = entry.fixedSkills.length ? `Fixed skills: ${entry.fixedSkills.join(", ")}` : "Fixed skills: (none)"; + const optional = (entry.optionalSkillGroups ?? []).map((group) => `Choose ${group.choose}: ${group.skills.join(", ")}`).join(" | "); + const optionalText = optional ? ` Optional: ${optional}.` : ""; + return `${entry.notes} ${fixed}.${optionalText} Wealth ${entry.wealthValue}. Occupation points/year ${entry.occupationPointsPerYear}.`; +} + +function toPursuitFallbackGearItem(entry: PursuitCatalogEntry): CatalogSeed { + const img = pursuitIconPath(entry.name) || ICONS.pursuitDefault; + const itemName = `Pursuit: ${entry.name}`; + return { + key: itemKey("gear", itemName), + folderKey: "pursuits", + img, + data: { + name: itemName, + type: "gear", + system: { + equipped: false, + stowed: true, + weight: 0, + cost: 0, + energyUse: 0, + powerSupplyNotes: "Pursuit reference", + isPowerPack: false, + energy: { value: 0, max: 0 }, + notes: pursuitAsGearNotes(entry) + }, + flags: { + ringworld: { + pursuitData: { + pursuitName: entry.name, + rollRange: entry.rollRange, + wealthValue: entry.wealthValue, + occupationPointsPerYear: entry.occupationPointsPerYear, + fixedSkills: entry.fixedSkills, + optionalSkillGroups: entry.optionalSkillGroups ?? [] + } + } + } + } + }; +} + +async function getOrCreateItemFolder(name: string, parentId: string | null): Promise { + const existing = game.folders.find( + (folder: any) => folder.type === "Item" && folder.name === name && (folder.folder?.id ?? null) === parentId + ); + if (existing) return existing; + + return Folder.create({ + name, + type: "Item", + folder: parentId, + sorting: "a" + }); +} + +async function ensureCatalogFolders(): Promise> { + const root = await getOrCreateItemFolder("Ringworld Catalog", null); + const species = await getOrCreateItemFolder("Species", root.id); + const skills = await getOrCreateItemFolder("Skills", root.id); + const pursuits = await getOrCreateItemFolder("Pursuits", root.id); + const gear = await getOrCreateItemFolder("Gear", root.id); + const armor = await getOrCreateItemFolder("Armor", gear.id); + const weapons = await getOrCreateItemFolder("Weapons", root.id); + const weaponMelee = await getOrCreateItemFolder("Melee Weapons", weapons.id); + const weaponRanged = await getOrCreateItemFolder("Ranged Weapons", weapons.id); + + return { + species: species.id, + skills: skills.id, + pursuits: pursuits.id, + gear: gear.id, + armor: armor.id, + weaponMelee: weaponMelee.id, + weaponRanged: weaponRanged.id + }; +} + +function buildAllSeeds(): CatalogSeed[] { + const canUsePursuitType = itemTypeAvailable("pursuit"); + return [ + ...SPECIES_CATALOG.map(toSpeciesItem), + ...RINGWORLD_BASE_SKILLS.map(toSkillItem), + ...(canUsePursuitType ? PURSUIT_CATALOG.map(toPursuitItem) : PURSUIT_CATALOG.map(toPursuitFallbackGearItem)), + ...WEAPON_CATALOG.map(toWeaponItem), + ...ARMOR_CATALOG.map(toArmorItem), + ...GEAR_CATALOG.map(toGearItem) + ]; +} + +export async function seedWorldCatalog(options?: { notify?: boolean }): Promise<{ added: number; updated: number }> { + if (!game.user?.isGM) return { added: 0, updated: 0 }; + + const folderIds = await ensureCatalogFolders(); + const seeds = buildAllSeeds(); + + const existing = new Map( + game.items.map((item: any) => [itemKey(item.type, item.name), item]) + ); + + const createPayload: any[] = []; + const updatePayload: any[] = []; + + for (const seed of seeds) { + const folderId = folderIds[seed.folderKey]; + const existingItem = existing.get(seed.key); + + if (!existingItem) { + createPayload.push({ + ...seed.data, + img: seed.img, + folder: folderId, + flags: { + ringworld: { + catalog: true, + catalogKey: seed.key + } + } + }); + continue; + } + + const update: Record = { _id: existingItem.id }; + + if ((existingItem.folder?.id ?? null) !== folderId) { + update.folder = folderId; + } + + if (String(existingItem.img || "") !== seed.img) { + update.img = seed.img; + } + + if (existingItem.flags?.ringworld?.catalog !== true) { + update["flags.ringworld.catalog"] = true; + } + + if (existingItem.flags?.ringworld?.catalogKey !== seed.key) { + update["flags.ringworld.catalogKey"] = seed.key; + } + + const existingSystem = foundry.utils.deepClone(existingItem.system ?? {}); + const mergedSystem = foundry.utils.mergeObject(existingSystem, seed.data.system ?? {}, { + inplace: false, + insertKeys: true, + insertValues: true, + overwrite: false + }); + const systemDiff = foundry.utils.diffObject(existingSystem, mergedSystem); + if (Object.keys(systemDiff).length > 0) { + update.system = mergedSystem; + } + + if (Object.keys(update).length > 1) { + updatePayload.push(update); + } + } + + if (createPayload.length) { + await Item.createDocuments(createPayload as any[]); + } + + if (updatePayload.length) { + await Item.updateDocuments(updatePayload as any[]); + } + + if (options?.notify) { + ui.notifications?.info(`Ringworld catalog synced: +${createPayload.length} new, ${updatePayload.length} updated.`); + } + + return { added: createPayload.length, updated: updatePayload.length }; +} + +export function getPlayableSpeciesFromWorld(): any[] { + return game.items.filter((item: any) => item.type === "species" && item.system.playable); +} + +export function ringworldSystemSettingKey(key: string): string { + return `${SYSTEM_ID}.${key}`; +} diff --git a/src/ringworld.ts b/src/ringworld.ts new file mode 100644 index 0000000..9f4b56e --- /dev/null +++ b/src/ringworld.ts @@ -0,0 +1,344 @@ +import "./styles/ringworld.scss"; + +import { RingworldActor } from "./module/actor/RingworldActor"; +import { RingworldItem } from "./module/item/RingworldItem"; +import { RingworldExplorerSheet } from "./module/sheets/actors/ExplorerSheet"; +import { RingworldCreatureSheet } from "./module/sheets/actors/CreatureSheet"; +import { RingworldVehicleSheet } from "./module/sheets/actors/VehicleSheet"; +import { RingworldSkillSheet } from "./module/sheets/items/SkillSheet"; +import { RingworldWeaponSheet } from "./module/sheets/items/WeaponSheet"; +import { RingworldArmorSheet } from "./module/sheets/items/ArmorSheet"; +import { RingworldGearSheet } from "./module/sheets/items/GearSheet"; +import { RingworldSpeciesSheet } from "./module/sheets/items/SpeciesSheet"; +import { RingworldPursuitSheet } from "./module/sheets/items/PursuitSheet"; +import { HIT_LOCATION_TABLE } from "./module/constants"; +import { createRandomExplorer } from "./module/character-tools"; +import { RINGWORLD_CATALOG_VERSION, seedWorldCatalog } from "./module/world-catalog"; +import { initializeImpulseTracker } from "./module/impulse-tracker"; +import { DEFAULT_SKILL_ICON, baseSkillByName, resolveSkillIcon } from "./module/skill-icons"; + +const ENCUMBRANCE_DATA_MIGRATION_VERSION = 2; + +function normName(value: unknown): string { + return String(value ?? "").trim().toLowerCase(); +} + +function worldItemWeightMap(): Map { + const map = new Map(); + for (const item of game.items ?? []) { + if (item.type !== "weapon" && item.type !== "armor" && item.type !== "gear") continue; + const key = `${item.type}:${normName(item.name)}`; + map.set(key, Number((item as any).system?.weight ?? 0) || 0); + } + return map; +} + +async function migrateActorItemEncumbranceData(): Promise { + if (!game.user?.isGM) return 0; + const weightMap = worldItemWeightMap(); + let touched = 0; + + for (const actor of game.actors ?? []) { + const updates: any[] = []; + + for (const item of actor.items ?? []) { + if (item.type !== "weapon" && item.type !== "armor" && item.type !== "gear") continue; + const patch: Record = { _id: item.id }; + let changed = false; + + if ((item as any).system?.equipped === undefined) { + patch["system.equipped"] = true; + changed = true; + } + + if ((item as any).system?.weight === undefined) { + const key = `${item.type}:${normName(item.name)}`; + patch["system.weight"] = Number(weightMap.get(key) ?? 0); + changed = true; + } + + if ((item as any).system?.stowed === undefined) { + patch["system.stowed"] = false; + changed = true; + } + + if (changed) { + updates.push(patch); + } + } + + if (updates.length) { + await actor.updateEmbeddedDocuments("Item", updates); + touched += updates.length; + } + } + + return touched; +} + +function injectDirectoryButton(htmlLike: JQuery | HTMLElement | null | undefined, className: string, label: string, onClick: () => Promise | void): void { + if (!htmlLike) return; + const html = "find" in (htmlLike as any) ? (htmlLike as JQuery) : $(htmlLike); + if (!html.length || html.find(`.${className}`).length) return; + + const button = $(``); + button.on("click", async () => { + await onClick(); + }); + + const containers = [ + html.find(".directory-footer"), + html.find("footer.directory-footer"), + html.find("footer"), + html.find(".footer-actions"), + html.find(".header-actions"), + html.find(".action-buttons") + ]; + + const container = containers.find((candidate) => candidate.length); + if (container) { + container.first().append(button); + return; + } + + let fallback = html.find(".ringworld-directory-controls"); + if (!fallback.length) { + fallback = $('
'); + html.append(fallback); + } + fallback.first().append(button); +} + +async function handleSeedCatalogClick(): Promise { + await seedWorldCatalog({ notify: true }); +} + +async function handleCreateRandomExplorerClick(): Promise { + const actor = await createRandomExplorer(); + actor?.sheet?.render(true); +} + +function ensureSidebarDirectoryButtons(): void { + if (!game.user?.isGM) return; + + const itemElement = (ui as any).items?.element; + injectDirectoryButton(itemElement, "ringworld-seed-items", "Seed Ringworld Items", handleSeedCatalogClick); + + const actorElement = (ui as any).actors?.element; + injectDirectoryButton(actorElement, "ringworld-random-actor", "Create Random Explorer", handleCreateRandomExplorerClick); +} + +function itemTypeAvailable(type: string): boolean { + const directTypes = (getDocumentClass("Item") as any)?.TYPES; + if (Array.isArray(directTypes)) return directTypes.includes(type); + + const docTypes = (game as any)?.system?.documentTypes?.Item; + if (Array.isArray(docTypes)) return docTypes.includes(type); + if (docTypes && typeof docTypes === "object") return Object.prototype.hasOwnProperty.call(docTypes, type); + + const configModels = (CONFIG as any)?.Item?.dataModels; + if (configModels && typeof configModels === "object" && Object.prototype.hasOwnProperty.call(configModels, type)) { + return true; + } + return false; +} + +Hooks.once("init", async () => { + console.log("ringworld | Initializing Ringworld system"); + initializeImpulseTracker(); + + const ActorRegistry = (foundry as any)?.documents?.collections?.Actors; + const ItemRegistry = (foundry as any)?.documents?.collections?.Items; + const BaseActorSheet = (foundry as any)?.appv1?.sheets?.ActorSheet; + const BaseItemSheet = (foundry as any)?.appv1?.sheets?.ItemSheet; + + (CONFIG as any).ringworld = { + hitLocations: HIT_LOCATION_TABLE + }; + CONFIG.Combat.initiative = foundry.utils.mergeObject(CONFIG.Combat.initiative ?? {}, { + formula: "1d100", + decimals: 0 + }, { inplace: false }); + + CONFIG.Actor.documentClass = RingworldActor as any; + CONFIG.Item.documentClass = RingworldItem as any; + + if (!ActorRegistry || !ItemRegistry || !BaseActorSheet || !BaseItemSheet) { + console.error("ringworld | Foundry sheet registries were not available during init."); + return; + } + + ActorRegistry.unregisterSheet("core", BaseActorSheet); + ActorRegistry.registerSheet("ringworld", RingworldExplorerSheet as any, { + types: ["explorer"], + makeDefault: true + }); + ActorRegistry.registerSheet("ringworld", RingworldCreatureSheet as any, { + types: ["creature"], + makeDefault: true + }); + ActorRegistry.registerSheet("ringworld", RingworldVehicleSheet as any, { + types: ["vehicle"], + makeDefault: true + }); + + ItemRegistry.unregisterSheet("core", BaseItemSheet); + ItemRegistry.registerSheet("ringworld", RingworldSkillSheet as any, { + types: ["skill"], + makeDefault: true + }); + ItemRegistry.registerSheet("ringworld", RingworldWeaponSheet as any, { + types: ["weapon"], + makeDefault: true + }); + ItemRegistry.registerSheet("ringworld", RingworldArmorSheet as any, { + types: ["armor"], + makeDefault: true + }); + ItemRegistry.registerSheet("ringworld", RingworldGearSheet as any, { + types: ["gear"], + makeDefault: true + }); + ItemRegistry.registerSheet("ringworld", RingworldSpeciesSheet as any, { + types: ["species"], + makeDefault: true + }); + if (itemTypeAvailable("pursuit")) { + ItemRegistry.registerSheet("ringworld", RingworldPursuitSheet as any, { + types: ["pursuit"], + makeDefault: true + }); + } + + game.settings.register("ringworld", "catalogSeedVersion", { + scope: "world", + config: false, + type: Number, + default: 0 + }); + game.settings.register("ringworld", "encumbranceDataMigrationVersion", { + scope: "world", + config: false, + type: Number, + default: 0 + }); + + Handlebars.registerHelper("eq", (a: unknown, b: unknown) => a === b); + Handlebars.registerHelper("or", (a: unknown, b: unknown) => Boolean(a || b)); +}); + +Hooks.once("ready", async () => { + const path = "systems/ringworld/resources/hit-locations/humanoid.json"; + try { + const response = await fetch(path); + if (response.ok) { + const table = await response.json(); + (CONFIG as any).ringworld.hitLocations = table.entries ?? HIT_LOCATION_TABLE; + } + } catch (_error) { + console.warn(`ringworld | Could not load ${path}, using built-in table`); + } + + if (game.user?.isGM) { + for (const combat of game.combats ?? []) { + const formula = String(combat?.settings?.initiative?.formula ?? "").trim(); + if (!formula || formula.toLowerCase() === "undefined") { + await combat.update({ + "settings.initiative.formula": "1d100", + "settings.initiative.decimals": 0 + }); + } + } + + const seededVersion = Number(game.settings.get("ringworld", "catalogSeedVersion") ?? 0); + if (seededVersion < RINGWORLD_CATALOG_VERSION) { + const result = await seedWorldCatalog({ notify: true }); + if (result.added >= 0) { + await game.settings.set("ringworld", "catalogSeedVersion", RINGWORLD_CATALOG_VERSION); + } + } + + const migratedVersion = Number(game.settings.get("ringworld", "encumbranceDataMigrationVersion") ?? 0); + if (migratedVersion < ENCUMBRANCE_DATA_MIGRATION_VERSION) { + const touched = await migrateActorItemEncumbranceData(); + await game.settings.set("ringworld", "encumbranceDataMigrationVersion", ENCUMBRANCE_DATA_MIGRATION_VERSION); + if (touched > 0) { + ui.notifications?.info(`Ringworld encumbrance migration updated ${touched} actor items.`); + } + } + } + + ensureSidebarDirectoryButtons(); + window.setTimeout(() => ensureSidebarDirectoryButtons(), 300); + + (game as any).ringworld = { + ...((game as any).ringworld ?? {}), + seedWorldCatalog: async (options?: { notify?: boolean }) => seedWorldCatalog(options), + createRandomExplorer: async () => createRandomExplorer(), + catalogVersion: RINGWORLD_CATALOG_VERSION, + systemId: "ringworld", + systemVersion: String(game.system?.version ?? "0.0.0") + }; + Hooks.callAll("ringworldReady", (game as any).ringworld); +}); + +Hooks.on("createActor", async (actor: any, _options: any, userId: string) => { + if (!game.user?.isGM) return; + if (userId !== game.user.id) return; + if (!actor || (actor.type !== "explorer" && actor.type !== "creature")) return; + + try { + const added = await actor.ensureBaseSkills?.(); + if (Number(added) > 0) { + console.log(`ringworld | Added ${added} base skills to ${actor.name}`); + } + } catch (error) { + console.error("ringworld | Failed to auto-populate base skills", error); + } +}); + +Hooks.on("preCreateItem", (item: any) => { + if (item.type !== "skill") return; + + const updates: Record = {}; + const name = String(item.name || "Skill"); + const currentImg = String(item.img || ""); + updates.img = resolveSkillIcon(name, currentImg); + + const base = baseSkillByName(name); + const currentSystem = (item as any).system ?? {}; + const currentValue = Number(currentSystem?.value ?? NaN); + const currentCategory = String(currentSystem?.category ?? "").trim(); + + updates.system = { + value: Number.isFinite(currentValue) ? currentValue : (base?.base ?? 0), + category: currentCategory || (base?.category ?? "Technical"), + specialization: String(currentSystem?.specialization ?? base?.specialization ?? "") + }; + + if (!updates.img) updates.img = DEFAULT_SKILL_ICON; + item.updateSource(updates); +}); + +Hooks.on("renderItemDirectory", (_app: any, html: JQuery) => { + if (!game.user?.isGM) return; + injectDirectoryButton(html, "ringworld-seed-items", "Seed Ringworld Items", handleSeedCatalogClick); + ensureSidebarDirectoryButtons(); +}); + +Hooks.on("renderActorDirectory", (_app: any, html: JQuery) => { + if (!game.user?.isGM) return; + injectDirectoryButton(html, "ringworld-random-actor", "Create Random Explorer", handleCreateRandomExplorerClick); + ensureSidebarDirectoryButtons(); +}); + +Hooks.on("renderSidebar", () => { + ensureSidebarDirectoryButtons(); +}); + +Hooks.on("changeSidebarTab", (app: any) => { + const tabName = String(app?.tabName ?? app?.id ?? app?.options?.id ?? ""); + if (tabName === "items" || tabName === "actors") { + ensureSidebarDirectoryButtons(); + } +}); diff --git a/src/styles/ringworld.scss b/src/styles/ringworld.scss new file mode 100644 index 0000000..add13d0 --- /dev/null +++ b/src/styles/ringworld.scss @@ -0,0 +1,1673 @@ +.ringworld-sheet { + color: #1b2a3a; + background: linear-gradient(165deg, #f4f7fb 0%, #e8eff8 55%, #f8fbff 100%); + font-size: 12.5px; + line-height: 1.3; + font-family: "Rajdhani", "Orbitron", "Eurostile", "Bank Gothic", "Segoe UI", sans-serif; + + input, + select, + textarea, + button { + border: 1px solid #91a6bf; + border-radius: 4px; + background: #ffffff; + color: #1b2a3a; + } + + input, + select, + textarea { + padding: 0.18rem 0.3rem; + } + + button { + cursor: pointer; + padding: 0.2rem 0.45rem; + background: linear-gradient(180deg, #f8fbff 0%, #dfe9f6 100%); + border-color: #6f8eb0; + } + + .sheet-header { + display: grid; + gap: 0.3rem; + margin-bottom: 0.3rem; + } + + .panel { + border: 1px solid #8fa8c2; + border-radius: 6px; + background: rgba(255, 255, 255, 0.78); + padding: 0.28rem; + margin-bottom: 0.2rem; + } + + .panel-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.4rem; + margin-bottom: 0.35rem; + } + + .toolbar { + display: flex; + align-items: center; + gap: 0.35rem; + margin-bottom: 0.35rem; + } + + .tabs { + display: flex; + gap: 0.35rem; + margin-bottom: 0.3rem; + + .item { + border: 1px solid #7d97b3; + border-radius: 4px; + padding: 0.2rem 0.5rem; + background: rgba(229, 238, 249, 0.85); + font-weight: 600; + font-size: 0.84rem; + } + } + + .char-grid, + .condition-grid, + .location-list { + display: grid; + gap: 0.3rem; + } + + .char-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .resource-row { + display: flex; + align-items: center; + gap: 0.35rem; + flex-wrap: wrap; + } + + .item-list { + list-style: none; + margin: 0; + padding: 0; + + .item { + display: grid; + grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) auto; + align-items: center; + gap: 0.35rem; + margin-bottom: 0.22rem; + padding: 0.2rem 0.3rem; + border: 1px solid #a0b4ca; + border-radius: 4px; + background: rgba(250, 253, 255, 0.92); + + > div { + min-width: 0; + } + + > div:not(.actions) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + + .actions { + display: flex; + justify-content: flex-end; + gap: 0.2rem; + + button { + font-size: 0.74rem; + padding: 0.15rem 0.35rem; + min-width: 1.4rem; + } + } + + .location-row { + display: grid; + grid-template-columns: 1.3fr 1fr auto 1fr; + align-items: center; + gap: 0.3rem; + } + + .inline-pair { + display: inline-flex; + align-items: center; + gap: 0.2rem; + } + + .skills-badge { + margin-left: auto; + border: 1px solid #7b98b7; + border-radius: 999px; + background: #edf4fe; + padding: 0.1rem 0.45rem; + font-size: 0.78rem; + } + + .rw-header h1 { + margin: 0; + text-transform: uppercase; + letter-spacing: 0.03em; + color: #2d3e54; + font-size: 1.72rem; + font-weight: 700; + } + + .rw-title-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.4rem; + } + + .rw-lock-status { + display: inline-block; + border: 1px solid #7f97b3; + border-radius: 999px; + background: rgba(233, 242, 252, 0.9); + color: #2b4057; + font-size: 0.73rem; + font-weight: 700; + padding: 0.1rem 0.45rem; + } + + .rw-profile { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.16rem; + + label { + display: grid; + gap: 0.06rem; + font-size: 0.75rem; + font-weight: 600; + } + + .rw-encumbrance-field { + grid-column: 1 / -1; + + input { + min-height: 1.55rem; + font-size: 0.88rem; + font-weight: 700; + color: #1f3348; + border-width: 2px; + border-color: #6486ac; + background: linear-gradient(180deg, #f9fcff 0%, #e5effc 100%); + } + } + } + + .rw-upper-grid { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr); + gap: 0.24rem; + align-items: start; + } + + .rw-left-stack, + .rw-right-stack { + display: grid; + gap: 0.2rem; + } + + .rw-quick { + display: grid; + gap: 0.2rem; + max-width: none; + width: 100%; + margin-bottom: 0; + } + + .rw-overview-list { + max-height: 10.9rem; + overflow-y: auto; + overflow-x: hidden; + padding-right: 0.08rem; + + .item { + min-height: 1.7rem; + margin-bottom: 0.16rem; + padding: 0.14rem 0.2rem; + gap: 0.18rem; + font-size: 0.73rem; + font-weight: 600; + + > div:not(.actions) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .item:nth-child(odd) { + background: rgba(228, 239, 251, 0.96); + } + } + + .item-title { + display: inline-flex; + align-items: center; + gap: 0.22rem; + min-width: 0; + + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .item-icon { + width: 1rem; + height: 1rem; + border-radius: 2px; + flex: 0 0 auto; + object-fit: cover; + } + + .rw-overview-weapons .rw-overview-list { + max-height: 11.1rem; + } + + .rw-overview-content { + display: grid; + grid-template-columns: minmax(0, 1.26fr) minmax(0, 0.74fr); + gap: 0.24rem; + align-items: start; + } + + .rw-overview-side { + display: grid; + gap: 0.24rem; + } + + .rw-attribute-board { + display: grid; + gap: 0.08rem; + border: 1px solid #92a7c0; + border-radius: 6px; + background: rgba(255, 255, 255, 0.68); + padding: 0.16rem 0.18rem; + } + + .attr-row { + display: grid; + grid-template-columns: minmax(82px, 0.44fr) minmax(0, 1.56fr); + gap: 0.06rem; + align-items: center; + padding: 0.03rem 0.08rem; + border-bottom: 1px dotted #8da4bf; + + &:last-child { + border-bottom: none; + } + } + + .rw-attribute-board .attr-row:nth-child(odd) { + background: rgba(228, 239, 251, 0.6); + border-radius: 4px; + } + + .lhs, + .rhs { + display: flex; + align-items: center; + gap: 0.1rem; + min-width: 0; + font-size: 0.8rem; + font-weight: 600; + color: #26384d; + } + + .lhs { + display: grid; + grid-template-columns: 2.6rem 2.9rem; + align-items: center; + column-gap: 0.12rem; + } + + .stat-roll-btn { + width: 2.5rem; + min-width: 2.5rem; + height: 1.35rem; + padding: 0; + font-size: 0.95rem; + font-weight: 800; + letter-spacing: 0.01em; + border-color: #6f8eb0; + } + + .lhs input { + width: 2.9rem; + text-align: center; + font-size: 0.8rem; + justify-self: start; + } + + .rhs > span:first-child { + min-width: 3.8rem; + } + + .rhs { + display: grid; + grid-template-columns: minmax(0, 0.95fr) minmax(7.8rem, 13.2rem); + align-items: center; + column-gap: 0.14rem; + } + + .rhs input[type="text"] { + width: 100%; + min-width: 0; + } + + .rhs input[type="number"] { + width: 2.8rem; + text-align: center; + } + + .rhs .inline-pair { + margin-left: 0; + justify-self: end; + } + + .attr-row button.roll-derived { + margin-left: 0.1rem; + } + + .rw-tools { + display: flex; + flex-wrap: wrap; + gap: 0.18rem; + align-items: center; + max-width: 100%; + + label { + display: inline-flex; + align-items: center; + gap: 0.2rem; + font-size: 0.78rem; + font-weight: 600; + } + + input { + width: 3.8rem; + text-align: center; + } + + .reset-conditions { + flex: 1; + min-width: 11rem; + } + } + + .rw-notes { + p { + margin: 0; + color: #3c5169; + font-size: 0.82rem; + } + } + + .skills-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.28rem; + } + + .skills-grid .skill-category:nth-child(5) { + grid-column: 2; + } + + .rw-character-builder { + display: grid; + gap: 0.28rem; + margin-bottom: 0.35rem; + } + + .rw-builder-actions { + display: inline-flex; + gap: 0.2rem; + flex-wrap: wrap; + } + + .rw-builder-pools { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.16rem; + font-size: 0.78rem; + + > div { + border: 1px solid #9ab0c8; + border-radius: 4px; + background: rgba(240, 247, 255, 0.86); + padding: 0.16rem 0.24rem; + } + } + + .rw-builder-pursuits { + display: grid; + gap: 0.16rem; + + h4 { + margin: 0; + font-size: 0.76rem; + text-transform: uppercase; + letter-spacing: 0.02em; + color: #2f455d; + } + } + + .builder-add-row, + .builder-pursuit-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 4.1rem auto auto; + gap: 0.16rem; + align-items: center; + } + + .builder-add-row { + grid-template-columns: minmax(0, 1fr) 4.1rem auto; + } + + .builder-pursuit-list { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: 0.14rem; + } + + .rw-builder-note { + margin: 0; + font-size: 0.75rem; + color: #324962; + } + + .skill-category { + margin: 0; + padding: 0.28rem; + + h3 { + margin: 0 0 0.15rem; + font-size: 0.88rem; + text-transform: uppercase; + letter-spacing: 0.02em; + color: #26364a; + } + + .root-max { + display: inline-grid; + grid-template-columns: auto 3.4rem; + align-items: center; + gap: 0.18rem; + margin-bottom: 0.18rem; + font-size: 0.72rem; + font-weight: 600; + } + + .root-max input[readonly] { + background: #edf3fb; + color: #2b4057; + } + } + + .compact-skill-list { + .skill-row { + grid-template-columns: minmax(0, 1fr) auto auto auto; + min-height: 1.42rem; + margin-bottom: 0.14rem; + padding: 0.1rem 0.16rem; + border-radius: 3px; + } + + .skill-name { + display: inline-flex; + align-items: center; + gap: 0.22rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.77rem; + line-height: 1.2; + padding-right: 0.1rem; + + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .skill-base, + .skill-value { + min-width: 2.4rem; + text-align: right; + font-size: 0.76rem; + font-variant-numeric: tabular-nums; + } + + .alloc-chip { + display: inline-block; + margin-left: 0.18rem; + padding: 0.04rem 0.2rem; + border-radius: 999px; + border: 1px solid #6f8eae; + background: #ecf4fd; + color: #2a445f; + font-size: 0.62rem; + font-weight: 700; + line-height: 1; + vertical-align: middle; + } + } + + .skill-actions { + gap: 0.12rem; + } + + .skill-columns-head { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto auto; + gap: 0.14rem; + padding: 0.02rem 0.18rem 0.14rem; + margin-bottom: 0.1rem; + border-bottom: 1px dashed #9ab0c8; + font-size: 0.66rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.02em; + color: #375069; + } + + .skill-icon { + width: 0.92rem; + height: 0.92rem; + border-radius: 2px; + border: 1px solid #87a2bf; + object-fit: cover; + background: #0a0d12; + flex: 0 0 auto; + } + + .icon-btn { + width: 1.34rem; + height: 1.34rem; + min-width: 1.34rem; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + font-size: 0.7rem; + line-height: 1; + + i { + pointer-events: none; + } + } + + .missing-skill { + border-color: #bf9863; + background: #fff8ec; + } + + .additional-skills summary { + cursor: pointer; + font-weight: 600; + } + + .rw-hit-body { + display: grid; + grid-template-columns: repeat(3, 74px); + grid-template-areas: + ". head ." + "lArm chest rArm" + ". abdomen ." + "lLeg . rLeg"; + gap: 0.12rem; + align-items: center; + justify-content: start; + width: 100%; + } + + .rw-hit-body.schema-dolphin { + grid-template-areas: + ". head ." + "lArm chest rArm" + ". abdomen ." + ". lLeg ." + ". rLeg ."; + } + + .rw-hit-body.schema-puppeteer { + grid-template-areas: + ". head ." + "lArm chest rArm" + "lLeg abdomen rLeg"; + } + + .rw-hit-body.schema-trinoc { + grid-template-areas: + ". head ." + "lArm chest rArm" + ". abdomen ." + "lLeg . rLeg"; + } + + .rw-hit-body.schema-bandersnatchi { + grid-template-areas: + ". head ." + "lArm chest rArm" + "lLeg abdomen rLeg"; + } + + .rw-hit-toolbar { + display: grid; + grid-template-columns: auto; + align-items: center; + gap: 0.1rem; + margin-bottom: 0.08rem; + } + + .rw-hit-toolbar .roll-hit-location { + justify-self: start; + } + + .rw-hit-main .rw-hit-body { + justify-content: center; + } + + .body-loc { + border: 1px solid #8ea7c2; + border-radius: 6px; + background: rgba(245, 250, 255, 0.95); + padding: 0.12rem 0.14rem; + display: grid; + gap: 0.05rem; + min-height: 68px; + + h4 { + margin: 0; + font-size: 0.63rem; + text-transform: uppercase; + color: #2c4158; + } + + label { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.2rem; + font-size: 0.6rem; + font-weight: 600; + } + + input { + width: 1.84rem; + text-align: center; + font-size: 0.6rem; + padding: 0.05rem 0.08rem; + } + } + + .body-head { + grid-area: head; + min-height: 64px; + } + + .body-lArm { + grid-area: lArm; + min-height: 70px; + } + + .body-rArm { + grid-area: rArm; + min-height: 70px; + } + + .body-chest { + grid-area: chest; + min-height: 70px; + } + + .body-abdomen { + grid-area: abdomen; + min-height: 70px; + } + + .body-lLeg { + grid-area: lLeg; + min-height: 68px; + } + + .body-rLeg { + grid-area: rLeg; + min-height: 68px; + } + + .rw-right-lower { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); + gap: 0.2rem; + align-items: start; + } + + .rw-portrait-panel { + display: grid; + gap: 0.1rem; + align-content: start; + margin-bottom: 0; + } + + .rw-portrait-img { + width: 100%; + max-width: none; + aspect-ratio: 4 / 3; + min-height: 7.4rem; + margin: 0 auto; + border: 1px solid #88a4c1; + border-radius: 6px; + object-fit: cover; + background: linear-gradient(160deg, #e1eaf5 0%, #f6fbff 100%); + cursor: pointer; + } + + .loc-roll { + font-size: 0.56rem; + color: #476482; + font-weight: 600; + } + + .loc-ap-total { + font-size: 0.56rem; + font-weight: 700; + color: #2e4762; + } + + .rw-hit-layout { + display: block; + } + + .rw-hit-summary { + border: 1px solid #9bb0c8; + border-radius: 6px; + background: rgba(248, 252, 255, 0.95); + padding: 0.14rem 0.18rem; + margin-bottom: 0; + + h4 { + margin: 0 0 0.14rem; + font-size: 0.74rem; + text-transform: uppercase; + color: #2d4259; + } + } + + .rw-hit-summary-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: baseline; + gap: 0.25rem; + border-bottom: 1px dotted #9eb1c8; + padding: 0.03rem 0; + font-size: 0.74rem; + font-weight: 600; + + &:last-of-type { + border-bottom: none; + } + } + + .rw-hit-summary-row:nth-child(odd) { + background: rgba(228, 239, 251, 0.6); + border-radius: 3px; + padding-left: 0.14rem; + padding-right: 0.14rem; + } + + .rw-hit-label { + color: #243a52; + } + + .rw-hit-values { + text-align: right; + white-space: nowrap; + } + + .rw-hit-unconscious { + display: grid; + gap: 0.1rem; + margin-top: 0.12rem; + font-size: 0.73rem; + font-weight: 600; + + input { + width: 4rem; + height: 1.25rem; + text-align: center; + } + } + + .rw-hit-summary-below { + margin-top: 0.05rem; + width: fit-content; + min-width: min(100%, 24rem); + max-width: 100%; + } + + .rw-weapon-list .item { + grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.42fr) minmax(0, 0.95fr) minmax(0, 0.58fr) minmax(0, 0.95fr) auto; + } + + .rw-armor-list .item { + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.42fr) minmax(0, 0.62fr) minmax(0, 0.72fr) auto; + } + + .rw-gear-list .item { + grid-template-columns: minmax(0, 1.36fr) minmax(0, 0.62fr) minmax(0, 1.02fr) auto; + } + + .rw-overview-weapons .rw-weapon-list .item { + grid-template-columns: minmax(0, 1.26fr) minmax(0, 1.02fr) minmax(0, 0.58fr) minmax(0, 0.96fr) auto; + } + + .rw-overview-armor .rw-armor-list .item { + grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.68fr) minmax(0, 0.78fr) auto; + } + + .rw-overview-gear .rw-gear-list .item { + grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.24fr) auto; + } + + @media (max-width: 1250px) { + .rw-upper-grid, + .rw-overview-content { + grid-template-columns: 1fr; + } + + .rw-right-lower { + grid-template-columns: 1fr; + } + } + + @media (max-width: 1100px) { + .rw-profile { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .rw-upper-grid, + .rw-overview-content { + grid-template-columns: 1fr; + } + + .attr-row { + grid-template-columns: 1fr; + gap: 0.2rem; + } + + .rhs .inline-pair { + margin-left: 0; + } + + .skills-grid { + grid-template-columns: 1fr; + } + + .skills-grid .skill-category:nth-child(5) { + grid-column: auto; + } + + .rw-builder-pools { + grid-template-columns: 1fr; + } + + .builder-add-row, + .builder-pursuit-row { + grid-template-columns: 1fr; + } + } +} + +.rw-builder-alloc-form { + display: grid; + gap: 0.4rem; + + .rw-builder-alloc-head { + display: grid; + grid-template-columns: 2.4rem minmax(0, 1fr); + gap: 0.45rem; + align-items: center; + } + + .rw-builder-alloc-head img { + width: 2.2rem; + height: 2.2rem; + border-radius: 4px; + border: 1px solid #7f97b3; + object-fit: cover; + background: #0b0f15; + } + + .rw-builder-alloc-head h3 { + margin: 0; + font-size: 0.95rem; + } + + .rw-builder-alloc-head p { + margin: 0.08rem 0 0; + font-size: 0.74rem; + color: #38516b; + } + + label { + display: grid; + gap: 0.12rem; + font-size: 0.8rem; + font-weight: 600; + } + + input[type="number"] { + width: 100%; + } + + .hint { + margin: 0; + font-size: 0.74rem; + color: #38516b; + } +} + +.rw-stat-roll-form { + display: grid; + gap: 0.42rem; + + .form-group { + display: grid; + gap: 0.12rem; + } + + label { + font-size: 0.78rem; + font-weight: 700; + } + + .hint { + margin: 0; + font-size: 0.74rem; + color: #375069; + } +} + +.directory .directory-footer .ringworld-seed-items, +.directory .directory-footer .ringworld-random-actor { + margin-top: 0.35rem; + width: 100%; +} + +.ringworld-directory-controls { + display: grid; + gap: 0.3rem; + margin-top: 0.35rem; +} + +.ringworld-directory-button { + width: 100%; + padding: 0.3rem 0.5rem; + border: 1px solid #7f9cba; + border-radius: 4px; + background: #eef5fd; + color: #24364b; +} + +.ringworld-chat-card { + border: 1px solid #9aa7b4; + border-radius: 8px; + background: linear-gradient(155deg, #f7fbff 0%, #e7f0fb 62%, #edf5ff 100%); + padding: 0.5rem 0.58rem; + font-family: "Rajdhani", "Orbitron", "Eurostile", "Bank Gothic", "Segoe UI", sans-serif; + box-shadow: inset 0 0 0 1px rgba(128, 168, 210, 0.28); + + h3 { + margin: 0; + line-height: 1.1; + font-size: 1.08rem; + color: #1f3550; + letter-spacing: 0.02em; + text-transform: uppercase; + } + + p { + margin: 0.22rem 0 0; + font-size: 0.95rem; + color: #223548; + } + + .rw-chat-header { + display: grid; + grid-template-columns: 2.25rem 1fr; + gap: 0.4rem; + align-items: center; + margin-bottom: 0.24rem; + } + + .rw-chat-actor-img { + width: 2.25rem; + height: 2.25rem; + border-radius: 6px; + object-fit: cover; + border: 1px solid #8ea3bc; + background: #f0f5fc; + } + + .rw-chat-header-text { + min-width: 0; + } + + .rw-chat-subtitle { + display: inline-flex; + align-items: center; + gap: 0.22rem; + font-weight: 700; + color: #2b425a; + letter-spacing: 0.02em; + } + + .rw-chat-tags { + display: grid; + gap: 0.18rem; + margin: 0.18rem 0 0.08rem; + } + + .rw-chat-tag { + display: inline-flex; + align-items: center; + gap: 0.22rem; + min-width: 0; + font-size: 0.9rem; + background: rgba(228, 239, 251, 0.78); + border: 1px solid #9fb4cb; + border-radius: 4px; + padding: 0.1rem 0.24rem; + } + + .rw-chat-tag-img { + width: 1rem; + height: 1rem; + border-radius: 3px; + object-fit: cover; + border: 1px solid #8ea3bc; + background: #f0f5fc; + flex: 0 0 auto; + } + + .rw-chat-outcome { + font-weight: 800; + margin-top: 0.06rem; + font-size: 2rem; + line-height: 0.94; + letter-spacing: 0.02em; + text-transform: uppercase; + } + + .rw-chat-notes { + font-size: 0.85rem; + color: #3a4e63; + margin-top: 0.34rem; + border-top: 1px dotted #90a6be; + padding-top: 0.26rem; + } + + .outcome.success, + .outcome.special { + color: #187047; + } + + .outcome.failure, + .outcome.fumble { + color: #b32929; + } + + .rw-chat-outcome.success, + .rw-chat-outcome.special { + color: #0d844d; + text-shadow: 0 0 8px rgba(13, 132, 77, 0.28); + } + + .rw-chat-outcome.failure, + .rw-chat-outcome.fumble { + color: #b01f1f; + text-shadow: 0 0 8px rgba(176, 31, 31, 0.22); + } + + .rw-chat-hitline { + margin-top: 0.28rem; + padding: 0.14rem 0.22rem; + border: 1px solid #7ca2cb; + border-radius: 5px; + background: rgba(226, 240, 255, 0.72); + } + + .rw-chat-hit-label { + font-size: 0.92rem; + font-weight: 700; + color: #2f4a66; + margin-right: 0.2rem; + } + + .rw-chat-hit-value { + font-size: 1.38rem; + font-weight: 800; + color: #284a8a; + letter-spacing: 0.02em; + text-transform: uppercase; + } + + .rw-chat-hit-roll { + font-size: 0.84rem; + color: #3a5775; + font-weight: 600; + } + + .rw-chat-damage-line { + margin-top: 0.18rem; + padding: 0.16rem 0.22rem; + border: 1px solid #ad8b57; + border-radius: 5px; + background: rgba(255, 244, 222, 0.82); + display: flex; + align-items: baseline; + gap: 0.26rem; + flex-wrap: wrap; + } + + .rw-chat-damage-label { + font-size: 0.92rem; + font-weight: 700; + color: #4f3d24; + } + + .rw-chat-damage-value { + font-size: 1.45rem; + line-height: 0.95; + font-weight: 900; + color: #8f2d1f; + letter-spacing: 0.01em; + min-width: 1.25rem; + } + + .rw-chat-damage-breakdown { + font-size: 0.85rem; + color: #665239; + font-weight: 600; + } +} + +.ringworld-attack-dialog { + font-family: "Rajdhani", "Orbitron", "Eurostile", "Bank Gothic", "Segoe UI", sans-serif; + + .window-header { + background: linear-gradient(155deg, #263950 0%, #1d2f46 100%); + color: #e6f2ff; + } + + .window-content { + background: linear-gradient(165deg, #f8fcff 0%, #e8f1fb 60%, #f4f9ff 100%); + padding: 0.42rem 0.48rem; + } + + .dialog-content { + margin-bottom: 0.35rem; + } + + .dialog-buttons { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.28rem; + } + + .dialog-buttons .dialog-button { + min-height: 2rem; + font-weight: 700; + letter-spacing: 0.02em; + border-radius: 5px; + border: 1px solid #6f8eb0; + background: linear-gradient(180deg, #f8fbff 0%, #dce8f6 100%); + color: #21374c; + } +} + +.ringworld-attack-modifiers { + display: grid; + gap: 0.3rem; + font-family: "Rajdhani", "Orbitron", "Eurostile", "Bank Gothic", "Segoe UI", sans-serif; + + .rw-attack-dialog-head { + display: grid; + grid-template-columns: 2.05rem 2.05rem 1fr; + gap: 0.34rem; + align-items: center; + padding: 0.22rem 0.26rem; + border: 1px solid #9ab1ca; + border-radius: 6px; + background: linear-gradient(155deg, #edf4fd 0%, #e0ebf8 100%); + } + + .rw-attack-dialog-avatar, + .rw-attack-dialog-weapon { + width: 2.05rem; + height: 2.05rem; + border-radius: 5px; + object-fit: cover; + border: 1px solid #87a2bf; + background: #f1f6fd; + } + + .rw-attack-dialog-title-block h3 { + margin: 0; + font-size: 0.95rem; + line-height: 1.05; + letter-spacing: 0.02em; + text-transform: uppercase; + color: #243d57; + } + + .rw-attack-dialog-title-block p { + margin: 0.08rem 0 0; + font-size: 0.78rem; + color: #3a5673; + font-weight: 600; + } + + .form-group { + display: grid; + grid-template-columns: 9.4rem minmax(0, 1fr); + align-items: center; + column-gap: 0.35rem; + margin: 0; + } + + .form-group > label { + margin: 0; + font-size: 0.88rem; + font-weight: 700; + color: #20364d; + } + + .form-group input, + .form-group select { + height: 1.9rem; + margin: 0; + } + + .notes { + margin: 0.1rem 0 0; + font-size: 0.8rem; + color: #34506d; + border-top: 1px dotted #91a7bf; + padding-top: 0.2rem; + } +} + +.ringworld-hit-location-dialog { + font-family: "Rajdhani", "Orbitron", "Eurostile", "Bank Gothic", "Segoe UI", sans-serif; + + .window-header { + background: linear-gradient(155deg, #263950 0%, #1d2f46 100%); + color: #e6f2ff; + } + + .window-content { + background: linear-gradient(165deg, #f8fcff 0%, #e8f1fb 60%, #f4f9ff 100%); + padding: 0.42rem 0.48rem; + } + + .dialog-buttons { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.24rem; + } +} + +.ringworld-hit-picker { + display: grid; + gap: 0.34rem; + + .rw-hit-picker-top { + display: grid; + } + + .rw-hit-picker-body { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-areas: + ". head ." + "lArm chest rArm" + ". abdomen ." + "lLeg . rLeg"; + gap: 0.18rem; + align-items: stretch; + } + + .rw-hit-picker-choice { + border: 1px solid #7f9fbe; + border-radius: 6px; + background: linear-gradient(180deg, #f6fbff 0%, #e0ebf8 100%); + color: #1f3650; + font-size: 0.86rem; + font-weight: 700; + min-height: 2rem; + padding: 0.14rem 0.24rem; + text-transform: uppercase; + letter-spacing: 0.02em; + } + + .rw-hit-picker-choice.active { + border-color: #3b6ea3; + background: linear-gradient(180deg, #d6ebff 0%, #bedcfa 100%); + box-shadow: 0 0 0 2px rgba(59, 110, 163, 0.22) inset; + } + + .rw-loc-head { grid-area: head; } + .rw-loc-l-arm { grid-area: lArm; min-height: 2.5rem; } + .rw-loc-r-arm { grid-area: rArm; min-height: 2.5rem; } + .rw-loc-chest { grid-area: chest; min-height: 2.5rem; } + .rw-loc-abdomen { grid-area: abdomen; min-height: 2.5rem; } + .rw-loc-l-leg { grid-area: lLeg; min-height: 2.45rem; } + .rw-loc-r-leg { grid-area: rLeg; min-height: 2.45rem; } + + &.schema-dolphin .rw-hit-picker-body { + grid-template-areas: + ". head ." + "lArm chest rArm" + ". abdomen ." + ". lLeg ." + ". rLeg ."; + } + + &.schema-puppeteer .rw-hit-picker-body, + &.schema-bandersnatchi .rw-hit-picker-body { + grid-template-areas: + ". head ." + "lArm chest rArm" + "lLeg abdomen rLeg"; + } +} + +#combat-tracker, +#combat { + &.ringworld-impulse-only { + > :not(.ringworld-impulse-panel) { + display: none !important; + } + + > #combat-tracker, + > .combat-tracker, + > .directory-list, + > .combatants { + display: none !important; + } + + .rw-hide-core-tracker { + display: none !important; + } + } + + &.ringworld-impulse-mode { + .rw-hide-initiative, + .token-initiative, + .combatant-initiative, + .initiative, + [data-action="rollInitiative"], + [data-control="rollInitiative"], + .combatant .fa-dice-d20 { + display: none !important; + } + } + + .ringworld-impulse-panel { + margin: 0 0 0.35rem; + border: 1px solid #6e88a6; + border-radius: 6px; + background: #e6eef8; + padding: 0.3rem; + font-size: 0.72rem; + color: #22364b; + } + + .rw-impulse-top { + display: grid; + grid-template-columns: auto auto 1fr; + align-items: center; + gap: 0.25rem; + margin-bottom: 0.28rem; + } + + .rw-impulse-clock strong { + font-size: 0.88rem; + } + + .rw-impulse-controls { + display: flex; + justify-content: flex-end; + gap: 0.18rem; + } + + .rw-impulse-controls button { + border: 1px solid #7895b6; + border-radius: 4px; + background: #f2f7fd; + color: #1f3348; + padding: 0.12rem 0.25rem; + font-size: 0.68rem; + line-height: 1.1; + } + + .rw-imp-list { + display: grid; + gap: 0.2rem; + max-height: 15rem; + overflow-y: auto; + padding-right: 0.08rem; + } + + .rw-imp-row { + border: 1px solid #8ba3bf; + border-radius: 4px; + background: #f7fbff; + padding: 0.18rem; + display: grid; + gap: 0.16rem; + } + + .rw-imp-row:nth-child(odd) { + background: #edf5ff; + } + + .rw-imp-row-head { + display: grid; + gap: 0.08rem; + } + + .rw-imp-name { + font-weight: 700; + line-height: 1.1; + } + + .rw-imp-meta { + font-size: 0.65rem; + color: #39506a; + line-height: 1.1; + } + + .rw-imp-row-controls { + display: flex; + flex-wrap: wrap; + gap: 0.15rem; + align-items: center; + } + + .rw-imp-row-controls select { + border: 1px solid #7f9dbc; + border-radius: 4px; + background: #ffffff; + color: #1f3348; + min-width: 0; + padding: 0.08rem 0.14rem; + font-size: 0.68rem; + flex: 1 1 9.5rem; + } + + .rw-imp-row-controls button { + border: 1px solid #7895b6; + border-radius: 4px; + background: #f2f7fd; + color: #1f3348; + padding: 0.12rem 0.26rem; + font-size: 0.66rem; + line-height: 1.1; + min-width: 2.8rem; + flex: 0 0 auto; + } + + .rw-imp-row-controls label { + display: inline-flex; + align-items: center; + gap: 0.12rem; + font-size: 0.63rem; + font-weight: 700; + color: #2b425b; + white-space: nowrap; + } + + .rw-imp-row-controls input[type="number"] { + width: 2.8rem; + border: 1px solid #7f9dbc; + border-radius: 4px; + background: #fff; + color: #1f3348; + padding: 0.08rem 0.12rem; + font-size: 0.66rem; + line-height: 1.1; + text-align: center; + } + + .rw-save-mods { + min-width: 4.2rem !important; + } +} + +.ringworld-impulse-chat-marker, +.ringworld-impulse-hud { + border: 1px solid #7694b6; + border-radius: 8px; + background: linear-gradient(165deg, rgba(238, 246, 255, 0.98) 0%, rgba(225, 238, 252, 0.96) 100%); + box-shadow: 0 6px 16px rgba(19, 39, 62, 0.18); + color: #1d344c; + font-family: "Rajdhani", "Orbitron", "Eurostile", "Bank Gothic", "Segoe UI", sans-serif; +} + +.ringworld-impulse-hud { + position: fixed; + left: 78px; + bottom: 18px; + width: 300px; + z-index: 72; + padding: 0.34rem 0.38rem; +} + +.ringworld-impulse-chat-marker { + margin: 0.25rem 0.25rem 0.35rem; + padding: 0.28rem 0.3rem; +} + +.rw-imp-mini-head { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.3rem; + font-size: 0.88rem; + margin-bottom: 0.22rem; + cursor: move; + user-select: none; + + strong { + font-size: 1.04rem; + letter-spacing: 0.02em; + } +} + +.ringworld-impulse-hud.dragging { + opacity: 0.93; +} + +.rw-imp-mini-list { + display: grid; + gap: 0.14rem; + max-height: 10.2rem; + overflow-y: auto; + margin-bottom: 0.26rem; + padding-right: 0.08rem; +} + +.rw-imp-mini-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 0.25rem; + align-items: center; + border: 1px solid #9bb1ca; + border-radius: 4px; + background: rgba(248, 252, 255, 0.98); + padding: 0.1rem 0.16rem; + font-size: 0.76rem; +} + +.rw-imp-mini-row:nth-child(odd) { + background: rgba(228, 239, 251, 0.85); +} + +.rw-imp-mini-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 700; +} + +.rw-imp-mini-action { + white-space: nowrap; + font-weight: 600; + color: #304a65; +} + +.rw-imp-mini-empty { + border: 1px dashed #92abc7; + border-radius: 4px; + background: rgba(247, 252, 255, 0.85); + padding: 0.2rem 0.24rem; + font-size: 0.75rem; + color: #3d5673; +} + +.rw-imp-mini-controls { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.16rem; + + button { + border: 1px solid #7694b6; + border-radius: 4px; + background: linear-gradient(180deg, #f8fbff 0%, #dce8f6 100%); + color: #21374c; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.01em; + padding: 0.12rem 0.2rem; + } +} diff --git a/styles/ringworld.css b/styles/ringworld.css new file mode 100644 index 0000000..aa4087e --- /dev/null +++ b/styles/ringworld.css @@ -0,0 +1 @@ +.ringworld-sheet{color:#1b2a3a;background:linear-gradient(165deg,#f4f7fb,#e8eff8 55%,#f8fbff);font-size:12.5px;line-height:1.3;font-family:Rajdhani,Orbitron,Eurostile,Bank Gothic,Segoe UI,sans-serif}.ringworld-sheet input,.ringworld-sheet select,.ringworld-sheet textarea,.ringworld-sheet button{border:1px solid #91a6bf;border-radius:4px;background:#fff;color:#1b2a3a}.ringworld-sheet input,.ringworld-sheet select,.ringworld-sheet textarea{padding:.18rem .3rem}.ringworld-sheet button{cursor:pointer;padding:.2rem .45rem;background:linear-gradient(180deg,#f8fbff,#dfe9f6);border-color:#6f8eb0}.ringworld-sheet .sheet-header{display:grid;gap:.3rem;margin-bottom:.3rem}.ringworld-sheet .panel{border:1px solid #8fa8c2;border-radius:6px;background:#ffffffc7;padding:.28rem;margin-bottom:.2rem}.ringworld-sheet .panel-header{display:flex;justify-content:space-between;align-items:center;gap:.4rem;margin-bottom:.35rem}.ringworld-sheet .toolbar{display:flex;align-items:center;gap:.35rem;margin-bottom:.35rem}.ringworld-sheet .tabs{display:flex;gap:.35rem;margin-bottom:.3rem}.ringworld-sheet .tabs .item{border:1px solid #7d97b3;border-radius:4px;padding:.2rem .5rem;background:#e5eef9d9;font-weight:600;font-size:.84rem}.ringworld-sheet .char-grid,.ringworld-sheet .condition-grid,.ringworld-sheet .location-list{display:grid;gap:.3rem}.ringworld-sheet .char-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.ringworld-sheet .resource-row{display:flex;align-items:center;gap:.35rem;flex-wrap:wrap}.ringworld-sheet .item-list{list-style:none;margin:0;padding:0}.ringworld-sheet .item-list .item{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr) auto;align-items:center;gap:.35rem;margin-bottom:.22rem;padding:.2rem .3rem;border:1px solid #a0b4ca;border-radius:4px;background:#fafdffeb}.ringworld-sheet .item-list .item>div{min-width:0}.ringworld-sheet .item-list .item>div:not(.actions){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ringworld-sheet .actions{display:flex;justify-content:flex-end;gap:.2rem}.ringworld-sheet .actions button{font-size:.74rem;padding:.15rem .35rem;min-width:1.4rem}.ringworld-sheet .location-row{display:grid;grid-template-columns:1.3fr 1fr auto 1fr;align-items:center;gap:.3rem}.ringworld-sheet .inline-pair{display:inline-flex;align-items:center;gap:.2rem}.ringworld-sheet .skills-badge{margin-left:auto;border:1px solid #7b98b7;border-radius:999px;background:#edf4fe;padding:.1rem .45rem;font-size:.78rem}.ringworld-sheet .rw-header h1{margin:0;text-transform:uppercase;letter-spacing:.03em;color:#2d3e54;font-size:1.72rem;font-weight:700}.ringworld-sheet .rw-title-row{display:flex;align-items:center;justify-content:space-between;gap:.4rem}.ringworld-sheet .rw-lock-status{display:inline-block;border:1px solid #7f97b3;border-radius:999px;background:#e9f2fce6;color:#2b4057;font-size:.73rem;font-weight:700;padding:.1rem .45rem}.ringworld-sheet .rw-profile{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.16rem}.ringworld-sheet .rw-profile label{display:grid;gap:.06rem;font-size:.75rem;font-weight:600}.ringworld-sheet .rw-profile .rw-encumbrance-field{grid-column:1/-1}.ringworld-sheet .rw-profile .rw-encumbrance-field input{min-height:1.55rem;font-size:.88rem;font-weight:700;color:#1f3348;border-width:2px;border-color:#6486ac;background:linear-gradient(180deg,#f9fcff,#e5effc)}.ringworld-sheet .rw-upper-grid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,.8fr);gap:.24rem;align-items:start}.ringworld-sheet .rw-left-stack,.ringworld-sheet .rw-right-stack{display:grid;gap:.2rem}.ringworld-sheet .rw-quick{display:grid;gap:.2rem;max-width:none;width:100%;margin-bottom:0}.ringworld-sheet .rw-overview-list{max-height:10.9rem;overflow-y:auto;overflow-x:hidden;padding-right:.08rem}.ringworld-sheet .rw-overview-list .item{min-height:1.7rem;margin-bottom:.16rem;padding:.14rem .2rem;gap:.18rem;font-size:.73rem;font-weight:600}.ringworld-sheet .rw-overview-list .item>div:not(.actions){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ringworld-sheet .rw-overview-list .item:nth-child(odd){background:#e4effbf5}.ringworld-sheet .item-title{display:inline-flex;align-items:center;gap:.22rem;min-width:0}.ringworld-sheet .item-title span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ringworld-sheet .item-icon{width:1rem;height:1rem;border-radius:2px;flex:0 0 auto;object-fit:cover}.ringworld-sheet .rw-overview-weapons .rw-overview-list{max-height:11.1rem}.ringworld-sheet .rw-overview-content{display:grid;grid-template-columns:minmax(0,1.26fr) minmax(0,.74fr);gap:.24rem;align-items:start}.ringworld-sheet .rw-overview-side{display:grid;gap:.24rem}.ringworld-sheet .rw-attribute-board{display:grid;gap:.08rem;border:1px solid #92a7c0;border-radius:6px;background:#ffffffad;padding:.16rem .18rem}.ringworld-sheet .attr-row{display:grid;grid-template-columns:minmax(82px,.44fr) minmax(0,1.56fr);gap:.06rem;align-items:center;padding:.03rem .08rem;border-bottom:1px dotted #8da4bf}.ringworld-sheet .attr-row:last-child{border-bottom:none}.ringworld-sheet .rw-attribute-board .attr-row:nth-child(odd){background:#e4effb99;border-radius:4px}.ringworld-sheet .lhs,.ringworld-sheet .rhs{display:flex;align-items:center;gap:.1rem;min-width:0;font-size:.8rem;font-weight:600;color:#26384d}.ringworld-sheet .lhs{display:grid;grid-template-columns:2.6rem 2.9rem;align-items:center;column-gap:.12rem}.ringworld-sheet .stat-roll-btn{width:2.5rem;min-width:2.5rem;height:1.35rem;padding:0;font-size:.95rem;font-weight:800;letter-spacing:.01em;border-color:#6f8eb0}.ringworld-sheet .lhs input{width:2.9rem;text-align:center;font-size:.8rem;justify-self:start}.ringworld-sheet .rhs>span:first-child{min-width:3.8rem}.ringworld-sheet .rhs{display:grid;grid-template-columns:minmax(0,.95fr) minmax(7.8rem,13.2rem);align-items:center;column-gap:.14rem}.ringworld-sheet .rhs input[type=text]{width:100%;min-width:0}.ringworld-sheet .rhs input[type=number]{width:2.8rem;text-align:center}.ringworld-sheet .rhs .inline-pair{margin-left:0;justify-self:end}.ringworld-sheet .attr-row button.roll-derived{margin-left:.1rem}.ringworld-sheet .rw-tools{display:flex;flex-wrap:wrap;gap:.18rem;align-items:center;max-width:100%}.ringworld-sheet .rw-tools label{display:inline-flex;align-items:center;gap:.2rem;font-size:.78rem;font-weight:600}.ringworld-sheet .rw-tools input{width:3.8rem;text-align:center}.ringworld-sheet .rw-tools .reset-conditions{flex:1;min-width:11rem}.ringworld-sheet .rw-notes p{margin:0;color:#3c5169;font-size:.82rem}.ringworld-sheet .skills-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.28rem}.ringworld-sheet .skills-grid .skill-category:nth-child(5){grid-column:2}.ringworld-sheet .rw-character-builder{display:grid;gap:.28rem;margin-bottom:.35rem}.ringworld-sheet .rw-builder-actions{display:inline-flex;gap:.2rem;flex-wrap:wrap}.ringworld-sheet .rw-builder-pools{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.16rem;font-size:.78rem}.ringworld-sheet .rw-builder-pools>div{border:1px solid #9ab0c8;border-radius:4px;background:#f0f7ffdb;padding:.16rem .24rem}.ringworld-sheet .rw-builder-pursuits{display:grid;gap:.16rem}.ringworld-sheet .rw-builder-pursuits h4{margin:0;font-size:.76rem;text-transform:uppercase;letter-spacing:.02em;color:#2f455d}.ringworld-sheet .builder-add-row,.ringworld-sheet .builder-pursuit-row{display:grid;grid-template-columns:minmax(0,1fr) 4.1rem auto auto;gap:.16rem;align-items:center}.ringworld-sheet .builder-add-row{grid-template-columns:minmax(0,1fr) 4.1rem auto}.ringworld-sheet .builder-pursuit-list{list-style:none;padding:0;margin:0;display:grid;gap:.14rem}.ringworld-sheet .rw-builder-note{margin:0;font-size:.75rem;color:#324962}.ringworld-sheet .skill-category{margin:0;padding:.28rem}.ringworld-sheet .skill-category h3{margin:0 0 .15rem;font-size:.88rem;text-transform:uppercase;letter-spacing:.02em;color:#26364a}.ringworld-sheet .skill-category .root-max{display:inline-grid;grid-template-columns:auto 3.4rem;align-items:center;gap:.18rem;margin-bottom:.18rem;font-size:.72rem;font-weight:600}.ringworld-sheet .skill-category .root-max input[readonly]{background:#edf3fb;color:#2b4057}.ringworld-sheet .compact-skill-list .skill-row{grid-template-columns:minmax(0,1fr) auto auto auto;min-height:1.42rem;margin-bottom:.14rem;padding:.1rem .16rem;border-radius:3px}.ringworld-sheet .compact-skill-list .skill-name{display:inline-flex;align-items:center;gap:.22rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.77rem;line-height:1.2;padding-right:.1rem}.ringworld-sheet .compact-skill-list .skill-name span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ringworld-sheet .compact-skill-list .skill-base,.ringworld-sheet .compact-skill-list .skill-value{min-width:2.4rem;text-align:right;font-size:.76rem;font-variant-numeric:tabular-nums}.ringworld-sheet .compact-skill-list .alloc-chip{display:inline-block;margin-left:.18rem;padding:.04rem .2rem;border-radius:999px;border:1px solid #6f8eae;background:#ecf4fd;color:#2a445f;font-size:.62rem;font-weight:700;line-height:1;vertical-align:middle}.ringworld-sheet .skill-actions{gap:.12rem}.ringworld-sheet .skill-columns-head{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto;gap:.14rem;padding:.02rem .18rem .14rem;margin-bottom:.1rem;border-bottom:1px dashed #9ab0c8;font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.02em;color:#375069}.ringworld-sheet .skill-icon{width:.92rem;height:.92rem;border-radius:2px;border:1px solid #87a2bf;object-fit:cover;background:#0a0d12;flex:0 0 auto}.ringworld-sheet .icon-btn{width:1.34rem;height:1.34rem;min-width:1.34rem;display:inline-flex;align-items:center;justify-content:center;padding:0;font-size:.7rem;line-height:1}.ringworld-sheet .icon-btn i{pointer-events:none}.ringworld-sheet .missing-skill{border-color:#bf9863;background:#fff8ec}.ringworld-sheet .additional-skills summary{cursor:pointer;font-weight:600}.ringworld-sheet .rw-hit-body{display:grid;grid-template-columns:repeat(3,74px);grid-template-areas:". head ." "lArm chest rArm" ". abdomen ." "lLeg . rLeg";gap:.12rem;align-items:center;justify-content:start;width:100%}.ringworld-sheet .rw-hit-body.schema-dolphin{grid-template-areas:". head ." "lArm chest rArm" ". abdomen ." ". lLeg ." ". rLeg ."}.ringworld-sheet .rw-hit-body.schema-puppeteer{grid-template-areas:". head ." "lArm chest rArm" "lLeg abdomen rLeg"}.ringworld-sheet .rw-hit-body.schema-trinoc{grid-template-areas:". head ." "lArm chest rArm" ". abdomen ." "lLeg . rLeg"}.ringworld-sheet .rw-hit-body.schema-bandersnatchi{grid-template-areas:". head ." "lArm chest rArm" "lLeg abdomen rLeg"}.ringworld-sheet .rw-hit-toolbar{display:grid;grid-template-columns:auto;align-items:center;gap:.1rem;margin-bottom:.08rem}.ringworld-sheet .rw-hit-toolbar .roll-hit-location{justify-self:start}.ringworld-sheet .rw-hit-main .rw-hit-body{justify-content:center}.ringworld-sheet .body-loc{border:1px solid #8ea7c2;border-radius:6px;background:#f5fafff2;padding:.12rem .14rem;display:grid;gap:.05rem;min-height:68px}.ringworld-sheet .body-loc h4{margin:0;font-size:.63rem;text-transform:uppercase;color:#2c4158}.ringworld-sheet .body-loc label{display:flex;justify-content:space-between;align-items:center;gap:.2rem;font-size:.6rem;font-weight:600}.ringworld-sheet .body-loc input{width:1.84rem;text-align:center;font-size:.6rem;padding:.05rem .08rem}.ringworld-sheet .body-head{grid-area:head;min-height:64px}.ringworld-sheet .body-lArm{grid-area:lArm;min-height:70px}.ringworld-sheet .body-rArm{grid-area:rArm;min-height:70px}.ringworld-sheet .body-chest{grid-area:chest;min-height:70px}.ringworld-sheet .body-abdomen{grid-area:abdomen;min-height:70px}.ringworld-sheet .body-lLeg{grid-area:lLeg;min-height:68px}.ringworld-sheet .body-rLeg{grid-area:rLeg;min-height:68px}.ringworld-sheet .rw-right-lower{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);gap:.2rem;align-items:start}.ringworld-sheet .rw-portrait-panel{display:grid;gap:.1rem;align-content:start;margin-bottom:0}.ringworld-sheet .rw-portrait-img{width:100%;max-width:none;aspect-ratio:4/3;min-height:7.4rem;margin:0 auto;border:1px solid #88a4c1;border-radius:6px;object-fit:cover;background:linear-gradient(160deg,#e1eaf5,#f6fbff);cursor:pointer}.ringworld-sheet .loc-roll{font-size:.56rem;color:#476482;font-weight:600}.ringworld-sheet .loc-ap-total{font-size:.56rem;font-weight:700;color:#2e4762}.ringworld-sheet .rw-hit-layout{display:block}.ringworld-sheet .rw-hit-summary{border:1px solid #9bb0c8;border-radius:6px;background:#f8fcfff2;padding:.14rem .18rem;margin-bottom:0}.ringworld-sheet .rw-hit-summary h4{margin:0 0 .14rem;font-size:.74rem;text-transform:uppercase;color:#2d4259}.ringworld-sheet .rw-hit-summary-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:baseline;gap:.25rem;border-bottom:1px dotted #9eb1c8;padding:.03rem 0;font-size:.74rem;font-weight:600}.ringworld-sheet .rw-hit-summary-row:last-of-type{border-bottom:none}.ringworld-sheet .rw-hit-summary-row:nth-child(odd){background:#e4effb99;border-radius:3px;padding-left:.14rem;padding-right:.14rem}.ringworld-sheet .rw-hit-label{color:#243a52}.ringworld-sheet .rw-hit-values{text-align:right;white-space:nowrap}.ringworld-sheet .rw-hit-unconscious{display:grid;gap:.1rem;margin-top:.12rem;font-size:.73rem;font-weight:600}.ringworld-sheet .rw-hit-unconscious input{width:4rem;height:1.25rem;text-align:center}.ringworld-sheet .rw-hit-summary-below{margin-top:.05rem;width:fit-content;min-width:min(100%,24rem);max-width:100%}.ringworld-sheet .rw-weapon-list .item{grid-template-columns:minmax(0,1.2fr) minmax(0,.42fr) minmax(0,.95fr) minmax(0,.58fr) minmax(0,.95fr) auto}.ringworld-sheet .rw-armor-list .item{grid-template-columns:minmax(0,1.1fr) minmax(0,.42fr) minmax(0,.62fr) minmax(0,.72fr) auto}.ringworld-sheet .rw-gear-list .item{grid-template-columns:minmax(0,1.36fr) minmax(0,.62fr) minmax(0,1.02fr) auto}.ringworld-sheet .rw-overview-weapons .rw-weapon-list .item{grid-template-columns:minmax(0,1.26fr) minmax(0,1.02fr) minmax(0,.58fr) minmax(0,.96fr) auto}.ringworld-sheet .rw-overview-armor .rw-armor-list .item{grid-template-columns:minmax(0,1.2fr) minmax(0,.68fr) minmax(0,.78fr) auto}.ringworld-sheet .rw-overview-gear .rw-gear-list .item{grid-template-columns:minmax(0,1.2fr) minmax(0,1.24fr) auto}@media (max-width: 1250px){.ringworld-sheet .rw-upper-grid,.ringworld-sheet .rw-overview-content,.ringworld-sheet .rw-right-lower{grid-template-columns:1fr}}@media (max-width: 1100px){.ringworld-sheet .rw-profile{grid-template-columns:repeat(2,minmax(0,1fr))}.ringworld-sheet .rw-upper-grid,.ringworld-sheet .rw-overview-content{grid-template-columns:1fr}.ringworld-sheet .attr-row{grid-template-columns:1fr;gap:.2rem}.ringworld-sheet .rhs .inline-pair{margin-left:0}.ringworld-sheet .skills-grid{grid-template-columns:1fr}.ringworld-sheet .skills-grid .skill-category:nth-child(5){grid-column:auto}.ringworld-sheet .rw-builder-pools,.ringworld-sheet .builder-add-row,.ringworld-sheet .builder-pursuit-row{grid-template-columns:1fr}}.rw-builder-alloc-form{display:grid;gap:.4rem}.rw-builder-alloc-form .rw-builder-alloc-head{display:grid;grid-template-columns:2.4rem minmax(0,1fr);gap:.45rem;align-items:center}.rw-builder-alloc-form .rw-builder-alloc-head img{width:2.2rem;height:2.2rem;border-radius:4px;border:1px solid #7f97b3;object-fit:cover;background:#0b0f15}.rw-builder-alloc-form .rw-builder-alloc-head h3{margin:0;font-size:.95rem}.rw-builder-alloc-form .rw-builder-alloc-head p{margin:.08rem 0 0;font-size:.74rem;color:#38516b}.rw-builder-alloc-form label{display:grid;gap:.12rem;font-size:.8rem;font-weight:600}.rw-builder-alloc-form input[type=number]{width:100%}.rw-builder-alloc-form .hint{margin:0;font-size:.74rem;color:#38516b}.rw-stat-roll-form{display:grid;gap:.42rem}.rw-stat-roll-form .form-group{display:grid;gap:.12rem}.rw-stat-roll-form label{font-size:.78rem;font-weight:700}.rw-stat-roll-form .hint{margin:0;font-size:.74rem;color:#375069}.directory .directory-footer .ringworld-seed-items,.directory .directory-footer .ringworld-random-actor{margin-top:.35rem;width:100%}.ringworld-directory-controls{display:grid;gap:.3rem;margin-top:.35rem}.ringworld-directory-button{width:100%;padding:.3rem .5rem;border:1px solid #7f9cba;border-radius:4px;background:#eef5fd;color:#24364b}.ringworld-chat-card{border:1px solid #9aa7b4;border-radius:8px;background:linear-gradient(155deg,#f7fbff,#e7f0fb 62%,#edf5ff);padding:.5rem .58rem;font-family:Rajdhani,Orbitron,Eurostile,Bank Gothic,Segoe UI,sans-serif;box-shadow:inset 0 0 0 1px #80a8d247}.ringworld-chat-card h3{margin:0;line-height:1.1;font-size:1.08rem;color:#1f3550;letter-spacing:.02em;text-transform:uppercase}.ringworld-chat-card p{margin:.22rem 0 0;font-size:.95rem;color:#223548}.ringworld-chat-card .rw-chat-header{display:grid;grid-template-columns:2.25rem 1fr;gap:.4rem;align-items:center;margin-bottom:.24rem}.ringworld-chat-card .rw-chat-actor-img{width:2.25rem;height:2.25rem;border-radius:6px;object-fit:cover;border:1px solid #8ea3bc;background:#f0f5fc}.ringworld-chat-card .rw-chat-header-text{min-width:0}.ringworld-chat-card .rw-chat-subtitle{display:inline-flex;align-items:center;gap:.22rem;font-weight:700;color:#2b425a;letter-spacing:.02em}.ringworld-chat-card .rw-chat-tags{display:grid;gap:.18rem;margin:.18rem 0 .08rem}.ringworld-chat-card .rw-chat-tag{display:inline-flex;align-items:center;gap:.22rem;min-width:0;font-size:.9rem;background:#e4effbc7;border:1px solid #9fb4cb;border-radius:4px;padding:.1rem .24rem}.ringworld-chat-card .rw-chat-tag-img{width:1rem;height:1rem;border-radius:3px;object-fit:cover;border:1px solid #8ea3bc;background:#f0f5fc;flex:0 0 auto}.ringworld-chat-card .rw-chat-outcome{font-weight:800;margin-top:.06rem;font-size:2rem;line-height:.94;letter-spacing:.02em;text-transform:uppercase}.ringworld-chat-card .rw-chat-notes{font-size:.85rem;color:#3a4e63;margin-top:.34rem;border-top:1px dotted #90a6be;padding-top:.26rem}.ringworld-chat-card .outcome.success,.ringworld-chat-card .outcome.special{color:#187047}.ringworld-chat-card .outcome.failure,.ringworld-chat-card .outcome.fumble{color:#b32929}.ringworld-chat-card .rw-chat-outcome.success,.ringworld-chat-card .rw-chat-outcome.special{color:#0d844d;text-shadow:0 0 8px rgba(13,132,77,.28)}.ringworld-chat-card .rw-chat-outcome.failure,.ringworld-chat-card .rw-chat-outcome.fumble{color:#b01f1f;text-shadow:0 0 8px rgba(176,31,31,.22)}.ringworld-chat-card .rw-chat-hitline{margin-top:.28rem;padding:.14rem .22rem;border:1px solid #7ca2cb;border-radius:5px;background:#e2f0ffb8}.ringworld-chat-card .rw-chat-hit-label{font-size:.92rem;font-weight:700;color:#2f4a66;margin-right:.2rem}.ringworld-chat-card .rw-chat-hit-value{font-size:1.38rem;font-weight:800;color:#284a8a;letter-spacing:.02em;text-transform:uppercase}.ringworld-chat-card .rw-chat-hit-roll{font-size:.84rem;color:#3a5775;font-weight:600}.ringworld-chat-card .rw-chat-damage-line{margin-top:.18rem;padding:.16rem .22rem;border:1px solid #ad8b57;border-radius:5px;background:#fff4ded1;display:flex;align-items:baseline;gap:.26rem;flex-wrap:wrap}.ringworld-chat-card .rw-chat-damage-label{font-size:.92rem;font-weight:700;color:#4f3d24}.ringworld-chat-card .rw-chat-damage-value{font-size:1.45rem;line-height:.95;font-weight:900;color:#8f2d1f;letter-spacing:.01em;min-width:1.25rem}.ringworld-chat-card .rw-chat-damage-breakdown{font-size:.85rem;color:#665239;font-weight:600}.ringworld-attack-dialog{font-family:Rajdhani,Orbitron,Eurostile,Bank Gothic,Segoe UI,sans-serif}.ringworld-attack-dialog .window-header{background:linear-gradient(155deg,#263950,#1d2f46);color:#e6f2ff}.ringworld-attack-dialog .window-content{background:linear-gradient(165deg,#f8fcff,#e8f1fb 60%,#f4f9ff);padding:.42rem .48rem}.ringworld-attack-dialog .dialog-content{margin-bottom:.35rem}.ringworld-attack-dialog .dialog-buttons{display:grid;grid-template-columns:1fr 1fr;gap:.28rem}.ringworld-attack-dialog .dialog-buttons .dialog-button{min-height:2rem;font-weight:700;letter-spacing:.02em;border-radius:5px;border:1px solid #6f8eb0;background:linear-gradient(180deg,#f8fbff,#dce8f6);color:#21374c}.ringworld-attack-modifiers{display:grid;gap:.3rem;font-family:Rajdhani,Orbitron,Eurostile,Bank Gothic,Segoe UI,sans-serif}.ringworld-attack-modifiers .rw-attack-dialog-head{display:grid;grid-template-columns:2.05rem 2.05rem 1fr;gap:.34rem;align-items:center;padding:.22rem .26rem;border:1px solid #9ab1ca;border-radius:6px;background:linear-gradient(155deg,#edf4fd,#e0ebf8)}.ringworld-attack-modifiers .rw-attack-dialog-avatar,.ringworld-attack-modifiers .rw-attack-dialog-weapon{width:2.05rem;height:2.05rem;border-radius:5px;object-fit:cover;border:1px solid #87a2bf;background:#f1f6fd}.ringworld-attack-modifiers .rw-attack-dialog-title-block h3{margin:0;font-size:.95rem;line-height:1.05;letter-spacing:.02em;text-transform:uppercase;color:#243d57}.ringworld-attack-modifiers .rw-attack-dialog-title-block p{margin:.08rem 0 0;font-size:.78rem;color:#3a5673;font-weight:600}.ringworld-attack-modifiers .form-group{display:grid;grid-template-columns:9.4rem minmax(0,1fr);align-items:center;column-gap:.35rem;margin:0}.ringworld-attack-modifiers .form-group>label{margin:0;font-size:.88rem;font-weight:700;color:#20364d}.ringworld-attack-modifiers .form-group input,.ringworld-attack-modifiers .form-group select{height:1.9rem;margin:0}.ringworld-attack-modifiers .notes{margin:.1rem 0 0;font-size:.8rem;color:#34506d;border-top:1px dotted #91a7bf;padding-top:.2rem}.ringworld-hit-location-dialog{font-family:Rajdhani,Orbitron,Eurostile,Bank Gothic,Segoe UI,sans-serif}.ringworld-hit-location-dialog .window-header{background:linear-gradient(155deg,#263950,#1d2f46);color:#e6f2ff}.ringworld-hit-location-dialog .window-content{background:linear-gradient(165deg,#f8fcff,#e8f1fb 60%,#f4f9ff);padding:.42rem .48rem}.ringworld-hit-location-dialog .dialog-buttons{display:grid;grid-template-columns:1fr 1fr;gap:.24rem}.ringworld-hit-picker{display:grid;gap:.34rem}.ringworld-hit-picker .rw-hit-picker-top{display:grid}.ringworld-hit-picker .rw-hit-picker-body{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-template-areas:". head ." "lArm chest rArm" ". abdomen ." "lLeg . rLeg";gap:.18rem;align-items:stretch}.ringworld-hit-picker .rw-hit-picker-choice{border:1px solid #7f9fbe;border-radius:6px;background:linear-gradient(180deg,#f6fbff,#e0ebf8);color:#1f3650;font-size:.86rem;font-weight:700;min-height:2rem;padding:.14rem .24rem;text-transform:uppercase;letter-spacing:.02em}.ringworld-hit-picker .rw-hit-picker-choice.active{border-color:#3b6ea3;background:linear-gradient(180deg,#d6ebff,#bedcfa);box-shadow:0 0 0 2px #3b6ea338 inset}.ringworld-hit-picker .rw-loc-head{grid-area:head}.ringworld-hit-picker .rw-loc-l-arm{grid-area:lArm;min-height:2.5rem}.ringworld-hit-picker .rw-loc-r-arm{grid-area:rArm;min-height:2.5rem}.ringworld-hit-picker .rw-loc-chest{grid-area:chest;min-height:2.5rem}.ringworld-hit-picker .rw-loc-abdomen{grid-area:abdomen;min-height:2.5rem}.ringworld-hit-picker .rw-loc-l-leg{grid-area:lLeg;min-height:2.45rem}.ringworld-hit-picker .rw-loc-r-leg{grid-area:rLeg;min-height:2.45rem}.ringworld-hit-picker.schema-dolphin .rw-hit-picker-body{grid-template-areas:". head ." "lArm chest rArm" ". abdomen ." ". lLeg ." ". rLeg ."}.ringworld-hit-picker.schema-puppeteer .rw-hit-picker-body,.ringworld-hit-picker.schema-bandersnatchi .rw-hit-picker-body{grid-template-areas:". head ." "lArm chest rArm" "lLeg abdomen rLeg"}#combat-tracker.ringworld-impulse-only>:not(.ringworld-impulse-panel),#combat.ringworld-impulse-only>:not(.ringworld-impulse-panel){display:none!important}#combat-tracker.ringworld-impulse-only>#combat-tracker,#combat-tracker.ringworld-impulse-only>.combat-tracker,#combat-tracker.ringworld-impulse-only>.directory-list,#combat-tracker.ringworld-impulse-only>.combatants,#combat.ringworld-impulse-only>#combat-tracker,#combat.ringworld-impulse-only>.combat-tracker,#combat.ringworld-impulse-only>.directory-list,#combat.ringworld-impulse-only>.combatants{display:none!important}#combat-tracker.ringworld-impulse-only .rw-hide-core-tracker,#combat.ringworld-impulse-only .rw-hide-core-tracker,#combat-tracker.ringworld-impulse-mode .rw-hide-initiative,#combat-tracker.ringworld-impulse-mode .token-initiative,#combat-tracker.ringworld-impulse-mode .combatant-initiative,#combat-tracker.ringworld-impulse-mode .initiative,#combat-tracker.ringworld-impulse-mode [data-action=rollInitiative],#combat-tracker.ringworld-impulse-mode [data-control=rollInitiative],#combat-tracker.ringworld-impulse-mode .combatant .fa-dice-d20,#combat.ringworld-impulse-mode .rw-hide-initiative,#combat.ringworld-impulse-mode .token-initiative,#combat.ringworld-impulse-mode .combatant-initiative,#combat.ringworld-impulse-mode .initiative,#combat.ringworld-impulse-mode [data-action=rollInitiative],#combat.ringworld-impulse-mode [data-control=rollInitiative],#combat.ringworld-impulse-mode .combatant .fa-dice-d20{display:none!important}#combat-tracker .ringworld-impulse-panel,#combat .ringworld-impulse-panel{margin:0 0 .35rem;border:1px solid #6e88a6;border-radius:6px;background:#e6eef8;padding:.3rem;font-size:.72rem;color:#22364b}#combat-tracker .rw-impulse-top,#combat .rw-impulse-top{display:grid;grid-template-columns:auto auto 1fr;align-items:center;gap:.25rem;margin-bottom:.28rem}#combat-tracker .rw-impulse-clock strong,#combat .rw-impulse-clock strong{font-size:.88rem}#combat-tracker .rw-impulse-controls,#combat .rw-impulse-controls{display:flex;justify-content:flex-end;gap:.18rem}#combat-tracker .rw-impulse-controls button,#combat .rw-impulse-controls button{border:1px solid #7895b6;border-radius:4px;background:#f2f7fd;color:#1f3348;padding:.12rem .25rem;font-size:.68rem;line-height:1.1}#combat-tracker .rw-imp-list,#combat .rw-imp-list{display:grid;gap:.2rem;max-height:15rem;overflow-y:auto;padding-right:.08rem}#combat-tracker .rw-imp-row,#combat .rw-imp-row{border:1px solid #8ba3bf;border-radius:4px;background:#f7fbff;padding:.18rem;display:grid;gap:.16rem}#combat-tracker .rw-imp-row:nth-child(odd),#combat .rw-imp-row:nth-child(odd){background:#edf5ff}#combat-tracker .rw-imp-row-head,#combat .rw-imp-row-head{display:grid;gap:.08rem}#combat-tracker .rw-imp-name,#combat .rw-imp-name{font-weight:700;line-height:1.1}#combat-tracker .rw-imp-meta,#combat .rw-imp-meta{font-size:.65rem;color:#39506a;line-height:1.1}#combat-tracker .rw-imp-row-controls,#combat .rw-imp-row-controls{display:flex;flex-wrap:wrap;gap:.15rem;align-items:center}#combat-tracker .rw-imp-row-controls select,#combat .rw-imp-row-controls select{border:1px solid #7f9dbc;border-radius:4px;background:#fff;color:#1f3348;min-width:0;padding:.08rem .14rem;font-size:.68rem;flex:1 1 9.5rem}#combat-tracker .rw-imp-row-controls button,#combat .rw-imp-row-controls button{border:1px solid #7895b6;border-radius:4px;background:#f2f7fd;color:#1f3348;padding:.12rem .26rem;font-size:.66rem;line-height:1.1;min-width:2.8rem;flex:0 0 auto}#combat-tracker .rw-imp-row-controls label,#combat .rw-imp-row-controls label{display:inline-flex;align-items:center;gap:.12rem;font-size:.63rem;font-weight:700;color:#2b425b;white-space:nowrap}#combat-tracker .rw-imp-row-controls input[type=number],#combat .rw-imp-row-controls input[type=number]{width:2.8rem;border:1px solid #7f9dbc;border-radius:4px;background:#fff;color:#1f3348;padding:.08rem .12rem;font-size:.66rem;line-height:1.1;text-align:center}#combat-tracker .rw-save-mods,#combat .rw-save-mods{min-width:4.2rem!important}.ringworld-impulse-chat-marker,.ringworld-impulse-hud{border:1px solid #7694b6;border-radius:8px;background:linear-gradient(165deg,#eef6fffa,#e1eefcf5);box-shadow:0 6px 16px #13273e2e;color:#1d344c;font-family:Rajdhani,Orbitron,Eurostile,Bank Gothic,Segoe UI,sans-serif}.ringworld-impulse-hud{position:fixed;left:78px;bottom:18px;width:300px;z-index:72;padding:.34rem .38rem}.ringworld-impulse-chat-marker{margin:.25rem .25rem .35rem;padding:.28rem .3rem}.rw-imp-mini-head{display:flex;justify-content:space-between;align-items:baseline;gap:.3rem;font-size:.88rem;margin-bottom:.22rem;cursor:move;-webkit-user-select:none;user-select:none}.rw-imp-mini-head strong{font-size:1.04rem;letter-spacing:.02em}.ringworld-impulse-hud.dragging{opacity:.93}.rw-imp-mini-list{display:grid;gap:.14rem;max-height:10.2rem;overflow-y:auto;margin-bottom:.26rem;padding-right:.08rem}.rw-imp-mini-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.25rem;align-items:center;border:1px solid #9bb1ca;border-radius:4px;background:#f8fcfffa;padding:.1rem .16rem;font-size:.76rem}.rw-imp-mini-row:nth-child(odd){background:#e4effbd9}.rw-imp-mini-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:700}.rw-imp-mini-action{white-space:nowrap;font-weight:600;color:#304a65}.rw-imp-mini-empty{border:1px dashed #92abc7;border-radius:4px;background:#f7fcffd9;padding:.2rem .24rem;font-size:.75rem;color:#3d5673}.rw-imp-mini-controls{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.16rem}.rw-imp-mini-controls button{border:1px solid #7694b6;border-radius:4px;background:linear-gradient(180deg,#f8fbff,#dce8f6);color:#21374c;font-size:.72rem;font-weight:700;letter-spacing:.01em;padding:.12rem .2rem} diff --git a/system.json b/system.json new file mode 100644 index 0000000..1dd5536 --- /dev/null +++ b/system.json @@ -0,0 +1,52 @@ +{ + "id": "ringworld", + "title": "Ringworld", + "description": "Ringworld RPG-compatible percentile system (BRP-like)", + "version": "0.1.0", + "url": "https://github.com/slaguru666/Ringworld", + "manifest": "https://raw.githubusercontent.com/slaguru666/Ringworld/main/system.json", + "download": "https://raw.githubusercontent.com/slaguru666/Ringworld/main/dist/ringworld-system.zip", + "compatibility": { + "minimum": "12", + "verified": "13.351", + "maximum": "13" + }, + "authors": [ + { + "name": "Codex" + } + ], + "esmodules": [ + "module/ringworld.mjs" + ], + "styles": [ + "styles/ringworld.css" + ], + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + } + ], + "documentTypes": { + "Actor": { + "explorer": {}, + "creature": {}, + "vehicle": {} + }, + "Item": { + "skill": {}, + "weapon": {}, + "armor": {}, + "gear": {}, + "species": {}, + "pursuit": {} + } + }, + "grid": { + "units": "m" + }, + "primaryTokenAttribute": "system.generalHp.value", + "secondaryTokenAttribute": "system.resources.energy.value" +} diff --git a/template.json b/template.json new file mode 100644 index 0000000..8dc5cd2 --- /dev/null +++ b/template.json @@ -0,0 +1,362 @@ +{ + "Actor": { + "explorer": { + "characteristics": { + "str": 10, + "mas": 10, + "con": 10, + "int": 10, + "pow": 10, + "dex": 10, + "app": 10, + "edu": 10, + "age": 25 + }, + "profile": { + "player": "", + "gender": "", + "chronologicalAge": 25, + "physiologicalAge": 25, + "species": "Human", + "gravity": "normal", + "homeworld": "", + "creditRating": 0 + }, + "derived": { + "actionRanking": 1, + "move": 3, + "run": 6, + "currentMove": 3, + "currentRun": 6 + }, + "encumbrance": { + "rawLoadKg": 0, + "effectiveLoadKg": 0, + "limitKg": 10, + "overLimitKg": 0, + "actionRankingPenalty": 0, + "overLimit": false, + "gravityMultiplier": 1 + }, + "combat": { + "damageMod": "0" + }, + "derivedRolls": { + "health": 50, + "reasoning": 50, + "luck": 50, + "dodge": 50 + }, + "skillRootMax": { + "agility": 50, + "communication": 50, + "knowledge": 50, + "perception": 50, + "technical": 50 + }, + "finance": { + "money": 0, + "inBank": 0 + }, + "notes": { + "defects": "", + "habits": "" + }, + "sheetToolsUnlocked": false, + "characterBuilder": { + "pursuits": [], + "allocations": {}, + "baseBySkillId": {} + }, + "unconsciousLevel": 0, + "generalHp": { + "value": 20, + "max": 20 + }, + "resources": { + "energy": { + "value": 20, + "max": 20 + } + }, + "conditions": { + "prone": false, + "unconscious": false, + "dead": false, + "dying": false, + "unusableLimbs": { + "rArm": false, + "lArm": false, + "rLeg": false, + "lLeg": false + } + }, + "hitLocationSchema": "auto", + "hitLocations": { + "rLeg": { + "label": "Right Leg", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + }, + "lLeg": { + "label": "Left Leg", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + }, + "abdomen": { + "label": "Abdomen", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + }, + "chest": { + "label": "Chest", + "hp": { "value": 7, "max": 7 }, + "armor": 0 + }, + "rArm": { + "label": "Right Arm", + "hp": { "value": 5, "max": 5 }, + "armor": 0 + }, + "lArm": { + "label": "Left Arm", + "hp": { "value": 5, "max": 5 }, + "armor": 0 + }, + "head": { + "label": "Head", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + } + } + }, + "creature": { + "characteristics": { + "str": 10, + "mas": 10, + "con": 10, + "int": 10, + "pow": 10, + "dex": 10, + "app": 10, + "edu": 10, + "age": 10 + }, + "profile": { + "player": "", + "gender": "", + "chronologicalAge": 10, + "physiologicalAge": 10, + "species": "", + "gravity": "normal", + "homeworld": "", + "creditRating": 0 + }, + "derived": { + "actionRanking": 1, + "move": 3, + "run": 6, + "currentMove": 3, + "currentRun": 6 + }, + "encumbrance": { + "rawLoadKg": 0, + "effectiveLoadKg": 0, + "limitKg": 10, + "overLimitKg": 0, + "actionRankingPenalty": 0, + "overLimit": false, + "gravityMultiplier": 1 + }, + "combat": { + "damageMod": "0" + }, + "derivedRolls": { + "health": 50, + "reasoning": 50, + "luck": 50, + "dodge": 50 + }, + "skillRootMax": { + "agility": 50, + "communication": 50, + "knowledge": 50, + "perception": 50, + "technical": 50 + }, + "finance": { + "money": 0, + "inBank": 0 + }, + "notes": { + "defects": "", + "habits": "" + }, + "unconsciousLevel": 0, + "generalHp": { + "value": 20, + "max": 20 + }, + "resources": { + "energy": { + "value": 0, + "max": 0 + } + }, + "conditions": { + "prone": false, + "unconscious": false, + "dead": false, + "dying": false, + "unusableLimbs": { + "rArm": false, + "lArm": false, + "rLeg": false, + "lLeg": false + } + }, + "hitLocationSchema": "auto", + "hitLocations": { + "rLeg": { + "label": "Right Leg", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + }, + "lLeg": { + "label": "Left Leg", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + }, + "abdomen": { + "label": "Abdomen", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + }, + "chest": { + "label": "Chest", + "hp": { "value": 7, "max": 7 }, + "armor": 0 + }, + "rArm": { + "label": "Right Arm", + "hp": { "value": 5, "max": 5 }, + "armor": 0 + }, + "lArm": { + "label": "Left Arm", + "hp": { "value": 5, "max": 5 }, + "armor": 0 + }, + "head": { + "label": "Head", + "hp": { "value": 6, "max": 6 }, + "armor": 0 + } + } + }, + "vehicle": { + "stats": { + "hp": { + "value": 30, + "max": 30 + }, + "armor": 0, + "speed": 0, + "power": { + "value": 0, + "max": 0 + } + } + } + }, + "Item": { + "skill": { + "value": 0, + "category": "", + "specialization": "" + }, + "weapon": { + "equipped": true, + "stowed": false, + "weight": 0, + "linkedSkill": "", + "linkedSkillId": "", + "attackType": "ranged", + "damageFormula": "1d6", + "range": { + "close": "", + "medium": "", + "long": "" + }, + "energyUsedPerAttack": 0, + "ammoUsedPerAttack": 0, + "powerSource": "actor", + "powerPackId": "", + "notes": "" + }, + "armor": { + "equipped": true, + "stowed": false, + "weight": 0, + "value": 0, + "coverage": { + "rLeg": false, + "lLeg": false, + "abdomen": false, + "chest": false, + "rArm": false, + "lArm": false, + "head": false + }, + "perLocation": { + "rLeg": 0, + "lLeg": 0, + "abdomen": 0, + "chest": 0, + "rArm": 0, + "lArm": 0, + "head": 0 + }, + "notes": "" + }, + "gear": { + "equipped": true, + "stowed": false, + "weight": 0, + "cost": 0, + "energyUse": 0, + "powerSupplyNotes": "", + "isPowerPack": false, + "energy": { + "value": 0, + "max": 0 + }, + "notes": "" + }, + "species": { + "playable": true, + "gravity": "normal", + "homeworld": "", + "modifiers": { + "str": 0, + "mas": 0, + "con": 0, + "int": 0, + "pow": 0, + "dex": 0, + "app": 0, + "edu": 0, + "age": 0 + }, + "notes": "" + }, + "pursuit": { + "wealthValue": 0, + "occupationPointsPerYear": 20, + "rollRange": "", + "fixedSkills": [], + "optionalSkillGroups": [], + "notes": "" + } + } +} diff --git a/templates/actor/creature-sheet.hbs b/templates/actor/creature-sheet.hbs new file mode 100644 index 0000000..b279af0 --- /dev/null +++ b/templates/actor/creature-sheet.hbs @@ -0,0 +1,62 @@ +
+
+ +
+ + + / {{system.generalHp.max}} + +
+
+ +
+

Hit Locations

+
+ {{#each hitLocations as |loc|}} +
+ {{loc.label}} (d20 {{loc.rollRange}}) + + / {{loc.hp.max}} + +
+ {{/each}} +
+
+ +
+
+ + +
+ +

Skills

+
    + {{#each skills as |item|}} +
  1. +
    {{item.name}}
    +
    {{item.system.value}}%
    +
    + + + +
    +
  2. + {{/each}} +
+ +

Weapons

+
    + {{#each weapons as |item|}} +
  1. +
    {{item.name}}
    +
    {{item.system.damageFormula}}
    +
    + + + +
    +
  2. + {{/each}} +
+
+
diff --git a/templates/actor/explorer-sheet.hbs b/templates/actor/explorer-sheet.hbs new file mode 100644 index 0000000..3300777 --- /dev/null +++ b/templates/actor/explorer-sheet.hbs @@ -0,0 +1,491 @@ +
+
+
+

Ringworld Explorer Sheet

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + +
+ +
+
+
+
+
Damage Mod
+
+
+
+
+ General Hit Points + + + / + + +
+
+
+
+
+ Health Roll + + + % + + +
+
+
+
+
+ Reasoning Roll + + + % + + +
+
+
+
+
+ Luck Roll + + + % + + +
+
+
+
+
+ Dodge Roll + + + % + + +
+
+
+
+
Defects
+
+
+
+
Habits
+
+
+ +
+ + +
+
+
+ +
+
+
+

Hit Locations

+
+ +
+ +
+ +
+
+ {{#each bodyLocations as |loc|}} +
+

{{loc.label}}

+
d20 {{loc.rollRange}}
+ +
AP Total: {{loc.totalArmor}}
+ +
+ {{/each}} +
+
+
+ +
+
+
+

Portrait

+
+ {{actor.name}} +
+ + +
+
+
+
+ + + +
+
+
+
+
+

Weapons

+ +
+
    + {{#each weapons as |item|}} +
  1. +
    {{item.name}}
    +
    {{item.system.linkedSkill}}
    +
    {{item.system.damageFormula}}
    +
    {{item.system.range.close}} / {{item.system.range.medium}} / {{item.system.range.long}}
    +
    + + + +
    +
  2. + {{/each}} +
+
+ +
+
+
+

Armor

+ +
+
    + {{#each armors as |item|}} +
  1. +
    {{item.name}}
    +
    Base AP {{item.system.value}}
    +
    {{#if item.system.stowed}}Stowed{{else}}{{#if item.system.equipped}}Equipped{{else}}Carried{{/if}}{{/if}}
    +
    + + +
    +
  2. + {{/each}} +
+
+
+
+
+ +
+
+ + + Missing: {{missingBaseSkillsCount}} +
+ +
+
+

Character Builder

+ {{#if sheetToolsUnlocked}} +
+ + + +
+ {{else}} + Tools Locked + {{/if}} +
+ +
+
Education (EDU-10)*20: {{builder.education.total}} | Spent {{builder.education.spent}} | Remaining {{builder.education.remaining}}
+
Pursuits (Age-EDU-6)*20: {{builder.pursuits.total}} | Spent {{builder.pursuits.spent}} | Remaining {{builder.pursuits.remaining}}
+
Special Interests INT*10: {{builder.special.total}} | Spent {{builder.special.spent}} | Remaining {{builder.special.remaining}}
+
Pursuit Years: {{builder.pursuitYears.total}} | Allocated {{builder.pursuitYears.spent}} | Remaining {{builder.pursuitYears.remaining}}
+
+ + {{#if sheetToolsUnlocked}} +
+

Pursuit Selections (controls pursuit-only skill spending)

+
+ + + +
+ +
    + {{#each builder.pursuitsList as |entry idx|}} +
  1. + + + + +
  2. + {{/each}} +
+
+ +

Use Allocate Builder Points on each skill row to spend points by pool. Overspending and invalid pool usage are blocked.

+ {{/if}} +
+ +
+ {{#each skillsByCategory as |group|}} +
+

{{group.category}}

+ +
+ Skill + Base % + Current % + Actions +
+ +
    + {{#each group.rows as |row|}} + {{#if row.itemId}} +
  1. +
    + + {{row.name}} +
    +
    {{row.base}}%
    +
    {{row.value}}% {{#if row.allocationTotal}}+{{row.allocationTotal}}{{/if}}
    +
    + {{#if ../../sheetToolsUnlocked}} + + {{/if}} + + + +
    +
  2. + {{else}} +
  3. +
    + + {{row.name}} +
    +
    {{row.base}}%
    +
    -
    +
    + +
    +
  4. + {{/if}} + {{/each}} +
+
+ {{/each}} +
+ +
+ Additional Skills ({{customSkills.length}}) +
+ Skill + Base % + Current % + Actions +
+
    + {{#each customSkills as |item|}} +
  1. +
    {{item.name}}
    +
    -
    +
    {{item.system.value}}% {{#if item.allocationTotal}}+{{item.allocationTotal}}{{/if}}
    +
    + {{#if ../sheetToolsUnlocked}} + + {{/if}} + + + +
    +
  2. + {{/each}} +
+
+
+ +
+
+
+

Weapons

+ +
+
    + {{#each weapons as |item|}} +
  1. +
    {{item.name}}
    +
    {{item.system.weight}}kg
    +
    {{#if item.system.stowed}}Stowed{{else}}{{#if item.system.equipped}}Equipped{{else}}Carried{{/if}}{{/if}} | {{item.system.linkedSkill}}
    +
    {{item.system.damageFormula}}
    +
    {{item.system.range.close}} / {{item.system.range.medium}} / {{item.system.range.long}}
    +
    + + + + + +
    +
  2. + {{/each}} +
+
+
+ +
+
+
+

Armor

+ +
+
    + {{#each armors as |item|}} +
  1. +
    {{item.name}}
    +
    {{item.system.weight}}kg
    +
    Base AP {{item.system.value}}
    +
    {{#if item.system.stowed}}Stowed{{else}}{{#if item.system.equipped}}Equipped{{else}}Carried{{/if}}{{/if}}
    +
    + + + + +
    +
  2. + {{/each}} +
+
+ +
+
+

Equipment

+ +
+
    + {{#each gear as |item|}} +
  1. +
    {{item.name}}
    +
    Weight {{item.system.weight}}
    +
    {{#if item.system.stowed}}Stowed{{else}}{{#if item.system.equipped}}Equipped{{else}}Carried{{/if}}{{/if}} | {{#if item.system.isPowerPack}}Power Pack {{item.system.energy.value}}/{{item.system.energy.max}}{{else}}Gear{{/if}}
    +
    + + + + +
    +
  2. + {{/each}} +
+
+
+
+
diff --git a/templates/actor/vehicle-sheet.hbs b/templates/actor/vehicle-sheet.hbs new file mode 100644 index 0000000..6fe9a64 --- /dev/null +++ b/templates/actor/vehicle-sheet.hbs @@ -0,0 +1,17 @@ +
+
+ +
+ +
+

Vehicle Stats (v1 stub)

+
+ + + + + + +
+
+
diff --git a/templates/chat/attack-card.hbs b/templates/chat/attack-card.hbs new file mode 100644 index 0000000..be1c752 --- /dev/null +++ b/templates/chat/attack-card.hbs @@ -0,0 +1,41 @@ +
+
+ {{actorName}} +
+

{{actorName}} attacks {{targetName}}

+

{{attackOutcomeLabel}}

+
+
+ +
+ + {{#if weaponImg}}{{weaponName}}{{/if}} + Weapon: {{weaponName}} + + + {{#if skillImg}}{{skillName}}{{/if}} + Skill: {{skillName}} + +
+ +

Attack: {{attackRoll}} vs {{attackTarget}}

+ + {{#if isSuccess}} +

+ Hit Location + {{hitLocationLabel}}{{#if hitLocationRoll}} (rolled {{hitLocationRoll}}){{/if}} +

+

+ Total Damage + {{finalDamage}} + ({{rawDamage}} - {{armor}}) +

+ {{#if hasTarget}} +

Remaining Location HP: {{remainingLocationHp}}

+

Remaining General HP: {{remainingGeneralHp}}

+ {{/if}} + {{/if}} + +

Energy: {{energySourceLabel}} spent {{energyCost}}, remaining {{energyRemaining}}

+

{{notes}}

+
diff --git a/templates/chat/skill-roll-card.hbs b/templates/chat/skill-roll-card.hbs new file mode 100644 index 0000000..f0cd178 --- /dev/null +++ b/templates/chat/skill-roll-card.hbs @@ -0,0 +1,18 @@ +
+
+ {{actorName}} +
+

{{actorName}}

+

+ {{#if skillImg}}{{skillName}}{{/if}} + {{skillName}} +

+
+
+

Roll: {{roll}} vs Target: {{target}}

+

+ {{outcomeLabel}} +

+ {{#if isSpecial}}

Special Success.

{{/if}} + {{#if isFumble}}

Fumble.

{{/if}} +
diff --git a/templates/item/armor-sheet.hbs b/templates/item/armor-sheet.hbs new file mode 100644 index 0000000..7620581 --- /dev/null +++ b/templates/item/armor-sheet.hbs @@ -0,0 +1,39 @@ +
+
+ +
+ +
+
+ + + + +
+ +

Coverage

+
+ + + + + + + +
+ +

Per-Location Override (0 = use base value)

+
+ + + + + + + +
+ + + +
+
diff --git a/templates/item/gear-sheet.hbs b/templates/item/gear-sheet.hbs new file mode 100644 index 0000000..f4b911d --- /dev/null +++ b/templates/item/gear-sheet.hbs @@ -0,0 +1,24 @@ +
+
+ +
+ +
+
+ + + + + + + + +
+ + + + + + +
+
diff --git a/templates/item/pursuit-sheet.hbs b/templates/item/pursuit-sheet.hbs new file mode 100644 index 0000000..3f3bdcc --- /dev/null +++ b/templates/item/pursuit-sheet.hbs @@ -0,0 +1,41 @@ +
+
+ +
+ +
+
+ + + +
+ +

Fixed Skills

+
    + {{#each fixedSkills as |skill|}} +
  1. +
    {{skill}}
    +
  2. + {{/each}} +
+ + {{#if optionalGroups.length}} +

Optional Skill Groups

+ {{#each optionalGroups as |group|}} +
+

Choose {{group.choose}}

+
    + {{#each group.skills as |skill|}} +
  1. +
    {{skill}}
    +
  2. + {{/each}} +
+
+ {{/each}} + {{/if}} + + + +
+
diff --git a/templates/item/skill-sheet.hbs b/templates/item/skill-sheet.hbs new file mode 100644 index 0000000..0c2b7b2 --- /dev/null +++ b/templates/item/skill-sheet.hbs @@ -0,0 +1,14 @@ +
+
+ + +
+ +
+
+ + + +
+
+
diff --git a/templates/item/species-sheet.hbs b/templates/item/species-sheet.hbs new file mode 100644 index 0000000..8b9ea64 --- /dev/null +++ b/templates/item/species-sheet.hbs @@ -0,0 +1,35 @@ +
+
+ +
+ +
+
+ + + +
+ +

Characteristic Modifiers

+
+ + + + + + + + + +
+ + + +
+
diff --git a/templates/item/weapon-sheet.hbs b/templates/item/weapon-sheet.hbs new file mode 100644 index 0000000..47d2093 --- /dev/null +++ b/templates/item/weapon-sheet.hbs @@ -0,0 +1,52 @@ +
+
+ + +
+ +
+
+ + + + + + + + + + + + + + +
+ + + +
+
diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8ff5192 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": false, + "skipLibCheck": true, + "allowJs": false, + "noEmit": true, + "types": [ + "@league-of-foundry-developers/foundry-vtt-types" + ] + }, + "include": [ + "src/**/*.ts", + "vite.config.ts" + ] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..40d809e --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,26 @@ +import { resolve } from "node:path"; +import { defineConfig } from "vite"; + +export default defineConfig({ + base: "", + build: { + emptyOutDir: false, + outDir: ".", + sourcemap: true, + lib: { + entry: resolve(__dirname, "src/ringworld.ts"), + formats: ["es"], + fileName: () => "module/ringworld.mjs" + }, + rollupOptions: { + output: { + assetFileNames: (assetInfo) => { + if ((assetInfo.name ?? "").endsWith(".css")) { + return "styles/ringworld.css"; + } + return "assets/[name][extname]"; + } + } + } + } +}); diff --git a/world-data/README.md b/world-data/README.md new file mode 100644 index 0000000..970f95e --- /dev/null +++ b/world-data/README.md @@ -0,0 +1,15 @@ +# Ringworld World Data + +This folder contains a starter world shell: + +- `ringworld-starter/world.json` + +## Usage + +1. Copy `ringworld-starter` into your Foundry `Data/worlds/` folder. +2. Start Foundry and launch the world. +3. Enable the `ringworld-content` module. +4. In Items directory, click **Import Ringworld Content**. + +The world starts empty by design and is meant to be populated using the system/module tools. + diff --git a/world-data/ringworld-starter/world.json b/world-data/ringworld-starter/world.json new file mode 100644 index 0000000..52f9864 --- /dev/null +++ b/world-data/ringworld-starter/world.json @@ -0,0 +1,14 @@ +{ + "title": "Ringworld Starter", + "id": "ringworld-starter", + "system": "ringworld", + "coreVersion": "13.351", + "compatibility": { + "minimum": "12", + "verified": "13" + }, + "systemVersion": "0.1.0", + "description": "Starter world shell for Ringworld. Use Ringworld Content module import button to populate catalog content.", + "lastPlayed": "", + "flags": {} +}