mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-08 03:42:27 +02:00
Add initial code for wave game
This commit is contained in:
parent
43bbac2457
commit
ba5f8d0831
4 changed files with 516 additions and 0 deletions
|
@ -0,0 +1,84 @@
|
|||
if (!Function.prototype.bind) {
|
||||
Function.prototype.bind = function(oThis) {
|
||||
if (typeof this !== 'function') {
|
||||
// closest thing possible to the ECMAScript 5
|
||||
// internal IsCallable function
|
||||
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
|
||||
}
|
||||
|
||||
var aArgs = Array.prototype.slice.call(arguments, 1),
|
||||
fToBind = this,
|
||||
fNOP = function() {},
|
||||
fBound = function() {
|
||||
return fToBind.apply(this instanceof fNOP
|
||||
? this
|
||||
: oThis,
|
||||
aArgs.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
|
||||
if (this.prototype) {
|
||||
// Function.prototype doesn't have a prototype property
|
||||
fNOP.prototype = this.prototype;
|
||||
}
|
||||
fBound.prototype = new fNOP();
|
||||
|
||||
return fBound;
|
||||
};
|
||||
}
|
||||
|
||||
(function() {
|
||||
function parseJSON(json) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch(e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Enemy = function() {
|
||||
};
|
||||
Enemy.prototype = {
|
||||
preload: function(entityID) {
|
||||
print("Loaded enemy entity");
|
||||
this.entityID = entityID;
|
||||
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
|
||||
|
||||
this.entityIDsThatHaveCollidedWithMe = [];
|
||||
|
||||
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
|
||||
var data = parseJSON(userData);
|
||||
if (data !== undefined && data.gameChannel !== undefined) {
|
||||
this.gameChannel = data.gameChannel;
|
||||
} else {
|
||||
print("targetEntity.js | ERROR: userData does not contain a game channel and/or team number");
|
||||
}
|
||||
},
|
||||
onCollide: function(entityA, entityB, collision) {
|
||||
print("Collided with: ", entityB);
|
||||
if (this.entityIDsThatHaveCollidedWithMe.indexOf(entityB) > -1) {
|
||||
return;
|
||||
}
|
||||
this.entityIDsThatHaveCollidedWithMe.push(entityB);
|
||||
|
||||
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
|
||||
|
||||
// If the other entity's name includes 'projectile' and we haven't hit it before,
|
||||
// continue on.
|
||||
if (colliderName.indexOf("projectile") > -1) {
|
||||
Messages.sendMessage(this.gameChannel, JSON.stringify({
|
||||
type: "enemy-killed",
|
||||
entityID: this.entityID,
|
||||
}));
|
||||
Entities.deleteEntity(this.entityID);
|
||||
} else if (colliderName.indexOf("goal") > -1) {
|
||||
Messages.sendMessage(this.gameChannel, JSON.stringify({
|
||||
type: "enemy-escaped",
|
||||
entityID: this.entityID,
|
||||
}));
|
||||
Entities.deleteEntity(this.entityID);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new Enemy();
|
||||
});
|
|
@ -0,0 +1,404 @@
|
|||
print("============= Script Starting =============");
|
||||
|
||||
if (!Function.prototype.bind) {
|
||||
Function.prototype.bind = function(oThis) {
|
||||
if (typeof this !== 'function') {
|
||||
// closest thing possible to the ECMAScript 5
|
||||
// internal IsCallable function
|
||||
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
|
||||
}
|
||||
|
||||
var aArgs = Array.prototype.slice.call(arguments, 1),
|
||||
fToBind = this,
|
||||
fNOP = function() {},
|
||||
fBound = function() {
|
||||
return fToBind.apply(this instanceof fNOP
|
||||
? this
|
||||
: oThis,
|
||||
aArgs.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
|
||||
if (this.prototype) {
|
||||
// Function.prototype doesn't have a prototype property
|
||||
fNOP.prototype = this.prototype;
|
||||
}
|
||||
fBound.prototype = new fNOP();
|
||||
|
||||
return fBound;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Utility functions
|
||||
function parseJSON(json) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function findSurfaceBelowPosition(pos) {
|
||||
var result = Entities.findRayIntersection({
|
||||
origin: pos,
|
||||
direction: { x: 0, y: -1, z: 0 }
|
||||
});
|
||||
if (result.intersects) {
|
||||
return result.intersection;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
// End of utility function
|
||||
|
||||
|
||||
|
||||
var GAME_STATES = {
|
||||
IDLE: 0,
|
||||
PLAYING: 1,
|
||||
GAME_OVER: 2,
|
||||
};
|
||||
|
||||
var COMM_CHANNEL_NAME = 'wavegame';
|
||||
var entityIDs = [];
|
||||
|
||||
function GameManager(gatePosition, roofPosition, spawnPositions, startButtonID) {
|
||||
this.gameState = GAME_STATES.IDLE;
|
||||
this.spawnPositions = spawnPositions;
|
||||
this.gatePosition = gatePosition;
|
||||
this.roofPosition = roofPosition;
|
||||
this.startButtonID = startButtonID;
|
||||
|
||||
// Gameplay state
|
||||
this.waveNumber = 0;
|
||||
this.livesLeft = 5;
|
||||
this.score = 0;
|
||||
this.nextWaveTimer = null;
|
||||
this.spawnEnemyTimers = [];
|
||||
this.enemyIDs = [];
|
||||
this.entityIDs = [];
|
||||
}
|
||||
GameManager.prototype = {
|
||||
cleanup: function() {
|
||||
for (var i = 0; i < this.entityIDs.length; i++) {
|
||||
Entities.deleteEntity(this.entityIDs[i]);
|
||||
}
|
||||
this.entityIDs = [];
|
||||
},
|
||||
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 });
|
||||
|
||||
// Initialize game state
|
||||
this.waveNumber = 0;
|
||||
this.score = 0;
|
||||
this.livesLeft = 5;
|
||||
|
||||
this.nextWaveTimer = Script.setTimeout(this.startNextWave.bind(this), 100);
|
||||
this.spawnEnemyTimers = [];
|
||||
this.checkEnemyPositionsTimer = null;
|
||||
this.enemyIDs = [];
|
||||
|
||||
this.gameState = GAME_STATES.PLAYING;
|
||||
this.checkEnemyPositionsTimer = Script.setInterval(this.checkForEscapedEnemies.bind(this), 100);
|
||||
|
||||
// Spawn bows
|
||||
const bowPosition = Vec3.sum(this.roofPosition, { x: 0, y: 1, z: 0 });
|
||||
for (var j = 0; j < 4; ++j) {
|
||||
this.entityIDs.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": -1,
|
||||
"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}]}}}"
|
||||
}));
|
||||
}
|
||||
},
|
||||
startNextWave: function() {
|
||||
print("Starting next wave");
|
||||
this.waveNumber++;
|
||||
this.enemyIDs = [];
|
||||
const numberOfEnemiesToSpawn = this.waveNumber * 2;
|
||||
print("Number to spawn:", numberOfEnemiesToSpawn, this.waveNumber);
|
||||
for (var i = 0; i < numberOfEnemiesToSpawn; ++i) {
|
||||
Vec3.print(i, this.spawnPositions[i % this.spawnPositions.length]);
|
||||
// Spawn enemy
|
||||
var enemyID = Entities.addEntity({
|
||||
name: "WG.Enemy",
|
||||
type: "Box",
|
||||
position: this.spawnPositions[i % this.spawnPositions.length],
|
||||
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,
|
||||
})
|
||||
});
|
||||
this.enemyIDs.push(enemyID);
|
||||
}
|
||||
},
|
||||
checkWaveComplete: function() {
|
||||
if (this.gameState !== GAME_STATES.PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.enemyIDs.length == 0) {
|
||||
Script.setTimeout(this.startNextWave.bind(this), 1000);
|
||||
}
|
||||
},
|
||||
checkForEscapedEnemies: function() {
|
||||
var enemiesEscaped = false;
|
||||
for (var i = this.enemyIDs.length - 1; i >= 0; --i) {
|
||||
var position = Entities.getEntityProperties(this.enemyIDs[i], 'position').position;
|
||||
if (position && position.z < this.gatePosition.z) {
|
||||
Entities.deleteEntity(this.enemyIDs[i]);
|
||||
this.enemyIDs.splice(i, 1);
|
||||
this.livesLeft--;
|
||||
enemiesEscaped = true;
|
||||
}
|
||||
}
|
||||
print("LIVES LEFT: ", this.livesLeft);
|
||||
if (this.livesLeft <= 0) {
|
||||
this.endGame();
|
||||
} else if (enemiesEscaped) {
|
||||
this.checkWaveComplete();
|
||||
}
|
||||
},
|
||||
endGame: function() {
|
||||
if (this.gameState !== GAME_STATES.PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entities.editEntity(this.startButtonID, { visible: true });
|
||||
|
||||
this.gameState = GAME_STATES.GAME_OVER;
|
||||
print("GAME OVER");
|
||||
|
||||
// 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;
|
||||
|
||||
Script.setTimeout(function() {
|
||||
this.gameState = GAME_STATES.IDLE;
|
||||
}.bind(this), 3000);
|
||||
},
|
||||
onEnemyKilled: function(entityID) {
|
||||
if (this.gameState !== GAME_STATES.PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = this.enemyIDs.indexOf(entityID);
|
||||
if (idx >= 0) {
|
||||
this.enemyIDs.splice(idx, 1);
|
||||
|
||||
// Update score
|
||||
this.score += 100;
|
||||
print("SCORE: ", this.score);
|
||||
|
||||
this.checkWaveComplete();
|
||||
}
|
||||
},
|
||||
// onEnemyEscaped: function(entityID) {
|
||||
// if (this.gameState !== GAME_STATES.PLAYING) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// this.livesLeft--;
|
||||
// print("LIVES LEFT: ", this.livesLeft);
|
||||
// if (this.livesLeft <= 0) {
|
||||
// this.endGame();
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
};
|
||||
|
||||
function createLocalGame() {
|
||||
const rootPosition = findSurfaceBelowPosition(MyAvatar.position);
|
||||
|
||||
// Create start button
|
||||
var buttonProperties = {
|
||||
type: 'Box',
|
||||
name: 'WG.StartButton',
|
||||
position: rootPosition,
|
||||
dimensions: { x: 1, y: 1, z: 1 },
|
||||
color: { red: 0, green: 255, blue: 0 },
|
||||
script: Script.resolvePath("startGameButton.js"),
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
wantsTrigger: true
|
||||
},
|
||||
gameChannel: COMM_CHANNEL_NAME
|
||||
}),
|
||||
}
|
||||
var buttonID = Entities.addEntity(buttonProperties);
|
||||
entityIDs.push(buttonID);
|
||||
|
||||
|
||||
// Generate goal that the enemies try to get to
|
||||
const goalPosition = Vec3.sum(rootPosition, { x: 0, y: -10, z: -20 });
|
||||
const BASES_HEIGHT = 16;
|
||||
const BASES_SIZE = 15;
|
||||
const BASES_WIDTH = 20;
|
||||
const BASES_DEPTH = 15;
|
||||
const ROOF_HEIGHT = 0.2;
|
||||
var arenaProperties = {
|
||||
name: 'WG.Arena.goal',
|
||||
type: 'Box',
|
||||
position: goalPosition,
|
||||
registrationPoint: { x: 0.5, y: 0, z: 0.5 },
|
||||
dimensions: { x: BASES_SIZE, y: BASES_HEIGHT, z: BASES_SIZE },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
script: Script.resolvePath("warpToTopEntity.js"),
|
||||
visible: false,
|
||||
collisionless: true,
|
||||
userData: JSON.stringify({
|
||||
gameChannel: COMM_CHANNEL_NAME,
|
||||
})
|
||||
};
|
||||
// Base block
|
||||
var arenaID = Entities.addEntity(arenaProperties);
|
||||
entityIDs.push(arenaID);
|
||||
|
||||
// Generate platform to shoot from
|
||||
goalPosition.y += BASES_HEIGHT - ROOF_HEIGHT;
|
||||
const roofPosition = goalPosition;
|
||||
var roofProperties = {
|
||||
name: 'WG.Roof',
|
||||
type: 'Box',
|
||||
position: goalPosition,
|
||||
registrationPoint: { x: 0.5, y: 0, z: 0.5 },
|
||||
dimensions: { x: BASES_SIZE, y: ROOF_HEIGHT, z: BASES_SIZE },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
script: Script.resolvePath('roofEntity.js'),
|
||||
userData: JSON.stringify({
|
||||
gameChannel: COMM_CHANNEL_NAME,
|
||||
})
|
||||
}
|
||||
var roofID = Entities.addEntity(roofProperties)
|
||||
entityIDs.push(roofID);
|
||||
|
||||
// Generate positions that the enemies spawn from. spawnOffsets is a list
|
||||
// of spawn position relative to rootPosition.
|
||||
const spawnOffsets = [
|
||||
{ x: -7.5, y: 0, z: 10 },
|
||||
{ x: -2.5, y: 0, z: 10 },
|
||||
{ x: 2.5, y: 0, z: 10 },
|
||||
{ x: 7.5, y: 0, z: 10 },
|
||||
];
|
||||
var spawnPositions = [];
|
||||
for (var i = 0; i < spawnOffsets.length; ++i) {
|
||||
const spawnPosition = Vec3.sum(rootPosition, spawnOffsets[i]);
|
||||
var spawnID = Entities.addEntity({
|
||||
name: 'WG.Spawn',
|
||||
type: 'Box',
|
||||
position: spawnPosition,
|
||||
registrationPoint: { x: 0.5, y: 0, z: 0.5 },
|
||||
dimensions: { x: 0.5, y: 0.5, z: 0.5},
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
});
|
||||
entityIDs.push(spawnID);
|
||||
spawnPositions.push(spawnPosition);
|
||||
}
|
||||
|
||||
var goalPositionFront = Vec3.sum(goalPosition, { x: 0, y: 0, z: BASES_SIZE / 2 });
|
||||
return new GameManager(goalPositionFront, roofPosition, spawnPositions, buttonID);
|
||||
}
|
||||
|
||||
function createACGame() {
|
||||
// TODO
|
||||
throw("AC not implemented");
|
||||
}
|
||||
|
||||
|
||||
// Setup game
|
||||
var gameManager;
|
||||
if (this.EntityViewer !== undefined) {
|
||||
gameManager = createACGame();
|
||||
} else {
|
||||
gameManager = createLocalGame();
|
||||
}
|
||||
|
||||
Messages.subscribe(COMM_CHANNEL_NAME);
|
||||
Messages.messageReceived.connect(function(channel, messageJSON, senderID) {
|
||||
print("playWaveGame.js | Recieved: " + messageJSON + " from " + senderID);
|
||||
if (channel === COMM_CHANNEL_NAME) {
|
||||
var message = parseJSON(messageJSON);
|
||||
if (message === undefined) {
|
||||
print("playWaveGame.js | Received non-json message");
|
||||
return;
|
||||
}
|
||||
switch (message.type) {
|
||||
case 'start-game':
|
||||
gameManager.startGame();
|
||||
break;
|
||||
case 'enemy-killed':
|
||||
gameManager.onEnemyKilled(message.entityID);
|
||||
break;
|
||||
case 'enemy-escaped':
|
||||
gameManager.onEnemyEscaped(message.entityID);
|
||||
break;
|
||||
default:
|
||||
print("playWaveGame.js | Ignoring unknown message type: ", message.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function cleanup() {
|
||||
for (var i = 0; i < entityIDs.length; ++i) {
|
||||
Entities.deleteEntity(entityIDs[i]);
|
||||
}
|
||||
gameManager.cleanup();
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
|
@ -6,7 +6,9 @@
|
|||
}
|
||||
|
||||
function signalAC() {
|
||||
print("Button pressed");
|
||||
var userData = Entities.getEntityProperties(itemID, ["userData"]).userData;
|
||||
print("Sending message to: ", JSON.parse(userData).gameChannel);
|
||||
Messages.sendMessage(JSON.parse(userData).gameChannel, JSON.stringify({
|
||||
type: 'start-game'
|
||||
}));
|
|
@ -0,0 +1,26 @@
|
|||
(function() {
|
||||
function parseJSON(json) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch(e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
var TeamArea = function() {
|
||||
};
|
||||
TeamArea.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
},
|
||||
enterEntity: function() {
|
||||
print("teamAreaEntity.js | Entered");
|
||||
var props = Entities.getEntityProperties(this.entityID, ['position', 'dimensions', 'registrationPoint']);
|
||||
var teleportPoint = props.position;
|
||||
teleportPoint.y += (props.dimensions.y * (1 - props.registrationPoint.y)) + 0.5;
|
||||
MyAvatar.position = teleportPoint;
|
||||
}
|
||||
};
|
||||
|
||||
return new TeamArea();
|
||||
});
|
Loading…
Reference in a new issue