Browse Source

more work!

1870-zone-sprite-atlas
Shaun 2 years ago
parent
commit
4f465af1b8
3 changed files with 941 additions and 908 deletions
  1. +18
    -907
      src/client/js/rendering/renderer.js
  2. +922
    -0
      src/client/js/rendering/rendererLegacy.js
  3. +1
    -1
      src/client/ui/shared/renderItem.js

+ 18
- 907
src/client/js/rendering/renderer.js View File

@@ -1,727 +1,20 @@
define([
'js/resources',
'js/system/events',
'js/misc/physics',
'js/rendering/effects',
'js/rendering/tileOpacity',
'js/rendering/particles',
'js/rendering/shaders/outline',
'js/rendering/spritePool',
'js/system/globals',
'js/rendering/renderLoginBackground',
'js/rendering/helpers/resetRenderer',
'js/rendering/textures'
'js/rendering/rendererLegacy'
], function (
resources,
events,
physics,
effects,
tileOpacity,
particles,
shaderOutline,
spritePool,
globals,
renderLoginBackground,
resetRenderer,
textures
rendererLegacy
) {
const mRandom = Math.random.bind(Math);

const particleLayers = ['particlesUnder', 'particles'];
const particleEngines = {};

return {
stage: null,
layers: {
particlesUnder: null,
objects: null,
mobs: null,
characters: null,
attacks: null,
effects: null,
particles: null,
lightPatches: null,
lightBeams: null,
tileSprites: null,
hiders: null
},

titleScreen: false,

width: 0,
height: 0,

showTilesW: 0,
showTilesH: 0,

pos: {
x: 0,
y: 0
},
moveTo: null,
moveSpeed: 0,
moveSpeedMax: 1.50,
moveSpeedInc: 0.5,

lastUpdatePos: {
x: 0,
y: 0
},

zoneId: null,

textures: {},
textureCache: {},

sprites: [],

lastTick: null,

hiddenRooms: null,

init: function () {
PIXI.settings.GC_MODE = PIXI.GC_MODES.AUTO;
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
PIXI.settings.SPRITE_MAX_TEXTURES = Math.min(PIXI.settings.SPRITE_MAX_TEXTURES, 16);
PIXI.settings.RESOLUTION = 1;

events.on('onToggleFullscreen', this.toggleScreen.bind(this));
events.on('onMoveSpeedChange', this.adaptCameraMoveSpeed.bind(this));
events.on('resetRenderer', resetRenderer.bind(this));

this.width = $('body').width();
this.height = $('body').height();

this.showTilesW = Math.ceil((this.width / scale) / 2) + 3;
this.showTilesH = Math.ceil((this.height / scale) / 2) + 3;

this.renderer = new PIXI.Renderer({
width: this.width,
height: this.height,
backgroundColor: '0x2d2136'
});

window.addEventListener('resize', this.onResize.bind(this));

$(this.renderer.view).appendTo('.canvas-container');

this.stage = new PIXI.Container();

let layers = this.layers;
Object.keys(layers).forEach(l => {
layers[l] = new PIXI.Container();
layers[l].layer = (l === 'tileSprites') ? 'tiles' : l;

this.stage.addChild(layers[l]);
});

const textureList = globals.clientConfig.textureList;
const sprites = resources.sprites;

textureList.forEach(t => {
this.textures[t] = new PIXI.BaseTexture(sprites[t]);
this.textures[t].scaleMode = PIXI.SCALE_MODES.NEAREST;
this.textures[t].size = this.textures[t].width / 8;
});

particleLayers.forEach(p => {
const engine = $.extend({}, particles);
engine.init({
r: this,
renderer: this.renderer,
stage: this.layers[p]
});

particleEngines[p] = engine;
});
},

toggleScreen: function () {
let isFullscreen = (window.innerHeight === screen.height);

if (isFullscreen) {
let doc = document;
(doc.cancelFullscreen || doc.msCancelFullscreen || doc.mozCancelFullscreen || doc.webkitCancelFullScreen).call(doc);
return 'Windowed';
}

let el = $('body')[0];
(el.requestFullscreen || el.msRequestFullscreen || el.mozRequestFullscreen || el.webkitRequestFullscreen).call(el);
return 'Fullscreen';
},

buildTitleScreen: function () {
this.titleScreen = true;

renderLoginBackground(this);
},

onResize: function () {
if (isMobile)
return;

this.width = $('body').width();
this.height = $('body').height();

this.showTilesW = Math.ceil((this.width / scale) / 2) + 3;
this.showTilesH = Math.ceil((this.height / scale) / 2) + 3;

this.renderer.resize(this.width, this.height);
if (window.player) {
this.setPosition({
x: (window.player.x - (this.width / (scale * 2))) * scale,
y: (window.player.y - (this.height / (scale * 2))) * scale
}, true);
}

if (this.titleScreen) {
this.clean();
this.buildTitleScreen();
}

events.emit('onResize');
},

getTexture: function (baseTex, cell) {
try {
let textureName = baseTex + '_' + cell;

let textureCache = this.textureCache;

let cached = textureCache[textureName];

if (!cached) {
let y = ~~(cell / 8);
let x = cell - (y * 8);
const texture = this.textures[baseTex];
const size = texture.size;

cached = new PIXI.Texture(texture, new PIXI.Rectangle(x * size, y * size, size, size));
textureCache[textureName] = cached;
}

return cached;
} catch (e) {
console.log(e, baseTex, cell);
}
},

clean: function () {
this.stage.removeChild(this.layers.hiders);
this.layers.hiders = new PIXI.Container();
this.layers.hiders.layer = 'hiders';
this.stage.addChild(this.layers.hiders);

let container = this.layers.tileSprites;
this.stage.removeChild(container);

this.layers.tileSprites = container = new PIXI.ParticleContainer(30000);
container.layer = 'tiles';
this.stage.addChild(container);

this.stage.children.sort((a, b) => {
if (a.layer === 'hiders')
return 1;
else if (b.layer === 'hiders')
return -1;
else if (a.layer === 'tiles')
return -1;
else if (b.layer === 'tiles')
return 1;
return 0;
});

this.textureCache = {};
},

buildTile: function (c, i, j) {
let alpha = tileOpacity.map(c);
let canFlip = tileOpacity.canFlip(c);

let tile = new PIXI.Sprite(this.getTexture('sprites', c));

tile.alpha = alpha;
tile.position.x = i * scale;
tile.position.y = j * scale;
tile.width = scale;
tile.height = scale;

if (canFlip && mRandom() < 0.5) {
tile.position.x += scale;
tile.scale.x = -scaleMult;
}

return tile;
},

onGetMap: async function (msg) {
tileOpacity.initMap(msg);

//Load sprite atlas
let spriteAtlas = null;
await new Promise(res => {
spriteAtlas = new Image();
spriteAtlas.onload = res;
spriteAtlas.src = msg.spriteAtlasPath;
});

//Build sprite texture
this.textures.sprites = new PIXI.BaseTexture(spriteAtlas);
this.textures.sprites.scaleMode = PIXI.SCALE_MODES.NEAREST;
this.textures.sprites.size = 8;

//Misc
this.titleScreen = false;
physics.init(msg.collisionMap);

let map = this.map = msg.map;
let w = this.w = map.length;
let h = this.h = map[0].length;

for (let i = 0; i < w; i++) {
let row = map[i];
for (let j = 0; j < h; j++) {
if (!row[j].split)
row[j] += '';

row[j] = row[j].split(',');
}
}

this.clean();
spritePool.clean();

this.stage.filters = [new PIXI.filters.AlphaFilter()];
this.stage.filterArea = new PIXI.Rectangle(0, 0, Math.max(w * scale, this.width), Math.max(h * scale, this.height));

this.hiddenRooms = msg.hiddenRooms;

this.sprites = _.get2dArray(w, h, 'array');

this.stage.children.sort((a, b) => {
if (a.layer === 'tiles')
return -1;
else if (b.layer === 'tiles')
return 1;
return 0;
});

if (this.zoneId !== null)
events.emit('onRezone', this.zoneId);
this.zoneId = msg.zoneId;

msg.clientObjects.forEach(c => {
c.zoneId = this.zoneId;
events.emit('onGetObject', c);
});
},

setPosition: function (pos, instant) {
pos.x += 16;
pos.y += 16;

let player = window.player;
if (player) {
let px = player.x;
let py = player.y;

let hiddenRooms = this.hiddenRooms || [];
let hLen = hiddenRooms.length;
for (let i = 0; i < hLen; i++) {
let h = hiddenRooms[i];
if (!h.discoverable)
continue;
if (
px < h.x ||
px >= h.x + h.width ||
py < h.y ||
py >= h.y + h.height ||
!physics.isInPolygon(px, py, h.area)
)
continue;

h.discovered = true;
}
}

if (instant) {
this.moveTo = null;
this.pos = pos;
this.stage.x = -~~this.pos.x;
this.stage.y = -~~this.pos.y;
} else
this.moveTo = pos;

this.updateSprites();
},

isVisible: function (x, y) {
let stage = this.stage;
let sx = -stage.x;
let sy = -stage.y;

let sw = this.width;
let sh = this.height;

return (!(x < sx || y < sy || x >= sx + sw || y >= sy + sh));
},

isHidden: function (x, y) {
let hiddenRooms = this.hiddenRooms;
let hLen = hiddenRooms.length;
if (!hLen)
return false;

const { player: { x: px, y: py } } = window;

let foundVisibleLayer = null;
let foundHiddenLayer = null;

hiddenRooms.forEach(h => {
const { discovered, layer, interior } = h;
const { x: hx, y: hy, width, height, area } = h;

//Is the tile outside the hider
if (
x < hx ||
x >= hx + width ||
y < hy ||
y >= hy + height
) {
//If the hider is an interior, the tile should be hidden if the player is inside the hider
if (interior) {
if (physics.isInPolygon(px, py, area))
foundHiddenLayer = layer;
}

return;
}

//Is the tile inside the hider
if (!physics.isInPolygon(x, y, area))
return;

if (discovered) {
foundVisibleLayer = layer;

return;
}

//Is the player outside the hider
if (
px < hx ||
px >= hx + width ||
py < hy ||
py >= hy + height
) {
foundHiddenLayer = layer;

return;
}

//Is the player inside the hider
if (!physics.isInPolygon(px, py, area)) {
foundHiddenLayer = layer;

return;
}

foundVisibleLayer = layer;
});

//We compare hider layers to cater for hiders inside hiders
return (foundHiddenLayer > foundVisibleLayer) || (foundHiddenLayer === 0 && foundVisibleLayer === null);
},

updateSprites: function () {
if (this.titleScreen)
return;

const player = window.player;
if (!player)
return;

const { w, h, width, height, stage, map, sprites } = this;

const x = ~~((-stage.x / scale) + (width / (scale * 2)));
const y = ~~((-stage.y / scale) + (height / (scale * 2)));

this.lastUpdatePos.x = stage.x;
this.lastUpdatePos.y = stage.y;

const container = this.layers.tileSprites;

const sw = this.showTilesW;
const sh = this.showTilesH;

let lowX = Math.max(0, x - sw + 1);
let lowY = Math.max(0, y - sh + 2);
let highX = Math.min(w, x + sw - 2);
let highY = Math.min(h, y + sh - 2);

let addedSprite = false;

const checkHidden = this.isHidden.bind(this);
const buildTile = this.buildTile.bind(this);

const newVisible = [];
const newHidden = [];

for (let i = lowX; i < highX; i++) {
let mapRow = map[i];
let spriteRow = sprites[i];

for (let j = lowY; j < highY; j++) {
const cell = mapRow[j];
if (!cell)
continue;

const cLen = cell.length;
if (!cLen)
return;

const rendered = spriteRow[j];
const isHidden = checkHidden(i, j);

if (isHidden) {
const nonFakeRendered = rendered.filter(r => !r.isFake);

const rLen = nonFakeRendered.length;
for (let k = 0; k < rLen; k++) {
const sprite = nonFakeRendered[k];

sprite.visible = false;
spritePool.store(sprite);
rendered.spliceWhere(s => s === sprite);
}

if (cell.visible) {
cell.visible = false;
newHidden.push({
x: i,
y: j
});
}

const hasFake = cell.some(c => c[0] === '-');
if (hasFake) {
const isFakeRendered = rendered.some(r => r.isFake);
if (isFakeRendered)
continue;
} else
continue;
} else {
const fakeRendered = rendered.filter(r => r.isFake);

const rLen = fakeRendered.length;
for (let k = 0; k < rLen; k++) {
const sprite = fakeRendered[k];

sprite.visible = false;
spritePool.store(sprite);
rendered.spliceWhere(s => s === sprite);
}

if (!cell.visible) {
cell.visible = true;
newVisible.push({
x: i,
y: j
});
}

const hasNonFake = cell.some(c => c[0] !== '-');
if (hasNonFake) {
const isNonFakeRendered = rendered.some(r => !r.isFake);
if (isNonFakeRendered)
continue;
} else
continue;
}

for (let k = 0; k < cLen; k++) {
let c = cell[k];
if (c === '0' || c === '')
continue;

const isFake = +c < 0;
if (isFake && !isHidden)
continue;
else if (!isFake && isHidden)
continue;

if (isFake)
c = -c;

c--;

let flipped = '';
if (tileOpacity.canFlip(c)) {
if (mRandom() < 0.5)
flipped = 'flip';
}

let tile = spritePool.getSprite(flipped + c);
if (!tile) {
tile = buildTile(c, i, j);
container.addChild(tile);
tile.type = c;
tile.sheetNum = tileOpacity.getSheetNum(c);
addedSprite = true;
} else {
tile.position.x = i * scale;
tile.position.y = j * scale;
if (flipped !== '')
tile.position.x += scale;
tile.visible = true;
}

if (isFake)
tile.isFake = isFake;

tile.z = k;

rendered.push(tile);
}
}
}

lowX = Math.max(0, lowX - 10);
lowY = Math.max(0, lowY - 10);
highX = Math.min(w - 1, highX + 10);
highY = Math.min(h - 1, highY + 10);

for (let i = lowX; i < highX; i++) {
const mapRow = map[i];
let spriteRow = sprites[i];
let outside = ((i >= x - sw) && (i < x + sw));
for (let j = lowY; j < highY; j++) {
if ((outside) && (j >= y - sh) && (j < y + sh))
continue;

const cell = mapRow[j];

if (cell.visible) {
cell.visible = false;
newHidden.push({ x: i, y: j });
}

let list = spriteRow[j];
let lLen = list.length;
for (let k = 0; k < lLen; k++) {
let sprite = list[k];
sprite.visible = false;
spritePool.store(sprite);
}
spriteRow[j] = [];
}
}

events.emit('onTilesVisible', newVisible, true);
events.emit('onTilesVisible', newHidden, false);

if (addedSprite)
container.children.sort((a, b) => a.z - b.z);
},

update: function () {
let time = +new Date();

if (this.moveTo) {
let deltaX = this.moveTo.x - this.pos.x;
let deltaY = this.moveTo.y - this.pos.y;

if (deltaX !== 0 || deltaY !== 0) {
let distance = Math.max(Math.abs(deltaX), Math.abs(deltaY));

let moveSpeedMax = this.moveSpeedMax;
if (this.moveSpeed < moveSpeedMax)
this.moveSpeed += this.moveSpeedInc;

let moveSpeed = this.moveSpeed;

if (moveSpeedMax < 1.6)
moveSpeed *= 1 + (distance / 200);

let elapsed = time - this.lastTick;
moveSpeed *= (elapsed / 15);

if (moveSpeed > distance)
moveSpeed = distance;

deltaX = (deltaX / distance) * moveSpeed;
deltaY = (deltaY / distance) * moveSpeed;

this.pos.x = this.pos.x + deltaX;
this.pos.y = this.pos.y + deltaY;
} else {
this.moveSpeed = 0;
this.moveTo = null;
}

let stage = this.stage;
if (window.staticCamera !== true) {
stage.x = -~~this.pos.x;
stage.y = -~~this.pos.y;
}

let halfScale = scale / 2;
if (Math.abs(stage.x - this.lastUpdatePos.x) > halfScale || Math.abs(stage.y - this.lastUpdatePos.y) > halfScale)
this.updateSprites();

events.emit('onSceneMove');
}

this.lastTick = time;
},

buildContainer: function (obj) {
let container = new PIXI.Container();
this.layers[obj.layerName || obj.sheetName].addChild(container);

return container;
},

buildRectangle: function (obj) {
let graphics = new PIXI.Graphics();

let alpha = obj.alpha;
if (obj.has('alpha'))
graphics.alpha = alpha;

let fillAlpha = obj.fillAlpha;
if (obj.has('fillAlpha'))
fillAlpha = 1;

graphics.beginFill(obj.color || '0x48edff', fillAlpha);

if (obj.strokeColor)
graphics.lineStyle(scaleMult, obj.strokeColor);

graphics.drawRect(0, 0, obj.w, obj.h);

graphics.endFill();

(obj.parent || this.layers[obj.layerName || obj.sheetName]).addChild(graphics);

graphics.position.x = obj.x;
graphics.position.y = obj.y;

return graphics;
},

moveRectangle: function (obj) {
obj.sprite.position.x = obj.x;
obj.sprite.position.y = obj.y;
obj.sprite.width = obj.w;
obj.sprite.height = obj.h;
},
const renderer = {
...rendererLegacy,

buildSpriteAsync: async function (config) {
const { sheetName, cell, container, layerName, visible } = config;
const { sheetName, cell, container, layerName, spriteConfig } = config;

const sprite = new PIXI.Sprite();
sprite.visible = visible;

if (spriteConfig) {
for (let p in spriteConfig)
sprite[p] = spriteConfig[p];
}

const textureExists = this.textures.hasOwnProperty(sheetName);
if (!textureExists)
@@ -735,206 +28,24 @@ define([
return sprite;
},

buildObject: function (obj) {
const { sheetName, parent: container, layerName, visible = true } = obj;

const sprite = new PIXI.Sprite();

obj.sprite = sprite;

this.setSprite(obj);

sprite.visible = visible;

const spriteContainer = container || this.layers[layerName || sheetName] || this.layers.objects;
spriteContainer.addChild(sprite);
buildTextSprite: function (config) {
const { text, container, layerName, fontSize = 14, color = 0xF2F5F5 } = config;

obj.w = sprite.width;
obj.h = sprite.height;

return sprite;
},

addFilter: function (sprite) {
let thickness = (sprite.width > scale) ? 8 : 16;

let filter = new shaderOutline(this.renderer.width, this.renderer.height, thickness, '0xffffff');
if (!sprite.filters)
sprite.filters = [filter];
else
sprite.filters.push();

return filter;
},

removeFilter: function (sprite, filter) {
if (sprite.filters)
sprite.filters = null;
},

buildText: function (obj) {
let textSprite = new PIXI.Text(obj.text, {
const textSprite = new PIXI.Text(text, {
fontFamily: 'bitty',
fontSize: (obj.fontSize || 14),
fill: obj.color || 0xF2F5F5,
fontSize: fontSize,
fill: color,
stroke: 0x2d2136,
strokeThickness: 4,
align: 'center'
});

textSprite.x = obj.x - (textSprite.width / 2);
textSprite.y = obj.y;

let parentSprite = obj.parent || this.layers[obj.layerName];
const parentSprite = container || this.layers[layerName];
parentSprite.addChild(textSprite);

return textSprite;
},

buildEmitter: function (config) {
const { layerName = 'particles' } = config;
const particleEngine = particleEngines[layerName];

return particleEngine.buildEmitter(config);
},

destroyEmitter: function (emitter) {
const particleEngine = emitter.particleEngine;

particleEngine.destroyEmitter(emitter);
},

loadTexture: async function (textureName, texturePath) {
texturePath = texturePath ?? textureName;

if (!texturePath.includes('.png'))
texturePath = `images/${texturePath}.png`;

const image = await textures.getLoader(texturePath);

//Build sprite texture
this.textures[textureName] = new PIXI.BaseTexture(image);
this.textures[textureName].scaleMode = PIXI.SCALE_MODES.NEAREST;
this.textures[textureName].size = this.textures[textureName].width / 8;
},

setSprite: async function (obj) {
const { sprite, sheetName, cell } = obj;

const textureExists = this.textures.hasOwnProperty(sheetName);
if (!textureExists)
await this.loadTexture(sheetName);

sprite.texture = this.getTexture(sheetName, cell);

const newSize = sprite.texture.width;

obj.w = newSize * scaleMult;
obj.h = obj.w;

sprite.width = obj.w;
sprite.height = obj.h;

if (newSize !== sprite.size) {
sprite.size = newSize;
this.setSpritePosition(obj);
}
},

setSpritePosition: function (obj) {
const { sprite, x, y, flipX, offsetX = 0, offsetY = 0 } = obj;

sprite.x = (x * scale) + (flipX ? scale : 0) + offsetX;
const oldY = sprite.y;
sprite.y = (y * scale) + offsetY;

if (sprite.width > scale) {
if (flipX)
sprite.x += scale;
else
sprite.x -= scale;

sprite.y -= (scale * 2);
}

if (oldY !== sprite.y)
this.reorder();

sprite.scale.x = flipX ? -scaleMult : scaleMult;
},

reorder: function () {
this.layers.mobs.children.sort((a, b) => b.y - a.y);
},

destroyObject: function (obj) {
if (obj.sprite.parent)
obj.sprite.parent.removeChild(obj.sprite);
},

//Changes the moveSpeedMax and moveSpeedInc variables
// moveSpeed changes when mounting and unmounting
// moveSpeed: 0 | moveSpeedMax: 1.5 | moveSpeedInc: 0.5
// moveSpeed: 200 | moveSpeedMax: 5.5 | moveSpeedInc: 0.2
// Between these values we should follow an exponential curve for moveSpeedInc since
// a higher chance will proc more often, meaning the buildup in distance becomes greater
adaptCameraMoveSpeed: function (moveSpeed) {
const factor = Math.sqrt(moveSpeed);
const maxValue = Math.sqrt(200);

this.moveSpeedMax = 1.5 + ((moveSpeed / 200) * 3.5);
this.moveSpeedInc = 0.2 + (((maxValue - factor) / maxValue) * 0.3);
},

updateMapAtPosition: function (x, y, mapCellString) {
const { map, sprites, layers: { tileSprites: container } } = this;

const row = sprites[x];
if (!row)
return;

const cell = row[y];
if (!cell)
return;

cell.forEach(c => {
c.visible = false;
spritePool.store(c);
});

cell.length = 0;

map[x][y] = mapCellString.split(',');

map[x][y].forEach(m => {
m--;
let tile = spritePool.getSprite(m);
if (!tile) {
tile = this.buildTile(m, x, y);
container.addChild(tile);
tile.type = m;
tile.sheetNum = tileOpacity.getSheetNum(m);
} else {
tile.position.x = x * scale;
tile.position.y = y * scale;
tile.visible = true;
}

cell.push(tile);
cell.visible = true;
});
},

render: function () {
if (!this.stage)
return;

effects.render();

particleLayers.forEach(p => particleEngines[p].update());

this.renderer.render(this.stage);
}
};

return renderer;
});

+ 922
- 0
src/client/js/rendering/rendererLegacy.js View File

@@ -0,0 +1,922 @@
define([
'js/resources',
'js/system/events',
'js/misc/physics',
'js/rendering/effects',
'js/rendering/tileOpacity',
'js/rendering/particles',
'js/rendering/shaders/outline',
'js/rendering/spritePool',
'js/system/globals',
'js/rendering/renderLoginBackground',
'js/rendering/helpers/resetRenderer',
'js/rendering/textures'
], function (
resources,
events,
physics,
effects,
tileOpacity,
particles,
shaderOutline,
spritePool,
globals,
renderLoginBackground,
resetRenderer,
textures
) {
const mRandom = Math.random.bind(Math);

const particleLayers = ['particlesUnder', 'particles'];
const particleEngines = {};

return {
stage: null,
layers: {
particlesUnder: null,
objects: null,
mobs: null,
characters: null,
attacks: null,
effects: null,
particles: null,
lightPatches: null,
lightBeams: null,
tileSprites: null,
hiders: null
},

titleScreen: false,

width: 0,
height: 0,

showTilesW: 0,
showTilesH: 0,

pos: {
x: 0,
y: 0
},
moveTo: null,
moveSpeed: 0,
moveSpeedMax: 1.50,
moveSpeedInc: 0.5,

lastUpdatePos: {
x: 0,
y: 0
},

zoneId: null,

textures: {},
textureCache: {},

sprites: [],

lastTick: null,

hiddenRooms: null,

init: function () {
PIXI.settings.GC_MODE = PIXI.GC_MODES.AUTO;
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
PIXI.settings.SPRITE_MAX_TEXTURES = Math.min(PIXI.settings.SPRITE_MAX_TEXTURES, 16);
PIXI.settings.RESOLUTION = 1;

events.on('onToggleFullscreen', this.toggleScreen.bind(this));
events.on('onMoveSpeedChange', this.adaptCameraMoveSpeed.bind(this));
events.on('resetRenderer', resetRenderer.bind(this));

this.width = $('body').width();
this.height = $('body').height();

this.showTilesW = Math.ceil((this.width / scale) / 2) + 3;
this.showTilesH = Math.ceil((this.height / scale) / 2) + 3;

this.renderer = new PIXI.Renderer({
width: this.width,
height: this.height,
backgroundColor: '0x2d2136'
});

window.addEventListener('resize', this.onResize.bind(this));

$(this.renderer.view).appendTo('.canvas-container');

this.stage = new PIXI.Container();

let layers = this.layers;
Object.keys(layers).forEach(l => {
layers[l] = new PIXI.Container();
layers[l].layer = (l === 'tileSprites') ? 'tiles' : l;

this.stage.addChild(layers[l]);
});

const textureList = globals.clientConfig.textureList;
const sprites = resources.sprites;

textureList.forEach(t => {
this.textures[t] = new PIXI.BaseTexture(sprites[t]);
this.textures[t].scaleMode = PIXI.SCALE_MODES.NEAREST;
this.textures[t].size = this.textures[t].width / 8;
});

particleLayers.forEach(p => {
const engine = $.extend({}, particles);
engine.init({
r: this,
renderer: this.renderer,
stage: this.layers[p]
});

particleEngines[p] = engine;
});
},

toggleScreen: function () {
let isFullscreen = (window.innerHeight === screen.height);

if (isFullscreen) {
let doc = document;
(doc.cancelFullscreen || doc.msCancelFullscreen || doc.mozCancelFullscreen || doc.webkitCancelFullScreen).call(doc);
return 'Windowed';
}

let el = $('body')[0];
(el.requestFullscreen || el.msRequestFullscreen || el.mozRequestFullscreen || el.webkitRequestFullscreen).call(el);
return 'Fullscreen';
},

buildTitleScreen: function () {
this.titleScreen = true;

renderLoginBackground(this);
},

onResize: function () {
if (isMobile)
return;

this.width = $('body').width();
this.height = $('body').height();

this.showTilesW = Math.ceil((this.width / scale) / 2) + 3;
this.showTilesH = Math.ceil((this.height / scale) / 2) + 3;

this.renderer.resize(this.width, this.height);
if (window.player) {
this.setPosition({
x: (window.player.x - (this.width / (scale * 2))) * scale,
y: (window.player.y - (this.height / (scale * 2))) * scale
}, true);
}

if (this.titleScreen) {
this.clean();
this.buildTitleScreen();
}

events.emit('onResize');
},

getTexture: function (baseTex, cell) {
try {
let textureName = baseTex + '_' + cell;

let textureCache = this.textureCache;

let cached = textureCache[textureName];

if (!cached) {
let y = ~~(cell / 8);
let x = cell - (y * 8);
const texture = this.textures[baseTex];
const size = texture.size;

cached = new PIXI.Texture(texture, new PIXI.Rectangle(x * size, y * size, size, size));
textureCache[textureName] = cached;
}

return cached;
} catch (e) {
console.log(e, baseTex, cell);
}
},

clean: function () {
this.stage.removeChild(this.layers.hiders);
this.layers.hiders = new PIXI.Container();
this.layers.hiders.layer = 'hiders';
this.stage.addChild(this.layers.hiders);

let container = this.layers.tileSprites;
this.stage.removeChild(container);

this.layers.tileSprites = container = new PIXI.ParticleContainer(30000);
container.layer = 'tiles';
this.stage.addChild(container);

this.stage.children.sort((a, b) => {
if (a.layer === 'hiders')
return 1;
else if (b.layer === 'hiders')
return -1;
else if (a.layer === 'tiles')
return -1;
else if (b.layer === 'tiles')
return 1;
return 0;
});

this.textureCache = {};
},

buildTile: function (c, i, j) {
let alpha = tileOpacity.map(c);
let canFlip = tileOpacity.canFlip(c);

let tile = new PIXI.Sprite(this.getTexture('sprites', c));

tile.alpha = alpha;
tile.position.x = i * scale;
tile.position.y = j * scale;
tile.width = scale;
tile.height = scale;

if (canFlip && mRandom() < 0.5) {
tile.position.x += scale;
tile.scale.x = -scaleMult;
}

return tile;
},

onGetMap: async function (msg) {
tileOpacity.initMap(msg);

//Load sprite atlas
let spriteAtlas = null;
await new Promise(res => {
spriteAtlas = new Image();
spriteAtlas.onload = res;
spriteAtlas.src = msg.spriteAtlasPath;
});

//Build sprite texture
this.textures.sprites = new PIXI.BaseTexture(spriteAtlas);
this.textures.sprites.scaleMode = PIXI.SCALE_MODES.NEAREST;
this.textures.sprites.size = 8;

//Misc
this.titleScreen = false;
physics.init(msg.collisionMap);

let map = this.map = msg.map;
let w = this.w = map.length;
let h = this.h = map[0].length;

for (let i = 0; i < w; i++) {
let row = map[i];
for (let j = 0; j < h; j++) {
if (!row[j].split)
row[j] += '';

row[j] = row[j].split(',');
}
}

this.clean();
spritePool.clean();

this.stage.filters = [new PIXI.filters.AlphaFilter()];
this.stage.filterArea = new PIXI.Rectangle(0, 0, Math.max(w * scale, this.width), Math.max(h * scale, this.height));

this.hiddenRooms = msg.hiddenRooms;

this.sprites = _.get2dArray(w, h, 'array');

this.stage.children.sort((a, b) => {
if (a.layer === 'tiles')
return -1;
else if (b.layer === 'tiles')
return 1;
return 0;
});

if (this.zoneId !== null)
events.emit('onRezone', this.zoneId);
this.zoneId = msg.zoneId;

msg.clientObjects.forEach(c => {
c.zoneId = this.zoneId;
events.emit('onGetObject', c);
});
},

setPosition: function (pos, instant) {
pos.x += 16;
pos.y += 16;

let player = window.player;
if (player) {
let px = player.x;
let py = player.y;

let hiddenRooms = this.hiddenRooms || [];
let hLen = hiddenRooms.length;
for (let i = 0; i < hLen; i++) {
let h = hiddenRooms[i];
if (!h.discoverable)
continue;
if (
px < h.x ||
px >= h.x + h.width ||
py < h.y ||
py >= h.y + h.height ||
!physics.isInPolygon(px, py, h.area)
)
continue;

h.discovered = true;
}
}

if (instant) {
this.moveTo = null;
this.pos = pos;
this.stage.x = -~~this.pos.x;
this.stage.y = -~~this.pos.y;
} else
this.moveTo = pos;

this.updateSprites();
},

isVisible: function (x, y) {
let stage = this.stage;
let sx = -stage.x;
let sy = -stage.y;

let sw = this.width;
let sh = this.height;

return (!(x < sx || y < sy || x >= sx + sw || y >= sy + sh));
},

isHidden: function (x, y) {
let hiddenRooms = this.hiddenRooms;
let hLen = hiddenRooms.length;
if (!hLen)
return false;

const { player: { x: px, y: py } } = window;

let foundVisibleLayer = null;
let foundHiddenLayer = null;

hiddenRooms.forEach(h => {
const { discovered, layer, interior } = h;
const { x: hx, y: hy, width, height, area } = h;

//Is the tile outside the hider
if (
x < hx ||
x >= hx + width ||
y < hy ||
y >= hy + height
) {
//If the hider is an interior, the tile should be hidden if the player is inside the hider
if (interior) {
if (physics.isInPolygon(px, py, area))
foundHiddenLayer = layer;
}

return;
}

//Is the tile inside the hider
if (!physics.isInPolygon(x, y, area))
return;

if (discovered) {
foundVisibleLayer = layer;

return;
}

//Is the player outside the hider
if (
px < hx ||
px >= hx + width ||
py < hy ||
py >= hy + height
) {
foundHiddenLayer = layer;

return;
}

//Is the player inside the hider
if (!physics.isInPolygon(px, py, area)) {
foundHiddenLayer = layer;

return;
}

foundVisibleLayer = layer;
});

//We compare hider layers to cater for hiders inside hiders
return (foundHiddenLayer > foundVisibleLayer) || (foundHiddenLayer === 0 && foundVisibleLayer === null);
},

updateSprites: function () {
if (this.titleScreen)
return;

const player = window.player;
if (!player)
return;

const { w, h, width, height, stage, map, sprites } = this;

const x = ~~((-stage.x / scale) + (width / (scale * 2)));
const y = ~~((-stage.y / scale) + (height / (scale * 2)));

this.lastUpdatePos.x = stage.x;
this.lastUpdatePos.y = stage.y;

const container = this.layers.tileSprites;

const sw = this.showTilesW;
const sh = this.showTilesH;

let lowX = Math.max(0, x - sw + 1);
let lowY = Math.max(0, y - sh + 2);
let highX = Math.min(w, x + sw - 2);
let highY = Math.min(h, y + sh - 2);

let addedSprite = false;

const checkHidden = this.isHidden.bind(this);
const buildTile = this.buildTile.bind(this);

const newVisible = [];
const newHidden = [];

for (let i = lowX; i < highX; i++) {
let mapRow = map[i];
let spriteRow = sprites[i];

for (let j = lowY; j < highY; j++) {
const cell = mapRow[j];
if (!cell)
continue;

const cLen = cell.length;
if (!cLen)
return;

const rendered = spriteRow[j];
const isHidden = checkHidden(i, j);

if (isHidden) {
const nonFakeRendered = rendered.filter(r => !r.isFake);

const rLen = nonFakeRendered.length;
for (let k = 0; k < rLen; k++) {
const sprite = nonFakeRendered[k];

sprite.visible = false;
spritePool.store(sprite);
rendered.spliceWhere(s => s === sprite);
}

if (cell.visible) {
cell.visible = false;
newHidden.push({
x: i,
y: j
});
}

const hasFake = cell.some(c => c[0] === '-');
if (hasFake) {
const isFakeRendered = rendered.some(r => r.isFake);
if (isFakeRendered)
continue;
} else
continue;
} else {
const fakeRendered = rendered.filter(r => r.isFake);

const rLen = fakeRendered.length;
for (let k = 0; k < rLen; k++) {
const sprite = fakeRendered[k];

sprite.visible = false;
spritePool.store(sprite);
rendered.spliceWhere(s => s === sprite);
}

if (!cell.visible) {
cell.visible = true;
newVisible.push({
x: i,
y: j
});
}

const hasNonFake = cell.some(c => c[0] !== '-');
if (hasNonFake) {
const isNonFakeRendered = rendered.some(r => !r.isFake);
if (isNonFakeRendered)
continue;
} else
continue;
}

for (let k = 0; k < cLen; k++) {
let c = cell[k];
if (c === '0' || c === '')
continue;

const isFake = +c < 0;
if (isFake && !isHidden)
continue;
else if (!isFake && isHidden)
continue;

if (isFake)
c = -c;

c--;

let flipped = '';
if (tileOpacity.canFlip(c)) {
if (mRandom() < 0.5)
flipped = 'flip';
}

let tile = spritePool.getSprite(flipped + c);
if (!tile) {
tile = buildTile(c, i, j);
container.addChild(tile);
tile.type = c;
tile.sheetNum = tileOpacity.getSheetNum(c);
addedSprite = true;
} else {
tile.position.x = i * scale;
tile.position.y = j * scale;
if (flipped !== '')
tile.position.x += scale;
tile.visible = true;
}

if (isFake)
tile.isFake = isFake;

tile.z = k;

rendered.push(tile);
}
}
}

lowX = Math.max(0, lowX - 10);
lowY = Math.max(0, lowY - 10);
highX = Math.min(w - 1, highX + 10);
highY = Math.min(h - 1, highY + 10);

for (let i = lowX; i < highX; i++) {
const mapRow = map[i];
let spriteRow = sprites[i];
let outside = ((i >= x - sw) && (i < x + sw));
for (let j = lowY; j < highY; j++) {
if ((outside) && (j >= y - sh) && (j < y + sh))
continue;

const cell = mapRow[j];

if (cell.visible) {
cell.visible = false;
newHidden.push({ x: i, y: j });
}

let list = spriteRow[j];
let lLen = list.length;
for (let k = 0; k < lLen; k++) {
let sprite = list[k];
sprite.visible = false;
spritePool.store(sprite);
}
spriteRow[j] = [];
}
}

events.emit('onTilesVisible', newVisible, true);
events.emit('onTilesVisible', newHidden, false);

if (addedSprite)
container.children.sort((a, b) => a.z - b.z);
},

update: function () {
let time = +new Date();

if (this.moveTo) {
let deltaX = this.moveTo.x - this.pos.x;
let deltaY = this.moveTo.y - this.pos.y;

if (deltaX !== 0 || deltaY !== 0) {
let distance = Math.max(Math.abs(deltaX), Math.abs(deltaY));

let moveSpeedMax = this.moveSpeedMax;
if (this.moveSpeed < moveSpeedMax)
this.moveSpeed += this.moveSpeedInc;

let moveSpeed = this.moveSpeed;

if (moveSpeedMax < 1.6)
moveSpeed *= 1 + (distance / 200);

let elapsed = time - this.lastTick;
moveSpeed *= (elapsed / 15);

if (moveSpeed > distance)
moveSpeed = distance;

deltaX = (deltaX / distance) * moveSpeed;
deltaY = (deltaY / distance) * moveSpeed;

this.pos.x = this.pos.x + deltaX;
this.pos.y = this.pos.y + deltaY;
} else {
this.moveSpeed = 0;
this.moveTo = null;
}

let stage = this.stage;
if (window.staticCamera !== true) {
stage.x = -~~this.pos.x;
stage.y = -~~this.pos.y;
}

let halfScale = scale / 2;
if (Math.abs(stage.x - this.lastUpdatePos.x) > halfScale || Math.abs(stage.y - this.lastUpdatePos.y) > halfScale)
this.updateSprites();

events.emit('onSceneMove');
}

this.lastTick = time;
},

buildContainer: function (obj) {
let container = new PIXI.Container();
this.layers[obj.layerName || obj.sheetName].addChild(container);

return container;
},

buildRectangle: function (obj) {
let graphics = new PIXI.Graphics();

let alpha = obj.alpha;
if (obj.has('alpha'))
graphics.alpha = alpha;

let fillAlpha = obj.fillAlpha;
if (obj.has('fillAlpha'))
fillAlpha = 1;

graphics.beginFill(obj.color || '0x48edff', fillAlpha);

if (obj.strokeColor)
graphics.lineStyle(scaleMult, obj.strokeColor);

graphics.drawRect(0, 0, obj.w, obj.h);

graphics.endFill();

(obj.parent || this.layers[obj.layerName || obj.sheetName]).addChild(graphics);

graphics.position.x = obj.x;
graphics.position.y = obj.y;

return graphics;
},

moveRectangle: function (obj) {
obj.sprite.position.x = obj.x;
obj.sprite.position.y = obj.y;
obj.sprite.width = obj.w;
obj.sprite.height = obj.h;
},

buildObject: function (obj) {
const { sheetName, parent: container, layerName, visible = true } = obj;

const sprite = new PIXI.Sprite();

obj.sprite = sprite;

this.setSprite(obj);

sprite.visible = visible;

const spriteContainer = container || this.layers[layerName || sheetName] || this.layers.objects;
spriteContainer.addChild(sprite);

obj.w = sprite.width;
obj.h = sprite.height;

return sprite;
},

addFilter: function (sprite) {
let thickness = (sprite.width > scale) ? 8 : 16;

let filter = new shaderOutline(this.renderer.width, this.renderer.height, thickness, '0xffffff');
if (!sprite.filters)
sprite.filters = [filter];
else
sprite.filters.push();

return filter;
},

removeFilter: function (sprite, filter) {
if (sprite.filters)
sprite.filters = null;
},

buildText: function (obj) {
let textSprite = new PIXI.Text(obj.text, {
fontFamily: 'bitty',
fontSize: (obj.fontSize || 14),
fill: obj.color || 0xF2F5F5,
stroke: 0x2d2136,
strokeThickness: 4,
align: 'center'
});

textSprite.x = obj.x - (textSprite.width / 2);
textSprite.y = obj.y;

let parentSprite = obj.parent || this.layers[obj.layerName];
parentSprite.addChild(textSprite);

return textSprite;
},

buildEmitter: function (config) {
const { layerName = 'particles' } = config;
const particleEngine = particleEngines[layerName];

return particleEngine.buildEmitter(config);
},

destroyEmitter: function (emitter) {
const particleEngine = emitter.particleEngine;

particleEngine.destroyEmitter(emitter);
},

loadTexture: async function (textureName, texturePath) {
texturePath = texturePath ?? textureName;

if (!texturePath.includes('.png'))
texturePath = `images/${texturePath}.png`;

const image = await textures.getLoader(texturePath);

//Build sprite texture
this.textures[textureName] = new PIXI.BaseTexture(image);
this.textures[textureName].scaleMode = PIXI.SCALE_MODES.NEAREST;
this.textures[textureName].size = this.textures[textureName].width / 8;
},

setSprite: async function (obj) {
const { sprite, sheetName, cell } = obj;

const textureExists = this.textures.hasOwnProperty(sheetName);
if (!textureExists)
await this.loadTexture(sheetName);

sprite.texture = this.getTexture(sheetName, cell);

const newSize = sprite.texture.width;

obj.w = newSize * scaleMult;
obj.h = obj.w;

sprite.width = obj.w;
sprite.height = obj.h;

if (newSize !== sprite.size) {
sprite.size = newSize;
this.setSpritePosition(obj);
}
},

setSpritePosition: function (obj) {
const { sprite, x, y, flipX, offsetX = 0, offsetY = 0 } = obj;

sprite.x = (x * scale) + (flipX ? scale : 0) + offsetX;
const oldY = sprite.y;
sprite.y = (y * scale) + offsetY;

if (sprite.width > scale) {
if (flipX)
sprite.x += scale;
else
sprite.x -= scale;

sprite.y -= (scale * 2);
}

if (oldY !== sprite.y)
this.reorder();

sprite.scale.x = flipX ? -scaleMult : scaleMult;
},

reorder: function () {
this.layers.mobs.children.sort((a, b) => b.y - a.y);
},

destroyObject: function (obj) {
if (obj.sprite.parent)
obj.sprite.parent.removeChild(obj.sprite);
},

//Changes the moveSpeedMax and moveSpeedInc variables
// moveSpeed changes when mounting and unmounting
// moveSpeed: 0 | moveSpeedMax: 1.5 | moveSpeedInc: 0.5
// moveSpeed: 200 | moveSpeedMax: 5.5 | moveSpeedInc: 0.2
// Between these values we should follow an exponential curve for moveSpeedInc since
// a higher chance will proc more often, meaning the buildup in distance becomes greater
adaptCameraMoveSpeed: function (moveSpeed) {
const factor = Math.sqrt(moveSpeed);
const maxValue = Math.sqrt(200);

this.moveSpeedMax = 1.5 + ((moveSpeed / 200) * 3.5);
this.moveSpeedInc = 0.2 + (((maxValue - factor) / maxValue) * 0.3);
},

updateMapAtPosition: function (x, y, mapCellString) {
const { map, sprites, layers: { tileSprites: container } } = this;

const row = sprites[x];
if (!row)
return;

const cell = row[y];
if (!cell)
return;

cell.forEach(c => {
c.visible = false;
spritePool.store(c);
});

cell.length = 0;

map[x][y] = mapCellString.split(',');

map[x][y].forEach(m => {
m--;
let tile = spritePool.getSprite(m);
if (!tile) {
tile = this.buildTile(m, x, y);
container.addChild(tile);
tile.type = m;
tile.sheetNum = tileOpacity.getSheetNum(m);
} else {
tile.position.x = x * scale;
tile.position.y = y * scale;
tile.visible = true;
}

cell.push(tile);
cell.visible = true;
});
},

render: function () {
if (!this.stage)
return;

effects.render();

particleLayers.forEach(p => particleEngines[p].update());

this.renderer.render(this.stage);
}
};
});

+ 1
- 1
src/client/ui/shared/renderItem.js View File

@@ -132,7 +132,7 @@ define([
if (item.spriteSize)
size = item.spriteSize;

if (spriteSizes[spritesheet])
if (spriteSizes?.[spritesheet])
size = spriteSizes[spritesheet];

const imgX = (-item.sprite[0] * size);


Loading…
Cancel
Save