content/hifi-content/caitlyn/production/hookshot/hookshot.js
2022-02-13 22:19:19 +01:00

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;
});