// // gun.js // examples // // Created by Brad Hefta-Gaub on 12/31/13. // Modified by Philip on 3/3/14 // Modified by Philip on 3/31/15 // Copyright 2013 High Fidelity, Inc. // // This is an example script that turns the hydra controllers and mouse into a entity gun. // It reads the controller, watches for trigger pulls, and launches entities. // When entities collide with voxels they blow little holes out of the voxels. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var RED = { red: 255, green: 0, blue: 0 }; var LASER_WIDTH = 2; var pointer = []; pointer.push(Overlays.addOverlay("line3d", { start: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, color: RED, alpha: 1, visible: true, lineWidth: LASER_WIDTH })); pointer.push(Overlays.addOverlay("line3d", { start: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, color: RED, alpha: 1, visible: true, lineWidth: LASER_WIDTH })); function getRandomFloat(min, max) { return Math.random() * (max - min) + min; } var lastX = 0; var lastY = 0; var yawFromMouse = 0; var pitchFromMouse = 0; var isMouseDown = false; var MIN_THROWER_DELAY = 1000; var MAX_THROWER_DELAY = 1000; var LEFT_BUTTON_3 = 3; var RELOAD_INTERVAL = 5; var KICKBACK_ANGLE = 15; var elbowKickAngle = 0.0; var rotationBeforeKickback; var showScore = false; // Load some sound to use for loading and firing var fireSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/GUN-SHOT2.raw"); var loadSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/Gun_Reload_Weapon22.raw"); var impactSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/BulletImpact2.raw"); var targetHitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/hit.raw"); var targetLaunchSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/shoot.raw"); var gunModel = "https://s3.amazonaws.com/hifi-public/cozza13/gun/m1911-handgun+1.fbx?version=4";//"http://public.highfidelity.io/models/attachments/HaloGun.fst"; var audioOptions = { volume: 0.9 } var shotsFired = 0; var shotTime = new Date(); var activeControllers = 0; // initialize our controller triggers var triggerPulled = new Array(); var numberOfTriggers = Controller.getNumberOfTriggers(); for (t = 0; t < numberOfTriggers; t++) { triggerPulled[t] = false; } var isLaunchButtonPressed = false; var score = 0; var bulletID = false; var targetID = false; // Create a reticle image in center of screen var screenSize = Controller.getViewportDimensions(); var reticle = Overlays.addOverlay("image", { x: screenSize.x / 2 - 16, y: screenSize.y / 2 - 16, width: 32, height: 32, imageURL: "https://d30y9cdsu7xlg0.cloudfront.net/svg/0d482e23-9694-44e4-ad1b-9dfe4e51a48b.svg?response-content-disposition=attachment%3B%20filename%3Dnoun_35240.svg&Expires=1427830554&Signature=SZFlJ2szvqEj0t345MJOKwr4qkeUl6zrSvHfne30uxAwrbZERp8ZW2IXJoCVqS2N0qQ6s1ZmT8sPBaKtqVhmkiPlJJUGiJz~RrVkeZq8StRgvhjz7ZA1BY~gq-lWI9tcuU6iCDDu1MKyWGPO9Wghr2IUMRG0y1WLpCOL9SR1jV4_&Key-Pair-Id=APKAI5ZVHAXN65CHVU2Q", //HIFI_PUBLIC_BUCKET + "images/billiardsReticle.png", color: { red: 255, green: 255, blue: 255}, alpha: 1 }); var offButton = Overlays.addOverlay("image", { x: screenSize.x - 48, y: 96, width: 32, height: 32, imageURL: HIFI_PUBLIC_BUCKET + "images/close.png", color: { red: 255, green: 255, blue: 255}, alpha: 1 }); var platformButton = Overlays.addOverlay("image", { x: screenSize.x - 48, y: 130, width: 32, height: 32, imageURL: HIFI_PUBLIC_BUCKET + "images/city.png", color: { red: 255, green: 255, blue: 255}, alpha: 1 }); var gridButton = Overlays.addOverlay("image", { x: screenSize.x - 48, y: 164, width: 32, height: 32, imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png", color: { red: 255, green: 255, blue: 255}, alpha: 1 }); if (showScore) { var text = Overlays.addOverlay("text", { x: screenSize.x / 2 - 100, y: screenSize.y / 2 - 50, width: 150, height: 50, color: { red: 0, green: 0, blue: 0}, textColor: { red: 255, green: 0, blue: 0}, topMargin: 4, leftMargin: 4, text: "Score: " + score }); } var BULLET_VELOCITY = 10.0; function shootBullet(position, velocity, grenade) { var BULLET_SIZE = 0.10; var BULLET_LIFETIME = 10.0; var BULLET_GRAVITY = -0.25; var GRENADE_VELOCITY = 15.0; var GRENADE_SIZE = 0.35; var GRENADE_GRAVITY = -9.8; var bVelocity = grenade ? Vec3.multiply(GRENADE_VELOCITY, Vec3.normalize(velocity)) : velocity; var bSize = grenade ? GRENADE_SIZE : BULLET_SIZE; var bGravity = grenade ? GRENADE_GRAVITY : BULLET_GRAVITY; bulletID = Entities.addEntity( { type: "Sphere", position: position, dimensions: { x: bSize, y: bSize, z: bSize }, color: { red: 0, green: 0, blue: 0 }, velocity: bVelocity, lifetime: BULLET_LIFETIME, gravity: { x: 0, y: bGravity, z: 0 }, damping: 0.01, density: 8000, ignoreCollisions: false, collisionsWillMove: true }); // Play firing sounds audioOptions.position = position; Audio.playSound(fireSound, audioOptions); shotsFired++; if ((shotsFired % RELOAD_INTERVAL) == 0) { Audio.playSound(loadSound, audioOptions); } // Kickback the arm if (elbowKickAngle > 0.0) { MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback); } rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm"); var armRotation = MyAvatar.getJointRotation("LeftForeArm"); armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE)); MyAvatar.setJointData("LeftForeArm", armRotation); elbowKickAngle = KICKBACK_ANGLE; } function shootTarget() { var TARGET_SIZE = 0.50; var TARGET_GRAVITY = 0.0; var TARGET_LIFETIME = 300.0; var TARGET_UP_VELOCITY = 0.0; var TARGET_FWD_VELOCITY = 0.0; var DISTANCE_TO_LAUNCH_FROM = 5.0; var ANGLE_RANGE_FOR_LAUNCH = 20.0; var camera = Camera.getPosition(); var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 }); targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection); var forwardVector = Quat.getFront(targetDirection); var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM)); var velocity = Vec3.multiply(forwardVector, TARGET_FWD_VELOCITY); velocity.y += TARGET_UP_VELOCITY; targetID = Entities.addEntity( { type: "Box", position: newPosition, dimensions: { x: TARGET_SIZE * (0.5 + Math.random()), y: TARGET_SIZE * (0.5 + Math.random()), z: TARGET_SIZE * (0.5 + Math.random()) / 4.0 }, color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, velocity: velocity, gravity: { x: 0, y: TARGET_GRAVITY, z: 0 }, lifetime: TARGET_LIFETIME, rotation: Camera.getOrientation(), damping: 0.1, density: 100.0, collisionsWillMove: true }); // Record start time shotTime = new Date(); // Play target shoot sound audioOptions.position = newPosition; Audio.playSound(targetLaunchSound, audioOptions); } function makeGrid(type, scale, size) { var separation = scale * 2; var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation()))); var x, y, z; var GRID_LIFE = 60.0; var dimensions; for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { for (z = 0; z < size; z++) { dimensions = { x: separation/2.0 * (0.5 + Math.random()), y: separation/2.0 * (0.5 + Math.random()), z: separation/2.0 * (0.5 + Math.random()) / 4.0 }; Entities.addEntity( { type: type, position: { x: pos.x + x * separation, y: pos.y + y * separation, z: pos.z + z * separation }, dimensions: dimensions, color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, velocity: { x: 0, y: 0, z: 0 }, gravity: { x: 0, y: 0, z: 0 }, lifetime: GRID_LIFE, rotation: Camera.getOrientation(), damping: 0.1, density: 100.0, collisionsWillMove: true }); } } } } function makePlatform(gravity, scale, size) { var separation = scale * 2; var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation()))); pos.y -= separation * size; var x, y, z; var TARGET_LIFE = 60.0; var INITIAL_GAP = 0.5; var dimensions; for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { for (z = 0; z < size; z++) { dimensions = { x: separation/2.0, y: separation, z: separation/2.0 }; Entities.addEntity( { type: "Box", position: { x: pos.x - (separation * size / 2.0) + x * separation, y: pos.y + y * (separation + INITIAL_GAP), z: pos.z - (separation * size / 2.0) + z * separation }, dimensions: dimensions, color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, velocity: { x: 0, y: 0, z: 0 }, gravity: { x: 0, y: gravity, z: 0 }, lifetime: TARGET_LIFE, damping: 0.1, density: 100.0, collisionsWillMove: true }); } } } // Make a floor for this stuff to fall onto Entities.addEntity({ type: "Box", position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z }, dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size }, color: { red: 128, green: 128, blue: 128 }, lifetime: TARGET_LIFE }); } function entityCollisionWithEntity(entity1, entity2, collision) { if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) && ((entity2.id == bulletID.id) || (entity2.id == targetID.id))) { score++; if (showScore) { Overlays.editOverlay(text, { text: "Score: " + score } ); } // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! Script.setTimeout(deleteBulletAndTarget, 500); // Turn the target and the bullet white Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); // play the sound near the camera so the shooter can hear it audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); Audio.playSound(targetHitSound, audioOptions); } } function keyPressEvent(event) { // if our tools are off, then don't do anything if (event.text == "t") { var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; Script.setTimeout(shootTarget, time); } else if ((event.text == ".") || (event.text == "SPACE")) { shootFromMouse(false); } else if (event.text == ",") { shootFromMouse(true); } else if (event.text == "r") { playLoadSound(); } else if (event.text == "s") { // Hit this key to dump a posture from hydra to log Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm")); Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm")); Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand")); } } function playLoadSound() { audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); Audio.playSound(loadSound, audioOptions); // Raise arm to firing posture takeFiringPose(); } function clearPose() { MyAvatar.clearJointData("LeftForeArm"); MyAvatar.clearJointData("LeftArm"); MyAvatar.clearJointData("LeftHand"); } function deleteBulletAndTarget() { Entities.deleteEntity(bulletID); Entities.deleteEntity(targetID); bulletID = false; targetID = false; } function takeFiringPose() { clearPose(); if (Controller.getNumberOfSpatialControls() == 0) { MyAvatar.setJointData("LeftForeArm", {x: -0.251919, y: -0.0415449, z: 0.499487, w: 0.827843}); MyAvatar.setJointData("LeftArm", { x: 0.470196, y: -0.132559, z: 0.494033, w: 0.719219}); MyAvatar.setJointData("LeftHand", { x: -0.0104815, y: -0.110551, z: -0.352111, w: 0.929333}); } } MyAvatar.attach(gunModel, "RightHand", {x:0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, -85, 79), 0.40); MyAvatar.attach(gunModel, "LeftHand", {x:-0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, 85, -79), 0.40); // Give a bit of time to load before playing sound Script.setTimeout(playLoadSound, 2000); function update(deltaTime) { if (bulletID && !bulletID.isKnownID) { bulletID = Entities.identifyEntity(bulletID); } if (targetID && !targetID.isKnownID) { targetID = Entities.identifyEntity(targetID); } if (activeControllers == 0) { if (Controller.getNumberOfSpatialControls() > 0) { activeControllers = Controller.getNumberOfSpatialControls(); clearPose(); } } var KICKBACK_DECAY_RATE = 0.125; if (elbowKickAngle > 0.0) { if (elbowKickAngle > 0.5) { var newAngle = elbowKickAngle * KICKBACK_DECAY_RATE; elbowKickAngle -= newAngle; var armRotation = MyAvatar.getJointRotation("LeftForeArm"); armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, -newAngle)); MyAvatar.setJointData("LeftForeArm", armRotation); } else { MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback); if (Controller.getNumberOfSpatialControls() > 0) { clearPose(); } elbowKickAngle = 0.0; } } // check for trigger press var numberOfTriggers = 2; var controllersPerTrigger = 2; if (numberOfTriggers == 2 && controllersPerTrigger == 2) { for (var t = 0; t < 2; t++) { var shootABullet = false; var triggerValue = Controller.getTriggerValue(t); if (triggerPulled[t]) { // must release to at least 0.1 if (triggerValue < 0.1) { triggerPulled[t] = false; // unpulled } } else { // must pull to at least if (triggerValue > 0.5) { triggerPulled[t] = true; // pulled shootABullet = true; } } var palmController = t * controllersPerTrigger; var palmPosition = Controller.getSpatialControlPosition(palmController); var fingerTipController = palmController + 1; var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController); var laserTip = Vec3.sum(Vec3.multiply(100.0, Vec3.subtract(fingerTipPosition, palmPosition)), palmPosition); // Update Lasers Overlays.editOverlay(pointer[t], { start: palmPosition, end: laserTip, alpha: 1 }); if (shootABullet) { var palmToFingerTipVector = { x: (fingerTipPosition.x - palmPosition.x), y: (fingerTipPosition.y - palmPosition.y), z: (fingerTipPosition.z - palmPosition.z) }; // just off the front of the finger tip var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2, y: fingerTipPosition.y + palmToFingerTipVector.y/2, z: fingerTipPosition.z + palmToFingerTipVector.z/2}; var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector)); shootBullet(position, velocity, false); } } } } function shootFromMouse(grenade) { var DISTANCE_FROM_CAMERA = 1.0; var camera = Camera.getPosition(); var forwardVector = Quat.getFront(Camera.getOrientation()); var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA)); var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY); shootBullet(newPosition, velocity, grenade); } function mouseReleaseEvent(event) { // position isMouseDown = false; } function mousePressEvent(event) { var clickedText = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); if (clickedOverlay == offButton) { Script.stop(); } else if (clickedOverlay == platformButton) { var platformSize = 3; makePlatform(-9.8, 1.0, platformSize); } else if (clickedOverlay == gridButton) { makeGrid("Box", 1.0, 3); } } function scriptEnding() { Overlays.deleteOverlay(reticle); Overlays.deleteOverlay(offButton); Overlays.deleteOverlay(platformButton); Overlays.deleteOverlay(gridButton); Overlays.deleteOverlay(pointer[0]); Overlays.deleteOverlay(pointer[1]); Overlays.deleteOverlay(text); MyAvatar.detachOne(gunModel); MyAvatar.detachOne(gunModel); clearPose(); } Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.keyPressEvent.connect(keyPressEvent);