From 1648b7e3d036d84ef3969ca5fce1a0381508b104 Mon Sep 17 00:00:00 2001 From: Asraelite Date: Fri, 31 Mar 2023 11:49:12 +0200 Subject: [PATCH] Remove improcket.min.js --- dist/improcket.min.js | 3190 ----------------------------------------- 1 file changed, 3190 deletions(-) delete mode 100644 dist/improcket.min.js diff --git a/dist/improcket.min.js b/dist/improcket.min.js deleted file mode 100644 index 608e08f..0000000 --- a/dist/improcket.min.js +++ /dev/null @@ -1,3190 +0,0 @@ -// Connectivity = [top, right, bottom, left] (same TRouBLe as CSS) - -const modules = { - capsule: { - small: { - name: 'Small Capsule', - tooltip: 'A small, simple capsule. Provides a small amount ' + - 'of rotational power and storage space.', - type: 'capsule', - id: 'small', - mass: 2, - value: 5, - connectivity: [false, false, true, false], - capacity: 3, - rotation: 1, - computation: 100, - }, - large: { - name: 'Large Capsule', - tooltip: 'A large, bulky capsule. Heavy, but has a lot of ' + - 'rotational power and storage space.', - type: 'capsule', - id: 'large', - mass: 4, - value: 10, - connectivity: [false, false, true, false], - capacity: 5, - rotation: 4, - computation: 130, - }, - advanced: { - name: 'Advanced Capsule', - tooltip: 'A futuristic rocket capsule. Has a lot of storage ' + - 'space and rotational power while still being light.', - type: 'capsule', - id: 'advanced', - mass: 2, - value: 30, - connectivity: [false, false, true, false], - capacity: 4, - rotation: 5, - computation: 150, - } - }, - fuel: { - small: { - name: 'Small Fuel Tank', - tooltip: 'A small flimsy tank with enough fuel for a short trip.', - type: 'fuel', - id: 'small', - mass: 1, - value: 1, - connectivity: [true, false, true, false], - fuelCapacity: 5 - }, - large: { - name: 'Large Fuel Tank', - tooltip: 'A large, heavy fuel tank capable of hold a lot of fuel.', - type: 'fuel', - id: 'large', - mass: 2, - value: 3, - connectivity: [true, false, true, false], - fuelCapacity: 15 - }, - advanced: { - name: 'Advanced Fuel Tank', - tooltip: 'A very efficient fuel storage tank.', - type: 'fuel', - id: 'advanced', - mass: 1, - value: 15, - connectivity: [true, false, true, false], - fuelCapacity: 12 - } - }, - thruster: { - light: { - name: 'Light Thruster', - tooltip: 'Powerful enough to lift a small ship, but not much ' + - 'more.', - type: 'thruster', - id: 'light', - mass: 2, - value: 3, - connectivity: [true, false, false, false], - thrust: 10 - }, - heavy: { - name: 'Heavy Thruster', - tooltip: 'A powerful thruster for lifting heavy ships.', - type: 'thruster', - id: 'heavy', - mass: 5, - value: 4, - connectivity: [true, false, false, false], - thrust: 40 - }, - advanced: { - name: 'Advanced Thruster', - tooltip: 'A very efficient thruster using advanced technology. ', - type: 'thruster', - id: 'advanced', - mass: 2, - value: 15, - connectivity: [true, false, false, false], - thrust: 30 - } - }, - connector: { - xheavy: { - name: 'Heavy 4-way Connector', - tooltip: 'Can connect ship parts in any direction, but is quite ' + - 'heavy', - type: 'connector', - id: 'xheavy', - mass: 2, - value: 3, - connectivity: [true, true, true, true] - }, - advanced: { - name: 'Advanced 4-way Connector', - tooltip: 'Connects ship parts while remaining light.', - type: 'connector', - id: 'advanced', - mass: 1, - value: 15, - connectivity: [true, true, true, true] - } - }, - gyroscope: { - small: { - name: 'Small Gyroscope', - tooltip: 'Provides a small amount of rotational power to the ship.', - type: 'gyroscope', - id: 'small', - mass: 3, - value: 7, - connectivity: [true, false, true, false], - rotation: 2 - }, - large: { - name: 'Large Gyroscope', - tooltip: 'Provides a lot of rotational force for large ships.', - type: 'gyroscope', - id: 'large', - mass: 5, - value: 15, - connectivity: [true, false, true, false], - rotation: 4 - } - }, - navigation: { - small: { - name: 'Navigational Computer', - tooltip: 'Increases the length of your predicted orbital path.', - type: 'navigation', - id: 'small', - mass: 1, - value: 10, - connectivity: [true, false, true, false], - computation: 150, - }, - }, - cargo: { - small: { - name: 'Cargo bay', - tooltip: 'A cargo bay for storing modules.', - type: 'cargo', - id: 'small', - mass: 1, - value: 5, - connectivity: [true, false, true, false], - capacity: 5 - } - } -}; - -const images = { - title: { - logo: 'logo.png', - logoSvg: 'logo2.svg' - }, - background: { - back: 'background_small.png', - middle: 'stars_back.png', - front: 'stars_front.png' - }, - modules: { - capsule: { - small: 'modules/small_capsule.svg', - large: 'modules/large_capsule.svg', - advanced: 'modules/advanced_capsule.svg' - }, - fuel: { - small: 'modules/small_fuel_tank.svg', - large: 'modules/large_fuel_tank.svg', - advanced: 'modules/advanced_fuel_tank.svg' - }, - thruster: { - light: { - off: 'modules/light_thruster.svg', - on: 'modules/light_thruster_on.svg' - }, - heavy: { - off: 'modules/heavy_thruster.svg', - on: 'modules/heavy_thruster_on.svg' - }, - advanced: { - off: 'modules/advanced_thruster.svg', - on: 'modules/advanced_thruster_on.svg' - } - }, - connector: { - xheavy: 'modules/xheavy_connector.svg', - advanced: 'modules/advanced_connector.svg', - }, - cargo: { - small: 'modules/cargo_bay.svg' - }, - gyroscope: { - small: 'modules/small_gyroscope.svg', - large: 'modules/large_gyroscope.svg' - }, - navigation: { - small: 'modules/small_navigation.svg', - }, - fuelcan: 'modules/fuelcan.svg' - }, - celestials: { - green: { - '0': 'celestials/green_0.svg', - '1': 'celestials/green_1.svg', - '2': 'celestials/green_2.svg', - '3': 'celestials/rock_0.svg', - '4': 'celestials/rock_1.svg', - '5': 'celestials/lava_0.svg' - } - } -}; - -const audio = { - itemPickup: 'up1.mp3', - fuelPickup: 'blip2.mp3', - endEdit: 'release1.mp3', - newPlanet: 'up2.mp3', - engine: 'rocket2.ogg', - music: 'music2.mp3', - toss: 'thunk1.mp3', - crash: 'crash2.mp3', - pause: 'swoosh1.mp3' -}; - -async function init$9() { - let parse = (obj, convert) => Object.entries(obj).forEach(([k, v]) => { - typeof v == 'object' ? parse(v, convert) : obj[k] = convert(v); - }); - - let promises = []; - parse(images, str => { - let img = new Image(); - img.src = 'img/' + str; - promises.push(new Promise((res) => { - img.addEventListener('load', res); - })); - return img; - }); - parse(audio, str => { - let audio = new Howl({ - src: ['audio/' + str] - }); - promises.push(new Promise((res) => { - audio.once('load', res); - })); - return audio; - }); - - await Promise.all(promises); -} - -class Module { - constructor(x, y, ship, { - name = 'Unnamed Module', - type = 'block', - id = 'unknown', - mass = 1, - fuelCapacity = 0, - ...properties - }) { - this.x = x; - this.y = y; - this.name = name; - this.type = type; - this.mass = mass; - this.ship = ship; - this.id = id; - this.images = images.modules[this.type][this.id]; - this.data = modules[this.type][this.id]; - - if (this.type == 'thruster') { - this.power = 0; - } - } - - reset() { - if (this.type == 'thruster') { - this.power = 0; - } - } - - get com() { - return this.ship.getWorldPoint(...this.localCom); - } - - get currentImage() { - if (this.type == 'thruster') { - return this.power > 0 ? this.images.on : this.images.off; - } else { - return this.images; - } - } - - get localCom() { - return [this.x + 0.5, this.y + 0.5]; - } -} - -/* - * Constants that do not change during gameplay. - * This can kind of be treated like a configuration file, I guess. - * - * All length units are relative to the size of a small ship module, which - * is always 1x1. - */ - -// For fixing floating point rounding errors. -const EPSILON = 1e-8; -// Don't change these. -const TAU$1 = Math.PI * 2; -// Unit length of sector. May affect spawning a bit. -const SECTOR_SIZE = 512; -// G, G-boy, The big G, Mr. G, g's big brother, G-dog -const GRAVITATIONAL_CONSTANT = 0.002; -// Perspective constraints. Higher zoom value = closer. -const MIN_ZOOM = 1; -const MAX_ZOOM = 100; -const DEFAULT_ZOOM = 10; -const ZOOM_SPEED = 0.01; -// Ship landing. Angle in radians. -const TIP_ANGLE = 0.25; -const TIP_SPEED = 0.03; -const CRASH_SPEED = 0.7; -// Ship flight mechanics. Speed measured in units per tick. -const FUEL_BURN_RATE = 0.5; -const THRUST_POWER = 0.004; -const TURN_POWER = 0.07; -// Distance at which an orbited planet will not be considered a parent body. -const MAX_PARENT_CELESTIAL_DISTANCE = 120; -// Ship editing. -const EDIT_MARGIN = 2; -// Floating items. -const ENTITY_ROTATION_RATE = 0.01; -// World generation. -const PLANET_SPAWN_RATE = 100; -const ENTITY_SPAWN_RATE = 8; -const MIN_CELESTIAL_SPACING = 15; -const FUEL_CAN_AMOUNT = 10000; - -const PLANET_IMAGE_SIZE = 250; - -class Body { - constructor(x, y, mass) { - this.x = x; - this.y = y; - this.r = 0; - this.xvel = 0; - this.yvel = 0; - this.rvel = 0; - this.rfriction = 0.9; - this.mass = mass; - } - - get com() { - return [this.x, this.y]; - } - - get pos() { - return [this.x, this.y]; - } - - get speed() { - return Math.sqrt(this.xvel ** 2 + this.yvel ** 2); - } - - angleDifference(a, b) { - return Math.atan2(Math.sin(a - b), Math.cos(a - b)); - } - - normalizeAngle(a = this.r) { - return ((a % TAU$1) + TAU$1) % TAU$1; - } - - getCelestialCollision(celestials) { - let result = false; - celestials.forEach(c => { - let dis = this.distanceTo(c); - if (dis < c.radius) result = c; - }); - return result; - } - - getWorldPoint(lx, ly, test) { - let [cx, cy] = this.localCom; - let [nx, ny] = this.rotateVector(lx - cx, ly - cy, this.r); - return [nx + this.x + cx, ny + this.y + cy]; - } - - getLocalPoint(wx, wy) { - let [lx, ly] = [wx - this.x, wy - this.y]; - let [cx, cy] = this.localCom; - let [nx, ny] = this.rotateVector(lx, ly, -this.r); - return [nx - cx, ny - cy]; - } - - rotateVector(x, y, r = this.r) { - return [(x * Math.cos(-r) + y * Math.sin(-r)), - -(-y * Math.cos(-r) + x * Math.sin(-r))]; - } - - // TODO: Remove and replace uses with `rotateVector`. - relativeVector(x, y) { - return this.rotateVector(x, y, this.r); - } - - tickMotion(speed = 1) { - this.x += this.xvel * speed; - this.y += this.yvel * speed; - this.r += this.rvel * speed; - this.rvel *= this.rfriction * speed; - } - - tickGravity(bodies, speed = 1) { - for (let body of bodies) { - const distanceSquared = this.distanceToSquared(body); - if (distanceSquared > (1000 ** 2)) continue; - let force = body.mass / distanceSquared * GRAVITATIONAL_CONSTANT; - let [[ax, ay], [bx, by]] = [this.com, body.com]; - let angle = Math.atan2(by - ay, bx - ax); - this.xvel += Math.cos(angle) * force * speed; - this.yvel += Math.sin(angle) * force * speed; - } - } - - distanceTo(body) { - let [[ax, ay], [bx, by]] = [this.com, body.com]; - return Math.max(Math.sqrt(((bx - ax) ** 2) + - ((by - ay) ** 2)), 1); - } - - distanceToSquared(body) { - let [[ax, ay], [bx, by]] = [this.com, body.com]; - return Math.max(((bx - ax) ** 2) + - ((by - ay) ** 2), 1); - } - - angleTo(ax, ay, bx, by) { - return Math.atan2(by - ay, bx - ax); - } - - approach(body, distance) { - let [[ax, ay], [bx, by]] = [this.com, body.com]; - let angle = Math.atan2(by - ay, bx - ax); - this.x += Math.cos(angle) * distance; - this.y += Math.sin(angle) * distance; - } - - halt() { - this.xvel = 0; - this.yvel = 0; - } - - applyDirectionalForce(x, y, r) { - let [vx, vy] = this.rotateVector(x, y); - this.xvel += vx / this.mass; - this.yvel += vy / this.mass; - this.rvel += r / this.mass; - } - - orbit(cel, altitude, angle = 0) { - this.gravity = true; - let speed = Math.sqrt(GRAVITATIONAL_CONSTANT * cel.mass / (altitude + cel.radius)); - let [cx, cy] = cel.com; - let [comX, comY] = this.localCom; - let [dx, dy] = this.rotateVector(0, -(altitude + cel.radius), angle); - [this.xvel, this.yvel] = this.rotateVector(speed, 0, angle); - this.x = cx + dx - comX; - this.y = cy + dy - comY; - } -} - -function createThrustExhaust(thruster) { - let ship = thruster.ship; - let [fx, fy] = ship.relativeVector(0, 0.2); - let [dx, dy] = ship.relativeVector((Math.random() - 0.5) * 0.5, 0.5); - let [cx, cy] = thruster.com; - particles.add(new Particle(cx + dx, cy + dy, { - xvel: ship.xvel + fx, - yvel: ship.yvel + fy, - color: '#f4c542', - lifetime: 5, - size: 0.07 - })); -} - -function createEndEditBurst(ship) { - for (let i = 0; i < 20; i++) { - particles.add(new Particle(...ship.poc, { - color: '#ccc', - lifetime: Math.random() * 30 + 25, - size: Math.random() * 0.3 + 0.05, - spray: 0.3, - gravity: true - })); - } -} - -function createCrash(ship) { - for (let i = 0; i < ship.mass + 3; i++) { - particles.add(new Particle(...ship.com, { - color: '#f2e860', - lifetime: Math.random() * 50 + 40, - size: Math.random() * 0.2 + 0.2, - spray: 0.9, - gravity: true - })); - } - for (let i = 0; i < ship.mass + 3; i++) { - particles.add(new Particle(...ship.com, { - color: '#f75722', - lifetime: Math.random() * 50 + 40, - size: Math.random() * 0.2 + 0.2, - spray: 0.5, - gravity: true - })); - } - for (let i = 0; i < ship.mass * 2 + 3; i++) { - particles.add(new Particle(...ship.com, { - color: '#888', - lifetime: Math.random() * 30 + 55, - size: Math.random() * 0.5 + 0.4, - spray: 2, - friction: 0.9, - gravity: false - })); - } -} - -function createPickupBurst(ship, point) { - for (let i = 0; i < 20; i++) { - particles.add(new Particle(...point, { - xvel: ship.xvel, - yvel: ship.yvel, - color: '#eae55d', - lifetime: Math.random() * 20 + 15, - friction: 0.95, - size: Math.random() * 0.2 + 0.05, - spray: 0.3 - })); - } -} - -function createItemToss(ship) { - particles.add(new Particle(...ship.com, { - xvel: ship.xvel, - yvel: ship.yvel, - color: '#a87234', - lifetime: 50, - size: 0.6, - spray: 0.4 - })); -} - -class Particle extends Body { - constructor(x, y, { - xvel = 0, - yvel = 0, - spray = 0.1, - fizzle = 0, - maxFizzle = fizzle * 2, - color = '#fff', - gravity = false, - lifetime = 50, - size = 0.1, - friction = 0.99 - }) { - super(x, y, 0.1); - - this.size = size; - this.xvel = xvel + (Math.random() - 0.5) * spray; - this.yvel = yvel + (Math.random() - 0.5) * spray; - this.friction = friction; - this.fizzle = fizzle; - this.maxFizzle = maxFizzle; - this.color = color; - this.gravity = gravity; - this.life = lifetime; - } - - get com() { - return [this.x - this.size / 2, this.y - this.size / 2]; - } - - tick() { - if (this.life-- <= 0) { - particles.delete(this); - return; - } - - this.tickMotion(); - if (this.gravity) this.tickGravity(celestials); - - this.xvel *= this.friction; - this.yvel *= this.friction; - this.x += (Math.random() - 0.5) * this.fizzle; - this.y += (Math.random() - 0.5) * this.fizzle; - if (this.fizzle < this.mazFizzle) this.fizzle *= 1.05; - } -} - -const items = new Map(); -let currentItem = null; -let capacity = 0; -let usedSpace = 0; - -let onupdate = () => {}; - -function init$8() { - items.clear(); - update(); -} - -function canToss() { - return !state.editing || message === 'inventory too full' - || message === ''; -} - -function getTiles() { - let list = Array.from(items.values()); - list.sort((a, b) => toId(...a.ident) < toId(...b.ident)); - usedSpace = list.reduce((a, b) => a + b.quantity, 0); - return list; -} - -function addItem(type, id) { - let mapId = toId(type, id); - if (!items.has(mapId)) items.set(mapId, new Tile$1(type, id)); - let tile = items.get(mapId); - tile.increase(); - update(); -} - -function removeItem(type, id) { - let mapId = toId(type, id); - if (!items.has(mapId)) return; - let tile = items.get(mapId); - tile.decrease(); - if (tile.quantity == 0) { - items.delete(mapId); - currentItem = null; - } - if (canToss()) - tossItem(); - update(); - validate(); -} - -function selectItem(type, id) { - currentItem = items.get(toId(type, id)); - update(); -} - -function setOnupdate(func) { - onupdate = func; -} - -function update() { - capacity = playerShip.cargoCapacity; - onupdate(); -} - -function toId(type, id) { - return `${type}.${id}`; -} - -class Tile$1 { - constructor(type, id, q = 0) { - this.type = type; - this.id = id; - this.mapId = toId(type, id); - this.quantity = q; - this.image = images.modules[type][id]; - this.data = modules[type][id]; - if (type === 'thruster') this.image = this.image.off; - } - - get textInfo() { - let text = this.data.name + '\n\n' + this.data.tooltip + '\n\n'; - text += 'Mass: ' + this.data.mass + '\n'; - - if (this.type === 'thruster') - text += 'Power: ' + this.data.thrust + '\n'; - if (this.type === 'fuel') - text += 'Fuel capacity: ' + this.data.fuelCapacity + '\n'; - if (this.type === 'capsule') { - text += 'Rotational power: ' + this.data.rotation + '\n'; - text += 'Cargo space: ' + this.data.capacity + '\n'; - } - - return text; - } - - get ident() { - return [this.type, this.id]; - } - - increase() { - this.quantity++; - } - - decrease() { - this.quantity = Math.max(0, this.quantity - 1); - } -} - -const playing = new Map(); - -function play(name) { - audio[name].play(); -} - -function start(name) { - if (!playing.has(name)) - playing.set(name, audio[name]); - - let howl = playing.get(name); - howl.loop(true); - howl.play(); -} - -function stop(name) { - if (!playing.has(name)) return false; - let howl = playing.get(name); - if (howl.playing()) { - howl.stop(); - return true; - } - return false; -} - -function toggle(name) { - if (!stop(name)) start(name); -} - -function volume(name, level) { - if (!playing.has(name)) return; - playing.get(name).volume(level); -} - -let shipLanded = false; -let score = 0; -let gameOverReason = ''; -let scoreText = ''; - -let notification = null; -let notLife = 0; - -let landedPlanets = new Set(); - -function init$7() { - score = 0; - shipLanded = false; -} - -function outOfFuel() { - gameOver('You ran out of fuel'); -} - -function playMusic() { - start('music'); - volume('music', 0.4); -} - -function notify(message, time = 80) { - if (notification === null) return; - notification.text = message; - notLife = time; -} - -function tick$6() { - if (notification === null) return; - if ((notLife-- <= 0 || state.gameOver) && !state.paused) - notification.text = ''; -} - -function setNotificationElement(el) { - notification = el; -} - -function startGame() { - init$7(); - state.gameOver = false; - changeView('game'); - perspective.reset(); - perspective.focusPlayer(); -} - -function toMenu() { - changeView('menu'); -} - -function togglePause() { - console.log(state.paused); - state.paused = !state.paused; - play('pause'); - if (state.paused) { - notify('Paused', 0); - } -} - -function landShip(planet) { - shipLanded = true; - if (!landedPlanets.has(planet)) { - newPlanet(planet); - } - state.landed = true; -} - -function howToPlay() { - changeView('instructions'); -} - -function newPlanet(planet) { - let value = (planet.radius * 2 + 50) | 0; - landedPlanets.add(planet); - play('newPlanet'); - score += value; - notify('Landed on new planet: +' + value); -} - -function launchShip() { - shipLanded = false; - state.landed = false; -} - -function crash() { - gameOver('You crashed'); - play('crash'); - createCrash(playerShip); -} - -function gameOver(reason) { - if (state.editing) - endEditing(); - gameOverReason = reason; - state.gameOver = true; - state.inventory = false; - state.editing = false; - perspective.changeZoom(MIN_ZOOM, 0.99); - let massScore = playerShip.mass * 100; - let fuelScore = playerShip.fuel * 50 | 0; - let finalScore = massScore + fuelScore + score; - scoreText = 'Ship mass: ' + - ' '.repeat(5 - ('' + massScore).length) + massScore + '\n' + - 'Remaining fuel: ' + - ' '.repeat(5 - ('' + fuelScore).length) + fuelScore + '\n' + - 'Score: ' + - ' '.repeat(5 - ('' + score).length) + score + '\n\n' + - 'Final score: ' + - ' '.repeat(5 - ('' + finalScore).length) + finalScore; -} - -function toggleEdit() { - if (state.editing) { - endEditing(); - return; - } - state.editing = true; - state.inventory = true; - init$5(); -} - -function toggleTrace$1() { - let trace = toggleTrace(); - notify('Path prediction: ' + (trace ? 'on' : 'off')); -} - -function toggleMarkers$1() { - let markers = toggleMarkers(); - notify('Item markers: ' + (markers ? 'on' : 'off')); -} - -function cycleRotationMode$1() { - let message = { - parent: 'planet', - local: 'ship', - universe: 'universe' - }[cycleRotationMode()]; - - notify('Rotation view: ' + message); -} - -function endEditing() { - let {valid, reason} = end(); - - if (valid) { - play('endEdit'); - createEndEditBurst(playerShip); - changePerspective('universe'); - state.editing = false; - state.inventory = false; - } -} - -function tossItem() { - createItemToss(playerShip); - play('toss'); -} - -function collectItem(type, id, name) { - if (type === 'fuelcan') { - playerShip.addFuel(FUEL_CAN_AMOUNT); - play('fuelPickup'); - score += 10; - notify('Collected fuel: +10'); - return true; - } else { - if (usedSpace >= capacity) { - notify('No space left in inventory', 60); - return false; - } - addItem(type, id); - play('itemPickup'); - score += 20; - notify(`Collected "${name}" module: +20`, 150); - return true; - } -} - -class Tracer extends Body { - constructor(ship) { - super(...ship.pos, 0.1); - - this.ship = ship; - this.path = []; - } - - run(distance) { - this.path = []; - [this.x, this.y] = this.ship.com; - [this.xvel, this.yvel] = [this.ship.xvel, this.ship.yvel]; - let dis = 0; - let last = this.pos; - let factor = 5; - - for (let i = 0; dis < distance; i++) { - if (this.tickPath(factor)) break; - this.path.push(this.pos); - - if (i % 10 === 0) { - let [lx, ly] = last; - dis += Math.sqrt((this.x - lx) ** 2 + (this.y - ly) ** 2); - last = this.pos; - } - - if (i > distance * 5) break; - } - - [this.x, this.y] = this.ship.com; - } - - tick() { - this.run(this.ship.computation); - } - - tickPath(speed) { - this.tickMotion(speed); - this.tickGravity(celestials, speed); - return !!this.getCelestialCollision(celestials); - } -} - -class Ship extends Body { - constructor(x, y) { - super(x, y, 0); - - this.localCom = [0, 0]; - this.modules = new Set(); - this.maxRadius = 0; - this.landed = false; - this.lastContactModule = null; - this.poc = this.com; - this.maxFuel = 0; - this.fuel = 0; - this.rotationPower = 0; - this.cargoCapacity = 0; - this.thrust = 0; - this.computation = 0; - this.crashed = false; - this.timeWithoutFuel = 0; - } - - get com() { - let [lx, ly] = this.localCom; - return [this.x + lx, this.y + ly]; - } - - get parentCelestial() { - let closest = null; - let closestDistance = 0; - - celestials.forEach(c => { - let dis = this.distanceTo(c); - if (closest === null || dis < closestDistance) { - closest = c; - closestDistance = dis; - } - }); - - if (closestDistance > MAX_PARENT_CELESTIAL_DISTANCE) - return null; - - return closest; - } - - tick() { - if (this.crashed) return; - if (!state.editing) this.tickMotion(); - if (!this.landed) this.tickGravity(celestials); - if (!state.editing) this.resolveCollisions(); - - this.modules.forEach(m => { - if (m.type == 'thruster' && m.power !== 0) { - for (let i = 0; i < 2; i++) createThrustExhaust(m); - } - }); - - this.modules.forEach(m => m.reset()); - - if (shipLanded != this.landed) { - if (this.landed) { - landShip(this.parentCelestial); - } else { - launchShip(); - } - } - - if (this.fuel === 0 && !state.gameOver) { - if (this.timeWithoutFuel++ > 300) - outOfFuel(); - } else { - this.timeWithoutFuel = 0; - } - } - - clearModules() { - this.modules.clear(); - } - - addFuel(amount) { - this.fuel = Math.min(this.fuel + amount, this.maxFuel); - } - - addModule(x, y, properties, options) { - let module = new Module(x, y, this, {...properties, ...options}); - this.modules.add(module); - this.refreshShape(); - } - - deleteModule(module) { - this.modules.delete(module); - this.refreshShape(); - } - - refreshShape() { - let points = []; - this.modules.forEach(m => points.push([...m.localCom, m.mass])); - this.mass = points.reduce((a, [,,b]) => a + b, 0); - this.localCom = points.reduce(([ax, ay], [bx, by, bm]) => - [ax + bx * bm, ay + by * bm], [0, 0]) - .map(x => x / this.mass); - let [lx, ly] = this.localCom; - this.maxRadius = points.reduce((a, [bx, by]) => - Math.max(Math.sqrt((bx - lx) ** 2 + (by - ly) ** 2), a), 0) + 1; - - this.maxFuel = 0; - this.rotationPower = 0; - this.cargoCapacity = 0; - this.thrust = 0; - this.computation = 0; - - this.modules.forEach(m => { - if (m.type === 'fuel') { - this.maxFuel += m.data.fuelCapacity; - } else if (m.type === 'capsule') { - this.rotationPower += m.data.rotation; - this.cargoCapacity += m.data.capacity; - this.computation += m.data.computation; - } else if (m.type === 'thruster') { - this.thrust += m.data.thrust; - } else if (m.type === 'gyroscope') { - this.rotationPower += m.data.rotation; - } else if (m.type === 'cargo') { - this.cargoCapacity += m.data.capacity; - } else if (m.type === 'navigation') { - this.computation += m.data.computation; - } - }); - } - - colliding(point, radius) { - let [px, py] = point; - let result = false; - - this.modules.forEach(m => { - let [mx, my] = this.getWorldPoint(...m.localCom); - let dis = Math.sqrt((py - my) ** 2 + (px - mx) ** 2); - if (dis < radius) result = true; - }); - - return result; - } - - resolveCollisions() { - this.landed = false; - - celestials.forEach(c => { - let dis = this.distanceTo(c); - - if (dis < c.radius + this.maxRadius) { - this.modules.forEach(m => { - this.checkModuleCollision(m, c); - }); - } - }); - } - - checkModuleCollision(module, body) { - let p = this.getWorldPoint(...module.localCom); - let dis = body.distanceTo({ com: p }); - if (dis < body.radius + 0.5 + EPSILON) { - this.approach(body, dis - (body.radius + 0.5)); - this.moduleCollided(module); - this.halt(); - this.resolveCelestialCollision(p, body, module); - this.poc = p; - this.lastContactModule = module; - } - } - - moduleCollided(module) { - if (this.landed) return; - let speed = Math.sqrt(this.xvel ** 2 + this.yvel ** 2); - if (module.type !== 'thruster' || speed > CRASH_SPEED) { - crash(); - this.crashed = true; - } - } - - resolveCelestialCollision(pos, cel, module) { - let celToCom = this.angleTo(...this.com, ...cel.com); - let celToPoc = this.angleTo(...pos, ...cel.com); - let pocToCom = this.angleTo(...this.com, ...pos); - let shipAngle = this.normalizeAngle(this.r + Math.PI / 2); - - let turnAngle = this.angleDifference(celToPoc, pocToCom); - let checkAngle = this.angleDifference(celToPoc, shipAngle); - let correctionAngle = this.angleDifference(shipAngle, celToCom); - - let [force] = this.rotateVector(0, 1, turnAngle); - - if (Math.abs(checkAngle) < TIP_ANGLE) { - [force] = this.rotateVector(0, 1, correctionAngle); - force *= 0.2; - } - - let canLand = Math.abs(checkAngle) < 0.03 - && Math.abs(this.rvel) < 0.001; - - if (canLand) { - this.landed = true; - this.rvel = 0; - this.r = celToCom - Math.PI / 2; - } - - this.rvel += force * TIP_SPEED; - } - - applyThrust({ forward = 0, left = 0, right = 0, back = 0, - turnLeft = 0, turnRight = 0}) { - - let thrustForce = -forward * THRUST_POWER * this.thrust; - let turnForce = (turnRight - turnLeft) * TURN_POWER; - if (this.fuel <= 0) { - this.fuel = 0; - thrustForce = 0; - } else { - this.fuel -= Math.abs(thrustForce) * FUEL_BURN_RATE; - } - turnForce *= this.rotationPower; - - this.applyDirectionalForce(0, thrustForce, turnForce); - - if (Math.abs(this.rvel) > 0.1) { - this.rvel *= 0.7; - } - - this.modules.forEach(m => { - if (m.type !== 'thruster' || thrustForce == 0) return; - m.power += forward; - }); - } -} - -class Celestial extends Body { - constructor(x, y, radius, { - density = 1, - type = 'rock' - }) { - let mass = (radius ** 2) * density; - super(x, y, mass); - this.radius = radius; - - this.type = type; - const imageArr = Object.values(images.celestials[this.type]); - const svgImage = imageArr[Math.random() * imageArr.length | 0]; - tempCanvas.width = PLANET_IMAGE_SIZE; - tempCanvas.height = PLANET_IMAGE_SIZE; - tempContext.clearRect(0, 0, PLANET_IMAGE_SIZE, PLANET_IMAGE_SIZE); - tempContext.drawImage(svgImage, 0, 0, PLANET_IMAGE_SIZE, PLANET_IMAGE_SIZE); - this.image = new Image(); - this.image.src = tempCanvas.toDataURL(); - // this.image = tempContext.getImageData(0, 0, PLANET_IMAGE_SIZE, PLANET_IMAGE_SIZE); - } - - get com() { - return [this.x + this.radius, this.y + this.radius]; - } - - get escapeVelocity() { - - } - - tick() { - - } - - get diameter() { - return this.radius * 2; - } -} - -class Entity extends Body { - constructor(x, y, type = 'fuel', id = 'small', { - xvel = 0, - yvel = 0, - gravity = false - } = {}) { - super(x, y, 0.1); - this.xvel = xvel; - this.yvel = yvel; - this.width = 1; - this.height = 1; - this.radius = (this.width + this.height) / 2; - this.type = type; - this.id = id; - if (this.type === 'fuelcan') { - this.image = images.modules.fuelcan; - this.name = 'Fuel Can'; - } else { - this.image = images.modules[type][id]; - this.name = modules[type][id].name; - if (this.type === 'thruster') - this.image = this.image.off; - } - this.gravity = gravity; - this.touched = false; - } - - get com() { - return [this.x + this.width / 2, this.y + this.height / 2]; - } - - get localCom() { - return [this.width / 2, this.height / 2]; - } - - remove() { - entities.delete(this); - } - - tick() { - if (Math.abs(playerShip.x - this.x) > 500 || - Math.abs(playerShip.y - this.y) > 500) return; - this.r += ENTITY_ROTATION_RATE; - this.tickMotion(); - if (this.gravity) this.tickGravity(celestials); - let col = this.getCelestialCollision(celestials); - - if (col !== false) { - this.remove(); - } - - if (playerShip.colliding(this.com, this.radius)) { - if (this.touched) return; - this.touched = true; - let success = collectItem(this.type, this.id, this.name); - if (!success) return; - createPickupBurst(playerShip, this.com); - this.remove(); - } else { - this.touched = false; - } - } -} - -let spawnedSectors = new Map(); - -const visibleRadius = (400 / MIN_ZOOM) + SECTOR_SIZE; - -function tick$5() { - let [px, py] = playerShip.com; - - for (let x = px - visibleRadius; x < px + visibleRadius; x += SECTOR_SIZE) - for (let y = py - visibleRadius; y < py + visibleRadius; y += SECTOR_SIZE) { - let [sx, sy] = [x / SECTOR_SIZE | 0, y / SECTOR_SIZE | 0]; - let id = `${sx}.${sy}`; - if (!spawnedSectors.has(id)) spawnSector(sx, sy); - } - - spawnedSectors.forEach((objects, key) => { - let [sx, sy] = key.split('.'); - let [wx, wy] = [sx * SECTOR_SIZE, sy * SECTOR_SIZE]; - let dis = (wx - px) ** 2 + (wy - py) ** 2; - if (dis > (SECTOR_SIZE * 4) ** 2) { - spawnedSectors.delete(key); - objects.forEach(remove); - } - }); -} - -function nearest(x, y, set) { - let closest = null; - let closestDis = 0; - - set.forEach(e => { - let dis = e.distanceTo({ com: [x, y] }); - if (closest === null || dis < closestDis) { - closest = e; - closestDis = dis; - } - }); - - return [closest, closestDis]; -} - -function spawnSector(x, y) { - let area = SECTOR_SIZE ** 2; - let spawned = new Set(); - - for (let i = 0; i < area / 1000; i++) { - let [px, py] = [(x + Math.random()) * SECTOR_SIZE, (y + Math.random()) * SECTOR_SIZE]; - if (Math.random() < PLANET_SPAWN_RATE / 1000) { - spawned.add(randomPlanet(px, py)); - } else if (Math.random() < ENTITY_SPAWN_RATE / 1000){ - spawned.add(randomEntity(px, py)); - } - } - - spawnedSectors.set(`${x}.${y}`, spawned); -} - -function randomPlanet(x, y, { - radius = Math.random() * 60 + 30, - type = 'green', - density = 3 - } = {}) { - - let [cel, dis] = nearest(x, y, celestials); - let mcs = MIN_CELESTIAL_SPACING; - - if (cel !== null && dis < Math.max(radius, cel.radius) * mcs) return; - - let planet = celestial$1(x, y, radius, { - density: density, - type: type - }); - - for (let i = 0.1; i < 10; i += 0.5) { - if (Math.random() > 0.95) { - let e = randomEntity(); - e.orbit(planet, i * radius, Math.random() * Math.PI * 2); - } - } - - for (let i = 0; i < 10; i++) { - if (Math.random() > 0.7) { - let e = randomEntity(); - e.orbit(planet, 1.5, Math.random() * Math.PI * 2); - e.gravity = false; - e.halt(); - } - } - - return planet; -} - -function randomEntity(x, y) { - let entity; - - if (Math.random() > 0.5) { - entity = new Entity(x, y, 'fuelcan'); - } else { - let type, id; - while (true) { - let arr = Object.entries(modules); - [type, arr] = arr[Math.random() * arr.length | 0]; - arr = Object.keys(arr); - id = arr[Math.random() * arr.length | 0]; - let value = modules[type][id].value; - if (Math.random() < (1 / value)) break; - } - entity = new Entity(x, y, type, id); - } - - entities.add(entity); - return entity; -} - -function player() { - let ship = new Ship(0, -45); - ship.addModule(0, 0, modules.capsule.small); - ship.addModule(0, 1, modules.fuel.small); - ship.addModule(0, 2, modules.thruster.light); - //ship.addModule(1, 2, modules.thruster.light); - //ship.addModule(-1, 2, modules.thruster.light); - ships.add(ship); - setPlayerShip(ship); - ship.addFuel(ship.maxFuel); - - let tracer = new Tracer(ship); - tracers.add(tracer); - - return ship; -} - -function startPlanet() { - let planet = randomPlanet(0, 0, { - radius: 40, - density: 3, - type: 'green' - }); - let fuel = new Entity(0, 0, 'fuelcan'); - entities.add(fuel); - fuel.orbit(planet, 10, -0.5); - return planet; -} - -function celestial$1(x, y, radius, params) { - let celestial = new Celestial(x - radius, y - radius, radius, params); - celestials.add(celestial); - return celestial; -} - -const entities = new Set(); -const celestials = new Set(); -const ships = new Set(); -const particles = new Set(); -const tracers = new Set(); - -let playerShip = null; - -let speed = 1; - -function setPlayerShip(ship) { - playerShip = ship; -} - -function init$6() { - clear(); - player(); - startPlanet(); - tick$5(); -} - -function clear() { - entities.clear(); - celestials.clear(); - ships.clear(); - particles.clear(); - tracers.clear(); -} - -function remove(object) { - entities.delete(object); - celestials.delete(object); -} - -function increaseSpeed() { - if (speed < 5) speed += 1; -} - -function decreaseSpeed() { - if (speed > 1) speed -= 1; -} - -function tick$4() { - for (let i = 0; i < speed; i++) { - particles.forEach(p => p.tick()); - celestials.forEach(c => c.tick()); - entities.forEach(e => e.tick()); - ships.forEach(s => s.tick()); - } - - tick$5(); - if (trace) tracers.forEach(t => t.tick()); -} - -let tiles = new Map(); -let width = 0; -let height = 0; -let position = [0, 0]; -let message = ''; -let info = ''; - -function init$5() { - let ship = playerShip; - let modules = ship.modules; - - tiles.clear(); - - modules.forEach(m => { - let pos = [m.x, m.y]; - tiles.set(posId(...pos), new Tile(...pos, m)); - }); - - message = ''; - adjustSize(); - - adjustGraphics(); -} - -function adjustGraphics() { - let neededZoom = canvas.width / (Math.max(width, height) + 10); - changePerspective('planet', 0, -3); - setZoom(neededZoom); -} - -function adjustSize() { - let margin = EDIT_MARGIN; - let [sx, ex, sy, ey] = getBoundaries(); - [width, height] = [ex - sx + margin * 2 + 1, ey - sy + margin * 2 + 1]; - position = [sx - margin, sy - margin]; - getAttributes(); -} - -function end() { - let result = validate(); - - result = { - valid: result === false, - reason: result - }; - - if (result.valid) { - let ship = playerShip; - let [ox, oy] = ship.com; - ship.clearModules(); - tiles.forEach(t => { - if (t.type === null) return; - ship.addModule(t.x, t.y, modules[t.type][t.id]); - }); - let [nx, ny] = ship.com; - let [dx, dy] = [nx - ox, ny - oy]; - ship.x -= dx; - ship.y -= dy; - const [rdx, rdy] = ship.rotateVector(dx, dy); - ship.x -= rdx; - ship.y -= rdy; - } - - return result; -} - -function getAttributes() { - let cargo = 0; - let fuel = 0; - let rotation = 0; - let mass = 0; - let thrust = 0; - let computation = 0; - - tiles.forEach(t => { - if (t.type === null) return; - if (t.type === 'fuel') { - fuel += t.module.fuelCapacity; - } else if (t.type === 'capsule') { - rotation += t.module.rotation; - cargo += t.module.capacity; - computation += t.module.computation; - } else if (t.type === 'thruster') { - thrust += t.module.thrust; - } else if (t.type === 'gyroscope') { - rotation += t.module.rotation; - } else if (t.type === 'cargo') { - cargo += t.module.capacity; - } else if (t.type === 'nafivation') { - computation += t.module.computation; - } - mass += t.module.mass; - }); - - info = 'Mass: ' + mass + '\n' + - 'Fuel capacity: ' + fuel + '\n' + - 'Thrust/mass ratio: ' + (thrust / Math.max(mass, 1)).toFixed(1) + '\n' + - 'Rotation speed: ' + (rotation / Math.max(mass, 1) * 100).toFixed(1) - + '\n' + - 'Cargo capacity: ' + cargo + '\n' + - 'Navigational computation: ' + computation; -} - -function validate() { - let capsulesFound = 0; - let thrustersFound = 0; - let fuelFound = 0; - let unvisited = new Set(); - - tiles.forEach(t => { - if (t.type !== null) unvisited.add(t); - }); - - let reason = ''; - - if (unvisited.size == 0) { - reason = 'no capsule'; - } - - let visit = (tile) => { - unvisited.delete(tile); - if (tile.type == 'capsule') capsulesFound++; - if (tile.type == 'thruster') thrustersFound++; - if (tile.type == 'fuel') fuelFound++; - tile.neighbours.forEach(n => { - if (unvisited.has(n) && n.neighbours.indexOf(tile) > -1) { - visit(n); - } - }); - }; - - if (unvisited.size > 0) - visit(unvisited.values().next().value); - - if (unvisited.size > 0) { - reason = 'not connected'; - } else if (capsulesFound === 0) { - reason = 'no capsule'; - } else if (thrustersFound === 0) { - reason = 'no thruster'; - } else if (fuelFound === 0) { - reason = 'no fuel tank'; - } else if (usedSpace > capacity) { - reason = 'inventory too full'; - } else { - reason = false; - } - - if (reason === false) { - message = ''; - } else { - message = reason; - } - - return reason; -} - -function positionAdjust(x, y) { - let [px, py] = position; - return [x + px, y + py]; -} - -function clickTile(x, y) { - if (currentItem === null) return; - let current = getTile(x, y).source; - if (current.type !== null) { - return; - } - - let pos = positionAdjust(x, y); - let id = posId(...pos); - tiles.set(id, new Tile(...pos, currentItem)); - removeItem(...currentItem.ident); - adjustSize(); - validate(); -} - -function rightClickTile(x, y) { - let current = getTile(x, y).source; - if (current.type === null) return; - let { x: tx, y: ty } = current; - let id = posId(tx, ty); - tiles.set(id, new Tile(tx, ty, null)); - addItem(current.type, current.id); - adjustSize(); - validate(); -} - -function getTile(x, y) { - let [px, py] = position; - return getRawTile(px + x, py + y); -} - -function getRawTile(x, y) { - let id = posId(x, y); - if (!tiles.has(id)) - tiles.set(id, new Tile(x, y, null)); - return tiles.get(id); - // TODO: Get linked tiles. -} - -function posId(x, y) { - return `${x}.${y}`; -} - -function getBoundaries() { - let sx = sy = ex = ey = null; - tiles.forEach(t => { - if (t.type === null) return; - if (sx === null || t.x < sx) sx = t.x; - if (ex === null || t.x > ex) ex = t.x; - if (sy === null || t.y < sy) sy = t.y; - if (ey === null || t.y > ey) ey = t.y; - }); - return [sx, ex, sy, ey]; -} - -class Tile { - constructor(x, y, module) { - if (module === null) { - this.module = null; - this.image = null; - this.type = null; - this.id = null; - } else { - ({type: this.type, id: this.id} = module); - this.module = modules[this.type][this.id]; - this.image = images.modules[this.type][this.id]; - if (module.type === 'thruster') this.image = this.image.off; - } - this.x = x; - this.y = y; - } - - get valid() { - return true; - } - - get neighbours() { - return [[0, -1], [1, 0], [0, 1], [-1, 0]].filter((_, i) => { - return this.module.connectivity[i]; - }).map(([dx, dy]) => getRawTile(this.x + dx, this.y + dy)); - } - - get source() { - return this; - } - - get drawPos() { - let [px, py] = pos; - return [this.x + px, this.y + py]; - } -} - -const mouse = { pressed: {}, held: {}, x: 0, y: 0, scroll: 0 }; -const keyCode = { pressed: {}, held: {} }; -const key = { pressed: {}, held: {} }; - -function tick$3() { - mouse.pressed = {}; - keyCode.pressed = {}; - key.pressed = {}; - mouse.scroll = 0; -} - -function init$4() { - window.addEventListener('keydown', event => { - keyCode.pressed[event.code] = !keyCode.held[event.code]; - keyCode.held[event.code] = true; - key.pressed[event.key] = !keyCode.held[event.key]; - key.held[event.key] = true; - }); - - window.addEventListener('keyup', event => { - keyCode.held[event.code] = false; - key.held[event.key] = false; - }); - // Ṕ͕͖ẖ̨’̖̺͓̪̹n̼͇͔̯̝̖g̙̩̭͕ͅͅl̻̰͘u͎̥͍̗ͅi̼̞̪̩͚̜͖ ̫̝̻͚͟m͎̳̙̭̩̩̕g̟̤̬̮l̫̕w̶͚͈͚̟͔’͖n͏̝͖̞̺ͅa͏̹͓̬̺f̗̬̬̬̖̫͜h͙̘̝̱̬̗͜ ̼͎͖C̱͔̱͖ṭ̬̱͖h̵̰̼̘̩ùlh̙́u̪̫ ̪̺̹̙̯R̞͓̹̞’͍͎͉͎̦͙ͅl͇̠̮y̙̪̰̪͙̖e̢̩͉͙̼h̗͔̹̳ ̶w̨̼͍̝̭̣̣ͅg̶̳̦̳a̴͉̹͙̭̟ͅh͈͎̞̜͉́’̼̜̠͞n̲a̯g̮͚͓̝l̠ ̹̹̱͙̝f̧̝͖̱h̪̟̻͖̖t͎͘aͅg̤̘͜n̶͈̻̻̝̳ - window.addEventListener('mousedown', event => { - mouse.pressed[event.button] = !mouse.held[event.button]; - mouse.held[event.button] = true; - tickAfterMouse = false; - }); - - window.addEventListener('mouseup', event => { - mouse.held[event.button] = false; - }); - - window.addEventListener('mousemove', event => { - let rect = canvas.getBoundingClientRect(); - mouse.x = event.clientX - rect.left; - mouse.y = event.clientY - rect.top; - }); - - window.addEventListener('wheel', event => { - mouse.scroll = event.deltaY; - // event.preventDefault(); - }); - - window.addEventListener('contextmenu', event => { - event.preventDefault(); - }); -} - -class Rect { - constructor(x = 0, y = 0, w = 0, h = 0) { - this.x = x; - this.y = y; - this.w = w; - this.h = h; - - this.onclick = null; - this.mouseHeld = false; - - this.rightMouseHeld = false; - this.onRightClick = null; - } - - click() {} - - rightClick() {} - - tickMouse() { - if (this.mouseHeld == true && !mouse.held[0] && this.mouseOver) - this.click(); - if (!this.mouseHeld && mouse.pressed[0] && this.mouseOver) - this.mouseHeld = true; - if (!mouse.held[0]) - this.mouseHeld = false; - - if (this.rightMouseHeld == true && !mouse.held[2] - &&this.mouseOver) - this.rightClick(); - if (!this.rightMouseHeld && mouse.pressed[2] && this.mouseOver) - this.rightMouseHeld = true; - if (!mouse.held[2]) - this.rightMouseHeld = false; - } - - get shape() { - return [this.x, this.y, this.w, this.h]; - } - - get end() { - return [this.x + this.w, this.y + this.h]; - } - - get center() { - return [this.x + this.w / 2, this.y + this.h / 2]; - } - - get mouseOver() { - return this.containsPoint(mouse.x, mouse.y); - } - - get mouseClicked() { - return this.mouseOver() && mouse.pressed[0]; - } - - containsPoint(x, y) { - return x > this.x && x < this.x + this.w - && y > this.y && y < this.y + this.h; - } -} - -const defaultOptions = { - draw: true, // Whether the element itself will be rendered. - drawChildren: true // Whether children will be rendered. -}; - -class GuiElement extends Rect { - constructor(x, y, w, h, options = {}) { - super(x, y, w, h); - this.children = new Set(); - this.parent = null; - - this.type = 'element'; - - this.options = Object.assign({}, defaultOptions, options); - } - - tickElement() { - this.tickMouse(); - this.tick(); - this.children.forEach(c => c.tickElement()); - } - - tick() { - - } - - get drawn() { - if (!this.options.drawChildren) return false; - if (!this.parent) return true; - return this.parent.drawn; - } - - append(element) { - this.children.add(element); - element.parent = this; - element.x += this.x; - element.y += this.y; - } - - clear() { - this.children.clear(); - } - // Code should be self-describing, comments are for fucking about. - // - Albert Einstein - - posRelative({x = null, xc = 0, y = null, yc = 0, w = null, h = null}) { - if (x !== null) - this.x = (this.parent.w * x) - (this.w * xc) + this.parent.x; - if (y !== null) - this.y = (this.parent.h * y) - (this.h * yc) + this.parent.y; - if (w !== null) - this.w = this.parent.w * w; - if (h !== null) - this.h = this.parent.h * h; - } -} - -class GuiFrame extends GuiElement { - constructor(x, y, w, h, options) { - super(x, y, w, h, options); - this.type = 'frame'; - } -} - -class GuiImage extends GuiElement { - constructor(src, x, y, w, h) { - w = w || src.width; - h = h || src.height; - super(x, y, w, h); - this.type = 'image'; - this.image = src; - this.imgRatio = src.width / src.height; - } - - scaleImage({ w = null, h = null }) { - if (w !== null && h === null) { - this.w = w; - this.h = w / this.imgRatio; - } else if (h !== null && w === null) { - this.h = h; - this.w = h / this.imgRatio; - } else if ( h !== null && w !== null) { - this.w = w; - this.h = h; - } - } -} - -class GuiButton extends GuiElement { - constructor(text, onclick, x, y, w = 100, h = 30) { - super(x, y, w, h); - this.type = 'button'; - this.text = text; - this.onclick = onclick; - } - - click() { - if (this.drawn && !this.options.disabled) - this.onclick(); - } -} - -class GuiItemButton extends GuiButton { - constructor(tile, onclick, x, y, w = 50, h = 50, { - padding = 0, - selected = false, - quantity = 1, - } = {}) { - super(null, onclick, x, y, w, h); - this.module = tile.module; - this.image = tile.image; - this.type = 'itemButton'; - this.padding = padding; - this.selected = selected; - this.quantity = quantity; - } - - click() { - if (this.drawn) - this.onclick('left'); - } - - rightClick() { - if (this.drawn) - this.onclick('right'); - } -} - -class GuiInventory extends GuiElement { - constructor(x, y, w = 100, h = 30) { - super(x, y, w, h); - this.type = 'inventory'; - this.tileWidth = 4; - this.tileHeight = 5; - this.currentPage = 0; - setOnupdate(this.updateTiles.bind(this)); - this.guiInfo = null; - } - - updateTiles() { - this.children.clear(); - - let tileRatio = this.tileWidth / this.tileHeight; - let rectRatio = this.w / this.h; - let tileSize; - let [ox, oy] = [0, 0]; - - if (tileRatio < rectRatio) { - tileSize = this.h / this.tileHeight; - ox = (this.w - (tileSize * this.tileWidth)) / 2; - } else { - tileSize = this.w / this.tileWidth; - oy = (this.h - (tileSize * this.tileHeight)) / 2; - } - - let spacing = 0.15 * tileSize; - let pageSize = this.tileWidth * this.tileHeight; - let offset = pageSize * this.currentPage; - let tiles = getTiles().slice(offset); - let tile; - let cur = currentItem; - - for (let y = 0; y < this.tileHeight; y++) - for (let x = 0; x < this.tileWidth && tiles.length; x++) { - y * this.tileWidth + (x % this.tileWidth) + offset; - tile = tiles.shift(); - - let ex = x * tileSize + spacing / 2 + ox; - let ey = y * tileSize + spacing / 2 + oy; - let [ew, eh] = [tileSize - spacing, tileSize - spacing]; - - let ident = tile.ident; - - let onclick = (button) => { - this.tileClicked(...ident, button); - }; - - let selected = cur !== null && tile.type === cur.type - && tile.id === cur.id; - - let el = new GuiItemButton(tile, onclick, ex, ey, ew, eh, { - padding: 0.1, - selected: selected, - quantity: tile.quantity - }); - - this.append(el); - } - - this.guiInfo.text = cur === null ? '' : cur.textInfo; - this.guiInfo.splitLines(); - } - - tick() { - if (state.inventory && !this.active) this.updateTiles(); - this.active = state.inventory; - this.parent.options.drawChildren = this.active; - if (!this.active) return; - - this.children; - } - - getTile(x, y) { - return this.getTile(x + px, y + py); - } - - tileClicked(type, id, button) { - if (button == 'left') selectItem(type, id); - - if (button == 'right') { - if (canToss()) { - removeItem(type, id); - } - } - - this.updateTiles(); - } -} - -class GuiEdit extends GuiElement { - constructor(x, y, w = 100, h = 30) { - super(x, y, w, h); - this.type = 'edit'; - this.tileWidth = 0; - this.tileHeight = 0; - this.active = false; - this.guiInventory = null; - } - - updateTiles() { - this.children.clear(); - - [this.tileWidth, this.tileHeight] = [width, height]; - - let tileRatio = this.tileWidth / this.tileHeight; - let rectRatio = this.w / this.h; - let tileSize; - let [ox, oy] = [0, 0]; - - if (tileRatio < rectRatio) { - tileSize = this.h / this.tileHeight; - ox = (this.w - (tileSize * this.tileWidth)) / 2; - } else { - tileSize = this.w / this.tileWidth; - oy = (this.h - (tileSize * this.tileHeight)) / 2; - } - - let spacing = 0.1 * tileSize; - - for (let x = 0; x < this.tileWidth; x++) - for (let y = 0; y < this.tileHeight; y++) { - let tile = getTile(x, y); - let ex = x * tileSize + spacing / 2 + ox; - let ey = y * tileSize + spacing / 2 + oy; - let [ew, eh] = [tileSize - spacing, tileSize - spacing]; - - let onclick = (button) => { - this.tileClicked(x, y, button); - }; - - let el = new GuiItemButton(tile, onclick, ex, ey, ew, eh); - this.append(el); - } - } - - tick() { - if (state.editing && !this.active) this.updateTiles(); - this.active = state.editing; - this.parent.options.drawChildren = this.active; - if (!this.active) return; - - this.textElements.info.text = info; - - [this.tileWidth, this.tileHeight] = [width, height]; - } - - getTile(x, y) { - let [px, py] = position; - return getTile(x + px, y + py); - } - - tileClicked(x, y, button) { - if (button == 'left') { - clickTile(x, y); - } else if (button == 'right') { - rightClickTile(x, y); - } - - this.updateTiles(); - this.guiInventory.updateTiles(); - } -} - -class GuiText extends GuiElement { - constructor(text = '', x, y, w, h, { - size = 10, - align = 'left', - valign = 'top', - color = '#fff' - } = {}) { - w = w; - h = h; - super(x, y, w, h); - this.type = 'text'; - this.color = color; - this.text = text; - this.spacing = size * 1.2; - this.font = size + 'px Courier New'; - this.align = align; - this.valign = valign; - } - - splitLines() { - // Not very robust, but good enough for now. Mebe fix later? - context.font = this.font; - let maxLineLength = (this.w / context.measureText('A').width) | 0; - maxLineLength = Math.max(maxLineLength, 1); - - let lines = []; - let currentLine = ''; - - this.text.split('\n').forEach(l => { - currentLine = ''; - l.split(' ').forEach(word => { - if (word.length + currentLine.length > maxLineLength) { - lines.push(currentLine.slice(0, -1)); - currentLine = ''; - } - currentLine += word + ' '; - }); - lines.push(currentLine.slice(0, -1)); - }); - - this.text = lines.join('\n'); - } -} - -function root$1() { - return new GuiFrame(0, 0, canvas.width, canvas.height, { - draw: false - }); -} - -function title() { - let shadow = root$1(); - let logo = new GuiImage(images.title.logo); - shadow.append(logo); - logo.scaleImage({ w: shadow.w * 0.7 }); - logo.posRelative({ x: 0.5, xc: 0.5, y: 0.2 }); - let start = new GuiButton('Start game', startGame, 0, 0, 200); - shadow.append(start); - start.posRelative({ x: 0.5, xc: 0.5, y: 1 }); - start.y -= 160; - - let secondFunction = () => {}; - let second = new GuiButton('Don\'t start game', secondFunction, 0, 0, 200); - shadow.append(second); - second.posRelative({ x: 0.5, xc: 0.5, y: 1 }); - second.y -= 110; - - let thirdFunction = howToPlay; - let third = new GuiButton('How to play', thirdFunction, 0, 0, 200); - shadow.append(third); - third.posRelative({ x: 0.5, xc: 0.5, y: 1 }); - third.y -= 60; - - return shadow; -} - -const instructionText = `\ -Flight controls - -WAD: Movement -Shift + WAD: Fine movement -E: Open/close inventory -R: Toggle item markers -T: Toggle path prediction -P: Pause/unpause -M: Toggle music - - -Ship editing and inventory controls - -Left click: Select module in inventory -Right click: Toss away module in inventory -Left click: Place module on ship -Right click: Remove module from ship -Escape: Exit ship editing screen - - -Fly around collecting modules and fuel, and land to build your ship using \ -those collected modules. Get the highest score possible without crashing or \ -running out of fuel. -`; - -function instructions() { - let shadow = root$1(); - - let frame = new GuiFrame(); - shadow.append(frame); - frame.posRelative({x: 0.1, y: 0.1, w: 0.8, h: 0.8}); - - let back = new GuiButton('Return to menu', toMenu, 0, 0, 200); - frame.append(back); - back.posRelative({ x: 0.5, xc: 0.5, y: 1 }); - back.y -= 60; - - let text = new GuiText(instructionText, 0, 0, 0, 0, { - size: 12, - align: 'left', - valign: 'top' - }); - frame.append(text); - text.posRelative({x: 0.05, y: 0.05, w: 0.9, h: 0.9}); - text.splitLines(); - - return shadow; -} - -function game() { - let shadow = root$1(); - - let editButton = new GuiButton('Edit rocket', toggleEdit, 0, 0, 200); - shadow.append(editButton); - editButton.posRelative({ x: 0.5, xc: 0.5, y: 1 }); - editButton.y -= 45; - editButton.tick = () => { - let usable = state.landed && !state.gameOver; - editButton.options.draw = usable; - editButton.options.disabled = usable && message !== ''; - if (state.editing) { - editButton.text = 'Finish'; - if (message !== '') editButton.text = '(' + message + ')'; - } else { - editButton.text = 'Edit rocket'; - } - }; - - let fuel = new GuiText('', 0, 0, 0, 0, { - size: 14, - align: 'right', - valign: 'bottom' - }); - shadow.append(fuel); - fuel.posRelative({x: 1, y: 1}); - fuel.y -= 10; - fuel.x -= 10; - fuel.tick = () => { - let ship = playerShip; - fuel.text = 'Fuel: ' + ship.fuel.toFixed(1) + '/' + - ship.maxFuel.toFixed(1); - }; - - let speed$1 = new GuiText('', 0, 0, 0, 0, { - size: 14, - align: 'right', - valign: 'bottom', - - }); - shadow.append(speed$1); - speed$1.posRelative({x: 1, y: 1}); - speed$1.y -= 30; - speed$1.x -= 10; - speed$1.tick = () => { - speed$1.text = 'Speed: ' + speed.toFixed(1) + 'x'; - }; - - let score$1 = new GuiText('', 0, 0, 0, 0, { - size: 14, - align: 'left', - valign: 'bottom' - }); - shadow.append(score$1); - score$1.posRelative({x: 0, y: 1}); - score$1.y -= 10; - score$1.x += 10; - score$1.tick = () => { - score$1.text = 'Score: ' + score; - }; - - - let editShadow = root$1(); - shadow.append(editShadow); - editShadow.posRelative({x: 0.45, y: 0, w: 0.55, h: 0.6}); - editShadow.x -= 10; - editShadow.y += 10; - - let edit = new GuiEdit(0, 0, 0, 0); - editShadow.append(edit); - edit.posRelative({w: 1, h: 1}); - - let editInfoText = new GuiText('', 0, 0, 0, 0, { - size: 12, - align: 'right' - }); - editShadow.append(editInfoText); - editInfoText.posRelative({x: 1, y: 1}); - editInfoText.y += 5; - editInfoText.x -= 20; - - let editWarnText = new GuiText('', 0, 0, 0, 0, { - size: 12, - align: 'center' - }); - editShadow.append(editWarnText); - editWarnText.posRelative({x: 0.5, y: 1}); - editWarnText.y += 20; - - edit.textElements = { - info: editInfoText, - warn: editWarnText - }; - - - let invShadow = root$1(); - shadow.append(invShadow); - invShadow.posRelative({x: 0, w: 0.4, h: 0.6}); - invShadow.x += 10; - invShadow.y += 10; - - let invElement = new GuiInventory(0, 0, 0, 0); - invShadow.append(invElement); - invElement.posRelative({w: 1, h: 0.8}); - - let capacityInfo = new GuiText('', 0, 0, 0, 0, { - size: 12, - align: 'left', - valign: 'bottom' - }); - invShadow.append(capacityInfo); - capacityInfo.posRelative({x: 0, y: 1}); - capacityInfo.y -= 5; - capacityInfo.x += 5; - capacityInfo.tick = () => { - capacityInfo.text = 'Space used: ' + usedSpace + ' / ' + - capacity; - }; - - let moduleInfo = new GuiText('', 0, 0, 0, 0, { - size: 12, - align: 'left', - valign: 'top' - }); - invShadow.append(moduleInfo); - moduleInfo.posRelative({x: 0, y: 1, w: 1}); - moduleInfo.splitLines(); - moduleInfo.y += 5; - invElement.guiInfo = moduleInfo; - - edit.guiInventory = invElement; - - - let notification = new GuiText('', 0, 0, 0, 0, { - size: 14, - align: 'center', - valign: 'top' - }); - shadow.append(notification); - notification.posRelative({x: 0.5}); - notification.y += 10; - setNotificationElement(notification); - - - let gameOver = root$1(); - shadow.append(gameOver); - gameOver.posRelative({x: 0.2, y: 0.2, w: 0.6, h: 0.6}); - - let gameOverMain = new GuiText('Game over', 0, 0, 0, 0, { - size: 48, - align: 'center', - valign: 'top' - }); - gameOver.append(gameOverMain); - gameOverMain.posRelative({x: 0.5}); - gameOverMain.y += 10; - gameOver.tick = () => { - gameOver.options.drawChildren = state.gameOver; - }; - - let gameOverReason$1 = new GuiText('', 0, 0, 0, 0, { - size: 14, - align: 'center', - valign: 'top' - }); - gameOver.append(gameOverReason$1); - gameOverReason$1.posRelative({x: 0.5}); - gameOverReason$1.y += 100; - gameOverReason$1.tick = () => { - gameOverReason$1.text = gameOverReason; - }; - - let gameOverScore = new GuiText('', 0, 0, 0, 0, { - size: 14, - align: 'center', - valign: 'top' - }); - gameOver.append(gameOverScore); - gameOverScore.posRelative({x: 0.5}); - gameOverScore.y += 200; - gameOverScore.tick = () => { - gameOverScore.text = scoreText; - }; - - let gameOverExit = new GuiButton('Main menu', toMenu, 0, 0, 200); - gameOver.append(gameOverExit); - gameOverExit.posRelative({ x: 0.5, xc: 0.5, y: 1 }); - gameOverExit.y -= 10; - - return shadow; -} - -const elements = new Set(); -let root; - -function init$3() { - elements.clear(); - root = root$1(); - changeView$1('menu'); -} - -function tick$2() { - root.tickElement(); -} - -function changeView$1(view) { - root.clear(); - - if (view === 'menu') { - root.append(title()); - } - - if (view === 'game') { - root.append(game()); - } - - if (view === 'instructions') { - root.append(instructions()); - } -} - -function render$3() { - renderElement(root); -} - -function renderElement(element) { - if (element.options.draw) { - if (element.type === 'frame') renderFrame(element); - if (element.type === 'image') renderImage(element); - if (element.type === 'button') renderButton(element); - if (element.type === 'edit') ; - if (element.type === 'itemButton') renderItemButton(element); - if (element.type === 'inventory') renderInventory(element); - if (element.type === 'text') renderText(element); - } - - if (element.options.drawChildren) - element.children.forEach(renderElement); -} - -function renderFrame(element) { - context.fillStyle = '#a3977c'; - context.fillRect(...element.shape); - context.lineWidth = 3; - context.strokeStyle = '#6d634b'; - context.strokeRect(...element.shape); -} - -function renderImage(element) { - context.drawImage(element.image, ...element.shape); -} - -function renderText(element) { - context.font = element.font; - context.textAlign = element.align; - context.textBaseline = element.valign; - context.fillStyle = element.color; - element.text.split('\n').forEach((line, i) => - context.fillText(line, element.x, element.y + i * element.spacing) - ); -} - -function renderButton(element) { - if (element.mouseHeld && !element.options.disabled) { - context.fillStyle = '#706244'; - } else if (element.mouseOver && !element.options.disabled) { - context.fillStyle = '#ad9869'; - } else { - context.fillStyle = '#917f58'; - } - - if (element.options.disabled) { - context.globalAlpha = 0.5; - } - - let [sx, sy, w, h] = element.shape; - let [ex, ey] = [sx + w, sy + h]; - let rad = 5; - - context.beginPath(); - context.moveTo(sx + rad, sy); - context.lineTo(ex - rad, sy); - context.quadraticCurveTo(ex, sy, ex, sy + rad); - context.lineTo(ex, ey - rad); - context.quadraticCurveTo(ex, ey, ex - rad, ey); - context.lineTo(sx + rad, ey); - context.quadraticCurveTo(sx, ey, sx, ey - rad); - context.lineTo(sx, sy + rad); - context.quadraticCurveTo(sx, sy, sx + rad, sy); - context.closePath(); - - context.fill(); - context.strokeStyle = '#541'; - context.lineWidth = 2; - context.stroke(); - - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.fillStyle = '#fff'; - context.font = '12pt Courier New'; - context.fillText(element.text, ...element.center); - - context.globalAlpha = 1; -} - -function renderItemButton(element) { - context.globalAlpha = 0.5; - if (element.mouseHeld || element.rightMouseHeld) { - context.fillStyle = '#080808'; - } else { - context.fillStyle = element.mouseOver ? '#222' : '#0e0e0e'; - } - - context.fillRect(...element.shape); - if (element.selected) { - context.strokeStyle = '#fff'; - context.lineWidth = 2; - } else { - context.strokeStyle = '#333'; - context.lineWidth = 1; - } - context.strokeRect(...element.shape); - context.globalAlpha = 1; - - if (element.image) { - let p = element.padding; - let ox = element.x + (p / 2 * element.w); - let oy = element.y + (p / 2 * element.h); - let [dw, dh] = [element.w * (1 - p), element.h * (1 - p)]; - context.drawImage(element.image, ox, oy, dw, dh); - } - - if (element.quantity > 1) { - context.textAlign = 'right'; - context.textBaseline = 'bottom'; - context.fillStyle = '#fff'; - context.font = 'bold 10pt Courier New'; - let [ex, ey] = element.end; - context.fillText('x' + element.quantity, ex - 2, ey - 2); - } -} - -function renderInventory(element) { - context.globalAlpha = 0.1; - context.fillStyle = '#541'; - context.fillRect(...element.parent.shape); - context.globalAlpha = 1; -} - -function render$2() { - for (particle of particles) renderParticle(particle); - for (celestial of celestials) if (isVisible(celestial)) renderCelestial(celestial); - if (trace) for (tracer of tracers) renderTracer(tracer); - for (ship of ships) renderShip(ship); - for (entity of entities) if (isVisible(entity)) renderEntity(entity); - - /* - if (typeof window.q === 'undefined') window.q = []; - q.forEach(p => { - context.fillStyle = p[2]; - context.fillRect(p[0] - 0.05, p[1] - 0.05, 0.1, 0.1); - }); - */ -} - -function isVisible(body) { - const [bx, by] = body.com; - const [px, py] = [perspective.x, perspective.y]; - perspective.bounds; - const [centerX, centerY] = [px, py]; - const margin = 1000; - return bx > centerX - margin && bx < centerX + margin && - by > centerY - margin && by < centerY + margin; -} - -function renderParticle(particle) { - context.fillStyle = particle.color; - context.fillRect(...particle.com, particle.size, particle.size); -} - -function renderEntity(entity) { - context.save(); - context.translate(...entity.com); - let alpha = Math.max(1 - ((perspective.zoom - 1) / 2), 0) ** 2; - if (alpha > 0 && markers) { - context.globalAlpha = alpha; - context.beginPath(); - context.arc(0, 0, 4, 0, 2 * Math.PI); - context.lineWidth = 1; - context.strokeStyle = '#31911b'; - if (entity.type === 'fuelcan') - context.strokeStyle = '#af4021'; - context.stroke(); - context.globalAlpha = 1; - } - context.rotate(entity.r); - context.drawImage(entity.image, -0.5, -0.5, 1, 1); - context.restore(); -} - -function renderShip(ship) { - if (ship.crashed) return; - context.save(); - context.translate(...ship.com); - context.rotate(ship.r); - let [cx, cy] = ship.localCom; - context.translate(-cx, -cy); - ship.modules.forEach(m => { - [m.x, m.y]; - if (state.editing) ; - context.drawImage(m.currentImage, m.x, m.y, 1, 1); - }); - context.restore(); -} - -({ - green: Object.values(images.celestials.green) -}); - -function renderCelestial(cel) { - context.drawImage(cel.image, cel.x, cel.y, - cel.diameter, cel.diameter); -} - -function renderTracer(tracer) { - context.lineWidth = 0.1; - context.strokeStyle = '#fff'; - context.beginPath(); - context.moveTo(...tracer.pos); - let path = tracer.path; - - for (let i = 0; i < path.length; i++) { - context.lineTo(...path[i]); - if (i % 5 === 0 || i == path.length - 1) { - context.stroke(); - context.globalAlpha = (1 - (i / path.length)) * 0.5; - } - } - - context.globalAlpha = 1; -} - -let patterns = null; - -function init$2() { - patterns = { - back: context.createPattern(images.background.back, 'repeat'), - middle: context.createPattern(images.background.middle, 'repeat'), - front: context.createPattern(images.background.front, 'repeat') - }; -} - -function render$1(angle) { - if (patterns === null) init$2(); - // renderLayer(patterns.back, 0.3, 1, angle); - // renderLayer(patterns.middle, 0.5, 0.3, angle); - // renderLayer(patterns.front, 0.7, 0.3, angle); -} - -const TAU = TAU$1; - -let canvas, context, tempCanvas, tempContext; -let perspective; -let trace = true; -let markers = true; - -function init$1() { - canvas = document.querySelector('#main'); - context = canvas.getContext('2d'); - tempCanvas = document.querySelector('#temp'); - tempContext = tempCanvas.getContext('2d'); - - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - - canvas.style.width = canvas.width + 'px'; - canvas.style.height = canvas.height + 'px'; - - perspective = new Perspective(); - - context.fillStyle = '#000'; - context.fillRect(0, 0, canvas.width, canvas.height); - context.font = '36px Courier New'; - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.fillStyle = '#fff'; - context.fillText('Loading...', canvas.width / 2, canvas.height / 2); -} - -function render() { - context.clearRect(0, 0, canvas.width, canvas.height); - context.fillStyle = '#000'; - context.fillRect(0, 0, canvas.width, canvas.height); - - context.beginPath(); - context.rect(0, 0, canvas.width, canvas.height); - context.clip(); - - context.save(); - perspective.tick(); - perspective.transformRotate(); - render$1(perspective.rotation); - perspective.transformCanvas(); - render$2(); - context.restore(); - - render$3(); -} - -function changePerspective(rotationMode, shiftX = 0, shiftY = 0) { - perspective.changeRotationMode(rotationMode); - perspective.changeShift(shiftX, shiftY); - perspective.transition = 1; -} - -function cycleRotationMode() { - if (perspective.rotationMode === 'parent') { - perspective.changeRotationMode('local'); - } else if (perspective.rotationMode === 'local') { - perspective.changeRotationMode('universe'); - } else { - perspective.changeRotationMode('parent'); - } - perspective.transition = 1; - return perspective.rotationMode; -} - -function toggleTrace() { - trace = !trace; - return trace; -} - -function toggleMarkers() { - markers = !markers; - return markers; -} - -function changeZoom(delta) { - perspective.zoomDelta(delta); -} - -function setZoom(target) { - perspective.changeZoom(target); -} - -class Perspective { - constructor() { - this.x = 0; - this.y = 0; - this.shiftX = 0; - this.shiftY = 0; - this.zoom = 0; - this.bounds = [0, 0, canvas.width, canvas.height]; - this.reset(); - } - - reset() { - this.rotationMode = 'universe'; - this.targetZoom = DEFAULT_ZOOM; - this.oldTarget = 0; - this.oldShift = [0, 0]; - this.shiftX = 0; - this.shiftY = 0; - this.oldZoom = 0; - this.transition = 0; - this.zoomTransition = 0; - this.zoomTransitionSpeed = 0.9; - this.rotation = 0; - this.targetRotation = 0; - this.zoom = DEFAULT_ZOOM; - this.targetZoom = this.zoom; - this.focus = null; - this.rotationFocus = null; - } - - changeRotationMode(mode) { - this.oldShift = this.currentShift; - this.oldTarget = this.currentRotation; - this.rotationMode = mode; - } - - changeShift(x, y) { - this.oldShift = this.currentShift; - [this.shiftX, this.shiftY] = [x, y]; - } - - changeZoom(zoom, speed = 0.9) { - this.oldZoom = this.currentZoom; - this.targetZoom = zoom; - this.zoomTransition = 1; - this.zoomTransitionSpeed = speed; - } - - get currentShift() { - let [ox, oy] = this.oldShift; - return [this.interpolate(this.shiftX, ox), - this.interpolate(this.shiftY, oy)]; - } - - get currentRotation() { - return this.interpolateAngles(this.targetRotation, this.oldTarget); - } - - get currentZoom() { - let t = this.zoomTransition; - return (this.oldZoom * t + this.targetZoom * (1 - t)); - } - - interpolate(cur, old, x = this.transition) { - return (old * x + cur * (1 - x)); - } - - interpolateAngles(cur, old, x = this.transition) { - return old + this.angleDifference(old, cur) * (1 - x); - } - - angleDifference(a, b) { - return Math.atan2(Math.sin(b - a), Math.cos(b - a)); - } - - tick() { - if (this.focus !== null) - [this.x, this.y] = this.focus.com; - - if (this.focus === null || this.rotationMode === 'universe') { - this.targetRotation = 0; - } else if (this.rotationMode === 'parent') { - let parent = this.focus.parentCelestial; - if (parent === null) { - this.targetRotation = 0; - } else { - let a = this.focus.angleTo(...this.focus.com, ...parent.com); - this.targetRotation = a - Math.PI / 2; - } - } else { - this.targetRotation = this.focus.r; - } - - this.normalize(); - - let dif = Math.abs(this.targetRotation - this.rotation); - this.rotationMet = dif < (this.rotationMet ? 0.3 : 0.05); - - this.rotation = this.currentRotation; - this.zoom = this.currentZoom; - - this.transition *= 0.9; - this.zoomTransition *= this.zoomTransitionSpeed; - } - - focusPlayer() { - this.focus = playerShip; - this.rotationFocus = playerShip; - } - - zoomDelta(delta) { - let factor = 1 + (ZOOM_SPEED * Math.abs(delta)); - let target = this.targetZoom * (delta > 0 ? factor : 1 / factor); - this.changeZoom(target, 0.7); - this.normalize(); - } - - normalize() { - this.targetZoom = Math.max(MIN_ZOOM, - Math.min(MAX_ZOOM, this.targetZoom)); - this.targetRotation %= TAU; - } - - transformRotate() { - let [,,bw, bh] = this.bounds; - context.translate(bw / 2, bh / 2); - context.rotate(-this.rotation); - context.translate(-bw / 2, -bh / 2); - } - - rotateVector(x, y, r = this.rotation) { - return [(x * Math.cos(r) - y * Math.sin(r)), - (y * Math.cos(r) - x * Math.sin(r))]; - } - - transformCanvas() { - let [,,bw, bh] = this.bounds; - let [sx, sy] = this.rotateVector(...this.currentShift, this.rotation); - let tx = -(this.x + sx) * this.zoom; - let ty = -(this.y + sy) * this.zoom; - context.translate(tx + bw / 2, ty + bh / 2); - context.scale(this.zoom, this.zoom); - } - - normalizeAngle(a = this.r) { - return ((a % TAU) + TAU) % TAU; - } -} - -const mapping = { - thrust: 'KeyW', - left: 'KeyA', - right: 'KeyD', - reduce: 'ShiftLeft', - exitEdit: 'Escape', - inventory: 'KeyE', - cycleRotation: 'KeyC', - toggleTrace: 'KeyT', - toggleMarkers: 'KeyR', - toggleMusic: 'KeyM', - togglePause: 'KeyP', - zoomIn: 'KeyZ', - zoomOut: 'KeyX', - increaseSpeed: 'Period', - decreaseSpeed: 'Comma', -}; - -let held, pressed; - -function tick$1() { - held = keyCode.held; - pressed = keyCode.pressed; - - if (state.editing) { - tickEditing(); - } else if (state.playing && !state.gameOver && !state.paused) { - tickPlaying(); - } - - if (!state.editing) { - if (mouse.scroll !== 0) { - // Fix for Firefox. - let delta = mouse.scroll > 0 ? -50 : 50; - changeZoom(delta); - } - - if (held[mapping.zoomIn]) { - changeZoom(-10); - } - - if (held[mapping.zoomOut]) { - changeZoom(10); - } - - if (pressed[mapping.togglePause] && !state.gameOver) { - togglePause(); - } - - if (pressed[mapping.increaseSpeed]) { - increaseSpeed(); - } - - if (pressed[mapping.decreaseSpeed]) { - decreaseSpeed(); - } - } - - if (state.gameOver) { - stop('engine'); - } - - if (pressed[mapping.toggleMusic]) { - toggle('music'); - } -} - -function tickPlaying() { - let power = held[mapping.reduce] ? 0.3 : 1; - - if (held[mapping.thrust] && playerShip.fuel !== 0) { - playerShip.applyThrust({ forward: power }); - let vol = Math.min(0.7, perspective.zoom / 10); - volume('engine', vol); - } else { - stop('engine'); - } - - if (pressed[mapping.thrust]) { - if (playerShip.fuel !== 0) { - start('engine'); - } else { - stop('engine'); - } - } - - if (held[mapping.left]) { - playerShip.applyThrust({ turnLeft: power }); - } - - if (held[mapping.right]) { - playerShip.applyThrust({ turnRight: power }); - } - - if (pressed[mapping.inventory]) { - state.inventory = !state.inventory; - } - - if (pressed[mapping.cycleRotation]) { - cycleRotationMode$1(); - } - - if (pressed[mapping.toggleTrace]) { - toggleTrace$1(); - } - - if (pressed[mapping.toggleMarkers]) { - toggleMarkers$1(); - } -} - -function tickEditing() { - if (pressed[mapping.exitEdit]) { - endEditing(); - } -} - -let state; - -async function init() { - state = { - view: 'menu', - playing: false, - editing: false, - paused: false, - inventory: false, - gameOver: false - }; - - init$1(); - await init$9(); - init$3(); - init$4(); - - playMusic(); - //events.startGame(); - - loop(tick); -} - -function changeView(view) { - state.view = view; - changeView$1(view); - - if (view === 'game') { - state.playing = true; - state.editing = false; - state.paused = false; - init$6(); - init$5(); - perspective.reset(); - init$8(); - } else if (view === 'instructions') { - state.playing = false; - changeView$1('instructions'); - } else if (view === 'menu') { - changeView$1('menu'); - clear(); - } -} - -function loop(fn, fps = 60) { - - (function loop(time) { - fn(); - - requestAnimationFrame(loop); - })(); -} -function tick() { - tick$6(); - - if (state.view == 'game' && !state.paused) { - tick$4(); - } - - tick$1(); - - tick$2(); - render(); - tick$3(); -} - -window.addEventListener('load', init); -//# sourceMappingURL=improcket.min.js.map