Needs a lot of cleanup. Data has been de-duplicated, and where identical copies existed, one of them has been replaced with a symlink. Some files have been excluded, such as binaries, installers and debug dumps. Some of that may still be present.
554 lines
20 KiB
JavaScript
554 lines
20 KiB
JavaScript
"use strict";
|
|
|
|
//
|
|
// CarrotHunt.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() {
|
|
var AppUi = Script.require('appUi');
|
|
|
|
var theGame;
|
|
var gameOn = false;
|
|
|
|
function killGame() {
|
|
gameOn = false;
|
|
if (theGame !== undefined) {
|
|
theGame.kill();
|
|
}
|
|
Print("Carotte Game End")
|
|
}
|
|
|
|
function createGame() {
|
|
Print("Carotte Game Start")
|
|
gameOn = true
|
|
theGame = new Game(theConfig, killGame);
|
|
}
|
|
|
|
function onGameButtonClicked() {
|
|
if (gameOn) {
|
|
killGame()
|
|
} else {
|
|
createGame()
|
|
}
|
|
}
|
|
|
|
|
|
//*****************************************************
|
|
// THE GAME CONFIG
|
|
//*****************************************************
|
|
|
|
var theConfig = {
|
|
grid: {
|
|
size: 5.0,
|
|
},
|
|
tile: {
|
|
},
|
|
terrain: {
|
|
numTiles: 16,
|
|
numCarrotsPerTile: 2,
|
|
},
|
|
carrot: {
|
|
dim: { x: 0.3, y: 1.0, z: 0.3 },
|
|
modelURL: Script.resolvePath("./asset/model/carrot2.fbx"),
|
|
clientScript: Script.resolvePath("./asset/carrot-client.js"),
|
|
serverScript: Script.resolvePath("./asset/carrot-server.js"),
|
|
},
|
|
props: {
|
|
models: [
|
|
{ url: Script.resolvePath("./asset/model/GRASS.fbx"), yoff: 0.7 },
|
|
{ url: Script.resolvePath("./asset/model/BUNNY3.fbx"), yoff: 0.9, },
|
|
{ url: Script.resolvePath("./asset/model/CABAGEfbx.fbx"), yoff: 0.7, dim: {"x": 0.8126206040382385, "y": 0.7492879271507263, "z": 0.8094865620136261} },
|
|
{ url: Script.resolvePath("./asset/model/Lowpoly_tree_sample.obj"), yoff: 2.9, dim: {"x": 4.765009880065918, "y": 5.040099143981934, "z": 3.54240512847900} },
|
|
{ url: Script.resolvePath("./asset/model/rainbow.fbx"), yoff: 1.0, dim: {x: 1.5, y: 1.5, z: 1.1} },
|
|
{ url: Script.resolvePath("./asset/model/mountain3.fbx"), yoff: 0.7, dim: {"x": 3.8655, "y": 5.6837, "z": 1.8314} },
|
|
{ url: Script.resolvePath("./asset/model/real%20rainbow.fbx"), yoff: 3.0, dim: {"x": 0.6, "y": 1.84, "z": 3.39} },
|
|
{ url: Script.resolvePath("./asset/model/Lowpoly_tree_sample.obj"), yoff: 2.9, dim: {"x": 4.765009880065918, "y": 5.040099143981934, "z": 3.54240512847900} },
|
|
{ url: Script.resolvePath("./asset/model/GRASS.fbx"), yoff: 0.7 },
|
|
{ url: Script.resolvePath("./asset/model/GRASS.fbx"), yoff: 0.7 },
|
|
{ url: Script.resolvePath("./asset/model/BUNNY3.fbx"), yoff: 0.9, },
|
|
{ url: Script.resolvePath("./asset/model/GRASS.fbx"), yoff: 0.7 },
|
|
{ url: Script.resolvePath("./asset/model/GRASS.fbx"), yoff: 0.7 },
|
|
{ url: Script.resolvePath("./asset/model/rainbow.fbx"), yoff: 1.0, dim: {x: 1.5, y: 1.5, z: 1.1} },
|
|
{ url: Script.resolvePath("./asset/model/rainbow.fbx"), yoff: 1.0, dim: {x: 1.5, y: 1.5, z: 1.1} },
|
|
{ url: Script.resolvePath("./asset/model/GRASS.fbx"), yoff: 0.7 },
|
|
],
|
|
|
|
notUsed: [
|
|
],
|
|
|
|
},
|
|
debug: false,
|
|
lifetime: 100,
|
|
|
|
audio: {
|
|
begin: SoundCache.getSound(Script.resolvePath("./asset/audio/cartoon-vintage-surf-organ-melody_zk633uNd.mp3")),
|
|
end: SoundCache.getSound(Script.resolvePath("./asset/audio/chinese-gong-crash_z1sgFyHO.mp3")),
|
|
tick: SoundCache.getSound(Script.resolvePath("./asset/audio/jg-032316-sfx-elearning-clock-ticking-for-5-seconds.mp3")),
|
|
},
|
|
banner: {
|
|
modelURL: Script.resolvePath("./asset/model/default-image-model.fbx"),
|
|
},
|
|
|
|
sessionID: MyAvatar.sessionUUID,
|
|
};
|
|
|
|
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": config.banner.modelURL,
|
|
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\":\"" + APP_IMAGE_URL + "\"}",
|
|
"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 () {
|
|
Script.clearTimeout(this.timer);
|
|
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");
|
|
};
|
|
|
|
//*****************************************************
|
|
// 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});
|
|
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())
|
|
} else if (this.winner == 1) {
|
|
userData.gameUUID = MyAvatar.sessionUUID
|
|
}
|
|
|
|
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));
|
|
};
|
|
|
|
|
|
//*****************************************************
|
|
// THE APP TABLET ICON ON OFF
|
|
//*****************************************************
|
|
var TABLET_BUTTON_NAME = "Carrot Hunt";
|
|
var UI_URL = Script.resolvePath("./CarrotHunt.html") + "?" + Date.now();
|
|
var ICON_URL = Script.resolvePath("./asset/carrot_off.svg");
|
|
var ACTIVE_ICON_URL = Script.resolvePath("./asset/carrot_on.svg");
|
|
var APP_IMAGE_URL = Script.resolvePath("./asset/CarrotHunt.png");
|
|
|
|
|
|
function fromUIContent (message) {
|
|
print("fromUIContent: " + JSON.stringify(message))
|
|
if (message.carrotHunt !== undefined) {
|
|
print("fromUIContent: " + JSON.stringify(message))
|
|
switch (message.carrotHunt) {
|
|
case "toggleGame":
|
|
onGameButtonClicked();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
var ui;
|
|
function startup() {
|
|
ui = new AppUi({
|
|
buttonName: TABLET_BUTTON_NAME,
|
|
home: UI_URL,
|
|
onOpened: function () {
|
|
print("Hello Carrot Hunter!");
|
|
},
|
|
onClosed: function () {
|
|
print("Bye Carrot Hunter!");
|
|
},
|
|
onMessage: fromUIContent,
|
|
normalButton: ICON_URL,
|
|
activeButton: ACTIVE_ICON_URL
|
|
});
|
|
}
|
|
startup();
|
|
Script.scriptEnding.connect(function () {
|
|
killGame()
|
|
});
|
|
|
|
}());
|