Merge pull request #9602 from huffman/feat/shortbow-entity-server

Add shortbow to marketplace scripts
This commit is contained in:
Ryan Huffman 2017-02-27 11:09:52 -08:00 committed by GitHub
commit 5b59939195
37 changed files with 3737 additions and 1 deletions

View file

@ -3431,7 +3431,7 @@ void Application::idle(float nsecsElapsed) {
#ifdef Q_OS_WIN
static std::once_flag once;
std::call_once(once, [] {
initCpuUsage();
initCpuUsage();
});
vec3 kernelUserAndSystem;

View file

@ -0,0 +1,111 @@
# Shortbow
Shortbow is a wave-based archery game.
## Notes
There are several design decisions that are based on certain characteristics of the High Fidelity platform,
and in particular, [server entity scripts](https://wiki.highfidelity.com/wiki/Creating_Entity_Server_Scripts),
which are touched on below.
It is recommended that you understand the basics of client entity scripts and server entity scripts (and their
differences) if you plan on digging into the shortbow code.
* Client entity scripts end in `ClientEntity.js` and server entity scripts end in `ServerEntity.js`.
* Server entity scripts are not guaranteed to have access to *other* entities, and should not rely on it.
* You should not rely on using `Entities.getEntityProperties` to access the properties of entities
other than the entity that the server entity script is running on. This also applies to other
functions like `Entities.findEntities`. This means that something like the `ShortGameManager` (described below)
will not know the entity IDs of entities like the start button or scoreboard text entities, so it
has to find ways to work around that limitation.
* You can, however, use `Entities.editEntity` to edit other entities.
* *NOTE*: It is likely that this will change in the future, and server entity scripts will be able to
query the existence of other entities in some way. This will obviate the need for some of the workarounds
used in shortbow.
* The Entity Script Server (where server entity scripts) does not run a physics simulation
* Server entity scripts do not generate collision events like would be used with
`Script.addEventHandler(entityID, "collisionWithEntity", collideHandler)`.
* High Fidelity distributes its physics simulation to clients, so collisions need to be handled
there. In the case of enemies in shortbow, for instance, the collisions are handled in the
client entity script `enemeyClientEntity.js`.
* If no client is present to run the physics simulation, entities will not physically interact with other
entities.
* But, entities will still apply their basic physical properties.
## Shortbow Game Manager
This section describes both `shortbowServerEntity.js` and `shortbowGameManager.js`. The `shortbowServerEntity.js` script
exists on the main arena model entity, and is the parent of all of the other parts of the game, other than the
enemies and bows that are spawned during gameplay.
The `shortbowServerEntity.js` script is a server entity script that runs the shortbow game. The actual logic for
the game is stored inside `shortbowGameManager.js`, in the `ShortbowGameManager` prototype.
## Enemy Scripts
These scripts exist on each enemy that is spawned.
### enemyClientEntity.js
This script handles collision events on the client. There are two collisions events that it is interested in:
1. Collisions with arrows
1. Arrow entities have "projectile" in their name
1. Any other entity with "projectile" in its name could be used to destroy the enemy
1. Collisions with the gate that the enemies roll towards
1. The gate entity has "GateCollider" in its name
### enemyServerEntity.js
This script acts as a fail-safe to work around some of the limitations that are mentioned under [Notes](#notes).
In particular, if no client is running the physics simulation of an enemy entity, it may fall through the floor
of the arena or never have a collision event generated against the gate. A server entity script also
cannot access the properties of another entity, so it can't know if the entity has moved far away from
the arena.
To handle this, this script is used to periodically send heartbeats to the [ShortbowGameManager](#shortbow-game-manager)
that runs the game. The heartbeats include the position of the enemy. If the script that received the
heartbeats hasn't heard from `enemyServerEntity.js` in too long, or the entity has moved too far away, it
will remove it from it's local list of enemies that still need to be destroyed. This ensure that the game
doesn't get into a "hung" state where it's waiting for an enemy that will never escape through the gate or be
destroyed.
## Start Button
These scripts exist on the start button.
### startGameButtonClientEntity.js
This script sends a message to the [ShortbowGameManager](#shortbow-game-manager) to request that the game be started.
### startGameButtonServerEntity.js
When the shortbow game starts the start button is hidden, and when the shortbow game ends it is shown again.
As described in the [Notes](#notes), server entity scripts cannot access other entities, including their IDs.
Because of this, the [ShortbowGameManager](#shortbow-game-manager) has no way of knowing the id of the start button,
and thus being able to use `Entities.editEntity` to set its `visible` property to `true` or `false`. One way around
this, and is what is used here, is to use `Messages` on a common channel to send messages to a server entity script
on the start button to request that it be shown or hidden.
## Display (Scoreboard)
This script exists on each text entity that scoreboard is composed of. The scoreboard area is composed of a text entity for each of the following: Score, High Score, Wave, Lives.
### displayServerEntity.js
The same problem that exists for [startGameButtonServerEntity.js](#startgamebuttonserverentityjs) exists for
the text entities on the scoreboard. This script works around the problem in the same way, but instead of
receiving a message that tells it whether to hide or show itself, it receives a message that contains the
text to update the text entity with. For intance, the "lives" display entity will receive a message each
time a life is lost with the current number of lives.
## How is the "common channel" determined that is used in some of the client and server entity scripts?
Imagine that there are two instances of shortbow next to each. If the channel being used is always the same,
and not differentiated in some way between the two instances, a "start game" message sent from [startGameButtonClientEntity.js](#startgamebuttoncliententityjs)
on one game will be received and handled by both games, causing both of them to start. We need a way to create
a channel that is unique to the scripts that are running for a particular instance of shortbow.
All of the entities in shortbow, besides the enemy entities, are children of the main arena entity that
runs the game. All of the scripts on these entities can access their parentID, so they can use
a prefix plus the parentID to generate a unique channel for that instance of shortbow.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

View file

@ -0,0 +1,671 @@
//
// Created by Seth Alves on 2016-9-7
// Copyright 2016 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
/* global MyAvatar, Vec3, Controller, Quat */
var GRAB_COMMUNICATIONS_SETTING = "io.highfidelity.isFarGrabbing";
setGrabCommunications = function setFarGrabCommunications(on) {
Settings.setValue(GRAB_COMMUNICATIONS_SETTING, on ? "on" : "");
}
getGrabCommunications = function getFarGrabCommunications() {
return !!Settings.getValue(GRAB_COMMUNICATIONS_SETTING, "");
}
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral
getGrabPointSphereOffset = function(handController) {
if (handController === Controller.Standard.RightHand) {
return GRAB_POINT_SPHERE_OFFSET;
}
return {
x: GRAB_POINT_SPHERE_OFFSET.x * -1,
y: GRAB_POINT_SPHERE_OFFSET.y,
z: GRAB_POINT_SPHERE_OFFSET.z
};
};
// controllerWorldLocation is where the controller would be, in-world, with an added offset
getControllerWorldLocation = function (handController, doOffset) {
var orientation;
var position;
var pose = Controller.getPoseValue(handController);
var valid = pose.valid;
var controllerJointIndex;
if (pose.valid) {
if (handController === Controller.Standard.RightHand) {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND");
} else {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
}
orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex));
position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)));
// add to the real position so the grab-point is out in front of the hand, a bit
if (doOffset) {
var offset = getGrabPointSphereOffset(handController);
position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset));
}
} else if (!HMD.isHandControllerAvailable()) {
// NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493
var VERTICAL_HEAD_LASER_OFFSET = 0.1;
position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0}));
orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }));
valid = true;
}
return {position: position,
translation: position,
orientation: orientation,
rotation: orientation,
valid: valid};
};
//
//
//
//
//
//
// bow.js
//
// This script attaches to a bow that you can pick up with a hand controller.
// Created by James B. Pollack @imgntn on 10/19/2015
// Copyright 2015 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
//
/*global Script, Controller, SoundCache, Entities, getEntityCustomData, setEntityCustomData, MyAvatar, Vec3, Quat, Messages */
function getControllerLocation(controllerHand) {
var standardControllerValue =
(controllerHand === "right") ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
return getControllerWorldLocation(standardControllerValue, true);
};
(function() {
Script.include("/~/system/libraries/utils.js");
const NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
const NOTCH_ARROW_SOUND_URL = Script.resolvePath('notch.wav');
const SHOOT_ARROW_SOUND_URL = Script.resolvePath('String_release2.L.wav');
const STRING_PULL_SOUND_URL = Script.resolvePath('Bow_draw.1.L.wav');
const ARROW_HIT_SOUND_URL = Script.resolvePath('Arrow_impact1.L.wav');
const ARROW_MODEL_URL = Script.resolvePath('arrow.fbx');
const ARROW_DIMENSIONS = {
x: 0.20,
y: 0.19,
z: 0.93
};
const MIN_ARROW_SPEED = 3.0;
const MAX_ARROW_SPEED = 30.0;
const ARROW_TIP_OFFSET = 0.47;
const ARROW_GRAVITY = {
x: 0,
y: -9.8,
z: 0
};
const ARROW_LIFETIME = 15; // seconds
const ARROW_PARTICLE_LIFESPAN = 2; // seconds
const TOP_NOTCH_OFFSET = 0.6;
const BOTTOM_NOTCH_OFFSET = 0.6;
const LINE_DIMENSIONS = {
x: 5.0,
y: 5.0,
z: 5.0
};
const DRAW_STRING_THRESHOLD = 0.80;
const DRAW_STRING_PULL_DELTA_HAPTIC_PULSE = 0.09;
const DRAW_STRING_MAX_DRAW = 0.7;
const MIN_ARROW_DISTANCE_FROM_BOW_REST = 0.2;
const MAX_ARROW_DISTANCE_FROM_BOW_REST = ARROW_DIMENSIONS.z - 0.2;
const MIN_HAND_DISTANCE_FROM_BOW_TO_KNOCK_ARROW = 0.25;
const MIN_ARROW_DISTANCE_FROM_BOW_REST_TO_SHOOT = 0.30;
const NOTCH_OFFSET_FORWARD = 0.08;
const NOTCH_OFFSET_UP = 0.035;
const SHOT_SCALE = {
min1: 0.0,
max1: 0.6,
min2: 1.0,
max2: 15.0
};
const USE_DEBOUNCE = false;
const TRIGGER_CONTROLS = [
Controller.Standard.LT,
Controller.Standard.RT,
];
function interval() {
var lastTime = new Date().getTime();
return function getInterval() {
var newTime = new Date().getTime();
var delta = newTime - lastTime;
lastTime = newTime;
return delta;
};
}
var checkInterval = interval();
var _this;
function Bow() {
_this = this;
return;
}
const STRING_NAME = 'Hifi-Bow-String';
const ARROW_NAME = 'Hifi-Arrow-projectile';
const STATE_IDLE = 0;
const STATE_ARROW_GRABBED = 1;
Bow.prototype = {
topString: null,
aiming: false,
arrowTipPosition: null,
preNotchString: null,
stringID: null,
arrow: null,
stringData: {
currentColor: {
red: 255,
green: 255,
blue: 255
}
},
state: STATE_IDLE,
sinceLastUpdate: 0,
preload: function(entityID) {
this.entityID = entityID;
this.stringPullSound = SoundCache.getSound(STRING_PULL_SOUND_URL);
this.shootArrowSound = SoundCache.getSound(SHOOT_ARROW_SOUND_URL);
this.arrowHitSound = SoundCache.getSound(ARROW_HIT_SOUND_URL);
this.arrowNotchSound = SoundCache.getSound(NOTCH_ARROW_SOUND_URL);
var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData;
print(userData);
this.userData = JSON.parse(userData);
this.stringID = null;
},
unload: function() {
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
Entities.deleteEntity(this.arrow);
},
startEquip: function(entityID, args) {
this.hand = args[0];
this.bowHand = args[0];
this.stringHand = this.bowHand === "right" ? "left" : "right";
Entities.editEntity(_this.entityID, {
collidesWith: "",
});
var data = getEntityCustomData('grabbableKey', this.entityID, {});
data.grabbable = false;
setEntityCustomData('grabbableKey', this.entityID, data);
this.initString();
var self = this;
this.updateIntervalID = Script.setInterval(function() { self.update(); }, 11);
},
getStringHandPosition: function() {
return getControllerLocation(this.stringHand).position;
},
releaseEquip: function(entityID, args) {
Script.clearInterval(this.updateIntervalID);
this.updateIntervalID = null;
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
var data = getEntityCustomData('grabbableKey', this.entityID, {});
data.grabbable = true;
setEntityCustomData('grabbableKey', this.entityID, data);
Entities.deleteEntity(this.arrow);
this.resetStringToIdlePosition();
this.destroyArrow();
Entities.editEntity(_this.entityID, {
collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar"
});
},
update: function(entityID) {
var self = this;
self.deltaTime = checkInterval();
//debounce during debugging -- maybe we're updating too fast?
if (USE_DEBOUNCE === true) {
self.sinceLastUpdate = self.sinceLastUpdate + self.deltaTime;
if (self.sinceLastUpdate > 60) {
self.sinceLastUpdate = 0;
} else {
return;
}
}
//invert the hands because our string will be held with the opposite hand of the first one we pick up the bow with
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[(this.hand === 'left') ? 1 : 0]);
this.bowProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
var notchPosition = this.getNotchPosition(this.bowProperties);
var stringHandPosition = this.getStringHandPosition();
var handToNotch = Vec3.subtract(notchPosition, stringHandPosition);
var pullBackDistance = Vec3.length(handToNotch);
if (this.state === STATE_IDLE) {
this.pullBackDistance = 0;
this.resetStringToIdlePosition();
if (this.triggerValue >= DRAW_STRING_THRESHOLD && pullBackDistance < MIN_HAND_DISTANCE_FROM_BOW_TO_KNOCK_ARROW && !this.backHandBusy) {
//the first time aiming the arrow
var handToDisable = (this.hand === 'right' ? 'left' : 'right');
this.state = STATE_ARROW_GRABBED;
}
}
if (this.state === STATE_ARROW_GRABBED) {
if (!this.arrow) {
var handToDisable = (this.hand === 'right' ? 'left' : 'right');
Messages.sendLocalMessage('Hifi-Hand-Disabler', handToDisable);
this.playArrowNotchSound();
this.arrow = this.createArrow();
this.playStringPullSound();
}
if (this.triggerValue < DRAW_STRING_THRESHOLD) {
if (pullBackDistance >= (MIN_ARROW_DISTANCE_FROM_BOW_REST_TO_SHOOT)) {
// The arrow has been pulled far enough back that we can release it
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
this.updateArrowPositionInNotch(true, true);
this.arrow = null;
this.state = STATE_IDLE;
this.resetStringToIdlePosition();
} else {
// The arrow has not been pulled far enough back so we just remove the arrow
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
Entities.deleteEntity(this.arrow);
this.arrow = null;
this.state = STATE_IDLE;
this.resetStringToIdlePosition();
}
} else {
this.updateArrowPositionInNotch(false, true);
this.updateString();
}
}
},
destroyArrow: function() {
var children = Entities.getChildrenIDs(this.entityID);
children.forEach(function(childID) {
var childName = Entities.getEntityProperties(childID, ["name"]).name;
if (childName == ARROW_NAME) {
Entities.deleteEntity(childID);
// Continue iterating through children in case we've ended up in
// a bad state where there are multiple arrows.
}
});
},
createArrow: function() {
this.playArrowNotchSound();
var arrow = Entities.addEntity({
name: ARROW_NAME,
type: 'Model',
modelURL: ARROW_MODEL_URL,
shapeType: 'simple-compound',
dimensions: ARROW_DIMENSIONS,
position: this.bowProperties.position,
parentID: this.entityID,
dynamic: false,
collisionless: true,
collisionSoundURL: ARROW_HIT_SOUND_URL,
damping: 0.01,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
creatorSessionUUID: MyAvatar.sessionUUID
})
});
var makeArrowStick = function(entityA, entityB, collision) {
Entities.editEntity(entityA, {
localAngularVelocity: {
x: 0,
y: 0,
z: 0
},
localVelocity: {
x: 0,
y: 0,
z: 0
},
gravity: {
x: 0,
y: 0,
z: 0
},
parentID: entityB,
dynamic: false,
collisionless: true,
collidesWith: ""
});
Script.removeEventHandler(arrow, "collisionWithEntity", makeArrowStick);
};
Script.addEventHandler(arrow, "collisionWithEntity", makeArrowStick);
return arrow;
},
initString: function() {
// Check for existence of string
var children = Entities.getChildrenIDs(this.entityID);
children.forEach(function(childID) {
var childName = Entities.getEntityProperties(childID, ["name"]).name;
if (childName == STRING_NAME) {
this.stringID = childID;
}
});
// If thie string wasn't found, create it
if (this.stringID === null) {
this.stringID = Entities.addEntity({
collisionless: true,
dimensions: { "x": 5, "y": 5, "z": 5 },
ignoreForCollisions: 1,
linePoints: [ { "x": 0, "y": 0, "z": 0 }, { "x": 0, "y": -1.2, "z": 0 } ],
lineWidth: 5,
color: { red: 153, green: 102, blue: 51 },
name: STRING_NAME,
parentID: this.entityID,
localPosition: { "x": 0, "y": 0.6, "z": 0.1 },
localRotation: { "w": 1, "x": 0, "y": 0, "z": 0 },
type: 'Line',
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
})
});
}
this.resetStringToIdlePosition();
},
// This resets the string to a straight line
resetStringToIdlePosition: function() {
Entities.editEntity(this.stringID, {
linePoints: [ { "x": 0, "y": 0, "z": 0 }, { "x": 0, "y": -1.2, "z": 0 } ],
lineWidth: 5,
localPosition: { "x": 0, "y": 0.6, "z": 0.1 },
localRotation: { "w": 1, "x": 0, "y": 0, "z": 0 },
});
},
updateString: function() {
var upVector = Quat.getUp(this.bowProperties.rotation);
var upOffset = Vec3.multiply(upVector, TOP_NOTCH_OFFSET);
var downVector = Vec3.multiply(-1, Quat.getUp(this.bowProperties.rotation));
var downOffset = Vec3.multiply(downVector, BOTTOM_NOTCH_OFFSET);
var backOffset = Vec3.multiply(-0.1, Quat.getFront(this.bowProperties.rotation));
var topStringPosition = Vec3.sum(this.bowProperties.position, upOffset);
this.topStringPosition = Vec3.sum(topStringPosition, backOffset);
var bottomStringPosition = Vec3.sum(this.bowProperties.position, downOffset);
this.bottomStringPosition = Vec3.sum(bottomStringPosition, backOffset);
var stringProps = Entities.getEntityProperties(this.stringID, ['position', 'rotation']);
var handPositionLocal = Vec3.subtract(this.arrowRearPosition, stringProps.position);
handPositionLocal = Vec3.multiplyQbyV(Quat.inverse(stringProps.rotation), handPositionLocal);
var linePoints = [
{ x: 0, y: 0, z: 0 },
handPositionLocal,
{ x: 0, y: -1.2, z: 0 },
];
Entities.editEntity(this.stringID, {
linePoints: linePoints,
});
},
getNotchPosition: function(bowProperties) {
var frontVector = Quat.getFront(bowProperties.rotation);
var notchVectorForward = Vec3.multiply(frontVector, NOTCH_OFFSET_FORWARD);
var upVector = Quat.getUp(bowProperties.rotation);
var notchVectorUp = Vec3.multiply(upVector, NOTCH_OFFSET_UP);
var notchPosition = Vec3.sum(bowProperties.position, notchVectorForward);
notchPosition = Vec3.sum(notchPosition, notchVectorUp);
return notchPosition;
},
updateArrowPositionInNotch: function(shouldReleaseArrow, doHapticPulses) {
//set the notch that the arrow should go through
var notchPosition = this.getNotchPosition(this.bowProperties);
//set the arrow rotation to be between the notch and other hand
var stringHandPosition = this.getStringHandPosition();
var handToNotch = Vec3.subtract(notchPosition, stringHandPosition);
var arrowRotation = Quat.rotationBetween(Vec3.FRONT, handToNotch);
var backHand = this.hand === 'left' ? 1 : 0;
var pullBackDistance = Vec3.length(handToNotch);
// pulse as arrow is drawn
if (doHapticPulses &&
Math.abs(pullBackDistance - this.pullBackDistance) > DRAW_STRING_PULL_DELTA_HAPTIC_PULSE) {
Controller.triggerHapticPulse(1, 20, backHand);
this.pullBackDistance = pullBackDistance;
}
if (pullBackDistance > DRAW_STRING_MAX_DRAW) {
pullBackDistance = DRAW_STRING_MAX_DRAW;
}
var handToNotchDistance = Vec3.length(handToNotch);
var stringToNotchDistance = Math.max(MIN_ARROW_DISTANCE_FROM_BOW_REST, Math.min(MAX_ARROW_DISTANCE_FROM_BOW_REST, handToNotchDistance));
var halfArrowVec = Vec3.multiply(Vec3.normalize(handToNotch), ARROW_DIMENSIONS.z / 2.0);
var offset = Vec3.subtract(notchPosition, Vec3.multiply(Vec3.normalize(handToNotch), stringToNotchDistance - ARROW_DIMENSIONS.z / 2.0));
var arrowPosition = offset;
// Set arrow rear position
var frontVector = Quat.getFront(arrowRotation);
var frontOffset = Vec3.multiply(frontVector, -ARROW_TIP_OFFSET);
var arrorRearPosition = Vec3.sum(arrowPosition, frontOffset);
this.arrowRearPosition = arrorRearPosition;
//if we're not shooting, we're updating the arrow's orientation
if (shouldReleaseArrow !== true) {
Entities.editEntity(this.arrow, {
position: arrowPosition,
rotation: arrowRotation
});
} else {
//shoot the arrow
var arrowAge = Entities.getEntityProperties(this.arrow, ["age"]).age;
//scale the shot strength by the distance you've pulled the arrow back and set its release velocity to be
// in the direction of the v
var arrowForce = this.scaleArrowShotStrength(stringToNotchDistance);
var handToNotchNorm = Vec3.normalize(handToNotch);
var releaseVelocity = Vec3.multiply(handToNotchNorm, arrowForce);
//make the arrow physical, give it gravity, a lifetime, and set our velocity
var arrowProperties = {
dynamic: true,
collisionless: false,
collidesWith: "static,dynamic,otherAvatar", // workaround: not with kinematic --> no collision with bow
velocity: releaseVelocity,
parentID: NULL_UUID,
gravity: ARROW_GRAVITY,
lifetime: arrowAge + ARROW_LIFETIME,
};
// add a particle effect to make the arrow easier to see as it flies
var arrowParticleProperties = {
accelerationSpread: { x: 0, y: 0, z: 0 },
alpha: 1,
alphaFinish: 0,
alphaSpread: 0,
alphaStart: 0.3,
azimuthFinish: 3.1,
azimuthStart: -3.14159,
color: { red: 255, green: 255, blue: 255 },
colorFinish: { red: 255, green: 255, blue: 255 },
colorSpread: { red: 0, green: 0, blue: 0 },
colorStart: { red: 255, green: 255, blue: 255 },
emitAcceleration: { x: 0, y: 0, z: 0 },
emitDimensions: { x: 0, y: 0, z: 0 },
emitOrientation: { x: -0.7, y: 0.0, z: 0.0, w: 0.7 },
emitRate: 0.01,
emitSpeed: 0,
emitterShouldTrail: 0,
isEmitting: 1,
lifespan: ARROW_PARTICLE_LIFESPAN,
lifetime: ARROW_PARTICLE_LIFESPAN + 1,
maxParticles: 1000,
name: 'arrow-particles',
parentID: this.arrow,
particleRadius: 0.132,
polarFinish: 0,
polarStart: 0,
radiusFinish: 0.35,
radiusSpread: 0,
radiusStart: 0.132,
speedSpread: 0,
textures: Script.resolvePath('arrow-sparkle.png'),
type: 'ParticleEffect'
};
Entities.addEntity(arrowParticleProperties);
// actually shoot the arrow
Entities.editEntity(this.arrow, arrowProperties);
// play the sound of a shooting arrow
this.playShootArrowSound();
Entities.addAction("travel-oriented", this.arrow, {
forward: { x: 0, y: 0, z: -1 },
angularTimeScale: 0.1,
tag: "arrow from hifi-bow",
ttl: ARROW_LIFETIME
});
}
},
scaleArrowShotStrength: function(value) {
var percentage = (value - MIN_ARROW_DISTANCE_FROM_BOW_REST)
/ (MAX_ARROW_DISTANCE_FROM_BOW_REST - MIN_ARROW_DISTANCE_FROM_BOW_REST);
return MIN_ARROW_SPEED + (percentage * (MAX_ARROW_SPEED - MIN_ARROW_SPEED)) ;
},
playStringPullSound: function() {
var audioProperties = {
volume: 0.10,
position: this.bowProperties.position
};
this.stringPullInjector = Audio.playSound(this.stringPullSound, audioProperties);
},
playShootArrowSound: function(sound) {
var audioProperties = {
volume: 0.15,
position: this.bowProperties.position
};
Audio.playSound(this.shootArrowSound, audioProperties);
},
playArrowNotchSound: function() {
var audioProperties = {
volume: 0.15,
position: this.bowProperties.position
};
Audio.playSound(this.arrowNotchSound, audioProperties);
},
changeStringPullSoundVolume: function(pullBackDistance) {
var audioProperties = {
volume: this.scaleSoundVolume(pullBackDistance),
position: this.bowProperties.position
};
this.stringPullInjector.options = audioProperties;
},
scaleSoundVolume: function(value) {
var min1 = SHOT_SCALE.min1;
var max1 = SHOT_SCALE.max1;
var min2 = 0;
var max2 = 0.2;
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
},
handleMessages: function(channel, message, sender) {
if (sender !== MyAvatar.sessionUUID) {
return;
}
if (channel !== 'Hifi-Object-Manipulation') {
return;
}
try {
var data = JSON.parse(message);
var action = data.action;
var hand = data.joint;
var isBackHand = ((_this.hand == "left" && hand == "RightHand") ||
(_this.hand == "right" && hand == "LeftHand"));
if ((action == "equip" || action == "grab") && isBackHand) {
_this.backHandBusy = true;
}
if (action == "release" && isBackHand) {
_this.backHandBusy = false;
}
} catch (e) {
print("WARNING: bow.js -- error parsing Hifi-Object-Manipulation message: " + message);
}
}
};
var bow = new Bow();
Messages.subscribe('Hifi-Object-Manipulation');
Messages.messageReceived.connect(bow.handleMessages);
return bow;
});

View file

@ -0,0 +1,44 @@
{
"Entities": [
{
"clientOnly": 0,
"collisionsWillMove": 1,
"compoundShapeURL": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow_collision_hull.obj",
"created": "2017-02-14T18:54:38Z",
"dimensions": {
"x": 0.039999999105930328,
"y": 1.2999999523162842,
"z": 0.20000000298023224
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -9.8000001907348633,
"z": 0
},
"id": "{73954924-2e18-4787-91a7-092c2afb6242}",
"lastEdited": 1487098438422164,
"lastEditedBy": "{d2da5e17-9125-414d-ac4e-cd7fba6c22f8}",
"modelURL": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow-deadly.fbx",
"name": "WG.Hifi-Bow",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 1.3159027099609375,
"x": -0.65795135498046875,
"y": -0.65795135498046875,
"z": -0.65795135498046875
},
"rotation": {
"w": 0.9717707633972168,
"x": 0.15437555313110352,
"y": -0.10472267866134644,
"z": -0.14421302080154419
},
"script": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/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}]}}}"
}
],
"Version": 67
}

View file

@ -0,0 +1,32 @@
{
"Entities": [ {
"collisionsWillMove": 1,
"compoundShapeURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/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": "http://hifi-content.s3.amazonaws.com/caitlyn/production/bow/bow-deadly.fbx",
"name": "Hifi-Bow",
"rotation": {
"w": 0.9718012809753418,
"x": 0.15440607070922852,
"y": -0.10469216108322144,
"z": -0.14418250322341919
},
"script": "http://hifi-content.s3.amazonaws.com/caitlyn/production/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}]}}}"
}
],
"Version": 57
}

View file

@ -0,0 +1,21 @@
v -0.016461 -0.431491 -0.033447
v -0.007624 0.437384 -0.046243
v 0.011984 -0.424659 -0.03691
v 0.015514 0.425913 -0.028648
v -0.010788 -0.421429 0.093711
v 0.007135 -0.423115 0.098735
v -0.010208 0.425558 0.096005
v 0.006734 0.43913 0.088902
f 1 2 3
f 3 2 4
f 5 6 7
f 7 6 8
f 1 5 2
f 2 5 7
f 3 4 6
f 6 4 8
f 1 3 5
f 5 3 6
f 2 7 4
f 4 7 8

Binary file not shown.

View file

@ -0,0 +1,67 @@
//
// 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
//
var leftHandPosition = {
"x": 0,
"y": 0.0559,
"z": 0.0159
};
var leftHandRotation = Quat.fromPitchYawRollDegrees(90, -90, 0);
var rightHandPosition = Vec3.multiplyVbyV(leftHandPosition, { x: -1, y: 0, z: 0 });
var rightHandRotation = Quat.fromPitchYawRollDegrees(90, 90, 0);
var userData = {
"grabbableKey": {
"grabbable": true
},
"wearable": {
"joints": {
"LeftHand": [
leftHandPosition,
leftHandRotation
],
"RightHand": [
rightHandPosition,
rightHandRotation
]
}
}
};
var id = Entities.addEntity({
"position": MyAvatar.position,
"collisionsWillMove": 1,
"compoundShapeURL": Script.resolvePath("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-deadly.fbx"),
"name": "Hifi-Bow",
"rotation": {
"w": 0.9718012809753418,
"x": 0.15440607070922852,
"y": -0.10469216108322144,
"z": -0.14418250322341919
},
"script": Script.resolvePath("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}]}}}",
"lifetime": 600
});
print("Created bow:", id);

View file

@ -0,0 +1,61 @@
//
// 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 utils */
(function() {
Script.include('utils.js');
function Enemy() {
}
Enemy.prototype = {
preload: function(entityID) {
this.entityID = entityID;
// To avoid sending extraneous messages and checking entities that we've already
// seen, we keep track of which entities we've collided with previously.
this.entityIDsThatHaveCollidedWithMe = [];
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
var data = utils.parseJSON(userData);
if (data !== undefined && data.gameChannel !== undefined) {
this.gameChannel = data.gameChannel;
} else {
print("enemyEntity.js | ERROR: userData does not contain a game channel and/or team number");
}
},
onCollide: function(entityA, entityB, collision) {
if (this.entityIDsThatHaveCollidedWithMe.indexOf(entityB) > -1) {
return;
}
this.entityIDsThatHaveCollidedWithMe.push(entityB);
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
if (colliderName.indexOf("projectile") > -1) {
Messages.sendMessage(this.gameChannel, JSON.stringify({
type: "enemy-killed",
entityID: this.entityID,
position: Entities.getEntityProperties(this.entityID, 'position').position
}));
Entities.deleteEntity(this.entityID);
} else if (colliderName.indexOf("GateCollider") > -1) {
Messages.sendMessage(this.gameChannel, JSON.stringify({
type: "enemy-escaped",
entityID: this.entityID,
position: Entities.getEntityProperties(this.entityID, 'position').position
}));
Entities.deleteEntity(this.entityID);
}
}
};
return new Enemy();
});

View file

@ -0,0 +1,41 @@
//
// 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 utils */
(function() {
Script.include('utils.js');
function Enemy() {
}
Enemy.prototype = {
preload: function(entityID) {
this.entityID = entityID;
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
var data = utils.parseJSON(userData);
if (data !== undefined && data.gameChannel !== undefined) {
this.gameChannel = data.gameChannel;
} else {
print("enemyServerEntity.js | ERROR: userData does not contain a game channel and/or team number");
}
var self = this;
this.heartbeatTimerID = Script.setInterval(function() {
Messages.sendMessage(self.gameChannel, JSON.stringify({
type: "enemy-heartbeat",
entityID: self.entityID,
position: Entities.getEntityProperties(self.entityID, 'position').position
}));
}, 1000);
},
unload: function() {
Script.clearInterval(this.heartbeatTimerID);
}
};
return new Enemy();
});

View file

@ -0,0 +1,877 @@
//
// 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 SHORTBOW_ENTITIES:true */
// This is a copy of the data in shortbow.json, which is an export of the shortbow
// scene.
//
// Because .json can't be Script.include'd directly, the contents are copied over
// to here and exposed as a global variable.
//
SHORTBOW_ENTITIES =
{
"Entities": [
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{02f39515-cab4-41d5-b315-5fb41613f844}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750446,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -5.1684012413024902,
"y": 0.54034698009490967,
"z": -11.257695198059082
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": -1.3604279756546021,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{3eae601e-3c6e-49ab-8f40-dedee32f7573}",
"lastEdited": 1487894036038423,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.1109838485717773,
"y": -1803.69189453125,
"z": -26.774648666381836
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"score\"}"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.71920669078826904,
"y": 3.3160061836242676,
"z": 2.2217941284179688
},
"id": "{04288f77-64df-4323-ac38-9c1960a393a5}",
"lastEdited": 1487893058314990,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx",
"name": "SB.StartButton",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -9.8358345031738281,
"y": 0.45674961805343628,
"z": -13.044205665588379
},
"queryAACube": {
"scale": 4.0558013916015625,
"x": -7.844393253326416,
"y": -1805.730224609375,
"z": -31.195960998535156
},
"rotation": {
"w": 1,
"x": 1.52587890625e-05,
"y": 1.52587890625e-05,
"z": 1.52587890625e-05
},
"script": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js",
"shapeType": "static-mesh",
"type": "Model",
"userData": "{\"grabbableKey\":{\"wantsTrigger\":true}}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{1196096f-bcc9-4b19-970d-605113474c1b}",
"lastEdited": 1487894037900323,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayHighScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.11102294921875,
"y": -1804.95654296875,
"z": -26.77461051940918
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"highscore\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{293c294d-1df5-461e-82a3-66abee852d44}",
"lastEdited": 1487894033695485,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayWave",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431310653686523,
"y": -1803.4239501953125,
"z": -24.204343795776367
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"wave\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{379afa7b-c668-4c4e-b290-e5c37fb3440c}",
"lastEdited": 1487893055310428,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"lineHeight": 0.5,
"name": "SB.DisplayLives",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431692123413086,
"y": -1804.6885986328125,
"z": -24.204303741455078
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"lives\"}"
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{760e81a1-a804-4f5e-9769-393d021fc8fe}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234633,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -1.89238440990448,
"y": -5.3368110656738281,
"z": 11.512755393981934
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.9147146940231323,
"y": -1809.7066650390625,
"z": -4.8219971656799316
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{0a76d0ac-6353-467b-8edc-56417d5a987c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235124,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 3.6569130420684814,
"y": -5.3365960121154785,
"z": 10.01292610168457
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 7.4640579223632812,
"y": -1809.7066650390625,
"z": -6.3216567039489746
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f8549c8a-e646-4feb-bbaf-70e7d5be755a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235339,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.8902750015258789,
"y": -5.3364419937133789,
"z": 10.195274353027344
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 12.697414398193359,
"y": -1809.7066650390625,
"z": -6.1391491889953613
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f3aea4ae-4445-4a2d-8d61-e9fd72f04008}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751269,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.5027251243591309,
"y": 0.54042834043502808,
"z": -11.257777214050293
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.3052481412887573,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{cc1ac907-124b-4372-8c4c-82d175546725}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751135,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 2.7972855567932129,
"y": 0.54059004783630371,
"z": -11.257938385009766
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 6.6052589416503906,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{e25ce690-e267-4c51-80a0-f63a72474b8a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751527,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.17114110291004181,
"y": 0.54050993919372559,
"z": -11.257858276367188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 3.979114294052124,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{91ee2285-38f8-4795-b6bc-7abc8dcde07c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750806,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 5.4656705856323242,
"y": 0.54067152738571167,
"z": -11.258020401000977
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 9.2736434936523438,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{d81e5fae-8a8d-4186-bbd2-0c3ae737b0f2}",
"ignoreForCollisions": 1,
"lastEdited": 1487892552671000,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 9.6099967956542969,
"y": 0.64012420177459717,
"z": -9.9802846908569336
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 13.417934417724609,
"y": -1803.730712890625,
"z": -26.314868927001953
},
"rotation": {
"w": 0.22495110332965851,
"x": -2.9734959753113799e-05,
"y": 0.97437006235122681,
"z": 2.9735869247815572e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{7056e21e-bce6-4c4b-bbca-36bea1dce303}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750993,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.1799373626708984,
"y": 0.54075431823730469,
"z": -11.258102416992188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 11.987911224365234,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{ed073620-e304-4b8e-b12a-5371b595bbf6}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234415,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.3618791103363037,
"y": -2.0691573619842529,
"z": 11.254574775695801
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.4453276395797729,
"y": -1806.43896484375,
"z": -5.0802912712097168
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{32ed7820-c386-4da1-b676-7e63762861a3}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234854,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.64757472276687622,
"y": -2.5217375755310059,
"z": 10.08248233795166
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 4.454803466796875,
"y": -1806.8917236328125,
"z": -6.2522788047790527
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 26.619264602661133,
"y": 14.24090576171875,
"z": 39.351066589355469
},
"id": "{d4c8f577-944d-4d50-ac85-e56387c0ef0a}",
"lastEdited": 1487892440231278,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx",
"name": "SB.Platform",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.097909502685070038,
"y": -1.0163799524307251,
"z": 2.0321114063262939
},
"queryAACube": {
"scale": 49.597328186035156,
"x": -20.681917190551758,
"y": -1829.9739990234375,
"z": -38.890060424804688
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 23.341892242431641,
"y": 12.223045349121094,
"z": 32.012016296386719
},
"friction": 1,
"id": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"lastEdited": 1487892440231832,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx",
"name": "SB.Scoreboard",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 41.461017608642578,
"x": -20.730508804321289,
"y": -20.730508804321289,
"z": -20.730508804321289
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"serverScripts": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js",
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 15.710711479187012,
"y": 4.7783288955688477,
"z": 1.6129581928253174
},
"id": "{84cdff6e-a68d-4bbf-8660-2d6a8c2f1fd0}",
"lastEdited": 1487892440231522,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.GateCollider",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.31728419661521912,
"y": -4.3002614974975586,
"z": -12.531644821166992
},
"queryAACube": {
"scale": 16.50031852722168,
"x": -3.913693904876709,
"y": -1816.709716796875,
"z": -36.905204772949219
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
}
],
"Version": 68
};
// Add LocalPosition to entity data if parent properties are available
var entities = SHORTBOW_ENTITIES.Entities;
var entitiesByID = {};
var i, entity;
for (i = 0; i < entities.length; ++i) {
entity = entities[i];
entitiesByID[entity.id] = entity;
}
for (i = 0; i < entities.length; ++i) {
entity = entities[i];
if (entity.parentID !== undefined) {
var parent = entitiesByID[entity.parentID];
if (parent !== undefined) {
entity.localPosition = Vec3.subtract(entity.position, parent.position);
delete entity.position;
}
}
}

View file

@ -0,0 +1,840 @@
{
"Entities": [
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{02f39515-cab4-41d5-b315-5fb41613f844}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750446,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -5.1684012413024902,
"y": 0.54034698009490967,
"z": -11.257695198059082
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": -1.3604279756546021,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{3eae601e-3c6e-49ab-8f40-dedee32f7573}",
"lastEdited": 1487894036038423,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.1109838485717773,
"y": -1803.69189453125,
"z": -26.774648666381836
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"score\"}"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.71920669078826904,
"y": 3.3160061836242676,
"z": 2.2217941284179688
},
"id": "{04288f77-64df-4323-ac38-9c1960a393a5}",
"lastEdited": 1487893058314990,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx",
"name": "SB.StartButton",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -9.8358345031738281,
"y": 0.45674961805343628,
"z": -13.044205665588379
},
"queryAACube": {
"scale": 4.0558013916015625,
"x": -7.844393253326416,
"y": -1805.730224609375,
"z": -31.195960998535156
},
"rotation": {
"w": 1,
"x": 1.52587890625e-05,
"y": 1.52587890625e-05,
"z": 1.52587890625e-05
},
"script": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js",
"shapeType": "static-mesh",
"type": "Model",
"userData": "{\"grabbableKey\":{\"wantsTrigger\":true}}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{1196096f-bcc9-4b19-970d-605113474c1b}",
"lastEdited": 1487894037900323,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayHighScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.11102294921875,
"y": -1804.95654296875,
"z": -26.77461051940918
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"highscore\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{293c294d-1df5-461e-82a3-66abee852d44}",
"lastEdited": 1487894033695485,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayWave",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431310653686523,
"y": -1803.4239501953125,
"z": -24.204343795776367
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"wave\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{379afa7b-c668-4c4e-b290-e5c37fb3440c}",
"lastEdited": 1487893055310428,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"lineHeight": 0.5,
"name": "SB.DisplayLives",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431692123413086,
"y": -1804.6885986328125,
"z": -24.204303741455078
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"lives\"}"
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{760e81a1-a804-4f5e-9769-393d021fc8fe}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234633,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -1.89238440990448,
"y": -5.3368110656738281,
"z": 11.512755393981934
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.9147146940231323,
"y": -1809.7066650390625,
"z": -4.8219971656799316
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{0a76d0ac-6353-467b-8edc-56417d5a987c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235124,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 3.6569130420684814,
"y": -5.3365960121154785,
"z": 10.01292610168457
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 7.4640579223632812,
"y": -1809.7066650390625,
"z": -6.3216567039489746
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f8549c8a-e646-4feb-bbaf-70e7d5be755a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235339,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.8902750015258789,
"y": -5.3364419937133789,
"z": 10.195274353027344
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 12.697414398193359,
"y": -1809.7066650390625,
"z": -6.1391491889953613
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f3aea4ae-4445-4a2d-8d61-e9fd72f04008}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751269,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.5027251243591309,
"y": 0.54042834043502808,
"z": -11.257777214050293
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.3052481412887573,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{cc1ac907-124b-4372-8c4c-82d175546725}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751135,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 2.7972855567932129,
"y": 0.54059004783630371,
"z": -11.257938385009766
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 6.6052589416503906,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{e25ce690-e267-4c51-80a0-f63a72474b8a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751527,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.17114110291004181,
"y": 0.54050993919372559,
"z": -11.257858276367188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 3.979114294052124,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{91ee2285-38f8-4795-b6bc-7abc8dcde07c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750806,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 5.4656705856323242,
"y": 0.54067152738571167,
"z": -11.258020401000977
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 9.2736434936523438,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{d81e5fae-8a8d-4186-bbd2-0c3ae737b0f2}",
"ignoreForCollisions": 1,
"lastEdited": 1487892552671000,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 9.6099967956542969,
"y": 0.64012420177459717,
"z": -9.9802846908569336
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 13.417934417724609,
"y": -1803.730712890625,
"z": -26.314868927001953
},
"rotation": {
"w": 0.22495110332965851,
"x": -2.9734959753113799e-05,
"y": 0.97437006235122681,
"z": 2.9735869247815572e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{7056e21e-bce6-4c4b-bbca-36bea1dce303}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750993,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.1799373626708984,
"y": 0.54075431823730469,
"z": -11.258102416992188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 11.987911224365234,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{ed073620-e304-4b8e-b12a-5371b595bbf6}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234415,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.3618791103363037,
"y": -2.0691573619842529,
"z": 11.254574775695801
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.4453276395797729,
"y": -1806.43896484375,
"z": -5.0802912712097168
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{32ed7820-c386-4da1-b676-7e63762861a3}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234854,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.64757472276687622,
"y": -2.5217375755310059,
"z": 10.08248233795166
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 4.454803466796875,
"y": -1806.8917236328125,
"z": -6.2522788047790527
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 26.619264602661133,
"y": 14.24090576171875,
"z": 39.351066589355469
},
"id": "{d4c8f577-944d-4d50-ac85-e56387c0ef0a}",
"lastEdited": 1487892440231278,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx",
"name": "SB.Platform",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.097909502685070038,
"y": -1.0163799524307251,
"z": 2.0321114063262939
},
"queryAACube": {
"scale": 49.597328186035156,
"x": -20.681917190551758,
"y": -1829.9739990234375,
"z": -38.890060424804688
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 23.341892242431641,
"y": 12.223045349121094,
"z": 32.012016296386719
},
"friction": 1,
"id": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"lastEdited": 1487892440231832,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx",
"name": "SB.Scoreboard",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 41.461017608642578,
"x": -20.730508804321289,
"y": -20.730508804321289,
"z": -20.730508804321289
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"serverScripts": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js",
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 15.710711479187012,
"y": 4.7783288955688477,
"z": 1.6129581928253174
},
"id": "{84cdff6e-a68d-4bbf-8660-2d6a8c2f1fd0}",
"lastEdited": 1487892440231522,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.GateCollider",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.31728419661521912,
"y": -4.3002614974975586,
"z": -12.531644821166992
},
"queryAACube": {
"scale": 16.50031852722168,
"x": -3.913693904876709,
"y": -1816.709716796875,
"z": -36.905204772949219
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
}
],
"Version": 68
}

View file

@ -0,0 +1,621 @@
//
// 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.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/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",
"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;
}
}
}
};

View file

@ -0,0 +1,42 @@
//
// 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 TEMPLATES:true, SHORTBOW_ENTITIES, ShortbowGameManager */
(function() {
Script.include('utils.js');
Script.include('shortbow.js');
Script.include('shortbowGameManager.js');
TEMPLATES = SHORTBOW_ENTITIES.Entities;
this.entityID = null;
var gameManager = null;
this.preload = function(entityID) {
this.entityID = entityID;
var bowPositions = [];
var spawnPositions = [];
for (var i = 0; i < TEMPLATES.length; ++i) {
var template = TEMPLATES[i];
if (template.name === "SB.BowSpawn") {
bowPositions.push(template.localPosition);
} else if (template.name === "SB.EnemySpawn") {
spawnPositions.push(template.localPosition);
}
}
gameManager = new ShortbowGameManager(this.entityID, bowPositions, spawnPositions);
};
this.unload = function() {
if (gameManager) {
gameManager.cleanup();
gameManager = null;
}
};
});

View file

@ -0,0 +1,210 @@
//
// 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 utils, SHORTBOW_ENTITIES, TEMPLATES:true */
Script.include('utils.js');
Script.include('shortbow.js');
Script.include('shortbowGameManager.js');
TEMPLATES = SHORTBOW_ENTITIES.Entities;
// Merge two objects into a new object. If a key name appears in both a and b,
// the value in a will be used.
//
// @param {object} a
// @param {object} b
// @returns {object} The new object
function mergeObjects(a, b) {
var obj = {};
var key;
for (key in b) {
obj[key] = b[key];
}
for (key in a) {
obj[key] = a[key];
}
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) {
var template = getTemplate(templateName);
if (template === null) {
print("ERROR, unknown template name:", templateName);
return null;
}
print("Spawning: ", templateName);
var properties = mergeObjects(overrides, template);
return Entities.addEntity(properties);
}
function spawnTemplates(templateName, overrides) {
var templates = getTemplates(templateName);
if (template.length === 0) {
print("ERROR, unknown template name:", templateName);
return [];
}
var spawnedEntities = [];
for (var i = 0; i < templates.length; ++i) {
print("Spawning: ", templateName);
var properties = mergeObjects(overrides, templates[i]);
spawnedEntities.push(Entities.addEntity(properties));
}
return spawnedEntities;
}
// 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) {
for (var i = 0; i < TEMPLATES.length; ++i) {
if (TEMPLATES[i].name === name) {
return TEMPLATES[i];
}
}
return null;
}
function getTemplates(name) {
var templates = [];
for (var i = 0; i < TEMPLATES.length; ++i) {
if (TEMPLATES[i].name === name) {
templates.push(TEMPLATES[i]);
}
}
return templates;
}
// Cleanup Shortbow template data
for (var i = 0; i < TEMPLATES.length; ++i) {
var template = TEMPLATES[i];
// Fixup model url
if (template.type === "Model") {
var urlParts = template.modelURL.split("/");
var filename = urlParts[urlParts.length - 1];
var newURL = Script.resolvePath("models/" + filename);
print("Updated url", template.modelURL, "to", newURL);
template.modelURL = newURL;
}
}
var entityIDs = [];
var scoreboardID = null;
var buttonID = null;
var waveDisplayID = null;
var scoreDisplayID = null;
var highScoreDisplayID = null;
var livesDisplayID = null;
var platformID = null;
function createLocalGame() {
var rootPosition = utils.findSurfaceBelowPosition(MyAvatar.position);
rootPosition.y += 6.11;
scoreboardID = spawnTemplate("SB.Scoreboard", {
position: rootPosition
});
entityIDs.push(scoreboardID);
// Create start button
buttonID = spawnTemplate("SB.StartButton", {
parentID: scoreboardID,
script: Script.resolvePath("startGameButtonClientEntity.js"),
userData: JSON.stringify({
grabbableKey: {
wantsTrigger: true
}
})
});
entityIDs.push(buttonID);
waveDisplayID = spawnTemplate("SB.DisplayWave", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "wave"
})
});
entityIDs.push(waveDisplayID);
scoreDisplayID = spawnTemplate("SB.DisplayScore", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "score"
})
});
entityIDs.push(scoreDisplayID);
livesDisplayID = spawnTemplate("SB.DisplayLives", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "lives"
})
});
entityIDs.push(livesDisplayID);
highScoreDisplayID = spawnTemplate("SB.DisplayHighScore", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "highscore"
})
});
entityIDs.push(highScoreDisplayID);
platformID = spawnTemplate("SB.Platform", {
parentID: scoreboardID
});
entityIDs.push(platformID);
spawnTemplate("SB.GateCollider", {
parentID: scoreboardID,
visible: false
});
entityIDs.push(platformID);
Entities.editEntity(scoreboardID, {
serverScripts: Script.resolvePath('shortbowServerEntity.js')
});
spawnTemplates("SB.BowSpawn", {
parentID: scoreboardID,
visible: false
});
spawnTemplates("SB.EnemySpawn", {
parentID: scoreboardID,
visible: false
});
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));
}
}
}
createLocalGame();
Script.stop();

View file

@ -0,0 +1,41 @@
//
// 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 utils */
(function() {
Script.include('utils.js');
function StartButton() {
}
StartButton.prototype = {
preload: function(entityID) {
this.entityID = entityID;
this.commChannel = "shortbow-" + Entities.getEntityProperties(entityID, 'parentID').parentID;
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
},
signalAC: function() {
Messages.sendMessage(this.commChannel, JSON.stringify({
type: 'start-game'
}));
},
onCollide: function(entityA, entityB, collision) {
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
if (colliderName.indexOf("projectile") > -1) {
this.signalAC();
}
}
};
StartButton.prototype.startNearTrigger = StartButton.prototype.signalAC;
StartButton.prototype.startFarTrigger = StartButton.prototype.signalAC;
StartButton.prototype.clickDownOnEntity = StartButton.prototype.signalAC;
return new StartButton();
});

View file

@ -0,0 +1,57 @@
//
// 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 utils:true */
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,
NOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof NOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
NOP.prototype = this.prototype;
}
fBound.prototype = new NOP();
return fBound;
};
}
utils = {
parseJSON: function(json) {
try {
return JSON.parse(json);
} catch (e) {
return undefined;
}
},
findSurfaceBelowPosition: function(pos) {
var result = Entities.findRayIntersection({
origin: pos,
direction: { x: 0.0, y: -1.0, z: 0.0 }
}, true);
if (result.intersects) {
return result.intersection;
}
return pos;
}
};