mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-08 00:42:22 +02:00
477 lines
14 KiB
JavaScript
477 lines
14 KiB
JavaScript
// sword.js
|
|
// examples
|
|
//
|
|
// Created by Seth Alves on 2015-6-10
|
|
// Copyright 2015 High Fidelity, Inc.
|
|
//
|
|
// Allow avatar to hold a sword
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
"use strict";
|
|
/*jslint vars: true*/
|
|
var Script, Entities, MyAvatar, Window, Overlays, Controller, Vec3, Quat, print, ToolBar, Settings; // Referenced globals provided by High Fidelity.
|
|
Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.js");
|
|
var zombieGameScriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/zombieFight.js?v2";
|
|
// var zombieGameScriptURL = "zombieFight.js";
|
|
Script.include(zombieGameScriptURL);
|
|
|
|
|
|
var zombieFight = new ZombieFight();
|
|
|
|
var hand = "right";
|
|
var zombieFight;
|
|
var nullActionID = "00000000-0000-0000-0000-000000000000";
|
|
var controllerID;
|
|
var controllerActive;
|
|
var swordID = null;
|
|
var actionID = nullActionID;
|
|
var dimensions = {
|
|
x: 0.3,
|
|
y: 0.15,
|
|
z: 2.0
|
|
};
|
|
var BUTTON_SIZE = 32;
|
|
|
|
var health = 100;
|
|
var healthLossOnHit = 10;
|
|
|
|
var swordModel = "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx";
|
|
// var swordCollisionShape = "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.obj";
|
|
var swordCollisionShape = "https://hifi-public.s3.amazonaws.com/eric/models/noHandleSwordCollisionShape.obj?=v2";
|
|
var swordCollisionSoundURL = "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/swordStrike1.wav";
|
|
var avatarCollisionSoundURL = "https://hifi-public.s3.amazonaws.com/eric/sounds/blankSound.wav"; //Just to avoid no collision callback bug
|
|
var zombieGameScriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/zombieFight.js";
|
|
var whichModel = "sword";
|
|
var originalAvatarCollisionSound;
|
|
|
|
var avatarCollisionSounds = [SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/avatarHit.wav"), SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/avatarHit2.wav?=v2")];
|
|
|
|
var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.sword.toolbar", function() {
|
|
return {
|
|
x: 100,
|
|
y: 380
|
|
};
|
|
});
|
|
|
|
var gameStarted = false;
|
|
|
|
var SWORD_IMAGE = "https://hifi-public.s3.amazonaws.com/images/sword/sword.svg"; // Toggle between brandishing/sheathing sword (creating if necessary)
|
|
var TARGET_IMAGE = "https://hifi-public.s3.amazonaws.com/images/sword/dummy2.svg"; // Create a target dummy
|
|
var CLEANUP_IMAGE = "http://s3.amazonaws.com/hifi-public/images/delete.png"; // Remove sword and all target dummies.f
|
|
var swordButton = toolBar.addOverlay("image", {
|
|
width: BUTTON_SIZE,
|
|
height: BUTTON_SIZE,
|
|
imageURL: SWORD_IMAGE,
|
|
alpha: 1
|
|
});
|
|
var targetButton = toolBar.addOverlay("image", {
|
|
width: BUTTON_SIZE,
|
|
height: BUTTON_SIZE,
|
|
imageURL: TARGET_IMAGE,
|
|
alpha: 1
|
|
});
|
|
|
|
var cleanupButton = toolBar.addOverlay("image", {
|
|
width: BUTTON_SIZE,
|
|
height: BUTTON_SIZE,
|
|
imageURL: CLEANUP_IMAGE,
|
|
alpha: 1
|
|
});
|
|
|
|
var flasher;
|
|
|
|
var leftHandClick = 14;
|
|
var leftTriggerValue = 0;
|
|
var prevLeftTriggerValue = 0;
|
|
|
|
|
|
var LEFT = 0;
|
|
var RIGHT = 1;
|
|
|
|
var leftPalm = 2 * LEFT;
|
|
var rightPalm = 2 * RIGHT;
|
|
var rightHandClick = 15;
|
|
var prevRightTriggerValue = 0;
|
|
var rightTriggerValue = 0;
|
|
var TRIGGER_THRESHOLD = 0.2;
|
|
|
|
var swordHeld = false;
|
|
|
|
function clearFlash() {
|
|
if (!flasher) {
|
|
return;
|
|
}
|
|
Script.clearTimeout(flasher.timer);
|
|
Overlays.deleteOverlay(flasher.overlay);
|
|
flasher = null;
|
|
}
|
|
|
|
function flash(color) {
|
|
return;
|
|
clearFlash();
|
|
flasher = {};
|
|
flasher.overlay = Overlays.addOverlay("text", {
|
|
backgroundColor: color,
|
|
backgroundAlpha: 0.7,
|
|
width: Window.innerWidth,
|
|
height: Window.innerHeight
|
|
});
|
|
flasher.timer = Script.setTimeout(clearFlash, 500);
|
|
}
|
|
|
|
|
|
var display2d, display3d;
|
|
|
|
function trackAvatarWithText() {
|
|
Entities.editEntity(display3d, {
|
|
position: Vec3.sum(MyAvatar.position, {
|
|
x: 0,
|
|
y: 1.5,
|
|
z: 0
|
|
}),
|
|
rotation: Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollDegrees(0, 180, 0))
|
|
});
|
|
}
|
|
|
|
function updateDisplay() {
|
|
var text = health.toString();
|
|
if (!display2d) {
|
|
display2d = Overlays.addOverlay("text", {
|
|
text: text,
|
|
font: {
|
|
size: 20
|
|
},
|
|
color: {
|
|
red: 0,
|
|
green: 255,
|
|
blue: 0
|
|
},
|
|
backgroundColor: {
|
|
red: 100,
|
|
green: 100,
|
|
blue: 100
|
|
}, // Why doesn't this and the next work?
|
|
backgroundAlpha: 0.9,
|
|
x: toolBar.x - 5, // I'd like to add the score to the toolBar and have it drag with it, but toolBar doesn't support text (just buttons).
|
|
y: toolBar.y - 30 // So next best thing is to position it each time as if it were on top.
|
|
});
|
|
display3d = Entities.addEntity({
|
|
name: MyAvatar.displayName + " score",
|
|
textColor: {
|
|
red: 255,
|
|
green: 255,
|
|
blue: 255
|
|
},
|
|
type: "Text",
|
|
text: text,
|
|
lineHeight: 0.14,
|
|
backgroundColor: {
|
|
red: 64,
|
|
green: 64,
|
|
blue: 64
|
|
},
|
|
dimensions: {
|
|
x: 0.3,
|
|
y: 0.2,
|
|
z: 0.01
|
|
},
|
|
});
|
|
Script.update.connect(trackAvatarWithText);
|
|
} else {
|
|
Overlays.editOverlay(display2d, {
|
|
text: text
|
|
});
|
|
Entities.editEntity(display3d, {
|
|
text: text
|
|
});
|
|
}
|
|
}
|
|
|
|
function removeDisplay() {
|
|
if (display2d) {
|
|
Overlays.deleteOverlay(display2d);
|
|
display2d = null;
|
|
Script.update.disconnect(trackAvatarWithText);
|
|
Entities.deleteEntity(display3d);
|
|
display3d = null;
|
|
}
|
|
}
|
|
|
|
|
|
function gotHit(collision) {
|
|
Audio.playSound(avatarCollisionSounds[randInt(0, avatarCollisionSounds.length)], {
|
|
position: MyAvatar.position,
|
|
volume: 0.5
|
|
});
|
|
health -= healthLossOnHit;
|
|
if (health <= 30) {
|
|
Overlays.editOverlay(display2d, {
|
|
color: {
|
|
red: 200,
|
|
green: 10,
|
|
blue: 10
|
|
}
|
|
});
|
|
}
|
|
|
|
if (health <= 0 && zombieFight) {
|
|
zombieFight.loseGame();
|
|
}
|
|
flash({
|
|
red: 255,
|
|
green: 0,
|
|
blue: 0
|
|
});
|
|
updateDisplay();
|
|
}
|
|
|
|
|
|
function isFighting() {
|
|
return swordID && (actionID !== nullActionID);
|
|
}
|
|
|
|
|
|
var inHand = false;
|
|
|
|
|
|
function isControllerActive() {
|
|
// I don't think the hydra API provides any reliable way to know whether a particular controller is active. Ask for both.
|
|
controllerActive = (Vec3.length(MyAvatar.leftHandPose.translation) > 0) || Vec3.length(MyAvatar.rightHandPose.translation) > 0;
|
|
return controllerActive;
|
|
}
|
|
|
|
|
|
function removeSword() {
|
|
if (swordID) {
|
|
print('deleting action ' + actionID + ' and entity ' + swordID);
|
|
Entities.deleteAction(swordID, actionID);
|
|
Entities.deleteEntity(swordID);
|
|
swordID = null;
|
|
actionID = nullActionID;
|
|
Controller.mouseMoveEvent.disconnect(mouseMoveEvent);
|
|
MyAvatar.collisionWithEntity.disconnect(gotHit);
|
|
// removeEventhHandler happens automatically when the entity is deleted.
|
|
}
|
|
inHand = false;
|
|
if (originalAvatarCollisionSound !== undefined) {
|
|
MyAvatar.collisionSoundURL = originalAvatarCollisionSound;
|
|
}
|
|
removeDisplay();
|
|
swordHeld = false;
|
|
}
|
|
|
|
function cleanUp(leaveButtons) {
|
|
if (!leaveButtons) {
|
|
toolBar.cleanup();
|
|
}
|
|
removeSword();
|
|
gameStarted = false;
|
|
zombieFight.cleanup();
|
|
}
|
|
|
|
function makeSword() {
|
|
var swordPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(5, Quat.getFront(MyAvatar.orientation)));
|
|
var orientationAdjustment = Quat.fromPitchYawRollDegrees(90, 0, 0);
|
|
|
|
swordID = Entities.addEntity({
|
|
type: "Model",
|
|
name: "sword",
|
|
modelURL: swordModel,
|
|
compoundShapeURL: swordCollisionShape,
|
|
dimensions: dimensions,
|
|
position: swordPosition,
|
|
rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
|
|
damping: 0.1,
|
|
collisionSoundURL: swordCollisionSoundURL,
|
|
restitution: 0.01,
|
|
dynamic: true,
|
|
});
|
|
|
|
if (originalAvatarCollisionSound === undefined) {
|
|
originalAvatarCollisionSound = MyAvatar.collisionSoundURL; // We won't get MyAvatar.collisionWithEntity unless there's a sound URL. (Bug.)
|
|
SoundCache.getSound(avatarCollisionSoundURL); // Interface does not currently "preload" this? (Bug?)
|
|
}
|
|
|
|
if (!isControllerActive()) {
|
|
grabSword("right");
|
|
}
|
|
MyAvatar.collisionSoundURL = avatarCollisionSoundURL;
|
|
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
|
MyAvatar.collisionWithEntity.connect(gotHit);
|
|
updateDisplay();
|
|
}
|
|
|
|
|
|
|
|
function grabSword(hand) {
|
|
if (!swordID) {
|
|
print("Create a sword by clicking on sword icon!")
|
|
return;
|
|
}
|
|
var handRotation;
|
|
if (hand === "right") {
|
|
handRotation = MyAvatar.rightHandPose.rotation;
|
|
|
|
} else if (hand === "left") {
|
|
handRotation = MyAvatar.leftHandPose.rotation;
|
|
}
|
|
var swordRotation = Entities.getEntityProperties(swordID).rotation;
|
|
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), swordRotation);
|
|
actionID = Entities.addAction("hold", swordID, {
|
|
relativePosition: {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: -dimensions.z * 0.5
|
|
},
|
|
relativeRotation: offsetRotation,
|
|
hand: hand,
|
|
timeScale: 0.05
|
|
});
|
|
if (actionID === nullActionID) {
|
|
print('*** FAILED TO MAKE SWORD ACTION ***');
|
|
cleanUp();
|
|
} else {
|
|
swordHeld = true;
|
|
}
|
|
}
|
|
|
|
function releaseSword() {
|
|
Entities.deleteAction(swordID, actionID);
|
|
actionID = nullActionID;
|
|
Entities.editEntity(swordID, {
|
|
velocity: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
},
|
|
angularVelocity: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
}
|
|
});
|
|
swordHeld = false;
|
|
}
|
|
|
|
function update() {
|
|
updateControllerState();
|
|
|
|
}
|
|
|
|
function updateControllerState() {
|
|
rightTriggerValue = Controller.getValue(Controller.Standard.RT);
|
|
leftTriggerValue =Controller.getValue(Controller.Standard.LT);
|
|
|
|
if (rightTriggerValue > TRIGGER_THRESHOLD && !swordHeld) {
|
|
grabSword("right")
|
|
} else if (rightTriggerValue < TRIGGER_THRESHOLD && prevRightTriggerValue > TRIGGER_THRESHOLD && swordHeld) {
|
|
releaseSword();
|
|
}
|
|
|
|
if (leftTriggerValue > TRIGGER_THRESHOLD && !swordHeld) {
|
|
grabSword("left")
|
|
} else if (leftTriggerValue < TRIGGER_THRESHOLD && prevLeftTriggerValue > TRIGGER_THRESHOLD && swordHeld) {
|
|
releaseSword();
|
|
}
|
|
|
|
prevRightTriggerValue = rightTriggerValue;
|
|
prevLeftTriggerValue = leftTriggerValue;
|
|
}
|
|
|
|
randFloat = function(low, high) {
|
|
return low + Math.random() * (high - low);
|
|
}
|
|
|
|
|
|
randInt = function(low, high) {
|
|
return Math.floor(randFloat(low, high));
|
|
}
|
|
|
|
function positionSword(swordOrientation) {
|
|
var reorient = Quat.fromPitchYawRollDegrees(0, -90, 0);
|
|
var baseOffset = {
|
|
x: -dimensions.z * 0.8,
|
|
y: 0,
|
|
z: 0
|
|
};
|
|
var offset = Vec3.multiplyQbyV(reorient, baseOffset);
|
|
swordOrientation = Quat.multiply(reorient, swordOrientation);
|
|
inHand = false;
|
|
Entities.updateAction(swordID, actionID, {
|
|
relativePosition: offset,
|
|
relativeRotation: swordOrientation,
|
|
hand: "right"
|
|
});
|
|
}
|
|
|
|
function resetToHand() { // For use with controllers, puts the sword in contact with the hand.
|
|
// Maybe coordinate with positionSword?
|
|
if (inHand) { // Optimization: bail if we're already inHand.
|
|
return;
|
|
}
|
|
print('Reset to hand');
|
|
Entities.updateAction(swordID, actionID, {
|
|
relativePosition: {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: -dimensions.z * 0.5
|
|
},
|
|
relativeRotation: Quat.fromVec3Degrees({
|
|
x: 45.0,
|
|
y: 0.0,
|
|
z: 0.0
|
|
}),
|
|
hand: "right", // It should not be necessary to repeat these two, but there seems to be a bug in that that
|
|
timeScale: 0.05 // they do not retain their earlier values if you don't repeat them.
|
|
});
|
|
inHand = true;
|
|
}
|
|
|
|
function mouseMoveEvent(event) {
|
|
//When a controller like the hydra gives a mouse event, the x/y is not meaningful to us, but we can detect with a truty deviceID
|
|
if (event.deviceID || !isFighting() || isControllerActive()) {
|
|
resetToHand();
|
|
return;
|
|
}
|
|
var windowCenterX = Window.innerWidth / 2;
|
|
var windowCenterY = Window.innerHeight / 2;
|
|
var mouseXCenterOffset = event.x - windowCenterX;
|
|
var mouseYCenterOffset = event.y - windowCenterY;
|
|
var mouseXRatio = mouseXCenterOffset / windowCenterX;
|
|
var mouseYRatio = mouseYCenterOffset / windowCenterY;
|
|
|
|
var swordOrientation = Quat.fromPitchYawRollDegrees(mouseYRatio * 90, mouseXRatio * 90, 0);
|
|
positionSword(swordOrientation);
|
|
}
|
|
|
|
|
|
function onClick(event) {
|
|
switch (Overlays.getOverlayAtPoint(event)) {
|
|
case swordButton:
|
|
if (!swordID) {
|
|
makeSword();
|
|
} else {
|
|
removeSword();
|
|
}
|
|
break;
|
|
case targetButton:
|
|
if (gameStarted) {
|
|
return;
|
|
}
|
|
Script.include("https://hifi-public.s3.amazonaws.com/eric/scripts/zombieFight.js");
|
|
zombieFight = new ZombieFight();
|
|
zombieFight.initiateZombieApocalypse();
|
|
gameStarted = true;
|
|
|
|
break;
|
|
case cleanupButton:
|
|
cleanUp('leaveButtons');
|
|
break;
|
|
}
|
|
}
|
|
|
|
Script.scriptEnding.connect(cleanUp);
|
|
Script.update.connect(update);
|
|
Controller.mousePressEvent.connect(onClick);
|