Created
December 15, 2018 14:22
-
-
Save CrazyPython/f1ebf51bec916eeabb8b2220c11c7b34 to your computer and use it in GitHub Desktop.
Barbarossa server
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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