From 68c014e6bcf3272541f19fc9925ebca96ec657ee Mon Sep 17 00:00:00 2001 From: CelesteBloodreign Date: Wed, 19 Jun 2024 18:10:15 -0400 Subject: [PATCH] dddice integration POC 1 --- .../getReplayChoicesInputProvider.ts | 3 +- .../ui/creature/actions/ActionDialog.vue | 4 +- .../client/ui/creature/actions/doAction.ts | 2 +- .../actions/input/diceProviders/dddice.ts | 132 ++++++++++++++++++ .../ui/creature/character/CharacterSheet.vue | 2 + .../client/ui/dialogStack/DialogStack.vue | 2 +- app/package-lock.json | 21 +++ app/package.json | 1 + 8 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 app/imports/client/ui/creature/actions/input/diceProviders/dddice.ts diff --git a/app/imports/api/engine/action/functions/userInput/getReplayChoicesInputProvider.ts b/app/imports/api/engine/action/functions/userInput/getReplayChoicesInputProvider.ts index ae6a706b1..9119238df 100644 --- a/app/imports/api/engine/action/functions/userInput/getReplayChoicesInputProvider.ts +++ b/app/imports/api/engine/action/functions/userInput/getReplayChoicesInputProvider.ts @@ -16,8 +16,7 @@ export default function getReplayChoicesInputProvider(actionId: string, decision }, // To roll dice, ignore the user and use the deterministic dice roller again rollDice(dice) { - decisionStack.pop(); - return dRoller(dice); + return Promise.resolve(decisionStack.pop()); }, choose() { return Promise.resolve(decisionStack.pop()); diff --git a/app/imports/client/ui/creature/actions/ActionDialog.vue b/app/imports/client/ui/creature/actions/ActionDialog.vue index b911d2532..807217317 100644 --- a/app/imports/client/ui/creature/actions/ActionDialog.vue +++ b/app/imports/client/ui/creature/actions/ActionDialog.vue @@ -88,6 +88,7 @@ import EngineActions from '/imports/api/engine/action/EngineActions'; import LogContent from '/imports/client/ui/log/LogContent.vue'; //import RollInput from '/imports/client/ui/creature/actions/input/RollInput.vue'; import TargetsInput from '/imports/client/ui/creature/actions/input/TargetsInput.vue'; +import {rollDice} from "/imports/client/ui/creature/actions/input/diceProviders/dddice"; export default { components: { @@ -215,7 +216,7 @@ export default { return this.promiseInput(); }, async rollDice(dice) { - return Promise.resolve(this.deterministicDiceRoller(dice)); + //return Promise.resolve(this.deterministicDiceRoller(dice)); /* Dice Animation and user control goes here: this.activeInputParams = { deterministicDiceRoller: this.deterministicDiceRoller, @@ -224,6 +225,7 @@ export default { this.activeInput = 'roll-input'; return this.promiseInput(); */ + return Promise.resolve(rollDice(dice)); }, async nextStep(task) { return this.promiseInput(); diff --git a/app/imports/client/ui/creature/actions/doAction.ts b/app/imports/client/ui/creature/actions/doAction.ts index 16d3d7b8f..414d8f3e2 100644 --- a/app/imports/client/ui/creature/actions/doAction.ts +++ b/app/imports/client/ui/creature/actions/doAction.ts @@ -81,7 +81,7 @@ function getErrorOnInputRequestProvider(actionId) { const errorOnInputRequest: InputProvider = { targetIds: throwInputRequestedError, nextStep: throwInputRequestedError, - rollDice: getDeterministicDiceRoller(actionId), + rollDice: throwInputRequestedError, choose: throwInputRequestedError, advantage: throwInputRequestedError, check: throwInputRequestedError, diff --git a/app/imports/client/ui/creature/actions/input/diceProviders/dddice.ts b/app/imports/client/ui/creature/actions/input/diceProviders/dddice.ts new file mode 100644 index 000000000..60a945a67 --- /dev/null +++ b/app/imports/client/ui/creature/actions/input/diceProviders/dddice.ts @@ -0,0 +1,132 @@ +import {ThreeDDice, ThreeDDiceAPI, ThreeDDiceRollEvent, IDiceRoll} from 'dddice-js'; + +let dddice; +const RENDER_MODE = 'on'; +const APP_NAME = 'Dice Cloud' +const theme = 'dddice-bees'; +const room ='4qR_jcc'; + +async function createGuestUserIfNeeded() { + let apiKey = window.localStorage.getItem('dddice.apiKey') as string; + if (!apiKey) { + console.log('creating guest account'); + apiKey = (await new ThreeDDiceAPI(undefined, APP_NAME).user.guest()).data; + await window.localStorage.setItem('dddice.apiKey', apiKey); + + } + return apiKey; +} + +const waitForRoll = () => +{ + return new Promise(resolve => { + dddice.off(ThreeDDiceRollEvent.RollFinished); + dddice.on(ThreeDDiceRollEvent.RollFinished, () => resolve()); + }); +} + +const rollDice = async ( + dice: { number: number, diceSize: number }[] +): Promise => { + + const diceToRoll: IDiceRoll[] = []; + dice.forEach(die =>{ + for(let i = 0; i < die.number; i++){ + diceToRoll.push({type:'d'+die.diceSize,theme}) + } + }) + + const roll = await dddice.api.roll.create(diceToRoll,{ + room: room, + }); + await waitForRoll(); + console.log(roll.data.values.map(value=>value.value_to_display)); + return [roll.data.values.map(value=>value.value_to_display)]; +}; + + +async function setUpDddiceSdk() { + console.log('setting up dddice sdk'); + const apiKey = await createGuestUserIfNeeded(); + if (apiKey && room && !dddice) { + try { + api = new ThreeDDiceAPI(apiKey, 'Dice Cloud'); + const user: IUser = (await api.user.get()).data; + + let canvas: HTMLCanvasElement = document.getElementById('dddice-canvas') as HTMLCanvasElement; + + if (dddice) { + // clear the board + if (canvas) { + canvas.remove(); + canvas = undefined; + } + // disconnect from echo + if (dddice.api?.connection) dddice.api.connection.disconnect(); + // stop the animation loop + dddice.stop(); + } + + if (RENDER_MODE === 'on') { + console.log('render mode is on'); + if (!canvas) { + // add canvas element to document + canvas = document.createElement('canvas'); + canvas.id = 'dddice-canvas'; + // if the css fails to load for any reason, using tailwinds classes here + // will disable the whole interface + canvas.style.top = '0px'; + canvas.style.position = 'fixed'; + canvas.style.pointerEvents = 'none'; + canvas.style.zIndex = '100000'; + canvas.style.opacity = '100'; + canvas.style.height = '100vh'; + canvas.style.width = '100vw'; + document.body.appendChild(canvas); + window.addEventListener( + 'resize', + () => dddice && dddice.renderer && dddice.resize(window.innerWidth, window.innerHeight), + ); + } + dddice = new ThreeDDice().initialize(canvas, apiKey, undefined, APP_NAME); + dddice.start(); + try { + dddice.api.room.join(room); + } catch (error) { + log.warn('eating error', error.response?.data?.data?.message); + } + dddice.connect(room, undefined, user.uuid); + //dddice.on(ThreeDDiceRollEvent.RollCreated, (roll: IRoll) => rollCreated(roll)); + dddice.off(ThreeDDiceRollEvent.RollFinished); + dddice.on(ThreeDDiceRollEvent.RollFinished, (roll: IRoll) => rollFinished(roll)); + } else { + console.log('render mode is off'); + dddice = new ThreeDDice(); + dddice.api = new ThreeDDiceAPI(apiKey, APP_NAME); + try { + dddice.api.room.join(room); + } catch (error) { + log.warn('eating error', error.response?.data?.data?.message); + } + dddice.api.connect(room, undefined, user.uuid); + //dddice.api.listen(ThreeDDiceRollEvent.RollCreated, (roll: IRoll) => rollCreated(roll)); + } + + + console.log('dddice is ready to roll!'); + } catch (e) { + console.error(e); + console.error(`dddice | ${e.response?.data?.data?.message ?? e}`); + return; + } + } + if (dddice) { + document.body.addEventListener('click', () => { + if (dddice && !dddice?.isDiceThrowing) { + dddice.clear(); + } + }); + } +} + +export {setUpDddiceSdk, rollDice}; \ No newline at end of file diff --git a/app/imports/client/ui/creature/character/CharacterSheet.vue b/app/imports/client/ui/creature/character/CharacterSheet.vue index 63a842b67..37c665486 100644 --- a/app/imports/client/ui/creature/character/CharacterSheet.vue +++ b/app/imports/client/ui/creature/character/CharacterSheet.vue @@ -144,6 +144,7 @@ import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue' import CharacterSheetFab from '/imports/client/ui/creature/character/CharacterSheetFab.vue'; import ActionsTab from '/imports/client/ui/creature/character/characterSheetTabs/ActionsTab.vue'; import CharacterSheetInitiative from '/imports/client/ui/creature/character/CharacterSheetInitiative.vue'; +import {setUpDddiceSdk} from "/imports/client/ui/creature/actions/input/diceProviders/dddice"; export default { components: { @@ -186,6 +187,7 @@ export default { }, }, mounted() { + setUpDddiceSdk(); this.$store.commit('setPageTitle', this.creature && this.creature.name || 'Character Sheet'); this.nameObserver = Creatures.find({ creatureId: this.creatureId, diff --git a/app/imports/client/ui/dialogStack/DialogStack.vue b/app/imports/client/ui/dialogStack/DialogStack.vue index d40cb0c54..388314ee7 100644 --- a/app/imports/client/ui/dialogStack/DialogStack.vue +++ b/app/imports/client/ui/dialogStack/DialogStack.vue @@ -197,7 +197,7 @@ // If the source and the hidden Element are different // hide the source and reveal the hidden element let originalSourceTransition = source.style.transition; - if (hiddenElement !== source){ + if (hiddenElement && hiddenElement !== source){ source.style.transition = 'none'; source.style.opacity = '0'; hiddenElement.style.opacity = null; diff --git a/app/package-lock.json b/app/package-lock.json index 4f5880617..76fe44141 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -4578,6 +4578,14 @@ "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", "dev": true }, + "@types/three": { + "version": "0.143.2", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.143.2.tgz", + "integrity": "sha512-HjsRWvd6rsXViFeOdU97/pHNDQknzJbFI0/5MrQ0joOlK0uixQH40sDJs/LwkNHhFYUvVENXAUQdKDeiQChHDw==", + "requires": { + "@types/webxr": "*" + } + }, "@types/underscore": { "version": "1.11.15", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.15.tgz", @@ -4590,6 +4598,11 @@ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", "dev": true }, + "@types/webxr": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.17.tgz", + "integrity": "sha512-JYcclaQIlisHRXM9dMF7SeVvQ54kcYc7QK1eKCExCTLKWnZDxP4cp/rXH4Uoa1j5+5oQJ0Cc2sZC/PWiiG4q2g==" + }, "@types/whatwg-url": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", @@ -5307,6 +5320,14 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" }, + "dddice-js": { + "version": "0.19.16", + "resolved": "https://registry.npmjs.org/dddice-js/-/dddice-js-0.19.16.tgz", + "integrity": "sha512-itCvC5jRwtZokTGtpXGKr60baM99bTn60uuz9d5QLfnHxmoHeKSE8mr3F2NKBxl2uL2Pt67ljbZSvVHSAmNwLw==", + "requires": { + "@types/three": "0.143.2" + } + }, "ddp-rate-limiter-mixin": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/ddp-rate-limiter-mixin/-/ddp-rate-limiter-mixin-1.1.10.tgz", diff --git a/app/package.json b/app/package.json index bffca6f18..644407284 100644 --- a/app/package.json +++ b/app/package.json @@ -36,6 +36,7 @@ "cytoscape-klay": "^3.1.4", "dagre": "^0.8.5", "date-fns": "^1.30.1", + "dddice-js": "^0.19.16", "ddp-rate-limiter-mixin": "^1.1.10", "discord.js": "^12.5.3", "dompurify": "^2.4.7",