Merge pull request #20 from Tronikart/master

Import from JSON implementation (v2)
This commit is contained in:
Futil
2023-06-05 20:09:15 -06:00
committed by GitHub
+325 -3
View File
@@ -31,8 +31,8 @@ Hooks.once('init', async function () {
};
// Define custom Entity classes
CONFIG.Actor.documentClass = MothershipActor;
CONFIG.Item.documentClass = MothershipItem;
CONFIG.Actor.documentClass = MothershipActor;
CONFIG.Item.documentClass = MothershipItem;
// Register sheet application classes
@@ -80,7 +80,7 @@ Hooks.once("ready", async function () {
/**
* Set default values for new actors' tokens
*/
Hooks.on("preCreateActor", (document, createData, options, userId) => {
Hooks.on("preCreateActor", (document, createData, options, userId) => {
let disposition = CONST.TOKEN_DISPOSITIONS.NEUTRAL;
if (createData.type == "creature") {
@@ -105,6 +105,17 @@ Hooks.once("ready", async function () {
}
})
Hooks.on('renderSidebarTab', async (app, html) => {
if (app.options.id == "actors") {
let button = $(`<button class="import-json"><i class="fas fa-file-import"></i> Import JSON</button>`);
button.click(function () {
d.render(true);
});
html.find(".directory-footer").append(button);
}
});
/* -------------------------------------------- */
/* Hotbar Macros */
@@ -144,6 +155,317 @@ async function createMothershipMacro(data, slot) {
return false;
}
/* -------------------------------------------- */
/* Actor from JSON */
/* -------------------------------------------- */
// If there's a better place for these, please do move them
let skillValues = {
"archeology": { "value": 10, "rank": "Trained" },
"art": { "value": 10, "rank": "Trained" },
"athletics": { "value": 10, "rank": "Trained" },
"botany": { "value": 10, "rank": "Trained" },
"chemistry": { "value": 10, "rank": "Trained" },
"computers": { "value": 10, "rank": "Trained" },
"geology": { "value": 10, "rank": "Trained" },
"industrialequipment": { "value": 10, "rank": "Trained" },
"juryrigging": { "value": 10, "rank": "Trained" },
"linguistics": { "value": 10, "rank": "Trained" },
"mathematics": { "value": 10, "rank": "Trained" },
"militarytraining": { "value": 10, "rank": "Trained" },
"rimwise": { "value": 10, "rank": "Trained" },
"theology": { "value": 10, "rank": "Trained" },
"zerog": { "value": 10, "rank": "Trained" },
"zoology": { "value": 10, "rank": "Trained" },
"asteroidmining": { "value": 15, "rank": "Expert" },
"ecology": { "value": 15, "rank": "Expert" },
"explosives": { "value": 15, "rank": "Expert" },
"fieldmedicine": { "value": 15, "rank": "Expert" },
"firearms": { "value": 15, "rank": "Expert" },
"hacking": { "value": 15, "rank": "Expert" },
"handtohandcombat": { "value": 15, "rank": "Expert" },
"mechanicalrepair": { "value": 15, "rank": "Expert" },
"mysticism": { "value": 15, "rank": "Expert" },
"pathology": { "value": 15, "rank": "Expert" },
"pharmacology": { "value": 15, "rank": "Expert" },
"physics": { "value": 15, "rank": "Expert" },
"piloting": { "value": 15, "rank": "Expert" },
"psychology": { "value": 15, "rank": "Expert" },
"tactics": { "value": 15, "rank": "Expert" },
"wildernesssurvival": { "value": 15, "rank": "Expert" },
"artificialintenligence": { "value": 20, "rank": "Master" },
"command": { "value": 20, "rank": "Master" },
"cybernetics": { "value": 20, "rank": "Master" },
"engineering": { "value": 20, "rank": "Master" },
"exobiology": { "value": 20, "rank": "Master" },
"hyperspace": { "value": 20, "rank": "Master" },
"planetology": { "value": 20, "rank": "Master" },
"robotics": { "value": 20, "rank": "Master" },
"sophontology": { "value": 20, "rank": "Master" },
"surgery": { "value": 20, "rank": "Master" },
"xenoesoterism": { "value": 20, "rank": "Master" },
}
let conditions = {
"adrenalinerush": "[+] on all rolls for the next 2d10 minutes. Reduce your Stress by 1d5.",
"anxious": "Gain 1 Stress.",
"jumpy": "Gain 1 Stress. All Close crewmembers gain 2 Stress.",
"overwhelmed": "All actions at [-] for 1d10 minutes. Permanently raise your Minimum Stress by 1.",
"coward": "Gain a new Condition: You must make a Fear Save to engage in violence or flee.",
"frightened": "Gain a new Condition: Phobia: When encountering your Phobia make a Fear Save [-] or gain 1d5 Stress.",
"nightmares": "Gain a new Condition: Sleep is difficult, gain [-] on all Rest Saves.",
"lossofconfidence": "Gain a new Condition: Choose one of your Skills and lose that Skill's bonus.",
"deflated": "Gain a new Condition: Whenever a Close crewmember fails a Save, gain 1 Stress.",
"doomed": "Gain a new Condition: You feel cursed and unlucky. All Critical Successes are instead Critical Failures.",
"paranoid": "For the next week, whenever someone joins your group (even if they only left for a short period of time), make a Fear Save or gain 1 Stress.",
"haunted": "Gain a new Condition: Something starts visiting you at night. In your dreams. Out of the corner of your eye. And soon it will start making demands.",
"deathwish": "For the next 24 hours, whenever you encounter a stranger or known enemy, you must make a Sanity Save or immediately attack them.",
"propheticvision": "You immediately experience an intense hallucination or vision of an impending terror or horrific event. Gain 1 Stress.",
"catatonic": "Become unresponsive and unmoving for 2d10 minutes. Reduce Stress by 1d10.",
"rage": "Immediately attack the closest crewmember until you inflict at least 2d10 DMG. If there is no crewmember Close, you attack your surrounding environment.",
"spiraling": "Gain a new Condition: You make Panic Checks with Disadvantage.",
"compoundingproblems": "Roll twice on this table. Permanently raise your Minimum Stress by 1.",
"heartattack": "Permanently lose 1 Wound. Gain [-] on all rolls for 1d10 hours. Permanently raise your Minimum Stress by 1.",
"collapse": "You no longer control this character. Hand your sheet to the Warden and roll up a new character to play."
}
async function createActorFromJson(jsonData) {
let actorData;
let newActor;
try {
actorData = JSON.parse(jsonData);
newActor = {
"name": actorData.name,
"type": "character",
"img": "icons/svg/mystery-man.svg",
"system": {
"health": {
"value": actorData.health,
"max": actorData.maxHealth,
"min": 0
},
"hits": {
"value": actorData.wounds,
"max": actorData.maxWounds
},
"biography": "",
"notes": "",
"weight": { "current": 0, "capacity": 0 },
"settings": { "useCalm": false },
"class": {
"value": actorData.characterClass.charAt(0).toUpperCase() + actorData.characterClass.slice(1)
},
"rank": { "value": "" },
"pronouns": { "value": actorData.pronouns },
"credits": { "value": actorData.credits },
"stressdesc": { "value": actorData.traumaResponse },
"xp": { "value": 0, "html": 0, "selectedSkill": "" },
"stressdesc": { "value": actorData.traumaResponse },
"attributes": { "level": { "value": actorData.highScore } },
"stats": {
"strength": {
"value": actorData.strength, "label": "Strength", "mod": null
},
"speed": {
"value": actorData.speed, "label": "Speed", "mod": null
},
"intellect": {
"value": actorData.intellect, "label": "Intellect", "mod": null
},
"combat": {
"value": actorData.combat, "label": "Combat", "mod": null
},
"sanity": {
"value": actorData.sanity, "label": "Sanity"
},
"fear": {
"value": actorData.fear, "label": "Fear"
},
"body": {
"value": actorData.body, "label": "Body"
}
},
"other": {
"stress": {
"value": actorData.stress, "min": actorData.minStress, "label": "Stress"
},
"resolve": 0
}
}
};
var items = [];
if (actorData.trinket) {
items.push({
"name": "Trinket: " + actorData.trinket, "type": "item"
})
}
if (actorData.patch) {
items.push({
"name": "Patch: " + actorData.patch, "type": "item"
})
}
// Equipment
for (let i = 0; i < actorData.equipment.length; i++) {
items.push({
"name": actorData.equipment[i].name,
"type": "item",
"system": {
"description": actorData.equipment[i].description,
"cost": actorData.equipment[i].cost,
"quantity": actorData.equipment[i].quantity
},
"img": "icons/svg/item-bag.svg",
})
}
// Items
for (let i = 0; i < actorData.items.length; i++) {
items.push({
"name": actorData.items[i].title,
"type": "item",
"system": {
"description": actorData.items[i].description,
"cost": actorData.items[i].cost,
"quantity": actorData.items[i].quantity
},
"img": "icons/svg/item-bag.svg",
})
}
// Armor
for (let i = 0; i < actorData.armor.length; i++) {
items.push({
"name": actorData.armor[i].name,
"type": "armor",
"system": {
"description": actorData.armor[i].notes,
"cost": actorData.armor[i].cost,
"bonus": actorData.armor[i].armorPoints,
"equipped": actorData.armor[i].equipped
},
"img": "icons/svg/item-bag.svg",
})
}
// Weapons
for (let i = 0; i < actorData.weapons.length; i++) {
items.push({
"name": actorData.weapons[i].weaponType,
"type": "weapon",
"system": {
"description": actorData.weapons[i].special,
"cost": actorData.weapons[i].cost,
"ranges": {
"short": 0,
"medium": 0,
"long": 0,
"value": actorData.weapons[i].weaponRange
},
"equipped": actorData.weapons[i].equipped,
"critEffect": actorData.weapons[i].critical,
"shots": actorData.weapons[i].shots,
"curShots": actorData.weapons[i].shots,
"ammo": actorData.weapons[i].magazines,
"shotsPerFire": "1",
"useAmmo": false,
"ammoType": "",
"critDmg": actorData.weapons[i].critical,
"critEffect": "",
"damage": actorData.weapons[i].damageString
},
"img": "icons/svg/item-bag.svg",
})
}
// Skills
for (let i = 0; i < actorData.skills.length; i++) {
// Really betting for the app to only export base skills, if in the future
// they add custom skills, they'll need to be checked if they even exist
let words = actorData.skills[i].split(/(?=[A-Z])/);
let skillName = words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
items.push({
"name": skillName,
"type": "skill",
"system": {
"description": "",
"bonus": skillValues[actorData.skills[i].toLowerCase()].value,
"rank": skillValues[actorData.skills[i].toLowerCase()].rank
},
"img": "icons/svg/item-bag.svg",
})
}
// Conditions
for (let i = 0; i < actorData.conditions.length; i++) {
// Same thing as skills
let conName = actorData.conditions[i].conditionType;
let words = conName.split(/(?=[A-Z])/);
let name = words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
//-Minor consideration since the app eports heartAttack for both
if (actorData.characterClass == "android" && conName.toLowerCase() == "heartattack") {
name = "Short Circuit"
}
items.push({
"name": name,
"type": "condition",
"system": {
"description": conditions[conName.toLowerCase()],
"treatment": { "value": 0, "html": "" }
},
"img": "icons/svg/item-bag.svg",
})
}
newActor.items = items
} catch (err) {
console.error(err);
ui.notifications.error("Invalid JSON, make sure you exported it from the Mothership Companion App");
return;
}
// Ensure the type of the actor (e.g., 'character') is correctly set according to your system
newActor.type = 'character';
// Here we assume the actorData structure is correctly formed according to your system needs
// You might need additional checks/transformations depending on the JSON data structure
try {
await Actor.create(newActor);
ui.notifications.info("Character imported successfully!");
} catch (err) {
console.error(err);
ui.notifications.error("Failed to import character, check console for more details.");
}
}
let d = new Dialog({
title: "Mothership Companion JSON Import",
content: `
<form>
<div class="form-group">
<label>JSON File:</label>
<input type="file" id="json-upload" accept=".json">
</div>
</form>
`,
buttons: {
upload: {
label: "Upload",
callback: (html) => {
let fileInput = html.find('#json-upload')[0];
let file = fileInput.files[0];
let reader = new FileReader();
reader.onload = (event) => {
let contents = event.target.result;
createActorFromJson(contents);
};
reader.onerror = (event) => {
console.error(`File could not be read: ${event.target.error}`);
};
reader.readAsText(file);
}
}
},
default: "upload"
})
d.render(true);
/**
* Roll Macro from a Weapon.