Shields
This commit is contained in:
1 parent
5512ea56e7
commit
444240e984
5 files changed
+99
-11
No files matched your search
@@ -64,6 +64,12 @@ export const WEAPON_TYPES = {
|
|||||||
largeDigBomb: {id:'largeDigBomb', name:'Large Dig Bomb', projectile:{type:'normal'}, explosion:{type:'digBomb', r:75}},
|
largeDigBomb: {id:'largeDigBomb', name:'Large Dig Bomb', projectile:{type:'normal'}, explosion:{type:'digBomb', r:75}},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SHIELD_TYPES = {
|
||||||
|
shield: {id:'shield', r:15, s:1, energy:100, projectileEffect:'nullify', color:'white'},
|
||||||
|
heavyShield: {id:'heavyShield', r:15, s:2, energy:200, projectileEffect:'nullify', color:'white'},
|
||||||
|
springShield: {id:'springShield', r:15, s:1, energy:100, projectileEffect:'spring', color:'deeppink'},
|
||||||
|
};
|
||||||
|
|
||||||
export const DEATH_SPECS = [
|
export const DEATH_SPECS = [
|
||||||
{type: 'blast', r: 5},
|
{type: 'blast', r: 5},
|
||||||
{type: 'blast', r: 15},
|
{type: 'blast', r: 15},
|
||||||
|
|||||||
+15
@@ -93,6 +93,21 @@ export function drawCircle(ctx, x, y, r, color) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function strokeCircle(ctx, x, y, r, color) {
|
||||||
|
x = Math.round(x);
|
||||||
|
y = Math.round(y);
|
||||||
|
r = Math.round(r);
|
||||||
|
|
||||||
|
let prevPoint;
|
||||||
|
for (let cy = -r; cy <= r; cy++) {
|
||||||
|
const w = Math.round(Math.sqrt(r*r - cy*cy));
|
||||||
|
if (!prevPoint) prevPoint = {cy, w};
|
||||||
|
drawLine(ctx, x-prevPoint.w, y+prevPoint.cy, x-w, y+cy, color);
|
||||||
|
drawLine(ctx, x+prevPoint.w, y+prevPoint.cy, x+w, y+cy, color);
|
||||||
|
prevPoint = {cy, w};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function drawSemiCircle(ctx, x, y, r, color) {
|
export function drawSemiCircle(ctx, x, y, r, color) {
|
||||||
x = Math.round(x);
|
x = Math.round(x);
|
||||||
y = Math.round(y);
|
y = Math.round(y);
|
||||||
|
|||||||
+43
-6
@@ -1,6 +1,6 @@
|
|||||||
import {AI_TYPES} from './ai.js';
|
import {AI_TYPES} from './ai.js';
|
||||||
import {DEATH_SPECS, EXPLOSION_SHAKE_REDUCTION_FACTOR, H, MAX_EXPLOSION_SHAKE_FACTOR, MAX_WIND, PARTICLE_AMOUNT, PARTICLE_FADE_AMOUNT, PARTICLE_MAX_POWER_FACTOR, PARTICLE_MIN_LIFETIME, PARTICLE_MIN_POWER_FACTOR, PARTICLE_POWER_REDUCTION_FACTOR, PARTICLE_TIME_FACTOR, PARTICLE_WIND_REDUCTION_FACTOR, PLAYER_ANGLE_FAST_INCREMENT, PLAYER_ANGLE_INCREMENT, PLAYER_ANGLE_TICK_SOUND_INTERVAL, PLAYER_COLORS, PLAYER_ENERGY_POWER_MULTIPLIER, PLAYER_EXPLOSION_PARTICLE_POWER, PLAYER_FALL_DAMAGE_FACTOR, PLAYER_FALL_DAMAGE_HEIGHT, PLAYER_INITIAL_POWER, PLAYER_MAX_ENERGY, PLAYER_POWER_FAST_INCREMENT, PLAYER_POWER_INCREMENT, PLAYER_POWER_TICK_SOUND_INTERVAL, PLAYER_STARTING_TOOLS, PLAYER_STARTING_WEAPONS, PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, TRAJECTORY_FADE_SPEED, TRAJECTORY_FLOAT_SPEED, W, WEAPON_TYPES, Z} from './constants.js';
|
import {DEATH_SPECS, EXPLOSION_SHAKE_REDUCTION_FACTOR, H, MAX_EXPLOSION_SHAKE_FACTOR, MAX_WIND, PARTICLE_AMOUNT, PARTICLE_FADE_AMOUNT, PARTICLE_MAX_POWER_FACTOR, PARTICLE_MIN_LIFETIME, PARTICLE_MIN_POWER_FACTOR, PARTICLE_POWER_REDUCTION_FACTOR, PARTICLE_TIME_FACTOR, PARTICLE_WIND_REDUCTION_FACTOR, PLAYER_ANGLE_FAST_INCREMENT, PLAYER_ANGLE_INCREMENT, PLAYER_ANGLE_TICK_SOUND_INTERVAL, PLAYER_COLORS, PLAYER_ENERGY_POWER_MULTIPLIER, PLAYER_EXPLOSION_PARTICLE_POWER, PLAYER_FALL_DAMAGE_FACTOR, PLAYER_FALL_DAMAGE_HEIGHT, PLAYER_INITIAL_POWER, PLAYER_MAX_ENERGY, PLAYER_POWER_FAST_INCREMENT, PLAYER_POWER_INCREMENT, PLAYER_POWER_TICK_SOUND_INTERVAL, PLAYER_STARTING_TOOLS, PLAYER_STARTING_WEAPONS, PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, SHIELD_TYPES, TRAJECTORY_FADE_SPEED, TRAJECTORY_FLOAT_SPEED, W, WEAPON_TYPES, Z} from './constants.js';
|
||||||
import {createCanvas, drawLine, drawRect, drawSemiCircle, drawText, loop, plot} from './gfx.js';
|
import {createCanvas, drawLine, drawRect, drawSemiCircle, drawText, loop, plot, strokeCircle} from './gfx.js';
|
||||||
import {afterKeyDelay, key} from './input.js';
|
import {afterKeyDelay, key} from './input.js';
|
||||||
import {clamp, deg2rad, distance, parable, random, randomInt, vec, wrap} from './math.js';
|
import {clamp, deg2rad, distance, parable, random, randomInt, vec, wrap} from './math.js';
|
||||||
import {PROJECTILE_TYPES} from './projectiles.js';
|
import {PROJECTILE_TYPES} from './projectiles.js';
|
||||||
@@ -63,6 +63,7 @@ function initPlayers() {
|
|||||||
const a = x > W/2 ? 45 : 180-45;
|
const a = x > W/2 ? 45 : 180-45;
|
||||||
players.push({
|
players.push({
|
||||||
name: `Player ${i+1}`,
|
name: `Player ${i+1}`,
|
||||||
|
dead: false,
|
||||||
x, y, a,
|
x, y, a,
|
||||||
c: color, cb: borderColor,
|
c: color, cb: borderColor,
|
||||||
p: PLAYER_INITIAL_POWER,
|
p: PLAYER_INITIAL_POWER,
|
||||||
@@ -70,9 +71,10 @@ function initPlayers() {
|
|||||||
weapons: PLAYER_STARTING_WEAPONS.map(x => ({...x})), // FIXME: Ghetto clone
|
weapons: PLAYER_STARTING_WEAPONS.map(x => ({...x})), // FIXME: Ghetto clone
|
||||||
currentWeapon: 0,
|
currentWeapon: 0,
|
||||||
energy: PLAYER_MAX_ENERGY,
|
energy: PLAYER_MAX_ENERGY,
|
||||||
|
shield: {type:'springShield', energy:SHIELD_TYPES.springShield.energy},
|
||||||
ai: i !== 0 ? sample(Object.keys(AI_TYPES)) : undefined,
|
ai: i !== 0 ? sample(Object.keys(AI_TYPES)) : undefined,
|
||||||
|
parachute: null,
|
||||||
fallHeight: 0,
|
fallHeight: 0,
|
||||||
dead: false,
|
|
||||||
});
|
});
|
||||||
clipTerrain(terrain, (ctx) => drawRect(ctx, x-4, 0, 8, y, ctx.color));
|
clipTerrain(terrain, (ctx) => drawRect(ctx, x-4, 0, 8, y, ctx.color));
|
||||||
i++;
|
i++;
|
||||||
@@ -197,8 +199,18 @@ function update() {
|
|||||||
screenShake = 0;
|
screenShake = 0;
|
||||||
explosionType.clip(explosion, terrain);
|
explosionType.clip(explosion, terrain);
|
||||||
explosionType.stop(explosion);
|
explosionType.stop(explosion);
|
||||||
|
|
||||||
for (let player of players) if (!player.dead) {
|
for (let player of players) if (!player.dead) {
|
||||||
player.energy -= explosionType.damage(explosion, player) || 0;
|
let damage = explosionType.damage(explosion, player);
|
||||||
|
let remainingDamage = damage;
|
||||||
|
if (!damage) continue;
|
||||||
|
|
||||||
|
if (player.shield) {
|
||||||
|
remainingDamage = clamp(0, damage-player.shield.energy, Infinity);
|
||||||
|
player.shield.energy = clamp(0, player.shield.energy-damage, Infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.energy -= remainingDamage;
|
||||||
}
|
}
|
||||||
explosions.splice(i, 1);
|
explosions.splice(i, 1);
|
||||||
}
|
}
|
||||||
@@ -264,6 +276,7 @@ function update() {
|
|||||||
player.weapons = player.weapons.filter(x => x.ammo > 0);
|
player.weapons = player.weapons.filter(x => x.ammo > 0);
|
||||||
player.tools = player.tools.filter(x => x.ammo > 0);
|
player.tools = player.tools.filter(x => x.ammo > 0);
|
||||||
player.currentWeapon = wrap(0, player.currentWeapon, player.weapons.length-1);
|
player.currentWeapon = wrap(0, player.currentWeapon, player.weapons.length-1);
|
||||||
|
if (player.shield && player.shield.energy <= 0) player.shield = null;
|
||||||
player.fallHeight = 0;
|
player.fallHeight = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +360,19 @@ export function isTank(x, y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTankShield(x, y) {
|
||||||
|
for (let player of players) {
|
||||||
|
if (player.dead) continue;
|
||||||
|
if (!player.shield) continue;
|
||||||
|
const shieldType = SHIELD_TYPES[player.shield.type];
|
||||||
|
const playerDistance = distance(x, y, player.x, player.y+PLAYER_TANK_Y_FOOTPRINT);
|
||||||
|
|
||||||
|
if (playerDistance - shieldType.r <= 1) {
|
||||||
|
return {player, shieldType};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
if (idle && particles.length===0) return;
|
if (idle && particles.length===0) return;
|
||||||
|
|
||||||
@@ -367,9 +393,20 @@ function draw() {
|
|||||||
|
|
||||||
function drawPlayers() {
|
function drawPlayers() {
|
||||||
for (let player of players) {
|
for (let player of players) {
|
||||||
const {x, y, a, c, cb, energy, dead} = player;
|
const {x, y, a, c, cb, energy, shield, dead} = player;
|
||||||
if (dead) continue;
|
if (dead) continue;
|
||||||
|
|
||||||
|
// Shield
|
||||||
|
if (shield) {
|
||||||
|
const {type, energy} = shield;
|
||||||
|
const shieldType = SHIELD_TYPES[type];
|
||||||
|
foreground.globalAlpha = energy / shieldType.energy;
|
||||||
|
for (let i=0; i<shieldType.s; i++) {
|
||||||
|
strokeCircle(foreground, x, y+PLAYER_TANK_Y_FOOTPRINT, shieldType.r+i, shieldType.color);
|
||||||
|
}
|
||||||
|
foreground.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Parachute
|
// Parachute
|
||||||
if (player.parachute) {
|
if (player.parachute) {
|
||||||
drawSemiCircle(foreground, x, y-15, 10, 'white');
|
drawSemiCircle(foreground, x, y-15, 10, 'white');
|
||||||
@@ -473,7 +510,7 @@ function drawStatus() {
|
|||||||
const {currentWeapon} = player;
|
const {currentWeapon} = player;
|
||||||
const weapon = player.weapons[currentWeapon];
|
const weapon = player.weapons[currentWeapon];
|
||||||
const weaponType = WEAPON_TYPES[weapon.type];
|
const weaponType = WEAPON_TYPES[weapon.type];
|
||||||
drawText(foreground, `${player.name} NRG:${player.energy} AIM:${player.a} PWR:${player.p} ${clamp(0, weapon.ammo, 99)} ${weaponType.name}`, 8, 8, player.c, 'left');
|
drawText(foreground, `${player.name} NRG:${player.energy} AIM:${player.a} PWR:${player.p} SHD:${player.shield?player.shield.energy:0} ${clamp(0, weapon.ammo, 99)} ${weaponType.name}`, 8, 8, player.c, 'left');
|
||||||
drawText(foreground, `WIND: ${wind<=0?'<':''}${Math.abs(wind)}${wind>=0?'>':''}`, W-8, 8, 'white', 'right');
|
drawText(foreground, `WIND: ${wind<=0?'<':''}${Math.abs(wind)}${wind>=0?'>':''}`, W-8, 8, 'white', 'right');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-1
@@ -1,6 +1,6 @@
|
|||||||
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 {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 {checkLineWith, drawLineVirtual} from './gfx.js';
|
import {checkLineWith, drawLineVirtual} from './gfx.js';
|
||||||
import {createParticles, isTank} from './main.js';
|
import {createParticles, isTank, isTankShield} from './main.js';
|
||||||
import {deg2rad, parable} from './math.js';
|
import {deg2rad, parable} from './math.js';
|
||||||
import {audio, createOsc} from './sound.js';
|
import {audio, createOsc} from './sound.js';
|
||||||
import {isTerrain, landHeight} from './terrain.js';
|
import {isTerrain, landHeight} from './terrain.js';
|
||||||
@@ -48,6 +48,20 @@ export const PROJECTILE_TYPES = {
|
|||||||
);
|
);
|
||||||
projectile.osc.frequency.setValueAtTime(f, audio.currentTime);
|
projectile.osc.frequency.setValueAtTime(f, audio.currentTime);
|
||||||
|
|
||||||
|
// FIXME: Better detection of player's own shield
|
||||||
|
const shieldHit = isTankShield(x, y);
|
||||||
|
if (shieldHit && shieldHit.player !== projectile.player) {
|
||||||
|
if (shieldHit.shieldType.projectileEffect === 'nullify') {
|
||||||
|
exploded = true;
|
||||||
|
break;
|
||||||
|
} else if (shieldHit.shieldType.projectileEffect === 'spring') {
|
||||||
|
projectile.ox = projectile.x;
|
||||||
|
projectile.oy = projectile.y -1;
|
||||||
|
projectile.t = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
y > H ||
|
y > H ||
|
||||||
isTank(x, y) ||
|
isTank(x, y) ||
|
||||||
@@ -133,6 +147,20 @@ export const PROJECTILE_TYPES = {
|
|||||||
x = projectile.x;
|
x = projectile.x;
|
||||||
y = projectile.y;
|
y = projectile.y;
|
||||||
|
|
||||||
|
// FIXME: Better detection of player's own shield
|
||||||
|
const shieldHit = isTankShield(x, y);
|
||||||
|
if (shieldHit && shieldHit.player !== projectile.player) {
|
||||||
|
if (shieldHit.shieldType.projectileEffect === 'nullify') {
|
||||||
|
finished = true;
|
||||||
|
break;
|
||||||
|
} else if (shieldHit.shieldType.projectileEffect === 'spring') {
|
||||||
|
projectile.ox = projectile.x;
|
||||||
|
projectile.oy = projectile.y - 10;
|
||||||
|
projectile.t = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (y > H || isTank(x, y)) {
|
if (y > H || isTank(x, y)) {
|
||||||
projectile.state = 'explode';
|
projectile.state = 'explode';
|
||||||
break;
|
break;
|
||||||
|
|||||||
+6
-4
@@ -1,4 +1,4 @@
|
|||||||
import {PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT} from './constants.js';
|
import {PLAYER_TANK_BOUNDING_RADIUS, PLAYER_TANK_Y_FOOTPRINT, SHIELD_TYPES} from './constants.js';
|
||||||
import {drawCircle, plot} from './gfx.js';
|
import {drawCircle, plot} from './gfx.js';
|
||||||
import {clamp, cycle, distance, randomInt} from './math.js';
|
import {clamp, cycle, distance, randomInt} from './math.js';
|
||||||
import {audio, createOsc} from './sound.js';
|
import {audio, createOsc} from './sound.js';
|
||||||
@@ -51,9 +51,11 @@ export const EXPLOSION_TYPES = {
|
|||||||
damage(explosion, player) {
|
damage(explosion, player) {
|
||||||
const {x, y, r} = explosion;
|
const {x, y, r} = explosion;
|
||||||
const dist = distance(x, y, player.x, player.y+PLAYER_TANK_Y_FOOTPRINT);
|
const dist = distance(x, y, player.x, player.y+PLAYER_TANK_Y_FOOTPRINT);
|
||||||
const tolerance = clamp(0, dist - r, Infinity);
|
const overlap = clamp(0, dist - r, Infinity);
|
||||||
if (tolerance <= PLAYER_TANK_BOUNDING_RADIUS) {
|
const shieldType = player.shield ? SHIELD_TYPES[player.shield.type] : null;
|
||||||
return Math.round(100 * (1 - tolerance / (PLAYER_TANK_BOUNDING_RADIUS+1)));
|
const radius = PLAYER_TANK_BOUNDING_RADIUS + (shieldType? shieldType.r : 0);
|
||||||
|
if (overlap <= radius) {
|
||||||
|
return Math.round(100 * (1 - overlap / (radius+1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user