nginx change

This commit is contained in:
ntr
2019-04-02 18:19:33 +11:00
parent 4c16afa786
commit 23c7ae7aab
99 changed files with 538 additions and 538 deletions
+219
View File
@@ -0,0 +1,219 @@
const toast = require('izitoast');
function registerEvents(registry, events, tutorial) {
function setCryps(cryps) {
registry.set('cryps', cryps);
tutorial('homepage');
}
function setCrypList(cryps) {
registry.set('crypList', cryps);
}
function setWs(ws) {
registry.set('ws', ws);
}
function setGame(game) {
if (game.phase === 'Skill') tutorial('skillPhase');
if (game.phase === 'Target') tutorial('targetPhase');
if (game.resolved.length) tutorial('resolutionPhase');
if (game.phase === 'Finish') tutorial('finishPhase');
return registry.set('game', game);
}
function setAccount(account) {
registry.set('account', account);
registry.set('home', true);
events.emit('ACCOUNT', account);
}
function setActiveSkill(skill) {
registry.set('activeSkill', skill);
}
function setMenu() {
registry.set('menu', true);
}
function setVbox(items) {
registry.set('vbox', items);
}
function setScores(scores) {
registry.set('scores', scores);
}
function setPlayerList(list) {
registry.set('playerList', list);
registry.set('homeInstances', true);
}
function setPlayer(player) {
registry.set('player', player);
if (!registry.get('inMenu')) {
setMenu();
}
}
function setZone(zone) {
registry.set('zone', zone);
}
function setGameList(gameList) {
registry.set('gameList', gameList);
}
function setCrypStatusUpdate(id, skill, target) {
registry.set('crypStatusUpdate', { id, skill, target });
}
events.on('SET_PLAYER', setPlayer);
events.on('SEND_SKILL', function skillActive(gameId, crypId, targetCrypId, skill) {
const ws = registry.get('ws');
ws.sendGameSkill(gameId, crypId, targetCrypId, skill);
setCrypStatusUpdate(crypId, skill, targetCrypId);
});
events.on('CRYP_ACTIVE', function crypActiveCb(cryp) {
const cryps = registry.get('cryps');
for (let i = 0; i < cryps.length; i += 1) {
if (cryps[i].id === cryp.id) cryps[i].active = !cryps[i].active;
}
return setCryps(cryps);
});
const errMessages = {
select_cryps: 'Select your cryps before battle using the numbered buttons next to the cryp avatar',
complete_nodes: 'You need to complete the previously connected nodes first',
max_skills: 'Your cryp can only learn a maximum of 4 skills',
};
function errorPrompt(type) {
const message = errMessages[type];
const OK_BUTTON = '<button type="submit">OK</button>';
toast.info({
theme: 'dark',
color: 'black',
timeout: false,
drag: false,
position: 'center',
maxWidth: window.innerWidth / 2,
close: false,
buttons: [
[OK_BUTTON, (instance, thisToast) => instance.hide({ transitionOut: 'fadeOut' }, thisToast)],
],
message,
});
}
function loginPrompt() {
const USER_INPUT = '<input className="input" type="email" placeholder="username" />';
const PASSWORD_INPUT = '<input className="input" type="password" placeholder="password" />';
const LOGIN_BUTTON = '<button type="submit">Login</button>';
const REGISTER_BUTTON = '<button type="submit">Register</button>';
const DEMO_BUTTON = '<button type="submit">Demo</button>';
const ws = registry.get('ws');
function submitLogin(instance, thisToast, button, e, inputs) {
const USERNAME = inputs[0].value;
const PASSWORD = inputs[1].value;
ws.sendAccountLogin(USERNAME, PASSWORD);
}
function submitRegister(instance, thisToast, button, e, inputs) {
const USERNAME = inputs[0].value;
const PASSWORD = inputs[1].value;
ws.sendAccountCreate(USERNAME, PASSWORD);
}
function submitDemo() {
ws.sendAccountDemo();
}
const existing = document.querySelector('#login'); // Selector of your toast
if (existing) toast.hide({}, existing, 'reconnect');
toast.question({
id: 'login',
theme: 'dark',
color: 'black',
timeout: false,
// overlay: true,
drag: false,
close: false,
title: 'LOGIN',
position: 'center',
inputs: [
[USER_INPUT, 'change', () => true, true], // true to focus
[PASSWORD_INPUT, 'change', () => true],
],
buttons: [
[LOGIN_BUTTON, submitLogin], // true to focus
[REGISTER_BUTTON, submitRegister], // true to focus
[DEMO_BUTTON, submitDemo], // true to focus
],
});
events.once('ACCOUNT', function closeLoginCb() {
const prompt = document.querySelector('#login'); // Selector of your toast
if (prompt) toast.hide({ transitionOut: 'fadeOut' }, prompt, 'event');
});
}
events.on('CRYP_SPAWN', function spawnPrompt() {
const NAME_INPUT = '<input className="input" type="email" placeholder="name" />';
const SPAWN_BUTTON = '<button type="submit">SPAWN</button>';
const ws = registry.get('ws');
function submitSpawn(instance, thisToast, button, e, inputs) {
const NAME = inputs[0].value;
ws.sendCrypSpawn(NAME);
instance.hide({ transitionOut: 'fadeOut' }, thisToast, 'button');
}
toast.question({
theme: 'dark',
color: 'black',
timeout: false,
// overlay: true,
drag: false,
close: true,
title: 'SPAWN CRYP',
position: 'center',
inputs: [
[NAME_INPUT, 'change', null, true], // true to focus
],
buttons: [
[SPAWN_BUTTON, submitSpawn], // true to focus
],
});
});
tutorial('welcome');
return {
errorPrompt,
loginPrompt,
setAccount,
setActiveSkill,
setCryps,
setCrypList,
setGame,
setMenu,
setPlayer,
setPlayerList,
setVbox,
setWs,
setGameList,
setZone,
setScores,
};
}
module.exports = registerEvents;
+17
View File
@@ -0,0 +1,17 @@
const renderCryps = require('./scenes/cryps');
const createSocket = require('./socket');
const registerEvents = require('./events');
const createTutorial = require('./tutorial');
document.fonts.load('10pt "Jura"').then(() => {
const game = renderCryps();
const tutorial = createTutorial();
const events = registerEvents(game.registry, game.events, tutorial);
const ws = createSocket(events);
events.setWs(ws);
events.setGameList([]);
ws.connect();
});
+13
View File
@@ -0,0 +1,13 @@
const genAvatar = (name) => {
let hash = 0;
if (name.length === 0) return hash;
// Probs don't need to hash using the whole string
for (let i = 0; i < name.length; i += 1) {
const chr = name.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash = hash & 10000; // We have avatars named 0-19
}
return `sprite${hash}`;
};
module.exports = genAvatar;
+166
View File
@@ -0,0 +1,166 @@
const Phaser = require('phaser');
const CHART = `
#ifdef GL_ES
precision mediump float;
#endif
#extension GL_OES_standard_derivatives : enable
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
float rand(float n){return fract(sin(n) * 43758.5453123 * time * 0.00001);}
float noise(float p){
float fl = floor(p);
float fc = fract(p);
// return mix(rand(fl), rand(fl + 1.0), p);
return mix(rand(fl), rand(fl + 1.0), p);
}
float getLine(vec2 p, float y){
float margin = 0.;
vec2 pos = p;
float a = time * 100. + y * 31.;
vec2 lineCenter = vec2(0.5, y);
pos -= lineCenter;
pos *- mat2(cos(a), -sin(a), sin(a), cos(a));
pos += lineCenter;
float marginb = 0.005;
float b = 0.004;
float t = y + (noise((pos.x + y) * 100.) - 0.5) * 0.02;
float f = (smoothstep(t - b, t, pos.y) - smoothstep(t, t + b, pos.y));
f *= smoothstep(margin - marginb, margin, pos.x) - smoothstep(1. - margin, 1. - margin + marginb, pos.x);
f *= 0.8;
float light = 0.5 + 0.5 * sin(time * .2);
vec2 point = vec2(margin + light * (1. - margin * 2.), t);
f += .008 / distance(pos, point);
return f;
}
void main( void ) {
vec2 p = gl_FragCoord.xy / resolution.xy;
float f = 0.;
for(int i = 0; i < 10; i++){
f += getLine(p, 0.1 + (0.8) / 10. * float(i));
}
vec3 color = vec3(0., .4, .6) * f;
gl_FragColor = vec4(color, 1.);
}`;
const STARS = `
//--- hatsuyuki ---
// by Catzpaw 2016
#ifdef GL_ES
precision mediump float;
#endif
#extension GL_OES_standard_derivatives : enable
uniform float time;
uniform vec2 resolution;
float hash(float x){
return fract(sin(x*133.3)*12.13);
}
void main(void){
vec2 uv=(gl_FragCoord.xy*2.-resolution.xy)/min(resolution.x,resolution.y);
vec3 c=vec3(.2,.2,.2);
float a=4.4;
float si=sin(a),co=cos(a);
uv*=mat2(co,-si,si,co);
uv*=length(uv+vec2(0,1.9))*.5+1.;
float v=1.-sin(hash(floor(uv.x*200.))*2.);
float b=clamp(abs(sin(5.*time*v+uv.y*(5./(2.+v))))-.95,0.,1.)*20.;
c*=v*b;
gl_FragColor = vec4(c,2);
}
`;
const PLASMA = `
precision mediump float;
uniform sampler2D uMainSampler;
uniform vec2 resolution;
uniform float time;
varying vec2 outTexCoord;
varying vec4 outTint;
#define MAX_ITER 4
void main( void )
{
vec2 v_texCoord = gl_FragCoord.xy / resolution;
vec2 p = v_texCoord * 8.0 - vec2(20.0);
vec2 i = p;
float c = 1.0;
float inten = .05;
for (int n = 0; n < MAX_ITER; n++)
{
float t = time * (1.0 - (3.0 / float(n+1)));
i = p + vec2(cos(t - i.x) + sin(t + i.y),
sin(t - i.y) + cos(t + i.x));
c += 1.0/length(vec2(p.x / (sin(i.x+t)/inten),
p.y / (cos(i.y+t)/inten)));
}
c /= float(MAX_ITER);
c = 1.5 - sqrt(c);
vec4 texColor = vec4(0.01, 0.01, 0.01, 1.0);
texColor.rgb *= (1.0 / (1.0 - (c + 0.05)));
gl_FragColor = texColor;
}
`;
const CustomPipeline = new Phaser.Class({
Extends: Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline,
initialize: function CustomPipeline (game) {
Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline.call(this, {
game,
renderer: game.renderer,
fragShader: STARS,
});
},
});
class Background extends Phaser.Scene {
constructor() {
super({ key: 'Background', active: true });
this.bgTime = 10.0;
}
create() {
const game = this.game;
this.customPipeline = game.renderer.addPipeline('Custom', new CustomPipeline(game));
this.customPipeline.setFloat2('resolution', 1600, 1000);
const sprite = this.add.sprite(800, 500);
sprite.setPipeline('Custom');
sprite.displayWidth = 1600 * window.devicePixelRatio;
sprite.displayHeight = 1000 * window.devicePixelRatio;
}
update() {
this.customPipeline.setFloat1('time', this.bgTime);
this.bgTime += 0.005;
}
}
module.exports = Background;
@@ -0,0 +1,250 @@
const Phaser = require('phaser');
const { POSITIONS: { COMBAT }, DELAYS } = require('./constants');
const randomColour = () => {
const colours = ['green', 'blue', 'red', 'white', 'yellow'];
return colours[Math.floor(Math.random() * 5)];
};
const animationParams = (sourceAlly) => {
const spawnLocation = sourceAlly ? COMBAT.width() * 0.35 : COMBAT.width() * 0.65;
const speed = sourceAlly ? 250 : -250;
const img = randomColour();
const angleMin = sourceAlly ? 320 : 180;
const angleMax = sourceAlly ? 360 : 220;
return { spawnLocation, speed, img, angleMin, angleMax };
};
const randomAttack = () => {
const animations = ['wall', 'spit', 'gravBlast', 'gravBomb', 'chargeBall'];
return animations[Math.floor(Math.random() * 5)];
};
class CombatSkills extends Phaser.GameObjects.Group {
constructor(scene) {
super(scene);
this.scene = scene;
}
getSkill(type, sourceAlly, targetAlly, castLocation) {
const genericHeal = ['Heal', 'Triage', 'TriageTick', 'DecayTick'];
const genericBlock = ['Block', 'Parry', 'Evasion', 'Shield'];
if (genericHeal.includes(type)) {
this.genericHeal(targetAlly, castLocation);
} else if (genericBlock.includes(type)) {
this.genericBlock(sourceAlly);
} else {
this[randomAttack()](sourceAlly);
}
}
genericHeal(sourceAlly, castLocation) {
// const { sourceX, sourceY } = getCrypPosition(sourcePos, 0);
const lifespan = DELAYS.ANIMATION_DURATION;
const colour = randomColour();
const particles = this.scene.add.particles(colour);
const x = sourceAlly ? COMBAT.width() * 0.3 : COMBAT.width() * 0.7;
const emitter2 = particles.createEmitter({
x: castLocation.x,
y: castLocation.y,
moveToX: x,
moveToY: COMBAT.height() * 0.2,
speed: 500,
lifespan: lifespan / 3,
scale: { start: 0.5, end: 1 },
quantity: 3,
_frequency: 20,
blendMode: 'ADD',
emitZone: { source: new Phaser.Geom.Rectangle(-200, -100, 400, 200) },
});
const emitter = particles.createEmitter({
x,
y: COMBAT.height() * 0.2,
angle: { min: 250, max: 290 },
speed: 250,
gravityY: 1000,
quantity: 4,
scale: { start: 0.1, end: 1 },
blendMode: 'ADD',
lifespan,
active: false,
});
this.add(particles);
this.scene.time.delayedCall(lifespan / 3, () => { emitter2.stop(); }, [], this);
this.scene.time.delayedCall(lifespan / 3, () => { emitter.active = true; }, [], this);
this.scene.time.delayedCall(lifespan, () => { emitter.stop(); }, [], this);
}
genericBlock(sourceAlly) {
const lifespan = DELAYS.ANIMATION_DURATION;
const colour = randomColour();
const x = sourceAlly ? COMBAT.width() * 0.3 : COMBAT.width() * 0.7;
const emitter1 = this.scene.add.particles(colour).createEmitter({
x,
y: COMBAT.height() * 0.4,
scale: { start: 0.75, end: 0.25 },
blendMode: 'ADD',
emitZone: {
source: new Phaser.Geom.Rectangle(-100, -100, 200, 200),
type: 'edge',
quantity: 24,
yoyo: true,
},
});
const emitter2 = this.scene.add.particles(colour).createEmitter({
x,
y: COMBAT.height() * 0.4,
blendMode: 'SCREEN',
scale: { start: 0.2, end: 0 },
speed: { min: -100, max: 100 },
quantity: 50,
active: false,
emitZone: {
source: new Phaser.Geom.Rectangle(-100, -100, 200, 200),
type: 'edge',
quantity: 50,
},
});
this.scene.time.delayedCall(lifespan / 2, () => { emitter1.stop(); }, [], this);
this.scene.time.delayedCall(lifespan / 2, () => { emitter2.active = true; }, [], this);
this.scene.time.delayedCall(lifespan, () => { emitter2.stop(); }, [], this);
}
wall(sourceAlly) {
const lifespan = DELAYS.ANIMATION_DURATION;
const { spawnLocation, speed, img } = animationParams(sourceAlly);
const particles = this.scene.add.particles(img);
const emitter = particles.createEmitter({
x: spawnLocation,
y: { min: COMBAT.height() * 0.2, max: COMBAT.height() * 0.5 },
speedX: { min: speed, max: speed * 2 },
scale: { start: 0.4, end: 0 },
quantity: 4,
blendMode: 'ADD',
lifespan,
});
this.add(particles);
this.scene.time.delayedCall(1000, () => { emitter.stop(); }, [], this.scene);
}
spit(sourceAlly) {
const lifespan = DELAYS.ANIMATION_DURATION;
const { spawnLocation, speed, img, angleMin, angleMax } = animationParams(sourceAlly);
const particles = this.scene.add.particles(img);
const emitter = particles.createEmitter({
x: spawnLocation,
y: COMBAT.height() * 0.35,
angle: { min: angleMin, max: angleMax },
speed: speed * 2,
scale: { start: 0.4, end: 1 },
gravityY: 250,
quantity: 4,
blendMode: 'ADD',
lifespan,
});
this.add(particles);
this.scene.time.delayedCall(lifespan, () => { emitter.stop(); }, [], this);
}
gravBomb(sourceAlly) {
const lifespan = DELAYS.ANIMATION_DURATION;
const { spawnLocation, img } = animationParams(!sourceAlly);
const particles = this.scene.add.particles(img);
const well = particles.createGravityWell({
x: spawnLocation,
y: COMBAT.height() * 0.25,
power: 4,
gravity: 500,
});
this.emitter = particles.createEmitter({
x: spawnLocation,
y: COMBAT.height() * 0.25,
speed: 1000,
scale: { start: 0.7, end: 1 },
blendMode: 'ADD',
lifespan,
});
this.add(particles);
this.scene.time.delayedCall(lifespan, () => { this.emitter.stop(); well.active = false; }, [], this.scene);
}
gravBlast(sourceAlly) {
const lifespan = DELAYS.ANIMATION_DURATION;
const WELL_END = lifespan / 2;
const img = randomColour();
const spawnLocation = sourceAlly ? COMBAT.width() * 0.35 : COMBAT.width() * 0.65;
const isEnemyLocation = sourceAlly ? COMBAT.width() * 0.7 : COMBAT.width() * 0.3;
const particles = this.scene.add.particles(img);
const bounds = sourceAlly
? { x: COMBAT.width() * 0.3, y: COMBAT.height() * 0.2, w: COMBAT.width() * 0.5, h: COMBAT.height() * 0.2 }
: { x: 0.2 * COMBAT.width(), y: COMBAT.height() * 0.2, w: COMBAT.width() * 0.5, h: COMBAT.height() * 0.2 };
const well = particles.createGravityWell({
x: spawnLocation,
y: COMBAT.height() * 0.35,
power: 4,
gravity: 500,
});
const emitter = particles.createEmitter({
x: spawnLocation,
y: COMBAT.height() * 0.35,
speed: 1000,
scale: { start: 0.7, end: 1 },
blendMode: 'ADD',
bounds,
lifespan,
});
this.add(particles);
this.scene.time.delayedCall(WELL_END, () => { emitter.stop(); well.x = isEnemyLocation; }, [], this.scene);
this.scene.time.delayedCall(lifespan, () => { well.active = false; }, [], this.scene);
}
chargeBall(sourceAlly) {
const lifespan = DELAYS.ANIMATION_DURATION;
const CHARGE_LIFESPAN = lifespan / 3;
const { img, spawnLocation } = animationParams(sourceAlly);
const targetLocation = sourceAlly ? 0.7 * COMBAT.width() : 0.25 * COMBAT.width();
const particles = this.scene.add.particles(img);
const emitter = particles.createEmitter({
x: 0,
y: 0,
moveToX: spawnLocation,
moveToY: COMBAT.height() * 0.1,
scale: 0.75,
quantity: 4,
_frequency: 20,
blendMode: 'ADD',
emitZone: { source: new Phaser.Geom.Rectangle(0, 0, COMBAT.width(), COMBAT.height()) },
lifespan: CHARGE_LIFESPAN,
});
const emitter2 = particles.createEmitter({
radial: false,
x: { min: spawnLocation, max: targetLocation, steps: 90 },
y: { min: COMBAT.height() * 0.1, max: COMBAT.height() * 0.4, steps: 90 },
quantity: 4,
gravityY: 0,
scale: { start: 2, end: 0.1, ease: 'Power3' },
blendMode: 'ADD',
active: false,
lifespan: CHARGE_LIFESPAN,
});
this.add(particles);
this.scene.time.delayedCall(CHARGE_LIFESPAN, () => { emitter.stop(); }, [], this.scene);
this.scene.time.delayedCall(CHARGE_LIFESPAN * 2, () => { emitter2.active = true; }, [], this.scene);
this.scene.time.delayedCall(lifespan, () => { emitter2.stop(); }, [], this.scene);
}
cleanup() {
this.children.entries.forEach(obj => obj.destroy());
}
}
module.exports = CombatSkills;
+239
View File
@@ -0,0 +1,239 @@
const Phaser = require('phaser');
const genAvatar = require('./avatar');
const StatBar = require('./elements/combat.statbar');
const { DELAYS, TEXT, POSITIONS: { COMBAT } } = require('./constants');
const CRYP_MARGIN = COMBAT.crypMargin();
const TEXT_MARGIN = COMBAT.textMargin();
const crypAvatarText = (team, iter) => {
const nameX = COMBAT.width() * team;
const nameY = COMBAT.y() + CRYP_MARGIN * iter + COMBAT.height() * 0.07;
const statusX = COMBAT.width() * team;
const statusY = COMBAT.y() + TEXT_MARGIN * 6 + CRYP_MARGIN * iter + COMBAT.height() * 0.07;
return { statusX, statusY, nameX, nameY };
};
const crypEffects = (team, iter) => {
const crypEffectsX = team ? COMBAT.width() - COMBAT.width() / 6.5 : COMBAT.width() / 6.5;
const crypEffectsY = TEXT_MARGIN * 2 + CRYP_MARGIN * iter;
return { crypEffectsX, crypEffectsY };
};
const crypPosition = (team, iter) => {
const crypAvatarX = team ? COMBAT.width() - COMBAT.width() / 6 : COMBAT.width() / 6;
const crypAvatarY = TEXT_MARGIN * 5 + CRYP_MARGIN * iter;
return { crypAvatarX, crypAvatarY };
};
class Effects extends Phaser.GameObjects.Group {
constructor(scene, team, iter) {
super(scene);
this.scene = scene;
const { crypEffectsX, crypEffectsY } = crypEffects(team, iter);
this.x = crypEffectsX; this.y = crypEffectsY;
this.effectCount = 0;
}
addEffect(effect) {
const y = this.y + this.effectCount * TEXT_MARGIN;
const text = `${effect.effect} for ${effect.duration} turn`;
const e = this.scene.add.text(this.x, y, text, TEXT.NORMAL);
e.effect = effect.effect;
this.add(e);
this.effectCount += 1;
}
removeEffect(effect) {
this.children.entries.forEach((e) => {
if (e.effect === effect) e.destroy();
});
}
update(effects) {
this.effectCount = 0;
this.children.entries.forEach(e => e.destroy());
effects.forEach((effect) => {
this.addEffect(effect);
});
return true;
}
}
class CrypImage extends Phaser.GameObjects.Image {
constructor(scene, team, iter, cryp) {
// Get coords
const { crypAvatarX, crypAvatarY } = crypPosition(team, iter);
const { statusX, statusY, nameX, nameY } = crypAvatarText(team, iter);
// Cryp display
// const avatar = team ? 'magmar' : 'alk';
super(scene, crypAvatarX, crypAvatarY, 'aztec', genAvatar(cryp.name));
this.setScale(0.5);
if (!team) this.flipX = true;
// Save position and cryp details
this.scene = scene;
this.iter = iter;
this.team = team;
this.cryp = cryp;
this.state = 'deselect';
// Add cryp name
scene.add.text(nameX, nameY, cryp.name, TEXT.NORMAL).setOrigin(team, 0);
// Add cryp stat bars
this.health = scene.add.existing(new StatBar(scene, this, 'HP'));
this.red_shield = scene.add.existing(new StatBar(scene, this, 'Red Shield'));
this.blue_shield = scene.add.existing(new StatBar(scene, this, 'Blue Shield'));
// this.evasion = scene.add.existing(new StatBar(scene, this, 'Evasion'));
this.effects = scene.add.existing(new Effects(scene, team, iter));
this.statusText = scene.add.text(statusX, statusY, '', TEXT.NORMAL);
}
select() {
this.setTint('0x00bb00');
this.state = 'select';
}
setKo() {
this.state = 'ko';
this.setTint('0x9d9ea0');
}
deselect() {
if (this.state !== 'ko') {
this.clearTint();
this.state = 'deselect';
}
}
clearStatus() {
this.statusText.text = '';
}
reduceDefense(amount, type) {
if (type === 'PhysDmg') {
this.red_shield.takeDamage(amount);
} else if (type === 'BlueDmg') {
this.blue_shield.takeDamage(amount);
}
}
takeDamage(props) {
const { amount, mitigation, category } = props;
if (mitigation) this.reduceDefense(mitigation, category);
this.setTint(0xff0000);
this.health.takeDamage(amount);
this.scene.time.delayedCall(DELAYS.DAMAGE_TICK, () => {
if (this.state !== 'ko') this.clearTint();
});
}
takeHealing(amount) {
this.setTint(0x00bb00);
this.health.takeDamage(amount * -1);
this.scene.time.delayedCall(DELAYS.DAMAGE_TICK, () => {
if (this.state !== 'ko') this.clearTint();
});
}
}
class CombatCryps extends Phaser.Scene {
constructor() {
super({ key: 'CombatCryps' });
}
create(game) {
this.cryps = this.add.group();
this.phase = game.phase;
this.account = this.registry.get('account');
this.drawCryps(game);
this.registry.events.on('changedata', this.updateData, this);
this.registry.set('crypStatusUpdate', false);
this.teams = game.teams.length;
}
updateData(parent, key, data) {
if (key === 'game' && data) {
if (data.teams.length !== this.teams) this.scene.restart(data);
const isAnimating = this.phase === 'animating';
this.game = data;
if (isAnimating) return false;
}
if (key === 'gamePhase' && data) {
const shouldUpdate = data !== this.phase;
this.phase = data;
if (shouldUpdate) {
this.cryps.children.entries.forEach(c => c.clearStatus());
this.drawCryps(this.game);
}
}
if (key === 'crypStatusUpdate' && data) {
this.updateCrypStatus(data);
}
return true;
}
drawCryps(game) {
const renderCryp = (cryp, iter, team) => {
// Add Image Avatar Class
const crypObj = new CrypImage(this, team, iter, cryp);
this.add.existing(crypObj);
this.cryps.add(crypObj);
return crypObj;
};
const renderTeam = (cryp, iter, team) => {
const crypObj = this.cryps.children.entries
.find(c => c.cryp.id === cryp.id)
|| renderCryp(cryp, iter, team);
crypObj.health.val = cryp.hp.value;
crypObj.red_shield.val = cryp.red_shield.value;
crypObj.blue_shield.val = cryp.red_shield.value;
crypObj.health.drawStatBar();
crypObj.red_shield.drawStatBar();
crypObj.blue_shield.drawStatBar();
crypObj.effects.update(cryp.effects);
};
const allyTeam = game.teams.find(t => t.id === this.account.id);
// in future there will be more than one
const [enemyTeam] = game.teams.filter(t => t.id !== this.account.id);
allyTeam.cryps.forEach((cryp, i) => renderTeam(cryp, i, 0));
if (!enemyTeam) return false;
enemyTeam.cryps.forEach((cryp, i) => renderTeam(cryp, i, 1));
return true;
}
selectCryp(crypId) {
this.cryps.children.entries.forEach(c => c.deselect());
if (crypId) this.cryps.children.entries.find(c => c.cryp.id === crypId).select();
}
updateCrypStatus(status) {
const sourceCryp = this.cryps.children.entries
.find(c => c.cryp.id === status.id);
const targetCryp = this.cryps.children.entries
.find(c => c.cryp.id === status.target);
if (this.phase === 'Skill') {
sourceCryp.statusText.text = `${status.skill} on ${targetCryp.cryp.name}`;
}
}
cleanUp() {
this.registry.events.off('changedata', this.updateData);
this.scene.remove();
}
}
module.exports = CombatCryps;
+89
View File
@@ -0,0 +1,89 @@
const Phaser = require('phaser');
const { POSITIONS: { COMBAT } } = require('./constants');
const CRYP_MARGIN = COMBAT.crypMargin();
const BOX_HEIGHT = CRYP_MARGIN * 0.8;
const BOX_WIDTH = COMBAT.width() * 0.2;
class CrypHitBox extends Phaser.GameObjects.Rectangle {
constructor(scene, iter, team, cback) {
const y = COMBAT.y() + COMBAT.height() * 0.05 + CRYP_MARGIN * iter;
super(scene, (COMBAT.width() - BOX_WIDTH) * team, y, BOX_WIDTH, BOX_HEIGHT, 0x222222);
this.setOrigin(0);
this.clickHandler = () => cback();
}
select() {
this.setFillStyle(0x003300);
}
deselect() {
this.setFillStyle(0x222222);
}
}
class CombatHitBox extends Phaser.Scene {
constructor() {
super({ key: 'CombatHitBox' });
}
create(phase) {
this.phase = phase;
this.registry.events.off('changedata', this.updateData);
this.registry.events.on('changedata', this.updateData, this);
if (phase === 'animating') return true;
this.selectHitBox(phase);
return true;
}
updateData(parent, key, data) {
if (key === 'game' && data) {
// In the case that we hit skill phase but teams change we restart
if (data.teams.length !== this.teams) this.scene.restart(data.phase);
}
if (key === 'gamePhase' && data) {
const shouldUpdate = data !== this.phase;
if (shouldUpdate) this.scene.restart(data);
return false;
}
return true;
}
selectHitBox(phase) {
const game = this.registry.get('game');
this.teams = game.teams.length;
if (phase === 'Skill') return this.skillHitBox(game);
return false;
}
skillHitBox(game) {
const account = this.registry.get('account');
const group = this.scene.get('CombatCryps').cryps;
const skillScene = this.scene.get('CombatSkills');
game.teams.forEach((t) => {
t.cryps.forEach((c) => {
const cback = () => {
const { activeSkill } = skillScene;
if (activeSkill) {
this.scene.get('CombatSkills').clearCrypActive(activeSkill.cryp.id);
activeSkill.activate();
skillScene.activeSkill = null;
this.game.events.emit('SEND_SKILL', game.id, activeSkill.cryp.id, c.id, activeSkill.skill.skill);
}
};
const crypSpawn = group.children.entries.find(s => s.cryp.id === c.id);
const team = c.account === account.id ? 0 : 1;
if (crypSpawn) this.add.existing(new CrypHitBox(this, crypSpawn.iter, team, cback));
});
});
this.scene.moveBelow('Combat');
}
cleanUp() {
this.registry.events.off('changedata', this.updateData);
this.scene.remove();
}
}
module.exports = CombatHitBox;
+138
View File
@@ -0,0 +1,138 @@
const Phaser = require('phaser');
const { throttle } = require('lodash');
const { TEXT, POSITIONS: { COMBAT } } = require('./constants');
const CombatLog = require('./combat.log');
const CombatCryps = require('./combat.cryps');
const CombatSkills = require('./combat.skills');
const CombatHitBox = require('./combat.hitbox');
const renderResolutions = require('./combat.render.resolutions');
class Combat extends Phaser.Scene {
constructor() {
super({ key: 'Combat' });
}
preload() {
this.load.image('proj', 'https://labs.phaser.io/assets/sprites/bullet.png');
this.load.image('blue', 'https://labs.phaser.io/assets/particles/blue.png');
this.load.image('green', 'https://labs.phaser.io/assets/particles/green.png');
this.load.image('red', 'https://labs.phaser.io/assets/particles/red.png');
this.load.image('white', 'https://labs.phaser.io/assets/particles/white.png');
this.load.image('yellow', 'https://labs.phaser.io/assets/particles/yellow.png');
}
create() {
console.log('creating game');
this.registry.events.off('changedata', this.updateData);
this.registry.events.on('changedata', this.updateData, this);
this.addLeaveGame();
this.registry.set('gamePhase', false);
this.registry.set('inGame', true);
this.registry.set('gameAnimating', false);
this.account = this.registry.get('account');
this.fetchGame = throttle(() => {
const game = this.registry.get('game');
if (game) {
const ws = this.registry.get('ws');
return ws.sendGameState(game.id);
}
return false;
}, 500);
return true;
}
startGame(game) {
this.scene.manager.add('CombatCryps', CombatCryps, true, game);
this.scene.manager.add('CombatLog', CombatLog, true, game);
this.renderedResolves = game.resolved.length; // In case you rejoin mid way
this.scene.manager.add('CombatSkills', CombatSkills, true, game.phase);
this.scene.manager.add('CombatHitBox', CombatHitBox, true, game.phase);
this.registry.set('gamePhase', game.phase);
this.phase = game.phase;
return true;
}
update() {
this.fetchGame();
return true;
}
updateData(parent, key, data) {
if (key === 'game') {
if (!data) return false;
const startGame = this.registry.get('gamePhase') === false;
if (startGame) { this.startGame(data); return true; }
this.checkAnimation(data);
// Game over?
// if (data.phase === 'Finish') {
// this.time.delayedCall(10000, () => {
// this.endGame();
// });
// }
}
return true;
}
checkAnimation(game) {
// Check cryps are loaded and whether game is animating
const cantAnimate = this.registry.get('gamePhase') === 'animating';
if (cantAnimate) return false;
if (game.resolved.length !== this.renderedResolves) {
const newResolutions = game.resolved.slice(this.renderedResolves);
renderResolutions(this, game, newResolutions);
this.renderedResolves = game.resolved.length;
return true;
}
if (this.phase !== game.phase) {
this.phase = game.phase;
this.registry.set('gamePhase', game.phase);
}
if (this.registry.get('gameLog') !== game.log.length) {
this.registry.set('gameLog', game.log.length);
}
return true;
}
addLeaveGame() {
const leaveGame = () => this.cleanUp();
this.input.keyboard.on('keydown_BACKSPACE', leaveGame, 0, this);
const LEAVE_HEIGHT = COMBAT.height() / 6;
const LEAVE_WIDTH = COMBAT.width() / 5;
const LEAVE_X = COMBAT.width() * 0.8;
const LEAVE_Y = COMBAT.height() * 0.9;
const menu = this.add
.rectangle(LEAVE_X, LEAVE_Y, LEAVE_WIDTH, LEAVE_HEIGHT, 0x440000)
.setInteractive()
.setOrigin(0)
.on('pointerdown', leaveGame);
this.add
.text(menu.getCenter().x, menu.getCenter().y, 'Menu', TEXT.HEADER)
.setOrigin(0.5, 0.5);
}
cleanUp() {
this.registry.events.off('changedata', this.updateData, this);
this.registry.events.off('setdata', this.updateData, this);
this.registry.set('inGame', null);
this.registry.set('menu', true);
this.registry.set('game', null);
const ACTIVE_SCENES = ['CombatLog', 'CombatCryps', 'CombatSkills', 'CombatHitBox'];
ACTIVE_SCENES.forEach((sKey) => {
if (this.scene.get(sKey)) this.scene.get(sKey).cleanUp();
});
this.scene.remove();
return true;
}
}
module.exports = Combat;
+50
View File
@@ -0,0 +1,50 @@
const Phaser = require('phaser');
const { POSITIONS: { COMBAT }, TEXT } = require('./constants');
class CombatLog extends Phaser.Scene {
constructor() {
super({ key: 'CombatLog' });
}
create(game) {
this.registry.events.on('changedata', this.updateData, this);
this.cameras.main.setViewport(COMBAT.LOG.x(), COMBAT.LOG.y(), COMBAT.LOG.width(), COMBAT.LOG.height());
this.log = this.add.text(0, 0, '', TEXT.NORMAL);
this.logIndex = game.log.length;
this.logData = game.log;
this.log.setWordWrapWidth(COMBAT.LOG.width());
this.log.setText(Array.from(game.log).reverse());
}
updateData(parent, key, data) {
const UPDATE_KEYS = ['game', 'gameLog'];
if (UPDATE_KEYS.includes(key) && data) {
if (key === 'game') {
this.logData = data.log;
}
if (key === 'gameLog') {
this.logIndex = data;
}
this.updateLog();
}
return true;
}
updateLog() {
// shallow copy because reverse mutates
if (this.logData.length > this.logIndex + 1
&& Array.from(this.logData)[this.logIndex].slice(-2) === 'KO') {
this.logIndex += 1;
this.registry.set('gameLog', this.logIndex);
}
// }
this.log.setText(Array.from(this.logData).slice(0, this.logIndex).reverse());
}
cleanUp() {
this.registry.events.off('changedata', this.updateData);
this.scene.remove();
}
}
module.exports = CombatLog;
@@ -0,0 +1,135 @@
const { eachSeries } = require('async');
const CombatAnimations = require('./combat.animations');
const {
DELAYS: { ANIMATION_DURATION, MOVE_CREEP, DAMAGE_TICK },
POSITIONS: { COMBAT },
} = require('./constants');
function findResolutionCryps(scene, group, resolution, game) {
const sourceSpawn = group.children.entries.find(c => c.cryp.id === resolution.source.id);
/* const sourceCryp = game.teams.find(t => t.cryps.find(c => c.id === resolution.source_cryp_id))
.cryps.find(c => c.id === resolution.source_cryp_id);
const targetCryp = game.teams.find(t => t.cryps.find(c => c.id === resolution.target_cryp_id))
.cryps.find(c => c.id === resolution.target_cryp_id);
*/
const targetSpawn = group.children.entries.find(c => c.cryp.id === resolution.target.id);
return { sourceSpawn, targetSpawn };
}
function calculateTweenParams(sourceSpawn, targetSpawn, account, skill) {
const tweenParams = (targets, centreSpot) => {
const enemy = targets.cryp.account !== account.id;
let x = centreSpot ? COMBAT.width() * 0.3 : targets.x;
x = (enemy && centreSpot) ? x + COMBAT.width() * 0.4 : x;
const y = centreSpot ? COMBAT.height() * 13.25 / 35 : targets.y;
const ease = 'Power1';
const duration = MOVE_CREEP;
return {
targets, x, y, ease, duration,
};
};
let moveSourceBattle = false;
let moveSourceOrig = false;
const targetOnlySkill = ['DecayTick'];
if (!(targetOnlySkill.includes(skill))) {
if (sourceSpawn.cryp.account !== targetSpawn.cryp.account) {
moveSourceBattle = tweenParams(sourceSpawn, true);
moveSourceOrig = tweenParams(sourceSpawn, false);
}
}
const moveTargetBattle = tweenParams(targetSpawn, true);
const moveTargetOrig = tweenParams(targetSpawn, false);
return {
moveSourceBattle, moveSourceOrig, moveTargetBattle, moveTargetOrig,
};
}
function animatePhase(scene, game, resolution, cb) {
// return early for disabled skills
if (resolution.length === 0) return cb();
if (resolution.event[0] === 'Disable'
|| resolution.event[0] === 'TargetKo'
|| resolution.event === 'Ko') return cb();
const group = scene.scene.get('CombatCryps').cryps;
const animations = new CombatAnimations(scene);
const account = scene.registry.get('account');
// Find cryps, targets
const { sourceSpawn, targetSpawn } = findResolutionCryps(scene, group, resolution, game);
const {
moveSourceBattle, moveSourceOrig, moveTargetBattle, moveTargetOrig,
} = calculateTweenParams(sourceSpawn, targetSpawn, account, resolution.event[1].skill);
const castParams = () => {
const x = (sourceSpawn === targetSpawn) ? moveTargetBattle.x : sourceSpawn.x;
const y = (sourceSpawn === targetSpawn) ? moveTargetBattle.y : sourceSpawn.y;
return { x, y };
};
const castLocation = castParams();
// Move cryps into position
if (moveSourceBattle) scene.tweens.add(moveSourceBattle);
scene.tweens.add(moveTargetBattle);
return scene.time.delayedCall(MOVE_CREEP, () => {
const sourceAlly = sourceSpawn.cryp.account === account.id;
const targetAlly = targetSpawn.cryp.account === account.id;
// animate animation
animations.getSkill(resolution.event[1].skill, sourceAlly, targetAlly, castLocation);
// Target cryp takes damage
scene.time.delayedCall(ANIMATION_DURATION, () => {
console.log(resolution);
if (resolution.event[0] === 'Damage') {
targetSpawn.takeDamage(resolution.event[1]);
scene.registry.set('gameLog', scene.registry.get('gameLog') + 1);
}
if (resolution.event[0] === 'Healing') {
targetSpawn.takeHealing(resolution.event[1]);
scene.registry.set('gameLog', scene.registry.get('gameLog') + 1);
}
if (resolution.event[0] === 'Effect') {
targetSpawn.effects.addEffect(resolution.event[1]);
console.log('target has new effect', resolution.event[1].effect);
}
if (resolution.event[0] === 'Removal') {
targetSpawn.effects.removeEffect(resolution.event[1].effect);
console.log('target effect removed', resolution.event[1].effect);
}
if (moveSourceOrig) scene.tweens.add(moveSourceOrig);
scene.tweens.add(moveTargetOrig);
// all done
scene.time.delayedCall(MOVE_CREEP, () => {
animations.destroy(true);
return cb();
});
});
});
}
function renderResolutions(scene, game, resolutions) {
scene.registry.set('gamePhase', 'animating');
scene.registry.set('gameLog', scene.registry.get('gameLog') + 1);
eachSeries(
resolutions,
(resolution, cb) => animatePhase(scene, game, resolution, cb),
(err) => {
if (err) return console.error(err);
scene.registry.set('gamePhase', 'Skill');
return true;
}
);
return true;
}
module.exports = renderResolutions;
+235
View File
@@ -0,0 +1,235 @@
const Phaser = require('phaser');
const { TEXT, POSITIONS: { COMBAT } } = require('./constants');
const CRYP_KEY_MAP = ['keydown_ONE', 'keydown_TWO', 'keydown_THREE'];
const SKILL_KEY_MAP = ['keydown_Q', 'keydown_W', 'keydown_E', 'keydown_R'];
const TARGET_KEY_MAP = ['keydown_SEVEN', 'keydown_EIGHT', 'keydown_NINE', 'keydown_ZERO'];
const CRYP_MARGIN = COMBAT.crypMargin();
const TEXT_MARGIN = COMBAT.textMargin();
const SKILL_WIDTH = COMBAT.width() / 10;
const SKILL_HEIGHT = COMBAT.height() / 30;
const skillPosition = (crypIter, skillIter) => {
const skillTextX = COMBAT.width() / 3.8;
const skillTextY = (TEXT_MARGIN * skillIter) * 1.5 + CRYP_MARGIN * crypIter + COMBAT.y() + COMBAT.height() * 0.07;
return [skillTextX, skillTextY];
};
const skillCheckHitBox = (scenePlugin, pointer) => {
const { list } = scenePlugin.get('CombatHitBox').children;
for (let i = 0; i < list.length; i += 1) {
if (Phaser.Geom.Rectangle.ContainsPoint(list[i].getBounds(),
pointer.position)) return list[i];
}
// If we didn't find a hitbox deselect them all
for (let i = 0; i < list.length; i += 1) list[i].deselect();
return false;
};
class CrypSkill extends Phaser.GameObjects.Container {
constructor(scene, x, y, skill, cryp) {
// Avatar will be a property of cryp
super(scene, x, y);
const CD_TEXT = skill.cd ? `(${skill.cd}T)` : '';
const SKILL_TEXT = `${skill.skill} ${CD_TEXT}`;
this.origX = x; this.origY = y;
this.skillBox = scene.add.rectangle(0, 0, SKILL_WIDTH, SKILL_HEIGHT, 0x222222);
this.skillText = scene.add.text(0, 0, SKILL_TEXT, TEXT.NORMAL).setOrigin(0.5, 0.5);
this.add(this.skillBox);
this.add(this.skillText);
this.state = 'deselect';
this.cryp = cryp;
this.skill = skill;
this.scene = scene;
this.setSize(SKILL_WIDTH, SKILL_HEIGHT);
this.setInteractive();
}
clickHandler() {
if (this.scene.phase === 'Skill') this.scene.activeSkill = this;
this.select();
}
select() {
this.scene.children.list.forEach((skill) => {
if (skill.state === 'select') skill.deselect();
});
this.skillBox.setFillStyle(0x004bfe);
this.state = 'select';
}
activate() {
this.scene.children.list.forEach((skill) => {
if (skill.state === 'select') skill.deselect();
});
this.skillBox.setFillStyle(0xff0000);
this.state = 'activate';
}
deselect() {
this.skillBox.setFillStyle(0x222222);
this.state = 'deselect';
}
}
class CombatSkills extends Phaser.Scene {
constructor() {
super({ key: 'CombatSkills' });
}
create(phase) {
this.phase = phase;
this.registry.events.off('changedata', this.updateData);
this.registry.events.on('changedata', this.updateData, this);
this.account = this.registry.get('account');
this.input.on('dragstart', (pointer, box) => {
box.clickHandler();
});
this.input.on('drag', (pointer, box, dragX, dragY) => {
const hitBox = skillCheckHitBox(this.scene, pointer);
if (hitBox) hitBox.select();
box.setPosition(dragX, dragY);
});
this.input.on('dragend', (pointer, box) => {
box.deselect();
const hitBox = skillCheckHitBox(this.scene, pointer);
if (hitBox) {
hitBox.clickHandler();
}
box.setPosition(box.origX, box.origY);
});
if (phase === 'animating') return true;
// can't set this.game cause of phaser class named the same
const game = this.registry.get('game');
this.renderSkills(game, phase);
return true;
}
updateData(parent, key, data) {
if (key === 'gamePhase' && data) {
const shouldUpdate = data !== this.phase;
if (shouldUpdate) {
this.scene.get('CombatCryps').selectCryp(null);
return this.scene.restart(data);
}
return false;
}
return true;
}
renderSkills(game, phase) {
if (phase === 'Skill') return this.renderSkillPhase(game);
return false;
}
renderSkillPhase(game) {
const { account } = this;
const { keyboard } = this.input;
const { events } = this.game;
const addSkill = (i, j, skill, cryp) => {
const skillTextPos = skillPosition(i, j);
const skillObj = new CrypSkill(this, skillTextPos[0], skillTextPos[1], skill, cryp);
if (skill.cd) {
skillObj.skillBox.setFillStyle(0x9d9ea0);
} else {
this.input.setDraggable(skillObj);
}
this.add.existing(skillObj);
return skillObj;
};
const team = game.teams.find(t => t.id === account.id);
const enemyTeam = game.teams.find(t => t.id !== account.id);
team.cryps.forEach((cryp) => {
// return early if KOd
if (cryp.hp.value === 0) return true;
// find the cryp position
const { iter } = this.scene.get('CombatCryps').cryps.children.entries.find(c => c.cryp.id === cryp.id);
// draw the skills
const skillButtons = cryp.skills.map((skill, j) => addSkill(iter, j, skill, cryp));
const bindCrypKeys = () => this.mapSkillKeys(skillButtons, game.id, cryp.id, team.id, enemyTeam.id, iter);
// reset everything
keyboard.on('keydown_ESC', bindCrypKeys, this);
events.on('SEND_SKILL', bindCrypKeys, this);
bindCrypKeys();
return true;
});
return true;
}
// FIXME
// needs to send crypId not team
mapSkillKeys(skillButtons, gameId, crypId, alliesId, enemyId, i) {
const { keyboard } = this.input;
keyboard.removeListener(CRYP_KEY_MAP[i]);
keyboard.on(CRYP_KEY_MAP[i], () => {
SKILL_KEY_MAP.forEach(k => keyboard.removeListener(k));
this.scene.get('CombatCryps').selectCryp(crypId);
skillButtons.forEach((button, j) => {
keyboard.on(SKILL_KEY_MAP[j], () => {
this.activeSkill = button;
button.select();
// clear existing keys
CRYP_KEY_MAP.forEach(k => keyboard.removeListener(k));
TARGET_KEY_MAP.forEach(k => keyboard.removeListener(k));
CRYP_KEY_MAP.forEach(k => keyboard.on(k, () => {
this.clearCrypActive(crypId);
button.activate();
this.activeSkill = null;
this.game.events.emit('SEND_SKILL', gameId, crypId, alliesId, button.skill.skill);
}));
TARGET_KEY_MAP.forEach(k => keyboard.on(k, () => {
this.clearCrypActive(crypId);
button.activate();
this.activeSkill = null;
this.game.events.emit('SEND_SKILL', gameId, crypId, enemyId, button.skill.skill);
}));
}, this);
});
}, this);
return true;
}
clearCrypActive(crypId) {
this.scene.scene.children.list.forEach((s) => {
if (s.cryp.id === crypId && s.state === 'activate') s.deselect();
});
}
clearKeys() {
TARGET_KEY_MAP.forEach(tKey => this.input.keyboard.removeListener(tKey));
CRYP_KEY_MAP.forEach(tKey => this.input.keyboard.removeListener(tKey));
SKILL_KEY_MAP.forEach(tKey => this.input.keyboard.removeListener(tKey));
}
cleanUp() {
this.registry.events.off('changedata', this.updateData);
this.scene.remove();
}
}
module.exports = CombatSkills;
+388
View File
@@ -0,0 +1,388 @@
// POSITIONING FNS
// floors prevent subpixel rendering which looks trash
const CANVAS_WIDTH = () => Math.floor(window.innerHeight * 1.6);
const CANVAS_HEIGHT = () => Math.floor(window.innerHeight);
const headerWidth = () => CANVAS_WIDTH();
const headerHeight = () => Math.floor(CANVAS_HEIGHT() * 0.05);
const menuCrypListWidth = () => Math.floor(CANVAS_WIDTH() * 0.3);
const menuCrypListHeight = () => Math.floor(CANVAS_HEIGHT() - headerHeight());
const menuCrypListX = () => Math.floor(CANVAS_WIDTH() * 0.3);
const menuCrypListY = () => headerHeight();
const itemListWidth = () => Math.floor(CANVAS_WIDTH() * 0.5);
const itemListHeight = () => Math.floor(CANVAS_HEIGHT() * 0.95);
const itemListX = () => 0;
const itemListY = () => headerHeight();
const itemBlockWidth = () => Math.floor(itemListWidth() * 0.12);
const itemBlockHeight = () => Math.floor(itemListHeight() * 0.04);
const menuNavigationWidth = () => Math.floor(CANVAS_WIDTH() * 0.5);
const menuNavigationHeight = () => Math.floor(CANVAS_HEIGHT() * 0.3);
const menuNavigationX = () => Math.floor(CANVAS_WIDTH() * 0.5);
const menuNavigationY = () => Math.floor(CANVAS_HEIGHT() * 0.7);
const menuMainWidth = () => Math.floor(CANVAS_WIDTH() * 0.4);
const menuMainHeight = () => Math.floor(CANVAS_HEIGHT() * 0.5);
const menuMainX = () => Math.floor(CANVAS_WIDTH() * 0.65);
const menuMainY = () => headerHeight();
const homeMainWidth = () => Math.floor(CANVAS_WIDTH() * 0.6);
const homeMainHeight = () => Math.floor(CANVAS_HEIGHT() * 0.5);
const homeMainX = () => Math.floor(CANVAS_WIDTH() * 0.4);
const homeMainY = () => headerHeight();
const combatWidth = () => CANVAS_WIDTH();
const combatHeight = () => CANVAS_HEIGHT() - headerHeight();
const combatY = () => headerHeight();
const combatX = () => 0;
const combatCrypMargin = () => Math.floor((CANVAS_HEIGHT() - headerHeight()) / 4.5);
const combatTextMargin = () => Math.floor((CANVAS_HEIGHT() - headerHeight()) / 35);
const statsWidth = () => Math.floor(CANVAS_WIDTH() - menuCrypListWidth());
const statsHeight = () => CANVAS_HEIGHT() - headerHeight();
const statsY = () => headerHeight();
const statsX = () => menuCrypListWidth();
const statsKnownX = () => Math.floor(statsX() + statsWidth() / 4);
const statsLearnableX = () => Math.floor(statsX() + statsWidth() / 2);
const statsTextMargin = () => 24;
const statsLearnableMargin = () => 12;
const logWidth = () => Math.floor(combatWidth() * 0.5);
const logHeight = () => Math.floor(combatHeight() * 0.3);
const logY = () => Math.floor(headerHeight() + (combatHeight() * 0.7));
const logX = () => Math.floor(combatWidth() * 0.2);
module.exports = {
TEXT: {
NORMAL: { fontFamily: 'Jura', fontSize: 18, color: '#ffffff' },
LEARNABLE: { fontFamily: 'Jura', fontSize: 18, color: '#ffffff' },
HEADER: { fontFamily: 'Jura', fontSize: 24, color: '#ffffff', fontStyle: 'bold' },
HOVER: { fontFamily: 'Jura', fontSize: 16, color: '#ffffff', backgroundColor: '#222222' },
},
POSITIONS: {
HEADER: {
width: headerWidth,
height: headerHeight,
},
CRYP_LIST: {
x: menuCrypListX,
y: menuCrypListY,
width: menuCrypListWidth,
height: menuCrypListHeight,
},
MENU_MAIN: {
x: menuMainX,
y: menuMainY,
width: menuMainWidth,
height: menuMainHeight,
},
HOME_MAIN: {
x: homeMainX,
y: homeMainY,
width: homeMainWidth,
height: homeMainHeight,
},
NAVIGATION: {
x: menuNavigationX,
y: menuNavigationY,
width: menuNavigationWidth,
height: menuNavigationHeight,
},
ITEM_LIST: {
x: itemListX,
y: itemListY,
width: itemListWidth,
height: itemListHeight,
itemWidth: itemBlockWidth,
itemHeight: itemBlockHeight,
},
STATS: {
x: statsX,
y: statsY,
width: statsWidth,
height: statsHeight,
knownX: statsKnownX,
learnableX: statsLearnableX,
textMargin: statsTextMargin,
learnableMargin: statsLearnableMargin,
},
COMBAT: {
x: combatX,
y: combatY,
width: combatWidth,
height: combatHeight,
crypMargin: combatCrypMargin,
textMargin: combatTextMargin,
LOG: {
x: logX,
y: logY,
width: logWidth,
height: logHeight,
},
},
},
COLOURS: {
BLUE: 0x004bfe,
CYAN: 0x27e7c0,
PURPLE: 0x61008c,
YELLOW: 0xfdfe02,
ORANGE: 0xff9215,
PINK: 0xe766b6,
GRAY: 0x9d9ea0,
LBLUE: 0x87c6f2,
GREEN: 0x166c4f,
BROWN: 0x583108,
BLACK: 0x000000,
RED: 0xff0000,
WHITE: 0xffffff,
SELECT: 0x003300,
},
DELAYS: {
MOVE_CREEP: 500,
DAMAGE_TICK: 500,
ANIMATION_DURATION: 1000,
// wall: [500],
// spit: [300, 500],
// gravBomb: [300, 500],
// gravBlast: [300, 500],
// chargeBall: [300, 500],
},
ITEMS: {
SKILLS: {
Amplify: {
description: 'increase the magic damage dealt by a cryp',
colours: '1 Green 1 Blue',
},
Attack: {
description: 'a fast attack with red damage',
upgrades: 'combine with 2 red / blue / green - red + blue attack not implemented',
},
Banish: {
description: 'target cryp is prevented from casting any skills and taking any damage',
colours: '1 Red 1 Green',
},
Blast: {
description: 'blast the target with magic damage',
colours: '2 Blue',
},
Block: {
description: 'decreases incoming red damage for 1T',
upgrades: 'combine with 2 red / blue / green',
},
Buff: {
description: 'increase target cryp speed',
upgrades: 'combine with 2 red / blue / green',
},
Clutch: {
description: '??????',
colours: '1 Red 1 Green',
},
Corrupt: {
description: 'Inflicts a dot to attacker while active',
colours: '2 Blue',
},
Curse: {
description: 'target cryp takes increased magic damage',
colours: '2 Blue',
},
Debuff: {
description: 'reduce target cryp speed',
upgrades: 'combine with 2 red / blue / green',
},
Decay: {
description: 'afflict a cryp with a blue damage based damage over time debuff',
colours: '1 Green 1 Blue',
},
Empower: {
description: 'increase the red damage dealt by a cryp',
colours: '2 Red',
},
Haste: {
description: 'magical skill that increases speed of target cryp',
colours: '1 Red 1 Blue',
},
Heal: {
description: 'heal a cryp with blue damage',
colours: '2 Green',
},
Hex: {
description: 'magical bsed skill that prevents target cryp from using any skills',
colours: '1 Red 1 Blue',
},
Hostility: {
description: 'magical bsed skill that prevents target cryp from using any skills',
colours: '2 Blue',
},
Invert: {
description: 'reverse ???',
colours: '1 Red 1 Blue',
},
Parry: {
description: 'prevents all red damage for 1T',
colours: '2 Red',
},
Purge: {
description: 'remove magical buffs from target cryp',
colours: '2 Green',
},
Purify: {
description: 'remove magical debuffs from target cryp',
colours: '1 Red 1 Green',
},
Recharge: {
description: 'restore something',
colours: '1 Red 1 Blue',
},
Reflect: {
description: 'reflect damage back to attacker',
colours: '2 Green',
},
Riposte: {
description: '???',
},
Ruin: {
description: 'Stun the entire enemy team',
colours: '2 Blue',
},
Shield: {
description: 'grants immunity to magical skills to target cryp',
colours: '1 Green 1 Blue',
},
Silence: {
description: 'prevent target cryp from casting magical skills',
colours: '1 Green 1 Blue',
},
Siphon: {
description: 'siphon hp from target cryp with a blue damage based debuff',
colours: '1 Green 1 Blue',
},
Slay: {
description: '????',
},
Slow: {
description: 'magical skill that reduces speed of target cryp',
colours: '1 Red 1 Green',
},
Snare: {
description: 'prevents red skills from being used for 2T',
colours: '2 Red',
},
Strangle: {
description: 'Stun the enemy and inflict physical damage over 3T',
colours: '2 Red',
},
Strike: {
description: 'Fast attacking red skill',
colours: '2 Red',
},
Stun: {
description: 'red skill hat prevents target cryp from using any skills',
upgrades: 'combine with 2 red / blue / green',
},
Taunt: {
description: 'Enemy skills will prioritise cryps with this skill active',
colours: '1 Red 1 Green',
},
Throw: {
description: 'stuns and makes the target take increased red damage',
colours: '2 Green',
},
Triage: {
description: 'grants a blue damage based healing over time buff',
colours: '2 Green',
},
},
SPECS: {
Damage: {
description: 'Increase red / green / blue power stats cryp',
upgrades: 'combine with 2 red / blue / green',
},
Hp: {
description: 'Increases health of cryp',
upgrades: 'combine with 2 red / blue / green',
},
Speed: {
description: 'Increases speed of cryp',
upgrades: 'combine with 2 red / blue / green',
},
},
COLOURS: {
Red: {
description: 'Used to create offensive type combos - fast and chaotic',
},
Green: {
description: 'Used to create defensive / healing type combos',
},
Blue: {
description: 'Used to create offensive type combos - slow and reliable',
},
},
},
};
+77
View File
@@ -0,0 +1,77 @@
const Phaser = require('phaser');
const Header = require('./header');
const Home = require('./home');
const Menu = require('./menu');
const Combat = require('./combat');
// const Background = require('./background');
function renderCryps() {
const config = {
type: Phaser.CANVAS,
// backgroundColor: '#181818',
resolution: window.devicePixelRatio,
scale: {
mode: Phaser.Scale.FIT,
width: Math.floor(window.innerHeight * 1.6),
height: Math.floor(window.innerHeight),
max: {
width: Math.floor(window.innerHeight * 1.6),
height: Math.floor(window.innerHeight),
},
},
antialias: true,
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: { y: 0 },
},
},
scene: [
// Background,
Header,
],
};
const game = new Phaser.Game(config);
function changeData(parent, key, data) {
// Don't load other scenes if you're not logged in
if (!game.registry.get('account')) return false;
if (key === 'home') {
if (data) return game.scene.add('Home', Home, true);
}
if (key === 'menu') {
if (!data || game.registry.get('inMenu')) return false;
if (data) return game.scene.add('Menu', Menu, true);
}
if (key === 'game') {
if (!data || game.registry.get('inGame')) return false;
return game.scene.add('Combat', Combat, true);
}
return true;
}
game.registry.events.on('changedata', changeData);
game.registry.events.on('setdata', changeData);
window.addEventListener('mouseup', () => game.registry.set('pan', false));
window.addEventListener('mousedown', () => game.registry.set('pan', true));
window.addEventListener('resize', () => {
game.scale.displaySize.maxWidth = window.innerHeight * 1.6;
game.scale.displaySize.maxHeight = window.innerHeight;
});
return game;
}
module.exports = renderCryps;
+132
View File
@@ -0,0 +1,132 @@
const Phaser = require('phaser');
const { TEXT } = require('.././constants');
const BOX = `
#ifdef GL_ES
precision mediump float;
#endif
#extension GL_OES_standard_derivatives : enable
#ifdef GL_ES
precision mediump float;
#endif
#extension GL_OES_standard_derivatives : enable
uniform float time;
uniform vec2 resolution;
uniform vec2 dimensions;
uniform vec2 offset;
uniform vec3 colour;
uniform sampler2D uMainSampler;
varying vec2 outTexCoord;
varying vec4 outTint;
float sdBox( in vec2 p, in vec2 b )
{
vec2 d = abs(p)-b;
return length(max(d,vec2(0))) + min(max(d.x,d.y),0.0);
}
void main()
{
vec2 p = (gl_FragCoord.xy / resolution.xy);
p.x -= offset.x / resolution.x + dimensions.x / (2.0 * resolution.x);
p.y -= -1.0 * offset.y / resolution.y + ((resolution.y - dimensions.y) / (2.0 * resolution.y)) + 0.5;
vec2 dim = 0.5 * dimensions / resolution.xy;
float d = sdBox(p, dim);
float tb = abs(sin(time)) + 0.9;
vec3 col = tb * colour - sign(d) * vec3(0.1);
col *= 0.0 + exp(-100.0 * abs(d));
col += colour * 0.8;
vec4 texel = texture2D(uMainSampler, outTexCoord);
texel *= vec4(outTint.rgb * outTint.a, outTint.a);
gl_FragColor = vec4(col, 1.0);
}
`;
class CustomPipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline {
constructor(game) {
super({ game, renderer: game.renderer, fragShader: BOX });
}
}
function rgbToHex(rgb) {
const getHex = (c) => {
const hex = Math.floor(c * 255).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
return `0x${getHex(rgb[0])}${getHex(rgb[1])}${getHex(rgb[2])}`;
}
class BoxEffect extends Phaser.GameObjects.Graphics {
constructor(scene, x, y, width, height, colour, tag) {
super(scene);
this.tag = tag;
this.customPipeline = scene.game.renderer.addPipeline(tag, new CustomPipeline(scene.game));
this.customPipeline.setFloat2('resolution', 1600, 1000);
this.customPipeline.setFloat2('offset', x, y);
this.customPipeline.setFloat2('dimensions', width, height);
this.customPipeline.setFloat3('colour', colour[0], colour[1], colour[2]);
this.bgTime = 10.0;
const radius = height / 2;
this.fillStyle(rgbToHex(colour), 1.0);
this.fillRect(x + radius, y, width - radius * 2, height);
this.fillCircle(x + radius, y + radius, radius);
this.fillCircle(x + width - radius, y + radius, radius);
this.on('destroy', () => {
scene.game.renderer.removePipeline(tag);
});
}
activate() {
this.setPipeline(this.tag);
}
deactivate() {
this.resetPipeline();
}
update() {
this.bgTime += 0.05;
this.customPipeline.setFloat1('time', this.bgTime);
}
}
class Button extends Phaser.GameObjects.Group {
constructor(scene, props) {
const {
x, y, width, height, colour, glTag, bText, callback,
} = props;
super(scene, { classType: BoxEffect, runChildUpdate: true });
const leaveGame = scene.add
.rectangle(x, y, width, height, 0xaaaaaa, 0xffffff)
.setInteractive()
.setOrigin(0);
const effect = scene.add.existing(new BoxEffect(scene, x, y, width, height, colour, glTag));
this.add(effect);
leaveGame
.on('pointerdown', callback)
.on('pointerover', () => effect.activate())
.on('pointerout', () => effect.deactivate());
this.buttonText = scene.add.text(leaveGame.getCenter().x, leaveGame.getCenter().y, bText, TEXT.HEADER).setOrigin(0.5, 0.5);
}
}
module.exports = Button;
@@ -0,0 +1,86 @@
const Phaser = require('phaser');
const { TEXT, POSITIONS: { COMBAT }, COLOURS } = require('.././constants');
const CRYP_MARGIN = COMBAT.crypMargin();
const TEXT_MARGIN = COMBAT.textMargin();
const statBarDimensions = (team, iter, margin) => {
const statBarWidth = COMBAT.width() * 0.07;
const statBarHeight = TEXT_MARGIN / 1.5;
const statBarX = (COMBAT.width() - statBarWidth) * team;
const statBarY = COMBAT.y() + TEXT_MARGIN * (margin + 1) + CRYP_MARGIN * iter + COMBAT.height() * 0.07;
return { statBarX, statBarY, statBarWidth, statBarHeight };
};
const statTextCoord = (team, iter, margin) => {
const statTextX = team ? COMBAT.width() - COMBAT.width() * 0.075 : COMBAT.width() * 0.075;
const statTextY = COMBAT.y() + TEXT_MARGIN * (margin + 1) + CRYP_MARGIN * iter + COMBAT.height() * 0.07;
return { statTextX, statTextY };
};
class StatBar extends Phaser.GameObjects.Graphics {
constructor(scene, cryp, type) {
super(scene);
this.crypObj = cryp;
this.type = type;
if (type === 'HP') {
this.val = this.crypObj.cryp.hp.value;
this.max = this.crypObj.cryp.hp.max;
this.margin = 0;
} else if (type === 'Red Shield') {
this.val = this.crypObj.cryp.red_shield.value;
this.max = this.crypObj.cryp.red_shield.max;
this.margin = 1;
} else if (type === 'Blue Shield') {
this.val = this.crypObj.cryp.blue_shield.value;
this.max = this.crypObj.cryp.blue_shield.max;
this.margin = 2;
} else if (type === 'Evasion') {
this.val = this.crypObj.cryp.evasion.value;
this.max = this.crypObj.cryp.evasion.max;
this.margin = 3;
}
const { statTextX, statTextY } = statTextCoord(cryp.team, cryp.iter, this.margin);
this.statText = scene.add.text(statTextX, statTextY, '', TEXT.NORMAL).setOrigin(cryp.team, 0);
this.drawStatBar();
}
drawStatBar() {
this.clear();
const {
statBarX, statBarY, statBarWidth, statBarHeight,
} = statBarDimensions(this.crypObj.team, this.crypObj.iter, this.margin);
this.statText.text = `${this.val.toString()} / ${this.max.toString()} ${this.type}`;
// Draw Black Border
this.fillStyle(COLOURS.BLACK);
this.fillRect(statBarX, statBarY, statBarWidth, statBarHeight);
// White fill
this.fillStyle(COLOURS.WHITE);
this.fillRect(statBarX + 2, statBarY + 2, statBarWidth - 4, statBarHeight - 4);
// Fill the health bar
const statPercentage = this.val / this.max;
if (statPercentage < 0.3) {
this.fillStyle(COLOURS.RED);
} else if (statPercentage < 0.65) {
this.fillStyle(COLOURS.YELLOW);
} else {
this.fillStyle(0x00ff00); // str8 up green
}
const statWidth = statBarWidth * statPercentage;
this.fillRect(statBarX + 2, statBarY + 2, statWidth, statBarHeight - 4);
}
takeDamage(value) {
if (value > 0) {
this.val = (value >= this.val) ? 0 : this.val -= value;
} else {
this.val = (this.val - value > this.max) ? this.max : this.val -= value;
}
if (this.val === 0 && this.type === 'HP') this.crypObj.setKo();
this.drawStatBar();
}
}
module.exports = StatBar;
+56
View File
@@ -0,0 +1,56 @@
const Phaser = require('phaser');
const {
TEXT,
COLOURS,
} = require('../constants');
function FindColour(item) {
// Future add skills and use a constants lookup file ??
switch (item) {
case 'Green': return 0x396E26;
case 'Red': return 0x622433;
case 'Blue': return 0x223158;
// case 'Green': return 0x043003;
// case 'Red': return 0x5C0202;
// case 'Blue': return 0x040345;
default: return 0x222222;
}
}
class Item extends Phaser.GameObjects.Container {
constructor(scene, item, index, x, y, width, height) {
super(scene, x, y);
this.scene = scene;
this.item = item;
this.index = index;
this.origX = x;
this.origY = y;
this.width = width;
this.height = height;
this.colour = FindColour(item);
this.box = scene.add
.rectangle(0, 0, width, height, this.colour);
this.text = scene.add
// .text(0, 0, `${action} x${count}`, TEXT.NORMAL)
.text(0, 0, `${item}`, TEXT.NORMAL)
.setOrigin(0.5, 0.5);
this.add(this.box);
this.add(this.text);
this.setSize(width, height);
this.setInteractive();
}
changeOrigin(x, y) {
this.origX = x + this.width / 2;
this.origY = y + this.height / 2;
}
}
module.exports = Item;
@@ -0,0 +1,73 @@
const Phaser = require('phaser');
class LineBox extends Phaser.GameObjects.Graphics {
constructor(scene, x, y, width, height, colour, speed) {
super(scene);
this.colour = colour;
this.x0 = x; this.x1 = x + width;
this.y0 = y; this.y1 = y + height;
const margin = Math.abs(Math.floor((this.x1 - this.x0) / 4));
this.lineCoord = [this.x0 + margin, this.x1 - margin, this.y0, this.y0, 0];
this.speed = speed;
}
update() {
this.clear();
let vertX = this.x1;
let horizY = this.y0;
const genLine = () => {
switch (this.lineCoord[4]) {
case 0:
if (this.lineCoord[1] < this.x1) return [1, 1, 0, 0, 0];
return [0, 0, 0, 0, 1];
case 1:
if (this.lineCoord[0] < this.x1) return [1, 0, 0, 1, 1];
return [0, 0, 0, 0, 2];
case 2:
if (this.lineCoord[3] < this.y1) return [0, 0, 1, 1, 2];
return [0, 0, 0, 0, 3];
case 3:
horizY = this.y1;
if (this.lineCoord[2] < this.y1) return [-1, 0, 1, 0, 3];
return [0, 0, 0, 0, 4];
case 4:
horizY = this.y1;
if (this.lineCoord[0] > this.x0) return [-1, -1, 0, 0, 4];
return [0, 0, 0, 0, 5];
case 5:
horizY = this.y1;
vertX = this.x0;
if (this.lineCoord[1] > this.x0) return [0, -1, -1, 0, 5];
return [0, 0, 0, 0, 6];
case 6:
vertX = this.x0;
if (this.lineCoord[2] >= this.y0) return [0, 0, -1, -1, 6];
return [0, 0, 0, 0, 7];
case 7:
vertX = this.x0;
if (this.lineCoord[3] >= this.y0) return [0, 1, 0, -1, 7];
return [0, 0, 0, 0, 0];
default: return false;
}
};
for (let i = 0; i < this.speed; i += 1) {
const delta = genLine();
this.lineCoord = this.lineCoord.map((x, j) => {
if (j < 4) return (x + delta[j]);
return delta[j];
});
}
this.lineStyle(5, this.colour, 1);
this.lineBetween(this.lineCoord[0], horizY, this.lineCoord[1], horizY);
this.lineBetween(vertX, this.lineCoord[2], vertX, this.lineCoord[3]);
}
}
class LineGroup extends Phaser.GameObjects.Group {
constructor(scene) {
super(scene, { classType: LineBox, runChildUpdate: true });
}
}
module.exports = { LineGroup, LineBox }
+53
View File
@@ -0,0 +1,53 @@
const Phaser = require('phaser');
const { TEXT, POSITIONS: { MENU_MAIN } } = require('./constants');
const X = MENU_MAIN.x();
const Y = MENU_MAIN.y();
const WIDTH = MENU_MAIN.width();
const HEIGHT = MENU_MAIN.height();
const ROW_SIZE = 50;
const LOBBY_WIDTH = WIDTH / 2;
class GameList extends Phaser.Scene {
constructor() {
super({ key: 'GameList' });
}
create(gameList) {
this.cameras.main.setViewport(X, Y, WIDTH, HEIGHT);
const ws = this.registry.get('ws');
this.add.text(WIDTH / 3, 0, 'PVP open games', TEXT.HEADER);
const gameRow = (game, i) => {
const GAME_X = WIDTH / 6;
const GAME_Y = 1.3 * ROW_SIZE * (i + 1);
const gameBox = this.add
.rectangle(GAME_X, GAME_Y, LOBBY_WIDTH, ROW_SIZE, 0x222222)
.setInteractive()
.setOrigin(0);
const TITLE = `${game.teams[0].cryps.map(c => c.name).join(', ')} - ${game.team_size}v${game.team_size}`;
this.add
.text(gameBox.getCenter().x, gameBox.getCenter().y, TITLE, TEXT.NORMAL)
.setOrigin(0.5, 0.5);
gameBox.on('pointerdown', () => {
const cryps = this.registry.get('cryps');
const team = cryps.filter(c => c.active).map(c => c.id);
ws.sendGameJoin(game.id, team);
});
};
gameList.forEach(gameRow);
return true;
}
cleanUp() {
this.scene.remove();
}
}
module.exports = GameList;
+27
View File
@@ -0,0 +1,27 @@
const Phaser = require('phaser');
const fs = require('fs');
const { TEXT } = require('./constants');
const aztecAtlas = require('../../assets/aztec.atlas.json');
class Header extends Phaser.Scene {
constructor() {
super({ key: 'Header', active: true });
}
preload() {
const aztecImg = new Image();
aztecImg.src = `data:image/png;base64,${new Buffer.from(fs.readFileSync('./assets/aztec.clean.png')).toString('base64')}`;
aztecImg.onload = () => {
this.textures.addAtlas('aztec', aztecImg, aztecAtlas);
};
}
create() {
this.add.text(0, 0, 'cryps.gg', TEXT.HEADER);
}
}
module.exports = Header;
+142
View File
@@ -0,0 +1,142 @@
const Phaser = require('phaser');
const { remove } = require('lodash');
const { TEXT, COLOURS, POSITIONS: { CRYP_LIST } } = require('./constants');
const genAvatar = require('./avatar');
const { LineGroup, LineBox } = require('./elements/outline.rotate');
const ROW_HEIGHT = CRYP_LIST.height() * 0.1;
const ROW_WIDTH = CRYP_LIST.width();
const menuY = CRYP_LIST.height() * 0.8;
const KEY_MAP = [
'keydown-ONE',
'keydown-TWO',
'keydown-THREE',
];
const NULL_UUID = '00000000-0000-0000-0000-000000000000';
class HomeCrypList extends Phaser.Scene {
constructor() {
super({ key: 'HomeCryps' });
}
updateData(parent, key, data) {
if (key === 'crypList') {
KEY_MAP.forEach(k => this.input.keyboard.removeListener(k));
this.scene.restart();
}
}
create() {
// this.cameras.main.setViewport(CRYP_LIST.x(), CRYP_LIST.y(), CRYP_LIST.width(), CRYP_LIST.height());
this.registry.events.on('changedata', this.updateData, this);
this.registry.events.on('setdata', this.updateData, this);
const cryps = this.registry.get('crypList');
const lineGroup = this.add.existing(new LineGroup(this));
if (!cryps) return true;
const ws = this.registry.get('ws');
this.activeCryps = [];
// We only display 3 cryps others can be viewed in cryp list (soon TM)
for (let i = 0; i < cryps.length; i += 1) {
const cryp = cryps[i];
const BOX_WIDTH = Math.floor(ROW_WIDTH / 5);
const ROW_X = BOX_WIDTH * 2 * (i % 3);
const ROW_Y = CRYP_LIST.y() + (Math.floor(i / 3)) * ROW_HEIGHT * 1.5;
const ACTIVE_FILL = 0.2;
const FILL = Object.values(COLOURS)[i];
// Selection of cryps
// Cryp avatar and interaction box
const cReady = this.add
.rectangle(ROW_X, ROW_Y + ROW_HEIGHT * 0.2, BOX_WIDTH * 2, ROW_HEIGHT, FILL)
.setInteractive()
.setOrigin(0);
cReady.setAlpha(0.2);
cReady.on('pointerdown', () => {
lineGroup.clear(true, true);
if (this.activeCryps.includes(cReady)) {
remove(this.activeCryps, n => n === cReady);
cReady.setAlpha(0.2);
} else {
this.activeCryps.push(cReady);
cReady.setAlpha(0.75);
lineGroup.add(this.add.existing(
new LineBox(this, cReady.x, cReady.y, cReady.width, cReady.height, cReady.fillColor, 3)
));
}
});
cReady.itemSelect = () => {
cReady.setFillStyle(COLOURS.SELECT);
};
cReady.itemDeselect = () => {
cReady.setFillStyle(FILL, ACTIVE_FILL);
};
cReady.cryp = cryp;
this.add.image(
cReady.getCenter().x,
cReady.getCenter().y,
'aztec',
genAvatar(cryp.name)
);
this.add.text(ROW_X + BOX_WIDTH, ROW_Y, cryp.name, TEXT.HEADER)
.setOrigin(0.5, 0.5);
}
// Add Spawn Cryp Option
const spawn = this.add
.rectangle(ROW_WIDTH * 0.05, menuY, ROW_WIDTH * 0.2, ROW_HEIGHT * 0.5, 0x888888)
.setInteractive()
.setOrigin(0)
.on('pointerdown', () => {
this.game.events.emit('CRYP_SPAWN');
});
this.add
.text(spawn.getCenter().x, spawn.getCenter().y, '+', TEXT.HEADER)
.setOrigin(0.5, 0.5);
const joinNormal = this.add
.rectangle(ROW_WIDTH * 0.3, menuY, ROW_WIDTH * 0.4, ROW_HEIGHT * 0.5, 0x888888)
.setInteractive()
.setOrigin(0)
.on('pointerdown', () => {
const playerCryps = [];
this.activeCryps.forEach(obj => playerCryps.push(obj.cryp.id));
ws.sendPlayerCrypsSet(NULL_UUID, playerCryps);
});
this.add
.text(joinNormal.getCenter().x, joinNormal.getCenter().y, 'Join Normal', TEXT.HEADER)
.setOrigin(0.5, 0.5);
const joinInstance = this.add
.rectangle(ROW_WIDTH * 0.8, menuY, ROW_WIDTH * 0.4, ROW_HEIGHT * 0.5, 0x888888)
.setInteractive()
.setOrigin(0)
.on('pointerdown', () => {
const playerCryps = [];
this.activeCryps.forEach(obj => playerCryps.push(obj.cryp.id));
ws.sendInstanceJoin(playerCryps);
});
this.add
.text(joinInstance.getCenter().x, joinInstance.getCenter().y, 'New Instance', TEXT.HEADER)
.setOrigin(0.5, 0.5);
return this;
}
cleanUp() {
KEY_MAP.forEach(k => this.input.keyboard.removeListener(k));
this.registry.events.off('changedata', this.updateData, this);
this.registry.events.off('setdata', this.updateData, this);
this.scene.remove();
}
}
module.exports = HomeCrypList;
@@ -0,0 +1,48 @@
const Phaser = require('phaser');
const { POSITIONS: { HOME_MAIN }, TEXT } = require('./constants');
const X = HOME_MAIN.x();
const Y = HOME_MAIN.y();
const WIDTH = HOME_MAIN.width();
const HEIGHT = HOME_MAIN.height();
const NULL_UUID = '00000000-0000-0000-0000-000000000000';
class HomeRankings extends Phaser.Scene {
constructor() {
super({ key: 'HomeInstances' });
}
create() {
this.add.text(X, Y, 'Instances Scene', TEXT.HEADER);
const playerList = this.registry.get('playerList');
const addInstance = (player, i) => {
const joinNormal = this.add
.rectangle(X, Y + HEIGHT * 0.15 * (i + 1), WIDTH * 0.5, HEIGHT * 0.1, 0x888888)
.setInteractive()
.setOrigin(0)
.on('pointerdown', () => {
if (player.ready) {
this.registry.set('player', player);
this.registry.get('ws').sendInstanceReady(player.instance);
} else {
this.game.events.emit('SET_PLAYER', player);
this.registry.get('ws').sendInstanceScores(player.instance);
}
});
const name = player.instance === NULL_UUID ? 'Normal Mode' : `${player.instance.substring(0, 5)}`;
const disp = player.ready ? name.concat(' - in progress') : name;
this.add
.text(joinNormal.getCenter().x, joinNormal.getCenter().y, disp, TEXT.NORMAL)
.setOrigin(0.5, 0.5);
};
playerList.forEach(addInstance);
}
cleanUp() {
this.scene.remove();
}
}
module.exports = HomeRankings;
+91
View File
@@ -0,0 +1,91 @@
const Phaser = require('phaser');
const HomeCryps = require('./home.cryps');
const HomeNavigation = require('./home.navigation');
const HomeRankings = require('./home.rankings');
const HomeNews = require('./home.news');
const HomeShop = require('./home.shop');
const HomeInstances = require('./home.instances');
const FIXED_SCENES = [
'HomeCryps',
'HomeNavigation',
];
const VAR_SCENES = [
'HomeRankings',
'HomeNews',
'HomeShop',
'HomeInstances',
];
class Home extends Phaser.Scene {
constructor() {
super({ key: 'Home', active: true });
}
create() {
this.registry.get('ws').sendAccountPlayers();
this.registry.events.on('changedata', this.updateData, this);
this.registry.events.on('setdata', this.updateData, this);
this.scene.manager.add('HomeCryps', HomeCryps, true);
this.scene.manager.add('HomeNavigation', HomeNavigation, true);
return true;
}
updateData(parent, key, data) {
if (!data) return false;
// Controls which scene shows in the main top right section
switch (key) {
case 'game': return this.cleanUp();
case 'menu': return this.cleanUp();
case 'homeInstances': return this.newMainScene('HomeInstances', HomeInstances, data);
case 'homeRankings': return this.newMainScene('HomeRankings', HomeRankings, data);
case 'homeNews': return this.newMainScene('HomeNews', HomeNews, data);
case 'homeShop': return this.newMainScene('HomeShop', HomeShop, data);
default: return false;
}
}
newMainScene(key, scene, data) {
let addScene = true;
// List of scenes which could be occupying the main section of the menu
VAR_SCENES.forEach((sKey) => {
if (this.scene.manager.keys[sKey]) {
if (key === sKey) {
// If there is new data for the current scene restart
this.scene.manager.keys[sKey].scene.restart(data);
addScene = false;
} else {
// Delete the old scene
this.scene.manager.keys[sKey].cleanUp();
}
}
});
if (addScene) this.scene.manager.add(key, scene, true, data);
}
cleanUp() {
this.registry.events.off('changedata', this.updateData, this);
this.registry.events.off('setdata', this.updateData, this);
// Delete scenes which could be showing before switching to battle scene
const removeScenes = (sKey) => {
if (this.scene.get(sKey)) this.scene.get(sKey).cleanUp();
};
FIXED_SCENES.forEach(removeScenes);
VAR_SCENES.forEach(removeScenes);
this.scene.remove();
}
}
module.exports = Home;
@@ -0,0 +1,61 @@
const Phaser = require('phaser');
const { TEXT, POSITIONS: { NAVIGATION } } = require('./constants');
const X = NAVIGATION.x();
const Y = NAVIGATION.y();
const WIDTH = NAVIGATION.width();
const HEIGHT = NAVIGATION.height();
const BTN_WIDTH = Math.floor(WIDTH / 6);
const BTN_HEIGHT = Math.floor(HEIGHT / 3);
class HomeNavigation extends Phaser.Scene {
constructor() {
super({ key: 'HomeNavigation' });
}
create() {
/* const ranks = this.add
.rectangle(X, Y, BTN_WIDTH, BTN_HEIGHT, 0x222222)
.setInteractive()
.setOrigin(0);
this.add
.text(ranks.getCenter().x, ranks.getCenter().y, 'Rankings', TEXT.HEADER)
.setOrigin(0.5, 0.5);
ranks.on('pointerdown', () => this.registry.set('homeRankings', true));
const news = this.add
.rectangle(X + BTN_WIDTH * 1.5, Y, BTN_WIDTH, BTN_HEIGHT, 0x222222)
.setInteractive()
.setOrigin(0);
this.add
.text(news.getCenter().x, news.getCenter().y, 'News', TEXT.HEADER)
.setOrigin(0.5, 0.5);
news.on('pointerdown', () => this.registry.set('homeNews', true));
const shop = this.add
.rectangle(X + BTN_WIDTH * 3, Y, BTN_WIDTH, BTN_HEIGHT, 0x222222)
.setInteractive()
.setOrigin(0);
this.add
.text(shop.getCenter().x, shop.getCenter().y, 'Shop', TEXT.HEADER)
.setOrigin(0.5, 0.5);
shop.on('pointerdown', () => this.registry.set('homeShop', true));
const instances = this.add
.rectangle(X + BTN_WIDTH * 4.5, Y, BTN_WIDTH, BTN_HEIGHT, 0x222222)
.setInteractive()
.setOrigin(0);
this.add
.text(instances.getCenter().x, instances.getCenter().y, 'Instances', TEXT.HEADER)
.setOrigin(0.5, 0.5);
instances.on('pointerdown', () => this.registry.set('homeInstances', true));*/
}
cleanUp() {
this.scene.remove();
}
}
module.exports = HomeNavigation;
+24
View File
@@ -0,0 +1,24 @@
const Phaser = require('phaser');
const { POSITIONS: { HOME_MAIN }, TEXT } = require('./constants');
const X = HOME_MAIN.x();
const Y = HOME_MAIN.y();
const WIDTH = HOME_MAIN.width();
const HEIGHT = HOME_MAIN.height();
class HomeNews extends Phaser.Scene {
constructor() {
super({ key: 'HomeNews' });
}
create() {
this.add.text(X, Y, 'News Scene', TEXT.HEADER);
this.add.text(X, Y + HEIGHT * 0.1, 'some other stuff here', TEXT.NORMAL);
}
cleanUp() {
this.scene.remove();
}
}
module.exports = HomeNews;
+24
View File
@@ -0,0 +1,24 @@
const Phaser = require('phaser');
const { POSITIONS: { HOME_MAIN }, TEXT } = require('./constants');
const X = HOME_MAIN.x();
const Y = HOME_MAIN.y();
const WIDTH = HOME_MAIN.width();
const HEIGHT = HOME_MAIN.height();
class HomeRankings extends Phaser.Scene {
constructor() {
super({ key: 'HomeRankings' });
}
create() {
this.add.text(X, Y, 'Rankings Scene', TEXT.HEADER);
this.add.text(X, Y + HEIGHT * 0.1, 'some stuff here', TEXT.NORMAL);
}
cleanUp() {
this.scene.remove();
}
}
module.exports = HomeRankings;
+24
View File
@@ -0,0 +1,24 @@
const Phaser = require('phaser');
const { POSITIONS: { HOME_MAIN }, TEXT } = require('./constants');
const X = HOME_MAIN.x();
const Y = HOME_MAIN.y();
const WIDTH = HOME_MAIN.width();
const HEIGHT = HOME_MAIN.height();
class HomeShop extends Phaser.Scene {
constructor() {
super({ key: 'HomeShop' });
}
create() {
this.add.text(X, Y, 'Shop Scene', TEXT.HEADER);
this.add.text(X, Y + HEIGHT * 0.1, 'rulul', TEXT.NORMAL);
}
cleanUp() {
this.scene.remove();
}
}
module.exports = HomeShop;
+78
View File
@@ -0,0 +1,78 @@
const Phaser = require('phaser');
const { POSITIONS: { MENU_MAIN }, TEXT, ITEMS: { SKILLS, SPECS, COLOURS } } = require('./constants');
const X = MENU_MAIN.x();
const Y = MENU_MAIN.y();
const WIDTH = MENU_MAIN.width();
const HEIGHT = MENU_MAIN.height();
const UNEQUIP_Y = Y + Math.floor(HEIGHT * 0.7);
const UNEQUIP_WIDTH = Math.floor(WIDTH * 0.4);
const UNEQUIP_HEIGHT = Math.floor(HEIGHT * 0.15);
class ItemInfo extends Phaser.Scene {
constructor() {
super({ key: 'ItemInfo' });
}
create(props) {
const { item, cryp } = props;
if (!item) return false;
// Default item text
let title = item;
let description = 'Description missing';
let upgrades = '';
let colours = '';
// Replace item text with constants if it exists
if (SKILLS[item]) {
title = `Skill Item - ${item}`;
({ description } = SKILLS[item]);
if (SKILLS[item].upgrades) ({ upgrades } = SKILLS[item]);
if (SKILLS[item].colours) ({ colours } = SKILLS[item]);
} else if (SPECS[item]) {
title = `Spec Item - ${item}`;
({ description } = SPECS[item]);
if (SPECS[item].upgrades) ({ upgrades } = SPECS[item]);
} else if (COLOURS[item]) {
title = `Colour Item - ${item}`;
({ description } = COLOURS[item]);
if (COLOURS[item].upgrades) ({ upgrades } = COLOURS[item]);
}
// Add text objects
this.add.text(X, Y, title, TEXT.HEADER);
this.add
.text(X, Y + HEIGHT * 0.1, description, TEXT.NORMAL)
.setWordWrapWidth(WIDTH * 0.75);
this.add
.text(X, Y + HEIGHT * 0.25, upgrades, TEXT.NORMAL)
.setWordWrapWidth(WIDTH * 0.75);
if (colours !== '') {
this.add
.text(X, Y + HEIGHT * 0.35, `Adds ${colours} to cryp`, TEXT.NORMAL)
.setWordWrapWidth(WIDTH * 0.75);
}
if (!cryp) return true;
const ws = this.registry.get('ws');
const { vbox } = this.registry.get('player');
const unEquip = this.add.rectangle(X, UNEQUIP_Y, UNEQUIP_WIDTH, UNEQUIP_HEIGHT, 0x222222)
.setOrigin(0, 0)
.setInteractive()
.on('pointerdown', () => {
ws.sendVboxUnequip(vbox.instance, cryp.id, item);
this.registry.set('crypStats', cryp);
});
this.add.text(unEquip.getCenter().x, unEquip.getCenter().y, 'unequip', TEXT.HEADER)
.setOrigin(0.5, 0.5);
return true;
}
cleanUp() {
this.scene.remove();
}
}
module.exports = ItemInfo;
+323
View File
@@ -0,0 +1,323 @@
const Phaser = require('phaser');
const Item = require('./elements/item');
const {
TEXT,
POSITIONS: { ITEM_LIST },
} = require('./constants');
const X = ITEM_LIST.x();
const Y = ITEM_LIST.y();
const WIDTH = ITEM_LIST.width();
const HEIGHT = ITEM_LIST.height();
const ITEM_WIDTH = ITEM_LIST.itemWidth();
const ITEM_HEIGHT = ITEM_LIST.itemHeight();
const BOX_X = X + ITEM_WIDTH * 0.5;
const BOX_Y = Y + ITEM_HEIGHT + ITEM_HEIGHT * 2;
const BOX_ROWS = 6;
const BOX_COLUMNS = 3;
const INV_X = X + ITEM_WIDTH * 0.5;
const INV_Y = Y + ITEM_HEIGHT * 12;
const INV_ROWS = 3;
const INV_COLUMNS = 3;
const COMB_X = X + ITEM_WIDTH * 0.5;
const COMB_Y = Y + ITEM_HEIGHT * 19;
const COMB_ROWS = 1;
const COMB_COLUMNS = 3;
const drawVbox = (graphics) => {
const boxDrawX = BOX_X - ITEM_WIDTH * 0.05;
const boxDrawY = BOX_Y - ITEM_HEIGHT * 0.05;
graphics.strokeRect(boxDrawX, boxDrawY, ITEM_WIDTH * 1.1 * BOX_COLUMNS, ITEM_HEIGHT * 1.1 * BOX_ROWS);
for (let i = 0; i < (BOX_COLUMNS - 1); i += 1) {
const x = boxDrawX + (i + 1) * ITEM_WIDTH * 1.1;
graphics.lineBetween(x, boxDrawY, x, boxDrawY + ITEM_HEIGHT * 1.1 * BOX_ROWS);
}
for (let i = 0; i < (BOX_ROWS - 1); i += 1) {
const y = boxDrawY + (i + 1) * ITEM_HEIGHT * 1.1;
graphics.lineBetween(boxDrawX, y, boxDrawX + ITEM_WIDTH * 1.1 * BOX_COLUMNS, y);
}
};
const drawInventory = (graphics) => {
const invDrawX = INV_X - ITEM_WIDTH * 0.05;
const invDrawY = INV_Y - ITEM_HEIGHT * 0.05;
graphics.strokeRect(invDrawX, invDrawY, ITEM_WIDTH * 1.1 * INV_COLUMNS, ITEM_HEIGHT * 1.1 * INV_ROWS);
for (let i = 0; i < (INV_COLUMNS - 1); i += 1) {
const x = invDrawX + (i + 1) * ITEM_WIDTH * 1.1;
graphics.lineBetween(x, invDrawY, x, invDrawY + ITEM_HEIGHT * 1.1 * INV_ROWS);
}
for (let i = 0; i < (INV_ROWS - 1); i += 1) {
const y = invDrawY + (i + 1) * ITEM_HEIGHT * 1.1;
graphics.lineBetween(invDrawX, y, invDrawX + ITEM_WIDTH * 1.1 * INV_COLUMNS, y);
}
};
const drawCombiner = (graphics) => {
const combDrawX = COMB_X - ITEM_WIDTH * 0.05;
const combDrawY = COMB_Y - ITEM_HEIGHT * 0.05;
graphics.strokeRect(combDrawX, combDrawY, ITEM_WIDTH * 1.1 * COMB_COLUMNS, ITEM_HEIGHT * 1.1 * COMB_ROWS);
for (let i = 0; i < (COMB_COLUMNS - 1); i += 1) {
const x = combDrawX + (i + 1) * ITEM_WIDTH * 1.1;
graphics.lineBetween(x, combDrawY, x, combDrawY + ITEM_HEIGHT * 1.1 * COMB_ROWS);
}
for (let i = 0; i <= (COMB_ROWS - 1); i += 1) {
const y = combDrawY + (i + 1) * ITEM_HEIGHT * 1.1;
graphics.lineBetween(combDrawX, y, combDrawX + ITEM_WIDTH * 1.1 * COMB_COLUMNS, y);
}
};
class CombinerHitBox extends Phaser.GameObjects.Rectangle {
constructor(scene, x, y, i) {
super(scene, x, y, ITEM_WIDTH, ITEM_HEIGHT, 0x000000);
this.setOrigin(0);
this.x = x;
this.y = y;
this.slot = i;
this.item = false;
this.itemSelect = () => this.setFillStyle(0x222222);
this.itemDeselect = () => this.setFillStyle(0x000000);
}
allocate(item) {
if (this.item) this.deallocate();
item.setPosition(this.x + item.width / 2, this.y + item.height / 2);
this.item = item;
}
deallocate() {
if (this.item) this.item.setPosition(this.item.origX, this.item.origY);
this.item = false;
}
}
class DeleteHitBox extends Phaser.GameObjects.Rectangle {
constructor(scene, x, y) {
super(scene, x, y, ITEM_WIDTH * 3.4, ITEM_HEIGHT * 1.25, 0x444444);
this.setOrigin(0);
this.itemSelect = () => this.setFillStyle(0xff0000);
this.itemDeselect = () => this.setFillStyle(0x444444);
}
}
const itemCheckHitbox = (scene, pointer) => {
const { list } = scene.scene.get('MenuCrypList').children;
const hitboxes = list.filter(c => c.cryp)
.concat(scene.children.list.filter(c => c instanceof CombinerHitBox || c instanceof DeleteHitBox));
let found;
for (let i = 0; i < hitboxes.length; i += 1) {
if (Phaser.Geom.Rectangle.ContainsPoint(hitboxes[i].getBounds(), pointer.position)) {
found = hitboxes[i];
} else {
hitboxes[i].itemDeselect();
}
}
return found;
};
class ItemList extends Phaser.Scene {
constructor() {
super({ key: 'ItemList', active: true });
}
updateData(parent, key, data) {
if (key === 'player' || key === 'scores') {
this.registry.events.off('changedata', this.updateData, this);
this.registry.events.off('setdata', this.updateData, this);
this.scene.restart();
}
}
create() {
const player = this.registry.get('player');
const scores = this.registry.get('scores') || [];
if (!player) return false;
const { vbox } = player;
this.registry.events.on('changedata', this.updateData, this);
this.registry.events.on('setdata', this.updateData, this);
if (!vbox.bound) return false;
if (!this.combinerItems || vbox.bound.length < this.registry.get('boundLength')) {
this.combinerItems = [-1, -1, -1];
}
this.registry.set('boundLength', vbox.bound.length);
const ws = this.registry.get('ws');
// Static Elements
const graphics = this.add.graphics();
graphics.lineStyle(5, 0x808080, 1.0);
drawCombiner(graphics);
drawInventory(graphics);
drawVbox(graphics);
this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 0.5, `vBox - ${vbox.bits}b`, TEXT.HEADER);
this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 11, 'inventory', TEXT.HEADER);
this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 18, 'iCombinator', TEXT.HEADER);
this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 23, `Wins: ${player.score.wins}`, TEXT.HEADER);
this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 24, `Losses: ${player.score.losses}`, TEXT.HEADER);
const discard = this.add
.rectangle(X + ITEM_WIDTH * 0.4, Y + ITEM_HEIGHT * 1.5, ITEM_WIDTH * 3.4, ITEM_HEIGHT * 1.25, 0x444444)
.setInteractive()
.setOrigin(0)
.on('pointerdown', () => this.registry.get('ws').sendVboxDiscard(vbox.instance));
this.add.text(discard.getCenter().x, discard.getCenter().y, 'discard - 5b', TEXT.HEADER)
.setOrigin(0.5, 0.5);
const combine = this.add
.rectangle(X + ITEM_WIDTH * 0.4, Y + ITEM_HEIGHT * 20.25, ITEM_WIDTH * 3.4, ITEM_HEIGHT * 1.25, 0x444444)
.setInteractive()
.setOrigin(0)
.on('pointerdown', () => {
ws.sendVboxCombine(vbox.instance, this.combinerItems);
this.combinerItems = [-1, -1, -1];
this.children.list.filter(obj => obj instanceof CombinerHitBox).forEach(cBox => cBox.deallocate());
});
this.add.text(combine.getCenter().x, combine.getCenter().y, 'combine', TEXT.HEADER)
.setOrigin(0.5, 0.5);
for (let i = 0; i < 3; i += 1) {
const ITEM_X = ITEM_WIDTH * 1.1 * (i % COMB_COLUMNS) + COMB_X;
const ITEM_Y = ITEM_HEIGHT * 1.1 * Math.floor(i / COMB_COLUMNS) + COMB_Y;
this.add.existing(new CombinerHitBox(this, ITEM_X, ITEM_Y, i));
}
const del = this.add.existing(new DeleteHitBox(this, X + ITEM_WIDTH * 0.4, Y + ITEM_HEIGHT * 15.5));
this.add.text(del.getCenter().x, del.getCenter().y, 'drop', TEXT.HEADER)
.setOrigin(0.5, 0.5);
// Generate Items
vbox.bound.forEach((item, i) => {
const ITEM_X = ITEM_WIDTH * 1.1 * (i % INV_COLUMNS) + INV_X + ITEM_WIDTH * 0.5;
const ITEM_Y = ITEM_HEIGHT * 1.1 * Math.floor(i / INV_COLUMNS) + INV_Y + ITEM_HEIGHT * 0.5;
const clickFn = () => this.registry.set('itemInfo', { item });
const itemBox = new Item(this, item, i, ITEM_X, ITEM_Y, ITEM_WIDTH, ITEM_HEIGHT);
itemBox.on('pointerdown', clickFn);
this.input.setDraggable(itemBox);
this.add.existing(itemBox);
});
vbox.free.forEach((type, i) => {
type.forEach((item, j) => {
const ITEM_X = ITEM_WIDTH * 1.1 * i + BOX_X + ITEM_WIDTH * 0.5;
const ITEM_Y = ITEM_HEIGHT * 1.1 * j + BOX_Y + ITEM_HEIGHT * 0.5;
const clickFn = () => {
this.registry.set('itemInfo', { item });
ws.sendVboxAccept(vbox.instance, i, j);
};
const itemBox = new Item(this, item, i, ITEM_X, ITEM_Y, ITEM_WIDTH, ITEM_HEIGHT);
itemBox.on('pointerdown', clickFn);
this.add.existing(itemBox);
});
});
// Restore previous combiner item slots
this.combinerItems.forEach((index, i) => {
if (index === -1) return false;
const item = this.children.list.filter(obj => obj instanceof Item).find(it => it.index === index);
const hitBox = this.children.list.filter(obj => obj instanceof CombinerHitBox).find(hb => hb.slot === i);
if (item) hitBox.allocate(item);
return true;
});
// allocation functions
const allocate = (item, hitBox) => {
hitBox.allocate(item);
this.combinerItems[hitBox.slot] = item.index;
this.registry.set('combinerItems', this.combinerItems);
};
const deallocate = (item) => {
const clearIndex = this.combinerItems.indexOf(item.index);
if (clearIndex !== -1) {
this.children.list.filter(obj => obj instanceof CombinerHitBox)
.forEach((cBox) => {
if (cBox.item === item) cBox.deallocate();
});
this.combinerItems[clearIndex] = -1;
this.registry.set('combinerItems', this.combinerItems);
}
item.setPosition(item.origX, item.origY);
};
const findUnallocated = () => {
for (let i = 0; i <= 2; i += 1) {
if (this.combinerItems[i] === -1) {
return this.children.list.filter(obj => obj instanceof CombinerHitBox).find(hb => hb.slot === i);
}
} return false;
};
// this.add.text(ITEM_WIDTH * 11, ITEM_HEIGHT * 1.1, 'Scoreboard', TEXT.HEADER);
// scores.forEach(([name, score], i) => {
// const SCORE_X = ITEM_WIDTH * 11;
// const SCORE_Y = ITEM_HEIGHT * 1.1 * (i + 2);
// this.add.text(SCORE_X, SCORE_Y, `${score.wins} - ${score.losses} | ${name}`, TEXT.NORMAL);
// });
// Add Handlers
this.input.on('dragstart', (pointer, item) => {
if (!(item instanceof Item)) return false;
return true;
});
this.input.on('drag', (pointer, item, dragX, dragY) => {
if (!(item instanceof Item)) return false;
item.setPosition(dragX, dragY);
const hitBox = itemCheckHitbox(this, pointer);
if (hitBox) hitBox.itemSelect();
return true;
});
this.input.on('dragend', (pointer, item) => {
if (!(item instanceof Item)) return false;
// Check first for hitbox interaction
const hitBox = itemCheckHitbox(this, pointer);
if (hitBox) {
// hitbox can only be the combinerhitbox, deletehitbox or cryp avatar
hitBox.itemDeselect();
if (hitBox instanceof CombinerHitBox) {
if (hitBox.item === item) deallocate(item);
else allocate(item, hitBox);
} else if (hitBox instanceof DeleteHitBox) {
ws.sendVboxReclaim(vbox.instance, item.index);
} else {
ws.sendVboxApply(vbox.instance, hitBox.cryp.id, item.index);
deallocate(item);
} return true;
}
// If not interacting with hitbox and didn't move much try to allocate the item
if (Math.hypot(item.x - item.origX, item.y - item.origY) < Math.hypot(item.width, item.height)) {
// Check theres a free combiner slot
const cBox = findUnallocated();
if (cBox) {
allocate(item, cBox);
return true;
}
}
// If the item hasn't been allocated above reset to natural location
// Check if item needs to be deallocated
// Scene will restart if there is vbox change
deallocate(item);
return true;
});
return this;
}
cleanUp() {
this.registry.events.off('changedata', this.updateData, this);
this.registry.events.off('setdata', this.updateData, this);
this.scene.remove();
}
}
module.exports = ItemList;
+137
View File
@@ -0,0 +1,137 @@
const Phaser = require('phaser');
const { TEXT, COLOURS, POSITIONS: { CRYP_LIST } } = require('./constants');
const genAvatar = require('./avatar');
const Item = require('./elements/item');
const BOX_WIDTH = Math.floor(CRYP_LIST.width());
const BOX_HEIGHT = Math.floor(CRYP_LIST.height() / 3.34);
const TEXT_MARGIN = 24;
const KEY_MAP = [
'keydown-ONE',
'keydown-TWO',
'keydown-THREE',
];
class MenuCrypList extends Phaser.Scene {
constructor() {
super({ key: 'MenuCrypList' });
}
create() {
this.registry.events.on('changedata', this.updateData, this);
this.registry.events.on('setdata', this.updateData, this);
const player = this.registry.get('player');
if (player) this.drawCryps(player.cryps);
}
updateData(parent, key, data) {
if (key === 'player') {
this.drawCryps(data.cryps);
}
}
drawCryps(cryps) {
if (!cryps) return true;
if (this.crypGroup) this.crypGroup.destroy(true);
this.crypGroup = this.add.group();
const addCryp = (cryp, i) => {
const ROW_X = CRYP_LIST.x();
const ROW_Y = CRYP_LIST.y() + BOX_HEIGHT * i * 1.1;
const ACTIVE_FILL = 0.2;
const FILL = Object.values(COLOURS)[i];
// Selection of cryps
const selectFn = () => {
this.registry.set('crypStats', cryp);
};
// Cryp interaction box for adding items
const crypInteract = this.add
.rectangle(ROW_X, ROW_Y, BOX_WIDTH, BOX_HEIGHT, FILL, ACTIVE_FILL)
.setInteractive()
.setOrigin(0)
.on('pointerdown', selectFn);
crypInteract.itemSelect = () => {
crypInteract.setFillStyle(COLOURS.SELECT);
};
crypInteract.itemDeselect = () => {
crypInteract.setFillStyle(FILL, ACTIVE_FILL);
};
crypInteract.cryp = cryp;
// Cryp Avatar
const { name } = cryp;
const crypImage = this.add.image(
crypInteract.getCenter().x - crypInteract.width / 4,
crypInteract.getCenter().y,
'aztec',
genAvatar(name)
);
// Add text info
const yCrypTextAlgin = Math.floor(crypInteract.y + TEXT_MARGIN / 2);
const xCrypNameAlign = Math.floor(crypInteract.x + crypInteract.width * 1 / 10);
const crypInfoText = this.add.text(xCrypNameAlign, yCrypTextAlgin, name, TEXT.HEADER);
const colourText = (c, j) => {
// Placeholder for when gems are implemented
const gemText = this.add.text(crypInteract.x + crypInteract.width * (j + 3) / 6, yCrypTextAlgin, `${c[0]} - ${c[1]}`, TEXT.HEADER);
this.crypGroup.add(gemText);
};
const { red, green, blue } = cryp.colours;
const CRYP_COLOURS = [
['R', red],
['G', green],
['B', blue],
];
CRYP_COLOURS.forEach(colourText);
this.crypGroup.addMultiple([crypInteract, crypImage, crypInfoText]);
const crypSkill = (stat, j) => {
const SKILL_WIDTH = Math.floor(BOX_WIDTH / 2);
const SKILL_HEIGHT = Math.floor(BOX_HEIGHT / 8);
const SKILL_X = crypInteract.getCenter().x + crypInteract.width / 4;
const SKILL_Y = crypInteract.y + SKILL_HEIGHT * 1.15 * (j + 1.6);
const itemObj = new Item(this, stat.skill, j, SKILL_X, SKILL_Y, SKILL_WIDTH, SKILL_HEIGHT);
this.add.existing(itemObj);
itemObj.setInteractive();
const itemInfo = { item: stat.skill, cryp };
itemObj.on('pointerdown', () => this.registry.set('itemInfo', itemInfo));
this.crypGroup.add(itemObj);
};
cryp.skills.forEach(crypSkill);
const crypSpec = (spec, j) => {
const SKILL_WIDTH = Math.floor(BOX_WIDTH * 0.15);
const SKILL_HEIGHT = Math.floor(BOX_HEIGHT * 0.2);
const SKILL_X = Math.floor(crypInteract.x + BOX_WIDTH * (0.6 + j) * 0.175);
const SKILL_Y = Math.floor(crypInteract.y + BOX_HEIGHT * 0.875);
const itemObj = new Item(this, spec, j, SKILL_X, SKILL_Y, SKILL_WIDTH, SKILL_HEIGHT);
itemObj.setInteractive();
const itemInfo = { item: spec, cryp };
itemObj.on('pointerdown', () => this.registry.set('itemInfo', itemInfo));
this.add.existing(itemObj);
this.crypGroup.add(itemObj);
};
cryp.specs.forEach(crypSpec);
};
cryps.forEach(addCryp);
return true;
}
cleanUp() {
KEY_MAP.forEach(k => this.input.keyboard.removeListener(k));
this.registry.events.off('changedata', this.updateData, this);
this.registry.events.off('setdata', this.updateData, this);
this.scene.remove();
}
}
module.exports = MenuCrypList;
+97
View File
@@ -0,0 +1,97 @@
const Phaser = require('phaser');
// Scenes constantly showing
const MenuCrypList = require('./menu.cryps.list');
const MenuNavigation = require('./menu.navigation');
const ItemList = require('./item.list');
// Scenes which change depending on menu context
const Zones = require('./zones');
const GameList = require('./game.list');
const StatSheet = require('./statsheet');
const ItemInfo = require('./item.info');
const FIXED_MENU_SCENES = [
'MenuCrypList',
'MenuNavigation',
'ItemList',
];
const MAIN_MENU_SCENES = [
'Zones',
'GameList',
'StatSheet',
'ItemInfo',
];
class Menu extends Phaser.Scene {
constructor() {
super({ key: 'Menu', active: true });
}
create() {
this.registry.events.on('changedata', this.updateData, this);
this.registry.events.on('setdata', this.updateData, this);
// Request the latest player state when we load the scene
const player = this.registry.get('player');
this.registry.get('ws').sendPlayerState(player.instance);
// When we load the menu request the latest items
// Item list will restart when the data comes in
this.scene.manager.add('MenuCrypList', MenuCrypList, true);
this.scene.manager.add('MenuNavigation', MenuNavigation, true);
this.scene.manager.add('ItemList', ItemList, true);
this.registry.set('inMenu', true);
return true;
}
updateData(parent, key, data) {
if (!data) return false;
// Controls which scene shows in the main top right section
switch (key) {
case 'game': return this.cleanUp();
case 'home': return this.cleanUp();
case 'zone': return this.newMainScene('Zones', Zones, data);
case 'gameList': return this.newMainScene('GameList', GameList, data);
case 'crypStats': return this.newMainScene('StatSheet', StatSheet, data);
case 'itemInfo': return this.newMainScene('ItemInfo', ItemInfo, data);
default: return false;
}
}
newMainScene(key, scene, data) {
let addScene = true;
// List of scenes which could be occupying the main section of the menu
MAIN_MENU_SCENES.forEach((sKey) => {
if (this.scene.manager.keys[sKey]) {
if (key === sKey) {
// If there is new data for the current scene restart
this.scene.manager.keys[sKey].scene.restart(data);
addScene = false;
} else {
// Delete the old scene
this.scene.manager.keys[sKey].cleanUp();
}
}
});
if (addScene) this.scene.manager.add(key, scene, true, data);
}
cleanUp() {
this.registry.events.off('changedata', this.updateData, this);
this.registry.events.off('setdata', this.updateData, this);
// Delete scenes which could be showing before switching to battle scene
const removeScenes = (sKey) => {
if (this.scene.get(sKey)) this.scene.get(sKey).cleanUp();
};
FIXED_MENU_SCENES.forEach(removeScenes);
MAIN_MENU_SCENES.forEach(removeScenes);
this.registry.set('inMenu', false);
this.combinerItems = this.registry.set('combinerItems', null);
this.scene.remove();
}
}
module.exports = Menu;
@@ -0,0 +1,50 @@
const Phaser = require('phaser');
const { TEXT, POSITIONS: { NAVIGATION } } = require('./constants');
const X = NAVIGATION.x();
const Y = NAVIGATION.y();
const WIDTH = NAVIGATION.width();
const HEIGHT = NAVIGATION.height();
const BTN_WIDTH = Math.floor(WIDTH / 4);
const BTN_HEIGHT = Math.floor(HEIGHT / 2);
class MenuNavigation extends Phaser.Scene {
constructor() {
super({ key: 'MenuNavigation' });
}
create() {
const ws = this.registry.get('ws');
const player = this.registry.get('player');
this.cameras.main.setViewport(X, Y, WIDTH, HEIGHT);
const ready = this.add
.rectangle(BTN_WIDTH * 3, 0, BTN_WIDTH, BTN_HEIGHT, 0x222222)
.setInteractive()
.setOrigin(0);
this.add
.text(ready.getCenter().x, ready.getCenter().y, 'Ready', TEXT.HEADER)
.setOrigin(0.5, 0.5);
ready.on('pointerdown', () => {
ws.sendInstanceReady(player.instance);
});
const menu = this.add
.rectangle(BTN_WIDTH * 3, BTN_HEIGHT, BTN_WIDTH, BTN_HEIGHT, 0x440000)
.setInteractive()
.setOrigin(0)
.on('pointerdown', () => this.registry.set('home', true));
this.add
.text(menu.getCenter().x, menu.getCenter().y, 'Menu', TEXT.HEADER)
.setOrigin(0.5, 0.5);
}
cleanUp() {
this.scene.remove();
}
}
module.exports = MenuNavigation;
+143
View File
@@ -0,0 +1,143 @@
const Phaser = require('phaser');
const { POSITIONS: { MENU_MAIN }, TEXT, SKILLS } = require('./constants');
const Item = require('./elements/item');
const X = MENU_MAIN.x();
const Y = MENU_MAIN.y();
const WIDTH = MENU_MAIN.width();
const HEIGHT = MENU_MAIN.height();
const TEXT_MARGIN = 24;
const SKILL_WIDTH = Math.floor(WIDTH / 10);
class DeleteHitBox extends Phaser.GameObjects.Rectangle {
constructor(scene, x, y) {
super(scene, x, y, SKILL_WIDTH, SKILL_WIDTH, 0x222222);
this.setOrigin(0);
this.itemSelect = () => this.setFillStyle(0xff0000);
this.itemDeselect = () => this.setFillStyle(0x222222);
}
}
const itemCheckHitbox = (scene, pointer) => {
const { list } = scene.scene.get('MenuCrypList').children;
const hitboxes = list.filter(c => c.cryp)
.concat(scene.children.list.filter(c => c instanceof DeleteHitBox));
let found;
for (let i = 0; i < hitboxes.length; i += 1) {
if (Phaser.Geom.Rectangle.ContainsPoint(hitboxes[i].getBounds(), pointer.position)) {
found = hitboxes[i];
} else {
hitboxes[i].itemDeselect();
}
}
return found;
};
class StatSheet extends Phaser.Scene {
constructor() {
super({ key: 'StatSheet' });
}
updateData(parent, key, data) {
if (key === 'player') {
console.log('grep');
console.log(data);
const cryp = data.cryps.find(c => c.id === this.cryp.id);
this.scene.restart(cryp);
}
}
create(cryp) {
this.registry.events.on('changedata', this.updateData, this);
// const ws = this.registry.get('ws');
const player = this.registry.get('player');
if (!player) return false;
// const { vbox } = player;
this.cryp = cryp;
/*
const del = this.add.existing(new DeleteHitBox(this, X + WIDTH * 0.7, Y + HEIGHT * 0.6));
this.add.text(del.getCenter().x, del.getCenter().y, 'unequip', TEXT.HEADER)
.setOrigin(0.5, 0.5);
*/
this.add.text(X, Y, cryp.name, TEXT.HEADER);
const crypStat = (stat, i) => {
const STAT_X = X;
const STAT_Y = Y + (i + 2) * TEXT_MARGIN;
this.add.text(STAT_X, STAT_Y, `${stat.stat}: ${stat.base} -> ${stat.value}`, TEXT.NORMAL);
};
const CRYP_STATS = [
cryp.hp,
cryp.red_shield,
cryp.blue_shield,
cryp.evasion,
cryp.red_damage,
cryp.blue_damage,
cryp.speed,
];
CRYP_STATS.forEach(crypStat);
/*
const knownSkill = (skill, i) => {
const SKILL_X = X + WIDTH * 0.4 + WIDTH * 0.125 * i;
const SKILL_Y = Y + HEIGHT * 0.15;
const itemObj = new Item(this, skill.skill, i, SKILL_X, SKILL_Y, SKILL_WIDTH, Math.floor(SKILL_WIDTH / 2));
// itemObj.on('pointerdown', () => );
this.input.setDraggable(itemObj);
this.add.existing(itemObj);
};
cryp.skills.forEach(knownSkill);
this.add.text(X + WIDTH * 0.35, Y, 'Skills', TEXT.HEADER);
this.add.text(X + WIDTH * 0.35, Y + HEIGHT * 0.25, 'Specs', TEXT.HEADER);
const knownSpec = (spec, i) => {
const SKILL_X = X + WIDTH * 0.4 + WIDTH * 0.125 * i;
const SKILL_Y = Y + HEIGHT * 0.4;
const itemObj = new Item(this, spec, i, SKILL_X, SKILL_Y, SKILL_WIDTH, Math.floor(SKILL_WIDTH / 2));
// itemObj.on('pointerdown', () => );
this.input.setDraggable(itemObj);
this.add.existing(itemObj);
};
cryp.specs.forEach(knownSpec);
this.input.on('drag', (pointer, item, dragX, dragY) => {
if (!(item instanceof Item)) return false;
item.setPosition(dragX, dragY);
const hitBox = itemCheckHitbox(this, pointer);
if (hitBox) hitBox.itemSelect();
return true;
});
this.input.on('dragend', (pointer, item) => {
if (!(item instanceof Item)) return false;
item.setPosition(item.origX, item.origY);
const hitBox = itemCheckHitbox(this, pointer);
if (hitBox) {
hitBox.itemDeselect();
// add socket function for unlearn here
ws.sendVboxUnequip(vbox.instance, cryp.id, item.item);
}
return true;
});
*/
return this;
}
cleanUp() {
this.registry.events.off('changedata', this.updateData, this);
this.scene.remove();
}
}
module.exports = StatSheet;
+41
View File
@@ -0,0 +1,41 @@
const Phaser = require('phaser');
const { TEXT, POSITIONS: { MENU_MAIN } } = require('./constants');
const X = MENU_MAIN.x();
const Y = MENU_MAIN.y();
const WIDTH = MENU_MAIN.width();
const HEIGHT = MENU_MAIN.height();
class ZoneControls extends Phaser.Scene {
constructor() {
super({ key: 'ZoneControls' });
}
create(zoneId) {
this.cameras.main.setViewport(X, Y, WIDTH, HEIGHT);
const menu = this.add
.rectangle(Math.floor(WIDTH * 0.7), Math.floor(HEIGHT * 0.8), Math.floor(WIDTH * 0.2), Math.floor(HEIGHT * 0.2), 0x888888)
.setInteractive()
.setOrigin(0);
this.add
.text(menu.getCenter().x, menu.getCenter().y, 'Clear', TEXT.HEADER)
.setOrigin(0.5, 0.5);
menu.on('pointerdown', () => {
this.registry.get('ws').sendZoneClose(zoneId);
this.scene.get('Zones').cleanUp();
});
return true;
}
cleanUp() {
this.scene.remove();
}
}
module.exports = ZoneControls;
+31
View File
@@ -0,0 +1,31 @@
const Phaser = require('phaser');
class ZoneNode extends Phaser.GameObjects.Sprite {
constructor(scene, x, y, id, success, tag) {
super(scene, x, y);
this.setTexture('eye');
this.scene = scene;
this.success = success;
// this.text = (text.indexOf(',') > -1) ? text.split(',') : text;
this.id = id;
this.setPosition(x, y);
const nodeNoDigits = tag.replace(/[0-9]/g, '');
switch (nodeNoDigits) {
case 'BOSS':
this.setScale(0.25);
break;
case 'MINIBOSS':
this.setScale(0.1);
break;
default:
this.setScale(0.05);
}
if (this.success) {
this.setTint(0xff0000);
}
this.text = tag;
}
}
module.exports = ZoneNode;
+129
View File
@@ -0,0 +1,129 @@
const Phaser = require('phaser');
const Node = require('./zone.node');
const ZoneControls = require('./zone.controls');
const { POSITIONS: { MENU_MAIN }, TEXT } = require('./constants');
const X = MENU_MAIN.x();
const Y = MENU_MAIN.y();
const WIDTH = MENU_MAIN.width();
const HEIGHT = MENU_MAIN.height();
// Mouse click hold to move, Q + E to zoom in and out
// Press 'A' to reset allocated passive nodes
class Zones extends Phaser.Scene {
constructor() {
super({ key: 'Zones' });
}
preload() {
this.load.image('eye', 'https://labs.phaser.io/assets/particles/green-orb.png');
}
create(zone) {
if (!zone) return false;
if (!this.scene.get('ZoneControls')) this.scene.manager.add('ZoneControls', ZoneControls, true, zone.id);
this.graphics = this.add.graphics();
this.cameras.main.setViewport(X, Y, WIDTH, HEIGHT);
this.addNodes(zone.graph.nodes);
this.drawEdges(zone.graph.edges);
this.addCameraControl();
this.addEvents(zone.id);
return this;
}
addNodes(nodeData) {
this.nodes = [];
nodeData.forEach((n, i) => {
this.nodes[i] = this.add.existing(
new Node(this, n.x * 50 + 200, n.y * 50 + 200, i, n.success, n.tag)
).setInteractive();
});
}
addCameraControl() {
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl({
camera: this.cameras.main,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.005,
drag: 0.0005,
maxSpeed: 0.001,
});
}
addEvents(zoneId) {
this.input.on('pointerover', (pointer, gameObjects) => {
if (gameObjects[0] instanceof Node) {
this.displayNodeText(gameObjects[0], pointer);
}
});
this.input.on('pointerout', (pointer, gameObjects) => {
if (gameObjects[0] instanceof Node) {
this.nodeText.destroy();
}
});
this.input.on('pointerup', (pointer, gameObjects) => {
if (Math.abs(pointer.upX - pointer.downX) < 5
&& Math.abs(pointer.upY - pointer.downY) < 5) {
// Check cursor hasn't significantly moved during point allocation
// If panning and mouse release is on node it won't allocate
if (gameObjects[0] instanceof Node) {
const team = this.registry.get('cryps').filter(c => c.active).map(c => c.id);
if (gameObjects[0].success) return false;
this.registry.get('ws').sendZoneJoin(zoneId, gameObjects[0].id, team);
}
}
return true;
});
this.input.on('pointermove', (pointer) => {
const zoomFactor = 2 / this.cameras.main.zoom;
if (this.registry.get('pan')) {
const points = pointer.getInterpolatedPosition(2);
this.cameras.main.scrollX -= zoomFactor * (points[1].x - points[0].x);
this.cameras.main.scrollY -= zoomFactor * (points[1].y - points[0].y);
}
}, this);
}
drawEdges(edgeData) {
this.graphics.clear();
edgeData.forEach((e) => {
const nodeA = this.nodes[e[0]];
const nodeB = this.nodes[e[1]];
if (nodeA.success && nodeB.success) {
this.graphics.lineStyle(10, 0xfff00f, 0.2);
} else {
this.graphics.lineStyle(2, 0xffffff, 0.2);
}
this.graphics.lineBetween(nodeA.x, nodeA.y, nodeB.x, nodeB.y);
return true;
});
}
displayNodeText(node, pointer) {
if (this.nodeText) this.nodeText.destroy();
this.nodeText = this.add.text(node.x, node.y, node.text, TEXT.HOVER).setPadding(32);
this.nodeText.setAlpha(0.8);
this.nodeText.setOrigin(pointer.x >= WIDTH * 0.65 ? 1 : 0,
pointer.y >= HEIGHT * 0.25 ? 1 : 0);
this.nodeText.setWordWrapWidth(450);
this.nodeText.setScale(1 / this.cameras.main.zoom);
}
camPan(bool) {
this.pan = bool;
}
update(delta) {
this.controls.update(delta);
}
cleanUp() {
if (this.scene.get('ZoneControls')) this.scene.get('ZoneControls').cleanUp();
this.scene.remove();
}
}
module.exports = Zones;
+335
View File
@@ -0,0 +1,335 @@
const toast = require('izitoast');
const cbor = require('borc');
const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://cryps.gg/ws' : 'ws://localhost:40000';
function errorToast(err) {
console.error(err);
return toast.error({
title: 'BEEP BOOP',
message: err,
position: 'topRight',
});
}
function createSocket(events) {
let ws;
// handle account auth within the socket itself
// https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
let account = null;
// -------------
// Outgoing
// -------------
function send(msg) {
console.log('outgoing msg', msg);
msg.token = account && account.token;
ws.send(cbor.encode(msg));
}
function sendAccountLogin(name, password) {
send({ method: 'account_login', params: { name, password } });
}
function sendAccountCreate(name, password) {
send({ method: 'account_create', params: { name, password } });
}
function sendAccountDemo() {
send({ method: 'account_demo', params: {} });
}
function sendAccountCryps() {
send({ method: 'account_cryps', params: {} });
}
function sendAccountPlayers() {
send({ method: 'account_players', params: {} });
}
function sendAccountZone() {
send({ method: 'account_zone', params: {} });
}
function sendCrypSpawn(name) {
send({ method: 'cryp_spawn', params: { name } });
}
function sendCrypLearn(id, skill) {
send({ method: 'cryp_learn', params: { id, skill } });
}
function sendCrypForget(id, skill) {
send({ method: 'cryp_forget', params: { id, skill } });
}
function sendGameState(id) {
send({ method: 'game_state', params: { id } });
}
function sendGamePve(crypIds, mode) {
send({ method: 'game_pve', params: { cryp_ids: crypIds, mode } });
}
function sendGamePvp(crypIds) {
send({ method: 'game_pvp', params: { cryp_ids: crypIds } });
}
function sendGameJoin(gameId, crypIds) {
send({ method: 'game_join', params: { game_id: gameId, cryp_ids: crypIds } });
}
function sendSpecForget(id, spec) {
send({ method: 'cryp_unspec', params: { id, spec } });
}
function sendPlayerCrypsSet(instanceId, crypIds) {
send({ method: 'player_cryps_set', params: { instance_id: instanceId, cryp_ids: crypIds } });
}
function sendPlayerState(instanceId) {
send({ method: 'player_state', params: { instance_id: instanceId } });
}
function sendVboxAccept(instanceId, group, index) {
send({ method: 'player_vbox_accept', params: { instance_id: instanceId, group, index } });
}
function sendVboxApply(instanceId, crypId, index) {
send({ method: 'player_vbox_apply', params: { instance_id: instanceId, cryp_id: crypId, index } });
}
function sendVboxUnequip(instanceId, crypId, target) {
send({ method: 'player_vbox_unequip', params: { instance_id: instanceId, cryp_id: crypId, target } });
}
function sendVboxDiscard(instanceId) {
send({ method: 'player_vbox_discard', params: { instance_id: instanceId } });
}
function sendVboxCombine(instanceId, indices) {
send({ method: 'player_vbox_combine', params: { instance_id: instanceId, indices } });
}
function sendVboxReclaim(instanceId, index) {
send({ method: 'player_vbox_reclaim', params: { instance_id: instanceId, index } });
}
function sendGameSkill(gameId, crypId, targetCrypId, skill) {
send({
method: 'game_skill',
params: {
game_id: gameId, cryp_id: crypId, target_cryp_id: targetCrypId, skill,
},
});
events.setActiveSkill(null);
}
function sendGameTarget(gameId, crypId, skillId) {
send({ method: 'game_target', params: { game_id: gameId, cryp_id: crypId, skill_id: skillId } });
events.setActiveSkill(null);
}
function sendZoneCreate() {
send({ method: 'zone_create', params: {} });
}
function sendZoneJoin(zoneId, nodeId, crypIds) {
send({ method: 'zone_join', params: { zone_id: zoneId, node_id: nodeId, cryp_ids: crypIds } });
}
function sendZoneClose(zoneId) {
send({ method: 'zone_close', params: { zone_id: zoneId } });
}
function sendInstanceJoin(cryps) {
send({ method: 'instance_join', params: { cryp_ids: cryps, pve: true } });
}
function sendInstanceReady(instanceId) {
send({ method: 'instance_ready', params: { instance_id: instanceId } });
}
function sendInstanceScores(instanceId) {
send({ method: 'instance_scores', params: { instance_id: instanceId } });
}
// -------------
// Incoming
// -------------
function accountLogin(res) {
const [struct, login] = res;
account = login;
events.setAccount(login);
sendAccountCryps();
}
function accountPlayerList(res) {
const [struct, playerList] = res;
events.setPlayerList(playerList);
}
function accountCryps(response) {
const [structName, cryps] = response;
events.setCrypList(cryps);
}
function gameState(response) {
const [structName, game] = response;
events.setGame(game);
}
function crypSpawn(response) {
const [structName, cryp] = response;
}
function gamePve(response) {
const [structName, game] = response;
}
function zoneState(response) {
const [structName, zone] = response;
events.setZone(zone);
}
function playerState(response) {
const [structName, player] = response;
events.setPlayer(player);
}
function instanceScores(response) {
const [structName, scores] = response;
events.setScores(scores);
}
// -------------
// Setup
// -------------
// when the server sends a reply it will have one of these message types
// this object wraps the reply types to a function
const handlers = {
cryp_spawn: crypSpawn,
cryp_forget: () => true,
cryp_learn: () => true,
game_pve: gamePve,
game_state: gameState,
account_login: accountLogin,
account_create: accountLogin,
account_cryps: accountCryps,
account_players: accountPlayerList,
instance_scores: instanceScores,
zone_create: res => console.log(res),
zone_state: zoneState,
zone_close: res => console.log(res),
player_state: playerState,
};
function errHandler(error) {
switch (error) {
case 'no active zone': return sendZoneCreate();
case 'no cryps selected': return events.errorPrompt('select_cryps');
case 'node requirements not met': return events.errorPrompt('complete_nodes');
case 'cryp at max skills (4)': return events.errorPrompt('max_skills');
default: return errorToast(error);
}
}
// decodes the cbor and
// calls the handlers defined above based on message type
function onMessage(event) {
// decode binary msg from server
const blob = new Uint8Array(event.data);
const res = cbor.decode(blob);
const { method, params } = res;
console.log(res);
// check for error and split into response type and data
if (res.err) return errHandler(res.err);
if (!handlers[method]) return errorToast(`${method} handler missing`);
return handlers[method](params);
}
function connect() {
ws = new WebSocket(SOCKET_URL);
ws.binaryType = 'arraybuffer';
// Connection opened
ws.addEventListener('open', () => {
toast.info({
message: 'connected',
position: 'topRight',
});
if (!account) events.loginPrompt();
if (process.env.NODE_ENV !== 'production') {
// send({ method: 'account_login', params: { name: 'ntr', password: 'grepgrepgrep' } });
}
return true;
});
// Listen for messages
ws.addEventListener('message', onMessage);
ws.addEventListener('error', (event) => {
console.error('WebSocket error', event);
// account = null;
// return setTimeout(connect, 5000);
});
ws.addEventListener('close', (event) => {
console.error('WebSocket closed', event);
toast.warning({
message: 'disconnected',
position: 'topRight',
});
return setTimeout(connect, 5000);
});
return ws;
}
return {
sendAccountLogin,
sendAccountCreate,
sendAccountDemo,
sendAccountCryps,
sendAccountPlayers,
sendAccountZone,
sendGameState,
sendGamePve,
sendGamePvp,
sendGameJoin,
sendGameSkill,
sendGameTarget,
sendCrypSpawn,
sendCrypLearn,
sendCrypForget,
sendSpecForget,
sendZoneCreate,
sendZoneJoin,
sendZoneClose,
sendInstanceJoin,
sendInstanceReady,
sendInstanceScores,
sendPlayerCrypsSet,
sendPlayerState,
sendVboxAccept,
sendVboxApply,
sendVboxReclaim,
sendVboxCombine,
sendVboxDiscard,
sendVboxUnequip,
connect,
};
}
module.exports = createSocket;
+135
View File
@@ -0,0 +1,135 @@
const toast = require('izitoast');
const OK_BUTTON = '<button type="submit">OK</button>';
const NO_MORE_BUTTON = '<button type="submit">Enough already...</button>';
function noMore(instance, thisToast) {
window.localStorage.setItem('tutorial', 'none');
return instance.hide({ transitionOut: 'fadeOut' }, thisToast);
}
const WELCOME_MESSAGE = `
Welcome to cryps.gg
Enter a username and password and press register to sign up,
or just press DEMO to quick start.
`;
const HOMEPAGE_MESSAGE = `
This homepage shows your cryps, joinable online games, PVE options and your items.\n
If you have no cryps yet, press SPAWN and give your cryp a name to create one.
Once you have made a cryp, click on them to visit their stat page and teach them some SKILLS.
The stat page also has descriptions of each skill and their effects.
cryps have 3 basic stats: hp, red damage and magic damage.
Toggle whether a cryp is selected for your team by clicking the coloured stripes next to the cryp or press 1,2,3.
Once you have a team ready press the New PVE Game button to start playing.
`;
const SKILL_PHASE_MESSAGE = `
A cryps battle has two main phases. This first phase is called the SKILL PHASE.
Your cryps are positioned on the left, your opponent's are on the right.
In the centre are your cryps' SKILLS, grayed out SKILLS are currently ON COOLDOWN.
A skill's cooldown reduces on every turn that cryp does not use a skill with a cooldown.
For the moment, drag ATTACK onto the opponent team to have your cryps attack them with red damage.
`;
// const TARGET_PHASE_MESSAGE = `
// This phase is the TARGET PHASE.
// In cryps you do not directly attack your opponent's cryps, you attack the opponent as a team
// and you and your opponent choose which cryp is the TARGET of each ability.
// Drag the incoming ATTACKS from the right hand side onto your own cryps.
// It's wise to spread the damage around!
// `;
const RESOLUTION_PHASE_MESSAGE = `
The second phase is called the RESOLUTION PHASE.
This phase happens automatically, every skill is RESOLVED in order of its SPEED.
This is important because attacks only RESOLVE while their caster is still able to use the skill,
a fast skill that does a small amount of damage may KO an opponent cryp, causing any SKILLS
they have used to no longer RESOLVE!
Another example of this is the skill STUN. STUN causes an opponent cryp to be unable to use any
abilities for TWO TURNS including the turn it resolves on. In other words it lasts for the rest of the turn it resolves on
and the whole next turn.
Try it now!
`;
const FINISH_PHASE_MESSAGE = `
gg! The game has now concluded, if you were the winner you have been awarded with a STAT REROLL ITEM.
You can use this to reroll a stat on a cryp which may be lacking.
A good metric is that if a stat is more than 1/2 of its STAMINA that's a good roll.
Now that you have learned the basics, press BACKSPACE to return to the main menu
and experiment with some combinations of SKILLS or replace the ones your cryps know in the STAT PAGE.
glhf!
`;
const STEPS = [
'init',
'welcome',
'homepage',
'skillPhase',
// 'targetPhase',
'resolutionPhase',
'finishPhase',
'none',
];
function showTutorial(message, step, nextStep) {
const existing = document.querySelector(`#${step}`); // Selector of your toast
if (existing) return false;
toast.info({
id: step,
theme: 'dark',
color: 'black',
timeout: false,
drag: false,
title: 'TUTORIAL',
position: 'bottomCenter',
maxWidth: window.innerWidth / 2,
close: false,
buttons: [
[NO_MORE_BUTTON, noMore],
[OK_BUTTON, (instance, thisToast) => {
const thisStep = STEPS.indexOf(step);
const storageStep = STEPS.indexOf(window.localStorage.getItem('tutorial'));
if (thisStep >= storageStep) {
window.localStorage.setItem('tutorial', nextStep);
}
return instance.hide({ transitionOut: 'fadeOut' }, thisToast);
}],
],
message,
});
return true;
}
function tutorial() {
function show(step) {
const currentStage = window.localStorage.getItem('tutorial');
if (currentStage === 'none') {
return false;
}
const thisStep = STEPS.indexOf(step);
const storageStep = STEPS.indexOf(window.localStorage.getItem('tutorial'));
if (thisStep < storageStep) return false;
if (step === 'welcome') return showTutorial(WELCOME_MESSAGE, 'welcome', 'homepage');
if (step === 'homepage') return showTutorial(HOMEPAGE_MESSAGE, 'homepage', 'skillPhase');
if (step === 'skillPhase') return showTutorial(SKILL_PHASE_MESSAGE, 'skillPhase', 'targetPhase');
// if (step === 'targetPhase') return showTutorial(TARGET_PHASE_MESSAGE, 'targetPhase', 'resolutionPhase');
if (step === 'resolutionPhase') return showTutorial(RESOLUTION_PHASE_MESSAGE, 'resolutionPhase', 'finishPhase');
if (step === 'finishPhase') return showTutorial(FINISH_PHASE_MESSAGE, 'finishPhase', 'none');
return true;
}
if (window.localStorage.getItem('tutorial') !== 'none') window.localStorage.setItem('tutorial', 'init');
return show;
}
module.exports = tutorial;
+36
View File
@@ -0,0 +1,36 @@
const get = require('lodash/get');
const stringSort = (k, desc) => {
if (desc) {
return (a, b) => {
if (!get(a, k)) return 1;
if (!get(b, k)) return -1;
return get(b, k).localeCompare(get(a, k));
};
}
return (a, b) => {
if (!get(a, k)) return 1;
if (!get(b, k)) return -1;
return get(a, k).localeCompare(get(b, k));
};
};
const numSort = (k, desc) => {
if (desc) {
return (a, b) => {
if (!get(a, k)) return 1;
if (!get(b, k)) return -1;
return get(b, k) - get(a, k);
};
}
return (a, b) => {
if (!get(a, k)) return 1;
if (!get(b, k)) return -1;
return get(a, k) - get(b, k);
};
};
module.exports = {
stringSort,
numSort,
};