content/hifi-content/davidback/development/zombies/zombieSurvivorScript.js
2022-02-13 22:49:05 +01:00

536 lines
20 KiB
JavaScript

//
// 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_RUST = "hifi://Rust/-31.67,4989.96,-13.19";
var DEAD_ZONE_EMPTY = "hifi://AvatarIslandDemo/7.25304,-332.286,7.87865";
var MINIMUM_STRIDE_MOVEMENT = 0.1;
var MINIMUM_FORWARD_STRIDES = 20;
var MINIMUM_BACKWARD_STRIDES = 20;
var STRIDE_MAX_DIFFERENCE = 0.1;
var STRIDE_EXTREME_LISTS = 5;
var MAX_STRIDE_COUNT = 80;
var MAX_VELOCITY = 10;
var VELOCITY_INCREASE_FACTOR = 10;
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 BITE_ANIMATION = "atp:/biteReaction.fbx";
var BITE_ANIMATION_START_FRAME = 0;
var BITE_ANIMATION_BLOOD_KEYFRAME = 0;
var BITE_ANIMATION_END_FRAME = 138;
var BITE_ANIMATION_FPS = 60;
var BLOOD_HAZE = "atp:/BloodCircle_169.png";
var BLOOD_COLOR = { "blue": 7, "green": 7, "red": 138 };
var BLOOD_PARTICLE_TEXTURE = "atp:/bloodDrip.png";
var BLOOD_HEAD_DIFFERENCE_MULTIPLE = 0.8;
var BLOOD_Z_OFFSET = 0.15;
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.125;
var HEALTH_HORIZONTAL_OFFSET_OVERLAY = 0.1;
var HEALTH_OFFSET_WRIST = { x:0, y:0.035, z:0 };
var HEALTH_SIZE_OVERHEAD_MULTIPLIER = 0.25;
var HEALTH_SIZE_WRIST_MULTIPLIER = 0.1;
var BRAIN_FULL_MODEL = "atp:/brainFull.fbx";
var BRAIN_DEAD_MODEL = "atp:/brainDead.fbx";
var BRAIN_MODEL_DIMENSIONS = { x:0.4732, y:0.2819, z:0.0092 };
var DEBUG_BITE_KEY = "b";
var DEBUG_RESET_HEALTH_KEY = "h";
var DEBUG_RUN_KEY = "r";
var DEBUG_ENABLED = false;
var currentVelocity = 0;
var previousLeftDotProduct = 0;
var previousRightDotProduct = 0;
var avatarUp = Vec3.ZERO;
var avatarRight = Vec3.ZERO;
var minLeftStrides;
var maxLeftStrides;
var minRightStrides;
var maxRightStrides;
var leftStrideForwardCount = 0;
var leftStrideBackwardCount = 0;
var rightStrideForwardCount = 0;
var rightStrideBackwardCount = 0;
var biteAnimationPlaying = false;
var bloodHaze;
var healthOverhead = [];
var healthWrist = [];
var localPositionOverhead;
var localPositionOverlay;
var localPositionWrist;
var currentHealthIndex = 0;
var debugForceRun = false;
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" && victimID === MyAvatar.sessionUUID) {
if (DEBUG_ENABLED) {
print("receiveBite message received from biter " + biterID);
}
biteReceived();
}
}
function keyPressEvent(event) {
if (!DEBUG_ENABLED) {
return;
}
if (DEBUG_BITE_KEY === event.text) {
print("biteReceived called from debug key press");
biteReceived();
}
if (DEBUG_RESET_HEALTH_KEY === event.text) {
resetHealth();
}
if (DEBUG_RUN_KEY === event.text) {
debugForceRun = !debugForceRun;
}
}
function biteReceived() {
var oldBites = Settings.getValue(BITES_SETTING_NAME, 0);
var newBites = oldBites;
newBites++;
Settings.setValue(BITES_SETTING_NAME, newBites);
if (DEBUG_ENABLED) {
print("biteReceived - " + 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);
if (newBites >= BITES_REQUIRED) {
if (!DEBUG_ENABLED) {
//Window.location = DEAD_ZONE_RUST;
Window.location = DEAD_ZONE_EMPTY;
} else {
print("DEAD - " + BITES_SETTING_NAME + " reset back to 0");
}
resetHealth();
}
}, timeOut);
var bloodTimeOut = MSEC_PER_SEC * BITE_ANIMATION_BLOOD_KEYFRAME / BITE_ANIMATION_FPS;
Script.setTimeout(function () {
bloodHaze = Overlays.addOverlay("image3d", {
alpha: 1,
drawInFront: true,
emissive: true,
localPosition: {x: 0, y: 0, z: -0.7 * MyAvatar.sensorToWorldScale},
localRotation: {x: 0, y: 0, z: 0, w: 1},
parentID: MyAvatar.sessionUUID,
parentJointIndex: -7,
scale: 3 * MyAvatar.sensorToWorldScale,
url: BLOOD_HAZE,
width: 1920,
height: 2160
});
var headPosition = MyAvatar.getJointPosition("Head");
var headDifference = Vec3.length(Vec3.subtract(headPosition, MyAvatar.position));
var bloodLocalPosition = { x:0, y:headDifference * BLOOD_HEAD_DIFFERENCE_MULTIPLE, z:BLOOD_Z_OFFSET };
var bloodPosition = Vec3.sum(MyAvatar.position, bloodLocalPosition);
var bloodEffect = {
alpha: 1,
alphaFinish: 0,
alphaSpread: 1,
alphaStart: 1,
clientOnly: 1,
color: BLOOD_COLOR,
colorFinish: BLOOD_COLOR,
colorSpread: BLOOD_COLOR,
colorStart: BLOOD_COLOR,
dimensions: {
x: 0.5,
y: 0.5,
z: 0.5
},
emitAcceleration: {
x: 0,
y: -2,
z: 0
},
emitDimensions: {
x: 1,
y: 0.2,
z: 1
},
emitRate: 7.5,
emitterShouldTrail: true,
emitSpeed: 0.15,
lifespan: 1.5,
lifetime: 1.5,
locked: true,
particleRadius: 0.2,
polarFinish: 0.6981316804885864,
position: bloodPosition,
radiusFinish: 0.2,
radiusStart: 0,
speedSpread: 0.3,
textures: BLOOD_PARTICLE_TEXTURE,
type: "ParticleEffect"
};
Entities.addEntity(bloodEffect);
}, bloodTimeOut);
loseHealth();
}
function getStride(stridesArray) {
var strideTotal = 0;
for (var i = 0; i < stridesArray.length; i++) {
strideTotal += stridesArray[i];
}
return strideTotal / stridesArray.length;
}
function resetLeftStride() {
maxLeftStrides = [];
minLeftStrides = [];
leftStrideForwardCount = 0;
leftStrideBackwardCount = 0;
}
function resetRightStride() {
maxRightStrides = [];
minRightStrides = [];
rightStrideForwardCount = 0;
rightStrideBackwardCount = 0;
}
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(avatarUp, Vec3.ZERO)) {
avatarUp = Quat.getUp(MyAvatar.orientation);
avatarRight = Quat.getRight(MyAvatar.orientation);
}
var leftHandPosition = Vec3.sum(MyAvatar.position, MyAvatar.getLeftHandPosition());
var rightHandPosition = Vec3.sum(MyAvatar.position, MyAvatar.getRightHandPosition());
var leftPlaneNormal = Vec3.cross(avatarUp, avatarRight);
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++;
}
var idx = 0;
if (minLeftStrides.length < STRIDE_EXTREME_LISTS) {
minLeftStrides.push(leftDotProduct);
minLeftStrides.sort(function(a, b) {
return a - b;
});
} else if (leftDotProduct < minLeftStrides[minLeftStrides.length - 1]) {
for (idx = 0; idx < minLeftStrides.length; idx++) {
if (leftDotProduct < minLeftStrides[idx]) {
minLeftStrides.splice(idx, 0, leftDotProduct);
minLeftStrides.pop();
break;
}
}
}
if (maxLeftStrides.length < STRIDE_EXTREME_LISTS) {
maxLeftStrides.push(leftDotProduct);
maxLeftStrides.sort(function(a, b) {
return b - a;
});
} else if (leftDotProduct > maxLeftStrides[maxLeftStrides.length - 1]) {
for (idx = 0; idx < maxLeftStrides.length; idx++) {
if (leftDotProduct > maxLeftStrides[idx]) {
maxLeftStrides.splice(idx, 0, leftDotProduct);
maxLeftStrides.pop();
break;
}
}
}
if (minRightStrides.length < STRIDE_EXTREME_LISTS) {
minRightStrides.push(rightDotProduct);
minRightStrides.sort(function(a, b) {
return a - b;
});
} else if (rightDotProduct < minRightStrides[minRightStrides.length - 1]) {
for (idx = 0; idx < minRightStrides.length; idx++) {
if (rightDotProduct < minRightStrides[idx]) {
minRightStrides.splice(idx, 0, rightDotProduct);
minRightStrides.pop();
break;
}
}
}
if (maxRightStrides.length < STRIDE_EXTREME_LISTS) {
maxRightStrides.push(rightDotProduct);
maxRightStrides.sort(function(a, b) {
return b - a;
});
} else if (rightDotProduct > maxRightStrides[maxRightStrides.length - 1]) {
for (idx = 0; idx < maxRightStrides.length; idx++) {
if (rightDotProduct > maxRightStrides[idx]) {
maxRightStrides.splice(idx, 0, rightDotProduct);
maxRightStrides.pop();
break;
}
}
}
var maxLeftStride = getStride(maxLeftStrides);
var minLeftStride = getStride(minLeftStrides);
var maxLeftStrideDifference = Math.abs(leftDotProduct - maxLeftStride);
var maxRightStride = getStride(maxRightStrides);
var minRightStride = getStride(minRightStrides);
var maxRightStrideDifference = Math.abs(rightDotProduct - maxRightStride);
if (minLeftStrides.length === STRIDE_EXTREME_LISTS && maxLeftStrides.length === STRIDE_EXTREME_LISTS &&
maxLeftStride - minLeftStride > MINIMUM_STRIDE_MOVEMENT && maxLeftStrideDifference < STRIDE_MAX_DIFFERENCE &&
leftStrideForwardCount >= MINIMUM_FORWARD_STRIDES && leftStrideBackwardCount >= MINIMUM_BACKWARD_STRIDES) {
var strideLengthLeft = maxLeftStride - minLeftStride;
var velocityIncreaseLeft = strideLengthLeft * VELOCITY_INCREASE_FACTOR;
currentVelocity += velocityIncreaseLeft;
if (currentVelocity > MAX_VELOCITY) {
currentVelocity = MAX_VELOCITY;
}
resetLeftStride();
if (DEBUG_ENABLED) {
print("********** LEFT STRIDE SUCCESS ********** stride length = " + strideLengthLeft);
}
}
if (minRightStrides.length === STRIDE_EXTREME_LISTS && maxRightStrides.length === STRIDE_EXTREME_LISTS &&
maxRightStride - minRightStride > MINIMUM_STRIDE_MOVEMENT && maxRightStrideDifference < STRIDE_MAX_DIFFERENCE &&
rightStrideForwardCount >= MINIMUM_FORWARD_STRIDES && rightStrideBackwardCount >= MINIMUM_BACKWARD_STRIDES) {
var strideLengthRight = maxRightStride - minRightStride;
var velocityIncreaseRight = strideLengthRight * VELOCITY_INCREASE_FACTOR;
currentVelocity += velocityIncreaseRight;
if (currentVelocity > MAX_VELOCITY) {
currentVelocity = MAX_VELOCITY;
}
resetRightStride();
if (DEBUG_ENABLED) {
print("********** RIGHT STRIDE SUCCESS ********** stride length = " + strideLengthRight);
}
}
// fail-safes in case stride detection gets into a weird state then reset our values and start over
if (leftStrideForwardCount > MAX_STRIDE_COUNT || leftStrideBackwardCount > MAX_STRIDE_COUNT) {
if (DEBUG_ENABLED) {
print("Max left stride counts reached - triggering left stride reset");
}
resetLeftStride();
}
if (rightStrideForwardCount > MAX_STRIDE_COUNT || rightStrideBackwardCount > MAX_STRIDE_COUNT) {
if (DEBUG_ENABLED) {
print("Max right stride counts reached - triggering right stride reset");
}
resetRightStride();
}
/*
print("minLeftStrides: ");
for (idx = 0; idx < minLeftStrides.length; idx++) {
print(minLeftStrides[idx]);
}
print("maxLeftStrides: ");
for (idx = 0; idx < maxLeftStrides.length; idx++) {
print(maxLeftStrides[idx]);
}
print("minRightStrides: ");
for (idx = 0; idx < minRightStrides.length; idx++) {
print(minRightStrides[idx]);
}
print("maxRightStrides: ");
for (idx = 0; idx < maxRightStrides.length; idx++) {
print(maxRightStrides[idx]);
}
*/
print("minL " + minLeftStride + " maxL " + maxLeftStride + " minR " + minRightStride + " maxR " + maxRightStride + " leftDot " + leftDotProduct + " rightDot " + rightDotProduct + " leftForw " + leftStrideForwardCount + " leftBack " + leftStrideBackwardCount + " rightForw " + rightStrideForwardCount + " rightBack " + rightStrideBackwardCount);
} else {
resetLeftStride();
resetRightStride();
}
if (debugForceRun && !biteAnimationPlaying) {
currentVelocity = MAX_VELOCITY;
}
var velocityDecrease = runControlsHeld ? VELOCITY_DECREASE_RUNNING : VELOCITY_DECREASE;
if (currentVelocity > 0) {
currentVelocity -= velocityDecrease;
}
if (currentVelocity < 0) {
currentVelocity = 0;
}
var cameraOrientation = Camera.getOrientation();
var velocityForward = Quat.getForward(cameraOrientation);
velocityForward.y = 0;
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;
}
function addHealth(deadHealth) {
var brain = Entities.addEntity({
clientOnly: 1,
dimensions: Vec3.multiply(BRAIN_MODEL_DIMENSIONS, HEALTH_SIZE_OVERHEAD_MULTIPLIER),
localPosition: localPositionOverhead,
modelURL: deadHealth ? BRAIN_DEAD_MODEL : BRAIN_FULL_MODEL,
name: "Zombie Brain",
parentID: MyAvatar.sessionUUID,
owningAvatarID: MyAvatar.sessionUUID,
type: "Model",
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
})
});
healthOverhead.push(brain);
var overlayProperties = {
alpha: 1,
dimensions: Vec3.multiply(BRAIN_MODEL_DIMENSIONS, HEALTH_SIZE_WRIST_MULTIPLIER),
localPosition: localPositionWrist,
localRotation: Quat.fromPitchYawRollDegrees(0, 90, 0),
parentID: MyAvatar.sessionUUID,
parentJointIndex: MyAvatar.getJointIndex("RightForeArm"),
url: deadHealth ? BRAIN_DEAD_MODEL : BRAIN_FULL_MODEL,
visible: true
};
healthWrist.push(Overlays.addOverlay("model", overlayProperties));
}
function loseHealth() {
var brainOverhead = healthOverhead[currentHealthIndex];
Entities.editEntity(brainOverhead, { modelURL: BRAIN_DEAD_MODEL });
var brainWrist = healthWrist[currentHealthIndex];
Overlays.editOverlay(brainWrist, { url: BRAIN_DEAD_MODEL });
currentHealthIndex++;
}
function initializeHealth() {
// fail-safe to make sure all brain avatar entities get deleted
Entities.findEntitiesByType('Model', MyAvatar.position, 2).forEach(function(entityID) {
var properties = Entities.getEntityProperties(entityID, ['name', 'parentID']);
if (properties.name === "Zombie Brain" && properties.parentID === MyAvatar.sessionUUID){
Entities.deleteEntity(entityID);
}
});
var currentBites = Settings.getValue(BITES_SETTING_NAME, 0);
for (var i = 0; i < BITES_REQUIRED; i++) {
if (i === 0) {
localPositionOverhead = {x:-HEALTH_HORIZONTAL_OFFSET_OVERHEAD, y:HEALTH_VERTICAL_OFFSET_OVERHEAD, z:0};
localPositionWrist = {x:0.06, y:0.15, z:0};
} else {
localPositionOverhead.x += HEALTH_HORIZONTAL_OFFSET_OVERHEAD;
localPositionWrist = Vec3.sum(localPositionWrist, HEALTH_OFFSET_WRIST);
}
var deadHealth = i < currentBites;
addHealth(deadHealth);
}
currentHealthIndex = currentBites > 0 ? currentBites : 0;
}
function removeHealth() {
for (var i = 0; i < BITES_REQUIRED; i++) {
if (healthOverhead[i]) {
Entities.deleteEntity(healthOverhead[i]);
}
if (healthWrist[i]) {
Overlays.deleteOverlay(healthWrist[i]);
}
}
healthOverhead = [];
healthWrist = [];
}
function resetHealth() {
Settings.setValue(BITES_SETTING_NAME, 0);
removeHealth();
initializeHealth();
}
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";
removeHealth();
}
function init() {
Script.scriptEnding.connect(shutdown);
Messages.subscribe(CHANNEL_NAME);
Messages.messageReceived.connect(onMessageReceived);
Controller.keyPressEvent.connect(keyPressEvent);
MyAvatar.motorMode = "dynamic";
MyAvatar.motorReferenceFrame = "world";
Script.update.connect(update);
Script.setTimeout(function () {
initializeHealth();
}, 2000);
}
init();