mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 23:56:29 +02:00
621 lines
22 KiB
JavaScript
621 lines
22 KiB
JavaScript
//
|
|
// Created by Ryan Huffman on 1/10/2017
|
|
// Copyright 2017 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
|
|
//
|
|
|
|
/* globals ShortbowGameManager:true, utils */
|
|
|
|
Script.include('utils.js');
|
|
|
|
// +--------+ +-----------+ +-----------------+
|
|
// | | | |<-----+ |
|
|
// | IDLE +----->| PLAYING | | BETWEEN_WAVES |
|
|
// | | | +----->| |
|
|
// +--------+ +-----+-----+ +-----------------+
|
|
// ^ |
|
|
// | v
|
|
// | +-------------+
|
|
// | | |
|
|
// +---------+ GAME_OVER |
|
|
// | |
|
|
// +-------------+
|
|
var GAME_STATES = {
|
|
IDLE: 0,
|
|
PLAYING: 1,
|
|
BETWEEN_WAVES: 2,
|
|
GAME_OVER: 3
|
|
};
|
|
|
|
// 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("sounds/gameOn.wav"));
|
|
var GAME_OVER_SOUND = SoundCache.getSound(Script.resolvePath("sounds/gameOver.wav"));
|
|
var WAVE_COMPLETE_SOUND = SoundCache.getSound(Script.resolvePath("sounds/waveComplete.wav"));
|
|
var EXPLOSION_SOUND = SoundCache.getSound(Script.resolvePath("sounds/explosion.wav"));
|
|
var TARGET_HIT_SOUND = SoundCache.getSound(Script.resolvePath("sounds/targetHit.wav"));
|
|
var ESCAPE_SOUND = SoundCache.getSound(Script.resolvePath("sounds/escape.wav"));
|
|
|
|
const STARTING_NUMBER_OF_LIVES = 6;
|
|
const ENEMIES_PER_WAVE_MULTIPLIER = 2;
|
|
const POINTS_PER_KILL = 100;
|
|
const ENEMY_SPEED = 3.0;
|
|
|
|
// 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(entityID, score, wave, numPlayers, onResposeReceived) {
|
|
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 request = new XMLHttpRequest();
|
|
request.onreadystatechange = function() {
|
|
print("ready state: ", request.readyState, request.status, request.readyState === request.DONE, request.response);
|
|
if (request.readyState === request.DONE && request.status === 200) {
|
|
print("Got response for high score: ", request.response);
|
|
var response = JSON.parse(request.responseText);
|
|
if (response.highScore !== undefined) {
|
|
onResposeReceived(response.highScore);
|
|
}
|
|
}
|
|
};
|
|
request.open('GET', URL + "?" + paramString);
|
|
request.timeout = 10000;
|
|
request.send();
|
|
}
|
|
|
|
function findChildrenWithName(parentID, name) {
|
|
var childrenIDs = Entities.getChildrenIDs(parentID);
|
|
var matchingIDs = [];
|
|
for (var i = 0; i < childrenIDs.length; ++i) {
|
|
var id = childrenIDs[i];
|
|
var childName = Entities.getEntityProperties(id, 'name').name;
|
|
if (childName === name) {
|
|
matchingIDs.push(id);
|
|
}
|
|
}
|
|
return matchingIDs;
|
|
}
|
|
|
|
function getPropertiesForEntities(entityIDs, desiredProperties) {
|
|
var properties = [];
|
|
for (var i = 0; i < entityIDs.length; ++i) {
|
|
properties.push(Entities.getEntityProperties(entityIDs[i], desiredProperties));
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
|
|
var baseEnemyProperties = {
|
|
"name": "SB.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
|
|
},
|
|
"lifetime": 30,
|
|
"id": "{ed8f7339-8bbd-4750-968e-c3ceb9d64721}",
|
|
"modelURL": Script.resolvePath("models/Amber.baked.fbx"),
|
|
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
|
"queryAACube": {
|
|
"scale": 1.0999215841293335,
|
|
"x": -0.54996079206466675,
|
|
"y": -0.54996079206466675,
|
|
"z": -0.54996079206466675
|
|
},
|
|
"shapeType": "sphere",
|
|
"type": "Model",
|
|
"script": Script.resolvePath('enemyClientEntity.js'),
|
|
"serverScripts": Script.resolvePath('enemyServerEntity.js')
|
|
};
|
|
|
|
function searchForChildren(parentID, names, callback, timeoutMs) {
|
|
// Map from name to entity ID for the children that have been found
|
|
var foundEntities = {};
|
|
for (var i = 0; i < names.length; ++i) {
|
|
foundEntities[names[i]] = null;
|
|
}
|
|
|
|
const CHECK_EVERY_MS = 500;
|
|
const maxChecks = Math.ceil(timeoutMs / CHECK_EVERY_MS);
|
|
|
|
var check = 0;
|
|
var intervalID = Script.setInterval(function() {
|
|
check++;
|
|
|
|
var childrenIDs = Entities.getChildrenIDs(parentID);
|
|
print("\tNumber of children:", childrenIDs.length);
|
|
|
|
for (var i = 0; i < childrenIDs.length; ++i) {
|
|
print("\t\t" + i + ".", Entities.getEntityProperties(childrenIDs[i]).name);
|
|
var id = childrenIDs[i];
|
|
var name = Entities.getEntityProperties(id, 'name').name;
|
|
var idx = names.indexOf(name);
|
|
if (idx > -1) {
|
|
foundEntities[name] = id;
|
|
print(name, id);
|
|
names.splice(idx, 1);
|
|
}
|
|
}
|
|
|
|
if (names.length === 0 || check >= maxChecks) {
|
|
Script.clearInterval(intervalID);
|
|
callback(foundEntities);
|
|
}
|
|
}, CHECK_EVERY_MS);
|
|
}
|
|
|
|
ShortbowGameManager = function(rootEntityID, bowPositions, spawnPositions) {
|
|
print("Starting game manager");
|
|
var self = this;
|
|
|
|
this.gameState = GAME_STATES.IDLE;
|
|
|
|
this.rootEntityID = rootEntityID;
|
|
this.bowPositions = bowPositions;
|
|
this.rootPosition = null;
|
|
this.spawnPositions = spawnPositions;
|
|
|
|
this.loadedChildren = false;
|
|
|
|
const START_BUTTON_NAME = 'SB.StartButton';
|
|
const WAVE_DISPLAY_NAME = 'SB.DisplayWave';
|
|
const SCORE_DISPLAY_NAME = 'SB.DisplayScore';
|
|
const LIVES_DISPLAY_NAME = 'SB.DisplayLives';
|
|
const HIGH_SCORE_DISPLAY_NAME = 'SB.DisplayHighScore';
|
|
|
|
const SEARCH_FOR_CHILDREN_TIMEOUT = 5000;
|
|
|
|
searchForChildren(rootEntityID, [
|
|
START_BUTTON_NAME,
|
|
WAVE_DISPLAY_NAME,
|
|
SCORE_DISPLAY_NAME,
|
|
LIVES_DISPLAY_NAME,
|
|
HIGH_SCORE_DISPLAY_NAME
|
|
], function(children) {
|
|
self.loadedChildren = true;
|
|
self.startButtonID = children[START_BUTTON_NAME];
|
|
self.waveDisplayID = children[WAVE_DISPLAY_NAME];
|
|
self.scoreDisplayID = children[SCORE_DISPLAY_NAME];
|
|
self.livesDisplayID = children[LIVES_DISPLAY_NAME];
|
|
self.highScoreDisplayID = children[HIGH_SCORE_DISPLAY_NAME];
|
|
|
|
sendAndUpdateHighScore(self.rootEntityID, self.score, self.waveNumber, 1, self.setHighScore.bind(self));
|
|
|
|
self.reset();
|
|
}, SEARCH_FOR_CHILDREN_TIMEOUT);
|
|
|
|
// Gameplay state
|
|
this.waveNumber = 0;
|
|
this.livesLeft = STARTING_NUMBER_OF_LIVES;
|
|
this.score = 0;
|
|
this.nextWaveTimer = null;
|
|
this.spawnEnemyTimers = [];
|
|
this.remainingEnemies = [];
|
|
this.bowIDs = [];
|
|
|
|
this.startButtonChannelName = 'button-' + this.rootEntityID;
|
|
|
|
// Entity client and server scripts will send messages to this channel
|
|
this.commChannelName = "shortbow-" + this.rootEntityID;
|
|
Messages.subscribe(this.commChannelName);
|
|
Messages.messageReceived.connect(this, this.onReceivedMessage);
|
|
print("Listening on: ", this.commChannelName);
|
|
Messages.sendMessage(this.commChannelName, 'hi');
|
|
};
|
|
ShortbowGameManager.prototype = {
|
|
reset: function() {
|
|
Entities.editEntity(this.startButtonID, { visible: true });
|
|
},
|
|
cleanup: function() {
|
|
Messages.unsubscribe(this.commChannelName);
|
|
Messages.messageReceived.disconnect(this, this.onReceivedMessage);
|
|
|
|
if (this.checkEnemiesTimer) {
|
|
Script.clearInterval(this.checkEnemiesTimer);
|
|
this.checkEnemiesTimer = null;
|
|
}
|
|
|
|
for (var i = this.bowIDs.length - 1; i >= 0; i--) {
|
|
Entities.deleteEntity(this.bowIDs[i]);
|
|
}
|
|
this.bowIDs = [];
|
|
for (i = 0; i < this.remainingEnemies.length; i++) {
|
|
Entities.deleteEntity(this.remainingEnemies[i].id);
|
|
}
|
|
this.remainingEnemies = [];
|
|
|
|
this.gameState = GAME_STATES.IDLE;
|
|
},
|
|
startGame: function() {
|
|
if (this.gameState !== GAME_STATES.IDLE) {
|
|
print("shortbowGameManagerManager.js | Error, trying to start game when not in idle state");
|
|
return;
|
|
}
|
|
|
|
if (this.loadedChildren === false) {
|
|
print('shortbowGameManager.js | Children have not loaded, not allowing game to start');
|
|
return;
|
|
}
|
|
|
|
print("Game started!!");
|
|
|
|
this.rootPosition = Entities.getEntityProperties(this.rootEntityID, 'position').position;
|
|
|
|
Entities.editEntity(this.startButtonID, { visible: false });
|
|
|
|
// Spawn bows
|
|
var bowSpawnEntityIDs = findChildrenWithName(this.rootEntityID, 'SB.BowSpawn');
|
|
var bowSpawnProperties = getPropertiesForEntities(bowSpawnEntityIDs, ['position', 'rotation']);
|
|
for (var i = 0; i < bowSpawnProperties.length; ++i) {
|
|
const props = bowSpawnProperties[i];
|
|
Vec3.print("Creating bow: " + i, props.position);
|
|
this.bowIDs.push(Entities.addEntity({
|
|
"position": props.position,
|
|
"rotation": props.rotation,
|
|
"collisionsWillMove": 1,
|
|
"compoundShapeURL": Script.resolvePath("bow/models/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/models/bow-deadly.baked.fbx"),
|
|
"name": "WG.Hifi-Bow",
|
|
"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(STARTING_NUMBER_OF_LIVES);
|
|
|
|
this.nextWaveTimer = Script.setTimeout(this.startNextWave.bind(this), 100);
|
|
this.spawnEnemyTimers = [];
|
|
this.checkEnemiesTimer = null;
|
|
this.remainingEnemies = [];
|
|
|
|
// 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.BETWEEN_WAVES;
|
|
|
|
Audio.playSound(BEGIN_BUILDING_SOUND, {
|
|
volume: 1.0,
|
|
position: this.rootPosition
|
|
});
|
|
},
|
|
startNextWave: function() {
|
|
if (this.gameState !== GAME_STATES.BETWEEN_WAVES) {
|
|
return;
|
|
}
|
|
|
|
print("Starting next wave");
|
|
this.gameState = GAME_STATES.PLAYING;
|
|
this.waveNumber++;
|
|
this.remainingEnemies= [];
|
|
this.spawnQueue = [];
|
|
this.spawnStartTime = Date.now();
|
|
|
|
Entities.editEntity(this.waveDisplayID, { text: this.waveNumber});
|
|
|
|
var numberOfEnemiesLeftToSpawn = this.waveNumber * ENEMIES_PER_WAVE_MULTIPLIER;
|
|
var delayBetweenSpawns = 2000 / Math.max(1, Math.log(this.waveNumber));
|
|
var currentDelay = 2000;
|
|
|
|
print("Number of enemies:", numberOfEnemiesLeftToSpawn);
|
|
this.checkEnemiesTimer = Script.setInterval(this.checkEnemies.bind(this), 100);
|
|
|
|
var enemySpawnEntityIDs = findChildrenWithName(this.rootEntityID, 'SB.EnemySpawn');
|
|
var enemySpawnProperties = getPropertiesForEntities(enemySpawnEntityIDs, ['position', 'rotation']);
|
|
|
|
for (var i = 0; i < numberOfEnemiesLeftToSpawn; ++i) {
|
|
print("Adding enemy");
|
|
var idx = Math.floor(Math.random() * enemySpawnProperties.length);
|
|
var props = enemySpawnProperties[idx];
|
|
this.spawnQueue.push({
|
|
spawnAt: currentDelay,
|
|
position: props.position,
|
|
rotation: props.rotation,
|
|
velocity: Vec3.multiply(ENEMY_SPEED, Quat.getFront(props.rotation))
|
|
|
|
});
|
|
currentDelay += delayBetweenSpawns;
|
|
}
|
|
|
|
print("Starting wave", this.waveNumber);
|
|
|
|
},
|
|
checkWaveComplete: function() {
|
|
if (this.gameState !== GAME_STATES.PLAYING) {
|
|
return;
|
|
}
|
|
|
|
if (this.spawnQueue.length === 0 && this.remainingEnemies.length === 0) {
|
|
this.gameState = GAME_STATES.BETWEEN_WAVES;
|
|
Script.setTimeout(this.startNextWave.bind(this), 5000);
|
|
|
|
Script.clearInterval(this.checkEnemiesTimer);
|
|
this.checkEnemiesTimer = 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);
|
|
}
|
|
},
|
|
setHighScore: function(highScore) {
|
|
print("Setting high score to:", this.highScoreDisplayID, highScore);
|
|
Entities.editEntity(this.highScoreDisplayID, { text: highScore });
|
|
},
|
|
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: this.score });
|
|
},
|
|
checkEnemies: function() {
|
|
if (this.gameState !== GAME_STATES.PLAYING) {
|
|
return;
|
|
}
|
|
|
|
// Check the spawn queueu to see if there are any enemies that need to
|
|
// be spawned
|
|
var waveElapsedTime = Date.now() - this.spawnStartTime;
|
|
while (this.spawnQueue.length > 0 && waveElapsedTime > this.spawnQueue[0].spawnAt) {
|
|
baseEnemyProperties.position = this.spawnQueue[0].position;
|
|
baseEnemyProperties.rotation = this.spawnQueue[0].rotation;
|
|
baseEnemyProperties.velocity= this.spawnQueue[0].velocity;
|
|
|
|
baseEnemyProperties.userData = JSON.stringify({
|
|
gameChannel: this.commChannelName,
|
|
grabbableKey: {
|
|
grabbable: false
|
|
}
|
|
});
|
|
|
|
var entityID = Entities.addEntity(baseEnemyProperties);
|
|
this.remainingEnemies.push({
|
|
id: entityID,
|
|
lastKnownPosition: baseEnemyProperties.position,
|
|
lastHeartbeat: Date.now()
|
|
});
|
|
this.spawnQueue.splice(0, 1);
|
|
Script.setTimeout(function() {
|
|
const JUMP_SPEED = 5.0;
|
|
var velocity = Entities.getEntityProperties(entityID, 'velocity').velocity;
|
|
velocity.y += JUMP_SPEED;
|
|
Entities.editEntity(entityID, { velocity: velocity });
|
|
|
|
}, 500 + Math.random() * 4000);
|
|
}
|
|
|
|
// Check the list of remaining enemies to see if any are too far away
|
|
// or haven't been heard from in awhile - if so, delete them.
|
|
var enemiesEscaped = false;
|
|
const MAX_UNHEARD_TIME_BEFORE_DESTROYING_ENTITY_MS = 5000;
|
|
const MAX_DISTANCE_FROM_GAME_BEFORE_DESTROYING_ENTITY = 200;
|
|
for (var i = this.remainingEnemies.length - 1; i >= 0; --i) {
|
|
var enemy = this.remainingEnemies[i];
|
|
var timeSinceLastHeartbeat = Date.now() - enemy.lastHeartbeat;
|
|
var distance = Vec3.distance(enemy.lastKnownPosition, this.rootPosition);
|
|
if (timeSinceLastHeartbeat > MAX_UNHEARD_TIME_BEFORE_DESTROYING_ENTITY_MS
|
|
|| distance > MAX_DISTANCE_FROM_GAME_BEFORE_DESTROYING_ENTITY) {
|
|
|
|
print("EXPIRING: ", enemy.id);
|
|
Entities.deleteEntity(enemy.id);
|
|
this.remainingEnemies.splice(i, 1);
|
|
Audio.playSound(TARGET_HIT_SOUND, {
|
|
volume: 1.0,
|
|
position: this.rootPosition
|
|
});
|
|
this.setScore(this.score + POINTS_PER_KILL);
|
|
enemiesEscaped = true;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
this.gameState = GAME_STATES.GAME_OVER;
|
|
print("GAME OVER");
|
|
|
|
// Update high score
|
|
sendAndUpdateHighScore(this.rootEntityID, this.score, this.waveNumber, 1, this.setHighScore.bind(this));
|
|
|
|
// Cleanup
|
|
Script.clearTimeout(this.nextWaveTimer);
|
|
this.nextWaveTimer = null;
|
|
var i;
|
|
for (i = 0; i < this.spawnEnemyTimers.length; ++i) {
|
|
Script.clearTimeout(this.spawnEnemyTimers[i]);
|
|
}
|
|
this.spawnEnemyTimers = [];
|
|
|
|
Script.clearInterval(this.checkEnemiesTimer);
|
|
this.checkEnemiesTimer = null;
|
|
|
|
|
|
for (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);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < this.remainingEnemies.length; i++) {
|
|
Entities.deleteEntity(this.remainingEnemies[i].id);
|
|
}
|
|
this.remainingEnemies = [];
|
|
|
|
// Wait a short time before showing the start button so that any current sounds
|
|
// can finish playing.
|
|
const WAIT_TO_REENABLE_GAME_TIMEOUT_MS = 3000;
|
|
Script.setTimeout(function() {
|
|
Entities.editEntity(this.startButtonID, { visible: true });
|
|
this.gameState = GAME_STATES.IDLE;
|
|
}.bind(this), WAIT_TO_REENABLE_GAME_TIMEOUT_MS);
|
|
},
|
|
onReceivedMessage: function(channel, messageJSON, senderID) {
|
|
if (channel === this.commChannelName) {
|
|
var message = utils.parseJSON(messageJSON);
|
|
if (message === undefined) {
|
|
print("shortbowGameManager.js | Received non-json message:", JSON.stringify(messageJSON));
|
|
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;
|
|
case 'enemy-heartbeat':
|
|
this.onEnemyHeartbeat(message.entityID, message.position);
|
|
break;
|
|
default:
|
|
print("shortbowGameManager.js | Ignoring unknown message type: ", message.type);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
onEnemyKilled: function(entityID, position) {
|
|
if (this.gameState !== GAME_STATES.PLAYING) {
|
|
return;
|
|
}
|
|
|
|
for (var i = this.remainingEnemies.length - 1; i >= 0; --i) {
|
|
var enemy = this.remainingEnemies[i];
|
|
if (enemy.id === entityID) {
|
|
this.remainingEnemies.splice(i, 1);
|
|
Audio.playSound(TARGET_HIT_SOUND, {
|
|
volume: 1.0,
|
|
position: this.rootPosition
|
|
});
|
|
|
|
// Update score
|
|
this.setScore(this.score + POINTS_PER_KILL);
|
|
print("SCORE: ", this.score);
|
|
|
|
this.checkWaveComplete();
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
onEnemyEscaped: function(entityID, position) {
|
|
if (this.gameState !== GAME_STATES.PLAYING) {
|
|
return;
|
|
}
|
|
|
|
var enemiesEscaped = false;
|
|
for (var i = this.remainingEnemies.length - 1; i >= 0; --i) {
|
|
var enemy = this.remainingEnemies[i];
|
|
if (enemy.id === entityID) {
|
|
Entities.deleteEntity(enemy.id);
|
|
this.remainingEnemies.splice(i, 1);
|
|
this.setLivesLeft(this.livesLeft - 1);
|
|
Audio.playSound(ESCAPE_SOUND, {
|
|
volume: 1.0,
|
|
position: this.rootPosition
|
|
});
|
|
enemiesEscaped = true;
|
|
}
|
|
}
|
|
if (this.livesLeft <= 0) {
|
|
this.endGame();
|
|
} else if (enemiesEscaped) {
|
|
this.checkWaveComplete();
|
|
}
|
|
},
|
|
onEnemyHeartbeat: function(entityID, position) {
|
|
for (var i = 0; i < this.remainingEnemies.length; i++) {
|
|
var enemy = this.remainingEnemies[i];
|
|
if (enemy.id === entityID) {
|
|
enemy.lastHeartbeat = Date.now();
|
|
enemy.lastKnownPosition = position;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|