JS structure

This commit is contained in:
sladecraven 2021-04-01 21:18:36 +02:00
parent 827ce595f2
commit cc46ce867e
8 changed files with 825 additions and 0 deletions

View File

@ -0,0 +1,155 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { VadentisUtility } from "./vadentis-utility.js";
/* -------------------------------------------- */
export class VadentisActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["sos", "sheet", "actor"],
template: "systems/foundryvtt-vadentis/templates/actor-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editStatSkill: false
});
}
/* -------------------------------------------- */
getData() {
let data = super.getData();
this.actor.checkDeck();
return data;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
//HtmlUtility._showControlWhen($(".gm-only"), game.user.isGM);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
item.sheet.render(true);
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.equipObject( li.data("item-id") );
this.render(true);
});
html.find('.item-worn').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.wornObject( li.data("item-id") );
this.render(true);
});
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
SoSUtility.confirmDelete(this, li);
});
html.find('.stat-label a').click((event) => {
let statName = event.currentTarget.attributes.name.value;
this.actor.rollStat(statName);
});
html.find('.skill-label a').click((event) => {
const li = $(event.currentTarget).parents(".item");
const skill = this.actor.getOwnedItem(li.data("item-id"));
this.actor.rollSkill(skill);
});
html.find('.weapon-label a').click((event) => {
const li = $(event.currentTarget).parents(".item");
const weapon = this.actor.getOwnedItem(li.data("item-id"));
this.actor.rollWeapon(weapon);
});
html.find('.skill-value').change((event) => {
let skillName = event.currentTarget.attributes.skillname.value;
//console.log("Competence changed :", skillName);
this.actor.updateSkill(skillName, parseInt(event.target.value));
});
html.find('.skill-xp').change((event) => {
let skillName = event.currentTarget.attributes.skillname.value;
//console.log("Competence changed :", skillName);
this.actor.updateSkillExperience(skillName, parseInt(event.target.value));
});
html.find('.wound-value').change((event) => {
let woundName = event.currentTarget.attributes.woundname.value;
//console.log("Competence changed :", skillName);
this.actor.updateWound(woundName, parseInt(event.target.value));
});
html.find('.reset-deck-full').click((event) => {
this.actor.resetDeckFull();
this.render(true);
});
html.find('.draw-new-edge').click((event) => {
this.actor.drawNewEdge();
this.render(true);
});
html.find('.reset-deck').click((event) => {
this.actor.resetDeck();
this.render(true);
});
html.find('.discard-card').click((event) => {
const cardName = $(event.currentTarget).data("discard");
this.actor.discardEdge( cardName );
});
html.find('.consequence-severity').click((event) => {
const li = $(event.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
let severity = $(event.currentTarget).val();
this.actor.updateOwnedItem( { _id: item._id, 'data.severity': severity});
this.render(true);
});
html.find('.lock-unlock-sheet').click((event) => {
this.options.editStatSkill = !this.options.editStatSkill;
this.render(true);
});
html.find('.item-link a').click((event) => {
const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.getOwnedItem(itemId);
item.sheet.render(true);
});
}
/* -------------------------------------------- */
async _onDrop(event) {
let toSuper = await SoSUtility.processItemDropEvent(this, event);
if ( toSuper) {
super._onDrop(event);
}
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Update the Actor
return this.object.update(formData);
}
}

52
modules/vadentis-actor.js Normal file
View File

@ -0,0 +1,52 @@
import { VadentisUtility } from "./vadentis-utility.js";
/* -------------------------------------------- */
/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class VadentisActor extends Actor {
/* -------------------------------------------- */
/**
* Override the create() function to provide additional SoS functionality.
*
* This overrided create() function adds initial items
* Namely: Basic skills, money,
*
* @param {Object} data Barebones actor data which this function adds onto.
* @param {Object} options (Unused) Additional options which customize the creation workflow.
*
*/
static async create(data, options) {
// Case of compendium global import
if (data instanceof Array) {
return super.create(data, options);
}
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
if (data.items) {
let actor = super.create(data, options);
return actor;
}
data.items = [];
let compendiumName = "foundryvtt-vadentis.competences";
if ( compendiumName ) {
let skills = await SoSUtility.loadCompendium(compendiumName);
data.items = data.items.concat( skills );
}
return super.create(data, options);
}
/* -------------------------------------------- */
async prepareData() {
super.prepareData();
}
}

189
modules/vadentis-combat.js Normal file
View File

@ -0,0 +1,189 @@
import { VadentisUtility } from "./vadentis-utility.js";
/* -------------------------------------------- */
export class VadentisCombat extends Combat {
/* -------------------------------------------- */
requestActions() {
if ( game.user.isGM && !this.actionsRequested) {
console.log("REQUEST ACTIONS !!!");
this.actionsRequested = true;
this.phaseSetup = {}; // Reset each new round/update
for( let combatant of this.combatants) {
this.setInitiative(combatant._id, -1 ); // Reset init
let uniq = randomID(16);
const name = combatant.actor ? combatant.actor.data.name : combatant.name;
if ( combatant.players[0]) {
// A player controls this combatant -> message !
ChatMessage.create( { content: `New round ! Click on the button below to declare the actions of ${name} for round ${this.round} !<br>
<a class='chat-card-button' id='button-declare-actions' data-uniq-id='${uniq}' data-combatant-id='${combatant._id}' data-combat-id='${this._id}' data-round='${this.round}'>Declare actions</a>`,
whisper: [ combatant.players[0].data._id] } );
} else {
ChatMessage.create( { content: `New round ! Click on the button below to declare the actions of ${name} for round ${this.round} !<br>
<a class='chat-card-button' id='button-declare-actions' data-uniq-id='${uniq}' data-combatant-id='${combatant._id}' data-combat-id='${this._id}' data-round='${this.round}'>Declare actions</a>`,
whisper: [ ChatMessage.getWhisperRecipients("GM") ] } );
}
}
}
}
/* -------------------------------------------- */
async nextRound() {
this.actionsRequested = false;
super.nextRound();
}
/* -------------------------------------------- */
gotoNextTurn() {
this.phaseNumber -= 1;
if ( this.phaseNumber <= 0) {
this.applyConsequences();
this.nextRound(); // Auto-switch to next round
} else {
this.nextTurn();
}
}
/* -------------------------------------------- */
async nextTurn() {
console.log("Going to phase !", this.phaseNumber );
// Get all actions for this phase
let phaseIndex = this.phaseNumber - 1;
let actionList = [];
let actionMsg = `<h4>Actions for phase ${this.phaseNumber}</h4>`;
for (let combatantId in this.phaseSetup ) {
let actionData = this.phaseSetup[combatantId];
if ( actionData.phaseArray[phaseIndex].name != 'No Action' ) {
let combatant = this.combatants.find( comb => comb._id == actionData.combatantId);
const name = combatant.actor ? combatant.actor.data.name : combatant.name;
actionList.push( { combatant: combatant,
action: actionData.phaseArray[phaseIndex],
isDone: false
});
actionMsg += `<br>${name} is going to : ${actionData.phaseArray[phaseIndex].name}`;
}
}
if ( actionList.length == 0) {
actionMsg += "<br>No actions for the phase !";
this.gotoNextTurn();
}
// Display a nice message
ChatMessage.create( { content: actionMsg });
// Now push specific messages
for ( let action of actionList) {
let uniq = randomID(16);
action.uniqId = uniq; // Easy tracking with chat messages
const name = action.combatant.actor ? action.combatant.actor.data.name : action.combatant.name;
if ( action.combatant.players[0]) {
// A player controls this combatant -> message !
ChatMessage.create( { content: `Phase ${this.phaseNumber} ! ${name} must perform a <strong>${action.action.name}</strong> action.
When done, click on the button below to close the action.
<a class='chat-card-button' id='button-end-action' data-uniq-id='${uniq}' data-combatant-id='${action.combatant._id}' data-combat-id='${this._id}' data-round='${this.round}'>Action is done !</a>`,
whisper: [ action.combatant.players[0].data._id] } );
} else {
ChatMessage.create( { content: `Phase ${this.phaseNumber} ! ${name} must perform a <strong>${action.action.name}</strong> action.<br>
When done, click on the button below to close the action.
<a class='chat-card-button' id='button-end-action' data-uniq-id='${uniq}' data-combatant-id='${action.combatant._id}' data-combat-id='${this._id}' data-round='${this.round}'>Action is done !</a>`,
whisper: [ ChatMessage.getWhisperRecipients("GM") ] } );
}
}
// Save for easy access
this.currentActions = actionList;
}
/* -------------------------------------------- */
applyConsequences( ) {
if (game.user.isGM ) {
for( let combatant of this.combatants) {
if (!combatant.actor) continue; // Can't check tokens without assigned actors, Maybe print chat message about bleeding happening so that the GM can manually track this?
let bleeding = combatant.actor.data.items.find( item => item.type == 'consequence' && item.name == 'Bleeding');
combatant.actor.applyConsequenceWound( bleeding.data.severity, "bleeding" );
}
}
}
/* -------------------------------------------- */
closeAction( uniqId) {
// Delete message !
const toDelete = game.messages.filter(it => it.data.content.includes( uniqId ));
toDelete.forEach(it => it.delete());
let action = this.currentActions.find( _action => _action.uniqId == uniqId );
if (action) {
action.isDone = true;
let filtered = this.currentActions.filter( _action => action.isDone );
if ( filtered.length == this.currentActions.length) { // All actions closed !
console.log("Going next turn !!!");
this.gotoNextTurn();
}
}
}
/* -------------------------------------------- */
getPhaseRank( actionConf) {
for (let i=2; i>=0; i--) {
let action = actionConf.phaseArray[i];
if (action.name != "No Action") {
return i+1;
}
}
return 0;
}
/* -------------------------------------------- */
getAPFromActor( actorId ) {
for( let combatant of this.combatants) {
//console.log(combatant);
if ( combatant.actor.data._id == actorId ) {
let phase = this.phaseSetup[combatant._id];
return phase.remainingAP;
}
}
return 0;
}
/* -------------------------------------------- */
decreaseAPFromActor( actorId ) {
for( let combatant of this.combatants) {
//console.log(combatant);
if ( combatant.actor.data._id == actorId ) {
let phase = this.phaseSetup[combatant._id];
phase.remainingAP -= 1;
if ( phase.remainingAP < 0 ) phase.remainingAP = 0;
}
}
}
/* -------------------------------------------- */
async setupActorActions(actionConf) {
console.log("Setting combat for phase : ", actionConf, actionConf.uniqId);
// Delete message !
const toDelete = game.messages.filter(it => it.data.content.includes( actionConf.uniqId ));
console.log("MESSAGE : ", toDelete);
toDelete.forEach(it => it.delete());
if ( !this.phaseSetup) this.phaseSetup = {}; // Opportunistic init
// Keep track
this.phaseSetup[actionConf.combatantId] = actionConf;
console.log( this.combatants );
//let combatant = this.combatants.find( comb => comb._id == actionConf.combatantId);
await this.setInitiative( actionConf.combatantId, this.getPhaseRank( actionConf ) );
let actionsDone = true
for( let combatant of this.combatants) {
if ( combatant.initiative == -1 ) actionsDone = false;
}
if ( actionsDone ) {
this.actionsRequested = false;
ChatMessage.create( { content: `Action declaration has been completed ! Now proceeding with actions.`,
whisper: [ ChatMessage.getWhisperRecipients("GM") ] } );
this.phaseNumber = 3;
this.nextTurn();
}
}
}

View File

@ -0,0 +1,85 @@
import { VadentisUtility } from "./vadentis-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class VadentisItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["foundryvtt-vadentis", "sheet", "item"],
template: "systems/foundryvtt-vadentis/templates/item-sheet.html",
width: 550,
height: 550
//tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => {} //new RdDItem(this.item.data).postItem()
})
return buttons
}
/* -------------------------------------------- */
/** @override */
setPosition(options={}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
async getData() {
let data = super.getData();
data.isGM = game.user.isGM;
return data;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.object.options.actor.getOwnedItem(li.data("item-id"));
item.sheet.render(true);
});
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.object.options.actor.deleteOwnedItem( li.data("item-id") ).then( this.render(true));
});
}
/* -------------------------------------------- */
get template()
{
let type = this.item.type;
return `systems/foundryvtt-vadentis/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
}
}

106
modules/vadentis-main.js Normal file
View File

@ -0,0 +1,106 @@
/**
* RdD system
* Author: Uberwald
* Software License: Prop
*/
/* -------------------------------------------- */
/* -------------------------------------------- */
// Import Modules
import { VadentisActor } from "./actor.js";
import { VadentisItemSheet } from "./item-sheet.js";
import { VadentisActorSheet } from "./actor-sheet.js";
import { VadentisUtility } from "./vadentis-utility.js";
import { VadentisCombat } from "./vadentis-combat.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
/************************************************************************************/
Hooks.once("init", async function () {
console.log(`Initializing Vadentis`);
/* -------------------------------------------- */
// preload handlebars templates
VadentisUtility.preloadHandlebarsTemplates();
/* -------------------------------------------- */
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1d20",
decimals: 0
};
/* -------------------------------------------- */
game.socket.on("system.foundryvtt-vadentis", data => {
VadentisUtility.onSocketMesssage(data);
});
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Actor.entityClass = VadentisActor;
CONFIG.Combat.entityClass = VadentisCombat;
CONFIG.Vadentis = {
}
/* -------------------------------------------- */
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("foundryvtt-vadentis", VadentisActorSheet, { types: ["character"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("foundryvtt-vadentis", VadentisItemSheet, { makeDefault: true });
// Init/registers
Hooks.on('renderChatLog', (log, html, data) => {
VadentisUtility.registerChatCallbacks(html);
});
// Init/registers
Hooks.on('updateCombat', (combat, round, diff, id) => {
VadentisUtility.updateCombat(combat, round, diff, id);
});
});
/* -------------------------------------------- */
function welcomeMessage() {
//ChatUtility.removeMyChatMessageContaining('<div id="welcome-message-sos">');
ChatMessage.create({
user: game.user._id,
whisper: [game.user._id],
content: `<div id="welcome-message-vadentis"><span class="rdd-roll-part">Bienvenue !</div>
` });
}
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.once("ready", function () {
// User warning
if (!game.user.isGM && game.user.character == undefined) {
ui.notifications.info("Attention ! Vous n'est connecté à aucun personnage");
ChatMessage.create({
content: "<b>WARNING</b> Le joueur " + game.user.name + " n'est pas connecté à un personnage !",
user: game.user._id
});
}
welcomeMessage();
});
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') {
let regExp = /(\S+)/g;
let commands = content.toLowerCase().match(regExp);
console.log(commands);
//if ( commands[0] == '/gmdeck') {
//game.system.sos.gmDeck.render( true );
//return false;
//}
}
return true;
});

View File

@ -0,0 +1,55 @@
/* -------------------------------------------- */
import { VadentisCombat } from "./vadentis-combat.js";
/* -------------------------------------------- */
export class VadentisUtility extends Entity {
/* -------------------------------------------- */
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/foundryvtt-vadentis/templates/actor-sheet.html',
'systems/foundryvtt-vadentis/templates/item-sheet.html'
]
return loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static fillRange (start, end) {
return Array(end - start + 1).fill().map((item, index) => start + index);
}
/* -------------------------------------------- */
static onSocketMesssage( msg ) {
if( !game.user.isGM ) return; // Only GM
if (msg.name == 'msg_declare_actions' ) {
}
}
/* -------------------------------------------- */
static async loadCompendiumNames(compendium) {
const pack = game.packs.get(compendium);
let competences;
await pack.getIndex().then(index => competences = index);
return competences;
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumItems = await SoSUtility.loadCompendiumNames(compendium);
const pack = game.packs.get(compendium);
let list = [];
for (let compendiumItem of compendiumItems) {
await pack.getEntity(compendiumItem._id).then(it => {
const item = it.data;
if (filter(item)) {
list.push(item);
}
});
};
return list;
}
}

35
system.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "foundryvtt-vadentis",
"title": "Vadentis",
"description": "Système Vadentis pour FoundryVTT",
"version": "0.0.1",
"manifestPlusVersion": "1.0.0",
"minimumCoreVersion": "0.7.5",
"compatibleCoreVersion": "0.7.9",
"templateVersion": 1,
"author": "Uberwald",
"esmodules": [ "module/vadentis-main.js" ],
"styles": ["styles/simple.css"],
"background" : "",
"media": [
],
"packs": [
],
"library": false,
"languages": [
{
"lang": "fr",
"name": "French",
"path": "lang/fr.json"
}
],
"gridDistance": 5,
"gridUnits": "m",
"primaryTokenAttribute": "",
"secondaryTokenAttribute": "",
"socket": true,
"url": "https://gitlab.com/LeRatierBretonnien/foundryvtt-shadows-over-sol/",
"manifest": "https://gitlab.com/LeRatierBretonnien/foundryvtt-shadows-over-sol/-/raw/master/system.json",
"download": "https://gitlab.com/LeRatierBretonnien/foundryvtt-shadows-over-sol/-/archive/master/foundryvtt-shadows-over-sol.zip",
"license": "LICENSE.txt"
}

148
template.json Normal file
View File

@ -0,0 +1,148 @@
{
"Actor": {
"types": ["character"],
"templates": {
"common": {
"stats": {
"donnee": {
"label": "Données",
"list": []
},
"experience": {
"total": 0,
"disponibe": 0,
"label": "Expérience"
},
"pointsvie": {
"value": 0,
"max": 0,
"label": "Points de Vie"
},
"pointsenergie": {
"value": 0,
"max": 0,
"label": "Points d'Energie"
},
"pointsadrenaline": {
"value": 0,
"max": 0,
"label": "Points d'Adrénaline"
},
"force": {
"base": 0,
"malus": 0,
"bonus": 0,
"label": "Force"
},
"esquive": {
"value": 0,
"label": "Esquive"
},
"attaque": {
"base": 0,
"malus": 0,
"bonus": 0,
"label": "Attaque"
},
"defense": {
"base": 0,
"malus": 0,
"bonus": 0,
"label": "Défense"
},
"matriseelementaire": {
"base": 0,
"malus": 0,
"bonus": 0,
"label": "Maîtrise élémentaire"
},
"devotion": {
"base": 0,
"malus": 0,
"bonus": 0,
"label": "Dévotion"
}
}
},
"background": {
"race": "",
"history": "Background",
"notes": "Notes",
"gmnotes": "Notes du MJ",
"eyes": "",
"hair": "",
"weight": "",
"genre": "",
"age": 0
}
},
"character": {
"templates": [ "background", "common" ]
}
},
"Item": {
"types": ["competence", "attribut", "technique", "sort", "arme", "tir", "armurebouclier", "equipement" ],
"attribut": {
"effect": "",
"xp": 0,
"notes": ""
},
"technique": {
"condition": "",
"effect": "",
"pacost": 1,
"xp": 0,
"notes": ""
},
"sort": {
"type": "",
"datachurch": "",
"xp": "",
"pe": "",
"target": "",
"difficulty": "",
"description": "",
"effect": "",
"critical": "",
"notes": "",
"damage": ""
},
"competence": {
"xp": "",
"base": "",
"bonus": "",
"malus": "",
"description": ""
},
"arme": {
"type": "",
"damage": "",
"criticaldamage": "",
"description": "",
"enc": 0,
"cost": 0
},
"tir": {
"type": "",
"damage": "",
"criticaldamage": "",
"munition": "",
"distance": "",
"description": "",
"enc": 0,
"cost": 0
},
"armurebouclier": {
"type": "",
"bonus": "",
"malus": "",
"enc": 0,
"cost": 0
},
"equipement": {
"description": "",
"enc": 0,
"cost": 0
}
}
}