376 lines
No EOL
13 KiB
JavaScript
376 lines
No EOL
13 KiB
JavaScript
//
|
|
// Hookgun.js
|
|
// examples
|
|
//
|
|
// Created by Matti 'Menithal' Lahtinen on 5/8/16.
|
|
// 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
|
|
//
|
|
|
|
(function() {
|
|
var BULLET_GRAVITY = {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
};
|
|
var BULLET_DIMENSIONS = {
|
|
x: 0.02,
|
|
y: 0.02,
|
|
z: 0.5
|
|
};
|
|
var BULLET_COLOR = {
|
|
red: 255,
|
|
green: 255,
|
|
blue: 255
|
|
};
|
|
var BULLET_LINEAR_DAMPING = 0;
|
|
var RELOAD_TIME = 4;
|
|
var RELOAD_THRESHOLD = 0.95;
|
|
|
|
var GUN_TIP_FWD_OFFSET = 0.08;
|
|
var BULLET_SPAWN_OFFSET = 0.4;
|
|
var GUN_TIP_UP_OFFSET = 0;
|
|
|
|
var GUN_FORCE = 80;
|
|
var HOOK_LIFE_TIME = 20;
|
|
var MAX_DISTANCE = 150;
|
|
var TRIGGER_CONTROLS = [
|
|
Controller.Standard.LT,
|
|
Controller.Standard.RT
|
|
];
|
|
|
|
var SHOOTING_1_SOUND = SoundCache.getSound("http://www.norteclabs.com/HF/resources/HookGunFire1.wav");
|
|
var SHOOTING_2_SOUND = SoundCache.getSound("http://www.norteclabs.com/HF/resources/HookGunFire2.wav");
|
|
|
|
var ROPE_SOUND = SoundCache.getSound("http://www.norteclabs.com/HF/resources/RopeSwoosh.wav");
|
|
|
|
var PULL_1_LOOP_SOUND = SoundCache.getSound("http://www.norteclabs.com/HF/resources/RolePull.wav");
|
|
var PULL_2_LOOP_SOUND = SoundCache.getSound("http://www.norteclabs.com/HF/resources/WindWindingRopePull.wav");
|
|
var soundInjector;
|
|
/*
|
|
Helper to determine if js object is empty
|
|
*/
|
|
function emptyObjectCheck(obj) {
|
|
return Object.keys(obj).length === 0 && JSON.stringify(obj) === JSON.stringify({});
|
|
}
|
|
// Gets the weapon uuid, and returns an object with found, and if it is true,
|
|
// has additional info such as the velocity, where it should spawn, but also provides information about weapon
|
|
function getWeaponBulletInfo(uuid) {
|
|
|
|
var properties = Entities.getEntityProperties(uuid, ["rotation", "position"])
|
|
|
|
if (emptyObjectCheck(properties)) {
|
|
return {
|
|
found: false
|
|
};
|
|
}
|
|
|
|
|
|
var frontVector = Quat.getFront(properties.rotation);
|
|
var frontOffset = Vec3.multiply(frontVector, GUN_TIP_FWD_OFFSET);
|
|
var bulletSpawnOffset = Vec3.multiply(frontVector, GUN_TIP_FWD_OFFSET + BULLET_SPAWN_OFFSET);
|
|
var tip = Vec3.multiply(frontVector, GUN_TIP_FWD_OFFSET + BULLET_SPAWN_OFFSET);
|
|
var upVector = Quat.getUp(properties.rotation);
|
|
var upOffset = Vec3.multiply(upVector, GUN_TIP_UP_OFFSET);
|
|
|
|
var forwardVec = Quat.getFront(Quat.multiply(properties.rotation, Quat.fromPitchYawRollDegrees(0, 0, 0)));
|
|
forwardVec = Vec3.normalize(forwardVec);
|
|
forwardVec = Vec3.multiply(forwardVec, GUN_FORCE);
|
|
forwardVec = Vec3.sum(MyAvatar.velocity, forwardVec); // lets keep it relative
|
|
var gunTipPosition = Vec3.sum(properties.position, frontOffset);
|
|
var bulletSpawnPosition = Vec3.sum(properties.position, bulletSpawnOffset);
|
|
|
|
return {
|
|
found: true,
|
|
velocity: forwardVec,
|
|
tipSpawn: bulletSpawnPosition,
|
|
tip: gunTipPosition,
|
|
rotation: properties.rotation,
|
|
position: properties.position
|
|
};
|
|
|
|
}
|
|
/*
|
|
Hook prototype constructor
|
|
Handles everything from the hook hitting and rope attached to it
|
|
as a child.
|
|
*/
|
|
function Hook(sourceId) {
|
|
this.sourceId = sourceId;
|
|
print("Creating Hook");
|
|
var weapon = getWeaponBulletInfo(this.sourceId);
|
|
if (!weapon.found) {
|
|
print("Invalid sourceID provided.")
|
|
return;
|
|
}
|
|
this.hookId = Entities.addEntity({
|
|
type: "Sphere",
|
|
name: "Hook",
|
|
visible: 0,
|
|
color: BULLET_COLOR,
|
|
dimensions: BULLET_DIMENSIONS,
|
|
damping: BULLET_LINEAR_DAMPING,
|
|
gravity: BULLET_GRAVITY,
|
|
dynamic: true,
|
|
lifetime: HOOK_LIFE_TIME,
|
|
rotation: weapon.rotation,
|
|
collisionMask:15,
|
|
collidesWith:"static,dynamic,kinematic,myAvatar,",
|
|
position: weapon.tipSpawn,
|
|
velocity: weapon.velocity,
|
|
restitution: 0
|
|
});
|
|
this.ropeId = Entities.addEntity({
|
|
collisionless: true,
|
|
type: "Box",
|
|
name: "Rope",
|
|
dimensions: {
|
|
x: 0.01,
|
|
y: 0.01,
|
|
z: 0.01
|
|
},
|
|
color: {
|
|
red: 0,
|
|
green: 0,
|
|
blue: 0
|
|
},
|
|
lifetime: HOOK_LIFE_TIME,
|
|
position: Entities.getEntityProperties(this.hookId, ["position"]).position
|
|
});
|
|
|
|
this.secured = false;
|
|
var hook = this;
|
|
var timer = 0;
|
|
this.updateThread = function(dt) {
|
|
timer += dt;
|
|
try {
|
|
var state = getWeaponBulletInfo(hook.sourceId)
|
|
var hookObject = Entities.getEntityProperties(hook.hookId, ["position"]);
|
|
|
|
if (!state.found) {
|
|
hook.cleanUp(hook);
|
|
return;
|
|
}
|
|
|
|
hook.connectRope(state.tip, hookObject.position, hook);
|
|
|
|
if (timer > HOOK_LIFE_TIME * 0.90) {
|
|
hook.cleanup(hook);
|
|
}
|
|
} catch (e) {
|
|
// Backup plan. If hook is deleted. just cancel self.
|
|
print("Failed on cycle. Disconnecting ")
|
|
hook.cleanup(hook);
|
|
Script.update.disconnect(hook.updateThread);
|
|
}
|
|
|
|
};
|
|
|
|
this.collision = function(entityA, entityB, collision) {
|
|
Script.removeEventHandler(entityA, "collisionWithEntity", hook.collide);
|
|
var entity = Entities.getEntityProperties(entityA);
|
|
|
|
var collisionEntity = Entities.getEntityProperties(entityB, ["userdata"]);
|
|
|
|
|
|
/* if (collisionEntity.userData !== "") {
|
|
var userData = JSON.parse(collisionEntity.userData)
|
|
var isHookable = userData.hookable !== undefined ? (userData.hookable !== undefined? userData.hookable:true) : true;
|
|
if (!isHookable) {
|
|
print("Collided", collisionEntity.id,scollisionEntity.userData);
|
|
hook.cleanup(hook);
|
|
return;
|
|
}
|
|
}*/
|
|
|
|
hook.secured = true;
|
|
Entities.editEntity(entityA, {
|
|
parentID: entityB,
|
|
position: collision.contactPoint,
|
|
dynamic: 0,
|
|
collisionless: 1,
|
|
velocity: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
}
|
|
});
|
|
};
|
|
|
|
Script.addEventHandler(this.hookId, "collisionWithEntity", this.collision);
|
|
Script.update.connect(this.updateThread);
|
|
};
|
|
/*
|
|
Hook Prototype definition
|
|
Due to some issues with keeping relativity, each function should keep context
|
|
*/
|
|
Hook.prototype = {
|
|
connectRope: function(fromPosition, toPosition, context) {
|
|
var ropeObject = Entities.getEntityProperties(context.ropeId, ["dimensions"]);
|
|
|
|
var rotation = Quat.multiply(Quat.lookAt(toPosition, fromPosition, Vec3.UP), {
|
|
w: 0.707,
|
|
z: 0,
|
|
x: 0.707,
|
|
y: 0
|
|
});
|
|
var distance = Vec3.distance(fromPosition, toPosition);
|
|
if (distance >= MAX_DISTANCE) {
|
|
context.cleanup(context);
|
|
return;
|
|
}
|
|
|
|
var scale = ropeObject.dimensions;
|
|
scale.y = distance;
|
|
var between = Vec3.mix(fromPosition, toPosition, 0.5);
|
|
|
|
Entities.editEntity(this.ropeId, {
|
|
dimensions: scale,
|
|
rotation: rotation,
|
|
position: between
|
|
});
|
|
|
|
},
|
|
cleanup: function(context) {
|
|
context.secured = false;
|
|
Entities.deleteEntity(context.ropeId);
|
|
Entities.deleteEntity(context.hookId);
|
|
Script.update.disconnect(context.updateThread);
|
|
context.ropeId = null;
|
|
context.hookId = null;
|
|
}
|
|
}
|
|
|
|
/*
|
|
HookGun prototype constructor.
|
|
Bound to the entity that this script is attached to. Each Hookgun has a hook object
|
|
that defines the hook when fired, and if it is secured to an object or not. Based on popgun script
|
|
*/
|
|
var GRAVITY = {
|
|
x: 0,
|
|
y: -9,
|
|
z: 0
|
|
};
|
|
var GlobalThrust = GRAVITY; // This is shared between all JS hookgun running locally.
|
|
var ActiveHand = {
|
|
0: {
|
|
secured: false
|
|
},
|
|
1: {
|
|
secured: false
|
|
}
|
|
};
|
|
var ActiveEquip = false;
|
|
|
|
function HookGun() {
|
|
this.hook = {
|
|
secured: false
|
|
};
|
|
return;
|
|
};
|
|
HookGun.prototype = {
|
|
hand: null,
|
|
canShoot: true,
|
|
hookBullet: {
|
|
secured: false
|
|
},
|
|
canShootTimeout: null,
|
|
updateThread: null,
|
|
startEquip: function(entityID, args) {
|
|
this.hand = args[0] == "left" ? 0 : 1;
|
|
var self = this;
|
|
ActiveHand[this.hand] = false;
|
|
// Doing this here instead of in the prototype,
|
|
// to maintain Context. "this" context is lost when it is a function passed to the Script.update thread
|
|
if (!ActiveEquip) {
|
|
this.updateThread = function(dt) {
|
|
var secured = ActiveHand[0].secured | ActiveHand[1].secured;
|
|
if (secured) {
|
|
var hookEntity = Entities.getEntityProperties(self.hookBullet.hookId, ["position"]);
|
|
var direction = self.calculateThrustDirection(MyAvatar.position, hookEntity.position);
|
|
var distance = Vec3.distance(MyAvatar.position, hookEntity.position);
|
|
|
|
GlobalThrust = Vec3.mix(GlobalThrust, Vec3.multiply(direction, 8000 * (distance / (MAX_DISTANCE / 2))), .5);
|
|
|
|
} else {
|
|
GlobalThrust = Vec3.mix(GlobalThrust, Vec3.multiply(GRAVITY, 100), .1);
|
|
}
|
|
|
|
MyAvatar.addThrust(Vec3.multiply(GlobalThrust, dt));
|
|
}
|
|
ActiveEquip = true;
|
|
}
|
|
Script.update.connect(this.updateThread); // Start monitoring if hook is secured or not.
|
|
},
|
|
calculateThrustDirection: function(fromPosition, toPosition) {
|
|
var rotation = Quat.lookAt(fromPosition, toPosition, Vec3.UP);
|
|
return Vec3.normalize(Quat.getFront(rotation));
|
|
},
|
|
continueEquip: function(entityID, args) {
|
|
ActiveEquip = true;
|
|
this.checkTriggerPressure(entityID, this.hand);
|
|
},
|
|
releaseEquip: function(entityID, args) {
|
|
var _this = this;
|
|
Script.update.disconnect(this.updateThread)
|
|
this.hookBullet = {
|
|
secured: false
|
|
};
|
|
ActiveEquip = false;
|
|
},
|
|
checkTriggerPressure: function(entityID, gunHand) {
|
|
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[gunHand]);
|
|
if (this.triggerValue >= RELOAD_THRESHOLD && this.canShoot) {
|
|
|
|
|
|
this.canShoot = false;
|
|
var gunProperties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
|
this.fire(entityID, gunHand, gunProperties);
|
|
|
|
ActiveHand[gunHand] = this.hookBullet;
|
|
} else if (this.triggerValue < RELOAD_THRESHOLD && !this.canShoot) {
|
|
this.canShoot = true;
|
|
|
|
|
|
this.hookBullet.cleanup(this.hookBullet);
|
|
this.hookBullet = {
|
|
secured: false
|
|
};
|
|
ActiveHand[gunHand] = this.hookBullet;
|
|
}
|
|
|
|
return;
|
|
},
|
|
fire: function(entityID, gunHand,gunProperties) {
|
|
var _this = this;
|
|
|
|
var forwardVec = Quat.getFront(Quat.multiply(gunProperties.rotation, Quat.fromPitchYawRollDegrees(0, 0, 0)));
|
|
forwardVec = Vec3.normalize(forwardVec);
|
|
forwardVec = Vec3.multiply(forwardVec, GUN_FORCE);
|
|
|
|
var shot = new Hook(entityID);
|
|
|
|
this.hookBullet = shot;
|
|
if (Math.random() > 0.5) {
|
|
Audio.playSound(SHOOTING_1_SOUND, {
|
|
volume: 0.25,
|
|
position: gunProperties.position
|
|
});
|
|
} else {
|
|
Audio.playSound(SHOOTING_2_SOUND, {
|
|
volume: 0.25,
|
|
position: gunProperties.position
|
|
});
|
|
}
|
|
},
|
|
preload: function(entityID) {
|
|
this.entityID = entityID;
|
|
}
|
|
};
|
|
|
|
var gun = new HookGun();
|
|
|
|
return gun;
|
|
}); |