Created
December 15, 2018 14:22
-
-
Save CrazyPython/f1ebf51bec916eeabb8b2220c11c7b34 to your computer and use it in GitHub Desktop.
Barbarossa server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// GUN DEFINITIONS | |
const dfltskl = exports.dfltskl = 7; | |
exports.combineStats = function(arr) { | |
try { | |
// Build a blank array of the appropiate length | |
let data = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; | |
arr.forEach(function(component) { | |
for (let i=0; i<data.length; i++) { | |
data[i] = data[i] * component[i]; | |
} | |
}); | |
return { | |
reload: data[0], | |
recoil: data[1], | |
shudder: data[2], | |
size: data[3], | |
health: data[4], | |
damage: data[5], | |
pen: data[6], | |
speed: data[7], | |
maxSpeed: data[8], | |
range: data[9], | |
density: data[10], | |
spray: data[11], | |
resist: data[12], | |
}; | |
} catch(err) { | |
console.log(err); | |
console.log(JSON.stringify(arr)); | |
} | |
}; | |
exports.skillSet = (() => { | |
let config = require('../../../config.json'); | |
let skcnv = { | |
rld: 0, | |
pen: 1, | |
str: 2, | |
dam: 3, | |
spd: 4, | |
shi: 5, | |
atk: 6, | |
hlt: 7, | |
rgn: 8, | |
mob: 9, | |
}; | |
return args => { | |
let skills = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | |
for (let s in args) { | |
if (!args.hasOwnProperty(s)) continue; | |
skills[skcnv[s]] = Math.round(config.MAX_SKILL * args[s]); | |
} | |
return skills; | |
}; | |
})(); | |
exports.g = { // Gun info here | |
droneDominator: [1.25, 1, 1, 0.9, 1.25, 1.5, 1.5, 1.05, 0.9, 1, 2, 1, 1], | |
destroyDominator: [4, 0, 1, 0.975, 8, 8, 6.25, 0.5, 1, 1.5, 1, 0.5, 1], | |
gunnerDominator: [0.65, 0, 1, 0.5, 1, 1, 1.2, 1.0, 1, 1, 1, 1.25, 1], | |
trapperDominator: [0.85, 0, 0.25, 1.1, 1, 1.2, 1.2, 0.7, 2, 2, 1, 0.5, 1], | |
trap: [36, 1, 0.25, 0.6, 1, 0.75, 1, 5, 1, 1, 1, 15, 3], | |
swarm: [18, 0.25, 0.05, 0.4, 1, 0.75, 1, 4, 1, 1, 1, 5, 1], | |
drone: [50, 0.25, 0.1, 0.6, 1, 1, 1, 2, 1, 1, 1, 0.1, 1], | |
factory: [60, 1, 0.1, 0.7, 1, 0.75, 1, 3, 1, 1, 1, 0.1, 1], | |
basic: [18, 1.4, 0.1, 1, 1, 0.75, 1, 4.5, 1, 1, 1, 15, 1], | |
/***************** RELOAD RECOIL SHUDDER SIZE HEALTH DAMAGE PEN SPEED MAX RANGE DENSITY SPRAY RESIST */ | |
blank: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
spam: [1.1, 1, 1, 1.05, 1, 1.1, 1, 0.9, 0.7, 1, 1, 1, 1.05], | |
minion: [1, 1, 2, 1, 0.4, 0.4, 1.2, 1, 1, 0.75, 1, 2, 1], | |
single: [1.05, 1, 1, 1, 1, 1, 1, 1.05, 1, 1, 1, 1, 1], | |
sniper: [1.35, 1, 0.25, 1, 1, 0.8, 1.1, 1.5, 1.5, 1, 1.5, 0.2, 1.15], | |
rifle: [0.8, 0.8, 1.5, 1, 0.8, 0.8, 0.9, 1, 1, 1, 1, 2, 1], | |
assass: [1.65, 1, 0.25, 1, 1.15, 1, 1.1, 1.18, 1.18, 1, 3, 1, 1.3], | |
hunter: [1.5, 0.7, 1, 0.95, 1, 0.9, 1, 1.1, 0.8, 1, 1.2, 1, 1.15], | |
hunter2: [1, 1, 1, 0.9, 1, 0.5, 1.0, 1, 1, 1, 1.2, 1, 1.1], | |
preda: [1.4, 0.8, 1, 0.8, 0.7, 0.9, 1.0, 0.9, 0.9, 1.5, 1, 1, 1], | |
snake: [0.4, 1, 4, 1, 1.5, 0.9, 1.2, 0.2, 0.35, 1, 3, 6, 0.5], | |
sidewind: [1.5, 2, 1, 1, 1.5, 0.9, 1, 0.15, 0.5, 1, 1, 1, 1], | |
snakeskin: [0.6, 1, 2, 1, 0.5, 0.5, 1, 1, 0.2, 0.4, 1, 5, 1], | |
mach: [0.5, 0.8, 1.7, 1, 0.7, 0.7, 1, 1, 0.8, 1, 1, 2.5, 1], | |
blaster: [1, 1.2, 1.25, 1.1, 1.5, 1, 0.6, 0.8, 0.33, 0.6, 0.5, 1.5, 0.8], | |
chain: [1.25, 1.33, 0.8, 1, 0.8, 1, 1.1, 1.25, 1.25, 1.1, 1.25, 0.5, 1.1], | |
mini: [1.25, 0.6, 1, 0.8, 0.55, 0.45, 1.25, 1.33, 1, 1, 1.25, 0.5, 1.1], | |
stream: [1.1, 0.6, 1, 1, 1, 0.65, 1, 1.24, 1, 1, 1, 1, 1], | |
shotgun: [8, 0.4, 1, 1.5, 1, 0.4, 0.8, 1.8, 0.6, 1, 1.2, 1.2, 1], | |
flank: [1, 1.2, 1, 1, 1.02, 0.81, 0.9, 1, 0.85, 1, 1.2, 1, 1], | |
tri: [1, 0.9, 1, 1, 0.9, 1, 1, 0.8, 0.8, 0.6, 1, 1, 1], | |
trifront: [1, 0.2, 1, 1, 1, 1, 1, 1.3, 1.1, 1.5, 1, 1, 1], | |
thruster: [1, 1.5, 2, 1, 0.5, 0.5, 0.7, 1, 1, 1, 1, 0.5, 0.7], | |
auto: /*pure*/ [1.8, 0.75, 0.5, 0.8, 0.9, 0.6, 1.2, 1.1, 1, 0.8, 1.3, 1, 1.25], | |
five: [1.15, 1, 1, 1, 1, 1, 1, 1.05, 1.05, 1.1, 2, 1, 1], | |
autosnipe: [1, 1, 1, 1.4, 2, 1, 1, 1, 1, 1, 1, 1, 1], | |
/***************** RELOAD RECOIL SHUDDER SIZE HEALTH DAMAGE PEN SPEED MAX RANGE DENSITY SPRAY RESIST */ | |
pound: [2, 1.6, 1, 1, 1, 2, 1, 0.85, 0.8, 1, 1.5, 1, 1.15], | |
destroy: [2.2, 1.8, 0.5, 1, 2, 2, 1.2, 0.65, 0.5, 1, 2, 1, 3], | |
anni: [0.8, 1.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
hive: [1.5, 0.8, 1, 0.8, 0.7, 0.3, 1, 1, 0.6, 1, 1, 1, 1], | |
arty: [1.2, 0.7, 1, 0.9, 1, 1, 1, 1.15, 1.1, 1, 1.5, 1, 1], | |
mortar: [1.2, 1, 1, 1, 1.1, 1, 1, 0.8, 0.8, 1, 1, 1, 1], | |
spreadmain: [0.78125, 0.25, 0.5, 1, 0.5, 1, 1, 1.5/0.78, 0.9/0.78,1, 1, 1, 1], | |
spread: [1.5, 1, 0.25, 1, 1, 1, 1, 0.7, 0.7, 1, 1, 0.25, 1], | |
skim: [1, 0.8, 0.8, 0.9, 1.35, 0.8, 2, 0.3, 0.3, 1, 1, 1, 1.1], | |
twin: [1, 0.5, 0.9, 1, 0.9, 0.7, 1, 1, 1, 1, 1, 1.2, 1], | |
bent: [1.1, 1, 0.8, 1, 0.9, 1, 0.8, 1, 1, 1, 0.8, 0.5, 1], | |
triple: [1.2, 0.667, 0.9, 1, 0.85, 0.85, 0.9, 1, 1, 1, 1.1, 0.9, 0.95], | |
quint: [1.5, 0.667, 0.9, 1, 1, 1, 0.9, 1, 1, 1, 1.1, 0.9, 0.95], | |
dual: [2, 1, 0.8, 1, 1.5, 1, 1, 1.3, 1.1, 1, 1, 1, 1.25], | |
double: [1, 1, 1, 1, 1, 0.9, 1, 1, 1, 1, 1, 1, 1], | |
hewn: [1.25, 1.5, 1, 1, 0.9, 0.85, 1, 1, 0.9, 1, 1, 1, 1], | |
puregunner: [1, 0.25, 1.5, 1.2, 1.35, 0.25, 1.25, 0.8, 0.65, 1, 1.5, 1.5, 1.2], | |
machgun: [0.66, 0.8, 2, 1, 1, 0.75, 1, 1.2, 0.8, 1, 1, 2.5, 1], | |
gunner: [1.25, 0.25, 1.5, 1.1, 1, 0.35, 1.35, 0.9, 0.8, 1, 1.5, 1.5, 1.2], | |
power: [1, 1, 0.6, 1.2, 1, 1, 1.25, 2, 1.7, 1, 2, 0.5, 1.5], | |
nail: [0.85, 2.5, 1, 0.8, 1, 0.7, 1, 1, 1, 1, 2, 1, 1], | |
fast: [1, 1, 1, 1, 1, 1, 1, 1.2, 1, 1, 1, 1, 1], | |
turret: [2, 1, 1, 1, 0.8, 0.6, 0.7, 1, 1, 1, 0.1, 1, 1], | |
/***************** RELOAD RECOIL SHUDDER SIZE HEALTH DAMAGE PEN SPEED MAX RANGE DENSITY SPRAY RESIST */ | |
battle: [1, 1, 1, 1, 1.25, 1.15, 1, 1, 0.85, 1, 1, 1, 1.1], | |
bees: [1.3, 1, 1, 1.4, 1, 1.5, 0.5, 3, 1.5, 1, 0.25, 1, 1], | |
carrier: [1.5, 1, 1, 1, 1, 0.8, 1, 1.3, 1.2, 1.2, 1, 1, 1], | |
hexatrap: [1.3, 1, 1.25, 1, 1, 1, 1, 0.8, 1, 0.5, 1, 1, 1], | |
block: [1.1, 2, 0.1, 1.5, 2, 1, 1.25, 1.5, 2.5, 1.25, 1, 1, 1.25], | |
construct: [1.3, 1, 1, 0.9, 1, 1, 1, 1, 1.1, 1, 1, 1, 1], | |
boomerang: [0.8, 1, 1, 1, 0.5, 0.5, 1, 0.75, 0.75, 1.333, 1, 1, 1], | |
over: [1.25, 1, 1, 0.85, 0.7, 0.8, 1, 1, 0.9, 1, 2, 1, 1], | |
meta: [1.333, 1, 1, 1, 1, 0.667, 1, 1, 1, 1, 1, 1, 1], | |
weak: [2, 1, 1, 1, 0.6, 0.6, 0.8, 0.5, 0.7, 0.25, 0.3, 1, 1], | |
master: [3, 1, 1, 0.7, 0.4, 0.7, 1, 1, 1, 0.1, 0.5, 1, 1], | |
sunchip: [5, 1, 1, 1.4, 0.5, 0.4, 0.6, 1, 1, 1, 0.8, 1, 1], | |
babyfactory: [1.5, 1, 1, 1, 1, 1, 1, 1, 1.35, 1, 1, 1, 1], | |
lowpower: [1, 1, 2, 1, 0.5, 0.5, 0.7, 1, 1, 1, 1, 0.5, 0.7], | |
zerorecoil: [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
halfrecoil: [1, 0.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
morerecoil: [1, 1.15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
muchmorerecoil: [1, 1.35, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
lotsmorrecoil: [1, 1.8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
tonsmorrecoil: [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
doublereload: [0.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
morereload: [0.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
halfreload: [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
lessreload: [1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
threequartersrof: [1.333, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
morespeed: [1, 1, 1, 1, 1, 1, 1, 1.3, 1.3, 1, 1, 1, 1], | |
bitlessspeed: [1, 1, 1, 1, 1, 1, 1, 0.93, 0.93, 1, 1, 1, 1], | |
slow: [1, 1, 1, 1, 1, 1, 1, 0.7, 0.7, 1, 1, 1, 1], | |
halfspeed: [1, 1, 1, 1, 1, 1, 1, 0.5, 0.5, 1, 1, 1, 1], | |
notdense: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.1, 1, 1], | |
halfrange: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0.5, 1, 1, 1], | |
fake: [1, 1, 1, 0.00001, 0.0001, 1, 1, 0.00001, 2, 0, 1, 1, 1], | |
/***************** RELOAD RECOIL SHUDDER SIZE HEALTH DAMAGE PEN SPEED MAX RANGE DENSITY SPRAY RESIST */ | |
op: [0.5, 1.3, 1, 1, 4, 4, 4, 3, 2, 1, 5, 2, 1], | |
protectorswarm: [5, 0.000001, 1, 1, 100, 1, 1, 1, 1, 0.5, 5, 1, 10], | |
}; | |
exports.gunCalcNames = { | |
default: 0, | |
bullet: 1, | |
drone: 2, | |
swarm: 3, | |
fixedReload: 4, | |
thruster: 5, | |
sustained: 6, | |
necro: 7, | |
trap: 8, | |
}; | |
// ENTITY DEFINITIONS | |
exports.genericEntity = { | |
NAME: '', | |
LABEL: 'Unknown Entity', | |
TYPE: 'unknown', | |
DAMAGE_CLASS: 0, // 0: def, 1: food, 2: tanks, 3: obstacles | |
DANGER: 0, | |
VALUE: 0, | |
SHAPE: 0, | |
COLOR: 16, | |
INDEPENDENT: false, | |
CONTROLLERS: ['doNothing'], | |
HAS_NO_MASTER: false, | |
MOTION_TYPE: 'glide', // motor, swarm, chase | |
FACING_TYPE: 'toTarget', // turnWithSpeed, withMotion, looseWithMotion, toTarget, looseToTarget | |
DRAW_HEALTH: false, | |
DRAW_SELF: true, | |
DAMAGE_EFFECTS: true, | |
RATEFFECTS: true, | |
MOTION_EFFECTS: true, | |
INTANGIBLE: false, | |
ACCEPTS_SCORE: true, | |
GIVE_KILL_MESSAGE: false, | |
CAN_GO_OUTSIDE_ROOM: false, | |
HITS_OWN_TYPE: 'normal', // hard, repel, never, hardWithBuffer | |
DIE_AT_LOW_SPEED: false, | |
DIE_AT_RANGE: false, | |
CLEAR_ON_MASTER_UPGRADE: false, | |
PERSISTS_AFTER_DEATH: false, | |
VARIES_IN_SIZE: false, | |
HEALTH_WITH_LEVEL: true, | |
CAN_BE_ON_LEADERBOARD: true, | |
HAS_NO_RECOIL: false, | |
AUTO_UPGRADE: 'none', | |
BUFF_VS_FOOD: false, | |
OBSTACLE: false, | |
CRAVES_ATTENTION: false, | |
NECRO: false, | |
UPGRADES_TIER_1: [], | |
UPGRADES_TIER_2: [], | |
UPGRADES_TIER_3: [], | |
SKILL: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
LEVEL: 0, | |
SKILL_CAP: [dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl], | |
GUNS: [], | |
MAX_CHILDREN: 0, | |
BODY: { | |
ACCELERATION: 1, | |
SPEED: 0, | |
HEALTH: 1, | |
RESIST: 1, | |
SHIELD: 0, | |
REGEN: 0, | |
DAMAGE: 1, | |
PENETRATION: 1, | |
RANGE: 0, | |
FOV: 1, | |
DENSITY: 1, | |
STEALTH: 1, | |
PUSHABILITY: 1, | |
HETERO: 2, | |
}, | |
FOOD: { | |
LEVEL: -1, | |
}, | |
}; | |
// FOOD | |
exports.food = { | |
TYPE: 'food', | |
DAMAGE_CLASS: 1, | |
CONTROLLERS: ['moreFoodWhenDie'], | |
HITS_OWN_TYPE: 'repel', | |
MOTION_TYPE: 'drift', | |
FACING_TYPE: 'turnWithSpeed', | |
VARIES_IN_SIZE: true, | |
BODY: { | |
ACCELERATION: 0.1, | |
STEALTH: 30, | |
PUSHABILITY: 1, | |
}, | |
DAMAGE_EFFECTS: false, | |
RATEFFECTS: false, | |
HEALTH_WITH_LEVEL: false, | |
}; | |
exports.base = base = { | |
ACCEL: 1.6, | |
SPEED: 5.25, | |
HEALTH: 20, | |
DAMAGE: 3, | |
RESIST: 1, | |
PENETRATION: 1.05, | |
SHIELD: 8, | |
REGEN: 0.025, | |
FOV: 1, | |
DENSITY: 0.5, | |
}; | |
exports.genericTank = { | |
LABEL: 'Unknown Class', | |
TYPE: 'tank', | |
DAMAGE_CLASS: 2, | |
DANGER: 5, | |
MOTION_TYPE: 'motor', | |
FACING_TYPE: 'toTarget', | |
SIZE: 12, | |
MAX_CHILDREN: 0, | |
DAMAGE_EFFECTS: false, | |
CONTROLLERS: [], | |
BODY: { // def | |
ACCELERATION: base.ACCEL, | |
SPEED: base.SPEED, | |
HEALTH: base.HEALTH, | |
DAMAGE: base.DAMAGE, | |
PENETRATION: base.PENETRATION, | |
SHIELD: base.SHIELD, | |
REGEN: base.REGEN, | |
FOV: base.FOV, | |
DENSITY: base.DENSITY, | |
PUSHABILITY: 0.9, | |
HETERO: 3, | |
}, | |
GUNS: [], | |
TURRETS: [], | |
GIVE_KILL_MESSAGE: true, | |
DRAW_HEALTH: true, | |
}; | |
exports.basePolygonDamage = 1; | |
exports.basePolygonHealth = 1; | |
exports.wepHealthFactor = wepHealthFactor = 0.5; | |
exports.wepDamageFactor = wepDamageFactor = 1.5; | |
exports.bullet = { | |
LABEL: 'Bullet', | |
TYPE: 'bullet', | |
ACCEPTS_SCORE: false, | |
BODY: { | |
PENETRATION: 1, | |
SPEED: 3.75, | |
RANGE: 90, | |
DENSITY: 1.25, | |
HEALTH: 0.33 * wepHealthFactor, | |
DAMAGE: 4 * wepDamageFactor, | |
PUSHABILITY: 0.3, | |
}, | |
FACING_TYPE: 'smoothWithMotion', | |
CAN_GO_OUTSIDE_ROOM: true, | |
HITS_OWN_TYPE: 'never', | |
// DIE_AT_LOW_SPEED: true, | |
DIE_AT_RANGE: true, | |
}; | |
exports.trap = { | |
LABEL: 'Thrown Trap', | |
TYPE: 'trap', | |
ACCEPTS_SCORE: false, | |
SHAPE: -3, | |
MOTION_TYPE: 'glide', // def | |
FACING_TYPE: 'turnWithSpeed', | |
HITS_OWN_TYPE: 'push', | |
DIE_AT_RANGE: true, | |
BODY: { | |
HEALTH: 1 * wepHealthFactor, | |
DAMAGE: 2 * wepDamageFactor, | |
RANGE: 450, | |
DENSITY: 2.5, | |
RESIST: 2.5, | |
SPEED: 0, | |
}, | |
}; | |
exports.drone = { | |
LABEL: 'Drone', | |
TYPE: 'drone', | |
ACCEPTS_SCORE: false, | |
DANGER: 2, | |
CONTROL_RANGE: 0, | |
SHAPE: 3, | |
MOTION_TYPE: 'chase', | |
FACING_TYPE: 'smoothToTarget', | |
CONTROLLERS: [ | |
'nearestDifferentMaster', | |
'canRepel', | |
'mapTargetToGoal', | |
'hangOutNearMaster' | |
], | |
AI: { BLIND: true, }, | |
BODY: { | |
PENETRATION: 1.2, | |
PUSHABILITY: 0.6, | |
ACCELERATION: 0.05, | |
HEALTH: 0.6 * wepHealthFactor, | |
DAMAGE: 1.25 * wepDamageFactor, | |
SPEED: 3.8, | |
RANGE: 200, | |
DENSITY: 0.03, | |
RESIST: 1.5, | |
FOV: 0.8, | |
}, | |
HITS_OWN_TYPE: 'hard', | |
DRAW_HEALTH: false, | |
CLEAR_ON_MASTER_UPGRADE: true, | |
BUFF_VS_FOOD: true, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global exports */ | |
const {combineStats, skillSet, g, dfltskl, statNames, gunCalcNames, food, basePolygonDamage, basePolygonHealth, wepHealthFactor, wepDamageFactor} = require('./basedefinitions.js') | |
exports.bullet = require('./basedefinitions.js').bullet | |
exports.food = food | |
exports.genericEntity = require('./basedefinitions.js').genericEntity | |
exports.genericTank = require('./basedefinitions.js').genericTank | |
exports.trap = require('./basedefinitions.js').trap | |
exports.flag = require("../gamemodes/ctf.definitions.js").flag | |
const base = { | |
ACCEL: 1.6, | |
SPEED: 5.25, | |
HEALTH: 20, | |
DAMAGE: 3, | |
RESIST: 1, | |
PENETRATION: 1.05, | |
SHIELD: 8, | |
REGEN: 0.025, | |
FOV: 1, | |
DENSITY: 0.5, | |
}; | |
// NAMES | |
const statnames = { | |
smasher: 1, | |
drone: 2, | |
necro: 3, | |
swarm: 4, | |
trap: 5, | |
generic: 6, | |
}; | |
// ENTITY DEFINITIONS | |
exports.genericEntity = { | |
NAME: '', | |
LABEL: 'Unknown Entity', | |
TYPE: 'unknown', | |
DAMAGE_CLASS: 0, // 0: def, 1: food, 2: tanks, 3: obstacles | |
DANGER: 0, | |
VALUE: 0, | |
SHAPE: 0, | |
COLOR: 16, | |
INDEPENDENT: false, | |
CONTROLLERS: ['doNothing'], | |
HAS_NO_MASTER: false, | |
MOTION_TYPE: 'glide', // motor, swarm, chase | |
FACING_TYPE: 'toTarget', // turnWithSpeed, withMotion, looseWithMotion, toTarget, looseToTarget | |
DRAW_HEALTH: false, | |
DRAW_SELF: true, | |
DAMAGE_EFFECTS: true, | |
RATEFFECTS: true, | |
MOTION_EFFECTS: true, | |
INTANGIBLE: false, | |
ACCEPTS_SCORE: true, | |
GIVE_KILL_MESSAGE: false, | |
CAN_GO_OUTSIDE_ROOM: false, | |
HITS_OWN_TYPE: 'normal', // hard, repel, never, hardWithBuffer | |
DIE_AT_LOW_SPEED: false, | |
DIE_AT_RANGE: false, | |
CLEAR_ON_MASTER_UPGRADE: false, | |
PERSISTS_AFTER_DEATH: false, | |
VARIES_IN_SIZE: false, | |
HEALTH_WITH_LEVEL: true, | |
CAN_BE_ON_LEADERBOARD: true, | |
HAS_NO_RECOIL: false, | |
AUTO_UPGRADE: 'none', | |
BUFF_VS_FOOD: false, | |
OBSTACLE: false, | |
CRAVES_ATTENTION: false, | |
NECRO: false, | |
UPGRADES_TIER_1: [], | |
UPGRADES_TIER_2: [], | |
UPGRADES_TIER_3: [], | |
SKILL: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
LEVEL: 0, | |
SKILL_CAP: [dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl, dfltskl], | |
GUNS: [], | |
MAX_CHILDREN: 0, | |
BODY: { | |
ACCELERATION: 1, | |
SPEED: 0, | |
HEALTH: 1, | |
RESIST: 1, | |
SHIELD: 0, | |
REGEN: 0, | |
DAMAGE: 1, | |
PENETRATION: 1, | |
RANGE: 0, | |
FOV: 1, | |
DENSITY: 1, | |
STEALTH: 1, | |
PUSHABILITY: 1, | |
HETERO: 2, | |
}, | |
FOOD: { | |
LEVEL: -1, | |
}, | |
}; | |
// FOOD | |
exports.food = { | |
TYPE: 'food', | |
DAMAGE_CLASS: 1, | |
CONTROLLERS: [], | |
HITS_OWN_TYPE: 'repel', | |
MOTION_TYPE: 'drift', | |
FACING_TYPE: 'turnWithSpeed', | |
VARIES_IN_SIZE: true, | |
BODY: { | |
STEALTH: 30, | |
PUSHABILITY: 1, | |
}, | |
DAMAGE_EFFECTS: false, | |
RATEFFECTS: false, | |
HEALTH_WITH_LEVEL: false, | |
}; | |
exports.dominationBody = { | |
LABEL: '', | |
CONTROLLERS: ['dontTurn'], | |
COLOR: 9, | |
SHAPE: 8, | |
//HETERO: 0, | |
INDEPENDENT: true, | |
}; | |
exports.droneDominator = { | |
PARENT: [exports.genericTank], | |
STAT_NAMES: statnames.drone, | |
LABEL: 'Dominator', | |
FACING_TYPE: 'autospin', | |
DANGER: 10, | |
SIZE: 30, | |
// TYPE: 'fixed', | |
SKILL: skillSet({ | |
dam: 2, | |
pen: 2, | |
str: 1, | |
}), | |
//TYPE: exports.drone, | |
MAX_CHILDREN:80, | |
CONTROLLERS: ['nearestDifferentMaster', 'mapAltToFire', 'setDominatorControl'], | |
BODY: { | |
HEALTH: 6148, | |
SHIELD: base.SHIELD * 1.25, | |
REGEN: base.REGEN * 0.75, | |
SPEED: 0, | |
ACCELERATION: base.ACCEL * 0.5, | |
PUSHABILITY: 0.15, | |
AUTOFIRE:true, | |
//TYPE: exports.drone, | |
}, | |
GUNS: [{ | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 0, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
//MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 0, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 60, 1 / 6], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
//MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 60, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 120, 1 / 3], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
//MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 120, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 180, 1 / 2], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
//MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 180, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 240, 2 / 3], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
//SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
//WAIT_TO_CYCLE: true, | |
//MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 240, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 300, 5 / 6], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
//SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
//WAIT_TO_CYCLE: true, | |
//MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 300, 0], | |
}], | |
AUTOFIRE: true, | |
TURRETS: [{ | |
POSITION: [22, 0, 0, 0, 360, 0], | |
TYPE: exports.dominationBody, | |
}], | |
GIVE_KILL_MESSAGE: true, | |
//ACCEPTS_SCORE: false, | |
}; | |
//drone_dominator: [1.25, 1, 1, 0.9, 1.25, 1.5, 1.5, 1.05, 0.9, 1, 2, 1, 1], | |
exports.dominator = require('./dominator.js').dominator | |
exports.destroyerDominator = require('./dominator.js').destroyerDominator | |
exports.trapperDominator = require('./dominator.js').trapperDominator | |
exports.gunnerDominator = require('./dominator.js').gunnerDominator | |
exports.hugePentagon = { | |
PARENT: [exports.food], | |
CONTROLLERS: ['moreFoodWhenDie'], | |
FOOD: { | |
LEVEL: 5, | |
}, | |
LABEL: 'Alpha Pentagon', | |
VALUE: 15000, | |
SHAPE: -5, | |
SIZE: 58, | |
COLOR: 14, | |
BODY: { | |
DAMAGE: 2 * basePolygonDamage, | |
DENSITY: 80, | |
HEALTH: 300 * basePolygonHealth, | |
RESIST: Math.pow(1.25, 3), | |
SHIELD: 40 * basePolygonHealth, | |
REGEN: 0.6, | |
}, | |
DRAW_HEALTH: true, | |
GIVE_KILL_MESSAGE: true, | |
}; | |
exports.bigPentagon = { | |
PARENT: [exports.food], | |
CONTROLLERS: ['moreFoodWhenDie'], | |
FOOD: { | |
LEVEL: 4, | |
}, | |
LABEL: 'Beta Pentagon', | |
VALUE: 2500, | |
SHAPE: 5, | |
SIZE: 30, | |
COLOR: 14, | |
BODY: { | |
DAMAGE: 2 * basePolygonDamage, | |
DENSITY: 30, | |
HEALTH: 50 * basePolygonHealth, | |
RESIST: Math.pow(1.25, 2), | |
SHIELD: 20 * basePolygonHealth, | |
REGEN: 0.2, | |
}, | |
DRAW_HEALTH: true, | |
GIVE_KILL_MESSAGE: true, | |
}; | |
exports.pentagon = { | |
PARENT: [exports.food], | |
CONTROLLERS: ['moreFoodWhenDie'], | |
FOOD: { | |
LEVEL: 3, | |
}, | |
LABEL: 'Pentagon', | |
VALUE: 400, | |
SHAPE: 5, | |
SIZE: 16, | |
COLOR: 14, | |
BODY: { | |
DAMAGE: 1.5 * basePolygonDamage, | |
DENSITY: 8, | |
HEALTH: 10 * basePolygonHealth, | |
RESIST: 1.25, | |
PENETRATION: 1.1, | |
}, | |
DRAW_HEALTH: true, | |
}; | |
exports.triangle = { | |
PARENT: [exports.food], | |
CONTROLLERS: ['moreFoodWhenDie'], | |
FOOD: { | |
LEVEL: 2, | |
}, | |
LABEL: 'Triangle', | |
VALUE: 120, | |
SHAPE: 3, | |
SIZE: 9, | |
COLOR: 2, | |
BODY: { | |
DAMAGE: basePolygonDamage, | |
DENSITY: 6, | |
HEALTH: 3 * basePolygonHealth, | |
RESIST: 1.15, | |
PENETRATION: 1.5, | |
}, | |
DRAW_HEALTH: true, | |
}; | |
exports.square = { | |
PARENT: [exports.food], | |
FOOD: { | |
LEVEL: 1, | |
}, | |
LABEL: 'Square', | |
CONTROLLERS: ['moreFoodWhenDie'], | |
VALUE: 30, | |
SHAPE: 4, | |
SIZE: 10, | |
COLOR: 13, | |
BODY: { | |
DAMAGE: basePolygonDamage, | |
DENSITY: 4, | |
HEALTH: basePolygonHealth, | |
PENETRATION: 2, | |
}, | |
DRAW_HEALTH: true, | |
INTANGIBLE: false, | |
}; | |
exports.egg = { | |
PARENT: [exports.food], | |
FOOD: { | |
LEVEL: 0, | |
}, | |
LABEL: 'Egg', | |
VALUE: 10, | |
SHAPE: 0, | |
SIZE: 5, | |
COLOR: 6, | |
INTANGIBLE: true, | |
BODY: { | |
DAMAGE: 0, | |
DENSITY: 2, | |
HEALTH: 0.0011, | |
PUSHABILITY: 0, | |
}, | |
DRAW_HEALTH: false, | |
}; | |
exports.greenpentagon = { | |
PARENT: [exports.food], | |
LABEL: 'Pentagon', | |
VALUE: 30000, | |
SHAPE: 5, | |
SIZE: 16, | |
COLOR: 1, | |
BODY: { | |
DAMAGE: 3, | |
DENSITY: 8, | |
HEALTH: 200, | |
RESIST: 1.25, | |
PENETRATION: 1.1, | |
}, | |
DRAW_HEALTH: true, | |
}; | |
exports.greentriangle = { | |
PARENT: [exports.food], | |
LABEL: 'Triangle', | |
VALUE: 7000, | |
SHAPE: 3, | |
SIZE: 9, | |
COLOR: 1, | |
BODY: { | |
DAMAGE: 1, | |
DENSITY: 6, | |
HEALTH: 60, | |
RESIST: 1.15, | |
PENETRATION: 1.5, | |
}, | |
DRAW_HEALTH: true, | |
}; | |
exports.greensquare = { | |
PARENT: [exports.food], | |
LABEL: 'Square', | |
VALUE: 2000, | |
SHAPE: 4, | |
SIZE: 10, | |
COLOR: 1, | |
BODY: { | |
DAMAGE: 0.5, | |
DENSITY: 4, | |
HEALTH: 20, | |
PENETRATION: 2, | |
}, | |
DRAW_HEALTH: true, | |
INTANGIBLE: false, | |
}; | |
exports.gem = { | |
PARENT: [exports.food], | |
LABEL: 'Gem', | |
VALUE: 2000, | |
SHAPE: 6, | |
SIZE: 5, | |
COLOR: 0, | |
BODY: { | |
DAMAGE: basePolygonDamage/4, | |
DENSITY: 4, | |
HEALTH: 10, | |
PENETRATION: 2, | |
RESIST: 2, | |
PUSHABILITY: 0.25, | |
}, | |
DRAW_HEALTH: true, | |
INTANGIBLE: false, | |
}; | |
exports.obstacle = { | |
TYPE: 'wall', | |
DAMAGE_CLASS: 1, | |
LABEL: 'Rock', | |
FACING_TYPE: 'turnWithSpeed', | |
SHAPE: -9, | |
BODY: { | |
PUSHABILITY: 0, | |
HEALTH: 10000, | |
SHIELD: 10000, | |
REGEN: 1000, | |
DAMAGE: 1, | |
RESIST: 100, | |
STEALTH: 1, | |
}, | |
VALUE: 0, | |
SIZE: 60, | |
COLOR: 16, | |
VARIES_IN_SIZE: true, | |
GIVE_KILL_MESSAGE: true, | |
ACCEPTS_SCORE: false, | |
}; | |
exports.babyObstacle = { | |
PARENT: [exports.obstacle], | |
SIZE: 25, | |
SHAPE: -7, | |
LABEL: "Gravel", | |
}; | |
// WEAPONS | |
exports.casing = { | |
PARENT: [exports.bullet], | |
LABEL: 'Shell', | |
TYPE: 'swarm', | |
}; | |
exports.swarm = { | |
LABEL: 'Swarm Drone', | |
TYPE: 'swarm', | |
ACCEPTS_SCORE: false, | |
SHAPE: 3, | |
MOTION_TYPE: 'swarm', | |
FACING_TYPE: 'smoothWithMotion', | |
CONTROLLERS: ['nearestDifferentMaster', 'mapTargetToGoal'], | |
CRAVES_ATTENTION: true, | |
BODY: { | |
ACCELERATION: 3, | |
PENETRATION: 1.5, | |
HEALTH: 0.35 * wepHealthFactor, | |
DAMAGE: 1.5 * wepDamageFactor, | |
SPEED: 4.5, | |
RESIST: 1.6, | |
RANGE: 225, | |
DENSITY: 12, | |
PUSHABILITY: 0.5, | |
FOV: 1.5, | |
}, | |
DIE_AT_RANGE: true, | |
BUFF_VS_FOOD: true, | |
}; | |
exports.bee = { | |
PARENT: [exports.swarm], | |
PERSISTS_AFTER_DEATH: true, | |
SHAPE: 4, | |
LABEL: 'Drone', | |
HITS_OWN_TYPE: 'hardWithBuffer', | |
}; | |
exports.autoswarm = { | |
PARENT: [exports.swarm], | |
AI: { FARMER: true, }, | |
INDEPENDENT: true, | |
}; | |
exports.lance = { | |
LABEL: 'Set Trap', | |
TYPE: 'lance', | |
PARENT: [exports.trap], | |
SHAPE: -4, //todo: change shape | |
MOTION_TYPE: 'motor', | |
CONTROLLERS: ['goToMasterTarget', 'lanceRetract'], | |
BODY: { | |
SPEED: 1, | |
DENSITY: 5, | |
}, | |
}; | |
exports.flag = { | |
LABEL: 'FLAG', | |
TYPE: 'flag', | |
BODY: { | |
} | |
} | |
exports.block = { | |
LABEL: 'Set Trap', | |
PARENT: [exports.trap], | |
SHAPE: -4, | |
MOTION_TYPE: 'motor', | |
CONTROLLERS: ['goToMasterTarget'], | |
BODY: { | |
SPEED: 1, | |
DENSITY: 5, | |
}, | |
}; | |
exports.boomerang = { | |
LABEL: 'Boomerang', | |
PARENT: [exports.trap], | |
CONTROLLERS: ['boomerang'], | |
MOTION_TYPE: 'motor', | |
HITS_OWN_TYPE: 'never', | |
SHAPE: -5, | |
BODY: { | |
SPEED: 1.25, | |
RANGE: 120, | |
}, | |
}; | |
exports.drone = { | |
LABEL: 'Drone', | |
TYPE: 'drone', | |
ACCEPTS_SCORE: false, | |
DANGER: 2, | |
CONTROL_RANGE: 0, | |
SHAPE: 3, | |
MOTION_TYPE: 'chase', | |
FACING_TYPE: 'smoothToTarget', | |
CONTROLLERS: [ | |
'nearestDifferentMaster', | |
'canRepel', | |
'mapTargetToGoal', | |
'hangOutNearMaster' | |
], | |
AI: { BLIND: true, }, | |
BODY: { | |
PENETRATION: 1.2, | |
PUSHABILITY: 0.6, | |
ACCELERATION: 0.05, | |
HEALTH: 0.6 * wepHealthFactor, | |
DAMAGE: 1.25 * wepDamageFactor, | |
SPEED: 3.8, | |
RANGE: 200, | |
DENSITY: 0.03, | |
RESIST: 1.5, | |
FOV: 0.8, | |
}, | |
HITS_OWN_TYPE: 'hard', | |
DRAW_HEALTH: false, | |
CLEAR_ON_MASTER_UPGRADE: true, | |
BUFF_VS_FOOD: true, | |
}; | |
exports.sunchip = { | |
PARENT: [exports.drone], | |
SHAPE: 4, | |
NECRO: true, | |
HITS_OWN_TYPE: 'hard', | |
BODY: { | |
FOV: 0.5, | |
}, | |
AI: { | |
BLIND: true, | |
FARMER: true, | |
}, | |
DRAW_HEALTH: false, | |
}; | |
exports.autosunchip = { | |
PARENT: [exports.sunchip], | |
AI: { | |
BLIND: true, | |
FARMER: true, | |
}, | |
INDEPENDENT: true, | |
}; | |
exports.gunchip = { | |
PARENT: [exports.drone], | |
SHAPE: -2, | |
NECRO: true, | |
HITS_OWN_TYPE: 'hard', | |
BODY: { | |
FOV: 0.5, | |
}, | |
AI: { | |
BLIND: true, | |
FARMER: true, | |
}, | |
DRAW_HEALTH: false, | |
}; | |
exports.missile = { | |
PARENT: [exports.bullet], | |
LABEL: 'Missile', | |
INDEPENDENT: true, | |
BODY: { | |
RANGE: 120, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 6, 1, 0, -2, 130, 0, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
SHOOT_SETTINGS: combineStats([g.basic, g.skim, g.doublereload, g.lowpower, g.muchmorerecoil, g.morespeed, g.morespeed]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
STAT_CALCULATOR: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 14, 6, 1, 0, 2, 230, 0, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
SHOOT_SETTINGS: combineStats([g.basic, g.skim, g.doublereload, g.lowpower, g.muchmorerecoil, g.morespeed, g.morespeed]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
STAT_CALCULATOR: gunCalcNames.thruster, | |
}, }, | |
], | |
}; | |
exports.hypermissile = { | |
PARENT: [exports.missile], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 6, 1, 0, -2, 150, 0, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
SHOOT_SETTINGS: combineStats([g.basic, g.skim, g.doublereload, g.lowpower, g.morerecoil, g.morespeed]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
STAT_CALCULATOR: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 14, 6, 1, 0, 2, 210, 0, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
SHOOT_SETTINGS: combineStats([g.basic, g.skim, g.doublereload, g.lowpower, g.morerecoil, g.morespeed]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
STAT_CALCULATOR: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 14, 6, 1, 0, -2, 90, 0.5, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
SHOOT_SETTINGS: combineStats([g.basic, g.skim, g.doublereload, g.lowpower, g.morerecoil, g.morespeed]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
}, }, { | |
POSITION: [ 14, 6, 1, 0, 2, 270, 0.5, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
SHOOT_SETTINGS: combineStats([g.basic, g.skim, g.doublereload, g.lowpower, g.morerecoil, g.morespeed]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
}, }, | |
], | |
}; | |
exports.snake = { | |
PARENT: [exports.bullet], | |
LABEL: 'Snake', | |
INDEPENDENT: true, | |
BODY: { | |
RANGE: 120, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 12, 1.4, 8, 0, 180, 0, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
STAT_CALCULATOR: gunCalcNames.thruster, | |
SHOOT_SETTINGS: combineStats([ | |
g.basic, g.sniper, g.hunter, g.hunter2, g.snake, g.snakeskin, | |
]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
}, }, { | |
POSITION: [ 10, 12, 0.8, 8, 0, 180, 0.5, ], | |
PROPERTIES: { | |
AUTOFIRE: true, | |
NEGATIVE_RECOIL: true, | |
STAT_CALCULATOR: gunCalcNames.thruster, | |
SHOOT_SETTINGS: combineStats([ | |
g.basic, g.sniper, g.hunter, g.hunter2, g.snake, | |
]), | |
TYPE: [exports.bullet, { PERSISTS_AFTER_DEATH: true, }], | |
}, }, | |
], | |
}; | |
exports.hive = { | |
PARENT: [exports.bullet], | |
LABEL: 'Hive', | |
BODY: { | |
RANGE: 90, | |
FOV: 0.5, | |
}, | |
FACING_TYPE: 'turnWithSpeed', | |
INDEPENDENT: true, | |
CONTROLLERS: ['alwaysFire', 'nearestDifferentMaster', 'targetSelf',], | |
AI: { NO_LEAD: true, }, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 7, 9.5, 0.6, 7, 0, 108, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.hive, g.bees]), | |
TYPE: exports.bee, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 9.5, 0.6, 7, 0, 180, 0.2, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.hive, g.bees]), | |
TYPE: exports.bee, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 9.5, 0.6, 7, 0, 252, 0.4, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.hive, g.bees]), | |
TYPE: exports.bee, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 9.5, 0.6, 7, 0, 324, 0.6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.hive, g.bees]), | |
TYPE: exports.bee, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 9.5, 0.6, 7, 0, 36, 0.8, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.hive, g.bees]), | |
TYPE: exports.bee, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, | |
], | |
}; | |
// TANK CLASSES | |
let gun = { }; | |
exports.autoTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Turret', | |
BODY: { | |
FOV: 0.8 | |
}, | |
COLOR: 16, | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 22, 10, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.morerecoil, g.turret]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.machineAutoTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Turret', | |
COLOR: 16, | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 11, 1.3, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.morerecoil, g.turret, g.mach, g.slow]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.autoSmasherTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Turret', | |
COLOR: 16, | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 6, 1, 0, 5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.morerecoil, g.turret, g.fast, g.mach, g.pound, g.morereload, g.morereload]), | |
TYPE: exports.bullet, | |
STAT_CALCULATOR: gunCalcNames.fixedReload, | |
}, }, { | |
POSITION: [ 20, 6, 1, 0, -5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.morerecoil, g.turret, g.fast, g.mach, g.pound, g.morereload, g.morereload]), | |
TYPE: exports.bullet, | |
STAT_CALCULATOR: gunCalcNames.fixedReload, | |
}, }, | |
], | |
}; | |
exports.oldAutoSmasherTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Turret', | |
COLOR: 16, | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 7, 1, 0, -5.75, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.lotsmorrecoil, g.morereload]), | |
TYPE: exports.bullet, | |
STAT_CALCULATOR: gunCalcNames.fixedReload, | |
}, }, { | |
POSITION: [ 20, 7, 1, 0, 5.75, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.lotsmorrecoil, g.morereload]), | |
TYPE: exports.bullet, | |
STAT_CALCULATOR: gunCalcNames.fixedReload, | |
}, }, | |
], | |
}; | |
exports.auto3gun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
FOV: 3, | |
}, | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 22, 10, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.auto]), | |
TYPE: exports.bullet, | |
}, } | |
], | |
}; | |
exports.auto5gun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
FOV: 3, | |
}, | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 24, 11, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.auto, g.five]), | |
TYPE: exports.bullet, | |
}, } | |
], | |
}; | |
exports.heavy3gun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
FOV: 2, | |
SPEED: 0.9, | |
}, | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 22, 14, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.auto]), | |
TYPE: exports.bullet, | |
}, } | |
], | |
}; | |
exports.masterGun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
FOV: 3, | |
}, | |
CONTROLLERS: ['nearestDifferentMaster'], | |
COLOR: 16, | |
MAX_CHILDREN: 6, | |
AI: { | |
NO_LEAD: true, | |
SKYNET: true, | |
FULL_VIEW: true, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 8, 14, 1.3, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.master]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
}, }, | |
], | |
}; | |
exports.sniper3gun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
FOV: 5, | |
}, | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 27, 9, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.auto, g.assass, g.autosnipe]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 5, 9, -1.5, 8, 0, 0, 0, ], | |
}, | |
], | |
}; | |
exports.bansheegun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
COLOR: 16, | |
INDEPENDENT: true, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 26, 10, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.auto, g.lessreload]), | |
TYPE: exports.bullet, | |
}, } | |
], | |
}; | |
exports.auto4gun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
FOV: 2, | |
}, | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 16, 4, 1, 0, -3.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.auto, g.gunner, g.twin, g.power, g.slow]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 16, 4, 1, 0, 3.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.auto, g.gunner, g.twin, g.power, g.slow]), | |
TYPE: exports.bullet, | |
}, } | |
], | |
}; | |
exports.bigauto4gun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 5, 1, 0, -4.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.auto, g.gunner, g.twin, g.twin, g.power, g.halfreload]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 14, 5, 1, 0, 4.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.auto, g.gunner, g.twin, g.twin, g.power, g.halfreload]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 16, 5, 1, 0, 0, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.auto, g.gunner, g.twin, g.twin, g.power, g.halfreload]), | |
TYPE: exports.bullet, | |
}, } | |
], | |
}; | |
exports.tritrapgun = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 16, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 2, 16, 1.1, 20, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.auto]), | |
TYPE: exports.block, | |
}, }, | |
], | |
}; | |
exports.smasherBody = { | |
LABEL: '', | |
CONTROLLERS: ['spin'], | |
COLOR: 9, | |
SHAPE: 6, | |
INDEPENDENT: true, | |
}; | |
exports.spikeBody = { | |
LABEL: '', | |
CONTROLLERS: ['spin'], | |
COLOR: 9, | |
SHAPE: -4, | |
INDEPENDENT: true, | |
}; | |
exports.spikeBody1 = { | |
LABEL: '', | |
CONTROLLERS: ['fastspin'], | |
COLOR: 9, | |
SHAPE: 3, | |
INDEPENDENT: true, | |
}; | |
exports.spikeBody2 = { | |
LABEL: '', | |
CONTROLLERS: ['reversespin'], | |
COLOR: 9, | |
SHAPE: 3, | |
INDEPENDENT: true, | |
}; | |
exports.megasmashBody = { | |
LABEL: '', | |
CONTROLLERS: ['spin'], | |
COLOR: 9, | |
SHAPE: -6, | |
INDEPENDENT: true, | |
}; | |
exports.dominator = require('./dominator.js').dominator | |
exports.baseSwarmTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Protector', | |
COLOR: 16, | |
BODY: { | |
FOV: 2, | |
}, | |
CONTROLLERS: ['nearestDifferentMaster'], | |
AI: { | |
NO_LEAD: true, | |
LIKES_SHAPES: true, | |
}, | |
INDEPENDENT: true, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 5, 4.5, 0.6, 7, 2, 0, 0.15, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.protectorswarm]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 5, 4.5, 0.6, 7, -2, 0, 0.15, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.protectorswarm]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 5, 4.5, 0.6, 7.5, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.protectorswarm]), | |
TYPE: [exports.swarm, { INDEPENDENT: true, AI: { LIKES_SHAPES: true, }, }, ], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, } | |
], | |
}; | |
exports.baseGunTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Protector', | |
BODY: { | |
FOV: 5, | |
}, | |
ACCEPTS_SCORE: false, | |
CONTROLLERS: ['nearestDifferentMaster'], | |
INDEPENDENT: true, | |
COLOR: 16, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 12, 12, 1, 6, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.destroy]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 11, 13, 1, 6, 0, 0, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.destroy]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 7, 13, -1.3, 6, 0, 0, 0, ], | |
} | |
], | |
}; | |
exports.dominationBody = { | |
LABEL: '', | |
CONTROLLERS: ['dontTurn'], | |
COLOR: 9, | |
SHAPE: 8, | |
INDEPENDENT: true, | |
}; | |
exports.baseProtector = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Base', | |
SIZE: 64, | |
DAMAGE_CLASS: 0, | |
ACCEPTS_SCORE: false, | |
SKILL: skillSet({ | |
rld: 1, | |
dam: 1, | |
pen: 1, | |
spd: 1, | |
str: 1, | |
}), | |
BODY: { // def | |
SPEED: 0, | |
HEALTH: 10000, | |
DAMAGE: 10, | |
PENETRATION: 0.25, | |
SHIELD: 1000, | |
REGEN: 100, | |
FOV: 1, | |
PUSHABILITY: 0, | |
HETERO: 0, | |
}, | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 25, 0, 0, 0, 360, 0], | |
TYPE: exports.dominationBody, | |
}, { | |
POSITION: [ 12, 7, 0, 45, 100, 0], | |
TYPE: exports.baseSwarmTurret, | |
}, { | |
POSITION: [ 12, 7, 0, 135, 100, 0], | |
TYPE: exports.baseSwarmTurret, | |
}, { | |
POSITION: [ 12, 7, 0, 225, 100, 0], | |
TYPE: exports.baseSwarmTurret, | |
}, { | |
POSITION: [ 12, 7, 0, 315, 100, 0], | |
TYPE: exports.baseSwarmTurret, | |
}, | |
], | |
GUNS: [ /***** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ { | |
POSITION: [ 4.5, 11.5, -1.3, 6, 0, 45, 0, ], }, { | |
POSITION: [ 4.5, 11.5, -1.3, 6, 0, 135, 0, ], }, { | |
POSITION: [ 4.5, 11.5, -1.3, 6, 0, 225, 0, ], }, { | |
POSITION: [ 4.5, 11.5, -1.3, 6, 0, 315, 0, ], }, { | |
POSITION: [ 4.5, 8.5, -1.5, 7, 0, 45, 0, ], }, { | |
POSITION: [ 4.5, 8.5, -1.5, 7, 0, 135, 0, ], }, { | |
POSITION: [ 4.5, 8.5, -1.5, 7, 0, 225, 0, ], }, { | |
POSITION: [ 4.5, 8.5, -1.5, 7, 0, 315, 0, ], }, | |
], | |
}; | |
exports.minion = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Minion', | |
TYPE: 'minion', | |
DAMAGE_CLASS: 0, | |
HITS_OWN_TYPE: 'hardWithBuffer', | |
FACING_TYPE: 'smoothToTarget', | |
BODY: { | |
FOV: 0.5, | |
SPEED: 3, | |
ACCELERATION: 0.4, | |
HEALTH: 5, | |
SHIELD: 0, | |
DAMAGE: 1.2, | |
RESIST: 1, | |
PENETRATION: 1, | |
DENSITY: 0.4, | |
}, | |
AI: { | |
BLIND: true, | |
}, | |
DRAW_HEALTH: false, | |
CLEAR_ON_MASTER_UPGRADE: true, | |
GIVE_KILL_MESSAGE: false, | |
CONTROLLERS: [ | |
'nearestDifferentMaster', 'mapAltToFire', 'minion', 'canRepel', 'hangOutNearMaster'], | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 17, 9, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.minion]), | |
WAIT_TO_CYCLE: true, | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.pillboxTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
COLOR: 16, | |
BODY: { | |
FOV: 2, | |
}, | |
HAS_NO_RECOIL: true, | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 22, 11, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.minion, g.turret, g.power, g.auto, g.notdense]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.pillbox = { | |
LABEL: 'Pillbox', | |
PARENT: [exports.trap], | |
SHAPE: -4, | |
MOTION_TYPE: 'motor', | |
CONTROLLERS: ['goToMasterTarget', 'nearestDifferentMaster'], | |
INDEPENDENT: true, | |
BODY: { | |
SPEED: 1, | |
DENSITY: 5, | |
}, | |
DIE_AT_RANGE: true, | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 11, 0, 0, 0, 360, 1], | |
TYPE: exports.pillboxTurret, | |
} | |
] | |
}; | |
exports.skimturret = { | |
PARENT: [exports.genericTank], | |
BODY: { | |
FOV: base.FOV * 2, | |
}, | |
COLOR: 2, | |
CONTROLLERS: ['canRepel', 'onlyAcceptInArc', 'mapAltToFire', 'nearestDifferentMaster'], | |
LABEL: '', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 10, 14, -0.5, 9, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.arty, g.arty, g.skim]), | |
TYPE: exports.hypermissile, | |
}, }, { | |
POSITION: [ 17, 15, 1, 0, 0, 0, 0, ], | |
}, | |
], | |
}; | |
exports.skimboss = { | |
PARENT: [exports.genericTank], | |
BODY: { | |
HEALTH: 300, | |
DAMAGE: 2, | |
SHIELD: 200, | |
}, | |
SHAPE: 3, | |
COLOR: 2, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 15, 5, 0, 60, 170, 0], | |
TYPE: exports.skimturret, | |
}, { | |
POSITION: [ 15, 5, 0, 180, 170, 0], | |
TYPE: exports.skimturret, | |
}, { | |
POSITION: [ 15, 5, 0, 300, 170, 0], | |
TYPE: exports.skimturret, | |
}, | |
], | |
}; | |
function makeAuto(type, name = -1, options = {}) { | |
let turret = { type: exports.autoTurret, size: 10, independent: true, }; | |
if (options.type != null) { turret.type = options.type; } | |
if (options.size != null) { turret.size = options.size; } | |
if (options.independent != null) { turret.independent = options.independent; } | |
let output = JSON.parse(JSON.stringify(type)); | |
let autogun = { | |
/********* SIZE X Y ANGLE ARC */ | |
POSITION: [ turret.size, 0, 0, 180, 360, 1,], | |
TYPE: [turret.type, { CONTROLLERS: ['nearestDifferentMaster'], INDEPENDENT: turret.independent, }], | |
}; | |
if (type.GUNS != null) { output.GUNS = type.GUNS; } | |
if (type.TURRETS == null) { output.TURRETS = [autogun]; } | |
else { output.TURRETS = [...type.TURRETS, autogun]; } | |
if (name == -1) { output.LABEL = 'Auto-' + type.LABEL; } else { output.LABEL = name; } | |
output.DANGER = type.DANGER + 1; | |
return output; | |
} | |
function makeHybrid(type, name = -1) { | |
let output = JSON.parse(JSON.stringify(type)); | |
let spawner = { | |
/********* LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 7, 12, 1.2, 8, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.weak]), | |
TYPE: [exports.drone, { INDEPENDENT: true, }], | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: false, | |
MAX_CHILDREN: 3, | |
}, }; | |
if (type.TURRETS != null) { output.TURRETS = type.TURRETS; } | |
if (type.GUNS == null) { output.GUNS = [spawner]; } | |
else { output.GUNS = [...type.GUNS, spawner]; } | |
if (name == -1) { output.LABEL = 'Hybrid ' + type.LABEL; } else { output.LABEL = name; } | |
return output; | |
} | |
exports.basic = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Basic', | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic]), | |
TYPE: exports.bullet, | |
LABEL: '', // def | |
STAT_CALCULATOR: 0, // def | |
WAIT_TO_CYCLE: false, // def | |
AUTOFIRE: false, // def | |
SYNCS_SKILLS: false, // def | |
MAX_CHILDREN: 0, // def | |
ALT_FIRE: false, // def | |
NEGATIVE_RECOIL: false, // def | |
}, }, | |
], | |
}; | |
exports.testbed = { | |
PARENT: [exports.genericTank], | |
LABEL: 'TESTBED', | |
RESET_UPGRADES: true, | |
SKILL: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0,], | |
LEVEL: -1, | |
BODY: { // def | |
SHIELD: 1000, | |
REGEN: 10, | |
HEALTH: 100, | |
DAMAGE: 10, | |
DENSITY: 20, | |
FOV: 2, | |
}, | |
TURRETS: [], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 10, -1.4, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.op]), | |
TYPE: [exports.bullet, { SHAPE: 5, }], | |
}, }, | |
], | |
}; | |
exports.single = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Single', | |
//CONTROLLERS: ['nearestDifferentMaster'], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 19, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.single]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 5.5, 8, -1.8, 6.5, 0, 0, 0, ], | |
} | |
], | |
}; | |
let smshskl = 12; //13; | |
exports.smash = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Smasher', | |
DANGER: 6, | |
BODY: { | |
FOV: base.FOV * 1.05, | |
DENSITY: base.DENSITY * 2, | |
}, | |
TURRETS: [{ /** SIZE X Y ANGLE ARC */ | |
POSITION: [ 21.5, 0, 0, 0, 360, 0,], | |
TYPE: exports.smasherBody, | |
}], | |
IS_SMASHER: true, | |
SKILL_CAP: [smshskl, 0, 0, 0, 0, smshskl, smshskl, smshskl, smshskl, smshskl,], | |
STAT_NAMES: statnames.smasher, | |
}; | |
exports.smashBullet = { // these are the smashers the dominator fires | |
PARENT: [exports.genericTank], | |
LABEL: 'Smasher', | |
DANGER: 6, | |
BODY: { | |
SPEED: 3, | |
ACCELERATION: 1, | |
HEALTH: 4, | |
SHIELD: 0, | |
DAMAGE: 2, | |
RESIST: 4, | |
PENETRATION: 4, | |
DENSITY: 2, | |
FOV: base.FOV * 0.7, | |
//DENSITY: base.DENSITY * 0.4, | |
//ACCELERATION: 1, | |
//PENETRATION: 1, | |
}, | |
AI: { | |
maxChaseDistanceFromMaster: 100, | |
NO_LEAD: true | |
}, | |
CAN_BE_ON_LEADERBOARD: false, | |
ACCEPTS_SCORE: false, | |
CONTROLLERS: ['nearestDifferentMaster','mapTargetToGoal', 'mapAltToFire', 'canRepel', 'hangOutNearMaster'], | |
TURRETS: [{ /** SIZE X Y ANGLE ARC */ | |
POSITION: [ 21.5, 0, 0, 0, 360, 0,], | |
TYPE: exports.smasherBody, | |
}], | |
VARIES_IN_SIZE: false, | |
//IS_SMASHER: true, | |
SKILL_CAP: [smshskl, 0, 0, 0, 0, smshskl, smshskl, smshskl, smshskl, smshskl,], | |
STAT_NAMES: statnames.smasher, | |
DAMAGE_EFFECTS: false, | |
DAMAGE_CLASS: 0, | |
HEALTH_WITH_LEVEL: false, | |
} | |
exports.smasherDominator = { | |
PARENT: [exports.dominator], | |
LABEL: 'Smasher Dominator', | |
DANGER: 1, | |
MAX_CHILDREN: 8, | |
GUNS: [{ | |
POSITION: [15.25, 6.75, 1, 0, 0, 0, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, /*g.destroyDominator,*/ g.zerorecoil]), | |
TYPE: exports.smashBullet, | |
AUTOFIRE: true, | |
//SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
}, | |
}, { | |
POSITION: [5, 6.75, -1.6, 6.75, 0, 0, 0], | |
}], | |
CONTROLLERS: ['nearestDifferentMaster',/*'spinWhenIdle'*/], | |
TURRETS: [{ /** SIZE X Y ANGLE ARC */ | |
POSITION: [ 21.5, 0, 0, 0, 360, 0,], | |
TYPE: exports.smasherBody, | |
}], | |
} | |
exports.megasmash = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Mega-Smasher', | |
DANGER: 7, | |
BODY: { | |
SPEED: base.speed * 1.05, | |
FOV: base.FOV * 1.1, | |
DENSITY: base.DENSITY * 4, | |
}, | |
IS_SMASHER: true, | |
SKILL_CAP: [smshskl, 0, 0, 0, 0, smshskl, smshskl, smshskl, smshskl, smshskl,], | |
STAT_NAMES: statnames.smasher, | |
TURRETS: [{ /** SIZE X Y ANGLE ARC */ | |
POSITION: [ 24, 0, 0, 0, 360, 0,], | |
TYPE: exports.megasmashBody, | |
}], | |
}; | |
exports.spike = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Spike', | |
DANGER: 7, | |
BODY: { | |
SPEED: base.speed*0.9, | |
DAMAGE: base.DAMAGE * 1.1, | |
FOV: base.FOV * 1.05, | |
DENSITY: base.DENSITY * 2, | |
}, | |
IS_SMASHER: true, | |
SKILL_CAP: [smshskl, 0, 0, 0, 0, smshskl, smshskl, smshskl, smshskl, smshskl,], | |
STAT_NAMES: statnames.smasher, | |
TURRETS: [{ /** SIZE X Y ANGLE ARC */ | |
POSITION: [ 20.5, 0, 0, 0, 360, 0,], | |
TYPE: exports.spikeBody, | |
}, { | |
POSITION: [ 20.5, 0, 0, 120, 360, 0,], | |
TYPE: exports.spikeBody, | |
}, { | |
POSITION: [ 20.5, 0, 0, 240, 360, 0,], | |
TYPE: exports.spikeBody, | |
}], | |
}; | |
exports.weirdspike = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Spike', | |
DANGER: 7, | |
BODY: { | |
DAMAGE: base.DAMAGE * 1.15, | |
FOV: base.FOV * 1.05, | |
DENSITY: base.DENSITY * 1.5, | |
}, | |
IS_SMASHER: true, | |
SKILL_CAP: [smshskl, 0, 0, 0, 0, smshskl, smshskl, smshskl, smshskl, smshskl,], | |
STAT_NAMES: statnames.smasher, | |
TURRETS: [{ /** SIZE X Y ANGLE ARC */ | |
POSITION: [ 20.5, 0, 0, 0, 360, 0,], | |
TYPE: exports.spikeBody1, | |
}, { | |
POSITION: [ 20.5, 0, 0, 180, 360, 0,], | |
TYPE: exports.spikeBody2, | |
}], | |
}; | |
exports.autosmash = makeAuto(exports.smash, 'Auto-Smasher', { type: exports.autoSmasherTurret, size: 11, }); | |
exports.autosmash.SKILL_CAP = [smshskl, smshskl, smshskl, smshskl, smshskl, smshskl, smshskl, smshskl, smshskl, smshskl,]; | |
exports.twin = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Twin', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 8, 1, 0, 5.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin]), | |
TYPE: exports.bullet, | |
}, }, { /* LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 8, 1, 0, -5.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.gunner = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Gunner', | |
DANGER: 6, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 12, 3.5, 1, 0, 7.25, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.fast]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 12, 3.5, 1, 0, -7.25, 0, 0.75, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.fast]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 16, 3.5, 1, 0, 3.75, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.fast]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 16, 3.5, 1, 0, -3.75, 0, 0.25, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.fast]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.machinegunner = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Machine Gunner', | |
DANGER: 6, | |
BODY: { | |
SPEED: base.SPEED * 0.9, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 3, 4.0, -3, 5, 0, 0.6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.machgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 14, 3, 4.0, -3, -5, 0, 0.8, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.machgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 14, 3, 4.0, 0, 2.5, 0, 0.4, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.machgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 14, 3, 4.0, 0, -2.5, 0, 0.2, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.machgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 14, 3, 4.0, 3, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.puregunner, g.machgun]), | |
TYPE: exports.bullet, | |
}, }, | |
] | |
}; | |
exports.autogunner = makeAuto(exports.gunner); | |
exports.nailgun = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Nailgun', | |
DANGER: 7, | |
BODY: { | |
FOV: base.FOV * 1.1, | |
SPEED: base.SPEED * 0.9, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 19, 2, 1, 0, -2.5, 0, 0.25, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.twin, g.nail]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 2, 1, 0, 2.5, 0, 0.75, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.twin, g.nail]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 2, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.twin, g.nail]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 5.5, 8, -1.8, 6.5, 0, 0, 0, ], | |
}, | |
], | |
}; | |
exports.double = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Double Twin', | |
DANGER: 6, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 8, 1, 0, 5.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, -5.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, 5.5, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, -5.5, 180, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.tripletwin = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Triple Twin', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 8, 1, 0, 5.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.spam, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, -5.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.spam, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, 5.5, 120, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.spam, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, -5.5, 120, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.spam, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, 5.5, 240, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.spam, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, -5.5, 240, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.spam, g.double]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.autodouble = makeAuto(exports.double, 'Auto-Double'); | |
exports.split = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Hewn Double', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 19, 8, 1, 0, 5.5, 25, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.twin, g.double, g.hewn, g.morerecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, -5.5, -25, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.twin, g.double, g.hewn, g.morerecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, 5.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double, g.hewn, g.morerecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, -5.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double, g.hewn, g.morerecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, 5.5, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double, g.hewn]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, -5.5, 180, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.double, g.hewn]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.bent = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Triple Shot', | |
DANGER: 6, | |
BODY: { | |
SPEED: base.SPEED * 0.9, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 19, 8, 1, 0, -2, -20, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, 2, 20, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 22, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.bentdouble = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Bent Double', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 19, 8, 1, 0, -1, -25, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, 1, 25, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 22, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, -1, 155, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, 1, -155, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent, g.double]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 22, 8, 1, 0, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent, g.double]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.penta = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Penta Shot', | |
DANGER: 7, | |
BODY: { | |
SPEED: base.SPEED * 0.85, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 16, 8, 1, 0, -3, -30, 0.667, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 3, 30, 0.667, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, -2, -15, 0.333, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, 2, 15, 0.333, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 22, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.bent]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.benthybrid = makeHybrid(exports.bent, 'Bent Hybrid'); | |
exports.triple = { | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
BODY: { | |
FOV: base.FOV * 1.05, | |
}, | |
LABEL: 'Triplet', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 10, 1, 0, 5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 10, 1, 0, -5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 21, 10, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.quint = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
BODY: { | |
FOV: base.FOV * 1.1, | |
}, | |
LABEL: 'Quintuplet', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 16, 10, 1, 0, -5, 0, 0.667, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple, g.quint]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 16, 10, 1, 0, 5, 0, 0.667, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple, g.quint]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 10, 1, 0, -3, 0, 0.333, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple, g.quint]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 10, 1, 0, 3, 0, 0.333, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple, g.quint]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 22, 10, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.triple, g.quint]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.dual = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
BODY: { | |
ACCEL: base.ACCEL * 0.8, | |
FOV: base.FOV * 1.1, | |
}, | |
LABEL: '', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 7, 1, 0, 5.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.dual, g.lowpower]), | |
TYPE: exports.bullet, | |
LABEL: 'Small', | |
}, }, { | |
POSITION: [ 18, 7, 1, 0, -5.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.dual, g.lowpower]), | |
TYPE: exports.bullet, | |
LABEL: 'Small', | |
}, }, { | |
POSITION: [ 16, 8.5, 1, 0, 5.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.dual]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 16, 8.5, 1, 0, -5.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.twin, g.dual]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.sniper = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Sniper', | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
FOV: base.FOV * 1.2, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 24, 8.5, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.rifle = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Rifle', | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
FOV: base.FOV * 1.225, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 10.5, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 24, 7, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.rifle]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.assassin = { | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
LABEL: 'Assassin', | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.6, | |
SPEED: base.SPEED * 0.85, | |
FOV: base.FOV * 1.4, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 27, 8.5, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.assass]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 5, 8.5, -1.6, 8, 0, 0, 0, ], | |
}, | |
], | |
}; | |
exports.ranger = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Ranger', | |
DANGER: 7, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.5, | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.5, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 32, 8.5, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.assass]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 5, 8.5, -1.6, 8, 0, 0, 0, ], | |
}, | |
], | |
}; | |
exports.autoass = makeAuto(exports.assassin, ""); | |
exports.hunter = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Hunter', | |
DANGER: 6, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
SPEED: base.SPEED * 0.9, | |
FOV: base.FOV * 1.25, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 24, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.hunter, g.hunter2]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 21, 12, 1, 0, 0, 0, 0.25, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.hunter]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.preda = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Predator', | |
DANGER: 7, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
SPEED: base.SPEED * 0.85, | |
FOV: base.FOV * 1.3, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 24, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.hunter, g.hunter2, g.hunter2, g.preda]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 21, 12, 1, 0, 0, 0, 0.15, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.hunter, g.hunter2, g.preda]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 16, 1, 0, 0, 0, 0.3, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.hunter, g.preda]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.poach = makeHybrid(exports.hunter, 'Poacher'); | |
exports.sidewind = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Sidewinder', | |
DANGER: 7, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.3, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 10, 11, -0.5, 14, 0, 0, 0, ], | |
}, { | |
POSITION: [ 21, 12, -1.1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.hunter, g.sidewind]), | |
TYPE: exports.snake, | |
STAT_CALCULATOR: gunCalcNames.sustained, | |
}, }, | |
], | |
}; | |
exports.director = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Director', | |
STAT_NAMES: statnames.drone, | |
DANGER: 5, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
FOV: base.FOV * 1.1, | |
}, | |
MAX_CHILDREN: 5, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 12, 1.2, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
}, }, | |
], | |
}; | |
exports.master = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
STAT_NAMES: statnames.drone, | |
DANGER: 7, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
FOV: base.FOV * 1.15, | |
}, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 16, 1, 0, 0, 0, 0], | |
TYPE: exports.masterGun, | |
}, { | |
POSITION: [ 16, 1, 0, 120, 0, 0], | |
TYPE: [exports.masterGun, { INDEPENDENT: true, }], | |
}, { | |
POSITION: [ 16, 1, 0, 240, 0, 0], | |
TYPE: [exports.masterGun, { INDEPENDENT: true, }], | |
}, | |
], | |
}; | |
exports.overseer = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Overseer', | |
DANGER: 6, | |
STAT_NAMES: statnames.drone, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
SPEED: base.SPEED * 0.9, | |
FOV: base.FOV * 1.1, | |
}, | |
MAX_CHILDREN: 8, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 12, 1.2, 8, 0, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
}, }, { | |
POSITION: [ 6, 12, 1.2, 8, 0, 270, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
}, }, | |
], | |
}; | |
exports.overlord = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Overlord', | |
DANGER: 7, | |
STAT_NAMES: statnames.drone, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.1, | |
}, | |
MAX_CHILDREN: 8, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 12, 1.2, 8, 0, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
}, }, { | |
POSITION: [ 6, 12, 1.2, 8, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
}, }, { | |
POSITION: [ 6, 12, 1.2, 8, 0, 270, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
}, }, { | |
POSITION: [ 6, 12, 1.2, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
}, }, | |
], | |
}; | |
exports.overtrap = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Overtrapper', | |
DANGER: 7, | |
STAT_NAMES: statnames.generic, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.6, | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.2, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 11, 1.2, 8, 0, 125, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over, g.meta]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 3, | |
}, }, { | |
POSITION: [ 6, 11, 1.2, 8, 0, 235, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over, g.meta]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 3, | |
}, }, { | |
POSITION: [ 14, 8, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 4, 8, 1.5, 14, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
exports.banshee = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
DANGER: 7, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.5, | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.1, | |
}, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 10, 8, 0, 0, 80, 0], | |
TYPE: exports.bansheegun, | |
}, { | |
POSITION: [ 10, 8, 0, 120, 80, 0], | |
TYPE: exports.bansheegun, | |
}, { | |
POSITION: [ 10, 8, 0, 240, 80, 0], | |
TYPE: exports.bansheegun, | |
}, | |
], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 11, 1.2, 8, 0, 60, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over, g.meta]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 2, | |
}, }, { | |
POSITION: [ 6, 11, 1.2, 8, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over, g.meta]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 2, | |
}, }, { | |
POSITION: [ 6, 11, 1.2, 8, 0, 300, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over, g.meta]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 2, | |
}, }, | |
] | |
}; | |
exports.autoover = makeAuto(exports.overseer, ""); | |
exports.overgunner = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Overgunner', | |
DANGER: 7, | |
STAT_NAMES: statnames.generic, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
SPEED: base.SPEED * 0.9, | |
FOV: base.FOV * 1.1, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 11, 1.2, 8, 0, 125, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over, g.meta]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 3, | |
}, }, { | |
POSITION: [ 6, 11, 1.2, 8, 0, 235, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.over, g.meta]), | |
TYPE: exports.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 3, | |
}, }, { | |
POSITION: [ 19, 2, 1, 0, -2.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.twin, g.slow, g.flank, g.lotsmorrecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 2, 1, 0, 2.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.twin, g.slow, g.flank, g.lotsmorrecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 12, 11, 1, 0, 0, 0, 0, ], | |
}, | |
], | |
}; | |
function makeSwarmSpawner(guntype) { | |
return { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
FOV: 2, | |
}, | |
CONTROLLERS: ['nearestDifferentMaster'], | |
COLOR: 16, | |
AI: { | |
NO_LEAD: true, | |
SKYNET: true, | |
FULL_VIEW: true, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 15, 0.6, 14, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: guntype, | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, } | |
], | |
}; | |
} | |
exports.cruiserGun = makeSwarmSpawner(combineStats([g.swarm])); | |
exports.cruiser = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Cruiser', | |
DANGER: 6, | |
FACING_TYPE: 'locksFacing', | |
STAT_NAMES: statnames.swarm, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
FOV: base.FOV * 1.2, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 7, 7.5, 0.6, 7, 4, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, -4, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, | |
], | |
}; | |
exports.battleship = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Battleship', | |
DANGER: 7, | |
STAT_NAMES: statnames.swarm, | |
FACING_TYPE: 'locksFacing', | |
BODY: { | |
ACCELERATION: base.ACCEL, | |
FOV: base.FOV * 1.2, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 7, 7.5, 0.6, 7, 4, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.battle]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
LABEL: 'Guided' | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, -4, 90, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: [exports.autoswarm], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
LABEL: 'Autonomous', | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, 4, 270, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: [exports.autoswarm], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
LABEL: 'Autonomous', | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, -4, 270, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.battle]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
LABEL: 'Guided' | |
}, }, | |
], | |
}; | |
exports.carrier = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Carrier', | |
DANGER: 7, | |
STAT_NAMES: statnames.swarm, | |
FACING_TYPE: 'locksFacing', | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
FOV: base.FOV * 1.3, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 7, 7.5, 0.6, 7, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.battle, g.carrier]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, 2, 40, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.battle, g.carrier]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, -2, -40, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.battle, g.carrier]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, } | |
], | |
}; | |
exports.autocruiser = makeAuto(exports.cruiser, ""); | |
exports.fortress = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Fortress', //'Palisade', | |
DANGER: 7, | |
STAT_NAMES: statnames.generic, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.2, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 7, 7.5, 0.6, 7, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: [exports.swarm, { CONTROLLERS: ['canRepel'] }], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, 0, 120, 1/3, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: [exports.swarm, { CONTROLLERS: ['canRepel'] }], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, 0, 240, 2/3, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: [exports.swarm, { CONTROLLERS: ['canRepel'] }], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 14, 9, 1, 0, 0, 60, 0, ], | |
}, { | |
POSITION: [ 4, 9, 1.5, 14, 0, 60, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.halfrange, g.slow]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 14, 9, 1, 0, 0, 180, 0, ], | |
}, { | |
POSITION: [ 4, 9, 1.5, 14, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.halfrange, g.slow]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 14, 9, 1, 0, 0, 300, 0, ], | |
}, { | |
POSITION: [ 4, 9, 1.5, 14, 0, 300, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.halfrange, g.slow]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
exports.underseer = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Underseer', | |
DANGER: 6, | |
STAT_NAMES: statnames.drone, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
SPEED: base.SPEED * 0.9, | |
FOV: base.FOV * 1.1, | |
}, | |
SHAPE: 4, | |
MAX_CHILDREN: 14, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 5, 12, 1.2, 8, 0, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.sunchip]), | |
TYPE: exports.sunchip, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.necro, | |
}, }, { | |
POSITION: [ 5, 12, 1.2, 8, 0, 270, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.sunchip]), | |
TYPE: exports.sunchip, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.necro, | |
}, }, | |
], | |
}; | |
exports.necromancer = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Necromancer', | |
DANGER: 7, | |
STAT_NAMES: statnames.necro, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.15, | |
}, | |
SHAPE: 4, | |
FACING_TYPE: 'autospin', | |
MAX_CHILDREN: 14, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 5, 12, 1.2, 8, 0, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.sunchip]), | |
TYPE: exports.sunchip, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.necro, | |
}, }, { | |
POSITION: [ 5, 12, 1.2, 8, 0, 270, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.sunchip]), | |
TYPE: exports.sunchip, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.necro, | |
}, }, { | |
POSITION: [ 5, 12, 1.2, 8, 0, 0, 0.25, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.sunchip, g.weak, g.doublereload]), | |
TYPE: exports.autosunchip, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
MAX_CHILDREN: 4, | |
STAT_CALCULATOR: gunCalcNames.necro, | |
LABEL: 'Guard', | |
}, }, { | |
POSITION: [ 5, 12, 1.2, 8, 0, 180, 0.75 ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.sunchip, g.weak, g.doublereload]), | |
TYPE: exports.autosunchip, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
MAX_CHILDREN: 4, | |
STAT_CALCULATOR: gunCalcNames.necro, | |
LABEL: 'Guard', | |
}, }, | |
], | |
}; | |
exports.lilfact = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
DANGER: 6, | |
STAT_NAMES: statnames.drone, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
ACCELERATION: base.ACCEL * 0.5, | |
FOV: 1.1, | |
}, | |
GUNS: [ { /**** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 4.5, 10, 1, 10.5, 0, 0, 0, ], | |
}, { | |
POSITION: [ 1, 12, 1, 15, 0, 0, 0, ], | |
PROPERTIES: { | |
MAX_CHILDREN: 4, | |
SHOOT_SETTINGS: combineStats([g.factory, g.babyfactory]), | |
TYPE: exports.minion, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
}, }, { | |
POSITION: [ 3.5, 12, 1, 8, 0, 0, 0, ], | |
} | |
], | |
}; | |
exports.factory = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Factory', | |
DANGER: 7, | |
STAT_NAMES: statnames.drone, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
FOV: 1.1, | |
}, | |
MAX_CHILDREN: 6, | |
GUNS: [ { /**** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 5, 11, 1, 10.5, 0, 0, 0, ], | |
}, { | |
POSITION: [ 2, 14, 1, 15.5, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.factory]), | |
TYPE: exports.minion, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
}, }, { | |
POSITION: [ 4, 14, 1, 8, 0, 0, 0, ], | |
} | |
], | |
}; | |
exports.machine = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Machine Gun', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 12, 10, 1.4, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.spray = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Sprayer', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 23, 7, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.lowpower, g.mach, g.morerecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 12, 10, 1.4, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.mini = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Minigun', | |
DANGER: 6, | |
BODY: { | |
FOV: 1.2, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 22, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 20, 8, 1, 0, 0, 0, 0.333, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0.667, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.stream = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Streamliner', | |
DANGER: 7, | |
BODY: { | |
FOV: 1.3, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 25, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini, g.stream]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 23, 8, 1, 0, 0, 0, 0.2, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini, g.stream]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 21, 8, 1, 0, 0, 0, 0.4, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini, g.stream]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 8, 1, 0, 0, 0, 0.6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini, g.stream]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 17, 8, 1, 0, 0, 0, 0.8, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mini, g.stream]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.hybridmini = makeHybrid(exports.mini, ""); | |
exports.minitrap = { | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
LABEL: '', | |
STAT_NAMES: statnames.trap, | |
BODY: { | |
FOV: 1.15, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 24, 8, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 4, 8, 1.3, 22, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.mini, g.halfrange]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 4, 8, 1.3, 18, 0, 0, 0.333, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.mini, g.halfrange]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 4, 8, 1.3, 14, 0, 0, 0.667, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.mini, g.halfrange]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
exports.pound = { | |
PARENT: [exports.genericTank], | |
DANGER: 5, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.8, | |
}, | |
LABEL: 'Pounder', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 12, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.destroy = { | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
}, | |
LABEL: 'Destroyer', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 21, 14, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.destroy]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.anni = { | |
PARENT: [exports.genericTank], | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
}, | |
LABEL: 'Annihilator', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20.5, 19.5, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.destroy, g.anni]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.hiveshooter = { | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.75, | |
SPEED: base.speed * 0.8, | |
}, | |
LABEL: '', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 14, -1.2, 5, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.destroy, g.hive]), | |
TYPE: exports.hive, | |
}, }, { | |
POSITION: [ 15, 12, 1, 5, 0, 0, 0, ], | |
} | |
], | |
}; | |
exports.hybrid = makeHybrid(exports.destroy, 'Hybrid'); | |
exports.shotgun2 = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
LABEL: 'Shotgun', | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
}, | |
GUNS: [ /***** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ { | |
POSITION: [ 4, 3, 1, 11, -3, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 4, 3, 1, 11, 3, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 4, 4, 1, 13, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.casing, | |
}, }, { | |
POSITION: [ 1, 4, 1, 12, -1, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.casing, | |
}, }, { | |
POSITION: [ 1, 4, 1, 11, 1, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.casing, | |
}, }, { | |
POSITION: [ 1, 3, 1, 13, -1, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 1, 3, 1, 13, 1, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 1, 2, 1, 13, 2, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.casing, | |
}, }, { | |
POSITION: [ 1, 2, 1, 13, -2, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun]), | |
TYPE: exports.casing, | |
}, }, { | |
POSITION: [ 15, 14, 1, 6, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.mach, g.shotgun, g.fake]), | |
TYPE: exports.casing, | |
}, }, { | |
POSITION: [ 8, 14, -1.3, 4, 0, 0, 0, ], } | |
], | |
}; | |
exports.builder = { | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
LABEL: 'Trapper', | |
STAT_NAMES: statnames.trap, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.15, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 12, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 2, 12, 1.1, 18, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block]), | |
TYPE: exports.block, | |
}, }, | |
], | |
}; | |
exports.engineer = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
LABEL: 'Engineer', | |
STAT_NAMES: statnames.trap, | |
BODY: { | |
SPEED: base.SPEED * 0.75, | |
FOV: base.FOV * 1.15, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 5, 11, 1, 10.5, 0, 0, 0, ], | |
}, { | |
POSITION: [ 3, 14, 1, 15.5, 0, 0, 0, ], | |
}, { | |
POSITION: [ 2, 14, 1.3, 18, 0, 0, 0, ], | |
PROPERTIES: { | |
MAX_CHILDREN: 6, | |
SHOOT_SETTINGS: combineStats([g.trap, g.block]), | |
TYPE: exports.pillbox, | |
SYNCS_SKILLS: true, | |
}, }, { | |
POSITION: [ 4, 14, 1, 8, 0, 0, 0, ] | |
} | |
], | |
}; | |
exports.construct = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Mega Trapper', | |
STAT_NAMES: statnames.trap, | |
DANGER: 7, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.5, | |
SPEED: base.SPEED * 0.7, | |
FOV: base.FOV * 1.15, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 18, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 2, 18, 1.2, 18, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.construct]), | |
TYPE: exports.block, | |
}, }, | |
], | |
}; | |
exports.autobuilder = makeAuto(exports.builder); | |
exports.conq = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
LABEL: '', | |
STAT_NAMES: statnames.trap, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 21, 14, 1, 0, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 14, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 2, 14, 1.1, 18, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block]), | |
TYPE: exports.block, | |
}, }, | |
], | |
}; | |
exports.bentboomer = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
LABEL: 'Boomer', | |
STAT_NAMES: statnames.trap, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.15, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 8, 10, 1, 8, -2, -35, 0, ], | |
}, { | |
POSITION: [ 8, 10, 1, 8, 2, 35, 0, ], | |
}, { | |
POSITION: [ 2, 10, 1.3, 16, -2, -35, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.fast, g.twin]), | |
TYPE: exports.boomerang, | |
}, }, { | |
POSITION: [ 2, 10, 1.3, 16, 2, 35, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.fast, g.twin]), | |
TYPE: exports.boomerang, | |
}, }, | |
], | |
}; | |
exports.boomer = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
LABEL: 'Boomer', | |
STAT_NAMES: statnames.trap, | |
FACING_TYPE: 'locksFacing', | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.15, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 5, 10, 1, 14, 0, 0, 0, ], | |
}, { | |
POSITION: [ 6, 10, -1.5, 7, 0, 0, 0, ], | |
}, { | |
//POSITION: [ 12, 15, 1, 0, 0, 0, 0, ], | |
// }, { | |
POSITION: [ 2, 10, 1.3, 18, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.boomerang]), | |
TYPE: exports.boomerang, | |
}, }, | |
], | |
}; | |
exports.quadtrapper = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
LABEL: '', | |
STAT_NAMES: statnames.trap, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.15, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 6, 1, 0, 0, 45, 0, ], | |
}, { | |
POSITION: [ 2, 6, 1.1, 14, 0, 45, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.weak]), | |
TYPE: exports.block, | |
}, }, { | |
POSITION: [ 14, 6, 1, 0, 0, 135, 0, ], | |
}, { | |
POSITION: [ 2, 6, 1.1, 14, 0, 135, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.weak]), | |
TYPE: exports.block, | |
}, }, { | |
POSITION: [ 14, 6, 1, 0, 0, 225, 0, ], | |
}, { | |
POSITION: [ 2, 6, 1.1, 14, 0, 225, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.weak]), | |
TYPE: exports.block, | |
}, }, { | |
POSITION: [ 14, 6, 1, 0, 0, 315, 0, ], | |
}, { | |
POSITION: [ 2, 6, 1.1, 14, 0, 315, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block, g.weak]), | |
TYPE: exports.block, | |
}, }, | |
], | |
}; | |
exports.artillery = { | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
LABEL: 'Artillery', | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 17, 3, 1, 0, -6, -7, 0.25, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty]), | |
TYPE: exports.bullet, | |
LABEL: 'Secondary', | |
}, }, { | |
POSITION: [ 17, 3, 1, 0, 6, 7, 0.75, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty]), | |
TYPE: exports.bullet, | |
LABEL: 'Secondary', | |
}, }, { | |
POSITION: [ 19, 12, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.arty]), | |
TYPE: exports.bullet, | |
LABEL: 'Heavy', | |
}, }, | |
], | |
}; | |
exports.mortar = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Mortar', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 13, 3, 1, 0, -8, -7, 0.6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin]), | |
TYPE: exports.bullet, | |
LABEL: 'Secondary', | |
}, }, { | |
POSITION: [ 13, 3, 1, 0, 8, 7, 0.8, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin]), | |
TYPE: exports.bullet, | |
LABEL: 'Secondary', | |
}, }, { | |
POSITION: [ 17, 3, 1, 0, -6, -7, 0.2, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin]), | |
TYPE: exports.bullet, | |
LABEL: 'Secondary', | |
}, }, { | |
POSITION: [ 17, 3, 1, 0, 6, 7, 0.4, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin]), | |
TYPE: exports.bullet, | |
LABEL: 'Secondary', | |
}, }, { | |
POSITION: [ 19, 12, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.arty]), | |
TYPE: exports.bullet, | |
LABEL: 'Heavy', | |
}, }, | |
], | |
}; | |
exports.skimmer = { | |
PARENT: [exports.genericTank], | |
BODY: { | |
FOV: base.FOV * 1.15, | |
}, | |
LABEL: 'Skimmer', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 10, 14, -0.5, 9, 0, 0, 0, ], | |
}, { | |
POSITION: [ 17, 15, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.arty, g.arty, g.skim]), | |
TYPE: exports.missile, | |
STAT_CALCULATOR: gunCalcNames.sustained, | |
}, }, | |
], | |
}; | |
exports.spread = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Spreadshot', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 13, 4, 1, 0, -0.8, -75, 5/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 14.5, 4, 1, 0, -1.0, -60, 4/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 16, 4, 1, 0, -1.6, -45, 3/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 17.5, 4, 1, 0, -2.4, -30, 2/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 19, 4, 1, 0, -3.0, -15, 1/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 13, 4, 1, 0, 0.8, 75, 5/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 14.5, 4, 1, 0, 1.0, 60, 4/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 16, 4, 1, 0, 1.6, 45, 3/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 17.5, 4, 1, 0, 2.4, 30, 2/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 19, 4, 1, 0, 3.0, 15, 1/6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.arty, g.twin, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Spread', | |
}, }, { | |
POSITION: [ 13, 10, 1.3, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.spreadmain, g.spread]), | |
TYPE: exports.bullet, | |
LABEL: 'Pounder', | |
}, }, | |
], | |
}; | |
exports.renegade = { | |
// todo: give lance trap the lanceRetract ai | |
PARENT: [exports.genericTank], | |
LABEL: 'Renegade', | |
MAX_CHILDREN: 1, | |
DANGER: 2, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 2, 12, 1.1, 18, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.block]), | |
TYPE: exports.lance, | |
}, }, | |
], | |
} | |
exports.flank = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Flank Guard', | |
BODY: { | |
SPEED: base.SPEED * 1.1, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 120, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 240, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.hexa = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Hexa Tank', | |
DANGER: 6, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 120, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 240, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 60, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 180, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 300, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.octo = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Octo Tank', | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 270, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 45, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 135, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 225, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 315, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank, g.spam]), | |
TYPE: exports.bullet, | |
}, }, | |
], | |
}; | |
exports.heptatrap = (() => { | |
let a = 360/7, d = 1/7; | |
return { | |
PARENT: [exports.genericTank], | |
LABEL: 'Hepta-Trapper', | |
DANGER: 7, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
}, | |
STAT_NAMES: statnames.trap, | |
HAS_NO_RECOIL: true, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 15, 7, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, a, 4*d, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, a, 4*d, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 2*a, 1*d, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 2*a, 1*d, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 3*a, 5*d, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 3*a, 5*d, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 4*a, 2*d, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 4*a, 2*d, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 5*a, 6*d, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 5*a, 6*d, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 6*a, 3*d, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 6*a, 3*d, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
})(); | |
exports.hexatrap = makeAuto({ | |
PARENT: [exports.genericTank], | |
LABEL: 'Hexa-Trapper', | |
DANGER: 7, | |
BODY: { | |
SPEED: base.SPEED * 0.8, | |
}, | |
STAT_NAMES: statnames.trap, | |
HAS_NO_RECOIL: true, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 15, 7, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 60, 0.5, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 60, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 120, 0, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 120, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 180, 0.5, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 180, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 240, 0, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 240, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, { | |
POSITION: [ 15, 7, 1, 0, 0, 300, 0.5, ], | |
}, { | |
POSITION: [ 3, 7, 1.7, 15, 0, 300, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}, 'Hexa-Trapper'); | |
exports.tri = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Tri-Angle', | |
BODY: { | |
HEALTH: base.HEALTH * 0.8, | |
SHIELD: base.SHIELD * 0.8, | |
DENSITY: base.DENSITY * 0.6, | |
}, | |
DANGER: 6, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.trifront, g.tonsmorrecoil]), | |
TYPE: exports.bullet, | |
LABEL: 'Front', | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 150, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 210, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, | |
], | |
}; | |
exports.booster = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Booster', | |
BODY: { | |
HEALTH: base.HEALTH * 0.6, | |
SHIELD: base.SHIELD * 0.6, | |
DENSITY: base.DENSITY * 0.2, | |
}, | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.trifront, g.muchmorerecoil]), | |
TYPE: exports.bullet, | |
LABEL: 'Front', | |
}, }, { | |
POSITION: [ 13, 8, 1, 0, -1, 135, 0.6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster, g.halfrecoil]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 13, 8, 1, 0, 1, 225, 0.6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster, g.halfrecoil]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 145, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 215, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, | |
], | |
}; | |
exports.fighter = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Fighter', | |
BODY: { | |
DENSITY: base.DENSITY * 0.6, | |
}, | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.trifront]), | |
TYPE: exports.bullet, | |
LABEL: 'Front', | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, -1, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.trifront]), | |
TYPE: exports.bullet, | |
LABEL: 'Side', | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 1, -90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.trifront]), | |
TYPE: exports.bullet, | |
LABEL: 'Side', | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 150, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 210, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, | |
], | |
}; | |
exports.brutalizer = { | |
PARENT: [exports.genericTank], | |
LABEL: '', | |
BODY: { | |
DENSITY: base.DENSITY * 0.6, | |
}, | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 18, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.trifront]), | |
TYPE: exports.bullet, | |
LABEL: 'Front', | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, -1, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: [exports.autoswarm], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 7, 7.5, 0.6, 7, 1, -90, 9, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm]), | |
TYPE: [exports.autoswarm], | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 150, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 210, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, | |
], | |
}; | |
exports.bomber = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Bomber', | |
BODY: { | |
DENSITY: base.DENSITY * 0.6, | |
}, | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.trifront]), | |
TYPE: exports.bullet, | |
LABEL: 'Front', | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 130, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri]), | |
TYPE: exports.bullet, | |
LABEL: 'Wing', | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 230, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri]), | |
TYPE: exports.bullet, | |
LABEL: 'Wing', | |
}, }, { | |
POSITION: [ 14, 8, 1, 0, 0, 180, 0, ], | |
}, { | |
POSITION: [ 4, 8, 1.5, 14, 0, 180, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.morerecoil]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
exports.autotri = makeAuto(exports.tri); | |
exports.autotri.BODY = { | |
SPEED: base.SPEED, | |
}; | |
exports.falcon = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Falcon', | |
DANGER: 7, | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.8, | |
FOV: base.FOV * 1.2, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 27, 8.5, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.assass, g.lessreload]), | |
TYPE: exports.bullet, | |
LABEL: 'Assassin', | |
ALT_FIRE: true, | |
}, }, { | |
POSITION: [ 5, 8.5, -1.6, 8, 0, 0, 0, ], | |
}, { | |
POSITION: [ 16, 8, 1, 0, 0, 150, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster, g.halfrecoil]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 16, 8, 1, 0, 0, 210, 0.1, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster, g.halfrecoil]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, { | |
POSITION: [ 18, 8, 1, 0, 0, 180, 0.6, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.tri, g.thruster, g.halfrecoil]), | |
TYPE: exports.bullet, | |
LABEL: gunCalcNames.thruster, | |
}, }, | |
], | |
}; | |
exports.auto3 = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Auto-3', | |
DANGER: 6, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 11, 8, 0, 0, 190, 0], | |
TYPE: exports.auto3gun, | |
}, { | |
POSITION: [ 11, 8, 0, 120, 190, 0], | |
TYPE: exports.auto3gun, | |
}, { | |
POSITION: [ 11, 8, 0, 240, 190, 0], | |
TYPE: exports.auto3gun, | |
}, | |
], | |
}; | |
exports.auto5 = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Auto-5', | |
DANGER: 7, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 11, 8, 0, 0, 190, 0], | |
TYPE: exports.auto5gun, | |
}, { | |
POSITION: [ 11, 8, 0, 72, 190, 0], | |
TYPE: exports.auto5gun, | |
}, { | |
POSITION: [ 11, 8, 0, 144, 190, 0], | |
TYPE: exports.auto5gun, | |
}, { | |
POSITION: [ 11, 8, 0, 216, 190, 0], | |
TYPE: exports.auto5gun, | |
}, { | |
POSITION: [ 11, 8, 0, 288, 190, 0], | |
TYPE: exports.auto5gun, | |
}, | |
], | |
}; | |
exports.heavy3 = { | |
BODY: { | |
SPEED: base.SPEED * 0.95, | |
}, | |
PARENT: [exports.genericTank], | |
LABEL: 'Mega-3', | |
DANGER: 7, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 14, 8, 0, 0, 190, 0], | |
TYPE: exports.heavy3gun, | |
}, { | |
POSITION: [ 14, 8, 0, 120, 190, 0], | |
TYPE: exports.heavy3gun, | |
}, { | |
POSITION: [ 14, 8, 0, 240, 190, 0], | |
TYPE: exports.heavy3gun, | |
}, | |
], | |
}; | |
exports.tritrap = { | |
LABEL: '', | |
BODY: { | |
SPEED: base.SPEED * 1.1, | |
}, | |
PARENT: [exports.genericTank], | |
DANGER: 6, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 12, 8, 0, 0, 190, 0], | |
TYPE: exports.tritrapgun, | |
}, { | |
POSITION: [ 12, 8, 0, 120, 190, 0], | |
TYPE: exports.tritrapgun, | |
}, { | |
POSITION: [ 12, 8, 0, 240, 190, 0], | |
TYPE: exports.tritrapgun, | |
}, | |
], | |
}; | |
exports.sniper3 = { | |
PARENT: [exports.genericTank], | |
DANGER: 7, | |
LABEL: '', | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.6, | |
SPEED: base.SPEED * 0.8, | |
FOV: base.FOV * 1.25, | |
}, | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 13, 8, 0, 0, 170, 0], | |
TYPE: exports.sniper3gun, | |
}, { | |
POSITION: [ 13, 8, 0, 120, 170, 0], | |
TYPE: exports.sniper3gun, | |
}, { | |
POSITION: [ 13, 8, 0, 240, 170, 0], | |
TYPE: exports.sniper3gun, | |
}, | |
], | |
}; | |
exports.auto4 = { | |
PARENT: [exports.genericTank], | |
DANGER: 5, | |
LABEL: 'Auto-4', | |
FACING_TYPE: 'autospin', | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 13, 6, 0, 45, 160, 0], | |
TYPE: exports.auto4gun, | |
}, { | |
POSITION: [ 13, 6, 0, 135, 160, 0], | |
TYPE: exports.auto4gun, | |
}, { | |
POSITION: [ 13, 6, 0, 225, 160, 0], | |
TYPE: exports.auto4gun, | |
}, { | |
POSITION: [ 13, 6, 0, 315, 160, 0], | |
TYPE: exports.auto4gun, | |
}, | |
], | |
}; | |
exports.flanktrap = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Trap Guard', | |
STAT_NAMES: statnames.generic, | |
DANGER: 6, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 20, 8, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.flank, g.flank]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 13, 8, 1, 0, 0, 180, 0, ], | |
}, { | |
POSITION: [ 4, 8, 1.7, 13, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
exports.guntrap = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Gunner Trapper', | |
DANGER: 7, | |
STAT_NAMES: statnames.generic, | |
BODY: { | |
FOV: base.FOV * 1.25, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 19, 2, 1, 0, -2.5, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.twin, g.tonsmorrecoil, g.lotsmorrecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 19, 2, 1, 0, 2.5, 0, 0.5, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunner, g.power, g.twin, g.tonsmorrecoil, g.lotsmorrecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 12, 11, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 13, 11, 1, 0, 0, 180, 0, ], | |
}, { | |
POSITION: [ 4, 11, 1.7, 13, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.fast, g.halfrecoil]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
exports.bushwhack = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Snipe Guard', | |
BODY: { | |
ACCELERATION: base.ACCEL * 0.7, | |
FOV: base.FOV * 1.2, | |
}, | |
DANGER: 7, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 24, 8.5, 1, 0, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.sniper, g.morerecoil]), | |
TYPE: exports.bullet, | |
}, }, { | |
POSITION: [ 13, 8.5, 1, 0, 0, 180, 0, ], | |
}, { | |
POSITION: [ 4, 8.5, 1.7, 13, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
// UPGRADE PATHS | |
exports.testbed.UPGRADES_TIER_1 = [ | |
exports.autocruiser, | |
exports.master, | |
exports.dual, | |
exports.hiveshooter, | |
exports.brutalizer, | |
exports.shotgun2, | |
exports.hybridmini, | |
exports.renegade, | |
]; | |
exports.basic.UPGRADES_TIER_1 = [exports.twin, exports.sniper, exports.machine, exports.flank, exports.director]; | |
exports.basic.UPGRADES_TIER_3 = [exports.single]; | |
exports.basic.UPGRADES_TIER_2 = [exports.smash]; | |
exports.smash.UPGRADES_TIER_3 = [exports.megasmash, exports.spike, exports.autosmash]; | |
exports.twin.UPGRADES_TIER_2 = [exports.double, exports.bent, exports.gunner, exports.hexa]; | |
exports.twin.UPGRADES_TIER_3 = [exports.triple]; | |
exports.double.UPGRADES_TIER_3 = [exports.tripletwin, exports.split, exports.autodouble, exports.bentdouble]; | |
exports.bent.UPGRADES_TIER_3 = [exports.penta, exports.spread, exports.benthybrid, exports.bentdouble, exports.triple]; | |
exports.gunner.UPGRADES_TIER_3 = [exports.autogunner, exports.nailgun, exports.auto4,exports.machinegunner]; | |
exports.sniper.UPGRADES_TIER_2 = [exports.assassin, exports.hunter, exports.mini, exports.builder]; | |
exports.sniper.UPGRADES_TIER_3 = [exports.bushwhack]; | |
exports.assassin.UPGRADES_TIER_3 = [exports.ranger, exports.falcon]; | |
exports.hunter.UPGRADES_TIER_3 = [exports.preda, exports.poach, exports.sidewind]; | |
exports.builder.UPGRADES_TIER_3 = [exports.construct, exports.autobuilder, exports.engineer, exports.boomer]; | |
exports.machine.UPGRADES_TIER_2 = [exports.destroy, exports.artillery, exports.mini, exports.gunner]; | |
exports.machine.UPGRADES_TIER_3 = [exports.spray]; | |
exports.destroy.UPGRADES_TIER_3 = [exports.anni, exports.hybrid, exports.construct, exports.shotgun2]; | |
exports.artillery.UPGRADES_TIER_3 = [exports.mortar, exports.spread, exports.skimmer]; | |
exports.mini.UPGRADES_TIER_3 = [exports.stream, exports.nailgun]; | |
exports.flank.UPGRADES_TIER_2 = [exports.hexa, exports.tri, exports.auto3, exports.flanktrap]; | |
exports.flank.UPGRADES_TIER_3 = []; | |
exports.tri.UPGRADES_TIER_3 = [exports.fighter, exports.booster, exports.falcon, exports.bomber, exports.autotri]; | |
exports.hexa.UPGRADES_TIER_3 = [exports.octo, exports.hexatrap]; | |
exports.auto3.UPGRADES_TIER_3 = [exports.auto5, exports.heavy3, exports.auto4]; | |
exports.flanktrap.UPGRADES_TIER_3 = [exports.bushwhack, exports.guntrap, exports.fortress, exports.bomber]; | |
exports.director.UPGRADES_TIER_2 = [exports.overseer, exports.cruiser, exports.underseer]; | |
exports.director.UPGRADES_TIER_3 = [exports.factory]; | |
exports.overseer.UPGRADES_TIER_3 = [exports.overlord, exports.overtrap, exports.overgunner]; | |
exports.underseer.UPGRADES_TIER_3 = [exports.necromancer]; | |
exports.cruiser.UPGRADES_TIER_3 = [exports.carrier, exports.battleship, exports.fortress]; | |
/*exports.smash.UPGRADES_TIER_3 = [exports.megasmash, exports.spike, exports.autosmash]; | |
exports.twin.UPGRADES_TIER_2 = [exports.double, exports.bent, exports.triple, exports.hexa]; | |
exports.double.UPGRADES_TIER_3 = [exports.tripletwin, exports.autodouble]; | |
exports.bent.UPGRADES_TIER_3 = [exports.penta, exports.benthybrid]; | |
exports.triple.UPGRADES_TIER_3 = [exports.quint]; | |
exports.sniper.UPGRADES_TIER_2 = [exports.assassin, exports.overseer, exports.hunter, exports.builder]; | |
exports.assassin.UPGRADES_TIER_3 = [exports.ranger]; | |
exports.overseer.UPGRADES_TIER_3 = [exports.overlord, exports.battleship | |
, exports.overtrap, exports.necromancer, exports.factory, exports.fortress]; | |
exports.hunter.UPGRADES_TIER_3 = [exports.preda, exports.poach]; | |
exports.builder.UPGRADES_TIER_3 = [exports.construct, exports.autobuilder]; | |
exports.machine.UPGRADES_TIER_2 = [exports.destroy, exports.gunner, exports.artillery]; | |
exports.destroy.UPGRADES_TIER_3 = [exports.anni, exports.hybrid]; | |
exports.gunner.UPGRADES_TIER_3 = [exports.autogunner, exports.mortar, exports.stream]; | |
exports.artillery.UPGRADES_TIER_3 = [exports.mortar, exports.spread, exports.skimmer]; | |
exports.machine.UPGRADES_TIER_3 = [exports.spray]; | |
exports.flank.UPGRADES_TIER_2 = [exports.hexa, exports.tri, exports.auto3, exports.flanktrap]; | |
exports.hexa.UPGRADES_TIER_3 = [exports.octo]; | |
exports.tri.UPGRADES_TIER_3 = [exports.booster, exports.fighter, exports.bomber, exports.autotri]; | |
exports.auto3.UPGRADES_TIER_3 = [exports.auto5, exports.heavy3]; | |
exports.flanktrap.UPGRADES_TIER_3 = [exports.guntrap, exports.fortress, exports.bomber];*/ | |
// NPCS: | |
exports.crasher = { | |
TYPE: 'crasher', | |
LABEL: 'Crasher', | |
COLOR: 5, | |
SHAPE: 3, | |
SIZE: 5, | |
VARIES_IN_SIZE: true, | |
CONTROLLERS: ['nearestDifferentMaster', 'mapTargetToGoal'], | |
AI: { NO_LEAD: true}, | |
BODY: { | |
SPEED: 5, | |
ACCEL: 0.01, | |
HEALTH: 0.5, | |
DAMAGE: 5, | |
PENETRATION: 2, | |
PUSHABILITY: 0.5, | |
DENSITY: 10, | |
RESIST: 2, | |
}, | |
MOTION_TYPE: 'motor', | |
FACING_TYPE: 'smoothWithMotion', | |
HITS_OWN_TYPE: 'hard', | |
HAS_NO_MASTER: true, | |
DRAW_HEALTH: true, | |
}; | |
exports.sentry = { | |
PARENT: [exports.genericTank], | |
TYPE: 'crasher', | |
LABEL: 'Sentry', | |
DANGER: 3, | |
COLOR: 5, | |
SHAPE: 3, | |
SIZE: 10, | |
SKILL: skillSet({ | |
rld: 0.5, | |
dam: 0.8, | |
pen: 0.8, | |
str: 0.1, | |
spd: 1, | |
atk: 0.5, | |
hlt: 0, | |
shi: 0, | |
rgn: 0.7, | |
mob: 0, | |
}), | |
VALUE: 1500, | |
VARIES_IN_SIZE: true, | |
CONTROLLERS: ['nearestDifferentMaster', 'mapTargetToGoal'], | |
AI: { NO_LEAD: true, }, | |
BODY: { | |
FOV: 0.5, | |
ACCEL: 0.006, | |
DAMAGE: base.DAMAGE * 2, | |
SPEED: base.SPEED * 0.5, | |
}, | |
MOTION_TYPE: 'motor', | |
FACING_TYPE: 'smoothToTarget', | |
HITS_OWN_TYPE: 'hard', | |
HAS_NO_MASTER: true, | |
DRAW_HEALTH: true, | |
GIVE_KILL_MESSAGE: true, | |
}; | |
exports.trapTurret = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Turret', | |
BODY: { | |
FOV: 0.5, | |
}, | |
INDEPENDENT: true, | |
CONTROLLERS: ['nearestDifferentMaster'], | |
COLOR: 16, | |
AI: { | |
SKYNET: true, | |
FULL_VIEW: true, | |
}, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 16, 14, 1, 0, 0, 0, 0, ], | |
}, { | |
POSITION: [ 4, 14, 1.8, 16, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.lowpower, g.fast, g.halfreload]), | |
TYPE: exports.trap, STAT_CALCULATOR: gunCalcNames.trap, | |
}, }, | |
], | |
}; | |
exports.sentrySwarm = { | |
PARENT: [exports.sentry], | |
DANGER: 3, | |
GUNS: [{ | |
POSITION: [ 7, 14, 0.6, 7, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.swarm, g.morerecoil]), | |
TYPE: exports.swarm, | |
STAT_CALCULATOR: gunCalcNames.swarm, | |
}, }, | |
], | |
}; | |
exports.sentryGun = makeAuto(exports.sentry, 'Sentry', { type: exports.heavy3gun, size: 12, }); | |
exports.sentryTrap = makeAuto(exports.sentry, 'Sentry', { type: exports.trapTurret, size: 12, }); | |
exports.miniboss = { | |
PARENT: [exports.genericTank], | |
TYPE: 'miniboss', | |
DANGER: 6, | |
SKILL: skillSet({ | |
rld: 0.7, | |
dam: 0.5, | |
pen: 0.8, | |
str: 0.8, | |
spd: 0.2, | |
atk: 0.3, | |
hlt: 1, | |
shi: 0.7, | |
rgn: 0.7, | |
mob: 0, | |
}), | |
LEVEL: 45, | |
CONTROLLERS: ['nearestDifferentMaster', 'minion', 'canRepel'], | |
AI: { NO_LEAD: true, }, | |
FACING_TYPE: 'autospin', | |
HITS_OWN_TYPE: 'hard', | |
BROADCAST_MESSAGE: 'A visitor has left!', | |
}; | |
exports.crasherSpawner = { | |
PARENT: [exports.genericTank], | |
LABEL: 'Spawned', | |
STAT_NAMES: statnames.drone, | |
CONTROLLERS: ['nearestDifferentMaster'], | |
COLOR: 5, | |
INDEPENDENT: true, | |
AI: { chase: true, }, | |
MAX_CHILDREN: 4, | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 12, 1.2, 8, 0, 0, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.weak, g.weak]), | |
TYPE: [exports.drone, { LABEL: 'Crasher', VARIES_IN_SIZE: true, DRAW_HEALTH: true }], | |
SYNCS_SKILLS: true, | |
AUTOFIRE: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
}, }, | |
], | |
}; | |
exports.elite = { | |
PARENT: [exports.miniboss], | |
LABEL: 'Elite Crasher', | |
COLOR: 5, | |
SHAPE: 3, | |
SIZE: 20, | |
VARIES_IN_SIZE: true, | |
VALUE: 150000, | |
BODY: { | |
FOV: 1.3, | |
SPEED: base.SPEED * 0.25, | |
HEALTH: base.HEALTH * 1.5, | |
SHIELD: base.SHIELD * 1.25, | |
REGEN: base.REGEN, | |
DAMAGE: base.DAMAGE * 2.5, | |
}, | |
}; | |
exports.elite_destroyer = { | |
PARENT: [exports.elite], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 5, 16, 1, 6, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.pound, g.destroy]), | |
TYPE: exports.bullet, | |
LABEL: 'Devastator', | |
}, }, { | |
POSITION: [ 5, 16, 1, 6, 0, 60, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.pound, g.destroy]), | |
TYPE: exports.bullet, | |
LABEL: 'Devastator', | |
}, }, { | |
POSITION: [ 5, 16, 1, 6, 0, -60, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.pound, g.pound, g.destroy]), | |
TYPE: exports.bullet, | |
LABEL: 'Devastator', | |
}, }, | |
], | |
TURRETS: [{ | |
/********* SIZE X Y ANGLE ARC */ | |
POSITION: [ 11, 0, 0, 180, 360, 0, ], | |
TYPE: [exports.crasherSpawner] | |
}, { | |
POSITION: [ 11, 0, 0, 60, 360, 0, ], | |
TYPE: [exports.crasherSpawner] | |
}, { | |
POSITION: [ 11, 0, 0, -60, 360, 0, ], | |
TYPE: [exports.crasherSpawner] | |
}, { | |
POSITION: [ 11, 0, 0, 0, 360, 1, ], | |
TYPE: [exports.bigauto4gun, { INDEPENDENT: true, COLOR: 5, }] | |
}, | |
], | |
}; | |
exports.elite_gunner = { | |
PARENT: [exports.elite], | |
GUNS: [ { /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 14, 16, 1, 0, 0, 180, 0, ], | |
}, { | |
POSITION: [ 4, 16, 1.5, 14, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.hexatrap]), | |
TYPE: [exports.pillbox, { INDEPENDENT: true, }], | |
}, }, { | |
POSITION: [ 6, 14, -2, 2, 0, 60, 0, ], | |
}, { | |
POSITION: [ 6, 14, -2, 2, 0, 300, 0, ], | |
} | |
], | |
AI: { NO_LEAD: false, }, | |
TURRETS: [{ | |
/********* SIZE X Y ANGLE ARC */ | |
POSITION: [ 14, 8, 0, 60, 180, 0, ], | |
TYPE: [exports.auto4gun], | |
}, { | |
POSITION: [ 14, 8, 0, 300, 180, 0, ], | |
TYPE: [exports.auto4gun], | |
}], | |
}; | |
exports.elite_sprayer = { | |
PARENT: [exports.elite], | |
AI: { NO_LEAD: false, }, | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 14, 6, 0, 180, 190, 0], | |
TYPE: [exports.spray, { COLOR: 5, }], | |
}, { | |
POSITION: [ 14, 6, 0, 60, 190, 0], | |
TYPE: [exports.spray, { COLOR: 5, }], | |
}, { | |
POSITION: [ 14, 6, 0, -60, 190, 0], | |
TYPE: [exports.spray, { COLOR: 5, }], | |
}, | |
], | |
}; | |
exports.palisade = (() => { | |
let props = { | |
SHOOT_SETTINGS: combineStats([g.factory, g.pound, g.halfreload, g.halfreload]), | |
TYPE: exports.minion, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
AUTOFIRE: true, | |
MAX_CHILDREN: 1, | |
SYNCS_SKILLS: true, | |
WAIT_TO_CYCLE: true, | |
}; | |
return { | |
PARENT: [exports.miniboss], | |
LABEL: 'Rogue Palisade', | |
COLOR: 17, | |
SHAPE: 6, | |
SIZE: 28, | |
VALUE: 500000, | |
BODY: { | |
FOV: 1.3, | |
SPEED: base.SPEED * 0.1, | |
HEALTH: base.HEALTH * 2, | |
SHIELD: base.SHIELD * 2, | |
REGEN: base.REGEN, | |
DAMAGE: base.DAMAGE * 3, | |
}, | |
GUNS: [ { /**** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 4, 6, -1.6, 8, 0, 0, 0, ], | |
PROPERTIES: props, }, { | |
POSITION: [ 4, 6, -1.6, 8, 0, 60, 0, ], | |
PROPERTIES: props, }, { | |
POSITION: [ 4, 6, -1.6, 8, 0, 120, 0, ], | |
PROPERTIES: props, }, { | |
POSITION: [ 4, 6, -1.6, 8, 0, 180, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.factory, g.pound]), | |
TYPE: exports.minion, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
AUTOFIRE: true, | |
MAX_CHILDREN: 1, | |
SYNCS_SKILLS: true, | |
WAIT_TO_CYCLE: true, | |
}, }, { | |
POSITION: [ 4, 6, -1.6, 8, 0, 240, 0, ], | |
PROPERTIES: props, }, { | |
POSITION: [ 4, 6, -1.6, 8, 0, 300, 0, ], | |
PROPERTIES: props, }, | |
], | |
TURRETS: [{ /* SIZE X Y ANGLE ARC */ | |
POSITION: [ 5, 10, 0, 30, 110, 0], | |
TYPE: exports.trapTurret, | |
}, { | |
POSITION: [ 5, 10, 0, 90, 110, 0], | |
TYPE: exports.trapTurret, | |
}, { | |
POSITION: [ 5, 10, 0, 150, 110, 0], | |
TYPE: exports.trapTurret, | |
}, { | |
POSITION: [ 5, 10, 0, 210, 110, 0], | |
TYPE: exports.trapTurret, | |
}, { | |
POSITION: [ 5, 10, 0, 270, 110, 0], | |
TYPE: exports.trapTurret, | |
}, { | |
POSITION: [ 5, 10, 0, 330, 110, 0], | |
TYPE: exports.trapTurret, | |
}, | |
], | |
}; | |
})(); | |
exports.bot = { | |
AUTO_UPGRADE: 'random', | |
FACING_TYPE: 'looseToTarget', | |
BODY: { | |
// SIZE: 10, | |
}, | |
//COLOR: 17, | |
NAME: "ai_", | |
LABEL: "Bot", | |
CONTROLLERS: [ | |
'avoid', 'nearestDifferentMaster', 'mapAltToFire', 'minionOrbitFarther', 'fleeAtLowHealth' | |
], | |
AI: { STRAFE: true, DOMINATOR_WHEN_NO_TARGET: true, | |
reverseDirection:true, | |
FOCUS_ON_DOMINATOR:true, | |
IGNORE_FOOD_AT_LEVEL45: true}, | |
}; | |
exports.observer = { | |
PARENT: [exports.genericTank], | |
DANGER: 0, | |
CONTROLLERS: ['mapTargetToGoal'], | |
LABEL: 'Observer', | |
BODY: { | |
FOV: 5, | |
HEALTH: base.HEALTH * 30, | |
SPEED: 20, | |
HETREO: 0, | |
} | |
} | |
exports.testbed.UPGRADES_TIER_1.push(exports.elite_sprayer); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global exports*/ | |
const {genericTank, skillSet, base, combineStats, g, bullet, trap, gunCalcNames, drone} = require('./basedefinitions.js') | |
exports.drone = require('./basedefinitions').drone | |
exports.dominationBody = { | |
LABEL: '', | |
CONTROLLERS: ['dontTurn'], | |
COLOR: 9, | |
SHAPE: -6, | |
//HETERO: 0, | |
INDEPENDENT: true, | |
}; | |
const statnames = { | |
smasher: 1, | |
drone: 2, | |
necro: 3, | |
swarm: 4, | |
trap: 5, | |
generic: 6, | |
}; | |
exports.dominator = { | |
PARENT: [genericTank], | |
LABEL: 'Dominator', | |
TYPE: 'fixed', | |
DANGER: 1, | |
SIZE: 90,//80 | |
FACING_TYPE: 'smoothToTarget', | |
SKILL: skillSet({ | |
dam: 2, | |
pen: 2, | |
str: 1, | |
}), | |
BODY: { | |
RESIST: 100, | |
SPEED: 0, | |
HEALTH: base.HEALTH * 30,//try 40 | |
DAMAGE: base.DAMAGE * 2, | |
PENETRATION: 0.25, | |
FOV: 1, | |
PUSHABILITY: 0, | |
HETERO: 0, | |
REGEN: 0, | |
SHIELD: 0, | |
}, | |
DAMAGE_EFFECTS: false, | |
CONTROLLERS: ['nearestDifferentMaster', 'alwaysFire', 'setDominatorControl'/*'spinWhenIdle'*/], | |
TURRETS: [{ | |
POSITION: [24, 0, 0, 0, 360, 0], | |
TYPE: exports.dominationBody, | |
}], | |
CAN_BE_ON_LEADERBOARD: false, | |
GIVE_KILL_MESSAGE: false, | |
ACCEPTS_SCORE: false, | |
}; | |
exports.destroyerDominator = { | |
PARENT: [exports.dominator], | |
CONTROLLERS: ['nearestDifferentMaster',/*'spinWhenIdle'*/], | |
GUNS: [{ | |
POSITION: [15.25, 6.75, 1, 0, 0, 0, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.destroyDominator]), | |
TYPE: bullet, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [5, 6.75, -1.6, 6.75, 0, 0, 0], | |
}], | |
}; | |
exports.gunnerDominator = { | |
PARENT: [exports.dominator], | |
GUNS: [{ | |
POSITION: [14.25, 3, 1, 0, -2, 0, 0.5], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunnerDominator]), | |
TYPE: bullet, | |
}, | |
}, { | |
POSITION: [14.25, 3, 1, 0, 2, 0, 0.5], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunnerDominator]), | |
TYPE: bullet, | |
}, | |
}, { | |
POSITION: [15.85, 3, 1, 0, 0, 0, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.basic, g.gunnerDominator]), | |
TYPE: bullet, | |
}, | |
}, { | |
POSITION: [5, 8.5, -1.6, 6.25, 0, 0, 0], | |
}], | |
}; | |
exports.trapperDominator = { | |
SIZE: 60, | |
PARENT: [exports.dominator], | |
FACING_TYPE: 'autospin', | |
GUNS: [{ | |
POSITION: [3.5, 3.75, 1, 8, 0, 0, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 0, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [3.5, 3.75, 1, 8, 0, 45, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 45, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [3.5, 3.75, 1, 8, 0, 90, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 90, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [3.5, 3.75, 1, 8, 0, 135, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 135, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [3.5, 3.75, 1, 8, 0, 180, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 180, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [3.5, 3.75, 1, 8, 0, 225, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 225, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [3.5, 3.75, 1, 8, 0, 270, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 270, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}, { | |
POSITION: [3.5, 3.75, 1, 8, 0, 315, 0], | |
}, { | |
POSITION: [1.25, 3.75, 1.7, 12, 0, 315, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.trap, g.trapperDominator]), | |
TYPE: trap, | |
AUTOFIRE: true, | |
}, | |
}] | |
}; | |
exports.drone_dominator = { | |
PARENT: [genericTank], | |
LABEL: 'Dominator', | |
// try disabling autospin | |
FACING_TYPE: 'autospin', | |
DANGER: 10, | |
SIZE: 90, | |
CONTROLLERS: ['nearestDifferentMaster', 'alwaysFire', 'mapAltToFire'], | |
BODY: { | |
HEALTH: 6148, | |
SHIELD: base.SHIELD * 1.25, | |
REGEN: base.REGEN * 0.75, | |
SPEED: base.SPEED * 0.25, | |
ACCELARATION: base.ACCEL * 0.5, | |
PUSHABILITY: 0.15, | |
AUTOFIRE:true, | |
}, | |
GUNS: [{ | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 0, 0], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 3, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 0, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 60, 1 / 6], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 3, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 60, 0], | |
}, { | |
//POSITION: [3.75, 4, 1.2, 8.5, 0, 120, 1 / 3], | |
// /*** LENGTH WIDTH ASPECT X Y ANGLE DELAY */ | |
POSITION: [ 6, 12, 1.2, 8, 0, 90, 0, ], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 3, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 120, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 180, 1 / 2], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 180, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 240, 2 / 3], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 240, 0], | |
}, { | |
POSITION: [3.75, 4, 1.2, 8.5, 0, 300, 5 / 6], | |
PROPERTIES: { | |
SHOOT_SETTINGS: combineStats([g.drone, g.droneDominator]), | |
TYPE: drone, | |
AUTOFIRE: true, | |
SYNCS_SKILLS: true, | |
STAT_CALCULATOR: gunCalcNames.drone, | |
WAIT_TO_CYCLE: true, | |
MAX_CHILDREN: 1, | |
}, | |
}, { | |
POSITION: [3.75, 4.45, -1.6, 7.2, 0, 300, 0], | |
}], | |
MAX_CHILDREN: 8, | |
STAT_NAMES: 2, | |
TURRETS: [{ | |
POSITION: [22, 0, 0, 0, 360, 0], | |
TYPE: exports.dominationBody, | |
}], | |
GIVE_KILL_MESSAGE: true, | |
//ACCEPTS_SCORE: false, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*jslint node: true */ | |
/*jshint -W061 */ | |
/*global goog, Map, let */ | |
//"use strict"; | |
// General requires | |
// Using google closure compiler would help | |
require('google-closure-library'); | |
goog.require('goog.structs.PriorityQueue'); | |
goog.require('goog.structs.QuadTree'); | |
// Import game settings. | |
const c = require(process.argv[2]); | |
// Import utilities. | |
const util = require('./lib/util'); | |
const ran = require('./lib/random'); | |
const hshg = require('./lib/hshg'); | |
const _ = require('lodash') | |
const gamemode = require('./gamemodes/ctf.js') | |
const ROID_TEAM = -101 | |
const CHECK_KEYS = false | |
let topPlayer = null | |
// Let's get a cheaper array removal thing | |
Array.prototype.remove = index => { | |
if(index === this.length - 1){ | |
return this.pop(); | |
} else { | |
let r = this[index]; | |
this[index] = this.pop(); | |
return r; | |
} | |
}; | |
// Define player keys | |
var keys = [ | |
'k', 'l', 'testk', 'testl', | |
// Focus Group | |
'ZNr3GBQOhD2CDDYpZD3JZkZ6hmhoF4wGiTYTikZlSLr1Z66yWKuVMitRkpUbPy6s', // Mine | |
'HKib09Ep3hIcwFXpiCj5iEkpLBN88HQ22hiFqg5alcxn4AYl6VcsPFTqMvllLt1D', // Parodia | |
'n9hx8iQH8453dWQpdDvJcAvPzQej80xQz86TxuYaJ8CaOr4hEH2zHPlSeayVPjFZ', // SGM | |
'5piWwi06VXdEuOsz1rbcHiglurbaYIPtslIgE0NNMGQgNcqErdJ4kUVYpDJsRlVC', // Aznaft | |
'q80UgWYIQVM2oZW5iQO6VRdLcOTuHkSgUx4U7NN8z76Ltgj7gVc6tSWvmpPkRUGH', // Licht | |
'9zcVcKxiv60ZoBr6CaO9ecjR3i0Mj9yx4Qgt9IGwzxps8Q5ge1GQJiYe59GBxKip', // Tenderlicious | |
'M67ZAZIgboiBcUtcKoHOuwXlQJWN9DEwhr0CIqR9xjiwpDyb4cUrwUIynKnuQmrU', // ManticoreKiller | |
'iBKZrtZEP6Gq1m1y4hpbIH2htBKegkaj6eyO70L9FMAEydiV4gA4ufiLWFx0R5C2', // JB Columbia | |
'zbH5Myv66HmR9Mda39xlLXa9TyBGzXnKZV7xpN5NCDTXorn52123eHY4kcZmPNLx', // Teal Knight | |
'pee4OZmPo9yrINv30kIMMVviEr1PRfiuIYQEOGXTK6lnLZumy9O942NabE8BiEce', // unnamed | |
'08IhobFLBYah8Mk8MKqqG6W576iS4jznjK4WnIsSzcFC0OhkIY51DQV0DWWsgfbg', // Pie | |
'36spA3cA2FNDamjM4SaiNNfNMkUMSERgduUvAL3Ms8bsioX4uoMyQteMWx1dRpdp', // Sergio | |
'i3tpmHTC2ty8CCzjhISDKO1MrkZOwmoWZ08XZLOg3IfCqbtAsdC8QPKPMhbPHQmV', // Corrupt X | |
'gQHpJkeGoxknxqkiX9msLhwS1NzikXa1RiOKOJD2o2zf15XL35P1YWZeMcivXLNB', // Jorjito Gamer | |
'kKWsRf0OdLWzipECohr5FqjuyecPZYOGxl1zAXabtllaWx2OVKfLTKBiit8KVg5j', // warrior | |
'77L1QgQgsTQrZHhTSuv1iK1NyvpBL9AYyvmkF21Sjp4T7ldxGodQnC9dM1YtvZzG', // TTTank | |
'M6I9vmmRiitxg07rBG2IuC7aNpp7LHQGVPtwGfkk3hIBR0jhlWwaqzpzPXqU2awM', // CX | |
'5AxKhPIu5jF3B3cIxjA2BHUy30ccYgEUXJmK16ksJotp9D9WVlY6QqPLDPGim1MK', // Faxaro | |
'kcrJTPqvhraysgCNrFZORGNR4UTMRvbQ2zuhI3iXpGyMg6wDtU5QMgcV8vNdLLHQ', // Mipha | |
'EXoiZYDuwSwmp7Zg0m7hdaLyv2PMbQgQorkwRznC0NC3saubVNtxVUGtOWZ2xdcz', // svorlds | |
'G0t2lQYeaTHHU8sp5ibNjFCCLMr41cPCOJRKUC5eUGfkUKDxpVwo5azomBSznZuR', // FTM | |
'kf2VcjtzpMvVwhlgIjq4MX6LWbIoNzcvfsxARS0qWiuVWf6BPPsQ2p1FgBVvNoB1', // pnvv / Cannon Man | |
'3hO6R7AOR0aiiFuRyGaHKrgJHjTEpsD2LZ866bhvlz2Ru9AT8QmiBNf5PZuXCFIA', // wowie's friend | |
'z272UlNODnYVK79jva6pybRpwtp1h0FdJh8F8JRQJ5VY9lPrcugp6nd403Op4voC', | |
'eOb4DCk81Hzay8Kgjcu6tbbpIUCveloxahmnkmg3aU6FlvdWjJd2Uui5cFQdsnby', | |
'9qGqNv5iYTSIhkCaMmZpvYhSpaLnHQJnj6m2gdoVWIXgLaFgIrbcFYHM8bcBsGYS', | |
'qqWz1E1uVtErG4N80YDVQJywzOk6PJFDrC6uzqoQ9XL2nNrCCr1KvY8XUEyCroHT', | |
'r0KXqfIifiavtqP3v0b5gqb5ArQY5sJWO7fjG4P6AFE5MRyfjDGK7sO7nXg23Tkv', | |
'nUzNolF4Yys4ua6x78GiVH0Fparcm8GyD60IZzVHji0b2gQL3citWEEi3b1J9iRT', | |
'XSxFurVLlc7o99nnakK5EPA2Z16tqBxP3xKcq5y4XOjRyfFRqaSxbBNRUtab71FH', | |
'uYLfr6k6wEmgMtGVna366Gujor3gUWhWUHgbsz2uUNhQ8OKkwzb1IpDehnz7dfFL', | |
'TVA4eYx29geFN6kb2Osyt5veaih0OOJG2MzB4qBBlUQr5CpRJqIhrTModxcT5NXI', | |
'eyQqQE0h0l6x7XpkXpnZdYPsRJgvdl6L8xAoEzF0ZGlTV8HH0wUePj03LuULDhSN', | |
'ZuOzwoZw4lCWwekTMh9bEAw4Tv92uLhzGN0DMDV2Rk7Sfn3Hsbf87ssHcvxTbDek', | |
// Public | |
'PUBLICRSUZbhCMu2ocDrhtje1ev6ff3eM6IxsCPUBLIC', | |
'PUBLICb7HbKa0zFp5PzJVkcu17GIbp56JeHxZlPUBLIC', | |
'PUBLICwxTybWuUrYfEA84kVunN5btV4vROYCW0PUBLIC', | |
'PUBLICfOKBjTZzW1VvoEfJTY3G7U2TcwT8iREyPUBLIC', | |
'PUBLICKKRLO0lpLy2IDHUdqzE0MBsZUhrBnYRpPUBLIC', | |
'PUBLICsC7wKFQ6CXPB241uA5RzORP2Z14CSO86PUBLIC', | |
'PUBLIC6criSrXdLBoTtIWQHCmcOPfzqgDZcGOiPUBLIC', | |
'PUBLIC3QdiZpPEAtB4gif0TEU3822qJz3W23J2PUBLIC', | |
'PUBLICEDZLxLjRRfa8tS5EqRIExtHpWq0MJSVZPUBLIC', | |
'PUBLIC5vmCtP1IjDnglKJk7AmWg3hAuZ4ZGGnVPUBLIC', | |
'PUBLICe1r6NsdjhOnpNuPqnskTzLvJoaXn3dsqPUBLIC', | |
'PUBLICTbfzA0MB2H6hRataGEQENmu1o9eOpytkPUBLIC', | |
'PUBLICpJlxtdn2iplYuIWXznUX3f6RHHPC3uFrPUBLIC', | |
'PUBLICadVvUN4mp0MTSAnsc3BKIJ6l40Y5sV00PUBLIC', | |
'TRUSTED5vmCtP1IjDnglKJk7sAmWg3hAuZ4ZGGnVTRUSTED', | |
'TRUSTEDe1r6NsdjhOnpNuPqnskTfzLvJoaXn3dsqTRUSTED', | |
'TRUSTEDTbfzA0MB2H6hRataGE3QENmu1o9eOpytkTRUSTED', | |
'TRUSTEDpJlxtdn2iplYuIWXsznUX3f6RHHPC3uFrTRUSTED', | |
'TRUSTEDadVvUN4mp0MTSAnsc3BKfIJ6l40Y5sV00TRUSTED', | |
'TRUSTED3nYR28Kwhnx1n6JvP4Tm r2dxLhrTvrcNTRUSTED', | |
'TRUSTEDNwHIdUtjLSmITUVNg5B6c4uVWiB7IFq2STRUSTED', | |
'TRUSTEDDIIocNBJS9mYstVFSuiwNxbQeEXOFlrPhTRUSTED', | |
'TRUSTED17rtKXqQ7wzek6Ejf9rGCfOdRr5vrm5AxTRUSTED', | |
'TRUSTEDWJkuJFZ2Wljq2WXasxHrM0Vsbra5iyb6vTRUSTED', | |
'TRUSTEDzxVdPsuU1yGRQrkbADH6rBaE8TKdAvJabTRUSTED', | |
'TRUSTED7nAZ3NBi9ZB07KfLV0cnGO0YEXoSGf1lLTRUSTED', | |
'TRUSTEDFyJTLBCrokyoFICQFi4hAGJd09jkCDqOJTRUSTED', | |
'TRUSTEDPBHbBZkW9foaXPDfGe6xq9Y6XvJhrwowqTRUSTED', | |
'TRUSTEDtTZe5CYcmmCQBLj0WztAHn5MnI0dhqNrXTRUSTED', | |
'GUDPOSTERNwR7FWcY1eeNkyiCrzGfuo3wGWhETFmbGUDPOSTER', | |
'GUDPOSTERR2gdw10L7u4auP3yr1G1EC59TnRA3H31GUDPOSTER', | |
'GUDPOSTERVLX8LwHtMrLIMFx0XdzTdauVAmSKV9SZGUDPOSTER', | |
'GUDPOSTER8Uk4cGa2ut3vFfaPmjbmRBtAXpFHXsBNGUDPOSTER', | |
'GUDPOSTERdHHy9pqMejwGZJ7nUZMRw0Mnc1g8UJ8oGUDPOSTER', | |
'GUDPOSTERrgZPXqFSJXdChEMvgQjjxjGZfsObOArCGUDPOSTER', | |
'GUDPOSTERysJI3BfzB2cRCDDdFkAaFWxZk5TNHwfvGUDPOSTER', | |
'GUDPOSTERlFps80nCJ6cnFGjyH9QoKqgETwGX1sIQGUDPOSTER', | |
'GUDPOSTERmED6CZg213gXoCYyDqxMLGFtuuCPn8NmGUDPOSTER', | |
'GUDPOSTERlSL92YPpoqh48GuQwydpGuocJAH6Vx5VGUDPOSTER', | |
'GIVEAWAYZ1yVvobK3MWgCBxYjFheJd3UrWW2ULJuGIVEAWAY', | |
'GIVEAWAYaVGcMBm3LwxmLkxxGSt6NNg9AUDsj5v5GIVEAWAY', | |
'GIVEAWAYAMkJmX3xKv3tiieS5oAfEsJbni4xInIwGIVEAWAY', | |
'GIVEAWAYi3AbdptFr9m2fGGqY9p6Vvi3uRX6ALHRGIVEAWAY', | |
'GIVEAWAYxwABlNSPU4291UJICWyeXQB4ET0ZyA0uGIVEAWAY', | |
'GIVEAWAYczPSwYnpHDGKaimREjN1e86N6CmSH0NWGIVEAWAY', | |
'GIVEAWAYDx3U7MOBNyDmjv6Rz6Le6wgG4Xk0cwilGIVEAWAY', | |
'GIVEAWAYCOr2yK7od6RRch52ToBO5s0xxizBVVajGIVEAWAY', | |
'GIVEAWAYV7fiIzckU8xQ57i3Bu8ngWetPOzS9ktvGIVEAWAY', | |
'GIVEAWAYpbo21yNoMcvwhbIeMOsqMIjzYKOLZyEgGIVEAWAY', | |
'500kBomberContestTokenVUBefeRUMQsLShjas4dhfSF', | |
'500kBomberContestTokenNSEefeRUMQsLShjbs4dhfSF', // TnT | |
'500kBomberContestTokenWDWefeRUMQsLShjcs4dhfSF', // crnz | |
'500kPoacherContestTokenZZb1FkYER7B0ZV7bs9df8s', | |
'500kAutoDoubleContestTokenKBSj41qloynOGws87X2', // JeShAn | |
'500kFortressContestTokenl2fd42tL7C6ZynSDF33ox', // Lucario | |
// Youtube | |
'SGMTokenGiveaway51NP3JOh9NKvsnVh6PDRGI1wALGXWLzE2jZXztWKxlyPN00w', | |
'SGMTokenGiveaway2puyw4VGFTTSqgxeFvvvqxMTzZ5S3XPtVQXLCSIOpW7Rxv8m', | |
'SGMTokenGiveawayYAu4abk9oLMaBqOXfx2QvSqznNqw7mTFv7lBFk5LJ7ksPd7W', | |
'SGMTokenGiveawaybgSA5xNNpo4Vhsfg8lOlop8f4FOPWk9VXcMvjl62JYWhKOWF', | |
'SGMTokenGiveawaya7C7vBTBPxgWEgg1g3UbYttE30A33aFVqEEd2pdV3PfbxvA0', | |
'SGMTokenGiveawayBFu7eKC22KxKYuFiUTOyjmMCpBhr1HseP7pNo4yl5xOZt9IS', | |
'SGMTokenGiveawayAHVq7eEAUWZzCtK4vcHslWIDMPykPAfsnq4jdsHYE3HIhlBO', | |
'SGMTokenGiveawayS0wxtOYFcnBirWbbP9EePvgo8rPVrhatpixkaH78CdKdtorr', | |
'SGMTokenGiveaway7p8JwRnATdS3H10gIKy5dKQXlbj93WplkC9NpfjNTREG9IQn', | |
'SGMTokenGiveawaynM1ffqsEM31Vv6KMmlxhs6Ug0s65FiyN3w9eP6QM7FmpbS2i', | |
'SGMTokenAa05Q1oDwf0Mxaw57vBTBPX3M25gjitRD0daHTObk796GqSJ3KUhKf5p', | |
'SGMTokenxg3Kw7jPUoxFOXbO4POF19iovCUnNzqoQ9XL2rTAoXoAtyHDZR5YFgAk', | |
'SGMToken7KteCaOERDa8TkfzIQIm54rhewlKL2lWIDMPykPAfsnq41MGxgogphB9', | |
'OMTokenIGnPS8RSGiP8lvTQDdve9ANPfSOyTgvPQMYdFlcn7IVcJg8oeGreEBYs', | |
'OMTokenLTARU3UJldlHUf8215Wg4AbdThRvA3j0wG2FbwyZCTixkaH78CdK8BnV', | |
'OMToken7sOXlNs9Qu58TmaCu9TpD4JkzRuGrKKOS74tZimimR8Iu5du7v6GRbRH', | |
'JBColombiaTokenwZXpYskkovgQL4jZlqS42xaqgVAvHZPZgVcccsBkHhsXhq69', | |
'JBColombiaToken8WwiA5demyL1gQZ9D5kvFMOwkJRc3STikct22cMoPmjfli69', | |
'JBColombiaTokenPDuZydKLePKQ9TyOMqiquI0YVHcCJBJb3pORyzfo42nHhT69', | |
'JBColombiaTokeniC0Eh8jMoncX4bAKbslR174tZimimBXoUGhvaKY0dBwbLI69', | |
'JBColombiaTokenWWqX44i7VqxtQB3qsViJHbJnK3FryxqgAAFerRFxYO2wJc69', | |
'JBColombiaTokenlzgPyfwuto7KY8BqxDserADmpeuMR31wxgD0dWpNWvHZv969', | |
'SMTokenlSrBG8RTazOHzZ6zeyBPFI1tOSiuDSJNcfozraRKb8votLtwmNFC964KG', | |
'SMTokennrNg7MzqzJe2xz11cKDETqCBKVhDiOS6x1gyTMV8EHLkRGGFXAHLUVUjk', | |
'SMTokenfjlzipOhA8Lfp38kt9FnzGKRg6g79hujlFVPbEyzsbEqbYOD2ohveMSh8', | |
'SMTokenNHPtbYKUDrR8MBQoQIymCwdbFSoHHNTuBMPvS4iugQigBMvfrGurB3qM4', | |
'SMTokenI33BqYnppCCVAMOkykIeOWIsmetgkymFK1A7XgeZGGW52xVq1xRKv38vC', | |
'SMTokenHxNBGJGRf6SqXAOIhgMEOuPUp4X4LszwBEeco3Wrw2IuOe3jxoWyLKdR0', | |
'SMTokennjophXq0WC3jzDpPrDbfXLE2eoFOMvQWKucR0ZwECIlXDBTQnF33uyDXd', | |
// Patreon / rewards | |
'tokenlordkarma88tokenlordkarma88tokenlordkarma88tokenlordkarma88', | |
'hereIsUrTokenBuddyThxForTheOverGunnerLmao', | |
'DukeonkledDukeonkleThankYouSoMuch123e911DukeonkledDukeonkledDuke', | |
'FireNationFireNationThanksATon018s380280FireNationFireNationFire', | |
'rewardTokenJSdf323H0Cj85aVOG3SPlgp7Y9BuBoFcwpmNFjfLEDQhOFTIpukdr', // Call | |
'rewardTokenDg2JDTp0rxDKXIPE8PHcmdHqWyH2CqPqpcAf6QcT8m2hgBZnJ7KHE', | |
'rewardTokenad3JTsTwuVLkQvfmVH2d2Ukbf8WbFuPBqTpYFdFx9AuZEnmv9EW8U', | |
'rewardTokenJsa43Tthn1M5Ey9oDRODzzrazqRxL28cTchgInjVCrSfnWEATdYeP', | |
'rewardTokensdfsJTyz2YMS3GLDfD2NvqXK46p1ScsmdLxI1owBkjHw983lwkR8Z', | |
// Wiki | |
'WIKIREWARDV7V0bZRP8lM3fUvwuAX7DC5FpZCU1AyJByaulkH9YHZ7WIKIREWARD', | |
'WIKIREWARDDOE8Iqg5K124sNXSR51WWycmCnFtCLjyF7uole5sgQgoWIKIREWARD', | |
'WIKIREWARD5z5xXA0flzxeRgGu6EjSWlOq23gdGoYALClfsUT143Y9WIKIREWARD', | |
'WIKIREWARD4DTEvdwSBKPBRCAJxeS9surL09uzxx33gAHmMYFldRsMWIKIREWARD', | |
'WIKIREWARDqGXxMucMJcSeqWFcAfCLVNStnmOezkzOUot8xbfpCuk1WIKIREWARD', | |
'EDITOR1eKAAURvtnHYFuUz6dzPqOwPt6SFWbacEucDnm8KroabolnzLZrdEDITOR', | |
'EDITOR38Gi67EFmLdh6nXuKqtRc79HKk34c6bQl08tbUeZlGcxBS2c350yEDITOR', | |
'EDITOR7mAKjd6XYprdtvbWqqUjEEfCqomx67aLSyG70eiFuvRVv2Eest27EDITOR', | |
'EDITORoNzv0DxKzLYY7YCYdIsRHdNz8DNNiuqI2I9mBM2blBpWZ39chumsEDITOR', | |
'EDITOR399V1FLGtsne5BMg5QfeeHdR63bxkV51Av0ET3F5y92q7EMhI8R3EDITOR', | |
'EDITORmUJbmoFVshllWIUb11kyXxQfyESa4t3SYcGRHSlWzLrzfwkHCIVUEDITOR', | |
// Themes | |
'YouAreTheCreatorOfBadlands', | |
'WowYouMadeADopeFishyTheme', | |
'ThanksForHelpingPlantAForest', | |
'MidnightIsSuperCoolNotYouTheTheme', | |
'DrinkBleachPlz', | |
'FrostyAndBeautifulJustLikeYourColdHeart', | |
]; | |
// Set up room. | |
global.fps = "Unknown"; | |
var roomSpeed = c.gameSpeed; | |
const room = { | |
lastCycle: undefined, | |
cycleSpeed: 1000 / roomSpeed / 30, | |
width: c.WIDTH, | |
height: c.HEIGHT, | |
setup: c.ROOM_SETUP, | |
xgrid: c.X_GRID, | |
ygrid: c.Y_GRID, | |
gameMode: c.MODE, | |
skillBoost: c.SKILL_BOOST, | |
scale: { | |
square: c.WIDTH * c.HEIGHT / 100000000, | |
linear: Math.sqrt(c.WIDTH * c.HEIGHT / 100000000), | |
}, | |
maxFood: c.WIDTH * c.HEIGHT / 100000 * c.FOOD_AMOUNT, | |
isInRoom: location => { | |
return !(location.x <= 0 || location.x >= c.WIDTH || location.y <= 0 || location.y >= c.HEIGHT) | |
}, | |
topPlayerID: -1, | |
}; | |
room.findType = type => { | |
let output = []; | |
let j = 0; | |
room.setup.forEach(row => { | |
let i = 0; | |
row.forEach(cell => { | |
if (cell === type) { | |
output.push({ x: (i + 0.5) * room.width / room.xgrid, y: (j + 0.5) * room.height / room.ygrid, }); | |
} | |
i++; | |
}); | |
j++; | |
}); | |
room[type] = output; | |
}; | |
room.findType('nest'); | |
room.findType('norm'); | |
room.findType('bas1'); | |
room.findType('bas2'); | |
room.findType('bas3'); | |
room.findType('bas4'); | |
room.findType('roid'); | |
room.findType('rock'); | |
room.findType('dmtr'); | |
room.nestFoodAmount = 1.5 * Math.sqrt(room.nest.length) / room.xgrid / room.ygrid; | |
room.random = () => { | |
return { | |
x: ran.irandom(room.width), | |
y: ran.irandom(room.height), | |
}; | |
}; | |
room.randomType = type => { | |
let selection = room[type][ran.irandom(room[type].length-1)]; | |
return { | |
x: ran.irandom(0.5*room.width/room.xgrid) * ran.choose([-1, 1]) + selection.x, | |
y: ran.irandom(0.5*room.height/room.ygrid) * ran.choose([-1, 1]) + selection.y, | |
}; | |
}; | |
room.hasType = type => room[type].length > 0 | |
room.gauss = clustering => { | |
let output; | |
do { | |
output = { | |
x: ran.gauss(room.width/2, room.height/clustering), | |
y: ran.gauss(room.width/2, room.height/clustering), | |
}; | |
} while (!room.isInRoom(output)); | |
}; | |
room.gaussInverse = clustering => { | |
let output; | |
do { | |
output = { | |
x: ran.gaussInverse(0, room.width, clustering), | |
y: ran.gaussInverse(0, room.height, clustering), | |
}; | |
} while (!room.isInRoom(output)); | |
return output; | |
}; | |
room.gaussRing = (radius, clustering) => { | |
let output; | |
do { | |
output = ran.gaussRing(room.width * radius, clustering); | |
output = { | |
x: output.x + room.width/2, | |
y: output.y + room.height / 2, | |
}; | |
} while (!room.isInRoom(output)); | |
return output; | |
}; | |
room.isIn = (type, location) => { | |
/*if (!(location.y instanceof Number) || !(location.x instanceof Number)) { | |
return false | |
}*/ | |
if (room.isInRoom(location)) { | |
let a = Math.floor(location.y * room.ygrid / room.height); | |
let b = Math.floor(location.x * room.xgrid / room.width); | |
return type === room.setup[a][b]; | |
} else { | |
return false; | |
} | |
}; | |
room.isInNorm = location => { | |
if (room.isInRoom(location)) { | |
let a = Math.floor(location.y * room.ygrid / room.height); | |
let b = Math.floor(location.x * room.xgrid / room.width); | |
let v = room.setup[a][b]; | |
return v !== 'nest'; | |
} else { | |
return false; | |
} | |
}; | |
room.isInRoomType = (location, roomType) => { | |
if (room.isInRoom(location)) { | |
let a = Math.floor(location.y * room.ygrid / room.height); | |
let b = Math.floor(location.x * room.xgrid / room.width); | |
let v = room.setup[a][b]; | |
return v === roomType; | |
} else { | |
return false; | |
} | |
}; | |
room.gaussType = (type, clustering) => { | |
let selection = room[type][ran.irandom(room[type].length-1)]; | |
let location = {}; | |
do { | |
location = { | |
x: ran.gauss(selection.x, room.width/room.xgrid/clustering), | |
y: ran.gauss(selection.y, room.height/room.ygrid/clustering), | |
}; | |
} while (!room.isIn(type, location)); | |
return location; | |
}; | |
util.log(room.width + ' x ' + room.height + ' room initalized.'); | |
// Define a vector | |
class Vector { | |
constructor(x, y) { //Vector constructor. | |
this.x = x; | |
this.y = y; | |
} | |
update() { | |
this.len = this.length; | |
this.dir = this.direction; | |
} | |
get length() { | |
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); | |
} | |
get direction() { | |
return Math.atan2(this.y, this.x); | |
} | |
} | |
function nullVector(v) { | |
v.x = 0; v.y = 0; //this guy's useful | |
} | |
// Get class definitions and index them | |
var Class = (() => { | |
let def = require('./lib/definitions'), | |
i = 0; | |
let all = [] | |
for (let k in def) { | |
if (!def.hasOwnProperty(k)) continue; | |
def[k].index = i++; | |
all.push(def[k]) | |
} | |
let playableTanks = [] | |
let queue = [def.basic] | |
let undefinedToArray = o => o === undefined ? [] : o | |
while (queue.length > 0) { | |
let popped = queue.pop() | |
if (popped === undefined) { | |
continue | |
} | |
playableTanks = playableTanks.concat(undefinedToArray(popped.UPGRADES_TIER_2), undefinedToArray(popped.UPGRADES_TIER_1), undefinedToArray(popped.UPGRADES_TIER_3)) | |
queue = queue.concat(popped.UPGRADES_TIER_2, popped.UPGRADES_TIER_1, popped.UPGRADES_TIER_3) | |
} | |
def.randomPlayable = () => ran.choose(playableTanks) | |
return def; | |
})(); | |
// Define IOs (AI) | |
function nearest(array, location, test = () => { return true; }) { | |
let minD = 9999999 | |
let minDEntity | |
let d; | |
array.forEach(instance => { | |
const d = Math.pow(instance.x - location.x, 2) + Math.pow(instance.y - location.y, 2); | |
if (test(instance, d) && d < minD) { | |
minD = d | |
minDEntity = instance | |
} | |
}); | |
if (minDEntity) { | |
return minDEntity; | |
} | |
} | |
function timeOfImpact(p, v, s) { | |
// Requires relative position and velocity to aiming point | |
let a = s * s - (v.x * v.x + v.y * v.y); | |
let b = p.x * v.x + p.y * v.y; | |
let c = p.x * p.x + p.y * p.y; | |
let d = b * b + a * c; | |
let t = 0; | |
if (d >= 0) { | |
t = Math.max(0, (b + Math.sqrt(d)) / a); | |
} | |
return t*0.9; | |
} | |
class IO { | |
constructor(body) { | |
this.body = body; | |
this.acceptsFromTop = true; | |
} | |
think() { | |
return { | |
target: null, | |
goal: null, | |
fire: null, | |
main: null, | |
alt: null, | |
power: null, | |
}; | |
} | |
} | |
function getFoodClass(level) { | |
let a = {}; | |
switch (level) { | |
case 0: | |
a = Class.egg; | |
break; | |
case 1: | |
a = Class.square; | |
break; | |
case 2: | |
a = Class.triangle; | |
break; | |
case 3: | |
a = Class.pentagon; | |
break; | |
case 4: | |
a = Class.bigPentagon; | |
break; | |
case 5: | |
a = Class.hugePentagon; | |
break; | |
default: | |
throw 'bad food level' | |
} | |
if (a !== {}) { | |
a.BODY.ACCELERATION = 0.005 / (a.FOOD.LEVEL + 1); | |
} | |
return a; | |
} | |
let dominatorControl = {} | |
//middle | |
//central | |
//center | |
const dominatorNameToIndex = {'NE': 0, 'NW': 1, 'MIDDLE': 2, 'SE': 3, 'SW': 4, 'MI': 2} | |
const colorteammapping = {[-2]: 10, [-3]: 11, [-4]: 12, [-5]: 15, [-101]: 3}; | |
class io_setDominatorControl extends IO { | |
constructor(body) { | |
super(body); | |
this.dominatorFlipped = false | |
this.originalHealth = this.body.health.amount | |
if (body.label.endsWith("Dominator")) { | |
const originalDestroy = body.destroy.bind(body) | |
const originalContemplationOfMortality = body.contemplationOfMortality.bind(body) | |
body.health.regenerate = () => null | |
body.contemplationOfMortality = () => { | |
this.think() | |
return originalContemplationOfMortality() | |
} | |
body.destroy = () => { | |
this.think() | |
return originalDestroy() | |
} | |
} | |
} | |
think() { | |
if(this.body.health.amount <= 0 && this.body.collisionArray.length >= 1 && !this.dominatorFlipped) { | |
let finalHitTank = this.body.collisionArray[this.body.collisionArray.length - 1].master | |
let dominatorIndex = this.body.name | |
let teamToDomName = {[-1]: "BLUE", [-2]: 'GREEN', [-3]: 'RED',[-4]:'PURPLE'} | |
if (this.body.team === -100) { | |
dominatorControl[dominatorIndex] = finalHitTank.team | |
sockets.broadcast("The " + this.body.name + " is now controlled by " + teamToDomName[dominatorControl[dominatorIndex]] + ".") | |
} else { | |
dominatorControl[dominatorIndex] = -100 | |
sockets.broadcast("The " + this.body.name + " is being contested.") | |
} | |
let newDominator = new Entity({x: this.body.x, y: this.body.y}) | |
newDominator.team = dominatorControl[dominatorIndex] | |
newDominator.color = colorteammapping[dominatorControl[dominatorIndex] - 1] | |
newDominator.name = this.body.name | |
newDominator.define(this.body._dom_kind) | |
newDominator._dom_kind = this.body._dom_kind | |
this.dominatorFlipped = true | |
console.log(newDominator.team, newDominator.color, newDominator.health.amount) | |
console.log("dominator chnaged hands") | |
if (dominatorControl[0] !== -100 && dominatorControl.every(t => dominatorControl[0] === t)) { | |
setTimeout(() => sockets.broadcast(teamToDomName[dominatorControl[0]] + " HAS WON THE GAME!"), 200) | |
setTimeout(() => sockets.broadcast("Arena closed: "), 1000*10) | |
setTimeout(() => { | |
// kill self and restart | |
}) | |
} | |
sockets.broadcastDominator(labels.indexOf(dominatorIndex), newDominator.color) | |
//entitiesNoFood.find(value => if) | |
} | |
} | |
} | |
class io_moreFoodWhenDie extends IO { | |
constructor(body) { | |
super(body); | |
const originaldestroy = body.destroy.bind(body) | |
const foodLevel = body.foodLevel | |
body.destroy = () => { | |
let levelToMake = foodLevel | |
// is this on barbarosssa server? | |
let splitOff = food.filter(o => o.isDead() === false) | |
let place | |
if (splitOff.length !== 0) { | |
splitOff=ran.choose(splitOff) | |
place = { | |
x: splitOff.x + splitOff.size * Math.cos(splitOff.facing), | |
y: splitOff.y + splitOff.size * Math.sin(splitOff.facing), | |
}; | |
} else { | |
place = room.random() | |
splitOff = {facing: ran.randomRange(0, Math.PI*2)} | |
} | |
let new_o = new Entity(place); | |
new_o.define(getFoodClass(levelToMake)); | |
new_o.team = -100; | |
new_o.facing = splitOff.facing + ran.randomRange(Math.PI / 2, Math.PI); | |
new_o.F = food.length | |
food.push(new_o); | |
originaldestroy() | |
} | |
} | |
} | |
class io_doNothing extends IO { | |
constructor(body) { | |
super(body); | |
this.acceptsFromTop = false; | |
} | |
think() { | |
return { | |
goal: { | |
x: this.body.x, | |
y: this.body.y, | |
}, | |
main: false, | |
alt: false, | |
fire: false, | |
}; | |
} | |
} | |
class io_moveInCircles extends IO { | |
constructor(body) { | |
super(body); | |
this.acceptsFromTop = false; | |
this.timer = ran.irandom(10) + 3; | |
this.goal = { | |
x: this.body.x + 10*Math.cos(-this.body.facing), | |
y: this.body.y + 10*Math.sin(-this.body.facing), | |
}; | |
} | |
think() { | |
if (!(this.timer--)) { | |
this.timer = 10; | |
this.goal = { | |
x: this.body.x + 10*Math.cos(-this.body.facing), | |
y: this.body.y + 10*Math.sin(-this.body.facing), | |
}; | |
} | |
return { goal: this.goal }; | |
} | |
} | |
class io_listenToPlayer extends IO { | |
constructor(b, p) { | |
super(b); | |
this.player = p; | |
this.acceptsFromTop = false; | |
} | |
// THE PLAYER MUST HAVE A VALID COMMAND AND TARGET OBJECT | |
think() { | |
let targ = { | |
x: this.player.target.x, | |
y: this.player.target.y, | |
}; | |
if (this.player.command.autospin) { | |
let kk = Math.atan2(this.body.control.target.y, this.body.control.target.x) + 0.02; | |
targ = { | |
x: 100 * Math.cos(kk), | |
y: 100 * Math.sin(kk), | |
}; | |
} | |
if (this.body.invuln) { | |
if (this.player.command.right || this.player.command.left || this.player.command.up || this.player.command.down || this.player.command.lmb) { | |
this.body.invuln = false; | |
} | |
} | |
this.body.autoOverride = this.player.command.override; | |
return { | |
target: targ, | |
goal: { | |
x: this.body.x + this.player.command.right - this.player.command.left, | |
y: this.body.y + this.player.command.down - this.player.command.up, | |
}, | |
// fire if autofire is on or if the left mouse button is being clicked | |
fire: this.player.command.lmb || this.player.command.autofire, | |
main: this.player.command.lmb || this.player.command.autospin || this.player.command.autofire, | |
alt: this.player.command.rmb, | |
}; | |
} | |
} | |
class io_mapTargetToGoal extends IO { | |
constructor(b) { | |
super(b); | |
} | |
think(input) { | |
if (input.main || input.alt) { | |
return { | |
goal: { | |
x: input.target.x + this.body.x, | |
y: input.target.y + this.body.y, | |
}, | |
power: 1, | |
}; | |
} | |
} | |
} | |
class io_boomerang extends IO { | |
constructor(b) { | |
super(b); | |
this.r = 0; | |
this.b = b; | |
this.m = b.master; | |
this.turnover = false; | |
let len = 10 * util.getDistance({x: 0, y:0}, b.master.control.target); | |
this.myGoal = { | |
x: 3 * b.master.control.target.x + b.master.x, | |
y: 3 * b.master.control.target.y + b.master.y, | |
}; | |
} | |
think(input) { | |
if (this.b.range > this.r) this.r = this.b.range; | |
let t = 1; //1 - Math.sin(2 * Math.PI * this.b.range / this.r) || 1; | |
if (!this.turnover) { | |
if (this.r && this.b.range < this.r * 0.5) { this.turnover = true; } | |
return { | |
goal: this.myGoal, | |
power: t, | |
}; | |
} else { | |
return { | |
goal: { | |
x: this.m.x, | |
y: this.m.y, | |
}, | |
power: t, | |
}; | |
} | |
} | |
} | |
class io_goToMasterTarget extends IO { | |
constructor(body) { | |
super(body); | |
this.myGoal = { | |
x: body.master.control.target.x + body.master.x, | |
y: body.master.control.target.y + body.master.y, | |
}; | |
this.countdown = 5; | |
} | |
think() { | |
if (this.countdown) { | |
if (util.getDistance(this.body, this.myGoal) < 1) { this.countdown--; } | |
return { | |
goal: { | |
x: this.myGoal.x, | |
y: this.myGoal.y, | |
}, | |
}; | |
} | |
} | |
} | |
class io_canRepel extends IO { | |
constructor(b) { | |
super(b); | |
} | |
think(input) { | |
if (input.alt && input.target) { | |
return { | |
target: { | |
x: -input.target.x, | |
y: -input.target.y, | |
}, | |
main: true, | |
}; | |
} | |
} | |
} | |
class io_alwaysFire extends IO { | |
constructor(body) { | |
super(body); | |
} | |
think() { | |
return { | |
fire: true, | |
}; | |
} | |
} | |
class io_targetSelf extends IO { | |
constructor(body) { | |
super(body); | |
} | |
think() { | |
return { | |
main: true, | |
target: { x: 0, y: 0, }, | |
}; | |
} | |
} | |
class io_mapAltToFire extends IO { | |
constructor(body) { | |
super(body); | |
} | |
think(input) { | |
if (input.alt) { | |
return { | |
fire: true, | |
}; | |
} | |
} | |
} | |
class io_onlyAcceptInArc extends IO { | |
constructor(body) { | |
super(body); | |
} | |
think(input) { | |
if (input.target && this.body.firingArc != null) { | |
if (Math.abs(util.angleDifference(Math.atan2(input.target.y, input.target.x), this.body.firingArc[0])) >= this.body.firingArc[1]) { | |
return { | |
fire: false, | |
alt: false, | |
main: false, | |
}; | |
} | |
} | |
} | |
} | |
class io_nearestDifferentMaster extends IO { | |
constructor(body) { | |
super(body); | |
this.targetLock = undefined; | |
this.tick = ran.irandom(30); | |
this.lead = 0; | |
this.validTargets = this.buildList(body.fov); | |
this.oldHealth = body.health.display(); | |
this.randomTarget = null | |
this.secondaryTargetLock = undefined | |
this.escapeDmtrTargt = null | |
} | |
buildList(range) { | |
// Establish whom we judge in reference to | |
let m = { x: this.body.x, y: this.body.y, }, | |
mm = { x: this.body.master.master.x, y: this.body.master.master.y, }, | |
mostDangerous = 0, | |
sqrRange = range * range, | |
keepTarget = false; | |
// Filter through everybody... | |
/*let out = n_nearest( | |
this.body, | |
(e, sqrdst) => { | |
if (Math.abs(e.x - m.x) < range && Math.abs(e.y - m.y) < range) { | |
if (e.health.amount > 0) { | |
if (!e.invuln) { | |
if (e.master.master.team !== this.body.master.master.team) { | |
if (e.master.master.team !== ROID_TEAM) { | |
if (e.type === 'tank' || e.type === 'crasher' || (!this.body.aiSettings.shapefriend && e.type === 'food')) { | |
if (!this.body.aiSettings.blind || (Math.abs(e.x - mm.x) < range && Math.abs(e.y - mm.y) < range)) | |
return sqrdst < range}}}}}}} | |
);*/ | |
//let lookFrom = (this.body.skill.level >= 45 && this.body.aiSettings.IGNORE_FOOD_AT_LEVEL45) ? entitiesNoFood :entities | |
let out = entities.filter(e => | |
// Only look at those within our view, and our parent's view, not dead, not our kind, not a bullet/trap/block etc | |
(Math.abs(e.x - m.x) < range && Math.abs(e.y - m.y) < range) && | |
(e.master.master.team !== this.body.master.master.team) && | |
(!e.invuln) && | |
(e.master.master.team !== ROID_TEAM) && | |
(e.type === 'tank' || e.type === 'crasher' || e.label === 'Dominator' || (!this.body.aiSettings.shapefriend && e.type === 'food')) && | |
(e.label !== 'Observer') && | |
(!this.body.aiSettings.blind || (Math.abs(e.x - mm.x) < range && Math.abs(e.y - mm.y) < range))); | |
out = out.map((e) => { | |
// Only look at those within range and arc (more expensive, so we only do it on the few) | |
let yaboi = false; | |
if (Math.pow(this.body.x - e.x, 2) + Math.pow(this.body.y - e.y, 2) < sqrRange) { | |
if (this.body.firingArc == null || this.body.aiSettings.view360) { | |
yaboi = true; | |
} else if (Math.abs(util.angleDifference(util.getDirection(this.body, e), this.body.firingArc[0])) < this.body.firingArc[1]) | |
yaboi = true; | |
} | |
if (yaboi) { | |
mostDangerous = Math.max(e.dangerValue, mostDangerous); | |
return e; | |
} | |
}).filter((e) => { | |
// Only return the highest tier of danger | |
if (e != null) { | |
if(e.label !== 'Dominator' || this.body.skill.level > 30) | |
if (this.body.aiSettings.farm || e.dangerValue === mostDangerous) { | |
if (this.targetLock) { if (e.id === this.targetLock.id) keepTarget = true; } | |
return e; | |
}} | |
}); | |
// Reset target if it's not in there | |
if (!keepTarget) this.targetLock = undefined; | |
return out; | |
} | |
think(input) { | |
// Override target lock upon other commands | |
if (input.main || input.alt || this.body.master.autoOverride) { | |
this.targetLock = undefined; return {}; | |
} | |
// Otherwise, consider how fast we can either move to ram it or shoot at a potiential target. | |
let tracking = this.body.topSpeed, | |
range = this.body.fov; | |
// Use whether we have functional guns to decide | |
for (let i=0; i<this.body.guns.length; i++) { | |
if (this.body.guns[i].canShoot && !this.body.aiSettings.skynet) { | |
let v = this.body.guns[i].getTracking(); | |
tracking = v.speed; | |
range = Math.min(range, v.speed * v.range); | |
break; | |
} | |
} | |
// Check if my target's alive | |
if (this.targetLock) { if (this.targetLock.health.amount <= 0) { | |
this.targetLock = undefined; | |
this.tick = 100; | |
} } | |
// Think damn hard | |
if (this.tick++ > 15 * roomSpeed) { | |
this.tick = 0; | |
this.validTargets = this.buildList(range); | |
// Ditch our old target if it's invalid | |
if (this.secondaryTargetLock && !room.isInRoomType({x: this.body.x, y: this.body.y}, 'dmtr') && c.FOCUS_ON_DOMINATOR) { | |
// don't follow the target outside the dominator cell if we were originally locked onto a dominator | |
this.targetLock = this.secondaryTargetLock | |
} | |
if (this.validTargets.indexOf(this.targetLock) === -1) { | |
if (room.isInRoomType({x: this.body.x, y: this.body.y}, 'dmtr')) { | |
this.secondaryTargetLock = this.targetLock | |
} | |
this.targetLock = undefined; | |
} | |
// Lock new target if we still don't have one. | |
if (this.targetLock == null && this.validTargets.length) { | |
this.targetLock = (this.validTargets.length === 1) ? this.validTargets[0] : nearest(this.validTargets, { x: this.body.x, y: this.body.y }); | |
this.tick = -90; | |
} | |
} | |
// Lock onto whoever's shooting me. | |
// let damageRef = (this.body.bond == null) ? this.body : this.body.bond; | |
// if (damageRef.collisionArray.length && damageRef.health.display() < this.oldHealth) { | |
// this.oldHealth = damageRef.health.display(); | |
// if (this.validTargets.indexOf(damageRef.collisionArray[0]) === -1) { | |
// this.targetLock = (damageRef.collisionArray[0].master.id === -1) ? damageRef.collisionArray[0].source : damageRef.collisionArray[0].master; | |
// } | |
// } | |
// Consider how fast it's moving and shoot at it | |
if (this.targetLock != null) { | |
let radial = this.targetLock.velocity; | |
let diff = { | |
x: this.targetLock.x - this.body.x, | |
y: this.targetLock.y - this.body.y, | |
}; | |
/// Refresh lead time | |
if (this.tick % 4 === 0) { | |
this.lead = 0; | |
// Find lead time (or don't) | |
if (!this.body.aiSettings.chase) { | |
let toi = timeOfImpact(diff, radial, tracking); | |
this.lead = toi; | |
} | |
} | |
this.randomTarget = null | |
// And return our aim | |
return { | |
target: { | |
x: diff.x + this.lead * radial.x, | |
y: diff.y + this.lead * radial.y, | |
}, | |
fire: true, | |
main: true, | |
}; | |
} | |
if (this.body.aiSettings.RANDOM_WHEN_NO_TARGET || this.body.aiSettings.DOMINATOR_WHEN_NO_TARGET) { | |
if(this.randomTarget === null || util.getDistance(this.randomTarget, {x:this.body.x, y:this.body.y}) < 300) { | |
if(this.body.aiSettings.RANDOM_WHEN_NO_TARGET) { | |
this.randomTarget = room.randomType('norm') | |
} else if (this.body.aiSettings.DOMINATOR_WHEN_NO_TARGET){ | |
if(this.body.skill.level < 30) { | |
this.randomTarget = room.randomType('norm') | |
} else { | |
this.randomTarget = room.randomType('dmtr') | |
} | |
} | |
} | |
if(room.isInRoomType({x: this.body.x, y: this.body.y}, 'dmtr') && | |
!room.isInRoomType(this.randomTarget, 'dmtr') && this.body.skill.level < 30) { | |
// get out of a dominator if we're not trying to go to a dominator | |
if(!this.escapeDmtrTarget ) { | |
// the dumbest possible algorithm to escape a location | |
// better would be to go in the direction opposite to the current (randomTarget) direction for X seconds | |
this.escapeDmtrTarget = ran.choose([ | |
{x: 0, y: 0}, | |
{x: 0, y: room.height}, | |
{x: room.width, y: 0}, | |
{x: room.width, y: room.height} | |
]) | |
} | |
return {goal:this.escapeDmtrTarget, power:1} | |
} | |
return { | |
goal: this.randomTarget, | |
power: 1, | |
} | |
} else { | |
return {}; | |
} | |
} | |
} | |
class io_avoid extends IO { | |
constructor(body) { | |
super(body); | |
} | |
think(input) { | |
let masterId = this.body.master.id; | |
let range = this.body.size * this.body.size * 100 ; | |
// does moving the variables inside the loop setup hurt performance? | |
let minD = range | |
let locationX = this.body.x | |
let locationY = this.body.y | |
// try moving "swarm" before "drone" | |
for (let i = 0; i < entitiesNoFood.length; i++) { | |
const e = entitiesNoFood[i] | |
const d = Math.pow(e.x - locationX, 2) + Math.pow(e.y - locationY, 2); | |
if ((d < minD) && | |
(e.master.id !== masterId) && | |
(e.type === 'bullet' || e.type === 'swarm' || e.type === 'drone' || e.type === 'trap' || e.type === 'block')) { | |
minD = d | |
this.avoid = e | |
} | |
} | |
// Aim at that target- | |
if (this.avoid != null) { | |
// Consider how fast it's moving. | |
let delt = new Vector(this.body.velocity.x - this.avoid.velocity.x, this.body.velocity.y - this.avoid.velocity.y); | |
let diff = new Vector(this.avoid.x - this.body.x, this.avoid.y - this.body.y); | |
let comp = (delt.x * diff. x + delt.y * diff.y) / delt.length / diff.length; | |
let goal = {}; | |
if (comp > 0) { | |
if (input.goal) { | |
let goalDist = Math.sqrt(range / (input.goal.x * input.goal.x + input.goal.y * input.goal.y)); | |
goal = { | |
x: input.goal.x * goalDist - diff.x * comp, | |
y: input.goal.y * goalDist - diff.y * comp, | |
}; | |
} else { | |
goal = { | |
x: -diff.x * comp, | |
y: -diff.y * comp, | |
}; | |
} | |
return {goal}; | |
} | |
} | |
} | |
} | |
class io_minion extends IO { | |
constructor(body) { | |
super(body); | |
this.turnwise = 1; | |
} | |
think(input) { | |
if (this.body.aiSettings.reverseDirection && ran.chance(0.005)) { this.turnwise = -1 * this.turnwise; } | |
if (input.target != null && (input.alt || input.main)) { | |
let sizeFactor = Math.sqrt(this.body.master.size / this.body.master.SIZE); | |
let leash = 60 * sizeFactor; | |
let orbit = 120 * sizeFactor; | |
let repel = 135 * sizeFactor; | |
let goal; | |
let power = 1; | |
let target = new Vector(input.target.x, input.target.y); | |
if (input.alt) { | |
// Leash | |
if (target.length < leash) { | |
goal = { | |
x: this.body.x + target.x, | |
y: this.body.y + target.y, | |
}; | |
// Spiral repel | |
} else if (target.length < repel) { | |
let dir = -this.turnwise * target.direction + Math.PI / 5; | |
goal = { | |
x: this.body.x + Math.cos(dir), | |
y: this.body.y + Math.sin(dir), | |
}; | |
// Free repel | |
} else { | |
goal = { | |
x: this.body.x - target.x, | |
y: this.body.y - target.y, | |
}; | |
} | |
} else if (input.main) { | |
// Orbit point | |
let dir = this.turnwise * target.direction + 0.01; | |
goal = { | |
x: this.body.x + target.x - orbit * Math.cos(dir), | |
y: this.body.y + target.y - orbit * Math.sin(dir), | |
}; | |
if (Math.abs(target.length - orbit) < this.body.size * 2) { | |
power = 0.7; | |
} | |
} | |
return { | |
goal: goal, | |
power: power, | |
}; | |
} | |
} | |
} | |
class io_minionOrbitFarther extends IO { | |
constructor(body) { | |
super(body); | |
this.turnwise = 1; | |
} | |
think(input) { | |
if (this.body.aiSettings.reverseDirection && ran.chance(0.005)) { this.turnwise = -1 * this.turnwise; } | |
if (input.target != null && (input.alt || input.main)) { | |
let sizeFactor = Math.sqrt(this.body.master.size / this.body.master.SIZE); | |
let leash = 60 * sizeFactor; | |
let orbit = 250 * sizeFactor; | |
let repel = 135 * sizeFactor; | |
let goal; | |
let power = 1; | |
let target = new Vector(input.target.x, input.target.y); | |
if (input.alt) { | |
// Leash | |
if (target.length < leash) { | |
goal = { | |
x: this.body.x + target.x, | |
y: this.body.y + target.y, | |
}; | |
// Spiral repel | |
} else if (target.length < repel) { | |
let dir = -this.turnwise * target.direction + Math.PI / 5; | |
goal = { | |
x: this.body.x + Math.cos(dir), | |
y: this.body.y + Math.sin(dir), | |
}; | |
// Free repel | |
} else { | |
goal = { | |
x: this.body.x - target.x, | |
y: this.body.y - target.y, | |
}; | |
} | |
} else if (input.main) { | |
// Orbit point | |
let dir = this.turnwise * target.direction + 0.01; | |
goal = { | |
x: this.body.x + target.x - orbit * Math.cos(dir), | |
y: this.body.y + target.y - orbit * Math.sin(dir), | |
}; | |
if (Math.abs(target.length - orbit) < this.body.size * 2) { | |
power = 0.7; | |
} | |
} | |
return { | |
goal: goal, | |
power: power, | |
}; | |
} | |
} | |
} | |
class io_lanceRetract extends IO { | |
think(input) { | |
if (input.alt) { | |
input.health.amount = 0 | |
} | |
} | |
} | |
class io_goToLanceOwner extends IO { | |
think(input) { | |
const LANCE_RANGE = 30 | |
let output = {} | |
if (input.alt) { | |
let out = entities.filter(e => | |
(e.type === 'lance') && // is a lance | |
(e.health.amount > 0) && // alive | |
// since this is operating on the tank itself, does it need .body.master.master? | |
(e.master.master.team === this.body.master.team) && // same team | |
(e.master.master.team === ROID_TEAM) && // not a rock | |
(Math.abs(e.x - input.x) < LANCE_RANGE && Math.abs(e.y - input.y) < LANCE_RANGE) // close enough to the lance | |
) | |
if (out.length > 0) { | |
// there is a target lance | |
const lance = out[0] | |
// move to lance owner | |
output.target = {x: lance.master.master.x, y: lance.master.master.y} | |
output.main = true | |
} | |
} | |
return output | |
} | |
} | |
class io_hangOutNearMaster extends IO { | |
constructor(body) { | |
super(body); | |
this.acceptsFromTop = false; | |
this.orbit = 30; | |
this.currentGoal = { x: this.body.source.x, y: this.body.source.y, }; | |
this.timer = 0; | |
} | |
think(input) { | |
if (this.body.source != this.body) { | |
let bound1 = this.orbit * 0.8 + this.body.source.size + this.body.size; | |
let bound2 = this.orbit * 1.5 + this.body.source.size + this.body.size; | |
let dist = util.getDistance(this.body, this.body.source) + Math.PI / 8; | |
let output = { | |
target: { | |
x: this.body.velocity.x, | |
y: this.body.velocity.y, | |
}, | |
goal: this.currentGoal, | |
power: undefined, | |
}; | |
// Set a goal | |
if (dist > bound2 || this.timer > 30) { | |
this.timer = 0; | |
let dir = util.getDirection(this.body, this.body.source) + Math.PI * ran.random(0.5); | |
let len = ran.randomRange(bound1, bound2); | |
let x = this.body.source.x - len * Math.cos(dir); | |
let y = this.body.source.y - len * Math.sin(dir); | |
this.currentGoal = { | |
x: x, | |
y: y, | |
}; | |
} | |
if (dist < bound2) { | |
output.power = 0.15; | |
if (ran.chance(0.3)) { this.timer++; } | |
} | |
return output; | |
} | |
} | |
} | |
class io_spin extends IO { | |
constructor(b) { | |
super(b); | |
this.a = 0; | |
} | |
think(input) { | |
this.a += 0.05; | |
let offset = 0; | |
if (this.body.bond != null) { | |
offset = this.body.bound.angle; | |
} | |
return { | |
target: { | |
x: Math.cos(this.a + offset), | |
y: Math.sin(this.a + offset), | |
}, | |
main: true, | |
}; | |
} | |
} | |
class io_fastspin extends IO { | |
constructor(b) { | |
super(b); | |
this.a = 0; | |
} | |
think(input) { | |
this.a += 0.072; | |
let offset = 0; | |
if (this.body.bond != null) { | |
offset = this.body.bound.angle; | |
} | |
return { | |
target: { | |
x: Math.cos(this.a + offset), | |
y: Math.sin(this.a + offset), | |
}, | |
main: true, | |
}; | |
} | |
} | |
class io_reversespin extends IO { | |
constructor(b) { | |
super(b); | |
this.a = 0; | |
} | |
think(input) { | |
this.a -= 0.05; | |
let offset = 0; | |
if (this.body.bond != null) { | |
offset = this.body.bound.angle; | |
} | |
return { | |
target: { | |
x: Math.cos(this.a + offset), | |
y: Math.sin(this.a + offset), | |
}, | |
main: true, | |
}; | |
} | |
} | |
class io_dontTurn extends IO { | |
constructor(b) { | |
super(b); | |
} | |
think(input) { | |
return { | |
target: { | |
x: 1, | |
y: 0, | |
}, | |
main: true, | |
}; | |
} | |
} | |
class io_fleeAtLowHealth extends IO { | |
constructor(b) { | |
super(b); | |
this.fear = util.clamp(ran.gauss(0.7, 0.15), 0.1, 0.9); | |
} | |
think(input) { | |
if (input.fire && input.target != null && this.body.health.amount < this.body.health.max * this.fear) { | |
return { | |
goal: { | |
x: this.body.x - input.target.x, | |
y: this.body.y - input.target.y, | |
}, | |
}; | |
} | |
} | |
} | |
/***** ENTITIES *****/ | |
// Define skills | |
const skcnv = { | |
rld: 0, | |
pen: 1, | |
str: 2, | |
dam: 3, | |
spd: 4, | |
shi: 5, | |
atk: 6, | |
hlt: 7, | |
rgn: 8, | |
mob: 9, | |
}; | |
const levelers = [ | |
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, | |
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, | |
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, | |
31, 32, 33, 34, 35, 36, 38, 40, 42, 44, | |
]; | |
class Skill { | |
constructor(inital = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) { // Just skill stuff. | |
this.raw = inital; | |
this.caps = []; | |
this.setCaps([ | |
c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, | |
c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL, c.MAX_SKILL | |
]); | |
this.name = [ | |
'Reload', | |
'Bullet Penetration', | |
'Bullet Health', | |
'Bullet Damage', | |
'Bullet Speed', | |
'Shield Capacity', | |
'Body Damage', | |
'Max Health', | |
'Shield Regeneration', | |
'Movement Speed', | |
]; | |
this.atk = 0; | |
this.hlt = 0; | |
this.spd = 0; | |
this.str = 0; | |
this.pen = 0; | |
this.dam = 0; | |
this.rld = 0; | |
this.mob = 0; | |
this.rgn = 0; | |
this.shi = 0; | |
this.rst = 0; | |
this.brst = 0; | |
this.ghost = 0; | |
this.acl = 0; | |
this.reset(); | |
} | |
reset() { | |
this.points = 0; | |
this.score = 0; | |
this.truescore = 0; | |
this.deduction = 0; | |
this.level = 0; | |
this.canUpgrade = false; | |
this.update(); | |
this.maintain(); | |
} | |
update() { | |
let curve = (() => { | |
function make(x) { return Math.log(4*x + 1) / Math.log(5); } | |
let a = []; | |
for (let i=0; i<c.MAX_SKILL*2; i++) { a.push(make(i/c.MAX_SKILL)); } | |
// The actual lookup function | |
return x => { return a[x * c.MAX_SKILL]; }; | |
})(); | |
function apply(f, x) { return (x<0) ? 1 / (1 - x * f) : f * x + 1; } | |
for (let i=0; i<10; i++) { | |
if (this.raw[i] > this.caps[i]) { | |
this.points += this.raw[i] - this.caps[i]; | |
this.points = Math.min(33, this.points) | |
this.raw[i] = this.caps[i]; | |
} | |
} | |
let attrib = []; | |
for (let i=0; i<5; i++) { for (let j=0; j<2; j+=1) { | |
attrib[i + 5*j] = curve( | |
( | |
this.raw[i + 5 * j] + | |
this.bleed(i, j) | |
) / c.MAX_SKILL); | |
} } | |
this.rld = Math.pow(0.5, attrib[skcnv.rld]); | |
this.pen = apply(2.5, attrib[skcnv.pen]); | |
this.str = apply(2, attrib[skcnv.str]); | |
this.dam = apply(3, attrib[skcnv.dam]); | |
this.spd = 0.5 + apply(1.5, attrib[skcnv.spd]); | |
this.acl = apply(0.5, attrib[skcnv.rld]); | |
this.rst = 0.5 * attrib[skcnv.str] + 2.5 * attrib[skcnv.pen]; | |
this.ghost = attrib[skcnv.pen]; | |
this.shi = c.GLASS_HEALTH_FACTOR * apply(3 / c.GLASS_HEALTH_FACTOR - 1, attrib[skcnv.shi]); | |
this.atk = apply(1, attrib[skcnv.atk]); | |
this.hlt = c.GLASS_HEALTH_FACTOR * apply(2 / c.GLASS_HEALTH_FACTOR - 1, attrib[skcnv.hlt]); | |
this.mob = apply(0.8, attrib[skcnv.mob]); | |
this.rgn = apply(25, attrib[skcnv.rgn]); | |
this.brst = 0.3 * (0.5 * attrib[skcnv.atk] + 0.5 * attrib[skcnv.hlt] + attrib[skcnv.rgn]); | |
} | |
set(thing) { | |
this.raw[0] = thing[0]; | |
this.raw[1] = thing[1]; | |
this.raw[2] = thing[2]; | |
this.raw[3] = thing[3]; | |
this.raw[4] = thing[4]; | |
this.raw[5] = thing[5]; | |
this.raw[6] = thing[6]; | |
this.raw[7] = thing[7]; | |
this.raw[8] = thing[8]; | |
this.raw[9] = thing[9]; | |
this.update(); | |
} | |
setCaps(thing) { | |
this.caps[0] = thing[0]; | |
this.caps[1] = thing[1]; | |
this.caps[2] = thing[2]; | |
this.caps[3] = thing[3]; | |
this.caps[4] = thing[4]; | |
this.caps[5] = thing[5]; | |
this.caps[6] = thing[6]; | |
this.caps[7] = thing[7]; | |
this.caps[8] = thing[8]; | |
this.caps[9] = thing[9]; | |
this.update(); | |
} | |
maintain() { | |
if (this.level < c.SKILL_CAP) { | |
if (this.score - this.deduction >= this.levelScore) { | |
this.deduction += this.levelScore; | |
this.level += 1; | |
this.points += this.levelPoints; | |
if (this.level === c.TIER_1 || this.level === c.TIER_2 || this.level === c.TIER_3) { | |
this.canUpgrade = true; | |
} | |
this.update(); | |
return true; | |
} | |
} | |
return false; | |
} | |
get levelScore() { | |
return Math.ceil(1.8 * Math.pow(this.level + 1, 1.8) - 2 * this.level + 1); | |
} | |
get progress() { | |
return (this.levelScore) ? (this.score - this.deduction) / this.levelScore : 0; | |
} | |
get levelPoints() { | |
if (levelers.findIndex(e => { return e === this.level; }) != -1) { return 1; } return 0; | |
} | |
cap(skill, real = false) { | |
if (!real && this.level < c.SKILL_SOFT_CAP) { | |
return Math.round(this.caps[skcnv[skill]] * c.SOFT_MAX_SKILL); | |
} | |
return this.caps[skcnv[skill]]; | |
} | |
bleed(i, j) { | |
let a = ((i + 2) % 5) + 5*j, | |
b = ((i + ((j === 1) ? 1 : 4)) % 5) + 5 * j; | |
let value = 0; | |
let denom = Math.max(c.MAX_SKILL, this.caps[i + 5*j]); | |
value += (1 - Math.pow(this.raw[a] / denom - 1, 2)) * this.raw[a] * c.SKILL_LEAK; | |
value -= Math.pow(this.raw[b] / denom, 2) * this.raw[b] * c.SKILL_LEAK ; | |
return value; | |
} | |
upgrade(stat) { | |
if (this.points && this.amount(stat) < this.cap(stat)) { | |
this.change(stat, 1); | |
this.points -= 1; | |
return true; | |
} | |
return false; | |
} | |
title(stat) { | |
return this.name[skcnv[stat]]; | |
} | |
/* | |
let i = skcnv[skill] % 5, | |
j = (skcnv[skill] - i) / 5; | |
let roundvalue = Math.round(this.bleed(i, j) * 10); | |
let string = ''; | |
if (roundvalue > 0) { string += '+' + roundvalue + '%'; } | |
if (roundvalue < 0) { string += '-' + roundvalue + '%'; } | |
return string; | |
*/ | |
amount(skill) { | |
return this.raw[skcnv[skill]]; | |
} | |
change(skill, levels) { | |
this.raw[skcnv[skill]] += levels; | |
this.update(); | |
} | |
} | |
const lazyRealSizes = (() => { | |
let o = [1, 1, 1]; | |
for (var i=3; i<16; i++) { | |
// We say that the real size of a 0-gon, 1-gon, 2-gon is one, then push the real sizes of triangles, squares, etc... | |
o.push( | |
Math.sqrt((2 * Math.PI / i) * (1 / Math.sin(2 * Math.PI / i))) | |
); | |
} | |
return o; | |
})(); | |
// Define how guns work | |
class Gun { | |
constructor(body, info) { | |
this.lastShot = { | |
time: 0, | |
power: 0, | |
}; | |
this.body = body; | |
this.master = body.source; | |
this.label = ''; | |
this.controllers = []; | |
this.children = []; | |
this.control = { | |
target: new Vector(0, 0), | |
goal: new Vector(0, 0), | |
main: false, | |
alt: false, | |
fire: false, | |
}; | |
this.canShoot = false; | |
if (info.PROPERTIES != null && info.PROPERTIES.TYPE != null) { | |
this.canShoot = true; | |
this.label = (info.PROPERTIES.LABEL == null) ? | |
'' : info.PROPERTIES.LABEL; | |
if (Array.isArray(info.PROPERTIES.TYPE)) { // This is to be nicer about our definitions | |
this.bulletTypes = info.PROPERTIES.TYPE; | |
this.natural = info.PROPERTIES.TYPE.BODY; | |
} else { | |
this.bulletTypes = [info.PROPERTIES.TYPE]; | |
} | |
// Pre-load bullet definitions so we don't have to recalculate them every shot | |
let natural = {}; | |
this.bulletTypes.forEach(function setNatural(type) { | |
if (type.PARENT != null) { // Make sure we load from the parents first | |
for (let i=0; i<type.PARENT.length; i++) { | |
setNatural(type.PARENT[i]); | |
} | |
} | |
if (type.BODY != null) { // Get values if they exist | |
for (let index in type.BODY) { | |
natural[index] = type.BODY[index]; | |
} | |
} | |
}); | |
this.natural = natural; // Save it | |
if (info.PROPERTIES.GUN_CONTROLLERS != null) { | |
let toAdd = []; | |
let self = this; | |
info.PROPERTIES.GUN_CONTROLLERS.forEach(function(ioName) { | |
toAdd.push(eval('new ' + ioName + '(self)')); | |
}); | |
this.controllers = toAdd.concat(this.controllers); | |
} | |
this.autofire = (info.PROPERTIES.AUTOFIRE == null) ? | |
false : info.PROPERTIES.AUTOFIRE; | |
this.altFire = (info.PROPERTIES.ALT_FIRE == null) ? | |
false : info.PROPERTIES.ALT_FIRE; | |
this.settings = (info.PROPERTIES.SHOOT_SETTINGS == null) ? | |
[] : info.PROPERTIES.SHOOT_SETTINGS; | |
this.calculator = (info.PROPERTIES.STAT_CALCULATOR == null) ? | |
'default' : info.PROPERTIES.STAT_CALCULATOR; | |
this.waitToCycle = (info.PROPERTIES.WAIT_TO_CYCLE == null) ? | |
false : info.PROPERTIES.WAIT_TO_CYCLE; | |
this.bulletStats = (info.PROPERTIES.BULLET_STATS == null || info.PROPERTIES.BULLET_STATS === 'master') ? | |
'master' : new Skill(info.PROPERTIES.BULLET_STATS); | |
this.settings = (info.PROPERTIES.SHOOT_SETTINGS == null) ? | |
[] : info.PROPERTIES.SHOOT_SETTINGS; | |
this.countsOwnKids = (info.PROPERTIES.MAX_CHILDREN == null) ? | |
false : info.PROPERTIES.MAX_CHILDREN; | |
this.syncsSkills = (info.PROPERTIES.SYNCS_SKILLS == null) ? | |
false : info.PROPERTIES.SYNCS_SKILLS; | |
this.negRecoil = (info.PROPERTIES.NEGATIVE_RECOIL == null) ? | |
false : info.PROPERTIES.NEGATIVE_RECOIL; | |
} | |
let position = info.POSITION; | |
this.length = position[0] / 10; | |
this.width = position[1] / 10; | |
this.aspect = position[2]; | |
let _off = new Vector(position[3], position[4]); | |
this.angle = position[5] * Math.PI / 180; | |
this.direction = _off.direction; | |
this.offset = _off.length / 10; | |
this.delay = position[6]; | |
this.position = 0; | |
this.motion = 0; | |
if (this.canShoot) { | |
this.cycle = !this.waitToCycle - this.delay; | |
this.trueRecoil = this.settings.recoil; | |
} | |
} | |
recoil() { | |
if (this.motion || this.position) { | |
// Simulate recoil | |
this.motion -= 0.25 * this.position / roomSpeed; | |
this.position += this.motion; | |
if (this.position < 0) { // Bouncing off the back | |
this.position = 0; | |
this.motion = -this.motion; | |
} | |
if (this.motion > 0) { | |
this.motion *= 0.75; | |
} | |
} | |
if (this.canShoot && !this.body.settings.hasNoRecoil) { | |
// Apply recoil to motion | |
if (this.motion > 0) { | |
let recoilForce = -this.position * this.trueRecoil * 0.045 / roomSpeed; | |
this.body.accel.x += recoilForce * Math.cos(this.body.facing + this.angle); | |
this.body.accel.y += recoilForce * Math.sin(this.body.facing + this.angle); | |
} | |
} | |
} | |
getSkillRaw() { | |
if (this.bulletStats === 'master') { | |
return [ | |
this.body.skill.raw[0], | |
this.body.skill.raw[1], | |
this.body.skill.raw[2], | |
this.body.skill.raw[3], | |
this.body.skill.raw[4], | |
0, 0, 0, 0, 0, | |
]; | |
} | |
return this.bulletStats.raw; | |
} | |
getLastShot() { | |
return this.lastShot; | |
} | |
live() { | |
// Do | |
this.recoil(); | |
// Dummies ignore this | |
if (this.canShoot) { | |
// Find the proper skillset for shooting | |
let sk = (this.bulletStats === 'master') ? this.body.skill : this.bulletStats; | |
// Decides what to do based on child-counting settings | |
let shootPermission = (this.countsOwnKids) ? | |
this.countsOwnKids > this.children.length * ((this.calculator === 'necro') ? sk.rld : 1) | |
: (this.body.maxChildren) ? | |
this.body.maxChildren > this.body.children.length * ((this.calculator === 'necro') ? sk.rld : 1) | |
: true; | |
// Override in invuln | |
if (this.body.master.invuln) { | |
shootPermission = false; | |
} | |
// Cycle up if we should | |
if (shootPermission || !this.waitToCycle) { | |
if (this.cycle < 1) { | |
this.cycle += 1 / this.settings.reload / roomSpeed / ((this.calculator === 'necro' || this.calculator === 'fixed reload') ? 1 : sk.rld); | |
} | |
} | |
// Firing routines | |
if (shootPermission && (this.autofire || ((this.altFire) ? this.body.control.alt : this.body.control.fire))) { | |
if (this.cycle >= 1) { | |
// Find the end of the gun barrel | |
let gx = | |
this.offset * Math.cos(this.direction + this.angle + this.body.facing) + | |
(1.5 * this.length - this.width * this.settings.size / 2) * Math.cos(this.angle + this.body.facing); | |
let gy = | |
this.offset * Math.sin(this.direction + this.angle + this.body.facing) + | |
(1.5 * this.length - this.width * this.settings.size / 2) * Math.sin(this.angle + this.body.facing); | |
// Shoot, multiple times in a tick if needed | |
while (shootPermission && this.cycle >= 1) { | |
this.fire(gx, gy, sk); | |
// Figure out if we may still shoot | |
shootPermission = (this.countsOwnKids) ? | |
this.countsOwnKids > this.children.length | |
: (this.body.maxChildren) ? | |
this.body.maxChildren > this.body.children.length | |
: true; | |
// Cycle down | |
this.cycle -= 1; | |
} | |
} // If we're not shooting, only cycle up to where we'll have the proper firing delay | |
} else if (this.cycle > !this.waitToCycle - this.delay) { | |
this.cycle = !this.waitToCycle - this.delay; | |
} | |
} | |
} | |
syncChildren() { | |
if (this.syncsSkills) { | |
let self = this; | |
this.children.forEach(function(o) { | |
o.define({ | |
BODY: self.interpret(), | |
SKILL: self.getSkillRaw(), | |
}); | |
o.refreshBodyAttributes(); | |
}); | |
} | |
} | |
fire(gx, gy, sk) { | |
// Recoil | |
this.lastShot.time = util.time(); | |
this.lastShot.power = 3 * Math.log(Math.sqrt(sk.spd) + this.trueRecoil + 1) + 1; | |
this.motion += this.lastShot.power; | |
// Find inaccuracy | |
let ss, sd; | |
do { | |
ss = ran.gauss(0, Math.sqrt(this.settings.shudder)); | |
} while (Math.abs(ss) >= this.settings.shudder * 2); | |
do { | |
sd = ran.gauss(0, this.settings.spray * this.settings.shudder); | |
} while (Math.abs(sd) >= this.settings.spray / 2); | |
sd *= Math.PI / 180; | |
// Find speed | |
let s = new Vector( | |
((this.negRecoil) ? -1 : 1) * this.settings.speed * c.runSpeed * sk.spd * (1 + ss) * Math.cos(this.angle + this.body.facing + sd), | |
((this.negRecoil) ? -1 : 1) * this.settings.speed * c.runSpeed * sk.spd * (1 + ss) * Math.sin(this.angle + this.body.facing + sd) | |
); | |
// Boost it if we should | |
if (this.body.velocity.length) { | |
let extraBoost = | |
Math.max(0, s.x * this.body.velocity.x + s.y * this.body.velocity.y) / this.body.velocity.length / s.length; | |
if (extraBoost) { | |
let len = s.length; | |
s.x += this.body.velocity.length * extraBoost * s.x / len; | |
s.y += this.body.velocity.length * extraBoost * s.y / len; | |
} | |
} | |
// Create the bullet | |
var o = new Entity({ | |
x: this.body.x + this.body.size * gx - s.x, | |
y: this.body.y + this.body.size * gy - s.y, | |
}, this.master.master); | |
/*let jumpAhead = this.cycle - 1; | |
if (jumpAhead) { | |
o.x += s.x * this.cycle / jumpAhead; | |
o.y += s.y * this.cycle / jumpAhead; | |
}*/ | |
o.velocity = s; | |
this.bulletInit(o); | |
o.coreSize = o.SIZE; | |
} | |
bulletInit(o) { | |
// Define it by its natural properties | |
this.bulletTypes.forEach(type => o.define(type)); | |
// Pass the gun attributes | |
o.define({ | |
BODY: this.interpret(), | |
SKILL: this.getSkillRaw(), | |
SIZE: this.body.size * this.width * this.settings.size / 2 , | |
LABEL: this.master.label + ((this.label) ? ' ' + this.label : '') + ' ' + o.label, | |
}); | |
o.color = this.body.master.color; | |
// Keep track of it and give it the function it needs to deutil.log itself upon death | |
if (this.countsOwnKids) { | |
o.parent = this; | |
this.children.push(o); | |
} else if (this.body.maxChildren) { | |
o.parent = this.body; | |
this.body.children.push(o); | |
this.children.push(o); | |
} | |
o.source = this.body; | |
o.facing = o.velocity.direction; | |
// Necromancers. | |
if (this.calculator === 7) { | |
let oo = o; | |
o.necro = host => { | |
let shootPermission = this.countsOwnKids ? this.countsOwnKids > this.children.length * | |
(this.bulletStats === 'master' ? this.body.skill.rld : this.bulletStats.rld) : | |
this.body.maxChildren ? this.body.maxChildren > this.body.children.length * | |
(this.bulletStats === 'master' ? this.body.skill.rld : this.bulletStats.rld) : true; | |
if (shootPermission) { | |
let save = { | |
facing: host.facing, | |
size: host.SIZE, | |
}; | |
host.define(Class.genericEntity); | |
this.bulletInit(host); | |
host.team = oo.master.master.team; | |
host.master = oo.master; | |
host.color = oo.color; | |
host.facing = save.facing; | |
host.SIZE = save.size; | |
host.health.amount = host.health.max; | |
return true; | |
} | |
return false; | |
}; | |
} | |
// Otherwise | |
o.refreshBodyAttributes(); | |
o.life(); | |
} | |
getTracking() { | |
return { | |
speed: c.runSpeed * ((this.bulletStats === 'master') ? this.body.skill.spd : this.bulletStats.spd) * | |
this.settings.maxSpeed * | |
this.natural.SPEED, | |
range: Math.sqrt((this.bulletStats === 'master') ? this.body.skill.spd : this.bulletStats.spd) * | |
this.settings.range * | |
this.natural.RANGE, | |
}; | |
} | |
interpret() { | |
let sizeFactor = this.master.size/this.master.SIZE; | |
let shoot = this.settings; | |
let sk = (this.bulletStats === 'master') ? this.body.skill : this.bulletStats; | |
// Defaults | |
let out = { | |
SPEED: shoot.maxSpeed * sk.spd, | |
HEALTH: shoot.health * sk.str, | |
RESIST: shoot.resist + sk.rst, | |
DAMAGE: shoot.damage * sk.dam, | |
PENETRATION: Math.max(1, shoot.pen * sk.pen), | |
RANGE: shoot.range / Math.sqrt(sk.spd), | |
DENSITY: shoot.density * sk.pen * sk.pen / sizeFactor, | |
PUSHABILITY: 1 / sk.pen, | |
HETERO: 3 - 2.8 * sk.ghost, | |
}; | |
// Special cases | |
switch (this.calculator) { | |
case 'thruster': | |
this.trueRecoil = this.settings.recoil * Math.sqrt(sk.rld * sk.spd); | |
break; | |
case 'sustained': | |
out.RANGE = shoot.range; | |
break; | |
case 'swarm': | |
out.PENETRATION = Math.max(1, shoot.pen * (0.5 * (sk.pen - 1) + 1)); | |
out.HEALTH /= shoot.pen * sk.pen; | |
break; | |
case 'trap': | |
case 'block': | |
out.PUSHABILITY = 1 / Math.pow(sk.pen, 0.5); | |
out.RANGE = shoot.range; | |
break; | |
case 'necro': | |
case 'drone': | |
out.PUSHABILITY = 1; | |
out.PENETRATION = Math.max(1, shoot.pen * (0.5 * (sk.pen - 1) + 1)); | |
out.HEALTH = (shoot.health * sk.str + sizeFactor) / Math.pow(sk.pen, 0.8); | |
out.DAMAGE = shoot.damage * sk.dam * Math.sqrt(sizeFactor) * shoot.pen * sk.pen; | |
out.RANGE = shoot.range * Math.sqrt(sizeFactor); | |
break; | |
} | |
// Go through and make sure we respect its natural properties | |
for (let property in out) { | |
if (this.natural[property] == null || !out.hasOwnProperty(property)) continue; | |
out[property] *= this.natural[property]; | |
} | |
return out; | |
} | |
} | |
// Define entities | |
// const because they won't be reassigned | |
const minimap = []; | |
const views = []; | |
const entitiesToAvoid = []; | |
const dirtyCheck = (p, r) => { return entitiesToAvoid.some(e => { return Math.abs(p.x - e.x) < r + e.size && Math.abs(p.y - e.y) < r + e.size; }); }; | |
const grid = new hshg.HSHG(); | |
let entitiesIdLog = 0; | |
const entities = []; | |
const entitiesNoFood = [] | |
const removeEntity = index => { | |
if(index === (entities.length - 1)){ | |
entities.pop() | |
return | |
} | |
entities[index] = entities.pop() | |
entities[index].i = index | |
}; | |
const removeEntityFromNoFood = index => { | |
if(entitiesNoFood.length === 0 || index >= (entitiesNoFood.length - 1)){ | |
entitiesNoFood.pop() | |
return | |
} | |
entitiesNoFood[index] = entitiesNoFood.pop() | |
entitiesNoFood[index].I = index | |
}; | |
const food = [] | |
const removeEntityFromFood = index => { | |
if(index === food.length - 1){ | |
food.pop() | |
return | |
} | |
food[index] = food.pop() | |
food[index].F = index | |
}; | |
var bringToLife = (() => { | |
let remapTarget = (i, ref, self) => { | |
if (i.target == null || (!i.main && !i.alt)) return undefined; | |
return { | |
x: i.target.x + ref.x - self.x, | |
y: i.target.y + ref.y - self.y, | |
}; | |
}; | |
let passer = (a, b, acceptsFromTop) => { | |
return index => { | |
if (a != null && a[index] != null && (b[index] == null || acceptsFromTop)) { | |
b[index] = a[index]; | |
} | |
}; | |
}; | |
return my => { | |
// Size | |
if (my.SIZE - my.coreSize) my.coreSize += (my.SIZE - my.coreSize) / 100; | |
// Think | |
let faucet = (my.settings.independent || my.source == null || my.source === my) ? {} : my.source.control; | |
let b = { | |
target: remapTarget(faucet, my.source, my), | |
goal: undefined, | |
fire: faucet.fire, | |
main: faucet.main, | |
alt: faucet.alt, | |
power: undefined, | |
}; | |
// Seek attention | |
if (my.settings.attentionCraver && !faucet.main && my.range) { | |
my.range -= 1; | |
} | |
// So we start with my master's thoughts and then we filter them down through our control stack | |
my.controllers.forEach(AI => { | |
let a = AI.think(b); | |
let passValue = passer(a, b, AI.acceptsFromTop); | |
passValue('target'); | |
passValue('goal'); | |
passValue('fire'); | |
passValue('main'); | |
passValue('alt'); | |
passValue('power'); | |
}); | |
my.control.target = (b.target == null) ? my.control.target : b.target; | |
my.control.goal = b.goal; | |
my.control.fire = b.fire; | |
my.control.main = b.main; | |
my.control.alt = b.alt; | |
my.control.power = (b.power == null) ? 1 : b.power; | |
// React | |
my.move(); | |
my.face(); | |
// Handle guns and turrets if we've got them | |
logs.bullets.set() | |
my.guns.forEach(gun => gun.live()); | |
my.turrets.forEach(turret => turret.life()); | |
logs.bullets.mark() | |
if (my.skill.maintain()) my.refreshBodyAttributes(); | |
}; | |
})(); | |
class HealthType { | |
constructor(health, type, resist = 0) { | |
this.max = health; | |
this.amount = health; | |
this.type = type; | |
this.resist = resist; | |
this.regen = 0; | |
} | |
set(health, regen = 0) { | |
this.amount = (this.max) ? this.amount / this.max * health : health; | |
this.max = health; | |
this.regen = regen; | |
} | |
display() { | |
return this.amount / this.max; | |
} | |
getDamage(amount, capped = true) {//what's an "Smi"? | |
switch (this.type) { | |
case 'dynamic': | |
return (capped) ? ( | |
Math.min(amount * this.permeability, this.amount) | |
) : ( | |
amount * this.permeability | |
); | |
case 'static': | |
return (capped) ? ( | |
Math.min(amount, this.amount) | |
) : ( | |
amount | |
); | |
} | |
} | |
regenerate(boost = false) { | |
boost /= 2; | |
let cons = 5; | |
switch (this.type) { | |
case 'static': | |
if (this.amount >= this.max || !this.amount) break; | |
this.amount += cons * (this.max / 10 / 60 / 2.5 + boost); | |
break; | |
case 'dynamic': | |
let r = util.clamp(this.amount / this.max, 0, 1); | |
if (!r) { | |
this.amount = 0.0001; | |
} | |
if (r === 1) { | |
this.amount = this.max; | |
} else { | |
this.amount += cons * (this.regen * Math.exp(-50 * Math.pow(Math.sqrt(0.5 * r) - 0.4, 2)) / 3 + r * this.max / 10 / 15 + boost); | |
} | |
break; | |
} | |
this.amount = util.clamp(this.amount, 0, this.max); | |
} | |
get permeability() { | |
switch(this.type) { | |
case 'static': return 1; | |
case 'dynamic': return (this.max) ? util.clamp(this.amount / this.max, 0, 1) : 0; | |
} | |
} | |
get ratio() { | |
return (this.max) ? util.clamp(1 - Math.pow(this.amount / this.max - 1, 4), 0, 1) : 0; | |
} | |
} | |
class Entity { | |
constructor(position, master = this) { | |
this.killCount = { solo: 0, assists: 0, bosses: 0, killers: [], }; | |
// Inheritance | |
this.master = master; | |
this.source = this; | |
this.parent = this; | |
// CTF: | |
this.hasFlag = false; | |
this.autoOverride = false; | |
this.controllers = []; | |
this.blend = { | |
color: '#FFFFFF', | |
amount: 0, | |
}; | |
// Objects | |
this.guns = []; | |
this.turrets = []; | |
this.upgrades = []; | |
this.settings = {}; | |
this.aiSettings = {}; | |
this.children = []; | |
// Define it | |
this.SIZE = 1; | |
// Initalize physics and collision | |
this.maxSpeed = 0; | |
this.facing = 0; | |
this.vfacing = 0; | |
this.range = 0; | |
this.damageRecieved = 0; | |
this.stepRemaining = 1; | |
this.x = position.x; | |
this.y = position.y; | |
this.damp = 0.05; | |
this.collisionArray = []; | |
this.invuln = false; | |
this.cameraShiftFacing = null | |
this.isInGrid = false; | |
this.velocity = new Vector(0, 0); | |
this.accel = new Vector(0, 0); | |
this.control = { | |
target: new Vector(0, 0), | |
goal: new Vector(0, 0), | |
main: false, | |
alt: false, | |
fire: false, | |
power: 0, | |
}; | |
// everything else | |
// Objects | |
this.skill = new Skill(); | |
this.health = new HealthType(1, 'static', 0); | |
this.shield = new HealthType(0, 'dynamic'); | |
this.define(Class.genericEntity); | |
this.i = entities.length | |
entities.push(this); | |
this.I = entitiesNoFood.length | |
entitiesNoFood.push(this) | |
// Get a new unique id | |
this.id = entitiesIdLog++; | |
this.team = this.id; | |
this.team = master.team; | |
// This is for collisions | |
//this.grid= | |
this.removeFromGrid = () => { if (this.isInGrid) { grid.removeObject(this); this.isInGrid = false; } }; | |
this.addToGrid = () => { | |
if (!this.isInGrid && this.bond == null) { grid.addObject(this); this.isInGrid = true; } | |
}; | |
this.updateAABB = () => {}; | |
this.getAABB = (() => { | |
let data = {}, savedSize = 0; | |
let getLongestEdge = (x1, y1, x2, y2) => { | |
return Math.max( | |
Math.abs(x2 - x1), | |
Math.abs(y2 - y1) | |
); | |
}; | |
this.updateAABB = active => { | |
if (this.bond != null) return 0; | |
if (!active) { data.active = false; return 0; } | |
// Get bounds | |
let x1 = Math.min(this.x, this.x + this.velocity.x + this.accel.x) - this.realSize - 5; | |
let y1 = Math.min(this.y, this.y + this.velocity.y + this.accel.y) - this.realSize - 5; | |
let x2 = Math.max(this.x, this.x + this.velocity.x + this.accel.x) + this.realSize + 5; | |
let y2 = Math.max(this.y, this.y + this.velocity.y + this.accel.y) + this.realSize + 5; | |
// Size check | |
let size = getLongestEdge(x1, y1, x2, y1); | |
let sizeDiff = savedSize / size; | |
// Update data | |
data = { | |
min: [x1, y1], | |
max: [x2, y2], | |
active: true, | |
size: size, | |
}; | |
// Update grid if needed | |
if (sizeDiff > Math.SQRT2 || sizeDiff < Math.SQRT1_2) { | |
this.removeFromGrid(); this.addToGrid(); | |
savedSize = data.size; | |
} | |
}; | |
return () => { return data; }; | |
})(); | |
this.updateAABB(true); | |
views.forEach(v => v.add(this)); | |
this.activation = (() => { | |
let active = true; | |
let timer = ran.irandom(15); | |
return { | |
update: () => { | |
//if (this.isDead()) return 0; | |
// Check if I'm in anybody's view | |
if (!active) { | |
this.removeFromGrid(); | |
// Remove bullets and swarm | |
if (this.settings.diesAtRange) this.kill(); | |
// Still have limited update cycles but do it much more slowly. | |
if (!(timer--)) active = true; | |
} else { | |
this.addToGrid(); | |
timer = 15; | |
active = views.some(v => v.check(this, 0.6)); | |
} | |
}, | |
check: c.ALWAYS_ACTIVATE ? () => true : () => { return active; } | |
}; | |
})(); | |
} | |
life() { bringToLife(this); } | |
addController(newIO) { | |
if (Array.isArray(newIO)) { | |
this.controllers = newIO.concat(this.controllers); | |
} else { | |
this.controllers.unshift(newIO); | |
} | |
} | |
define(set) { | |
if (set.PARENT != null) { | |
for (let i=0; i<set.PARENT.length; i++) { | |
this.define(set.PARENT[i]); | |
} | |
} | |
if (set.index != null) { | |
this.index = set.index; | |
} | |
if (set.NAME != null) { | |
this.name = set.NAME; | |
} | |
if (set.LABEL != null) { | |
this.label = set.LABEL; | |
} | |
if (set.TYPE != null) { | |
this.type = set.TYPE; | |
} | |
if (set.SHAPE != null) { | |
this.shape = set.SHAPE; | |
} | |
if (set.COLOR != null) { | |
this.color = set.COLOR; | |
} | |
if (set.MOTION_TYPE != null) { | |
this.motionType = set.MOTION_TYPE; | |
} | |
if (set.FACING_TYPE != null) { | |
this.facingType = set.FACING_TYPE; | |
} | |
if (set.DRAW_HEALTH != null) { | |
this.settings.drawHealth = set.DRAW_HEALTH; | |
} | |
if (set.DRAW_SELF != null) { | |
this.settings.drawShape = set.DRAW_SELF; | |
} | |
if (set.DAMAGE_EFFECTS != null) { | |
this.settings.damageEffects = set.DAMAGE_EFFECTS; | |
} | |
if (set.RATIO_EFFECTS != null) { | |
this.settings.ratioEffects = set.RATIO_EFFECTS; | |
} | |
if (set.MOTION_EFFECTS != null) { | |
this.settings.motionEffects = set.MOTION_EFFECTS; | |
} | |
if (set.ACCEPTS_SCORE != null) { | |
this.settings.acceptsScore = set.ACCEPTS_SCORE; | |
} | |
if (set.GIVE_KILL_MESSAGE != null) { | |
this.settings.givesKillMessage = set.GIVE_KILL_MESSAGE; | |
} | |
if (set.CAN_GO_OUTSIDE_ROOM != null) { | |
this.settings.canGoOutsideRoom = set.CAN_GO_OUTSIDE_ROOM; | |
} | |
if (set.HITS_OWN_TYPE != null) { | |
this.settings.hitsOwnType = set.HITS_OWN_TYPE; | |
} | |
if (set.DIE_AT_LOW_SPEED != null) { | |
this.settings.diesAtLowSpeed = set.DIE_AT_LOW_SPEED; | |
} | |
if (set.DIE_AT_RANGE != null) { | |
this.settings.diesAtRange = set.DIE_AT_RANGE; | |
} | |
if (set.INDEPENDENT != null) { | |
this.settings.independent = set.INDEPENDENT; | |
} | |
if (set.PERSISTS_AFTER_DEATH != null) { | |
this.settings.persistsAfterDeath = set.PERSISTS_AFTER_DEATH; | |
} | |
if (set.CLEAR_ON_MASTER_UPGRADE != null) { | |
this.settings.clearOnMasterUpgrade = set.CLEAR_ON_MASTER_UPGRADE; | |
} | |
if (set.HEALTH_WITH_LEVEL != null) { | |
this.settings.healthWithLevel = set.HEALTH_WITH_LEVEL; | |
} | |
if (set.ACCEPTS_SCORE != null) { | |
this.settings.acceptsScore = set.ACCEPTS_SCORE; | |
} | |
if (set.OBSTACLE != null) { | |
this.settings.obstacle = set.OBSTACLE; | |
} | |
if (set.NECRO != null) { | |
this.settings.isNecromancer = set.NECRO; | |
} | |
if (set.AUTO_UPGRADE != null) { | |
this.settings.upgrading = set.AUTO_UPGRADE; | |
} | |
if (set.HAS_NO_RECOIL != null) { | |
this.settings.hasNoRecoil = set.HAS_NO_RECOIL; | |
} | |
if (set.CRAVES_ATTENTION != null) { | |
this.settings.attentionCraver = set.CRAVES_ATTENTION; | |
} | |
if (set.BROADCAST_MESSAGE != null) { | |
this.settings.broadcastMessage = (set.BROADCAST_MESSAGE === '') ? undefined : set.BROADCAST_MESSAGE; | |
} | |
if (set.DAMAGE_CLASS != null) { | |
this.settings.damageClass = set.DAMAGE_CLASS; | |
} | |
if (set.BUFF_VS_FOOD != null) { | |
this.settings.buffVsFood = set.BUFF_VS_FOOD; | |
} | |
if (set.CAN_BE_ON_LEADERBOARD != null) { | |
this.settings.leaderboardable = set.CAN_BE_ON_LEADERBOARD; | |
} | |
if (set.INTANGIBLE != null) { | |
this.intangibility = set.INTANGIBLE; | |
} | |
if (set.IS_SMASHER != null) { | |
this.settings.reloadToAcceleration = set.IS_SMASHER; | |
} | |
if (set.STAT_NAMES != null) { | |
this.settings.skillNames = set.STAT_NAMES; | |
} | |
if (set.AI != null) { | |
this.aiSettings = set.AI; | |
} | |
if (set.DANGER != null) { | |
this.dangerValue = set.DANGER; | |
} | |
if (set.VARIES_IN_SIZE != null) { | |
this.settings.variesInSize = set.VARIES_IN_SIZE; | |
this.squiggle = (this.settings.variesInSize) ? ran.randomRange(0.8, 1.2) : 1; | |
} | |
if (set.RESET_UPGRADES) { | |
this.upgrades = []; | |
} | |
if (set.UPGRADES_TIER_1 != null) { | |
set.UPGRADES_TIER_1.forEach((e) => { | |
this.upgrades.push({ class: e, level: c.TIER_1, index: e.index,}); | |
}); | |
} | |
if (set.UPGRADES_TIER_2 != null) { | |
set.UPGRADES_TIER_2.forEach((e) => { | |
this.upgrades.push({ class: e, level: c.TIER_2, index: e.index,}); | |
}); | |
} | |
if (set.UPGRADES_TIER_3 != null) { | |
set.UPGRADES_TIER_3.forEach((e) => { | |
this.upgrades.push({ class: e, level: c.TIER_3, index: e.index,}); | |
}); | |
} | |
if (set.SIZE != null) { | |
this.SIZE = set.SIZE * this.squiggle; | |
if (this.coreSize == null) { this.coreSize = this.SIZE; } | |
} | |
if (set.SKILL != null && set.SKILL != []) { | |
if (set.SKILL.length != 10) { | |
throw('Inappropiate skill raws.'); | |
} | |
this.skill.set(set.SKILL); | |
} | |
if (set.LEVEL != null) { | |
if (set.LEVEL === -1) { | |
this.skill.reset(); | |
} | |
while (this.skill.level < c.SKILL_CHEAT_CAP && this.skill.level < set.LEVEL) { | |
this.skill.score += this.skill.levelScore; | |
this.skill.maintain(); | |
} | |
this.refreshBodyAttributes(); | |
} | |
if (set.SKILL_CAP != null && set.SKILL_CAP != []) { | |
if (set.SKILL_CAP.length != 10) { | |
throw('Inappropiate skill caps.'); | |
} | |
this.skill.setCaps(set.SKILL_CAP); | |
} | |
if (set.VALUE != null) { | |
this.skill.score = Math.max(this.skill.score, set.VALUE * this.squiggle); | |
} | |
if (set.ALT_ABILITIES != null) { | |
this.abilities = set.ALT_ABILITIES; | |
} | |
if (set.GUNS != null) { | |
let newGuns = []; | |
set.GUNS.forEach((gundef) => { | |
newGuns.push(new Gun(this, gundef)); | |
}); | |
this.guns = newGuns; | |
} | |
if (set.MAX_CHILDREN != null) { | |
this.maxChildren = set.MAX_CHILDREN; | |
} | |
if (set.FOOD != null) { | |
if (set.FOOD.LEVEL != null) { | |
this.foodLevel = set.FOOD.LEVEL; | |
this.foodCountup = 0; | |
} | |
} | |
if (set.BODY != null) { | |
if (set.BODY.ACCELERATION != null) { | |
this.ACCELERATION = set.BODY.ACCELERATION; | |
} | |
if (set.BODY.SPEED != null) { | |
this.SPEED = set.BODY.SPEED; | |
} | |
if (set.BODY.HEALTH != null) { | |
this.HEALTH = set.BODY.HEALTH; | |
} | |
if (set.BODY.RESIST != null) { | |
this.RESIST = set.BODY.RESIST; | |
} | |
if (set.BODY.SHIELD != null) { | |
this.SHIELD = set.BODY.SHIELD; | |
} | |
if (set.BODY.REGEN != null) { | |
this.REGEN = set.BODY.REGEN; | |
} | |
if (set.BODY.DAMAGE != null) { | |
this.DAMAGE = set.BODY.DAMAGE; | |
} | |
if (set.BODY.PENETRATION != null) { | |
this.PENETRATION = set.BODY.PENETRATION; | |
} | |
if (set.BODY.FOV != null) { | |
this.FOV = set.BODY.FOV; | |
} | |
if (set.BODY.RANGE != null) { | |
this.RANGE = set.BODY.RANGE; | |
} | |
if (set.BODY.SHOCK_ABSORB != null) { | |
this.SHOCK_ABSORB = set.BODY.SHOCK_ABSORB; | |
} | |
if (set.BODY.DENSITY != null) { | |
this.DENSITY = set.BODY.DENSITY; | |
} | |
if (set.BODY.STEALTH != null) { | |
this.STEALTH = set.BODY.STEALTH; | |
} | |
if (set.BODY.PUSHABILITY != null) { | |
this.PUSHABILITY = set.BODY.PUSHABILITY; | |
} | |
if (set.BODY.HETERO != null) { | |
this.heteroMultiplier = set.BODY.HETERO; | |
} | |
this.refreshBodyAttributes(); | |
} | |
if (set.TURRETS != null) { | |
let o; | |
this.turrets.forEach(o => o.destroy()); | |
this.turrets = []; | |
set.TURRETS.forEach(def => { | |
o = new Entity(this, this.master); | |
((Array.isArray(def.TYPE)) ? def.TYPE : [def.TYPE]).forEach(type => o.define(type)); | |
o.bindToMaster(def.POSITION, this); | |
}); | |
} | |
if (set.mockup != null) { | |
this.mockup = set.mockup; | |
} | |
if (set.CONTROLLERS != null) { | |
let toAdd = []; | |
set.CONTROLLERS.forEach((ioName) => { | |
toAdd.push(eval('new io_' + ioName + '(this)')); | |
}); | |
this.addController(toAdd); | |
} | |
if (this.type === 'food' && this.I !== -1) { | |
removeEntityFromNoFood(this.I) | |
this.I = -1 | |
} | |
} | |
refreshBodyAttributes() { | |
let speedReduce = Math.pow(this.size / (this.coreSize || this.SIZE), 1); | |
this.acceleration = c.runSpeed * this.ACCELERATION / speedReduce; | |
if (this.settings.reloadToAcceleration) this.acceleration *= this.skill.acl; | |
this.topSpeed = c.runSpeed * this.SPEED * this.skill.mob / speedReduce; | |
if (this.settings.reloadToAcceleration) this.topSpeed /= Math.sqrt(this.skill.acl); | |
this.health.set( | |
(((this.settings.healthWithLevel) ? 2 * this.skill.level : 0) + this.HEALTH) * this.skill.hlt | |
); | |
this.health.resist = 1 - 1 / Math.max(1, this.RESIST + this.skill.brst); | |
this.shield.set( | |
(((this.settings.healthWithLevel) ? 0.6 * this.skill.level : 0) + this.SHIELD) * this.skill.shi, | |
Math.max(0, ((((this.settings.healthWithLevel) ? 0.006 * this.skill.level : 0) + 1) * this.REGEN) * this.skill.rgn) | |
); | |
this.damage = this.DAMAGE * this.skill.atk; | |
this.penetration = this.PENETRATION + 1.5 * (this.skill.brst + 0.8 * (this.skill.atk - 1)); | |
if (!this.settings.dieAtRange || !this.range) { | |
this.range = this.RANGE; | |
} | |
this.fov = Math.max(this.FOV * 250 * Math.sqrt(this.size) * (1 + 0.003 * this.skill.level), 280); | |
this.density = (1 + 0.08 * this.skill.level) * this.DENSITY; | |
this.stealth = this.STEALTH; | |
this.pushability = this.PUSHABILITY; | |
} | |
bindToMaster(position, bond) { | |
this.bond = bond; | |
this.source = bond; | |
this.bond.turrets.push(this); | |
this.skill = this.bond.skill; | |
this.label = this.bond.label + ' ' + this.label; | |
// It will not be in collision calculations any more nor shall it be seen. | |
this.removeFromGrid(); | |
this.settings.drawShape = false; | |
// Get my position. | |
this.bound = {}; | |
this.bound.size = position[0] / 20; | |
let _off = new Vector(position[1], position[2]); | |
this.bound.angle = position[3] * Math.PI / 180; | |
this.bound.direction = _off.direction; | |
this.bound.offset = _off.length / 10; | |
this.bound.arc = position[4] * Math.PI / 180; | |
// Figure out how we'll be drawn. | |
this.bound.layer = position[5]; | |
// Initalize. | |
this.facing = this.bond.facing + this.bound.angle; | |
this.facingType = 'bound'; | |
this.motionType = 'bound'; | |
this.move(); | |
} | |
get size() { | |
if (this.bond == null) return (this.coreSize || this.SIZE) * (1 + this.skill.level / 45); | |
return this.bond.size * this.bound.size; | |
} | |
get mass() { | |
return this.density * (this.size * this.size + 1); | |
} | |
get realSize() { | |
return this.size * ((Math.abs(this.shape) > lazyRealSizes.length) ? 1 : lazyRealSizes[Math.abs(this.shape)]); | |
} | |
get m_x() { | |
return (this.velocity.x + this.accel.x) / roomSpeed; | |
} | |
get m_y() { | |
return (this.velocity.y + this.accel.y) / roomSpeed; | |
} | |
camera(tur = false) { | |
let out = { | |
type: 0 + tur * 0x01 + this.settings.drawHealth * 0x02 + (this.type === 'tank') * 0x04, | |
id: this.id, | |
index: this.index, | |
x: this.x, | |
y: this.y , | |
vx: this.velocity.x, | |
vy: this.velocity.y, | |
size: this.size, | |
rsize: this.realSize, | |
status: 1, | |
health: this.health.display(), | |
shield: this.shield.display(), | |
facing: this.facing, | |
vfacing: this.vfacing, | |
twiggle: this.facingType === 'autospin' || (this.facingType === 'locksFacing' && this.control.alt), | |
layer: (this.bond != null) ? this.bound.layer : | |
(this.type === 'wall') ? 11 : | |
(this.type === 'food') ? 10 : | |
(this.type === 'tank') ? 5 : | |
(this.type === 'crasher') ? 1 : | |
0, | |
color: this.color, | |
name: this.name, | |
score: this.skill.score, | |
guns: this.guns.map(gun => gun.getLastShot()), | |
turrets: this.turrets.map(turret => turret.camera(true)), | |
}; | |
if (this.label === 'Predator') { | |
if (this.control.alt) { | |
if (this.cameraShiftFacing === null) { | |
const hyp = Math.sqrt((this.fov * 0.6 + 1.5 * this.size + 100)**2 + (this.fov * 0.6 * 0.5625 + 1.5 * this.size + 100)**2) | |
out.x = (hyp * Math.cos(this.facing))*0.77 + this.x | |
out.y = (hyp * Math.sin(this.facing))*0.77 + this.y | |
this.cameraShiftFacing = [out.x, out.y] | |
} | |
[out.x, out.y] = this.cameraShiftFacing | |
} else { | |
this.cameraShiftFacing = null | |
} | |
} | |
if(this.id === room.topPlayerID) { | |
topPlayer = this | |
} | |
/*if (this.label === 'Observer') { | |
if (this.control.alt) { | |
if (this.cameraShiftFacing) { | |
return this.cameraShiftFacing | |
} else if (topPlayer && (this.cameraShiftFacing == undefined)) { | |
this.cameraShiftFacing = topPlayer.camera() | |
return topPlayer.camera() | |
} | |
} else { | |
this.cameraShiftFacing = null | |
} | |
}*/ | |
return out | |
} | |
skillUp(stat) { | |
let suc = this.skill.upgrade(stat); | |
if (suc) { | |
this.refreshBodyAttributes(); | |
this.guns.forEach(function(gun) { | |
gun.syncChildren(); | |
}); | |
} | |
return suc; | |
} | |
upgrade(number) { | |
if (number < this.upgrades.length && this.skill.level >= this.upgrades[number].level) { | |
let saveMe = this.upgrades[number].class; | |
this.upgrades = []; | |
this.define(saveMe); | |
//this.sendMessage('You have upgraded to ' + this.label + '.'); | |
let ID = this.id; | |
entities.forEach(instance => { | |
if (instance.settings.clearOnMasterUpgrade && instance.master.id === ID) { | |
instance.kill(); | |
} | |
}); | |
this.skill.update(); | |
this.refreshBodyAttributes(); | |
} | |
} | |
damageMultiplier() { | |
switch (this.type) { | |
case 'swarm': | |
return 0.25 + 1.5 * util.clamp(this.range / (this.RANGE + 1), 0, 1); | |
default: return 1; | |
} | |
} | |
move() { | |
let g = { | |
x: this.control.goal.x - this.x, | |
y: this.control.goal.y - this.y, | |
}, | |
gactive = (g.x !== 0 || g.y !== 0), | |
engine = { | |
x: 0, | |
y: 0, | |
}, | |
a = this.acceleration / roomSpeed; | |
switch (this.motionType) { | |
case 'glide': | |
this.maxSpeed = this.topSpeed; | |
this.damp = 0.05; | |
break; | |
case 'motor': | |
this.maxSpeed = 0; | |
if (this.topSpeed) { | |
this.damp = a / this.topSpeed; | |
} | |
if (gactive) { | |
let len = Math.sqrt(g.x * g.x + g.y * g.y); | |
engine = { | |
x: a * g.x / len, | |
y: a * g.y / len, | |
}; | |
} | |
break; | |
case 'swarm': | |
this.maxSpeed = this.topSpeed; | |
let l = util.getDistance({ x: 0, y: 0, }, g) + 1; | |
if (gactive && l > this.size) { | |
let desiredxspeed = this.topSpeed * g.x / l, | |
desiredyspeed = this.topSpeed * g.y / l, | |
turning = Math.sqrt((this.topSpeed * Math.max(1, this.range) + 1) / a); | |
engine = { | |
x: (desiredxspeed - this.velocity.x) / Math.max(5, turning), | |
y: (desiredyspeed - this.velocity.y) / Math.max(5, turning), | |
}; | |
} else { | |
if (this.velocity.length < this.topSpeed) { | |
engine = { | |
x: this.velocity.x * a / 20, | |
y: this.velocity.y * a / 20, | |
}; | |
} | |
} | |
break; | |
case 'chase': | |
if (gactive) { | |
let l = util.getDistance({ x: 0, y: 0, }, g); | |
if (l > this.size * 2) { | |
this.maxSpeed = this.topSpeed; | |
let desiredxspeed = this.topSpeed * g.x / l, | |
desiredyspeed = this.topSpeed * g.y / l; | |
engine = { | |
x: (desiredxspeed - this.velocity.x) * a, | |
y: (desiredyspeed - this.velocity.y) * a, | |
}; | |
} else { | |
this.maxSpeed = 0; | |
} | |
} else { | |
this.maxSpeed = 0; | |
} | |
break; | |
case 'drift': | |
this.maxSpeed = 0; | |
engine = { | |
x: g.x * a, | |
y: g.y * a, | |
}; | |
break; | |
case 'bound': | |
let bound = this.bound, ref = this.bond; | |
this.x = ref.x + ref.size * bound.offset * Math.cos(bound.direction + bound.angle + ref.facing); | |
this.y = ref.y + ref.size * bound.offset * Math.sin(bound.direction + bound.angle + ref.facing); | |
this.bond.velocity.x += bound.size * this.accel.x; | |
this.bond.velocity.y += bound.size * this.accel.y; | |
this.firingArc = [ref.facing + bound.angle, bound.arc / 2]; | |
nullVector(this.accel); | |
this.blend = ref.blend; | |
break; | |
} | |
this.accel.x += engine.x * this.control.power; | |
this.accel.y += engine.y * this.control.power; | |
} | |
face() { | |
let t = this.control.target, | |
tactive = (t.x !== 0 || t.y !== 0), | |
oldFacing = this.facing; | |
switch(this.facingType) { | |
case 'autospin': | |
this.facing += 0.02 / roomSpeed; | |
break; | |
case 'turnWithSpeed': | |
this.facing += this.velocity.length / 90 * Math.PI / roomSpeed; | |
break; | |
case 'withMotion': | |
this.facing = this.velocity.direction; | |
break; | |
case 'smoothWithMotion': | |
case 'looseWithMotion': | |
this.facing += util.loopSmooth(this.facing, this.velocity.direction, 4 / roomSpeed); | |
break; | |
case 'withTarget': | |
case 'toTarget': | |
this.facing = Math.atan2(t.y, t.x); | |
break; | |
case 'locksFacing': | |
if (!this.control.alt) this.facing = Math.atan2(t.y, t.x); | |
break; | |
case 'looseWithTarget': | |
case 'looseToTarget': | |
case 'smoothToTarget': | |
this.facing += util.loopSmooth(this.facing, Math.atan2(t.y, t.x), 4 / roomSpeed); | |
break; | |
case 'bound': | |
let givenangle; | |
if (this.control.main) { | |
givenangle = Math.atan2(t.y, t.x); | |
let diff = util.angleDifference(givenangle, this.firingArc[0]); | |
if (Math.abs(diff) >= this.firingArc[1]) { | |
givenangle = this.firingArc[0];// - util.clamp(Math.sign(diff), -this.firingArc[1], this.firingArc[1]); | |
} | |
} else { | |
givenangle = this.firingArc[0]; | |
} | |
this.facing += util.loopSmooth(this.facing, givenangle, 4 / roomSpeed); | |
break; | |
default: | |
throw 'Unknown facing type ' + this.facingType | |
} | |
// todo: get a function to calculate the reference angle without a loop | |
// Loop | |
while (this.facing < 0) { | |
this.facing += 2 * Math.PI; | |
} | |
if (this.facing > 0) { | |
this.facing = this.facing % (2 * Math.PI) | |
} | |
this.vfacing = util.angleDifference(oldFacing, this.facing) * roomSpeed; | |
} | |
takeSelfie() { | |
this.flattenedPhoto = null; | |
this.photo = (this.settings.drawShape) ? this.camera() : this.photo = undefined; | |
} | |
physics() { | |
/*if (this.accel.x == null || this.velocity.x == null) { | |
util.error('Void Error!'); | |
util.error(this.collisionArray); | |
util.error(this.label); | |
util.error(this); | |
nullVector(this.accel); nullVector(this.velocity); | |
}*/ | |
// Apply acceleration | |
this.velocity.x += this.accel.x; | |
this.velocity.y += this.accel.y; | |
// Reset acceleration | |
nullVector(this.accel); | |
// Apply motion | |
this.stepRemaining = 1; | |
this.x += this.stepRemaining * this.velocity.x / roomSpeed; | |
this.y += this.stepRemaining * this.velocity.y / roomSpeed; | |
} | |
friction() { | |
var motion = this.velocity.length, | |
excess = motion - this.maxSpeed; | |
if (excess > 0 && this.damp) { | |
var k = this.damp / roomSpeed, | |
drag = excess / (k + 1), | |
finalvelocity = this.maxSpeed + drag; | |
this.velocity.x = finalvelocity * this.velocity.x / motion; | |
this.velocity.y = finalvelocity * this.velocity.y / motion; | |
} | |
} | |
confinementToTheseEarthlyShackles() { | |
if (this.x == null || this.x == null) { | |
util.error('Void Error!'); | |
util.error(this.collisionArray); | |
util.error(this.label); | |
util.error(this); | |
nullVector(this.accel); nullVector(this.velocity); | |
return 0; | |
} | |
if (!this.settings.canGoOutsideRoom) { | |
this.accel.x -= Math.min(this.x - this.realSize + 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed; | |
this.accel.x -= Math.max(this.x + this.realSize - room.width - 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed; | |
this.accel.y -= Math.min(this.y - this.realSize + 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed; | |
this.accel.y -= Math.max(this.y + this.realSize - room.height - 50, 0) * c.ROOM_BOUND_FORCE / roomSpeed; | |
} | |
if (room.gameMode === 'tdm' && this.type !== 'food') { | |
let loc = { x: this.x, y: this.y, }; | |
if ( | |
(this.team !== -1 && room.isIn('bas1', loc)) || | |
(this.team !== -2 && room.isIn('bas2', loc)) || | |
(this.team !== -3 && room.isIn('bas3', loc)) || | |
(this.team !== -4 && room.isIn('bas4', loc)) | |
) { this.kill(); } | |
} | |
if (_.has(gamemode, 'mortality') && gamemode.mortality(this)) { | |
this.kill() | |
} | |
} | |
contemplationOfMortality() { | |
if (this.invuln) { | |
this.damageRecieved = 0; | |
return false; | |
} | |
// Life-limiting effects | |
if (this.settings.diesAtRange) { | |
this.range -= 1 / roomSpeed; | |
if (this.range < 0) { | |
this.kill(); | |
} | |
} | |
if (this.settings.diesAtLowSpeed) { | |
if (!this.collisionArray.length && this.velocity.length < this.topSpeed / 2) { | |
this.health.amount -= this.health.getDamage(1 / roomSpeed); | |
} | |
} | |
// Shield regen and damage | |
if (this.shield.max) { | |
//if (this.damageRecieved !== 0) { | |
let shieldDamage = this.shield.getDamage(this.damageRecieved); | |
this.damageRecieved -= shieldDamage; | |
this.shield.amount -= shieldDamage; | |
//} | |
} | |
// Health damage | |
//if (this.damageRecieved !== 0) { | |
let healthDamage = this.health.getDamage(this.damageRecieved); | |
this.blend.amount = 1; | |
this.health.amount -= healthDamage; | |
//} | |
this.damageRecieved = 0; | |
// Check for death | |
if (this.isDead()) { | |
// this should be in a seperate loop | |
// Initalize message arrays | |
let killers = [], killTools = [], notJustFood = false; | |
// If I'm a tank, call me a nameless player | |
let name = (this.master.name === '') ? | |
(this.master.type === 'tank') ? | |
"a nameless player's " + this.label : | |
(this.master.type === 'miniboss') ? | |
"a visiting " + this.label : | |
util.addArticle(this.label) | |
: | |
this.master.name + "'s " + this.label; | |
// Calculate the jackpot | |
let jackpot = Math.ceil(util.getJackpot(this.skill.score + this.skill.truescore) / this.collisionArray.length); | |
// Now for each of the things that kill me... | |
this.collisionArray.forEach(instance => { | |
if (instance.type === 'wall') return 0; | |
if (instance.master.settings.acceptsScore) { // If it's not food, give its master the score | |
if (instance.master.type === 'tank' || instance.master.type === 'miniboss') notJustFood = true; | |
instance.master.skill.score += jackpot; | |
killers.push(instance.master); // And keep track of who killed me | |
} else if (instance.settings.acceptsScore) { | |
instance.skill.score += jackpot; | |
if (this.type !== 'food') { | |
instance.skill.truescore += jackpot; | |
} | |
} | |
killTools.push(instance); // Keep track of what actually killed me | |
}); | |
if((this.type === 'tank' || this.type === 'miniboss')) { | |
// Remove duplicates | |
killers = killers.filter((elem, index, self) => { return index == self.indexOf(elem); }); | |
// If there's no valid killers (you were killed by food), change the message to be more passive | |
let killText = (notJustFood) ? '' : "You have been killed by ", | |
dothISendAText = this.settings.givesKillMessage; | |
killers.forEach(instance => { | |
this.killCount.killers.push(instance.index); | |
if (this.type === 'tank') { | |
if (killers.length > 1) instance.killCount.assists++; else instance.killCount.solo++; | |
} else if (this.type === "miniboss") instance.killCount.bosses++; | |
}); | |
// Add the killers to our death message, also send them a message | |
if (notJustFood) { | |
killers.forEach(instance => { | |
if (instance.master.type !== 'food' && instance.master.type !== 'crasher') { | |
killText += (instance.name === '') ? (killText === '') ? 'An unnamed player' : 'an unnamed player' : instance.name; | |
killText += ' and '; | |
} | |
// Only if we give messages | |
if (dothISendAText) { | |
instance.sendMessage('You killed ' + name + ((killers.length > 1) ? ' (with some help).' : '.')); | |
} | |
}); | |
// Prepare the next part of the next | |
killText = killText.slice(0, -4); | |
killText += 'killed you with '; | |
} | |
// Broadcast | |
if (this.settings.broadcastMessage) sockets.broadcast(this.settings.broadcastMessage); | |
// Add the implements to the message | |
killTools.forEach((instance) => { | |
killText += util.addArticle(instance.label) + ' and '; | |
}); | |
// Prepare it and clear the collision array. | |
killText = killText.slice(0, -5); | |
if (killText === 'You have been kille') killText = 'You have died a stupid death'; | |
this.sendMessage(killText + '.'); | |
// todo: make the spawn level algorithm like diep (logarithmic to 0.5) | |
if(this.socket) { | |
this.socket.spawnLevel = Math.ceil(this.skill.level / 2) | |
} | |
// If I'm the leader, broadcast it: | |
if (this.id === room.topPlayerID) { | |
let usurptText = (this.name === '') ? 'The leader': this.name; | |
if (notJustFood) { | |
usurptText += ' has been usurped by'; | |
killers.forEach(instance => { | |
usurptText += ' '; | |
usurptText += (instance.name === '') ? 'an unnamed player' : instance.name; | |
usurptText += ' and'; | |
}); | |
usurptText = usurptText.slice(0, -4); | |
usurptText += '!'; | |
} else { | |
usurptText += ' fought a polygon... and the polygon won.'; | |
} | |
sockets.broadcast(usurptText); | |
} | |
} | |
// Kill it | |
return true; | |
} | |
if(this.isDead()) { | |
return true | |
} | |
return false; | |
} | |
protect() { | |
entitiesToAvoid.push(this); | |
this.isProtected = true; | |
} | |
sendMessage(message) { } // Dummy | |
kill() { | |
this.health.amount = -1; | |
} | |
destroy() { | |
// Remove from the protected entities list | |
if (this.isProtected) util.remove(entitiesToAvoid, entitiesToAvoid.indexOf(this)); | |
// Remove from minimap | |
let i = minimap.findIndex(entry => { return entry[0] === this.id; }); | |
if (i != -1) util.remove(minimap, i); | |
// Remove this from views | |
views.forEach(v => v.remove(this)); | |
// Remove from parent lists if needed | |
if (this.parent != null) util.remove(this.parent.children, this.parent.children.indexOf(this)); | |
/*// Kill all of its children | |
let ID = this.id; | |
entities.forEach(instance => { | |
if (instance.source.id === this.id) { | |
if (instance.settings.persistsAfterDeath) { | |
instance.source = instance; | |
} else { | |
instance.kill(); | |
} | |
} | |
if (instance.parent && instance.parent.id === this.id) { | |
instance.parent = null; | |
} | |
if (instance.master.id === this.id) { | |
instance.kill(); | |
instance.master = instance; | |
} | |
});*/ | |
// Remove everything bound to it | |
this.turrets.forEach(t => t.destroy()); | |
// Remove from the collision grid | |
this.removeFromGrid(); | |
// Remove from the entities array | |
// "i" is entities array index | |
removeEntity(this.i) | |
if (this.I !== -1) { | |
removeEntityFromNoFood(this.I) | |
} | |
if(this.F) { | |
removeEntityFromFood(this.F) | |
} | |
} | |
isDead() { | |
return this.health.amount <= 0; | |
} | |
} | |
/*** SERVER SETUP ***/ | |
// Make a speed monitor | |
var logs = (() => { | |
let logger = (() => { | |
// The two basic functions | |
function set(obj) { | |
obj.time = util.time(); | |
} | |
function mark(obj) { | |
obj.data.push(util.time() - obj.time); | |
} | |
function record(obj) { | |
let o = util.averageArray(obj.data); | |
obj.data = []; | |
return o; | |
} | |
function sum(obj) { | |
let o = util.sumArray(obj.data); | |
obj.data = []; | |
return o; | |
} | |
function tally(obj) { | |
obj.count++; | |
} | |
function count(obj) { | |
let o = obj.count; | |
obj.count = 0; | |
return o; | |
} | |
// Return the logger creator | |
return () => { | |
let internal = { | |
data: [], | |
time: util.time(), | |
count: 0, | |
}; | |
// Return the new logger | |
return { | |
set: () => set(internal), | |
mark: () => mark(internal), | |
record: () => record(internal), | |
sum: () => sum(internal), | |
count: () => count(internal), | |
tally: () => tally(internal), | |
}; | |
}; | |
})(); | |
// Return our loggers | |
return { | |
entities: logger(), | |
bullets: logger(), | |
collide: logger(), | |
network: logger(), | |
minimap: logger(), | |
misc2: logger(), | |
misc3: logger(), | |
physics: logger(), | |
life: logger(), | |
selfie: logger(), | |
master: logger(), | |
activation: logger(), | |
loops: logger(), | |
}; | |
})(); | |
// Essential server requires | |
var express = require('express'), | |
http = require('http'), | |
url = require('url'), | |
WebSocket = require('ws'), | |
app = express(), | |
fs = require('fs'), | |
exportDefintionsToClient = (() => { | |
function rounder(val) { | |
if (Math.abs(val) < 0.00001) val = 0; | |
return +val.toPrecision(6); | |
} | |
// Define mocking up functions | |
function getMockup(e, positionInfo) { | |
return { | |
index: e.index, | |
name: e.label, | |
x: rounder(e.x), | |
y: rounder(e.y), | |
color: e.color, | |
shape: e.shape, | |
size: rounder(e.size), | |
realSize: rounder(e.realSize), | |
facing: rounder(e.facing), | |
layer: e.layer, | |
statnames: e.settings.skillNames, | |
position: positionInfo, | |
guns: e.guns.map(function(gun) { | |
return { | |
offset: rounder(gun.offset), | |
direction: rounder(gun.direction), | |
length: rounder(gun.length), | |
width: rounder(gun.width), | |
aspect: rounder(gun.aspect), | |
angle: rounder(gun.angle), | |
}; | |
}), | |
turrets: e.turrets.map(function (t) { | |
let out = getMockup(t, {}); | |
out.sizeFactor = rounder(t.bound.size); | |
out.offset = rounder(t.bound.offset); | |
out.direction = rounder(t.bound.direction); | |
out.layer = rounder(t.bound.layer); | |
out.angle = rounder(t.bound.angle); | |
return out; | |
}), | |
}; | |
} | |
function getDimensions(entities) { | |
/* Ritter's Algorithm (Okay it got serious modified for how we start it) | |
* 1) Add all the ends of the guns to our list of points needed to be bounded and a couple points for the body of the tank.. | |
*/ | |
let endpoints = []; | |
let pointDisplay = []; | |
let pushEndpoints = function(model, scale, focus={ x: 0, y: 0 }, rot=0) { | |
let s = Math.abs(model.shape); | |
let z = (Math.abs(s) > lazyRealSizes.length) ? 1 : lazyRealSizes[Math.abs(s)]; | |
if (z === 1) { // Body (octagon if circle) | |
for (let i=0; i<2; i+=0.5) { | |
endpoints.push({x: focus.x + scale * Math.cos(i*Math.PI), y: focus.y + scale * Math.sin(i*Math.PI)}); | |
} | |
} else { // Body (otherwise vertices) | |
for (let i = (s % 2) ? 0 : Math.PI / s; i < s; i++) { | |
let theta = (i / s) * 2 * Math.PI; | |
endpoints.push({x: focus.x + scale * z * Math.cos(theta), y: focus.y + scale * z * Math.sin(theta)}); | |
} | |
} | |
model.guns.forEach(function(gun) { | |
let h = (gun.aspect > 0) ? scale * gun.width / 2 * gun.aspect : scale * gun.width / 2; | |
let r = Math.atan2(h, scale * gun.length) + rot; | |
let l = Math.sqrt(scale * scale * gun.length * gun.length + h * h); | |
let x = focus.x + scale * gun.offset * Math.cos(gun.direction + gun.angle + rot); | |
let y = focus.y + scale * gun.offset * Math.sin(gun.direction + gun.angle + rot); | |
endpoints.push({ | |
x: x + l * Math.cos(gun.angle + r), | |
y: y + l * Math.sin(gun.angle + r), | |
}); | |
endpoints.push({ | |
x: x + l * Math.cos(gun.angle - r), | |
y: y + l * Math.sin(gun.angle - r), | |
}); | |
pointDisplay.push({ | |
x: x + l * Math.cos(gun.angle + r), | |
y: y + l * Math.sin(gun.angle + r), | |
}); | |
pointDisplay.push({ | |
x: x + l * Math.cos(gun.angle - r), | |
y: y + l * Math.sin(gun.angle - r), | |
}); | |
}); | |
model.turrets.forEach(function(turret) { | |
pushEndpoints( | |
turret, turret.bound.size, | |
{ | |
x: turret.bound.offset * Math.cos(turret.bound.angle), | |
y: turret.bound.offset * Math.sin(turret.bound.angle) | |
}, | |
turret.bound.angle | |
); | |
}); | |
}; | |
pushEndpoints(entities, 1); | |
// 2) Find their mass center | |
let massCenter = { x: 0, y: 0 }; | |
/*endpoints.forEach(function(point) { | |
massCenter.x += point.x; | |
massCenter.y += point.y; | |
}); | |
massCenter.x /= endpoints.length; | |
massCenter.y /= endpoints.length;*/ | |
// 3) Choose three different points (hopefully ones very far from each other) | |
let chooseFurthestAndRemove = function(furthestFrom) { | |
let index = 0; | |
if (furthestFrom != -1) { | |
let list = new goog.structs.PriorityQueue(); | |
let d; | |
for (let i=0; i<endpoints.length; i++) { | |
let thisPoint = endpoints[i]; | |
d = Math.pow(thisPoint.x - furthestFrom.x, 2) + Math.pow(thisPoint.y - furthestFrom.y, 2) + 1; | |
list.enqueue(1/d, i); | |
} | |
index = list.dequeue(); | |
} | |
let output = endpoints[index]; | |
endpoints.splice(index, 1); | |
return output; | |
}; | |
let point1 = chooseFurthestAndRemove(massCenter); // Choose the point furthest from the mass center | |
let point2 = chooseFurthestAndRemove(point1); // And the point furthest from that | |
// And the point which maximizes the area of our triangle (a loose look at this one) | |
let chooseBiggestTriangleAndRemove = function(point1, point2) { | |
let list = new goog.structs.PriorityQueue(); | |
let index = 0; | |
let a; | |
for (let i=0; i<endpoints.length; i++) { | |
let thisPoint = endpoints[i]; | |
a = Math.pow(thisPoint.x - point1.x, 2) + Math.pow(thisPoint.y - point1.y, 2) + | |
Math.pow(thisPoint.x - point2.x, 2) + Math.pow(thisPoint.y - point2.y, 2); | |
/* We need neither to calculate the last part of the triangle | |
* (because it's always the same) nor divide by 2 to get the | |
* actual area (because we're just comparing it) | |
*/ | |
list.enqueue(1/a, i); | |
} | |
index = list.dequeue(); | |
let output = endpoints[index]; | |
endpoints.splice(index, 1); | |
return output; | |
}; | |
let point3 = chooseBiggestTriangleAndRemove(point1, point2); | |
// 4) Define our first enclosing circle as the one which seperates these three furthest points | |
function circleOfThreePoints(p1, p2, p3) { | |
let x1 = p1.x; | |
let y1 = p1.y; | |
let x2 = p2.x; | |
let y2 = p2.y; | |
let x3 = p3.x; | |
let y3 = p3.y; | |
let denom = | |
x1 * (y2 - y3) - | |
y1 * (x2 - x3) + | |
x2 * y3 - | |
x3 * y2; | |
let xy1 = x1*x1 + y1*y1; | |
let xy2 = x2*x2 + y2*y2; | |
let xy3 = x3*x3 + y3*y3; | |
let x = ( // Numerator | |
xy1 * (y2 - y3) + | |
xy2 * (y3 - y1) + | |
xy3 * (y1 - y2) | |
) / (2 * denom); | |
let y = ( // Numerator | |
xy1 * (x3 - x2) + | |
xy2 * (x1 - x3) + | |
xy3 * (x2 - x1) | |
) / (2 * denom); | |
let r = Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2)); | |
let r2 = Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2)); | |
let r3 = Math.sqrt(Math.pow(x - x3, 2) + Math.pow(y - y3, 2)); | |
if (r != r2 || r != r3) { | |
//util.log('somethings fucky'); | |
} | |
return {x: x, y: y, radius: r}; | |
} | |
let c = circleOfThreePoints(point1, point2, point3); | |
pointDisplay = [ | |
{ x: rounder(point1.x), y: rounder(point1.y), }, | |
{ x: rounder(point2.x), y: rounder(point2.y), }, | |
{ x: rounder(point3.x), y: rounder(point3.y), }, | |
]; | |
let centerOfCircle = { x: c.x, y: c.y }; | |
let radiusOfCircle = c.radius; | |
// 5) Check to see if we enclosed everything | |
function checkingFunction() { | |
for(var i=endpoints.length; i>0; i--) { | |
// Select the one furthest from the center of our circle and remove it | |
point1 = chooseFurthestAndRemove(centerOfCircle); | |
let vectorFromPointToCircleCenter = new Vector(centerOfCircle.x - point1.x, centerOfCircle.y - point1.y); | |
// 6) If we're still outside of this circle build a new circle which encloses the old circle and the new point | |
if (vectorFromPointToCircleCenter.length > radiusOfCircle) { | |
pointDisplay.push({ x: rounder(point1.x), y: rounder(point1.y), }); | |
// Define our new point as the far side of the cirle | |
let dir = vectorFromPointToCircleCenter.direction; | |
point2 = { | |
x: centerOfCircle.x + radiusOfCircle * Math.cos(dir), | |
y: centerOfCircle.y + radiusOfCircle * Math.sin(dir), | |
}; | |
break; | |
} | |
} | |
// False if we checked everything, true if we didn't | |
return !!endpoints.length; | |
} | |
while (checkingFunction()) { // 7) Repeat until we enclose everything | |
centerOfCircle = { | |
x: (point1.x + point2.x) / 2, | |
y: (point1.y + point2.y) / 2, | |
}; | |
radiusOfCircle = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)) / 2; | |
} | |
// 8) Since this algorithm isn't perfect but we know our shapes are bilaterally symmetrical, we bind this circle along the x-axis to make it behave better | |
return { | |
middle: { x: rounder(centerOfCircle.x), y: 0 }, | |
axis: rounder(radiusOfCircle * 2), | |
points: pointDisplay, | |
}; | |
} | |
// Save them | |
let mockupData = []; | |
for (let k in Class) { | |
try { | |
if (!Class.hasOwnProperty(k)) continue; | |
let type = Class[k]; | |
// Create a reference entities which we'll then take an image of. | |
let temptank = new Entity({x: 0, y: 0}); | |
temptank.define(type); | |
temptank.name = type.LABEL; // Rename it (for the upgrades menu). | |
// Fetch the mockup. | |
type.mockup = { | |
body: temptank.camera(true), | |
position: getDimensions(temptank), | |
}; | |
// This is to pass the size information about the mockup that we didn't have until we created the mockup | |
type.mockup.body.position = type.mockup.position; | |
// Add the new data to the thing. | |
mockupData.push(getMockup(temptank, type.mockup.position)); | |
// Kill the reference entities. | |
temptank.destroy(); | |
} catch(error) { | |
util.error(error); | |
util.error(k); | |
util.error(Class[k]); | |
} | |
} | |
// Build the function to return | |
let writeData = JSON.stringify(mockupData); | |
return loc => { | |
util.log('Preparing definition export.'); | |
fs.writeFileSync(loc, writeData, 'utf8', (err) => { | |
if (err) return util.error(err); | |
}); | |
util.log('Mockups written to ' + loc + '!'); | |
}; | |
})(), | |
generateVersionControlHash = (() => { | |
let crypto = require('crypto'); | |
let write = (() => { | |
let hash = [null, null]; | |
return (loc, data, numb) => { | |
// The callback is executed on reading completion | |
hash[numb] = crypto.createHash('sha1').update(data).digest('hex'); | |
if (hash[0] && hash[1]) { | |
let finalHash = hash[0] + hash[1]; | |
util.log('Client hash generated. Hash is "' + finalHash + '".'); | |
// Write the hash to a place the client can read it. | |
fs.writeFileSync(loc, finalHash, 'utf8', err => { | |
if (err) return util.error(err); | |
}); | |
util.log('Hash written to ' + loc + '!'); | |
} | |
}; | |
})(); | |
return loc => { | |
let path1 = __dirname + '/../client/js/app.js'; | |
let path2 = __dirname + '/lib/definitions.js'; | |
util.log('Building client version hash, reading from ' + path1 + ' and ' + path2 + '...'); | |
// Read the client application | |
fs.readFile(path1, 'utf8', (err, data) => { | |
if (err) return util.error(err); | |
util.log('app.js complete.'); | |
write(loc, data, 0); | |
}); | |
fs.readFile(path2, 'utf8', (err, data) => { | |
if (err) return util.error(err); | |
util.log('definitions.js complete.'); | |
write(loc, data, 1); | |
}); | |
}; | |
})(); | |
// Give the client upon request | |
exportDefintionsToClient(__dirname + '/../client/json/mockups.json'); | |
generateVersionControlHash(__dirname + '/../client/api/vhash'); | |
if (c.servesStatic) app.use(express.static((c.STATIC_BIN || __dirname) + '/../client')); | |
// Websocket behavior | |
const sockets = (() => { | |
const protocol = require('./lib/fasttalk'); | |
let clients = [], players = [], bannedIPs = [], suspiciousIPs = [], connectedIPs = [], | |
bannedNames = [ | |
'FREE_FOOD_LUCARIO', | |
'FREE FOOD' | |
]; | |
return { | |
broadcast: message => { | |
clients.forEach(socket => { | |
socket.talk('m', message); | |
}); | |
}, | |
broadcastDominator: (message, message2) => { | |
clients.forEach(socket => { | |
socket.talk('D', message, message2) | |
}) | |
}, | |
connect: (() => { | |
// Define shared functions | |
// Closing the socket | |
function close(socket) { | |
// Free the IP | |
let n = connectedIPs.findIndex(w => { return w.ip === socket.ip; }); | |
if (n !== -1) { | |
util.log(socket.ip + " disconnected."); | |
util.remove(connectedIPs, n); | |
} | |
// Free the token | |
if (socket.key != '') { | |
keys.push(socket.key); | |
util.log("Token freed."); | |
} | |
// Figure out who the player was | |
let player = socket.player, | |
index = players.indexOf(player); | |
// Remove the player if one was created | |
if (index != -1) { | |
// Kill the body if it exists | |
if (player.body != null) { | |
player.body.invuln = false; | |
setTimeout(() => { | |
player.body.kill(); | |
}, 10000); | |
} | |
// Disconnect everything | |
util.log('[INFO] User ' + player.name + ' disconnected!'); | |
util.remove(players, index); | |
} else { | |
util.log('[INFO] A player disconnected before entering the game.'); | |
} | |
// Free the view | |
util.remove(views, views.indexOf(socket.view)); | |
// Remove the socket | |
util.remove(clients, clients.indexOf(socket)); | |
util.log('[INFO] Socket closed. Views: ' + views.length + '. Clients: ' + clients.length + '.'); | |
} | |
// Banning | |
function ban(socket) { | |
if (bannedIPs.findIndex(ip => { return ip === socket.ip; }) === -1) { | |
bannedIPs.push(socket.ip); | |
} // No need for duplicates | |
socket.terminate(); | |
util.warn(socket.ip + ' banned!'); | |
} | |
// Being kicked | |
function kick(socket, reason = 'No reason given.') { | |
let n = suspiciousIPs.findIndex(n => { return n.ip === socket.ip; }); | |
if (n === -1) { | |
suspiciousIPs.push({ ip: socket.ip, warns: 1, }); | |
util.warn(reason + ' Kicking. 1 warning.'); | |
} else { | |
suspiciousIPs[n].warns++; | |
util.warn(reason + ' Kicking. ' + suspiciousIPs[n].warns + ' warnings.'); | |
if (suspiciousIPs[n].warns >= c.socketWarningLimit) { | |
ban(socket); | |
} | |
} | |
socket.lastWords('K'); | |
} | |
// Handle incoming messages | |
function incoming(message, socket) { | |
// Only accept binary | |
if (!(message instanceof ArrayBuffer)) { socket.kick('Non-binary packet.'); return 1; } | |
// Decode it | |
let m = protocol.decode(message); | |
// Make sure it looks legit | |
if (m === -1) { socket.kick('Malformed packet.'); return 1; } | |
// Log the message request | |
socket.status.requests++; | |
// Remember who we are | |
let player = socket.player; | |
// Handle the request | |
switch (m.shift()) { | |
case 'k': { // key verification | |
if (m.length !== 1) { socket.kick('Ill-sized key request.'); return 1; } | |
// Get data | |
let key = m[0]; | |
// Verify it | |
if (typeof key !== 'string') { socket.kick('Weird key offered.'); return 1; } | |
if (key.length > 64) { socket.kick('Overly-long key offered.'); return 1; } | |
if (socket.status.verified) { socket.kick('Duplicate player spawn attempt.'); return 1; } | |
// Otherwise proceed to check if it's available. | |
if (keys.indexOf(key) != -1 || (CHECK_KEYS === false)) { | |
// Save the key | |
socket.key = key.substr(0, 64); | |
// Make it unavailable | |
util.remove(keys, keys.indexOf(key)); | |
socket.verified = true; | |
// Proceed | |
socket.talk('w', true); | |
util.log('[INFO] A socket was verified with the token: '); util.log(key); | |
util.log('Clients: ' + clients.length); | |
} else { | |
// If not, kick 'em (nicely) | |
util.log('[INFO] Invalid player verification attempt.'); | |
socket.lastWords('w', false); | |
} | |
} break; | |
case 's': { // spawn request | |
if (!socket.status.deceased) { socket.kick('Trying to spawn while already alive.'); return 1; } | |
if (m.length !== 2) { socket.kick('Ill-sized spawn request.'); return 1; } | |
// Get data | |
let name = m[0].replace(c.BANNED_CHARACTERS_REGEX, ''); | |
let needsRoom = m[1]; | |
// Verify it | |
if (typeof name != 'string') { socket.kick('Bad spawn request.'); return 1; } | |
if (encodeURI(name).split(/%..|./).length > 48) { socket.kick('Overly-long name.'); return 1; } | |
if (needsRoom !== 0 && needsRoom !== 1) { socket.kick('Bad spawn request.'); return 1; } | |
// Bring to life | |
socket.status.deceased = false; | |
// Define the player. | |
if (players.indexOf(socket.player) != -1) { util.remove(players, players.indexOf(socket.player)); } | |
// Free the old view | |
if (views.indexOf(socket.view) != -1) { util.remove(views, views.indexOf(socket.view)); socket.makeView(); } | |
socket.player = socket.spawn(name); | |
/* | |
for (let [index, dominatorTeam] of dominatorControl.entries()) { | |
socket.talk('D', index, colorteammapping[dominatorTeam - 1]) | |
}*/ | |
// Give it the room state | |
if (needsRoom) { | |
socket.talk( | |
'R', | |
room.width, | |
room.height, | |
JSON.stringify(c.ROOM_SETUP), | |
JSON.stringify(util.serverStartTime), | |
roomSpeed | |
); | |
} | |
// Start the update rhythm immediately | |
socket.update(0); | |
// Log it | |
util.log('[INFO] ' + (m[0]) + (needsRoom ? ' joined' : ' rejoined') + ' the game! Players: ' + players.length); | |
} break; | |
case 'S': { // clock syncing | |
if (m.length !== 1) { socket.kick('Ill-sized sync packet.'); return 1; } | |
// Get data | |
let synctick = m[0]; | |
// Verify it | |
if (typeof synctick !== 'number') { socket.kick('Weird sync packet.'); return 1; } | |
// Bounce it back | |
socket.talk('S', synctick, util.time()); | |
} break; | |
case 'p': { // ping | |
if (m.length !== 1) { socket.kick('Ill-sized ping.'); return 1; } | |
// Get data | |
let ping = m[0]; | |
// Verify it | |
if (typeof ping !== 'number') { socket.kick('Weird ping.'); return 1; } | |
// Pong | |
socket.talk('p', m[0]); // Just pong it right back | |
socket.status.lastHeartbeat = util.time(); | |
} break; | |
case 'd': { // downlink | |
if (m.length !== 1) { socket.kick('Ill-sized downlink.'); return 1; } | |
// Get data | |
let time = m[0]; | |
// Verify data | |
if (typeof time !== 'number') { socket.kick('Bad downlink.'); return 1; } | |
// The downlink indicates that the client has received an update and is now ready to receive more. | |
socket.status.receiving = 0; | |
socket.camera.ping = util.time() - time; | |
socket.camera.lastDowndate = util.time(); | |
// Schedule a new update cycle | |
// Either fires immediately or however much longer it's supposed to wait per the config. | |
socket.update(Math.max(0, (1000 / c.networkUpdateFactor) - (util.time() - socket.camera.lastUpdate))); | |
} break; | |
case 'C': { // command packet | |
if (m.length !== 3) { socket.kick('Ill-sized command packet.'); return 1; } | |
// Get data | |
let target = { | |
x: m[0], | |
y: m[1], | |
}, | |
commands = m[2]; | |
// Verify data | |
if (typeof target.x !== 'number' || typeof target.y !== 'number' || typeof commands !== 'number') { socket.kick('Weird downlink.'); return 1; } | |
if (commands > 255 || target.x !== Math.round(target.x) || target.y !== Math.round(target.y)) { socket.kick('Malformed command packet.'); return 1; } | |
// Put the new target in | |
player.target = target; | |
// Process the commands | |
let val = [false, false, false, false, false, false, false, false]; | |
for (let i=7; i>=0; i--) { | |
if (commands >= Math.pow(2, i)) { | |
commands -= Math.pow(2, i); | |
val[i] = true; | |
} | |
} | |
player.command.up = val[0]; | |
player.command.down = val[1]; | |
player.command.left = val[2]; | |
player.command.right = val[3]; | |
player.command.lmb = val[4]; | |
player.command.mmb = val[5]; | |
player.command.rmb = val[6]; | |
// Update the thingy | |
socket.timeout.set(commands); | |
} break; | |
case 't': { // player toggle | |
if (m.length !== 1) { socket.kick('Ill-sized toggle.'); return 1; } | |
// Get data | |
let given = '', | |
tog = m[0]; | |
// Verify request | |
if (typeof tog !== 'number') { socket.kick('Weird toggle.'); return 1; } | |
// Decipher what we're supposed to do. | |
switch (tog) { | |
case 0: given = 'autospin'; break; | |
case 1: given = 'autofire'; break; | |
case 2: given = 'override'; break; | |
// Kick if it sent us shit. | |
default: socket.kick('Bad toggle.'); return 1; | |
} | |
// Apply a good request. | |
if (player.command != null && player.body != null) { | |
player.command[given] = !player.command[given]; | |
// Send a message. | |
player.body.sendMessage(given.charAt(0).toUpperCase() + given.slice(1) + ((player.command[given]) ? ' enabled.' : ' disabled.')); | |
} | |
} break; | |
case 'U': { // upgrade request | |
if (m.length !== 1) { socket.kick('Ill-sized upgrade request.'); return 1; } | |
// Get data | |
let number = m[0]; | |
// Verify the request | |
if (typeof number != 'number' || number < 0) { socket.kick('Bad upgrade request.'); return 1; } | |
// Upgrade it | |
if (player.body != null) { | |
player.body.upgrade(number); // Ask to upgrade | |
} | |
} break; | |
case 'x': { // skill upgrade request | |
if (m.length !== 1) { socket.kick('Ill-sized skill request.'); return 1; } | |
let number = m[0], stat = ''; | |
// Verify the request | |
if (typeof number != 'number') { socket.kick('Weird stat upgrade request.'); return 1; } | |
// Decipher it | |
switch (number) { | |
case 0: stat = 'atk'; break; | |
case 1: stat = 'hlt'; break; | |
case 2: stat = 'spd'; break; | |
case 3: stat = 'str'; break; | |
case 4: stat = 'pen'; break; | |
case 5: stat = 'dam'; break; | |
case 6: stat = 'rld'; break; | |
case 7: stat = 'mob'; break; | |
case 8: stat = 'rgn'; break; | |
case 9: stat = 'shi'; break; | |
default: socket.kick('Unknown stat upgrade request.'); return 1; | |
} | |
// Apply it | |
if (player.body != null) { | |
player.body.skillUp(stat); // Ask to upgrade a stat | |
} | |
} break; | |
case 'L': { // level up cheat | |
if (m.length !== 0) { | |
socket.kick('Ill-sized level-up request.'); | |
return 1; | |
} | |
// cheatingbois | |
if (player.body != null && c.SANDBOX_MODE) { | |
if (player.body.skill.level < c.SKILL_CHEAT_CAP || ((socket.key === 'testk' || socket.key === 'testl') && player.body.skill.level < 45)) { | |
player.body.skill.score += player.body.skill.levelScore; | |
player.body.skill.maintain(); | |
player.body.refreshBodyAttributes(); | |
} | |
} | |
} break; | |
case '0': { // testbed cheat | |
if (m.length !== 0) { socket.kick('Ill-sized testbed request.'); return 1; } | |
// cheatingbois | |
if (player.body != null) { if (socket.key === 'testk' || socket.key ==='testl') { | |
player.body.define(Class.testbed); | |
} } | |
} break; | |
case 'z': { // leaderboard desync report | |
if (m.length !== 0) { socket.kick('Ill-sized level-up request.'); return 1; } | |
// Flag it to get a refresh on the next cycle | |
socket.status.needsFullLeaderboard = true; | |
} break; | |
default: socket.kick('Bad packet index.'); | |
} | |
} | |
// Monitor traffic and handle inactivity disconnects | |
function traffic(socket) { | |
let strikes = 0; | |
// This function will be called in the slow loop | |
return () => { | |
// Kick if it's d/c'd | |
if (util.time() - socket.status.lastHeartbeat > c.maxHeartbeatInterval) { | |
socket.kick('Heartbeat lost.'); return 0; | |
} | |
// Add a strike if there's more than 50 requests in a second | |
if (socket.status.requests > 50) { | |
strikes++; | |
} else { | |
strikes = 0; | |
} | |
// Kick if we've had 3 violations in a row | |
if (strikes > 3) { | |
socket.kick('Socket traffic volume violation!'); | |
return 0; | |
} | |
// Reset the requests | |
socket.status.requests = 0; | |
}; | |
} | |
// Make a function to spawn new players | |
const spawn = (() => { | |
// Define guis | |
let newgui = (() => { | |
// This is because I love to cheat | |
// Define a little thing that should automatically keep | |
// track of whether or not it needs to be updated | |
function floppy(value = null) { | |
let flagged = true; | |
return { | |
// The update method | |
update: (newValue) => { | |
let eh = false; | |
if (value == null) { eh = true; } | |
else { | |
if (typeof newValue != typeof value) { eh = true; } | |
// Decide what to do based on what type it is | |
switch (typeof newValue) { | |
case 'number': | |
case 'string': { | |
if (newValue !== value) { eh = true; } | |
} break; | |
case 'object': { | |
if (Array.isArray(newValue)) { | |
if (newValue.length !== value.length) { eh = true; } | |
else { | |
for (let i=0, len=newValue.length; i<len; i++) { | |
if (newValue[i] !== value[i]) eh = true; | |
} | |
} | |
break; | |
} | |
} // jshint ignore:line | |
default: | |
util.error(newValue); | |
throw new Error('Unsupported type for a floppyvar!'); | |
} | |
} | |
// Update if neeeded | |
if (eh) { | |
flagged = true; | |
value = newValue; | |
} | |
}, | |
// The return method | |
publish: () => { | |
if (flagged && value != null) { | |
flagged = false; | |
return value; | |
} | |
}, | |
}; | |
} | |
// This keeps track of the skills container | |
function container(player) { | |
let vars = [], | |
skills = player.body.skill, | |
out = [], | |
statnames = ['atk', 'hlt', 'spd', 'str', 'pen', 'dam', 'rld', 'mob', 'rgn', 'shi']; | |
// Load everything (b/c I'm too lazy to do it manually) | |
statnames.forEach(a => { | |
vars.push(floppy()); | |
vars.push(floppy()); | |
vars.push(floppy()); | |
}); | |
return { | |
update: () => { | |
let needsupdate = false, i = 0; | |
// Update the things | |
statnames.forEach(a => { | |
vars[i++].update(skills.title(a)); | |
vars[i++].update(skills.cap(a)); | |
vars[i++].update(skills.cap(a, true)); | |
}); | |
/* This is a forEach and not a find because we need | |
* each floppy cyles or if there's multiple changes | |
* (there will be), we'll end up pushing a bunch of | |
* excessive updates long after the first and only | |
* needed one as it slowly hits each updated value | |
*/ | |
vars.forEach(e => { | |
if (e.publish() != null) needsupdate = true; | |
}); | |
if (needsupdate) { | |
// Update everything | |
statnames.forEach(a => { | |
out.push(skills.title(a)); | |
out.push(skills.cap(a)); | |
out.push(skills.cap(a, true)); | |
}); | |
} | |
}, | |
/* The reason these are seperate is because if we can | |
* can only update when the body exists, we might have | |
* a situation where we update and it's non-trivial | |
* so we need to publish but then the body dies and so | |
* we're forever sending repeated data when we don't | |
* need to. This way we can flag it as already sent | |
* regardless of if we had an update cycle. | |
*/ | |
publish: () => { | |
if (out.length) { let o = out.splice(0, out.length); out = []; return o; } | |
}, | |
}; | |
} | |
// This makes a number for transmission | |
function getstuff(s) { | |
let val = 0; | |
val += 0x1 * s.amount('atk'); | |
val += 0x10 * s.amount('hlt'); | |
val += 0x100 * s.amount('spd'); | |
val += 0x1000 * s.amount('str'); | |
val += 0x10000 * s.amount('pen'); | |
val += 0x100000 * s.amount('dam'); | |
val += 0x1000000 * s.amount('rld'); | |
val += 0x10000000 * s.amount('mob'); | |
val += 0x100000000 * s.amount('rgn'); | |
val += 0x1000000000 * s.amount('shi'); | |
return val.toString(36); | |
} | |
// These are the methods | |
function update(gui) { | |
let b = gui.master.body; | |
// We can't run if we don't have a body to look at | |
if (!b) return 0; | |
gui.bodyid = b.id; | |
// Update most things | |
gui.fps.update(Math.min(1, global.fps / roomSpeed / 1000 * 30)); | |
gui.color.update(gui.master.teamColor); | |
gui.label.update(b.index); | |
gui.score.update(b.skill.score); | |
//gui.truescore.update(b.skill.truescore) | |
gui.points.update(b.skill.points); | |
// Update the upgrades | |
let upgrades = []; | |
b.upgrades.forEach(function(e) { | |
if (b.skill.level >= e.level) { | |
upgrades.push(e.index); | |
} | |
}); | |
gui.upgrades.update(upgrades); | |
// Update the stats and skills | |
gui.stats.update(); | |
gui.skills.update(getstuff(b.skill)); | |
// Update physics | |
gui.accel.update(b.acceleration); | |
gui.topspeed.update(b.topSpeed); | |
} | |
function publish(gui) { | |
let o = { | |
fps: gui.fps.publish(), | |
label: gui.label.publish(), | |
score: gui.score.publish(), | |
points: gui.points.publish(), | |
upgrades: gui.upgrades.publish(), | |
color: gui.color.publish(), | |
statsdata: gui.stats.publish(), | |
skills: gui.skills.publish(), | |
accel: gui.accel.publish(), | |
top: gui.topspeed.publish(), | |
//truescore: gui.truescore.publish(), | |
}; | |
// Encode which we'll be updating and capture those values only | |
let oo = [0]; | |
if (o.fps != null) { oo[0] += 0x0001; oo.push(o.fps || 1); } | |
if (o.label != null) { | |
oo[0] += 0x0002; | |
oo.push(o.label); | |
oo.push(o.color || gui.master.teamColor); | |
oo.push(gui.bodyid); | |
} | |
if (o.score != null) { oo[0] += 0x0004; oo.push(o.score); } | |
if (o.points != null) { oo[0] += 0x0008; oo.push(o.points); } | |
if (o.upgrades != null) { oo[0] += 0x0010; oo.push(o.upgrades.length, ...o.upgrades); } | |
if (o.statsdata != null){ oo[0] += 0x0020; oo.push(...o.statsdata); } | |
if (o.skills != null) { oo[0] += 0x0040; oo.push(o.skills); } | |
if (o.accel != null) { oo[0] += 0x0080; oo.push(o.accel); } | |
//if (o.truescore != null){ oo[0] += 0x0090; oo.push(o.truescore); } | |
if (o.top != null) { oo[0] += 0x0100; oo.push(o.top); } | |
// Output it | |
return oo; | |
} | |
// This is the gui creator | |
return (player) => { | |
// This is the protected gui data | |
let gui = { | |
master: player, | |
fps: floppy(), | |
label: floppy(), | |
score: floppy(), | |
truescore: floppy(), | |
points: floppy(), | |
upgrades: floppy(), | |
color: floppy(), | |
skills: floppy(), | |
topspeed: floppy(), | |
accel: floppy(), | |
stats: container(player), | |
bodyid: -1, | |
}; | |
// This is the gui itself | |
return { | |
update: () => update(gui), | |
publish: () => publish(gui), | |
}; | |
}; | |
})(); | |
// Define the entities messaging function | |
function messenger(socket, content) { | |
socket.talk('m', content); | |
} | |
// The returned player definition function | |
return (socket, name) => { | |
let player = {}, loc = {}; | |
// Find the desired team (if any) and from that, where you ought to spawn | |
player.team = socket.rememberedTeam; | |
switch (room.gameMode) { | |
case "tdm": { | |
// Count how many others there are | |
let census = [1, 1, 1, 1], scoreCensus = [1, 1, 1, 1]; | |
players.forEach(p => { | |
census[p.team - 1]++; | |
if (p.body != null) { scoreCensus[p.team - 1] += p.body.skill.score; } | |
}); | |
let possiblities = []; | |
for (let i = 0, m = 0; i < 3; i++) { | |
if (i+1===1 || i+1===3) { | |
//only allow two teams | |
let v = Math.round(1000000 * (room['bas' + (i + 1)].length + 1) / (census[i] + 1) / scoreCensus[i]); | |
if (v > m) { | |
m = v; | |
possiblities = [i]; | |
} | |
if (v == m) { | |
possiblities.push(i); | |
} | |
} | |
} | |
// Choose from one of the least ones | |
if (player.team == null) { player.team = ran.choose(possiblities) + 1; } | |
// Make sure you're in a base | |
if (room['bas' + player.team].length) do { loc = room.randomType('bas' + player.team); } while (dirtyCheck(loc, 50)); | |
else do { loc = room.gaussInverse(5); } while (dirtyCheck(loc, 50)); | |
} break; | |
default: do { loc = room.gaussInverse(5); } while (dirtyCheck(loc, 50)); | |
} | |
socket.rememberedTeam = player.team; | |
// Create and bind a body for the player host | |
let body = new Entity(loc); | |
body.protect(); | |
if(c.SPAWN_AS_OBSERVER === true) { | |
body.define(Class.observer) | |
} else { | |
body.define(Class.basic); // Start as a basic tank | |
} | |
body.name = name; // Define the name | |
// Dev hax | |
if (socket.key === 'testl' || socket.key === 'testk') { | |
body.name = "\u0000"; | |
body.define({ CAN_BE_ON_LEADERBOARD: false, }); | |
} | |
body.addController(new io_listenToPlayer(body, player)); // Make it listen | |
body.sendMessage = content => messenger(socket, content); // Make it speak | |
body.socket = socket | |
body.invuln = true; // Make it safe | |
player.body = body; | |
if(socket.spawnLevel) { | |
while (body.skill.level < socket.spawnLevel) { | |
//if (body.skill.score - body.skill.deduction >= body.skill.levelScore) { | |
body.skill.deduction += body.skill.levelScore; | |
body.skill.level += 1; | |
body.skill.points += body.skill.levelPoints; | |
body.skill.update(); | |
//} | |
if (body.skill.level === c.TIER_1 || body.skill.level === c.TIER_2 || body.skill.level === c.TIER_3) { | |
body.skill.canUpgrade = true; | |
} | |
} | |
} | |
// Decide how to color and team the body | |
switch (room.gameMode) { | |
case "tdm": { | |
body.team = -player.team; | |
body.color = [10, 11, 12, 15][player.team - 1]; | |
} break; | |
default: { | |
body.color = (c.RANDOM_COLORS) ? | |
ran.choose([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) : 12; // red | |
} | |
} | |
// Decide what to do about colors when sending updates and stuff | |
player.teamColor = (!c.RANDOM_COLORS && room.gameMode === 'ffa') ? 10 : body.color; // blue | |
// Set up the targeting structure | |
player.target = { | |
x: 0, | |
y: 0 | |
}; | |
// Set up the command structure | |
player.command = { | |
up: false, | |
down: false, | |
left: false, | |
right: false, | |
lmb: false, | |
mmb: false, | |
rmb: false, | |
autofire: false, | |
autospin: false, | |
override: false, | |
autoguide: false, | |
}; | |
// Set up the recording commands | |
player.records = (() => { | |
let begin = util.time(); | |
return () => { | |
return [ | |
player.body.skill.score, | |
Math.floor((util.time() - begin) / 1000), | |
player.body.killCount.solo, | |
player.body.killCount.assists, | |
player.body.killCount.bosses, | |
player.body.killCount.killers.length, | |
...player.body.killCount.killers | |
]; | |
}; | |
})(); | |
// Set up the player's gui | |
player.gui = newgui(player); | |
// Save the the player | |
player.socket = socket; | |
players.push(player); | |
// Focus on the new player | |
socket.camera.x = body.x; socket.camera.y = body.y; socket.camera.fov = 2000; | |
// Mark it as spawned | |
socket.status.hasSpawned = true; | |
body.sendMessage('Welcome to the game.') | |
if (c.MODE !== 'tdm') { | |
body.sendMessage('You are invulnerable until you move or shoot.'); | |
} | |
// Move the client camera | |
socket.talk('c', socket.camera.x, socket.camera.y, socket.camera.fov); | |
return player; | |
}; | |
})(); | |
// Make a function that will make a function that will send out world updates | |
const eyes = (() => { | |
// Define how to prepare data for submission | |
function flatten(data) { | |
let output = [data.type]; // We will remove the first entry in the persepective method | |
if (data.type & 0x01) { | |
output.push( | |
// 1: facing | |
data.facing, | |
// 2: layer | |
data.layer | |
); | |
} else { | |
output.push( | |
// 1: id | |
data.id, | |
// 2: index | |
data.index, | |
// 3: x | |
data.x, | |
// 4: y | |
data.y, | |
// 5: vx | |
data.vx, | |
// 6: vy | |
data.vy, | |
// 7: size | |
data.size, | |
// 8: facing | |
data.facing, | |
// 9: vfacing | |
data.vfacing, | |
// 10: twiggle | |
data.twiggle, | |
// 11: layer | |
data.layer, | |
// 12: color | |
data.color, | |
// 13: health | |
Math.ceil(255 * data.health), | |
// 14: shield | |
Math.round(255 * data.shield) | |
); | |
if (data.type & 0x04) { | |
output.push( | |
// 15: name | |
data.name, | |
// 16: score | |
data.score | |
); | |
} | |
} | |
// Add the gun data to the array | |
let gundata = [data.guns.length]; | |
data.guns.forEach(lastShot => { | |
gundata.push(lastShot.time, lastShot.power); | |
}); | |
output.push(...gundata); | |
// For each turret, add their own output | |
let turdata = [data.turrets.length]; | |
data.turrets.forEach(turret => { turdata.push(...flatten(turret)); }); | |
// Push all that to the array | |
output.push(...turdata); | |
// Return it | |
return output; | |
} | |
function perspective(e, player, data) { | |
if (player.body != null) { | |
if (player.body.id === e.master.id) { | |
data = data.slice(); // So we don't mess up references to the original | |
// Set the proper color if it's on our team | |
data[12] = player.teamColor; | |
// And make it force to our mouse if it ought to | |
if (player.command.autospin) { | |
data[10] = 1; | |
} | |
} | |
} | |
return data; | |
} | |
function check(camera, obj) { | |
return Math.abs(obj.x - camera.x) < camera.fov * 0.6 + 1.5 * obj.size + 100 && | |
Math.abs(obj.y - camera.y) < camera.fov * 0.6 * 0.5625 + 1.5 * obj.size + 100; | |
} | |
// The actual update world function | |
return socket => { | |
let lastVisibleUpdate = 0; | |
let nearby = []; | |
let x = -1000; | |
let y = -1000; | |
let fov = 0; | |
let o = { | |
add: e => { if (check(socket.camera, e)) nearby.push(e); }, | |
remove: e => { let i = nearby.indexOf(e); if (i !== -1) util.remove(nearby, i); }, | |
check: (e, f) => { return check(socket.camera, e); }, //Math.abs(e.x - x) < e.size + f*fov && Math.abs(e.y - y) < e.size + f*fov; }, | |
gazeUpon: () => { | |
logs.network.set(); | |
let player = socket.player, | |
camera = socket.camera; | |
// If nothing has changed since the last update, wait (approximately) until then to update | |
let rightNow = room.lastCycle; | |
if (rightNow === camera.lastUpdate) { | |
socket.update(5 + room.cycleSpeed - util.time() + rightNow); | |
return 1; | |
} | |
// ...elseeeeee... | |
// Update the record. | |
camera.lastUpdate = rightNow; | |
// Get the socket status | |
socket.status.receiving++; | |
// Now prepare the data to emit | |
let setFov = camera.fov; | |
// If we are alive, update the camera | |
if (player.body != null) { | |
// But I just died... | |
if (player.body.isDead()) { | |
socket.status.deceased = true; | |
// Let the client know it died | |
socket.talk('F', ...player.records()); | |
// Remove the body | |
player.body = null; | |
} | |
// I live! | |
else if (player.body.photo) { | |
// Update camera position and motion | |
camera.x = player.body.photo.x; | |
camera.y = player.body.photo.y; | |
camera.vx = player.body.photo.vx; | |
camera.vy = player.body.photo.vy; | |
// Get what we should be able to see | |
setFov = player.body.fov; | |
// Get our body id | |
player.viewId = player.body.id; | |
} | |
} | |
if (player.body == null) { // u dead bro | |
setFov = 2000; | |
} | |
// Smoothly transition view size | |
camera.fov += Math.max((setFov - camera.fov) / 30, setFov - camera.fov); | |
// Update my stuff | |
x = camera.x; y = camera.y; fov = camera.fov; | |
// Find what the user can see. | |
// Update which entities are nearby | |
if (camera.lastUpdate - lastVisibleUpdate > c.visibleListInterval) { | |
// Update our timer | |
lastVisibleUpdate = camera.lastUpdate; | |
// And update the nearby list | |
nearby = entities.map(e => { if (check(socket.camera, e)) return e; }).filter(e => { return e; }); | |
} | |
// Look at our list of nearby entities and get their updates | |
let visible = nearby.map(function mapthevisiblerealm(e) { | |
if (e.photo) { | |
if ( | |
Math.abs(e.x - x) < fov/2 + 1.5*e.size && | |
Math.abs(e.y - y) < fov/2 * (9/16) + 1.5*e.size | |
) { | |
// Grab the photo | |
if (!e.flattenedPhoto) e.flattenedPhoto = flatten(e.photo); | |
return perspective(e, player, e.flattenedPhoto); | |
} | |
} | |
}).filter(e => { return e; }); | |
// Spread it for upload | |
let numberInView = visible.length, | |
view = []; | |
visible.forEach(e => { | |
view.push(...e); | |
}); | |
// Update the gui | |
player.gui.update(); | |
// Send it to the player | |
socket.talk( | |
'u', | |
rightNow, | |
camera.x, | |
camera.y, | |
setFov, | |
camera.vx, | |
camera.vy, | |
...player.gui.publish(), | |
numberInView, | |
...view | |
); | |
// Queue up some for the front util.log if needed | |
if (socket.status.receiving < c.networkFrontlog) { | |
socket.update(Math.max( | |
0, | |
(1000 / c.networkUpdateFactor) - (camera.lastDowndate - camera.lastUpdate), | |
camera.ping / c.networkFrontlog | |
)); | |
} else { | |
socket.update(c.networkFallbackTime); | |
} | |
logs.network.mark(); | |
}, | |
}; | |
views.push(o); | |
return o; | |
}; | |
})(); | |
// Make a function that will send out minimap | |
// and leaderboard updates. We'll also start | |
// the mm/lb updating loop here. It runs at 1Hz | |
// and also kicks inactive sockets | |
const broadcast = (() => { | |
// This is the public information we need for broadcasting | |
let readmap, readlb; | |
// Define fundamental functions | |
const getminimap = (() => { | |
// Build a map cleaner | |
function flatten(data) { | |
// In case it's all filtered away, we'll still have something to work with | |
if (data == null) data = []; | |
let out = [data.length]; | |
// Push it flat | |
data.forEach(d => out.push(...d)); | |
return out; | |
} | |
let cleanmapreader = (() => { | |
function flattener() { | |
let internalmap = []; | |
// Define the flattener | |
// Make a test function | |
function challenge(value, challenger) { | |
return value[1] === challenger[0] && | |
value[2] === challenger[1] && | |
value[3] === challenger[2]; | |
} | |
// Return our functions | |
return { | |
update: (data) => { | |
// Flag all old data as to be removed | |
internalmap.forEach(e => e[0] = -1); | |
// Round all the old data | |
data = data.map(d => { | |
return [ | |
Math.round(255 * util.clamp(d[0] / room.width, 0, 1)), | |
Math.round(255 * util.clamp(d[1] / room.height, 0, 1)), | |
d[2] | |
]; | |
}); | |
// Add new data and stabilze existing data, then emove old data | |
data.forEach(d => { | |
// Find if it's already there | |
let i = internalmap.findIndex(e => { return challenge(e, d); }); | |
if (i === -1) { // if not add it | |
internalmap.push([1, ...d]); | |
} else { // if so, flag it as stable | |
internalmap[i][0] = 0; | |
} | |
}); | |
// Export all new and old data | |
let ex = internalmap.filter(e => e[0] !== 0); | |
// Remove outdated data | |
internalmap = internalmap.filter(e => e[0] !== -1); | |
// Flatten the exports | |
let f = flatten(ex); | |
return f; | |
}, | |
exportall: () => { | |
// Returns a flattened version of the map with blanket add requests | |
return flatten(internalmap.map(e => { return [1, e[1], e[2], e[3]]; })); | |
}, | |
}; | |
} | |
// Define the function | |
return (room.gameMode === 'ffa') ? | |
// ffa function builder | |
(() => { | |
// Make flatteners | |
let publicmap = flattener(); | |
// Return the function | |
return () => { | |
// Updates | |
let clean = publicmap.update(minimap.map(function(entry) { | |
return [entry[1], entry[2], (entry[4] === 'miniboss') ? entry[3] : 17]; | |
})); | |
let full = publicmap.exportall(); | |
// Reader | |
return (team, everything = false) => { return (everything) ? full : clean; }; | |
}; | |
})() : | |
// tdm function builder | |
(() => { | |
// Make flatteners | |
let team1map = flattener(); | |
let team2map = flattener(); | |
let team3map = flattener(); | |
let team4map = flattener(); | |
// Return the function | |
return () => { | |
let updates = {[-1]: [], [-2]: [], [-3]: [], [-4]: [], [-5]: []} | |
for (let entry of minimap) { | |
if (-5 <= entry[5] && entry[5] <= -1) { | |
updates[entry[5]].push([entry[1], entry[2], entry[3]]) | |
} | |
} | |
let clean = [ | |
team1map.update(updates[-1]), | |
team2map.update(updates[-2]), | |
team3map.update(updates[-3]), | |
team4map.update(updates[-4]) | |
] | |
let full = [ | |
team1map.exportall(), | |
team2map.exportall(), | |
team3map.exportall(), | |
team4map.exportall() | |
]; | |
// The reader | |
return (team, everything = false) => { return (everything) ? full[team-1] : clean[team-1]; }; | |
}; | |
})(); | |
})(); | |
let serialized = {[1]:[],[2]:[],[3]:[],[4]:[]} | |
setInterval(() => { | |
let minimaps = {[-1]: new Map(), [-2]: new Map(), [-3]: new Map(), [-4]: new Map(), [-5]: new Map()} | |
entitiesNoFood.forEach((my) => { | |
if ((-5 <= my.team && my.team <= -1 && my.type === 'tank' && my.skill.level >= 15) || | |
(_.has(gamemode, 'shouldBeVisibleOnMinimap') && gamemode.shouldBeVisibleOnMinimap(my))) { | |
minimaps[-1].set(my.id, [Math.round(255 * util.clamp(my.x / room.width, 0, 1)), Math.round(255 * util.clamp(my.y / room.height, 0, 1)), my.color]) | |
} | |
}) | |
for (let [team, minimap] of Object.entries(minimaps)) { | |
let to_flatten = [] | |
for (let e of minimap.values()) { | |
to_flatten.push([1, e[0], e[1], e[2]]) | |
} | |
serialized[team] = flatten(to_flatten) | |
} | |
}, 1000); | |
// Return the builder function. This itself returns | |
// a reader for the map (will change based on team) | |
return () => { | |
return team => serialized[-1] | |
}; | |
})(); | |
const getleaderboard = (() => { | |
let lb = { full: [], updates: [], }; | |
// We'll reuse these lists over and over again | |
let list = new goog.structs.PriorityQueue(); | |
// This puts things in the data structure | |
function listify(instance) { | |
if ( | |
instance.settings.leaderboardable && | |
instance.settings.drawShape && | |
( | |
instance.type === 'tank' || | |
instance.killCount.solo || | |
instance.killCount.assists | |
) | |
) { | |
list.enqueue(1 / (instance.skill.score + 1), instance); | |
} | |
} | |
// Build a function to prepare for export | |
let flatten = (() => { | |
let leaderboard = {}; | |
// Define our index manager | |
let indices = (() => { | |
let data = [], removed = []; | |
// Provide the index manager methods | |
return { | |
flag: () => { | |
data.forEach(index => { | |
index.status = -1; | |
}); | |
if (data == null) { | |
data = []; | |
} | |
}, | |
cull: () => { | |
removed = []; | |
data = data.filter(index => { | |
let doit = index.status === -1; | |
if (doit) removed.push(index.id); | |
return !doit; | |
}); | |
return removed; | |
}, | |
add: id => { | |
data.push({id: id, status: 1,}); | |
}, | |
stabilize: id => { | |
data[data.findIndex(index => { | |
return index.id === id; | |
})].status = 0; | |
}, | |
}; | |
})(); | |
// This processes it | |
let process = (() => { | |
// A helpful thing | |
function barcolor(entry) { | |
switch (entry.team) { | |
case -100: return entry.color; | |
case -1: return 10; | |
case -2: return 11; | |
case -3: return 12; | |
case -4: return 15; | |
default: { | |
if (room.gameMode === 'tdm') return entry.color; | |
return 11; | |
} | |
} | |
} | |
// A shared (and protected) thing | |
function getfull(entry) { | |
return [ | |
-entry.id, | |
Math.round(entry.skill.score), | |
entry.index, | |
entry.name, | |
entry.color, | |
barcolor(entry), | |
]; | |
} | |
return { | |
normal: entry => { | |
// Check if the entry is already there | |
let id = entry.id, | |
score = Math.round(entry.skill.score); | |
let lb = leaderboard['_' + id]; | |
if (lb != null) { | |
// Unflag it for removal | |
indices.stabilize(id); | |
// Figure out if we need to update anything | |
if (lb.score !== score || lb.index !== entry.index) { | |
// If so, update our record first | |
lb.score = score; | |
lb.index = entry.index; | |
// Return it for broadcasting | |
return [ | |
id, | |
score, | |
entry.index, | |
]; | |
} | |
} else { | |
// Record it | |
indices.add(id); | |
leaderboard['_' + id] = { | |
score: score, | |
name: entry.name, | |
index: entry.index, | |
color: entry.color, | |
bar: barcolor(entry), | |
}; | |
// Return it for broadcasting | |
return getfull(entry); | |
} | |
}, | |
full: entry => { return getfull(entry); }, | |
}; | |
})(); | |
// The flattening functions | |
return data => { | |
// Start | |
indices.flag(); | |
// Flatten the orders | |
let orders = data.map(process.normal).filter(e => { return e; }), | |
refresh = data.map(process.full).filter(e => { return e; }), | |
flatorders = [], | |
flatrefresh = []; | |
orders.forEach(e => flatorders.push(...e)); | |
refresh.forEach(e => flatrefresh.push(...e)); | |
// Find the stuff to remove | |
let removed = indices.cull(); | |
// Make sure we sync the leaderboard | |
removed.forEach(id => { delete leaderboard['_' + id]; }); | |
return { | |
updates: [removed.length, ...removed, orders.length, ...flatorders], | |
full: [-1, refresh.length, ...flatrefresh], // The -1 tells the client it'll be a full refresh | |
}; | |
}; | |
})(); | |
// The update function (returns a reader) | |
return () => { | |
list.clear(); | |
// Sort everything | |
entities.forEach(listify); | |
// Get the top ten | |
let topTen = []; | |
for (let i=0; i<10; i++) { | |
// Only if there's anything in the list of course | |
if (list.getCount()) { | |
topTen.push(list.dequeue()); | |
} else { | |
break; | |
} | |
} | |
if (_.has(gamemode, 'customizeLeaderboard')) | |
topTen = gamemode.customizeLeaderboard(topTen) | |
topTen = topTen.filter(e => { return e; }); | |
room.topPlayerID = (topTen.length) ? topTen[0].id : -1; | |
// Remove empty values and process it | |
lb = flatten(topTen); | |
// Return the reader | |
return (full = false) => { | |
return full ? lb.full : lb.updates; | |
}; | |
}; | |
})(); | |
// Define a 1 Hz update loop | |
function slowloop() { | |
// Build the minimap | |
logs.minimap.set(); | |
readmap = getminimap(); | |
// Build the leaderboard | |
readlb = getleaderboard(); | |
logs.minimap.mark(); | |
// Check sockets | |
let time = util.time(); | |
clients.forEach(socket => { | |
if (socket.timeout.check(time)) socket.kick('Kicked for inactivity.'); | |
if (time - socket.statuslastHeartbeat > c.maxHeartbeatInterval) socket.kick('Lost heartbeat.'); | |
}); | |
} | |
// Start it | |
setInterval(slowloop, 1000); | |
// Give the broadcast method | |
return socket => { | |
// Make sure it's spawned first | |
if (socket.status.hasSpawned) { | |
let m = [0], lb = [0, 0]; | |
m = readmap(socket.player.team, socket.status.needsFullMap); | |
socket.status.needsFullMap = false; | |
lb = readlb(socket.status.needsFullLeaderboard); | |
socket.status.needsFullLeaderboard = false; | |
// Don't broadcast if you don't need to | |
if (m !== [0] || lb !== [0, 0]) { socket.talk('b', ...m, ...lb); } | |
} | |
}; | |
})(); | |
// Build the returned function | |
// This function initalizes the socket upon connection | |
return (socket, req) => { | |
// Get information about the new connection and verify it | |
if (c.servesStatic || req.connection.remoteAddress === '::ffff:127.0.0.1' || req.connection.remoteAddress === '::1') { | |
socket.ip = req.headers['x-forwarded-for']; | |
// Make sure we're not banned... | |
if (bannedIPs.findIndex(ip => { return ip === socket.ip; }) !== -1) { | |
socket.terminate(); | |
return 1; | |
} | |
// Make sure we're not already connected... | |
if (!c.servesStatic) { | |
let n = connectedIPs.findIndex(w => { return w.ip === socket.ip; }); | |
if (n !== -1) { | |
// Don't allow more than 2 | |
if (connectedIPs[n].number > 1) { | |
util.warn('Too many connections from the same IP. [' + socket.ip + ']'); | |
socket.terminate(); | |
return 1; | |
} else connectedIPs[n].number++; | |
} else connectedIPs.push({ ip: socket.ip, number: 1, }); | |
} | |
} else { | |
// Don't let banned IPs connect. | |
util.warn(req.connection.remoteAddress); | |
util.warn(req.headers['x-forwarded-for']); | |
socket.terminate(); | |
util.warn('Inappropiate connection request: header spoofing. Socket terminated.'); | |
return 1; | |
} | |
util.log(socket.ip + ' is trying to connect...'); | |
// Set it up | |
socket.binaryType = 'arraybuffer'; | |
socket.key = ''; | |
socket.player = { camera: {}, }; | |
socket.timeout = (() => { | |
let mem = 0; | |
let timer = 0; | |
return { | |
set: val => { if (mem !== val) { mem = val; timer = util.time(); } }, | |
check: time => { return timer && time - timer > c.maxHeartbeatInterval; }, | |
}; | |
})(); | |
// Set up the status container | |
socket.status = { | |
verified: false, | |
receiving: 0, | |
deceased: true, | |
requests: 0, | |
hasSpawned: false, | |
needsFullMap: true, | |
needsFullLeaderboard: true, | |
lastHeartbeat: util.time(), | |
}; | |
// Set up loops | |
socket.loops = (() => { | |
let nextUpdateCall = null; // has to be started manually | |
let trafficMonitoring = setInterval(() => traffic(socket), 1500); | |
let broadcastingGuiStuff = setInterval(() => broadcast(socket), 1000); | |
// Return the loop methods | |
return { | |
setUpdate: timeout => { | |
nextUpdateCall = timeout; | |
}, | |
cancelUpdate: () => { | |
clearTimeout(nextUpdateCall); | |
}, | |
terminate: () => { | |
clearTimeout(nextUpdateCall); | |
clearTimeout(trafficMonitoring); | |
clearTimeout(broadcastingGuiStuff); | |
}, | |
}; | |
})(); | |
// Set up the camera | |
socket.camera = { | |
x: undefined, | |
y: undefined, | |
vx: 0, | |
vy: 0, | |
lastUpdate: util.time(), | |
lastDowndate: undefined, | |
fov: 2000, | |
}; | |
// Set up the viewer | |
socket.makeView = () => { socket.view = eyes(socket); }; | |
socket.makeView(); | |
// Put the fundamental functions in the socket | |
socket.ban = () => ban(socket); | |
socket.kick = reason => kick(socket, reason); | |
socket.talk = (...message) => { | |
if (socket.readyState === socket.OPEN) { | |
socket.send(protocol.encode(message), {binary: true,}); | |
} | |
}; | |
socket.lastWords = (...message) => { | |
if (socket.readyState === socket.OPEN) { | |
socket.send(protocol.encode(message), { binary: true, }, () => setTimeout(() => socket.terminate(), 1000)); | |
} | |
}; | |
socket.on('message', message => incoming(message, socket)); | |
socket.on('close', () => { socket.loops.terminate(); close(socket); }); | |
socket.on('error', e => { util.log('[ERROR]:'); util.error(e); }); | |
// Put the player functions in the socket | |
socket.spawn = name => { return spawn(socket, name); }; | |
// And make an update | |
socket.update = time => { | |
socket.loops.cancelUpdate(); | |
socket.loops.setUpdate(setTimeout(() => { | |
socket.view.gazeUpon(); | |
}, time)); | |
}; | |
// Log it | |
clients.push(socket); | |
util.log('[INFO] New socket opened with ', socket.ip); | |
}; | |
})(), | |
}; | |
})(); | |
/**** GAME SETUP ****/ | |
// Define how the game lives | |
// The most important loop. Fast looping. | |
var gameloop = (() => { | |
// Collision stuff | |
let collide = (() => { | |
function simplecollide(my, n) { | |
let diff = (1 + util.getDistance(my, n) / 2) * roomSpeed; | |
let a = (my.intangibility) ? 1 : my.pushability, | |
b = (n.intangibility) ? 1 : n.pushability, | |
c = 0.05 * (my.x - n.x) / diff, | |
d = 0.05 * (my.y - n.y) / diff; | |
my.accel.x += a / (b + 0.3) * c; | |
my.accel.y += a / (b + 0.3) * d; | |
n.accel.x -= b / (a + 0.3) * c; | |
n.accel.y -= b / (a + 0.3) * d; | |
} | |
function firmcollide(my, n, buffer = 0) { | |
let item1 = { x: my.x + my.m_x, y: my.y + my.m_y, }; | |
let item2 = { x: n.x + n.m_x, y: n.y + n.m_y, }; | |
let dist = util.getDistance(item1, item2); | |
let s1 = Math.max(my.velocity.length, my.topSpeed); | |
let s2 = Math.max(n.velocity.length, n.topSpeed); | |
let strike1, strike2; | |
if (buffer > 0 && dist <= my.realSize + n.realSize + buffer) { | |
let repel = (my.acceleration + n.acceleration) * (my.realSize + n.realSize + buffer - dist) / buffer / roomSpeed; | |
my.accel.x += repel * (item1.x - item2.x) / dist; | |
my.accel.y += repel * (item1.y - item2.y) / dist; | |
n.accel.x -= repel * (item1.x - item2.x) / dist; | |
n.accel.y -= repel * (item1.y - item2.y) / dist; | |
} | |
while (dist <= my.realSize + n.realSize && !(strike1 && strike2)) { | |
strike1 = false; strike2 = false; | |
if (my.velocity.length <= s1) { | |
my.velocity.x -= 0.05 * (item2.x - item1.x) / dist / roomSpeed; | |
my.velocity.y -= 0.05 * (item2.y - item1.y) / dist / roomSpeed; | |
} else { strike1 = true; } | |
if (n.velocity.length <= s2) { | |
n.velocity.x += 0.05 * (item2.x - item1.x) / dist / roomSpeed; | |
n.velocity.y += 0.05 * (item2.y - item1.y) / dist / roomSpeed; | |
} else { strike2 = true; } | |
item1 = { x: my.x + my.m_x, y: my.y + my.m_y, }; | |
item2 = { x: n.x + n.m_x, y: n.y + n.m_y, }; | |
dist = util.getDistance(item1, item2); | |
} | |
} | |
function reflectcollide(wall, bounce) { | |
let delt = new Vector(wall.x - bounce.x, wall.y - bounce.y); | |
let dist = delt.length; | |
let diff = wall.size + bounce.size - dist; | |
if (diff > 0) { | |
bounce.accel.x -= diff * delt.x / dist; | |
bounce.accel.y -= diff * delt.y / dist; | |
return 1; | |
} | |
return 0; | |
} | |
function advancedcollide(my, n, doDamage, doInelastic, nIsFirmCollide = false) { | |
// Prepare to check | |
let tock = Math.min(my.stepRemaining, n.stepRemaining), | |
combinedRadius = n.size + my.size, | |
motion = { | |
_me: new Vector(my.m_x, my.m_y), | |
_n: new Vector(n.m_x, n.m_y), | |
}, | |
delt = new Vector( | |
tock * (motion._me.x - motion._n.x), | |
tock * (motion._me.y - motion._n.y) | |
), | |
diff = new Vector(my.x - n.x, my.y - n.y), | |
dir = new Vector((n.x - my.x) / diff.length, (n.y - my.y) / diff.length), | |
component = Math.max(0, dir.x * delt.x + dir.y * delt.y); | |
if (component >= diff.length - combinedRadius) { // A simple check | |
// A more complex check | |
let goahead = false, | |
tmin = 1 - tock, | |
tmax = 1, | |
A = Math.pow(delt.x, 2) + Math.pow(delt.y, 2), | |
B = 2*delt.x*diff.x + 2*delt.y*diff.y, | |
C = Math.pow(diff.x, 2) + Math.pow(diff.y, 2) - Math.pow(combinedRadius, 2), | |
det = B * B - (4 * A * C), | |
t; | |
if (!A || det < 0 || C < 0) { // This shall catch mathematical errors | |
t = 0; | |
if (C < 0) { // We have already hit without moving | |
goahead = true; | |
} | |
} else { | |
let t1 = (-B - Math.sqrt(det)) / (2*A), | |
t2 = (-B + Math.sqrt(det)) / (2 * A); | |
if (t1 < tmin || t1 > tmax) { // 1 is out of range | |
if (t2 < tmin || t2 > tmax) { // 2 is out of range; | |
t = false; | |
} else { // 1 is out of range but 2 isn't | |
t = t2; goahead = true; | |
} | |
} else { // 1 is in range | |
if (t2 >= tmin && t2 <= tmax) { // They're both in range! | |
t = Math.min(t1, t2); goahead = true; // That means it passed in and then out again. Let's use when it's going in | |
} else { // Only 1 is in range | |
t = t1; goahead = true; | |
} | |
} | |
} | |
/********* PROCEED ********/ | |
if (goahead) { | |
// Add to record | |
my.collisionArray.push(n); | |
n.collisionArray.push(my); | |
if (t) { // Only if we still need to find the collision | |
// Step to where the collision occured | |
my.x += motion._me.x * t; | |
my.y += motion._me.y * t; | |
n.x += motion._n.x * t; | |
n.y += motion._n.y * t; | |
my.stepRemaining -= t; | |
n.stepRemaining -= t; | |
// Update things | |
diff = new Vector(my.x - n.x, my.y - n.y); | |
dir = new Vector((n.x - my.x) / diff.length, (n.y - my.y) / diff.length); | |
component = Math.max(0, dir.x * delt.x + dir.y * delt.y); | |
} | |
let componentNorm = component / delt.length; | |
/************ APPLY COLLISION ***********/ | |
// Prepare some things | |
let reductionFactor = 1, | |
deathFactor = { | |
_me: 1, | |
_n: 1, | |
}, | |
accelerationFactor = (delt.length) ? ( | |
(combinedRadius / 4) / (Math.floor(combinedRadius / delt.length) + 1) | |
) : ( | |
0.001 | |
), | |
depth = { | |
_me: util.clamp((combinedRadius - diff.length) / (2 * my.size), 0, 1), //1: I am totally within it | |
_n: util.clamp((combinedRadius - diff.length) / (2 * n.size), 0, 1), //1: It is totally within me | |
}, | |
combinedDepth = { | |
up: depth._me * depth._n, | |
down: (1-depth._me) * (1-depth._n), | |
}, | |
pen = { | |
_me: { | |
sqr: Math.pow(my.penetration, 2), | |
sqrt: Math.sqrt(my.penetration), | |
}, | |
_n: { | |
sqr: Math.pow(n.penetration, 2), | |
sqrt: Math.sqrt(n.penetration), | |
}, | |
}, | |
savedHealthRatio = { | |
_me: my.health.ratio, | |
_n: n.health.ratio, | |
}; | |
if (doDamage) { | |
let speedFactor = { // Avoid NaNs and infinities | |
_me: (my.maxSpeed) ? ( Math.pow(motion._me.length/my.maxSpeed, 0.25) ) : ( 1 ), | |
_n: (n.maxSpeed) ? ( Math.pow(motion._n.length/n.maxSpeed, 0.25) ) : ( 1 ), | |
}; | |
/********** DO DAMAGE *********/ | |
let bail = false; | |
if (my.shape === n.shape && my.settings.isNecromancer && n.type === 'food') { | |
bail = my.necro(n); | |
} else if (my.shape === n.shape && n.settings.isNecromancer && my.type === 'food') { | |
bail = n.necro(my); | |
} | |
if (!bail) { | |
// Calculate base damage | |
let resistDiff = my.health.resist - n.health.resist, | |
damage = { | |
_me: | |
c.DAMAGE_CONSTANT * | |
my.damage * | |
(1 + resistDiff) * | |
(1 + n.heteroMultiplier * (my.settings.damageClass === n.settings.damageClass)) * | |
((my.settings.buffVsFood && n.settings.damageType === 1) ? 3 : 1 ) * | |
my.damageMultiplier() * | |
Math.min(2, Math.max(speedFactor._me, 1) * speedFactor._me), | |
_n: | |
c.DAMAGE_CONSTANT * | |
n.damage * | |
(1 - resistDiff) * | |
(1 + my.heteroMultiplier * (my.settings.damageClass === n.settings.damageClass)) * | |
((n.settings.buffVsFood && my.settings.damageType === 1) ? 3 : 1) * | |
n.damageMultiplier() * | |
Math.min(2, Math.max(speedFactor._n, 1) * speedFactor._n), | |
}; | |
// Advanced damage calculations | |
if (my.settings.ratioEffects) { | |
damage._me *= Math.min(1, Math.pow(Math.max(my.health.ratio, my.shield.ratio), 1 / my.penetration)); | |
} | |
if (n.settings.ratioEffects) { | |
damage._n *= Math.min(1, Math.pow(Math.max(n.health.ratio, n.shield.ratio), 1 / n.penetration)); | |
} | |
if (my.settings.damageEffects) { | |
damage._me *= | |
accelerationFactor * | |
(1 + (componentNorm - 1) * (1 - depth._n) / my.penetration) * | |
(1 + pen._n.sqrt * depth._n - depth._n) / pen._n.sqrt; | |
} | |
if (n.settings.damageEffects) { | |
damage._n *= | |
accelerationFactor * | |
(1 + (componentNorm - 1) * (1 - depth._me) / n.penetration) * | |
(1 + pen._me.sqrt * depth._me - depth._me) / pen._me.sqrt; | |
} | |
// Find out if you'll die in this cycle, and if so how much damage you are able to do to the other target | |
let damageToApply = { | |
_me: damage._me, | |
_n: damage._n, | |
}; | |
if (n.shield.max) { | |
damageToApply._me -= n.shield.getDamage(damageToApply._me); | |
} | |
if (my.shield.max) { | |
damageToApply._n -= my.shield.getDamage(damageToApply._n); | |
} | |
let stuff = my.health.getDamage(damageToApply._n, false); | |
deathFactor._me = (stuff > my.health.amount) ? my.health.amount / stuff : 1; | |
stuff = n.health.getDamage(damageToApply._me, false); | |
deathFactor._n = (stuff > n.health.amount) ? n.health.amount / stuff : 1; | |
reductionFactor = Math.min(deathFactor._me, deathFactor._n); | |
// Now apply it | |
my.damageRecieved += damage._n * deathFactor._n; | |
n.damageRecieved += damage._me * deathFactor._me; | |
} | |
} | |
/************* DO MOTION ***********/ | |
if (nIsFirmCollide < 0) { | |
nIsFirmCollide *= -0.5; | |
my.accel.x -= nIsFirmCollide * component * dir.x; | |
my.accel.y -= nIsFirmCollide * component * dir.y; | |
n.accel.x += nIsFirmCollide * component * dir.x; | |
n.accel.y += nIsFirmCollide * component * dir.y; | |
} else if (nIsFirmCollide > 0) { | |
n.accel.x += nIsFirmCollide * (component * dir.x + combinedDepth.up); | |
n.accel.y += nIsFirmCollide * (component * dir.y + combinedDepth.up); | |
} else { | |
// Calculate the impulse of the collision | |
let elasticity = 2 - 4 * Math.atan(my.penetration * n.penetration) / Math.PI; | |
if (doInelastic && my.settings.motionEffects && n.settings.motionEffects) { | |
elasticity *= savedHealthRatio._me / pen._me.sqrt + savedHealthRatio._n / pen._n.sqrt; | |
} else { | |
elasticity *= 2; | |
} | |
let spring = 2 * Math.sqrt(savedHealthRatio._me * savedHealthRatio._n) / roomSpeed, | |
elasticImpulse = | |
Math.pow(combinedDepth.down, 2) * | |
elasticity * component * | |
my.mass * n.mass / (my.mass + n.mass), | |
springImpulse = | |
c.KNOCKBACK_CONSTANT * spring * combinedDepth.up, | |
impulse = -(elasticImpulse + springImpulse) * (1 - my.intangibility) * (1 - n.intangibility), | |
force = { | |
x: impulse * dir.x, | |
y: impulse * dir.y, | |
}, | |
modifiers = { | |
_me: c.KNOCKBACK_CONSTANT * my.pushability / my.mass * deathFactor._n, | |
_n: c.KNOCKBACK_CONSTANT * n.pushability / n.mass * deathFactor._me, | |
}; | |
// Apply impulse as force | |
my.accel.x += modifiers._me * force.x; | |
my.accel.y += modifiers._me * force.y; | |
n.accel.x -= modifiers._n * force.x; | |
n.accel.y -= modifiers._n * force.y; | |
} | |
} | |
} | |
} | |
// The actual collision resolution function | |
return collision => { | |
// Pull the two objects from the collision grid | |
let instance = collision[0], | |
other = collision[1]; | |
// Check for ghosts... | |
//if (!instance.activation.check() && !other.activation.check()) { util.warn('Tried to collide with an inactive instance.'); return 0; } | |
// Handle walls | |
if (_.has(gamemode, 'collide') && gamemode.collide(instance, other, Entity, Class, room)) { | |
return | |
} | |
if (instance.type === 'wall' || other.type === 'wall') { | |
let a = (instance.type === 'bullet' || other.type === 'bullet') ? | |
1 + 10 / (Math.max(instance.velocity.length, other.velocity.length) + 10) : | |
1; | |
if (instance.type === 'wall') advancedcollide(instance, other, false, false, a); | |
else advancedcollide(other, instance, false, false, a); | |
} else | |
// If they can firm collide, do that | |
if ((instance.type === 'crasher' && other.type === 'food') || (other.type === 'crasher' && instance.type === 'food')) { | |
firmcollide(instance, other); | |
} else | |
// Otherwise, collide normally if they're from different teams | |
if (instance.team !== other.team) { | |
advancedcollide(instance, other, true, true); | |
} else | |
// Ignore them if either has asked to be | |
if (instance.settings.hitsOwnType === 'never' || other.settings.hitsOwnType === 'never') { | |
// Do jack | |
} else | |
// Standard collision resolution | |
if (instance.settings.hitsOwnType === other.settings.hitsOwnType) { | |
switch (instance.settings.hitsOwnType) { | |
case 'push': advancedcollide(instance, other, false, false); break; | |
case 'hard': firmcollide(instance, other); break; | |
case 'hardWithBuffer': firmcollide(instance, other, 30); break; | |
case 'repel': simplecollide(instance, other); break; | |
} | |
} | |
}; | |
})(); | |
// Living stuff | |
function entitiesliveloop (my) { | |
// Consider death. | |
if (my.contemplationOfMortality()) my.destroy(); | |
else { | |
if (my.bond == null) { | |
// Resolve the physical behavior from the last collision cycle. | |
//logs.physics.set(); | |
my.physics(); | |
//logs.physics.mark(); | |
} | |
if (my.activation.check()) { | |
logs.entities.tally(); | |
// Think about my actions. | |
logs.life.set(); | |
my.life(); | |
logs.life.mark(); | |
// Apply friction. | |
my.friction(); | |
my.confinementToTheseEarthlyShackles(); | |
logs.selfie.set(); | |
my.takeSelfie(); | |
logs.selfie.mark(); | |
} | |
} | |
// Update collisions. | |
my.collisionArray = []; | |
} | |
let time; | |
// Return the loop function | |
return () => { | |
logs.loops.tally(); | |
logs.master.set(); | |
logs.activation.set(); | |
for(let i = 0; i < entities.length; ++i) { | |
// entitiesactivationloop | |
const e = entities[i] | |
e.collisionArray = []; | |
e.activation.update(); | |
e.updateAABB(e.activation.check()); | |
} | |
logs.activation.mark(); | |
// Do collisions | |
logs.collide.set(); | |
if (entities.length > 1) { | |
// Load the grid | |
grid.update(); | |
// Run collisions in each grid | |
grid.queryForCollisionPairs().forEach(collide); | |
} | |
logs.collide.mark(); | |
// Do entities life | |
logs.entities.set(); | |
// the controllers for food only run on the constructor | |
for(let i = 0; i < entities.length; ++i) { | |
entitiesliveloop(entities[i]) | |
} | |
logs.entities.mark(); | |
logs.master.mark(); | |
// Remove dead entities | |
room.lastCycle = util.time(); | |
}; | |
//let expected = 1000 / c.gameSpeed / 30; | |
//let alphaFactor = (delta > expected) ? expected / delta : 1; | |
//roomSpeed = c.gameSpeed * alphaFactor; | |
//setTimeout(moveloop, 1000 / roomSpeed / 30 - delta); | |
})(); | |
// A less important loop. Runs at an actual 5Hz regardless of game speed. | |
var maintainloop = (() => { | |
// Place obstacles | |
function placeRoids() { | |
function placeRoid(type, entityClass) { | |
let x = 0; | |
let position; | |
do { | |
position = room.randomType(type); | |
x++; | |
if (x>200) { util.warn("Could not place some roids."); return 0; } | |
} while (dirtyCheck(position, 10 + entityClass.SIZE)); | |
let o = new Entity(position); | |
o.define(entityClass); | |
o.team = -101; | |
o.facing = ran.randomAngle(); | |
o.protect(); | |
o.life(); | |
} | |
// Start placing them | |
let roidcount = room.roid.length * room.width * room.height / room.xgrid / room.ygrid / 50000 / 1.5; | |
let rockcount = room.rock.length * room.width * room.height / room.xgrid / room.ygrid / 250000 / 1.5; | |
let count = 0; | |
for (let i=Math.ceil(roidcount); i; i--) { count++; placeRoid('roid', Class.obstacle); } | |
for (let i=Math.ceil(roidcount * 0.3); i; i--) { count++; placeRoid('roid', Class.babyObstacle); } | |
for (let i=Math.ceil(rockcount * 0.8); i; i--) { count++; placeRoid('rock', Class.obstacle); } | |
for (let i=Math.ceil(rockcount * 0.5); i; i--) { count++; placeRoid('rock', Class.babyObstacle); } | |
util.log('Placing ' + count + ' obstacles!'); | |
} | |
placeRoids(); | |
// Spawning functions | |
let spawnBosses = (() => { | |
let timer = 0; | |
let boss = (() => { | |
let i = 0, | |
names = [], | |
bois = [Class.egg], | |
n = 0, | |
begin = 'yo some shit is about to move to a lower position', | |
arrival = 'Something happened lol u should probably let Neph know this broke', | |
loc = 'norm'; | |
let spawn = () => { | |
let spot, m = 0; | |
do { | |
spot = room.randomType(loc); m++; | |
} while (dirtyCheck(spot, 500) && m<30); | |
let o = new Entity(spot); | |
o.define(ran.choose(bois)); | |
o.team = -100; | |
o.name = names[i++]; | |
}; | |
return { | |
prepareToSpawn: (classArray, number, nameClass, typeOfLocation = 'norm') => { | |
n = number; | |
bois = classArray; | |
loc = typeOfLocation; | |
names = ran.chooseBossName(nameClass, number); | |
i = 0; | |
if (n === 1) { | |
begin = 'A visitor is coming.'; | |
arrival = names[0] + ' has arrived.'; | |
} else { | |
begin = 'Visitors are coming.'; | |
arrival = ''; | |
for (let i=0; i<n-2; i++) arrival += names[i] + ', '; | |
arrival += names[n-2] + ' and ' + names[n-1] + ' have arrived.'; | |
} | |
}, | |
spawn: () => { | |
sockets.broadcast(begin); | |
for (let i=0; i<n; i++) { | |
setTimeout(spawn, ran.randomRange(3500, 5000)); | |
} | |
// Wrap things up. | |
setTimeout(() => sockets.broadcast(arrival), 5000); | |
util.log('[SPAWN] ' + arrival); | |
}, | |
}; | |
})(); | |
return census => { | |
if (timer > 6000 && ran.dice(16000 - timer)) { | |
util.log('[SPAWN] Preparing to spawn...'); | |
timer = 0; | |
let choice = []; | |
switch (ran.chooseChance(40, 1)) { | |
case 0: | |
choice = [[Class.elite_destroyer], 2, 'a', 'norm']; | |
break; | |
case 1: | |
choice = [[Class.palisade], 1, 'castle', 'norm']; | |
sockets.broadcast('A strange trembling...'); | |
break; | |
} | |
boss.prepareToSpawn(...choice); | |
setTimeout(boss.spawn, 3000); | |
// Set the timeout for the spawn functions | |
} else if (!census.miniboss) timer++; | |
}; | |
})(); | |
let spawnCrasher = census => { | |
if (census.crasher < 50) { | |
let spot, i = 30; | |
do { spot = room.randomType('nest'); i--; if (!i) return 0; } while (dirtyCheck(spot, 100)); | |
let type = Class.crasher; | |
let o = new Entity(spot); | |
o.define(type); | |
o.team = -100; | |
} | |
}; | |
/*spawn dominators in the nestlet spawnDom = census => { | |
if (ran.chance(1 - 0.5 * census.crasher / room.maxFood / room.nestFoodAmount)) { | |
let spot, i = 30; | |
do { spot = room.randomType('nest'); i--; if (!i) return 0; } while (dirtyCheck(spot, 100)); | |
let type = Class.destroyerDominator | |
let o = new Entity(spot); | |
o.define(type); | |
o.team = -100; | |
} | |
};*/ | |
// The NPC function | |
let makenpcs = (() => { | |
// Make base protectors if needed. | |
/*let f = (loc, team) => { | |
let o = new Entity(loc); | |
o.define(Class.baseProtector); | |
o.team = -team; | |
o.color = [10, 11, 12, 15][team-1]; | |
}; | |
for (let i=1; i<5; i++) { | |
room['bas' + i].forEach((loc) => { f(loc, i); }); | |
}*/ | |
// Return the spawning function | |
const labels = ['NE', 'NW', 'MIDDLE', 'SE', 'SW'] | |
let spawnDominator = (loc, index, label) => { | |
let o = new Entity(loc); | |
let kind = ran.choose([Class.destroyerDominator, Class.trapperDominator, Class.gunnerDominator, Class.smasherDominator]) | |
o.define(kind); | |
dominatorControl[index] = -100 | |
o.team = dominatorControl[index]; | |
o.name = label + ' ' + o.label | |
o._dom_kind = kind | |
}; | |
room['dmtr'].forEach((loc, index) => { | |
spawnDominator(loc, labels[index], labels[index]); | |
}); | |
// spawn in the middlemost nest | |
/*room['nest'].forEach((loc, index) => { | |
spawnDominator(loc, 2, 'CENTRAL'); | |
})*/ | |
let bots = []; | |
let botTeamCounts = {[-1]:0,[-2]:0,[-3]:0,[-4]:0} | |
return () => { | |
let census = { | |
crasher: 0, | |
miniboss: 0, | |
tank: 0, | |
}; | |
let npcs = entities.map(function npcCensus(instance) { | |
if (census[instance.type] != null) { | |
census[instance.type]++; | |
return instance; | |
} | |
}).filter(e => { | |
return e; | |
}); | |
// Spawning | |
// spawnDom(census) | |
//spawnCrasher(census) | |
spawnBosses(census); | |
if (c.BOTS !== 0) { | |
if (bots.length < c.BOTS) { | |
// Find a team with one of the lowest player counts (breaks ties towards lower numbered teams) | |
let lowestTeamNo | |
let lowestTeamPlayerCount = Infinity | |
for(let i of c.BOT_TEAMS) { | |
if (botTeamCounts[-i] < lowestTeamPlayerCount) { | |
lowestTeamPlayerCount = botTeamCounts[-i] | |
lowestTeamNo = -i | |
} | |
} | |
let team = lowestTeamNo | |
let o = new Entity(room.randomType('bas'+-team)); | |
// should store the | |
let botClass = Class.randomPlayable() | |
o.define(Class.bot); | |
o.define(botClass); | |
o.gainSpeed = Math.max(ran.gauss(16, 6), 1) | |
o.name = ran.chooseBotName(); | |
o.refreshBodyAttributes() | |
o.team = team | |
o._botClass = botClass | |
const colorteammapping = {0: 10, 1: 11, 2: 12, 3: 15, [-101]: 3}; | |
o.color = colorteammapping[-(o.team) - 1]; | |
if (c.BOTS_START_AT_LEVEL_45) { | |
o.skill.score = 23.6 *1000 | |
} | |
bots.push(o); | |
botTeamCounts[team]++ | |
} | |
// Remove dead ones | |
bots = bots.filter(e => { | |
if (e.isDead()) { | |
botTeamCounts[e.team]-- | |
// respawn bot | |
let team = e.team | |
let o = new Entity(room.randomType('bas'+-team)); | |
let botClass = e._botClass | |
o.define(Class.bot); | |
o.define(botClass); | |
o.gainSpeed = e.gainSpeed | |
o.name = e.name; | |
o.skill.score = Math.min(e.skill.score / 2, 23.7*1000) | |
o.refreshBodyAttributes() | |
// Choose from one of the least ones | |
o.team = team | |
o._botClass = botClass | |
const colorteammapping = {0: 10, 1: 11, 2: 12, 3: 15, [-101]: 3}; | |
o.color = colorteammapping[-(o.team) - 1]; | |
if (c.BOTS_START_AT_LEVEL_45) { | |
o.skill.score = 23.6 *1000 | |
} | |
bots.push(o); | |
botTeamCounts[team]++ | |
return false | |
} else { | |
return true | |
} | |
}); | |
// Slowly upgrade them | |
bots.forEach(o => { | |
if (o.skill.level < 45) { | |
o.skill.score += o.gainSpeed | |
o.skill.maintain(); | |
const skill = ran.choose(Object.entries(skcnv))[0] | |
if (o.skill.upgrade(skill) && o.skill.level > 30) { | |
//console.log(o.name + ' upgraded ' + skill + ' to ' + o.skill.amount(skill)) | |
} | |
} | |
}); | |
} | |
}; | |
})(); | |
// The big food function | |
let makefood = (() => { | |
let foodSpawners = []; | |
// The two essential functions | |
let placeNewFood = (position, scatter, level, allowInNest = false) => { | |
if (room.isInRoomType(position, 'nest')) { | |
level = 3 | |
} | |
let o = nearest(food, position); | |
let mitosis = false; | |
let seed = false; | |
// Find the nearest food and determine if we can do anything with it | |
if (o != null) { | |
for (let i=50; i>0; i--) { | |
if (scatter == -1 || util.getDistance(position, o) < scatter) { | |
if (ran.dice((o.foodLevel + 1) * (o.foodLevel + 1))) { | |
mitosis = true; break; | |
} else { | |
seed = true; break; | |
} | |
} | |
} | |
} | |
// Decide what to do | |
if (scatter != -1 || mitosis || seed) { | |
// Splitting | |
if (o != null && (mitosis || seed) && room.isIn('nest', o) === allowInNest) { | |
let levelToMake = (mitosis) ? o.foodLevel : level, | |
place = { | |
x: o.x + o.size * Math.cos(o.facing), | |
y: o.y + o.size * Math.sin(o.facing), | |
}; | |
let new_o = new Entity(place); | |
new_o.define(getFoodClass(levelToMake)); | |
new_o.team = -100; | |
new_o.facing = o.facing + ran.randomRange(Math.PI/2, Math.PI); | |
new_o.F = food.length | |
food.push(new_o); | |
return new_o; | |
} | |
// Brand new | |
else if (room.isIn('nest', position) === allowInNest) { | |
if (!dirtyCheck(position, 20)) { | |
o = new Entity(position); | |
o.define(getFoodClass(level)); | |
o.team = -100; | |
o.facing = ran.randomAngle(); | |
o.F = food.length | |
food.push(o); | |
return o; | |
} | |
} | |
} | |
}; | |
// Define foodspawners | |
class FoodSpawner { | |
constructor() { | |
this.foodToMake = Math.ceil(Math.abs(ran.gauss(0, room.scale.linear*80))); | |
this.size = Math.sqrt(this.foodToMake) * 25; | |
// Determine where we ought to go | |
let position = {}; let o; | |
do { | |
position = room.gaussRing(1 / 3, 20); | |
o = placeNewFood(position, this.size, 0); | |
} while (o == null); | |
// Produce a few more | |
for (let i=Math.ceil(Math.abs(ran.gauss(0, 4))); i<=0; i--) { | |
placeNewFood(o, this.size, 0); | |
} | |
// Set location | |
this.x = o.x; | |
this.y = o.y; | |
//util.debug('FoodSpawner placed at ('+this.x+', '+this.y+'). Set to produce '+this.foodToMake+' food.'); | |
} | |
rot() { | |
if (--this.foodToMake < 0) { | |
//util.debug('FoodSpawner rotted, respawning.'); | |
util.remove(foodSpawners, foodSpawners.indexOf(this)); | |
foodSpawners.push(new FoodSpawner()); | |
} | |
} | |
} | |
// Add them | |
/*foodSpawners.push(new FoodSpawner()); | |
foodSpawners.push(new FoodSpawner()); | |
foodSpawners.push(new FoodSpawner()); | |
foodSpawners.push(new FoodSpawner());*/ | |
// Food making functions | |
let makeGroupedFood = () => { // Create grouped food | |
// Choose a location around a spawner | |
let spawner = foodSpawners[ran.irandom(foodSpawners.length - 1)], | |
bubble = ran.gaussRing(spawner.size, 1/4); | |
placeNewFood({ x: spawner.x + bubble.x, y: spawner.y + bubble.y, }, -1, 0); | |
spawner.rot(); | |
}; | |
let makeDistributedFood = () => { // Distribute food everywhere | |
//util.debug('Creating new distributed food.'); | |
let spot = {}; | |
do { spot = room.gaussRing(1/2, 2); } while (room.isInNorm(spot)); | |
placeNewFood(spot, 0.01 * room.width, 0); | |
}; | |
let makeAnywhereFood = () => { // distribute food *actually* everywhere | |
let spot = room.random() | |
// todo: calibrate the food level distribution to diep.io levels | |
let level = ran.choose([1, 1, 1, 1, 1, 2, 2, 3]) | |
placeNewFood(spot, 0.01 * room.width, level); | |
} | |
for (let i = 0; i < c.INITIAL_FOOD_AMOUNT; i++) { | |
makeAnywhereFood() | |
} | |
let makeCornerFood = () => { // Distribute food in the corners | |
let spot = {}; | |
do { spot = room.gaussInverse(5); } while (room.isInNorm(spot)); | |
placeNewFood(spot, 0.05 * room.width, 0); | |
}; | |
let makeNestFood = () => { // Make nest pentagons | |
let spot = room.randomType('nest'); | |
placeNewFood(spot, 0.01 * room.width, 3, true); | |
}; | |
if (room.hasType('nest')) { | |
for (let i = 0; i < c.INITIAL_NEST_FOOD_AMOUNT; i++) { | |
makeNestFood() | |
} | |
} | |
// Return the full function | |
return () => { | |
/* | |
// Find and understand all food | |
let census = { | |
[0]: 0, // Egg | |
[1]: 0, // Square | |
[2]: 0, // Triangle | |
[3]: 0, // Penta | |
[4]: 0, // Beta | |
[5]: 0, // Alpha | |
[6]: 0, | |
tank: 0, | |
sum: 0, | |
}; | |
let censusNest = { | |
[0]: 0, // Egg | |
[1]: 0, // Square | |
[2]: 0, // Triangle | |
[3]: 0, // Penta | |
[4]: 0, // Beta | |
[5]: 0, // Alpha | |
[6]: 0, | |
sum: 0, | |
}; | |
// Do the censusNest | |
food = entities.map(instance => { | |
try { | |
if (instance.type === 'tank') { | |
census.tank++; | |
} else if (instance.foodLevel > -1) { | |
if (room.isIn('nest', { x: instance.x, y: instance.y, })) { censusNest.sum++; censusNest[instance.foodLevel]++; } | |
else { census.sum++; census[instance.foodLevel]++; } | |
return instance; | |
} | |
} catch (err) { util.error(instance.label); util.error(err); instance.kill(); } | |
}).filter(e => { return e; }); | |
// Sum it up | |
let maxFood = 1 + room.maxFood + 15 * census.tank; | |
let maxNestFood = 1 + room.maxFood * room.nestFoodAmount; | |
let foodAmount = census.sum; | |
let nestFoodAmount = censusNest.sum; | |
/*********** ROT OLD SPAWNERS **********/ | |
/* | |
foodSpawners.forEach(spawner => { if (ran.chance(1 - foodAmount/maxFood)) spawner.rot(); }); | |
/************** MAKE FOOD **************/ | |
/* | |
while (ran.chance(0.8 * (1 - foodAmount * foodAmount / maxFood / maxFood))) { | |
switch (ran.chooseChance(10, 2, 1)) { | |
case 0: makeGroupedFood(); break; | |
case 1: makeDistributedFood(); break; | |
case 2: makeCornerFood(); break; | |
} | |
} | |
while (ran.chance(0.5 * (1 - nestFoodAmount * nestFoodAmount / maxNestFood / maxNestFood))) makeNestFood(); | |
/************* UPGRADE FOOD ************/ | |
/* | |
if (!food.length) return 0; | |
for (let i=Math.ceil(food.length / 100); i>0; i--) { | |
let o = food[ran.irandom(food.length - 1)], // A random food instance | |
oldId = -1000, | |
overflow, location; | |
// Bounce 6 times | |
for (let j=0; j<6; j++) { | |
overflow = 10; | |
// Find the nearest one that's not the last one | |
do { o = nearest(food, { x: ran.gauss(o.x, 30), y: ran.gauss(o.y, 30), }); | |
} while (o.id === oldId && --overflow); | |
if (!overflow) continue; | |
// Configure for the nest if needed | |
let proportions = c.FOOD, | |
cens = census, | |
amount = foodAmount; | |
if (room.isIn('nest', o)) { | |
proportions = c.FOOD_NEST; | |
cens = censusNest; | |
amount = nestFoodAmount; | |
} | |
// Upgrade stuff | |
o.foodCountup += Math.ceil(Math.abs(ran.gauss(0, 10))); | |
while (o.foodCountup >= (o.foodLevel + 1) * 100) { | |
o.foodCountup -= (o.foodLevel + 1) * 100; | |
if (ran.chance(1 - cens[o.foodLevel + 1] / amount / proportions[o.foodLevel + 1])) { | |
o.define(getFoodClass(o.foodLevel + 1)); | |
} | |
} | |
} | |
}*/ | |
}; | |
})(); | |
// Define food and food spawning | |
gamemode.setup(Entity, Class, room) | |
return () => { | |
// Do stuff | |
makenpcs(); | |
makefood(); | |
// Regen health and update the grid | |
entities.forEach(instance => { | |
if (instance.shield.max) { | |
instance.shield.regenerate(); | |
} | |
if (instance.health.amount) { | |
instance.health.regenerate(instance.shield.max && instance.shield.max === instance.shield.amount); | |
} | |
}); | |
}; | |
})(); | |
// This is the checking loop. Runs at 1Hz. | |
var speedcheckloop = (() => { | |
let fails = 0; | |
// Return the function | |
return () => { | |
let activationtime = logs.activation.sum(), | |
collidetime = logs.collide.sum(), | |
movetime = logs.entities.sum(), | |
playertime = logs.network.sum(), | |
maptime = logs.minimap.sum(), | |
physicstime = logs.physics.sum(), | |
lifetime = logs.life.sum(), | |
bullettime = logs.bullets.sum(), | |
selfietime = logs.selfie.sum(); | |
let sum = logs.master.record(); | |
let loops = logs.loops.count(), | |
active = logs.entities.count(); | |
global.fps = (1000/sum).toFixed(2); | |
if (sum > 1000 / roomSpeed / 30) { | |
//fails++; | |
util.warn('LOOPS: ' + loops + '. ENTITY #: ' + entities.length + '//' + Math.round(active/loops) + '. VIEW #: ' + views.length + '. BACKLOGGED :: ' + (sum * roomSpeed * 3).toFixed(2) + '%!'); | |
util.warn('Total activation time: ' + activationtime); | |
util.warn('Total collision time: ' + collidetime); | |
util.warn('Total cycle time: ' + (movetime - lifetime)); | |
util.warn('Total player update time: ' + playertime); | |
util.warn('Total lb+minimap processing time: ' + maptime); | |
//util.warn('Total entity physics calculation time: ' + physicstime); | |
util.warn('Total entity life+thought cycle time: ' + (lifetime - bullettime)); | |
util.warn('Total entity selfie-taking time: ' + selfietime); | |
util.warn('Total gun+turrent life time: ' + bullettime) | |
util.warn('Total time: ' + (activationtime + collidetime + movetime + playertime + maptime + physicstime + selfietime)); | |
if (fails > 60) { | |
util.error("FAILURE!"); | |
//process.exit(1); | |
} | |
} else { | |
fails = 0; | |
} | |
}; | |
})(); | |
/** BUILD THE SERVERS **/ | |
// Turn the server on | |
var server = http.createServer(app); | |
var websockets = (() => { | |
// Configure the websocketserver | |
let config = { server: server }; | |
if (c.servesStatic) { | |
server.listen(c.port, function httpListening() { | |
util.log((new Date()) + ". Joint HTTP+Websocket server turned on, listening on port "+server.address().port + "."); | |
}); | |
} else { | |
config.port = c.port; | |
util.log((new Date()) + 'Websocket server turned on, listening on port ' + c.port + '.'); | |
} | |
// Build it | |
return new WebSocket.Server(config); | |
})().on('connection', sockets.connect); | |
// Bring it to life | |
setInterval(gameloop, room.cycleSpeed); | |
setInterval(maintainloop, 200); | |
// don't interfere with lineprof's profiling cycle | |
setTimeout(() => | |
setInterval(speedcheckloop, 1000), | |
500 | |
); | |
// Graceful shutdown | |
let shutdownWarning = false; | |
if (process.platform === "win32") { | |
var rl = require("readline").createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}); | |
rl.on("SIGINT", () => { | |
process.emit("SIGINT"); | |
}); | |
} | |
process.on("SIGINT", () => { | |
if (!shutdownWarning) { | |
shutdownWarning = true; | |
sockets.broadcast("The server is shutting down."); | |
util.log('Server going down! Warning broadcasted.'); | |
setTimeout(() => { | |
sockets.broadcast("Arena closed."); | |
util.log('Final warning broadcasted.'); | |
setTimeout(() => { | |
util.warn('Process ended.'); | |
process.exit(); | |
}, 3000); | |
}, 17000); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment