diff --git a/src/WorldFacade.js b/src/WorldFacade.js index c9379fc..6fa9b43 100644 --- a/src/WorldFacade.js +++ b/src/WorldFacade.js @@ -389,7 +389,7 @@ class WorldFacade { } // TODO: pass data with roll - such as roll name. Passed back at the end in the results - roll(notation, {theme, themeColor, newStartPoint = true} = {}) { + roll(notation, {theme = this.config.theme, themeColor = this.config.themeColor, newStartPoint = true} = {}) { // note: to add to a roll on screen use .add method // reset the offscreen worker and physics worker with each new roll this.clear() @@ -403,14 +403,14 @@ class WorldFacade { newStartPoint }) - const parsedNotation = this.createNotationArray(notation) + const parsedNotation = this.createNotationArray(notation, this.themesLoadedData[theme].diceAvailable) this.#makeRoll(parsedNotation, collectionId) // returns a Promise that is resolved in onRollComplete return this.rollCollectionData[collectionId].promise } - add(notation, {theme, themeColor, newStartPoint = true} = {}) { + add(notation, {theme = this.config.theme, themeColor = this.config.themeColor, newStartPoint = true} = {}) { const collectionId = this.#collectionIndex++ @@ -422,7 +422,7 @@ class WorldFacade { newStartPoint }) - const parsedNotation = this.createNotationArray(notation) + const parsedNotation = this.createNotationArray(notation, this.themesLoadedData[theme].diceAvailable) this.#makeRoll(parsedNotation, collectionId) // returns a Promise that is resolved in onRollComplete @@ -514,8 +514,12 @@ class WorldFacade { let id = notation.id !== undefined ? notation.id : this.#idIndex++ index = hasGroupId ? notation.groupId : this.#groupIndex + const dieType = Number.isInteger(notation.sides) ? `d${notation.sides}` : notation.sides + + notation.sides = dieType + const roll = { - sides: notation.sides, + sides: dieType, groupId: index, collectionId: collection.id, rollId, @@ -530,22 +534,23 @@ class WorldFacade { collection.rolls.push(this.rollDiceData[rollId]) // TODO: eliminate the 'd' for more flexible naming such as 'fate' - ensure numbers are strings - if (roll.sides === 'fate' && (!diceAvailable.includes(`d${roll.sides}`) && !diceExtra.includes(`d${roll.sides}`))){ + if (roll.sides === 'fate' && (!diceAvailable.includes(roll.sides) && !diceExtra.includes(roll.sides))){ console.warn(`fate die unavailable in '${theme}' theme. Using fallback.`) const min = -1 const max = 1 roll.value = Random.range(min,max) this.#DiceWorld.addNonDie(roll) - } else if(this.config.suspendSimulation || (!diceAvailable.includes(`d${roll.sides}`) && !diceExtra.includes(`d${roll.sides}`))){ + } + else if(this.config.suspendSimulation || (!diceAvailable.includes(roll.sides) && !diceExtra.includes(roll.sides))){ // check if the requested roll is available in the current theme, if not then use crypto fallback - console.warn(this.config.suspendSimulation ? "3D simulation suspended. Using fallback." : `${roll.sides} sided die unavailable in '${theme}' theme. Using fallback.`) + console.warn(this.config.suspendSimulation ? "3D simulation suspended. Using fallback." : `${roll.sides} die unavailable in '${theme}' theme. Using fallback.`) roll.value = Random.range(1, roll.sides) this.#DiceWorld.addNonDie(roll) } else { let parentTheme - if(diceExtra.includes(`d${roll.sides}`)) { - const parentThemeName = diceInherited[`d${roll.sides}`] + if(diceExtra.includes(roll.sides)) { + const parentThemeName = diceInherited[roll.sides] parentTheme = this.themesLoadedData[parentThemeName] } this.#DiceWorld.add({ @@ -577,7 +582,7 @@ class WorldFacade { // accepts array of notations eg: ['4d6','2d10'] // accepts object {sides:int, qty:int} // accepts array of objects eg: [{sides:int, qty:int, mods:[]}] - createNotationArray(input){ + createNotationArray(input, diceAvailable){ const notation = Array.isArray( input ) ? input : [ input ] let parsedNotation = [] @@ -620,7 +625,7 @@ class WorldFacade { // console.log('roll', roll) // if notation is an array of strings if ( typeof roll === 'string' ) { - parsedNotation.push( this.parse( roll ) ) + parsedNotation.push( this.parse( roll, diceAvailable ) ) } else if ( typeof notation === 'object' ) { verifyRollId( roll ) verifyObject( roll ) && parsedNotation.push( roll ) @@ -632,10 +637,12 @@ class WorldFacade { // parse text die notation such as 2d10+3 => {number:2, type:6, modifier:3} // taken from https://github.com/ChapelR/dice-notation - parse(notation) { - const diceNotation = /(\d+)[dD](\d+)(.*)$/i + parse(notation, diceAvailable) { + const diceNotation = /(\d+)([dD]{1}\d+)(.*)$/i const percentNotation = /(\d+)[dD]([0%]+)(.*)$/i - const fudgeNotation = /(\d+)df+(ate)*$/i + const fudgeNotation = /(\d+)[dD](f+[ate]*)(.*)$/i + // const customNotation = /(\d+)[dD](.*)([+-])/i + const customNotation = /(\d+)[dD]([\d\w]+)([+-]{0,1}\d+)?/i const modifier = /([+-])(\d+)/ const cleanNotation = notation.trim().replace(/\s+/g, '') const validNumber = (n, err) => { @@ -647,7 +654,7 @@ class WorldFacade { } // match percentNotation before diceNotation - const roll = cleanNotation.match(percentNotation) || cleanNotation.match(diceNotation) || cleanNotation.match(fudgeNotation); + const roll = cleanNotation.match(percentNotation) || cleanNotation.match(diceNotation) || cleanNotation.match(fudgeNotation) || cleanNotation.match(customNotation); let mod = 0; const msg = 'Invalid notation: ' + notation + ''; @@ -673,8 +680,10 @@ class WorldFacade { returnObj.sides = '100' // as string, not number } else if(cleanNotation.match(fudgeNotation)){ returnObj.sides = 'fate' // force lowercase + } else if(diceAvailable.includes(cleanNotation.match(customNotation)[2])){ + returnObj.sides = roll[2] // dice type instead of number } else { - returnObj.sides = validNumber(roll[2], msg); + returnObj.sides = roll[2]; } return returnObj diff --git a/src/components/Dice.js b/src/components/Dice.js index 831d869..ed4e1c0 100644 --- a/src/components/Dice.js +++ b/src/components/Dice.js @@ -27,7 +27,7 @@ class Dice { constructor(options, scene) { this.config = {...defaultOptions, ...options} this.id = this.config.id !== undefined ? this.config.id : Date.now() - this.dieType = `d${this.config.sides}` + this.dieType = this.config.sides this.comboKey = `${this.config.theme}_${this.dieType}` this.scene = scene this.createInstance() @@ -49,7 +49,11 @@ class Dice { // start the instance under the floor, out of camera view dieInstance.position.y = -100 - dieInstance.scaling = new Vector3(this.config.scale,this.config.scale,this.config.scale) + dieInstance.scaling = new Vector3( + dieInstance.scaling.x * this.config.scale, + dieInstance.scaling.y * this.config.scale, + dieInstance.scaling.z * this.config.scale + ) if(this.config.enableShadows){ // let's keep this simple for now since we know there's only one directional light @@ -72,7 +76,7 @@ class Dice { const { sides, theme = 'default', meshName, colorSuffix} = options // create a key for this die type and theme for caching and instance creation - const dieMeshName = meshName + '_d' + sides + const dieMeshName = meshName + '_' + sides const dieMaterialName = dieMeshName + '_' + theme + colorSuffix let die = scene.getMeshByName(dieMaterialName) @@ -130,7 +134,11 @@ class Dice { } // shrink the colliders if( model.name.includes("collider")) { - model.scaling = new Vector3(.9,.9,.9) + model.scaling = new Vector3( + model.scaling.x * .9, + model.scaling.y * .9, + model.scaling.z * .9 + ) } // check if d100 is available as a mesh - otherwise we'll clone a d10 if (!has_d100) { @@ -144,6 +152,7 @@ class Dice { model.freezeWorldMatrix() model.isPickable = false model.doNotSyncBoundingInfo = true + // model.scaling = new Vector3(model.scaling) // prefix all the meshes ids from this file with the file name so we can find them later e.g.: 'default-dice_d10' and 'default-dice_d10_collider' // model.id = meshName + '_' + model.id model.name = meshName + '_' + model.name diff --git a/src/components/physics.worker.js b/src/components/physics.worker.js index 3d7a956..bce7ebc 100644 --- a/src/components/physics.worker.js +++ b/src/components/physics.worker.js @@ -20,14 +20,14 @@ let spinScale = 60 const defaultOptions = { size: 9.5, - startingHeight: 12, - spinForce: 3, - throwForce: 2, + startingHeight: 8, + spinForce: 6, + throwForce: 5, gravity: 1, mass: 1, friction: .8, restitution: .1, - linearDamping: .4, + linearDamping: .5, angularDamping: .4, settleTimeout: 5000, // TODO: toss: "center", "edge", "allEdges" @@ -83,7 +83,8 @@ self.onmessage = (e) => { if(e.data.options.newStartPoint){ setStartPosition() } - addDie(e.data.options) + const newDie = addDie(e.data.options) + rollDie(newDie) break; case "rollDie": // TODO: this won't work, need a die object @@ -393,7 +394,7 @@ const removeBoxFromWorld = () => { const addDie = (options) => { const { sides, id, meshName, scale} = options - let cType = `d${sides}_collider` + let cType = `${sides}_collider` const comboKey = `${meshName}_${cType}` const colliderMass = colliders[comboKey]?.physicsMass || .1 const mass = colliderMass * config.mass * config.scale // feature? mass should go up with scale, but it throws off the throwForce and spinForce scaling @@ -410,8 +411,10 @@ const addDie = (options) => { newDie.mass = mass physicsWorld.addRigidBody(newDie) bodies.push(newDie) + + return newDie // console.log(`added collider for `, type) - rollDie(newDie) + // rollDie(newDie) } const rollDie = (die) => { @@ -498,7 +501,7 @@ const update = (delta) => { const speed = rb.getLinearVelocity().length() const tilt = rb.getAngularVelocity().length() - if(speed < .01 && tilt < .01 || rb.timeout < 0) { + if(speed < .01 && tilt < .005 || rb.timeout < 0) { // flag the second param for this body so it can be processed in World, first param will be the roll.id diceBufferView[(i*8) + 1] = rb.id diceBufferView[(i*8) + 2] = -1