diff --git a/2001/ze_doomglaven/build/index.js b/2001/ze_doomglaven/build/index.js index cbc60f6..6e13e27 100644 --- a/2001/ze_doomglaven/build/index.js +++ b/2001/ze_doomglaven/build/index.js @@ -1,647 +1,672 @@ -import { Instance, PointTemplate } from 'cs_script/point_script'; - -class MathUtils { - static clamp(value, min, max) { - return Math.min(Math.max(value, min), max); - } -} - -const RAD_TO_DEG = 180 / Math.PI; - -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 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); - } -} -class Vec3 { - x; - y; - z; - static Zero = new Vec3(0, 0, 0); - 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); - } - /** - * 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); - } - distanceSquared(vector) { - return Vector3Utils.distanceSquared(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)); - } -} -class Euler { - pitch; - yaw; - roll; - static Zero = new Euler(0, 0, 0); - 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; - } - 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); - } -} - -const announce = (message, delay) => Instance.EntFireAtName({ name: "server", input: "Command", value: `say *${message.toUpperCase()}*`, delay }); -const generateUniqueRandomNumbers = (min, max, count) => { - if (max < min || count <= 0) { - return []; - } - const result = []; - const possibleNumbers = Array.from({ length: max - min + 1 }, (_, i) => i + min); - while (result.length < count) { - const randomIndex = Math.floor(Math.random() * possibleNumbers.length); - const randomElement = possibleNumbers[randomIndex]; - if (randomElement === undefined) { - return result; - } - result.push(randomElement); - possibleNumbers.splice(randomIndex, 1); - } - return result; -}; - -const locations = ["dock", "street", "sewer", "cave", "library", "chapel", "secret-one", "secret-two", "secret-three"]; -const chestTypes = ["thunder", "quake", "heal", "trap-fire", "trap-poison", "key", "empty"]; -const chestSettings = { - dock: { - possibleSpawns: 12, - heal: 0, - thunder: 1, - quake: 1, - key: 0, - empty: 0, - "trap-fire": 1, - "trap-poison": 1 - }, - street: { - possibleSpawns: 24, - heal: 1, - thunder: 0, - quake: 0, - key: 0, - empty: 0, - "trap-fire": 1, - "trap-poison": 1 - }, - sewer: { - possibleSpawns: 7, - heal: 1, - thunder: 1, - quake: 0, - key: 0, - empty: 0, - "trap-fire": 1, - "trap-poison": 1 - }, - cave: { - possibleSpawns: 12, - heal: 0, - thunder: 1, - quake: 0, - key: 0, - empty: 0, - "trap-fire": 1, - "trap-poison": 1 - }, - library: { - possibleSpawns: 8, - heal: 0, - thunder: 0, - quake: 1, - key: 0, - empty: 0, - "trap-fire": 1, - "trap-poison": 1 - }, - chapel: { - possibleSpawns: 27, - heal: 0, - thunder: 0, - quake: 0, - key: 1, - empty: 18, - "trap-fire": 2, - "trap-poison": 2 - }, - "secret-one": { - possibleSpawns: 2, - heal: 0, - thunder: 0, - quake: 2, - key: 0, - empty: 0, - "trap-fire": 0, - "trap-poison": 0 - }, - "secret-two": { - possibleSpawns: 0, - heal: 0, - thunder: 0, - quake: 0, - key: 0, - empty: 0, - "trap-fire": 0, - "trap-poison": 0 - }, - "secret-three": { - possibleSpawns: 4, - heal: 0, - thunder: 2, - quake: 0, - key: 0, - empty: 0, - "trap-fire": 0, - "trap-poison": 0 - } -}; -const getTotalSpawns = (location) => Object.values(chestSettings[location]).reduce((acc, curr) => acc + curr, 0) - chestSettings[location].possibleSpawns; -const spawnChest = (location, position, type) => Instance.EntFireAtName({ name: `spawnable_chest-${type}_maker`, input: "ForceSpawnAtEntityOrigin", value: `spawner_chest_${location}${position}` }); -const spawnChestsForLocation = (location) => { - const chestPositions = generateUniqueRandomNumbers(1, chestSettings[location].possibleSpawns, getTotalSpawns(location)); - chestTypes.forEach(chestType => { - const spawnPositions = chestPositions.splice(0, chestSettings[location][chestType]); - spawnPositions.forEach(position => { - spawnChest(location, position, chestType); - }); - }); -}; -const spawnAllChests = () => locations.forEach(spawnChestsForLocation); - -const POSSIBLE_SPAWNS = 15; -const spawnAllGrimoires = () => { - const grimoirePositions = generateUniqueRandomNumbers(1, POSSIBLE_SPAWNS, 4); - Instance.EntFireAtName({ name: "spell_grimoire-gnashing_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[0]}` }); - Instance.EntFireAtName({ name: "spell_grimoire-gasping_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[1]}` }); - Instance.EntFireAtName({ name: "spell_grimoire-gyration_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[2]}` }); - Instance.EntFireAtName({ name: "spell_grimoire-gluttony_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[3]}` }); -}; - -const TOTAL_BREAKABLE_WINDOWS = 26; -const TOTAL_OUTSIDE_LIGHTNING = 15; -let placedGrimoires = 0; -Instance.OnScriptInput("resetState", () => { - placedGrimoires = 0; -}); -Instance.OnScriptInput("placeGnashingGrimoire", () => placeGrimoire("gnashing")); -Instance.OnScriptInput("placeGaspingGrimoire", () => placeGrimoire("gasping")); -Instance.OnScriptInput("placeGyrationGrimoire", () => placeGrimoire("gyration")); -Instance.OnScriptInput("placeGluttonyGrimoire", () => placeGrimoire("gluttony")); -const placeGrimoire = (grimoireName) => { - placedGrimoires++; - Instance.EntFireAtName({ name: `spell_grimoire-${grimoireName}_book`, input: "Kill" }); - Instance.EntFireAtName({ name: `spell_grimoire-${grimoireName}_trigger`, input: "Kill" }); - Instance.EntFireAtName({ name: `ritual_placedbook${placedGrimoires}`, input: "Enable" }); - Instance.EntFireAtName({ name: `ritual_lightning_timer${placedGrimoires}`, input: "Enable" }); - Instance.EntFireAtName({ name: "ritual_book_sound", input: "StartSound" }); - if (placedGrimoires <= 3) { - announce(`GRIMOIRE OF ${grimoireName.toUpperCase()} PLACED`, 0); - announce(`${placedGrimoires}/3 GRIMOIRES PLACED`, 0.5); - } - if (placedGrimoires === 3) { - Instance.EntFireAtName({ name: "ritual_start_relay", input: "Trigger" }); - } - if (placedGrimoires === 4) { - announce("A 4TH GRIMOIRE?", 1); - } - triggerRitualLightning(); -}; -const triggerRitualLightning = () => { - const windowNumber = Math.floor(Math.random() * TOTAL_BREAKABLE_WINDOWS) + 1; - Instance.EntFireAtName({ name: `library_window${windowNumber}_particle`, input: "Start" }); - Instance.EntFireAtName({ name: `library_window${windowNumber}_particle`, input: "Stop", delay: 2 }); - Instance.EntFireAtName({ name: `library_window${windowNumber}_particle2`, input: "Start" }); - Instance.EntFireAtName({ name: `library_window${windowNumber}_particle2`, input: "Stop", delay: 2 }); - Instance.EntFireAtName({ name: `library_window${windowNumber}_breakable`, input: "Break" }); - Instance.EntFireAtName({ name: "ritual_lightning_sound", input: "StartSound" }); - Instance.EntFireAtName({ name: "sound_lightning_distant", input: "StartSound" }); -}; -Instance.OnScriptInput("triggerRitualLightning", triggerRitualLightning); -Instance.OnScriptInput("triggerOutsideLightning", () => { - const lightningNumber = Math.floor(Math.random() * TOTAL_OUTSIDE_LIGHTNING) + 1; - Instance.EntFireAtName({ name: `library_random-lightning${lightningNumber}`, input: "ForceSpawn" }); -}); -Instance.OnScriptInput("spawnRandomSpawnables", () => { - spawnAllChests(); - spawnAllGrimoires(); -}); -const boostAmount = 300; -Instance.OnScriptInput("triggerThunderItem", ({ activator }) => { - if (!activator) { - return; - } - const spawner = Instance.FindEntitiesByName("effect_thunder_template")[0]; - if (!spawner || !(spawner instanceof PointTemplate)) { - return; - } - const currentOrigin = new Vec3(activator.GetAbsOrigin()); - const currentDirection = new Euler(activator.GetEyeAngles()); - spawner.ForceSpawn(currentOrigin.add(new Vec3(0, 0, 96)), currentDirection.normal); - const backwardsVelocity = new Vec3({ - x: -currentDirection.forward.x * boostAmount, - y: -currentDirection.forward.y * boostAmount, - z: -currentDirection.forward.z * boostAmount - }); - const currentVelocity = activator.GetAbsVelocity(); - activator.Teleport({ velocity: backwardsVelocity.add(currentVelocity) }); -}); +import { Instance, PointTemplate } from 'cs_script/point_script'; + +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +const RAD_TO_DEG = 180 / Math.PI; + +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 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); + } +} +class Vec3 { + x; + y; + z; + static Zero = new Vec3(0, 0, 0); + 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); + } + /** + * 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); + } + distanceSquared(vector) { + return Vector3Utils.distanceSquared(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)); + } +} +class Euler { + pitch; + yaw; + roll; + static Zero = new Euler(0, 0, 0); + 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; + } + 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); + } +} + +const announce = (message, delay) => Instance.EntFireAtName({ name: "server", input: "Command", value: `say *${message.toUpperCase()}*`, delay }); +const generateUniqueRandomNumbers = (min, max, count) => { + if (max < min || count <= 0) { + return []; + } + const result = []; + const possibleNumbers = Array.from({ length: max - min + 1 }, (_, i) => i + min); + while (result.length < count) { + const randomIndex = Math.floor(Math.random() * possibleNumbers.length); + const randomElement = possibleNumbers[randomIndex]; + if (randomElement === undefined) { + return result; + } + result.push(randomElement); + possibleNumbers.splice(randomIndex, 1); + } + return result; +}; + +const locations = ["dock", "street", "sewer", "cave", "library", "chapel", "secret-one", "secret-two", "secret-three"]; +const chestTypes = ["thunder", "quake", "heal", "trap-fire", "trap-poison", "key", "empty"]; +const chestSettings = { + dock: { + possibleSpawns: 12, + heal: 0, + thunder: 1, + quake: 1, + key: 0, + empty: 0, + "trap-fire": 1, + "trap-poison": 1 + }, + street: { + possibleSpawns: 24, + heal: 1, + thunder: 0, + quake: 0, + key: 0, + empty: 0, + "trap-fire": 1, + "trap-poison": 1 + }, + sewer: { + possibleSpawns: 7, + heal: 1, + thunder: 1, + quake: 0, + key: 0, + empty: 0, + "trap-fire": 1, + "trap-poison": 1 + }, + cave: { + possibleSpawns: 12, + heal: 0, + thunder: 1, + quake: 0, + key: 0, + empty: 0, + "trap-fire": 1, + "trap-poison": 1 + }, + library: { + possibleSpawns: 8, + heal: 0, + thunder: 0, + quake: 1, + key: 0, + empty: 0, + "trap-fire": 1, + "trap-poison": 1 + }, + chapel: { + possibleSpawns: 27, + heal: 0, + thunder: 0, + quake: 0, + key: 1, + empty: 18, + "trap-fire": 2, + "trap-poison": 2 + }, + "secret-one": { + possibleSpawns: 2, + heal: 0, + thunder: 0, + quake: 2, + key: 0, + empty: 0, + "trap-fire": 0, + "trap-poison": 0 + }, + "secret-two": { + possibleSpawns: 0, + heal: 0, + thunder: 0, + quake: 0, + key: 0, + empty: 0, + "trap-fire": 0, + "trap-poison": 0 + }, + "secret-three": { + possibleSpawns: 4, + heal: 0, + thunder: 2, + quake: 0, + key: 0, + empty: 0, + "trap-fire": 0, + "trap-poison": 0 + } +}; +const getTotalSpawns = (location) => Object.values(chestSettings[location]).reduce((acc, curr) => acc + curr, 0) - chestSettings[location].possibleSpawns; +const spawnChest = (location, position, type) => Instance.EntFireAtName({ name: `spawnable_chest-${type}_maker`, input: "ForceSpawnAtEntityOrigin", value: `spawner_chest_${location}${position}` }); +const spawnChestsForLocation = (location) => { + const chestPositions = generateUniqueRandomNumbers(1, chestSettings[location].possibleSpawns, getTotalSpawns(location)); + chestTypes.forEach(chestType => { + const spawnPositions = chestPositions.splice(0, chestSettings[location][chestType]); + spawnPositions.forEach(position => { + spawnChest(location, position, chestType); + }); + }); +}; +const spawnAllChests = () => locations.forEach(spawnChestsForLocation); + +const POSSIBLE_SPAWNS = 15; +const spawnAllGrimoires = () => { + const grimoirePositions = generateUniqueRandomNumbers(1, POSSIBLE_SPAWNS, 4); + Instance.EntFireAtName({ name: "spell_grimoire-gnashing_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[0]}` }); + Instance.EntFireAtName({ name: "spell_grimoire-gasping_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[1]}` }); + Instance.EntFireAtName({ name: "spell_grimoire-gyration_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[2]}` }); + Instance.EntFireAtName({ name: "spell_grimoire-gluttony_maker", input: "ForceSpawnAtEntityOrigin", value: `spawner_grimoire${grimoirePositions[3]}` }); +}; + +const TOTAL_BREAKABLE_WINDOWS = 26; +const TOTAL_OUTSIDE_LIGHTNING = 15; +let placedGrimoires = 0; +Instance.OnScriptInput("resetState", () => { + placedGrimoires = 0; +}); +Instance.OnScriptInput("placeGnashingGrimoire", () => placeGrimoire("gnashing")); +Instance.OnScriptInput("placeGaspingGrimoire", () => placeGrimoire("gasping")); +Instance.OnScriptInput("placeGyrationGrimoire", () => placeGrimoire("gyration")); +Instance.OnScriptInput("placeGluttonyGrimoire", () => placeGrimoire("gluttony")); +const placeGrimoire = (grimoireName) => { + placedGrimoires++; + Instance.EntFireAtName({ name: `spell_grimoire-${grimoireName}_book`, input: "Kill" }); + Instance.EntFireAtName({ name: `spell_grimoire-${grimoireName}_trigger`, input: "Kill" }); + Instance.EntFireAtName({ name: `ritual_placedbook${placedGrimoires}`, input: "Enable" }); + Instance.EntFireAtName({ name: `ritual_lightning_timer${placedGrimoires}`, input: "Enable" }); + Instance.EntFireAtName({ name: "ritual_book_sound", input: "StartSound" }); + if (placedGrimoires <= 3) { + announce(`GRIMOIRE OF ${grimoireName.toUpperCase()} PLACED`, 0); + announce(`${placedGrimoires}/3 GRIMOIRES PLACED`, 0.5); + } + if (placedGrimoires === 3) { + Instance.EntFireAtName({ name: "ritual_start_relay", input: "Trigger" }); + } + if (placedGrimoires === 4) { + announce("A 4TH GRIMOIRE?", 1); + } + triggerRitualLightning(); +}; +const triggerRitualLightning = () => { + const windowNumber = Math.floor(Math.random() * TOTAL_BREAKABLE_WINDOWS) + 1; + Instance.EntFireAtName({ name: `library_window${windowNumber}_particle`, input: "Start" }); + Instance.EntFireAtName({ name: `library_window${windowNumber}_particle`, input: "Stop", delay: 2 }); + Instance.EntFireAtName({ name: `library_window${windowNumber}_particle2`, input: "Start" }); + Instance.EntFireAtName({ name: `library_window${windowNumber}_particle2`, input: "Stop", delay: 2 }); + Instance.EntFireAtName({ name: `library_window${windowNumber}_breakable`, input: "Break" }); + Instance.EntFireAtName({ name: "ritual_lightning_sound", input: "StartSound" }); + Instance.EntFireAtName({ name: "sound_lightning_distant", input: "StartSound" }); +}; +Instance.OnScriptInput("triggerRitualLightning", triggerRitualLightning); +Instance.OnScriptInput("triggerOutsideLightning", () => { + const lightningNumber = Math.floor(Math.random() * TOTAL_OUTSIDE_LIGHTNING) + 1; + Instance.EntFireAtName({ name: `library_random-lightning${lightningNumber}`, input: "ForceSpawn" }); +}); +Instance.OnScriptInput("spawnRandomSpawnables", () => { + spawnAllChests(); + spawnAllGrimoires(); +}); +const boostAmount = 300; +Instance.OnScriptInput("triggerThunderItem", ({ activator }) => { + if (!activator) { + return; + } + const spawner = Instance.FindEntitiesByName("effect_thunder_template")[0]; + if (!spawner || !(spawner instanceof PointTemplate)) { + return; + } + const currentOrigin = new Vec3(activator.GetAbsOrigin()); + const currentDirection = new Euler(activator.GetEyeAngles()); + spawner.ForceSpawn(currentOrigin.add(new Vec3(0, 0, 96)), currentDirection.normal); + const backwardsVelocity = new Vec3({ + x: -currentDirection.forward.x * boostAmount, + y: -currentDirection.forward.y * boostAmount, + z: -currentDirection.forward.z * boostAmount + }); + const currentVelocity = activator.GetAbsVelocity(); + activator.Teleport({ velocity: backwardsVelocity.add(currentVelocity) }); +}); +const musicOptions = ["the_elven_ruins", "skavenhorde1", "ussingen_destroyed", "last_stand_edit", "the_grey_seers", "escape_and_panic"]; +let currentMusic; +const playMusic = (musicToPlay) => { + currentMusic = musicToPlay; + if (musicToPlay) { + Instance.EntFireAtName({ name: `music_${musicToPlay}`, input: "StartSound" }); + } + const otherMusic = musicOptions.filter(option => option !== musicToPlay); + otherMusic.forEach(option => { + Instance.EntFireAtName({ name: `music_${option}`, input: "StopSound", delay: 0.5 }); + }); +}; +Instance.OnScriptInput("playMusicTheElvenRuins", () => playMusic("the_elven_ruins")); +Instance.OnScriptInput("playMusicSkavenHorde1", () => playMusic("skavenhorde1")); +Instance.OnScriptInput("playMusicUssingenDestroyed", () => playMusic("ussingen_destroyed")); +Instance.OnScriptInput("playMusicLastStandEdit", () => playMusic("last_stand_edit")); +Instance.OnScriptInput("playMusicTheGreySeers", () => playMusic("the_grey_seers")); +Instance.OnScriptInput("playMusicEscapeAndPanic", () => playMusic("escape_and_panic")); +Instance.OnScriptInput("onMusicFinished", ({ caller }) => { + if (!caller || caller.GetEntityName() !== `music_${currentMusic}`) { + return; + } + playMusic(currentMusic); +}); +Instance.OnScriptInput("stopMusic", () => playMusic(undefined));