├── 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 |
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 |
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 |
--------------------------------------------------------------------------------