// // zombieSurvivorScript.js // // Created by David Back on 2/8/18. // Copyright 2018 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 CHANNEL_NAME = "ZOMBIE_BITE"; var DEAD_ZONE_POS = { x:-1.887, y:4.39989, z:-53.2878 }; var MINIMUM_STRIDE_MOVEMENT = 0.015; var MINIMUM_FORWARD_STRIDES = 20; var MINIMUM_BACKWARD_STRIDES = 20; var MAX_VELOCITY = 10; var VELOCITY_INCREASE_FACTOR = 60; var VELOCITY_DECREASE = 0.2; var VELOCITY_DECREASE_RUNNING = 0.05; var RUN_CONTROLS = [Controller.Standard.LeftGrip, Controller.Standard.RightGrip]; var RUN_CONTROLS_THRESHOLD = 0.9; var ADDITIONAL_BODY_OFFSET = {x:-0.097595, y:-0.018677, z:-0.088746}; var BITE_ANIMATION = "atp:/biteReaction.fbx"; var BITE_ANIMATION_START_FRAME = 0; var BITE_ANIMATION_BLOOD_KEYFRAME = 10; var BITE_ANIMATION_END_FRAME = 138; var BITE_ANIMATION_FPS = 60; var BLOOD_HAZE = "https://hifi-content.s3.amazonaws.com/davidback/development/zombies/BloodSphere2.fbx"; var MSEC_PER_SEC = 1000; var BITES_REQUIRED = 3; var BITES_SETTING_NAME = "ZombieBiteCount"; var HEALTH_VERTICAL_OFFSET_OVERHEAD = 1; var HEALTH_VERTICAL_OFFSET_OVERLAY = -0.25; var HEALTH_HORIZONTAL_OFFSET_OVERHEAD = 0.1; var HEALTH_HORIZONTAL_OFFSET_OVERLAY = 0.1; var HEALTH_OFFSET_WRIST = { x:0, y:0.03, z:0 }; var FORWARD_OFFSET = 0.2; var HEART_MODEL_URL = "http://hifi-content.s3-us-west-1.amazonaws.com/rebecca/zombies/models/1410%20Heart.obj"; var DEBUG_BITE_KEY = "f"; var DEBUG_PRINT = true; var FORCE_TEST_RUN = false; var currentVelocity = 0; var previousLeftDotProduct = 0; var previousRightDotProduct = 0; var bodyOffset; var additionalBodyOffset; var minLeftStride; var maxLeftStride; var minRightStride; var maxRightStride; var leftStrideForwardCount = 0; var previousLeftStrideForwardCount = 0; var leftStrideBackwardCount = 0; var rightStrideForwardCount = 0; var previousRightStrideForwardCount = 0; var rightStrideBackwardCount = 0; var biteAnimationPlaying = false; var bloodHaze = undefined; var healthOverhead = []; var healthWrist = []; var localPositionOverhead; var localPositionOverlay; var localPositionWrist; function onMessageReceived(channel, message, senderID) { var messageData = JSON.parse(message); var type = messageData['type']; var biterID = messageData['biterID']; var victimID = messageData['victimID']; if (type == "receiveBite") { if (DEBUG_PRINT) { print("receiveBite message received from biter " + biterID + " biting " + victimID); } if (victimID == MyAvatar.sessionUUID) { if (DEBUG_PRINT) { print("Bitten by zombie " + biterID); } biteReceived(); } } } function keyPressEvent(event) { if (DEBUG_BITE_KEY == event.text) { if (DEBUG_PRINT) { print("biteReceived called from debug key press"); } biteReceived(); } } function biteReceived() { var oldBites = Settings.getValue(BITES_SETTING_NAME, 0); var newBites = oldBites; newBites++; Settings.setValue(BITES_SETTING_NAME, newBites); if (DEBUG_PRINT) { print(BITES_SETTING_NAME + " setting changed from " + oldBites + " to " + newBites); } var frameCount = BITE_ANIMATION_END_FRAME - BITE_ANIMATION_START_FRAME; MyAvatar.overrideAnimation(BITE_ANIMATION, BITE_ANIMATION_FPS, false, BITE_ANIMATION_START_FRAME, BITE_ANIMATION_END_FRAME); biteAnimationPlaying = true; var timeOut = MSEC_PER_SEC * frameCount / BITE_ANIMATION_FPS; Script.setTimeout(function () { biteAnimationPlaying = false; MyAvatar.restoreAnimation(); Overlays.deleteOverlay(bloodHaze); bloodHaze = undefined; if (newBites >= BITES_REQUIRED) { //MyAvatar.goToLocation(DEAD_ZONE_POS, true, MyAvatar.orientation); Settings.setValue(BITES_SETTING_NAME, 0); initHealth(); if (DEBUG_PRINT) { print("DEAD - teleport to dead zone - " + BITES_SETTING_NAME + " reset back to 0"); } } }, timeOut); var bloodTimeOut = MSEC_PER_SEC * BITE_ANIMATION_BLOOD_KEYFRAME / BITE_ANIMATION_FPS; Script.setTimeout(function () { var localPositionBlood = Vec3.subtract(MyAvatar.getJointPosition("Head"), MyAvatar.position); var localRotationBlood = Quat.fromPitchYawRollDegrees(0, 0, 0); bloodHaze = Overlays.addOverlay("model", { localPosition: localPositionBlood, localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0), parentID: MyAvatar.sessionUUID, dimensions: { x: 1, y: 1, z: 1 }, url: BLOOD_HAZE }); }, bloodTimeOut); loseHealth(); } function update() { var runControlsHeld = true; for (var i = 0; i < RUN_CONTROLS.length; i++) { var controlValue = Controller.getValue(RUN_CONTROLS[i]); if (controlValue < RUN_CONTROLS_THRESHOLD) { runControlsHeld = false; } } if (runControlsHeld && !biteAnimationPlaying) { if (Vec3.equal(bodyOffset, Vec3.ZERO)) { bodyOffset = Vec3.subtract(MyAvatar.getJointPosition("Body"), MyAvatar.position); additionalBodyOffset = Vec3.sum(bodyOffset, ADDITIONAL_BODY_OFFSET); } var leftHandPosition = Vec3.sum(MyAvatar.position, MyAvatar.getLeftHandPosition()); var rightHandPosition = Vec3.sum(MyAvatar.position, MyAvatar.getRightHandPosition()); var leftPlaneNormal = Vec3.cross(bodyOffset, additionalBodyOffset); var rightPlaneNormal = leftPlaneNormal; var toLeftHand = Vec3.subtract(leftHandPosition, MyAvatar.position); var leftDotProduct = Vec3.dot(leftPlaneNormal, toLeftHand); var toRightHand = Vec3.subtract(rightHandPosition, MyAvatar.position); var rightDotProduct = Vec3.dot(rightPlaneNormal, toRightHand); var leftHandDifference = leftDotProduct - previousLeftDotProduct; var leftHandDirection = leftHandDifference > 0 ? 1 : -1; if (leftHandDirection === 1) { leftStrideForwardCount++; } else { leftStrideBackwardCount++; } var rightHandDifference = rightDotProduct - previousRightDotProduct; var rightHandDirection = rightHandDifference > 0 ? 1 : -1; if (rightHandDirection === 1) { rightStrideForwardCount++; } else { rightStrideBackwardCount++; } if (minLeftStride == undefined || leftDotProduct < minLeftStride) { minLeftStride = leftDotProduct; } if (maxLeftStride == undefined || leftDotProduct > maxLeftStride) { maxLeftStride = leftDotProduct; } if (minRightStride == undefined || rightDotProduct < minRightStride) { minRightStride = rightDotProduct; } if (maxRightStride == undefined || rightDotProduct > maxRightStride) { maxRightStride = rightDotProduct; } if (minLeftStride != undefined && maxLeftStride != undefined && maxLeftStride - minLeftStride > MINIMUM_STRIDE_MOVEMENT && leftStrideForwardCount >= MINIMUM_FORWARD_STRIDES && leftStrideBackwardCount >= MINIMUM_BACKWARD_STRIDES) { var maxLeftStrideDifference = Math.abs(leftDotProduct - maxLeftStride); if (maxLeftStrideDifference > 0 && maxLeftStrideDifference < 0.01) { var strideLength = maxLeftStride - minLeftStride; var velocityIncrease = strideLength * VELOCITY_INCREASE_FACTOR; currentVelocity += velocityIncrease; if (currentVelocity > MAX_VELOCITY) { currentVelocity = MAX_VELOCITY; } maxLeftStride = undefined; minLeftStride = undefined; leftStrideForwardCount = 0; leftStrideBackwardCount = 0; } } if (minRightStride != undefined && maxRightStride != undefined && maxRightStride - minRightStride > MINIMUM_STRIDE_MOVEMENT && rightStrideForwardCount >= MINIMUM_FORWARD_STRIDES && rightStrideBackwardCount >= MINIMUM_BACKWARD_STRIDES) { var maxRightStrideDifference = Math.abs(rightDotProduct - maxRightStride); if (maxRightStrideDifference > 0 && maxRightStrideDifference < 0.01) { var strideLength = maxRightStride - minRightStride; var velocityIncrease = strideLength * VELOCITY_INCREASE_FACTOR; currentVelocity += velocityIncrease; if (currentVelocity > MAX_VELOCITY) { currentVelocity = MAX_VELOCITY; } maxRightStride = undefined; minRightStride = undefined; rightStrideForwardCount = 0; rightStrideBackwardCount = 0; } } } else { maxLeftStride = undefined; minLeftStride = undefined; maxRightStride = undefined; minRightStride = undefined; leftStrideForwardCount = 0; leftStrideBackwardCount = 0; rightStrideForwardCount = 0; rightStrideBackwardCount = 0; } if (FORCE_TEST_RUN && !biteAnimationPlaying) { currentVelocity = MAX_VELOCITY; } var velocityDecrease = runControlsHeld ? VELOCITY_DECREASE_RUNNING : VELOCITY_DECREASE; if (currentVelocity > 0) { currentVelocity -= velocityDecrease; } if (currentVelocity < 0) { currentVelocity = 0; } var velocityForward = { x:0, y:0, z:1 }; var newVelocity = currentVelocity; if (newVelocity < 0) { newVelocity = 0; } var newVelocityVector = Vec3.multiply(velocityForward, newVelocity); if (Vec3.length(newVelocityVector) > MAX_VELOCITY) { newVelocityVector = Vec3.normalize(newVelocityVector); newVelocityVector = Vec3.multiply(newVelocityVector, MAX_VELOCITY); } MyAvatar.motorVelocity = newVelocityVector; previousLeftDotProduct = leftDotProduct; previousRightDotProduct = rightDotProduct; if (bloodHaze !== undefined) { var localRotationBlood = Quat.multiply(Quat.inverse(MyAvatar.orientation), Quat.multiply(Camera.orientation, Quat.fromPitchYawRollDegrees(0, 0, 0))); Overlays.editOverlay(bloodHaze, { localRotation: localRotationBlood }); } } function addHealth() { var heart = Entities.addEntity({ "clientOnly": 1, "dimensions": { "x": 0.075, "y": 0.075, "z": 0.075 }, "localPosition": localPositionOverhead, "modelURL": HEART_MODEL_URL, "name": "Heart CC-BY Poly by Google", "parentID": MyAvatar.sessionUUID, "parentJointName": "Hips", "owningAvatarID": MyAvatar.sessionUUID, "type": "Model", "userData": "{\"grabbableKey\":{\"grabbable\":false}}" }); healthOverhead.push(heart); //spawnOverlayReplica(heart); spawnWristOverlay(); } function loseHealth() { if (healthOverhead[0]) { Entities.deleteEntity(healthOverhead[0]); healthOverhead.shift(); } if (healthWrist[0]) { Overlays.deleteOverlay(healthWrist[0]); healthWrist.shift(); } } function spawnOverlayReplica(entity) { var overlayProperties = { url: HEART_MODEL_URL, alpha: 0.9, parentID: entity, localPosition: {x: 0, y: HEALTH_VERTICAL_OFFSET_OVERLAY, z: 0.5}, dimensions: { x: 0.04, y: 0.04, z: 0.04 } }; Overlays.addOverlay("model", overlayProperties); } function spawnWristOverlay() { // maybe change this to an image overlay var overlayProperties = { url: HEART_MODEL_URL, alpha: 0.9, parentID: MyAvatar.sessionUUID, parentJointIndex: MyAvatar.getJointIndex("RightForeArm"), localPosition: localPositionWrist, dimensions: { x: 0.02, y: 0.02, z: 0.02 } }; healthWrist.push(Overlays.addOverlay("model", overlayProperties)); } function initHealth() { localPositionOverhead = {x:-HEALTH_HORIZONTAL_OFFSET_OVERHEAD, y:HEALTH_VERTICAL_OFFSET_OVERHEAD, z:0}; localPositionWrist = {x:0.06, y:0.15, z:0}; addHealth(); for (var i = 1; i < BITES_REQUIRED; i++) { localPositionOverhead.x += HEALTH_HORIZONTAL_OFFSET_OVERHEAD; localPositionWrist = Vec3.sum(localPositionWrist, HEALTH_OFFSET_WRIST); addHealth(); } } function shutdown() { Messages.unsubscribe(CHANNEL_NAME); Messages.messageReceived.disconnect(onMessageReceived); Controller.keyPressEvent.disconnect(keyPressEvent); MyAvatar.motorVelocity = { x:0, y:0, z:0 }; MyAvatar.motorMode = "simple"; for (var i = 0; i < BITES_REQUIRED; i++) { loseHealth(); } } function init() { Script.scriptEnding.connect(shutdown); Messages.subscribe(CHANNEL_NAME); Messages.messageReceived.connect(onMessageReceived); Controller.keyPressEvent.connect(keyPressEvent); MyAvatar.motorMode = "dynamic"; MyAvatar.motorReferenceFrame = "camera"; Script.update.connect(update); Script.setTimeout(function () { initHealth(); }, 500); } init();