├── shared ├── .eslintrc.yml ├── modulo.js ├── weapon.js ├── convert.js ├── smg.js ├── lmg.js ├── knife.js ├── shotgun.js ├── rapid_fire_weapon.js ├── planet.js ├── shot.js ├── enemy.js └── player.js ├── client ├── .eslintrc.yml ├── controller │ ├── controls │ │ ├── index.js │ │ ├── pointer.js │ │ ├── gamepad.js │ │ └── keyboard.js │ ├── servers.js │ ├── index.js │ ├── audio.js │ ├── chat.js │ ├── settings.js │ ├── socket.js │ ├── history.js │ ├── weapons.js │ ├── loop.js │ └── dialogs.js ├── model │ ├── dialogs.js │ ├── platform.js │ ├── index.js │ ├── game.js │ ├── controls.js │ ├── chat.js │ ├── settings.js │ └── entities.js ├── game │ ├── lmg.js │ ├── smg.js │ ├── knife.js │ ├── readme.md │ ├── shotgun.js │ ├── enemy.js │ ├── shot.js │ ├── weapon.js │ └── planet.js ├── convert.js └── view │ ├── controls │ ├── onscreen.js │ ├── index.js │ └── gamepad.js │ ├── notif.js │ ├── index.js │ ├── url.js │ ├── settings.js │ ├── weapons.js │ ├── servers.js │ ├── search.js │ ├── sorting.js │ ├── history.js │ ├── windowbox.js │ └── hud.js ├── tests ├── .eslintrc.yml └── websocket-fuzzer.js ├── server ├── mods │ ├── example │ │ ├── engine.js │ │ ├── on_message.js │ │ ├── lmg.js │ │ ├── shot.js │ │ ├── enemy.js │ │ ├── knife.js │ │ ├── planet.js │ │ ├── player.js │ │ ├── shotgun.js │ │ ├── rapid_fire_weapon.js │ │ ├── smg.js │ │ └── weapon.js │ └── capture │ │ ├── shot.js │ │ ├── enemy.js │ │ ├── planet.js │ │ ├── rapid_fire_weapon.js │ │ ├── lmg.js │ │ ├── smg.js │ │ ├── knife.js │ │ ├── on_message.js │ │ ├── shotgun.js │ │ ├── engine.js │ │ ├── weapon.js │ │ └── player.js ├── resource_loader.js ├── convert.js ├── proto_mut.js ├── ips.js └── logger.js ├── static ├── assets │ ├── audio │ │ ├── laser.ogg │ │ ├── gunshot.ogg │ │ ├── jetpack.ogg │ │ ├── laser.opus │ │ ├── gunshot.opus │ │ ├── jetpack.opus │ │ ├── interstellar.ogg │ │ ├── step │ │ │ ├── grass_0.ogg │ │ │ ├── grass_1.ogg │ │ │ ├── grass_2.ogg │ │ │ ├── grass_3.ogg │ │ │ ├── grass_4.ogg │ │ │ ├── concrete_0.ogg │ │ │ ├── concrete_1.ogg │ │ │ ├── concrete_2.ogg │ │ │ ├── concrete_3.ogg │ │ │ ├── concrete_4.ogg │ │ │ ├── grass_0.opus │ │ │ ├── grass_1.opus │ │ │ ├── grass_2.opus │ │ │ ├── grass_3.opus │ │ │ ├── grass_4.opus │ │ │ ├── concrete_0.opus │ │ │ ├── concrete_1.opus │ │ │ ├── concrete_2.opus │ │ │ ├── concrete_3.opus │ │ │ └── concrete_4.opus │ │ ├── interstellar.opus │ │ ├── throwingKnife.ogg │ │ └── throwingKnife.opus │ ├── images │ │ ├── background.png │ │ ├── cordonbleu.png │ │ ├── sort_arrow_up.svg │ │ ├── sort_arrow_down.svg │ │ ├── sort_arrow_double.svg │ │ ├── shotgunBall.svg │ │ ├── knife.svg │ │ ├── alienBlue_birthmark.svg │ │ ├── meteorTiny2.svg │ │ ├── jetpackParticle.svg │ │ ├── alienGreen_birthmark.svg │ │ ├── meteorTiny1.svg │ │ ├── alienYellow_mouth_happy.svg │ │ ├── alienBeige_mouth_happy.svg │ │ ├── heartFilled.svg │ │ ├── laserBeam.svg │ │ ├── rifleShot.svg │ │ ├── alienBeige_mouth_unhappy.svg │ │ ├── alienYellow_mouth_unhappy.svg │ │ ├── alienGreen_mouth_surprise.svg │ │ ├── alienPink_birthmark.svg │ │ ├── alienBlue_mouth_happy.svg │ │ ├── alienGreen_mouth_happy.svg │ │ ├── alienGreen_mouth_unhappy.svg │ │ ├── alienYellow_walk1.svg │ │ ├── meteorMed2.svg │ │ ├── alienYellow_walk2.svg │ │ ├── meteorSmall2.svg │ │ ├── alienBeige_walk2.svg │ │ ├── meteorSmall1.svg │ │ ├── alienBlue_mouth_surprise.svg │ │ ├── meteorMed1.svg │ │ ├── alienBeige_walk1.svg │ │ ├── heartNotFilled.svg │ │ ├── alienBlue_mouth_unhappy.svg │ │ ├── alienYellow_mouth_surprise.svg │ │ ├── alienYellow_stand.svg │ │ ├── alienBeige_stand.svg │ │ ├── muzzle.svg │ │ ├── shotgun.svg │ │ ├── alienYellow_jump.svg │ │ ├── alienBeige_jump.svg │ │ ├── alienPink_mouth_surprise.svg │ │ ├── alienYellow_duck.svg │ │ ├── alienBeige_duck.svg │ │ ├── alienBeige_mouth_surprise.svg │ │ ├── alienBlue_duck.svg │ │ ├── alienGreen_duck.svg │ │ ├── alienPink_duck.svg │ │ ├── alienPink_mouth_happy.svg │ │ ├── heartHalfFilled.svg │ │ ├── alienPink_mouth_unhappy.svg │ │ ├── jetpackFire.svg │ │ ├── controls │ │ │ ├── jetpack.svg │ │ │ ├── crouch.svg │ │ │ ├── moveRight.svg │ │ │ ├── moveLeft.svg │ │ │ └── jump.svg │ │ ├── astronaut_helmet.svg │ │ ├── meteorBig3.svg │ │ ├── meteorBig4.svg │ │ ├── github.svg │ │ ├── alienPink_walk1.svg │ │ ├── alienBlue_walk2.svg │ │ ├── alienBlue_walk1.svg │ │ ├── alienPink_walk2.svg │ │ ├── ripple.svg │ │ ├── alienGreen_stand.svg │ │ ├── meteorBig1.svg │ │ ├── alienGreen_jump.svg │ │ ├── alienGreen_walk2.svg │ │ ├── alienGreen_walk1.svg │ │ ├── alienBlue_stand.svg │ │ ├── alienBlue_jump.svg │ │ ├── laserBeamDead.svg │ │ ├── alienPink_stand.svg │ │ ├── alienPink_jump.svg │ │ ├── planet.svg │ │ ├── meteorBig2.svg │ │ ├── enemyGreen2.svg │ │ ├── enemyBlack2.svg │ │ ├── lmg.svg │ │ ├── alienBeige_badge.svg │ │ ├── alienYellow_badge.svg │ │ ├── enemyBlue2.svg │ │ ├── enemyRed2.svg │ │ ├── alienYellow_hurt.svg │ │ ├── alienBlue_badge.svg │ │ ├── alienBeige_hurt.svg │ │ ├── alienGreen_badge.svg │ │ ├── smg.svg │ │ ├── enemyBlack3.svg │ │ ├── enemyGreen3.svg │ │ ├── alienPink_badge.svg │ │ ├── enemyBlue3.svg │ │ ├── enemyRed3.svg │ │ ├── enemyBlack5.svg │ │ ├── enemyGreen5.svg │ │ ├── alienGreen_hurt.svg │ │ ├── enemyGreen1.svg │ │ ├── enemyBlack1.svg │ │ ├── enemyBlue5.svg │ │ ├── enemyRed5.svg │ │ ├── alienBlue_hurt.svg │ │ ├── enemyBlue1.svg │ │ ├── enemyRed1.svg │ │ └── jetpack.svg │ └── fonts │ │ ├── open_sans_300.woff2 │ │ ├── open_sans_400.woff │ │ └── open_sans_400.woff2 ├── manifest.webmanifest └── layout.css ├── .tern-project ├── .babelrc ├── .gitignore ├── .eslintrc.yml ├── package.json ├── doc └── modding.md └── nginx └── jumpsuit.space /shared/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | globals: 2 | resources: false 3 | -------------------------------------------------------------------------------- /client/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | globals: 2 | MasterConnection: false 3 | -------------------------------------------------------------------------------- /tests/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parserOptions: 2 | sourceType: "module" 3 | -------------------------------------------------------------------------------- /server/mods/example/engine.js: -------------------------------------------------------------------------------- 1 | export * from '../capture/engine.js'; 2 | -------------------------------------------------------------------------------- /server/mods/example/on_message.js: -------------------------------------------------------------------------------- 1 | export * from '../capture/on_message.js'; 2 | -------------------------------------------------------------------------------- /server/mods/example/lmg.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/lmg.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/example/shot.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/shot.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/capture/shot.js: -------------------------------------------------------------------------------- 1 | import def from '../../../shared/shot.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/example/enemy.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/enemy.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/example/knife.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/knife.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/example/planet.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/planet.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/example/player.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/player.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/capture/enemy.js: -------------------------------------------------------------------------------- 1 | import def from '../../../shared/enemy.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/capture/planet.js: -------------------------------------------------------------------------------- 1 | import def from '../../../shared/planet.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /server/mods/example/shotgun.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/shotgun.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /static/assets/audio/laser.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/laser.ogg -------------------------------------------------------------------------------- /client/controller/controls/index.js: -------------------------------------------------------------------------------- 1 | import './keyboard.js'; 2 | import './pointer.js'; 3 | import './gamepad.js'; 4 | -------------------------------------------------------------------------------- /static/assets/audio/gunshot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/gunshot.ogg -------------------------------------------------------------------------------- /static/assets/audio/jetpack.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/jetpack.ogg -------------------------------------------------------------------------------- /static/assets/audio/laser.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/laser.opus -------------------------------------------------------------------------------- /server/mods/example/rapid_fire_weapon.js: -------------------------------------------------------------------------------- 1 | import def from '../capture/rapid_fire_weapon.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /static/assets/audio/gunshot.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/gunshot.opus -------------------------------------------------------------------------------- /static/assets/audio/jetpack.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/jetpack.opus -------------------------------------------------------------------------------- /server/mods/capture/rapid_fire_weapon.js: -------------------------------------------------------------------------------- 1 | import def from '../../../shared/rapid_fire_weapon.js'; 2 | export default def; 3 | -------------------------------------------------------------------------------- /static/assets/audio/interstellar.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/interstellar.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/grass_0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_0.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/grass_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_1.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/grass_2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_2.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/grass_3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_3.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/grass_4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_4.ogg -------------------------------------------------------------------------------- /static/assets/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/images/background.png -------------------------------------------------------------------------------- /static/assets/images/cordonbleu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/images/cordonbleu.png -------------------------------------------------------------------------------- /static/assets/audio/interstellar.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/interstellar.opus -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_0.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_1.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_2.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_3.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_4.ogg -------------------------------------------------------------------------------- /static/assets/audio/step/grass_0.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_0.opus -------------------------------------------------------------------------------- /static/assets/audio/step/grass_1.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_1.opus -------------------------------------------------------------------------------- /static/assets/audio/step/grass_2.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_2.opus -------------------------------------------------------------------------------- /static/assets/audio/step/grass_3.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_3.opus -------------------------------------------------------------------------------- /static/assets/audio/step/grass_4.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/grass_4.opus -------------------------------------------------------------------------------- /static/assets/audio/throwingKnife.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/throwingKnife.ogg -------------------------------------------------------------------------------- /static/assets/audio/throwingKnife.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/throwingKnife.opus -------------------------------------------------------------------------------- /static/assets/fonts/open_sans_300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/fonts/open_sans_300.woff2 -------------------------------------------------------------------------------- /static/assets/fonts/open_sans_400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/fonts/open_sans_400.woff -------------------------------------------------------------------------------- /static/assets/fonts/open_sans_400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/fonts/open_sans_400.woff2 -------------------------------------------------------------------------------- /client/model/dialogs.js: -------------------------------------------------------------------------------- 1 | export let modalOpen = false; 2 | 3 | export function setIsModalOpen(bool) { 4 | modalOpen = bool; 5 | } 6 | -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_0.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_0.opus -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_1.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_1.opus -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_2.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_2.opus -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_3.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_3.opus -------------------------------------------------------------------------------- /static/assets/audio/step/concrete_4.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KordonBleu/jumpsuit/HEAD/static/assets/audio/step/concrete_4.opus -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "browser" 4 | ], 5 | "plugins": { 6 | "node": {}, 7 | "es_modules": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": [ 4 | ["module-resolver", { "alias": { "<@convert@>": "./server/convert.js" } }] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /shared/modulo.js: -------------------------------------------------------------------------------- 1 | export default function(dividend, divisor) { 2 | return (dividend + divisor*Math.ceil(Math.abs(dividend / divisor))) % divisor; 3 | } 4 | -------------------------------------------------------------------------------- /static/assets/images/sort_arrow_up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/game/lmg.js: -------------------------------------------------------------------------------- 1 | import Lmg from '../../shared/lmg.js'; 2 | 3 | Lmg.prototype.offsetX = 13; 4 | Lmg.prototype.offsetY = -15; 5 | 6 | export default Lmg; 7 | -------------------------------------------------------------------------------- /client/game/smg.js: -------------------------------------------------------------------------------- 1 | import Smg from '../../shared/smg.js'; 2 | 3 | Smg.prototype.offsetX = 13; 4 | Smg.prototype.offsetY = -3; 5 | 6 | export default Smg; 7 | -------------------------------------------------------------------------------- /client/game/knife.js: -------------------------------------------------------------------------------- 1 | import Knife from '../../shared/knife.js'; 2 | 3 | Knife.prototype.offsetX = 23; 4 | Knife.prototype.offsetY = -20; 5 | 6 | export default Knife; 7 | -------------------------------------------------------------------------------- /server/mods/capture/lmg.js: -------------------------------------------------------------------------------- 1 | import Lmg from '../../../shared/lmg.js'; 2 | 3 | Lmg.prototype.muzzleX = 81; 4 | Lmg.prototype.muzzleY = 6; 5 | 6 | export default Lmg; 7 | -------------------------------------------------------------------------------- /server/mods/capture/smg.js: -------------------------------------------------------------------------------- 1 | import Smg from '../../../shared/smg.js'; 2 | 3 | Smg.prototype.muzzleX = 58; 4 | Smg.prototype.muzzleY = -2; 5 | 6 | export default Smg; 7 | -------------------------------------------------------------------------------- /server/mods/example/smg.js: -------------------------------------------------------------------------------- 1 | import Smg from '../../../shared/smg.js'; 2 | 3 | Smg.prototype.muzzleX = 58; 4 | Smg.prototype.muzzleY = -2; 5 | 6 | export default Smg; 7 | -------------------------------------------------------------------------------- /static/assets/images/sort_arrow_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/mods/capture/knife.js: -------------------------------------------------------------------------------- 1 | import Knife from '../../../shared/knife.js'; 2 | 3 | Knife.prototype.muzzleX = 23; 4 | Knife.prototype.muzzleY = 0; 5 | 6 | export default Knife; 7 | -------------------------------------------------------------------------------- /client/controller/servers.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | import * as socket from './socket.js'; 3 | 4 | view.servers.bindPlay(slaveCo => { 5 | socket.makeNewCurrentConnection(slaveCo); 6 | }); 7 | -------------------------------------------------------------------------------- /static/assets/images/sort_arrow_double.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shared/weapon.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | constructor(owner) { 3 | this.owner = owner; 4 | this.recoil; // right now only used by the client but should also be used by the server to shit the shot's initial position sligtly 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /server/mods/capture/on_message.js: -------------------------------------------------------------------------------- 1 | export function onControls(player, controlsObj, angle) { 2 | player.aimAngle = angle; 3 | for (let i in controlsObj) { 4 | if (player.controls[i] !== 2 || controlsObj[i] === 0) player.controls[i] = controlsObj[i]; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /shared/convert.js: -------------------------------------------------------------------------------- 1 | export function radToBrad(rad, precision) { 2 | return Math.round(rad/(2*Math.PI) * ((1 << precision*8) - 1)); 3 | } 4 | export function bradToRad(brad, precision) { 5 | return brad/((1 << precision*8) - 1) * (2*Math.PI); 6 | } 7 | 8 | export * from '<@convert@>'; 9 | -------------------------------------------------------------------------------- /shared/smg.js: -------------------------------------------------------------------------------- 1 | import RapidFireWeapon from '<@RapidFireWeapon@>'; 2 | 3 | export default class Smg extends RapidFireWeapon { 4 | constructor(owner) { 5 | super(owner); 6 | } 7 | } 8 | Smg.prototype.cycleLength = 5; 9 | Smg.prototype.spray = 0.04; 10 | 11 | Smg.prototype.type = 'Smg'; 12 | -------------------------------------------------------------------------------- /shared/lmg.js: -------------------------------------------------------------------------------- 1 | import RapidFireWeapon from '<@RapidFireWeapon@>'; 2 | 3 | export default class Lmg extends RapidFireWeapon { 4 | constructor(owner) { 5 | super(owner); 6 | } 7 | } 8 | Lmg.prototype.cycleLength = 9; 9 | Lmg.prototype.spray = 0.025; 10 | 11 | Lmg.prototype.type = 'Lmg'; 12 | -------------------------------------------------------------------------------- /client/convert.js: -------------------------------------------------------------------------------- 1 | export function stringToBuffer(string) { 2 | let encoder = new TextEncoder('utf8'); 3 | 4 | return encoder.encode(string); 5 | } 6 | export function bufferToString(arrayBuffer) { 7 | let decoder = new TextDecoder('utf8'); 8 | 9 | return decoder.decode(arrayBuffer); 10 | } 11 | -------------------------------------------------------------------------------- /client/model/platform.js: -------------------------------------------------------------------------------- 1 | export const isMobile = navigator.userAgent.match(/(?:Android)|(?:webOS)|(?:iPhone)|(?:iPad)|(?:iPod)|(?:BlackBerry)|(?:Windows Phone)/i), 2 | isUnsupported = !navigator.userAgent.match(/(?:Firefox)|(?:Chrome)/i), 3 | supportsGamepad = 'ongamepadconnected' in window || 'ongamepaddisconnected' in window; 4 | -------------------------------------------------------------------------------- /shared/knife.js: -------------------------------------------------------------------------------- 1 | import Weapon from '<@Weapon@>'; 2 | import Shot from './shot.js'; 3 | 4 | export default class Knife extends Weapon { 5 | constructor(owner) { 6 | super(owner); 7 | } 8 | } 9 | Knife.prototype.spray = 0.005; 10 | Knife.prototype.shotType = Shot.prototype.TYPES.KNIFE; 11 | 12 | Knife.prototype.type = 'Knife'; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | npm-debug.log 3 | 4 | /static/bundle.js 5 | /master_server_bundle.js 6 | /game_server_bundle.js 7 | 8 | /build_config.json 9 | /game_config.json 10 | /master_config.json 11 | 12 | /server/mods/* 13 | !/server/mods/capture 14 | !/server/mods/capture/* 15 | !/server/mods/example 16 | !/server/mods/example/* 17 | -------------------------------------------------------------------------------- /shared/shotgun.js: -------------------------------------------------------------------------------- 1 | import Weapon from '<@Weapon@>'; 2 | import Shot from './shot.js'; 3 | 4 | export default class Shotgun extends Weapon { 5 | constructor(owner) { 6 | super(owner); 7 | } 8 | } 9 | Shotgun.prototype.spray = 0.05; 10 | Shotgun.prototype.shotType = Shot.prototype.TYPES.BALL; 11 | 12 | Shotgun.prototype.type = 'Shotgun'; 13 | -------------------------------------------------------------------------------- /server/mods/capture/shotgun.js: -------------------------------------------------------------------------------- 1 | import Shotgun from '../../../shared/shotgun.js'; 2 | 3 | Shotgun.fire = function fire() { 4 | let shots = []; 5 | for (let i = -1; i !== 1; ++i) { 6 | shots = shots.concat(this.prototype.fire.call(this, i*0.12)); 7 | } 8 | 9 | return shots; 10 | }; 11 | 12 | Shotgun.prototype.muzzleX = 84; 13 | Shotgun.prototype.muzzleY = 2; 14 | 15 | export default Shotgun; 16 | -------------------------------------------------------------------------------- /server/resource_loader.js: -------------------------------------------------------------------------------- 1 | import { getFinalResNames } from '../shared/resource_list.js'; 2 | const sizeOf = require('image-size'); 3 | 4 | let resources = {}; 5 | 6 | getFinalResNames((baseName, variants) => { 7 | resources[baseName] = sizeOf('../static/assets/images/' + baseName + '.svg'); 8 | 9 | for (let variant in variants) resources[variant] = resources[baseName]; 10 | }); 11 | 12 | export default resources; 13 | -------------------------------------------------------------------------------- /shared/rapid_fire_weapon.js: -------------------------------------------------------------------------------- 1 | import Weapon from '<@Weapon@>'; 2 | import Shot from './shot.js'; 3 | 4 | export default class RapidFireWeapon extends Weapon { 5 | constructor(owner) { 6 | super(owner); 7 | this.cycle = 0; 8 | } 9 | canRapidFire() { 10 | this.cycle = ++this.cycle % this.cycleLength; 11 | return this.cycle === 0; 12 | } 13 | } 14 | RapidFireWeapon.prototype.shotType = Shot.prototype.TYPES.BULLET; 15 | -------------------------------------------------------------------------------- /server/convert.js: -------------------------------------------------------------------------------- 1 | export function stringToBuffer(string) { 2 | let buf = Buffer.from(string, 'utf8'); 3 | 4 | return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); 5 | } 6 | export function bufferToString(arrayBuffer) { 7 | let StringDecoder = require('string_decoder').StringDecoder, 8 | decoder = new StringDecoder('utf8'), 9 | tmpBuf = Buffer.from(arrayBuffer); 10 | 11 | return decoder.write(tmpBuf); 12 | } 13 | -------------------------------------------------------------------------------- /client/controller/index.js: -------------------------------------------------------------------------------- 1 | import './audio.js'; 2 | import './chat.js'; 3 | import './dialogs.js'; 4 | import './history.js'; 5 | import './controls/index.js'; 6 | import './settings.js'; 7 | import './servers.js'; 8 | import './weapons.js'; 9 | import './socket.js'; 10 | import './connection.js'; 11 | 12 | 13 | import resPromise from '../model/resource_loader.js'; 14 | 15 | resPromise.then((resources) => { 16 | window.resources = resources; 17 | }); 18 | -------------------------------------------------------------------------------- /server/proto_mut.js: -------------------------------------------------------------------------------- 1 | //TODO: this better, if possible 2 | Array.prototype.actualLength = function() { 3 | let value = 0; 4 | for (let entry of this) if (entry !== undefined) value++; 5 | return value; 6 | }; 7 | Array.prototype.append = function(item) { 8 | for (let i = 0; i !== this.length; i++) { 9 | if (this[i] === null || this[i] === undefined) { 10 | this[i] = item; 11 | return i; 12 | } 13 | } 14 | return this.push(item) - 1; 15 | }; 16 | -------------------------------------------------------------------------------- /static/assets/images/shotgunBall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | commonjs: true 4 | es6: true 5 | node: true 6 | extends: 'eslint:recommended' 7 | rules: 8 | indent: 9 | - error 10 | - tab 11 | - SwitchCase: 1 12 | linebreak-style: 13 | - error 14 | - unix 15 | quotes: 16 | - error 17 | - single 18 | semi: 19 | - error 20 | - always 21 | no-trailing-spaces: "error" 22 | no-console: 0 23 | parserOptions: 24 | sourceType: "module" 25 | -------------------------------------------------------------------------------- /static/assets/images/knife.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/model/index.js: -------------------------------------------------------------------------------- 1 | import * as chat from './chat.js'; 2 | import * as dialogs from './dialogs.js'; 3 | import * as controls from './controls.js'; 4 | import * as entities from './entities.js'; 5 | import * as game from './game.js'; 6 | import * as platform from './platform.js'; 7 | import * as resources from './resource_loader.js'; 8 | import settings from './settings.js'; 9 | 10 | export { chat, controls, dialogs, entities, game, platform, resources, settings }; 11 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_birthmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorTiny2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/jetpackParticle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/game/readme.md: -------------------------------------------------------------------------------- 1 | This directory contains files that cannot be fit into the MVC, or files that would slow down a refactor process if they were to be integrated. 2 | 3 | The problem with these file is that they fall both into the model (positional data for example) and into the view (draw() method), but because of inheritance it is impossible to split them. Using the [ECS pattern](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system) and thus composition would fix this. 4 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_birthmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorTiny1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/controls/onscreen.js: -------------------------------------------------------------------------------- 1 | export function hide() { 2 | [].forEach.call(document.getElementById('gui-controls').querySelectorAll('img'), function(element) { 3 | element.removeAttribute('style'); 4 | }); 5 | } 6 | export function displayJetpack() { 7 | document.getElementById('jump').setAttribute('src', '/assets/images/controls/jetpack.svg'); 8 | } 9 | export function displayJump() { 10 | document.getElementById('jump').setAttribute('src', '/assets/images/controls/jump.svg'); 11 | } 12 | -------------------------------------------------------------------------------- /static/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JumpSuit", 3 | "short_name": "JumpSuit", 4 | "start_url": ".", 5 | "scope": "/", 6 | "display": "standalone", 7 | "background_color": "#121012", 8 | "theme_color": "#ad3726", 9 | "description": "Multiplayer shooter game - Take control of the galaxy!", 10 | "orientation": "landscape", 11 | "lang": "en-US", 12 | "dir": "ltr", 13 | "icons": [{ 14 | "src": "assets/images/cordonbleu.png", 15 | "sizes": "64x64", 16 | "type": "image/png" 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /client/model/game.js: -------------------------------------------------------------------------------- 1 | export let ownIdx = null, 2 | state = null, 3 | scores = null, 4 | ownHealth = null, 5 | ownFuel = null; 6 | 7 | export function setState(newState) { 8 | state = newState; 9 | } 10 | export function setOwnIdx(newOwnIdx) { 11 | ownIdx = newOwnIdx; 12 | } 13 | export function setOwnHealth(newOwnHealth) { 14 | ownHealth = newOwnHealth; 15 | } 16 | export function setOwnFuel(newOwnFuel) { 17 | ownFuel = newOwnFuel; 18 | } 19 | export function setScores(newScores) { 20 | scores = newScores; 21 | } 22 | -------------------------------------------------------------------------------- /shared/planet.js: -------------------------------------------------------------------------------- 1 | import vinage from 'vinage'; 2 | 3 | export default class Planet { 4 | constructor(x, y, radius, type) { 5 | this.box = new vinage.Circle(new vinage.Point(x, y), radius); 6 | this.atmosBox = new vinage.Circle(this.box.center, Math.floor(radius * (1.5 + Math.random()/2))); 7 | this.resetProgress(); 8 | this.type = type || Math.round(Math.random()); 9 | } 10 | resetProgress() { 11 | this.progress = 0; 12 | this.team = 'neutral'; 13 | } 14 | } 15 | Planet.prototype.typeEnum = { 16 | CONCRETE: 0, 17 | GRASS: 1 18 | }; 19 | -------------------------------------------------------------------------------- /client/game/shotgun.js: -------------------------------------------------------------------------------- 1 | import Shotgun from '../../shared/shotgun.js'; 2 | import * as model from '../model/index.js'; 3 | 4 | export default class CltShotgun extends Shotgun { 5 | constructor(owner) { 6 | super(owner); 7 | } 8 | draw(context, isMe) { 9 | Object.getPrototypeOf(Object.getPrototypeOf(this)).draw.call(this, context, isMe); 10 | if (model.settings.particles === 'true' && this.muzzleFlash === true) { 11 | this.recoil = 27; 12 | } 13 | } 14 | } 15 | 16 | CltShotgun.prototype.offsetX = 13; 17 | CltShotgun.prototype.offsetY = -5; 18 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_mouth_happy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/controller/audio.js: -------------------------------------------------------------------------------- 1 | import * as model from '../model/index.js'; 2 | import * as view from '../view/index.js'; 3 | 4 | // callbacks called by the view 5 | view.audio.bindSfxVolChange(volume => { 6 | model.settings.volEffects = volume; 7 | view.audio.setSfxGain(volume); 8 | }); 9 | view.audio.bindMusicVolChange(volume => { 10 | model.settings.volMusic = volume; 11 | view.audio.setMusicGain(volume); 12 | }); 13 | 14 | // initialisation 15 | view.audio.setSfxGain(parseInt(model.settings.volEffects, 10)); 16 | view.audio.setMusicGain(parseInt(model.settings.volMusic, 10)); 17 | -------------------------------------------------------------------------------- /shared/shot.js: -------------------------------------------------------------------------------- 1 | import vinage from 'vinage'; 2 | import resources from '../server/resource_loader.js'; 3 | 4 | export default function Shot(x, y, angle, origin, type) { 5 | this.box = new vinage.Rectangle(new vinage.Point(x, y), resources['laserBeam'].width, resources['laserBeam'].height, angle); 6 | this.lifeTime = 100; 7 | this.origin = origin; 8 | this.type = type || 0; 9 | } 10 | Shot.prototype.TYPES = { 11 | LASER: 0, 12 | BULLET: 1, 13 | KNIFE: 2, //a knife is no shot but can be handled the same way 14 | BALL: 3 15 | }; 16 | Shot.prototype.speed = [30, 25, 13, 22]; 17 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_mouth_happy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/heartFilled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/laserBeam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/rifleShot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_mouth_unhappy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_mouth_unhappy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_mouth_surprise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_birthmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/websocket-fuzzer.js: -------------------------------------------------------------------------------- 1 | if (process.argv[2] === undefined) { 2 | console.log("Please specify the location"); 3 | process.exit(1); 4 | } 5 | 6 | let WebSocket = require('ws'); 7 | let ws = new WebSocket('ws://' + process.argv[2]); 8 | 9 | ws.on('open', function open() { 10 | setInterval(function() { 11 | let array = new Uint8Array(1 + Math.round(Math.random()*99)); 12 | 13 | for (let i = 0; i !== array.byteLength; ++i) { 14 | array[i] = Math.round(Math.random()*255); 15 | } 16 | console.log(array); 17 | 18 | ws.send(array.buffer, { binary: true, mask: true }); 19 | }, 0); 20 | }); 21 | -------------------------------------------------------------------------------- /server/mods/capture/engine.js: -------------------------------------------------------------------------------- 1 | export * from '../../../shared/engine.js'; 2 | 3 | export function doPhysicsClient(universe, planets, shots, players) { 4 | shots.forEach(function(shot, si) { 5 | if (--shot.lifeTime === 0 || 6 | players.some(function(player) { if (player.pid !== shot.origin && universe.collide(shot.box, player.box)) return true; }) || 7 | planets.some(function(planet) { if (universe.collide(shot.box, planet.box)) return true; })) shots.splice(si, 1); 8 | //delete shot, if lifetime equals 0 OR collision with a player that hasn't shot the shot OR collision with a planet 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_mouth_happy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_mouth_happy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_mouth_unhappy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_walk1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorMed2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_walk2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorSmall2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_walk2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorSmall1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_mouth_surprise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorMed1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /shared/enemy.js: -------------------------------------------------------------------------------- 1 | import vinage from 'vinage'; 2 | import resources from '../server/resource_loader.js'; 3 | 4 | export default class Enemy { 5 | constructor(x, y, appearance) { 6 | this.appearance = appearance || 'enemy' + this.resources[Math.floor(Math.random() * this.resources.length)]; 7 | this.box = new vinage.Rectangle(new vinage.Point(x, y), resources[this.appearance].width, resources[this.appearance].height); 8 | this.aggroBox = new vinage.Circle(new vinage.Point(x, y), 350); 9 | this.fireRate = 0; 10 | } 11 | } 12 | Enemy.prototype.resources = ['Black1', 'Black2', 'Black3', 'Black4', 'Black5', 'Blue1', 'Blue2', 'Blue3', 'Green1', 'Green2', 'Red1', 'Red2', 'Red3']; 13 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_walk1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/game/enemy.js: -------------------------------------------------------------------------------- 1 | import Enemy from '../../shared/enemy.js'; 2 | 3 | export default class extends Enemy { 4 | constructor(x, y, appearance) { 5 | super(x, y, appearance); 6 | } 7 | draw(context, windowBox) { 8 | windowBox.drawRotatedImage(context, 9 | window.resources[this.appearance], 10 | windowBox.wrapX(this.box.center.x), 11 | windowBox.wrapY(this.box.center.y), 12 | this.box.angle 13 | ); 14 | } 15 | drawAtmos(context, windowBox) { 16 | context.fillStyle = '#aaa'; 17 | context.strokeStyle = context.fillStyle; 18 | 19 | windowBox.strokeAtmos( 20 | context, 21 | windowBox.wrapX(this.box.center.x), 22 | windowBox.wrapY(this.box.center.y), 23 | 350, 4 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /static/assets/images/heartNotFilled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /server/mods/capture/weapon.js: -------------------------------------------------------------------------------- 1 | import Weapon from '../../../shared/weapon.js'; 2 | import Shot from './shot.js'; 3 | 4 | export default class extends Weapon { 5 | fire(angleOffset) { 6 | let shift = this.owner.looksLeft ? -1 : 1, 7 | inaccuracy = (2*Math.random()-1)*this.spray; 8 | 9 | let shotX = this.owner.box.center.x + this.muzzleX * Math.sin(this.owner.aimAngle) + this.muzzleY * shift * Math.sin(this.owner.aimAngle - Math.PI / 2), 10 | shotY = this.owner.box.center.y - this.muzzleX * Math.cos(this.owner.aimAngle) - this.muzzleY * shift * Math.cos(this.owner.aimAngle - Math.PI / 2); 11 | 12 | return [new Shot(shotX, shotY, this.owner.aimAngle + (angleOffset === undefined ? 0 : angleOffset) + inaccuracy, this.owner.pid, this.shotType)]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/mods/example/weapon.js: -------------------------------------------------------------------------------- 1 | import Weapon from '../../../shared/weapon.js'; 2 | import Shot from './shot.js'; 3 | 4 | export default class extends Weapon { 5 | fire(angleOffset) { 6 | let shift = this.owner.looksLeft ? -1 : 1, 7 | inaccuracy = (2*Math.random()-1)*this.spray; 8 | 9 | let shotX = this.owner.box.center.x + this.muzzleX * Math.sin(this.owner.aimAngle) + this.muzzleY * shift * Math.sin(this.owner.aimAngle - Math.PI / 2), 10 | shotY = this.owner.box.center.y - this.muzzleX * Math.cos(this.owner.aimAngle) - this.muzzleY * shift * Math.cos(this.owner.aimAngle - Math.PI / 2); 11 | 12 | return [new Shot(shotX, shotY, this.owner.aimAngle + (angleOffset === undefined ? 0 : angleOffset) + inaccuracy, this.owner.pid, this.shotType)]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_mouth_unhappy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_mouth_surprise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/controller/chat.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | import * as model from '../model/chat.js'; 3 | import * as wsClt from './socket.js'; 4 | 5 | 6 | view.chat.bindChatKeyDown((key, value, selectionStart, selectionEnd) => { // TODO: much of this should go to the model 7 | switch (key) { 8 | case 'Enter': 9 | if (!wsClt.currentConnection.alive()) return; 10 | wsClt.currentConnection.sendChat(value); 11 | view.chat.clearChatInput(); 12 | break; 13 | case 'Tab': { 14 | view.chat.printPlayerList(model.search); 15 | model.updateAutocomplete(value, selectionStart, selectionEnd); 16 | view.chat.autoComplete(); 17 | break; 18 | } 19 | default: 20 | model.autocompleteOff(); 21 | view.chat.printPlayerList(''); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_stand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/notif.js: -------------------------------------------------------------------------------- 1 | let previousTimeoutId = -1; 2 | 3 | export function showNotif(title, desc) { 4 | if (!title && !desc) return; 5 | if (previousTimeoutId !== -1) clearTimeout(previousTimeoutId); 6 | 7 | let notifBox = document.getElementById('notif-box'); 8 | notifBox.setAttribute('data-title', title); 9 | notifBox.setAttribute('data-desc', desc); 10 | notifBox.classList.remove('hidden'); 11 | previousTimeoutId = setTimeout(() => { 12 | notifBox.classList.add('hidden'); 13 | previousTimeoutId = -1; 14 | }, 4000); 15 | } 16 | 17 | let badCoEl = document.getElementById('gui-bad-connection'); 18 | export function showBadConnection() { 19 | badCoEl.classList.remove('hidden'); 20 | } 21 | export function hideBadConnection() { 22 | badCoEl.classList.add('hidden'); 23 | } 24 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_stand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/muzzle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/shotgun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_mouth_surprise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/index.js: -------------------------------------------------------------------------------- 1 | import * as audio from './audio.js'; 2 | import * as chat from './chat.js'; 3 | import * as controls from './controls/index.js'; 4 | import * as dialogs from './dialogs.js'; 5 | import * as draw from './draw.js'; 6 | import * as history from './history.js'; 7 | import * as notif from './notif.js'; 8 | import * as search from './search.js'; 9 | import * as servers from './servers.js'; 10 | import * as settings from './settings.js'; 11 | import * as sorting from './sorting.js'; 12 | import * as views from './views.js'; 13 | import * as weapons from './weapons.js'; 14 | import windowBox from './windowbox.js'; 15 | import * as hud from './hud.js'; 16 | 17 | export { audio, chat, controls, dialogs, draw, history, notif, search, servers, settings, sorting, views, weapons, windowBox, hud }; 18 | 19 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_duck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_duck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_mouth_surprise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_duck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_duck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_duck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/url.js: -------------------------------------------------------------------------------- 1 | const urlSafeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$\'()*+,;=:@'; // https://tools.ietf.org/html/rfc3986#section-3.3 2 | export const escapedUrlSafeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-._~!$\'()*+,;=:@'; 3 | 4 | export function encodeUint(lobbyNb) { 5 | let upperDigit = Math.trunc(lobbyNb/urlSafeChars.length), 6 | lobbyCode = urlSafeChars.charAt(lobbyNb%urlSafeChars.length); 7 | 8 | if (upperDigit === 0) return lobbyCode; 9 | else return encodeUint(upperDigit) + lobbyCode; 10 | } 11 | export function decodeUint(lobbyCode) { 12 | let lobbyNb = 0; 13 | for (let i = 0; i !== lobbyCode.length; ++i) lobbyNb += Math.pow(urlSafeChars.length, lobbyCode.length - i - 1) * urlSafeChars.indexOf(lobbyCode.charAt(i)); 14 | 15 | return lobbyNb; 16 | } 17 | -------------------------------------------------------------------------------- /client/controller/settings.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | import * as model from '../model/index.js'; 3 | import * as socket from './socket.js'; 4 | 5 | // name 6 | view.settings.setName(model.settings.name); 7 | view.settings.bindName(name => { 8 | model.settings.name = name; 9 | socket.currentConnection.setPreferences(); 10 | }); 11 | 12 | // meteors 13 | view.settings.checkMeteors(model.settings.meteors === 'true'); 14 | view.settings.bindCheckMeteors(checked => { 15 | if (checked) view.draw.startMeteorSpawning(); 16 | else view.draw.stopMeteorSpawning(); 17 | 18 | model.settings.meteors = checked.toString(); 19 | }); 20 | 21 | // particles 22 | view.settings.checkParticles(model.settings.particles === 'true'); 23 | view.settings.bindCheckParticles(checked => { 24 | model.settings.particles = checked.toString(); 25 | }); 26 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_mouth_happy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/heartHalfFilled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_mouth_unhappy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/jetpackFire.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/model/controls.js: -------------------------------------------------------------------------------- 1 | import settings from './settings.js'; 2 | import * as bimap from '../../shared/bimap.js'; 3 | 4 | export let keyMap = new bimap.KeyActionMap(settings.keymap); 5 | 6 | export function resetKeyMap() { 7 | delete settings.keymap; 8 | keyMap.parse(settings.keymap); 9 | } 10 | 11 | export let selfAngle = 0; 12 | 13 | export let selfControls = { 14 | changeWeapon: 0, 15 | crouch: 0, 16 | jetpack: 0, 17 | jump: 0, 18 | moveLeft: 0, 19 | moveRight: 0, 20 | run: 0, 21 | shoot: 0 22 | }; 23 | 24 | export function clean() { 25 | selfAngle = 0; 26 | for (let key in selfControls) { 27 | selfControls[key] = 0; 28 | } 29 | } 30 | 31 | export let gamepadId = null; 32 | export function setGamepadId(newGamepadId) { 33 | gamepadId = newGamepadId; 34 | } 35 | 36 | export let zoomFactor = 1; 37 | export function setZoomFactor(newZoomFactor) { 38 | zoomFactor = Math.max(0.25, Math.min(4, newZoomFactor)); 39 | } 40 | -------------------------------------------------------------------------------- /static/assets/images/controls/jetpack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/astronaut_helmet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/game/shot.js: -------------------------------------------------------------------------------- 1 | import Shot from '../../shared/shot.js'; 2 | import windowBox from '../view/windowbox.js'; 3 | 4 | export default class extends Shot { 5 | constructor(x, y, angle, origin, type) { 6 | super(x, y, angle, origin, type); 7 | } 8 | draw(context, dead) { 9 | let resourceKey; 10 | if (this.type === this.TYPES.BULLET && !dead) resourceKey = 'rifleShot'; 11 | else if (this.type === this.TYPES.KNIFE && !dead) resourceKey = 'knife'; 12 | else if (this.type === this.TYPES.LASER) resourceKey = (dead ? 'laserBeamDead' : 'laserBeam'); 13 | else if (this.type === this.TYPES.BALL) resourceKey = 'shotgunBall'; 14 | 15 | if (resourceKey === undefined) return; 16 | windowBox.drawRotatedImage(context, 17 | window.resources[resourceKey], 18 | windowBox.wrapX(this.box.center.x), 19 | windowBox.wrapY(this.box.center.y), 20 | this.box.angle + (resourceKey === 'knife' ? (100 - this.lifeTime) * Math.PI * 0.04 - Math.PI / 2 : 0) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /static/assets/images/meteorBig3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorBig4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/controller/socket.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | 3 | import Connection from './connection.js'; 4 | 5 | export let masterSocket = new MasterConnection((location.protocol === 'http:' ? 'ws://' : 'wss://') + location.hostname + (location.port === '' ? '' : ':' + location.port)); 6 | masterSocket.addEventListener('slaveadded', slaveCo => { 7 | view.servers.addServerRow(slaveCo); 8 | //TODO: view.applyLobbySearch();//in case the page was refreshed 9 | }); 10 | masterSocket.addEventListener('slaveremoved', slaveCo => { 11 | view.servers.removeServer(slaveCo); 12 | }); 13 | 14 | export var currentConnection; 15 | 16 | export function makeNewCurrentConnection(slaveCo, id) { 17 | if (currentConnection !== undefined && currentConnection.alive()) currentConnection.close(); 18 | new Connection(slaveCo, id).then((connection) => { 19 | currentConnection = connection; 20 | }).catch((err) => { 21 | view.dialogs.showDialog('Couldn\'t create connection', err.messsage); 22 | console.error(err); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /static/assets/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/controls/crouch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/controls/moveRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/controls/index.js: -------------------------------------------------------------------------------- 1 | import * as keyboard from './keyboard.js'; 2 | import * as gamepad from './gamepad.js'; 3 | import * as pointer from './pointer.js'; 4 | import * as onscreen from './onscreen.js'; 5 | 6 | export { keyboard, gamepad, pointer, onscreen }; 7 | 8 | export function enable() { 9 | window.addEventListener('keydown', keyboard.keyboardHandler); 10 | window.addEventListener('keyup', keyboard.keyboardHandler); 11 | 12 | window.addEventListener('touchstart', pointer.handleInputMobile); 13 | window.addEventListener('touchmove', pointer.handleInputMobile); 14 | window.addEventListener('touchend', pointer.handleInputMobile); 15 | } 16 | export function disable() { 17 | window.removeEventListener('keydown', keyboard.keyboardHandler); 18 | window.removeEventListener('keyup', keyboard.keyboardHandler); 19 | 20 | window.removeEventListener('touchstart', pointer.handleInputMobile); 21 | window.removeEventListener('touchmove', pointer.handleInputMobile); 22 | window.removeEventListener('touchend', pointer.handleInputMobile); 23 | } 24 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_walk1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_walk2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_walk1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_walk2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/ripple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /static/assets/images/controls/moveLeft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/settings.js: -------------------------------------------------------------------------------- 1 | /* Meteors */ 2 | const meteorsElement = document.getElementById('meteor-option'); 3 | export function checkMeteors(bool) { 4 | meteorsElement.checked = bool; 5 | } 6 | export function bindCheckMeteors(handler) { 7 | meteorsElement.addEventListener('change', e => { 8 | handler(e.target.checked); 9 | }); 10 | } 11 | 12 | /* Particles */ 13 | const particleElement = document.getElementById('particle-option'); 14 | export function checkParticles(bool) { 15 | particleElement.checked = bool; 16 | } 17 | export function bindCheckParticles(handler) { 18 | particleElement.addEventListener('change', e => { 19 | handler(e.target.checked); 20 | }); 21 | } 22 | 23 | 24 | /* Name */ 25 | const nameElement = document.getElementById('name'); 26 | nameElement.addEventListener('keydown', e => { 27 | if (e.key === 'Enter') e.target.blur(); 28 | }); 29 | export function setName(name) { 30 | nameElement.value = name; 31 | } 32 | export function bindName(handler) { 33 | nameElement.addEventListener('blur', e => { 34 | handler(e.target.value); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /client/model/chat.js: -------------------------------------------------------------------------------- 1 | import * as entities from './entities.js'; 2 | 3 | let autocomplete = false; 4 | 5 | export let search, 6 | searchIndex, 7 | textParts; 8 | 9 | export function autocompleteOff() { 10 | autocomplete = false; 11 | } 12 | export function updateAutocomplete(value, selectionStart, selectionEnd) { 13 | if (!autocomplete) { 14 | autocomplete = true; 15 | 16 | let text = (selectionStart === 0) ? '' : value.substr(0, selectionStart); 17 | search = text.substr((text.lastIndexOf(' ') === -1) ? 0 : text.lastIndexOf(' ') + 1); 18 | 19 | searchIndex = 0; 20 | textParts = [value.substr(0, selectionStart - search.length), value.substr(selectionEnd)]; 21 | } else { 22 | searchIndex++; 23 | if (searchIndex === getFilteredPlayerList().length) searchIndex = 0; 24 | } 25 | } 26 | export function getFilteredPlayerList() { 27 | let filteredPlayerList = []; 28 | for (let pid in entities.players) { 29 | if (entities.players[pid].name.indexOf(search) !== -1) filteredPlayerList.push(entities.players[pid].name); 30 | } 31 | 32 | return filteredPlayerList; 33 | } 34 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_stand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorBig1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/controller/history.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | import * as socket from './socket.js'; 3 | import * as loop from '../controller/loop.js'; 4 | import * as entities from '../model/entities.js'; 5 | 6 | 7 | view.history.bindHistoryNavigation(state => { 8 | if (state === view.history.HISTORY_MENU) { 9 | if (socket.currentConnection !== undefined) socket.currentConnection.close(); 10 | loop.stop(); 11 | entities.clean(); 12 | view.views.hideScores(); 13 | view.chat.clearChat(); 14 | view.views.showMenu(); 15 | } else { 16 | let { serverId, lobbyId } = view.history.getConnectionIds(); 17 | console.log(serverId); 18 | if (serverId !== null) { 19 | let entry, slaveCo; 20 | for (entry of view.servers.slaveRows) { 21 | if (serverId === entry[0].id) { 22 | slaveCo = entry[0]; 23 | break; 24 | } 25 | } 26 | if (slaveCo) socket.makeNewCurrentConnection(slaveCo, lobbyId); 27 | else { 28 | view.notif.showNotif('Ooops.', 'We haven\'t found the server you are looking for!'); 29 | view.history.push(); 30 | } 31 | } 32 | } 33 | }); 34 | 35 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_walk2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_walk1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/controller/weapons.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | import * as model from '../model/index.js'; 3 | import * as socket from './socket.js'; 4 | 5 | function getNextWeapon(weaponType) { 6 | const nextWeapon = { 7 | Lmg: 'Smg', 8 | Smg: 'Knife', 9 | Knife: 'Shotgun', 10 | Shotgun: 'Lmg' 11 | }; 12 | 13 | return nextWeapon[weaponType]; 14 | } 15 | 16 | view.weapons.setPrimaryWeapon(model.settings.primary); 17 | view.weapons.setSecondaryWeapon(model.settings.secondary); 18 | 19 | view.weapons.bindClickPrimaryWeapon(weapon => { 20 | let nextWeapon = getNextWeapon(weapon); 21 | console.log(weapon, nextWeapon); 22 | view.weapons.setPrimaryWeapon(nextWeapon); 23 | model.settings.primary = nextWeapon; 24 | if (typeof socket.currentConnection !== 'undefined') socket.currentConnection.setPreferences(); 25 | }); 26 | 27 | view.weapons.bindClickSecondaryWeapon(weapon => { 28 | let nextWeapon = getNextWeapon(weapon); 29 | view.weapons.setSecondaryWeapon(nextWeapon); 30 | model.settings.secondary = nextWeapon; 31 | if (typeof socket.currentConnection !== 'undefined') socket.currentConnection.setPreferences(); 32 | }); 33 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_stand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/weapons.js: -------------------------------------------------------------------------------- 1 | const primaryWeaponElement = document.getElementById('primary-weapon'), 2 | secondaryWeaponElement = document.getElementById('secondary-weapon'); 3 | 4 | const weaponNames = { 5 | Lmg: 'Borpov', 6 | Smg: 'Pezcak', 7 | Knife: 'Throwing knife', 8 | Shotgun: 'Azard' 9 | }; 10 | 11 | function setGun(element, type) { 12 | console.log(element.dataset.currentWeapon); 13 | element.dataset.currentWeapon = type; 14 | element.childNodes[0].src = '/assets/images/' + type.toLowerCase() + '.svg'; 15 | element.childNodes[1].textContent = weaponNames[type]; 16 | } 17 | 18 | export function setPrimaryWeapon(weaponType) { 19 | setGun(primaryWeaponElement, weaponType); 20 | } 21 | export function setSecondaryWeapon(weaponType) { 22 | setGun(secondaryWeaponElement, weaponType); 23 | } 24 | export function bindClickPrimaryWeapon(handler) { 25 | primaryWeaponElement.addEventListener('click', e => { 26 | handler(e.currentTarget.dataset.currentWeapon); 27 | }); 28 | } 29 | export function bindClickSecondaryWeapon(handler) { 30 | secondaryWeaponElement.addEventListener('click', e => { 31 | handler(e.currentTarget.dataset.currentWeapon); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /static/assets/images/laserBeamDead.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/controller/loop.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | import * as engine from '../model/engine.js'; 3 | import * as model from '../model/index.js'; 4 | 5 | let animationFrameId; 6 | let started = false; 7 | 8 | function loop() { 9 | engine.doPhysicsClient(model.entities.universe, model.entities.planets, model.entities.shots, model.entities.players); 10 | engine.doPrediction(model.entities.universe, model.entities.players, model.entities.enemies, model.entities.shots); 11 | view.draw.draw(); 12 | animationFrameId = window.requestAnimationFrame(loop); 13 | } 14 | 15 | export function start() { 16 | if (!started) { 17 | started = true; 18 | view.controls.enable(); 19 | view.views.focusGame(); 20 | view.draw.startMeteorSpawning(); 21 | loop(); 22 | } 23 | } 24 | export function stop() { 25 | if (started) { 26 | started = false; 27 | view.controls.disable(); 28 | view.controls.onscreen.hide(); 29 | view.audio.stopAllJetpacks(); 30 | view.chat.clearChat(); 31 | view.draw.stopMeteorSpawning(); 32 | model.entities.planets.length = 0; 33 | model.entities.enemies.length = 0; 34 | window.cancelAnimationFrame(animationFrameId); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jumpsuit", 3 | "version": "0.0.0", 4 | "description": "A sweet canvas game", 5 | "dependencies": { 6 | "colors": "1.x", 7 | "enslavism": "0.x", 8 | "image-size": "0.x", 9 | "ipaddr.js": "1.x", 10 | "rollup": "0.x", 11 | "rollup-plugin-alias": "1.x", 12 | "rollup-plugin-commonjs": "8.x", 13 | "rollup-plugin-eslint": "3.x", 14 | "rollup-plugin-node-resolve": "2.x", 15 | "rollup-plugin-replace": "1.x", 16 | "vinage": "0.x" 17 | }, 18 | "devDependencies": { 19 | "ava": "0.x", 20 | "babel-register": "6.x", 21 | "babel-polyfill": "6.x", 22 | "babel-plugin-module-resolver": "2.x", 23 | "source-map-support": "0.x", 24 | "stylelint": "7.x" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/KordonBleu/jumpsuit" 29 | }, 30 | "readme": "readme.md", 31 | "license": "MPL-2.0", 32 | "scripts": { 33 | "test": "ava ./tests/payloads.js ./tests/subpayloads.js", 34 | "stylelint": "stylelint ./static/*.css" 35 | }, 36 | "ava": { 37 | "require": [ 38 | "babel-polyfill", 39 | "babel-register" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_stand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/servers.js: -------------------------------------------------------------------------------- 1 | const lobbyListElement = document.getElementById('lobby-list'); 2 | 3 | export let slaveRows = new Map(); 4 | export function addServerRow(slaveCo) { 5 | let row = document.createElement('tr'), 6 | serverNameTd = document.createElement('td'), 7 | modNameTd = document.createElement('td'), 8 | buttonTd = document.createElement('td'), 9 | button = document.createElement('button'); 10 | 11 | serverNameTd.textContent = slaveCo.userData.serverName; 12 | modNameTd.textContent = slaveCo.userData.modName; 13 | 14 | button.textContent = 'Play!'; 15 | button.slaveCo = slaveCo; 16 | 17 | buttonTd.appendChild(button); 18 | row.appendChild(serverNameTd); 19 | row.appendChild(modNameTd); 20 | row.appendChild(buttonTd); 21 | 22 | lobbyListElement.insertBefore(row, lobbyListElement.firstChild); 23 | 24 | slaveRows.set(slaveCo, row); 25 | } 26 | export function removeServer(slaveCo) { 27 | slaveRows.get(slaveCo).remove(); 28 | slaveRows.delete(slaveCo); 29 | } 30 | 31 | export function bindPlay(handler) { 32 | lobbyListElement.addEventListener('click', e => { 33 | if (e.target.tagName === 'BUTTON') { 34 | handler(e.target.slaveCo); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/planet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/meteorBig2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/search.js: -------------------------------------------------------------------------------- 1 | import * as servers from './servers.js'; 2 | 3 | let searchInput = document.getElementById('lobby-search'); 4 | 5 | export function applyLobbySearch() { 6 | for (let entry of servers.slaveRows) { 7 | let tableRow = entry[1], 8 | tableItem = tableRow.firstChild, 9 | newValue = tableItem.textContent, 10 | serverName = entry[0].userData.serverName; 11 | 12 | if (searchInput.value !== '') { 13 | let match, offset = 0, regex = new RegExp(searchInput.value.replace(/[#-.]|[[-^]|[?|{}]/g, '\\$&'), 'gi'); //clean RegEx 14 | while ((match = regex.exec(serverName)) !== null) { 15 | newValue = newValue.substr(0, match.index + offset) + '' + newValue.substr(match.index + offset, match[0].length) + '' + newValue.substr(offset + match.index + match[0].length); 16 | offset += 7; //... inserted so the string length and match indexes change 17 | } 18 | if (offset === 0) tableRow.classList.add('hidden'); 19 | else tableRow.classList.remove('hidden'); 20 | } else tableRow.classList.remove('hidden'); 21 | 22 | tableItem.innerHTML = newValue; 23 | } 24 | } 25 | 26 | document.getElementById('lobby-search-reset').addEventListener('click', e => { 27 | e.preventDefault(); 28 | searchInput.value = ''; 29 | }); 30 | searchInput.addEventListener('input', applyLobbySearch); 31 | -------------------------------------------------------------------------------- /client/controller/controls/pointer.js: -------------------------------------------------------------------------------- 1 | import * as model from '../../model/index.js'; 2 | import * as view from '../../view/index.js'; 3 | import * as socket from '../socket.js'; 4 | 5 | 6 | view.controls.pointer.bindMouseMove(angle => { 7 | if (!model.dialogs.modalOpen && !view.chat.chatInUse()) model.controls.selfAngle = angle; 8 | }); 9 | 10 | view.controls.pointer.bindWheel(deltaY => { 11 | if (!view.chat.chatInUse() && !model.dialogs.modalOpen) { 12 | let deltaFactor = deltaY > 0 ? 0.5 : 2; // 1/2 or 2/1 13 | model.controls.setZoomFactor(model.controls.zoomFactor * deltaFactor); 14 | view.views.resizeCanvas(); 15 | } 16 | }); 17 | 18 | view.controls.pointer.bindMouseDown(() => { // left click 19 | if (socket.currentConnection.alive()) model.controls.selfControls['shoot'] = 1; 20 | }, (e) => { // right click 21 | view.controls.pointer.startDrag(e); 22 | }); 23 | 24 | view.controls.pointer.bindMouseUp(() => { // left click 25 | if (socket.currentConnection.alive()) model.controls.selfControls['shoot'] = 0; 26 | }, () => { // right click 27 | view.controls.pointer.finishDrag(); 28 | }); 29 | 30 | view.controls.pointer.setTouchcontrolsHandler((action, pressure) => { 31 | if (model.controls.selfControls[action] !== undefined) { 32 | model.controls.selfControls[action] = pressure; 33 | socket.currentConnection.refreshControls(); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /client/controller/controls/gamepad.js: -------------------------------------------------------------------------------- 1 | import * as model from '../../model/index.js'; 2 | import * as view from '../../view/index.js'; 3 | import * as socket from '../socket.js'; 4 | 5 | if (model.platform.supportsGamepad) { 6 | view.controls.gamepad.bindGamepadConnection(id => { 7 | if (model.controls.gamepadId !== null) { 8 | view.notif.showNotif('Gamepad connected', 'Gamepad #' + id + ' has been ignored because there is already a gamepad connected'); 9 | } else { 10 | model.controls.setGamepadId(id); 11 | view.notif.showNotif('Gamepad connected', 'Gamepad #' + id + ' is set as controlling device'); 12 | view.controls.gamepad.bindUpdateControls(updateControls, id); 13 | } 14 | }); 15 | view.controls.gamepad.bindGamepadDisconnection(id => { 16 | view.notif.showNotif('Gamepad disconnected', 'Gamepad #' + id + ' was disconnected'); 17 | if (model.controls.gamepadId === id) { 18 | model.controls.setGamepadId(null); // TODO: if there are other gamepads use those 19 | } 20 | }); 21 | } 22 | function updateControls(jump, run, crouch, moveLeft, moveRight) { 23 | model.controls.selfControls.jump = jump; 24 | model.controls.selfControls.run = run; 25 | model.controls.selfControls.crouch = crouch; 26 | model.controls.selfControls.moveLeft = moveLeft; 27 | model.controls.selfControls.moveRight = moveRight; 28 | 29 | socket.currentConnection.refreshControls(); 30 | } 31 | -------------------------------------------------------------------------------- /server/ips.js: -------------------------------------------------------------------------------- 1 | //IPS aka Intrusion Prevention System 2 | 3 | import logger from './logger.js'; 4 | 5 | let attackers = new Map();//not an object in order to use an ipaddr object as a key 6 | 7 | export function banned(ip) { 8 | for (let [savIp, savObj] of attackers.entries()) {//is using strings faster? 9 | if (ip.match(savIp, 128) && savObj.banned) return true;//we would need to create a string per call but looping wouldn't be necessary 10 | } 11 | return false; 12 | } 13 | export function ban(ip) { 14 | logger(logger.INFO, 'Received garbage from: ' + ip + '. Temporarily banning IP...'); 15 | function unBan() { 16 | this.banned = false; 17 | } 18 | function unDistrust() { 19 | attackers.delete(this); 20 | } 21 | 22 | for (let [savIp, savObj] of attackers.entries()) { 23 | if (ip.match(savIp, 128)) { 24 | savObj.attackAmount++; 25 | clearTimeout(savObj.banId); 26 | clearTimeout(savObj.distrustId); 27 | savObj.banId = setTimeout(unBan.bind(savObj), savObj.attackAmount*500); 28 | savObj.distrustId = setTimeout(unDistrust.bind(savIp), savObj.attackAmount*1000); 29 | savObj.banned = true; 30 | return; 31 | } 32 | } 33 | 34 | let metadataObj = { 35 | attackAmount: 1, 36 | banned: true 37 | }; 38 | metadataObj.banId = setTimeout(unBan.bind(metadataObj), 500); 39 | metadataObj.distrustId = setTimeout(unDistrust.bind(ip), 1000); 40 | 41 | attackers.set(ip, metadataObj); 42 | } 43 | -------------------------------------------------------------------------------- /client/view/controls/gamepad.js: -------------------------------------------------------------------------------- 1 | /* Gamepads */ 2 | 3 | export function bindGamepadConnection(handler) { 4 | window.addEventListener('gamepadconnected', e => { 5 | handler(e.gamepad.index); 6 | }); 7 | } 8 | export function bindGamepadDisconnection(handler) { 9 | window.addEventListener('gamepaddisconnected', e => { 10 | handler(e.gamepad.index); 11 | }); 12 | } 13 | 14 | let intervalId = null; 15 | export function bindUpdateControls(handler, gamepadId) { 16 | intervalId = setInterval(gamepadId => { 17 | if (gamepadId === -1) return; 18 | let gamepads = navigator.getGamepads ? navigator.getGamepads() : [], 19 | g = gamepads[gamepadId]; 20 | if (typeof(g) !== 'undefined') { 21 | let moveLeft = 0, 22 | moveRight = 0; 23 | if (g.axes[0] < -0.2 || g.axes[0] > 0.2) { 24 | if (g.axes[0] < 0) { 25 | moveLeft = Math.abs(g.axes[0]); 26 | } else { 27 | moveRight = Math.abs(g.axes[0]); 28 | } 29 | } 30 | /*if (g.axes[2] < -0.2 || g.axes[2] > 0.2) drag.x = -canvas.width / 2 * g.axes[2]; 31 | else drag.x = 0; 32 | if ((g.axes[3] < -0.2 || g.axes[3] > 0.2)) drag.y = -canvas.height / 2 * g.axes[3]; 33 | else drag.y = 0;*/ 34 | handler( 35 | g.buttons[0].value, 36 | g.buttons[1].value, 37 | g.buttons[4].value, 38 | moveLeft, 39 | moveRight 40 | ); 41 | } 42 | }, 50, gamepadId); 43 | } 44 | export function unbindUpdateControls() { 45 | clearInterval(intervalId); 46 | } 47 | -------------------------------------------------------------------------------- /doc/modding.md: -------------------------------------------------------------------------------- 1 | # The modding API 2 | 3 | ## Getting started 4 | 5 | A mod is contained in a single directory under `server/mods/`, which makes it easy to manage (for example as a git repository). 6 | 7 | If you want your server to run a mod called "MyMod" for example, you must create a directory `MyMod`. Then in `build_config.json`, you must set the value `mod` to `"MyMod"` (the default mod is `"capture"`). 8 | Your mod must contain the files `enemy.js`, `engine.js`, `knife.js`, `lmg.js`, `on_message.js`, `planet.js`, `player.js`, `rapid_fire_weapon.js`, `shotgun.js`, `shot.js`, `smg.js` and `weapon.js`. 9 | Your files must export at least the same things as the default mod *capture*, and your own version of classes must have the same signatures and return types as *capture*'s. 10 | 11 | It is advised you copy the example mod *example*, then modify it to make it into whatever you want it to be! 12 | To do so, you can have a look at how the default mod *capture* is made. 13 | 14 | Note that when you modify something, it only applies to the server. We will never serve client code from your mod for obvious **security reasons**. 15 | 16 | 17 | ## Requests 18 | 19 | Feedback from modders is appreciated. 20 | Besides, this API is yet quite new, might lacks features, etc. If you think it could be improved, you are welcome to open an [issue](https://github.com/KordonBleu/jumpsuit/issues) or a [pull request](https://github.com/KordonBleu/jumpsuit/pulls). 21 | -------------------------------------------------------------------------------- /server/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | Once `import`ed, the logger can be used like this `logger(logger.INFO, 'This is a message printed to the console');`. 3 | However, as user input should never be trusted, logger provide a facility to escape it in case it contains characters that could be interpreted by the terminal in a way that is unwanted. 4 | Every argument coming after the message will be escaped and inserted into the message, replacing `{argumentPositionNumber}`. 5 | Example: `logger(logger.INFO, '"{0}" won the the match! "{1}" comes second and "{2}" third.', playerName1, playerName2, playerName3);` 6 | */ 7 | 8 | require('colors'); 9 | 10 | export default function logger(type, content, ...toBeEscaped) { 11 | function enumToString(e) { 12 | switch (e) { 13 | case 0: return '[DEV]'.cyan.bold; 14 | case 1: return '[INFO]'.yellow.bold; 15 | case 2: return '[ERR]'.red.bold; 16 | case 3: return '[REGISTER]'.bold; 17 | case 4: return '[REGISTER]'.green.bold; 18 | } 19 | return ''; 20 | } 21 | 22 | if (type === 0 && process.env.NODE_ENV !== 'development') return; 23 | 24 | let timestamp = ('[' + Math.round(Date.now() / 1000).toString(16) + ']').grey; 25 | console.log(timestamp + enumToString(type) + ' ' + content.replace(/\{(\d+)\}/g, (match, n) => { 26 | //sanitize string for console output 27 | return toBeEscaped[n].toString().replace(/[\u0000-\u001F\u007F-\u009F]/g, '\ufffd'); 28 | })); 29 | } 30 | logger.DEV = 0; 31 | logger.INFO = 1; 32 | logger.ERROR = 2; 33 | logger.REGISTER = 3; 34 | logger.S_REGISTER = 4; 35 | -------------------------------------------------------------------------------- /client/view/sorting.js: -------------------------------------------------------------------------------- 1 | /*let lobbyTableHeaderRowElement = document.getElementById('lobby-table').firstElementChild.firstElementChild; 2 | lobbyTableHeaderRowElement.addEventListener('click', function(e) { 3 | if (e.target.tagName === 'IMG') { 4 | switch (e.target.getAttribute('src')) { 5 | case '/assets/images/sort_arrow_double.svg': 6 | e.target.setAttribute('src', '/assets/images/sort_arrow_down.svg'); 7 | for (let elem of lobbyTableHeaderRowElement.children) { 8 | let arrowImg = elem.lastElementChild; 9 | if (elem.lastElementChild !== null && e.target !== arrowImg) { 10 | arrowImg.setAttribute('src', '/assets/images/sort_arrow_double.svg'); 11 | } 12 | } 13 | 14 | switch (e.target.previousSibling.data.trim()) { 15 | case 'Lobby name': 16 | wsClt.serverList.sort(function(a, b) { 17 | return b.name.trim().localeCompare(a.name.trim()); 18 | }); 19 | break; 20 | case 'Players': 21 | wsClt.serverList.sort(function(a, b) { 22 | if (a.players < b.players || a.players > b.players) return a.players < b.players ? -1 : 1; 23 | else return a.maxPlayers < b.maxPlayers ? -1 : a.maxPlayers > b.maxPlayers ? 1 : 0; 24 | }); 25 | } 26 | break; 27 | case '/assets/images/sort_arrow_down.svg': 28 | e.target.setAttribute('src', '/assets/images/sort_arrow_up.svg'); 29 | wsClt.serverList.reverse(); 30 | break; 31 | case '/assets/images/sort_arrow_up.svg': 32 | e.target.setAttribute('src', '/assets/images/sort_arrow_down.svg'); 33 | wsClt.serverList.reverse(); 34 | break; 35 | } 36 | addServerRow(); 37 | } 38 | });*/ 39 | -------------------------------------------------------------------------------- /client/game/weapon.js: -------------------------------------------------------------------------------- 1 | import Weapon from '../../shared/weapon.js'; 2 | import * as model from '../model/index.js'; 3 | 4 | export default class extends Weapon { 5 | constructor(owner) { 6 | super(owner); 7 | this.recoil = 0; 8 | } 9 | draw(context, weaponAngle) { 10 | let weaponRotFact = this.owner.looksLeft === true ? -(weaponAngle - this.owner.box.angle + Math.PI/2) : (weaponAngle - this.owner.box.angle + 3*Math.PI/2); 11 | 12 | this.recoil = this.recoil < 0.05 ? 0 : this.recoil * 0.7; 13 | context.rotate(weaponRotFact); 14 | if (model.settings.particles === 'true' && this.muzzleFlash === true) { 15 | var muzzleX = this.muzzleX*model.controls.zoomFactor + window.resources['muzzle'].width*0.5*model.controls.zoomFactor, 16 | muzzleY = this.muzzleY*model.controls.zoomFactor - window.resources['muzzle'].height*0.25*model.controls.zoomFactor; 17 | 18 | context.drawImage(window.resources[(Math.random() > 0.5 ? 'muzzle' : 'muzzle2')], 19 | muzzleX, muzzleY + this.offsetY*model.controls.zoomFactor, 20 | window.resources['muzzle'].width * model.controls.zoomFactor, 21 | window.resources['muzzle'].height * model.controls.zoomFactor);//muzzle flash 22 | 23 | this.muzzleFlash = false; 24 | this.recoil = 10; 25 | } 26 | context.drawImage(window.resources[this.type.toLowerCase()], // this is ugly buuuuuuut... it works 27 | (this.offsetX - this.recoil)*model.controls.zoomFactor, 28 | this.offsetY*model.controls.zoomFactor, 29 | window.resources[this.type.toLowerCase()].width*model.controls.zoomFactor, 30 | window.resources[this.type.toLowerCase()].height*model.controls.zoomFactor 31 | ); 32 | context.rotate(-weaponRotFact); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/view/history.js: -------------------------------------------------------------------------------- 1 | import * as url from './url.js'; 2 | import * as dialogs from './dialogs.js'; 3 | 4 | export const HISTORY_MENU = 0, 5 | HISTORY_GAME = 1; 6 | 7 | export function getConnectionIds() { 8 | let res = location.hash.match(new RegExp('^#srv=([' + url.escapedUrlSafeChars + ']+)(?:&lobby=([' + url.escapedUrlSafeChars + ']+))?')); 9 | if (res === null) { 10 | return { 11 | serverId: null, 12 | lobbyId: null 13 | }; 14 | } else { 15 | let [, serverId, lobbyId] = res; 16 | serverId = serverId === undefined ? null : url.decodeUint(serverId); 17 | lobbyId = lobbyId === undefined ? null : url.decodeUint(lobbyId); 18 | return { 19 | serverId, 20 | lobbyId 21 | }; 22 | } 23 | } 24 | 25 | let navHandler; 26 | export function push(serverId, lobbyId) { 27 | if (isNaN(serverId)) { 28 | if (history.state === HISTORY_MENU) return; //prevent being in menu twice 29 | history.pushState(HISTORY_MENU, '', location.pathname); 30 | navHandler(HISTORY_MENU); 31 | } else { //assumes serverId is defined too 32 | history.pushState(HISTORY_GAME, '', location.pathname + '#srv=' + url.encodeUint(serverId) + (lobbyId !== null ? '&lobby=' + url.encodeUint(lobbyId) : '')); 33 | } 34 | } 35 | export function init() { 36 | let ids = getConnectionIds(); 37 | if (ids.serverId === null) history.replaceState(HISTORY_MENU, '', location.pathname); 38 | else dialogs.showAutoConnect(); 39 | } 40 | export function reset() { 41 | history.replaceState(HISTORY_MENU, '', location.pathname); 42 | } 43 | export function bindHistoryNavigation(handler) { 44 | navHandler = handler; 45 | window.addEventListener('popstate', () => { 46 | navHandler(history.state || HISTORY_MENU); 47 | }); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /nginx/jumpsuit.space: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name jumpsuit.space; 4 | 5 | return 301 https://$host$request_uri; 6 | 7 | access_log /var/log/nginx/jumpsuit_access.log combined; 8 | } 9 | 10 | server { 11 | listen 443 ssl http2; 12 | server_name jumpsuit.space; 13 | 14 | add_header Cache-Control "public, no-cache, must-revalidate, proxy-revalidate"; 15 | etag off; # no need to have both this and Last-Modified 16 | 17 | add_header Access-Control-Allow-Origin "http://jumpsuit.space"; 18 | 19 | ssl_protocols TLSv1.2; 20 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; 21 | 22 | ssl_certificate /etc/letsencrypt/live/jumpsuit.space/fullchain.pem; 23 | ssl_certificate_key /etc/letsencrypt/live/jumpsuit.space/privkey.pem; 24 | 25 | location = / { 26 | rewrite ^ /index.html; 27 | } 28 | 29 | location / { 30 | root /home/jumpsuit/jumpsuit/static/; 31 | try_files $uri @node; 32 | } 33 | location = /enslavism/client.js { 34 | #root /home/jumpsuit/enslavism/; 35 | proxy_pass http://localhost:8080; 36 | } 37 | 38 | location = /vinage.js { 39 | root /home/jumpsuit/jumpsuit/node_modules/vinage/; 40 | } 41 | location = /ipaddr.min.js { 42 | root /home/jumpsuit/jumpsuit/node_modules/ipaddr.js/; 43 | } 44 | location @node { 45 | proxy_http_version 1.1; 46 | proxy_set_header Upgrade $http_upgrade; 47 | proxy_set_header Connection "upgrade"; 48 | 49 | proxy_set_header X-Forwarded-For $remote_addr; 50 | 51 | proxy_pass http://localhost:8080; 52 | } 53 | 54 | access_log /var/log/nginx/jumpsuit_ssl_access.log combined; 55 | } 56 | -------------------------------------------------------------------------------- /static/assets/images/enemyGreen2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlack2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/lmg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/model/settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | The default export of this module acts like an object containing values gotten from the storage. 3 | When one of these is modified, the change is also applied to the storage. 4 | If it is rather `delete`d, it is replaced by the default value. 5 | */ 6 | 7 | const defaultKeymap = { 8 | ShiftLeft: 'run', 9 | Space: 'jump', 10 | ArrowUp: 'jump', 11 | ArrowLeft: 'moveLeft', 12 | ArrowRight: 'moveRight', 13 | ArrowDown: 'crouch', 14 | KeyA: 'moveLeft', 15 | KeyD: 'moveRight', 16 | KeyS: 'crouch', 17 | KeyT: 'chat', 18 | Digit1: 'changeWeapon', 19 | Digit2: 'changeWeapon' 20 | }, 21 | defaultSettings = { 22 | volMusic: '20', 23 | volEffects: '50', 24 | meteors: 'true', 25 | particles: 'true', 26 | name: 'Unnamed player', 27 | primary: 'Lmg', 28 | secondary: 'Smg', 29 | keymap: JSON.stringify(defaultKeymap), 30 | defaultKeymap: defaultKeymap 31 | }; 32 | 33 | let settings = {}; 34 | 35 | for (let key in defaultSettings) { 36 | let fromStorage = localStorage.getItem('settings.' + key); 37 | settings[key] = fromStorage === null ? defaultSettings[key] : fromStorage; 38 | } 39 | 40 | let proxy = new Proxy(settings, { 41 | get: (target, property) => { 42 | if(target.hasOwnProperty(property)) return target[property]; 43 | else { 44 | let val = localStorage.getItem('settings.' + property); 45 | 46 | return val === null ? undefined : val; // make it look like a normal object 47 | } 48 | }, 49 | set: (target, property, value) => { 50 | target[property] = value.toString(); 51 | localStorage.setItem('settings.' + property, value); 52 | 53 | return true; 54 | }, 55 | deleteProperty: (target, property) => { 56 | if (defaultSettings[property] !== undefined) proxy[property] = defaultSettings[property]; 57 | 58 | return true; 59 | } 60 | }); 61 | 62 | export default proxy; 63 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlue2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/controller/controls/keyboard.js: -------------------------------------------------------------------------------- 1 | import * as view from '../../view/index.js'; 2 | import * as model from '../../model/index.js'; 3 | 4 | view.controls.keyboard.initKeyTable(); 5 | view.controls.keyboard.setKeyResetDisabledStatus(model.controls.keyMap.compare(model.settings.defaultKeymap)); 6 | 7 | view.controls.keyboard.bindResetButton(() => { 8 | model.controls.resetKeyMap(); 9 | view.controls.keyboard.initKeyTable(); 10 | view.controls.keyboard.setKeyResetDisabledStatus(true); 11 | }); 12 | 13 | 14 | view.controls.keyboard.bindSetKey((action, keyCode, previousKeyCode, setCellContent, deselectRow) => { 15 | if (!model.controls.keyMap.keyTaken(keyCode)) { 16 | if (previousKeyCode !== '') model.controls.keyMap.deleteKey(previousKeyCode); 17 | model.controls.keyMap.addMapping(action, keyCode); 18 | 19 | setCellContent(keyCode); 20 | deselectRow(); 21 | 22 | model.settings.keymap = model.controls.keyMap.stringify(); 23 | view.controls.keyboard.setKeyResetDisabledStatus(model.controls.keyMap.compare(model.settings.defaultKeymap)); 24 | 25 | } else view.controls.keyboard.showKeyAssignError(keyCode); 26 | }); 27 | 28 | view.controls.keyboard.setKeyboardHandler(e => { 29 | let pressure = (e.type === 'keydown') * 1; 30 | 31 | if (!view.chat.chatInUse() && !model.dialogs.modalOpen) { 32 | let triggered = model.controls.keyMap.getAction(e.code); 33 | 34 | if (model.controls.selfControls[triggered] !== undefined) { 35 | e.preventDefault(); 36 | view.controls.keyboard.setOnscreenControlOpacity(pressure * 0.7 + 0.3, triggered); 37 | model.controls.selfControls[triggered] = pressure; 38 | } else if (triggered === 'chat' && pressure === 1) { 39 | e.preventDefault(); 40 | window.setTimeout(function() { // prevent the letter corresponding to 41 | view.chat.focusChat(); // the 'chat' control (most likelly 't') 42 | }, 0); // from being written in the chat 43 | } 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /client/controller/dialogs.js: -------------------------------------------------------------------------------- 1 | import * as view from '../view/index.js'; 2 | import * as platform from '../model/platform.js'; 3 | import * as socket from './socket.js'; 4 | 5 | view.dialogs.bindSettingsButtons(view.dialogs.openSettingsBox); 6 | view.dialogs.bindCloseSettingsButton(view.dialogs.closeSettingsBox); 7 | 8 | view.dialogs.bindInfoButton(view.dialogs.openInfoBox); 9 | view.dialogs.bindCloseInfoButton(view.dialogs.closeInfoBox); 10 | 11 | view.dialogs.bindLeaveButtons(() => { 12 | view.history.push(); 13 | }); 14 | 15 | view.dialogs.bindDialogCloseButton(); 16 | 17 | if (platform.isUnsupported) { // neither Chrome nor Firefox 18 | view.dialogs.showDialog('Unsupported Device', 'We only support Chrome and Firefox on desktop for now. Or rather this game works best on those browser.
Please come back using one of these (on old computers, Firefox is generally faster).'); 19 | } else if (platform.isMobile) { // Chrome or Firefox mobile 20 | view.dialogs.showDialog('Unsupported Device', 'The game is likely not to work properly on your device. We plan to support this platform but we\'re focusing major issues and bugs. Thus the game might not work properly. At the moment, this game works best on Chrome and Firefox on desktop.
Please come back using one of these (on old computers, Firefox is generally faster)'); 21 | } 22 | 23 | view.dialogs.bindAutoConnectSearch((index) => { 24 | console.log(index); 25 | let { serverId, lobbyId } = view.history.getConnectionIds(); 26 | let entry, slaveCo; 27 | for (entry of view.servers.slaveRows) { 28 | slaveCo = entry[0]; 29 | if (serverId === slaveCo.id) { 30 | socket.makeNewCurrentConnection(slaveCo, lobbyId); 31 | view.dialogs.hideAutoConnect(); 32 | break; 33 | } 34 | } 35 | if (index == 20) { 36 | view.notif.showNotif('Ooops.', 'We haven\'t found the server you are looking for!'); 37 | view.dialogs.hideAutoConnect(); //quit search 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /static/assets/images/enemyRed2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienYellow_hurt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/windowbox.js: -------------------------------------------------------------------------------- 1 | import vinage from 'vinage'; 2 | import modulo from '../../shared/modulo.js'; 3 | import * as model from '../model/index.js'; 4 | 5 | let canvas = document.getElementById('canvas'), 6 | windowBox = new vinage.Rectangle(new vinage.Point(null, null), canvas.clientWidth, canvas.clientHeight); 7 | windowBox.wrapX = function(entityX) {//get the position where the entity can be drawn on the screen 8 | return (modulo(entityX + model.entities.universe.width/2 - this.center.x, model.entities.universe.width) -model.entities.universe.width/2 + canvas.width/2 - (this.width*model.controls.zoomFactor - this.width)/2) * model.controls.zoomFactor; 9 | }; 10 | windowBox.wrapY = function(entityY) {//get the position where the entity can be drawn on the screen 11 | return (modulo(entityY + model.entities.universe.height/2 - this.center.y, model.entities.universe.height) -model.entities.universe.height/2 + canvas.height/2 - (this.height*model.controls.zoomFactor - this.height)/2) * model.controls.zoomFactor; 12 | }; 13 | windowBox.strokeAtmos = function(context, cx, cy, r, sw) { 14 | context.beginPath(); 15 | context.arc(cx, cy, r*model.controls.zoomFactor, 0, 2 * Math.PI, false); 16 | context.globalAlpha = 0.1; 17 | context.fill(); 18 | context.globalAlpha = 1; 19 | context.lineWidth = sw*model.controls.zoomFactor; 20 | context.stroke(); 21 | context.closePath(); 22 | }; 23 | windowBox.drawRotatedImage = function(context, image, x, y, angle, sizeX, sizeY, mirrorX, mirrorY) { 24 | sizeX *= model.controls.zoomFactor; 25 | sizeY *= model.controls.zoomFactor; 26 | 27 | context.translate(x, y); 28 | context.rotate(angle); 29 | context.scale(mirrorX === true ? -1 : 1, mirrorY === true ? -1 : 1); 30 | let wdt = sizeX || image.width*model.controls.zoomFactor, 31 | hgt = sizeY || image.height*model.controls.zoomFactor; 32 | context.drawImage(image, -(wdt / 2), -(hgt / 2), wdt, hgt); 33 | context.resetTransform(); 34 | }; 35 | 36 | export default windowBox; 37 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienBeige_hurt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/game/planet.js: -------------------------------------------------------------------------------- 1 | import Planet from '../../shared/planet.js'; 2 | import * as model from '../model/index.js'; 3 | 4 | export default class CltPlanet extends Planet { 5 | constructor(x, y, radius, type) { 6 | super(x, y, radius, type); 7 | this.color = 'rgb(80,80,80)'; 8 | } 9 | updateColor() { 10 | if (this.team === 'neutral') this.color = 'rgb(80,80,80)'; 11 | else { 12 | let fadeRGB = []; 13 | for (let j = 0; j <= 2; j++) fadeRGB[j] = Math.round(this.progress / 100 * (parseInt(this.teamColors[this.team].substr(1 + j * 2, 2), 16) - 80) + 80); 14 | 15 | this.color = 'rgb(' + fadeRGB[0] + ',' + fadeRGB[1] + ',' + fadeRGB[2] + ')'; 16 | } 17 | } 18 | draw(context, windowBox) { 19 | let cx = windowBox.wrapX(this.box.center.x), 20 | cy = windowBox.wrapY(this.box.center.y); 21 | 22 | //draw planet 23 | context.beginPath(); 24 | context.arc(cx, cy, this.box.radius*model.controls.zoomFactor, 0, 2 * Math.PI, false); 25 | context.closePath(); 26 | context.fill(); 27 | 28 | //apply texture 29 | windowBox.drawRotatedImage(context, window.resources['planet'], cx, cy, this.box.radius*model.controls.zoomFactor / 200 * Math.PI, 2*this.box.radius, 2*this.box.radius); 30 | 31 | //draw progress indicator 32 | context.beginPath(); 33 | context.arc(cx, cy, 50*model.controls.zoomFactor, -Math.PI * 0.5, (this.progress / 100) * Math.PI * 2 - Math.PI * 0.5, false); 34 | context.lineWidth = 10*model.controls.zoomFactor; 35 | context.strokeStyle = 'rgba(0, 0, 0, 0.2)'; 36 | context.stroke(); 37 | context.closePath(); 38 | } 39 | drawAtmos(context, windowBox) { 40 | context.fillStyle = this.color; 41 | context.strokeStyle = context.fillStyle; 42 | 43 | windowBox.strokeAtmos( 44 | context, 45 | windowBox.wrapX(this.box.center.x), 46 | windowBox.wrapY(this.box.center.y), 47 | this.box.radius*1.75, 2 48 | ); 49 | } 50 | } 51 | CltPlanet.prototype.teamColors = {'alienBeige': '#e5d9be', 'alienBlue': '#a2c2ea', 'alienGreen': '#8aceb9', 'alienPink': '#f19cb7', 'alienYellow': '#fed532' }; 52 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/smg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlack3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyGreen3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienPink_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlue3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyRed3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/view/hud.js: -------------------------------------------------------------------------------- 1 | import * as model from '../model/index.js'; 2 | import * as views from './views.js'; 3 | 4 | export function readyPointCounter(scoresObj) { 5 | let pointsElement = document.getElementById('gui-points'); 6 | while (pointsElement.firstChild) pointsElement.removeChild(pointsElement.firstChild); // clear score count GUI 7 | for (let team in scoresObj) { 8 | let teamItem = document.createElement('div'); 9 | teamItem.id = 'gui-points-' + team; 10 | pointsElement.appendChild(teamItem); 11 | } 12 | } 13 | export function updatePointCounter() { 14 | for (let team in model.game.scores) { 15 | let element = document.getElementById('gui-points-' + team); 16 | if (element !== null) element.textContent = model.game.scores[team]; 17 | } 18 | views.centerElement(document.getElementById('gui-points')); 19 | } 20 | 21 | export function initMinimap() { 22 | let minimapCanvas = document.getElementById('gui-minimap-canvas'); 23 | //the minimap ALWAYS has the same SURFACE, the dimensions however vary depending on the universe size 24 | let minimapSurface = Math.pow(150, 2),//TODO: make it relative to the window, too 25 | //(width)x * (height)x = minimapSurface 26 | unitSize = Math.sqrt(minimapSurface/(model.entities.universe.width*model.entities.universe.height));//in pixels 27 | minimapCanvas.width = unitSize*model.entities.universe.width; 28 | minimapCanvas.height = unitSize*model.entities.universe.height; 29 | } 30 | 31 | export function showWarmupStatus() { 32 | document.getElementById('gui-warmup').classList.remove('hidden'); 33 | } 34 | export function hideWarmupStatus() { 35 | document.getElementById('gui-warmup').classList.add('hidden'); 36 | } 37 | 38 | export function updateHealth() { 39 | Array.prototype.forEach.call(document.querySelectorAll('#gui-health div'), (element, index) => { 40 | let state = 'heartFilled'; 41 | if (index * 2 + 2 <= model.game.ownHealth) state = 'heartFilled'; 42 | else if (index * 2 + 1 === model.game.ownHealth) state = 'heartHalfFilled'; 43 | else state = 'heartNotFilled'; 44 | element.className = state; 45 | }); 46 | } 47 | export function updateFuel() { 48 | let staminaElem = document.getElementById('gui-stamina'); 49 | if (staminaElem.value !== model.game.ownFuel) staminaElem.value = model.game.ownFuel; 50 | } 51 | -------------------------------------------------------------------------------- /static/layout.css: -------------------------------------------------------------------------------- 1 | @media all and (max-width: 900px) { 2 | html { 3 | font-size: .9em; 4 | } 5 | .sidebox { 6 | width: 75vw; 7 | } 8 | #gui-points { 9 | font-size: 1.5em; 10 | } 11 | #gui-points th { 12 | width: 80px; 13 | } 14 | #gui-stamina { 15 | top: 44px; 16 | width: 120px; 17 | } 18 | #gui-health { 19 | top: 14px; 20 | } 21 | #gui-controls li img { 22 | width: 65px; 23 | height: 65px; 24 | } 25 | #chat-input-container { 26 | width: calc(100vw - 340px); 27 | } 28 | #gui-minimap-canvas { 29 | top: 6px; 30 | transform-origin: top right; 31 | transform: scale(.75); 32 | } 33 | #gui-chat { 34 | left: 12px; 35 | top: 107px; 36 | width: 250px; 37 | height: calc(100vh - 4vh - 205px); 38 | } 39 | #gui-options { 40 | top: 64px; 41 | left: 6px; 42 | right: initial; 43 | /* right attribute needs to be reset, otherwise div would be stretched all over the screen, eventhough this isn't 44 | visible to the viewer, it prevents from shooting in case the mouse cursor is over the div's area */ 45 | } 46 | #lobby-table th:last-child { 47 | width: 6em; 48 | } 49 | #info-button { 50 | display: none; 51 | } 52 | #menu-box { 53 | flex-direction: column; 54 | } 55 | #sidebar { 56 | order: -1; 57 | min-width: none; 58 | } 59 | #sidebar > div { display: inline-block; } 60 | } 61 | @media all and (max-width: 640px) { 62 | html { 63 | font-size: .8em; 64 | } 65 | button { 66 | min-width: 74px; 67 | padding: 3px; 68 | } 69 | .sidebox { 70 | max-width: initial; 71 | width: 100vw; 72 | } 73 | #gui-warmup { 74 | font-size: 1.8em; 75 | padding-left: 5%; 76 | padding-right: 5%; 77 | } 78 | #gui-points { 79 | font-size: 1.25em; 80 | } 81 | #gui-chat { 82 | top: 95px; 83 | height: calc(100vh - 4vh - 193px); 84 | } 85 | #gui-points th { 86 | width: 40px; 87 | } 88 | #gui-controls li img { 89 | width: 50px; 90 | height: 50px; 91 | } 92 | #gui-chat-input-container { 93 | width: calc(100vw - 270px); 94 | } 95 | #gui-minimap-canvas { 96 | transform: scale(.6); 97 | } 98 | #gui-options { 99 | top: 60px; 100 | } 101 | #lobby-table th:last-child { 102 | width: 5.2em; 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /static/assets/images/controls/jump.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlack5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyGreen5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/alienGreen_hurt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyGreen1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlack1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlue5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyRed5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /shared/player.js: -------------------------------------------------------------------------------- 1 | import vinage from 'vinage'; 2 | import resources from '../server/resource_loader.js'; 3 | 4 | import Smg from '<@Smg@>'; 5 | import Lmg from '<@Lmg@>'; 6 | import Shotgun from '<@Shotgun@>'; 7 | import Knife from '<@Knife@>'; 8 | 9 | export default class Player { 10 | constructor() { 11 | this.box = new vinage.Rectangle(new vinage.Point(0, 0), 0, 0); 12 | this.controls = { 13 | jump: 0, 14 | crouch: 0, 15 | jetpack: 0, 16 | moveLeft: 0, 17 | moveRight: 0, 18 | run: 0, 19 | changeWeapon: 0, 20 | shoot: 0 21 | }; 22 | this.velocity = new vinage.Vector(0, 0); 23 | this._walkFrame = 'stand'; 24 | 25 | this.jetpack = false; 26 | this.health = 8; 27 | this.fillStamina(); 28 | this.attachedPlanet = -1; 29 | this.lastlyAimedAt = Date.now(); 30 | 31 | this.weapons = { 32 | Smg: new Smg(this), 33 | Lmg: new Lmg(this), 34 | Shotgun: new Shotgun(this), 35 | Knife: new Knife(this) 36 | }; 37 | this.armedWeapon = this.weapons.Lmg; 38 | this.carriedWeapon = this.weapons.Smg; 39 | 40 | this.aimAngle = 0; 41 | } 42 | get appearance() { 43 | return this._appearance; 44 | } 45 | set appearance(newAppearance) { 46 | this._appearance = newAppearance; 47 | this.setBoxSize(); 48 | } 49 | get walkFrame() { 50 | return this._walkFrame; 51 | } 52 | set walkFrame(newWalkFrame) { 53 | this._walkFrame = newWalkFrame; 54 | this.setBoxSize(); 55 | } 56 | 57 | increaseStamina(by) { // returns whether the stamina has been succesfully increased 58 | let predicStamina = this.stamina + by; 59 | 60 | if (this.stamina === this.maxStamina) return false; 61 | else if (predicStamina > this.maxStamina) { 62 | this.stamina = this.maxStamina; 63 | return true; 64 | } else { 65 | this.stamina = predicStamina; 66 | return true; 67 | } 68 | } 69 | decreaseStamina(by) { // returns whether the stamina has been succesfully decerased 70 | let predicStamina = this.stamina - by; 71 | 72 | if (this.stamina === 0) return false; 73 | else if (predicStamina < 0) { 74 | this.stamina = 0; 75 | return true; 76 | } else { 77 | this.stamina = predicStamina; 78 | return true; 79 | } 80 | } 81 | fillStamina() { 82 | this.stamina = this.maxStamina; 83 | } 84 | 85 | setBoxSize() { 86 | this.box.width = resources[this.appearance + '_' + this.walkFrame].width; 87 | this.box.height = resources[this.appearance + '_' + this.walkFrame].height; 88 | } 89 | } 90 | Player.prototype.maxStamina = 300; 91 | -------------------------------------------------------------------------------- /static/assets/images/alienBlue_hurt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/assets/images/enemyBlue1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /server/mods/capture/player.js: -------------------------------------------------------------------------------- 1 | import Player from '../../../shared/player.js'; 2 | import { config } from '../../config_loader.js'; 3 | import * as monitor from '../../monitor.js'; 4 | 5 | export default class SrvPlayer extends Player { 6 | constructor(dc) { 7 | super(); 8 | this.dc = dc; 9 | 10 | this.lastMessage = Date.now(); 11 | this._lastHurt = 0; 12 | this._walkCounter = 0; 13 | 14 | this.jumpState = this.jumpStates.FLOATING; 15 | } 16 | 17 | send(data) { 18 | try { 19 | this.dc.send(data); 20 | if (config.monitor) { 21 | monitor.traffic.beingConstructed.out += data.byteLength;//record outgoing traffic for logging 22 | } 23 | } catch (err) { 24 | console.error(err); 25 | } 26 | } 27 | 28 | get hurt() { 29 | return Date.now() - this._lastHurt < 600; 30 | } 31 | set hurt(hurt) { 32 | this._lastHurt = hurt ? Date.now() : 0; 33 | } 34 | 35 | setWalkFrame() { 36 | if (this.box === undefined) return; 37 | if (this.attachedPlanet === -1){ 38 | this.walkFrame = 'jump'; 39 | } else { 40 | let walkFlag = (this.controls['moveLeft'] > 0) * 1 | (this.controls['moveRight'] > 0) * 2 | (this.controls['run'] > 0) * 4; 41 | if (!(walkFlag & 3) || (walkFlag & 3) === 3) this.walkFrame = (this.controls['crouch']) ? 'duck' : 'stand'; 42 | else if (this._walkCounter++ >= (walkFlag >> 2 ? 6 : 10)) { 43 | this._walkCounter = 0; 44 | this.walkFrame = (this.walkFrame === 'walk1') ? 'walk2' : 'walk1'; 45 | } 46 | this.setBoxSize(); 47 | } 48 | } 49 | 50 | updateJumpState(jumpPressed) { 51 | switch (this.jumpState) { 52 | case this.jumpStates.JUMPING: 53 | if (!jumpPressed) this.jumpState = this.jumpStates.FLOATING; 54 | break; 55 | case this.jumpStates.FLOATING: 56 | if (jumpPressed) this.jumpState = this.jumpStates.JETPACK; 57 | break; 58 | case this.jumpStates.JETPACK: 59 | if (!jumpPressed) this.jumpState = this.jumpStates.FLOATING; 60 | } 61 | } 62 | jump() { 63 | this.jumpState = this.jumpStates.JUMPING; 64 | this.attachedPlanet = -1; 65 | 66 | this.velocity.x = Math.sin(this.box.angle) * 6; 67 | this.velocity.y = -Math.cos(this.box.angle) * 6; 68 | 69 | this.box.center.x += this.velocity.x; 70 | this.box.center.y += this.velocity.y; 71 | } 72 | } 73 | SrvPlayer.prototype.jumpStates = { // FSM 74 | JUMPING: 0, // the player has pressed space (they may hold it) 75 | FLOATING: 1, // the player has released space 76 | JETPACK: 2 // the player has pressed space again (they may hold it) 77 | }; 78 | -------------------------------------------------------------------------------- /static/assets/images/enemyRed1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/model/entities.js: -------------------------------------------------------------------------------- 1 | import vinage from 'vinage'; 2 | import * as game from './game.js'; 3 | import * as view from '../view/index.js'; 4 | 5 | import Planet from '../game/planet.js'; 6 | import Enemy from '../game/enemy.js'; 7 | import Player from '../game/player.js'; 8 | import Shot from '../game/shot.js'; 9 | 10 | export let universe = new vinage.Rectangle(new vinage.Point(0, 0), null, null), // these parameters will be overwritten later 11 | players = [], 12 | planets = [], 13 | enemies = [], 14 | shots = [], 15 | deadShots = []; 16 | 17 | export function addPlanet(x, y, radius, type) { 18 | planets.push(new Planet(x, y, radius, type)); 19 | } 20 | export function updatePlanet(id, ownedBy, progress) { 21 | planets[id].team = ownedBy; 22 | planets[id].progress = progress; 23 | planets[id].updateColor(); 24 | } 25 | 26 | export function addEnemy(x, y, appearance) { 27 | enemies.push(new Enemy(x, y, appearance)); 28 | } 29 | export function updateEnemy(id, angle) { 30 | enemies[id].box.angle = angle; 31 | } 32 | 33 | export function addPlayer(pid, appearance, homographId, name) { 34 | let newPlayer = new Player(pid, appearance, homographId, name); 35 | players[pid] = newPlayer; 36 | view.chat.printChatMessage(undefined, undefined, newPlayer.getFinalName() + ' joined the game'); 37 | } 38 | export function updatePlayer(pid, x, y, attachedPlanet, angle, looksLeft, jetpack, hurt, walkFrame, armedWeapon, carriedWeapon, aimAngle) { 39 | if (!players[pid]) return; 40 | players[pid].playSteps(players[game.ownIdx], walkFrame, x, y); 41 | players[pid].playJetpack(players[game.ownIdx], jetpack); 42 | 43 | if (jetpack) view.controls.onscreen.displayJetpack(); 44 | else view.controls.onscreen.displayJump(); 45 | 46 | players[pid].update(x, y, attachedPlanet, angle, looksLeft, jetpack, hurt, walkFrame, armedWeapon, carriedWeapon, aimAngle); 47 | } 48 | export function addShot(x, y, angle, origin, type) { 49 | view.audio.laserModel.makeSound(view.audio.makePanner(x - players[game.ownIdx].box.center.x, y - players[game.ownIdx].box.center.y)).start(0); 50 | let shot = new Shot(x, y, angle, origin, type); 51 | shots.push(shot); 52 | let originatingPlayer = players.find(element => { 53 | return element !== undefined && element.pid === origin; 54 | }); 55 | if (originatingPlayer) originatingPlayer.armedWeapon.muzzleFlash = type === shot.TYPES.BULLET || type === shot.TYPES.BALL; 56 | } 57 | 58 | export function clean() { 59 | players = []; 60 | planets = []; 61 | enemies = []; 62 | shots = []; 63 | deadShots = []; 64 | } 65 | -------------------------------------------------------------------------------- /static/assets/images/jetpack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | --------------------------------------------------------------------------------