diff --git a/src/constants.js b/src/constants.js index 2c4b124..28daedd 100644 --- a/src/constants.js +++ b/src/constants.js @@ -27,6 +27,9 @@ export const PLAYER_STARTING_WEAPONS = [ {type: 'missile', ammo:5}, {type: 'babyNuke', ammo:3}, {type: 'nuke', ammo:1}, + {type: 'babyRoller', ammo:5}, + {type: 'roller', ammo:3}, + {type: 'superRoller', ammo:1}, {type: 'mirv', ammo:5}, {type: 'xmirv', ammo:3}, {type: 'leapfrog', ammo:3}, @@ -45,6 +48,9 @@ export const WEAPON_TYPES = { missile: {id:'missile', name:'Missile', projectile:{type:'normal'}, explosion:{type:'blast', r:20}}, babyNuke: {id:'babyNuke', name:'Baby Nuke', projectile:{type:'normal'}, explosion:{type:'blast', r:50}}, nuke: {id:'nuke', name:'Nuke', projectile:{type:'normal'}, explosion:{type:'blast', r:100}}, + babyRoller: {id:'babyRoller', name:'Baby Roller', projectile:{type:'roller'}, explosion:{type:'blast', r:15}}, + roller: {id:'roller', name:'Roller', projectile:{type:'roller'}, explosion:{type:'blast', r:35}}, + superRoller: {id:'superRoller', name:'Super Roller', projectile:{type:'roller'}, explosion:{type:'blast', r:60}}, leapfrog: {id:'leapfrog', name:'Leapfrog', projectile:{type:'leapfrog', n:3, s:10}, explosion:{type:'blast', r:35}}, mirv: {id:'mirv', name:'MIRV', projectile:{type:'mirv', n:3, s:8}, explosion:{type:'blast', r:35}}, xmirv: {id:'xmirv', name:'X-MIRV', projectile:{type:'mirv', n:5, s:8}, explosion:{type:'blast', r:45}}, diff --git a/src/gfx.js b/src/gfx.js index c20415c..9ef2d6c 100644 --- a/src/gfx.js +++ b/src/gfx.js @@ -53,10 +53,20 @@ export function drawLine(ctx, x1, y1, x2, y2, color, plotFn=plot) { export function drawLineVirtual(x1, y1, x2, y2, color) { const points = []; - drawLine(null, x1, y1, x2, y2, color, (ctx, x, y, c) => points.push({x, y, c})); + drawLine( + null, x1, y1, x2, y2, color, + (ctx, x, y, c) => points.push({x, y, c}) + ); return points; } +export function checkLineWith(x1, y1, x2, y2, fn) { + drawLine( + null, x1, y1, x2, y2, null, + (ctx, x, y, c) => fn(x, y) + ); +} + export function drawRect(ctx, x, y, w, h, color) { x = Math.round(x); y = Math.round(y); w = Math.round(w); h = Math.round(h); diff --git a/src/projectiles.js b/src/projectiles.js index 4fafcca..60c7647 100644 --- a/src/projectiles.js +++ b/src/projectiles.js @@ -1,9 +1,9 @@ import {H, PROJECTILE_ITERATIONS_PER_FRAME, PROJECTILE_ITERATION_PROGRESS, PROJECTILE_MAX_SOUND_FREQUENCY, PROJECTILE_MIN_SOUND_FREQUENCY, PROJECTILE_POWER_REDUCTION_FACTOR, PROJECTILE_WIND_REDUCTION_FACTOR, WEAPON_TYPES} from './constants.js'; -import {drawLineVirtual} from './gfx.js'; -import {isTank, createParticles} from './main.js'; +import {checkLineWith, drawLineVirtual} from './gfx.js'; +import {createParticles, isTank} from './main.js'; import {deg2rad, parable} from './math.js'; import {audio, createOsc} from './sound.js'; -import {isTerrain} from './terrain.js'; +import {isTerrain, landHeight} from './terrain.js'; import {EXPLOSION_TYPES} from './weapons.js'; @@ -77,6 +77,125 @@ export const PROJECTILE_TYPES = { }, }, + roller: { + create(spec, player, weapon, ox, oy, a, p, wind) { + const osc = createOsc('sine'); + osc.start(); + + return [{ + type: 'roller', + player, weapon, + x:ox, y:oy, ox, oy, a, p, + t: 0, osc, wind, + state: 'flying', + d: 0, + }]; + }, + stop(projectile) { + projectile.osc.stop(0); + }, + update(projectile, terrain, projectiles, trajectories, explosions) { + const prevProjectile = {...projectile}; + const {state, weapon, player, wind} = projectile; + const weaponType = WEAPON_TYPES[weapon.type]; + let finished = false; + + if (state === 'flying') { + const {ox, oy, a, p, t} = projectile; + for (let i=0; i { + if (prevPoint && isTerrain(terrain, prevPoint.x, prevPoint.y) && !isTerrain(terrain, x, y)) { + projectile.x = x; + projectile.y = y; + } else if (prevPoint && !isTerrain(terrain, prevPoint.x, prevPoint.y) && isTerrain(terrain, x, y)) { + projectile.x = prevPoint.x; + projectile.y = prevPoint.y; + } + prevPoint = {x, y}; + } + ); + } else { + projectile.t += PROJECTILE_ITERATION_PROGRESS; + projectile.x = x; + projectile.y = y; + } + + x = projectile.x; + y = projectile.y; + + if (y > H || isTank(x, y)) { + projectile.state = 'explode'; + break; + } else { + const hitLeft = isTerrain(terrain, x-1, y) || isTerrain(terrain, x-1, y+1); + const hitRight = isTerrain(terrain, x+1, y) || isTerrain(terrain, x+1, y+1); + const hitBottom = isTerrain(terrain, x, y+1); + if (hitLeft || hitRight || hitBottom) { + projectile.state = 'rolling'; + projectile.d = ( + hitLeft? 1 : + hitRight? -1 : + Math.sign(projectile.x - prevProjectile.x) + ); + break; + } + } + } + } + + else if (state === 'rolling') { + const {x, y, d} = projectile; + const nextY = landHeight(terrain, x+d); + + if (nextY < y || y > H || isTank(x, y)) { + projectile.state = 'explode'; + } else { + projectile.x += d; + projectile.y = nextY; + } + } + + else if (state === 'explode') { + const {x, y, p} = projectile; + const explosionSpec = weaponType.explosion; + const explosionType = EXPLOSION_TYPES[explosionSpec.type]; + explosions.push(explosionType.create(explosionSpec, x, y)); + // @ts-ignore: canvas color hack + createParticles(x, y, p, terrain.color); + finished = true; + } + + const f = ( + (1 - (1 / H * projectile.y)) * + (PROJECTILE_MAX_SOUND_FREQUENCY - PROJECTILE_MIN_SOUND_FREQUENCY) + + PROJECTILE_MIN_SOUND_FREQUENCY + ); + projectile.osc.frequency.setValueAtTime(f, audio.currentTime); + + let trajectory = drawLineVirtual( + prevProjectile.x, prevProjectile.y, + projectile.x, projectile.y, player.c, + ); + + trajectory + .slice(0, trajectory.length-1) // Cut last pixel to prevent overlap + .map(x => ({...x, a:255})) // Add alpha to all lines + .forEach(x => trajectories.push(x)); + + return !finished; + }, + }, + mirv: { create(spec, player, weapon, ox, oy, a, p, wind) { const {n, s} = spec;