diff --git a/img/icons/action_none.svg b/img/icons/action_none.svg new file mode 100644 index 0000000..ba74b52 --- /dev/null +++ b/img/icons/action_none.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/ui/background_sheet01.webp b/img/ui/background_sheet01.webp new file mode 100644 index 0000000..8b52ff6 Binary files /dev/null and b/img/ui/background_sheet01.webp differ diff --git a/module/actor.js b/module/actor.js index fc4a386..1d95258 100644 --- a/module/actor.js +++ b/module/actor.js @@ -168,8 +168,6 @@ export class SoSActor extends Actor { let html = await renderTemplate('systems/foundryvtt-shadows-over-sol/templates/dialog-flip.html', flipData); new SoSFlipDialog(flipData, html).render(true); - //console.log("STAT", this); - //let result = this.cardDeck.doFlipStat( duplicate(this.data.data.stat[statKey]) ); } /* -------------------------------------------- */ diff --git a/module/sos-combat.js b/module/sos-combat.js new file mode 100644 index 0000000..9e08a9c --- /dev/null +++ b/module/sos-combat.js @@ -0,0 +1,30 @@ +import { SoSCardDeck } from "./sos-card-deck.js"; +import { SoSUtility } from "./sos-utility.js"; +import { SoSFlipDialog } from "./sos-flip-dialog.js"; +import { SoSDialogCombatActions } from "./sos-dialog-combat-actions.js"; + +/* -------------------------------------------- */ +export class SoSCombat extends Combat { + + /* -------------------------------------------- */ + async nextRound() { + console.log("NEXT ROUND !!!!"); + + if ( game.user.isGM ) { + ChatMessage.create( { content: `New round !! Click on the button below to declare your actions for round ${this.round} !
+ Declare actions`, + whisper: game.users.map(user => user.data._id) } ); + } + super.nextRound(); + } + + /* -------------------------------------------- */ + setupActorActions(actionConf) { + if ( !this.phaseSetup) this.phaseSetup = []; // Opportunistic init + if ( !this.phaseSetup[this.round] ) this.phaseSetup[this.round] = {}; // Bis + + // Keep track + this.phaseSetup[this.round][actionConf.userId] = actionConf; + } + +} diff --git a/module/sos-dialog-combat-actions.js b/module/sos-dialog-combat-actions.js new file mode 100644 index 0000000..8fd9954 --- /dev/null +++ b/module/sos-dialog-combat-actions.js @@ -0,0 +1,130 @@ +import { SoSUtility } from "./sos-utility.js"; + +/* -------------------------------------------- */ +export class SoSDialogCombatActions extends Dialog { + + /* -------------------------------------------- */ + static async create( combatId, round ) { + + let combatActions = { + actionsList: await SoSUtility.loadCompendium( 'foundryvtt-shadows-over-sol.combat-actions' ), + actionPoints: SoSUtility.fillRange(0, 6), + combatId: combatId, + round: round + } + for ( let i=0; i', + label: "Validate", + callback: (html) => { this.validateActions(html) } + } + }, + default: "validate" + }; + super(dialogConf, options); + + this.combatActions = combatActions; + } + + /* -------------------------------------------- */ + async close() { + let ap = Number($('#remain-ap').text()); + if ( ap >= 0 ) { + super.close(); + + let action3Index = $('#action3').val(); + let action3 = duplicate(this.combatActions.actionsList[action3Index]); + let action2Index = $('#action2').val(); + let action2 = duplicate(this.combatActions.actionsList[action2Index]._id); + let action1Index = $('#action1').val(); + let action1 = duplicate(this.combatActions.actionsList[action1Index]._id); + + game.socket.emit("system.foundryvtt-reve-de-dragon", { + name: "msg_declare_actions", data: { + userId: game.userId, + phaseArray: [ action1, action2, action3], + remainingAP: ap + } } ); + } else { + ui.notifications.warn("Action Points are below 0 ! Please check your phases !"); + } + } + + /* -------------------------------------------- */ + validateActions( html ) { + } + + /* -------------------------------------------- */ + resetAP() { + let maxAP = $('#actionMax').val(); + $('#remain-ap').text( maxAP); + + $('#action3').val( this.combatActions.noActionId ); + $('#action2').val( this.combatActions.noActionId ); + $('#action1').val( this.combatActions.noActionId ); + $('#action3').prop('disabled', false); + $('#action2').prop('disabled', false); + $('#action1').prop('disabled', false); + } + + /* -------------------------------------------- */ + updateAP( ) { + let remainAP = $('#actionMax').val(); + + let action3Index = $('#action3').val(); + if ( this.combatActions.actionsList[action3Index].name != 'No Action') { + remainAP -= 3; + } + + let action2Index = $('#action2').val(); + if ( this.combatActions.actionsList[action2Index].name != 'No Action') { + remainAP -= 2; + } + + let action1Index = $('#action1').val(); + if ( this.combatActions.actionsList[action1Index].name != 'No Action') { + remainAP -= 1; + } + + $('#remain-ap').text( remainAP); + + if ( remainAP < 0 ) { + $("#remain-ap").addClass("text-red"); + ui.notifications.warn("No more Action Points ! Please check your phases !"); + } else { + $("#remain-ap").removeClass("text-red"); + } + + } + + /* -------------------------------------------- */ + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + html.find('#actionMax').change((event) => { + this.resetAP( ); + }); + html.find('.action-select').change((event) => { + this.updateAP( ); + }); + + } + +} diff --git a/module/sos-main.js b/module/sos-main.js index 7e20e96..eaf1a27 100644 --- a/module/sos-main.js +++ b/module/sos-main.js @@ -12,6 +12,7 @@ import { SoSActor } from "./actor.js"; import { SoSItemSheet } from "./item-sheet.js"; import { SoSActorSheet } from "./actor-sheet.js"; import { SoSUtility } from "./sos-utility.js"; +import { SoSCombat } from "./sos-combat.js"; /* -------------------------------------------- */ /* Foundry VTT Initialization */ @@ -51,6 +52,7 @@ Hooks.once("init", async function () { /* -------------------------------------------- */ // Define custom Entity classes CONFIG.Actor.entityClass = SoSActor; + CONFIG.Combat.entityClass = SoSCombat; CONFIG.SoS = { } @@ -61,6 +63,11 @@ Hooks.once("init", async function () { Items.unregisterSheet("core", ItemSheet); Items.registerSheet("foundryvtt-shadows-over-sol", SoSItemSheet, { makeDefault: true }); + // Init/registers + Hooks.on('renderChatLog', (log, html, data) => { + SoSUtility.registerChatCallbacks(html); + }); + // Patch the initiative formula _patch_initiative(); diff --git a/module/sos-utility.js b/module/sos-utility.js index 1378a13..939b30a 100644 --- a/module/sos-utility.js +++ b/module/sos-utility.js @@ -1,5 +1,8 @@ +/* -------------------------------------------- */ +import { SoSDialogCombatActions } from "./sos-dialog-combat-actions.js"; -export class SoSUtility { +/* -------------------------------------------- */ + export class SoSUtility { /* -------------------------------------------- */ static async preloadHandlebarsTemplates() { @@ -24,6 +27,14 @@ export class SoSUtility { return Array(end - start + 1).fill().map((item, index) => start + index); } + /* -------------------------------------------- */ + onSocketMesssage( msg ) { + if (msg.name == 'msg_declare_actions' ) { + let combat = game.combats.get( msg.data.combatId); // Get the associated combat + combat.setupActorActions( msg.data ); + } + } + /* -------------------------------------------- */ static async loadCompendiumNames(compendium) { const pack = game.packs.get(compendium); @@ -49,4 +60,21 @@ export class SoSUtility { return list; } + /* -------------------------------------------- */ + static async openDeclareActions( event) { + event.preventDefault(); + let round = event.currentTarget.attributes['data-round'].value; + let combatId = event.currentTarget.attributes['data-combat-id'].value; + let d = await SoSDialogCombatActions.create( combatId, round ); + d.render(true); + } + + /* -------------------------------------------- */ + static async registerChatCallbacks(html) { + html.on("click", '#button-declare-actions', event => { + SoSUtility.openDeclareActions( event ); + }); + + } + } \ No newline at end of file diff --git a/packs/combat-actions.db b/packs/combat-actions.db index 5c0fb55..1702456 100644 --- a/packs/combat-actions.db +++ b/packs/combat-actions.db @@ -17,3 +17,4 @@ {"name":"Taking Cover","permission":{"default":0,"pJLHbu8WlBVyfXG4":3},"type":"action","data":{"type":"move","minap":1,"description":"

Sometimes a basic move or a change in posture will be enough to gain a character the Cover or Concealment consequences. To make the most of these, however, the character will need to make a take cover action. There is a difference between “taking cover” and simply having cover. Think of it like this: if Monique is being fired upon and runs to where a stack of barrels is between her and her attacker, she has cover from the attacks. At the same time, if she wants to shoot back, the barrels are in her way as well. She has cover, but she hasn’t “taken cover” to make the most of it.

\n

When a character takes cover, she needs to have moved immediately next to the object providing her with the cover consequence. She then performs the take cover action, setting herself up so that the cover does not hinder her attacks but still provides her with protection against the attacks of others. Without this, the cover’s bonus to Defense applies both to incoming and outgoing attacks.

\n

 

\n

 

"},"flags":{},"img":"systems/foundryvtt-shadows-over-sol/img/icons/action_cover.svg","effects":[],"_id":"pwq6CB24QhNkrt1s"} {"name":"Posture","permission":{"default":0,"pJLHbu8WlBVyfXG4":3},"type":"action","data":{"type":"move","minap":1,"description":"

By default, characters are assumed to be standing. There are times, however, when a character might want to kneel, crouch, sit or lay down. Changing between any of these postures is a move action, and one change in posture may be tacked on for free to another move action, such as a basic move or taking cover. If performed by itself, changing posture costs the usual amount of AP for the phase.

\n

 

"},"flags":{},"img":"systems/foundryvtt-shadows-over-sol/img/icons/action_posture.svg","effects":[],"_id":"tF8mIKv0zi2HLv9B"} {"name":"Drawing/Reloading","permission":{"default":0,"pJLHbu8WlBVyfXG4":3},"type":"action","data":{"type":"interact","minap":1,"description":"

Drawing a weapon or reloading one is a simple interact action that involves taking a weapon out or putting ammunition into one. This costs at least 1 AP if the weapon or magazine was in a holster or other easy-to-access position. It costs at least 2 AP if it was harder to reach, such as stowed at the top of a pack or tucked inside one’s boot. Some weapons may have longer reload times, requiring more AP. This will be noted in the weapon’s properties.

\n

 

\n

 

"},"flags":{},"img":"systems/foundryvtt-shadows-over-sol/img/icons/action_reload.svg","effects":[],"_id":"uSQw858IiBrWkeSj"} +{"name":"No Action","permission":{"default":0,"pJLHbu8WlBVyfXG4":3},"type":"action","data":{"type":"other","minap":0,"description":0},"flags":{},"img":"systems/foundryvtt-shadows-over-sol/img/icons/action_none.svg","effects":[],"_id":"bqSieyyOLSY8tU2U"} diff --git a/styles/simple.css b/styles/simple.css index 65733f5..19cb29b 100644 --- a/styles/simple.css +++ b/styles/simple.css @@ -203,7 +203,6 @@ table {border: 1px solid #7a7971;} } /* Styles limited to foundryvtt-reve-de-dragon sheets */ - .foundryvtt-shadows-over-sol .sheet-header { -webkit-box-flex: 0; -ms-flex: 0 0 210px; @@ -394,7 +393,8 @@ table {border: 1px solid #7a7971;} /* ======================================== */ /* Sheet */ .window-app.sheet .window-content .sheet-header{ - background: #011d33 url("img/bg_header.webp") no-repeat left top; + background: url('../img/ui/background_sheet01.webp') no-repeat left top; + /*background: #011d33 url("img/bg_header.webp") no-repeat left top;*/ color: rgba(255, 255, 255, 1); } @@ -405,6 +405,10 @@ table {border: 1px solid #7a7971;} margin-bottom: 0.25rem; } +.sheet .sheet-header { + background: url('../img/ui/background_sheet01.webp') no-repeat left top; +} + .window-app .window-content, .window-app.sheet .window-content .sheet-body{ background: rgb(245,245,240) url("img/bg_left.jpg") no-repeat left top; } @@ -876,7 +880,7 @@ ul, li { border-radius: 0; background: rgba(30, 25, 20, 1); background-origin: padding-box; - border-image: url(img/ui/footer-button.png) 10 repeat; + /*border-image: url(img/ui/footer-button.png) 10 repeat;*/ border-image-width: 4px; border-image-outset: 0px; } @@ -884,7 +888,7 @@ ul, li { #controls .scene-control.active, #controls .control-tool.active, #controls .scene-control:hover, #controls .control-tool:hover { background: rgba(72, 46, 28, 1); background-origin: padding-box; - border-image: url(img/ui/footer-button.png) 10 repeat; + /*border-image: url(img/ui/footer-button.png) 10 repeat;*/ border-image-width: 4px; border-image-outset: 0px; box-shadow: 0 0 3px #ff6400; @@ -896,7 +900,7 @@ ul, li { } #hotbar #action-bar .macro { - border-image: url(img/ui/bg_control.jpg) 21 repeat; + /*border-image: url(img/ui/bg_control.jpg) 21 repeat;*/ border-image-slice: 6 6 6 6 fill; border-image-width: 6px 6px 6px 6px; border-image-outset: 0px 0px 0px 0px; @@ -909,7 +913,7 @@ ul, li { } #players { - border-image: url(img/ui/footer-button.png) 10 repeat; + /*border-image: url(img/ui/footer-button.png) 10 repeat;*/ border-image-width: 4px; border-image-outset: 0px; background: rgba(30, 25, 20, 1); @@ -922,7 +926,7 @@ ul, li { #navigation #scene-list .scene.nav-item { background: rgba(30, 25, 20, 1); background-origin: padding-box; - border-image: url(img/ui/footer-button.png) 10 repeat; + /*border-image: url(img/ui/footer-button.png) 10 repeat;*/ border-image-width: 4px; border-image-outset: 0px; } @@ -930,7 +934,7 @@ ul, li { #navigation #scene-list .scene.view, #navigation #scene-list .scene.context { background: rgba(72, 46, 28, 1); background-origin: padding-box; - border-image: url(img/ui/footer-button.png) 10 repeat; + /*border-image: url(img/ui/footer-button.png) 10 repeat;*/ border-image-width: 4px; border-image-outset: 0px; box-shadow: 0 0 3px #ff6400; @@ -939,7 +943,7 @@ ul, li { #navigation #nav-toggle { background: rgba(30, 25, 20, 1); background-origin: padding-box; - border-image: url(img/ui/footer-button.png) 10 repeat; + /*border-image: url(img/ui/footer-button.png) 10 repeat;*/ border-image-width: 4px; border-image-outset: 0px; } @@ -971,7 +975,7 @@ ul, li { width: 360px; background: rgba(30, 25, 20, 0.9); - border-image: url(img/ui/bg_control.jpg) 21 repeat; + /*border-image: url(img/ui/bg_control.jpg) 21 repeat;*/ border-image-slice: 6 6 6 6 fill; border-image-width: 6px 6px 6px 6px; border-image-outset: 0px 0px 0px 0px; @@ -1006,6 +1010,11 @@ ul, li { opacity: 1; } +.text-red { + color: red; + font-weight: bold; +} + .chat-card-button { box-shadow: inset 0px 1px 0px 0px #a6827e; background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%); diff --git a/templates/dialog-combat-actions.html b/templates/dialog-combat-actions.html new file mode 100644 index 0000000..05941b6 --- /dev/null +++ b/templates/dialog-combat-actions.html @@ -0,0 +1,59 @@ +
+ +

Combat Actions for round {{round}}

+ +
+
+
+ + +
+
+ +
+
+
+
+ + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+ +
\ No newline at end of file