From 665eb14d82aef7ad5b12f72fdc0020c6d9c83b4d Mon Sep 17 00:00:00 2001 From: Zephyr Date: Mon, 27 Jul 2026 21:48:52 +0800 Subject: [PATCH] add ze_diddle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0727 地图脚本添加 --- 2001/ze_diddle/manager.js | 5294 ++++++++++++++++++ 2001/ze_diddle/ze_diddle/blobarm.js | 913 +++ 2001/ze_diddle/ze_diddle/bloblaser.js | 1080 ++++ 2001/ze_diddle/ze_diddle/curseorb.js | 1038 ++++ 2001/ze_diddle/ze_diddle/diddlebaby.js | 1296 +++++ 2001/ze_diddle/ze_diddle/diddlecannonammo.js | 1022 ++++ 2001/ze_diddle/ze_diddle/diddledbosshp.js | 901 +++ 2001/ze_diddle/ze_diddle/diddledickhealth.js | 1135 ++++ 2001/ze_diddle/ze_diddle/diddlejihad.js | 1074 ++++ 2001/ze_diddle/ze_diddle/fetus.js | 1681 ++++++ 2001/ze_diddle/ze_diddle/filter.js | 64 + 2001/ze_diddle/ze_diddle/itemcontroller.js | 1229 ++++ 2001/ze_diddle/ze_diddle/laser.js | 1023 ++++ 2001/ze_diddle/ze_diddle/omahamortar.js | 999 ++++ 2001/ze_diddle/ze_diddle/shieldhp.js | 1001 ++++ 2001/ze_diddle/ze_diddle/shrekheadpath.js | 1000 ++++ 2001/ze_diddle/ze_diddle/vaginaface.js | 1326 +++++ 2001/ze_diddle/ze_diddle/velocity.js | 920 +++ 18 files changed, 22996 insertions(+) create mode 100644 2001/ze_diddle/manager.js create mode 100644 2001/ze_diddle/ze_diddle/blobarm.js create mode 100644 2001/ze_diddle/ze_diddle/bloblaser.js create mode 100644 2001/ze_diddle/ze_diddle/curseorb.js create mode 100644 2001/ze_diddle/ze_diddle/diddlebaby.js create mode 100644 2001/ze_diddle/ze_diddle/diddlecannonammo.js create mode 100644 2001/ze_diddle/ze_diddle/diddledbosshp.js create mode 100644 2001/ze_diddle/ze_diddle/diddledickhealth.js create mode 100644 2001/ze_diddle/ze_diddle/diddlejihad.js create mode 100644 2001/ze_diddle/ze_diddle/fetus.js create mode 100644 2001/ze_diddle/ze_diddle/filter.js create mode 100644 2001/ze_diddle/ze_diddle/itemcontroller.js create mode 100644 2001/ze_diddle/ze_diddle/laser.js create mode 100644 2001/ze_diddle/ze_diddle/omahamortar.js create mode 100644 2001/ze_diddle/ze_diddle/shieldhp.js create mode 100644 2001/ze_diddle/ze_diddle/shrekheadpath.js create mode 100644 2001/ze_diddle/ze_diddle/vaginaface.js create mode 100644 2001/ze_diddle/ze_diddle/velocity.js diff --git a/2001/ze_diddle/manager.js b/2001/ze_diddle/manager.js new file mode 100644 index 0000000..5c29eed --- /dev/null +++ b/2001/ze_diddle/manager.js @@ -0,0 +1,5294 @@ +import { Instance } from 'cs_script/point_script'; + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function setInterval(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + everyNSeconds: ms / 1000, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function clearTimeout(id) { + tasks = tasks.filter((task) => task.id !== id); +} +const clearInterval = clearTimeout; +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +function EntFire(name, input, value = '', delay = 0, activator = null, caller = null) { + Instance.EntFireAtName({ + name, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function VectorAdd(a, b) { + return Vector3Utils.add(a, b); +} +function VectorScale(vector, scale) { + return Vector3Utils.scale(vector, scale); +} +function TraceLine(start, end, ignoreEntity = null, ignorePlayers = false) { + const trace = Instance.TraceLine({ + start, + end, + ignoreEntity: ignoreEntity ?? undefined, + ignorePlayers, + }); + return trace.fraction; +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function findAllByName(name) { + return Instance.FindEntitiesByName(name); +} +function findAllByClass(classname) { + return Instance.FindEntitiesByClass(classname); +} +function findByNameWithin(name, origin, radius, previous = null) { + return findWithin(findAllByName(name), origin, radius, previous); +} +function findByClassWithin(classname, origin, radius, previous = null) { + return findWithin(findAllByClass(classname), origin, radius, previous); +} +function findByNameNearest(name, origin, radius) { + let nearest = null; + let nearestDistance = radius; + for (const entity of findAllByName(name)) { + if (!isValidEntity(entity)) + continue; + const currentDistance = distance(getOrigin(entity), origin); + if (currentDistance > nearestDistance) + continue; + nearest = entity; + nearestDistance = currentDistance; + } + return nearest; +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function getAngles(entity) { + if (entity == null || !entity.IsValid()) + return angles(); + return angles(entity.GetAbsAngles()); +} +function getVelocity(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsVelocity()); +} +function setOrigin(entity, position) { + if (!isValidEntity(entity) || position == null) + return; + teleportEntity(entity, position, null, null); +} +function setVelocity(entity, velocity) { + if (!isValidEntity(entity) || velocity == null) + return; + teleportEntity(entity, null, null, velocity); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function getTeam(entity) { + if (entity == null || !entity.IsValid()) + return 0; + return entity.GetTeamNumber(); +} +function getClassname(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetClassName(); +} +function getForwardVector(entity) { + return getAngles(entity).forward; +} +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} +function randomFloat(min, max) { + return Math.random() * (max - min) + min; +} +function setEntityKeyValue(entity, key, value, delay = 0) { + EntFireTarget(entity, 'AddOutput', `${key} ${value}`, delay); +} +const entityState = new WeakMap(); +function getEntityState(entity) { + if (entity == null || !entity.IsValid()) + return null; + let state = entityState.get(entity); + if (state === undefined) { + state = Object.create(null); + entityState.set(entity, state); + } + return state; +} +function forceSpawnTemplate(templateName, position = null, rotation = null) { + const template = Instance.FindEntityByName(templateName); + if (template == null || !template.IsValid()) { + throw new Error(`[ze_diddle] missing point_template '${templateName}'`); + } + return forceSpawnResolvedTemplate(template, templateName, position, rotation); +} +function forceSpawnResolvedTemplate(template, label, position, rotation) { + const classname = template.GetClassName(); + if (classname !== 'point_template') { + throw new Error(`[ze_diddle] '${label}' must be point_template, got '${classname}'`); + } + const spawned = template.ForceSpawn(position == null ? undefined : vec(position), rotation == null ? undefined : angles(rotation)); + if (spawned == null) { + throw new Error(`[ze_diddle] ForceSpawn failed for '${label}'`); + } + const spawnedEntities = spawned.filter(isNotNull); + if (spawnedEntities.length <= 0) { + throw new Error(`[ze_diddle] ForceSpawn returned no entities for '${label}'`); + } + return spawnedEntities; +} +const Entities = { + FindByName(previous, name) { + return findAfter(findAllByName(name), previous); + }, + FindByClassname(previous, classname) { + return findAfter(findAllByClass(classname), previous); + }, + FindByNameWithin(previous, name, origin, radius) { + return findByNameWithin(name, origin, radius, previous); + }, + FindByClassnameWithin(previous, classname, origin, radius) { + return findByClassWithin(classname, origin, radius, previous); + }, + FindByNameNearest(name, origin, radius) { + return findByNameNearest(name, origin, radius); + }, +}; +function Vector(x = 0, y = 0, z = 0) { + return vec(x, y, z); +} +const RandomInt = randomInt; +const RandomFloat = randomFloat; +const sqrt = Math.sqrt; +function printl(message) { + Instance.Msg(`${String(message)}\n`); +} +function findWithin(entities, origin, radius, previous) { + let previousFound = previous == null; + for (const entity of entities) { + if (!isValidEntity(entity)) + continue; + if (!previousFound) { + previousFound = entity === previous; + continue; + } + if (entity === previous) + continue; + if (distance(getOrigin(entity), origin) <= radius) + return entity; + } + return null; +} +function findAfter(entities, previous) { + if (previous == null) + return entities[0] ?? null; + const index = entities.findIndex((entity) => entity === previous); + if (index < 0) + return null; + return entities[index + 1] ?? null; +} +function isNotNull(value) { + return value != null; +} +// TODO: 以后可能通过ModSharp实现 +function FrameTime() { + return 0.015625; +} + +const SCRIPT_PREFIX = 'ze_diddle/manager'; +const self = Instance.FindEntityByName('Map_Manager'); +function scheduleInternalScript(callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + scheduleScript(SCRIPT_PREFIX, callback, delay, inputDataOrActivator, nextCaller); +} +//===================================\\ +// Script by Luffaren (STEAM_0:1:22521282) +// (PUT THIS IN: csgo/scripts/vscripts/luffaren/_mapscripts/ze_diddle/) +// +// Patched version intended for use with GFL ze_diddle_v3 stripper, included in the release +// Adds "Diddle Extreme", where you must beat everything in a single round (with some help/items) +//===================================\\ +// To instantly skip to finale (even on extreme), call SkipToFinale() just after the round begins (before the first stage gets picked). +//===================================\\ +const stagepickdelay = 10.0; //how many extra seconds to delay the stage-picking (allowing players to vote + give breathing room) +const coins_max_normalmode = 250; +const coins_max_extrememode = 300; +var coins_max = 250; +var coins = 0; +var coins_lastround = 0; +var piv = null; +var vaginacount = 0; +var babycount = 0; +var checkpoint = false; +var firststage = true; +var stagepool = [0, 1, 2, 3, 4]; +var stageskip = []; +var pivv = false; +var pivvv = false; +var buyers = []; +var wcheck = null; +var customers = []; +var shopactive = false; +var shop_cheat = null; //player who shoots secret wall gets prioritized to the shop (only works for one player each round) +var extreme = false; +var stageChosen = -1; +var normalTorchCooldowns = true; +let shopDefendTimer = null; +const items = +//ITEM PRICE LIST: +[ + 20, //0 - heal + 60, //1 - legendary heal + 60, //2 - push (pre-stripper-V3: 40) + 20, //3 - small dick + 40, //4 - big dick + 40, //5 - diddlecannon (pre-stripper-V3: 50) + 30, //6 - wall big + 20, //7 - wall mid + 10, +]; //8 - wall small +const item_mincost = 10; //ITEM THAT COSTS THE LEAST (update this if you update item prices) +const items_priceindicator = [ + [Vector(8045, 190, 220), Vector(0, 0, 0), items[0]], + [Vector(8045, 320, 220), Vector(0, 0, 0), items[1]], + [Vector(8045, 520, 220), Vector(0, 0, 0), items[2]], + [Vector(8045, 608, 220), Vector(0, 0, 0), items[3]], + [Vector(8045, 710, 220), Vector(0, 0, 0), items[4]], + [Vector(7930, 813, 220), Vector(0, 0, 0), items[5]], + [Vector(7800, 813, 220), Vector(0, 0, 0), items[6]], + [Vector(7570, 472, 220), Vector(0, 0, 0), items[7]], + [Vector(7570, 336, 220), Vector(0, 0, 0), items[8]], +]; +function SpawnItemPriceIndicators() { + EntFire('shoppricestripperfix', 'Kill', '', 0.0, null); + for (let i = 0; i < items_priceindicator.length; i++) { + let [pos, rot] = items_priceindicator[i]; + let itemIndex = i; + forceSpawnTemplate('diddle_shop_price_indicator_' + String(itemIndex), pos, rot); + } +} +//STAGES: +// 0 = shrek (The limbo of Shrek) +// 1 = turtle (The virus) +// 2 = beach (mm1ri12mk2ns2hk11sf2rr6ey2eg21/Omaha beach) +// 3 = diglett (The revenge of the dicklett temple part 2) +// 4 = weaboo (Weeaboo Annihilation) +// 5 = finale (The final Diddle) +// X = warmup (The Cantina of Diddle) +// X = Xtreme (The ultimate Xtreme Diddle heaven multicolor experience) +var ticklavawalkfix = true; +function LavaWalkFix() { + if (!ticklavawalkfix) + return; + scheduleInternalScript(() => { + LavaWalkFix(); + }, 2.0, null, null); + let h = null; + while (null != + (h = Entities.FindByClassnameWithin(h, 'player', Vector(224, -8576, 1280), 2112))) { + if (getTeam(h) == 3 && h.GetHealth() > 0) { + setOrigin(h, Vector(2760, -8438, 991)); + setVelocity(h, Vector()); + } + } +} +const cursewallbreak_rangeoffset = -80; //how much extra range to scan for walls (from curse-orb), negative values work, -199 at lowest! +const cursewallbreak_tickrate = 0.2; //how often to scan for/break walls (in seconds) +function CurseWallBreakTick(inputData) { + const { activator, caller } = inputData; + if (caller == null || !caller.IsValid()) + return; + scheduleInternalScript((inputData) => { + CurseWallBreakTick(inputData); + }, cursewallbreak_tickrate, null, caller); + let h = Entities.FindByNameNearest('ITEMX_qaz_item_shields1*', getOrigin(caller), 230 + cursewallbreak_rangeoffset); + if (h != null) { + EntFireByHandle(h, 'Break', '', 0.0, null, null); + EntFireByHandle(caller, 'FireUser1', '', 0.0, null, null); + return; + } + h = Entities.FindByNameNearest('ITEMX_qaz_item_shields2*', getOrigin(caller), 280 + cursewallbreak_rangeoffset); + if (h != null) { + EntFireByHandle(h, 'Break', '', 0.0, null, null); + EntFireByHandle(caller, 'FireUser1', '', 0.0, null, null); + return; + } + h = Entities.FindByNameNearest('ITEMX_qaz_item_shields3*', getOrigin(caller), 380 + cursewallbreak_rangeoffset); + if (h != null) { + EntFireByHandle(h, 'Break', '', 0.0, null, null); + EntFireByHandle(caller, 'FireUser1', '', 0.0, null, null); + return; + } +} +//walls get 100+(X*Tcount)hp depending on the wall (small green:80 / mid red:150 / big yellow:220) +const autoHurtWallNearTP_DAMAGE = 500; +const autoHurtGreenWallNearOtherWall_DAMAGE = 10; //every ~0.50s, was 5dmg in stripper #4 (but that was for green-walls only, now it's all walls) +function AutoHurtWallNearTP(inputData, type) { + const { activator, caller } = inputData; + //Instance.Msg(`[AutoHurtWallNearTP] called! type=${type}, caller=${caller ? 'valid' : 'null'}\n`) + if (caller == null || !caller.IsValid()) { + //Instance.Msg('[AutoHurtWallNearTP] caller is invalid, returning\n') + return; + } + //Instance.Msg(`[AutoHurtWallNearTP] caller name=${caller.GetEntityName()}, class=${caller.GetClassName()}\n`) + //1=green (small), 2=red (medium), 3=yellow (big) + let radius = 200; + if (type == 2) + radius = 280; + else if (type == 3) + radius = 380; + let nearother = false; + const callerPos = getOrigin(caller); + // 查找附近的 info_teleport_destination + let teleportent_scan = Entities.FindByClassnameWithin(null, 'info_teleport_destination', callerPos, radius); + if (teleportent_scan == null || !teleportent_scan.IsValid()) { + //Instance.Msg('[AutoHurtWallNearTP] no info_teleport_destination found, checking for other walls\n') + let h = null; + // 检查 shields1 + while (null != (h = Entities.FindByNameWithin(h, 'ITEMX_qaz_item_shields1*', callerPos, radius))) { + if (h != caller) { + nearother = true; + teleportent_scan = h; + //Instance.Msg(`[AutoHurtWallNearTP] found nearby shields1: ${h.GetEntityName()}\n`) + break; + } + } + if (teleportent_scan == null || !teleportent_scan.IsValid()) { + // 检查 shields2 + while (null != (h = Entities.FindByNameWithin(h, 'ITEMX_qaz_item_shields2*', callerPos, radius))) { + if (h != caller) { + nearother = true; + teleportent_scan = h; + //Instance.Msg(`[AutoHurtWallNearTP] found nearby shields2: ${h.GetEntityName()}\n`) + break; + } + } + if (teleportent_scan == null || !teleportent_scan.IsValid()) { + // 检查 shields3 + while (null != (h = Entities.FindByNameWithin(h, 'ITEMX_qaz_item_shields3*', callerPos, radius))) { + if (h != caller) { + nearother = true; + teleportent_scan = h; + //Instance.Msg(`[AutoHurtWallNearTP] found nearby shields3: ${h.GetEntityName()}\n`) + break; + } + } + } + } + } + // 决定后续调度和扣血 + if (teleportent_scan != null && teleportent_scan.IsValid()) { + // 有 teleport 或其他墙,持续检测并扣血 + scheduleInternalScript((inputData) => { + AutoHurtWallNearTP(inputData, type); + }, 0.5, null, caller); + const damage = nearother ? autoHurtGreenWallNearOtherWall_DAMAGE : autoHurtWallNearTP_DAMAGE; + //Instance.Msg(`[AutoHurtWallNearTP] removing ${damage} hp (${nearother ? 'near other wall' : 'near teleporter'})\n`) + EntFireByHandle(caller, 'RemoveHealth', String(damage), 0.0, null, null); + } + else { + // 附近什么都没有,延迟 3 秒后再试 + //Instance.Msg('[AutoHurtWallNearTP] no teleporter or other wall found, retrying in 3s\n') + scheduleInternalScript((inputData) => { + AutoHurtWallNearTP(inputData, type); + }, 3.0, null, caller); + } +} +const EXMVOTE_EXTREME_PERCENTAGE = 65.0; //the player-vote-percentage to vote for extreme-mode (by shooting upwards in the spawn) +const EXMVOTE_NORMAL_PERCENTAGE = 60.0; //the player-vote-percentage to vote for normal-mode (by shooting upwards in the spawn) +var EXMVOTE_PERCENTAGE = 60.0; +var exmvote_voteallowed = false; +var exmvote_voted = false; +var exmvote_gtext = null; +var exmvote_gtext_2 = null; +var exmvote_playercount = 0; +var exmvote_playervotes = 0; +const exmvote_playervoted = []; +function ExtremeModeVote(inputData) { + const { activator, caller } = inputData; + if (!exmvote_voteallowed) + return; + if (caller == null || !caller.IsValid()) + return; + if (exmvote_voted) + return; + if (activator == null || + !activator.IsValid() || + getClassname(activator) != 'player' || + activator.GetHealth() <= 0) + return; + for (const pvs of exmvote_playervoted) { + if (pvs == activator) + return; + } + exmvote_playervoted.push(activator); + exmvote_playervotes++; + EntFire('extremevote_gfade', 'Fade', '', 0.0, activator); + if (exmvote_playervotes >= + Math.trunc((exmvote_playercount * EXMVOTE_PERCENTAGE) / 100)) { + exmvote_voted = true; + let extoggle = !extreme; + ResetMap(); + extreme = extoggle; + let cmode = 'NORMAL'; + if (extreme) + cmode = 'EXTREME'; + EntFire('server', 'Command', 'say [' + cmode + ' MODE VOTE] PASSED - SLAYING', 0.0, activator); + EntFire('server', 'Command', 'say [' + cmode + ' MODE VOTE] PASSED - SLAYING', 0.01, activator); + EntFire('server', 'Command', 'say [' + cmode + ' MODE VOTE] PASSED - SLAYING', 0.02, activator); + EntFire('server', 'Command', 'say [' + cmode + ' MODE VOTE] PASSED - SLAYING', 0.03, activator); + EntFire('server', 'Command', 'say [' + cmode + ' MODE VOTE] PASSED - SLAYING', 0.04, activator); + //EntFire('extremevote_gtext','AddOutput','message [' + cmode + ' MODE VOTE] PASSED - SLAYING',0.02,null,) //CS2 no game_text + //EntFire('extremevote_gtext', 'Display', '', 0.03, null) //CS2 no game_text + scheduleInternalScript(() => { + KillAllButton(); + }, 0.05, null, null); + } +} +function WarnVoteShowMessage() { + if (!exmvote_voteallowed) + return; + scheduleInternalScript(() => { + WarnVoteShowMessage(); + }, 0.2, null, null); + if (exmvote_gtext_2 == null || !exmvote_gtext_2.IsValid()) { + exmvote_gtext_2 = Entities.FindByName(null, 'extremevote_gtext_2'); + if (exmvote_gtext_2 == null || !exmvote_gtext_2.IsValid()) + return; + } + EntFireByHandle(exmvote_gtext_2, 'SetMessage', '[EXTREME MODE VOTE]\nGet ready to vote at the start of the next round\nYou can do this by shooting straight up in the spawn-area\nYou have ~' + + String(10 + Math.trunc(stagepickdelay)) + + " seconds to vote\nIt's your vote, do just as you wish!"); + //EntFireByHandle(exmvote_gtext_2, 'Display', '', 0.01, null, null) +} +function ExtremeModeVoteShowMessage() { + if (!exmvote_voteallowed) + return; + scheduleInternalScript(() => { + ExtremeModeVoteShowMessage(); + }, 0.02, null, null); + let cmode = 'EXTREME'; + if (extreme) + cmode = 'NORMAL'; + if (exmvote_gtext == null || !exmvote_gtext.IsValid()) { + exmvote_gtext = Entities.FindByName(null, 'extremevote_gtext'); + if (exmvote_gtext == null || !exmvote_gtext.IsValid()) + return; + } + EntFireByHandle(exmvote_gtext, 'SetMessage', '[' + + cmode + + ' MODE VOTE]\nYou can vote by shooting straight up\nVotes: (' + + String(exmvote_playervotes) + + '/' + + String(Math.trunc((exmvote_playercount * EXMVOTE_PERCENTAGE) / 100)) + + ')'); + //EntFireByHandle(exmvote_gtext, 'Display', '', 0.01, null, null) +} +function ResetAllScore() { + let hlist = []; + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + let sc = getEntityState(h); + if ('playershopbias' in sc) + sc.playershopbias = 0; + else + sc.playershopbias = 0; + } + ResetPlayerScore(hlist); + for (const sb of SBplayers) { + sb.score = 0; + } +} +//function to restore shop bias score if lost, only utilized through the luffarenmaps.smx plugin +const SBplayersrestore = []; +const SBplayers = []; +function TickShopBiasPlayers() { + scheduleInternalScript(() => { + TickShopBiasPlayers(); + }, 1.0, null, null); + if (SBplayersrestore.length <= 0) + return; + let SBplayersagain = []; + let looped = false; + while (!looped) { + let sb = SBplayersrestore.splice(0, 1)[0]; + if (sb != null) { + let foundsbplayer = false; + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + let sc = getEntityState(h); + if ('userid' in sc) { + if (sc.userid == sb) { + foundsbplayer = true; + for (const sbr of SBplayers) { + if (sc.userid == sbr.userid) { + if ('playershopbias' in sc) + sc.playershopbias = 0; + else + sc.playershopbias = 0; + ResetPlayerScore([h]); + AddPlayerScore(sbr.score, [h]); + sc.playershopbias = sbr.score - 1; + AddPlayerShopBias({}, 1, h); + break; + } + } + break; + } + } + } + if (!foundsbplayer) + SBplayersagain.push(sb); + } + if (SBplayersrestore.length <= 0) { + looped = false; + break; + } + } + if (SBplayersagain.length > 0) { + SBplayersrestore.splice(0); + for (const ssba of SBplayersagain) + SBplayersrestore.push(ssba); + } +} +function PlayerKillEvent(inputData) { + const { activator, caller } = inputData; + AddPlayerShopBias(inputData, 1, activator, true); +} +function ShowPlayerScoreNewRound(inputData) { + const { activator, caller } = inputData; + if (activator == null || !activator.IsValid()) + return; + let sc = getEntityState(activator); + if (!('playershopbias' in sc)) + sc.playershopbias = 0; + AddPlayerScore(sc.playershopbias, [activator]); +} +var plscore_index = 0; +function AddPlayerScore(score, handles) { + if (score == 0) + return; + plscore_index++; + if (plscore_index > 10) + plscore_index = 1; + let scent = Entities.FindByName(null, 'score_' + String(plscore_index)); + if (scent == null || !scent.IsValid()) + return; + setEntityKeyValue(scent, 'points', score); + for (const h of handles) { + if (h == null || !h.IsValid() || getClassname(h) != 'player') + continue; + EntFireByHandle(scent, 'ApplyScore', '', 0.01, h, null); + } +} +function ResetPlayerScore(handles) { + for (const h of handles) { + if (h == null || !h.IsValid() || getClassname(h) != 'player') + continue; + EntFire('score_reset', 'ApplyScore', '', 0.0, h); + } +} +function AddPlayerShopBias(inputData, value = 0, handle = null, noscore = false) { + const { activator, caller } = inputData; + //call to add score to player/!activator through map-based things + if (handle == null || !handle.IsValid()) { + handle = activator; + if (handle == null || !handle.IsValid()) + return; + } + if (getClassname(handle) != 'player') + return; + let sc = getEntityState(handle); + if ('playershopbias' in sc) + sc.playershopbias += value; + else + sc.playershopbias = value; + if (SBplayers.length > 0) { + //restore-feature is active through luffarenmaps.smx + if ('userid' in sc) { + for (const sbr of SBplayers) { + if (sc.userid == sbr.userid) { + sbr.score = sc.playershopbias; + break; + } + } + } + } + if (!noscore) + AddPlayerScore(value, [handle]); + return sc.playershopbias; +} +function ScrambleArray(arr) { + if (arr == null || arr.length <= 0) + return; + let scrambled = false; + let arr_scrambled = []; + while (!scrambled) { + let arrindex = RandomInt(0, arr.length - 1); + arr_scrambled.push(arr[arrindex]); + arr.splice(arrindex, 1)[0]; + if (arr.length <= 0) + scrambled = true; + } + return arr_scrambled; +} +function GetSortedShopBiasList(candidates) { + //candidates = array of player-handles + //(1) set up a temporary list that is completely validated, no invalid players + let wlist = []; + for (const c of candidates) { + if (c == null || + !c.IsValid() || + getClassname(c) != 'player' || + getTeam(c) != 3 || + c.GetHealth() <= 0) + continue; + wlist.push(c); + } + //(2) sort the list based on each player's score (if they match, do a 50/50 decision to go higher/lower) + let n = wlist.length; + for (let i = 0; i < n; i++) { + for (let j = 1; j < n - i; j++) { + if (AddPlayerShopBias({}, 0, wlist[j - 1]) < + AddPlayerShopBias({}, 0, wlist[j]) || + (AddPlayerShopBias({}, 0, wlist[j - 1]) == + AddPlayerShopBias({}, 0, wlist[j]) && + RandomFloat(0.0, 100.0) > 50.0)) { + let temp = wlist[j - 1]; + wlist[j - 1] = wlist[j]; + wlist[j] = temp; + } + } + } + //(3) create the final bias list, and add more instances of the top-scored compared to the lower-scored + let rlist = []; + let add_amount = wlist.length; + for (const h of wlist) { + for (let i = 0; i < add_amount; i++) + rlist.push(h); + add_amount--; + } + //(4) return the biased list, it should look something like this with a list of 5 players + //[1,1,1,1,1,2,2,2,2,3,3,3,4,4,5] + return rlist; +} +function Mapor() { + for (const uid of mappers_userids) { + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (!('userid' in getEntityState(h))) + continue; + if (uid == getEntityState(h).userid) + EntFireByHandle(h, 'KeyValues', 'targetname doodler3', 0.0, null, null); + } + } +} +const distance_KillDiddleBabyShrekEx = 1500; //babyface kill distance from house (runs once every loop) +const distance_door_KillDiddleBabyShrekEx = 300; //baby kill distance from house entrance door (ticks every 0.10s from every loop, until the outer door breaks) +var stoptick_KillDiddleBabyShrekEx = false; +function KillDiddleBabyShrekEx() { + if (!extreme) + return; + stoptick_KillDiddleBabyShrekEx = false; + scheduleInternalScript(() => { + KillDiddleBabyShrekExTick(); + }, 0.1, null, null); + for (let h; (h = Entities.FindByNameWithin(h, 'i_diddlebaby_phys*', Vector(-14402, -14206, 13377), distance_KillDiddleBabyShrekEx));) { + EntFireByHandle(h, 'Break', '', 0.0, null, null); + } +} +function KillDiddleBabyShrekExTick() { + if (stoptick_KillDiddleBabyShrekEx) + return; + scheduleInternalScript(() => { + KillDiddleBabyShrekExTick(); + }, 0.1, null, null); + for (let h; (h = Entities.FindByNameWithin(h, 'i_diddlebaby_phys*', Vector(-13924, -14037, 13405), distance_door_KillDiddleBabyShrekEx));) { + EntFireByHandle(h, 'Break', '', 0.0, null, null); + } +} +var stoptick_KillDiddleDicklettEx = false; +const distance_KillDiddleDicklettEx = 350; +function KillDiddleDicklettExTick() { + if (stoptick_KillDiddleDicklettEx) + return; + scheduleInternalScript(() => { + KillDiddleDicklettExTick(); + }, 0.1, null, null); + for (let h; (h = Entities.FindByNameWithin(h, 'i_diddlebaby_phys*', Vector(-13312, 15618, 13548), distance_KillDiddleDicklettEx));) { + EntFireByHandle(h, 'Break', '', 0.0, null, null); + } +} +var firstrealround = true; +function RoundStart() { + if (shopDefendTimer !== null) { + clearInterval(shopDefendTimer); + shopDefendTimer = null; + } + exmvote_voteallowed = false; + finale_curseorbcheese_tick = true; + zombe_item_users.splice(0); + zombe_item_users = []; + if (extreme) { + vaginaface_limit = 10; + coins_max = coins_max_extrememode; + scheduleInternalScript(() => { + VoteMsg(); + }, 4.0, null, null); + exmvote_voteallowed = true; + scheduleInternalScript(() => { + exmvote_voteallowed = false; + }, 10.9, null, null); + ExevRoundStart(); + ShrekDiddleCannonTick(); + } + else { + vaginaface_limit = 4; + coins_max = coins_max_normalmode; + EntFire('ExtremeShoot*', 'Kill', '', 0.0, null); + } + humanitems_firstround = true; + stageChosen = -1; + normalTorchCooldowns = true; + firststage = true; + ResetDamageFilter(); + scheduleInternalScript(() => { + TickZombieStrip(); + }, 10.0, null, null); + EntFire('fog', 'SetFarZ', '-1', 0.0, self); + shop_cheat = null; + buyers = []; + customers = []; + shopactive = false; + babycount = 0; + TickShopBiasPlayers(); + //EntFire('shopgate','AddOutput','OnBreak>Map_AntiShop_Giltch_Relay>Trigger> >' +String(antishopoverdefend_startdelay) +'>1',0.0,null) //need test, replaced by entities trigger + vaginacount = 0; + if (piv != null) + EntFireByHandle(piv, 'KeyValues', 'targetname doodler3', 5.0, null, null); + let p = null; + while (null != (p = Entities.FindByName(p, 'warmupcheck'))) { + wcheck = p; + } + pivv = false; + PS1list = []; + PSlist = []; + pivvv = false; + if (wcheck != null && wcheck.IsValid()) + Initialize(); + else { + coins = 0 + coins_lastround; + if (checkpoint) { + EntFire('stage_manager', 'InValue', 'finale', 0.0, self); + scheduleInternalScript(() => { + Mapor(); + }, 15.0, null, null); + stagepool = []; + CheckMaxedCoinsCheckpoint(); + SpawnBlobElements(); + let shopcheatrng = RandomInt(0, 100); + if (shopcheatrng > 50) { + EntFire('stripstrop_shopcheat', 'KeyValues', 'origin 4488 880 600', 0.0, null); + EntFire('stripstrop_diddlefriend', 'KeyValues', 'origin 4488 1168 600', 0.0, null); + } + else { + EntFire('stripstrop_diddlefriend', 'KeyValues', 'origin 4488 880 600', 0.0, null); + EntFire('stripstrop_shopcheat', 'KeyValues', 'origin 4488 1168 600', 0.0, null); + } + specdevils.splice(0); + shopangels.splice(0); + tickingsinners = true; + TickInflatingSinners(); + } + else { + stagepool = [0, 1, 2, 3, 4]; + SkipCheck(); + if (stagepool.length > 0) { + if (stagepool.length >= 4) { + //stripper #4 had '5' + exmvote_voteallowed = true; + scheduleInternalScript(() => { + ExemVoteAutoAFKVote(); + }, 10.0, null, null); + scheduleInternalScript(() => { + exmvote_voteallowed = false; + }, 10.9, null, null); + } + scheduleInternalScript(() => { + PickStage(); + }, 11.0 + stagepickdelay, null, null); + } + else + ReachedCheckpoint(); + } + if (exmvote_voteallowed) { + if (extreme) + EXMVOTE_PERCENTAGE = EXMVOTE_NORMAL_PERCENTAGE; + else + EXMVOTE_PERCENTAGE = EXMVOTE_EXTREME_PERCENTAGE; + exmvote_voted = false; + exmvote_playervoted.splice(0); + exmvote_playervotes = 0; + exmvote_playercount = 0; + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if ('exemvote_posyaw' in getEntityState(h)) + delete getEntityState(h).exemvote_posyaw; + } + h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (h.GetHealth() > 0) { + exmvote_playercount++; + let exemVotePlayer = h; + scheduleInternalScript(() => { + let state = getEntityState(exemVotePlayer); + if (state != null) + state.exemvote_posyaw = getOrigin(exemVotePlayer); + }, 0.5, null, null); + scheduleInternalScript(() => { + let state = getEntityState(exemVotePlayer); + if (state != null && state.exemvote_posyaw != null) + state.exemvote_posyaw.z = getAngles(exemVotePlayer).yaw; + }, 0.53, null, null); + } + } + ExtremeModeVoteShowMessage(); + } + function ExemVoteAutoAFKVote() { + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (h.GetHealth() <= 0) + continue; + if (getTeam(h) != 3 && getTeam(h) != 2) + continue; //no specs! + if (!('exemvote_posyaw' in getEntityState(h))) + continue; + let savpos = getEntityState(h).exemvote_posyaw; + let curpos = getOrigin(h); + curpos.z = getAngles(h).yaw; + if (Math.trunc(savpos.x) == Math.trunc(curpos.x) && + Math.trunc(savpos.y) == Math.trunc(curpos.y) && + savpos.z == curpos.z) + EntFire('extreme_mode_vote_shootbrush', 'RemoveHealth', '5', 0.0, h); + } + } + CheckStageState(); + RenderCoinCount(); + if (!firstrealround) { + let scoreadd_time = 0.0; + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (h == null || !h.IsValid()) + continue; + if (getTeam(h) == 3 || getTeam(h) == 2) { + if (h.GetHealth() > 0) { + AddPlayerShopBias({}, SHOPBIAS_ADD_PLAYEDROUND, h, true); + scheduleInternalScript((inputData) => { + ShowPlayerScoreNewRound(inputData); + }, scoreadd_time, h, null); + scoreadd_time += 0.01; + } + } + } + //AddPlayerScore(SHOPBIAS_ADD_PLAYEDROUND,hsclist); = removed because broken + } + else { + firstrealround = false; + ResetAllScore(); + } + } +} +function RenderCoinCount() { + const hundreds = Math.floor(coins / 100); + const tens = Math.floor((coins % 100) / 10); + const units = coins % 10; + const maxHundreds = Math.floor(coins_max / 100); + const maxTens = Math.floor((coins_max % 100) / 10); + const maxUnits = coins_max % 10; + EntFire('coin_text', 'SetDataControlPointX', String(hundreds), 0.0, self); + EntFire('coin_text', 'SetDataControlPointY', String(tens), 0.0, self); + EntFire('coin_text', 'SetDataControlPointZ', String(units), 0.0, self); + EntFire('coin_text_max', 'SetDataControlPointX', String(maxHundreds), 0.0, self); + EntFire('coin_text_max', 'SetDataControlPointY', String(maxTens), 0.0, self); + EntFire('coin_text_max', 'SetDataControlPointZ', String(maxUnits), 0.0, self); + scheduleInternalScript(() => RenderCoinCount(), 0.2, null, null); +} +function CheckStageState() { + let ss = [0, 1, 2, 3, 4]; + for (let i = 0; i < ss.length; i += 1) { + let exists = false; + for (let j = 0; j < stagepool.length; j += 1) { + if (stagepool[j] == ss[i]) { + exists = true; + } + } + if (!exists) { + if (ss[i] == 0) + EntFire('stagedoor_shrek', 'Open', '', 0.0, null); + else if (ss[i] == 1) + EntFire('stagedoor_turtle', 'Open', '', 0.0, null); + else if (ss[i] == 2) + EntFire('stagedoor_soldier', 'Open', '', 0.0, null); + else if (ss[i] == 3) + EntFire('stagedoor_diglett', 'Open', '', 0.0, null); + else if (ss[i] == 4) + EntFire('stagedoor_weeb', 'Open', '', 0.0, null); + } + } +} +function TeleportLate(inputData) { + const { activator, caller } = inputData; + EntFireByHandle(activator, 'KeyValues', 'origin ' + lx + ' ' + ly + ' ' + lz, 0.0, self, self); +} +function SetShopCheat(inputData) { + const { activator, caller } = inputData; + shop_cheat = activator; +} +function CheckAutoSlay(stageind) { + if (firststage) { + firststage = false; + } + else if (!extreme) { + let ctc_check = 0; + let tc_check = 0; + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetHealth() <= 0) + continue; + if (player.GetTeamNumber() == 2) + tc_check++; + else if (player.GetTeamNumber() == 3) + ctc_check++; + } + if (ctc_check > 0 && tc_check > 0) { + const ccratio = ctc_check / tc_check; + if ((stageind == 0 && ccratio < 1.4) || //0 = shrek > 35ct / 25t + (stageind == 1 && ccratio < 1.0) || //1 = turtle > 30ct / 30t + (stageind == 2 && ccratio < 0.7) || //2 = omaha > 25ct / 35t + (stageind == 3 && ccratio < 2.0) || //3 = diglett > 40ct / 20t + (stageind == 4 && ccratio < 0.5) || //4 = weaboo > 20ct / 40t + (stageind == 5 && ccratio < 2.0) //5 = finale > 40ct / 20t + ) { + EntFire('server', 'Command', 'say ***NOT ENOUGH HUMANS - SLAYING TO SAVE TIME***', 0.0, null); + EntFire('server', 'Command', 'say ***NOT ENOUGH HUMANS - SLAYING TO SAVE TIME***', 0.01, null); + EntFire('server', 'Command', 'say ***NOT ENOUGH HUMANS - SLAYING TO SAVE TIME***', 0.02, null); + EntFire('server', 'Command', 'say ***NOT ENOUGH HUMANS - SLAYING TO SAVE TIME***', 0.03, null); + EntFire('server', 'Command', 'say ***NOT ENOUGH HUMANS - SLAYING TO SAVE TIME***', 0.04, null); + KillAllT(); + } + } + } +} +function PickStage() { + let stage = -1; + if (stageChosen == -1) { + let r = RandomInt(0, stagepool.length - 1); + CheckAutoSlay(r); + stage = stagepool[r]; + } + else { + stage = stageChosen; + } + if (extreme) { + KillZombieItems(); + scheduleInternalScript(() => { + SpawnExtremeZombieItem(stage); + }, 0.5, self, self); + } + if (stage == 0) { + EntFire('ExtremeShoot*', 'SetHealth', '999999', 0.0, self); + EntFire('ExtremeShootShrek', 'Kill', '', 0.0, self); + EntFire('stage_manager', 'InValue', 'shrek', 0.0, self); + EntFire('fog', 'SetFogStartDistance', '0', 0.2, self); + EntFire('fog', 'SetFogEndDistance', '40000', 0.2, self); + EntFire('fog', 'SetFogColor', '0 255 0', 0.2, self); + scheduleInternalScript(() => { + TeleportTeam(1, -15289, -14238, 13980); + }, 0.5, self, self); + scheduleInternalScript(() => { + TeleportTeam(2, -15289, -14238, 13980); + }, 8.5, self, self); + EntFire('luff_zhealth', 'FireUser1', '', 8.4, self); + } + else if (stage == 1) { + EntFire('ExtremeShoot*', 'SetHealth', '999999', 0.0, self); + EntFire('ExtremeShootTurtle', 'Kill', '', 0.0, self); + EntFire('stage_manager', 'InValue', 'turtle', 0.0, self); + EntFire('fog', 'SetFogStartDistance', '-500', 0.2, self); + EntFire('fog', 'SetFogEndDistance', '25000', 0.2, self); + EntFire('fog', 'SetFogColor', '255 255 100', 0.2, self); + //EntFire('fog', 'setfogmaxopacity', '0.01', 0.2, self) + scheduleInternalScript(() => { + TeleportTeam(1, 5365, -15070, -14442); + }, 0.5, self, self); + scheduleInternalScript(() => { + TeleportTeam(2, 5365, -15070, -14442); + }, 8.5, self, self); + } + else if (stage == 2) { + EntFire('ExtremeShoot*', 'SetHealth', '999999', 0.0, self); + EntFire('ExtremeShootBeach', 'Kill', '', 0.0, self); + EntFire('stage_manager', 'InValue', 'beach', 0.0, self); + EntFire('fog', 'SetFogStartDistance', '-500', 0.2, self); + EntFire('fog', 'SetFogEndDistance', '8000', 0.2, self); + EntFire('fog', 'SetFogColor', '255 255 255', 0.2, self); + scheduleInternalScript(() => { + TeleportTeam(1, -15164, 1177, 15410); + }, 0.5, self, self); + //scheduleInternalScript(() => { TeleportTeam(2,-15164,1177,15410); }, 15.50, self, self); //TPTEAM-removed in stripper #5, might have caused double-TP? + } + else if (stage == 3) { + EntFire('ExtremeShoot*', 'SetHealth', '999999', 0.0, self); + EntFire('ExtremeShootDiglett', 'Kill', '', 0.0, self); + EntFire('stage_manager', 'InValue', 'diglett', 0.0, self); + EntFire('fog', 'SetFogStartDistance', '40', 0.2, self); + EntFire('fog', 'SetFogEndDistance', '800', 0.2, self); + EntFire('fog', 'SetFogColor', '0 0 0', 0.2, self); + scheduleInternalScript(() => { + TeleportTeam(1, -10396, 12158, 15566); + }, 0.5, self, self); + scheduleInternalScript(() => { + TeleportTeam(2, -10123, 12980, 15760); + }, 2.0, self, self); + } + else if (stage == 4) { + EntFire('ExtremeShoot*', 'SetHealth', '999999', 0.0, self); + EntFire('ExtremeShootWeeaboo', 'Kill', '', 0.0, self); + EntFire('stage_manager', 'InValue', 'weaboo', 0.0, self); + EntFire('fog', 'SetFogStartDistance', '1000', 0.2, self); + EntFire('fog', 'SetFogEndDistance', '2500', 0.2, self); + EntFire('fog', 'SetFarz', '2750', 0.2, self); + EntFire('fog', 'SetFogColor', '0 0 0', 0.2, self); + scheduleInternalScript(() => { + TeleportTeam(1, -4103, 0, -3641); + }, 0.5, self, self); + scheduleInternalScript(() => { + TeleportTeam(2, -4103, 0, -3641); + }, 20.5, self, self); + } + scheduleInternalScript(() => { + }, 0.55, null, null); +} +function ResetOverlay() { + let p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + //EntFire('client', 'Command', 'r_screenoverlay XXLUFF_NULLXX', 0.0, p) + //EntFire('client', 'Command', 'r_screenoverlay ', 0.02, p) + EntFireByHandle(p, 'KeyValues', 'targetname fuckface', 0.0, null, null); + } +} +function ResetDamageFilter() { + let p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (p != null) + EntFireByHandle(p, 'SetDamageFilter', '', 0.0, null, null); + } +} +function KillAllButton() { + let p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (p != null && p.IsValid() && p.GetHealth() > 0) + EntFireByHandle(p, 'SetHealth', '-69', 0.0, null, null); + } +} +function TeleportTeam(team, _x, _y, _z) { + //TEAM 1 = ct/humans, TEAM 2 = t/zombies + lx = _x; + ly = _y; + lz = _z; + team = 4 - team; + let p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (p != null && + p.IsValid() && + getTeam(p) == team && + p.GetHealth() > 0 && + true) { + EntFireByHandle(p, 'KeyValues', 'origin ' + _x + ' ' + _y + ' ' + _z, 0.0, self, self); + } + } +} +function KillAllT() { + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetTeamNumber() != 2 || player.GetHealth() <= 0) + continue; + Instance.EntFireAtTarget({ target: player, input: 'SetHealth', value: '-69', delay: 0.1 }); + } + Instance.EntFireAtName({ name: 'zr_toggle_respawn', input: 'Disable' }); + Instance.EntFireAtName({ name: 'KillAllT', input: 'Enable' }); +} +function KillAllCT() { + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetTeamNumber() != 3 || player.GetHealth() <= 0) + continue; + Instance.EntFireAtTarget({ target: player, input: 'SetHealth', value: '-69', delay: 0.0 }); + } +} +function ApplyStageScore() { + let p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (getTeam(p) == 3) ; + } +} +function CheckMaxedCoins() { + if (coins >= coins_max) { + EntFire('allcoins_effect', 'Trigger', '', 0.0, null); + CheckMaxedCoinsCheckpoint(); + } +} +function CheckMaxedCoinsCheckpoint() { + if (coins >= coins_max) + EntFire('allcoins_event', 'Trigger', '', 0.0, null); +} +function ReachedCheckpoint() { + coins_lastround = 0 + coins; + checkpoint = true; + EntFire('stage_manager', 'InValue', 'finale', 0.0, self); + SpawnBlobElements(); +} +var ddicktimeout = 1.2; +var ddickdead = false; +function DiddleDickBossInitHP() { + if (extreme) + EntFire('dd_hp', 'FireUser1', '', 0.0, null); //extreme only (more base HP) + else + EntFire('dd_hp', 'FireUser2', '', 0.0, null); //default values for normal +} +function DiddleDickBossInit() { + ddicktimeout = 1.2; + ddickdead = false; + DiddleDickBossTick(); +} +function DiddleDickBossTick() { + if (ddickdead) + return; + scheduleInternalScript(() => { + DiddleDickBossTick(); + }, 0.1, null, null); + ddicktimeout -= 0.05; + if (ddicktimeout <= 0) { + //timer is disabled when it should be enabled + EntFire('dd_timer', 'Enable', '', 0.0, null); + ddicktimeout = 1.2; + printl('[ERROR - DIDDLEDICK BOSS] - timed out, enabling logic_timer'); + } + let dc = Entities.FindByName(null, 'dd_case'); + if (dc == null || !dc.IsValid()) { + //no random case named "dd_case" is active, rename closest neighbour + printl('[ERROR - DIDDLEDICK BOSS] - invalid logic_case, reassigning next one'); + if (Entities.FindByName(null, 'dd_case_2') != null) + EntFire('dd_case_2', 'KeyValues', 'targetname dd_case', 0.0, null); + else if (Entities.FindByName(null, 'dd_case_3') != null) + EntFire('dd_case_3', 'KeyValues', 'targetname dd_case', 0.0, null); + else if (Entities.FindByName(null, 'dd_case_4') != null) + EntFire('dd_case_4', 'KeyValues', 'targetname dd_case', 0.0, null); + } +} +const SHOPBIAS_ADD_WONSTAGE = 100; //+ the amount of T's //given to CT's on cleared stage +const SHOPBIAS_ADD_WINSTAGE_NOWINNER = 80; //given to T's on cleared stage +const SHOPBIAS_ADD_PLAYEDROUND = 30; //given to players on round start +const coopresetroundstarttime_humanexploit_check_delay = 10.0; //probably don't need to be tweaked +function ClearedStageCheckHumanPostExploit() { + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (!('isokhumanhaha' in getEntityState(h))) { + if (getTeam(h) == 3 && h.GetHealth() > 0) + EntFireByHandle(h, 'SetHealth', '-1', 0.0, null, null); + } + else + delete getEntityState(h).isokhumanhaha; + } +} +const extreme_cleastage_vaginaface_chance_percentage = 8.0; +const extreme_cleastage_vaginaface_iterations = 5; +function ClearedStage(stageindex) { + if (!extreme) + GiveHumansHP(); + else { + KillZombieItems(); + try { + for (let i = 0; i < extreme_cleastage_vaginaface_iterations; i++) { + let rand = RandomFloat(0.0, 100.0); + if (rand > extreme_cleastage_vaginaface_chance_percentage) + continue; + ExevSpawn('s_vaginaface', Vector(2048 + RandomInt(-300, 300), 1024 + RandomInt(-300, 300), 1600 + RandomInt(-200, 0)), null, null); + } + } + catch (e) { + printl('ClearedStage extreme vaginaspawn error: ' + e); + } + } + coins_lastround = coins; + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (getTeam(h) == 3 && h.GetHealth() > 0) { + getEntityState(h).isokhumanhaha = true; + } + } + scheduleInternalScript(() => { + ClearedStageCheckHumanPostExploit(); + }, coopresetroundstarttime_humanexploit_check_delay, null, null); + EntFire('coin', 'FireUser1', '', 0.0, self); + //EntFire('ctwinscore', 'AddScoreCT', '', 0.0, self) + //EntFire('ctwinscore', 'AddScoreCT', '', 0.0, self) //added a second one in hope of making it work - TODO (keep or remove?) + EntFire('fog', 'SetFogStartDistance', '500', 0.0, self); + EntFire('fog', 'SetFogEndDistance', '15000', 0.0, self); + EntFire('fog', 'SetFogColor', '255 200 100', 0.0, self); + EntFire('fog', 'SetFarz', '-1', 0.0, self); + h = null; + let talivecount = 0; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (h == null || !h.IsValid()) + continue; + if (h.GetHealth() > 0 && getTeam(h) == 2) + talivecount++; + } + h = null; + let hctlist = []; + let htlist = []; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (h == null || !h.IsValid()) + continue; + if (h.GetHealth() > 0 && getTeam(h) == 3) { + hctlist.push(h); + AddPlayerShopBias({}, SHOPBIAS_ADD_WONSTAGE + talivecount, h, true); + } + else if (getTeam(h) == 2) { + htlist.push(h); + AddPlayerShopBias({}, SHOPBIAS_ADD_WINSTAGE_NOWINNER, h, true); + } + } + AddPlayerScore(SHOPBIAS_ADD_WONSTAGE + talivecount, hctlist); + AddPlayerScore(SHOPBIAS_ADD_WINSTAGE_NOWINNER, htlist); + for (let i = 0; i < stagepool.length; i += 1) { + if (stagepool[i] == stageindex) { + stagepool.splice(i, 1)[0]; + break; + } + } + if (stagepool.length <= 0) { + ReachedCheckpoint(); + CheckAutoSlay(5); + } + else + scheduleInternalScript(() => { + PickStage(); + }, 5.0 + stagepickdelay, null, null); + CheckStageState(); + ApplyStageScore(); + for (let i = 0.0; i < 3.0; i += 0.05) { + scheduleInternalScript(() => { + PreventZCheeseSpawn(); + }, i, null, null); + } + for (let i = 0.01; i < 5.0 + stagepickdelay; i += 1.0) { + scheduleInternalScript(() => { + PreventZCheeseSpawn(); + }, i, null, null); + } +} +function PreventZCheeseSpawn() { + let h = null; + while (null != + (h = Entities.FindByClassnameWithin(h, 'player', Vector(2048, 1024, 72), 870))) { + if (getTeam(h) == 2) { + setOrigin(h, Vector(1197, 971, 597)); + setVelocity(h, Vector()); + } + } +} +function SkipCheck() { + let ss = []; + for (let i = 0; i < stagepool.length; i += 1) { + for (let j = 0; j < stageskip.length; j += 1) { + if (stagepool[i] == stageskip[j]) { + ss.push(stagepool[i]); + break; + } + } + } + while (ss.length > 0) { + let idxx = ss.pop(); + for (let k = 0; k < stagepool.length; k += 1) { + if (stagepool[k] == idxx) { + stagepool.splice(k, 1)[0]; + break; + } + } + } +} +const farzvalue = 'X68Xhich_'; +const fogcontrollervalue = 'zombie_TP'; +function SkipStage(stageindex) { + for (let j = 0; j < stageskip.length; j += 1) { + if (stageskip[j] == stageindex) + break; + } + stageskip.push(stageindex); +} +function Initialize() { + EntFire('stage_manager', 'InValue', 'warmup', 0.0, self); + ResetMap(true); //warmup, reset all shop bias score + WarnVoteShowMessage(); +} +function SkipToFinale() { + coins_lastround = 0 + coins; + checkpoint = true; +} +function GetAllCoins() { + coins_lastround = coins_max; + coins = coins_max; + CheckMaxedCoinsCheckpoint(); +} +function ResetMapBase(haha_boobies = true) { + coins = 0; + coins_lastround = 0; + checkpoint = false; + stagepool = [0, 1, 2, 3, 4]; + stageskip = []; + wcheck = null; +} +function ResetMap(button = false) { + ResetMapBase(); + extreme = false; + if (button) + //admin button pressed, reset all shop bias score + ResetAllScore(); +} +function ExtremeCheck() { + if (extreme) { + ResetMapBase(); + } +} +function HealExtremeCheck() { + if (!extreme) { + EntFire('aX69XTurtleHealButton*', 'FireUser4', '', 0.0, null); + } +} +function TorchExtremeCheck() { + if (extreme) { + normalTorchCooldowns = false; + } + else { + EntFire('aX69Xhich*', 'Kill', '', 0.0, null); + } +} +//added variables for these in stripper #2 +const torchcooldown = 100.0; //300s in #1-3 +const torchcooldown_normal = 20.0; +function TorchCooldownVis(inputData) { + const { activator, caller } = inputData; + if (caller == null || !caller.IsValid()) + return; + if (extreme && !normalTorchCooldowns) { + let trfx = 5; + for (let i = 0; i < torchcooldown - 5; i++) { + trfx--; + if (trfx <= 0) { + trfx = 5; + EntFireByHandle(caller, 'Color', '255 0 0', i, null, caller); //CS2 not work, replaced by particle + } + } + EntFireByHandle(caller, 'Color', '255 0 0', 0.0, null, caller); + EntFireByHandle(caller, 'FireUser4', '', torchcooldown, null, caller); + } + else { + let trfx = 5; + for (let i = 0; i < torchcooldown_normal - 5; i++) { + trfx--; + if (trfx <= 0) { + trfx = 5; + EntFireByHandle(caller, 'Color', '255 0 0', i, null, caller); + } + } + EntFireByHandle(caller, 'Color', '255 0 0', 0.0, null, caller); + EntFireByHandle(caller, 'FireUser4', '', torchcooldown_normal, null, caller); + } +} +function TorchCooldown(inputData) { + const { activator, caller } = inputData; + if (extreme && !normalTorchCooldowns) + EntFireByHandle(caller, 'Unlock', '', torchcooldown, activator, caller); + else + EntFireByHandle(caller, 'Unlock', '', torchcooldown_normal, activator, caller); +} +function VoteMsg() { + if (stagepool.length != 0 && extreme) { + EntFire('server', 'Command', 'say ***EXTREME MODE IS ACTIVE, YOU CAN SHOOT THE STAGE PICTURES TO SELECT THEM***', 0.0, null); + } +} +function WonBasicBitch() { + if (!extreme) { + scheduleInternalScript(() => { + ResetMap(); + }, 0.0, null, null); + scheduleInternalScript(() => { + SkipToFinale(); + }, 0.05, null, null); + scheduleInternalScript(() => { + GetAllCoins(); + }, 0.04, null, null); + scheduleInternalScript(() => { + GiveScoreToMapWinners(100); + }, 0.0, null, null); + } + else { + EntFire('server', 'Command', 'say ***YOU WENT THROUGH ALL OF EXTREME WITHOUT GETTING ALL COINS!?***', 3.01, null); + EntFire('server', 'Command', 'say ***YOU WENT THROUGH ALL OF EXTREME WITHOUT GETTING ALL COINS!?***', 3.02, null); + EntFire('server', 'Command', 'say ***YOU WENT THROUGH ALL OF EXTREME WITHOUT GETTING ALL COINS!?***', 3.03, null); + EntFire('server', 'Command', 'say ***WHAT A SHAMEFUL DISPLAY, DO IT AGAIN AND DO IT RIGHT!!!***', 4.01, null); + EntFire('server', 'Command', 'say ***WHAT A SHAMEFUL DISPLAY, DO IT AGAIN AND DO IT RIGHT!!!***', 4.02, null); + EntFire('server', 'Command', 'say ***WHAT A SHAMEFUL DISPLAY, DO IT AGAIN AND DO IT RIGHT!!!***', 4.03, null); + scheduleInternalScript(() => { + GiveScoreToMapWinners(100); + }, 0.0, null, null); + } +} +//OLD SYSTEM, BACK FOR #3 +function OmahaMortarExtremeRender(inputData) { + const { activator, caller } = inputData; + if (caller == null || !caller.IsValid()) + return; + if (!extreme) { + EntFireByHandle(caller, 'Kill', '', 0.0, null, null); + return; + } + scheduleInternalScript((inputData) => { + OmahaMortarExtremeRender(inputData); + }, 0.02, null, caller); + let sc = getEntityState(caller); + if (!('colorstate' in sc)) { + sc.colorstate = 0; + sc.scalestate = false; + EntFireByHandle(caller, 'StartGlowing', '', 0.0, null, null); + EntFireByHandle(caller, 'SetGlowRange', '2000', 0.0, null, null); + //EntFireByHandle(caller, 'AddOutput', 'glowstyle 1', 0.0, null, null) + EntFireByHandle(caller, 'SetGlowOverride', '255 255 255', 0.0, null, null); + EntFireByHandle(caller, 'SetScale', '1.5', 0.0, null, null); + //EntFireByHandle(caller, 'AddOutput', 'rendermode 2', 0.0, null, null) + //EntFireByHandle(caller, 'AddOutput', 'renderamt 0', 0.0, null, null) + EntFireByHandle(caller, 'Enable', '', 0.0, null, null); + return; + } + sc.colorstate++; + if (sc.colorstate > 4) + sc.colorstate = 0; + sc.scalestate = !sc.scalestate; + if (sc.scalestate) + EntFireByHandle(caller, 'SetScale', '1.5', 0.0, null, null); + else + EntFireByHandle(caller, 'SetScale', '2', 0.0, null, null); + if (sc.colorstate == 0) + EntFireByHandle(caller, 'SetGlowOverride', '255 255 255', 0.0, null, null); + else if (sc.colorstate == 1) + EntFireByHandle(caller, 'SetGlowOverride', '255 0 0', 0.0, null, null); + else if (sc.colorstate == 2) + EntFireByHandle(caller, 'SetGlowOverride', '255 255 0', 0.0, null, null); + else if (sc.colorstate == 3) + EntFireByHandle(caller, 'SetGlowOverride', '0 0 0', 0.0, null, null); +} +const fetus_spawn_zombie_extra = 4; //+1 that always spawns originally +function FetusSpawnZombieExtra(inputData) { + const { activator, caller } = inputData; + if (!extreme) + return; + if (caller == null || !caller.IsValid()) + return; + let zztim = 0.1; + for (let i = 0; i < fetus_spawn_zombie_extra; i++) { + EntFireByHandle(caller, 'FireUser3', '', zztim, null, null); + zztim += 0.1; + } +} +function OmahaTrimDelayers(interval = 1.0, steprange = 250) { + let stime = 0.0; + let spos = -11216; + while (spos > -13872) { + const spawnPosition = Vector(spos, 6090, 14130); + scheduleInternalScript(() => { + forceSpawnTemplate('blobblaser_tem1', spawnPosition, null); + }, stime + 0.01, null, null); + stime += interval; + spos -= steprange; + } +} +function DisplayServerWinChat() { + if (!extreme) { + EntFire('server', 'Command', 'say ***YOU SHALL NOW ENTER A NEW CHALLENGE...***', 0.0, null); + EntFire('server', 'Command', 'say ***ENABLING EXTREME MODE...***', 1.0, null); + EntFire('server', 'Command', 'say ***ENABLING EXTREME MODE...***', 1.01, null); + EntFire('server', 'Command', 'say ***ENABLING EXTREME MODE...***', 1.02, null); + } + else { + EntFire('server', 'Command', 'say ***YOU HAVE WON EXTREME MODE!***', 0.0, null); + EntFire('server', 'Command', 'say ***YOU HAVE WON EXTREME MODE!***', 0.01, null); + EntFire('server', 'Command', 'say ***YOU HAVE WON EXTREME MODE!***', 0.02, null); + EntFire('server', 'Command', 'say ***YOU ARE EXTREMELY LEGENDARY!***', 1.0, null); + EntFire('server', 'Command', 'say ***YOU ARE EXTREMELY LEGENDARY!***', 1.01, null); + EntFire('server', 'Command', 'say ***YOU ARE EXTREMELY LEGENDARY!***', 1.02, null); + EntFire('server', 'Command', 'say ***MAP IS OVER - RTV***', 16.05, null); + } +} +function GiveScoreToMapWinners(scoreamount) { + let p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (p != null && p.IsValid() && getTeam(p) == 3 && p.GetHealth() > 0) + AddPlayerShopBias({}, scoreamount, p); + } +} +const turtlefalldown_damage = 70; +var turtlefalldown_platformgone = false; +function TurtleFallDownBoss(inputData) { + const { activator, caller } = inputData; + if (activator == null || !activator.IsValid()) + return; + if (getTeam(activator) != 3) + return; + if (!extreme) { + EntFire('X69XTurtleBossHP1', 'RemoveHealth', '9000', 0.0, activator); + EntFire('X69XTurtleBossHP2', 'RemoveHealth', '9000', 0.0, activator); + } + else { + EntFire('X69XTurtleBossHP1', 'RemoveHealth', '1500', 0.0, activator); + EntFire('X69XTurtleBossHP2', 'RemoveHealth', '1500', 0.0, activator); + let pnewhealth = activator.GetHealth() - turtlefalldown_damage; + if (pnewhealth > 0) { + setVelocity(activator, Vector(0, 0, 0)); + let x = getOrigin(activator).x; + //middle / left / right + if (!turtlefalldown_platformgone) { + if (x > 10464 && x < 11488) + setOrigin(activator, Vector(10944, -9984, -15072)); + else if (x < 10944) + setOrigin(activator, Vector(9728, -10544, -15072)); + else + setOrigin(activator, Vector(12272, -10544, -15072)); + } + else { + if (x > 10464 && x < 11488) + setOrigin(activator, Vector(10944, -9592, -15072)); + else if (x < 10944) + setOrigin(activator, Vector(9728, -10136, -15072)); + else + setOrigin(activator, Vector(12272, -10144, -15072)); + } + EntFireByHandle(activator, 'SetHealth', String(pnewhealth), 0.0, null, null); + } + } +} +function KillShrekFinalRun() { + if (extreme) + EntFire('X69Xluff_npc_phys2gg*', 'Break', '', 0.1, null); + else + EntFire('X69Xluff_npc_phys2gg*', 'Break', '', 1.5, null); +} +function ShrekDiddleCannonTick() { + let hh = null; + let hlist = []; + while (null != (hh = Entities.FindByName(hh, 'X69Xluff_npc_phys2gg*'))) { + hlist.push(hh); + } + if (hlist.length > 0) { + scheduleInternalScript(() => { + ShrekDiddleCannonTick(); + }, 0.01, null, null); + for (const h of hlist) { + let p = null; + while (null != + (p = Entities.FindByNameWithin(p, 'projectile_hurt*', getOrigin(h), 100))) { + EntFireByHandle(p, 'FireUser1', '', 0.0, null, null); + EntFireByHandle(h, 'RemoveHealth', '300', 0.0, null, null); + } + } + } + else + scheduleInternalScript(() => { + ShrekDiddleCannonTick(); + }, 5.0, null, null); +} +function DiddleFriendShopCheck() { + let p = null; + while (null != + (p = Entities.FindByClassnameWithin(p, 'player', Vector(7960, 92, 444), 1250))) { + if (p != null && p.IsValid() && getTeam(p) == 2 && p.GetHealth() > 0) { + let isangel = false; + for (const df of shopangels) { + if (p == df) { + isangel = true; + break; + } + } + if (!isangel) { + setVelocity(p, Vector(0, 0, 0)); + setOrigin(p, Vector(2603, 1027, 156)); + let sc = getEntityState(p); + if ('userid' in sc) { + for (const sbb of SBplayers) { + if (sc.userid == sbb.userid) + EntFire('server', 'Command', 'say [' + + String(sbb.name) + + ' may have tried inflating for an item]', 0.0, null); + } + } + } + } + } +} +function DiddleFriendPostCheck(inputData) { + const { activator, caller } = inputData; + if (activator == null || !activator.IsValid()) + return; + if ((getTeam(activator) != 3 && getTeam(activator) != 2) || + activator.GetHealth() <= 0) + scheduleInternalScript((inputData) => { + DiddleFriendPostCheck(inputData); + }, 2.0, activator, null); + else if (activator.GetHealth() > 0 && getTeam(activator) == 3) + EntFire('diddlefriend_relay', 'Trigger', '', 0.0, activator); +} +const specdevils = []; +const shopangels = []; +var tickingsinners = false; +function TickInflatingSinners() { + if (!tickingsinners) + return; + scheduleInternalScript(() => { + TickInflatingSinners(); + }, 0.05, null, null); + let p = null; + let zfound = false; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (p != null && p.IsValid() && getTeam(p) == 2 && p.GetHealth() > 1009) { + //zombie just detected + zfound = true; + break; + } + else if (p != null && p.IsValid() && getTeam(p) != 2 && getTeam(p) != 3) + specdevils.push(p); + else if (p != null && p.IsValid() && p.GetHealth() <= 0) + specdevils.push(p); + } + if (zfound) { + tickingsinners = false; + p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (p != null && p.IsValid() && getTeam(p) == 2) { + let isdevil = false; + for (const pd of specdevils) { + if (pd == p) { + isdevil = true; + break; + } + } + if (!isdevil) + shopangels.push(p); //mother zombies + } + } + } +} +var weebhousedoor = null; +const yellowlaserspawn_warningindicator_distance = 700; +function YellowLaserSpawned(inputData) { + const { activator, caller } = inputData; + if (!extreme) + return; + if (caller == null || !caller.IsValid()) + return; + let p = null; + while (null != + (p = Entities.FindByClassnameWithin(p, 'player', getOrigin(caller), yellowlaserspawn_warningindicator_distance))) { + if (p != null && p.IsValid() && getTeam(p) == 3 && p.GetHealth() > 0) { + EntFire('extreme_yellowlaser_gtext', 'Start', '', 0.0, p); + EntFire('extreme_yellowlaser_gtext', 'Stop', '', 5.0, p); + } + } +} +function GiveHumansHP() { + let p = null; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (p != null && + p.IsValid() && + getTeam(p) == 3 && + p.GetHealth() > 0 && + p.GetHealth() < 100) { + EntFireByHandle(p, 'SetHealth', '100', 0.0, self, self); + } + } +} +var vaginaface_limit = 4; //added in #9 +function AddVagina(inputData) { + const { activator, caller } = inputData; + //gets called from vaginaface_base as activator + vaginacount++; + if (vaginacount > vaginaface_limit) + //was hardcoded to 4 from the core map, changed to this var in #9 + EntFireByHandle(activator, 'FireUser2', '', 0.0, self, self); +} +function RemoveVagina() { + vaginacount--; +} +const amount_TurtleBossLowerFloowExtreme = 50; +function TurtleBossLowerFloowExtremeExecution() { + const entities = Instance.FindEntitiesByName('X69XTurtleBreakable14'); + if (!entities || !entities.length) + return; + for (const entity of entities) { + const origin = entity.GetAbsOrigin(); + const newOrigin = Vector(origin.x, origin.y, origin.z - amount_TurtleBossLowerFloowExtreme); + setOrigin(entity, newOrigin); + } +} +function TurtleBossLowerFloowExtreme() { + if (!extreme) + return; + EntFireByHandle(self, 'RunScriptInput', 'TurtleBossLowerFloowExtremeExecution', 0.05, null, null); +} +function OverrideVaginaFaceHP() { + if (!extreme) + return; + EntFire('i_vaginaface_script_*', 'RunScriptInput', 'ExfaceHP', 0.0, null, null); +} +function OverrideBabyFaceHP() { + if (!extreme) + return; + //base: 100 (will always be this) + //each: 25 (Z:50, will always be this) + //let hp = caller + //if (hp != null && hp.IsValid()) + EntFire('i_diddlebaby_script*', 'RunScriptInput', 'HpAddSet_15', 0.0, null, null); +} +const cakeheal_extreme = 200; +const cakeheal_normal = 150; +const cakeheal_dicklettboss2entry = 100; +function CakeHealTouch(inputData) { + const { activator, caller } = inputData; + if (activator == null || !activator.IsValid()) + return; + let curhp = activator.GetHealth(); + if (curhp <= 0) + return; + let newhp = 100; + let isbluecake = false; + if (extreme) { + if ('dicklettboss2entry' in getEntityState(caller)) { + newhp = 0 + cakeheal_dicklettboss2entry; + isbluecake = true; + } + else + newhp = 0 + cakeheal_extreme; + } + else + newhp = 0 + cakeheal_normal; + if (curhp >= newhp) { + if (isbluecake) { + newhp = curhp - 1; + } + else + return; + } + EntFireByHandle(activator, 'KeyValues', 'health ' + String(newhp), 0.0, null, null); +} +const babylist = []; +function SpawnBaby(inputData) { + const { activator, caller } = inputData; + babycount++; + babylist.splice(0, 0, caller); + let rrr22 = RandomFloat(0.0, 0.5); + scheduleInternalScript(() => { + CheckMaxBabies(); + }, rrr22, null, null); +} +function CheckMaxBabies() { + if (babycount > 4) + RemoveAndKillOldestBaby(); +} +function RemoveAndKillOldestBaby() { + if (babylist.length > 0 && + babylist.at(-1) != null && + babylist.at(-1).IsValid()) + EntFireByHandle(babylist.at(-1), 'FireUser3', '', 0.0, null, null); + //something went wrong, no matter though, since hammer auto-cleans babies +} +function RemoveBaby(inputData) { + const { activator, caller } = inputData; + for (let i = 0; i < babylist.length; i += 1) { + if (caller == babylist[i]) { + babylist.splice(i, 1)[0]; + babycount--; + break; + } + } +} +function SpawnBlobElements() { + EntFire('' + farzvalue + fogcontrollervalue, 'KeyValues', 'targetname X68X_blobfire', 0.4, null); +} +var PSN = false; +var PSR = false; +var PS1list = []; +var PSlist = []; +const PSBlist = []; +function AddCoins(amount) { + coins += amount; + if (coins > coins_max) + coins = coins_max; + CheckMaxedCoins(); +} +function CheckPS(inputData) { + const { activator, caller } = inputData; + let ex = false; + for (let i = 0; i < PSBlist.length; i += 1) { + if (PSBlist[i] == activator) { + ex = true; + break; + } + } + if (!ex) { + for (let i = 0; i < PSlist.length; i += 1) { + if (PSlist[i] == activator) { + piv = activator; + EntFireByHandle(caller, 'break', '', 0.0, null, null); + break; + } + } + } +} +function CheckPSAffirmal(inputData) { + const { activator, caller } = inputData; + if (activator == piv) + piv = null; + PSBlist.push(activator); +} +function LeavePS(inputData) { + const { activator, caller } = inputData; + if (activator == piv) + piv = null; +} +function ExcludePS() { } +function TryPS1(inputData) { + const { activator, caller } = inputData; + let ex = false; + for (let i = 0; i < PSBlist.length; i += 1) { + if (PSBlist[i] == activator) { + ex = true; + break; + } + } + for (let i = 0; i < PSlist.length; i += 1) { + if (PSlist[i] == activator) { + ex = true; + break; + } + } + if (!ex) + PS1list.push(activator); +} +function TryPS() { } +var psx = 0; +var psy = 0; +function InitPS() { + let r = 0; + r = RandomInt(0, 1); + if (r == 0) + PSR = true; + else + PSR = false; + r = RandomInt(0, 1); + if (r == 0) { + psy = 1168; + } + else { + psy = 880; + } + r = RandomInt(0, 1); + if (r == 0) + PSN = true; + else + PSN = false; + let idx = RandomInt(1, 24); + if (idx > 12) { + if (psy == 1168) + psy = 880; + else + psy = 1168; + } + if (!PSR) { + if (PSN && idx <= 12) + psx = 2952 + 256 * idx; + else if (PSN) + psx = 6280 - 256 * (idx - 12); + else if (!PSN && idx <= 12) + psx = 6280 - 256 * idx; + else if (!PSN) + psx = 2952 + 256 * (idx - 12); + } + else { + if (PSN && idx <= 12) + psx = 6280 - 256 * idx; + else if (PSN) + psx = 2952 + 256 * (idx - 12); + else if (!PSN && idx <= 12) + psx = 2952 + 256 * idx; + else if (!PSN) + psx = 6280 - 256 * (idx - 12); + } + EntFire('fwp', 'KeyValues', 'origin ' + psx + ' ' + psy + ' 1344', 0.0, null); + //EntFire('finale_hborg', 'AddOutput', 'material 10', 0.0, null) //not work in CS2 + //EntFire('finale_lbox', 'AddOutput', 'material 10', 0.0, null) +} +var lx = 1557; +var ly = 1024; +var lz = 256; +function GateOpenCheck() { + if (piv != null && piv.IsValid() && piv.GetHealth() > 0 && getTeam(piv) == 2) + EntFireByHandle(piv, 'KeyValues', 'origin 7700 690 30', 0.0, null, null); +} +function BuyItem(inputData, itemindex) { + const { activator, caller } = inputData; + //HOW TO USE: + //> in the store, add one buy-button for each item + //> OnPressed > manager > RunScriptInput > BuyItem(itemindex); (SEE LIST OF ITEMS ABOVE) + //> The script will process the request and check the price + //> IF the item is affordable, it will go through with the purchase and return FireUser1 + //> OnUser1 > SPAWN-ITEM-LOGIC + //> IF the item is NOT affordable, it will cancel the purchase and return FireUser2 + //> OnUser1 > ITEM-DENIED-LOGIC + // (MIGHT BE GOOD TO HAVE A ~5 SEC DELAY FOR EACH BUY-BUTTON TO AVOID SPAMMING) + let isp = false; + if (items[itemindex] > coins) { + if (activator != piv) + EntFireByHandle(caller, 'FireUser2', '', 0.0, activator, activator); + else + isp = true; + } + else + isp = true; + if (isp) { + let exists = false; + for (let i = 0; i < buyers.length; i += 1) { + if (activator == buyers[i]) { + exists = true; + break; + } + } + if (!exists) { + coins -= items[itemindex]; + if (coins < 0) + coins = 0; + EntFireByHandle(caller, 'FireUser1', '', 0.0, activator, activator); + buyers.push(activator); + if (coins < item_mincost) { + shopactive = false; + EntFire('shopgate', 'Break', '', 1.0, self); + EntFire('server', 'Command', 'say ***SHOP IS SOLD OUT***', 0.0, self); + EntFire('server', 'Command', 'say ***COME IN TO PICK UP LEFTOVERS (IF ANY)***', 1.0, self); + } + } + } +} +function AddCustomer(inputData) { + const { activator, caller } = inputData; + if (activator != piv) { + let exists = false; + for (let i = 0; i < buyers.length; i += 1) { + if (activator == buyers[i]) + exists = true; + } + for (let i = 0; i < customers.length; i += 1) { + if (activator == customers[i]) + exists = true; + } + if (!exists) + customers.push(activator); + } +} +function LeaveCustomer(inputData) { + const { activator, caller } = inputData; + if (activator != piv) { + if (activator != null && activator.IsValid()) { + for (let i = 0; i < customers.length; i += 1) { + if (activator == customers[i]) { + customers.splice(i, 1)[0]; + break; + } + } + for (let i = 0; i < customers.length; i += 1) { + if (activator == customers[i]) { + customers.splice(i, 1)[0]; + break; + } + } + } + } +} +//userid-list for all mappers, only utilized through the luffarenmaps.smx plugin +//gives all 5 mappers priority if hugging the shop-door +const mappers_userids = []; +function PickCustomer() { + if (shopactive && customers.length > 0) { + if (piv == shop_cheat) + shop_cheat = null; + let c = piv; + if (piv == null || !piv.IsValid() || pivv || getTeam(piv) != 3) { + let mappersrequesting = []; + if (mappers_userids.length > 0) { + for (const cus of customers) { + if (cus != null && + cus.IsValid() && + cus.GetHealth() > 0 && + getTeam(cus) == 3) { + if (!('userid' in getEntityState(cus))) + continue; + if (getEntityState(cus).userid == null) + continue; + for (const muid of mappers_userids) { + if (getEntityState(cus).userid == muid) { + mappersrequesting.push(cus); + break; + } + } + } + } + } + if (mappersrequesting.length > 0) + c = mappersrequesting[RandomInt(0, mappersrequesting.length - 1)]; + else if (shop_cheat != null && + shop_cheat.IsValid() && + getTeam(shop_cheat) == 3 && + shop_cheat.GetHealth() > 0) + c = shop_cheat; //shop cheat (someone shot secret wall) + else + c = customers[RandomInt(0, customers.length - 1)]; + } + else if (pivvv) { + if (shop_cheat != null && + shop_cheat.IsValid() && + getTeam(shop_cheat) == 3 && + shop_cheat.GetHealth() > 0) + c = shop_cheat; + else { + customers = ScrambleArray(customers); + customers = GetSortedShopBiasList(customers); + c = customers[RandomInt(0, customers.length - 1)]; + } + } + if (c == null || !c.IsValid()) + scheduleInternalScript(() => { + PickCustomer(); + }, 0.1, self, self); + else { + EntFireByHandle(c, 'KeyValues', 'origin 7800 400 300', 0.0, self, self); + scheduleInternalScript((inputData) => { + LeaveCustomer(inputData); + }, 0.0, c, c); + scheduleInternalScript((inputData) => { + SafeCustomerExit(inputData); + }, 7.0, c, null); + if (c == piv) + pivv = true; + scheduleInternalScript(() => { + PickCustomer(); + }, 7.05, self, self); + //****** + //if buyer is shop_cheat, null shop_cheat for this round + //****** + if (c == shop_cheat) + shop_cheat = null; + } + } + else if (shopactive && customers.length == 0) + scheduleInternalScript(() => { + PickCustomer(); + }, 0.1, self, self); +} +function SafeCustomerExit(inputData) { + const { activator, caller } = inputData; + if (activator == null || !activator.IsValid()) + return; + if (getTeam(activator) == 3) + setOrigin(activator, Vector(7300, 300, 200)); + else + EntFireByHandle(activator, 'SetHealth', '-1', 0.0, null, null); +} +var humanitems_firstround = true; +function SpawnExtremeZombieItem(stage) { + //TODO - spawn in zombie items based on the stages (only runs if it's extreme-mode) + let humanitem_pos = Vector(0, 0, 0); + // ITEMX_tem_luffaren_baby + // ITEMX_tem_luffaren_airjump + // ITEMX_tem_luffaren_curse + // ITEMX_tem_luffaren_jihad + // ITEMX_tem_hich_zombiespeed + if (stage == 0) { + //shrek + humanitem_pos = Vector(-14144, -14332, 13316); + //old positions in stripper #4 (is now moved outside the house, because of wall-stacking cheese) + //ExevSpawn("ITEMX_tem_luffaren_baby",Vector(-14456,-14072,13320)); + //ExevSpawn("ITEMX_tem_luffaren_airjump",Vector(-14456,-13976,13320)); //was curse in #3, is now airjump since #4 + ExevSpawn('ITEMX_tem_luffaren_baby', Vector(-13660, -14360, 13300)); + ExevSpawn('ITEMX_tem_luffaren_airjump', Vector(-13660, -14460, 13300)); + } + else if (stage == 1) { + //turtle + humanitem_pos = Vector(6144, -12608, -15294); + ExevSpawn('ITEMX_tem_luffaren_baby', Vector(4872, -12672, -15288)); + ExevSpawn('ITEMX_tem_luffaren_curse', Vector(4872, -12544, -15288)); + ExevSpawn('ITEMX_tem_hich_zombiespeed', Vector(4872, -12416, -15288)); + } + else if (stage == 2) { + //omaha + humanitem_pos = Vector(-12288, -1296, 13786); + ExevSpawn('ITEMX_tem_luffaren_baby', Vector(-12472, -1240, 13776)); + ExevSpawn('ITEMX_tem_luffaren_airjump', Vector(-12472, -1112, 13776)); + ExevSpawn('ITEMX_tem_hich_zombiespeed', Vector(-12472, -984, 13776)); //was curse in #3, is now zombiespeed since #4 + ExevSpawn('ITEMX_tem_luffaren_jihad', Vector(-12520, -1240, 13780)); //added in #9 + } + else if (stage == 3) { + //dicklett + humanitem_pos = Vector(-12392, 13882, 15400); + ExevSpawn('ITEMX_tem_hich_zombiespeed', Vector(-10232, 11944, 15448)); //was curse in #3, is now zombiespeed since #4 + ExevSpawn('ITEMX_tem_luffaren_airjump', Vector(-10360, 11944, 15448)); + ExevSpawn('ITEMX_tem_luffaren_baby', Vector(-10488, 11944, 15448)); + } + else if (stage == 4) { + //weaboo + humanitem_pos = Vector(-4111, 1088, -3662); + ExevSpawn('ITEMX_tem_luffaren_jihad', Vector(-4464, 1024, -3656)); + ExevSpawn('ITEMX_tem_luffaren_baby', Vector(-4336, 1024, -3656)); + ExevSpawn('ITEMX_tem_luffaren_airjump', Vector(-4208, 1024, -3656)); + ExevSpawn('ITEMX_tem_luffaren_curse', Vector(-4080, 1024, -3656)); + } + if (humanitems_firstround) { + //HUMAN ITEM SPAWNS ON EXTREME + humanitems_firstround = false; + //-------------------------------- + //green wall: ITEMX_tem_qaz_small + //red wall: ITEMX_tem_qaz_mid + //yellow wall: ITEMX_tem_qaz_big + //small dick: ITEMX_tem_ord_dick + //big dick: ITEMX_tem_ord_bigdick + //push: ITEMX_tem_Turtle_push + //diddlecannon: ITEMX_tem_luffaren_diddlecannon + //red heal: ITEMX_tem_hich_heal + //yellow heal: ITEMX_tem_hich_legendaryheal + //-------------------------------- + ExevSpawn('ITEMX_tem_hich_heal', VectorAdd(humanitem_pos, Vector(-64, 96, 4))); + ExevSpawn('ITEMX_tem_qaz_small', VectorAdd(humanitem_pos, Vector(-64, 32, 4))); + ExevSpawn('ITEMX_tem_qaz_small', VectorAdd(humanitem_pos, Vector(-64, -32, 4))); + ExevSpawn('ITEMX_tem_qaz_mid', VectorAdd(humanitem_pos, Vector(-64, -96, 4))); + ExevSpawn('ITEMX_tem_ord_dick', VectorAdd(humanitem_pos, Vector(64, 96, 4))); + ExevSpawn('ITEMX_tem_Turtle_push', VectorAdd(humanitem_pos, Vector(64, 32, 4))); + ExevSpawn('ITEMX_tem_luffaren_diddlecannon', VectorAdd(humanitem_pos, Vector(64, -32, 4))); + //ExevSpawn("XXXXXXXXXXXXXXX",VectorAdd(humanitem_pos, Vector(64,-96,4))); + //ExevSpawn("XXXXXXXXXXXXXXX",VectorAdd(humanitem_pos, Vector(0,96,4))); + //ExevSpawn("XXXXXXXXXXXXXXX",VectorAdd(humanitem_pos, Vector(0,32,4))); + //ExevSpawn("XXXXXXXXXXXXXXX",VectorAdd(humanitem_pos, Vector(0,-32,4))); + //ExevSpawn("XXXXXXXXXXXXXXX",VectorAdd(humanitem_pos, Vector(0,-96,4))); + } +} +//=============================================================================== +// EXTRA EVENTS ON EXTREME (spawning templates, extra logic, etc) +//=============================================================================== +/*-------------------------------------------------------------------- +;ExtremeEvent#X XXXXX - XXXXXXXXXXXXXXXXXXXX +modify: +{ + match: + { + "classname" "XXXXXXXXXXXXXXXXXXXX" + "targetname" "XXXXXXXXXXXXXXXXXXXX" + } + insert: + { + "OnXXXXXXXX" "managerRunScriptInputExtremeEvent(XXXXXXXXXX);0.001" + } +} +--------------------------------------------------------------------*/ +//===========[ SPAWNER HELPERS FOR EXTREME EVENTS ]===============\\ +// +// EntFire("XXXXX","XXXXX","",0.00,null); +// +// [spawning something] +// ExevSpawn("XXXXXXXXXX",Vector,Vector,time); +// ExevSpawn("XXXXXXXXXX",Vector,Vector); +// ExevSpawn("XXXXXXXXXX",Vector); +// +// [setting up spawnbounds] +// exev_spawnbounds.length = 0; +// exev_spawnbounds.push(ExSpawnBounds("XXXXXXXXXX",minX,maxX,minY,maxY,Z,rot)); +// ... +// exev_spawnrate = 0.50; +// scheduleInternalScript(() => { if(!exev_spawnticking)ExevSpawnTick(); }, 0.00, null, null); +// scheduleInternalScript(() => { exev_spawnbounds.length = 0; }, 30.00, null, null);//STOP +// +// [setting up spawns] +// exev_spawns.length = 0; +// exev_spawns.push(ExSpawn("XXXXXXXXXX",Vector,Vector)); +// ... +// exev_spawnrate = 0.50; +// scheduleInternalScript(() => { if(!exev_spawnticking)ExevSpawnTick(); }, 0.00, null, null); +// scheduleInternalScript(() => { exev_spawns.length = 0; }, 30.00, null, null);//STOP +// +// [spawning something on a random CT] +// GetRandomCT(XXXXXXXXXX,Vector(0,0,48)); +// scheduleInternalScript(() => { GetRandomCT("XXXXXXXXXX",Vector(0,0,48)); }, 0.00, null, null); +// +// blue laser template: blobblaser_tem (blue laser explodes at 3.0s after spawn) +// yellow laser template: blobblaser_tem1 (yellow laser explodes at 6.0s after spawn) +// shrek npc template: X69Xluff_shrekspawn (min 0.52s: EntFire("X69Xluff_npc_phys2gg*","RunScriptInput"," AddHP(10,5); ",0.55,null); +// tall laser template: upslash_spawner (put template ~384 / ~640 units above ground, movedir is yaw:90) +// wide laser template: slash_spawner (jump 16 units above ground, crouch 72 units above ground, movedir is yaw:90) +// vaginaface template: s_vaginaface (centered template) +// babyface template: s_diddlebaby (centered template) +// shrekface template s_shrekface (spawn at 8 units above ground????) +// turtle cake template: itemturtleTemplateHeal2 (centered template) +// curse orb template: s_curse (centered template, forward dir should be 0 0 0) +// 10-coin template: s_coin_3 (put template exactly at ground, center 0 units above) +// mortarstrike template: s_mortar (centered template) +// +//================================================================\\ +const extreme_extracakes_in_weeb = 2; //3 in #3 +const extreme_shreks_in_weeb = 5; +function ExtremeEvent(inputData, index) { + const { activator, caller } = inputData; + if (!extreme && index != 71) + //case 71 fixes a thing needed for normal as well + return; + printl('ExtremeEvent#' + String(index) + ' triggered!'); + switch (index) { + case 1: { + //shrek - every time the shrek-house door is shot/broken (for timed yellow laser on hole) + //floor opens after 40.0s, spawn yellow laser at (-9040,-13408,13360) before that + ExevSpawn('blobblaser_tem1', Vector(-9040, -13408, 13360), null, 34.0); + break; + } + case 2: { + //shrek - spawn logic for layer #5 (luff_layer > OnCase05) + //spawn shrek npc (original one spawns at 0.0s on this trigger), spawn at 0.30s at pos: (-9208 -13415 13362) + ExevSpawn('X69Xluff_shrekspawn', Vector(-9208, -13415, 13362), null, 0.3); + break; + } + case 3: { + //shrek - spawn logic for layer #4 (luff_layer > OnCase04) + //spawn some vaginafaces out in the darkness at pos (-14485,-14750,14030),(-12271,-13113,13479),(-9141,-13397,13525) + ExevSpawn('s_vaginaface', Vector(-14485, -14750, 14030), null, null); + ExevSpawn('s_vaginaface', Vector(-12271, -13113, 13479), null, null); + ExevSpawn('s_vaginaface', Vector(-9141, -13397, 13525), null, null); + break; + } + case 4: { + //shrek - just when reaching the final hallway-part + //spawn a 10-coin at pos (-8799,-13407,12797), in final room before jumping down into the black, gotta hold/use a torch to get it! + ExevSpawn('s_coin_3', Vector(-8799, -13407, 12797), null, 1.0); + //startbreak breaks at 22.50s, spawn 3 yellow lasers here to time it at positions (-1792 -12800 13408),(Y: -12512),(Y: -13088) + ExevSpawn('blobblaser_tem1', Vector(-1792, -12800, 13408), null, 17.0); + ExevSpawn('blobblaser_tem1', Vector(-1792, -12512, 13408), null, 17.0); + ExevSpawn('blobblaser_tem1', Vector(-1792, -13088, 13408), null, 17.0); + //endbreak breaks at 54.70s, spawn yellow laser at the top of the ramp (originally curse-orb, but it was way too dark/OP) + ExevSpawn('blobblaser_tem1', Vector(9216, -12796, 14432), Vector(0, 0, 0), 65.0); + break; + } + case 5: { + //shrek - when the final run endbreak has broken (60s until stage exit, finaly ramp/hold) + //spawn in a yellow laser at pos (9376,-12800,14432), maybe at around ~35s + ExevSpawn('blobblaser_tem1', Vector(9376, -12800, 14432), null, 30.0); + //spawn in jump/crouch lasers at pos: (7040,-12800,14416/14472), yaw:270 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(7040, -12800, 14416), Vector(0, 270, 0))); + //exev_spawns.push(ExSpawn("slash_spawner",Vector(7040,-12800,14472),Vector(0,270,0))); (commented out - make it jump-only due to the darkness) + exev_spawnrate = 2.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) { + ExevSpawnTick(); + } + }, 20.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 47.0, null, null); //STOP + //kill zombie items: + scheduleInternalScript(() => { + KillZombieItems(); + }, 50.0, null, null); + break; + } + case 6: { + //shrek - spawn logic for layer #2 (luff_layer > OnCase02) + //spawn mortars, min:(-11392,-14720,14080) max:(-8960,-12288,14080) + exev_spawnbounds.splice(0); + exev_spawnbounds.push(ExSpawnBounds('s_mortar', -11392, -8960, -14720, -12288, 14080, Vector(0, 0, 0))); + exev_spawnrate = 0.85; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 18.0, null, null); + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 40.0, null, null); //STOP + break; + } + case 7: { + //turtle - just as you enter the stage + //spawn a shrek near the first 5-coin at pos (6585,-10000,-15190) + ExevSpawn('X69Xluff_shrekspawn', Vector(6585, -1e4, -15190)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.6, null); + //spawn babies at pos (4736,-8576,-14816),(6016,-8576,-14816),(3840,-11520,-14592),(6912,-11520,-14592) + ExevSpawn('s_diddlebaby', Vector(6912, -11520, -14592)); + ExevSpawn('s_diddlebaby', Vector(4736, -8576, -14816)); + ExevSpawn('s_diddlebaby', Vector(6016, -8576, -14816)); + ExevSpawn('s_diddlebaby', Vector(3840, -11520, -14592)); + //spawn vaginafaces at pos (6016,-9632,-15200),(5376,-9632,-15200) + ExevSpawn('s_vaginaface', Vector(6016, -9632, -15200)); + ExevSpawn('s_vaginaface', Vector(5376, -9632, -15200)); + //spawn upslash-lasers at yaw:180 and X-range: (4352,6400) at Y/Z pos (-12296 -14928) + //spawn slash-lasers at yaw:180 and X-range: (4352,6400) at Y/Z pos (-12296 -15288) + //exev_spawnbounds.push(ExSpawnBounds("slash_spawner",4352,6400,-12296,-12296,-15288,Vector(0,180,0))); //commented out in #4 + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', 4352, 6400, -12296, -12296, -14928, Vector(0, 180, 0))); + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', 4352, 6400, -12296, -12296, -14928, Vector(0, 180, 0))); + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', 4352, 6400, -12296, -12296, -14928, Vector(0, 180, 0))); + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', 4352, 6400, -12296, -12296, -14928, Vector(0, 180, 0))); + exev_spawnrate = 1.0; //was 0.60 in #3 + if (!exev_spawnticking) + ExevSpawnTick(); + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 8.0, null, null); //STOP + break; + } + case 8: { + //turtle - trigger that enables the very first push-elevator in 10s + //push enables at 10s, takes maybe 1-2s to get up, spawn yellow laser so it explodes at 11.3s at pos (5376,-8640,-13792) + ExevSpawn('blobblaser_tem1', Vector(5376, -8640, -13792), Vector(0, 0, 0), 5.3); + //type a servermessage about the above too^ + EntFire('server', 'Command', 'say *This is extreme, you better not doorhug...*', 3.0, null); + //spawn 2 shreks at pos (7040,-9152,-13760),(3712,-9152,-13760) - RNG 1 extra between(5760,-10624,-13024),(4992,-10624,-13024) + ExevSpawn('X69Xluff_shrekspawn', Vector(7040, -9152, -13760)); + ExevSpawn('X69Xluff_shrekspawn', Vector(3712, -9152, -13760)); + if (RandomInt(0, 1) == 1) + ExevSpawn('X69Xluff_shrekspawn', Vector(5760, -10624, -13024)); + else + ExevSpawn('X69Xluff_shrekspawn', Vector(4992, -10624, -13024)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.6, null); + //spawn a 10-coin at pos (3747,-11356,-13769), make sure there's no props/free clearance to get there + ExevSpawn('s_coin_3', Vector(3747, -11356, -13769)); + //stripper#8: spawn yellow laser down below as well after some time, to prevent cheesing the defense too hard + ExevSpawn('blobblaser_tem1', Vector(5376, -8737, -14785), Vector(0, 0, 0), 30.0); + break; + } + case 9: { + //turtle - when triggering LEFT top hold just after push-elevator (door breaks at 40.0s) + //mid-in-hold:(5888,-10496,-13056) just-behind-door:(5888,-10816,-13056) top-of-ramp:(5888,-11328,-13112) + //spawn yellow laser somewhere, maybe time it with the door opening? + ExevSpawn('blobblaser_tem1', Vector(5888, -10816, -13056), null, 34.5); + break; + } + case 10: { + //turtle - when triggering RIGHT top hold just after push-elevator (door breaks at 40.0s) + //mid-in-hold:(4864,-10496,-13056) just-behind-door:(4864,-10816,-13056) top-of-ramp:(4864,-11328,-13112) + //spawn yellow laser somewhere, maybe time it with the door opening? + ExevSpawn('blobblaser_tem1', Vector(4864, -10816, -13056), null, 34.5); + break; + } + case 11: { + //turtle - when the first turtle trim 'TurtleLinear1' (near the end of the first area) has triggered 'OnFullyOpened' + //make 'TurtleLinear1' close/open a few times, juke those players + //add a server console message too, "say *That was a close one!*" runs 3.00s after ´Open´ has been called + EntFire('TurtleLinear1', 'Close', '', 0.8, null); + EntFire('server', 'Command', 'say *Wait, this is extreme mode!*', 1.0, null); + EntFire('server', 'Command', "say *It's pretty extreme, right?*", 2.0, null); + EntFire('TurtleLinear1', 'Open', '', 3.0, null); + EntFire('server', 'Command', 'say *Watch it, here it comes again!*', 3.0, null); + break; + } + case 12: { + //turtle - when triggering the final hold of the first area (gate closes at 40.0s, tp enables at 50.0s) + //spawn vaginafaces at the left/rigtht at the end of the first-final-area:(6592,-14528,-13568),(4160,-14528,-13568) + ExevSpawn('s_vaginaface', Vector(6592, -14528, -13568)); + ExevSpawn('s_vaginaface', Vector(4160, -14528, -13568)); + //spawn vaginafaces across the second area:(14848,-8640,-12928),(15417,-11868,-13094),(13797,-14076,-14602),(10398,-13584,-15244),(10972,-12783,-14183) + ExevSpawn('s_vaginaface', Vector(14848, -8640, -12928)); + ExevSpawn('s_vaginaface', Vector(15417, -11868, -13094)); + ExevSpawn('s_vaginaface', Vector(13797, -14076, -14602)); + ExevSpawn('s_vaginaface', Vector(10398, -13584, -15244)); + ExevSpawn('s_vaginaface', Vector(10972, -12783, -14183)); + //spawn babyfaces across the second area:(13930,-10304,-13883),(14850,-7040,-13242),(14400,-14015,-14219),(10340,-15327,-13462),(9417,-11764,-14979) + ExevSpawn('s_diddlebaby', Vector(13930, -10304, -13883)); + ExevSpawn('s_diddlebaby', Vector(14850, -7040, -13242)); + ExevSpawn('s_diddlebaby', Vector(14400, -14015, -14219)); + ExevSpawn('s_diddlebaby', Vector(10340, -15327, -13462)); + ExevSpawn('s_diddlebaby', Vector(9417, -11764, -14979)); + //spawn 2 curse orbs across the start of the second area:(14400,-8576,-14272,y:0),(15296,-8576,-14272,y:180), maybe at ~55.0s? + ExevSpawn('s_curse', Vector(15296, -8576, -14272), Vector(0, 180, 0), 55.0); + ExevSpawn('s_curse', Vector(14400, -8576, -14272), Vector(0, 0, 0), 55.0); + break; + } + case 13: { + //turtle - when a player just enters the cake-room + //spawn a vaginaface, make the climber stressed, TP-out enables at 7.0s:(7863,-14095,-13620),yaw:90 + ExevSpawn('s_vaginaface', Vector(7863, -14095, -13620)); + break; + } + case 14: { + //turtle - when entering trigger just before the long bridge in the second area (triggering 2nd turtle-trim) + //'TurtleLinear2' runs 'Open' + 'TurtleSound6' runs 'PlaySound' at 8.0s, repeat that and toggle between Close/Open a few times + //add some extra server messages to indicate that the turtles are coming back^ ("say *Do you hear that again..?*") + EntFire('TurtleLinear2', 'Close', '', 12.0, null); + EntFire('TurtleSound6', 'PlaySound', '', 12.0, null); + EntFire('server', 'Command', 'say *Extreme funk!*', 12.0, null); + //| + EntFire('TurtleLinear2', 'Open', '', 18.0, null); + EntFire('TurtleSound6', 'PlaySound', '', 18.0, null); + EntFire('server', 'Command', 'say *Extreme punk!*', 18.0, null); + //| + EntFire('TurtleLinear2', 'Close', '', 24.0, null); + EntFire('TurtleSound6', 'PlaySound', '', 24.0, null); + EntFire('server', 'Command', 'say *Extreme dunk!*', 24.0, null); + //| + EntFire('TurtleLinear2', 'Open', '', 30.0, null); + EntFire('TurtleSound6', 'PlaySound', '', 30.0, null); + EntFire('server', 'Command', 'say *Extreme gunk!*', 30.0, null); + break; + } + case 15: { + //turtle - when triggering the long bridge hold in the second area (z-push enables after 27.0s, hold opens after 30.0s) + //after ~10s, spawn tall lasers towards the bridge at a fast rate for ~15s:(14310,-13928/-13208,-14096),yaw:90 + exev_spawnbounds.splice(0); + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', 14700, 14310, -13928, -13208, -14096, Vector(0, 90, 0))); + //after ~10.0s, start spawning jump+crouch lasers for ~15.0s on the bottom path,(x:10572,y:-13376/-13856,z:-14832/-14776),yaw:90 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(13500, -13376, -14832), Vector(0, 90, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(13500, -13376, -14776), Vector(0, 90, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(13500, -13856, -14832), Vector(0, 90, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(13500, -13856, -14776), Vector(0, 90, 0))); + exev_spawnrate = 2.0; //was 0.70 in #3 + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 10.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + exev_spawnbounds.splice(0); + }, 25.0, null, null); //STOP + break; + } + case 16: { + //turtle - just when triggering the final hold before the boss arena (opens after 30.0s) + //spawn jump lasers relative to the very top of the ramp, after ~10.0s for 15.0s:(10496/11008,-15360,-13296),yaw:0 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(10496, -15360, -13296), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(11008, -15360, -13296), Vector(0, 0, 0))); + exev_spawnrate = 1.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 10.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 25.0, null, null); //STOP + //spawn a yellow laser, time it to exactly blow with how doorhuggers fall down, at pos:(10944,-12416,-14768) + ExevSpawn('blobblaser_tem1', Vector(10944, -12416, -14768), null, 26.4); + break; + } + case 17: { + //turtle - when you enter the trigger that starts the boss (boss starts at ~21.52s) + //start spawning blue lasers around random players at random, random offset too (start at ~10s, do it a few times until the boss starts) + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem', Vector(0, 0, 48)); + }, 10.0, null, null); + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem', Vector(0, 0, 48)); + }, 14.0, null, null); + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem', Vector(0, 0, 48)); + }, 17.0, null, null); + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem', Vector(0, 0, 48)); + }, 21.0, null, null); + //kill zombie items: (nah, this never worked due to a stripper-fuckup, but now it's fixed, but just don't do it, keep it extreme) + //scheduleInternalScript(() => { KillZombieItems(); }, 15.00, null, null); + //LASERS: + //(move it further back by increasing the Y value, less-negative) + //yaw: 180 + //jump height: -15134 + //crouchheight: -15078 + //leftplat1: 9472,-8192 ExevSpawn("slash_spawner",Vector(9472,-8192,rls[RandomInt(0,1)]),Vector(0,180,0),0.00); + //leftplat2: 9984,-8192 ExevSpawn("slash_spawner",Vector(9984,-8192,rls[RandomInt(0,1)]),Vector(0,180,0),0.00); + //leftbridge: 10464,-7840 ExevSpawn("slash_spawner",Vector(10464,-7840,rls[RandomInt(0,1)]),Vector(0,180,0),0.00); + //middleplat: 10944,-7488 ExevSpawn("slash_spawner",Vector(10944,-7488,rls[RandomInt(0,1)]),Vector(0,180,0),0.00); + //rightbridge: 11456,-7840 ExevSpawn("slash_spawner",Vector(11456,-7840,rls[RandomInt(0,1)]),Vector(0,180,0),0.00); + //rightplat2: 12000,-8192 ExevSpawn("slash_spawner",Vector(12000,-8192,rls[RandomInt(0,1)]),Vector(0,180,0),0.00); + //rightplat1: 12512,-8192 ExevSpawn("slash_spawner",Vector(12512,-8192,rls[RandomInt(0,1)]),Vector(0,180,0),0.00); + // var rls = [-15134,-15078]; + break; + } + case 18: { + //turtle - when boss1 logic_case attack 1 is triggered (Chill - get on bridge) + //----------> starts hurting at 4.0s - ends at 7.0s + //spawn lasers at 3.0s on the bridges, until 7.0s + let rls = [-15134, -15078]; + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.0); + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.0); + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.0); + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 6.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 6.0); + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 7.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 7.0); + break; + } + case 19: { + //turtle - when boss1 logic_case attack 2 is triggered (Spears - get off bridge) + //----------> spears rise at 4.0s - goes down at 7.0s + //spawn lasers at 3.0s on the 3 platforms, until 7.0s + let rls = [-15134, -15078]; + ExevSpawn('slash_spawner', Vector(9472, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.0); + ExevSpawn('slash_spawner', Vector(9984, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.0); + ExevSpawn('slash_spawner', Vector(12000, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.0); + ExevSpawn('slash_spawner', Vector(12512, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.0); + ExevSpawn('slash_spawner', Vector(9472, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.0); + ExevSpawn('slash_spawner', Vector(9984, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.0); + ExevSpawn('slash_spawner', Vector(12000, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 6.0); + ExevSpawn('slash_spawner', Vector(12512, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 6.0); + ExevSpawn('slash_spawner', Vector(9472, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 7.0); + ExevSpawn('slash_spawner', Vector(9984, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 7.0); + ExevSpawn('slash_spawner', Vector(12000, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 8.0); + ExevSpawn('slash_spawner', Vector(12512, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 8.0); + break; + } + case 20: { + //turtle - when boss1 logic_case attack 3 is triggered (Launch - crouch on side-platforms) + //----------> lasers spawn at 3.0s - moves at 4.0s + //spawn blue lasers on 2 random players + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem', Vector(0, 0, 48)); + }, 2.0, null, null); + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem', Vector(0, 0, 48)); + }, 3.0, null, null); + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem', Vector(0, 0, 48)); + }, 4.0, null, null); + break; + } + case 21: { + //turtle - when boss1 logic_case attack 4 is triggered (Water Blast - get off middle-platform) + //----------> water blast starts at 3.0s - ends at 6.0s + //spawn lasers at 2.0s on the bridges, until 6.0s + let rls = [-15134, -15078]; + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 2.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 2.5); + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.5); + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.5); + ExevSpawn('slash_spawner', Vector(10464, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.0); + ExevSpawn('slash_spawner', Vector(11456, -7840, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.5); + break; + } + case 22: { + //turtle - when boss2 logic_case attack 1 is triggered (Turtle - dodge pictures in middle-platform) + //----------> pictures spawn at 3.0s - first moves at 4.0s - second moves at 5.0s + //spawn 2 vaginafaces to cause some mayhem, at pos:(9728,-7680,-14592),(12288,-7680,-14592) + ExevSpawn('s_vaginaface', Vector(9728, -7680, -14592)); + ExevSpawn('s_vaginaface', Vector(12288, -7680, -14592)); + break; + } + case 23: { + //turtle - when boss2 logic_case attack 2 is triggered (Launch - jump and crouch on side-platforms) + //----------> lasers spawn at 3.0s - bottom moves at 4.0s - top moves at 5.0s + //spawn lasers in the middle platform a few times, from 2.0s to 5.0s + let rls = [-15134, -15078]; + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 2.0); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 2.5); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.0); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.5); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.0); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.5); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.0); + break; + } + case 24: { + //turtle - when boss2 logic_case attack 3 is triggered (Error - a block spawns on a random platform) + //----------> error spawns at ~4.0 - 5.0s at random - breaks at ~11.0 - 12.0s + //spawn 2 vaginafaces at pos:(9728,-7680,-14592),(12288,-7680,-14592) + ExevSpawn('s_vaginaface', Vector(9728, -7680, -14592)); + ExevSpawn('s_vaginaface', Vector(12288, -7680, -14592)); + break; + } + case 25: { + //turtle - when boss2 logic_case attack 4 is triggered (29 - jump forward then back on the side-platforms) + //----------> front platform goes up at 0.0s - main platform breaks at 4.0s - main platform respawns at 6.0s - front platform goes down at 8.0s + //spawn lasers at 1.0s on the platforms, until 9.0s + let rls = [-15134, -15078]; + ExevSpawn('slash_spawner', Vector(9472, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 1.0); + ExevSpawn('slash_spawner', Vector(9984, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 2.0); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 3.0); + ExevSpawn('slash_spawner', Vector(12000, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 4.0); + ExevSpawn('slash_spawner', Vector(12512, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 5.0); + ExevSpawn('slash_spawner', Vector(12000, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 6.0); + ExevSpawn('slash_spawner', Vector(10944, -7488, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 7.0); + ExevSpawn('slash_spawner', Vector(9984, -8192, rls[RandomInt(0, 1)]), Vector(0, 180, 0), 8.0); + break; + } + case 26: { + //weeb - just when players enter the stage + //allow players to climb up the middle house rooftop: + weebhousedoor = Entities.FindByNameNearest('finale_doorhold_2', Vector(10608, 6272, -2176), 128); + EntFireByHandle(weebhousedoor, 'KeyValues', 'origin -4570 1420 -3590', 0.0, null, null); + EntFireByHandle(weebhousedoor, 'KeyValues', 'angles 0 0 -120', 0.0, null, null); + //start tick to spawn mortars, babies and vaginafaces above players randomly + exev_weebticking = true; + scheduleInternalScript(() => { + ExevWeebTick(); + }, RandomFloat(15.0, 30.0), null, null); //5.0-20.0 in #3 + //spawn 1-2 yellow lasers in the torch-house, kill horny players, at pos:(-4222,932,-3642),(-4222,729,-3642) + ExevSpawn('blobblaser_tem1', Vector(-4222, 932, -3642), null, 0.2); + ExevSpawn('blobblaser_tem1', Vector(-4222, 729, -3642), null, 0.5); + //spawn 1-3 cakes around within the area at random (traceline downwards, make sure the Z is reasonable): + //spawn a random 10-coin within the area at random (traceline downwards, make sure the Z is reasonable): + //MEASUREMENTS: + //------ minX: -11848 + //------ minY: -7752 + //------ maxX: 3656 + //------ maxY: 7752 + //------ maxZ: -3400 (if above this, for example 3200, then it's too high, perhaps in a tree) + //------ topZ: -2555 (start scanning/spawning stuff from this height) + let weebcakes_spawned = 0; + let weebcoin_spawned = false; + while (weebcakes_spawned < extreme_extracakes_in_weeb) { + let raypos = Vector(RandomInt(-11848, 3656), RandomInt(-7752, 7752), -2555); + let raydist = TraceLine(raypos, VectorAdd(raypos, Vector(0, 0, -2e3)), null); + let rayhit = VectorAdd(raypos, VectorScale(Vector(0, 0, -2e3), raydist)); + rayhit.z += 48; + if (rayhit.z > -3400) + continue; + let justgetahouse = Entities.FindByNameNearest('X69Xhichhouse', rayhit, 1000); + if (justgetahouse != null && justgetahouse.IsValid()) + continue; + if (!weebcoin_spawned) { + ExevSpawn('s_coin_3', rayhit); + weebcoin_spawned = true; + continue; + } + weebcakes_spawned++; + ExevSpawn('itemturtleTemplateHeal2', rayhit); + } + EntFire('aX69XTurtleCake*', 'StartGlowing', '', 1.0, null); + EntFire('aX69XTurtleCake*', 'SetGlowRange', '10000', 1.0, null); + //EntFire('aX69XTurtleCake*', 'AddOutput', 'glowstyle 3', 1.0, null) + EntFire('aX69XTurtleCake*', 'SetScale', '5.0', 1.0, null); + EntFire('aX69XTurtleCake*', 'SetGlowOverride', '255 255 0', 1.0, null); + //spawn some shreks around the arena, with low HP, can't spawn too close to center to prevent RNG mass-trim + for (let i = 0; i < extreme_shreks_in_weeb; i++) { + let center = Vector(-4096, 0, -2688); + let range_min = 3500; + let range_max = 7400; + let dir = angles(0, RandomInt(0, 360), 0).forward; + let spawnpos = VectorAdd(center, VectorScale(dir, RandomInt(range_min, range_max))); + ExevSpawn('X69Xluff_shrekspawn', spawnpos); + } + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.55, null); + break; + } + case 27: { + //weeb - when triggering the end relay (where TP-out enables 'in 10 secs') + //restore the rooftop climber + if (weebhousedoor != null && weebhousedoor.IsValid()) { + EntFireByHandle(weebhousedoor, 'KeyValues', 'origin 10608 6272 -2176', 15.0, null, null); + EntFireByHandle(weebhousedoor, 'KeyValues', 'angles 0 0 0', 15.0, null, null); + } + //end tick to spawn mortars, babies and vaginafaces above players randomly + exev_weebticking = false; + //kill off babies and vaginafaces after 9.5s, if any (i_diddlebaby_phys* break), (i_vaginaface_hp* break) + EntFire('i_diddlebaby_phys*', 'Break', '', 0.0, null); + EntFire('i_vaginaface_hp*', 'Break', '', 0.0, null); + //kill zombie items: + scheduleInternalScript(() => { + KillZombieItems(); + }, 9.0, null, null); + break; + } + case 28: { + //dicklett - just when entering the stage + //after ~12s, spawn a curseorb at pos(-10400,12032,15480),yaw: 90 (+/- 15 at random) + ExevSpawn('s_curse', Vector(-10400, 12032, 15480), Vector(0, 90 + RandomInt(-15, 15), 0), 12.0); + EntFire('GlitchPush', 'Enable', '', 0.0, null); + break; + } + case 29: { + //dicklett - when triggering the start-relay for boss#1 (it spawns after 4.0s, the small hurt enables at 5.0s) + //spawn a yellow laser after ~3.5s at pos:(-13322,13294,15272), just under the rocks that breaks when you kill the 1st boss + ExevSpawn('blobblaser_tem1', Vector(-13322, 13294, 15272), null, 2.0); //was 3.5s in #3 + break; + } + case 30: { + //dicklett - just when you enter the cave after defeating boss #1 (sidewalls break at 25+30s, final wall breaks at 45s) + //spawn a curseorb at pos:(-15232,15616,14936), yaw:0, spawn after ~12s + ExevSpawn('s_curse', Vector(-15200, 15616, 14940), Vector(0, 0, 0), 12.0); + //spawn a yellow laser at pos:(-13515,15689,14939), at 28s + ExevSpawn('blobblaser_tem1', Vector(-13515, 15689, 14939), null, 28.0); + //spawn a shrek at pos:(-14182,15624,13593) + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(-14182, 15624, 13593)); + }, 60.0, null, null); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 60.55, null); + //spawn a couple vaginafaces at pos:(-14206,15437,13704),(-14239,15867,13705) + ExevSpawn('s_vaginaface', Vector(-14206, 15437, 13704)); + ExevSpawn('s_vaginaface', Vector(-14239, 15867, 13705)); + //kill off the auto-heal-trigger att the boss #2 entry/door + EntFire('ord_xxxxxx_boss2_entryheal', 'Kill', '', 5.0, null); + //start ticking the baby-killer at the boss #2 entry door + scheduleInternalScript(() => { + KillDiddleDicklettExTick(); + }, 50.0, null, null); + //spawn an edited cake-heal item that only heals up to 100hp, to compensate for the above, giving players freedom to do it themselves + SpawnDicklettBoss2LimitedCake(); + break; + } + case 31: { + //dicklett - when triggering the start-relay for boss#2 (boss starts instantly, 'AddHP(2000,1200)' runs at 0.00 atm) + //stop ticking the baby-killer at the boss #2 entry door + scheduleInternalScript(() => { + stoptick_KillDiddleDicklettEx = true; + }, 10.0, null, null); + //set HP on 'Ord_lvl_02_boss_break' a bit higher, RunScriptInput > AddHP(3000,1500) > at 0.10s + EntFire('Ord_lvl_02_boss_break', 'FireUser4', '', 0.1, null); + //start spawning lasers randomly, X:-14848, Y:(14976/16256), Z:13600, yaw:270, crouchheight:13544, jumpheight:13488 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-14848, 14976, 13544), Vector(0, 270, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-14848, 14976, 13488), Vector(0, 270, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-14848, 16256, 13544), Vector(0, 270, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-14848, 16256, 13488), Vector(0, 270, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawns.push(ExSpawn('blobblaser_tem', Vector(0, 0, 15000), Vector(0, 0, 0))); + exev_spawnrate = 1.5; + exev_spawnbounds.splice(0); + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', -14848, -14848, 14976, 16256, 13900, Vector(0, 270, 0))); + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 1.0, null, null); + break; + } + case 32: { + //dicklett - boss#2 attack (push) - enables push at 1.0s, disables push at 9.0s + //spawn 3 blue lasers in front of each pillar, at pos:(-14064,15840,13536),(-14064,15456,13536),(-13536,15616,13536) + ExevSpawn('blobblaser_tem', Vector(-14064, 15840, 13536)); + ExevSpawn('blobblaser_tem', Vector(-14064, 15456, 13536)); + ExevSpawn('blobblaser_tem', Vector(-13536, 15616, 13536)); + //re-enable the push 'Ord_lvl_02_boss_push' just after it disables at 9.00s, then re-disable it at 12.0s, making it last longer + //(stripper-added a bunch of 'Enable' commands to prevent having it disabled if the attack is re-picked too early) + EntFire('Ord_lvl_02_boss_push', 'Enable', '', 9.02, null); + EntFire('Ord_lvl_02_boss_push', 'Enable', '', 9.1, null); + EntFire('Ord_lvl_02_boss_push', 'Disable', '', 12.0, null); + break; + } + case 33: { + //dicklett - boss#2 attack (ground) - spawns at 0.50s - goes down at ~10.0s + //spawn a yellow laser at a random player, forcing the team to relocate/hide behind a pillar or die (GetRandomCT()) + GetRandomCT('blobblaser_tem1', Vector(0, 0, 48)); + break; + } + case 34: { + //dicklett - when boss#2 dies (exit door opens after 10.0s, zombie-floor rises after 17.0s) + //stop ticking the lasers that started with the boss + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + exev_spawns.splice(0); + }, 0.0, null, null); //STOP + //spawn a yellow laser in the final-hold tunnel before TP-ing back to the 2nd loop, at pos:(-15488,15616,13536), at 17.0s + ExevSpawn('blobblaser_tem1', Vector(-15488, 15616, 13536), null, 17.0); + break; + } + case 35: { + //dicklett - when boss#3 starts (ForceSpawn at 0.05s, AddHP(4000,2500) at 2.00s) + //set HP on 'X69XOrd_main_mid_diglett_break' by RunScriptInput > AddHP(5000,2700) > at 2.50s + EntFire('X69XOrd_main_mid_diglett_break', 'FireUser4', '', 2.5, null); + //start a random, slow mortar barrage (minX:-13984, maxX:-12192, minY:12080, maxY:14512, Z:16056) + exev_spawnbounds.splice(0); + exev_spawnbounds.push(ExSpawnBounds('s_mortar', -13984, -12192, 12080, 14512, 16056, Vector(0, 0, 0))); + exev_spawnrate = 2.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 3.0, null, null); + //spawn some yellow lasers on top of the safe/cheese pillar spots: + ExevSpawn('blobblaser_tem1', Vector(-12130, 12128, 15877), null, 7.0); + ExevSpawn('blobblaser_tem1', Vector(-12130, 12399, 15877), null, 7.0); + ExevSpawn('blobblaser_tem1', Vector(-12130, 12884, 15877), null, 7.0); + ExevSpawn('blobblaser_tem1', Vector(-12130, 13172, 15877), null, 7.0); + break; + } + case 36: { + //dicklett - boss#3 attack (quake - starts hurting at 2.0s - ends hurting at 8.50s) + //make the mortar barrage tick very, very slow for the duration of the earthquake (to prevent bullshit) + exev_spawnrate = 5.0; + //spawn B-lasers:(-13262,12663,15483),(-12827,12659,15471),(-12675,12865,15461),(-12667,13316,15470),(-12692,13630,15458),(-13299,13908,15452) + ExevSpawn('blobblaser_tem', Vector(-13262, 12663, 15483)); + ExevSpawn('blobblaser_tem', Vector(-12827, 12659, 15471)); + ExevSpawn('blobblaser_tem', Vector(-12675, 12865, 15461)); + ExevSpawn('blobblaser_tem', Vector(-12667, 13316, 15470)); + ExevSpawn('blobblaser_tem', Vector(-12692, 13630, 15458)); + ExevSpawn('blobblaser_tem', Vector(-13299, 13908, 15452)); + ExevSpawn('blobblaser_tem', Vector(-13262, 12663, 15483), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12827, 12659, 15471), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12675, 12865, 15461), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12667, 13316, 15470), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12692, 13630, 15458), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-13299, 13908, 15452), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-13262, 12663, 15483), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12827, 12659, 15471), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12675, 12865, 15461), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12667, 13316, 15470), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12692, 13630, 15458), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-13299, 13908, 15452), null, 5.0); + break; + } + case 37: { + //dicklett - boss#3 attack (laser - starts at 0.10s - ends at 8.0s) + //make the mortar barrage tick much faster for the duration of the laser (since it's currently very easy/not much of a danger) + exev_spawnrate = 0.4; + break; + } + case 38: { + //dicklett - when boss#3 dies (rocks leading to cave breaks at 11.0s, zcage tele enables at 25.0s) + //stop the random, slow mortar barrage tick that begun with the boss + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 0.0, null, null); //STOP + //spawn a yellow laser at ~5.5s at pos:(-13322,13294,15272), just under the rocks that breaks when you kill the 3rd boss + ExevSpawn('blobblaser_tem1', Vector(-13322, 13294, 15272), null, 5.5); + break; + } + case 39: { + //dicklett - when reaching hold just before the small, curved surf-portion (opens for real at 10s) + //spawn 3 vaginafaces at bottom of the surf-portion, pos:(-11360,11840,14272),(-11360,11584,14272),(-11360,11728,14272) + ExevSpawn('s_vaginaface', Vector(-11360, 11840, 14272)); + ExevSpawn('s_vaginaface', Vector(-11360, 11584, 14272)); + ExevSpawn('s_vaginaface', Vector(-11360, 11728, 14272)); + //break 'ord_surfprotector2' instantly, make the hold harder + EntFire('ord_surfprotector2', 'Break', '', 0.0, null); + //spawn a yellow laser at the initial crouchhold at pos:(-14560,13184,14896) + ExevSpawn('blobblaser_tem1', Vector(-14560, 13184, 14896)); + break; + } + case 40: { + //dicklett - when reaching the hold/door just after the small, curved surf-portion (door opens after 10.0s) + //spawn 2 shreks at pos:(-12960,12967,14157),(-11623,13996,14418) + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(-12960, 12967, 14157)); + }, 20.0, null, null); + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(-11623, 13996, 14418)); + }, 20.0, null, null); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 20.55, null); + //'Ord_lvl_01_door_03' opens at 10s, close it at 11, then open it at 12, also print some server message about it + EntFire('Ord_lvl_01_door_03', 'Close', '', 11.0, null); + EntFire('Ord_lvl_01_door_03', 'Open', '', 12.0, null); + EntFire('server', 'Command', 'say *Extreme mode to the rescue!*', 11.0, null); + EntFire('server', 'Command', 'say *Alright now you may proceed*', 12.5, null); + //spawn 3 vaginafaces at pos:(-12188,12929,14216),(-11847,13807,14179),(-12875,14242,14469) + ExevSpawn('s_vaginaface', Vector(-12188, 12929, 14216)); + ExevSpawn('s_vaginaface', Vector(-11847, 13807, 14179)); + ExevSpawn('s_vaginaface', Vector(-12875, 14242, 14469)); + //start spawning lasers across the lava jump where you came from, after ~20.0s at pos:(-11520,11488,14128/14184), yaw:0 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-11520, 11488, 14128), Vector(0, 0, 0))); + //exev_spawns.push(ExSpawn("slash_spawner",Vector(-11520,11488,14184),Vector(0,0,0))); (commented out - make it jump-only due to the darkness) + exev_spawnrate = 3.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 20.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 60.0, null, null); //STOP + break; + } + case 41: { + //dicklett - when pressing lever in the lavajumps/maze after the small, curved surf-portion (door opens after 30.0s) + //spawn vaginafaces at pos:(-11382,14490,14166),(-11527,14466,15061),(-13197,13383,14950) + ExevSpawn('s_vaginaface', Vector(-11382, 14490, 14166)); + ExevSpawn('s_vaginaface', Vector(-11527, 14466, 15061)); + ExevSpawn('s_vaginaface', Vector(-13197, 13383, 14950)); + //spawn a shrek at pos:(-13250,13886,14950) + ExevSpawn('X69Xluff_shrekspawn', Vector(-13200, 13400, 14950)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.55, null); + //start spawning jumplasers at pos:(-13448,13312,14952), yaw:0 after ~40s, stop after ~60s + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-13448, 13312, 14932), Vector(0, 0, 0))); + exev_spawnrate = 2.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 40.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 60.0, null, null); //STOP + break; + } + case 42: { + //dicklett - when triggering final hold before boss#4 (shortcut breaks at 18.0s, bosswall breaks at 22.0s, AddHP(4000,4500) at 31.50s) + //spawn a yellow laser just inside the shortcut after 14.0s at pos(-12964,14230,14921) + ExevSpawn('blobblaser_tem1', Vector(-12964, 14230, 14921), null, 14.0); + //set bossHP on 'Ord_lvl_01_boss_break' by RunScriptInput > AddHP(5000,5000); at 32.00s + EntFire('Ord_lvl_01_boss_break', 'FireUser4', '', 32.0, null); + break; + } + case 43: { + //dicklett - boss#4 attack (jizz attack right - get away from right side) *hurt start 2.0s - end 6.0s* + //spawn a vaginaface on the right side at pos:(-12096,12928,15040) + ExevSpawn('s_vaginaface', Vector(-12096, 12928, 15040)); + break; + } + case 44: { + //dicklett - boss#4 attack (jizz attack mid - get away from the middle) *hurt start 2.0s - end 6.0s* + //spawn a curseorb on the left or right side at random, pos:(-12160,13440,14848),(-12160,12928,14848), yaw:180+-10 + if (RandomInt(0, 1) == 1) + ExevSpawn('s_curse', Vector(-12160, 13440, 14848), Vector(0, 180 + RandomInt(-10, 10), 0)); + else + ExevSpawn('s_curse', Vector(-12160, 12928, 14848), Vector(0, 180 + RandomInt(-10, 10), 0)); + break; + } + case 45: { + //dicklett - boss#4 attack (jizz attack left - get away from left side) *hurt start 2.0s - end 6.0s* + //spawn a vaginaface on the left side at pos:(-12096,13440,15040) + ExevSpawn('s_vaginaface', Vector(-12096, 13440, 15040)); + break; + } + case 46: { + //dicklett - boss#4 attack (earthquake - get on platform) *rise at 1.0s, hurt at 3.0s, stop hurt at 6.0, de-rise at 7.0s* + //Enable 'Ord_lvl_01_boss_hurt_platform' at 1.50s (will hurt slightly, and require people to stay attentive) (was 0.00 in stripper #4) + EntFire('Ord_lvl_01_boss_hurt_platform', 'Enable', '', 1.5, null); + break; + } + case 47: { + //dicklett - boss#4 attack (fart - hug the front of the pillar) *start at 0.0s - end at ~8.0-10.0s* + //spawn jumplasers for ~8.0s at 2 possible positions:(-11840,13440,14849),(-11840,12928,14849), yaw:90 + let randlaser = [ + Vector(-11840, 13440, 14849), + Vector(-11840, 12928, 14849), + ]; + ExevSpawn('slash_spawner', randlaser[RandomInt(0, 1)], Vector(0, 90, 0), 0.0); + ExevSpawn('slash_spawner', randlaser[RandomInt(0, 1)], Vector(0, 90, 0), 2.0); + ExevSpawn('slash_spawner', randlaser[RandomInt(0, 1)], Vector(0, 90, 0), 4.0); + ExevSpawn('slash_spawner', randlaser[RandomInt(0, 1)], Vector(0, 90, 0), 6.0); + ExevSpawn('slash_spawner', randlaser[RandomInt(0, 1)], Vector(0, 90, 0), 8.0); + break; + } + case 48: { + //dicklett - when boss#4 dies (exit-door opens slowly at 20.0s, zcage breaks at 30s which is kinda when exit-door is open) + //spawn jump/crouch lasers towards the final hallway after ~31.0s at pos:(-13056,13184,14928),(-13056,13184,14984), yaw:270 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-13056, 13184, 14928), Vector(0, 270, 0))); + //exev_spawns.push(ExSpawn("slash_spawner",Vector(-13056,13184,14984),Vector(0,270,0))); (commented out - make it jump-only due to the darkness) + exev_spawnrate = 4.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 31.0, null, null); + break; + } + case 49: { + //dicklett - just when you exit-TP out after defeating boss#4 (when entering final hallway-hold) + //stop spawning jump and crouchlasers from the previous area + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 0.0, null, null); //STOP + //spawn a yellow laser after ~20.0s at pos:(-10400,12160,15488) + ExevSpawn('blobblaser_tem1', Vector(-10400, 12160, 15488), null, 20.0); + //spawn a 10-coin inside the hallway where zombies come from, probably need a torch to get it, at pos:(-10321,11945,15450) + ExevSpawn('s_coin_3', Vector(-10321, 11945, 15450), null, 4.0); + //disable the CT-push so ct's can get the 10-coin: + EntFire('stripstrop_ord_wtf_push', 'Disable', '', 0.0, null); + break; + } + case 50: { + //dicklett - when final boss starts (AddHP(4000,5500) at 2.0s) + //set HP on 'X69XOrd_main_large_diglett_break' by RunScriptInput > AddHP(5000,5700); > at 2.20s + EntFire('X69XOrd_main_large_diglett_break', 'FireUser4', '', 2.2, null); + //spawn a yellow laser after 5.0s at pos:(-12959,13043,15472) + ExevSpawn('blobblaser_tem1', Vector(-12959, 13043, 15472), null, 5.0); + //spawn some yellow lasers on top of the safe/cheese pillar spots: + ExevSpawn('blobblaser_tem1', Vector(-12130, 12128, 15877), null, 8.0); + ExevSpawn('blobblaser_tem1', Vector(-12130, 12399, 15877), null, 8.0); + ExevSpawn('blobblaser_tem1', Vector(-12130, 12884, 15877), null, 8.0); + ExevSpawn('blobblaser_tem1', Vector(-12130, 13172, 15877), null, 8.0); + break; + } + case 51: { + //dicklett - boss#5 attack (quake - starts hurting at 2.0s - ends hurting at 8.50s) + //spawn a yellow laser on a random CT at 8.00s + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem1', Vector(0, 0, 48)); + }, 8.0, null, null); + //spawn B-lasers:(-13262,12663,15483),(-12827,12659,15471),(-12675,12865,15461),(-12667,13316,15470),(-12692,13630,15458),(-13299,13908,15452) + ExevSpawn('blobblaser_tem', Vector(-13262, 12663, 15483)); + ExevSpawn('blobblaser_tem', Vector(-12827, 12659, 15471)); + ExevSpawn('blobblaser_tem', Vector(-12675, 12865, 15461)); + ExevSpawn('blobblaser_tem', Vector(-12667, 13316, 15470)); + ExevSpawn('blobblaser_tem', Vector(-12692, 13630, 15458)); + ExevSpawn('blobblaser_tem', Vector(-13299, 13908, 15452)); + ExevSpawn('blobblaser_tem', Vector(-13262, 12663, 15483), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12827, 12659, 15471), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12675, 12865, 15461), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12667, 13316, 15470), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-12692, 13630, 15458), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-13299, 13908, 15452), null, 3.0); + ExevSpawn('blobblaser_tem', Vector(-13262, 12663, 15483), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12827, 12659, 15471), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12675, 12865, 15461), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12667, 13316, 15470), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-12692, 13630, 15458), null, 5.0); + ExevSpawn('blobblaser_tem', Vector(-13299, 13908, 15452), null, 5.0); + break; + } + case 52: { + //boss#5 attack (laser - starts at 5.0s - ends at 14.0s) + //spawn a yellow laser on a random CT at 1.00s + scheduleInternalScript(() => { + GetRandomCT('blobblaser_tem1', Vector(0, 0, 48)); + }, 1.0, null, null); + break; + } + case 53: { + //dicklett - when defeating boss#5 (coin spawns at 5.0s, the rocks leading to the TP breaks after 9.0s) + //spawn a vaginaface after 5.0s at pos:(-12178,14485,16094) + ExevSpawn('s_vaginaface', Vector(-12178, 14485, 16094), null, 5.0); + //spawn a yellow laser at ~4.5s at pos:(-13322,13294,15272), just under the rocks that breaks when you kill the 5th boss + ExevSpawn('blobblaser_tem1', Vector(-13322, 13294, 15272), null, 4.5); + //kill zombie items: + scheduleInternalScript(() => { + KillZombieItems(); + }, 5.0, null, null); + break; + } + case 54: { + //omaha - when entering the stage + //spawn vaginafaces at pos:(-13312,3584,15488),(-11776,3584,15488),(-12608,3584,15168) + ExevSpawn('s_vaginaface', Vector(-13312, 3584, 15488)); + ExevSpawn('s_vaginaface', Vector(-11776, 3584, 15488)); + ExevSpawn('s_vaginaface', Vector(-12608, 3584, 15168)); + //spawn babyfaces at pos:(-13312,2048,15488),(-11776,2048,15488),(-12608,2048,15168) + ExevSpawn('s_diddlebaby', Vector(-13312, 2048, 15488)); + ExevSpawn('s_diddlebaby', Vector(-11776, 2048, 15488)); + ExevSpawn('s_diddlebaby', Vector(-12608, 2048, 15168)); + //spawn shreks at pos:(-12553,3041,13916),(-13557,1010,13947),(-11514,1977,13909) + ExevSpawn('X69Xluff_shrekspawn', Vector(-12553, 3041, 13916)); + ExevSpawn('X69Xluff_shrekspawn', Vector(-13557, 1010, 13947)); + ExevSpawn('X69Xluff_shrekspawn', Vector(-11514, 1977, 13909)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.55, null); + //teleport x% of the zombies in front of the humans with 500hp at pos:(-13760/-11328,3072,14208), prevent going above Y:4310 + scheduleInternalScript(() => { + ExtremeOmahaTPZ(); + }, 16.0, null, null); //1.00 in #7, 16.00 in #8 (as z's TP back at ~15s for whatever reason) + break; + } + case 55: { + //omaha - when triggering the explosive wall (explodes at 29.5s) + //spawn a 10-coin under the z-shortcut bridge, sneaky spot + ExevSpawn('s_coin_3', Vector(-12774, 5759, 13615)); + //start a heavy mortar barrage at ~20.0s until 30.0s (minX:-13824,minY:3392,maxX:-11264,maxY:4096,Z:15360) + exev_spawnbounds.splice(0); + exev_spawnbounds.push(ExSpawnBounds('s_mortar', -13824, -11264, 3870, 4300, 14400, Vector(0, 0, 0))); //minY was 3400, Z was 15360 in #3 + exev_spawnrate = 0.5; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 12.0, null, null); + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 22.0, null, null); //STOP + break; + } + case 56: { + //omaha - when reaching hold just after the spike-bridge (z-ladder opens at 20.0s, hold opens at 25.0s) + //spawn shreks at pos:(-13710,9620,14147),(-11959,9716,14168) + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(-13710, 9620, 14147)); + }, 25.0, null, null); + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(-11959, 9716, 14168)); + }, 25.0, null, null); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 25.55, null); + //spawn vaginafaces at pos:(-13728,9745,14195),(-11980,9033,14176),(-11695,8838,14193),(-12827,7848,14240) + ExevSpawn('s_vaginaface', Vector(-13728, 9745, 14195)); + ExevSpawn('s_vaginaface', Vector(-11980, 9033, 14176)); + ExevSpawn('s_vaginaface', Vector(-11695, 8838, 14193)); + ExevSpawn('s_vaginaface', Vector(-12827, 7848, 14240)); + //spawn babyfaces at pos:(-13722,7539,14161),(-11627,9770,14154),(-11366,7845,14189),(-12130,8123,14148) + ExevSpawn('s_diddlebaby', Vector(-13722, 7539, 14161)); + ExevSpawn('s_diddlebaby', Vector(-11627, 9770, 14154)); + ExevSpawn('s_diddlebaby', Vector(-11366, 7845, 14189)); + ExevSpawn('s_diddlebaby', Vector(-12130, 8123, 14148)); + //spawn jump lasers, start:30s,end:60s,pos:(-3936,9808,14112/14168),Y:180 + pos:(-11248,9920,14112/14168),Y:90 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(-13936, 9808, 14112), Vector(0, 180, 0))); + //exev_spawns.push(ExSpawn("slash_spawner",Vector(-13936,9808,14168),Vector(0,180,0))); (commented out for jump lasers only) + exev_spawns.push(ExSpawn('slash_spawner', Vector(-11248, 9920, 14112), Vector(0, 90, 0))); + //exev_spawns.push(ExSpawn("slash_spawner",Vector(-11248,9920,14168),Vector(0,90,0))); (commented out for jump lasers only) + exev_spawnrate = 1.5; //stripper #4 was at 1.00 + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 30.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 60.0, null, null); //STOP + //spawn some yellow lasers to prevent cheese-defending for 'too' long after the hold is open (50.0s) + ExevSpawn('blobblaser_tem1', Vector(-12551, 6785, 14137), null, 50.0); + ExevSpawn('blobblaser_tem1', Vector(-12551, 6785, 14137), null, 55.0); + ExevSpawn('blobblaser_tem1', Vector(-12551, 7535, 14137), null, 54.0); + ExevSpawn('blobblaser_tem1', Vector(-13231, 7556, 14123), null, 58.0); + ExevSpawn('blobblaser_tem1', Vector(-12551, 7535, 14137), null, 60.0); + ExevSpawn('blobblaser_tem1', Vector(-13714, 7700, 14137), null, 62.0); + break; + } + case 57: { + //omaha - when reaching trigger below helicopter (final hold, TP-out for CT's enable after 80.0s) + //spawn a shrek at pos:(-12608,8880,14800) + ExevSpawn('X69Xluff_shrekspawn', Vector(-12608, 8880, 14800)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.55, null); + //spawn a yellow laser at pos:(-13031,8882,14737) at 0.00s, 6.00s and 12.00s + ExevSpawn('blobblaser_tem1', Vector(-13031, 8882, 14737)); + ExevSpawn('blobblaser_tem1', Vector(-13031, 8882, 14737), null, 6.0); + ExevSpawn('blobblaser_tem1', Vector(-13031, 8882, 14737), null, 12.0); + //spawn some yellow lasers in the heli to make players panic, they explode just a small small moment after you exit though + ExevSpawn('blobblaser_tem1', Vector(-13040, 8875, 14791), null, 74.2); + ExevSpawn('blobblaser_tem1', Vector(-12850, 8930, 14791), null, 74.2); + ExevSpawn('blobblaser_tem1', Vector(-12850, 8830, 14791), null, 74.2); + EntFire('server', 'Command', "say OMG IT'S ABOUT TO BLOW UP", 74.0, null); + EntFire('server', 'Command', "say OMG IT'S ABOUT TO BLOW UP", 74.01, null); + EntFire('server', 'Command', "say OMG IT'S ABOUT TO BLOW UP", 74.02, null); + EntFire('server', 'Command', 'say AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 78.0, null); + EntFire('server', 'Command', 'say AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 78.01, null); + EntFire('server', 'Command', 'say AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 78.02, null); + //kill zombie items: + scheduleInternalScript(() => { + KillZombieItems(); + }, 78.0, null, null); + break; + } + case 58: { + //finale - when the spawn doors start opening at the very start + //spawn jump/crouch lasers for ~20s at pos(6208,1024,80/136),Y:90 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(6208, 1024, 80), Vector(0, 90, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(6208, 1024, 136), Vector(0, 90, 0))); + exev_spawnrate = 3.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 0.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 20.5, null, null); //STOP + //spawn tall lasers starting at ~20s at pos(2944,896/1152,192),y:270 + exev_spawnbounds.splice(0); + scheduleInternalScript(() => { + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', 2100, 2944, 896, 1152, 768, Vector(0, 270, 0))); + }, 20.0, null, null); + scheduleInternalScript(() => { + exev_spawnrate = 1.5; + }, 20.0, null, null); + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 20.05, null, null); + //spawn 'ITEMX_tem_luffaren_baby' at pos:(8000,570,30) + ExevSpawn('ITEMX_tem_luffaren_baby', Vector(8000, 570, 30)); + //after ~30-50s, spawn a yellow laser at pos:(7358,334,216) + ExevSpawn('blobblaser_tem1', Vector(8000, 570, 30), null, RandomFloat(30.0, 50.0)); + //spawn shreks at pos:(6963,427,129),(5603,1027,116) + ExevSpawn('X69Xluff_shrekspawn', Vector(6963, 427, 129)); + ExevSpawn('X69Xluff_shrekspawn', Vector(5603, 1027, 116)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.55, null); + //clear and re-check the shopangels, would probably cause mass-confusion otherwise + specdevils.splice(0); + shopangels.splice(0); + tickingsinners = true; + TickInflatingSinners(); + break; + } + case 59: { + //finale - when triggering first gate-hold (just after the diddle-shop, it opens after 20.0s) + //spawn crouchlasers after 20s at a fast rate at pos:(8000,3840,-696),Y:90 until 42s, then clear/stop all the ticks! + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 18.0, null, null); + scheduleInternalScript(() => { + exev_spawns.push(ExSpawn('slash_spawner', Vector(8000, 3840, -696), Vector(0, 90, 0))); + }, 18.5, null, null); + scheduleInternalScript(() => { + exev_spawnrate = 0.1; + exev_spawnbounds.splice(0); + if (!exev_spawnticking) + ExevSpawnTick(); + }, 20.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + exev_spawnbounds.splice(0); + }, 47.0, null, null); //STOP + //spawn a shrek at pos:(8365,6397,-2267) + ExevSpawn('X69Xluff_shrekspawn', Vector(8365, 6397, -2267)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.55, null); + break; + } + case 60: { + //finale - when triggering second gate hold (before splitup to elevator on the left or long path on the right (opens after 20.0s) + //spawn a vaginaface at pos:(9910,6587,-2777) + ExevSpawn('s_vaginaface', Vector(9910, 6587, -2777)); + //spawn shreks at pos:(13608,6987,-1511),(11540,7006,-73),(13180,6767,2224) + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(13608, 6987, -1511)); + }, 85.0, null, null); + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(11540, 7006, -73)); + }, 85.0, null, null); + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(13180, 6767, 2224)); + }, 85.0, null, null); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 85.55, null); + //start spawning jump/crouchlasers after 20s until 50s at pos:(14208,6432,-2288/-2232),Y:90 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(14350, 6432, -2288), Vector(0, 90, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(14350, 6432, -2232), Vector(0, 90, 0))); + exev_spawnrate = 1.2; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 20.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 50.0, null, null); //STOP + break; + } + case 61: { + //finale - when reaching the long, top hold in the blob area (door opens at 50.0s) + //after 30s, spawn a curseorb at pos:(12480,6464,2224),yaw=0+-10 + ExevSpawn('s_curse', Vector(12480, 6464, 2224), Vector(0, 0 + RandomInt(-10, 10), 0), 30.0); + //spawn a shrek at pos:(15415,2229,1908) + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(15415, 2229, 1908)); + }, 55.0, null, null); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 55.55, null); + //break 'finale_breakladder2' at 60.0s (a bit earlier than usual) + EntFire('finale_breakladder2', 'Break', '', 60.0, null); + break; + } + case 62: { + //finale - when triggering hold just before jumping into the vagina (gate opens after 20.0s) + //after 18.0s, spawn a curseorb at pos:(13600,2688,1880),yaw=0 + ExevSpawn('s_curse', Vector(13600, 2688, 1880), null, 18.0); + //after 17.3s, spawn a yellow laser at pos:(13984,2688,1072) + ExevSpawn('blobblaser_tem1', Vector(13984, 2688, 1072), null, 17.3); + break; + } + case 63: { + //finale - when triggering first hold in the vagina (opens after 15.0s) + //spawn vaginafaces at pos:(13977,-2292,6241),(14095,-2826,7359) + ExevSpawn('s_vaginaface', Vector(13977, -2292, 6241)); + ExevSpawn('s_vaginaface', Vector(14095, -2826, 7359)); + //spawn a shrek after 15.0s at pos:(13067,-1213,6219) + ExevSpawn('X69Xluff_shrekspawn', Vector(13067, -1213, 6219), null, 15.0); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 15.55, null); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 16.0, null); + //spawn a vaginaface after 8.0s at pos:(15817,-3188,6738) + ExevSpawn('s_vaginaface', Vector(15817, -3188, 6738), null, 8.0); + break; + } + case 64: { + //finale - when triggering second hold in the vagina (opens after 30.0s) + //spawn a yellow laser after 24.0s at pos:(14100,-2751,7390) + ExevSpawn('blobblaser_tem1', Vector(14100, -2751, 7390), null, 24.0); + //spawn a vaginaface:(14473,-3252,7889) + ExevSpawn('s_vaginaface', Vector(14473, -3252, 7889)); + break; + } + case 65: { + //finale - when triggering third hold in the vagina (opens after 15.0s) + //spawn vaginafaces at pos:(11320,-2319,9348),(11672,-1695,7935),(12127,-1810,7516) + ExevSpawn('s_vaginaface', Vector(11320, -2319, 9348)); + ExevSpawn('s_vaginaface', Vector(11672, -1695, 7935)); + ExevSpawn('s_vaginaface', Vector(12127, -1810, 7516)); + break; + } + case 66: { + //finale - when triggering fourth hold in the vagina (opens after 30.0s) + //Break 'v_shitbreak' at 25.0s (it breaks at 37.0s originally) + EntFire('v_shitbreak', 'Break', '', 25.0, null); + //spawn a babyface at pos:(13611,-1626,11240) + ExevSpawn('s_diddlebaby', Vector(13611, -1626, 11240)); + //spawn vaginafaces at pos:(13882,-1190,9723),(14475,-2699,10094) + ExevSpawn('s_vaginaface', Vector(13882, -1190, 9723)); + ExevSpawn('s_vaginaface', Vector(14475, -2699, 10094)); + break; + } + case 67: { + //finale - when triggering fifth/final hold in the vagina (fetus freakout at ~22.0s, babyspawns at 30.0s, opens at 40.0s) + //spawn vaginafaces at pos:(15150,-3552,10532),(12019,-3599,2450) + ExevSpawn('s_vaginaface', Vector(15150, -3552, 10532)); + ExevSpawn('s_vaginaface', Vector(12019, -3599, 2450)); + //spawn a yellow laser after 30.0s at pos:(13890,-2981,10098) + ExevSpawn('blobblaser_tem1', Vector(13890, -2981, 10098), null, 30.0); + break; + } + case 68: { + //finale - when in the middle of the staircase leading to the magma cave (reaching it after ~15.0s, Z-tp enables at 60.0s) + //spawn vaginafaces at pos:(12032,-3584,1056),(9524,-5157,2188),(14346,-5356,2051),(15042,-6453,1559) + ExevSpawn('s_vaginaface', Vector(12032, -3584, 1056)); + ExevSpawn('s_vaginaface', Vector(9524, -5157, 2188)); + ExevSpawn('s_vaginaface', Vector(14346, -5356, 2051)); + ExevSpawn('s_vaginaface', Vector(15042, -6453, 1559)); + //spawn shreks at pos:(14979,-6397,1570),(12025,-5596,1321) + ExevSpawn('X69Xluff_shrekspawn', Vector(14979, -6397, 1570)); + ExevSpawn('X69Xluff_shrekspawn', Vector(12025, -5596, 1321)); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 0.55, null); + //spawn a babyface at pos:(10967,-5989,1325) + ExevSpawn('s_diddlebaby', Vector(10967, -5989, 1325)); + //spawn jumplasers starting at 15.0s, ending at 45.0s at pos:(15360,-6400,1552/1608),yaw=90 + exev_spawns.splice(0); + exev_spawns.push(ExSpawn('slash_spawner', Vector(15360, -6400, 1552), Vector(0, 90, 0))); + exev_spawns.push(ExSpawn('slash_spawner', Vector(15360, -6400, 1608), Vector(0, 90, 0))); + exev_spawnrate = 0.8; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 15.0, null, null); + scheduleInternalScript(() => { + exev_spawns.splice(0); + }, 45.0, null, null); //STOP + break; + } + case 69: { + //finale - when reaching gate hold just before the fetus boss (opens after 40.0s) + //spawn a yellow laser at 36.5s at pos:(15840,-6944,1552) + ExevSpawn('blobblaser_tem1', Vector(15840, -6944, 1552), null, 36.5); + //spawn a curseorb at 50.0s at pos:(14464,-8704,1280),Y:0 + ExevSpawn('s_curse', Vector(14464, -8704, 1280), null, 50.0); + break; + } + case 70: { + //finale - when triggering the fetus hold relay (bridge-jump breaks at 15.0s, fetus boss spawns in vagina/starts at 20.0s) + //spawn a handful of vaginafaces with some delays in between at pos:(13344,-9102,4040) + //CS2 fixed pos to (13344,-9102,3736) and delay + ExevSpawn('s_vaginaface', Vector(13344, -9102, 3736), null, 20.0); + ExevSpawn('s_vaginaface', Vector(13344, -9102, 3736), null, 22.0); //20.5 + ExevSpawn('s_vaginaface', Vector(13344, -9102, 3736), null, 24.0); //21 + ExevSpawn('s_vaginaface', Vector(13344, -9102, 3736), null, 30.0); + ExevSpawn('s_vaginaface', Vector(13344, -9102, 3736), null, 45.0); + ExevSpawn('s_vaginaface', Vector(13344, -9102, 3736), null, 60.0); + ExevSpawn('s_vaginaface', Vector(13344, -9102, 3736), null, 90.0); + //start spawning in mortarstrikes across the arena:(minX:9280,maxX:14656,minY:-10432,maxY:-7872,Z:2000) + exev_spawnbounds.splice(0); + exev_spawns.splice(0); + exev_spawnbounds.push(ExSpawnBounds('s_mortar', 9280, 14656, -10432, -7872, 2000, Vector(0, 0, 0))); + exev_spawnrate = 0.8; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 15.0, null, null); + break; + } + case 71: { + //finale - when the fetus boss is defeated (zcage-rock-hold breaks after 128.0s, zcage breaks after 134.0s) + if (extreme) { + //stop spawning in mortarstrikes across the arena + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 0.0, null, null); //STOP + //spawn yellow lasers at 16.0s at pos:(9043,-9560,1034),(9202,-8161,990) + ExevSpawn('blobblaser_tem1', Vector(9043, -9560, 1034), null, 16.0); + ExevSpawn('blobblaser_tem1', Vector(9202, -8161, 990), null, 16.0); + //spawn a yellow laser at 110.0s at pos:(2645,-8483,910) + ExevSpawn('blobblaser_tem1', Vector(2645, -8483, 910), null, 110.0); + //spawn vaginafaces at pos:(7266,-8779,2499),(8777,-5799,1770),(4585,-8369,1442) + ExevSpawn('s_vaginaface', Vector(7266, -8779, 2499)); + ExevSpawn('s_vaginaface', Vector(8777, -5799, 1770)); + ExevSpawn('s_vaginaface', Vector(4585, -8369, 1442)); + //break the zcage by FireUser1 on 'finale_zcage' at 131.0s, a few seconds earlier than usual + EntFire('finale_zcage', 'FireUser1', '', 131.0, null); + } + //spawn some triggers inside the displacement that prevents a fucked shortcut + let cheesetrigs = [ + { + pos: Vector(2224, -9120, 2432), + rot: Vector(0, 35, 0), + minsmaxs: Vector(112, 304, 1664), + }, + { + pos: Vector(2576, -8952, 2432), + rot: Vector(0, 0, 0), + minsmaxs: Vector(112, 304, 1664), + }, + { + pos: Vector(3136, -9096, 2432), + rot: Vector(0, 0, 0), + minsmaxs: Vector(64, 376, 1664), + }, + { + pos: Vector(1480, -9212, 2432), + rot: Vector(0, 0, 0), + minsmaxs: Vector(48, 24, 1664), + }, + { + pos: Vector(-4188, -8115, 1928), + rot: Vector(0, 47, 0), + minsmaxs: Vector(200, 40, 200), + }, + { + pos: Vector(-4560, -8310, 2380), + rot: Vector(0, 20, 0), + minsmaxs: Vector(150, 80, 500), + }, + { + pos: Vector(-4646, -7830, 1639), + rot: Vector(0, 0, 0), + minsmaxs: Vector(150, 400, 2000), + }, + { + pos: Vector(-4440, -9080, 1467), + rot: Vector(0, 0, 15), + minsmaxs: Vector(350, 200, 2000), + }, + { + pos: Vector(-151, -8980, 1022), + rot: Vector(0, -12, 20), + minsmaxs: Vector(50, 600, 2000), + }, + ]; + let finaleanticheese_index = 0; + for (const ctrig of cheesetrigs) { + let spawnedTrigs = forceSpawnTemplate('diddle_finale_anticheese_postbabyboss_' + + String(finaleanticheese_index), ctrig.pos, ctrig.rot); + let trig = spawnedTrigs[0] ?? null; + finaleanticheese_index++; + if (trig == null || !trig.IsValid()) + continue; + EntFireByHandle(trig, 'Enable', '', 0.1, null, null); + EntFireByHandle(trig, 'Kill', '', 300.0, null, null); + } + //scan for curse orbs inside the displacement to prevent humans gettins trimmed off + scheduleInternalScript(() => { + FinaleCurseOrbCheeseScan(); + }, 0.0, null, null); + scheduleInternalScript(() => { + finale_curseorbcheese_tick = false; + }, 150.0, null, null); + break; + } + case 72: { + //finale - 2-split hold just after the lavarise zcage-rock-hold (breaks/opens after 20.0s) + //spawn vaginafaces at pos:(-3603,-8062,1797),(-6247,-10060,2993),(-2609,-8986,1906) + ExevSpawn('s_vaginaface', Vector(-3603, -8062, 1797)); + ExevSpawn('s_vaginaface', Vector(-6247, -10060, 2993)); + ExevSpawn('s_vaginaface', Vector(-2609, -8986, 1906)); + //spawn yellow lasers after 3.0s at pos:(-1219,-7817,1537),(-794,-8401,1082) + ExevSpawn('blobblaser_tem1', Vector(-1219, -7817, 1537), null, 3.0); + ExevSpawn('blobblaser_tem1', Vector(-794, -8401, 1082), null, 3.0); + break; + } + case 73: { + //finale - when reaching final normal-mode hold, just before diddle-heaven (seal spawns in at 11.0s) + //start spawning upslash lasers randomly after ~15.0s:(X:-2976,minY:-8880,maxY:-8328,Z:1536), yaw:90 + exev_spawnbounds.splice(0); + exev_spawnbounds.push(ExSpawnBounds('upslash_spawner', -2976, -2976, -8880, -8328, 2200, Vector(0, 90, 0))); + exev_spawnrate = 1.0; + scheduleInternalScript(() => { + if (!exev_spawnticking) + ExevSpawnTick(); + }, 15.0, null, null); + break; + } + case 74: { + //finale - when the lavarise reaches the top and you don't have all coins (no diddle heaven) + //stop spawning the upslash lasers + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 0.0, null, null); //STOP + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 20.0, null, null); //STOP + break; + } + case 75: { + //finale - when the lavarise reaches the top and you have all coins (go to diddle heaven, jump enables at 4.50s) + //stop spawning the upslash lasers + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 0.0, null, null); //STOP + scheduleInternalScript(() => { + exev_spawnbounds.splice(0); + }, 20.0, null, null); //STOP + //spawn a vaginaface at pos:(-8100,-8742,1196) (-9190,-7682,1360) + ExevSpawn('s_vaginaface', Vector(-9190, -7682, 1360)); + break; + } + case 76: { + //finale - when reaching the exit doors of the diddle heaven chapel (z-cage breaks at 0.0s, doors opens at 15.0s) + //spawn vaginafaces:(-11942,-7746,1660),(-14019,-6677,2872),(-14794,-8486,1784),(-15138,-9941,2765),(-12206,-9837,2128) + ExevSpawn('s_vaginaface', Vector(-11942, -7746, 1660)); + ExevSpawn('s_vaginaface', Vector(-14019, -6677, 2872)); + ExevSpawn('s_vaginaface', Vector(-14794, -8486, 1784)); + ExevSpawn('s_vaginaface', Vector(-15138, -9941, 2765)); + ExevSpawn('s_vaginaface', Vector(-12206, -9837, 2128)); + //spawn shrek at pos:(-14598,-10906,2631) + scheduleInternalScript(() => { + ExevSpawn('X69Xluff_shrekspawn', Vector(-14598, -10906, 2631)); + }, 40.0, null, null); + EntFire('X69Xluff_npc_phys2gg*', 'FireUser2', '', 40.55, null); + //spawn a yellow laser at 17.0s at pos:(-11291,-7753,1731) + ExevSpawn('blobblaser_tem1', Vector(-11291, -7753, 1731), null, 17.0); + break; + } + case 77: { + //finale - when reaching the diddledick boss gate (gate breaks after 30.0s, boss starts at ~35.0s, diddlefriend roof goes down at 60.0s) + //spawn a curseorb at 30.0s at pos:(-14592,-12032,2592),yaw=90 + ExevSpawn('s_curse', Vector(-14592, -12032, 2592), Vector(0, 90, 0), 30.0); + //kill off the torch items (also print a server message about it) + EntFire('aX69Xhich*', 'Kill', '', 19.0, null); + EntFire('server', 'Command', 'say ***REMOVING TORCHES IN 5 SECONDS...***', 14.0, null); + EntFire('server', 'Command', 'say ***REMOVING TORCHES IN 4 SECONDS...***', 15.0, null); + EntFire('server', 'Command', 'say ***REMOVING TORCHES IN 3 SECONDS...***', 16.0, null); + EntFire('server', 'Command', 'say ***REMOVING TORCHES IN 2 SECONDS...***', 17.0, null); + EntFire('server', 'Command', 'say ***REMOVING TORCHES IN 1 SECONDS...***', 18.0, null); + EntFire('server', 'Command', 'say ***TORCHES REMOVED, GOOD LUCK...***', 19.0, null); + break; + } + case 78: { + //finale - when a zombie teleports just in front of the diddledick boss (by random timer) + //run more times (for more zombies to TP in front): EntFire("dd_hp","RunScriptInput","SpawnZombie",0.00,null); + EntFire('Map_DD_Health', 'RunScriptInput', 'SpawnZombie', 0.1, null); + EntFire('Map_DD_Health', 'RunScriptInput', 'SpawnZombie', 0.2, null); + EntFire('Map_DD_Health', 'RunScriptInput', 'SpawnZombie', 0.3, null); + EntFire('Map_DD_Health', 'RunScriptInput', 'SpawnZombie', 0.4, null); + EntFire('Map_DD_Health', 'RunScriptInput', 'SpawnZombie', 0.5, null); + EntFire('Map_DD_Health', 'RunScriptInput', 'SpawnZombie', 0.6, null); + break; + } + case 79: { + //finale - when the diddledick boss dies (endpush enables after 25.0s) + //move the slashes and rotate them 180 degrees through the below, then keep spawning them on a timer until 24.0s: + // IO delay in nextframe + EntFireByHandle(self, 'RunScriptInput', 'ExtremeEventCase79', 0.0, null, null); + EntFire('dd_attack_upslash_*', 'KeyValues', 'angles 0 180 0', 0.0, null); + EntFire('dd_attack_highslash', 'KeyValues', 'angles 0 180 0', 0.0, null); + EntFire('dd_attack_lowslash', 'KeyValues', 'angles 0 180 0', 0.0, null); + exev_finalboss_laserticking = true; + scheduleInternalScript(() => { + ExevTickFinalbossLaser(); + }, 7.0, null, null); + scheduleInternalScript(() => { + exev_finalboss_laserticking = false; + }, 25.0, null, null); //STOP + //spawn some final yellow lasers at the jump, the most evil ones in the entire map for sure (i'm sorry, maybe?) + ExevSpawn('blobblaser_tem1', Vector(-14592, -15360, 2048), null, 19.0); + ExevSpawn('blobblaser_tem1', Vector(-14592, -15360, 2048), null, 20.0); + ExevSpawn('blobblaser_tem1', Vector(-14592, -15360, 2048), null, 21.0); + ExevSpawn('blobblaser_tem1', Vector(-14592, -15360, 2048), null, 22.0); + ExevSpawn('blobblaser_tem1', Vector(-14592, -15360, 2048), null, 23.0); + break; + } + case 80: { + //finale - when a CT wins by teleporting into the white box win area (triggers for each !activator that is the CT) + //if LuffarenMaps.smx is running on the server, print out the playername as a winner + if (SBplayers.length <= 0) + return; + let p = activator; + if (p != null && p.IsValid() && p.GetHealth() > 0 && getTeam(p) == 3) { + let sc = getEntityState(p); + if ('userid' in sc) { + for (const sbb of SBplayers) { + if (sc.userid == sbb.userid) + EntFire('server', 'Command', 'say ExWin:[' + + String(sbb.steamid) + + ' ' + + String(sbb.name) + + ']', 0.0, null); + } + } + } + break; + } + } +} +function ExSpawn(_template, _pos, _rot = Vector(0, 0, 0)) { + return { template: _template, pos: _pos, rot: _rot }; +} +function ExSpawnBounds(_template, _minX, _maxX, _minY, _maxY, _Z, _rot) { + return { + template: _template, + minX: _minX, + maxX: _maxX, + minY: _minY, + maxY: _maxY, + Z: _Z, + rot: _rot, + }; +} +function ExSpawnQueue(_spawn, _time) { + return { spawn: _spawn, time: _time }; +} +function ExevWeebTick() { + if (!exev_weebticking) + return; + scheduleInternalScript(() => { + ExevWeebTick(); + }, RandomFloat(exev_weebtick_min, exev_weebtick_max), null, null); + let amount_vaginaface = 0; + let amount_babyface = 0; + let h = null; + while (null != (h = Entities.FindByName(h, 'i_vaginaface_model*'))) { + amount_vaginaface++; + } + h = null; + while (null != (h = Entities.FindByName(h, 'i_diddlebaby_model*'))) { + amount_babyface++; + } + let spawnlist = []; + let spawnheight = 850; + if (amount_vaginaface <= 3) + spawnlist.push('s_vaginaface'); + if (amount_babyface <= 3) + spawnlist.push('s_diddlebaby'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + //more since #3 (OG #3 mortars above^): + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + spawnlist.push('s_mortar'); + //spawnlist.push("blobblaser_tem1"); //commented out in #4 + spawnlist.push('blobblaser_tem1'); + let spawnitem = spawnlist[RandomInt(0, spawnlist.length - 1)]; + if (spawnitem == 'blobblaser_tem1') + spawnheight = 48; + GetRandomCT(spawnitem, Vector(0, 0, spawnheight)); +} +function ExevTickFinalbossLaser() { + if (!exev_finalboss_laserticking) + return; + scheduleInternalScript(() => { + ExevTickFinalbossLaser(); + }, exev_finalboss_lasertickrate, null, null); + if (RandomInt(0, 4) == 1) + EntFire('dd_attack_slash_spawn', 'PickRandom', '', 0.0, null); + else + EntFire('dd_attack_upslash_spawn', 'PickRandom', '', 0.0, null); +} +var exev_finalboss_laserticking = false; +const exev_finalboss_lasertickrate = 0.9; +var exev_omaha_zamount = 5; +const exev_omaha_zhealthcap = 300; +var exev_omaha_zombies = []; +const exev_weebtick_min = 0.5; //0.1 in #3 +const exev_weebtick_max = 20.0; //5.0 in #3 +var exev_weebticking = false; +var exev_spawnmakers = []; //ExSpawnMaker(name,handle); +var exev_spawnbounds = []; //ExSpawnBounds(template,minX,maxX,minY,maxY,Z,rot) +var exev_spawns = []; //ExSpawn(template,pos,!rot); +var exev_spawnrate = 0.5; //spawn/tickrate for the spawns/spawnbounds^ (stops ticking when there's no spawns/spawnbounds) +var exev_spawnticking = false; +var exev_spawnqueue = []; //spawn-queue for time-based spawns +function ExevRoundStart() { + //reset states (triggered every round start IF 'extreme' is TRUE) + for (const sm of exev_spawnmakers) { + if (sm.handle != null && sm.handle.IsValid()) + EntFireByHandle(sm.handle, 'Kill', '', 0.0, null, null); + } + scheduleInternalScript(() => { + FetusFaceBoobie(); + }, RandomFloat(1.0, 500.0), null, null); + exev_finalboss_laserticking = false; + exev_omaha_zamount = 5; + exev_omaha_zombies.splice(0); + exev_omaha_zombies = []; + exev_weebticking = false; + exev_spawnmakers.splice(0); + exev_spawnbounds.splice(0); + exev_spawns.splice(0); + exev_spawnqueue.splice(0); + exev_spawnmakers = []; + exev_spawnbounds = []; + exev_spawns = []; + exev_spawnqueue = []; + exev_spawnticking = false; + exev_spawnrate = 0.1; + ExevSpawnTick(); + ExevSpawnQueueTick(); +} +function ExevSpawnTick() { + if (exev_spawns.length <= 0 && exev_spawnbounds.length <= 0) { + exev_spawnticking = false; + return; + } + exev_spawnticking = true; + scheduleInternalScript(() => { + ExevSpawnTick(); + }, exev_spawnrate, null, null); + if (exev_spawns.length > 0) { + let sp = exev_spawns[RandomInt(0, exev_spawns.length - 1)]; + ExevSpawn(sp.template, sp.pos, sp.rot); + } + if (exev_spawnbounds.length > 0) { + let spb = exev_spawnbounds[RandomInt(0, exev_spawnbounds.length - 1)]; + let sp_pos = Vector(RandomFloat(spb.minX, spb.maxX), RandomFloat(spb.minY, spb.maxY), spb.Z); + ExevSpawn(spb.template, sp_pos, spb.rot); + } +} +function ExevSpawnQueueTick() { + scheduleInternalScript(() => { + ExevSpawnQueueTick(); + }, 0.01, null, null); + let cleaned = true; + for (const q of exev_spawnqueue) { + q.time -= FrameTime(); + if (q.time <= 0.0) { + cleaned = false; + ExevSpawn(q.spawn.template, q.spawn.pos, q.spawn.rot); + } + } + while (!cleaned) { + cleaned = true; + for (let i = 0; i < exev_spawnqueue.length; i++) { + if (exev_spawnqueue[i].time <= 0.0) { + cleaned = false; + exev_spawnqueue.splice(i, 1)[0]; + break; + } + } + } +} +function ExevSpawn(template, pos, rot = Vector(0, 0, 0), time = 0.0) { + if (rot == null) + rot = Vector(0, 0, 0); + if (time > 0.0) { + exev_spawnqueue.push(ExSpawnQueue(ExSpawn(template, pos, rot), time)); + return; + } + forceSpawnTemplate(template, pos, rot); +} +function GetRandomCT(tem = null, ofs = null) { + let hlist = []; + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (h != null && h.IsValid() && h.GetHealth() > 0 && getTeam(h) == 3) + hlist.push(h); + } + if (hlist.length > 0) { + let r = hlist[RandomInt(0, hlist.length - 1)]; + if (tem != null) { + const spawnPos = ofs == null ? getOrigin(r) : VectorAdd(getOrigin(r), ofs); + ExevSpawn(tem, spawnPos); + } + return r; + } + return null; +} +function ExtremeOmahaTPZ() { + exev_omaha_zombies.splice(0); + let h = null; + while (null != (h = Entities.FindByClassname(h, 'player'))) { + if (h != null && h.IsValid() && h.GetHealth() > 2001 && getTeam(h) == 2) + exev_omaha_zombies.push(h); + } + if (exev_omaha_zombies.length <= 2 + exev_omaha_zamount) + return; + let trimmed = false; + while (!trimmed) { + trimmed = true; + if (exev_omaha_zombies.length > 0) + exev_omaha_zombies.splice(RandomInt(0, exev_omaha_zombies.length - 1), 1); + else + return; + if (exev_omaha_zombies.length > exev_omaha_zamount) + trimmed = false; + } + for (const h of exev_omaha_zombies) { + setOrigin(h, Vector(RandomInt(-13760, -11328), 3900, 14000)); //Vector(RandomInt(-13760,-11328),3072,14208) in #<=7 + h.SetHealth(exev_omaha_zhealthcap); + setVelocity(h, Vector(0, 0, 0)); + } + EntFire('server', 'Command', 'say *' + + String(exev_omaha_zombies.length) + + " Z'S ARE IN FRONT WITH LOW HP, KILL THEM!*", 0.0, null); + EntFire('server', 'Command', 'say *' + + String(exev_omaha_zombies.length) + + " Z'S ARE IN FRONT WITH LOW HP, KILL THEM!*", 0.01, null); + EntFire('server', 'Command', 'say *' + + String(exev_omaha_zombies.length) + + " Z'S ARE IN FRONT WITH LOW HP, KILL THEM!*", 0.02, null); + EntFire('server', 'Command', 'say *THEY ALSO DIE BY TRAVERSING TOO FAR - BATTLE IT OUT!*', 1.03, null); + scheduleInternalScript(() => { + ExtremeOmahaTPZTick(); + }, 0.5, null, null); +} +function ExtremeOmahaTPZTick() { + if (exev_omaha_zombies.length <= 0) + return; + scheduleInternalScript(() => { + ExtremeOmahaTPZTick(); + }, 0.1, null, null); + let cleaned = true; + for (const h of exev_omaha_zombies) { + if (h == null || + !h.IsValid() || + h.GetHealth() <= 0 || + getTeam(h) != 2 || + h.GetHealth() > 1000 + exev_omaha_zhealthcap) + cleaned = false; + else { + if (getOrigin(h).y > 4310 || getOrigin(h).y < -1400) { + //h.SetOrigin(Vector(RandomInt(-13760,-11328),3072,14208)); //#6 (tp fucked humans up, exploitable) + EntFireByHandle(h, 'SetDamageFilter', '', 0.0, null, null); //#7 (just ensure the zombie to die instead) + EntFireByHandle(h, 'SetHealth', '-1', 0.02, null, null); //#7 + EntFireByHandle(h, 'SetDamageFilter', '', 0.04, null, null); //#7 + EntFireByHandle(h, 'SetHealth', '-1', 0.08, null, null); //#7 + } + else if (h.GetHealth() > exev_omaha_zhealthcap) + EntFireByHandle(h, 'KeyValues', 'health ' + String(exev_omaha_zhealthcap), 0.0, null, null); + } + } + while (!cleaned) { + cleaned = true; + for (let i = 0; i < exev_omaha_zombies.length; i++) { + let h = exev_omaha_zombies[i]; + if (h == null || + !h.IsValid() || + h.GetHealth() <= 0 || + getTeam(h) != 2 || + h.GetHealth() > 1000 + exev_omaha_zhealthcap) { + cleaned = false; + exev_omaha_zombies.splice(i, 1)[0]; + if (h == null || !h.IsValid()) ; + else { + EntFireByHandle(h, 'SetHealth', '-1', 0.0, null, null); + } + break; + } + } + } +} +const CAKE_NADEREFILL_RANGE = 768; //512 in #3 +function GrenadeRefillCake(inputData) { + const { activator, caller } = inputData; + if (!extreme) + return; + if (caller != null && caller.IsValid()) { + //should be cake button handle + let h = null; + let interval = 0.05; + while (null != + (h = Entities.FindByClassnameWithin(h, 'player', getOrigin(caller), CAKE_NADEREFILL_RANGE))) { + if (h != null && h.IsValid() && h.GetHealth() > 0 && getTeam(h) == 3) { + scheduleInternalScript((inputData) => { + GrenadeRefillCakePlayer(inputData); + }, interval, h, h); + interval += 0.05; //do this to not refill nades on everyone at the same exact time, spread the load (seems to be edict-heavy otherwise) + } + } + } +} +function GrenadeRefillCakePlayer(inputData) { + const { activator, caller } = inputData; + if (activator != null && + activator.IsValid() && + activator.GetHealth() > 0 && + getTeam(activator) == 3) + EntFire('stripstrop_nade_refill', 'Use', '', 0.0, activator); +} +var finale_curseorbcheese_tick = true; +const finale_curseorbcheese_tickrate = 0.3; +const finale_curseorbcheese_scanrange = 1084; +const finale_curseorbcheese_scanpos = Vector(2640, -9680, 956); +function FinaleCurseOrbCheeseScan() { + if (!finale_curseorbcheese_tick) + return; + scheduleInternalScript(() => { + FinaleCurseOrbCheeseScan(); + }, finale_curseorbcheese_tickrate, null, null); + for (let h; (h = Entities.FindByNameWithin(h, 'i_curse_trigger*', finale_curseorbcheese_scanpos, finale_curseorbcheese_scanrange));) { + EntFireByHandle(h, 'FireUser1', '', 0.0, null, null); + } +} +var zombe_item_users = []; +function PickedUpZombieKnife(inputData) { + const { activator, caller } = inputData; + if (activator == null || + !activator.IsValid() || + activator.GetHealth() <= 0 || + getTeam(activator) != 2) + return; + zombe_item_users.push(activator); + let tp_pos = Vector(7275, 310, 245); + if (!checkpoint) + tp_pos = Vector(2603, 1027, 156); + setOrigin(activator, tp_pos); +} +function KillZombieItems() { + for (const h of zombe_item_users) { + if (h == null || !h.IsValid() || h.GetHealth() <= 0 || getTeam(h) != 2) + continue; + EntFireByHandle(h, 'SetHealth', '-69', 0.0, null, null); + EntFireByHandle(h, 'KeyValues', 'targetname notzitemhaha', 0.0, null, null); + } + zombe_item_users.splice(0); + zombe_item_users = []; + EntFire('zombieitem', 'KeyValues', 'targetname notzitemhaha', 0.0, null); + EntFire('ITEMX_hich_zmboost_*', 'Kill', '', 0.05, null); + EntFire('ITEMX_luff_zmjihad_p2*', 'Kill', '', 0.05, null); + EntFire('ITEMX_luff_zmjihad_shake*', 'Kill', '', 0.05, null); + EntFire('ITEMX_luff_zmjihad_ex*', 'Kill', '', 0.05, null); +} +//spawns an edited cake that's capped to 100hp only, only for extreme +//replaces the auto-heal-trigger at the dicklett boss #2 entry +//this removes the "handholding" from the map, albeit a very minor thing, it's nice to var players be in charge of their own fate +//the cake will spawn randomly within the fall-down tunnel just before dicklett boss #2, players gotta communicate/stay attentive to get it +var SpawnDicklettBoss2LimitedCake_pos = Vector(); +function SpawnDicklettBoss2LimitedCake() { + scheduleInternalScript(() => { + SpawnDicklettBoss2LimitedCakePost(); + }, 1.0, null, null); + SpawnDicklettBoss2LimitedCake_pos = Vector(RandomInt(-12192, -11872), RandomInt(15472, 15744), RandomInt(13888, 14656)); + ExevSpawn('itemturtleTemplateHeal2', SpawnDicklettBoss2LimitedCake_pos, Vector(0, 0, 0), 0.0); +} +function SpawnDicklettBoss2LimitedCakePost() { + let caketrig = Entities.FindByNameNearest('aX69XTurtleHealTrigger*', SpawnDicklettBoss2LimitedCake_pos, 500); + if (caketrig == null || !caketrig.IsValid()) { + printl('[SpawnDicklettBoss2LimitedCakePost error] - TRIGGER NOT FOUND AT POS: ' + + SpawnDicklettBoss2LimitedCake_pos); + return; + } + getEntityState(caketrig).dicklettboss2entry = true; + let cakemodel = Entities.FindByNameNearest('aX69XTurtleCake*', SpawnDicklettBoss2LimitedCake_pos, 500); + if (cakemodel == null || !cakemodel.IsValid()) { + printl('[SpawnDicklettBoss2LimitedCakePost error] - MODEL NOT FOUND AT POS: ' + + SpawnDicklettBoss2LimitedCake_pos); + return; + } + EntFireByHandle(cakemodel, 'SetScale', '1.10', 0.0, null, null); + //EntFireByHandle(cakemodel, 'AddOutput', 'rendermode 2', 0.0, null, null) + EntFireByHandle(cakemodel, 'Color', '0 255 200', 0.0, null, null); + EntFireByHandle(cakemodel, 'StartGlowing', '', 0.0, null, null); + EntFireByHandle(cakemodel, 'SetGlowRange', '1000', 0.0, null, null); + //EntFireByHandle(cakemodel, 'AddOutput', 'glowstyle 3', 0.0, null, null) + EntFireByHandle(cakemodel, 'SetGlowOverride', '0 200 255', 0.0, null, null); + let cakesound = Entities.FindByNameNearest('tttsound17*', SpawnDicklettBoss2LimitedCake_pos, 500); + if (cakesound == null || !cakesound.IsValid()) { + printl('[SpawnDicklettBoss2LimitedCakePost error] - SOUND NOT FOUND AT POS: ' + + SpawnDicklettBoss2LimitedCake_pos); + return; + } + //EntFireByHandle(cakesound, 'AddOutput', 'pitch 200', 0.0, null, null) +} +//NOTE^ the strip-triggers are now stripped out for #9 (the function +// the function/ticker below is used to check/strip players now: +const zstriptick_tickrate = 0.2; +const zstriptick_range = 100; +const zstriptick_xyrange = 40; +const zstriptick_minhp = 666; +function TickZombieStrip() { + scheduleInternalScript(() => { + TickZombieStrip(); + }, zstriptick_tickrate, null, null); + for (let h; (h = Entities.FindByName(h, 'ITEMX_hich_zmboost_knife*'));) { + let p1 = getOrigin(h); + p1.z = 0; + for (let hh; (hh = Entities.FindByClassnameWithin(hh, 'player', getOrigin(h), zstriptick_range));) { + if (hh == null || + !hh.IsValid() || + getTeam(hh) != 2 || + hh.GetHealth() < zstriptick_minhp) + continue; + printl('[zstriptick] player in scan range: ' + hh); + let p2 = getOrigin(hh); + p2.z = 0; + let dist = GetZstripDis(p1, p2); + if (dist <= zstriptick_xyrange) { + printl('[zstriptick] player in pickup range: ' + hh); + let already_holder = false; + for (const p of zombe_item_users) { + if (p == hh) { + already_holder = true; + break; + } + } + if (already_holder) { + printl('[zstriptick] player is already item-holder: ' + hh); + continue; + } + //zombe_item_users.push(hh); =---- this is done when the player actually picks up the knife + EntFire('stripstrop_wepwepstrip', 'Strip', '', 0.0, hh); + EntFireByHandle(h, 'ToggleCanBePickedUp', '', 0.0, null, null); + EntFireByHandle(h, 'ToggleCanBePickedUp', '', 0.1, null, null); + EntFireByHandle(h, 'ToggleCanBePickedUp', '', 0.12, null, null); + EntFireByHandle(h, 'ToggleCanBePickedUp', '', 0.14, null, null); + EntFireByHandle(h, 'ToggleCanBePickedUp', '', 0.16, null, null); + printl('[zstriptick] player is not item-holder - STRIPPING: ' + hh); + setVelocity(hh, Vector()); + setOrigin(hh, VectorAdd(getOrigin(h), Vector(0, 0, -10))); //TODO - ensure that this works, can you get stuck? + break; + } + } + } +} +function GetZstripDis(v1, v2) { + return sqrt((v1.x - v2.x) * (v1.x - v2.x) + + (v1.y - v2.y) * (v1.y - v2.y) + + (v1.z - v2.z) * (v1.z - v2.z)); +} +function FetusFaceBoobie() { + let time = RandomFloat(1.0, 30.0); + EntFire('stripstrop_fetusface_particle', 'Stop', '', 0.0, null); + EntFire('stripstrop_fetusface_particle', 'Start', '', 0.02, null); + EntFire('stripstrop_fetusface_particle', 'Stop', '', time, null); + scheduleInternalScript(() => { + FetusFaceBoobie(); + }, time + RandomFloat(1.0, 500.0), null, null); +} +const antishopoverdefend_pos = Vector(7780, 320, 236); //pos to check for nearby CT's +const antishopoverdefend_dist = 1500; //radius to check, relative from 'pos' +const antishopoverdefend_rate = 7.0; //how often to check/spawn yellow lasers +function AntiShopOverdefend() { + // 清除之前的定时器,防止多个循环同时运行 + if (shopDefendTimer !== null) { + clearInterval(shopDefendTimer); + shopDefendTimer = null; + } + // 启动新的循环定时器 + shopDefendTimer = setInterval(() => { + let hlist = []; + let h = null; + while (null != + (h = Entities.FindByClassnameWithin(h, 'player', antishopoverdefend_pos, antishopoverdefend_dist))) { + if (h == null || + !h.IsValid() || + getClassname(h) != 'player' || + getTeam(h) != 3 || + h.GetHealth() <= 0) + continue; + hlist.push(h); + } + if (hlist.length > 0) { + let hplayer = hlist[RandomInt(0, hlist.length - 1)]; + if (hplayer == null || !hplayer.IsValid()) + return; + const spawnPosition = VectorAdd(getOrigin(hplayer), Vector(0, 0, 48)); + // 直接生成,无需额外延迟 + forceSpawnTemplate('blobblaser_tem1', spawnPosition, null); + } + }, antishopoverdefend_rate * 1000); // 转换为毫秒 +} +function RecodeMeLaserHurt(inputData) { + const { activator, caller } = inputData; + if (caller == null || !caller.IsValid()) + return; + const state = getEntityState(caller); + if (state != null) + state.recodeMeLaserHurt = true; + printl('[ze_diddle] RecodeMeLaserHurt requires map-side replacement for !self Hurt() outputs'); +} +function TickFinaleTPMover() { + scheduleInternalScript(() => { + TickFinaleTPMover(); + }, 1.0, null, null); + let tpddd = Entities.FindByName(null, 'finaletd_extra_zstart'); + if (tpddd == null) + return; + let tpdddplat = Entities.FindByName(null, 'extra_zombieplatform'); + if (tpdddplat == null) + return; + setOrigin(tpddd, VectorAdd(getOrigin(tpdddplat), Vector(0, 0, 10))); +} +//------------------------------------------------------------------------------------------------------------ +//force is assigned in output via stripper, +//this push works a bit differently, it SETS force, doesn't ADD force +//should be safe against wonky boosts, like in mapeadores +//because grounded players are a bit more sturdy, there's a check that makes them jump if z-velocity is within bounds +// +//the original trigger_push push is: +// discopush: 700 +// torch: 500 +// +//should be fine, but in worst case the +// +//in #5 it's: +// discopush: 600 +// torch: 100 +//------------------------------------------------------------------------------------------------------------ +const pushplayerfromtrigger_z_bounds = 5; //if player Z-velocity is above -X and under -X = auto-jump player +const pushplayerfromtrigger_z_amt = 251; //auto-jump force (251 seems like a safe limit) +function PushPlayerFromTrigger(inputData, force) { + const { activator, caller } = inputData; + let dir = getForwardVector(caller); + dir.z = 0.0; + dir = dir.normal; + dir = VectorScale(dir, force); + let zofs = Vector(0, 0, getVelocity(activator).z); + if (getVelocity(activator).z > -pushplayerfromtrigger_z_bounds && + getVelocity(activator).z < pushplayerfromtrigger_z_bounds) + zofs.z = pushplayerfromtrigger_z_amt; + setVelocity(activator, VectorAdd(dir, zofs)); +} +function PushTrigger(inputData, time, rate) { + const { activator, caller } = inputData; + let ii = 0.0; + EntFireByHandle(caller, 'Enable', '', 0.0, null, null); + for (let i = 0.0; i < time; i += rate) { + ii += rate; + EntFireByHandle(caller, 'Toggle', '', ii, null, null); + } + EntFireByHandle(caller, 'Disable', '', ii, null, null); + EntFireByHandle(caller, 'Disable', '', ii + 0.2, null, null); + EntFireByHandle(caller, 'Disable', '', ii + 0.5, null, null); + EntFireByHandle(caller, 'Disable', '', ii + 1.0, null, null); +} +function ExtremeEventCase79() { + const upslash = Instance.FindEntitiesByName('dd_attack_upslash_*'); + const highslash = Instance.FindEntitiesByName('dd_attack_highslash'); + const lowslash = Instance.FindEntitiesByName('dd_attack_lowslash'); + const entities = [...upslash, ...highslash, ...lowslash]; + for (const entity of entities) { + const origin = entity.GetAbsOrigin(); + const newOrigin = Vector(origin.x, origin.y + 3200, origin.z); + setOrigin(entity, newOrigin); + } +} +//------------------------------------------------------------------------------------------------------------ +const EXTERNAL_INPUT_ALIASES = [ + input('ExtremeEventCase79', 'ExtremeEventCase(79)', () => ExtremeEventCase79(), 'new'), + input('AddCoins_1', 'AddCoins(1)', () => AddCoins(1), 'vmf'), + input('AddCoins_5', 'AddCoins(5)', () => AddCoins(5), 'vmf'), + input('AddCoins_10', 'AddCoins(10)', () => AddCoins(10), 'vmf'), + input('AddCustomer', 'AddCustomer()', (inputData) => AddCustomer(inputData), 'vmf'), + input('LeaveCustomer', 'LeaveCustomer()', (inputData) => LeaveCustomer(inputData), 'vmf'), + input('PickCustomer', 'PickCustomer()', () => PickCustomer(), 'vmf'), + input('InitPS', 'InitPS()', () => InitPS(), 'vmf'), + input('TryPS', 'TryPS()', () => TryPS(), 'vmf'), + input('TryPS1', 'TryPS1()', (inputData) => TryPS1(inputData), 'vmf'), + input('LeavePS', 'LeavePS()', (inputData) => LeavePS(inputData), 'vmf'), + input('ExcludePS', 'ExcludePS()', () => ExcludePS(), 'vmf'), + input('CheckPS', 'CheckPS()', (inputData) => CheckPS(inputData), 'vmf'), + input('CheckPSAffirmal', 'CheckPSAffirmal()', (inputData) => CheckPSAffirmal(inputData), 'vmf'), + ...Array.from({ length: 9 }, (_, i) => input(`BuyItem_${i}`, `BuyItem(${i})`, (inputData) => BuyItem(inputData, i), 'vmf')), + ...Array.from({ length: 5 }, (_, i) => input(`ClearedStage_${i}`, `ClearedStage(${i})`, () => ClearedStage(i), 'vmf')), + ...Array.from({ length: 5 }, (_, i) => input(`SkipStage_${i}`, `SkipStage(${i})`, () => SkipStage(i), 'vmf')), + input('SkipToFinale', 'SkipToFinale()', () => SkipToFinale(), 'vmf'), + input('SpawnBaby', 'SpawnBaby()', (inputData) => SpawnBaby(inputData), 'vmf'), + input('RemoveBaby', 'RemoveBaby()', (inputData) => RemoveBaby(inputData), 'vmf'), + input('AddVagina', 'AddVagina()', (inputData) => AddVagina(inputData), 'vmf'), + input('TurtleBossLowerFloowExtremeExecution', 'TurtleBossLowerFloowExtremeExecution()', () => TurtleBossLowerFloowExtremeExecution(), 'new'), + input('RemoveVagina', 'RemoveVagina()', () => RemoveVagina(), 'vmf'), + input('RoundStart', 'RoundStart()', () => RoundStart(), 'vmf'), + input('ResetMap', 'ResetMap()', () => ResetMap(), 'vmf+stripper'), + input('ResetMap_ButtonTrue', 'ResetMap(true)', () => ResetMap(true), 'stripper'), + input('ResetMapBase', 'ResetMapBase()', () => ResetMapBase(), 'stripper'), + input('ResetMapBase_False', 'ResetMapBase(false)', () => ResetMapBase(false), 'stripper'), + input('ResetOverlay', 'ResetOverlay()', () => ResetOverlay(), 'vmf'), + input('GateOpenCheck', 'GateOpenCheck()', () => GateOpenCheck(), 'vmf'), + input('GetAllCoins', 'GetAllCoins()', () => GetAllCoins(), 'vmf'), + input('TeleportLate', 'TeleportLate()', (inputData) => TeleportLate(inputData), 'vmf'), + input('SetShopCheat', 'SetShopCheat()', (inputData) => SetShopCheat(inputData), 'vmf'), + input('KillAllButton', 'KillAllButton()', () => KillAllButton(), 'vmf+stripper'), + input('KillAllCT', 'KillAllCT()', () => KillAllCT(), 'vmf'), + input('KillAllT', 'KillAllT()', () => KillAllT(), 'vmf'), + input('ResetDamageFilter', 'ResetDamageFilter()', () => ResetDamageFilter(), 'vmf'), + ...[-1, 0, 1, 2, 3, 4].map((value) => input(value < 0 ? 'SetStageChosen_M1' : `SetStageChosen_${value}`, `stageChosen = ${value}`, () => { + stageChosen = value; + }, 'stripper')), + input('SetShopActiveTrue', 'shopactive = true', () => { + shopactive = true; + }, 'vmf'), + ...[1, 2, 3, 5, -10].map((value) => input(value < 0 ? 'AddPlayerShopBias_M10' : `AddPlayerShopBias_${value}`, `AddPlayerShopBias(${value})`, (inputData) => AddPlayerShopBias(inputData, value), 'stripper')), + input('PushPlayerFromTrigger_400', 'PushPlayerFromTrigger(400)', (inputData) => PushPlayerFromTrigger(inputData, 400), 'stripper'), + input('PushPlayerFromTrigger_600', 'PushPlayerFromTrigger(600)', (inputData) => PushPlayerFromTrigger(inputData, 600), 'stripper'), + input('PushTrigger_2_5_0_10', 'PushTrigger(2.5,0.10)', (inputData) => PushTrigger(inputData, 2.5, 0.1), 'stripper'), + input('PushTrigger_7_0_0_10', 'PushTrigger(7.0,0.10)', (inputData) => PushTrigger(inputData, 7.0, 0.1), 'stripper'), + input('AutoHurtWallNearTP_1', 'AutoHurtWallNearTP(1)', (inputData) => AutoHurtWallNearTP(inputData, 1), 'stripper'), + input('AutoHurtWallNearTP_2', 'AutoHurtWallNearTP(2)', (inputData) => AutoHurtWallNearTP(inputData, 2), 'stripper'), + input('AutoHurtWallNearTP_3', 'AutoHurtWallNearTP(3)', (inputData) => AutoHurtWallNearTP(inputData, 3), 'stripper'), + input('ExtremeModeVote', 'ExtremeModeVote()', (inputData) => ExtremeModeVote(inputData), 'stripper'), + input('VoteMsg', 'VoteMsg()', () => VoteMsg(), 'stripper'), + input('ExtremeCheck', 'ExtremeCheck()', () => ExtremeCheck(), 'stripper'), + input('HealExtremeCheck', 'HealExtremeCheck()', () => HealExtremeCheck(), 'stripper'), + input('TorchExtremeCheck', 'TorchExtremeCheck()', () => TorchExtremeCheck(), 'stripper'), + ...Array.from({ length: 80 }, (_, i) => input(`ExtremeEvent_${i + 1}`, `ExtremeEvent(${i + 1})`, (inputData) => ExtremeEvent(inputData, i + 1), 'stripper')), + input('DiddleDickBossInit', 'DiddleDickBossInit()', () => DiddleDickBossInit(), 'stripper'), + input('DiddleDickBossInitHP', 'DiddleDickBossInitHP()', () => DiddleDickBossInitHP(), 'stripper'), + input('DiddleFriendPostCheck', 'DiddleFriendPostCheck()', (inputData) => DiddleFriendPostCheck(inputData), 'stripper'), + input('DiddleFriendShopCheck', 'DiddleFriendShopCheck()', () => DiddleFriendShopCheck(), 'stripper'), + input('SpawnItemPriceIndicators', 'SpawnItemPriceIndicators()', () => SpawnItemPriceIndicators(), 'stripper'), + input('CakeHealTouch', 'CakeHealTouch()', (inputData) => CakeHealTouch(inputData), 'stripper'), + input('GrenadeRefillCake', 'GrenadeRefillCake()', (inputData) => GrenadeRefillCake(inputData), 'stripper'), + input('TorchCooldown', 'TorchCooldown()', (inputData) => TorchCooldown(inputData), 'stripper'), + input('TorchCooldownVis', 'TorchCooldownVis()', (inputData) => TorchCooldownVis(inputData), 'stripper'), + input('FetusSpawnZombieExtra', 'FetusSpawnZombieExtra()', (inputData) => FetusSpawnZombieExtra(inputData), 'stripper'), + input('PickedUpZombieKnife', 'PickedUpZombieKnife()', (inputData) => PickedUpZombieKnife(inputData), 'stripper'), + input('PlayerKillEvent', 'PlayerKillEvent()', (inputData) => PlayerKillEvent(inputData), 'stripper'), + input('GiveScoreToMapWinners_300', 'GiveScoreToMapWinners(300)', () => GiveScoreToMapWinners(300), 'stripper'), + input('OverrideBabyFaceHP', 'OverrideBabyFaceHP()', () => OverrideBabyFaceHP(), 'stripper'), + input('OverrideVaginaFaceHP', 'OverrideVaginaFaceHP()', () => OverrideVaginaFaceHP(), 'stripper'), + input('RecodeMeLaserHurt', 'RecodeMeLaserHurt()', (inputData) => RecodeMeLaserHurt(inputData), 'stripper'), + input('WonBasicBitch', 'WonBasicBitch()', () => WonBasicBitch(), 'stripper'), + input('DisplayServerWinChat', 'DisplayServerWinChat()', () => DisplayServerWinChat(), 'stripper'), + input('YellowLaserSpawned', 'YellowLaserSpawned()', (inputData) => YellowLaserSpawned(inputData), 'stripper'), + input('CurseWallBreakTick', 'CurseWallBreakTick()', (inputData) => CurseWallBreakTick(inputData), 'stripper'), + input('LavaWalkFix', 'LavaWalkFix()', () => LavaWalkFix(), 'stripper'), + input('TickFinaleTPMover', 'TickFinaleTPMover()', () => TickFinaleTPMover(), 'stripper'), + input('OmahaMortarExtremeRender', 'OmahaMortarExtremeRender()', (inputData) => OmahaMortarExtremeRender(inputData), 'stripper'), + input('OmahaTrimDelayers', 'OmahaTrimDelayers()', () => OmahaTrimDelayers(), 'stripper'), + input('KillDiddleBabyShrekEx', 'KillDiddleBabyShrekEx()', () => KillDiddleBabyShrekEx(), 'stripper'), + input('KillShrekFinalRun', 'KillShrekFinalRun()', () => KillShrekFinalRun(), 'stripper'), + input('TurtleBossLowerFloowExtreme', 'TurtleBossLowerFloowExtreme()', () => TurtleBossLowerFloowExtreme(), 'stripper'), + input('TurtleFallDownBoss', 'TurtleFallDownBoss()', (inputData) => TurtleFallDownBoss(inputData), 'stripper'), + input('AntiShopOverdefend', 'AntiShopOverdefend()', () => AntiShopOverdefend(), 'runtime', 'RoundStart injects this fixed input into shopgate OnBreak.'), + input('SetExtremeTrue', 'extreme = true', () => { + extreme = true; + }, 'stripper'), + input('SetDdickDeadTrue', 'ddickdead = true', () => { + ddickdead = true; + }, 'stripper'), + input('SetDdickTimeout_1_20', 'ddicktimeout = 1.20', () => { + ddicktimeout = 1.2; + }, 'stripper'), + input('SetTickLavaWalkFixTrue', 'ticklavawalkfix = true', () => { + ticklavawalkfix = true; + }, 'stripper'), + input('SetTickLavaWalkFixFalse', 'ticklavawalkfix = false', () => { + ticklavawalkfix = false; + }, 'stripper'), + input('SetStopTickKillDiddleBabyShrekExTrue', 'stoptick_KillDiddleBabyShrekEx=true', () => { + stoptick_KillDiddleBabyShrekEx = true; + }, 'stripper'), + input('SetTurtleFallDownPlatformGoneTrue', 'turtlefalldown_platformgone = true', () => { + turtlefalldown_platformgone = true; + }, 'stripper'), + input('SetTurtleFallDownPlatformGoneFalse', 'turtlefalldown_platformgone = false', () => { + turtlefalldown_platformgone = false; + }, 'stripper'), +]; +function OnRoundEnd(reason) { + if (shopDefendTimer !== null) { + clearInterval(shopDefendTimer); + shopDefendTimer = null; + } + EntFire('killstuff', 'Trigger', '', 0.0, null); + //EntFire('trigger_teleport', 'Kill', '', 0.0, null); //dont work in cs2 + EntFire('Map_Manager', 'RunScriptInput', 'ExtremeCheck', 0.0, null); + //EntFire('cmd', 'Command', 'say >>Round END', 0.0, null); //debug text + const teleports = Instance.FindEntitiesByClass('trigger_teleport'); + for (let i = 0; i < teleports.length; i++) { + const ent = teleports[i]; + if (ent) { + ent.Remove(); // remove trigger_teleport + } + } + ddickdead = true; +} +Instance.OnRoundEnd(OnRoundEnd); +installScheduler(); +registerInputAliases(SCRIPT_PREFIX, EXTERNAL_INPUT_ALIASES); +Instance.Msg('[ze_diddle] manager script by luffaren, ported by codex with GPT.'); +Instance.Msg('[ze_diddle] manager script loaded with ' + + EXTERNAL_INPUT_ALIASES.length + + ' external input aliases'); diff --git a/2001/ze_diddle/ze_diddle/blobarm.js b/2001/ze_diddle/ze_diddle/blobarm.js new file mode 100644 index 0000000..c17d7fb --- /dev/null +++ b/2001/ze_diddle/ze_diddle/blobarm.js @@ -0,0 +1,913 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, (data) => callback({ + ...data, + activator: data?.activator, + caller: data?.caller, + })); +} + +//===================================\\ +// Blob Arm script by Luffaren (STEAM_0:1:22521282) +// CS2 Port - manager.ts style, only Death and SpawnArms +// ===================================\\ +function input(name, _legacyExpression, handler, source = "vmf", notes = "") { + return { name, handler, source, notes }; +} +// ---------- 脚本状态 ---------- +let blobtimer = 4.00; +// ---------- 核心函数 ---------- +function Death() { + blobtimer += 2.00; + Instance.EntFireAtName({ name: "blobb_timer", input: "UpperRandomBound", value: blobtimer.toString() }); + Instance.EntFireAtName({ name: "blobb_timer", input: "LowerRandomBound", value: (blobtimer / 3).toString() }); +} +function SpawnArms() { + const spawns = [ + { delay: 0.00, origin: "9393 3348 -1154", angles: "60 -50 0" }, + { delay: 0.10, origin: "11047 4811 -2052", angles: "55 -47 100" }, + { delay: 0.20, origin: "11032 4780 94", angles: "-33 -57 45" }, + { delay: 0.30, origin: "12639 4684 -1658", angles: "8 -102 70" }, + { delay: 0.40, origin: "13577 3640 -2852", angles: "55 -170 260" }, + { delay: 0.50, origin: "11124 4600 -2259", angles: "-30 -50 120" }, + { delay: 0.60, origin: "12638 4765 -2515", angles: "-10 -70 180" }, + { delay: 0.70, origin: "12953 4076 -18", angles: "25 -108 0" }, + { delay: 0.80, origin: "11146 4779 -1961", angles: "-5 -25 -45" }, + { delay: 0.90, origin: "13645 3317 -1460", angles: "0 -120 0" }, + { delay: 1.00, origin: "12465 3855 1844", angles: "56 -150 270" }, + { delay: 1.10, origin: "9850 3551 1113", angles: "28 -33 270" }, + { delay: 1.20, origin: "11226 4624 1804", angles: "13 -35 -20" }, + { delay: 1.30, origin: "13536 2172 -1871", angles: "4 163 45" }, + { delay: 1.40, origin: "12903 3820 -464", angles: "-24 -118 15" }, + { delay: 1.50, origin: "12903 3820 -464", angles: "-24 -118 15" }, + { delay: 1.60, origin: "8919 2787 -1831", angles: "1 -12 25" }, + { delay: 1.70, origin: "9911 1940 -50", angles: "2 26 0" }, + { delay: 1.80, origin: "13318 5012 -2604", angles: "-46 -130 0" }, + { delay: 1.90, origin: "13764 2449 -2987", angles: "29 163 0" }, + { delay: 2.00, origin: "13153 4319 -1497", angles: "32 -145 180" }, + ]; + for (const spawn of spawns) { + Instance.EntFireAtName({ name: "s_blobarm", input: "KeyValues", value: "origin " + spawn.origin, delay: spawn.delay }); + Instance.EntFireAtName({ name: "s_blobarm", input: "KeyValues", value: "angles " + spawn.angles, delay: spawn.delay }); + Instance.EntFireAtName({ name: "s_blobarm", input: "ForceSpawn", delay: spawn.delay + 0.05 }); + } +} +// ---------- 处理器函数 ---------- +function handleDeath() { + Death(); +} +function handleSpawnArms() { + SpawnArms(); +} +// ---------- 外部输入别名列表 ---------- +const EXTERNAL_INPUT_ALIASES = [ + input("Death", "Death()", handleDeath, "vmf", "Trigger death animation and update timer"), + input("SpawnArms", "SpawnArms()", handleSpawnArms, "vmf", "Spawn multiple blob arms with predefined positions"), +]; +// ---------- 注册函数 ---------- +function registerInputAliases() { + for (const entry of EXTERNAL_INPUT_ALIASES) { + registerFixedScriptInput(entry.name, entry.handler); + } + //Instance.Msg(`[BlobArm] Registered ${EXTERNAL_INPUT_ALIASES.length} inputs\n`); +} +// 顶层执行注册 +registerInputAliases(); diff --git a/2001/ze_diddle/ze_diddle/bloblaser.js b/2001/ze_diddle/ze_diddle/bloblaser.js new file mode 100644 index 0000000..9f6e136 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/bloblaser.js @@ -0,0 +1,1080 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function getClassname(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetClassName(); +} + +//===================================\\ +// Blob Laser script by Luffaren (STEAM_0:1:22521282) +// CS2 Port - ignore player self, fixed eye offset +// ===================================\\ +function input(name, _legacyExpression, handler, source = "vmf", notes = "") { + return { name, handler, source, notes }; +} +// ---------- 脚本状态 ---------- +const SCRIPT_PREFIX = 'ze_diddle/bloblaser'; +const SCRIPT = "Map_BlobLaser"; +const BLOB_CENTER = { x: 11320, y: 2608, z: 2616 }; +const EYE_OFFSET = 48; +// ---------- 伤害相关 ---------- +let damageLoopActive = false; +const DAMAGE_INTERVAL = 0.1; // 0.1秒检测一次 +const DAMAGE_PER_HIT = 2; // 每次2点,即20点/秒 +// ---------- 通用内部函数 ---------- +function executeLaser(targetName, laserName, relayName, temName, onDelay) { + const scriptEnt = Instance.FindEntityByName(SCRIPT); + const players = Instance.FindEntitiesByClass("player"); + const plist = []; + for (const player of players) { + if (player && player.IsValid() && player.GetTeamNumber() === 3 && player.GetHealth() > 0) { + const ppos = player.GetAbsOrigin(); + const traceStart = { x: ppos.x, y: ppos.y, z: ppos.z + EYE_OFFSET }; + const ignoreList = [scriptEnt, player].filter(e => e && e.IsValid()); + const trace = Instance.TraceLine({ + start: traceStart, + end: BLOB_CENTER, + ignoreEntity: ignoreList.length ? ignoreList : undefined + }); + if (trace.fraction >= 1.0) { + plist.push(player); + } + } + } + let selected = null; + if (plist.length > 0) { + const target = plist.length === 1 ? plist[0] : plist[Math.floor(Math.random() * plist.length)]; + if (target && target.IsValid() && target.GetHealth() > 0) { + selected = target; + ////printl(`[BlobLaser] Laser ${laserName} targeting player: ${target.GetEntityName()}`); + const poss = target.GetAbsOrigin(); + const pos = `${poss.x} ${poss.y} ${poss.z + EYE_OFFSET}`; + const targetEnt = Instance.FindEntityByName(targetName); + if (targetEnt && targetEnt.IsValid()) { + Instance.EntFireAtName({ name: targetName, input: "KeyValues", value: "origin " + pos }); + } + const laserEnt = Instance.FindEntityByName(laserName); + if (laserEnt && laserEnt.IsValid()) { + Instance.EntFireAtName({ name: laserName, input: "Start", delay: 0.10 }); + Instance.EntFireAtName({ name: laserName, input: "Stop", delay: onDelay }); + } + const temEnt = Instance.FindEntityByName(temName); + if (temEnt && temEnt.IsValid()) { + Instance.EntFireAtName({ name: temName, input: "KeyValues", value: "origin " + pos }); + Instance.EntFireAtName({ name: temName, input: "ForceSpawn", delay: 0.10 }); + } + Instance.EntFireAtName({ name: relayName, input: "Disable" }); + Instance.EntFireAtName({ name: relayName, input: "Enable", delay: onDelay }); + } + else { + Instance.EntFireAtName({ name: relayName, input: "Enable" }); + } + } + else { + Instance.EntFireAtName({ name: relayName, input: "Enable" }); + } + return selected; +} +// ---------- 伤害循环 ---------- +function startDamageLoop() { + if (damageLoopActive) + return; + damageLoopActive = true; + //printl("[BlobLaser] Damage loop started"); + damageLoopTick(); +} +function damageLoopTick() { + if (!damageLoopActive) + return; + const start = BLOB_CENTER; + const targetEnt = Instance.FindEntityByName("blobb_laser_target1"); + if (!targetEnt || !targetEnt.IsValid()) { + //printl("[BlobLaser] Target entity missing, stopping damage loop"); + stopDamageLoop(); + return; + } + const end = targetEnt.GetAbsOrigin(); + const scriptEnt = Instance.FindEntityByName(SCRIPT); + const trace = Instance.TraceLine({ + start, + end, + ignoreEntity: scriptEnt && scriptEnt.IsValid() ? scriptEnt : undefined, + ignorePlayers: false + }); + if (trace.didHit && trace.hitEntity && trace.hitEntity.IsValid()) { + const hit = trace.hitEntity; + if (getClassname(hit) === "player" && hit.GetTeamNumber() === 3 && hit.GetHealth() > 0) { + const newHealth = hit.GetHealth() - DAMAGE_PER_HIT; + EntFireByHandle(hit, "SetHealth", String(newHealth), 0.0, null, null); + //printl(`[BlobLaser] Damaged player, new health: ${newHealth}`); + } + } + // 继续循环 + scheduleScript(SCRIPT_PREFIX, () => damageLoopTick(), DAMAGE_INTERVAL); +} +function stopDamageLoop() { + damageLoopActive = false; + //printl("[BlobLaser] Damage loop stopped"); +} +// ---------- 处理器函数 ---------- +function handleStartLaser() { + //printl("[BlobLaser] StartLaser called"); + executeLaser("blobb_laser_target", "blobb_laser", "blobb_laser_relay", "blobblaser_tem", 3.10); + // 不添加伤害 +} +function handleStartLaser1() { + //printl("[BlobLaser] StartLaser1 called"); + const target = executeLaser("blobb_laser_target1", "blobb_laser1", "blobb_laser_relay1", "blobblaser_tem1", 6.10); + if (target && target.IsValid()) { + //printl("[BlobLaser] Starting damage loop due to valid target"); + startDamageLoop(); + // 6.1秒后停止伤害 + scheduleScript(SCRIPT_PREFIX, () => stopDamageLoop(), 6.10); + } +} +// ---------- 外部输入别名列表 ---------- +const EXTERNAL_INPUT_ALIASES = [ + input("StartLaser", "StartLaser()", handleStartLaser, "vmf", "Small laser targeting visible CT"), + input("StartLaser1", "StartLaser1()", handleStartLaser1, "vmf", "Big laser targeting visible CT with damage"), +]; +// ---------- 注册 ---------- +function registerInputAliases() { + for (const entry of EXTERNAL_INPUT_ALIASES) { + registerFixedScriptInput(entry.name, entry.handler); + } +} +registerInputAliases(); +installScheduler(); // 必须添加! diff --git a/2001/ze_diddle/ze_diddle/curseorb.js b/2001/ze_diddle/ze_diddle/curseorb.js new file mode 100644 index 0000000..230e260 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/curseorb.js @@ -0,0 +1,1038 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function requireSelf(entity, scriptName) { + return requireEntity(entity, `${scriptName} SetSelf`); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function VectorAdd(a, b) { + return Vector3Utils.add(a, b); +} +function VectorScale(vector, scale) { + return Vector3Utils.scale(vector, scale); +} +function TraceLine(start, end, ignoreEntity = null, ignorePlayers = false) { + const trace = Instance.TraceLine({ + start, + end, + ignoreEntity: ignoreEntity ?? undefined, + ignorePlayers, + }); + return trace.fraction; +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function getAngles(entity) { + if (entity == null || !entity.IsValid()) + return angles(); + return angles(entity.GetAbsAngles()); +} +function setOrigin(entity, position) { + if (!isValidEntity(entity) || position == null) + return; + teleportEntity(entity, position, null, null); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function getForwardVector(entity) { + return getAngles(entity).forward; +} + +const SPEED = 3.0; +let self = null; +let ticking = false; +function SetSelf(inputData) { + const caller = inputData.caller; + if (caller && caller.IsValid()) { + self = caller; + } +} +function Init() { + if (ticking) + return; + ticking = true; + Tick(); +} +function Tick() { + if (!ticking) + return; + const orb = requireSelf(self, 'curseorb'); + const forward = getForwardVector(orb); + setOrigin(orb, VectorAdd(getOrigin(orb), VectorScale(forward, SPEED))); + if (TraceLine(getOrigin(orb), VectorAdd(getOrigin(orb), VectorScale(forward, 20)), orb, true) < 1.0) { + EntFireByHandle(orb, 'FireUser1', '', 0.0, null, null); + } + scheduleScript('ze_diddle/curseorb', () => Tick(), 0.01); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SetSelf', 'SetSelf()', (inputData) => SetSelf(inputData), 'new'), + input('Init', 'Init()', () => Init(), 'vmf'), +]; +registerInputAliases('ze_diddle/curseorb', EXTERNAL_INPUT_ALIASES); +installScheduler(); diff --git a/2001/ze_diddle/ze_diddle/diddlebaby.js b/2001/ze_diddle/ze_diddle/diddlebaby.js new file mode 100644 index 0000000..1b911c6 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/diddlebaby.js @@ -0,0 +1,1296 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFire(name, input, value = '', delay = 0, activator = null, caller = null) { + Instance.EntFireAtName({ + name, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function requireSelf(entity, scriptName) { + return requireEntity(entity, `${scriptName} SetSelf`); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function VectorAdd(a, b) { + return Vector3Utils.add(a, b); +} +function VectorScale(vector, scale) { + return Vector3Utils.scale(vector, scale); +} +function TraceLine(start, end, ignoreEntity = null, ignorePlayers = false) { + const trace = Instance.TraceLine({ + start, + end, + ignoreEntity: ignoreEntity ?? undefined, + ignorePlayers, + }); + return trace.fraction; +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function findAllByName(name) { + return Instance.FindEntitiesByName(name); +} +function findAllByClass(classname) { + return Instance.FindEntitiesByClass(classname); +} +function findByNameWithin(name, origin, radius, previous = null) { + return findWithin(findAllByName(name), origin, radius, previous); +} +function findByClassWithin(classname, origin, radius, previous = null) { + return findWithin(findAllByClass(classname), origin, radius, previous); +} +function findByNameNearest(name, origin, radius) { + let nearest = null; + let nearestDistance = radius; + for (const entity of findAllByName(name)) { + if (!isValidEntity(entity)) + continue; + const currentDistance = distance(getOrigin(entity), origin); + if (currentDistance > nearestDistance) + continue; + nearest = entity; + nearestDistance = currentDistance; + } + return nearest; +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function getAngles(entity) { + if (entity == null || !entity.IsValid()) + return angles(); + return angles(entity.GetAbsAngles()); +} +function setOrigin(entity, position) { + if (!isValidEntity(entity) || position == null) + return; + teleportEntity(entity, position, null, null); +} +function setAngles(entity, pitch, yaw, roll) { + if (!isValidEntity(entity) || pitch == null) + return; + const nextAngles = typeof pitch === 'object' + ? angles(pitch) + : angles(pitch, yaw ?? 0, roll ?? 0); + teleportEntity(entity, null, nextAngles, null); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function getTeam(entity) { + if (entity == null || !entity.IsValid()) + return 0; + return entity.GetTeamNumber(); +} +function getClassname(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetClassName(); +} +function getName(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetEntityName(); +} +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} +function randomFloat(min, max) { + return Math.random() * (max - min) + min; +} +const Entities = { + FindByName(previous, name) { + return findAfter(findAllByName(name), previous); + }, + FindByClassname(previous, classname) { + return findAfter(findAllByClass(classname), previous); + }, + FindByNameWithin(previous, name, origin, radius) { + return findByNameWithin(name, origin, radius, previous); + }, + FindByClassnameWithin(previous, classname, origin, radius) { + return findByClassWithin(classname, origin, radius, previous); + }, + FindByNameNearest(name, origin, radius) { + return findByNameNearest(name, origin, radius); + }, +}; +function Vector(x = 0, y = 0, z = 0) { + return vec(x, y, z); +} +const RandomInt = randomInt; +const RandomFloat = randomFloat; +function findWithin(entities, origin, radius, previous) { + let previousFound = previous == null; + for (const entity of entities) { + if (!isValidEntity(entity)) + continue; + if (!previousFound) { + previousFound = entity === previous; + continue; + } + if (entity === previous) + continue; + if (distance(getOrigin(entity), origin) <= radius) + return entity; + } + return null; +} +function findAfter(entities, previous) { + if (previous == null) + return entities[0] ?? null; + const index = entities.findIndex((entity) => entity === previous); + if (index < 0) + return null; + return entities[index + 1] ?? null; +} + +let HP_ADD = 75; +let TARGET_RANGE = 4000; +let SPEED = 5; +let self = null; +let zombie = false; +let model = null; +let sound = ''; +let laser = null; +let ltarget = null; +let spawner = null; +let moving = false; +let particle = null; +function SetSelf(inputData) { + const caller = inputData.caller; + if (caller && caller.IsValid()) { + self = caller; + } +} +function SetEnt(inputData, index) { + const caller = requireEntity(inputData.caller, `SetEnt(${index}) caller`); + if (index == 1) + model = caller; + else if (index == 2) + sound = getName(caller); + else if (index == 3) + laser = caller; + else if (index == 4) + ltarget = caller; + else if (index == 5) + spawner = caller; + else if (index == 6) + particle = caller; +} +function HpAddSet(add) { + HP_ADD = add; +} +function Init() { + const baby = requireSelf(self, 'diddlebaby'); + IsZombieBaby(); + scheduleScript('ze_diddle/diddlebaby', () => PlayBabySound(), RandomFloat(0.0, 15.0)); + scheduleScript('ze_diddle/diddlebaby', () => PrepareLaser(), 2.0); + RunAnimation(); + TickMovement(); + let hp = 100; + let player = null; + while (null != (player = Entities.FindByClassname(player, 'player'))) { + if (getTeam(player) == 3) + hp += HP_ADD; + } + EntFireByHandle(baby, 'SetHealth', String(hp), 0.0, null, null); +} +function IsZombieBaby() { + const baby = requireSelf(self, 'diddlebaby'); + const check = Entities.FindByNameNearest('ITEMX_hich_zmboost_maker2*', getOrigin(baby), 128); + if (isValidEntity(check)) { + EntFireByHandle(model, 'SetScale', '1.2', 0.0, null, null); + EntFireByHandle(model, 'Color', '255 0 0', 0.0, null, null); + zombie = true; + HP_ADD = 50; + TARGET_RANGE = 10000; + SPEED = 8; + } +} +function PlayBabySound() { + EntFire(sound, 'StopSound', '0', 0.0, null); + //EntFire(sound, 'Volume', '0', 0.0, null) + EntFire(sound, 'StartSound', '', 0.03, null); + //EntFire(sound, 'Volume', '10', 0.03, null) + scheduleScript('ze_diddle/diddlebaby', () => PlayBabySound(), zombie ? RandomFloat(5.0, 15.0) : RandomFloat(5.0, 30.0)); +} +function RunAnimation() { + const animation = `idle${RandomInt(1, 9)}`; + EntFireByHandle(model, 'SetAnimationLooping', animation, 0.0, null, null); + //EntFireByHandle(model, 'SetDefaultAnimation', animation, 0.05, null, null) + scheduleScript('ze_diddle/diddlebaby', () => RunAnimation(), RandomFloat(0.1, 20.0)); +} +function HitBaby() { + const animation = `idle${RandomInt(1, 9)}`; + EntFireByHandle(model, 'SetAnimationLooping', animation, 0.0, null, null); + //EntFireByHandle(model, 'SetDefaultAnimation', animation, 0.01, null, null) + EntFireByHandle(particle, 'FireUser1', '', 0.0, null, null); +} +function CheckDiddleCannonProjectile() { + const baby = requireSelf(self, 'diddlebaby'); + let projectile = null; + while (null != + (projectile = Entities.FindByNameWithin(projectile, 'projectile_hurt*', getOrigin(baby), 56))) { + EntFireByHandle(projectile, 'FireUser1', '', 0.0, null, null); + EntFireByHandle(baby, 'RemoveHealth', '300', 0.0, null, null); + } + let elevator = null; + while (null != + (elevator = Entities.FindByNameWithin(elevator, 'finale_firstelev*', getOrigin(baby), 300))) { + EntFireByHandle(baby, 'RemoveHealth', '50000', 0.0, null, null); + } +} +function PrepareLaser() { + scheduleScript('ze_diddle/diddlebaby', () => StartLaser(), zombie ? RandomFloat(0.0, 1.0) : RandomFloat(0.0, 5.0)); +} +function StartLaser() { + const baby = requireSelf(self, 'diddlebaby'); + const laserEntity = requireEntity(laser, 'StartLaser laser'); + const candidates = []; + let player = null; + while (null != (player = Entities.FindByClassname(player, 'player'))) { + if (isValidEntity(player) && + getClassname(player) == 'player' && + getTeam(player) == 3 && + player.GetHealth() > 0 && + distance(getOrigin(player), getOrigin(baby)) <= TARGET_RANGE) { + const playerOrigin = player.GetEyePosition(); + if (TraceLine(playerOrigin, getOrigin(laserEntity), [baby, player]) >= 1.0) { + candidates.push(player); + } + } + } + if (candidates.length <= 0) { + scheduleScript('ze_diddle/diddlebaby', () => StartLaser(), 0.5); + return; + } + const target = candidates[candidates.length == 1 ? 0 : RandomInt(0, candidates.length - 1)]; + if (!isValidEntity(target) || target.GetHealth() <= 0) { + scheduleScript('ze_diddle/diddlebaby', () => StartLaser(), 0.5); + return; + } + const targetOrigin = getOrigin(target); + targetOrigin.z += 48; + setOrigin(ltarget, targetOrigin); + EntFireByHandle(laser, 'Start', '', 0.1, null, null); + EntFireByHandle(laser, 'Stop', '', 3.1, null, null); + EntFireByHandle(spawner, 'Forcespawn', '', 0.1, null, null); + moving = true; + scheduleScript('ze_diddle/diddlebaby', () => { + moving = false; + }, 3.1); + scheduleScript('ze_diddle/diddlebaby', () => PrepareLaser(), 3.11); +} +function TickMovement() { + const baby = requireSelf(self, 'diddlebaby'); + CheckDiddleCannonProjectile(); + if (moving && isValidEntity(ltarget)) { + if (distance(getOrigin(baby), getOrigin(ltarget)) <= SPEED + 2) { + moving = false; + } + else { + const origin = getOrigin(baby); + const targetOrigin = getOrigin(ltarget); + const direction = Vector(targetOrigin.x - origin.x, targetOrigin.y - origin.y, targetOrigin.z - origin.z); + const length = direction.length; + const normal = length > 0.0 ? VectorScale(direction, 1.0 / length) : Vector(); + FaceDirection(normal); + setOrigin(baby, VectorAdd(origin, VectorScale(normal, SPEED))); + setOrigin(particle, getOrigin(baby)); + const babyAngles = getAngles(baby); + setAngles(particle, babyAngles); + } + } + scheduleScript('ze_diddle/diddlebaby', () => TickMovement(), 0.01); +} +function FaceDirection(direction) { + if (!self || !self.IsValid()) + throw new Error('FaceDirection: self is not valid'); + const horizontal = Math.sqrt(direction.x * direction.x + direction.y * direction.y); + const yaw = (Math.atan2(direction.y, direction.x) * 180.0) / Math.PI; + const pitch = (-Math.atan2(direction.z, horizontal) * 180.0) / Math.PI; + setAngles(self, pitch, yaw, 0.0); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SetSelf', 'SetSelf()', (inputData) => SetSelf(inputData), 'new'), + ...Array.from({ length: 6 }, (_, i) => { + const index = i + 1; + return input(`SetEnt_${index}`, `SetEnt(${index})`, (inputData) => SetEnt(inputData, index), 'vmf'); + }), + input('Init', 'Init()', () => Init(), 'vmf'), + input('HitBaby', 'HitBaby()', () => HitBaby(), 'vmf'), + input('HpAddSet_15', 'HP_ADD = 15', () => HpAddSet(15), 'stripper'), +]; +registerInputAliases('ze_diddle/diddlebaby', EXTERNAL_INPUT_ALIASES); +installScheduler(); diff --git a/2001/ze_diddle/ze_diddle/diddlecannonammo.js b/2001/ze_diddle/ze_diddle/diddlecannonammo.js new file mode 100644 index 0000000..00c17de --- /dev/null +++ b/2001/ze_diddle/ze_diddle/diddlecannonammo.js @@ -0,0 +1,1022 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function requireSelf(entity, scriptName) { + return requireEntity(entity, `${scriptName} SetSelf`); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function getClassname(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetClassName(); +} + +let self = null; +let ammo = 300; +let text = null; +let target = null; +function SetSelf(inputData) { + const caller = inputData.caller; + if (caller && caller.IsValid()) { + self = caller; + } +} +function RenderCoinCount() { + const item = requireSelf(self, 'diddlecannonammo'); + if (target != null && target.IsValid()) { + EntFireByHandle(text, 'SetMessage', `AMMO: ${ammo}`, 0.0, target, target); + //EntFireByHandle(text, 'Display', '', 0.02, target, target) + if (getDistance(target, item) > 200) + target = null; + } + scheduleScript('ze_diddle/diddlecannonammo', () => RenderCoinCount(), 0.2); +} +function UseItem() { + ammo--; + if (ammo <= 0) + EntFireByHandle(requireSelf(self, 'diddlecannonammo'), 'FireUser2', '', 0.0, null, null); +} +function SetText(inputData) { + text = requireEntity(inputData.caller, 'SetText caller'); +} +function PickUp(inputData) { + target = requireEntity(inputData.activator, 'PickUp activator'); +} +function getDistance(a, b) { + const aOrigin = getOrigin(a); + const bOrigin = getOrigin(b); + if (getClassname(a) == 'player') + aOrigin.z += 48; + else if (getClassname(b) == 'player') + bOrigin.z += 48; + return distance(aOrigin, bOrigin); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SetSelf', 'SetSelf()', (inputData) => SetSelf(inputData), 'new'), + input('RenderCoinCount', 'RenderCoinCount()', () => RenderCoinCount(), 'vmf'), + input('UseItem', 'UseItem()', () => UseItem(), 'vmf'), + input('SetText', 'SetText()', (inputData) => SetText(inputData), 'vmf'), + input('PickUp', 'PickUp()', (inputData) => PickUp(inputData), 'vmf'), +]; +registerInputAliases('ze_diddle/diddlecannonammo', EXTERNAL_INPUT_ALIASES); +installScheduler(); diff --git a/2001/ze_diddle/ze_diddle/diddledbosshp.js b/2001/ze_diddle/ze_diddle/diddledbosshp.js new file mode 100644 index 0000000..aa62967 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/diddledbosshp.js @@ -0,0 +1,901 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, (data) => callback({ + ...data, + activator: data?.activator, + caller: data?.caller, + })); +} + +//===================================\\ +// Didldedick Boss HP script by Luffaren (STEAM_0:1:22521282) +// CS2 Port - manager.ts style +// ===================================\\ +function input(name, _legacyExpression, handler, source = "vmf", notes = "") { + return { name, handler, source, notes }; +} +// ---------- 核心更新函数 ---------- +function UpdateBossHP(bossName, textName) { + const boss = Instance.FindEntityByName(bossName); + const worldText = Instance.FindEntityByName(textName); + if (!worldText || !worldText.IsValid()) + return; + if (!boss || !boss.IsValid()) { + Instance.EntFireAtTarget({ + target: worldText, + input: "SetMessage", + value: "BOSS: [DEFEATED]" + }); + return; + } + const health = Math.floor(boss.GetHealth()); + Instance.EntFireAtTarget({ + target: worldText, + input: "SetMessage", + value: `BOSS HP: ${health}` + }); +} +// ---------- 处理器函数(每个BOSS一个) ---------- +function handleBoss1() { UpdateBossHP("X69XTurtleBossHP1", "X69XTurtleBossHP1_Hint"); } +function handleBoss2() { UpdateBossHP("X69XTurtleBosshp2", "X69XTurtleBosshp2_Hint"); } +function handleBoss3() { UpdateBossHP("Ord_lvl_01_boss_break", "Ord_lvl_01_boss_break_Hint"); } +function handleBoss4() { UpdateBossHP("Ord_lvl_02_boss_break", "Ord_lvl_02_boss_break_Hint"); } +function handleBoss5() { UpdateBossHP("X69XOrd_main_mid_diglett_break", "X69XOrd_main_mid_diglett_break_Hint"); } +function handleBoss6() { UpdateBossHP("X69XOrd_main_large_diglett_break", "X69XOrd_main_large_diglett_break_Hint"); } +// 如需更多,继续添加 handleBoss6... +// ---------- 外部输入别名列表 ---------- +const EXTERNAL_INPUT_ALIASES = [ + input("DisplayBossHP_1", "DisplayBossHP_1()", handleBoss1, "vmf", "Update HP of Boss 1"), + input("DisplayBossHP_2", "DisplayBossHP_2()", handleBoss2, "vmf", "Update HP of Boss 2"), + input("DisplayBossHP_3", "DisplayBossHP_3()", handleBoss3, "vmf", "Update HP of Boss 3"), + input("DisplayBossHP_4", "DisplayBossHP_4()", handleBoss4, "vmf", "Update HP of Boss 4"), + input("DisplayBossHP_5", "DisplayBossHP_5()", handleBoss5, "vmf", "Update HP of Boss 5"), + input("DisplayBossHP_6", "DisplayBossHP_6()", handleBoss6, "vmf", "Update HP of Boss 6"), +]; +// ---------- 注册函数 ---------- +function registerInputAliases() { + for (const entry of EXTERNAL_INPUT_ALIASES) { + registerFixedScriptInput(entry.name, entry.handler); + } + //Instance.Msg(`[BossHP] Registered ${EXTERNAL_INPUT_ALIASES.length} inputs\n`); +} +// 顶层执行注册 +registerInputAliases(); diff --git a/2001/ze_diddle/ze_diddle/diddledickhealth.js b/2001/ze_diddle/ze_diddle/diddledickhealth.js new file mode 100644 index 0000000..aafcb57 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/diddledickhealth.js @@ -0,0 +1,1135 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFire(name, input, value = '', delay = 0, activator = null, caller = null) { + Instance.EntFireAtName({ + name, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function findAllByName(name) { + return Instance.FindEntitiesByName(name); +} +function findAllByClass(classname) { + return Instance.FindEntitiesByClass(classname); +} +function findByNameWithin(name, origin, radius, previous = null) { + return findWithin(findAllByName(name), origin, radius, previous); +} +function findByClassWithin(classname, origin, radius, previous = null) { + return findWithin(findAllByClass(classname), origin, radius, previous); +} +function findByNameNearest(name, origin, radius) { + let nearest = null; + let nearestDistance = radius; + for (const entity of findAllByName(name)) { + if (!isValidEntity(entity)) + continue; + const currentDistance = distance(getOrigin(entity), origin); + if (currentDistance > nearestDistance) + continue; + nearest = entity; + nearestDistance = currentDistance; + } + return nearest; +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function setOrigin(entity, position) { + if (!isValidEntity(entity) || position == null) + return; + teleportEntity(entity, position, null, null); +} +function setVelocity(entity, velocity) { + if (!isValidEntity(entity) || velocity == null) + return; + teleportEntity(entity, null, null, velocity); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function getTeam(entity) { + if (entity == null || !entity.IsValid()) + return 0; + return entity.GetTeamNumber(); +} +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} +const Entities = { + FindByName(previous, name) { + return findAfter(findAllByName(name), previous); + }, + FindByClassname(previous, classname) { + return findAfter(findAllByClass(classname), previous); + }, + FindByNameWithin(previous, name, origin, radius) { + return findByNameWithin(name, origin, radius, previous); + }, + FindByClassnameWithin(previous, classname, origin, radius) { + return findByClassWithin(classname, origin, radius, previous); + }, + FindByNameNearest(name, origin, radius) { + return findByNameNearest(name, origin, radius); + }, +}; +function Vector(x = 0, y = 0, z = 0) { + return vec(x, y, z); +} +const RandomInt = randomInt; +function printl(message) { + Instance.Msg(`${String(message)}\n`); +} +function findWithin(entities, origin, radius, previous) { + let previousFound = previous == null; + for (const entity of entities) { + if (!isValidEntity(entity)) + continue; + if (!previousFound) { + previousFound = entity === previous; + continue; + } + if (entity === previous) + continue; + if (distance(getOrigin(entity), origin) <= radius) + return entity; + } + return null; +} +function findAfter(entities, previous) { + if (previous == null) + return entities[0] ?? null; + const index = entities.findIndex((entity) => entity === previous); + if (index < 0) + return null; + return entities[index + 1] ?? null; +} + +let self = null; +let maxhp = 10000000.0; +let index = 1; +let ticking = false; +function Initialize() { + // 直接查找名为 dd_hp 的实体 + const hpEntity = Entities.FindByName(null, 'dd_hp'); + if (!hpEntity || !hpEntity.IsValid()) { + printl('[diddledickhealth] Initialize: dd_hp not found or invalid!'); + return; + } + self = hpEntity; + maxhp = self.GetHealth(); + if (!ticking) { + ticking = true; + Tick(); + } +} +function Tick() { + if (!self || !self.IsValid() || self.GetHealth() <= 0) { + ticking = false; + return; + } + const ratio = self.GetHealth() / maxhp; + if (index == 1 && ratio <= 0.75) { + index++; + EntFire('dd_case_2', 'FireUser1', '', 0.0, null); + } + else if (index == 2 && ratio <= 0.5) { + index++; + EntFire('dd_case_3', 'FireUser1', '', 0.0, null); + } + else if (index == 3 && ratio <= 0.25) { + index++; + EntFire('dd_case_4', 'FireUser1', '', 0.0, null); + } + scheduleScript('ze_diddle/diddledickhealth', () => Tick(), 0.01); +} +function SpawnZombie() { + const zombies = []; + let player = null; + while (null != (player = Entities.FindByClassname(player, 'player'))) { + if (player.IsValid() && player.GetHealth() > 0 && getTeam(player) == 2) { + zombies.push(player); + } + } + if (zombies.length === 0) + return; + const zombie = zombies[RandomInt(0, zombies.length - 1)]; + if (!zombie || !zombie.IsValid()) + return; + setOrigin(zombie, Vector(-14592, -14048, 2688)); + EntFireByHandle(zombie, 'SetHealth', '666', 0.0, null, null); + setVelocity(zombie, Vector()); +} +const EXTERNAL_INPUT_ALIASES = [ + input('Initialize', 'Initialize()', () => Initialize(), 'vmf+stripper'), + input('SpawnZombie', 'SpawnZombie()', () => SpawnZombie(), 'vmf+stripper'), +]; +registerInputAliases('ze_diddle/diddledickhealth', EXTERNAL_INPUT_ALIASES); +installScheduler(); diff --git a/2001/ze_diddle/ze_diddle/diddlejihad.js b/2001/ze_diddle/ze_diddle/diddlejihad.js new file mode 100644 index 0000000..72629f0 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/diddlejihad.js @@ -0,0 +1,1074 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function findAllByName(name) { + return Instance.FindEntitiesByName(name); +} +function findAllByClass(classname) { + return Instance.FindEntitiesByClass(classname); +} +function findByNameWithin(name, origin, radius, previous = null) { + return findWithin(findAllByName(name), origin, radius, previous); +} +function findByClassWithin(classname, origin, radius, previous = null) { + return findWithin(findAllByClass(classname), origin, radius, previous); +} +function findByNameNearest(name, origin, radius) { + let nearest = null; + let nearestDistance = radius; + for (const entity of findAllByName(name)) { + if (!isValidEntity(entity)) + continue; + const currentDistance = distance(getOrigin(entity), origin); + if (currentDistance > nearestDistance) + continue; + nearest = entity; + nearestDistance = currentDistance; + } + return nearest; +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +const Entities = { + FindByName(previous, name) { + return findAfter(findAllByName(name), previous); + }, + FindByClassname(previous, classname) { + return findAfter(findAllByClass(classname), previous); + }, + FindByNameWithin(previous, name, origin, radius) { + return findByNameWithin(name, origin, radius, previous); + }, + FindByClassnameWithin(previous, classname, origin, radius) { + return findByClassWithin(classname, origin, radius, previous); + }, + FindByNameNearest(name, origin, radius) { + return findByNameNearest(name, origin, radius); + }, +}; +function findWithin(entities, origin, radius, previous) { + let previousFound = previous == null; + for (const entity of entities) { + if (!isValidEntity(entity)) + continue; + if (!previousFound) { + previousFound = entity === previous; + continue; + } + if (entity === previous) + continue; + if (distance(getOrigin(entity), origin) <= radius) + return entity; + } + return null; +} +function findAfter(entities, previous) { + if (previous == null) + return entities[0] ?? null; + const index = entities.findIndex((entity) => entity === previous); + if (index < 0) + return null; + return entities[index + 1] ?? null; +} + +const SCRIPT_PREFIX = 'ze_diddle/diddlejihad'; +const DELAY = 0.5; // 延迟秒数,可调整 +/** + * 检测当前按钮附近是否有 "NO_JIHAD" 实体 + * 若有则立即触发 FireUser3(取消),若无则启动延迟二次检测 + */ +function CheckJihad(inputData) { + const caller = inputData.caller; + if (!isValidEntity(caller)) { + //Instance.Msg(`[${SCRIPT_PREFIX}] CheckJihad: caller is invalid\n`) + return; + } + const origin = getOrigin(caller); + const noJihad = Entities.FindByNameWithin(null, 'NO_JIHAD', origin, 512); + if (isValidEntity(noJihad)) { + // 第一次检测就发现有 NO_JIHAD,立即禁止 + EntFireByHandle(caller, 'FireUser3', '', 0.0, null, null); + return; + } + // 第一次检测通过,启动延迟二次检测 + //Instance.Msg(`[${SCRIPT_PREFIX}] First check passed, scheduling second check in ${DELAY}s\n`) + scheduleScript(SCRIPT_PREFIX, () => delayedCheck(caller), DELAY); +} +//延迟二次检测:再次检查 NO_JIHAD,若没有则触发爆炸(FireUser4),若有则取消(FireUser3) +function delayedCheck(caller) { + if (!isValidEntity(caller)) { + //Instance.Msg(`[${SCRIPT_PREFIX}] delayedCheck: caller invalid\n`) + return; + } + const origin = getOrigin(caller); + const noJihad = Entities.FindByNameWithin(null, 'NO_JIHAD', origin, 512); + if (isValidEntity(noJihad)) { + // 二次检测发现有 NO_JIHAD,取消爆炸 + //Instance.Msg(`[${SCRIPT_PREFIX}] Second check found NO_JIHAD, cancelling explosion\n`) + EntFireByHandle(caller, 'FireUser3', '', 0.0, null, null); + } + else { + // 二次检测通过,执行爆炸 + //Instance.Msg(`[${SCRIPT_PREFIX}] Second check passed, triggering explosion\n`) + EntFireByHandle(caller, 'FireUser4', '', 0.0, null, null); + } +} +const EXTERNAL_INPUT_ALIASES = [ + input('CheckJihad', 'CheckJihad()', (inputData) => CheckJihad(inputData), 'vmf'), +]; +registerInputAliases(SCRIPT_PREFIX, EXTERNAL_INPUT_ALIASES); +installScheduler(); +////Instance.Msg(`[${SCRIPT_PREFIX}] script loaded with ${EXTERNAL_INPUT_ALIASES.length} external input aliases\n`) diff --git a/2001/ze_diddle/ze_diddle/fetus.js b/2001/ze_diddle/ze_diddle/fetus.js new file mode 100644 index 0000000..0e53be3 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/fetus.js @@ -0,0 +1,1681 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFire(name, input, value = '', delay = 0, activator = null, caller = null) { + Instance.EntFireAtName({ + name, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function VectorAdd(a, b) { + return Vector3Utils.add(a, b); +} +function VectorScale(vector, scale) { + return Vector3Utils.scale(vector, scale); +} +function TraceLine(start, end, ignoreEntity = null, ignorePlayers = false) { + const trace = Instance.TraceLine({ + start, + end, + ignoreEntity: ignoreEntity ?? undefined, + ignorePlayers, + }); + return trace.fraction; +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function findAllByName(name) { + return Instance.FindEntitiesByName(name); +} +function findAllByClass(classname) { + return Instance.FindEntitiesByClass(classname); +} +function findByNameWithin(name, origin, radius, previous = null) { + return findWithin(findAllByName(name), origin, radius, previous); +} +function findByClassWithin(classname, origin, radius, previous = null) { + return findWithin(findAllByClass(classname), origin, radius, previous); +} +function findByNameNearest(name, origin, radius) { + let nearest = null; + let nearestDistance = radius; + for (const entity of findAllByName(name)) { + if (!isValidEntity(entity)) + continue; + const currentDistance = distance(getOrigin(entity), origin); + if (currentDistance > nearestDistance) + continue; + nearest = entity; + nearestDistance = currentDistance; + } + return nearest; +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function getAngles(entity) { + if (entity == null || !entity.IsValid()) + return angles(); + return angles(entity.GetAbsAngles()); +} +function setOrigin(entity, position) { + if (!isValidEntity(entity) || position == null) + return; + teleportEntity(entity, position, null, null); +} +function setAngles(entity, pitch, yaw, roll) { + if (!isValidEntity(entity) || pitch == null) + return; + const nextAngles = typeof pitch === 'object' + ? angles(pitch) + : angles(pitch, 0, 0); + teleportEntity(entity, null, nextAngles, null); +} +function setVelocity(entity, velocity) { + if (!isValidEntity(entity) || velocity == null) + return; + teleportEntity(entity, null, null, velocity); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function getTeam(entity) { + if (entity == null || !entity.IsValid()) + return 0; + return entity.GetTeamNumber(); +} +function getClassname(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetClassName(); +} +function getName(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetEntityName(); +} +function getForwardVector(entity) { + return getAngles(entity).forward; +} +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} +const Entities = { + FindByName(previous, name) { + return findAfter(findAllByName(name), previous); + }, + FindByClassname(previous, classname) { + return findAfter(findAllByClass(classname), previous); + }, + FindByNameWithin(previous, name, origin, radius) { + return findByNameWithin(name, origin, radius, previous); + }, + FindByClassnameWithin(previous, classname, origin, radius) { + return findByClassWithin(classname, origin, radius, previous); + }, + FindByNameNearest(name, origin, radius) { + return findByNameNearest(name, origin, radius); + }, +}; +function Vector(x = 0, y = 0, z = 0) { + return vec(x, y, z); +} +const RandomInt = randomInt; +const sqrt = Math.sqrt; +function findWithin(entities, origin, radius, previous) { + let previousFound = previous == null; + for (const entity of entities) { + if (!isValidEntity(entity)) + continue; + if (!previousFound) { + previousFound = entity === previous; + continue; + } + if (entity === previous) + continue; + if (distance(getOrigin(entity), origin) <= radius) + return entity; + } + return null; +} +function findAfter(entities, previous) { + if (previous == null) + return entities[0] ?? null; + const index = entities.findIndex((entity) => entity === previous); + if (index < 0) + return null; + return entities[index + 1] ?? null; +} + +const BOSS_ENTITY_NAME = 'npc_base_1'; +const SCRIPT_ENTITY_NAME = 'npc_base_1_script'; +const CLIENT_COMMAND_NAME = 'diddle_fetus_clientcommand'; +const SCRIPT_PREFIX = 'ze_diddle/fetus'; +let dead = false; +let autotarget = true; +let drawtarget = false; +let drawdamage = false; +let target = null; +let target_time_reset = 0.0; +let target_time = 0.0; +let targetdistance = 2000; +let tf = null; +let ts = null; +let s_tf = 800; +let s_ts = 75; +let hp = null; +let hpbar = null; +let hpbarmaxhealth = 1000000; +let hpbarhealth = 1000000; +let model = null; +let head = null; +let headparent = null; +let ticking = false; +const damagepoints = []; +const damageactives = []; +const damagedistancebaseds = []; +const damageamounts = []; +const damageradiuses = []; +const damagerefires = []; +const damageplayers = []; +const damageplayersrefire = []; +let halfhp = 0; +let rageinit = false; +let lpos = Vector(); +let shrink = 1.0; +let target_isonright = false; +let target_isonfront = false; +let target_isondeadzone = false; +const target_deadzone = 25.0; +function scheduleInternalScript(callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + scheduleScript(SCRIPT_PREFIX, callback, delay, inputDataOrActivator, nextCaller); +} +function SetEnt(inputData, i) { + const { activator, caller } = inputData; + const entity = requireEntity(caller, `SetEnt(${i}) caller`); + if (i == 1) + tf = entity; + else if (i == 2) + ts = entity; + else if (i == 3) + ; + else if (i == 4) + hp = entity; + else if (i == 5) + model = entity; + else if (i == 6) + hpbar = entity; + else if (i == 7) + getName(entity); + else if (i == 8) + head = entity; + else if (i == 9) + headparent = entity; +} +function SetDamagePoint(inputData, activator_or_caller, damage_amount, enabled, distance_based, damage_radius, damage_refire) { + const { activator, caller } = inputData; + let exists = false; + let eindex = -1; + for (let i = 0; i < damagepoints.length - 1; i++) { + if (damagepoints[i] == activator || damagepoints[i] == caller) { + exists = true; + eindex = i; + break; + } + } + if (exists) { + damageactives[eindex] = enabled; + damagedistancebaseds[eindex] = distance_based; + damageamounts[eindex] = damage_amount; + damageradiuses[eindex] = damage_radius; + damagerefires[eindex] = damage_refire; + } + else { + const point = requireEntity(caller, 'SetDamagePoint caller'); + damagepoints.push(point); + damageactives.push(enabled); + damagedistancebaseds.push(distance_based); + damageamounts.push(damage_amount); + damageradiuses.push(damage_radius); + damagerefires.push(damage_refire); + } +} +function GetDamageBasedOnDistance(center, _target, damageradius, damageamount) { + let totaldamage = 0.0; + const targetdistance = GetDistance(center, _target); + if (targetdistance < damageradius) { + totaldamage = damageamount * (1.0 - targetdistance / damageradius); + } + return totaldamage; +} +function DealDamage(_target, _index) { + if (!_target.IsValid() || _target.GetHealth() <= 0) + return; + let exists = false; + let eindex = -1; + for (let j = 0; j < damageplayers.length; j++) { + if (damageplayers[j] == _target) { + exists = true; + eindex = j; + break; + } + } + if (!exists) { + ApplyDamage(_target, _index); + damageplayers.push(_target); + damageplayersrefire.push(damagerefires[_index]); + PlayClientHurtSound(_target); + return; + } + let hurtindex = -1; + for (let i = 0; i < damageplayers.length; i++) { + if (damageplayers[i] == _target) { + hurtindex = i; + break; + } + } + if (hurtindex >= 0 && damageplayersrefire[hurtindex] <= 0) { + ApplyDamage(_target, _index); + damageplayersrefire[eindex] = damagerefires[_index]; + PlayClientHurtSound(_target); + } +} +function TickDamageRefire() { + for (let i = 0; i < damageplayers.length; i++) { + if (damageplayersrefire[i] > 0) + damageplayersrefire[i] -= 0.03; + } +} +function TickDamageCheck() { + for (let i = 0; i < damagepoints.length; i++) { + const damagepoint = damagepoints[i]; + if (!isValidEntity(damagepoint) || !damageactives[i]) + continue; + const origin = getOrigin(damagepoint); + if (drawdamage) + DrawDamageDebug(damagepoint, damageradiuses[i]); + if (isValidEntity(target) && + getClassname(target) != 'player' && + target.GetHealth() > 0 && + GetDistanceVector(origin, getOrigin(target)) <= damageradiuses[i]) { + DealDamage(target, i); + } + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetTeamNumber() != 3 || player.GetHealth() <= 0) + continue; + const dist = GetDistanceVector(origin, getOrigin(player)); + if (dist <= damageradiuses[i]) { + DealDamage(player, i); + } + } + let wall = null; + while (null != + (wall = Entities.FindByNameWithin(wall, 'fetuswall', origin, damageradiuses[i]))) { + if (getClassname(wall) != 'func_breakable') + continue; + EntFire('s_fetuswalleffect_maker', 'ForceSpawnAtEntityOrigin', '!activator', 0.0, wall); + EntFireByHandle(wall, 'Break', '', 0.01, null, null); + } + } +} +function Start() { + lpos = getOrigin(BossEntity()); + ticking = true; + Tick(); + TickHead(); +} +function TickHealthBar() { + if (!isValidEntity(hp)) { + hpbarhealth = 0; + EntFireByHandle(hpbar, 'setalphascale', '0', 0.0, null, null); + } + else if (isValidEntity(hpbar)) { + hpbarhealth = hp.GetHealth(); + if (hpbarhealth > hpbarmaxhealth * 0.9) { + EntFireByHandle(hpbar, 'setalphascale', '10', 0.0, null, null); + } + if (hpbarhealth <= hpbarmaxhealth * 0.1) { + EntFireByHandle(hpbar, 'setalphascale', '1', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.2) { + EntFireByHandle(hpbar, 'setalphascale', '2', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.3) { + EntFireByHandle(hpbar, 'setalphascale', '3', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.4) { + EntFireByHandle(hpbar, 'setalphascale', '4', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.5) { + EntFireByHandle(hpbar, 'setalphascale', '5', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.6) { + EntFireByHandle(hpbar, 'setalphascale', '6', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.7) { + EntFireByHandle(hpbar, 'setalphascale', '7', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.8) { + EntFireByHandle(hpbar, 'setalphascale', '8', 0.0, null, null); + } + else if (hpbarhealth <= hpbarmaxhealth * 0.9) { + EntFireByHandle(hpbar, 'setalphascale', '9', 0.0, null, null); + } + } +} +function SpawnZombie() { + const players = []; + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetTeamNumber() != 2 || player.GetHealth() <= 0) + continue; + players.push(player); + } + if (players.length <= 0) + return; + const loc = requireEntity(Entities.FindByName(null, 'feb_baby'), 'feb_baby'); + const location = VectorAdd(getOrigin(loc), Vector(0, 0, 32)); + const pickedPlayer = players[RandomInt(0, players.length - 1)]; + setOrigin(pickedPlayer, location); + EntFireByHandle(pickedPlayer, 'SetHealth', '666', 0.0, null, null); + scheduleInternalScript(() => { + setVelocity(pickedPlayer, Vector()); + }, 0.01, pickedPlayer, null); +} +function TickHead() { + const boss = BossEntity(); + if (GetDistanceVector(getOrigin(boss), lpos) < 2) + UnStuck(); + lpos = getOrigin(boss); + if (!rageinit && hpbarhealth <= halfhp) { + rageinit = true; + EntFire('npc_base_1', 'Disablemotion', '', 0.0, null); + EntFire('fb_attack_timer', 'Disable', '', 0.0, null); + EntFire('fb_attack_timer', 'Enable', '', 7.0, null); + EntFire('finale_escapetimer', 'Enable', '', 0.0, null); + EntFire('finale_escapetimer2', 'Enable', '', 0.0, null); + EntFire('fb_soundrage_relay', 'FireUser1', '', 0.0, null); + EntFire('dd_shake_intro', 'StartShake', '', 0.0, null); + EntFire('dd_shake_intro', 'StartShake', '', 3.0, null); + EntFire('dd_shake_intro', 'StartShake', '', 7.0, null); + EntFire('fb_swingrage_relay', 'Enable', '', 0.0, null); + EntFire('fb_swing_relay', 'Disable', '', 0.0, null); + EntFire('fb_sound_relay', 'Disable', '', 0.0, null); + EntFire('fb_sound_timer', 'Disable', '', 0.0, null); + EntFire('fb_soundrage_1', 'StartSound', '', 0.0, null); + EntFire('fb_sound_1', 'StopSound', '', 0.0, null); + EntFire('fb_sound_2', 'StopSound', '', 0.0, null); + EntFire('fb_sound_3', 'StopSound', '', 0.0, null); + EntFire('fb_sound_4', 'StopSound', '', 0.0, null); + EntFire('fb_sound_5', 'StopSound', '', 0.0, null); + EntFire('fb_sound_timer', 'Enable', '', 10.0, null); + EntFire('fb_body', 'SetAnimationNotLooping', 'ragemode', 0.0, null); + EntFire('fb_head', 'SetAnimationNotLooping', 'hurt', 0.0, null); + EntFire('fb_head', 'SetAnimationNotLooping', 'hurt', 3.0, null); + EntFire('fb_head', 'SetAnimationNotLooping', 'hurt', 7.0, null); + EntFire('fb_head', 'SetAnimationNotLooping', 'hurt', 10.0, null); + EntFire('fb_head', 'SetIdleAnimationLooping', 'freak', 4.02, null); + EntFire('fb_body', 'SetIdleAnimationLooping', 'rage_crawl', 0.02, null); + EntFire('i_npc_tf_1', 'KeyValues', 'force 1000', 0.0, null); + EntFire('i_npc_tf_1', 'Deactivate', '', 0.05, null); + EntFire('i_npc_ts_1', 'Deactivate', '', 0.05, null); + EntFire('npc_base_1', 'Enablemotion', '', 4.0, null); + EntFire('feb_timer_zombie', 'Disable', '', 0.0, null); + EntFire('feb_timer_baby', 'Disable', '', 0.0, null); + EntFire('i_npc_tf_1', 'Dectivate', '', 5.02, null); + EntFire('i_npc_ts_1', 'Dectivate', '', 5.02, null); + EntFire('i_npc_tf_1', 'Activate', '', 5.05, null); + EntFire('i_npc_ts_1', 'Activate', '', 5.05, null); + } + if (isValidEntity(head) && isValidEntity(headparent)) { + setOrigin(head, getOrigin(headparent)); + if (isValidEntity(target)) { + setAngles(head, Vector3Utils.lookAt(getOrigin(head), getOrigin(target))); + } + if (!dead) { + scheduleInternalScript(() => { + TickHead(); + }, 0.01, null, null); + } + } +} +function Tick() { + if (!ticking) + return; + if (dead) { + if (shrink <= 0.05) + ticking = false; + else + shrink = 0.9975 * shrink; + EntFire('fb_body', 'SetScale', String(shrink), 0.0, null); + EntFire('fb_head', 'SetScale', String(shrink), 0.0, null); + EntFire('fb_head', 'SetParentAttachment', 'head1', 0.0, null); + } + else if (!isValidEntity(target) || + target_time <= 0 || + getTeam(target) == 2 || + target.GetHealth() <= 0 || + GetDistance(BossEntity(), target) > targetdistance) { + EntFireByHandle(tf, 'Deactivate', '', 0.0, null, null); + EntFireByHandle(ts, 'Deactivate', '', 0.0, null, null); + target_time = target_time_reset; + if (autotarget) + SearchTarget(); + TickDamageRefire(); + TickDamageCheck(); + } + else { + TraceCheck(); + UpdateDirectionCheck(); + Move(); + TickDamageRefire(); + TickDamageCheck(); + target_time -= 0.03; + if (drawtarget && isValidEntity(hp)) { + const targetPosition = getOrigin(target); + if (getClassname(target) == 'player') + targetPosition.z += 48; + DebugDrawLine(targetPosition, getOrigin(hp), 255, 255, 0, 0.04); + } + } + scheduleInternalScript(() => { + Tick(); + }, 0.02, null, null); +} +function TraceCheck() { + const boss = BossEntity(); + const start = getOrigin(boss); + const end = VectorAdd(start, VectorScale(getForwardVector(boss), 50)); + const fraction = TraceLine(start, end, boss); + if (fraction < 1.0) { + //Instance.Msg(`[Fetus] TraceCheck collision detected! fraction=${fraction}, boss pos=${start}, forward=${getForwardVector(boss)}`) + EntFireByHandle(tf, 'Deactivate', '', 0.0, null, null); + EntFireByHandle(tf, 'KeyValues', 'angles 0 180 0', 0.0, null, null); + EntFireByHandle(tf, 'KeyValues', 'Force 10000', 0.0, null, null); + EntFireByHandle(tf, 'Activate', '', 0.02, null, null); + EntFireByHandle(tf, 'KeyValues', 'Force ' + String(s_tf), 0.1, null, null); + EntFireByHandle(tf, 'KeyValues', 'angles 0 0 0', 0.1, null, null); + EntFireByHandle(tf, 'Deactivate', '', 0.1, null, null); + } +} +function UpdateDirectionCheck() { + const currentTarget = requireEntity(target, 'UpdateDirectionCheck target'); + const boss = BossEntity(); + const p = Vector3Utils.subtract(getOrigin(currentTarget), getOrigin(boss)); + const distance2D = GetDistanceXY(boss, currentTarget); + // 近距离保护:当目标很近时,停止转向,避免疯狂旋转 + const NEAR_THRESHOLD = 100.0; + if (distance2D < NEAR_THRESHOLD) { + target_isonright = false; + target_isonfront = true; + target_isondeadzone = true; + //Instance.Msg(`[Fetus] UpdateDirection: near target, stop turning\n`) + return; + } + const up = getAngles(boss).up; + const forward = getAngles(boss).forward; + // 左右判断(原 Nut:forward 叉积 p,点乘 up) + let a = Vector3Utils.cross(forward, p); + let angle = Vector3Utils.dot(a, up) / (distance2D / 2); + target_isonright = angle < 0; + target_isondeadzone = (angle < target_deadzone / 100 && angle > -0.25); + // 前后判断:点积 + const dot = Vector3Utils.dot(forward, p); + target_isonfront = dot > 0; + //Instance.Msg(`[Fetus] UpdateDirection: dot=${dot.toFixed(2)}, isonfront=${target_isonfront}, isonright=${target_isonright}, isdeadzone=${target_isondeadzone}\n`) +} +function Move() { + if (target_isondeadzone && target_isonfront) { + EntFireByHandle(ts, 'Deactivate', '', 0.0, null, null); + EntFireByHandle(tf, 'Activate', '', 0.01, null, null); + } + else if (target_isonright) { + EntFireByHandle(ts, 'Deactivate', '', 0.0, null, null); + EntFireByHandle(ts, 'KeyValues', 'angles 0 270 0', 0.0, null, null); + EntFireByHandle(ts, 'Activate', '', 0.01, null, null); + EntFireByHandle(tf, 'Activate', '', 0.01, null, null); + } + else { + EntFireByHandle(ts, 'Deactivate', '', 0.0, null, null); + EntFireByHandle(ts, 'KeyValues', 'angles 0 90 0', 0.0, null, null); + EntFireByHandle(ts, 'Activate', '', 0.01, null, null); + EntFireByHandle(tf, 'Activate', '', 0.01, null, null); + } + if (!target_isonfront) { + UnStuck(); + EntFireByHandle(tf, 'Activate', '', 0.01, null, null); + } +} +function UnStuck() { + EntFire('i_npc_ts_1', 'KeyValues', 'force 1700', 0.0, null); + EntFire('i_npc_ts_1', 'KeyValues', 'force ' + String(s_ts), 0.05, null); +} +function SearchTarget() { + const players = []; + const bossPos = getOrigin(BossEntity()); + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetTeamNumber() != 3 || player.GetHealth() <= 0) + continue; + const dist = GetDistanceVector(bossPos, getOrigin(player)); + if (dist <= 20000) { + players.push(player); + } + } + if (players.length == 1) + target = players[0]; + else if (players.length > 1) + target = players[RandomInt(0, players.length - 1)]; +} +function SetHealth(base_hp, foreachplayer_hpadd) { + if (!isValidEntity(hp)) + return; + let hpadd = 0; + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetTeamNumber() != 3 || player.GetHealth() <= 0) + continue; + hpadd += foreachplayer_hpadd; + } + const health = base_hp + hpadd; + EntFireByHandle(hp, 'SetHealth', String(health), 0.0, null, null); + hpbarmaxhealth = health; + hpbarhealth = health; + scheduleInternalScript(() => { + TickHealthBar(); + }, 0.01, null, null); + EntFireByHandle(hpbar, 'FireUser2', '', 0.02, null, null); + halfhp = health * 0.3; +} +function SetSpeed(forward, turning) { + s_tf = forward; + s_ts = turning; + EntFireByHandle(tf, 'Deactivate', '', 0.0, null, null); + EntFireByHandle(ts, 'Deactivate', '', 0.0, null, null); + EntFireByHandle(tf, 'KeyValues', 'Force ' + String(forward), 0.0, null, null); + EntFireByHandle(ts, 'KeyValues', 'Force ' + String(turning), 0.0, null, null); +} +function SetTargetTime(time) { + target_time_reset = time; +} +function SetTargetMethod(target_distance, _target_closest, _target_closest_timereset, _attack_or_flee) { + targetdistance = target_distance; +} +function SetAutoTarget(state) { + autotarget = state; +} +function SetDrawTarget(state) { + drawtarget = state; +} +function SetDrawDamage(state) { + drawdamage = state; +} +function Death() { + dead = true; + EntFireByHandle(ts, 'FireUser4', '', 0.0, null, null); + EntFireByHandle(tf, 'FireUser4', '', 0.0, null, null); + EntFireByHandle(model, 'FireUser4', '', 0.0, null, null); + EntFire('fb_soundrage_case', 'Pickrandom', '', 0.0, null); + EntFire('fb_sound_timer', 'Disable', '', 0.0, null); + EntFire('fb_sound_timer', 'Kill', '', 0.05, null); + EntFire('fb_attack_timer', 'Disable', '', 0.0, null); + EntFire('fb_attack_timer', 'Kill', '', 0.05, null); + if (isValidEntity(hpbar)) { + EntFireByHandle(hpbar, 'FireUser3', '', 0.25, null, null); + } +} +function GetDistance(vector1, vector2) { + const origin1 = getOrigin(vector1); + const origin2 = getOrigin(vector2); + let z1 = origin1.z; + let z2 = origin2.z; + if (getClassname(vector1) == 'player') + z1 += 48; + else if (getClassname(vector2) == 'player') + z2 += 48; + return sqrt((origin1.x - origin2.x) * (origin1.x - origin2.x) + + (origin1.y - origin2.y) * (origin1.y - origin2.y) + + (z1 - z2) * (z1 - z2)); +} +function GetDistanceVector(vector1, vector2) { + return sqrt((vector1.x - vector2.x) * (vector1.x - vector2.x) + + (vector1.y - vector2.y) * (vector1.y - vector2.y) + + (vector1.z - vector2.z) * (vector1.z - vector2.z)); +} +function GetDistanceXY(vector1, vector2) { + const origin1 = getOrigin(vector1); + const origin2 = getOrigin(vector2); + return sqrt((origin1.x - origin2.x) * (origin1.x - origin2.x) + + (origin1.y - origin2.y) * (origin1.y - origin2.y)); +} +function ApplyDamage(targetEntity, index) { + const amount = damagedistancebaseds[index] + ? GetDamageBasedOnDistance(damagepoints[index], targetEntity, damageradiuses[index], damageamounts[index]) + : damageamounts[index]; + EntFireByHandle(targetEntity, 'SetHealth', String(targetEntity.GetHealth() - amount), 0.0, null, null); +} +function PlayClientHurtSound(targetEntity) { + if (getClassname(targetEntity) != 'player') + return; + const clientCmd = Entities.FindByName(null, CLIENT_COMMAND_NAME); + if (!isValidEntity(clientCmd)) { + // 实体不存在时静默返回,不播放音效 + return; + } + //EntFireByHandle(clientCmd,'Command','play *luffaren/clienthurt.mp3',0.0,targetEntity,targetEntity); +} +function BossEntity() { + return requireEntity(Entities.FindByName(null, BOSS_ENTITY_NAME), BOSS_ENTITY_NAME); +} +function DrawDamageDebug(entity, radius) { + const origin = getOrigin(entity); + const entityAngles = getAngles(entity); + DrawAxis(origin, entityAngles.forward, radius, 255, 0, 0); + DrawAxis(origin, entityAngles.left, radius, 255, 0, 0); + DrawAxis(origin, entityAngles.up, radius, 255, 0, 0); +} +function DrawAxis(origin, axis, radius, r, g, b) { + DebugDrawLine(VectorAdd(origin, VectorScale(axis, -radius)), VectorAdd(origin, VectorScale(axis, radius)), r, g, b, 0.04); +} +function DebugDrawLine(start, end, r, g, b, duration) { + Instance.DebugLine(start, end, duration, { r, g, b }); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SpawnZombie', 'SpawnZombie()', () => SpawnZombie(), 'vmf+stripper'), + input('Start', 'Start()', () => Start(), 'vmf'), + ...[1, 2, 3, 4, 6, 7, 8, 9].map((index) => input(`SetEnt_${index}`, `SetEnt(${index})`, (inputData) => SetEnt(inputData, index), 'vmf')), + input('SetDamagePoint_2_15_T_F_128_0_75', 'SetDamagePoint(2,15,true,false,128,0.75)', (inputData) => SetDamagePoint(inputData, 2, 15, true, false, 128, 0.75), 'vmf'), + input('SetDamagePoint_2_80_T_F_92_0_5', 'SetDamagePoint(2,80,true,false,92,0.5)', (inputData) => SetDamagePoint(inputData, 2, 80, true, false, 92, 0.5), 'vmf'), + input('SetDamagePoint_2_100_T_F_160_0_5', 'SetDamagePoint(2,100,true,false,160,0.5)', (inputData) => SetDamagePoint(inputData, 2, 100, true, false, 160, 0.5), 'vmf'), + input('Death', 'Death()', () => Death(), 'vmf'), + input('TickHealthBar', 'TickHealthBar()', () => TickHealthBar(), 'vmf'), + input('SetDrawTargetFalse', 'SetDrawTarget(false)', () => SetDrawTarget(false), 'vmf'), + input('SetAutoTargetTrue', 'SetAutoTarget(true)', () => SetAutoTarget(true), 'vmf'), + input('SetTargetMethod_10000_False_2_1', 'SetTargetMethod(10000,false,2,1)', () => SetTargetMethod(10000), 'vmf'), + input('SetTargetTime_10', 'SetTargetTime(10.00)', () => SetTargetTime(10.0), 'vmf'), + input('SetSpeed_800_80', 'SetSpeed(800,80)', () => SetSpeed(800, 80), 'vmf'), + input('SetHealth_3000_12000', 'SetHealth(3000,12000)', () => SetHealth(3000, 12000), 'vmf'), + input('SetDrawDamageFalse', 'SetDrawDamage(false)', () => SetDrawDamage(false), 'vmf'), +]; +installScheduler(); +registerInputAliases(SCRIPT_PREFIX, EXTERNAL_INPUT_ALIASES); +Instance.Msg('[ze_diddle] fetus script by luffaren, ported by Kxnrl with codex.'); +Instance.Msg('[ze_diddle] fetus script loaded with ' + + EXTERNAL_INPUT_ALIASES.length + + ' external input aliases for ' + + SCRIPT_ENTITY_NAME); diff --git a/2001/ze_diddle/ze_diddle/filter.js b/2001/ze_diddle/ze_diddle/filter.js new file mode 100644 index 0000000..49d105d --- /dev/null +++ b/2001/ze_diddle/ze_diddle/filter.js @@ -0,0 +1,64 @@ +import { Instance } from 'cs_script/point_script'; + +//===================================\\ +// Filter script by Luffaren (STEAM_0:1:22521282) +// CS2 Port - Simplified for SetFilterOwner(1) and FilterCheck(1,1) +// ===================================\\ +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, (data) => callback({ + ...data, + activator: data?.activator, + caller: data?.caller, + })); +} +function input(name, _legacyExpression, handler, source = "vmf", notes = "") { + return { name, handler, source, notes }; +} +// ---------- 脚本状态 ---------- +let owner = null; +// ---------- 处理器函数 ---------- +// 设置所有者:捡起神器时调用,activator 为玩家 +function handleSetFilterOwner(context) { + const activator = context.activator; + if (activator && activator.IsValid()) { + owner = activator; + //Instance.Msg(`[Filter] Owner set to ${activator.GetEntityName()}\n`); + } +} +// 检查并触发:按下按钮时调用,activator 为玩家,caller 为按钮 +function handleFilterCheck(context) { + const activator = context.activator; + const caller = context.caller; + if (!owner) { + //Instance.Msg("[Filter] FilterCheck: No owner set\n"); + return; + } + if (!activator || !activator.IsValid()) { + //Instance.Msg("[Filter] FilterCheck: Invalid activator\n"); + return; + } + if (!caller || !caller.IsValid()) { + //Instance.Msg("[Filter] FilterCheck: Invalid caller\n"); + return; + } + if (owner === activator) { + // 匹配,对 caller(按钮)触发 FireUser1,按钮的 OnUser1 将执行地图逻辑 + Instance.EntFireAtTarget({ target: caller, input: "FireUser1", activator: activator }); + //Instance.Msg(`[Filter] FilterCheck passed, fired FireUser1 on ${caller.GetEntityName()}\n`); + } +} +// ---------- 注册输入别名 ---------- +const EXTERNAL_INPUT_ALIASES = [ + input("SetFilterOwner", "SetFilterOwner()", handleSetFilterOwner, "vmf", "Set owner to activator"), + input("FilterCheck", "FilterCheck()", handleFilterCheck, "vmf", "Check if activator is owner, then FireUser1 on caller"), +]; +function registerInputAliases() { + for (const entry of EXTERNAL_INPUT_ALIASES) { + registerFixedScriptInput(entry.name, entry.handler); + } + //Instance.Msg(`[Filter] Registered ${EXTERNAL_INPUT_ALIASES.length} inputs\n`); +} +registerInputAliases(); diff --git a/2001/ze_diddle/ze_diddle/itemcontroller.js b/2001/ze_diddle/ze_diddle/itemcontroller.js new file mode 100644 index 0000000..3e44d22 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/itemcontroller.js @@ -0,0 +1,1229 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function requireSelf(entity, scriptName) { + return requireEntity(entity, `${scriptName} SetSelf`); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function getAngles(entity) { + if (entity == null || !entity.IsValid()) + return angles(); + return angles(entity.GetAbsAngles()); +} +function setOrigin(entity, position) { + if (!isValidEntity(entity) || position == null) + return; + teleportEntity(entity, position, null, null); +} +function setAngles(entity, pitch, yaw, roll) { + if (!isValidEntity(entity) || pitch == null) + return; + const nextAngles = typeof pitch === 'object' + ? angles(pitch) + : angles(pitch, 0, 0); + teleportEntity(entity, null, nextAngles, null); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function getClassname(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetClassName(); +} +function forceSpawnTemplateEntity(templateEntity, context, position = null, rotation = null) { + return forceSpawnResolvedTemplate(requireEntity(templateEntity, `${context} point_template`), context, position, rotation); +} +function forceSpawnResolvedTemplate(template, label, position, rotation) { + const classname = template.GetClassName(); + if (classname !== 'point_template') { + throw new Error(`[ze_diddle] '${label}' must be point_template, got '${classname}'`); + } + const spawned = template.ForceSpawn(position == null ? undefined : vec(position), rotation == null ? undefined : angles(rotation)); + if (spawned == null) { + throw new Error(`[ze_diddle] ForceSpawn failed for '${label}'`); + } + const spawnedEntities = spawned.filter(isNotNull); + if (spawnedEntities.length <= 0) { + throw new Error(`[ze_diddle] ForceSpawn returned no entities for '${label}'`); + } + return spawnedEntities; +} +function isNotNull(value) { + return value != null; +} + +let self = null; +let player = null; +let distcheck = null; +let backup = null; +let base = null; +let pistol = null; +let killed = false; +let cooldown = 0.0; +let initwindow = true; +let ticking = false; +let tickingbackup = false; +let tickingdropped = false; +let tickingcooldown = false; +const BACKUP_COOLDOWN_MAX = 0.5; +const BACKUP_COOLDOWN_STEP = 0.02; +function SetSelf(inputData) { + const caller = inputData.caller; + if (caller && caller.IsValid()) { + self = caller; + } +} +function SetDistcheck(inputData) { + distcheck = requireEntity(inputData.caller, 'SetDistcheck caller'); +} +function SetBackup(inputData) { + backup = requireEntity(inputData.caller, 'SetBackup caller'); +} +function SetBase(inputData) { + base = requireEntity(inputData.caller, 'SetBase caller'); +} +function SetPistol(inputData) { + pistol = requireEntity(inputData.caller, 'SetPistol caller'); +} +function KillItem() { + if (!killed) { + EntFireByHandle(pistol, 'Kill', '', 0.0, null, null); + EntFireByHandle(base, 'Kill', '', 0.0, null, null); + EntFireByHandle(distcheck, 'Kill', '', 0.0, null, null); + killBackupBinding(0.0); + EntFireByHandle(self, 'Kill', '', 0.0, null, null); + } + killed = true; +} +function DoNotRespawn() { + killed = true; +} +function UseItem(cd) { + cooldown = cd; + if (!tickingcooldown) + TickCooldown(); +} +function PickUp(inputData) { + player = requireEntity(inputData.activator, 'PickUp activator'); + initwindow = false; + tickingdropped = false; + if (!ticking) { + ticking = true; + Tick(); + } + if (!tickingbackup) { + tickingbackup = true; + TickBackup(); + } +} +function Drop() { + ticking = false; + if (CheckNullValidity()) { + EntFireByHandle(base, 'ClearParent', '', 0.0, null, null); + tickingdropped = true; + TickDropped(); + } + else { + RunBackup(); + } +} +function CloseInitWindow() { + EntFireByHandle(pistol, 'KeyValues', 'CanBePickedUp 1', 0.0, null, null); + initwindow = false; +} +function InitBackUp(cd) { + if (initwindow) { + initwindow = false; + cooldown = cd; + if (!tickingcooldown) + TickCooldown(); + EntFireByHandle(self, 'Disable', '', 0.0, null, null); + } +} +function RunBackup() { + if (!killed) { + const currentSelf = requireSelf(self, 'itemcontroller'); + const backupTemplate = requireEntity(backup, 'RunBackup backup'); + const controllerClass = getClassname(currentSelf); + const initInput = getInitBackupInputName(cooldown); + killed = true; + EntFireByHandle(base, 'Kill', '', 0.0, null, null); + EntFireByHandle(distcheck, 'Kill', '', 0.0, null, null); + scheduleScript('ze_diddle/itemcontroller', () => { + const spawned = forceSpawnTemplateEntity(backupTemplate, 'itemcontroller SetBackup caller', getOrigin(backupTemplate)); + const spawnedController = requireEntity(spawned.find((entity) => isValidEntity(entity) && getClassname(entity) == controllerClass) ?? null, 'RunBackup spawned controller'); + EntFireByHandle(spawnedController, 'RunScriptInput', initInput, 0.18); + }, 0.02); + killBackupBinding(0.1); + EntFireByHandle(self, 'Kill', '', 0.1, null, null); + } + else { + EntFireByHandle(base, 'Kill', '', 0.0, null, null); + EntFireByHandle(distcheck, 'Kill', '', 0.0, null, null); + } +} +function TickDropped() { + if (CheckNullValidity()) { + if (tickingdropped) { + setOrigin(base, getOrigin(distcheck)); + setAngles(base, getAngles(distcheck)); + scheduleScript('ze_diddle/itemcontroller', () => TickDropped(), 0.01); + } + } + else { + RunBackup(); + } +} +function TickBackup() { + if (CheckNullValidity()) { + if (base != null && base.IsValid()) { + setOrigin(backup, getOrigin(base)); + scheduleScript('ze_diddle/itemcontroller', () => TickBackup(), 0.01); + } + } +} +function TickCooldown() { + if (cooldown <= 0) { + cooldown = 0; + tickingcooldown = false; + EntFireByHandle(self, 'FireUser1', '', 0.0, null, null); + } + else { + cooldown -= 0.02; + scheduleScript('ze_diddle/itemcontroller', () => TickCooldown(), 0.01); + } +} +function Tick() { + if (CheckNullValidity()) { + const dist = distance(getOrigin(player), getOrigin(distcheck)); + if (player == null || player.GetHealth() <= 0 || dist > 100) { + Drop(); + } + else { + scheduleScript('ze_diddle/itemcontroller', () => Tick(), 0.05); + } + } + else { + RunBackup(); + } +} +function CheckNullValidity() { + return (player != null && + player.IsValid() && + distcheck != null && + distcheck.IsValid() && + base != null && + base.IsValid()); +} +function killBackupBinding(delay) { + if (getClassname(backup) == 'point_template') + return; + EntFireByHandle(backup, 'Kill', '', delay, null, null); +} +function getInitBackupInputName(value) { + const normalized = normalizeBackupCooldown(value); + const hundredths = Math.round(normalized * 100); + const whole = Math.floor(hundredths / 100); + const fraction = String(hundredths % 100).padStart(2, '0'); + return `InitBackUp_${whole}_${fraction}`; +} +function normalizeBackupCooldown(value) { + if (value <= 0) + return 0; + const rounded = Math.round(value / BACKUP_COOLDOWN_STEP) * BACKUP_COOLDOWN_STEP; + return Math.min(BACKUP_COOLDOWN_MAX, Math.max(0, rounded)); +} +function createInitBackUpInputs() { + const count = Math.round(BACKUP_COOLDOWN_MAX / BACKUP_COOLDOWN_STEP); + return Array.from({ length: count + 1 }, (_, index) => { + const value = Number((index * BACKUP_COOLDOWN_STEP).toFixed(2)); + return input(getInitBackupInputName(value), `InitBackUp(${value.toFixed(2)})`, () => InitBackUp(value), 'runtime'); + }); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SetSelf', 'SetSelf()', (inputData) => SetSelf(inputData), 'new'), + input('SetDistcheck', 'SetDistcheck()', (inputData) => SetDistcheck(inputData), 'vmf'), + input('SetBackup', 'SetBackup()', (inputData) => SetBackup(inputData), 'vmf'), + input('SetBase', 'SetBase()', (inputData) => SetBase(inputData), 'vmf'), + input('SetPistol', 'SetPistol()', (inputData) => SetPistol(inputData), 'vmf'), + input('KillItem', 'KillItem()', () => KillItem(), 'vmf+stripper'), + input('DoNotRespawn', 'DoNotRespawn()', () => DoNotRespawn(), 'vmf'), + input('UseItem_0_5', 'UseItem(0.5)', () => UseItem(0.5), 'vmf'), + input('PickUp', 'PickUp()', (inputData) => PickUp(inputData), 'vmf'), + input('Drop', 'Drop()', () => Drop(), 'vmf+stripper'), + input('CloseInitWindow', 'CloseInitWindow()', () => CloseInitWindow(), 'vmf'), + ...createInitBackUpInputs(), +]; +registerInputAliases('ze_diddle/itemcontroller', EXTERNAL_INPUT_ALIASES); +installScheduler(); diff --git a/2001/ze_diddle/ze_diddle/laser.js b/2001/ze_diddle/ze_diddle/laser.js new file mode 100644 index 0000000..405d196 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/laser.js @@ -0,0 +1,1023 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function getClassname(entity) { + if (entity == null || !entity.IsValid()) + return ''; + return entity.GetClassName(); +} + +let fade = false; +let fadevalue = 255; +let ticking = false; +let FADE_TIME = 1.0; +let visual = null; +function Initialize(inputData) { + visual = requireEntity(inputData.caller, 'Initialize caller'); +} +function Hurt(inputData) { + const activator = inputData.activator; + if (activator != null && + activator.IsValid() && + getClassname(activator) == 'player') { + EntFireByHandle(activator, 'SetHealth', String(Math.trunc(activator.GetHealth() - fadevalue / 25)), 0.0, null, null); + } +} +function FadeIn(time) { + FADE_TIME = Math.max(time, 0.0); + fade = false; + if (!ticking) { + //EntFireByHandle(visual, 'KeyValues', 'rendermode 1', 0.0, null, null) + ticking = true; + Tick(); + } +} +function FadeOut(time) { + FADE_TIME = Math.max(time, 0.0); + fade = true; + if (!ticking) { + //EntFireByHandle(visual, 'KeyValues', 'rendermode 1', 0.0, null, null) + ticking = true; + Tick(); + } +} +function Tick() { + const step = FADE_TIME <= 0 ? 255 : 255 / (50.0 * FADE_TIME); + if (fade) { + if (fadevalue > 0) { + fadevalue -= step; + scheduleScript('my-lib/build/laser', () => Tick(), 0.01); + } + else { + fadevalue = 0; + ticking = false; + } + } + else if (fadevalue < 255) { + fadevalue += step; + scheduleScript('my-lib/build/laser', () => Tick(), 0.01); + } + else { + fadevalue = 255; + ticking = false; + } + EntFireByHandle(visual, 'Alpha', String(fadevalue), 0.0, null, null); +} +const EXTERNAL_INPUT_ALIASES = [ + input('Initialize', 'Initialize()', (inputData) => Initialize(inputData), 'vmf'), + input('Hurt', 'Hurt()', (inputData) => Hurt(inputData), 'vmf'), + input('FadeIn_3_0', 'FadeIn(3.0)', () => FadeIn(3.0), 'vmf'), + input('FadeIn_0_1', 'FadeIn(0.1)', () => FadeIn(0.1), 'vmf'), + input('FadeOut_1_0', 'FadeOut(1.0)', () => FadeOut(1.0), 'vmf'), + input('FadeOut_1_5', 'FadeOut(1.5)', () => FadeOut(1.5), 'vmf'), +]; +registerInputAliases('ze_diddle/laser', EXTERNAL_INPUT_ALIASES); +installScheduler(); diff --git a/2001/ze_diddle/ze_diddle/omahamortar.js b/2001/ze_diddle/ze_diddle/omahamortar.js new file mode 100644 index 0000000..7c1e970 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/omahamortar.js @@ -0,0 +1,999 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function forceSpawnTemplate(templateName, position = null, rotation = null) { + const template = Instance.FindEntityByName(templateName); + if (template == null || !template.IsValid()) { + throw new Error(`[ze_diddle] missing point_template '${templateName}'`); + } + const classname = template.GetClassName(); + if (classname !== 'point_template') { + throw new Error(`[ze_diddle] '${templateName}' must be point_template, got '${classname}'`); + } + const spawned = template.ForceSpawn(position == null ? undefined : vec(position), rotation == null ? undefined : angles(rotation)); + if (spawned == null) { + throw new Error(`[ze_diddle] ForceSpawn failed for '${templateName}'`); + } + const spawnedEntities = spawned.filter(isNotNull); + if (spawnedEntities.length <= 0) { + throw new Error(`[ze_diddle] ForceSpawn returned no entities for '${templateName}'`); + } + return spawnedEntities; +} +function isNotNull(value) { + return value != null; +} + +//===================================\\ +// Omaha Mortar script by Luffaren (STEAM_0:1:22521282) +// CS2 Port - manager.ts style, fixed inputs, SetNextThink driven +// ===================================\\ +function input(name, _legacyExpression, handler, source = "vmf", notes = "") { + return { name, handler, source, notes }; +} +// ---------- 每个实例独立状态 ---------- +let amount = 0; +let minrate = 0.00; +let maxrate = 0.00; +let mortarOrigin = { x: 0, y: 0, z: 0 }; +let isActive = false; +let nextFireTime = 0; +// ---------- 核心函数 ---------- +function SpawnBarrage(_amount, _minrate, _maxrate, caller) { + // 重置状态 + amount = _amount; + minrate = _minrate; + maxrate = _maxrate; + if (caller && caller.IsValid()) { + mortarOrigin = caller.GetAbsOrigin(); + } + else { + const center = Instance.FindEntityByName("mortar_center"); + if (center && center.IsValid()) { + mortarOrigin = center.GetAbsOrigin(); + } + else { + mortarOrigin = { x: -12503.8, y: -2848, z: 13844 }; + } + } + // 如果已经激活,先停止 + if (isActive) { + isActive = false; + Instance.SetThink(() => { }); // 清空 Think + } + // 如果有炮弹要发射,启动循环 + if (amount > 0) { + isActive = true; + // 立即发射第一发 + SpawnMortar(); + // 如果还有剩余,安排下一次 + if (amount > 0) { + const rand = Math.random() * (maxrate - minrate) + minrate; + nextFireTime = Instance.GetGameTime() + rand; + Instance.SetThink(onThink); + Instance.SetNextThink(nextFireTime); + } + else { + isActive = false; + } + } +} +function SpawnMortar() { + if (amount <= 0) + return; + const x = Math.floor(mortarOrigin.x - 1312 + Math.random() * 2624); + const y = Math.floor(mortarOrigin.y - 1312 + Math.random() * 2624); + const z = mortarOrigin.z; + const pos = { x, y, z }; + const zeroAngles = { pitch: 0, yaw: 0, roll: 0 }; + try { + forceSpawnTemplate("s_mortar", pos, zeroAngles); + } + catch (e) { + // 静默失败 + } + amount--; +} +function onThink() { + if (!isActive || amount <= 0) { + isActive = false; + return; + } + const currentTime = Instance.GetGameTime(); + if (currentTime >= nextFireTime) { + SpawnMortar(); + if (amount > 0) { + const rand = Math.random() * (maxrate - minrate) + minrate; + nextFireTime = currentTime + rand; + } + else { + isActive = false; + return; + } + } + // 继续下一帧检查 + Instance.SetNextThink(Instance.GetGameTime() + 0.01); +} +// ---------- 5 个处理器 ---------- +function handleSpawnBarrage1(context) { + SpawnBarrage(50, 0.5, 2.0, context.caller); +} +function handleSpawnBarrage2(context) { + SpawnBarrage(30, 0.2, 1.5, context.caller); +} +function handleSpawnBarrage3(context) { + SpawnBarrage(20, 1.0, 3.0, context.caller); +} +function handleSpawnBarrage4(context) { + SpawnBarrage(50, 1.0, 3.0, context.caller); +} +function handleSpawnBarrage5(context) { + SpawnBarrage(100, 0.2, 2.0, context.caller); +} +// ---------- 外部输入别名列表 ---------- +const EXTERNAL_INPUT_ALIASES = [ + input("SpawnBarrage_1", "SpawnBarrage_1()", handleSpawnBarrage1, "vmf", "Barrage: 50 rounds, 0.5-2.0s interval"), + input("SpawnBarrage_2", "SpawnBarrage_2()", handleSpawnBarrage2, "vmf", "Barrage: 30 rounds, 0.2-1.5s interval"), + input("SpawnBarrage_3", "SpawnBarrage_3()", handleSpawnBarrage3, "vmf", "Barrage: 20 rounds, 1.0-3.0s interval"), + input("SpawnBarrage_4", "SpawnBarrage_4()", handleSpawnBarrage4, "vmf", "Barrage: 50 rounds, 1.0-3.0s interval"), + input("SpawnBarrage_5", "SpawnBarrage_5()", handleSpawnBarrage5, "vmf", "Barrage: 100 rounds, 0.2-2.0s interval"), +]; +// ---------- 注册函数 ---------- +function registerInputAliases() { + for (const entry of EXTERNAL_INPUT_ALIASES) { + registerFixedScriptInput(entry.name, entry.handler); + } +} +registerInputAliases(); diff --git a/2001/ze_diddle/ze_diddle/shieldhp.js b/2001/ze_diddle/ze_diddle/shieldhp.js new file mode 100644 index 0000000..9763bac --- /dev/null +++ b/2001/ze_diddle/ze_diddle/shieldhp.js @@ -0,0 +1,1001 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function requireSelf(entity, scriptName) { + return requireEntity(entity, `${scriptName} SetSelf`); +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function findAllByName(name) { + return Instance.FindEntitiesByName(name); +} +function findAllByClass(classname) { + return Instance.FindEntitiesByClass(classname); +} +function findByNameWithin(name, origin, radius, previous = null) { + return findWithin(findAllByName(name), origin, radius, previous); +} +function findByClassWithin(classname, origin, radius, previous = null) { + return findWithin(findAllByClass(classname), origin, radius, previous); +} +function findByNameNearest(name, origin, radius) { + let nearest = null; + let nearestDistance = radius; + for (const entity of findAllByName(name)) { + if (!isValidEntity(entity)) + continue; + const currentDistance = distance(getOrigin(entity), origin); + if (currentDistance > nearestDistance) + continue; + nearest = entity; + nearestDistance = currentDistance; + } + return nearest; +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function getTeam(entity) { + if (entity == null || !entity.IsValid()) + return 0; + return entity.GetTeamNumber(); +} +const Entities = { + FindByName(previous, name) { + return findAfter(findAllByName(name), previous); + }, + FindByClassname(previous, classname) { + return findAfter(findAllByClass(classname), previous); + }, + FindByNameWithin(previous, name, origin, radius) { + return findByNameWithin(name, origin, radius, previous); + }, + FindByClassnameWithin(previous, classname, origin, radius) { + return findByClassWithin(classname, origin, radius, previous); + }, + FindByNameNearest(name, origin, radius) { + return findByNameNearest(name, origin, radius); + }, +}; +function findWithin(entities, origin, radius, previous) { + let previousFound = previous == null; + for (const entity of entities) { + if (!isValidEntity(entity)) + continue; + if (!previousFound) { + previousFound = entity === previous; + continue; + } + if (entity === previous) + continue; + if (distance(getOrigin(entity), origin) <= radius) + return entity; + } + return null; +} +function findAfter(entities, previous) { + if (previous == null) + return entities[0] ?? null; + const index = entities.findIndex((entity) => entity === previous); + if (index < 0) + return null; + return entities[index + 1] ?? null; +} + +let self = null; +function SetSelf(inputData) { + const caller = inputData.caller; + if (caller && caller.IsValid()) { + self = caller; + } +} +function AddHP(addAmount) { + const shield = requireSelf(self, 'shieldhp'); + let hp = 100; + let player = null; + while (null != (player = Entities.FindByClassname(player, 'player'))) { + if (getTeam(player) == 2) + hp += addAmount; + } + EntFireByHandle(shield, 'SetHealth', String(hp), 0.0, null, null); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SetSelf', 'SetSelf()', (inputData) => SetSelf(inputData), 'new'), + ...[70, 80, 100, 150, 220].map((amount) => input(`AddHP_${amount}`, `AddHP(${amount})`, () => AddHP(amount), 'vmf+stripper')), +]; +registerInputAliases('ze_diddle/shieldhp', EXTERNAL_INPUT_ALIASES); diff --git a/2001/ze_diddle/ze_diddle/shrekheadpath.js b/2001/ze_diddle/ze_diddle/shrekheadpath.js new file mode 100644 index 0000000..bd2d278 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/shrekheadpath.js @@ -0,0 +1,1000 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFire(name, input, value = '', delay = 0, activator = null, caller = null) { + Instance.EntFireAtName({ + name, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} +function forceSpawnTemplate(templateName, position = null, rotation = null) { + const template = Instance.FindEntityByName(templateName); + if (template == null || !template.IsValid()) { + throw new Error(`[ze_diddle] missing point_template '${templateName}'`); + } + return forceSpawnResolvedTemplate(template, templateName, position, rotation); +} +function forceSpawnResolvedTemplate(template, label, position, rotation) { + const classname = template.GetClassName(); + if (classname !== 'point_template') { + throw new Error(`[ze_diddle] '${label}' must be point_template, got '${classname}'`); + } + const spawned = template.ForceSpawn(position == null ? undefined : vec(position), rotation == null ? undefined : angles(rotation)); + if (spawned == null) { + throw new Error(`[ze_diddle] ForceSpawn failed for '${label}'`); + } + const spawnedEntities = spawned.filter(isNotNull); + if (spawnedEntities.length <= 0) { + throw new Error(`[ze_diddle] ForceSpawn returned no entities for '${label}'`); + } + return spawnedEntities; +} +function Vector(x = 0, y = 0, z = 0) { + return vec(x, y, z); +} +const RandomInt = randomInt; +function isNotNull(value) { + return value != null; +} + +function SpawnShrekHeads() { + for (let index = 0; index <= 40; index++) { + const delay = index / 10; + scheduleScript('ze_diddle/shrekheadpath', () => SpawnDoodler(), delay); + } + EntFire('X69Xluff_npc_phys2gg*', 'FireUser1', '', 25.0, null); + EntFire('X69Xluff_npc_killme*', 'Enable', '', 25.0, null); + EntFire('X69Xluff_npc_killme*', 'Enable', '', 26.0, null); +} +function SpawnDoodler() { + forceSpawnTemplate('s_shrekface', Vector(RandomInt(-1e3, 5500), RandomInt(-13296, -12304), 13320)); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SpawnShrekHeads', 'SpawnShrekHeads()', () => SpawnShrekHeads(), 'vmf'), +]; +registerInputAliases('ze_diddle/shrekheadpath', EXTERNAL_INPUT_ALIASES); +installScheduler(); diff --git a/2001/ze_diddle/ze_diddle/vaginaface.js b/2001/ze_diddle/ze_diddle/vaginaface.js new file mode 100644 index 0000000..1a8cc3f --- /dev/null +++ b/2001/ze_diddle/ze_diddle/vaginaface.js @@ -0,0 +1,1326 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +let idPool = 0; +let tasks = []; +function setTimeout(callback, ms) { + const id = idPool++; + tasks.unshift({ + id, + atSeconds: Instance.GetGameTime() + ms / 1000, + callback, + }); + return id; +} +function runSchedulerTick() { + for (let i = tasks.length - 1; i >= 0; i--) { + const task = tasks[i]; + if (Instance.GetGameTime() < task.atSeconds) + continue; + if (task.everyNSeconds === undefined) + tasks.splice(i, 1); + else + task.atSeconds = Instance.GetGameTime() + task.everyNSeconds; + try { + task.callback(); + } + catch (err) { + Instance.Msg('An error occurred inside a scheduler task'); + if (err instanceof Error) { + Instance.Msg(err.message); + Instance.Msg(err.stack ?? ''); + } + } + } +} + +function EntFire(name, input, value = '', delay = 0, activator = null, caller = null) { + Instance.EntFireAtName({ + name, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +function EntFireTarget(target, input, value = '', delay = 0, activator = null, caller = null) { + if (!isValidEntity(target)) + return; + Instance.EntFireAtTarget({ + target, + input, + value: value == null ? '' : String(value), + delay, + activator: activator ?? undefined, + caller: caller ?? undefined, + }); +} +const EntFireByHandle = EntFireTarget; +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function scheduleScript(prefix, callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + const inputData = normalizeInputData(inputDataOrActivator, nextCaller); + setTimeout(() => runInputCallback(prefix, callback, inputData), delay * 1000); +} +function installScheduler() { + Instance.SetNextThink(Instance.GetGameTime()); + Instance.SetThink(() => { + Instance.SetNextThink(Instance.GetGameTime()); + runSchedulerTick(); + }); +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function normalizeInputData(inputDataOrActivator = {}, nextCaller = null) { + if (inputDataOrActivator == null || + (typeof inputDataOrActivator === 'object' && + 'IsValid' in inputDataOrActivator)) { + const activator = inputDataOrActivator; + return { + activator: activator ?? undefined, + caller: nextCaller ?? undefined, + }; + } + const inputData = inputDataOrActivator; + return { + activator: inputData.activator, + caller: inputData.caller, + }; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function distance(a, b) { + return Vector3Utils.distance(a, b); +} +function VectorAdd(a, b) { + return Vector3Utils.add(a, b); +} +function VectorScale(vector, scale) { + return Vector3Utils.scale(vector, scale); +} +function TraceLine(start, end, ignoreEntity = null, ignorePlayers = false) { + const trace = Instance.TraceLine({ + start, + end, + ignoreEntity: ignoreEntity ?? undefined, + ignorePlayers, + }); + return trace.fraction; +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function findAllByName(name) { + return Instance.FindEntitiesByName(name); +} +function findAllByClass(classname) { + return Instance.FindEntitiesByClass(classname); +} +function findByNameWithin(name, origin, radius, previous = null) { + return findWithin(findAllByName(name), origin, radius, previous); +} +function findByClassWithin(classname, origin, radius, previous = null) { + return findWithin(findAllByClass(classname), origin, radius, previous); +} +function findByNameNearest(name, origin, radius) { + let nearest = null; + let nearestDistance = radius; + for (const entity of findAllByName(name)) { + if (!isValidEntity(entity)) + continue; + const currentDistance = distance(getOrigin(entity), origin); + if (currentDistance > nearestDistance) + continue; + nearest = entity; + nearestDistance = currentDistance; + } + return nearest; +} +function getOrigin(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsOrigin()); +} +function setOrigin(entity, position) { + if (!isValidEntity(entity) || position == null) + return; + teleportEntity(entity, position, null, null); +} +function setAngles(entity, pitch, yaw, roll) { + if (!isValidEntity(entity) || pitch == null) + return; + const nextAngles = typeof pitch === 'object' + ? angles(pitch) + : angles(pitch, yaw ?? 0, roll); + teleportEntity(entity, null, nextAngles, null); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function getTeam(entity) { + if (entity == null || !entity.IsValid()) + return 0; + return entity.GetTeamNumber(); +} +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} +const Entities = { + FindByName(previous, name) { + return findAfter(findAllByName(name), previous); + }, + FindByClassname(previous, classname) { + return findAfter(findAllByClass(classname), previous); + }, + FindByNameWithin(previous, name, origin, radius) { + return findByNameWithin(name, origin, radius, previous); + }, + FindByClassnameWithin(previous, classname, origin, radius) { + return findByClassWithin(classname, origin, radius, previous); + }, + FindByNameNearest(name, origin, radius) { + return findByNameNearest(name, origin, radius); + }, +}; +function Vector(x = 0, y = 0, z = 0) { + return vec(x, y, z); +} +const RandomInt = randomInt; +const sqrt = Math.sqrt; +function findWithin(entities, origin, radius, previous) { + let previousFound = previous == null; + for (const entity of entities) { + if (!isValidEntity(entity)) + continue; + if (!previousFound) { + previousFound = entity === previous; + continue; + } + if (entity === previous) + continue; + if (distance(getOrigin(entity), origin) <= radius) + return entity; + } + return null; +} +function findAfter(entities, previous) { + if (previous == null) + return entities[0] ?? null; + const index = entities.findIndex((entity) => entity === previous); + if (index < 0) + return null; + return entities[index + 1] ?? null; +} + +let self = null; +const SCRIPT_PREFIX = 'ze_diddle/vaginaface'; +let target = null; +let speed = 0.0; +const speed_acceleration = 0.1; +const speed_max = 15.0; +let model = null; +let s_target = null; +let s_hit = null; +let s_hurt1 = null; +let s_hurt2 = null; +let s_hurt3 = null; +let s_die = null; +let hp = null; +let dead = false; +let deadbig = false; +let deadscale = 1.0; +let retarget = 14; +let ticking = false; +let hurtAnimSoundCooldown = false; +function SetSelf(inputData) { + const caller = inputData.caller; + if (caller && caller.IsValid()) { + self = caller; + } +} +function scheduleInternalScript(callback, delay = 0, inputDataOrActivator = {}, nextCaller = null) { + scheduleScript(SCRIPT_PREFIX, callback, delay, inputDataOrActivator, nextCaller); +} +function SetEnt(inputData, i) { + const { caller } = inputData; + const entity = requireEntity(caller, `SetEnt(${i}) caller`); + if (i == 1) + model = entity; + else if (i == 2) + s_target = entity; + else if (i == 3) + s_hit = entity; + else if (i == 4) + s_hurt1 = entity; + else if (i == 5) + s_hurt2 = entity; + else if (i == 6) + s_hurt3 = entity; + else if (i == 7) + s_die = entity; + else if (i == 8) + hp = entity; +} +// ---------- 辅助函数:构建忽略列表(自身 + 其他鬼脸模型) ---------- +function buildIgnoreList() { + const list = []; + if (isValidEntity(model)) { + list.push(model); + } + let other = null; + while (null != (other = Entities.FindByName(other, 'i_vaginaface_model*'))) { + if (other !== model && isValidEntity(other)) { + list.push(other); + } + } + return list; +} +function Start() { + if (!ticking) { + ticking = true; + scheduleInternalScript(() => Tick(), 0.0); + } + SetHealth(); + EntFire('Map_Manager', 'RunScriptInput', 'AddVagina', 0.0, self); +} +function CheckDiddleCannonProjectile() { + let p = null; + while (null != + (p = Entities.FindByNameWithin(p, 'projectile_hurt*', getOrigin(self), 100))) { + EntFireByHandle(p, 'FireUser1', '', 0.0, null, null); + EntFireByHandle(hp, 'RemoveHealth', '400', 0.0, null, null); + } +} +function Tick() { + if (!ticking) + return; + if (!dead && isValidEntity(target) && target.GetHealth() > 0) { + retarget -= 0.02; + CheckDiddleCannonProjectile(); + const t1 = getOrigin(self); + const t2 = target.GetEyePosition(); + const ignoreList = buildIgnoreList(); // ← 动态构建忽略列表 + if (retarget <= 0.0 || TraceLine(t1, t2, ignoreList, true) < 1.0) { + SearchTarget(); + } + else { + const dir = Vector(t2.x - t1.x, t2.y - t1.y, t2.z - t1.z); + const length = dir.length; + const normal = length > 0.0 ? VectorScale(dir, 1.0 / length) : Vector(); + const newpos = VectorAdd(t1, VectorScale(normal, speed)); + FaceDirection(normal); + setOrigin(self, newpos); + if (speed < speed_max) + speed += speed_acceleration; + } + } + else if (dead) { + if (!deadbig) { + deadscale += 0.1; + if (deadscale > 2.0) + deadbig = true; + } + else { + deadscale -= 0.1; + if (deadscale <= 0.0) { + deadscale = 0.01; + EntFireByHandle(model, 'Disable', '', 0.0, null, null); + } + } + EntFireByHandle(model, 'SetScale', String(deadscale), 0.0, null, null); + } + else { + SearchTarget(); + } + scheduleInternalScript(() => Tick(), 0.01); +} +function Die() { + dead = true; + EntFireByHandle(s_die, 'StartSound', '', 0.0, null, null); + SetHurtAnimation(); +} +function Hurt() { + speed = 0.0; + SetHurtAnimation(); + if (!hurtAnimSoundCooldown) { + hurtAnimSoundCooldown = true; + const rand = GetRandomValue(2); + if (rand == 0) + EntFireByHandle(s_hurt1, 'StartSound', '', 0.0, null, null); + if (rand == 1) + EntFireByHandle(s_hurt2, 'StartSound', '', 0.0, null, null); + if (rand == 2) + EntFireByHandle(s_hurt3, 'StartSound', '', 0.0, null, null); + scheduleInternalScript(() => { + hurtAnimSoundCooldown = false; + }, 2.0, null, null); + } +} +function HitPlayer() { + EntFireByHandle(s_hit, 'StartSound', '', 0.0, null, null); + speed = 0.0; +} +function SearchTarget() { + if (dead) + return; + if (!isValidEntity(target) || target.GetHealth() <= 0) { + target = null; + speed = 0.0; + } + const ignoreList = buildIgnoreList(); // ← 构建忽略列表 + let p = null; + const candidates = []; + while (null != (p = Entities.FindByClassname(p, 'player'))) { + if (getTeam(p) == 3) { + const ppos = p.GetEyePosition(); + if (TraceLine(getOrigin(self), ppos, ignoreList, true) == 1.0) { + candidates.push(p); + } + } + } + if (candidates.length > 0) { + retarget = 14; + EntFireByHandle(s_target, 'StartSound', '', 0.0, null, null); + target = candidates[GetRandomValue(candidates.length - 1)]; + } +} +function SetHealth() { + const base_hp = 500; + const foreachplayer_hpadd = 300; + SetHealthByPlayerCount(base_hp, foreachplayer_hpadd); +} +function ExfaceHP() { + const base_hp = 100; + const foreachplayer_hpadd = 30; + SetHealthByPlayerCount(base_hp, foreachplayer_hpadd); +} +function SetHealthByPlayerCount(baseHp, foreachPlayerHpAdd) { + if (hp != null && hp.IsValid()) { + let hpadd = 0; + const all = Instance.GetAllPlayerControllers(); + for (const controller of all) { + const player = controller.GetPlayerPawn(); + if (!player || player.GetTeamNumber() != 3 || player.GetHealth() <= 0) + continue; + hpadd += foreachPlayerHpAdd; + } + EntFireByHandle(hp, 'SetHealth', String(baseHp + hpadd), 0.0, null, null); + } +} +function GetRandomValue(max) { + return RandomInt(0, max); +} +function SetHurtAnimation() { + const rand = GetRandomValue(2); + if (rand == 0) + EntFireByHandle(model, 'SetAnimationNotLooping', 'hurt_1', 0.0, null, null); + if (rand == 1) + EntFireByHandle(model, 'SetAnimationNotLooping', 'hurt_2', 0.0, null, null); + if (rand == 2) + EntFireByHandle(model, 'SetAnimationNotLooping', 'hurt_3', 0.0, null, null); +} +function FaceDirection(direction) { + const yaw = (Math.atan2(direction.y, direction.x) * 180.0) / Math.PI; + const horizontal = sqrt(direction.x * direction.x + direction.y * direction.y); + const pitch = (-Math.atan2(direction.z, horizontal) * 180.0) / Math.PI; + setAngles(self, pitch, yaw, 0.0); +} +const EXTERNAL_INPUT_ALIASES = [ + input("SetSelf", "SetSelf()", (inputData) => SetSelf(inputData), 'new'), + input('Start', 'Start()', () => Start(), 'vmf'), + ...Array.from({ length: 8 }, (_, i) => { + const index = i + 1; + return input(`SetEnt_${index}`, `SetEnt(${index})`, (inputData) => SetEnt(inputData, index), 'vmf'); + }), + input('Hurt', 'Hurt()', () => Hurt(), 'vmf'), + input('Die', 'Die()', () => Die(), 'vmf'), + input('HitPlayer', 'HitPlayer()', () => HitPlayer(), 'vmf'), + input('ExfaceHP', 'OverrideVaginaFaceHP()', () => ExfaceHP(), 'stripper'), +]; +registerInputAliases(SCRIPT_PREFIX, EXTERNAL_INPUT_ALIASES); +installScheduler(); +Instance.Msg('[ze_diddle] vaginaface script loaded with ' + + EXTERNAL_INPUT_ALIASES.length + + ' external input aliases'); diff --git a/2001/ze_diddle/ze_diddle/velocity.js b/2001/ze_diddle/ze_diddle/velocity.js new file mode 100644 index 0000000..439a146 --- /dev/null +++ b/2001/ze_diddle/ze_diddle/velocity.js @@ -0,0 +1,920 @@ +import { Instance } from 'cs_script/point_script'; + +const RAD_TO_DEG = 180 / Math.PI; +const DEG_TO_RAD = Math.PI / 180; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +class Vector3Utils { + static equals(a, b) { + return a.x === b.x && a.y === b.y && a.z === b.z; + } + static add(a, b) { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + static subtract(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + static scale(vector, scale) { + return new Vec3(vector.x * scale, vector.y * scale, vector.z * scale); + } + static multiply(a, b) { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + static divide(vector, divider) { + if (typeof divider === 'number') { + if (divider === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider, vector.y / divider, vector.z / divider); + } + else { + if (divider.x === 0 || divider.y === 0 || divider.z === 0) + throw Error('Division by zero'); + return new Vec3(vector.x / divider.x, vector.y / divider.y, vector.z / divider.z); + } + } + static length(vector) { + return Math.sqrt(Vector3Utils.lengthSquared(vector)); + } + static lengthSquared(vector) { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; + } + static length2D(vector) { + return Math.sqrt(Vector3Utils.length2DSquared(vector)); + } + static length2DSquared(vector) { + return vector.x ** 2 + vector.y ** 2; + } + static normalize(vector) { + const length = Vector3Utils.length(vector); + return length ? Vector3Utils.divide(vector, length) : Vec3.Zero; + } + static dot(a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + static cross(a, b) { + return new Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + static inverse(vector) { + return new Vec3(-vector.x, -vector.y, -vector.z); + } + static distance(a, b) { + return Vector3Utils.subtract(a, b).length; + } + static distanceSquared(a, b) { + return Vector3Utils.subtract(a, b).lengthSquared; + } + static distance2D(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).length; + } + static distance2DSquared(a, b) { + return new Vec3(a.x - b.x, a.y - b.y, 0).lengthSquared; + } + static floor(vector) { + return new Vec3(Math.floor(vector.x), Math.floor(vector.y), Math.floor(vector.z)); + } + static vectorAngles(vector) { + let yaw = 0; + let pitch = 0; + if (!vector.y && !vector.x) { + if (vector.z > 0) + pitch = -90; + else + pitch = 90; + } + else { + yaw = Math.atan2(vector.y, vector.x) * RAD_TO_DEG; + pitch = Math.atan2(-vector.z, Vector3Utils.length2D(vector)) * RAD_TO_DEG; + } + return new Euler({ + pitch, + yaw, + roll: 0, + }); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + // a + (b - a) * t + return new Vec3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t); + } + static directionTowards(a, b) { + return Vector3Utils.subtract(b, a).normal; + } + static lookAt(a, b) { + return Vector3Utils.directionTowards(a, b).eulerAngles; + } + static withX(vector, x) { + return new Vec3(x, vector.y, vector.z); + } + static withY(vector, y) { + return new Vec3(vector.x, y, vector.z); + } + static withZ(vector, z) { + return new Vec3(vector.x, vector.y, z); + } + static round(vector) { + return new Vec3(Math.round(vector.x), Math.round(vector.y), Math.round(vector.z)); + } + static ceil(vector) { + return new Vec3(Math.ceil(vector.x), Math.ceil(vector.y), Math.ceil(vector.z)); + } + static map(vector, callback) { + return new Vec3(callback(vector.x), callback(vector.y), callback(vector.z)); + } +} +class Vec3 { + x; + y; + z; + static get Zero() { + return new Vec3(0, 0, 0); + } + static get Forward() { + return new Vec3(1, 0, 0); + } + static get Right() { + return new Vec3(0, 1, 0); + } + static get Up() { + return new Vec3(0, 0, 1); + } + constructor(xOrVector, y, z) { + if (typeof xOrVector === 'object') { + this.x = xOrVector.x === 0 ? 0 : xOrVector.x; + this.y = xOrVector.y === 0 ? 0 : xOrVector.y; + this.z = xOrVector.z === 0 ? 0 : xOrVector.z; + } + else { + this.x = xOrVector === 0 ? 0 : xOrVector; + this.y = y === 0 ? 0 : y; + this.z = z === 0 ? 0 : z; + } + } + get length() { + return Vector3Utils.length(this); + } + get lengthSquared() { + return Vector3Utils.lengthSquared(this); + } + get length2D() { + return Vector3Utils.length2D(this); + } + get length2DSquared() { + return Vector3Utils.length2DSquared(this); + } + /** + * Normalizes the vector (Dividing the vector by its length to have the length be equal to 1 e.g. [0.0, 0.666, 0.333]) + */ + get normal() { + return Vector3Utils.normalize(this); + } + get inverse() { + return Vector3Utils.inverse(this); + } + /** + * Floor (Round down) each vector component + */ + get floored() { + return Vector3Utils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return Vector3Utils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return Vector3Utils.round(this); + } + /** + * Calculates the angles from a forward vector + */ + get eulerAngles() { + return Vector3Utils.vectorAngles(this); + } + toString() { + return `Vec3: [${this.x}, ${this.y}, ${this.z}]`; + } + equals(vector) { + return Vector3Utils.equals(this, vector); + } + add(vector) { + return Vector3Utils.add(this, vector); + } + subtract(vector) { + return Vector3Utils.subtract(this, vector); + } + divide(vector) { + return Vector3Utils.divide(this, vector); + } + scale(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + multiply(scaleOrVector) { + return typeof scaleOrVector === 'number' + ? Vector3Utils.scale(this, scaleOrVector) + : Vector3Utils.multiply(this, scaleOrVector); + } + dot(vector) { + return Vector3Utils.dot(this, vector); + } + cross(vector) { + return Vector3Utils.cross(this, vector); + } + distance(vector) { + return Vector3Utils.distance(this, vector); + } + distance2D(vector) { + return Vector3Utils.distance2D(this, vector); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(this, vector); + } + distance2DSquared(vector) { + return Vector3Utils.distance2DSquared(this, vector); + } + /** + * Linearly interpolates the vector to a point based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + */ + lerpTo(vector, fraction, clamp = true) { + return Vector3Utils.lerp(this, vector, fraction, clamp); + } + /** + * Gets the normalized direction vector pointing towards specified point (subtracting two vectors) + */ + directionTowards(vector) { + return Vector3Utils.directionTowards(this, vector); + } + /** + * Returns an angle pointing towards a point from the current vector + */ + lookAt(vector) { + return Vector3Utils.lookAt(this, vector); + } + /** + * Returns the same vector but with a supplied X component + */ + withX(x) { + return Vector3Utils.withX(this, x); + } + /** + * Returns the same vector but with a supplied Y component + */ + withY(y) { + return Vector3Utils.withY(this, y); + } + /** + * Returns the same vector but with a supplied Z component + */ + withZ(z) { + return Vector3Utils.withZ(this, z); + } +} + +class EulerUtils { + static equals(a, b) { + return a.pitch === b.pitch && a.yaw === b.yaw && a.roll === b.roll; + } + static normalize(angle) { + const normalizeAngle = (angle) => { + angle = angle % 360; + if (angle > 180) + return angle - 360; + if (angle < -180) + return angle + 360; + return angle; + }; + return new Euler(normalizeAngle(angle.pitch), normalizeAngle(angle.yaw), normalizeAngle(angle.roll)); + } + static forward(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const cosPitch = Math.cos(pitchInRad); + return new Vec3(cosPitch * Math.cos(yawInRad), cosPitch * Math.sin(yawInRad), -Math.sin(pitchInRad)); + } + static right(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(-1 * sinRoll * sinPitch * cosYaw + -1 * cosRoll * -sinYaw, -1 * sinRoll * sinPitch * sinYaw + -1 * cosRoll * cosYaw, -1 * sinRoll * cosPitch); + } + static up(angle) { + const pitchInRad = (angle.pitch / 180) * Math.PI; + const yawInRad = (angle.yaw / 180) * Math.PI; + const rollInRad = (angle.roll / 180) * Math.PI; + const sinPitch = Math.sin(pitchInRad); + const sinYaw = Math.sin(yawInRad); + const sinRoll = Math.sin(rollInRad); + const cosPitch = Math.cos(pitchInRad); + const cosYaw = Math.cos(yawInRad); + const cosRoll = Math.cos(rollInRad); + return new Vec3(cosRoll * sinPitch * cosYaw + -sinRoll * -sinYaw, cosRoll * sinPitch * sinYaw + -sinRoll * cosYaw, cosRoll * cosPitch); + } + static lerp(a, b, fraction, clamp = true) { + let t = fraction; + if (clamp) { + t = MathUtils.clamp(t, 0, 1); + } + const lerpComponent = (start, end, t) => { + // Calculate the shortest angular distance + let delta = end - start; + // Normalize delta to [-180, 180] range to find shortest path + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + // Interpolate using the shortest path + return start + delta * t; + }; + // a + (b - a) * t + return new Euler(lerpComponent(a.pitch, b.pitch, t), lerpComponent(a.yaw, b.yaw, t), lerpComponent(a.roll, b.roll, t)); + } + static withPitch(angle, pitch) { + return new Euler(pitch, angle.yaw, angle.roll); + } + static withYaw(angle, yaw) { + return new Euler(angle.pitch, yaw, angle.roll); + } + static withRoll(angle, roll) { + return new Euler(angle.pitch, angle.yaw, roll); + } + static rotateTowards(current, target, maxStep) { + const rotateComponent = (current, target, step) => { + let delta = target - current; + if (delta > 180) { + delta -= 360; + } + else if (delta < -180) { + delta += 360; + } + if (Math.abs(delta) <= step) { + return target; + } + else { + return current + Math.sign(delta) * step; + } + }; + return new Euler(rotateComponent(current.pitch, target.pitch, maxStep), rotateComponent(current.yaw, target.yaw, maxStep), rotateComponent(current.roll, target.roll, maxStep)); + } + static clamp(angle, min, max) { + return new Euler(MathUtils.clamp(angle.pitch, min.pitch, max.pitch), MathUtils.clamp(angle.yaw, min.yaw, max.yaw), MathUtils.clamp(angle.roll, min.roll, max.roll)); + } + static round(angle) { + return new Euler(Math.round(angle.pitch), Math.round(angle.yaw), Math.round(angle.roll)); + } + static floor(angle) { + return new Euler(Math.floor(angle.pitch), Math.floor(angle.yaw), Math.floor(angle.roll)); + } + static ceil(angle) { + return new Euler(Math.ceil(angle.pitch), Math.ceil(angle.yaw), Math.ceil(angle.roll)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + static Forward = new Euler(1, 0, 0); + static Right = new Euler(0, 1, 0); + static Up = new Euler(0, 0, 1); + constructor(pitchOrAngle, yaw, roll) { + if (typeof pitchOrAngle === 'object') { + this.pitch = pitchOrAngle.pitch === 0 ? 0 : pitchOrAngle.pitch; + this.yaw = pitchOrAngle.yaw === 0 ? 0 : pitchOrAngle.yaw; + this.roll = pitchOrAngle.roll === 0 ? 0 : pitchOrAngle.roll; + } + else { + this.pitch = pitchOrAngle === 0 ? pitchOrAngle : pitchOrAngle; + this.yaw = yaw === 0 ? 0 : yaw; + this.roll = roll === 0 ? 0 : roll; + } + } + /** + * Returns angle with every componented clamped from -180 to 180 + */ + get normal() { + return EulerUtils.normalize(this); + } + /** + * Returns a normalized forward direction vector + */ + get forward() { + return EulerUtils.forward(this); + } + /** + * Returns a normalized backward direction vector + */ + get backward() { + return this.forward.inverse; + } + /** + * Returns a normalized right direction vector + */ + get right() { + return EulerUtils.right(this); + } + /** + * Returns a normalized left direction vector + */ + get left() { + return this.right.inverse; + } + /** + * Returns a normalized up direction vector + */ + get up() { + return EulerUtils.up(this); + } + /** + * Returns a normalized down direction vector + */ + get down() { + return this.up.inverse; + } + /** + * Floor (Round down) each vector component + */ + get floor() { + return EulerUtils.floor(this); + } + /** + * Ceil (Round up) each vector component + */ + get ceil() { + return EulerUtils.ceil(this); + } + /** + * Rounds each vector component + */ + get round() { + return EulerUtils.round(this); + } + toString() { + return `Euler: [${this.pitch}, ${this.yaw}, ${this.roll}]`; + } + equals(angle) { + return EulerUtils.equals(this, angle); + } + /** + * Linearly interpolates the angle to an angle based on a 0.0-1.0 fraction + * Clamp limits the fraction to [0,1] + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + lerp(angle, fraction, clamp = true) { + return EulerUtils.lerp(this, angle, fraction, clamp); + } + /** + * Returns the same angle but with a supplied pitch component + */ + withPitch(pitch) { + return EulerUtils.withPitch(this, pitch); + } + /** + * Returns the same angle but with a supplied yaw component + */ + withYaw(yaw) { + return EulerUtils.withYaw(this, yaw); + } + /** + * Returns the same angle but with a supplied roll component + */ + withRoll(roll) { + return EulerUtils.withRoll(this, roll); + } + /** + * Rotates an angle towards another angle by a specific step + * ! Euler angles are not suited for interpolation, prefer to use quarternions instead + */ + rotateTowards(angle, maxStep) { + return EulerUtils.rotateTowards(this, angle, maxStep); + } + /** + * Clamps each component (pitch, yaw, roll) between the corresponding min and max values + */ + clamp(min, max) { + return EulerUtils.clamp(this, min, max); + } +} + +class Matrix3x4 { + // no need for constructor as the array is initialised to 0 by default + m = new Float32Array(12); + // using a single dimensional array for performance, the matrix indices look like this + // so column index 3, row index 2 would be array index 11. + // 0 1 2 3 + // + // 0 0 1 2 3 + // 1 4 5 6 7 + // 2 8 9 10 11 + // set to identity + constructor() { + this.m.fill(0); + this.m[0] = 1; + this.m[5] = 1; + this.m[10] = 1; + } + equals(mat2, tolerance = 1e-5) { + for (let i = 0; i < 12; ++i) { + if (Math.abs(this.m[i] - mat2.m[i]) > tolerance) + return false; + } + return true; + } + get isIdentity() { + return this.equals(Matrix3x4.identityMatrix); + } + get isValid() { + if (!this.isOrthogonal) { + return false; + } + for (let i = 0; i < 12; i++) { + if (!Number.isFinite(this.m[i])) + return false; + } + return true; + } + // multiplying an orthogonal matrix with its transpose should always give us the identity matrix. + get isOrthogonal() { + return this.multiply(this.inverse).isIdentity; + } + /** + * Inverts the matrix. Actually a transpose but as long as our matrix stays orthogonal it should be the same. + */ + get inverse() { + const retMat = new Matrix3x4(); + // transpose the matrix + retMat.m[0] = this.m[0]; + retMat.m[1] = this.m[4]; + retMat.m[2] = this.m[8]; + retMat.m[4] = this.m[1]; + retMat.m[5] = this.m[5]; + retMat.m[6] = this.m[9]; + retMat.m[8] = this.m[2]; + retMat.m[9] = this.m[6]; + retMat.m[10] = this.m[10]; + // convert translation to new space + const x = this.m[3]; + const y = this.m[7]; + const z = this.m[11]; + retMat.m[3] = -(x * retMat.m[0] + y * retMat.m[1] + z * retMat.m[2]); + retMat.m[7] = -(x * retMat.m[4] + y * retMat.m[5] + z * retMat.m[6]); + retMat.m[11] = -(x * retMat.m[8] + y * retMat.m[9] + z * retMat.m[10]); + return retMat; + } + setOrigin(x, y, z) { + this.m[3] = x; + this.m[7] = y; + this.m[11] = z; + } + get origin() { + return new Vec3(this.m[3], this.m[7], this.m[11]); + } + set origin({ x, y, z }) { + this.setOrigin(x, y, z); + } + setAngles(pitch, yaw, roll) { + const ay = DEG_TO_RAD * yaw; + const ax = DEG_TO_RAD * pitch; + const az = DEG_TO_RAD * roll; + const sy = Math.sin(ay), cy = Math.cos(ay); + const sp = Math.sin(ax), cp = Math.cos(ax); + const sr = Math.sin(az), cr = Math.cos(az); + this.m[0] = cp * cy; + this.m[4] = cp * sy; + this.m[8] = -sp; + this.m[1] = sr * sp * cy + cr * -sy; + this.m[5] = sr * sp * sy + cr * cy; + this.m[9] = sr * cp; + this.m[2] = cr * sp * cy + -sr * -sy; + this.m[6] = cr * sp * sy + -sr * cy; + this.m[10] = cr * cp; + } + set angles(angles) { + this.setAngles(angles.pitch, angles.yaw, angles.roll); + } + get angles() { + const returnAngles = new Euler(0, 0, 0); + const forward0 = this.m[0]; + const forward1 = this.m[4]; + const xyDist = Math.sqrt(forward0 * forward0 + forward1 * forward1); + if (xyDist > 0.001) { + returnAngles.yaw = Math.atan2(forward1, forward0) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = Math.atan2(this.m[9], this.m[10]) * RAD_TO_DEG; + } + else { + // gimbal lock + returnAngles.yaw = Math.atan2(-this.m[1], this.m[5]) * RAD_TO_DEG; + returnAngles.pitch = Math.atan2(-this.m[8], xyDist) * RAD_TO_DEG; + returnAngles.roll = 0.0; + } + return returnAngles; + } + get forward() { + return new Vec3(this.m[0], this.m[4], this.m[8]); + } + set forward(vec) { + // normalise because users can not be trusted + const fwd = vec.normal; + let right; + if (Math.abs(fwd.dot(Vec3.Up)) > 0.999) { + // forward is nearly the same as up/down, use world forward instead to avoid divide by zero + right = fwd.cross(Vec3.Forward).normal; + } + else { + // this makes the right vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + right = Vec3.Up.cross(fwd).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get backward() { + return new Vec3(-this.m[0], -this.m[4], -this.m[8]); + } + set backward(vec) { + this.forward = vec.inverse; + } + get right() { + return new Vec3(-this.m[1], -this.m[5], -this.m[9]); + } + set right(vec) { + // normalise because users can not be trusted + const right = vec.normal; + let fwd; + if (Math.abs(right.dot(Vec3.Up)) > 0.999) { + // right is nearly the same as up/down, use world forward instead to avoid divide by zero + fwd = Vec3.Forward.cross(right).normal; + } + else { + // this makes the forward vector always perpendicular to world up vector, + // it makes the orientation of everything more stable. + fwd = right.cross(Vec3.Up).normal; + } + const up = fwd.cross(right).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get left() { + return this.right.inverse; + } + set left(vec) { + this.right = vec.inverse; + } + get up() { + return new Vec3(this.m[2], this.m[6], this.m[10]); + } + set up(vec) { + // normalise because users can not be trusted + const up = vec.normal; + let right; + if (Math.abs(up.dot(Vec3.Forward)) > 0.999) { + right = Vec3.Right.cross(up).normal; + } + else { + right = up.cross(Vec3.Forward).normal; + } + const fwd = right.cross(up).normal; + this.m[0] = fwd.x; + this.m[4] = fwd.y; + this.m[8] = fwd.z; + this.m[1] = right.x; + this.m[5] = right.y; + this.m[9] = right.z; + this.m[2] = up.x; + this.m[6] = up.y; + this.m[10] = up.z; + } + get down() { + return new Vec3(-this.m[2], -this.m[6], -this.m[10]); + } + set down(vec) { + this.up = vec.inverse; + } + multiply(mat2) { + const out = new Matrix3x4(); + const m1 = this.m; + const m2 = mat2.m; + const m3 = out.m; + m3[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8]; + m3[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9]; + m3[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10]; + m3[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3]; + m3[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8]; + m3[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9]; + m3[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10]; + m3[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7]; + m3[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8]; + m3[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9]; + m3[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10]; + m3[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11]; + return out; + } + // assume this matrix is a pure rotation matrix, and rotate vec + rotateVec3(vec) { + // dot product input vec with the rotation part of the matrix + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10]); + } + // almost the same as the rotate function, but it then adds on the translation part + // copy pasted for performance + transformVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[1] + vec.z * this.m[2] + this.m[3], vec.x * this.m[4] + vec.y * this.m[5] + vec.z * this.m[6] + this.m[7], vec.x * this.m[8] + vec.y * this.m[9] + vec.z * this.m[10] + this.m[11]); + } + /** + * Rotates by the inverse of the matrix. + */ + rotateInverseVec3(vec) { + return new Vec3(vec.x * this.m[0] + vec.y * this.m[4] + vec.z * this.m[8], vec.x * this.m[1] + vec.y * this.m[5] + vec.z * this.m[9], vec.x * this.m[2] + vec.y * this.m[6] + vec.z * this.m[10]); + } + /** + * Transform vec by the transpose of the matrix, assuming the matrix is orthogonal this is also the inverse. + */ + transformInverseVec3(vec) { + const vecMy = vec.x - this.m[3]; + const vecMx = vec.y - this.m[7]; + const vecMz = vec.z - this.m[11]; + return new Vec3(vecMy * this.m[0] + vecMx * this.m[4] + vecMz * this.m[8], vecMy * this.m[1] + vecMx * this.m[5] + vecMz * this.m[9], vecMy * this.m[2] + vecMx * this.m[6] + vecMz * this.m[10]); + } + toString() { + return `\n [${this.m[0]}, ${this.m[1]}, ${this.m[2]}, ${this.m[3]}] + \nMatrix3_4: [${this.m[4]}, ${this.m[5]}, ${this.m[6]}, ${this.m[7]}] + \n [${this.m[8]}, ${this.m[9]}, ${this.m[10]}, ${this.m[11]}]`; + } + toArray() { + return this.m; + } + static getScaleMatrix(x, y, z) { + const matrix = new Matrix3x4(); + matrix.m[0] = x; + matrix.m[5] = y; + matrix.m[10] = z; + return matrix; + } + static identityMatrix = Object.freeze(new Matrix3x4()); +} + +/** 2D curve point vector class */ +class CurvePoint { + x = 0; + y = 0; + constructor(x, y) { + this.x = x; + this.y = y; + } + static Zero = new CurvePoint(0, 0); + toString() { + return `CurvePoint: [${this.x}, ${this.y}]`; + } + add(point) { + return new CurvePoint(this.x + point.x, this.y + point.y); + } + subtract(point) { + return new CurvePoint(this.x - point.x, this.y - point.y); + } + multiply(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x * point, this.y * point); + } + else { + return new CurvePoint(this.x * point.x, this.y * point.y); + } + } + divide(point) { + if (typeof point === 'number') { + return new CurvePoint(this.x / point, this.y / point); + } + else { + return new CurvePoint(this.x / point.x, this.y / point.y); + } + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + normalise() { + const len = this.length(); + if (len < 1e-9) + return CurvePoint.Zero; + return this.divide(len); + } +} + +function registerFixedScriptInput(name, callback) { + const trimmed = name.trim(); + if (trimmed.length === 0) + throw new Error('script input name cannot be empty'); + Instance.OnScriptInput(trimmed, callback); +} +function input(name, legacyExpression, handler, source = 'vmf+stripper', notes = '') { + if (!/^[A-Za-z0-9_]+$/.test(name)) { + throw new Error(`invalid fixed script input name: ${name}`); + } + return { name, legacyExpression, handler, source, notes }; +} +function registerInputAliases(prefix, aliases) { + for (const entry of aliases) { + registerFixedScriptInput(entry.name, (inputData) => runInputCallback(prefix, entry.handler, inputData)); + } +} +function runInputCallback(prefix, callback, inputData = {}) { + try { + return callback(inputData); + } + catch (err) { + Instance.Msg(`[${prefix}] ${formatError(err)}\n`); + } +} +function requireEntity(entity, context) { + if (!isValidEntity(entity)) { + throw new Error(`${context} requires a valid entity`); + } + return entity; +} +function formatError(error) { + if (error instanceof Error) + return error.stack ?? error.message; + return String(error); +} +function vec(x = 0, y = 0, z = 0) { + if (typeof x === 'object' && x !== null) + return new Vec3(x); + return new Vec3(Number(x), y, z); +} +function angles(pitch = 0, yaw = 0, roll = 0) { + if (typeof pitch === 'object' && pitch !== null) { + if ('pitch' in pitch) + return new Euler(pitch); + return new Euler(pitch.x, pitch.y, pitch.z); + } + return new Euler(Number(pitch), yaw, roll); +} +function isValidEntity(entity) { + if (entity == null) + return false; + return entity.IsValid(); +} +function getVelocity(entity) { + if (entity == null || !entity.IsValid()) + return vec(); + return vec(entity.GetAbsVelocity()); +} +function setVelocity(entity, velocity) { + if (!isValidEntity(entity) || velocity == null) + return; + teleportEntity(entity, null, null, velocity); +} +function teleportEntity(entity, position = null, nextAngles = null, velocity = null) { + if (entity == null || !entity.IsValid()) + return; + entity.Teleport(position == null ? null : vec(position), nextAngles == null ? null : angles(nextAngles), velocity == null ? null : vec(velocity)); +} +function Vector(x = 0, y = 0, z = 0) { + return vec(x, y, z); +} + +function SetVelocity(inputData, x, y, z) { + const activator = requireEntity(inputData.activator, 'SetVelocity activator'); + const velocity = getVelocity(activator); + setVelocity(activator, Vector(x == -1 ? velocity.x : x, y == -1 ? velocity.y : y, z == -1 ? velocity.z : z)); +} +const EXTERNAL_INPUT_ALIASES = [ + input('SetVelocity_0_0_0', 'SetVelocity(0,0,0)', (inputData) => SetVelocity(inputData, 0, 0, 0), 'vmf+stripper'), + input('SetVelocity_M1_M1_400', 'SetVelocity(-1,-1,400)', (inputData) => SetVelocity(inputData, -1, -1, 400), 'vmf'), + input('SetVelocity_0_200_300', 'self.SetVelocity(Vector(0,200,300))', (inputData) => SetVelocity(inputData, 0, 200, 300), 'stripper'), +]; +registerInputAliases('ze_diddle/velocity', EXTERNAL_INPUT_ALIASES);