15 Commits

Author SHA1 Message Date
uberwald 6a0f43d45a Use folders
Release Creation / build (release) Successful in 44s
2026-06-08 22:45:11 +02:00
uberwald 19f8e6484d Use folders
Release Creation / build (release) Failing after 43s
2026-06-08 22:34:49 +02:00
uberwald 1286ca663e Use folders
Release Creation / build (release) Successful in 50s
2026-06-08 22:31:31 +02:00
uberwald 3fe5df7390 Use folders 2026-06-08 22:31:10 +02:00
uberwald 9ee6545a3c Various fixes for official release 2026-06-08 22:21:25 +02:00
uberwald 3851a38c9f Various fixes for official release 2026-06-08 22:20:44 +02:00
uberwald 7623123bb5 Fix load/Capacity 2026-06-07 21:32:05 +02:00
uberwald 2fb6bfe444 FIx map
Release Creation / build (release) Successful in 40s
2026-06-02 19:44:57 +02:00
uberwald 53b7e8cc9d Enhance text style 2026-05-29 23:03:37 +02:00
uberwald 7f758afa56 Fix CSS issues and add omen re-roll function
Release Creation / build (release) Successful in 42s
2026-05-23 09:19:32 +02:00
uberwald 7beda6c331 Fix CSS issues and add omen re-roll function 2026-05-23 08:48:29 +02:00
uberwald 12bf771e9d Fixes around character sheets and buttons
Release Creation / build (release) Successful in 44s
2026-05-19 13:30:05 +02:00
uberwald 1b10a77748 Fix as per CSV sheet tracking + creature explanation
Release Creation / build (release) Successful in 46s
2026-05-17 18:01:35 +02:00
uberwald 374854cc8b Fix as per CSV sheet tracking + creature explanation 2026-05-17 17:43:33 +02:00
uberwald a572c66678 FIx compendiums 2026-05-13 22:43:58 +02:00
185 changed files with 4902 additions and 1378 deletions
+10 -10
View File
@@ -50,13 +50,13 @@ jobs:
system.json
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
#- name: Publish to Foundry server
# uses: djlechuck/foundryvtt-publish-package-action@v1
# with:
# token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
# id: fvtt-machine-gods-noxian-expanse
# version: ${{github.event.release.tag_name}}
# manifest: 'https://www.uberwald.me/gitea/public/uberwald/fvtt-machine-gods-noxian-expanse/releases/download/latest/system.json'
# notes: 'https://www.uberwald.me/gitea/uberwald/fvtt-machine-gods-noxian-expanse/releases/download/${{github.event.release.tag_name}}/fvtt-machine-gods-noxian-expanse.zip'
# compatibility-minimum: '13'
# compatibility-verified: '13'
- name: Publish to Foundry server
uses: djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
id: fvtt-machine-gods-noxian-expanse
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/uberwald/fvtt-machine-gods-noxian-expanse/releases/download/${{github.event.release.tag_name}}/system.json'
notes: 'https://www.uberwald.me/gitea/uberwald/fvtt-machine-gods-noxian-expanse/releases/download/${{github.event.release.tag_name}}/fvtt-machine-gods-noxian-expanse.zip'
compatibility-minimum: '14'
compatibility-verified: '14'
+1
View File
@@ -1,3 +1,4 @@
.github/
rules/
node_modules/
.history/
-94
View File
@@ -1,94 +0,0 @@
{
"id": "fvtt-machine-gods-noxian-expanse",
"title": "Machine Gods of the Noxian Expanse",
"description": "Foundry VTT system for Machine Gods of the Noxian Expanse.",
"version": "14..0",
"authors": [
{
"name": "Uberwald"
}
],
"compatibility": {
"minimum": "13",
"verified": "14"
},
"esmodules": [
"fvtt-machine-gods-noxian-expanse.mjs"
],
"styles": [
"css/mgne.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"documentTypes": {
"Actor": {
"character": {
"htmlFields": [
"description",
"notes"
]
},
"creature": {
"htmlFields": [
"description",
"special",
"notes"
]
},
"companion": {
"htmlFields": [
"description",
"notes"
]
}
},
"Item": {
"weapon": {
"htmlFields": [
"description"
]
},
"armor": {
"htmlFields": [
"description"
]
},
"shield": {
"htmlFields": [
"description"
]
},
"equipment": {
"htmlFields": [
"description"
]
},
"resonance-core": {
"htmlFields": [
"description"
]
},
"artifact": {
"htmlFields": [
"description"
]
},
"feature": {
"htmlFields": [
"description"
]
}
}
},
"grid": {
"distance": 10,
"units": "ft"
},
"primaryTokenAttribute": "hp",
"socket": false
}
-94
View File
@@ -1,94 +0,0 @@
{
"id": "fvtt-machine-gods-noxian-expanse",
"title": "Machine Gods of the Noxian Expanse",
"description": "Foundry VTT system for Machine Gods of the Noxian Expanse.",
"version": "14..0",
"authors": [
{
"name": "Uberwald"
}
],
"compatibility": {
"minimum": "13",
"verified": "14"
},
"esmodules": [
"fvtt-machine-gods-noxian-expanse.mjs"
],
"styles": [
"css/mgne.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"documentTypes": {
"Actor": {
"character": {
"htmlFields": [
"description",
"notes"
]
},
"creature": {
"htmlFields": [
"description",
"special",
"notes"
]
},
"companion": {
"htmlFields": [
"description",
"notes"
]
}
},
"Item": {
"weapon": {
"htmlFields": [
"description"
]
},
"armor": {
"htmlFields": [
"description"
]
},
"shield": {
"htmlFields": [
"description"
]
},
"equipment": {
"htmlFields": [
"description"
]
},
"resonance-core": {
"htmlFields": [
"description"
]
},
"artifact": {
"htmlFields": [
"description"
]
},
"feature": {
"htmlFields": [
"description"
]
}
}
},
"grid": {
"distance": 10,
"units": "ft"
},
"primaryTokenAttribute": "hp",
"socket": false
}
-94
View File
@@ -1,94 +0,0 @@
{
"id": "fvtt-machine-gods-noxian-expanse",
"title": "Machine Gods of the Noxian Expanse",
"description": "Foundry VTT system for Machine Gods of the Noxian Expanse.",
"version": "14.0.0",
"authors": [
{
"name": "Uberwald"
}
],
"compatibility": {
"minimum": "13",
"verified": "14"
},
"esmodules": [
"fvtt-machine-gods-noxian-expanse.mjs"
],
"styles": [
"css/mgne.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"documentTypes": {
"Actor": {
"character": {
"htmlFields": [
"description",
"notes"
]
},
"creature": {
"htmlFields": [
"description",
"special",
"notes"
]
},
"companion": {
"htmlFields": [
"description",
"notes"
]
}
},
"Item": {
"weapon": {
"htmlFields": [
"description"
]
},
"armor": {
"htmlFields": [
"description"
]
},
"shield": {
"htmlFields": [
"description"
]
},
"equipment": {
"htmlFields": [
"description"
]
},
"resonance-core": {
"htmlFields": [
"description"
]
},
"artifact": {
"htmlFields": [
"description"
]
},
"feature": {
"htmlFields": [
"description"
]
}
}
},
"grid": {
"distance": 10,
"units": "ft"
},
"primaryTokenAttribute": "hp",
"socket": false
}
-94
View File
@@ -1,94 +0,0 @@
{
"id": "fvtt-machine-gods-noxian-expanse",
"title": "Machine Gods of the Noxian Expanse",
"description": "Foundry VTT system for Machine Gods of the Noxian Expanse.",
"version": "14.0.0",
"authors": [
{
"name": "Uberwald"
}
],
"compatibility": {
"minimum": "14",
"verified": "14"
},
"esmodules": [
"fvtt-machine-gods-noxian-expanse.mjs"
],
"styles": [
"css/mgne.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"documentTypes": {
"Actor": {
"character": {
"htmlFields": [
"description",
"notes"
]
},
"creature": {
"htmlFields": [
"description",
"special",
"notes"
]
},
"companion": {
"htmlFields": [
"description",
"notes"
]
}
},
"Item": {
"weapon": {
"htmlFields": [
"description"
]
},
"armor": {
"htmlFields": [
"description"
]
},
"shield": {
"htmlFields": [
"description"
]
},
"equipment": {
"htmlFields": [
"description"
]
},
"resonance-core": {
"htmlFields": [
"description"
]
},
"artifact": {
"htmlFields": [
"description"
]
},
"feature": {
"htmlFields": [
"description"
]
}
}
},
"grid": {
"distance": 10,
"units": "ft"
},
"primaryTokenAttribute": "hp",
"socket": false
}
-94
View File
@@ -1,94 +0,0 @@
{
"id": "fvtt-machine-gods-noxian-expanse",
"title": "Machine Gods of the Noxian Expanse",
"description": "Foundry VTT system for Machine Gods of the Noxian Expanse.",
"version": "14.0.0",
"authors": [
{
"name": "Uberwald"
}
],
"compatibility": {
"minimum": "14",
"verified": "14"
},
"esmodules": [
"fvtt-machine-gods-noxian-expanse.mjs"
],
"styles": [
"css/mgne.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"documentTypes": {
"Actor": {
"character": {
"htmlFields": [
"description",
"notes"
]
},
"creature": {
"htmlFields": [
"description",
"special",
"notes"
]
},
"companion": {
"htmlFields": [
"description",
"notes"
]
}
},
"Item": {
"weapon": {
"htmlFields": [
"description"
]
},
"armor": {
"htmlFields": [
"description"
]
},
"shield": {
"htmlFields": [
"description"
]
},
"equipment": {
"htmlFields": [
"description"
]
},
"resonance-core": {
"htmlFields": [
"description"
]
},
"artifact": {
"htmlFields": [
"description"
]
},
"feature": {
"htmlFields": [
"description"
]
}
}
},
"grid": {
"distance": 10,
"units": "ft"
},
"primaryTokenAttribute": "hp",
"socket": false
}
+13
View File
@@ -0,0 +1,13 @@
This is the official Foundry module for Machine Gods of the Noxian Expanse
Core Rules https://blackoathgames.com/machine-gods-of-the-noxian-expanse
In this release, you will find:
- All the necessary tools to create your Reclaimer and get them ready to explore the Expanse
- Tools to set up a game of Machine Gods, with everything in place to add your own gear, creatures, and more
- Automated rolls for all the core functionality of the game (Ability checks, Resonance Cores, Weapons, etc.)
- Full support for the game's mechanics, based on Mörk Borg
A game by Blackoath Entertainment https://blackoathgames.com/
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

+13
View File
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<!-- Claw / trait mark: three slash marks with a radiating spark -->
<g fill="none" stroke="#c0392b" stroke-linecap="round" stroke-linejoin="round">
<!-- Three diagonal claw marks -->
<line x1="22" y1="20" x2="48" y2="62" stroke-width="7" opacity="0.9"/>
<line x1="36" y1="18" x2="58" y2="60" stroke-width="7" opacity="0.75"/>
<line x1="50" y1="18" x2="68" y2="58" stroke-width="7" opacity="0.6"/>
<!-- Bolt / energy accent -->
<polyline points="60,30 52,50 64,50 54,72" stroke="#e74c3c" stroke-width="4" opacity="0.85"/>
</g>
<!-- Outer ring frame -->
<circle cx="50" cy="50" r="46" fill="none" stroke="#7f1d1d" stroke-width="3" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

+13
View File
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<!-- Resonation icon: stylised signal/energy wave within a circuit frame -->
<circle cx="50" cy="50" r="46" fill="none" stroke="#c8a94a" stroke-width="3" opacity="0.85"/>
<circle cx="50" cy="50" r="36" fill="none" stroke="#c8a94a" stroke-width="1.5" opacity="0.45"/>
<!-- Wave / resonation lines -->
<path d="M18 50 Q26 34 34 50 Q42 66 50 50 Q58 34 66 50 Q74 66 82 50"
fill="none" stroke="#dd9b2a" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Corner circuit nodes -->
<circle cx="50" cy="18" r="4" fill="#c8a94a" opacity="0.9"/>
<circle cx="50" cy="82" r="4" fill="#c8a94a" opacity="0.9"/>
<line x1="50" y1="22" x2="50" y2="37" stroke="#c8a94a" stroke-width="2" opacity="0.6"/>
<line x1="50" y1="63" x2="50" y2="78" stroke="#c8a94a" stroke-width="2" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+1040 -174
View File
File diff suppressed because it is too large Load Diff
+130 -4
View File
@@ -20,6 +20,7 @@ Hooks.once("init", () => {
character: models.MGNECharacter,
creature: models.MGNECreature,
companion: models.MGNECompanion,
party: models.MGNEParty,
}
CONFIG.Combat.documentClass = documents.MGNECombat
@@ -32,6 +33,7 @@ Hooks.once("init", () => {
"resonance-core": models.MGNEResonanceCore,
artifact: models.MGNEArtifact,
feature: models.MGNEFeature,
"creature-trait": models.MGNECreatureTrait,
}
foundry.applications.sheets.ActorSheetV2 && foundry.documents.collections.Actors.unregisterSheet(
@@ -43,6 +45,7 @@ Hooks.once("init", () => {
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECharacterSheet, { types: ["character"], makeDefault: true, label: SYSTEM.actorTypes.character.label })
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECreatureSheet, { types: ["creature"], makeDefault: true, label: SYSTEM.actorTypes.creature.label })
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECompanionSheet, { types: ["companion"], makeDefault: true, label: SYSTEM.actorTypes.companion.label })
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNEPartySheet, { types: ["party"], makeDefault: true, label: SYSTEM.actorTypes.party.label })
foundry.applications.sheets.ItemSheetV2 && foundry.documents.collections.Items.unregisterSheet(
"core",
@@ -57,16 +60,63 @@ Hooks.once("init", () => {
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEResonanceCoreSheet, { types: ["resonance-core"], makeDefault: true, label: SYSTEM.itemTypes["resonance-core"].label })
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEArtifactSheet, { types: ["artifact"], makeDefault: true, label: SYSTEM.itemTypes.artifact.label })
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEFeatureSheet, { types: ["feature"], makeDefault: true, label: SYSTEM.itemTypes.feature.label })
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNECreatureTraitSheet, { types: ["creature-trait"], makeDefault: true, label: SYSTEM.itemTypes["creature-trait"].label })
Handlebars.registerHelper("isEqual", (left, right) => left === right)
Handlebars.registerHelper("includes", (collection, value) => {
if (!collection) return false
if (collection instanceof Set) return collection.has(value)
if (Array.isArray(collection)) return collection.includes(value)
return false
})
})
Hooks.once("setup", () => {
localizeSystemConfig()
})
Hooks.once("ready", () => {
// Strip phantom null-_id entries from all compendium indices.
// Foundry V14 LevelDatabase creates !items!null entries on open under Node24.
if (typeof CompendiumCollection !== "undefined") {
const orig = CompendiumCollection.prototype.getIndex;
CompendiumCollection.prototype.getIndex = async function(opts) {
const r = await orig.call(this, opts);
for (const k of [...this.index.keys()]) { if (!k) this.index.delete(k); }
return r;
};
}
});
Hooks.once("ready", async () => {
console.info(`${SYSTEM_ID} | Ready`)
// Clean anything already in the index before we patched
for (const pack of game.packs) {
for (const k of [...pack.index.keys()]) { if (!k) pack.index.delete(k); }
}
if (!game.user.isGM) return
if (game.scenes.getName("Noxian Expanse")) return
const pack = game.packs.get("fvtt-machine-gods-noxian-expanse.scenes")
if (!pack) {
console.warn(`${SYSTEM_ID} | Scenes compendium not found`)
return
}
const index = await pack.getIndex()
const entry = index.find(s => s.name === "Noxian Expanse")
if (!entry) {
console.warn(`${SYSTEM_ID} | Noxian Expanse scene not found in compendium`)
return
}
const doc = await pack.getDocument(entry._id)
if (doc) {
const scene = await Scene.create(doc.toObject(), { keepId: false })
if (scene) {
await scene.activate()
}
}
})
Hooks.on("deleteCombat", (combat) => {
@@ -82,7 +132,7 @@ Hooks.on("deleteCombat", (combat) => {
if (!cores.length) return
const lines = cores.map(c => `<li><strong>${c.parent.name}</strong> — ${c.name} (${c.system.usageDie})</li>`).join("")
ChatMessage.create({
content: `<article class="mgne-chat-card mode-check"><div class="chat-card-body"><p class="chat-special">⚙️ ${game.i18n.localize("MGNE.Notification.RollUsageDiceReminder")}</p><ul style="margin:.3rem 0 0 1rem;padding:0">${lines}</ul></div></article>`,
content: `<article class="mgne-chat-card"><div class="chat-card-body" style="color:#ccb292;font-size:0.72rem;line-height:1.4"><p style="margin:0">⚙️ ${game.i18n.localize("MGNE.Notification.RollUsageDiceReminder")}</p><ul style="margin:.3rem 0 0 1rem;padding:0;color:#ccb292;font-size:0.72rem">${lines}</ul></div></article>`,
})
})
@@ -111,6 +161,17 @@ Hooks.on("renderChatMessageHTML", (message, element) => {
const root = element instanceof HTMLElement ? element : element?.[0]
if (!root) return
// Dice tooltip toggle
root.querySelectorAll("[data-action='toggle-dice-tooltip']").forEach(trigger => {
trigger.addEventListener("click", () => {
const tooltip = trigger.closest(".chat-card-body")?.querySelector(".chat-dice-tooltip")
if (!tooltip) return
const isHidden = tooltip.hidden
tooltip.hidden = !isHidden
trigger.classList.toggle("tooltip-open", isHidden)
})
})
root.querySelectorAll(".mgne-roll-damage-btn").forEach(btn => {
btn.addEventListener("click", async () => {
const actorId = btn.dataset.actorId
@@ -125,6 +186,67 @@ Hooks.on("renderChatMessageHTML", (message, element) => {
})
})
root.querySelectorAll(".mgne-omen-reroll-btn").forEach(btn => {
btn.addEventListener("click", async () => {
const actorId = btn.dataset.actorId
const actor = game.actors.get(actorId)
if (!actor) {
ui.notifications.warn(game.i18n.localize("MGNE.Notification.ActorNotFound"))
return
}
const currentOmens = actor.system.omens?.current ?? 0
if (currentOmens === 0) {
ui.notifications.warn(game.i18n.localize("MGNE.Notification.NoOmensLeft"))
return
}
// Save original state for rollback
const originalOmens = currentOmens
// Disable button and show loading state
const originalInnerHTML = btn.innerHTML
btn.disabled = true
btn.innerHTML = '<i class="fa-solid fa-star"></i> ' + game.i18n.localize("MGNE.Common.Processing")
try {
// Spend the omen
await actor.update({ "system.omens.current": Math.max(0, currentOmens - 1) })
// Re-roll with the same parameters
const abilityId = btn.dataset.abilityId
const label = btn.dataset.label
const baseDR = parseInt(btn.dataset.baseDr ?? "12", 10)
const modifier = parseInt(btn.dataset.modifier ?? "0", 10)
const rollType = btn.dataset.rollType ?? "check"
const itemId = btn.dataset.itemId
const item = itemId ? actor.items.get(itemId) : null
await MGNERoll.promptCheck({
actor,
abilityId,
label,
baseDR,
modifier,
rollType,
item,
})
} catch (error) {
// Rollback omen spend on error
const currentOmensAfterError = actor.system.omens?.current ?? 0
if (currentOmensAfterError < originalOmens) {
await actor.update({ "system.omens.current": originalOmens })
}
ui.notifications.error(game.i18n.localize("MGNE.Notification.RollError"))
console.error("Omen re-roll failed:", error)
} finally {
// Restore button state
btn.disabled = false
btn.innerHTML = originalInnerHTML
}
})
})
root.querySelectorAll(".mgne-apply-damage-select").forEach(select => {
const isAllowed = game.user.isGM || message.isAuthor
if (!isAllowed) {
@@ -135,6 +257,7 @@ Hooks.on("renderChatMessageHTML", (message, element) => {
const card = select.closest(".mgne-chat-card")
const damageTotal = parseInt(card?.dataset.damageTotal ?? "0", 10) || 0
const damageCritical = card?.dataset.damageCritical === "true"
const targetActorId = card?.dataset.targetActorId ?? null
const tokens = canvas.scene?.tokens.contents ?? []
for (const token of tokens) {
@@ -142,6 +265,9 @@ Hooks.on("renderChatMessageHTML", (message, element) => {
const opt = document.createElement("option")
opt.value = token.id
opt.textContent = token.name
if (targetActorId && token.actor.id === targetActorId) {
opt.selected = true
}
select.appendChild(opt)
}
@@ -185,7 +311,7 @@ Hooks.on("renderChatMessageHTML", (message, element) => {
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: targetActor }),
rolls: [reduceRoll],
content: `<article class="mgne-chat-card mode-check"><div class="chat-card-body"><p class="chat-special">${reduceMsg}</p></div></article>`,
content: `<article class="mgne-chat-card"><div class="chat-card-body"><p style="margin:0;color:#ccb292;font-size:0.72rem;line-height:1.4">${reduceMsg}</p></div></article>`,
})
}
}
+164 -37
View File
@@ -4,7 +4,8 @@
"ActorTypes": {
"character": "Character",
"creature": "Creature",
"companion": "Companion"
"companion": "Companion",
"party": "Party"
},
"ItemTypes": {
"weapon": "Weapon",
@@ -13,7 +14,8 @@
"equipment": "Equipment",
"resonance-core": "Resonance Core",
"artifact": "Artifact",
"feature": "Feature"
"feature": "Feature",
"creature-trait": "Creature Trait"
},
"Abilities": {
"agility": "Agility",
@@ -37,6 +39,24 @@
"melee": "Melee",
"ranged": "Ranged"
},
"WeaponProperties": {
"ammo": "Ammo",
"awkward": "Awkward",
"binding": "Binding",
"durant": "Durant",
"finesse": "Finesse",
"fling": "Fling",
"fragile": "Fragile",
"glinting": "Glinting",
"overbearing": "Overbearing",
"parrying": "Parrying",
"precise": "Precise",
"razored": "Razored",
"ringing": "Ringing",
"two-handed": "Two-Handed",
"unwieldy": "Unwieldy",
"versatile": "Versatile"
},
"Resonations": {
"accelerate": "Accelerate",
"blast": "Blast",
@@ -71,7 +91,9 @@
"daily": "Daily Resources",
"equipment": "Equipment",
"features": "Features",
"notes": "Notes"
"notes": "Notes",
"members": "Members",
"loot": "Loot"
},
"Character": {
"Background": "Background",
@@ -92,6 +114,8 @@
"ResonancePerDay": "Resonance per Day",
"ArtifactSync": "Artifact Sync",
"CarryingCapacity": "Carrying Capacity",
"Load": "Load",
"CapacityModifier": "Carrying Capacity",
"Rations": "Rations",
"Kiffol": "Kiffol",
"Weapons": "Weapons",
@@ -200,6 +224,9 @@
"carryCapacity": {
"label": "Carrying Capacity"
},
"carryCapacityModifier": {
"label": "Capacity Modifier"
},
"rations": {
"label": "Rations"
},
@@ -231,22 +258,23 @@
},
"Creature": {
"Special": "Special",
"Traits": "Traits",
"AddTrait": "Add Trait",
"Type": "Type",
"Number": "Number",
"ActionTable": "Action Table",
"RollAction": "Roll Action",
"DropTableHint": "Drop a Roll Table here to link it",
"ClearTable": "Clear",
"OpenTable": "Open Table",
"NoTableLinked": "No action table is linked to this creature.",
"TableNotFound": "The linked action table could not be found.",
"Types": {
"Human": "Human",
"Construct": "Construct",
"Animal": "Animal"
},
"FIELDS": {
"abilities": {
"label": "Abilities",
"agility": {
"label": "Agility"
},
"presence": {
"label": "Presence"
},
"strength": {
"label": "Strength"
},
"toughness": {
"label": "Toughness"
}
},
"hp": {
"label": "HP",
"value": {
@@ -265,20 +293,17 @@
"label": "Armor Die"
}
},
"attack": {
"label": "Attack",
"damage": {
"label": "Damage"
}
"creatureType": {
"label": "Type"
},
"number": {
"label": "Number"
},
"actionTableUuid": {
"label": "Action Table"
},
"description": {
"label": "Description"
},
"special": {
"label": "Special"
},
"notes": {
"label": "Notes"
}
}
},
@@ -288,6 +313,9 @@
"Specialty": "Specialty",
"AdventuringBehavior": "Adventuring Behavior",
"CombatBehavior": "Combat Behavior",
"RollBehavior": "Roll Behavior",
"NoTableLinked": "No behavior table is linked.",
"TableNotFound": "The linked behavior table could not be found.",
"FIELDS": {
"abilities": {
"label": "Abilities",
@@ -337,11 +365,11 @@
"specialtyText": {
"label": "Specialty"
},
"adventuringBehavior": {
"label": "Adventuring Behavior"
"adventuringBehaviorUuid": {
"label": "Adventuring Behavior Table"
},
"combatBehavior": {
"label": "Combat Behavior"
"combatBehaviorUuid": {
"label": "Combat Behavior Table"
},
"upkeep": {
"label": "Upkeep"
@@ -354,6 +382,27 @@
}
}
},
"Party": {
"Members": "Members",
"Loot": "Shared Loot",
"Credits": "Credits",
"MoveUp": "Move Up",
"MoveDown": "Move Down",
"RemoveMember": "Remove Member",
"DropMemberHint": "Drop a Character or Companion actor here to add them to the party.",
"DropLootHint": "Drop items here to add them to the shared loot.",
"FIELDS": {
"memberRefs": {
"label": "Members"
},
"credits": {
"label": "Credits"
},
"notes": {
"label": "Notes"
}
}
},
"DataModel": {
"abilities": {
"agility": {
@@ -373,6 +422,7 @@
"Common": {
"Attack": "Attack",
"ArmorDie": "Armor Die",
"ArmorSave": "Armor Save",
"ArtifactId": "Artifact Id",
"Broken": "Broken",
"BurnedOut": "Burned Out",
@@ -383,6 +433,7 @@
"Current": "Current",
"Damage": "Damage",
"Depleted": "Depleted",
"Durability": "Durability",
"Defense": "Defense",
"Delete": "Delete",
"Description": "Description",
@@ -405,6 +456,7 @@
"QuantityShort": "Qty",
"Range": "Range",
"Resonation": "Resonation",
"Processing": "Processing...",
"Roll": "Roll",
"No": "No",
"Subtype": "Subtype",
@@ -416,15 +468,23 @@
"Unsynchronized": "Unsynchronized",
"Usage": "Usage",
"UsageDie": "Usage Die",
"DurabilityDie": "Durability Die",
"RollDurability": "Roll Durability",
"Used": "Used",
"Value": "Value"
"Value": "Value",
"Weight": "Weight",
"Name": "Name",
"Type": "Type"
},
"Empty": {
"NoArtifacts": "No artifacts yet.",
"NoEquipment": "No equipment yet.",
"NoFeatures": "No features yet.",
"NoResonanceCores": "No resonance cores yet.",
"NoWeapons": "No weapons yet."
"NoTraits": "No traits yet.",
"NoWeapons": "No weapons yet.",
"NoMembers": "No members yet. Drop a character or companion here.",
"NoLoot": "No shared loot yet. Drop items here."
},
"RulesSnapshot": {
"Checks": "Checks use d20 + ability vs DR.",
@@ -457,11 +517,16 @@
"StudyHelp": "Each action lowers the target number by 1, to a minimum of 2+."
},
"Notification": {
"ActorNotFound": "Actor not found.",
"ActorOrItemNotFound": "Actor or item not found for damage roll.",
"CannotSyncMore": "{actor} cannot synchronize any more artifacts today.",
"NoOmensLeft": "No omens left to spend!",
"RollError": "Failed to re-roll. Omen refunded.",
"ItemBroken": "{item} is broken.",
"ItemBurnedOut": "{item} is burned out.",
"ItemDepleted": "{item} is already depleted.",
"ItemDurabilityDepleted": "{item} durability is already depleted.",
"NoArmorEquipped": "No armor or shield equipped.",
"ResonancePerDayReached": "{actor} has already used all resonations for today.",
"ResonationBlocked": "{actor} cannot invoke Resonations — feedback block active (1 hour). Clear it manually once the hour has passed.",
"ResonationFeedbackBlocked": "Feedback! {actor} suffers D2 damage (bypasses armor) and cannot invoke Resonations for 1 hour.",
@@ -473,6 +538,7 @@
"ApplyDamageTo": "Apply {amount} damage to {target}",
"AppliedDamageText": "Applied damage: {amount}.",
"ArmorAbsorbed": "Armor absorbed {amount}.",
"ArmorSave": "Armor Save",
"ArmorDegradedCritical": "Critical: {item} armor downgraded to {die}.",
"ArmorNothingToDegrade": "Critical: no armor to downgrade.",
"AttackFumble": "Attack fumble: the weapon breaks.",
@@ -504,6 +570,9 @@
"ItemAttackLabel": "{item} Attack",
"ItemDamageLabel": "{item} Damage",
"ItemNowDepleted": "The item is depleted.",
"ItemNowBroken": "The item is now broken.",
"ItemDurabilityLabel": "{item} Durability",
"DurabilityLabel": "{item} Durability Check",
"ItemUsageLabel": "{item} Usage",
"MoraleBrokenText": "The actor breaks down and may flee, surrender, or grant +d4 damage to opponents.",
"MoraleCheck": "Morale Check",
@@ -524,6 +593,7 @@
"OutcomeSuccess": "Success",
"QuickRestLabel": "{actor} Takes a Quick Rest",
"RestoredHP": "Restored {amount} HP",
"RerollWithOmen": "Reroll with Omen",
"RollDamage": "Roll Damage",
"TakesDamageLabel": "{actor} Takes Damage",
"TargetName": "Target: {target}",
@@ -536,8 +606,10 @@
"Mode": {
"action": "Action",
"apply-damage": "Damage",
"armor": "Armor",
"check": "Check",
"damage": "Damage",
"durability": "Durability",
"flee": "Fleeing",
"generic": "Roll",
"morale": "Morale",
@@ -573,6 +645,12 @@
},
"broken": {
"label": "Broken"
},
"durabilityDie": {
"label": "Durability Die"
},
"weight": {
"label": "Weight"
}
}
},
@@ -592,6 +670,12 @@
},
"broken": {
"label": "Broken"
},
"durabilityDie": {
"label": "Durability Die"
},
"weight": {
"label": "Weight"
}
}
},
@@ -611,6 +695,12 @@
},
"broken": {
"label": "Broken"
},
"durabilityDie": {
"label": "Durability Die"
},
"weight": {
"label": "Weight"
}
}
},
@@ -636,6 +726,15 @@
},
"consumable": {
"label": "Consumable"
},
"broken": {
"label": "Broken"
},
"durabilityDie": {
"label": "Durability Die"
},
"weight": {
"label": "Weight"
}
}
},
@@ -652,6 +751,15 @@
},
"burnedOut": {
"label": "Burned Out"
},
"broken": {
"label": "Broken"
},
"durabilityDie": {
"label": "Durability Die"
},
"weight": {
"label": "Weight"
}
}
},
@@ -674,6 +782,12 @@
},
"synchronizedTo": {
"label": "Synchronized To"
},
"durabilityDie": {
"label": "Durability Die"
},
"weight": {
"label": "Weight"
}
}
},
@@ -684,15 +798,27 @@
},
"featureId": {
"label": "Feature Id"
},
"capacity": {
"label": "Capacity"
}
}
},
"CreatureTrait": {
"Trigger": "Trigger",
"TriggerPlaceholder": "e.g. When the creature attacks, on a critical hit…",
"FIELDS": {
"description": { "label": "Description" },
"trigger": { "label": "Trigger" }
}
}
},
"TYPES": {
"Actor": {
"character": "Character",
"creature": "Creature",
"companion": "Companion"
"companion": "Companion",
"party": "Party"
},
"Item": {
"weapon": "Weapon",
@@ -701,7 +827,8 @@
"equipment": "Equipment",
"resonance-core": "Resonance Core",
"artifact": "Artifact",
"feature": "Feature"
"feature": "Feature",
"creature-trait": "Creature Trait"
}
}
}
+93 -11
View File
@@ -19,7 +19,12 @@
.window-header {
background:
linear-gradient(90deg, fade(@blood, 40%), fade(@ember, 18%) 25%, transparent 70%),
linear-gradient(
90deg,
fade(@blood, 40%),
fade(@ember, 18%) 25%,
transparent 70%
),
linear-gradient(180deg, color-mix(in srgb, @bg-char 80%, black), @bg-void);
border-bottom: 1px solid fade(@gold-acid, 35%);
color: @bone;
@@ -64,7 +69,11 @@
.window-content {
background:
radial-gradient(circle at top left, fade(@ember, 12%), transparent 28%),
radial-gradient(circle at top right, fade(@verdigris, 10%), transparent 24%),
radial-gradient(
circle at top right,
fade(@verdigris, 10%),
transparent 24%
),
linear-gradient(180deg, fade(@bg-char, 30%), fade(@bg-void, 28%)),
url("@{page-bg-url}") center top / cover no-repeat,
linear-gradient(180deg, @bg-char, @bg-void 120%);
@@ -107,8 +116,7 @@
border: 1px solid fade(@bone, 22%);
border-radius: @radius-sm;
background:
linear-gradient(180deg, fade(@bone, 4%), transparent 60%),
@bg-input;
linear-gradient(180deg, fade(@bone, 4%), transparent 60%), @bg-input;
color: @parchment;
padding: 0.4rem 0.52rem;
box-shadow: inset 0 1px 0 fade(white, 4%);
@@ -116,7 +124,9 @@
&:focus {
outline: none;
border-color: fade(@ember-bright, 70%);
box-shadow: 0 0 0 1px fade(@ember-bright, 25%), inset 0 1px 0 fade(white, 6%);
box-shadow:
0 0 0 1px fade(@ember-bright, 25%),
inset 0 1px 0 fade(white, 6%);
}
}
@@ -131,7 +141,10 @@
calc(100% - 11px) calc(50% - 2px),
calc(100% - 7px) calc(50% - 2px),
0 0;
background-size: 4px 4px, 4px 4px, auto;
background-size:
4px 4px,
4px 4px,
auto;
background-repeat: no-repeat;
}
@@ -147,16 +160,61 @@
.application.mgne label {
.caps-heading();
color: @label-ink;
color: @bone;
font-size: 0.74rem;
}
// Item sheets: dark labels for parchment backgrounds
.application.mgne.item-sheet label,
.application.mgne.item-sheet .item-form-row > label {
color: @bg-char;
}
.application.mgne fieldset {
.panel-shell();
.ornate-frame();
margin: 0;
}
// Character sheet: Rules Snapshot fieldset text - dark color for parchment bg
.application.mgne.character fieldset p {
color: @bg-char;
}
// ProseMirror / toggled editor inside fieldset — enforce contrast on dark bg
.application.mgne fieldset {
.editor,
.editor-content,
.ProseMirror,
.document-editor {
color: lighten(@parchment, 8%);
background: transparent;
}
.editor-content,
.ProseMirror {
p,
li {
color: lighten(@parchment, 8%);
}
h1,
h2,
h3 {
color: @gold-acid;
}
.is-empty::before {
color: fade(@gold-acid, 40%);
}
}
.editor-menu button {
color: @bone;
background: fade(@bg-void, 55%);
&:hover {
color: lighten(@parchment, 8%);
background: fade(@gold-acid, 20%);
}
}
}
.application.mgne legend {
.caps-heading();
color: @gold-acid;
@@ -178,17 +236,17 @@
}
.application.mgne .empty-state {
color: fade(@ash, 94%);
color: @bg-char;
font-style: italic;
letter-spacing: 0.03em;
}
.application.mgne .rollable {
color: @ember-bright;
text-shadow: 0 0 12px fade(@ember, 18%);
color: lighten(@parchment, 8%);
text-shadow: 0 0 12px fade(@gold-acid, 18%);
&:hover {
color: lighten(@ember-bright, 10%);
color: lighten(@parchment, 12%);
}
}
@@ -218,3 +276,27 @@
opacity: 0.5;
}
}
// ============================================================
// PAUSE OVERLAY — replace default Foundry spinner with system logo
// ============================================================
#pause {
& > img {
content: url("../assets/ui/machine_god_skull.png");
animation: none;
width: 260px;
height: auto;
filter: drop-shadow(0 0 18px rgba(0, 0, 0, 0.8));
}
figcaption {
font-family: @font-display;
font-size: 2rem;
font-weight: bold;
color: @label-ink;
text-shadow:
0 0 10px rgba(0, 0, 0, 0.9),
1px 1px 2px #000;
letter-spacing: 2px;
}
}
+332 -82
View File
@@ -3,8 +3,8 @@
.ornate-frame();
display: flex;
flex-direction: column;
gap: 0.6rem;
padding: 0.68rem;
gap: 0.5rem;
padding: 0.62rem 0.68rem;
color: @iron;
background:
radial-gradient(circle at top right, fade(@verdigris, 10%), transparent 22%),
@@ -17,128 +17,351 @@
.mgne-chat-card .chat-card-header {
display: flex;
gap: 0.6rem;
gap: 0.55rem;
align-items: center;
padding-bottom: 0.4rem;
border-bottom: 1px solid fade(@gold-acid, 14%);
}
.mgne-chat-card .chat-card-header img {
width: 36px;
height: 36px;
width: 34px;
height: 34px;
object-fit: cover;
border-radius: 50%;
border: 1px solid fade(@gold-acid, 38%);
box-shadow: 0 0 0 2px fade(@parchment, 6%);
border: 1px solid fade(@gold-acid, 32%);
box-shadow: 0 0 0 2px fade(@parchment, 5%);
flex-shrink: 0;
}
.mgne-chat-card .chat-eyebrow {
display: none;
.mgne-chat-card .chat-header-text {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.05rem;
}
.mgne-chat-card h3 {
.caps-heading();
margin: 0;
color: @label-ink;
font-size: 0.82rem;
}
.mgne-chat-card .chat-actor,
.mgne-chat-card .chat-subtitle,
.mgne-chat-card .chat-formula,
.mgne-chat-card .chat-special {
margin: 0;
color: darken(@label-ink, 22%);
font-size: 0.78rem;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mgne-chat-card .chat-actor {
color: @iron;
}
.mgne-chat-card .chat-formula {
color: @iron;
code {
color: @parchment;
background: fade(@bg-void, 75%);
padding: 0.1em 0.42em;
border-radius: 3px;
}
}
.mgne-chat-card .chat-result-line {
display: flex;
justify-content: space-between;
align-items: end;
padding: 0.38rem 0.55rem;
border: 1px solid fade(@gold-acid, 28%);
border-radius: @radius-sm;
background: fade(@bg-void, 78%);
}
.mgne-chat-card .chat-result-label {
.caps-heading();
font-size: 0.62rem;
color: @parchment;
}
.mgne-chat-card .chat-result-total {
font-family: @font-display;
font-size: 1.08rem;
color: @parchment;
}
.mgne-chat-card .chat-outcome {
.caps-heading();
margin: 0;
padding: 0.3rem 0.55rem;
color: darken(@label-ink, 12%);
font-size: 0.68rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mgne-chat-card .chat-mode-badge {
.caps-heading();
font-size: 0.6rem;
letter-spacing: 0.1em;
padding: 0.15rem 0.45rem;
border-radius: 999px;
background: fade(@bg-void, 70%);
border: 1px solid fade(@bone, 22%);
color: @ash;
flex-shrink: 0;
line-height: 1.3;
}
.mgne-chat-card .chat-card-body {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
// Subtitle line
.mgne-chat-card .chat-subtitle {
margin: 0;
color: darken(@label-soft, 10%);
font-size: 0.72rem;
letter-spacing: 0.03em;
}
// Formula display
.mgne-chat-card .chat-formula-wrap {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.5rem;
background: fade(@bg-void, 60%);
border-radius: @radius-sm;
background: fade(@bg-void, 72%);
border-left: 3px solid fade(@ember-bright, 70%);
color: lighten(@ember-bright, 12%);
font-size: 0.8rem;
border: 1px solid fade(@bone, 10%);
}
.mgne-chat-card .chat-special {
padding: 0.42rem 0.55rem;
border-left: 3px solid fade(@ember-bright, 55%);
background: fade(@bg-input, 85%);
.mgne-chat-card .chat-formula-label {
.caps-heading();
font-size: 0.6rem;
color: @gold-acid;
letter-spacing: 0.08em;
flex-shrink: 0;
}
.mgne-chat-card .chat-formula-code {
font-family: "Courier New", monospace;
font-size: 0.76rem;
color: @parchment;
background: fade(@bg-void, 50%);
padding: 0.08rem 0.45rem;
border-radius: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// Outcome display (check results, morale)
.mgne-chat-card .chat-outcome {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.55rem;
border-radius: @radius-sm;
background: fade(@bg-void, 68%);
border-left: 3px solid fade(@ember-bright, 60%);
&.has-tooltip {
cursor: pointer;
user-select: none;
&:hover { filter: brightness(1.1); }
}
.chat-outcome-icon {
flex-shrink: 0;
font-size: 0.85rem;
width: 1.2rem;
text-align: center;
opacity: 0.8;
}
.chat-outcome-label {
.caps-heading();
flex: 1;
font-size: 0.72rem;
letter-spacing: 0.06em;
color: lighten(@ember-bright, 12%);
}
.chat-outcome-total {
font-family: @font-display;
font-size: 1.18rem;
font-weight: 700;
color: @parchment;
line-height: 1;
}
.chat-tooltip-icon {
opacity: 0.5;
font-size: 0.7rem;
margin-left: 0.15rem;
flex-shrink: 0;
transition: opacity 0.15s;
}
&.tooltip-open .chat-tooltip-icon { opacity: 1; }
}
// Damage total display
.mgne-chat-card .chat-damage-total {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.6rem;
border-radius: @radius-sm;
background: fade(@bg-void, 68%);
border-left: 3px solid fade(@ember-bright, 60%);
&.has-tooltip {
cursor: pointer;
user-select: none;
&:hover { filter: brightness(1.1); }
}
.chat-damage-label {
.caps-heading();
font-size: 0.65rem;
letter-spacing: 0.06em;
color: @ash;
flex: 1;
}
.chat-damage-number {
font-family: @font-display;
font-size: 1.3rem;
font-weight: 700;
color: lighten(@ember-bright, 8%);
line-height: 1;
}
.chat-tooltip-icon {
opacity: 0.5;
font-size: 0.7rem;
flex-shrink: 0;
transition: opacity 0.15s;
}
&.tooltip-open .chat-tooltip-icon { opacity: 1; }
}
// Outcome classes — success/crit/steady color
.mgne-chat-card.outcome-critical-success,
.mgne-chat-card.outcome-success,
.mgne-chat-card.outcome-steady {
border-color: fade(@verdigris, 42%);
.chat-outcome {
color: lighten(@verdigris, 28%);
border-left-color: fade(@verdigris, 80%);
border-left-color: fade(@verdigris, 75%);
.chat-outcome-label { color: lighten(@verdigris, 28%); }
.chat-outcome-icon { color: lighten(@verdigris, 30%); }
}
.chat-damage-total {
border-left-color: fade(@verdigris, 60%);
.chat-damage-number { color: lighten(@verdigris, 25%); }
}
}
// Outcome classes — failure/broken/fumble color
.mgne-chat-card.outcome-failure,
.mgne-chat-card.outcome-broken,
.mgne-chat-card.outcome-fumble {
border-color: fade(@blood, 48%);
.chat-outcome {
color: lighten(@ember-bright, 18%);
border-left-color: fade(@ember-bright, 80%);
border-left-color: fade(@ember-bright, 75%);
.chat-outcome-label { color: lighten(@ember-bright, 18%); }
.chat-outcome-icon { color: lighten(@blood, 10%); }
}
.chat-damage-total {
border-left-color: fade(@ember-bright, 60%);
}
}
.mgne-chat-card.mode-apply-damage .chat-result-total,
.mgne-chat-card.mode-damage .chat-result-total {
color: lighten(@ember-bright, 8%);
// Dice tooltip reveal panel
.mgne-chat-card .chat-dice-tooltip {
padding: 0.35rem 0.5rem;
background: fade(@bg-void, 85%);
border: 1px solid fade(@bone, 18%);
border-radius: @radius-sm;
.dice-roll {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.dice-formula {
font-family: "Courier New", monospace;
font-size: 0.7rem;
color: fade(@parchment, 72%);
}
.dice-tooltip {
font-size: 0.75rem;
}
.dice {
margin: 0.1rem 0;
}
.die-icon, .die-face {
color: @gold-acid;
font-size: 0.78rem;
}
.part-formula { color: fade(@parchment, 78%); font-size: 0.7rem; }
.part-total { color: @parchment; font-weight: 600; }
.max { color: lighten(@verdigris, 20%); }
.min { color: lighten(@ember-bright, 10%); }
.dice-total {
font-family: @font-display;
font-size: 1rem;
color: @gold-acid;
text-align: center;
border-top: 1px solid fade(@bone, 16%);
padding-top: 0.2rem;
margin-top: 0.1rem;
}
}
// Special text
.mgne-chat-card .chat-special-wrap {
display: flex;
align-items: flex-start;
gap: 0.4rem;
padding: 0.35rem 0.5rem;
border-left: 2px solid fade(@ember-bright, 40%);
background: fade(@bg-input, 80%);
border-radius: @radius-sm;
}
.mgne-chat-card .chat-special-icon {
flex-shrink: 0;
font-size: 0.7rem;
color: @ember-bright;
margin-top: 0.08rem;
}
.mgne-chat-card .chat-special-text {
color: @parchment;
font-size: 0.72rem;
line-height: 1.4;
}
// Omen reminders
.mgne-chat-card .chat-omen-remind,
.mgne-chat-card .chat-omen-used {
margin: 0;
padding: 0.35rem 0.5rem;
border-radius: @radius-sm;
font-size: 0.7rem;
display: flex;
align-items: center;
gap: 0.35rem;
}
.mgne-chat-card .chat-omen-remind {
background: fade(@gold-acid, 18%);
border: 1px solid fade(@gold-acid, 35%);
color: lighten(@gold-acid, 18%);
i {
color: @gold-acid;
font-size: 0.7rem;
}
}
.mgne-chat-card .chat-omen-neutralize {
background: fade(@ember-bright, 18%);
border-color: fade(@ember-bright, 35%);
color: lighten(@ember-bright, 15%);
i {
color: @ember-bright;
}
}
.mgne-chat-card .chat-omen-used {
background: fade(@verdigris, 18%);
border: 1px solid fade(@verdigris, 32%);
color: lighten(@verdigris, 32%);
i {
color: lighten(@verdigris, 35%);
font-size: 0.7rem;
}
}
// Card action buttons container
.mgne-chat-card .chat-card-actions {
margin-top: 0.2rem;
margin-top: 0.1rem;
}
// Roll damage button
.mgne-roll-damage-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
gap: 0.4rem;
width: 100%;
padding: 0.38rem 0.7rem;
border: 1px solid @ember-bright;
@@ -146,7 +369,7 @@
background: @ember;
color: #f8ede0;
font-family: @font-display;
font-size: 0.78rem;
font-size: 0.76rem;
font-weight: 600;
letter-spacing: 0.06em;
cursor: pointer;
@@ -154,7 +377,7 @@
.dmg-formula {
color: #fff5e8;
font-size: 0.75rem;
font-size: 0.73rem;
font-weight: 700;
}
@@ -181,8 +404,35 @@
}
}
// Omen reroll button
.mgne-omen-reroll-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
width: 100%;
padding: 0.38rem 0.7rem;
border: 1px solid @gold-acid;
border-radius: @radius-sm;
background: darken(@gold-acid, 15%);
color: #fff5d0;
font-family: @font-display;
font-size: 0.76rem;
font-weight: 600;
letter-spacing: 0.06em;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
&:hover {
background: lighten(darken(@gold-acid, 15%), 8%);
border-color: lighten(@gold-acid, 12%);
color: #fff;
}
}
// Apply damage select
.chat-apply-actions {
margin-top: 0.35rem;
margin-top: 0.25rem;
}
.mgne-apply-damage-select {
@@ -193,7 +443,7 @@
background: @bg-char;
color: @bone;
font-family: @font-body;
font-size: 0.8rem;
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
+731 -8
View File
@@ -123,9 +123,15 @@
}
.application.mgne .condition-value-grid {
grid-template-columns: max-content 1fr;
grid-template-columns: max-content minmax(auto, 9rem);
align-items: center;
margin-bottom: 0.6rem;
select {
width: auto;
min-width: 5rem;
max-width: 9rem;
}
}
.application.mgne .condition-flag-grid {
@@ -197,15 +203,15 @@
}
.application.mgne .resource-box-inline-track .numeric-caption {
color: @label-ink;
color: @bone;
}
.application.mgne .resource-box-inline-track .numeric-caption-strong {
padding: 0.08rem 0.34rem;
border-radius: 999px;
background: fade(@gold-acid, 18%);
border: 1px solid fade(@label-ink, 28%);
color: darken(@label-ink, 4%);
border: 1px solid fade(@bone, 28%);
color: @bone;
font-size: 0.6rem;
letter-spacing: 0.11em;
}
@@ -255,7 +261,7 @@
.application.mgne .numeric-caption {
.caps-heading();
color: fade(@label-soft, 94%);
color: lighten(@label-soft, 2%);
font-size: 0.58rem;
letter-spacing: 0.12em;
}
@@ -473,6 +479,18 @@
font-size: 0.82rem;
}
// Item sheets: dark h3 for parchment backgrounds
.application.mgne.item-sheet h3,
.application.mgne.item-sheet .inventory-header h3 {
color: @bg-char;
}
// Character sheet: Conditions block - use dark color like item sheets
.application.mgne.character .condition-value-grid label,
.application.mgne.character .condition-flag-grid label {
color: @bg-char;
}
.application.mgne .item-row {
position: relative;
display: grid;
@@ -530,10 +548,23 @@
gap: 0.35rem;
align-items: center;
width: fit-content;
padding: 0.1rem 0.3rem 0.1rem 0.2rem;
border-radius: 4px;
transition: background 0.15s, color 0.15s;
&.active {
color: @ember-bright;
color: #f5dfa0;
font-weight: 600;
text-shadow: 0 0 8px fade(@gold-acid, 55%);
background: fade(@gold-acid, 14%);
border: 1px solid fade(@gold-acid, 28%);
margin: -1px; // compensate border so layout doesn't shift
}
&:not(.active) {
color: @bone;
border: 1px solid transparent;
margin: -1px;
}
}
@@ -565,8 +596,8 @@
}
.application.mgne .resource-label-accent {
color: @ember-bright;
text-shadow: 0 0 12px fade(@ember, 18%);
color: lighten(@parchment, 8%);
text-shadow: 0 0 12px fade(@gold-acid, 18%);
}
.application.mgne .ability-score {
@@ -628,6 +659,32 @@
@bg-panel-soft;
}
// ─── Companion sheet label contrast ──────────────────────────────────────────
// Panel-shell backgrounds expose the parchment page texture; @bone caps labels
// blend into the warm background. Use dark ink — matching the character sheet's
// condition-label treatment.
.application.mgne.companion {
// Section headers (Attack, Adventuring/Combat Behavior, Description, Notes)
.inventory-header h3 {
color: @bg-char;
}
// Compact resource labels (Morale, Armor Die) — HP uses resource-label-accent and stays light
.resource-box > label:not(.resource-label-accent) {
color: @bg-char;
}
// Labels inside fieldsets (Label, Damage in the Attack section)
fieldset label {
color: @bg-char;
}
// Floating grid labels (They Value, Trait, Specialty)
.grid > div > label {
color: @bg-char;
}
}
@media (max-width: 960px) {
.application.mgne .sheet-header,
.application.mgne .resource-bar,
@@ -641,3 +698,669 @@
grid-template-columns: 1fr;
}
}
// ─── Weight badges ────────────────────────────────────────────────────────────
.item-weight-badge {
display: inline-flex;
align-items: center;
padding: 0 0.45em;
border-radius: 3px;
font-family: @font-body;
font-size: 0.7em;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
white-space: nowrap;
border: 1px solid transparent;
&.weight-trivial {
color: fade(@bone, 45%);
border-color: fade(@bone, 20%);
}
&.weight-light {
color: @bone;
border-color: fade(@bone, 35%);
background: fade(@bone, 8%);
}
&.weight-normal {
color: @gold-acid;
border-color: fade(@gold-acid, 40%);
background: fade(@gold-acid, 10%);
}
&.weight-heavy {
color: @bone;
border-color: fade(@gold-acid, 45%);
background: fade(@ash, 12%);
}
}
// ─── Load display ─────────────────────────────────────────────────────────────
.load-display {
font-weight: 700;
letter-spacing: 0.04em;
cursor: default;
}
.load-overloaded {
color: lighten(@parchment, 8%) !important;
text-shadow: 0 0 6px fade(@gold-acid, 50%);
}
.resource-box-overloaded {
border-color: fade(@gold-acid, 60%) !important;
background: fade(@ash, 10%) !important;
.resource-label-accent {
color: lighten(@parchment, 8%) !important;
}
}
// ─── Item description tooltip ─────────────────────────────────────────────────
.item-name[data-tooltip] {
cursor: help;
&:hover { text-decoration: underline dotted fade(@gold-acid, 55%); }
}
#tooltip.mgne-item-tooltip {
max-width: 22rem;
font-family: @font-body;
font-size: 0.82rem;
line-height: 1.5;
color: @parchment;
background: darken(@bg-panel-soft, 4%);
border: 1px solid fade(@gold-acid, 35%);
border-radius: 4px;
padding: 0.5rem 0.7rem;
box-shadow: 0 4px 14px fade(#000, 55%);
}
// ─── Broken / Durability badges ───────────────────────────────────────────────
.item-broken-badge {
display: inline-block;
margin-left: 0.3em;
padding: 0.05em 0.4em;
font-size: 0.65em;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: lighten(@parchment, 8%);
background: fade(@ash, 14%);
border: 1px solid fade(@bone, 45%);
border-radius: 3px;
vertical-align: middle;
}
.item-burned-badge {
color: @gold-acid;
background: fade(@gold-acid, 12%);
border-color: fade(@gold-acid, 40%);
}
.item-durability-badge {
display: inline-flex;
align-items: center;
gap: 0.25em;
padding: 0.1em 0.45em;
font-size: 0.7em;
font-weight: 700;
color: fade(@bone, 65%);
border: 1px solid fade(@bone, 20%);
border-radius: 3px;
white-space: nowrap;
letter-spacing: 0.03em;
&.durability-broken {
color: lighten(@parchment, 8%);
border-color: fade(@bone, 40%);
background: fade(@ash, 10%);
text-decoration: line-through;
}
}
.item-row-broken {
opacity: 0.6;
filter: grayscale(0.4);
.item-name {
text-decoration: line-through;
color: lighten(@parchment, 6%);
}
}
// ─── Weapon properties ────────────────────────────────────────────────────────
.weapon-properties-grid {
display: flex;
flex-wrap: wrap;
gap: 0.35rem 0.6rem;
padding: 0.5rem 0.6rem;
background: fade(@bone, 4%);
border: 1px solid fade(@bone, 12%);
border-radius: 4px;
margin-bottom: 0.5rem;
.property-check {
display: flex;
align-items: center;
gap: 0.3em;
font-family: @font-body;
font-size: 0.82rem;
color: @parchment;
cursor: pointer;
white-space: nowrap;
padding: 0.15em 0.4em;
border-radius: 3px;
transition: background 0.15s;
&:hover { background: fade(@gold-acid, 8%); }
input[type="checkbox"] { accent-color: @gold-acid; }
}
}
.weapon-property-badge {
display: inline-block;
margin-left: 0.3em;
padding: 0.05em 0.35em;
font-size: 0.62em;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: fade(@gold-acid, 85%);
background: fade(@gold-acid, 10%);
border: 1px solid fade(@gold-acid, 30%);
border-radius: 3px;
vertical-align: middle;
}
// ─── Creature trait trigger ───────────────────────────────────────────────────
.creature-trait-trigger {
font-size: 0.82em;
color: fade(@parchment, 65%);
font-style: italic;
margin-left: 0.2em;
}
// ─── Creature traits container ────────────────────────────────────────────────
.creature-traits-container {
border: 1px solid fade(@blood, 35%);
border-left: 3px solid fade(@blood, 65%);
border-radius: 4px;
background: linear-gradient(135deg, fade(@blood, 5%), transparent 60%),
fade(@bg-panel-soft, 85%);
padding: 0.6rem 0.75rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.creature-traits-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
border-bottom: 1px solid fade(@blood, 25%);
padding-bottom: 0.4rem;
margin-bottom: 0.1rem;
h3 {
margin: 0;
font-family: @font-display;
font-size: 0.82rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: lighten(@blood, 8%);
}
button {
font-size: 0.72rem;
padding: 0.15em 0.6em;
background: fade(@blood, 12%);
border: 1px solid fade(@blood, 35%);
color: lighten(@blood, 4%);
border-radius: 3px;
cursor: pointer;
transition: background 0.15s, color 0.15s;
&:hover {
background: fade(@blood, 22%);
color: lighten(@blood, 10%);
}
}
}
.creature-traits-list {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.creature-trait-card {
background: fade(@bg-void, 30%);
border: 1px solid fade(@blood, 20%);
border-radius: 3px;
padding: 0.35rem 0.5rem;
transition: background 0.15s, border-color 0.15s;
&:hover {
background: fade(@blood, 8%);
border-color: fade(@blood, 35%);
}
}
.creature-trait-card-header {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.creature-trait-name {
display: flex;
align-items: center;
gap: 0.35em;
font-family: @font-body;
font-size: 0.88rem;
font-weight: 600;
color: @parchment;
flex: 1;
min-width: 0;
cursor: default;
i { color: fade(@blood, 70%); font-size: 0.75em; }
}
.creature-trait-trigger {
font-size: 0.75rem;
font-style: italic;
color: fade(@parchment, 55%);
white-space: nowrap;
}
.creature-trait-card .item-actions {
display: flex;
gap: 0.25rem;
margin-left: auto;
flex-shrink: 0;
button {
width: 1.6rem;
height: 1.6rem;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid fade(@bone, 18%);
color: fade(@bone, 50%);
border-radius: 3px;
cursor: pointer;
font-size: 0.72rem;
transition: background 0.15s, color 0.15s, border-color 0.15s;
&:hover {
background: fade(@bone, 10%);
color: @bone;
border-color: fade(@bone, 35%);
}
}
}
// ============================================================
// CREATURE SHEET — META ROW + ACTION TABLE
// ============================================================
.creature-meta-row {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
padding: 0.45rem 0.6rem;
background: fade(@bg-panel-soft, 50%);
border: 1px solid fade(@ember, 20%);
border-radius: 4px;
}
.creature-meta-label {
font-family: @font-display;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: @parchment;
white-space: nowrap;
}
.creature-type-group {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
}
.creature-type-checkbox {
display: flex;
align-items: center;
gap: 0.3em;
font-size: 0.83rem;
color: @parchment !important;
cursor: pointer;
user-select: none;
> span {
color: @parchment !important;
}
input[type="checkbox"] {
appearance: checkbox;
-webkit-appearance: checkbox;
accent-color: @ember;
width: 0.9rem;
height: 0.9rem;
cursor: pointer;
margin: 0;
flex-shrink: 0;
&::before, &::after {
display: none;
}
}
}
.creature-number-group {
display: flex;
align-items: center;
gap: 0.4rem;
flex-shrink: 0;
}
.creature-number-input {
width: 4rem;
text-align: center;
font-family: @font-body;
font-size: 0.9rem;
color: @parchment;
background: @bg-input;
border: 1px solid fade(@ember, 30%);
border-radius: 3px;
padding: 0.15em 0.3em;
&:focus {
outline: none;
border-color: fade(@ember, 60%);
background: fade(@bg-input, 90%);
}
}
.creature-action-table-section {
.action-table-drop-zone {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 4px;
min-height: 2.4rem;
font-size: 0.88rem;
color: @parchment;
i { font-size: 1em; color: @ember; }
.action-table-name {
font-weight: 600;
font-family: @font-display;
color: @parchment;
}
&.drop-hint {
border: 2px dashed fade(@ember, 35%);
background: fade(@ember, 4%);
color: @parchment;
font-style: italic;
justify-content: center;
cursor: default;
transition: background 0.15s, border-color 0.15s;
// Companion sheet: darker text for better contrast
.application.mgne.companion & {
color: @bg-char;
}
i { color: fade(@ember, 50%); }
&:hover, &.drag-over {
background: fade(@ember, 9%);
border-color: fade(@ember, 60%);
color: fade(@parchment, 70%);
i { color: @ember; }
}
}
}
.action-table-buttons {
display: flex;
gap: 0.3rem;
align-items: center;
button {
font-size: 0.75rem;
padding: 0.15em 0.55em;
background: fade(@ash, 10%);
border: 1px solid fade(@gold-acid, 30%);
color: lighten(@parchment, 6%);
border-radius: 3px;
cursor: pointer;
transition: background 0.15s, color 0.15s;
display: flex;
align-items: center;
gap: 0.3em;
&:hover {
background: fade(@ash, 22%);
color: @bone;
}
}
}
}
// ============================================================
// PARTY ACTOR SHEET
// ============================================================
.application.mgne.party {
.party-credits-row {
display: flex;
align-items: center;
gap: 0.6rem;
margin-top: 0.5rem;
padding: 0.4rem 0.6rem;
border: 1px solid fade(@gold-acid, 35%);
border-radius: @radius-sm;
background: fade(@bg-input, 70%);
}
.party-credits-stepper {
display: flex;
align-items: center;
gap: 0.3rem;
}
.credits-input {
width: 5rem;
text-align: center;
font-size: 1.05rem;
font-weight: 700;
font-family: @font-display;
color: @gold-acid;
background: fade(@bg-void, 60%);
border: 1px solid fade(@gold-acid, 30%);
border-radius: @radius-sm;
padding: 0.2rem 0.4rem;
}
.credits-symbol {
color: @gold-acid;
font-family: @font-display;
font-size: 1.1rem;
font-weight: 700;
margin: 0 0.1rem;
}
.credits-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
padding: 0.15rem 0.4rem;
font-size: 0.75rem;
font-weight: 700;
border: 1px solid fade(@bone, 28%);
border-radius: @radius-sm;
background: fade(@bg-input, 80%);
color: @bone;
cursor: pointer;
user-select: none;
transition: background 0.15s, border-color 0.15s;
&:hover {
background: fade(@gold-acid, 18%);
border-color: fade(@gold-acid, 55%);
color: @gold-acid;
}
}
}
// ── Party member list ─────────────────────────────────────────
.application.mgne.party .party-member-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: 0.4rem;
}
.application.mgne.party .party-member-row {
display: grid;
grid-template-columns: 2rem 1fr 5rem 6rem 5.5rem;
gap: 0.5rem;
align-items: center;
padding: 0.3rem 0.4rem;
border-radius: @radius-sm;
&.party-member-header {
color: @dust;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid fade(@bone, 18%);
padding-bottom: 0.25rem;
margin-bottom: 0.1rem;
}
&:not(.party-member-header) {
background: fade(@bg-input, 75%);
border: 1px solid transparent;
&:hover {
border-color: fade(@gold-acid, 22%);
background: fade(@bg-input, 90%);
}
}
}
.application.mgne.party .party-member-portrait {
width: 2rem;
height: 2rem;
object-fit: cover;
border-radius: 3px;
border: 1px solid fade(@bone, 25%);
}
.application.mgne.party .party-member-name a {
color: @parchment;
font-weight: 700;
font-size: 0.9rem;
&:hover { color: @gold-acid; }
}
.application.mgne.party .party-member-hp {
color: @bone;
font-family: @font-display;
font-size: 0.82rem;
font-weight: 700;
text-align: center;
}
.application.mgne.party .party-member-type {
color: lighten(@dust, 12%);
font-size: 0.78rem;
font-style: italic;
}
// ── Party loot list ────────────────────────────────────────────
.application.mgne.party .party-loot-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: 0.4rem;
}
.application.mgne.party .party-loot-row {
display: grid;
grid-template-columns: 2rem 1fr 7rem 4rem;
gap: 0.5rem;
align-items: center;
padding: 0.3rem 0.4rem;
border-radius: @radius-sm;
&.party-loot-header {
color: @dust;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid fade(@bone, 18%);
padding-bottom: 0.25rem;
margin-bottom: 0.1rem;
}
&:not(.party-loot-header) {
background: fade(@bg-input, 75%);
border: 1px solid transparent;
&:hover {
border-color: fade(@gold-acid, 22%);
background: fade(@bg-input, 90%);
}
}
}
.application.mgne.party .item-portrait {
width: 2rem;
height: 2rem;
object-fit: cover;
border-radius: 3px;
border: 1px solid fade(@bone, 25%);
}
.application.mgne.party .item-type-label {
color: @dust;
font-size: 0.78rem;
font-style: italic;
}
// ── Party drop hint ────────────────────────────────────────────
.application.mgne.party .party-drop-hint {
color: @parchment;
font-size: 0.75rem;
font-style: italic;
text-align: center;
margin-top: 0.5rem;
padding: 0.3rem;
border: 1px dashed fade(@bone, 18%);
border-radius: @radius-sm;
}
+9 -9
View File
@@ -9,16 +9,16 @@
@bg-input: rgba(17, 12, 10, 0.72);
@parchment: #ccb292;
@bone: #ab8b68;
@dust: #7e664f;
@ash: #5f4d40;
@label-ink: #6e3d2a;
@label-soft: #81533b;
@iron: #52453c;
@copper: #8d5f3f;
@dust: #a68e78;
@ash: #958068;
@label-ink: #a67054;
@label-soft: #b1856c;
@iron: #a08878;
@copper: #b49c73;
@verdigris: #4f7d73;
@ember: #b7461f;
@ember-bright: #dd6b2d;
@blood: #7f1d17;
@ember: #d87a4f;
@ember-bright: #e89a5f;
@blood: #e85a50;
@gold-acid: #c49a45;
@shadow-heavy: 0 14px 30px rgba(0, 0, 0, 0.36);
@shadow-inset: inset 0 1px 0 rgba(255, 236, 203, 0.08), inset 0 0 0 1px rgba(255, 236, 203, 0.03);
+2
View File
@@ -3,6 +3,7 @@ export { default as MGNEItemSheet } from "./sheets/base-item-sheet.mjs"
export { default as MGNECharacterSheet } from "./sheets/character-sheet.mjs"
export { default as MGNECreatureSheet } from "./sheets/creature-sheet.mjs"
export { default as MGNECompanionSheet } from "./sheets/companion-sheet.mjs"
export { default as MGNEPartySheet } from "./sheets/party-sheet.mjs"
export { default as MGNEWeaponSheet } from "./sheets/weapon-sheet.mjs"
export { default as MGNEArmorSheet } from "./sheets/armor-sheet.mjs"
export { default as MGNEShieldSheet } from "./sheets/shield-sheet.mjs"
@@ -10,3 +11,4 @@ export { default as MGNEEquipmentSheet } from "./sheets/equipment-sheet.mjs"
export { default as MGNEResonanceCoreSheet } from "./sheets/resonance-core-sheet.mjs"
export { default as MGNEArtifactSheet } from "./sheets/artifact-sheet.mjs"
export { default as MGNEFeatureSheet } from "./sheets/feature-sheet.mjs"
export { default as MGNECreatureTraitSheet } from "./sheets/creature-trait-sheet.mjs"
@@ -64,6 +64,8 @@ export default class MGNEActorSheet extends HandlebarsApplicationMixin(foundry.a
switch (rollType) {
case "ability":
return this.document.rollAbility(target.dataset.abilityId)
case "armor":
return this.document.rollArmorSave()
case "defense":
return this.document.rollDefense()
case "weapon":
@@ -78,6 +80,8 @@ export default class MGNEActorSheet extends HandlebarsApplicationMixin(foundry.a
return this.document.rollResonation(itemId)
case "morale":
return this.document.rollMorale()
case "durability":
return this.document.rollDurability(itemId)
case "usage":
return this.document.rollUsage(itemId)
default:
+11 -7
View File
@@ -2,6 +2,10 @@ import MGNEActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs"
import { buildCharacterSelectOptions } from "./select-options.mjs"
export function stripHtml(html) {
return (html ?? "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()
}
export default class MGNECharacterSheet extends MGNEActorSheet {
static DEFAULT_OPTIONS = {
classes: ["character"],
@@ -79,16 +83,16 @@ export default class MGNECharacterSheet extends MGNEActorSheet {
break
case "equipment":
context.tab = context.tabs.equipment
context.weapons = doc.itemTypes.weapon
context.armors = doc.itemTypes.armor
context.shields = doc.itemTypes.shield
context.equipmentItems = doc.itemTypes.equipment
context.cores = doc.itemTypes["resonance-core"]
context.artifacts = doc.itemTypes.artifact
context.weapons = doc.itemTypes.weapon.map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
context.armors = doc.itemTypes.armor.map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
context.shields = doc.itemTypes.shield.map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
context.equipmentItems = doc.itemTypes.equipment.map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
context.cores = doc.itemTypes["resonance-core"].map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
context.artifacts = doc.itemTypes.artifact.map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
break
case "features":
context.tab = context.tabs.features
context.features = doc.itemTypes.feature
context.features = doc.itemTypes.feature.map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
break
case "notes":
context.tab = context.tabs.notes
+70 -6
View File
@@ -1,5 +1,4 @@
import MGNEActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs"
export default class MGNECompanionSheet extends MGNEActorSheet {
static DEFAULT_OPTIONS = {
@@ -8,6 +7,14 @@ export default class MGNECompanionSheet extends MGNEActorSheet {
width: 820,
height: 700,
},
actions: {
rollAdventuringBehavior: MGNECompanionSheet.prototype._rollAdventuringBehavior,
clearAdventuringBehavior: MGNECompanionSheet.prototype._clearAdventuringBehavior,
openAdventuringBehavior: MGNECompanionSheet.prototype._openAdventuringBehavior,
rollCombatBehavior: MGNECompanionSheet.prototype._rollCombatBehavior,
clearCombatBehavior: MGNECompanionSheet.prototype._clearCombatBehavior,
openCombatBehavior: MGNECompanionSheet.prototype._openCombatBehavior,
},
}
static PARTS = {
@@ -16,11 +23,68 @@ export default class MGNECompanionSheet extends MGNEActorSheet {
async _prepareContext() {
const context = await super._prepareContext()
context.abilityList = SYSTEM.abilityOrder.map(id => ({
id,
...SYSTEM.abilities[id],
value: context.source.system.abilities?.[id]?.value ?? 0,
}))
const resolveTable = async (uuid) => {
if (!uuid) return null
const table = await fromUuid(uuid).catch(() => null)
return table ? { name: table.name, uuid } : null
}
context.adventuringTable = await resolveTable(this.document.system.adventuringBehaviorUuid)
context.combatTable = await resolveTable(this.document.system.combatBehaviorUuid)
return context
}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data?.type === "RollTable") {
const table = await fromUuid(data.uuid)
if (!table) return super._onDrop(event)
// Determine drop target by proximity to each section
const target = event.target.closest("[data-behavior-slot]")
const slot = target?.dataset.behaviorSlot
if (slot === "adventuring") {
await this.document.update({ "system.adventuringBehaviorUuid": data.uuid })
return
}
if (slot === "combat") {
await this.document.update({ "system.combatBehaviorUuid": data.uuid })
return
}
// Fallback: first empty slot, then adventuring
const sys = this.document.system
if (!sys.adventuringBehaviorUuid) {
await this.document.update({ "system.adventuringBehaviorUuid": data.uuid })
} else {
await this.document.update({ "system.combatBehaviorUuid": data.uuid })
}
return
}
return super._onDrop(event)
}
async _rollAdventuringBehavior() { await this._rollTable("adventuringBehaviorUuid") }
async _clearAdventuringBehavior() { await this.document.update({ "system.adventuringBehaviorUuid": "" }) }
async _openAdventuringBehavior() { await this._openTable("adventuringBehaviorUuid") }
async _rollCombatBehavior() { await this._rollTable("combatBehaviorUuid") }
async _clearCombatBehavior() { await this.document.update({ "system.combatBehaviorUuid": "" }) }
async _openCombatBehavior() { await this._openTable("combatBehaviorUuid") }
async _rollTable(uuidField) {
const uuid = this.document.system[uuidField]
if (!uuid) return ui.notifications.warn(game.i18n.localize("MGNE.Companion.NoTableLinked"))
const table = await fromUuid(uuid).catch(() => null)
if (!table) return ui.notifications.warn(game.i18n.localize("MGNE.Companion.TableNotFound"))
await table.draw()
}
async _openTable(uuidField) {
const uuid = this.document.system[uuidField]
if (!uuid) return
const table = await fromUuid(uuid).catch(() => null)
if (table) table.sheet.render(true)
}
}
+65 -6
View File
@@ -1,12 +1,17 @@
import MGNEActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs"
import { stripHtml } from "./character-sheet.mjs"
export default class MGNECreatureSheet extends MGNEActorSheet {
static DEFAULT_OPTIONS = {
classes: ["creature"],
position: {
width: 760,
height: 640,
height: 680,
},
actions: {
rollActionTable: MGNECreatureSheet.prototype._rollActionTable,
clearActionTable: MGNECreatureSheet.prototype._clearActionTable,
openActionTable: MGNECreatureSheet.prototype._openActionTable,
},
}
@@ -14,13 +19,67 @@ export default class MGNECreatureSheet extends MGNEActorSheet {
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/creature-main.hbs" },
}
_processSubmitData(event, form, submitData) {
// Foundry sends null for unchecked checkboxes in a SetField array — strip them
if (Array.isArray(submitData.system?.creatureType)) {
submitData.system.creatureType = submitData.system.creatureType.filter(v => v != null && v !== "")
}
return super._processSubmitData(event, form, submitData)
}
async _prepareContext() {
const context = await super._prepareContext()
context.abilityList = SYSTEM.abilityOrder.map(id => ({
id,
...SYSTEM.abilities[id],
value: context.source.system.abilities?.[id]?.value ?? 0,
context.traits = (this.document.itemTypes["creature-trait"] ?? [])
.map(i => ({ id: i.id, name: i.name, img: i.img, system: i.system, tooltip: stripHtml(i.system.description) }))
// Resolve linked action table
const uuid = this.document.system.actionTableUuid
if (uuid) {
const table = await fromUuid(uuid).catch(() => null)
context.actionTable = table ? { name: table.name, uuid } : null
} else {
context.actionTable = null
}
// Build creature type checkboxes
const typeSet = this.document.system.creatureType ?? new Set()
context.creatureTypes = ["human", "construct", "animal"].map(key => ({
key,
label: game.i18n.localize(`MGNE.Creature.Types.${key.charAt(0).toUpperCase() + key.slice(1)}`),
checked: typeSet.has(key),
}))
return context
}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data?.type === "RollTable") {
const table = await fromUuid(data.uuid)
if (table) {
await this.document.update({ "system.actionTableUuid": data.uuid })
return
}
}
return super._onDrop(event)
}
async _rollActionTable() {
const uuid = this.document.system.actionTableUuid
if (!uuid) return ui.notifications.warn(game.i18n.localize("MGNE.Creature.NoTableLinked"))
const table = await fromUuid(uuid).catch(() => null)
if (!table) return ui.notifications.warn(game.i18n.localize("MGNE.Creature.TableNotFound"))
await table.draw()
}
async _clearActionTable() {
await this.document.update({ "system.actionTableUuid": "" })
}
async _openActionTable() {
const uuid = this.document.system.actionTableUuid
if (!uuid) return
const table = await fromUuid(uuid).catch(() => null)
if (table) table.sheet.render(true)
}
}
@@ -0,0 +1,7 @@
import MGNEItemSheet from "./base-item-sheet.mjs"
export default class MGNECreatureTraitSheet extends MGNEItemSheet {
static PARTS = {
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/creature-trait.hbs" },
}
}
+162
View File
@@ -0,0 +1,162 @@
import MGNEActorSheet from "./base-actor-sheet.mjs"
const SYSTEM_ID = "fvtt-machine-gods-noxian-expanse"
const PARTY_LOOT_TYPES = new Set(["weapon", "armor", "shield", "equipment", "resonance-core", "artifact"])
export default class MGNEPartySheet extends MGNEActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["party"],
position: { width: 820, height: 640 },
actions: {
openMember: MGNEPartySheet.#onOpenMember,
removeMember: MGNEPartySheet.#onRemoveMember,
moveMemberUp: MGNEPartySheet.#onMoveMemberUp,
moveMemberDown: MGNEPartySheet.#onMoveMemberDown,
adjustCredits: MGNEPartySheet.#onAdjustCredits,
},
}
/** @override */
static PARTS = {
main: { template: `systems/${SYSTEM_ID}/templates/party-main.hbs` },
tabs: { template: `systems/${SYSTEM_ID}/templates/party-tabs.hbs` },
members: { template: `systems/${SYSTEM_ID}/templates/party-members.hbs` },
loot: { template: `systems/${SYSTEM_ID}/templates/party-loot.hbs` },
notes: { template: `systems/${SYSTEM_ID}/templates/party-notes.hbs` },
}
tabGroups = { sheet: "members" }
#getTabs() {
const tabs = {
members: { id: "members", group: "sheet", label: game.i18n.localize("MGNE.Tabs.members") },
loot: { id: "loot", group: "sheet", label: game.i18n.localize("MGNE.Tabs.loot") },
notes: { id: "notes", group: "sheet", label: game.i18n.localize("MGNE.Tabs.notes") },
}
for (const tab of Object.values(tabs)) {
tab.active = this.tabGroups[tab.group] === tab.id
tab.cssClass = tab.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "members": {
context.tab = context.tabs.members
// Build member list using actorId for moves; store refIdx (position in
// memberRefs) so move actions always operate on the correct slot even
// when some refs point to deleted actors.
const refs = doc.system.memberRefs ?? []
const members = []
for (let refIdx = 0; refIdx < refs.length; refIdx++) {
const actor = game.actors?.get(refs[refIdx].id)
if (!actor) continue
members.push({
id: actor.id,
refIdx,
name: actor.name,
img: actor.img,
type: actor.type,
typeLabel: game.i18n.localize(`TYPES.Actor.${actor.type}`),
hp: actor.system.hp
? `${actor.system.hp.value ?? "—"}/${actor.system.hp.max ?? "—"}`
: "—",
})
}
// isFirst/isLast based on visible list, but swap uses refIdx
for (let vi = 0; vi < members.length; vi++) {
members[vi].isFirst = vi === 0
members[vi].isLast = vi === members.length - 1
}
context.members = members
break
}
case "loot": {
context.tab = context.tabs.loot
context.lootItems = doc.items.contents
.filter(i => PARTY_LOOT_TYPES.has(i.type))
.map(i => ({
id: i.id,
img: i.img,
name: i.name,
typeLabel: game.i18n.localize(`TYPES.Item.${i.type}`),
}))
break
}
case "notes":
context.tab = context.tabs.notes
break
}
return context
}
/** @override */
async _onDrop(event) {
if (!this.isEditable) return
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data.type === "Actor") {
const actor = await fromUuid(data.uuid)
if (!actor || !["character", "companion"].includes(actor.type)) return
const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? [])
if (refs.some(r => r.id === actor.id)) return
refs.push({ id: actor.id })
return this.document.update({ "system.memberRefs": refs })
}
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
if (!item || !PARTY_LOOT_TYPES.has(item.type)) return
return this.document.createEmbeddedDocuments("Item", [item.toObject()])
}
}
// ── Actions ─────────────────────────────────────────────────────────────────
static async #onOpenMember(_event, target) {
const actor = game.actors?.get(target.dataset.actorId)
if (actor) actor.sheet.render(true)
}
static async #onRemoveMember(_event, target) {
const id = target.dataset.actorId
const refs = (this.document.system.memberRefs ?? []).filter(r => r.id !== id)
await this.document.update({ "system.memberRefs": refs })
}
static async #onMoveMemberUp(_event, target) {
const refIdx = parseInt(target.dataset.refIdx, 10)
if (refIdx <= 0) return
const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? []);
[refs[refIdx - 1], refs[refIdx]] = [refs[refIdx], refs[refIdx - 1]]
await this.document.update({ "system.memberRefs": refs })
}
static async #onMoveMemberDown(_event, target) {
const refIdx = parseInt(target.dataset.refIdx, 10)
const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? [])
if (refIdx >= refs.length - 1) return;
[refs[refIdx], refs[refIdx + 1]] = [refs[refIdx + 1], refs[refIdx]]
await this.document.update({ "system.memberRefs": refs })
}
static async #onAdjustCredits(_event, target) {
const delta = parseInt(target.dataset.delta, 10)
const current = this.document.system.credits ?? 0
await this.document.update({ "system.credits": Math.max(0, current + delta) })
}
}
@@ -31,12 +31,13 @@ export function buildSharedSelectOptions() {
armorPenalties: numericOptions(0, 6),
shieldPenalties: numericOptions(0, 4),
weaponCategories: objectOptions(SYSTEM.weaponCategories),
weaponProperties: Object.entries(SYSTEM.weaponProperties).map(([key, p]) => ({ value: key, label: p.label, hint: p.hint })),
weightCategories: objectOptions(SYSTEM.weightCategories),
usageDice: objectOptions(SYSTEM.usageDieChoices),
armorDice: objectOptions(SYSTEM.armorDieChoices),
omenDice: objectOptions(SYSTEM.omenDieChoices),
resonanceList: objectOptions(SYSTEM.resonanceList),
equipmentSubtypes: objectOptions(SYSTEM.equipmentSubtypes),
artifactIds: objectOptions(SYSTEM.artifactChoices),
featureIds: objectOptions(SYSTEM.featureChoices),
}
}
+33 -1
View File
@@ -28,6 +28,7 @@ export const SYSTEM = {
character: { id: "character", label: "Character" },
creature: { id: "creature", label: "Creature" },
companion: { id: "companion", label: "Companion" },
party: { id: "party", label: "Party" },
},
itemTypes: {
weapon: { id: "weapon", label: "Weapon", icon: itemIcon("weapon") },
@@ -37,6 +38,7 @@ export const SYSTEM = {
"resonance-core": { id: "resonance-core", label: "Resonance Core", icon: itemIcon("resonance-core") },
artifact: { id: "artifact", label: "Artifact", icon: itemIcon("artifact") },
feature: { id: "feature", label: "Feature", icon: itemIcon("feature") },
"creature-trait": { id: "creature-trait", label: "Creature Trait", icon: itemIcon("creature-trait") },
},
abilities: {
agility: { id: "agility", label: "Agility" },
@@ -63,10 +65,40 @@ export const SYSTEM = {
armorDieChoices: dieChoiceLabels(["d12", "d10", "d8", "d6", "d4", "d2", "0"]),
omenDice: ["d2", "d4", "d6", "d8"],
omenDieChoices: dieChoiceLabels(["d2", "d4", "d6", "d8"]),
weightCategories: {
trivial: "Trivial",
light: "Light",
normal: "Normal",
heavy: "Heavy",
},
weaponCategories: {
melee: "Melee",
ranged: "Ranged",
},
weaponProperties: {
ammo: { label: "Ammo", hint: "Requires ammunition. Improvised in melee or without ammo." },
awkward: { label: "Awkward", hint: "+1 DR to attacks." },
binding: { label: "Binding", hint: "Beat DR by 4+ on same-size or smaller target: inflict Restrained." },
durant: { label: "Durant", hint: "Durability die is D8." },
finesse: { label: "Finesse", hint: "Attacks can use either Presence or Strength." },
fling: { label: "Fling", hint: "Can make ranged attacks. Roll D4 Usage Die to avoid losing it." },
fragile: { label: "Fragile", hint: "Durability die is D4." },
glinting: { label: "Glinting", hint: "After attacking, next attack vs same target this combat gains -1 DR." },
overbearing:{ label: "Overbearing",hint: "+2 damage." },
parrying: { label: "Parrying", hint: "-1 DR to avoiding melee attacks while wielding." },
precise: { label: "Precise", hint: "-1 DR to hit." },
razored: { label: "Razored", hint: "Beat DR by 4+: inflict Bleeding (1)." },
ringing: { label: "Ringing", hint: "Beat DR by 4+ and target not Stunned: inflict Stunned (1)." },
"two-handed":{ label: "Two-Handed",hint: "Requires both hands." },
unwieldy: { label: "Unwieldy", hint: "Cannot attack more than once per Round." },
versatile: { label: "Versatile", hint: "Two-handed: -1 DR to hit and +1 damage." },
},
get weaponPropertyLabels() {
return Object.fromEntries(Object.entries(this.weaponProperties).map(([k, v]) => [k, v.label]))
},
get weaponPropertyHints() {
return Object.fromEntries(Object.entries(this.weaponProperties).map(([k, v]) => [k, v.hint]))
},
resonanceList: {
accelerate: "Accelerate",
blast: "Blast",
@@ -107,7 +139,7 @@ export const SYSTEM = {
{ name: "Clothing (Average)", type: "armor", armorDie: "d2", penalty: 0 },
{ name: "Helm", type: "armor", armorDie: "d2", penalty: 0 },
{ name: "Medium Shield", type: "shield", armorDie: "d4", penalty: 0 },
{ name: "Gambeson", type: "armor", armorDie: "d4", penalty: 0 },
{ name: "Gambeson", type: "armor", armorDie: "d2", penalty: 0 },
{ name: "Padded Leather", type: "armor", armorDie: "d4", penalty: 1 },
{ name: "Chain Shirt", type: "armor", armorDie: "d6", penalty: 1 },
{ name: "Half Plate", type: "armor", armorDie: "d8", penalty: 2 },
+14 -1
View File
@@ -93,6 +93,10 @@ export default class MGNEActor extends Actor {
return result
}
async rollArmorSave() {
return MGNERoll.rollArmorSave(this)
}
async rollWeapon(itemId) {
const item = this.items.get(itemId)
if (!item) return null
@@ -129,7 +133,10 @@ export default class MGNEActor extends Actor {
async rollProfileAttack() {
const attackBaseLabel = normalizeGenericActionLabel(this.system.attack?.label ?? t("MGNE.Common.Attack"), t("MGNE.Common.Attack"))
const attackLabel = formatActionLabel(attackBaseLabel, t("MGNE.Common.Attack"))
const result = await this.rollAbility("strength", {
// Creatures have no ability scores; ability value defaults to 0 via roll.mjs
const result = await MGNERoll.promptCheck({
actor: this,
abilityId: "strength",
label: attackLabel,
rollType: "attack",
})
@@ -220,6 +227,12 @@ export default class MGNEActor extends Actor {
return item.rollUsage()
}
async rollDurability(itemId) {
const item = this.items.get(itemId)
if (!item) return null
return MGNERoll.rollDurability(item)
}
async quickRest() {
const roll = await (new Roll("1d4")).evaluate()
const hp = this.system.hp?.value ?? 0
+126 -12
View File
@@ -48,33 +48,54 @@ function numericOptions(min, max, current = null) {
})
}
const OUTCOME_ICONS = {
"critical-success": '<i class="fa-solid fa-star"></i>',
"success": '<i class="fa-solid fa-check"></i>',
"steady": '<i class="fa-solid fa-minus"></i>',
"failure": '<i class="fa-solid fa-xmark"></i>',
"broken": '<i class="fa-solid fa-skull"></i>',
"fumble": '<i class="fa-solid fa-skull"></i>',
"rolled": '<i class="fa-solid fa-bolt"></i>',
}
const SPECIAL_ICONS = {
default: '<i class="fa-solid fa-circle-info"></i>',
}
async function renderCard(context) {
const outcomeClass = `${context.outcome ?? ""}`
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
const eyebrow = context.eyebrow ?? getChatModeLabel(context.mode ?? "generic")
const normalizedEyebrow = `${eyebrow}`.trim().toLowerCase()
const normalizedLabel = `${context.label ?? ""}`.trim().toLowerCase()
const modeLabel = getChatModeLabel(context.mode ?? "generic")
const outcomeIcon = OUTCOME_ICONS[outcomeClass] || ""
const specialIcon = SPECIAL_ICONS.default
// Render dice tooltip HTML if a roll was provided
const diceTooltip = context._roll ? await context._roll.render() : null
return foundry.applications.handlebars.renderTemplate(`systems/${SYSTEM_ID}/templates/chat-message.hbs`, {
...context,
modeClass: context.mode ?? "generic",
eyebrow: "",
modeLabel,
outcomeClass,
outcomeIcon,
specialIcon,
diceTooltip,
})
}
export default class MGNERoll {
static async promptCheck({ actor, abilityId, label, baseDR = 12, rollType = "check", item = null }) {
static async promptCheck({ actor, abilityId, label, baseDR = 12, modifier = 0, rollType = "check", item = null }) {
const abilityLabel = SYSTEM.abilities[abilityId]?.label ?? abilityId
const content = await foundry.applications.handlebars.renderTemplate(`systems/${SYSTEM_ID}/templates/roll-dialog.hbs`, {
actorName: actor.name,
label,
abilityLabel,
baseDR,
modifier,
drOptions: numericOptions(6, 20, baseDR),
modifierOptions: numericOptions(-6, 6, 0),
modifierOptions: numericOptions(-6, 6, modifier),
omens: actor.system.omens?.current ?? 0,
rollType,
})
@@ -95,15 +116,15 @@ export default class MGNERoll {
if (!dialogData) return null
const modifier = Number.parseInt(dialogData.modifier ?? 0, 10) || 0
const dialogModifier = Number.parseInt(dialogData.modifier ?? 0, 10) || 0
const spendOmen = Boolean(dialogData.spendOmen)
// Re-read omens after dialog close to avoid race condition (omen could have changed)
const currentOmensAfterDialog = actor.system.omens?.current ?? 0
const canSpendOmen = spendOmen && currentOmensAfterDialog > 0
const dr = (Number.parseInt(dialogData.dr ?? baseDR, 10) || baseDR) - (canSpendOmen ? 4 : 0)
const abilityValue = actor.system.abilities?.[abilityId]?.value ?? 0
const sign = modifier >= 0 ? "+" : "-"
const formula = modifier === 0 ? `1d20 + ${abilityValue}` : `1d20 + ${abilityValue} ${sign} ${Math.abs(modifier)}`
const sign = dialogModifier >= 0 ? "+" : "-"
const formula = dialogModifier === 0 ? `1d20 + ${abilityValue}` : `1d20 + ${abilityValue} ${sign} ${Math.abs(dialogModifier)}`
const roll = await (new Roll(formula)).evaluate()
const natural = roll.dice?.[0]?.results?.[0]?.result ?? roll.total
@@ -139,6 +160,7 @@ export default class MGNERoll {
}
const showDamageButton = rollType === "attack" && (success || critical) && !!item
const showOmenRerollButton = Boolean(omenRerollReminder)
const contentHtml = await renderCard({
mode: "check",
actorName: actor.name,
@@ -156,6 +178,15 @@ export default class MGNERoll {
damageItemId: showDamageButton ? item.id : null,
damageFormula: showDamageButton ? (item.system.damage || "1") : null,
damageCritical: showDamageButton && critical,
showOmenRerollButton,
omenRerollActorId: showOmenRerollButton ? actor.id : null,
omenRerollAbilityId: showOmenRerollButton ? abilityId : null,
omenRerollLabel: showOmenRerollButton ? label : null,
omenRerollBaseDR: showOmenRerollButton ? baseDR : null,
omenRerollModifier: showOmenRerollButton ? dialogModifier : 0,
omenRerollRollType: showOmenRerollButton ? rollType : null,
omenRerollItemId: showOmenRerollButton ? item?.id : null,
_roll: roll,
})
await ChatMessage.create({
@@ -181,6 +212,7 @@ export default class MGNERoll {
total: roll.total,
outcome: broken ? t("MGNE.Roll.OutcomeBroken") : t("MGNE.Roll.OutcomeSteady"),
specialText: broken ? t("MGNE.Roll.MoraleBrokenText") : "",
_roll: roll,
})
await ChatMessage.create({
@@ -192,7 +224,7 @@ export default class MGNERoll {
return { roll, broken }
}
static async rollDamage({ actor, item }) {
static async rollDamage({ actor, item, targetActor = null }) {
const damageBonus = await actor.consumePendingDamageBonus(item.id)
const multiplier = damageBonus?.multiplier ?? 1
const baseFormula = item.system.damage || "1"
@@ -226,7 +258,7 @@ export default class MGNERoll {
mode: "damage",
actorName: actor.name,
actorImg: actor.img,
label: `${item.name} Damage`,
label: f("MGNE.Roll.ItemDamageLabel", { item: item.name }),
subtitle: null,
formula: roll.formula,
total: roll.total,
@@ -236,6 +268,8 @@ export default class MGNERoll {
showApplyButton: true,
damageTotal: roll.total,
damageCritical: isCritical,
damageTargetActorId: targetActor?.id ?? null,
_roll: roll,
})
await ChatMessage.create({
@@ -247,7 +281,7 @@ export default class MGNERoll {
return { roll }
}
static async rollFlatDamage({ actor, label, formula }) {
static async rollFlatDamage({ actor, label, formula, targetActor = null }) {
const damageBonus = await actor.consumePendingDamageBonus("profile-attack")
const multiplier = damageBonus?.multiplier ?? 1
const baseFormula = formula || "1"
@@ -267,6 +301,8 @@ export default class MGNERoll {
showApplyButton: true,
damageTotal: roll.total,
damageCritical: isCritical,
damageTargetActorId: targetActor?.id ?? null,
_roll: roll,
})
await ChatMessage.create({
@@ -278,6 +314,39 @@ export default class MGNERoll {
return { roll }
}
static async rollArmorSave(actor) {
const formula = actor.getArmorRollFormula()
const items = actor.getEquippedArmorItems()
if (formula === "0") {
ui.notifications.warn(t("MGNE.Notification.NoArmorEquipped"))
return null
}
const roll = await (new Roll(formula)).evaluate()
const armorNames = items.map(i => i.name).join(" + ")
const contentHtml = await renderCard({
mode: "armor",
actorName: actor.name,
actorImg: actor.img,
label: t("MGNE.Roll.ArmorSave"),
subtitle: armorNames,
formula: roll.formula,
total: roll.total,
outcome: f("MGNE.Roll.ArmorAbsorbed", { amount: roll.total }),
_roll: roll,
})
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor }),
rolls: [roll],
content: contentHtml,
})
return roll
}
static async rollUsage(item) {
const currentDie = item.system.usageDie
if (!currentDie || currentDie === "depleted") {
@@ -303,6 +372,7 @@ export default class MGNERoll {
total: roll.total,
outcome: depleted ? f("MGNE.Roll.DowngradedTo", { die: nextDie.toUpperCase() }) : t("MGNE.Roll.NoChange"),
specialText: depleted && nextDie === "depleted" ? t("MGNE.Roll.ItemNowDepleted") : "",
_roll: roll,
})
await ChatMessage.create({
@@ -314,6 +384,48 @@ export default class MGNERoll {
return { roll, depleted, nextDie }
}
static async rollDurability(item) {
const currentDie = item.system.durabilityDie
if (!currentDie || currentDie === "depleted") {
ui.notifications.warn(f("MGNE.Notification.ItemDurabilityDepleted", { item: item.name }))
return null
}
const roll = await (new Roll(`1${currentDie}`)).evaluate()
const degraded = roll.total <= 2
const nextDie = degraded ? stepDownDie(currentDie) : currentDie
const nowBroken = degraded && nextDie === "depleted"
const updates = { "system.durabilityDie": nextDie }
if (nowBroken) {
updates["system.broken"] = true
if ("equipped" in (item.system ?? {})) updates["system.equipped"] = false
}
await item.update(updates)
const contentHtml = await renderCard({
mode: "durability",
actorName: item.parent?.name ?? item.name,
actorImg: item.img,
label: f("MGNE.Roll.DurabilityLabel", { item: item.name }),
subtitle: f("MGNE.Roll.CurrentDie", { die: currentDie.toUpperCase() }),
formula: roll.formula,
total: roll.total,
outcome: degraded
? f("MGNE.Roll.DowngradedTo", { die: nextDie.toUpperCase() })
: t("MGNE.Roll.NoChange"),
specialText: nowBroken ? t("MGNE.Roll.ItemNowBroken") : "",
_roll: roll,
})
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: item.parent ?? null }),
rolls: [roll],
content: contentHtml,
})
return { roll, degraded, nextDie, nowBroken }
}
static async applyDamageCard({ actor, sourceActor = null, sourceItem = null, amount, armorRoll = null, appliedDamage, newHp, breakText = "", defenseFumbleText = "", criticalArmorText = "" }) {
const contentHtml = await renderCard({
mode: "apply-damage",
@@ -335,6 +447,7 @@ export default class MGNERoll {
criticalArmorText,
breakText ? f("MGNE.Roll.BreakText", { text: breakText }) : "",
]),
_roll: armorRoll ?? null,
})
await ChatMessage.create({
@@ -359,6 +472,7 @@ export default class MGNERoll {
total: roll.total,
outcome,
specialText,
_roll: roll,
})
await ChatMessage.create({
+2
View File
@@ -1,6 +1,7 @@
export { default as MGNECharacter } from "./character.mjs"
export { default as MGNECreature } from "./creature.mjs"
export { default as MGNECompanion } from "./companion.mjs"
export { default as MGNEParty } from "./party.mjs"
export { default as MGNEWeapon } from "./weapon.mjs"
export { default as MGNEArmor } from "./armor.mjs"
export { default as MGNEShield } from "./shield.mjs"
@@ -8,3 +9,4 @@ export { default as MGNEEquipment } from "./equipment.mjs"
export { default as MGNEResonanceCore } from "./resonance-core.mjs"
export { default as MGNEArtifact } from "./artifact.mjs"
export { default as MGNEFeature } from "./feature.mjs"
export { default as MGNECreatureTrait } from "./creature-trait.mjs"
+8
View File
@@ -12,8 +12,16 @@ export default class MGNEArmor extends foundry.abstract.TypeDataModel {
choices: SYSTEM.armorDieChoices,
}),
penalty: numberField(0, 0, 6),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "heavy",
choices: SYSTEM.weightCategories,
}),
equipped: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
}
}
+8 -6
View File
@@ -5,12 +5,6 @@ export default class MGNEArtifact extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: htmlField(""),
artifactId: new foundry.data.fields.StringField({
required: true,
nullable: false,
initial: "shiver-lens",
choices: SYSTEM.artifactChoices,
}),
synchronized: booleanField(false),
synchronizedTo: stringField(""),
usageDie: new foundry.data.fields.StringField({
@@ -20,6 +14,14 @@ export default class MGNEArtifact extends foundry.abstract.TypeDataModel {
choices: SYSTEM.usageDieChoices,
}),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
}
}
+29 -1
View File
@@ -25,6 +25,7 @@ export default class MGNECharacter extends foundry.abstract.TypeDataModel {
}),
conditions: conditionSchema(),
carryCapacity: numberField(8, 0),
carryCapacityModifier: numberField(0, 0),
rations: numberField(0, 0),
kiffol: numberField(0, 0),
background: stringField(""),
@@ -40,11 +41,38 @@ export default class MGNECharacter extends foundry.abstract.TypeDataModel {
prepareDerivedData() {
super.prepareDerivedData()
this.carryCapacity = (this.abilities.strength?.value ?? 0) + 8
this.resonance.remaining = Math.max(0, (this.resonance.max ?? 0) - (this.resonance.used ?? 0))
this.syncLimit = Math.max(0, this.abilities.toughness?.value ?? 0)
this.syncRemaining = Math.max(0, this.syncLimit - (this.artifactSync.used ?? 0))
this.armorFormula = this.parent?.getArmorRollFormula?.() ?? "0"
// Compute carry capacity: base STR + 8 + feature capacities + direct modifier
const featureCapacity = (this.parent?.items ?? [])
.filter(i => i.type === "feature")
.reduce((sum, f) => sum + (f.system?.capacity ?? 0), 0)
this.carryCapacity = (this.abilities.strength?.value ?? 0) + 8 + featureCapacity + (this.carryCapacityModifier ?? 0)
// Compute current load per RAW:
// Only items with a weight field count — features and creature-traits are excluded
// trivial = 0, light = 10 per slot, normal = 1, heavy = 2
let normalLoad = 0
let lightCount = 0
let heavyCount = 0
for (const item of (this.parent?.items ?? [])) {
if (!("weight" in (item.system ?? {}))) continue // no weight field (features, traits)
if (item.system?.carried === false) continue // not being carried
const w = item.system?.weight ?? "normal"
if (w === "trivial") continue
else if (w === "light") lightCount++
else if (w === "normal") normalLoad++
else if (w === "heavy") heavyCount++
}
normalLoad += Math.floor(lightCount / 10)
this.lightItemCount = lightCount
this.heavyItemCount = heavyCount
this.currentLoad = normalLoad + heavyCount * 2
this.overloaded = this.currentLoad > this.carryCapacity
}
/** @override */
+11 -4
View File
@@ -1,12 +1,11 @@
import { SYSTEM } from "../config/system.mjs"
import { abilitySchema, htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
import { htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
export default class MGNECompanion extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
abilities: abilitySchema(),
hp: trackSchema(1, 1),
morale: numberField(7, 2, 12),
armor: new fields.SchemaField({
@@ -19,8 +18,8 @@ export default class MGNECompanion extends foundry.abstract.TypeDataModel {
valueText: stringField(""),
traitText: stringField(""),
specialtyText: stringField(""),
adventuringBehavior: htmlField(""),
combatBehavior: htmlField(""),
adventuringBehaviorUuid: stringField(""),
combatBehaviorUuid: stringField(""),
upkeep: stringField("3d10c per full rest"),
description: htmlField(""),
notes: htmlField(""),
@@ -29,4 +28,12 @@ export default class MGNECompanion extends foundry.abstract.TypeDataModel {
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.Companion"]
/** @override */
static migrateData(source) {
// Remove old html behavior fields if present
if ("adventuringBehavior" in source) delete source.adventuringBehavior
if ("combatBehavior" in source) delete source.combatBehavior
return super.migrateData(source)
}
}
+13
View File
@@ -0,0 +1,13 @@
import { htmlField, stringField } from "./shared.mjs"
export default class MGNECreatureTrait extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: htmlField(""),
trigger: stringField(""),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.CreatureTrait"]
}
+20 -8
View File
@@ -1,27 +1,39 @@
import { SYSTEM } from "../config/system.mjs"
import { abilitySchema, htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
import { htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
const CREATURE_TYPES = ["human", "construct", "animal"]
export default class MGNECreature extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
abilities: abilitySchema(),
hp: trackSchema(1, 1),
morale: numberField(7, 2, 12),
armor: new fields.SchemaField({
die: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.armorDieChoices }),
}),
attack: new fields.SchemaField({
label: stringField("Attack"),
damage: stringField("1d4"),
}),
creatureType: new fields.SetField(
new fields.StringField({ required: true, choices: CREATURE_TYPES }),
{ required: true, nullable: false, initial: [] }
),
number: stringField("1"),
actionTableUuid: stringField(""),
description: htmlField(""),
special: htmlField(""),
notes: htmlField(""),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.Creature"]
/** @override */
static migrateData(source) {
// Remove old attack field if present (no longer part of the schema)
if ("attack" in source) delete source.attack
// Form submissions send null for unchecked checkboxes in array fields — filter them out
if (Array.isArray(source.creatureType)) {
source.creatureType = source.creatureType.filter(v => v != null && v !== "")
}
return super.migrateData(source)
}
}
+9
View File
@@ -21,6 +21,15 @@ export default class MGNEEquipment extends foundry.abstract.TypeDataModel {
choices: SYSTEM.usageDieChoices,
}),
consumable: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
}
}
+6 -5
View File
@@ -1,15 +1,16 @@
import { SYSTEM } from "../config/system.mjs"
import { htmlField } from "./shared.mjs"
import { htmlField, numberField } from "./shared.mjs"
export default class MGNEFeature extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
featureId: new foundry.data.fields.StringField({
required: true,
nullable: false,
initial: "akimbo-hit-priest",
choices: SYSTEM.featureChoices,
required: false,
nullable: true,
blank: true,
initial: "",
}),
capacity: numberField(0, 0),
description: htmlField(""),
}
}
+20
View File
@@ -0,0 +1,20 @@
import { htmlField } from "./shared.mjs"
export default class MGNEParty extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
memberRefs: new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({ required: true, nullable: false, blank: false }),
})
),
credits: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
notes: htmlField(""),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.Party"]
}
+9
View File
@@ -18,6 +18,15 @@ export default class MGNEResonanceCore extends foundry.abstract.TypeDataModel {
choices: SYSTEM.usageDieChoices,
}),
burnedOut: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "trivial",
choices: SYSTEM.weightCategories,
}),
}
}
+8
View File
@@ -12,8 +12,16 @@ export default class MGNEShield extends foundry.abstract.TypeDataModel {
choices: SYSTEM.armorDieChoices,
}),
penalty: numberField(0, 0, 4),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
equipped: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
}
}
+28 -1
View File
@@ -13,7 +13,14 @@ export default class MGNEWeapon extends foundry.abstract.TypeDataModel {
}),
damage: stringField("1d4"),
range: stringField("Touch"),
properties: stringField(""),
properties: new foundry.data.fields.SetField(
new foundry.data.fields.StringField({ required: true, nullable: false, blank: false }),
{ required: true, nullable: false, initial: [] }
),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
usageDie: new foundry.data.fields.StringField({
required: true,
nullable: false,
@@ -23,9 +30,29 @@ export default class MGNEWeapon extends foundry.abstract.TypeDataModel {
quantity: numberField(1, 0),
equipped: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.Weapon"]
/**
* Migrate old string-based properties field to the new SetField format.
* @override
*/
static migrateData(source) {
// Old data stored properties as a plain string; convert to empty array
if (typeof source.properties === "string") {
source.properties = []
}
// Remove any null/undefined/blank entries that may have crept in
if (Array.isArray(source.properties)) {
source.properties = source.properties.filter(p => p != null && p !== "")
}
return super.migrateData(source)
}
}
+528 -21
View File
@@ -7,10 +7,13 @@
"": {
"name": "fvtt-machine-gods-noxian-expanse",
"version": "0.1.0",
"hasInstallScript": true,
"license": "UNLICENSED",
"devDependencies": {
"@foundryvtt/foundryvtt-cli": "^3.0.3",
"less": "^4.6.4"
"classic-level": "^3.0.0",
"less": "^4.6.4",
"patch-package": "^8.0.1"
}
},
"node_modules/@foundryvtt/foundryvtt-cli": {
@@ -35,6 +38,53 @@
"node": ">17.0.0"
}
},
"node_modules/@foundryvtt/foundryvtt-cli/node_modules/abstract-level": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz",
"integrity": "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"catering": "^2.1.0",
"is-buffer": "^2.0.5",
"level-supports": "^4.0.0",
"level-transcoder": "^1.0.1",
"module-error": "^1.0.1",
"queue-microtask": "^1.2.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@foundryvtt/foundryvtt-cli/node_modules/classic-level": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.4.1.tgz",
"integrity": "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"abstract-level": "^1.0.2",
"catering": "^2.1.0",
"module-error": "^1.0.1",
"napi-macros": "^2.2.2",
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@foundryvtt/foundryvtt-cli/node_modules/level-supports": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz",
"integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/@seald-io/binary-search-tree": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.3.tgz",
@@ -53,23 +103,29 @@
"util": "^0.12.5"
}
},
"node_modules/@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/abstract-level": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz",
"integrity": "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-3.1.1.tgz",
"integrity": "sha512-CW2gKbJFTuX1feMvOrvsVMmijAOgI9kg2Ie9Dq3gOcMt/dVVoVmqNlLcEUCT13NxHFMEajcUcVBIplbyDroDiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"catering": "^2.1.0",
"is-buffer": "^2.0.5",
"level-supports": "^4.0.0",
"level-supports": "^6.2.0",
"level-transcoder": "^1.0.1",
"module-error": "^1.0.1",
"queue-microtask": "^1.2.3"
"maybe-combine-errors": "^1.0.0",
"module-error": "^1.0.1"
},
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/ansi-regex": {
@@ -142,6 +198,19 @@
],
"license": "MIT"
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -240,22 +309,37 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/classic-level": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.4.1.tgz",
"integrity": "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-3.0.0.tgz",
"integrity": "sha512-yGy8j8LjPbN0Bh3+ygmyYvrmskVita92pD/zCoalfcC9XxZj6iDtZTAnz+ot7GG8p9KLTG+MZ84tSA4AhkgVZQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"abstract-level": "^1.0.2",
"catering": "^2.1.0",
"abstract-level": "^3.1.0",
"module-error": "^1.0.1",
"napi-macros": "^2.2.2",
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/cliui": {
@@ -309,6 +393,21 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -416,6 +515,29 @@
"node": ">=6"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/find-yarn-workspace-root": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"micromatch": "^4.0.2"
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -432,6 +554,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -519,8 +656,17 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC",
"optional": true
"license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
@@ -694,6 +840,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -724,6 +886,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -772,6 +944,33 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"dev": true,
"license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
@@ -785,6 +984,59 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-stable-stringify": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
"integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
"call-bound": "^1.0.4",
"isarray": "^2.0.5",
"jsonify": "^0.0.1",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/jsonfile": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonify": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
"dev": true,
"license": "Public Domain",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/klaw-sync": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.11"
}
},
"node_modules/less": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz",
@@ -812,13 +1064,13 @@
}
},
"node_modules/level-supports": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz",
"integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/level-supports/-/level-supports-6.2.0.tgz",
"integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
"node": ">=16"
}
},
"node_modules/level-transcoder": {
@@ -880,6 +1132,30 @@
"node": ">= 0.4"
}
},
"node_modules/maybe-combine-errors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz",
"integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -894,6 +1170,16 @@
"node": ">=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/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
@@ -967,6 +1253,33 @@
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/open": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
@@ -977,6 +1290,89 @@
"node": ">= 0.10"
}
},
"node_modules/patch-package": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.2",
"ci-info": "^3.7.0",
"cross-spawn": "^7.0.3",
"find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^10.0.0",
"json-stable-stringify": "^1.0.2",
"klaw-sync": "^6.0.0",
"minimist": "^1.2.6",
"open": "^7.4.2",
"semver": "^7.5.3",
"slash": "^2.0.0",
"tmp": "^0.2.4",
"yaml": "^2.2.2"
},
"bin": {
"patch-package": "index.js"
},
"engines": {
"node": ">=14",
"npm": ">5"
}
},
"node_modules/patch-package/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/patch-package/node_modules/semver": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
@@ -1103,6 +1499,39 @@
"node": ">= 0.4"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -1142,6 +1571,52 @@
"node": ">=8"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tmp": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.14"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@@ -1156,6 +1631,22 @@
"which-typed-array": "^1.1.2"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/which-typed-array": {
"version": "1.1.20",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
@@ -1206,6 +1697,22 @@
"node": ">=10"
}
},
"node_modules/yaml": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+5 -2
View File
@@ -10,10 +10,13 @@
"watch:css": "lessc --watch less/mgne.less css/mgne.css",
"build:packs": "node ./tools/packCompendiums.mjs",
"unpack:packs": "node ./tools/unpackCompendiums.mjs",
"build": "npm run build:css && npm run build:packs"
"build": "npm run build:css && npm run build:packs",
"postinstall": "patch-package"
},
"devDependencies": {
"@foundryvtt/foundryvtt-cli": "^3.0.3",
"less": "^4.6.4"
"classic-level": "^3.0.0",
"less": "^4.6.4",
"patch-package": "^8.0.1"
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000006
MANIFEST-000032
+14 -8
View File
@@ -1,8 +1,14 @@
2026/05/08-21:55:31.407696 7f90423fc6c0 Recovering log #4
2026/05/08-21:55:31.469619 7f90423fc6c0 Delete type=3 #2
2026/05/08-21:55:31.469673 7f90423fc6c0 Delete type=0 #4
2026/05/08-23:13:08.593524 7f9040fff6c0 Level-0 table #9: started
2026/05/08-23:13:08.596826 7f9040fff6c0 Level-0 table #9: 1354 bytes OK
2026/05/08-23:13:08.603659 7f9040fff6c0 Delete type=0 #7
2026/05/08-23:13:08.630872 7f9040fff6c0 Manual compaction at level-0 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
2026/05/08-23:13:08.630900 7f9040fff6c0 Manual compaction at level-1 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
2026/06/08-22:30:14.302066 7f5208bff6c0 Recovering log #29
2026/06/08-22:30:14.311260 7f5208bff6c0 Delete type=3 #27
2026/06/08-22:30:14.311276 7f5208bff6c0 Delete type=0 #29
2026/06/08-22:30:22.508073 7f51ba7fc6c0 Level-0 table #35: started
2026/06/08-22:30:22.511267 7f51ba7fc6c0 Level-0 table #35: 1820 bytes OK
2026/06/08-22:30:22.517613 7f51ba7fc6c0 Delete type=0 #33
2026/06/08-22:30:22.543986 7f51ba7fc6c0 Manual compaction at level-0 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 41 : 1
2026/06/08-22:30:22.543992 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:30:22.546985 7f51ba7fc6c0 Generated table #36@0: 9 keys, 1445 bytes
2026/06/08-22:30:22.546989 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 1445 bytes
2026/06/08-22:30:22.553011 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:30:22.553091 7f51ba7fc6c0 Delete type=2 #31
2026/06/08-22:30:22.553161 7f51ba7fc6c0 Delete type=2 #35
2026/06/08-22:30:22.563914 7f51ba7fc6c0 Manual compaction at level-0 from '!items!null' @ 41 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
+14 -5
View File
@@ -1,5 +1,14 @@
2026/05/08-21:43:50.273188 7fe6227fc6c0 Delete type=3 #1
2026/05/08-21:43:50.275745 7fe621ffb6c0 Level-0 table #5: started
2026/05/08-21:43:50.279227 7fe621ffb6c0 Level-0 table #5: 1176 bytes OK
2026/05/08-21:43:50.285623 7fe621ffb6c0 Delete type=0 #3
2026/05/08-21:43:50.285800 7fe621ffb6c0 Manual compaction at level-0 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!mgne-shd-medshield' @ 0 : 0; will stop at (end)
2026/06/08-22:28:49.244498 7f51baffd6c0 Recovering log #24
2026/06/08-22:28:49.253761 7f51baffd6c0 Delete type=3 #22
2026/06/08-22:28:49.253785 7f51baffd6c0 Delete type=0 #24
2026/06/08-22:29:08.699377 7f51ba7fc6c0 Level-0 table #30: started
2026/06/08-22:29:08.702874 7f51ba7fc6c0 Level-0 table #30: 1820 bytes OK
2026/06/08-22:29:08.709319 7f51ba7fc6c0 Delete type=0 #28
2026/06/08-22:29:08.715547 7f51ba7fc6c0 Manual compaction at level-0 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 33 : 1
2026/06/08-22:29:08.715551 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:29:08.718787 7f51ba7fc6c0 Generated table #31@0: 9 keys, 1445 bytes
2026/06/08-22:29:08.718802 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 1445 bytes
2026/06/08-22:29:08.724761 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:29:08.724834 7f51ba7fc6c0 Delete type=2 #26
2026/06/08-22:29:08.724899 7f51ba7fc6c0 Delete type=2 #30
2026/06/08-22:29:08.737992 7f51ba7fc6c0 Manual compaction at level-0 from '!items!null' @ 33 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000006
MANIFEST-000030
+14 -8
View File
@@ -1,8 +1,14 @@
2026/05/08-21:55:31.289051 7f90433fe6c0 Recovering log #4
2026/05/08-21:55:31.355282 7f90433fe6c0 Delete type=3 #2
2026/05/08-21:55:31.355328 7f90433fe6c0 Delete type=0 #4
2026/05/08-23:13:08.563586 7f9040fff6c0 Level-0 table #9: started
2026/05/08-23:13:08.566679 7f9040fff6c0 Level-0 table #9: 4899 bytes OK
2026/05/08-23:13:08.572808 7f9040fff6c0 Delete type=0 #7
2026/05/08-23:13:08.593335 7f9040fff6c0 Manual compaction at level-0 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at (end)
2026/05/08-23:13:08.593380 7f9040fff6c0 Manual compaction at level-1 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at (end)
2026/06/08-22:30:14.280655 7f51bb7fe6c0 Recovering log #27
2026/06/08-22:30:14.289768 7f51bb7fe6c0 Delete type=3 #25
2026/06/08-22:30:14.289787 7f51bb7fe6c0 Delete type=0 #27
2026/06/08-22:30:22.461106 7f51ba7fc6c0 Level-0 table #33: started
2026/06/08-22:30:22.464602 7f51ba7fc6c0 Level-0 table #33: 4899 bytes OK
2026/06/08-22:30:22.471064 7f51ba7fc6c0 Delete type=0 #31
2026/06/08-22:30:22.489630 7f51ba7fc6c0 Manual compaction at level-0 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at '!actors!null' @ 21 : 1
2026/06/08-22:30:22.489634 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:30:22.492585 7f51ba7fc6c0 Generated table #34@0: 5 keys, 3562 bytes
2026/06/08-22:30:22.492593 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 3562 bytes
2026/06/08-22:30:22.499213 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:30:22.499275 7f51ba7fc6c0 Delete type=2 #29
2026/06/08-22:30:22.499325 7f51ba7fc6c0 Delete type=2 #33
2026/06/08-22:30:22.507968 7f51ba7fc6c0 Manual compaction at level-0 from '!actors!null' @ 21 : 1 .. '!actors!null' @ 0 : 0; will stop at (end)
+14 -5
View File
@@ -1,5 +1,14 @@
2026/05/08-21:43:50.302039 7fe6227fc6c0 Delete type=3 #1
2026/05/08-21:43:50.303179 7fe621ffb6c0 Level-0 table #5: started
2026/05/08-21:43:50.306422 7fe621ffb6c0 Level-0 table #5: 2597 bytes OK
2026/05/08-21:43:50.313231 7fe621ffb6c0 Delete type=0 #3
2026/05/08-21:43:50.313379 7fe621ffb6c0 Manual compaction at level-0 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!mgne-comp-silicon-cantor' @ 0 : 0; will stop at (end)
2026/06/08-22:28:49.222288 7f51bb7fe6c0 Recovering log #22
2026/06/08-22:28:49.232786 7f51bb7fe6c0 Delete type=3 #20
2026/06/08-22:28:49.232806 7f51bb7fe6c0 Delete type=0 #22
2026/06/08-22:29:08.601469 7f51ba7fc6c0 Level-0 table #28: started
2026/06/08-22:29:08.604604 7f51ba7fc6c0 Level-0 table #28: 4899 bytes OK
2026/06/08-22:29:08.610544 7f51ba7fc6c0 Delete type=0 #26
2026/06/08-22:29:08.630909 7f51ba7fc6c0 Manual compaction at level-0 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at '!actors!null' @ 17 : 1
2026/06/08-22:29:08.630913 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:29:08.633954 7f51ba7fc6c0 Generated table #29@0: 5 keys, 3562 bytes
2026/06/08-22:29:08.633965 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 3562 bytes
2026/06/08-22:29:08.639624 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:29:08.639660 7f51ba7fc6c0 Delete type=2 #24
2026/06/08-22:29:08.639710 7f51ba7fc6c0 Delete type=2 #28
2026/06/08-22:29:08.649354 7f51ba7fc6c0 Manual compaction at level-0 from '!actors!null' @ 17 : 1 .. '!actors!null' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000006
MANIFEST-000030
+14 -8
View File
@@ -1,8 +1,14 @@
2026/05/08-21:55:31.191179 7f90423fc6c0 Recovering log #4
2026/05/08-21:55:31.286675 7f90423fc6c0 Delete type=3 #2
2026/05/08-21:55:31.286748 7f90423fc6c0 Delete type=0 #4
2026/05/08-23:13:08.553332 7f9040fff6c0 Level-0 table #9: started
2026/05/08-23:13:08.556569 7f9040fff6c0 Level-0 table #9: 10414 bytes OK
2026/05/08-23:13:08.563470 7f9040fff6c0 Delete type=0 #7
2026/05/08-23:13:08.593313 7f9040fff6c0 Manual compaction at level-0 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
2026/05/08-23:13:08.593373 7f9040fff6c0 Manual compaction at level-1 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
2026/06/08-22:30:14.270038 7f5208bff6c0 Recovering log #27
2026/06/08-22:30:14.279434 7f5208bff6c0 Delete type=3 #25
2026/06/08-22:30:14.279458 7f5208bff6c0 Delete type=0 #27
2026/06/08-22:30:22.441981 7f51ba7fc6c0 Level-0 table #33: started
2026/06/08-22:30:22.445390 7f51ba7fc6c0 Level-0 table #33: 10411 bytes OK
2026/06/08-22:30:22.452159 7f51ba7fc6c0 Delete type=0 #31
2026/06/08-22:30:22.471146 7f51ba7fc6c0 Manual compaction at level-0 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 181 : 1
2026/06/08-22:30:22.471150 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:30:22.474171 7f51ba7fc6c0 Generated table #34@0: 37 keys, 8891 bytes
2026/06/08-22:30:22.474190 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 8891 bytes
2026/06/08-22:30:22.480499 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:30:22.480746 7f51ba7fc6c0 Delete type=2 #29
2026/06/08-22:30:22.480804 7f51ba7fc6c0 Delete type=2 #33
2026/06/08-22:30:22.507958 7f51ba7fc6c0 Manual compaction at level-0 from '!items!null' @ 181 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
+14 -5
View File
@@ -1,5 +1,14 @@
2026/05/08-21:43:50.329948 7fe6227fc6c0 Delete type=3 #1
2026/05/08-21:43:50.330941 7fe621ffb6c0 Level-0 table #5: started
2026/05/08-21:43:50.335360 7fe621ffb6c0 Level-0 table #5: 8674 bytes OK
2026/05/08-21:43:50.341467 7fe621ffb6c0 Delete type=0 #3
2026/05/08-21:43:50.341610 7fe621ffb6c0 Manual compaction at level-0 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!mgne-feat-66' @ 0 : 0; will stop at (end)
2026/06/08-22:28:49.211463 7f51bbfff6c0 Recovering log #22
2026/06/08-22:28:49.220803 7f51bbfff6c0 Delete type=3 #20
2026/06/08-22:28:49.220825 7f51bbfff6c0 Delete type=0 #22
2026/06/08-22:29:08.581725 7f51ba7fc6c0 Level-0 table #28: started
2026/06/08-22:29:08.585305 7f51ba7fc6c0 Level-0 table #28: 10414 bytes OK
2026/06/08-22:29:08.591259 7f51ba7fc6c0 Delete type=0 #26
2026/06/08-22:29:08.610605 7f51ba7fc6c0 Manual compaction at level-0 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 145 : 1
2026/06/08-22:29:08.610609 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:29:08.614617 7f51ba7fc6c0 Generated table #29@0: 37 keys, 8891 bytes
2026/06/08-22:29:08.614629 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 8891 bytes
2026/06/08-22:29:08.620444 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:29:08.620779 7f51ba7fc6c0 Delete type=2 #24
2026/06/08-22:29:08.620832 7f51ba7fc6c0 Delete type=2 #28
2026/06/08-22:29:08.649344 7f51ba7fc6c0 Manual compaction at level-0 from '!items!null' @ 145 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000010
View File
+7
View File
@@ -0,0 +1,7 @@
2026/06/08-22:30:14.247791 7f5208bff6c0 Recovering log #8
2026/06/08-22:30:14.256936 7f5208bff6c0 Delete type=3 #6
2026/06/08-22:30:14.256960 7f5208bff6c0 Delete type=0 #8
2026/06/08-22:30:22.435551 7f51ba7fc6c0 Level-0 table #13: started
2026/06/08-22:30:22.435653 7f51ba7fc6c0 Level-0 table #13: 0 bytes OK
2026/06/08-22:30:22.441906 7f51ba7fc6c0 Delete type=0 #11
2026/06/08-22:30:22.471135 7f51ba7fc6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
+7
View File
@@ -0,0 +1,7 @@
2026/06/08-22:28:49.189521 7f51bb7fe6c0 Recovering log #4
2026/06/08-22:28:49.198820 7f51bb7fe6c0 Delete type=3 #2
2026/06/08-22:28:49.198836 7f51bb7fe6c0 Delete type=0 #4
2026/06/08-22:29:08.575568 7f51ba7fc6c0 Level-0 table #9: started
2026/06/08-22:29:08.575599 7f51ba7fc6c0 Level-0 table #9: 0 bytes OK
2026/06/08-22:29:08.581655 7f51ba7fc6c0 Delete type=0 #7
2026/06/08-22:29:08.610595 7f51ba7fc6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000008
MANIFEST-000031
+14 -15
View File
@@ -1,15 +1,14 @@
2026/05/08-21:55:31.471617 7f9043bff6c0 Recovering log #7
2026/05/08-21:55:31.534327 7f9043bff6c0 Delete type=0 #7
2026/05/08-21:55:31.534392 7f9043bff6c0 Delete type=3 #6
2026/05/08-23:13:08.603800 7f9040fff6c0 Level-0 table #11: started
2026/05/08-23:13:08.607838 7f9040fff6c0 Level-0 table #11: 30878 bytes OK
2026/05/08-23:13:08.613810 7f9040fff6c0 Delete type=0 #9
2026/05/08-23:13:08.630881 7f9040fff6c0 Manual compaction at level-0 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zpxl7WL2sfrQwhFr' @ 0 : 0; will stop at (end)
2026/05/08-23:13:08.630910 7f9040fff6c0 Manual compaction at level-1 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zpxl7WL2sfrQwhFr' @ 0 : 0; will stop at '!tables.results!zpxl7WL2sfrQwhFr' @ 428 : 1
2026/05/08-23:13:08.630915 7f9040fff6c0 Compacting 1@1 + 1@2 files
2026/05/08-23:13:08.634532 7f9040fff6c0 Generated table #12@1: 436 keys, 40143 bytes
2026/05/08-23:13:08.634545 7f9040fff6c0 Compacted 1@1 + 1@2 files => 40143 bytes
2026/05/08-23:13:08.641716 7f9040fff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2026/05/08-23:13:08.641873 7f9040fff6c0 Delete type=2 #5
2026/05/08-23:13:08.642088 7f9040fff6c0 Delete type=2 #11
2026/05/08-23:13:08.669116 7f9040fff6c0 Manual compaction at level-1 from '!tables.results!zpxl7WL2sfrQwhFr' @ 428 : 1 .. '!tables.results!zpxl7WL2sfrQwhFr' @ 0 : 0; will stop at (end)
2026/06/08-22:30:14.312612 7f51bb7fe6c0 Recovering log #28
2026/06/08-22:30:14.321802 7f51bb7fe6c0 Delete type=3 #26
2026/06/08-22:30:14.321819 7f51bb7fe6c0 Delete type=0 #28
2026/06/08-22:30:22.527571 7f51ba7fc6c0 Level-0 table #34: started
2026/06/08-22:30:22.531034 7f51ba7fc6c0 Level-0 table #34: 36447 bytes OK
2026/06/08-22:30:22.536962 7f51ba7fc6c0 Delete type=0 #32
2026/06/08-22:30:22.553199 7f51ba7fc6c0 Manual compaction at level-0 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zZwxMwSXnTigTfwv' @ 0 : 0; will stop at '!tables.results!zZwxMwSXnTigTfwv' @ 2187 : 1
2026/06/08-22:30:22.553204 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:30:22.557404 7f51ba7fc6c0 Generated table #35@0: 436 keys, 40321 bytes
2026/06/08-22:30:22.557428 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 40321 bytes
2026/06/08-22:30:22.563722 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:30:22.563803 7f51ba7fc6c0 Delete type=2 #30
2026/06/08-22:30:22.563872 7f51ba7fc6c0 Delete type=2 #34
2026/06/08-22:30:22.569911 7f51ba7fc6c0 Manual compaction at level-0 from '!tables.results!zZwxMwSXnTigTfwv' @ 2187 : 1 .. '!tables.results!zZwxMwSXnTigTfwv' @ 0 : 0; will stop at (end)
+14 -3
View File
@@ -1,3 +1,14 @@
2026/05/08-21:43:50.541628 7f42173ff6c0 Recovering log #4
2026/05/08-21:43:50.552528 7f42173ff6c0 Delete type=3 #2
2026/05/08-21:43:50.552584 7f42173ff6c0 Delete type=0 #4
2026/06/08-22:28:49.254855 7f5208bff6c0 Recovering log #23
2026/06/08-22:28:49.264171 7f5208bff6c0 Delete type=3 #21
2026/06/08-22:28:49.264195 7f5208bff6c0 Delete type=0 #23
2026/06/08-22:29:08.671838 7f51ba7fc6c0 Level-0 table #29: started
2026/06/08-22:29:08.675902 7f51ba7fc6c0 Level-0 table #29: 36492 bytes OK
2026/06/08-22:29:08.681972 7f51ba7fc6c0 Delete type=0 #27
2026/06/08-22:29:08.682176 7f51ba7fc6c0 Manual compaction at level-0 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zXqNG5BZhujFQKgC' @ 0 : 0; will stop at '!tables.results!zmWoeAL1ttcplGwL' @ 1530 : 0
2026/06/08-22:29:08.682179 7f51ba7fc6c0 Compacting 1@0 + 1@1 files
2026/06/08-22:29:08.686025 7f51ba7fc6c0 Generated table #30@0: 436 keys, 40541 bytes
2026/06/08-22:29:08.686040 7f51ba7fc6c0 Compacted 1@0 + 1@1 files => 40541 bytes
2026/06/08-22:29:08.692738 7f51ba7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/06/08-22:29:08.692811 7f51ba7fc6c0 Delete type=2 #25
2026/06/08-22:29:08.692886 7f51ba7fc6c0 Delete type=2 #29
2026/06/08-22:29:08.709397 7f51ba7fc6c0 Manual compaction at level-0 from '!tables.results!zmWoeAL1ttcplGwL' @ 1530 : 0 .. '!tables.results!zXqNG5BZhujFQKgC' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File

Some files were not shown because too many files have changed in this diff Show More