diff --git a/module/mosh.js b/module/mosh.js index 8017418..533cc6f 100644 --- a/module/mosh.js +++ b/module/mosh.js @@ -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.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: ` +
+ `, + 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.