mirror of
https://github.com/overte-org/overte.git
synced 2025-04-14 08:47:16 +02:00
Update shortbow to use Entity Server Script
This commit is contained in:
parent
312c2b8138
commit
b60651487e
5 changed files with 637 additions and 548 deletions
|
@ -26,6 +26,7 @@
|
||||||
this.entityIDsThatHaveCollidedWithMe.push(entityB);
|
this.entityIDsThatHaveCollidedWithMe.push(entityB);
|
||||||
|
|
||||||
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
|
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
|
||||||
|
print("Hit: ", entityB);
|
||||||
|
|
||||||
// If the other entity's name includes 'projectile' and we haven't hit it before,
|
// If the other entity's name includes 'projectile' and we haven't hit it before,
|
||||||
// continue on.
|
// continue on.
|
||||||
|
@ -34,9 +35,10 @@
|
||||||
Messages.sendMessage(this.gameChannel, JSON.stringify({
|
Messages.sendMessage(this.gameChannel, JSON.stringify({
|
||||||
type: "enemy-killed",
|
type: "enemy-killed",
|
||||||
entityID: this.entityID,
|
entityID: this.entityID,
|
||||||
|
position: Entities.getEntityProperties(this.entityID, 'position').position
|
||||||
}));
|
}));
|
||||||
Entities.deleteEntity(this.entityID);
|
Entities.deleteEntity(this.entityID);
|
||||||
} else if (colliderName.indexOf("goal") > -1) {
|
} else if (colliderName.indexOf("GateCollider") > -1) {
|
||||||
Messages.sendMessage(this.gameChannel, JSON.stringify({
|
Messages.sendMessage(this.gameChannel, JSON.stringify({
|
||||||
type: "enemy-escaped",
|
type: "enemy-escaped",
|
||||||
entityID: this.entityID,
|
entityID: this.entityID,
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
print("============= Script Starting =============");
|
print("============= Script Starting =============");
|
||||||
|
|
||||||
var BEGIN_BUILDING_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/gameOn.wav"));
|
Script.include('utils.js?' + Date.now());
|
||||||
var GAME_OVER_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/gameOver.wav"));
|
|
||||||
var WAVE_COMPLETE_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/waveComplete.wav"));
|
|
||||||
var EXPLOSION_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/explosion.wav"));
|
|
||||||
var TARGET_HIT_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/targetHit.wav"));
|
|
||||||
var ESCAPE_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/escape.wav"));
|
|
||||||
|
|
||||||
Script.include('utils.js');
|
|
||||||
Script.include('shortbow.js?' + Date.now());
|
Script.include('shortbow.js?' + Date.now());
|
||||||
var TEMPLATES = SHORTBOW_ENTITIES.Entities;
|
Script.include('shortbowGameManager.js?' + Date.now());
|
||||||
print(utils.parseJSON);
|
TEMPLATES = SHORTBOW_ENTITIES.Entities;
|
||||||
print(utils.findSurfaceBelowPosition);
|
|
||||||
|
|
||||||
// Merge two objects into a new object. If a key name appears in both a and b,
|
// Merge two objects into a new object. If a key name appears in both a and b,
|
||||||
// the value in a will be used.
|
// the value in a will be used.
|
||||||
|
//
|
||||||
|
// @param {object} a
|
||||||
|
// @param {object} b
|
||||||
|
// @returns {object} The new object
|
||||||
function mergeObjects(a, b) {
|
function mergeObjects(a, b) {
|
||||||
var obj = {};
|
var obj = {};
|
||||||
for (var key in b) {
|
for (var key in b) {
|
||||||
|
@ -26,6 +22,15 @@ function mergeObjects(a, b) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spawn an entity from a template.
|
||||||
|
//
|
||||||
|
// The overrides can be used to override or add properties in the template. For instance,
|
||||||
|
// it's common to override the `position` property so that you can set the position
|
||||||
|
// of the entity to be spawned.
|
||||||
|
//
|
||||||
|
// @param {string} templateName The name of the template to spawn
|
||||||
|
// @param {object} overrides An object containing properties that will override
|
||||||
|
// any properties set in the template.
|
||||||
function spawnTemplate(templateName, overrides) {
|
function spawnTemplate(templateName, overrides) {
|
||||||
var template = getTemplate(templateName);
|
var template = getTemplate(templateName);
|
||||||
if (template === null) {
|
if (template === null) {
|
||||||
|
@ -37,6 +42,11 @@ function spawnTemplate(templateName, overrides) {
|
||||||
return Entities.addEntity(properties);
|
return Entities.addEntity(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TEMPLATES contains a dictionary of different named entity templates. An entity
|
||||||
|
// template is just a list of properties.
|
||||||
|
//
|
||||||
|
// @param name Name of the template to get
|
||||||
|
// @return {object} The matching template, or null if not found
|
||||||
function getTemplate(name) {
|
function getTemplate(name) {
|
||||||
for (var i = 0; i < TEMPLATES.length; ++i) {
|
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||||
if (TEMPLATES[i].name == name) {
|
if (TEMPLATES[i].name == name) {
|
||||||
|
@ -46,7 +56,7 @@ function getTemplate(name) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup ShortBow template data
|
// Cleanup Shortbow template data
|
||||||
var scoreboardTemplate = getTemplate('SB.Scoreboard');
|
var scoreboardTemplate = getTemplate('SB.Scoreboard');
|
||||||
Vec3.print("Scoreboard:", scoreboardTemplate.position);
|
Vec3.print("Scoreboard:", scoreboardTemplate.position);
|
||||||
for (var i = 0; i < TEMPLATES.length; ++i) {
|
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||||
|
@ -68,475 +78,8 @@ for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var GAME_STATES = {
|
|
||||||
IDLE: 0,
|
|
||||||
PLAYING: 1,
|
|
||||||
BETWEEN_WAVES: 2,
|
|
||||||
GAME_OVER: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
var COMM_CHANNEL_NAME = 'wavegame';
|
|
||||||
var entityIDs = [];
|
var entityIDs = [];
|
||||||
|
|
||||||
var baseEnemyProperties = {
|
|
||||||
name: "WG.Enemy",
|
|
||||||
type: "Box",
|
|
||||||
registrationPoint: { x: 0.5, y: 0, z: 0.5 },
|
|
||||||
dimensions: { x: 0.7, y: 0.7, z: 0.7 },
|
|
||||||
velocity: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
z: -3
|
|
||||||
},
|
|
||||||
dynamic: true,
|
|
||||||
gravity: {
|
|
||||||
x: 0,
|
|
||||||
y: -10,
|
|
||||||
z: 0,
|
|
||||||
},
|
|
||||||
restitution: 0,
|
|
||||||
friction: 0,
|
|
||||||
damping: 0,
|
|
||||||
linearDamping: 0,
|
|
||||||
lifetime: 100,
|
|
||||||
script: Script.resolvePath('enemyEntity.js'),
|
|
||||||
userData: JSON.stringify({
|
|
||||||
gameChannel: COMM_CHANNEL_NAME,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var baseEnemyProperties = {
|
|
||||||
damping: 0,
|
|
||||||
linearDamping: 0,
|
|
||||||
angularDamping: 0,
|
|
||||||
"acceleration": {
|
|
||||||
"x": 0,
|
|
||||||
"y": -9,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"angularVelocity": {
|
|
||||||
"x": -0.058330666273832321,
|
|
||||||
"y": -0.77943277359008789,
|
|
||||||
"z": -2.1163818836212158
|
|
||||||
},
|
|
||||||
"clientOnly": 0,
|
|
||||||
"collisionsWillMove": 1,
|
|
||||||
"dimensions": {
|
|
||||||
"x": 0.63503998517990112,
|
|
||||||
"y": 0.63503998517990112,
|
|
||||||
"z": 0.63503998517990112
|
|
||||||
},
|
|
||||||
"dynamic": 1,
|
|
||||||
"gravity": {
|
|
||||||
"x": 0,
|
|
||||||
"y": -15,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"id": "{ed8f7339-8bbd-4750-968e-c3ceb9d64721}",
|
|
||||||
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/marblecollection/Amber.fbx?2",
|
|
||||||
"name": "SB.Enemy",
|
|
||||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
|
||||||
"queryAACube": {
|
|
||||||
"scale": 1.0999215841293335,
|
|
||||||
"x": -0.54996079206466675,
|
|
||||||
"y": -0.54996079206466675,
|
|
||||||
"z": -0.54996079206466675
|
|
||||||
},
|
|
||||||
//"restitution": 0.99000000953674316,
|
|
||||||
"rotation": {
|
|
||||||
"w": 0.52459806203842163,
|
|
||||||
"x": 0.3808099627494812,
|
|
||||||
"y": -0.16060420870780945,
|
|
||||||
"z": 0.74430292844772339
|
|
||||||
},
|
|
||||||
"shapeType": "sphere",
|
|
||||||
"type": "Model",
|
|
||||||
velocity: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
z: -3
|
|
||||||
},
|
|
||||||
script: Script.resolvePath('enemyEntity.js'),
|
|
||||||
userData: JSON.stringify({
|
|
||||||
gameChannel: COMM_CHANNEL_NAME,
|
|
||||||
"grabbableKey": {
|
|
||||||
"grabbable": false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Encode a set of key-value pairs into a param string. Does NOT do any URL escaping.
|
|
||||||
function encodeURLParams(params) {
|
|
||||||
var paramPairs = [];
|
|
||||||
for (var key in params) {
|
|
||||||
paramPairs.push(key + "=" + params[key]);
|
|
||||||
}
|
|
||||||
return paramPairs.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendAndUpdateHighScore(highScoreDisplayID, entityID, score, wave, numPlayers) {
|
|
||||||
const URL = 'https://script.google.com/macros/s/AKfycbwbjCm9mGd1d5BzfAHmVT_XKmWyUYRkjCEqDOKm1368oM8nqWni/exec';
|
|
||||||
print("Sending high score");
|
|
||||||
|
|
||||||
const paramString = encodeURLParams({
|
|
||||||
entityID: entityID,
|
|
||||||
score: score,
|
|
||||||
wave: wave,
|
|
||||||
numPlayers: numPlayers
|
|
||||||
});
|
|
||||||
|
|
||||||
var req = new XMLHttpRequest();
|
|
||||||
req.onreadystatechange = function() {
|
|
||||||
print("ready state: ", req.readyState, req.status, req.readyState === req.DONE, req.response);
|
|
||||||
if (req.readyState === req.DONE && req.status === 200) {
|
|
||||||
print("Got response for high score: ", req.response);
|
|
||||||
var response = JSON.parse(req.responseText);
|
|
||||||
if (response.highScore !== undefined) {
|
|
||||||
Entities.editEntity(highScoreDisplayID, {
|
|
||||||
text: response.highScore
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
req.open('GET', URL + "?" + paramString);
|
|
||||||
req.timeout = 10000;
|
|
||||||
req.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The method of checking the local entity for the high score is currently disabled.
|
|
||||||
// As of 1/9/2017 we don't have support for getting nearby entity data in server entity scripts,
|
|
||||||
// so until then we have to rely on a remote source to store and retrieve that information.
|
|
||||||
function getHighScoreFromDisplay(entityID) {
|
|
||||||
var highScore = parseInt(Entities.getEntityProperties(entityID, 'text').text);
|
|
||||||
print("High score is: ", entityID, highScore);
|
|
||||||
if (highScore === NaN) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return highScore;
|
|
||||||
}
|
|
||||||
function setHighScoreOnDisplay(entityID, highScore) {
|
|
||||||
print("Setting high score to: ", entityID, highScore);
|
|
||||||
Entities.editEntity(entityID, {
|
|
||||||
text: highScore
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function GameManager(rootPosition, gatePosition, bowPositions, spawnPositions, rootEntityID, startButtonID, waveDisplayID, scoreDisplayID, livesDisplayID, highScoreDisplayID) {
|
|
||||||
this.gameState = GAME_STATES.IDLE;
|
|
||||||
|
|
||||||
this.bowPositions = bowPositions;
|
|
||||||
this.rootPosition = rootPosition;
|
|
||||||
this.spawnPositions = spawnPositions;
|
|
||||||
this.gatePosition = gatePosition;
|
|
||||||
this.rootEntityID = rootEntityID;
|
|
||||||
this.startButtonID = startButtonID;
|
|
||||||
this.waveDisplayID = waveDisplayID;
|
|
||||||
this.scoreDisplayID = scoreDisplayID;
|
|
||||||
this.livesDisplayID = livesDisplayID;
|
|
||||||
this.highScoreDisplayID = highScoreDisplayID;
|
|
||||||
|
|
||||||
// Gameplay state
|
|
||||||
this.waveNumber = 0;
|
|
||||||
this.livesLeft = 5;
|
|
||||||
this.score = 0;
|
|
||||||
this.nextWaveTimer = null;
|
|
||||||
this.spawnEnemyTimers = [];
|
|
||||||
this.enemyIDs = [];
|
|
||||||
this.entityIDs = [];
|
|
||||||
this.bowIDs = [];
|
|
||||||
|
|
||||||
sendAndUpdateHighScore(this.highScoreDisplayID, this.rootEntityID, this.score + 10, this.waveNumber, 1);
|
|
||||||
}
|
|
||||||
GameManager.prototype = {
|
|
||||||
cleanup: function() {
|
|
||||||
for (var i = 0; i < this.entityIDs.length; i++) {
|
|
||||||
Entities.deleteEntity(this.entityIDs[i]);
|
|
||||||
}
|
|
||||||
this.entityIDs = [];
|
|
||||||
for (var i = this.bowIDs.length - 1; i >= 0; i--) {
|
|
||||||
Entities.deleteEntity(this.bowIDs[i]);
|
|
||||||
}
|
|
||||||
this.bowIDs = [];
|
|
||||||
for (var i = 0; i < this.enemyIDs.length; i++) {
|
|
||||||
Entities.deleteEntity(this.enemyIDs[i]);
|
|
||||||
}
|
|
||||||
this.enemyIDs = [];
|
|
||||||
},
|
|
||||||
startGame: function() {
|
|
||||||
if (this.gameState !== GAME_STATES.IDLE) {
|
|
||||||
print("playWaveGameManager.js | Error, trying to start game when not in idle state");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Game started!!");
|
|
||||||
|
|
||||||
Entities.editEntity(this.startButtonID, { visible: false });
|
|
||||||
|
|
||||||
|
|
||||||
// Spawn bows
|
|
||||||
for (var i = 0; i < this.bowPositions.length; ++i) {
|
|
||||||
const bowPosition = this.bowPositions[i];
|
|
||||||
Vec3.print("Creating bow: ", bowPosition);
|
|
||||||
this.bowIDs.push(Entities.addEntity({
|
|
||||||
position: bowPosition,
|
|
||||||
"collisionsWillMove": 1,
|
|
||||||
"compoundShapeURL": Script.resolvePath("bow/bow_collision_hull.obj"),
|
|
||||||
"created": "2016-09-01T23:57:55Z",
|
|
||||||
"dimensions": {
|
|
||||||
"x": 0.039999999105930328,
|
|
||||||
"y": 1.2999999523162842,
|
|
||||||
"z": 0.20000000298023224
|
|
||||||
},
|
|
||||||
"dynamic": 1,
|
|
||||||
"gravity": {
|
|
||||||
"x": 0,
|
|
||||||
"y": -9.8,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"modelURL": Script.resolvePath("bow/bow-deadly.fbx"),
|
|
||||||
"name": "WG.Hifi-Bow",
|
|
||||||
"rotation": {
|
|
||||||
"w": 0.9718012809753418,
|
|
||||||
"x": 0.15440607070922852,
|
|
||||||
"y": -0.10469216108322144,
|
|
||||||
"z": -0.14418250322341919
|
|
||||||
},
|
|
||||||
"script": Script.resolvePath("bow/bow.js"),
|
|
||||||
"shapeType": "compound",
|
|
||||||
"type": "Model",
|
|
||||||
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}"
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize game state
|
|
||||||
this.waveNumber = 0;
|
|
||||||
this.setScore(0);
|
|
||||||
this.setLivesLeft(6);
|
|
||||||
|
|
||||||
this.nextWaveTimer = Script.setTimeout(this.startNextWave.bind(this), 100);
|
|
||||||
this.spawnEnemyTimers = [];
|
|
||||||
this.checkEnemyPositionsTimer = null;
|
|
||||||
this.enemyIDs = [];
|
|
||||||
|
|
||||||
// SpawnQueue is a list of enemies left to spawn. Each entry looks like:
|
|
||||||
//
|
|
||||||
// { spawnAt: 1000, position: { x: 0, y: 0, z: 0 } }
|
|
||||||
//
|
|
||||||
// where spawnAt is the number of millseconds after the start of the wave
|
|
||||||
// to spawn the enemy. The list is sorted by spawnAt, ascending.
|
|
||||||
this.spawnQueue = [];
|
|
||||||
|
|
||||||
this.gameState = GAME_STATES.PLAYING;
|
|
||||||
|
|
||||||
Audio.playSound(BEGIN_BUILDING_SOUND, {
|
|
||||||
volume: 1.0,
|
|
||||||
position: this.rootPosition
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
startNextWave: function() {
|
|
||||||
print("Starting next wave");
|
|
||||||
this.gameState = GAME_STATES.PLAYING;
|
|
||||||
this.waveNumber++;
|
|
||||||
this.enemyIDs = [];
|
|
||||||
this.spawnQueue = [];
|
|
||||||
this.spawnStartTime = Date.now();
|
|
||||||
|
|
||||||
Entities.editEntity(this.waveDisplayID, {
|
|
||||||
text: this.waveNumber
|
|
||||||
});
|
|
||||||
|
|
||||||
var numberOfEnemiesLeftToSpawn = this.waveNumber * 2;
|
|
||||||
var delayBetweenSpawns = 2000 / Math.max(1, Math.log(this.waveNumber));
|
|
||||||
var currentDelay = 2000;
|
|
||||||
|
|
||||||
print("Number of enemies:", numberOfEnemiesLeftToSpawn);
|
|
||||||
this.checkEnemyPositionsTimer = Script.setInterval(this.checkForEscapedEnemies.bind(this), 100);
|
|
||||||
|
|
||||||
for (var i = 0; i < numberOfEnemiesLeftToSpawn; ++i) {
|
|
||||||
print("Adding enemy");
|
|
||||||
var idx = Math.floor(Math.random() * this.spawnPositions.length);
|
|
||||||
this.spawnQueue.push({ spawnAt: currentDelay, position: this.spawnPositions[idx] });
|
|
||||||
currentDelay += delayBetweenSpawns;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Starting wave", this.waveNumber);
|
|
||||||
|
|
||||||
},
|
|
||||||
checkWaveComplete: function() {
|
|
||||||
if (this.gameState !== GAME_STATES.PLAYING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.spawnQueue.length <= 0 && this.enemyIDs.length === 0) {
|
|
||||||
this.gameState = GAME_STATES.BETWEEN_WAVES;
|
|
||||||
Script.setTimeout(this.startNextWave.bind(this), 5000);
|
|
||||||
|
|
||||||
Script.clearInterval(this.checkEnemyPositionsTimer);
|
|
||||||
this.checkEnemyPositionsTimer = null;
|
|
||||||
|
|
||||||
// Play after 1.5s to let other sounds finish playing
|
|
||||||
var self = this;
|
|
||||||
Script.setTimeout(function() {
|
|
||||||
Audio.playSound(WAVE_COMPLETE_SOUND, {
|
|
||||||
volume: 1.0,
|
|
||||||
position: self.rootPosition
|
|
||||||
});
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setLivesLeft: function(lives) {
|
|
||||||
lives = Math.max(0, lives);
|
|
||||||
this.livesLeft = lives;
|
|
||||||
Entities.editEntity(this.livesDisplayID, {
|
|
||||||
text: this.livesLeft
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setScore: function(score) {
|
|
||||||
this.score = score;
|
|
||||||
Entities.editEntity(this.scoreDisplayID, {
|
|
||||||
text: utils.formatNumberWithCommas(this.score)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
checkSpawnQueue: function() {
|
|
||||||
var waveElapsedTime = Date.now() - this.spawnStartTime;
|
|
||||||
while (this.spawnQueue.length > 0 && waveElapsedTime > this.spawnQueue[0].spawnAt) {
|
|
||||||
baseEnemyProperties.position = this.spawnQueue[0].position;
|
|
||||||
var entityID = Entities.addEntity(baseEnemyProperties);
|
|
||||||
this.enemyIDs.push(entityID);
|
|
||||||
this.spawnQueue.splice(0, 1);
|
|
||||||
Script.setTimeout(function() {
|
|
||||||
var velocity = Entities.getEntityProperties(entityID, 'velocity').velocity;
|
|
||||||
velocity.y += 5;
|
|
||||||
Entities.editEntity(entityID, { velocity: velocity });
|
|
||||||
|
|
||||||
}, 500 + Math.random() * 4000);
|
|
||||||
}
|
|
||||||
//print("Spawn queue size: ", this.spawnQueue.length, "Elapsed time: ", waveElapsedTime, "Number of enemies:", this.enemyIDs.length);
|
|
||||||
},
|
|
||||||
checkForEscapedEnemies: function() {
|
|
||||||
// Move this somewhere else?
|
|
||||||
this.checkSpawnQueue();
|
|
||||||
|
|
||||||
var enemiesEscaped = false;
|
|
||||||
for (var i = this.enemyIDs.length - 1; i >= 0; --i) {
|
|
||||||
var position = Entities.getEntityProperties(this.enemyIDs[i], 'position').position;
|
|
||||||
if (position === undefined) {
|
|
||||||
// If the enemy can no longer be found, assume it was hit
|
|
||||||
this.enemyIDs.splice(i, 1);
|
|
||||||
Audio.playSound(TARGET_HIT_SOUND, {
|
|
||||||
volume: 1.0,
|
|
||||||
position: this.rootPosition,
|
|
||||||
});
|
|
||||||
this.setScore(this.score + 100);
|
|
||||||
enemiesEscaped = true;
|
|
||||||
} else if (position.z < this.gatePosition.z) {
|
|
||||||
Entities.deleteEntity(this.enemyIDs[i]);
|
|
||||||
this.enemyIDs.splice(i, 1);
|
|
||||||
this.setLivesLeft(this.livesLeft - 1);
|
|
||||||
Audio.playSound(ESCAPE_SOUND, {
|
|
||||||
volume: 1.0,
|
|
||||||
position: this.rootPosition
|
|
||||||
});
|
|
||||||
enemiesEscaped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//print("LIVES LEFT: ", this.livesLeft, this.numberOfEntitiesLeftForWave);
|
|
||||||
if (this.livesLeft <= 0) {
|
|
||||||
this.endGame();
|
|
||||||
} else if (enemiesEscaped) {
|
|
||||||
this.checkWaveComplete();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
endGame: function() {
|
|
||||||
if (this.gameState !== GAME_STATES.PLAYING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
Script.setTimeout(function() {
|
|
||||||
Audio.playSound(GAME_OVER_SOUND, {
|
|
||||||
volume: 1.0,
|
|
||||||
position: self.rootPosition
|
|
||||||
});
|
|
||||||
}, 1500);
|
|
||||||
|
|
||||||
//Entities.editEntity(this.livesDisplayID, { text: "GAME OVER" });
|
|
||||||
|
|
||||||
this.gameState = GAME_STATES.GAME_OVER;
|
|
||||||
print("GAME OVER");
|
|
||||||
|
|
||||||
|
|
||||||
// Update high score
|
|
||||||
sendAndUpdateHighScore(this.highScoreDisplayID, this.rootEntityID, this.score, this.waveNumber, 1);
|
|
||||||
|
|
||||||
//var highScore = getHighScoreFromDisplay(this.highScoreDisplayID);
|
|
||||||
//if (this.score > highScore) {
|
|
||||||
// setHighScoreOnDisplay(this.highScoreDisplayID, this.score);
|
|
||||||
//} else {
|
|
||||||
// print("Score not higher", this.score, highScore);
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
Script.clearTimeout(this.nextWaveTimer);
|
|
||||||
this.nextWaveTimer = null;
|
|
||||||
for (var i = 0; i < this.spawnEnemyTimers.length; ++i) {
|
|
||||||
Script.clearTimeout(this.spawnEnemyTimers[i]);
|
|
||||||
}
|
|
||||||
this.spawnEnemyTimers = [];
|
|
||||||
|
|
||||||
Script.clearInterval(this.checkEnemyPositionsTimer);
|
|
||||||
this.checkEnemyPositionsTimer = null;
|
|
||||||
|
|
||||||
|
|
||||||
for (var i = this.bowIDs.length - 1; i >= 0; i--) {
|
|
||||||
var id = this.bowIDs[i];
|
|
||||||
print("Checking bow: ", id);
|
|
||||||
var userData = utils.parseJSON(Entities.getEntityProperties(id, 'userData').userData);
|
|
||||||
var bowIsHeld = userData.grabKey !== undefined && userData.grabKey !== undefined && userData.grabKey.refCount > 0
|
|
||||||
print("Held: ", bowIsHeld);
|
|
||||||
if (!bowIsHeld) {
|
|
||||||
Entities.deleteEntity(id);
|
|
||||||
this.bowIDs.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Script.setTimeout(function() {
|
|
||||||
Entities.editEntity(this.startButtonID, { visible: true });
|
|
||||||
this.gameState = GAME_STATES.IDLE;
|
|
||||||
}.bind(this), 3000);
|
|
||||||
|
|
||||||
for (var i = 0; i < this.enemyIDs.length; i++) {
|
|
||||||
Entities.deleteEntity(this.enemyIDs[i]);
|
|
||||||
}
|
|
||||||
this.enemyIDs = [];
|
|
||||||
},
|
|
||||||
onEnemyKilled: function(entityID, position) {
|
|
||||||
if (this.gameState !== GAME_STATES.PLAYING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var idx = this.enemyIDs.indexOf(entityID);
|
|
||||||
if (idx >= 0) {
|
|
||||||
this.enemyIDs.splice(idx, 1);
|
|
||||||
Audio.playSound(TARGET_HIT_SOUND, {
|
|
||||||
volume: 1.0,
|
|
||||||
//position: position,
|
|
||||||
position: this.rootPosition,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update score
|
|
||||||
this.setScore(this.score + 100);
|
|
||||||
print("SCORE: ", this.score);
|
|
||||||
|
|
||||||
this.checkWaveComplete();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Eventually these will need to be found at runtime by the AC
|
|
||||||
var rootPosition = null;
|
var rootPosition = null;
|
||||||
var goalPosition = null;
|
var goalPosition = null;
|
||||||
var scoreboardID = null;
|
var scoreboardID = null;
|
||||||
|
@ -545,6 +88,7 @@ var waveDisplayID = null;
|
||||||
var scoreDisplayID = null;
|
var scoreDisplayID = null;
|
||||||
var highScoreDisplayID = null;
|
var highScoreDisplayID = null;
|
||||||
var livesDisplayID = null;
|
var livesDisplayID = null;
|
||||||
|
var platformID = null;
|
||||||
function createLocalGame() {
|
function createLocalGame() {
|
||||||
rootPosition = utils.findSurfaceBelowPosition(MyAvatar.position);
|
rootPosition = utils.findSurfaceBelowPosition(MyAvatar.position);
|
||||||
rootPosition.y += 6.11;
|
rootPosition.y += 6.11;
|
||||||
|
@ -562,7 +106,7 @@ function createLocalGame() {
|
||||||
grabbableKey: {
|
grabbableKey: {
|
||||||
wantsTrigger: true
|
wantsTrigger: true
|
||||||
},
|
},
|
||||||
gameChannel: COMM_CHANNEL_NAME
|
gameChannel: 'shortbow-' + scoreboardID
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
entityIDs.push(buttonID);
|
entityIDs.push(buttonID);
|
||||||
|
@ -574,7 +118,6 @@ function createLocalGame() {
|
||||||
const ROOF_HEIGHT = 0.2;
|
const ROOF_HEIGHT = 0.2;
|
||||||
|
|
||||||
goalPosition.y += BASES_HEIGHT - ROOF_HEIGHT;
|
goalPosition.y += BASES_HEIGHT - ROOF_HEIGHT;
|
||||||
const roofPosition = goalPosition;
|
|
||||||
|
|
||||||
waveDisplayID = spawnTemplate("SB.DisplayWave", {
|
waveDisplayID = spawnTemplate("SB.DisplayWave", {
|
||||||
parentID: scoreboardID
|
parentID: scoreboardID
|
||||||
|
@ -596,90 +139,56 @@ function createLocalGame() {
|
||||||
});
|
});
|
||||||
entityIDs.push(highScoreDisplayID);
|
entityIDs.push(highScoreDisplayID);
|
||||||
|
|
||||||
|
platformID = spawnTemplate("SB.Platform", {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var gameHasBeenBuilt = false;
|
|
||||||
function buildGame() {
|
|
||||||
const BASES_SIZE = 15;
|
|
||||||
|
|
||||||
// TODO: Generate these when a button is pressed
|
|
||||||
var platformID = spawnTemplate("SB.Platform", {
|
|
||||||
parentID: scoreboardID
|
parentID: scoreboardID
|
||||||
});
|
});
|
||||||
entityIDs.push(platformID);
|
entityIDs.push(platformID);
|
||||||
|
|
||||||
var bowPositions = [];
|
spawnTemplate("SB.GateCollider", {
|
||||||
var spawnPositions = [];
|
parentID: scoreboardID,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
entityIDs.push(platformID);
|
||||||
|
|
||||||
|
Entities.editEntity(scoreboardID, {
|
||||||
|
userData: JSON.stringify({
|
||||||
|
platformID: platformID,
|
||||||
|
buttonID: buttonID,
|
||||||
|
waveDisplayID: waveDisplayID,
|
||||||
|
scoreDisplayID: scoreDisplayID,
|
||||||
|
livesDisplayID: livesDisplayID,
|
||||||
|
highScoreDisplayID: highScoreDisplayID,
|
||||||
|
}),
|
||||||
|
serverScripts: Script.resolvePath('shortbowServerEntity.js')
|
||||||
|
});
|
||||||
|
|
||||||
|
bowPositions = [];
|
||||||
|
spawnPositions = [];
|
||||||
for (var i = 0; i < TEMPLATES.length; ++i) {
|
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||||
var template = TEMPLATES[i];
|
var template = TEMPLATES[i];
|
||||||
if (template.name === "SB.BowSpawn") {
|
if (template.name === "SB.BowSpawn") {
|
||||||
bowPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
bowPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
||||||
Vec3.print("PUshing bow position", Vec3.sum(rootPosition, template.localPosition));
|
Vec3.print("Pushing bow position", Vec3.sum(rootPosition, template.localPosition));
|
||||||
} else if (template.name === "SB.EnemySpawn") {
|
} else if (template.name === "SB.EnemySpawn") {
|
||||||
spawnPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
spawnPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
||||||
Vec3.print("PUshing spawnposition", Vec3.sum(rootPosition, template.localPosition));
|
Vec3.print("Pushing spawnposition", Vec3.sum(rootPosition, template.localPosition));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gameHasBeenBuilt = true;
|
const BASES_SIZE = 15;
|
||||||
|
goalPositionFront = Vec3.sum(goalPosition, { x: 0, y: 0, z: BASES_SIZE / 2 });
|
||||||
var goalPositionFront = Vec3.sum(goalPosition, { x: 0, y: 0, z: BASES_SIZE / 2 });
|
|
||||||
return new GameManager(rootPosition, goalPositionFront, bowPositions, spawnPositions, platformID, buttonID, waveDisplayID, scoreDisplayID, livesDisplayID, highScoreDisplayID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createACGame() {
|
if (Script.isClientScript()) {
|
||||||
// TODO
|
|
||||||
throw("AC not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Setup game
|
|
||||||
var gameManager;
|
|
||||||
if (this.EntityViewer !== undefined) {
|
|
||||||
createACGame();
|
|
||||||
} else {
|
|
||||||
createLocalGame();
|
createLocalGame();
|
||||||
}
|
//var gameManager = new ShortbowGameManager(rootPosition, goalPositionFront, bowPositions, spawnPositions, scoreboardID, buttonID, waveDisplayID, scoreDisplayID, livesDisplayID, highScoreDisplayID);
|
||||||
|
|
||||||
Messages.subscribe(COMM_CHANNEL_NAME);
|
function cleanup() {
|
||||||
Messages.messageReceived.connect(function(channel, messageJSON, senderID) {
|
for (var i = 0; i < entityIDs.length; ++i) {
|
||||||
print("playWaveGame.js | Recieved: " + messageJSON + " from " + senderID);
|
Entities.deleteEntity(entityIDs[i]);
|
||||||
if (channel === COMM_CHANNEL_NAME) {
|
|
||||||
var message = utils.parseJSON(messageJSON);
|
|
||||||
if (message === undefined) {
|
|
||||||
print("playWaveGame.js | Received non-json message");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (message.type) {
|
|
||||||
case 'build-game':
|
|
||||||
break;
|
|
||||||
case 'start-game':
|
|
||||||
if (gameHasBeenBuilt) {
|
|
||||||
gameManager.startGame();
|
|
||||||
} else {
|
|
||||||
gameManager = buildGame();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'enemy-killed':
|
|
||||||
gameManager.onEnemyKilled(message.entityID, message.position);
|
|
||||||
break;
|
|
||||||
case 'enemy-escaped':
|
|
||||||
gameManager.onEnemyEscaped(message.entityID);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
print("playWaveGame.js | Ignoring unknown message type: ", message.type);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
gameManager.cleanup();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
function cleanup() {
|
Script.scriptEnding.connect(cleanup);
|
||||||
for (var i = 0; i < entityIDs.length; ++i) {
|
|
||||||
Entities.deleteEntity(entityIDs[i]);
|
|
||||||
}
|
|
||||||
gameManager.cleanup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Script.scriptEnding.connect(cleanup);
|
|
||||||
|
|
|
@ -0,0 +1,518 @@
|
||||||
|
Script.include('utils.js');
|
||||||
|
|
||||||
|
// Load the sounds that we will be using in the game so they are ready to be
|
||||||
|
// used when we need them.
|
||||||
|
var BEGIN_BUILDING_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/gameOn.wav"));
|
||||||
|
var GAME_OVER_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/gameOver.wav"));
|
||||||
|
var WAVE_COMPLETE_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/waveComplete.wav"));
|
||||||
|
var EXPLOSION_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/explosion.wav"));
|
||||||
|
var TARGET_HIT_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/targetHit.wav"));
|
||||||
|
var ESCAPE_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/escape.wav"));
|
||||||
|
|
||||||
|
// +--------+ +-----------+ +-----------------+
|
||||||
|
// | | | <------+ |
|
||||||
|
// | IDLE +------> PLAYING | | BETWEEN_WAVES |
|
||||||
|
// | | | +------> |
|
||||||
|
// +----^---+ +-----+-----+ +-----------------+
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// | +------v------+
|
||||||
|
// | | |
|
||||||
|
// +---------+ GAME_OVER |
|
||||||
|
// | |
|
||||||
|
// +-------------+
|
||||||
|
var GAME_STATES = {
|
||||||
|
IDLE: 0,
|
||||||
|
PLAYING: 1,
|
||||||
|
BETWEEN_WAVES: 2,
|
||||||
|
GAME_OVER: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Encode a set of key-value pairs into a param string. Does NOT do any URL escaping.
|
||||||
|
function encodeURLParams(params) {
|
||||||
|
var paramPairs = [];
|
||||||
|
for (var key in params) {
|
||||||
|
paramPairs.push(key + "=" + params[key]);
|
||||||
|
}
|
||||||
|
return paramPairs.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendAndUpdateHighScore(highScoreDisplayID, entityID, score, wave, numPlayers) {
|
||||||
|
const URL = 'https://script.google.com/macros/s/AKfycbwbjCm9mGd1d5BzfAHmVT_XKmWyUYRkjCEqDOKm1368oM8nqWni/exec';
|
||||||
|
print("Sending high score");
|
||||||
|
|
||||||
|
const paramString = encodeURLParams({
|
||||||
|
entityID: entityID,
|
||||||
|
score: score,
|
||||||
|
wave: wave,
|
||||||
|
numPlayers: numPlayers
|
||||||
|
});
|
||||||
|
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
print("ready state: ", req.readyState, req.status, req.readyState === req.DONE, req.response);
|
||||||
|
if (req.readyState === req.DONE && req.status === 200) {
|
||||||
|
print("Got response for high score: ", req.response);
|
||||||
|
var response = JSON.parse(req.responseText);
|
||||||
|
if (response.highScore !== undefined) {
|
||||||
|
Entities.editEntity(highScoreDisplayID, {
|
||||||
|
text: response.highScore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
req.open('GET', URL + "?" + paramString);
|
||||||
|
req.timeout = 10000;
|
||||||
|
req.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The method of checking the local entity for the high score is currently disabled.
|
||||||
|
// As of 1/9/2017 we don't have support for getting nearby entity data in server entity scripts,
|
||||||
|
// so until then we have to rely on a remote source to store and retrieve that information.
|
||||||
|
function getHighScoreFromDisplay(entityID) {
|
||||||
|
var highScore = parseInt(Entities.getEntityProperties(entityID, 'text').text);
|
||||||
|
print("High score is: ", entityID, highScore);
|
||||||
|
if (highScore === NaN) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return highScore;
|
||||||
|
}
|
||||||
|
function setHighScoreOnDisplay(entityID, highScore) {
|
||||||
|
print("Setting high score to: ", entityID, highScore);
|
||||||
|
Entities.editEntity(entityID, {
|
||||||
|
text: highScore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseEnemyProperties = {
|
||||||
|
name: "WG.Enemy",
|
||||||
|
damping: 0,
|
||||||
|
linearDamping: 0,
|
||||||
|
angularDamping: 0,
|
||||||
|
"acceleration": {
|
||||||
|
"x": 0,
|
||||||
|
"y": -9,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"angularVelocity": {
|
||||||
|
"x": -0.058330666273832321,
|
||||||
|
"y": -0.77943277359008789,
|
||||||
|
"z": -2.1163818836212158
|
||||||
|
},
|
||||||
|
"clientOnly": 0,
|
||||||
|
"collisionsWillMove": 1,
|
||||||
|
"dimensions": {
|
||||||
|
"x": 0.63503998517990112,
|
||||||
|
"y": 0.63503998517990112,
|
||||||
|
"z": 0.63503998517990112
|
||||||
|
},
|
||||||
|
"dynamic": 1,
|
||||||
|
"gravity": {
|
||||||
|
"x": 0,
|
||||||
|
"y": -15,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"id": "{ed8f7339-8bbd-4750-968e-c3ceb9d64721}",
|
||||||
|
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/marblecollection/Amber.fbx?2",
|
||||||
|
"name": "SB.Enemy",
|
||||||
|
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||||
|
"queryAACube": {
|
||||||
|
"scale": 1.0999215841293335,
|
||||||
|
"x": -0.54996079206466675,
|
||||||
|
"y": -0.54996079206466675,
|
||||||
|
"z": -0.54996079206466675
|
||||||
|
},
|
||||||
|
//"restitution": 0.99000000953674316,
|
||||||
|
"rotation": {
|
||||||
|
"w": 0.52459806203842163,
|
||||||
|
"x": 0.3808099627494812,
|
||||||
|
"y": -0.16060420870780945,
|
||||||
|
"z": 0.74430292844772339
|
||||||
|
},
|
||||||
|
"shapeType": "sphere",
|
||||||
|
"type": "Model",
|
||||||
|
velocity: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: -30
|
||||||
|
},
|
||||||
|
script: Script.resolvePath('enemyEntity.js'),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ShortbowGameManager = function(rootPosition, gatePosition, bowPositions, spawnPositions, rootEntityID, startButtonID, waveDisplayID, scoreDisplayID, livesDisplayID, highScoreDisplayID) {
|
||||||
|
this.gameState = GAME_STATES.IDLE;
|
||||||
|
|
||||||
|
this.bowPositions = bowPositions;
|
||||||
|
this.rootPosition = rootPosition;
|
||||||
|
this.spawnPositions = spawnPositions;
|
||||||
|
this.gatePosition = gatePosition;
|
||||||
|
this.rootEntityID = rootEntityID;
|
||||||
|
this.startButtonID = startButtonID;
|
||||||
|
this.waveDisplayID = waveDisplayID;
|
||||||
|
this.scoreDisplayID = scoreDisplayID;
|
||||||
|
this.livesDisplayID = livesDisplayID;
|
||||||
|
this.highScoreDisplayID = highScoreDisplayID;
|
||||||
|
print(waveDisplayID, scoreDisplayID, livesDisplayID);
|
||||||
|
|
||||||
|
// Gameplay state
|
||||||
|
this.waveNumber = 0;
|
||||||
|
this.livesLeft = 5;
|
||||||
|
this.score = 0;
|
||||||
|
this.nextWaveTimer = null;
|
||||||
|
this.spawnEnemyTimers = [];
|
||||||
|
this.enemyIDs = [];
|
||||||
|
this.entityIDs = [];
|
||||||
|
this.bowIDs = [];
|
||||||
|
|
||||||
|
this.commChannelName = "shortbow-" + this.rootEntityID;
|
||||||
|
print("Listening on: ", this.commChannelName);
|
||||||
|
|
||||||
|
Messages.subscribe(this.commChannelName);
|
||||||
|
Messages.messageReceived.connect(this, this.onReceivedMessage);
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
sendAndUpdateHighScore(this.highScoreDisplayID, this.rootEntityID, this.score, this.waveNumber, 1);
|
||||||
|
}
|
||||||
|
ShortbowGameManager.prototype = {
|
||||||
|
reset: function() {
|
||||||
|
Entities.editEntity(this.startButtonID, {
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cleanup: function() {
|
||||||
|
Messages.unsubscribe(this.commChannelName);
|
||||||
|
Messages.messageReceived.disconnect(this, this.onReceivedMessage);
|
||||||
|
|
||||||
|
for (var i = 0; i < this.entityIDs.length; i++) {
|
||||||
|
Entities.deleteEntity(this.entityIDs[i]);
|
||||||
|
}
|
||||||
|
this.entityIDs = [];
|
||||||
|
for (var i = this.bowIDs.length - 1; i >= 0; i--) {
|
||||||
|
Entities.deleteEntity(this.bowIDs[i]);
|
||||||
|
}
|
||||||
|
this.bowIDs = [];
|
||||||
|
for (var i = 0; i < this.enemyIDs.length; i++) {
|
||||||
|
Entities.deleteEntity(this.enemyIDs[i]);
|
||||||
|
}
|
||||||
|
this.enemyIDs = [];
|
||||||
|
},
|
||||||
|
onReceivedMessage: function(channel, messageJSON, senderID) {
|
||||||
|
print("playWaveGame.js | Recieved: " + messageJSON + " from " + senderID, channel, this.commChannelName);
|
||||||
|
|
||||||
|
if (channel === this.commChannelName) {
|
||||||
|
var message = utils.parseJSON(messageJSON);
|
||||||
|
if (message === undefined) {
|
||||||
|
print("playWaveGame.js | Received non-json message");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (message.type) {
|
||||||
|
case 'start-game':
|
||||||
|
this.startGame();
|
||||||
|
break;
|
||||||
|
case 'enemy-killed':
|
||||||
|
this.onEnemyKilled(message.entityID, message.position);
|
||||||
|
break;
|
||||||
|
case 'enemy-escaped':
|
||||||
|
this.onEnemyEscaped(message.entityID);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print("playWaveGame.js | Ignoring unknown message type: ", message.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startGame: function() {
|
||||||
|
if (this.gameState !== GAME_STATES.IDLE) {
|
||||||
|
print("playWaveGameManager.js | Error, trying to start game when not in idle state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Game started!!");
|
||||||
|
|
||||||
|
Entities.editEntity(this.startButtonID, { visible: false });
|
||||||
|
|
||||||
|
|
||||||
|
// Spawn bows
|
||||||
|
for (var i = 0; i < this.bowPositions.length; ++i) {
|
||||||
|
const bowPosition = this.bowPositions[i];
|
||||||
|
Vec3.print("Creating bow: ", bowPosition);
|
||||||
|
this.bowIDs.push(Entities.addEntity({
|
||||||
|
position: bowPosition,
|
||||||
|
"collisionsWillMove": 1,
|
||||||
|
"compoundShapeURL": Script.resolvePath("bow/bow_collision_hull.obj"),
|
||||||
|
"created": "2016-09-01T23:57:55Z",
|
||||||
|
"dimensions": {
|
||||||
|
"x": 0.039999999105930328,
|
||||||
|
"y": 1.2999999523162842,
|
||||||
|
"z": 0.20000000298023224
|
||||||
|
},
|
||||||
|
"dynamic": 1,
|
||||||
|
"gravity": {
|
||||||
|
"x": 0,
|
||||||
|
"y": -9.8,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"modelURL": Script.resolvePath("bow/bow-deadly.fbx"),
|
||||||
|
"name": "WG.Hifi-Bow",
|
||||||
|
"rotation": {
|
||||||
|
"w": 0.9718012809753418,
|
||||||
|
"x": 0.15440607070922852,
|
||||||
|
"y": -0.10469216108322144,
|
||||||
|
"z": -0.14418250322341919
|
||||||
|
},
|
||||||
|
"script": Script.resolvePath("bow/bow.js"),
|
||||||
|
"shapeType": "compound",
|
||||||
|
"type": "Model",
|
||||||
|
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize game state
|
||||||
|
this.waveNumber = 0;
|
||||||
|
this.setScore(0);
|
||||||
|
this.setLivesLeft(60);
|
||||||
|
|
||||||
|
this.nextWaveTimer = Script.setTimeout(this.startNextWave.bind(this), 100);
|
||||||
|
this.spawnEnemyTimers = [];
|
||||||
|
this.checkEnemyPositionsTimer = null;
|
||||||
|
this.enemyIDs = [];
|
||||||
|
|
||||||
|
// SpawnQueue is a list of enemies left to spawn. Each entry looks like:
|
||||||
|
//
|
||||||
|
// { spawnAt: 1000, position: { x: 0, y: 0, z: 0 } }
|
||||||
|
//
|
||||||
|
// where spawnAt is the number of millseconds after the start of the wave
|
||||||
|
// to spawn the enemy. The list is sorted by spawnAt, ascending.
|
||||||
|
this.spawnQueue = [];
|
||||||
|
|
||||||
|
this.gameState = GAME_STATES.PLAYING;
|
||||||
|
|
||||||
|
Audio.playSound(BEGIN_BUILDING_SOUND, {
|
||||||
|
volume: 1.0,
|
||||||
|
position: this.rootPosition
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
startNextWave: function() {
|
||||||
|
print("Starting next wave");
|
||||||
|
this.gameState = GAME_STATES.PLAYING;
|
||||||
|
this.waveNumber++;
|
||||||
|
this.enemyIDs = [];
|
||||||
|
this.spawnQueue = [];
|
||||||
|
this.spawnStartTime = Date.now();
|
||||||
|
|
||||||
|
print("Editing wave number 500", this.waveDisplayID, this.waveNumber);
|
||||||
|
Entities.editEntity('{1b630f6a-a6e4-4bca-be5e-d8d2709bb278}', {
|
||||||
|
text: '500',//this.waveNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
var numberOfEnemiesLeftToSpawn = this.waveNumber * 2;
|
||||||
|
var delayBetweenSpawns = 2000 / Math.max(1, Math.log(this.waveNumber));
|
||||||
|
var currentDelay = 2000;
|
||||||
|
|
||||||
|
print("Number of enemies:", numberOfEnemiesLeftToSpawn);
|
||||||
|
this.checkEnemyPositionsTimer = Script.setInterval(this.checkForEscapedEnemies.bind(this), 100);
|
||||||
|
|
||||||
|
for (var i = 0; i < numberOfEnemiesLeftToSpawn; ++i) {
|
||||||
|
print("Adding enemy");
|
||||||
|
var idx = Math.floor(Math.random() * this.spawnPositions.length);
|
||||||
|
this.spawnQueue.push({ spawnAt: currentDelay, position: this.spawnPositions[idx] });
|
||||||
|
currentDelay += delayBetweenSpawns;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Starting wave", this.waveNumber);
|
||||||
|
|
||||||
|
},
|
||||||
|
checkWaveComplete: function() {
|
||||||
|
if (this.gameState !== GAME_STATES.PLAYING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.spawnQueue.length <= 0 && this.enemyIDs.length === 0) {
|
||||||
|
this.gameState = GAME_STATES.BETWEEN_WAVES;
|
||||||
|
Script.setTimeout(this.startNextWave.bind(this), 5000);
|
||||||
|
|
||||||
|
Script.clearInterval(this.checkEnemyPositionsTimer);
|
||||||
|
this.checkEnemyPositionsTimer = null;
|
||||||
|
|
||||||
|
// Play after 1.5s to let other sounds finish playing
|
||||||
|
var self = this;
|
||||||
|
Script.setTimeout(function() {
|
||||||
|
Audio.playSound(WAVE_COMPLETE_SOUND, {
|
||||||
|
volume: 1.0,
|
||||||
|
position: self.rootPosition
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setLivesLeft: function(lives) {
|
||||||
|
lives = Math.max(0, lives);
|
||||||
|
this.livesLeft = lives;
|
||||||
|
Entities.editEntity(this.livesDisplayID, {
|
||||||
|
text: this.livesLeft
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setScore: function(score) {
|
||||||
|
this.score = score;
|
||||||
|
Entities.editEntity(this.scoreDisplayID, {
|
||||||
|
text: utils.formatNumberWithCommas(this.score)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkSpawnQueue: function() {
|
||||||
|
var waveElapsedTime = Date.now() - this.spawnStartTime;
|
||||||
|
while (this.spawnQueue.length > 0 && waveElapsedTime > this.spawnQueue[0].spawnAt) {
|
||||||
|
baseEnemyProperties.position = this.spawnQueue[0].position;
|
||||||
|
|
||||||
|
baseEnemyProperties.userData = JSON.stringify({
|
||||||
|
gameChannel: this.commChannelName,
|
||||||
|
grabbableKey: {
|
||||||
|
grabbable: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var entityID = Entities.addEntity(baseEnemyProperties);
|
||||||
|
this.enemyIDs.push(entityID);
|
||||||
|
this.spawnQueue.splice(0, 1);
|
||||||
|
Script.setTimeout(function() {
|
||||||
|
var velocity = Entities.getEntityProperties(entityID, 'velocity').velocity;
|
||||||
|
velocity.y += 5;
|
||||||
|
Entities.editEntity(entityID, { velocity: velocity });
|
||||||
|
|
||||||
|
}, 500 + Math.random() * 4000);
|
||||||
|
}
|
||||||
|
//print("Spawn queue size: ", this.spawnQueue.length, "Elapsed time: ", waveElapsedTime, "Number of enemies:", this.enemyIDs.length);
|
||||||
|
},
|
||||||
|
checkForEscapedEnemies: function() {
|
||||||
|
// Move this somewhere else?
|
||||||
|
this.checkSpawnQueue();
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
var enemiesEscaped = false;
|
||||||
|
for (var i = this.enemyIDs.length - 1; i >= 0; --i) {
|
||||||
|
var position = Entities.getEntityProperties(this.enemyIDs[i], 'position').position;
|
||||||
|
if (position === undefined) {
|
||||||
|
// If the enemy can no longer be found, assume it was hit
|
||||||
|
//this.enemyIDs.splice(i, 1);
|
||||||
|
//Audio.playSound(TARGET_HIT_SOUND, {
|
||||||
|
// volume: 1.0,
|
||||||
|
// position: this.rootPosition,
|
||||||
|
//});
|
||||||
|
//this.setScore(this.score + 100);
|
||||||
|
//enemiesEscaped = true;
|
||||||
|
} else if (position.z < this.gatePosition.z) {
|
||||||
|
Entities.deleteEntity(this.enemyIDs[i]);
|
||||||
|
this.enemyIDs.splice(i, 1);
|
||||||
|
this.setLivesLeft(this.livesLeft - 1);
|
||||||
|
Audio.playSound(ESCAPE_SOUND, {
|
||||||
|
volume: 1.0,
|
||||||
|
position: this.rootPosition
|
||||||
|
});
|
||||||
|
enemiesEscaped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//print("LIVES LEFT: ", this.livesLeft, this.numberOfEntitiesLeftForWave);
|
||||||
|
if (this.livesLeft <= 0) {
|
||||||
|
this.endGame();
|
||||||
|
} else if (enemiesEscaped) {
|
||||||
|
this.checkWaveComplete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endGame: function() {
|
||||||
|
if (this.gameState !== GAME_STATES.PLAYING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
Script.setTimeout(function() {
|
||||||
|
Audio.playSound(GAME_OVER_SOUND, {
|
||||||
|
volume: 1.0,
|
||||||
|
position: self.rootPosition
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
//Entities.editEntity(this.livesDisplayID, { text: "GAME OVER" });
|
||||||
|
this.gameState = GAME_STATES.GAME_OVER;
|
||||||
|
print("GAME OVER");
|
||||||
|
|
||||||
|
// Update high score
|
||||||
|
sendAndUpdateHighScore(this.highScoreDisplayID, this.rootEntityID, this.score, this.waveNumber, 1);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
Script.clearTimeout(this.nextWaveTimer);
|
||||||
|
this.nextWaveTimer = null;
|
||||||
|
for (var i = 0; i < this.spawnEnemyTimers.length; ++i) {
|
||||||
|
Script.clearTimeout(this.spawnEnemyTimers[i]);
|
||||||
|
}
|
||||||
|
this.spawnEnemyTimers = [];
|
||||||
|
|
||||||
|
Script.clearInterval(this.checkEnemyPositionsTimer);
|
||||||
|
this.checkEnemyPositionsTimer = null;
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = this.bowIDs.length - 1; i >= 0; i--) {
|
||||||
|
var id = this.bowIDs[i];
|
||||||
|
print("Checking bow: ", id);
|
||||||
|
var userData = utils.parseJSON(Entities.getEntityProperties(id, 'userData').userData);
|
||||||
|
var bowIsHeld = userData.grabKey !== undefined && userData.grabKey !== undefined && userData.grabKey.refCount > 0
|
||||||
|
print("Held: ", bowIsHeld);
|
||||||
|
if (!bowIsHeld) {
|
||||||
|
Entities.deleteEntity(id);
|
||||||
|
this.bowIDs.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.setTimeout(function() {
|
||||||
|
Entities.editEntity(this.startButtonID, { visible: true });
|
||||||
|
this.gameState = GAME_STATES.IDLE;
|
||||||
|
}.bind(this), 3000);
|
||||||
|
|
||||||
|
for (var i = 0; i < this.enemyIDs.length; i++) {
|
||||||
|
Entities.deleteEntity(this.enemyIDs[i]);
|
||||||
|
}
|
||||||
|
this.enemyIDs = [];
|
||||||
|
},
|
||||||
|
onEnemyKilled: function(entityID, position) {
|
||||||
|
if (this.gameState !== GAME_STATES.PLAYING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = this.enemyIDs.indexOf(entityID);
|
||||||
|
if (idx >= 0) {
|
||||||
|
this.enemyIDs.splice(idx, 1);
|
||||||
|
Audio.playSound(TARGET_HIT_SOUND, {
|
||||||
|
volume: 1.0,
|
||||||
|
//position: position,
|
||||||
|
position: this.rootPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update score
|
||||||
|
this.setScore(this.score + 100);
|
||||||
|
print("SCORE: ", this.score);
|
||||||
|
|
||||||
|
this.checkWaveComplete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEnemyEscaped: function(entityID, position) {
|
||||||
|
var enemiesEscaped = false;
|
||||||
|
for (var i = this.enemyIDs.length - 1; i >= 0; --i) {
|
||||||
|
if (this.enemyIDs[i] == entityID) {
|
||||||
|
Entities.deleteEntity(this.enemyIDs[i]);
|
||||||
|
this.enemyIDs.splice(i, 1);
|
||||||
|
this.setLivesLeft(this.livesLeft - 1);
|
||||||
|
Audio.playSound(ESCAPE_SOUND, {
|
||||||
|
volume: 1.0,
|
||||||
|
position: this.rootPosition
|
||||||
|
});
|
||||||
|
enemiesEscaped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//print("LIVES LEFT: ", this.livesLeft, this.numberOfEntitiesLeftForWave);
|
||||||
|
if (this.livesLeft <= 0) {
|
||||||
|
this.endGame();
|
||||||
|
} else if (enemiesEscaped) {
|
||||||
|
this.checkWaveComplete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
(function() {
|
||||||
|
Script.include('utils.js?' + Date.now());
|
||||||
|
Script.include('playWaveGame.js?' + Date.now());
|
||||||
|
Script.include('shortbowGameManager.js?' + Date.now());
|
||||||
|
|
||||||
|
this.entityID = null;
|
||||||
|
var gameManager = null;
|
||||||
|
this.preload = function(entityID) {
|
||||||
|
this.entityID = entityID;
|
||||||
|
|
||||||
|
var props = Entities.getEntityProperties(entityID, ['position', 'userData']);
|
||||||
|
var data = utils.parseJSON(props.userData);
|
||||||
|
if (data === undefined) {
|
||||||
|
print("Error parsing shortbow entity userData, returning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootPosition = props.position;
|
||||||
|
|
||||||
|
// Generate goal that the enemies try to get to
|
||||||
|
goalPosition = Vec3.sum(rootPosition, { x: 0, y: -10, z: -20 });
|
||||||
|
const BASES_HEIGHT = 16;
|
||||||
|
const ROOF_HEIGHT = 0.2;
|
||||||
|
|
||||||
|
goalPosition.y += BASES_HEIGHT - ROOF_HEIGHT;
|
||||||
|
|
||||||
|
var platformID = data.platformID;
|
||||||
|
var buttonID = data.buttonID;
|
||||||
|
var waveDisplayID = data.waveDisplayID;
|
||||||
|
var livesDisplayID = data.livesDisplayID;
|
||||||
|
var scoreDisplayID = data.scoreDisplayID;
|
||||||
|
var highScoreDisplayID = data.highScoreDisplayID;
|
||||||
|
|
||||||
|
const BASES_SIZE = 15;
|
||||||
|
var goalPositionFront = Vec3.sum(goalPosition, { x: 0, y: 0, z: BASES_SIZE / 2 });
|
||||||
|
|
||||||
|
var bowPositions = [];
|
||||||
|
var spawnPositions = [];
|
||||||
|
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||||
|
var template = TEMPLATES[i];
|
||||||
|
if (template.name === "SB.BowSpawn") {
|
||||||
|
bowPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
||||||
|
Vec3.print("Pushing bow position", Vec3.sum(rootPosition, template.localPosition));
|
||||||
|
} else if (template.name === "SB.EnemySpawn") {
|
||||||
|
spawnPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
||||||
|
Vec3.print("Pushing spawnposition", Vec3.sum(rootPosition, template.localPosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameManager = new ShortbowGameManager(rootPosition, goalPositionFront, bowPositions, spawnPositions, this.entityID, buttonID, waveDisplayID, scoreDisplayID, livesDisplayID, highScoreDisplayID);
|
||||||
|
};
|
||||||
|
this.unload = function() {
|
||||||
|
if (gameManager) {
|
||||||
|
gameManager.cleanup();
|
||||||
|
gameManager = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -5,7 +5,9 @@
|
||||||
};
|
};
|
||||||
StartButton.prototype = {
|
StartButton.prototype = {
|
||||||
preload: function(entityID) {
|
preload: function(entityID) {
|
||||||
|
print("Preloading start button");
|
||||||
this.entityID = entityID;
|
this.entityID = entityID;
|
||||||
|
this.commChannel = "shortbow-" + Entities.getEntityProperties(entityID, 'parentID').parentID;
|
||||||
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
|
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
|
||||||
},
|
},
|
||||||
signalAC: function() {
|
signalAC: function() {
|
||||||
|
|
Loading…
Reference in a new issue