"use strict"; // // App.js // Team 8 Hifi Hackathon 2018 // Chang Kayla Sam Lab // // Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // (function() { //***************************************************** // THE APP TABLET ICON ON OFF //***************************************************** var TABLET_BUTTON_NAME = "Carrot Hunt"; // var ICON_URL = Script.resolvePath("https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/carrot.svg"); var ICON_URL = "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/CarrotHunt.png"; var ACTIVE_ICON_URL = Script.resolvePath("../models/asset/carrot%20%281%29.svg"); // var ACTIVE_ICON_URL = "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/CarrotHunt.png"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, activeIcon: ACTIVE_ICON_URL }); var gameOn = false; var window; function onClicked() { if (gameOn) { killGame() } else { createGame() } } button.clicked.connect(onClicked); Script.scriptEnding.connect(function () { tablet.removeButton(button); button.clicked.disconnect(onClicked); killGame() }); //***************************************************** // THE GAME CONFIG //***************************************************** var theConfig = { grid: { size: 5.0, }, tile: { modelURL: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/model.obj", }, terrain: { numTiles: 16, numCarrotsPerTile: 2, }, carrot: { dim: { x: 0.3, y: 1.0, z: 0.3 }, modelURL: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/carrot2.fbx", clientScript: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/code/carrot-client.js", serverScript:"https://hifi-public.s3.amazonaws.com/sam/2018-oct/code/carrot-server.js", }, props: { models: [ { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/GRASS.fbx", yoff: 0.7 }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/BUNNY3.fbx", yoff: 0.9, }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/BUNNY3.fbx", yoff: 0.9, }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/CABAGEfbx.fbx", yoff: 0.7, dim: {"x": 0.8126206040382385, "y": 0.7492879271507263, "z": 0.8094865620136261} }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/Lowpoly_tree_sample.obj", yoff: 2.9, dim: {"x": 4.765009880065918, "y": 5.040099143981934, "z": 3.54240512847900} }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/rainbow.fbx", yoff: 1.0, dim: {x: 1.5, y: 1.5, z: 1.1} }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/real%20rainbow.fbx", yoff: 3.0, dim: {"x": 0.6, "y": 1.84, "z": 3.39} }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/Lowpoly_tree_sample.obj", yoff: 2.9, dim: {"x": 4.765009880065918, "y": 5.040099143981934, "z": 3.54240512847900} }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/GRASS.fbx", yoff: 0.7 }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/GRASS.fbx", yoff: 0.7 }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/GRASS.fbx", yoff: 0.7 }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/mountain3.fbx", yoff: 0.7, dim: {"x": 3.8655, "y": 5.6837, "z": 1.8314} }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/rainbow.fbx", yoff: 1.0, dim: {x: 1.5, y: 1.5, z: 1.1} }, { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/rainbow.fbx", yoff: 1.0, dim: {x: 1.5, y: 1.5, z: 1.1} }, ], notUsed: [ { url: "https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/model%20%281%29.fbx", yoff: 1.2, dim: {"x":1.58, "y": 1.74, "z": 1.54} }, ], }, debug: false, lifetime: 50, audio: { begin: SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sam/2018-oct/sounds/cartoon-vintage-surf-organ-melody_zk633uNd.mp3"), end: SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sam/2018-oct/sounds/chinese-gong-crash_z1sgFyHO.mp3"), tick: SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sam/2018-oct/sounds/jg-032316-sfx-elearning-clock-ticking-for-5-seconds.mp3"), } }; function Print(m) { if (true) { print(m) } } var AUDIO_VOLUME_LEVEL = 0.25; function playSound(sound, position, repeat) { if (sound.downloaded) { return Audio.playSound(sound, { //position: position, //localOnly: (local === undefined) ? false : local, loop: (repeat === undefined) ? false : repeat, volume: AUDIO_VOLUME_LEVEL }); } } //***************************************************** // THE GAME //***************************************************** function Game(config, killer) { Print("Game Created:" + JSON.stringify(this)); playSound(theConfig.audio.begin) this.grid = new Grid(config); this.balance = new Balance(config); this.tiles = [] var numTiles = config.terrain.numTiles; for (var index = 0; index < numTiles; index++) { this.tiles.push(new Tile(index, config, this.grid, this.balance)); } this.tickSound = playSound(config.audio.tick, 1, false) this.killself = killer this.bannerProperties = { "dimensions": { "x": 10, "y": 5, "z": 0.009200000204145908 }, "ignoreForCollisions": true, "modelURL": "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", /* "rotation": { "w": 1, "x": -1.52587890625e-05, "y": -1.52587890625e-05, "z": -1.52587890625e-05 },*/ position: tileCoordRelPosToPos(this.grid, {x: -4, y: 0, z: 2}, { x: 0, y: 8.0, z: 0}), rotation: this.grid.stageOrientation, "shapeType": "box", "textures": "{\"tex.picture\":\"https://hifi-public.s3.amazonaws.com/sam/2018-oct/models/CarrotHunt.png\"}", "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":true}}", lifetime: config.lifetime, } this.banner = Entities.addEntity( this.bannerProperties ); // put an end to this in lifetime seconds this.timer = Script.setTimeout(killer, config.lifetime * 1000); } Game.prototype.win = function () { this.killself(); } Game.prototype.kill = function () { this.timer.stop(); Entities.deleteEntity(this.banner); this.balance.kill(); for (var i = 0; i < this.tiles.length; i++) { this.tiles[i].kill(); } this.tickSound.stop(); playSound(theConfig.audio.end) Print("Game Killed:" + JSON.stringify(this)); }; var theGame = {} function killGame() { gameOn = false; theGame.kill(); button.editProperties({isActive: false}) Print("Carotte Game End") } function createGame() { Print("Carotte Game Start") gameOn = true theGame = new Game(theConfig, killGame); button.editProperties({isActive: true}); } //***************************************************** // BALANCE //***************************************************** function Balance(config) { var numTiles = config.terrain.numTiles; var numCarrotsPerTile = config.terrain.numCarrotsPerTile; // Total carrots this.numCarrots = numTiles * numCarrotsPerTile; // Find the tile containing the winning carrot var winningTileIndex = Math.floor(numTiles * Math.random()); // Find the winning carrot in there var carrotIndex = winningTileIndex * numCarrotsPerTile; var winningCarrotIndex = carrotIndex + Math.floor(numCarrotsPerTile * Math.random()); this.winningTile = winningTileIndex; this.winningCarrots = winningCarrotIndex; // pick a looser carrot var loosingCarrotIndex = winningCarrotIndex; while (loosingCarrotIndex == winningCarrotIndex) { loosingCarrotIndex = Math.floor(this.numCarrots * Math.random()); } this.loosingCarrots = loosingCarrotIndex; // Based on the number of props and the number of tiles, assign some: var numProps = config.props.models.length; var ratioPropsToTile = numProps / numTiles; var maxNumProps = Math.min(numProps, numTiles); this.propTiles = []; for (var i = 0; i < numProps; i++) { var propTileIndex = Math.floor(numTiles * Math.random()); while (this.propTiles.indexOf(propTileIndex) !== -1) { propTileIndex = Math.floor(numTiles * Math.random()); } this.propTiles.push(propTileIndex) } } Balance.prototype.getCarrotState = function (index) { if (this.winningCarrots == index) { return 1; } else if (this.loosingCarrots == index) { return -1; } else { return 0; } } Balance.prototype.isWinningTile = function (index) { return this.winningTileIndex == index } Balance.prototype.isPropTile = function (tileIndex) { return -1 != this.propTiles.indexOf(tileIndex); } Balance.prototype.getPropAtTile = function (tileIndex) { return this.propTiles.indexOf(tileIndex); } Balance.prototype.kill = function () { Print("Balance Killed:" + JSON.stringify(this)); }; //***************************************************** // GRID //***************************************************** var GRID_AXIS_UNIT = 1.0; var GRID_CELL_SIZE = 5.0; var GRID_HEX_H = (GRID_CELL_SIZE / 2) * 0.86602540378; var GRID_HEX_V = GRID_CELL_SIZE / 4; var GRID_X_OFFSET = 3 * GRID_HEX_V; var GRID_Y_OFFSET = GRID_AXIS_UNIT; var GRID_Z_OFFSET = 2 * GRID_HEX_H; var GRID_ROOT_Y_OFFSET = -GRID_AXIS_UNIT; var GRID_HEX_TILE_DIM = {x: 4 * GRID_HEX_V, y: GRID_AXIS_UNIT, z: GRID_HEX_H * 2.0} function Grid(config) { this.stageOrientation = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0); this.stageRoot = {"x":0.0,"y":0.0,"z":0.0}; this.stageAxisA = Vec3.multiply(GRID_AXIS_UNIT, Quat.getForward(this.stageOrientation)); this.stageAxisB = Vec3.multiply(GRID_AXIS_UNIT, Quat.getRight(this.stageOrientation)); this.stageAxisC = Vec3.multiply(GRID_AXIS_UNIT, Quat.getUp(this.stageOrientation)); this.initializeGrid = function() { MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0); var orientation = MyAvatar.orientation; orientation = Quat.safeEulerAngles(orientation); orientation.x = 0; orientation = Quat.fromVec3Degrees(orientation); this.stageOrientation = orientation; this.stageAxisA = Vec3.multiply(GRID_AXIS_UNIT, Quat.getForward(this.stageOrientation)); this.stageAxisB = Vec3.multiply(GRID_AXIS_UNIT, Quat.getRight(this.stageOrientation)); this.stageAxisC = Vec3.multiply(GRID_AXIS_UNIT, Quat.getUp(this.stageOrientation)); var pos = Vec3.sum(MyAvatar.position, Vec3.multiply(-1.0, Quat.getForward(orientation))); this.stageRoot = Vec3.sum(pos, Vec3.multiply(-1.0, Quat.getUp(orientation))); Print("Game Killed:" + JSON.stringify(this)); } this.initializeGrid(); this.generateTileCoords = function(numTiles) { var tiles = []; var index = 0; var radius = Math.ceil(Math.sqrt(numTiles)); var halfRadius = Math.max(1, Math.floor(radius * 0.5)); Print("radius = " + radius + "halfRadius = " + halfRadius) for (var j = 1; j <=radius; j++) { for (var i = 0; i < radius; i++) { tiles.push({x: -i, y: 0, z: j}); // Print(JSON.stringify(tiles)) index++; if (index >= numTiles) { return tiles; } } } return tiles; } this.tiles = this.generateTileCoords(config.terrain.numTiles); this.getTileCoord = function(index) { if (index < this.tiles.length) { return this.tiles[index]; } return {x: 0, y: 0, z: 0}; }; this.getResetCoord = function() { return {x: 0, y: 5.0, z: 0}; }; } function tileCoordToPos(grid, coord) { var isEven = coord.z == 2 * Math.floor(coord.z / 2); return Vec3.sum(grid.stageRoot, { x: coord.z * GRID_X_OFFSET, y: coord.y, z: coord.x * GRID_Z_OFFSET + (coord.z * GRID_HEX_H)} ); } function tileCoordRelPosToPos(grid, coord, pos) { var origin = tileCoordToPos(grid, coord) return Vec3.sum(origin, { x: pos.z * 0.6 * 4.0 * GRID_HEX_V, y: pos.y, z: pos.x * 0.6 * 2.0 * GRID_HEX_H} ); } function tileDim() { return { x: GRID_CELL_SIZE,y: GRID_AXIS_UNIT,z: GRID_CELL_SIZE }; // return GRID_HEX_TILE_DIM; } //***************************************************** // TERRAIN TILE //***************************************************** var shapeTypes = [ "none", "box", "sphere", "compound", "simple-hull", "simple-compound", "static-mesh" ]; function Tile(index, config, grid, balance) { this.index = index; this.winner = balance.isWinningTile(index); this.coord = grid.getTileCoord(this.index); var pos = tileCoordToPos(grid, this.coord); if (config.debug) { if (this.winner) { print("Tile Winner:" + this.index + JSON.stringify(this.coord)); } else { print("Tile not Winner:" + this.index + JSON.stringify(this.coord)); } } this.entityProperties = { type: "Shape", shape: "Hexagon", name: "Tile-" + index, color: (this.winner && config.debug ? { red: 120, green: 20, blue: 240 } : { red: 120 + Math.random() * 50, green: 175 + Math.random() * 75, blue: 100 + Math.random() * 150 }), position: pos, rotation: grid.stageOrientation, dimensions: tileDim(), lifetime: config.lifetime, userData: JSON.stringify({ grabbableKey: { grabbable: false } }), shapeType:shapeTypes[1] } this.entity = Entities.addEntity( this.entityProperties ); this.entityProperties = Entities.getEntityProperties(this.entity); this.carrots = [] var numCarrots = config.terrain.numCarrotsPerTile; var carrotIndex = this.index * numCarrots; for (var i = 0; i < numCarrots; i++) { this.carrots.push(new Carrot(carrotIndex, this, config, grid, balance)); carrotIndex++; } this.props = []; if (balance.isPropTile(index)) { this.props.push(new Prop(balance.getPropAtTile(index), this, config, grid, balance)); } // print("Tile Created:" + JSON.stringify(this)); } Tile.prototype.kill = function () { for (var i = 0; i < this.props.length; i++) { this.props[i].kill(); } for (var i = 0; i < this.carrots.length; i++) { this.carrots[i].kill(); } if (this.entity) { Entities.deleteEntity(this.entity); } Print("Tile Killed:" + JSON.stringify(this)); }; //***************************************************** // CARROT //***************************************************** function Carrot(index, tile, config, grid, balance) { this.tileCoord = tile.coord; this.winner = balance.getCarrotState(index); this.tileRelPos = {x: Math.random() - 0.5, y: 0.5, z: Math.random() - 0.5}; var pos = tileCoordRelPosToPos(grid, this.tileCoord, this.tileRelPos); var userData = { grabbableKey: { grabbable: false }, winner: this.winner } // if looser capture the reset pos if (this.winner == -1) { userData.resetPos = tileCoordToPos(grid, grid.getResetCoord()) } this.entityProperties = { type: "Model", name: "Carrot-" + index, position: pos, rotation: grid.stageOrientation, dimensions: config.carrot.dim, lifetime: config.lifetime, modelURL: config.carrot.modelURL, userData: JSON.stringify(userData), shapeType:shapeTypes[4], script: config.carrot.clientScript + "?" + Date.now(), serverScripts: config.carrot.serverScript + "?" + Date.now(), } this.entity = Entities.addEntity( this.entityProperties ); this.entityProperties = Entities.getEntityProperties(this.entity); // Print("Carrot Created:" + JSON.stringify(this)); } Carrot.prototype.kill = function () { if (this.entity) { Entities.deleteEntity(this.entity); // this.entity = null } Print("Carrot Killed:" + JSON.stringify(this)); }; //***************************************************** // PROP //***************************************************** function Prop(index, tile, config, grid) { this.tileCoord = tile.coord; this.tileRelPos = {x: Math.random() - 0.5, y: config.props.models[index].yoff, z: Math.random() - 0.5}; var pos = tileCoordRelPosToPos(grid, this.tileCoord, this.tileRelPos); this.entityProperties = { type: "Model", name: "Prop-" + index, position: pos, rotation: grid.stageOrientation, lifetime: config.lifetime, modelURL: config.props.models[index].url, userData: JSON.stringify({ grabbableKey: { grabbable: false } }), } if (config.props.models[index].dim) { this.entityProperties.dimensions = config.props.models[index].dim; } this.entity = Entities.addEntity( this.entityProperties ); this.entityProperties = Entities.getEntityProperties(this.entity); // Print("Prop Created:" + JSON.stringify(this)); } Prop.prototype.kill = function () { if (this.entity) { Entities.deleteEntity(this.entity); // this.entity = null } Print("Prop Killed:" + JSON.stringify(this)); }; }());