mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 14:15:51 +02:00
Merge pull request #130 from ponydevs/archive-update-v0.53.2
Archive update v0.53.2
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
@@ -272,6 +272,8 @@ $argentina-blue: #74acdf;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make $action-bar-space 46px on desktop
|
||||
|
||||
.actions-modal-opened {
|
||||
$action-bar-space: 80px;
|
||||
|
||||
|
||||
+15
-12
@@ -15,6 +15,7 @@ export interface Contributors {
|
||||
contributors: Contributor[];
|
||||
}
|
||||
|
||||
// The Team
|
||||
export const CREDITS: Credit[] = [
|
||||
// example:
|
||||
// {
|
||||
@@ -81,28 +82,30 @@ export const CREDITS: Credit[] = [
|
||||
|
||||
export const CONTRIBUTORS: Contributors[] = [
|
||||
{
|
||||
group: 'Musicians',
|
||||
group: 'Music Composers',
|
||||
contributors: [
|
||||
{ name: 'Wandering Artist', links: ['https://wanderingartist.bandcamp.com/', 'https://www.youtube.com/user/WanderingArtistMusic'] }
|
||||
]
|
||||
{ name: 'Wandering Artist', links: ['https://wanderingartist.bandcamp.com/', 'https://www.youtube.com/user/WanderingArtistMusic'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Artists & Animators',
|
||||
group: 'Programmers',
|
||||
contributors: [
|
||||
{ name: 'Industrialice' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Artists',
|
||||
contributors: [
|
||||
{ name: 'Shino', links: ['https://www.deviantart.com/shinodage'] },
|
||||
{ name: 'ChiraChan', links: ['https://www.deviantart.com/chiramii-chan', 'https://chirachan-art.tumblr.com/'] },
|
||||
{ name: 'Goodly', links: ['https://www.deviantart.com/goodlyay'] },
|
||||
{ name: 'TioRafaJP', links: ['https://www.deviantart.com/tiorafajp', 'https://www.youtube.com/user/RafaelJP2'] },
|
||||
{ name: 'ShareMyShipment', links: ['https://www.deviantart.com/sharemyshipment'] },
|
||||
{ name: 'Velenor', links: ['https://www.deviantart.com/velenor'] },
|
||||
{ name: 'Velenor' },
|
||||
{ name: 'Meno', links: ['https://twitter.com/menojar', 'https://www.deviantart.com/menojar'] },
|
||||
{ name: 'OtakuAP', links: ['https://www.deviantart.com/otakuap'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Artists',
|
||||
contributors: [
|
||||
{ name: 'Disastral' },
|
||||
{ name: 'Meno', links: ['https://www.deviantart.com/menojar'] },
|
||||
{ name: 'CyberPon3', links: ['https://twitter.com/CyberPon3'] },
|
||||
{ name: 'Paulpeoples', links: ['https://www.deviantart.com/paulpeopless'] },
|
||||
{ name: 'Velvet-Frost', links: ['https://www.deviantart.com/velvet-frost'] },
|
||||
{ name: 'Jet7Wave', links: ['https://www.deviantart.com/jetwave'] },
|
||||
@@ -119,7 +122,7 @@ export const CONTRIBUTORS: Contributors[] = [
|
||||
{ name: 'Towmacow Waffles', links: ['https://www.deviantart.com/towmacowwaffles'] },
|
||||
{ name: 'OrchidPony', links: ['https://www.deviantart.com/orchidpony'] },
|
||||
{ name: 'Cherry Cerise', links: ['https://www.deviantart.com/cherryceriseart'] },
|
||||
{ name: 'Radio' },
|
||||
{ name: 'RADIOstations', links: ['https://twitter.com/stagbeeble'] },
|
||||
{ name: 'Ultimate Fluff' },
|
||||
{ name: 'SC', links: ['https://0somecunt0.tumblr.com/tagged/sfw'] },
|
||||
{ name: 'SailorDolpin', links: ['https://vk.com/id324582699'] },
|
||||
|
||||
+22
-6
@@ -18,9 +18,14 @@ const SELECTED_ENTITY_BOUNDS = withAlphaFloat(ORANGE, 0.5);
|
||||
function drawEntities(batch: PaletteSpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
|
||||
const drawHidden = options.drawHidden;
|
||||
let entitiesDrawn = 0;
|
||||
let depthStep = 1.0 / entities.length;
|
||||
|
||||
for (let entity of entities) {
|
||||
if (isBoundsVisible(camera, entity.bounds, entity.x, entity.y) && (!isHidden(entity) || drawHidden)) {
|
||||
const depth = depthStep * entitiesDrawn;
|
||||
entity.depth = depth;
|
||||
batch.depth = depth;
|
||||
|
||||
for (const entity of entities) {
|
||||
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.bounds, entity.x, entity.y)) {
|
||||
if (entity.type === PONY_TYPE) {
|
||||
drawPonyEntity(batch, entity as Pony, options);
|
||||
entitiesDrawn++;
|
||||
@@ -45,38 +50,47 @@ function drawEntities(batch: PaletteSpriteBatch, entities: Entity[], camera: Cam
|
||||
|
||||
export function drawEntityLights(batch: SpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
|
||||
const drawHidden = options.drawHidden;
|
||||
let drawn = 0;
|
||||
|
||||
for (const entity of entities) {
|
||||
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLight)) {
|
||||
console.error('Cannot draw entity light', entity);
|
||||
}
|
||||
|
||||
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightBounds, entity.x, entity.y)) {
|
||||
if (isBoundsVisible(camera, entity.lightBounds, entity.x, entity.y) && (!isHidden(entity) || drawHidden)) {
|
||||
if (entity.type === PONY_TYPE) {
|
||||
drawPonyEntityLight(batch, entity as Pony, options);
|
||||
} else {
|
||||
entity.drawLight!(batch, options);
|
||||
}
|
||||
++drawn;
|
||||
}
|
||||
}
|
||||
|
||||
return drawn;
|
||||
}
|
||||
|
||||
export function drawEntityLightSprites(batch: SpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
|
||||
const drawHidden = options.drawHidden;
|
||||
let drawn = 0;
|
||||
|
||||
for (const entity of entities) {
|
||||
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLightSprite)) {
|
||||
console.error('Cannot draw entity light sprite', entity);
|
||||
}
|
||||
|
||||
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightSpriteBounds, entity.x, entity.y)) {
|
||||
if (isBoundsVisible(camera, entity.lightSpriteBounds, entity.x, entity.y) && (!isHidden(entity) || drawHidden)) {
|
||||
batch.depth = entity.depth;
|
||||
if (entity.type === PONY_TYPE) {
|
||||
drawPonyEntityLightSprite(batch, entity as Pony, options);
|
||||
} else {
|
||||
entity.drawLightSprite!(batch, options);
|
||||
}
|
||||
++drawn;
|
||||
}
|
||||
}
|
||||
|
||||
return drawn;
|
||||
}
|
||||
|
||||
export function hasDrawLight(entity: Entity) {
|
||||
@@ -99,10 +113,11 @@ export function hasLightSprite(entity: Entity) {
|
||||
}
|
||||
|
||||
export function drawMap(
|
||||
batch: PaletteSpriteBatch, map: WorldMap, camera: Camera, player: Pony, options: DrawOptions,
|
||||
tileSets: TileSets, selectedEntities: Entity[],
|
||||
batch: PaletteSpriteBatch, map: WorldMap, camera: Camera, player: Pony,
|
||||
options: DrawOptions, tileSets: TileSets, selectedEntities: Entity[],
|
||||
) {
|
||||
TIMING && timeStart('forEachRegion');
|
||||
batch.depth = 1.0;
|
||||
if (BETA && options.engine === Engine.Whiteness) {
|
||||
batch.drawRect(WHITE, 0, 0, toScreenX(map.width), toScreenY(map.height));
|
||||
} else if (BETA && options.engine === Engine.LayeredTiles) {
|
||||
@@ -118,6 +133,7 @@ export function drawMap(
|
||||
|
||||
TIMING && timeStart('drawEntities');
|
||||
const entitiesDrawn = drawEntities(batch, map.entitiesDrawable, camera, options);
|
||||
batch.depth = 1.0;
|
||||
TIMING && timeEnd();
|
||||
|
||||
if (BETA || TOOLS) {
|
||||
|
||||
+218
-166
@@ -5,7 +5,7 @@ import {
|
||||
EntityState, Pony, Notification, TileType, Action, IServerActions, Season, WorldState, Holiday,
|
||||
TileSets, ChatMessage, PartyInfo, Entity, DrawOptions, Engine, defaultDrawOptions, PonyStateFlags,
|
||||
DoAction, ChatType, WorldStateFlags, DebugFlags, MessageType, AccountSettings, Matrix4,
|
||||
FakeEntity, SelectFlags, WorldMap, MapType, MapFlags, EntityFlags, houseTiles, isValidTile
|
||||
FakeEntity, SelectFlags, WorldMap, MapType, MapFlags, EntityFlags, houseTiles, isValidTile, GraphicsQuality
|
||||
} from '../common/interfaces';
|
||||
import {
|
||||
clamp, lengthOfXY, setFlag, hasFlag, boundsIntersect, point, toInt, lerpColor, distanceXY
|
||||
@@ -27,7 +27,7 @@ import { toggleWalls } from '../common/mixins';
|
||||
import { getEntityTypeName, hammer, broom, createAnEntity, saw, placeableEntities, shovel } from '../common/entities';
|
||||
import { hasExtendedInfo, setHeadAnimation, createPony } from '../common/pony';
|
||||
import { PaletteManager } from '../graphics/paletteManager';
|
||||
import { getRenderTargetSize, isWebGL2 } from '../graphics/webgl/webglUtils';
|
||||
import { isWebGL2 } from '../graphics/webgl/webglUtils';
|
||||
import { drawFullScreenMessage, drawNames, drawChat } from '../graphics/graphicsUtils';
|
||||
import { Key } from './input/input';
|
||||
import { loadAndInitSpriteSheets } from './spriteUtils';
|
||||
@@ -62,7 +62,7 @@ import { initializeToys } from './ponyDraw';
|
||||
import { ErrorReporter } from '../components/services/errorReporter';
|
||||
import { mockPaletteManager } from '../common/ponyInfo';
|
||||
import { timeStart, timeEnd, timingCollate, timeReset } from './timing';
|
||||
import { bindFrameBuffer, unbindFrameBuffer, resizeFrameBuffer } from '../graphics/webgl/frameBuffer';
|
||||
import { createFrameBuffer, bindFrameBuffer, unbindFrameBuffer, disposeFrameBuffer } from '../graphics/webgl/frameBuffer';
|
||||
import { WebGL, initWebGL, disposeWebGL, initWebGLResources } from './webgl';
|
||||
import { bindTexture } from '../graphics/webgl/texture2d';
|
||||
import {
|
||||
@@ -208,7 +208,6 @@ export class PonyTownGame implements Game {
|
||||
supporterPony = createPony(0, 0, SUPPORTER_PONY, mockPaletteManager.addArray(defaultPalette), mockPaletteManager);
|
||||
discordPony = createPony(0, 0, DISCORD_PONY, mockPaletteManager.addArray(defaultPalette), mockPaletteManager);
|
||||
scale: number;
|
||||
failedFBO = false;
|
||||
rightOverride?: boolean;
|
||||
headTurnedOverride?: boolean;
|
||||
stateOverride?: EntityState;
|
||||
@@ -248,6 +247,8 @@ export class PonyTownGame implements Game {
|
||||
private deltaMultiplier = 1;
|
||||
private lastDraw = 0;
|
||||
private entitiesDrawn = 0;
|
||||
private lightEntitiesDrawn = 0;
|
||||
private lightSpriteEntitiesDrawn = 0;
|
||||
private lastFps = performance.now();
|
||||
private frames = 0;
|
||||
private drawFps = 0;
|
||||
@@ -274,6 +275,16 @@ export class PonyTownGame implements Game {
|
||||
private errorReporter: ErrorReporter,
|
||||
private zone: NgZone,
|
||||
) {
|
||||
if (settings.browser.lowGraphicsMode !== undefined) {
|
||||
settings.browser.graphicsQuality = (settings.browser.lowGraphicsMode === true ? GraphicsQuality.Low : GraphicsQuality.High);
|
||||
settings.browser.lowGraphicsMode = undefined;
|
||||
settings.saveBrowserSettings();
|
||||
}
|
||||
if (settings.browser.graphicsQuality === undefined) {
|
||||
settings.browser.graphicsQuality = GraphicsQuality.High;
|
||||
settings.saveBrowserSettings();
|
||||
}
|
||||
|
||||
this.scale = this.getScale();
|
||||
this.audio.initTracks(this.season, this.holiday, this.map.type);
|
||||
this.audio.setVolume(this.volume);
|
||||
@@ -290,7 +301,22 @@ export class PonyTownGame implements Game {
|
||||
return this.settings.browser.volume || 0;
|
||||
}
|
||||
get disableLighting() {
|
||||
return !!this.settings.browser.lowGraphicsMode || this.failedFBO;
|
||||
if (this.settings.browser.graphicsQuality === GraphicsQuality.Low) {
|
||||
return true;
|
||||
}
|
||||
if (this.webgl) {
|
||||
return this.webgl.failedFBO;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
get disableLightMasksFixing() {
|
||||
if (this.settings.browser.graphicsQuality !== GraphicsQuality.High) {
|
||||
return true;
|
||||
}
|
||||
if (this.webgl) {
|
||||
return this.webgl.failedDepthBuffer;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
get frameDelay() {
|
||||
return (this.settings.browser.powerSaving || this.editingActions) ? (1000 / 45) : 0;
|
||||
@@ -325,8 +351,16 @@ export class PonyTownGame implements Game {
|
||||
}
|
||||
}
|
||||
private toggleDisableLighting() {
|
||||
if (!this.failedFBO) {
|
||||
this.settings.browser.lowGraphicsMode = !this.settings.browser.lowGraphicsMode;
|
||||
if (!this.webgl) {
|
||||
return;
|
||||
}
|
||||
if (!this.webgl.failedFBO) {
|
||||
if (this.settings.browser.graphicsQuality === GraphicsQuality.Low) {
|
||||
this.settings.browser.graphicsQuality = GraphicsQuality.High;
|
||||
}
|
||||
else {
|
||||
this.settings.browser.graphicsQuality = GraphicsQuality.Low;
|
||||
}
|
||||
this.settings.saveBrowserSettings();
|
||||
}
|
||||
}
|
||||
@@ -1198,10 +1232,8 @@ export class PonyTownGame implements Game {
|
||||
this.lastDraw = now;
|
||||
|
||||
// draw
|
||||
const {
|
||||
gl, frameBuffer, frameBufferSheet, spriteShader, spriteBatch, lightShader, paletteBatch, paletteShader,
|
||||
palettes,
|
||||
} = this.webgl;
|
||||
const { gl, frameBuffer, frameBuffer2, spriteBatch, paletteBatch, palettes, failedFBO,
|
||||
mergeShader, paletteShader, spriteShader, spriteShaderWithColor, lightShader } = this.webgl;
|
||||
|
||||
TIMING && timeStart('draw');
|
||||
|
||||
@@ -1246,7 +1278,7 @@ export class PonyTownGame implements Game {
|
||||
drawOptions.engine = this.engine;
|
||||
}
|
||||
|
||||
ortho(this.fboMatrix, 0, gl.drawingBufferWidth / actualScale, gl.drawingBufferHeight / actualScale, 0, 0, 1000);
|
||||
ortho(this.fboMatrix, 0, gl.drawingBufferWidth / actualScale, gl.drawingBufferHeight / actualScale, 0, 0, 1000, false);
|
||||
TIMING && timeEnd();
|
||||
|
||||
TIMING && timeStart('ensureAllVisiblePon...');
|
||||
@@ -1263,132 +1295,134 @@ export class PonyTownGame implements Game {
|
||||
lerpColor(light, white, 0.3);
|
||||
}
|
||||
|
||||
if (this.engine === Engine.NewLighting) {
|
||||
// ...
|
||||
} else if (this.disableLighting) {
|
||||
ortho(this.viewMatrix, camera.x, camera.x + camera.w, camera.actualY + camera.h, camera.actualY, 0, 1000);
|
||||
lerpColor(light, white, 0.1); // adjust lighting for missing lights
|
||||
// you'd draw directly onto the screen only when there's no framebuffer
|
||||
// or the graphics is low and framebuffer size matches screen size
|
||||
|
||||
// color -> screen
|
||||
gl.clearColor(bg[0], bg[1], bg[2], bg[3]);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
TIMING && timeStart('initializeFrameBuffers');
|
||||
this.initializeFrameBuffers(this.webgl, width, height, this.settings.browser.graphicsQuality === GraphicsQuality.High);
|
||||
TIMING && timeEnd();
|
||||
|
||||
const useDepthBuffer =
|
||||
!failedFBO && (this.settings.browser.graphicsQuality === GraphicsQuality.High) && !!frameBuffer!.depthStencilRenderbuffer;
|
||||
const useLighting = !failedFBO && (this.settings.browser.graphicsQuality !== GraphicsQuality.Low);
|
||||
const drawSceneDirectlyOntoScreen =
|
||||
failedFBO || (useLighting === false);
|
||||
|
||||
drawOptions.useDepthBuffer = useDepthBuffer;
|
||||
|
||||
let matrixTop = camera.actualY;
|
||||
let matrixBottom = camera.actualY + camera.h;
|
||||
if (!drawSceneDirectlyOntoScreen) {
|
||||
[matrixTop, matrixBottom] = [matrixBottom, matrixTop];
|
||||
}
|
||||
|
||||
ortho(this.viewMatrix, camera.x, camera.x + camera.w, matrixBottom, matrixTop, 0, 1000, false);
|
||||
|
||||
let mapDrawingColor = white;
|
||||
if (!useLighting) {
|
||||
mapDrawingColor = light;
|
||||
lerpColor(mapDrawingColor, white, 0.1); // adjust lighting for missing lights
|
||||
}
|
||||
|
||||
gl.clearColor(bg[0], bg[1], bg[2], bg[3]);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendEquation(gl.FUNC_ADD);
|
||||
gl.depthFunc(gl.ALWAYS);
|
||||
|
||||
if (drawSceneDirectlyOntoScreen) {
|
||||
TIMING && timeStart('color -> screen');
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendEquation(gl.FUNC_ADD);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
this.drawMap(this.webgl, this.map, this.viewMatrix, light, drawOptions);
|
||||
} else {
|
||||
ortho(this.viewMatrix, camera.x, camera.x + camera.w, camera.actualY, camera.actualY + camera.h, 0, 1000);
|
||||
|
||||
TIMING && timeStart('initializeFrameBuffer');
|
||||
this.initializeFrameBuffer(this.webgl, width, height);
|
||||
this.drawMap(this.webgl, this.map, this.viewMatrix, mapDrawingColor, drawOptions);
|
||||
TIMING && timeEnd();
|
||||
}
|
||||
else {
|
||||
// if (!isWebGL2(gl)) {
|
||||
// gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
// gl.clear(gl.COLOR_BUFFER_BIT); // hack, doing this clear fixes black screen issues for some older devices
|
||||
// }
|
||||
|
||||
if (!frameBuffer) {
|
||||
DEVELOPMENT && console.warn('No frame buffer');
|
||||
return;
|
||||
let clearMask = gl.COLOR_BUFFER_BIT;
|
||||
if (useDepthBuffer) {
|
||||
gl.depthMask(true);
|
||||
clearMask |= gl.DEPTH_BUFFER_BIT;
|
||||
}
|
||||
|
||||
// color -> fbo
|
||||
TIMING && timeStart('color -> fbo');
|
||||
// gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer.handle);
|
||||
bindFrameBuffer(gl, frameBuffer);
|
||||
gl.viewport(0, 0, frameBuffer.width, frameBuffer.height);
|
||||
gl.clearColor(bg[0], bg[1], bg[2], bg[3]);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT); // | gl.DEPTH_BUFFER_BIT);
|
||||
TIMING && timeStart('color -> framebuffer');
|
||||
bindFrameBuffer(gl, frameBuffer!);
|
||||
gl.viewport(0, 0, frameBuffer!.width, frameBuffer!.height); // clearing the whole surface is preferable for most GPUs
|
||||
gl.clear(clearMask);
|
||||
gl.viewport(0, 0, width, height);
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
//gl.depthFunc(gl.LEQUAL);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendEquation(gl.FUNC_ADD);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
this.drawMap(this.webgl, this.map, this.viewMatrix, white, drawOptions);
|
||||
this.drawMap(this.webgl, this.map, this.viewMatrix, mapDrawingColor, drawOptions);
|
||||
gl.depthMask(false);
|
||||
TIMING && timeEnd();
|
||||
|
||||
// color -> screen
|
||||
TIMING && timeStart('color -> screen');
|
||||
// gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
unbindFrameBuffer(gl);
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
gl.clearColor(0, 0, 0, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
// gl.disable(gl.DEPTH_TEST);
|
||||
gl.disable(gl.BLEND);
|
||||
if (useLighting) {
|
||||
TIMING && timeStart('light -> fbo');
|
||||
bindFrameBuffer(gl, frameBuffer2!);
|
||||
gl.viewport(0, 0, frameBuffer2!.width, frameBuffer2!.height); // clearing the whole surface is preferable for most GPUs
|
||||
gl.clearColor(light[0], light[1], light[2], light[3]);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.viewport(0, 0, width, height);
|
||||
gl.blendFunc(gl.ONE, gl.ONE);
|
||||
|
||||
gl.useProgram(spriteShader.program);
|
||||
gl.uniformMatrix4fv(spriteShader.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform4fv(spriteShader.uniforms.lighting, white);
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, frameBufferSheet.texture!.width);
|
||||
bindTexture(gl, 0, frameBufferSheet.texture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.drawImage(WHITE, 0, 0, width, height, 0, 0, width, height);
|
||||
spriteBatch.end();
|
||||
TIMING && timeEnd();
|
||||
gl.useProgram(lightShader.program);
|
||||
gl.uniformMatrix4fv(lightShader.uniforms.transform, false, this.viewMatrix);
|
||||
gl.uniform4fv(lightShader.uniforms.lighting, white);
|
||||
spriteBatch.begin();
|
||||
this.lightEntitiesDrawn = drawEntityLights(spriteBatch, this.map.entitiesLight, this.camera, drawOptions);
|
||||
spriteBatch.end();
|
||||
|
||||
// light -> fbo
|
||||
TIMING && timeStart('light -> fbo');
|
||||
// gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer.handle);
|
||||
bindFrameBuffer(gl, frameBuffer);
|
||||
gl.viewport(0, 0, frameBuffer.width, frameBuffer.height);
|
||||
gl.clearColor(light[0], light[1], light[2], light[3]);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.viewport(0, 0, width, height);
|
||||
//gl.enable(gl.DEPTH_TEST);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendEquation(gl.FUNC_ADD);
|
||||
gl.blendFunc(gl.ONE, gl.ONE);
|
||||
TIMING && timeEnd();
|
||||
if (useDepthBuffer) {
|
||||
gl.depthFunc(gl.GEQUAL);
|
||||
}
|
||||
gl.useProgram(spriteShaderWithColor.program);
|
||||
gl.uniformMatrix4fv(spriteShaderWithColor.uniforms.transform, false, this.viewMatrix);
|
||||
gl.uniform4fv(spriteShaderWithColor.uniforms.lighting, white);
|
||||
gl.uniform2f(spriteShaderWithColor.uniforms.textureSize,
|
||||
normalSpriteSheet.texture!.width, normalSpriteSheet.texture!.height);
|
||||
bindTexture(gl, 0, normalSpriteSheet.texture);
|
||||
spriteBatch.begin();
|
||||
this.lightSpriteEntitiesDrawn = drawEntityLightSprites(spriteBatch, this.map.entitiesLightSprite, this.camera, drawOptions);
|
||||
spriteBatch.end();
|
||||
gl.depthFunc(gl.ALWAYS);
|
||||
if (isWebGL2(gl)) {
|
||||
(gl as WebGL2RenderingContext).invalidateFramebuffer(gl.FRAMEBUFFER, [gl.DEPTH_ATTACHMENT]);
|
||||
}
|
||||
TIMING && timeEnd();
|
||||
|
||||
// shadows
|
||||
//for (const e of map.entities) {
|
||||
// if (e.drawShadow && camera.isBoundVisible(e.shadowBounds || e.bounds, e.x, e.y)) {
|
||||
// this.spriteBatch.depth = camera.mapDepth(e.y);
|
||||
// e.drawShadow(this.spriteBatch);
|
||||
// }
|
||||
//}
|
||||
TIMING && timeStart('color + lights -> screen');
|
||||
unbindFrameBuffer(gl);
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
gl.disable(gl.BLEND);
|
||||
|
||||
// soft lights
|
||||
TIMING && timeStart('drawEntityLights');
|
||||
gl.useProgram(lightShader.program);
|
||||
gl.uniformMatrix4fv(lightShader.uniforms.transform, false, this.viewMatrix);
|
||||
gl.uniform4fv(lightShader.uniforms.lighting, white);
|
||||
spriteBatch.begin();
|
||||
drawEntityLights(spriteBatch, this.map.entitiesLight, this.camera, drawOptions);
|
||||
spriteBatch.end();
|
||||
TIMING && timeEnd();
|
||||
gl.useProgram(mergeShader.program);
|
||||
gl.uniformMatrix4fv(mergeShader.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform2f(mergeShader.uniforms.textureSize, frameBuffer!.colorTexture.width, frameBuffer!.colorTexture.height);
|
||||
bindTexture(gl, 0, frameBuffer!.colorTexture);
|
||||
bindTexture(gl, 1, frameBuffer2!.colorTexture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.drawImage(WHITE, 0, 0, width, height, 0, 0, width, height);
|
||||
spriteBatch.end();
|
||||
TIMING && timeEnd();
|
||||
}
|
||||
else {
|
||||
TIMING && timeStart('color framebuffer -> screen');
|
||||
unbindFrameBuffer(gl);
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
gl.disable(gl.BLEND);
|
||||
|
||||
// light sprites
|
||||
TIMING && timeStart('drawEntityLightSprites');
|
||||
gl.useProgram(spriteShader.program);
|
||||
gl.uniformMatrix4fv(spriteShader.uniforms.transform, false, this.viewMatrix);
|
||||
gl.uniform4fv(spriteShader.uniforms.lighting, white);
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, normalSpriteSheet.texture!.width);
|
||||
bindTexture(gl, 0, normalSpriteSheet.texture);
|
||||
spriteBatch.begin();
|
||||
drawEntityLightSprites(spriteBatch, this.map.entitiesLightSprite, this.camera, drawOptions);
|
||||
spriteBatch.end();
|
||||
TIMING && timeEnd();
|
||||
|
||||
// light -> screen
|
||||
TIMING && timeStart('light -> screen');
|
||||
// gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
unbindFrameBuffer(gl);
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendEquation(gl.FUNC_ADD);
|
||||
gl.blendFunc(gl.DST_COLOR, gl.ZERO);
|
||||
|
||||
gl.useProgram(spriteShader.program);
|
||||
gl.uniformMatrix4fv(spriteShader.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform4fv(spriteShader.uniforms.lighting, white);
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, frameBufferSheet.texture!.width);
|
||||
bindTexture(gl, 0, frameBufferSheet.texture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.drawImage(WHITE, 0, 0, width, height, 0, 0, width, height);
|
||||
spriteBatch.end();
|
||||
TIMING && timeEnd();
|
||||
gl.useProgram(spriteShader.program);
|
||||
gl.uniformMatrix4fv(spriteShader.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform4fv(spriteShader.uniforms.lighting, white);
|
||||
gl.uniform2f(spriteShader.uniforms.textureSize, frameBuffer!.colorTexture.width, frameBuffer!.colorTexture.height);
|
||||
bindTexture(gl, 0, frameBuffer!.colorTexture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.drawImage(WHITE, 0, 0, width, height, 0, 0, width, height);
|
||||
spriteBatch.end();
|
||||
TIMING && timeEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ui -> screen
|
||||
@@ -1401,7 +1435,7 @@ export class PonyTownGame implements Game {
|
||||
gl.uniformMatrix4fv(paletteShader.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform4fv(paletteShader.uniforms.lighting, white);
|
||||
gl.uniform1f(paletteShader.uniforms.pixelSize, this.paletteManager.pixelSize);
|
||||
gl.uniform1f(paletteShader.uniforms.textureSize, paletteSpriteSheet.texture!.width);
|
||||
gl.uniform1f(paletteShader.uniforms.textureSize, 1.0 / paletteSpriteSheet.texture!.width);
|
||||
bindTexture(gl, 0, paletteSpriteSheet.texture);
|
||||
bindTexture(gl, 1, this.paletteManager.texture);
|
||||
paletteBatch.begin();
|
||||
@@ -1467,12 +1501,13 @@ export class PonyTownGame implements Game {
|
||||
paletteBatch.end();
|
||||
TIMING && timeEnd();
|
||||
|
||||
gl.useProgram(spriteShader.program);
|
||||
gl.uniformMatrix4fv(spriteShader.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform4fv(spriteShader.uniforms.lighting, white);
|
||||
gl.useProgram(spriteShaderWithColor.program);
|
||||
gl.uniformMatrix4fv(spriteShaderWithColor.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform4fv(spriteShaderWithColor.uniforms.lighting, white);
|
||||
|
||||
if (BETA && this.showMinimap && this.minimap) {
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, normalSpriteSheet.texture!.width);
|
||||
gl.uniform2f(spriteShaderWithColor.uniforms.textureSize,
|
||||
normalSpriteSheet.texture!.width, normalSpriteSheet.texture!.height);
|
||||
bindTexture(gl, 0, normalSpriteSheet.texture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.save();
|
||||
@@ -1500,7 +1535,8 @@ export class PonyTownGame implements Game {
|
||||
}
|
||||
|
||||
if (BETA && this.debug.showRegions && this.player) {
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, normalSpriteSheet.texture!.width);
|
||||
gl.uniform2f(spriteShaderWithColor.uniforms.textureSize,
|
||||
normalSpriteSheet.texture!.width, normalSpriteSheet.texture!.height);
|
||||
bindTexture(gl, 0, normalSpriteSheet.texture);
|
||||
spriteBatch.begin();
|
||||
drawDebugRegions(spriteBatch, this.map, this.player, this.camera);
|
||||
@@ -1510,22 +1546,23 @@ export class PonyTownGame implements Game {
|
||||
const showFPS = !!this.settings.browser.showFps;
|
||||
const showHelp = BETA && this.input.isPressed(Key.F1);
|
||||
const showPalette = DEVELOPMENT && this.debug.showPalette;
|
||||
const showAdditionalStats = false;
|
||||
|
||||
if (showFPS || showHelp || showPalette) {
|
||||
if (showFPS || showHelp || showPalette || showAdditionalStats) {
|
||||
// 1 to 1 pixel scale drawing
|
||||
TIMING && timeStart('showFps');
|
||||
const scale = 2;
|
||||
// const height = gl.drawingBufferHeight / (ratio * scale);
|
||||
ortho(this.fboMatrix, 0, gl.drawingBufferWidth / ratio, gl.drawingBufferHeight / ratio, 0, 0, 1000);
|
||||
gl.uniformMatrix4fv(spriteShader.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, normalSpriteSheet.texture!.width);
|
||||
ortho(this.fboMatrix, 0, gl.drawingBufferWidth / ratio, gl.drawingBufferHeight / ratio, 0, 0, 1000, false);
|
||||
gl.uniformMatrix4fv(spriteShaderWithColor.uniforms.transform, false, this.fboMatrix);
|
||||
gl.uniform2f(spriteShaderWithColor.uniforms.textureSize,
|
||||
normalSpriteSheet.texture!.width, normalSpriteSheet.texture!.height);
|
||||
bindTexture(gl, 0, normalSpriteSheet.texture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.save();
|
||||
spriteBatch.scale(scale, scale);
|
||||
|
||||
if (showFPS) {
|
||||
drawText(spriteBatch, this.drawFps.toFixed(), fontSmall, BLACK, 2, 2);
|
||||
drawOutlinedText(spriteBatch, this.drawFps.toFixed(), fontSmall, WHITE, BLACK, 2, 2);
|
||||
|
||||
if (this.timingsText) {
|
||||
const size = measureText(this.timingsText, fontMono);
|
||||
@@ -1543,19 +1580,18 @@ export class PonyTownGame implements Game {
|
||||
}
|
||||
}
|
||||
|
||||
// if (DEVELOPMENT) {
|
||||
// const width = gl.drawingBufferWidth / (ratio * scale);
|
||||
// const { isCollidingCount, isCollidingObjectCount } = getCollisionStats();
|
||||
// const text =
|
||||
// `${isCollidingCount.toString().padStart(7)} calls\n` +
|
||||
// `${isCollidingObjectCount.toString().padStart(7)} total checks\n` +
|
||||
// `${this.markedColliding.toString().padStart(7)} player checks`;
|
||||
// const size = measureText(text, fontMono);
|
||||
// const x = width - 160;
|
||||
// const y = 26;
|
||||
// spriteBatch.drawRect(0x000000aa, x, y, 150, size.h + 10);
|
||||
// drawText(spriteBatch, text, fontMono, WHITE, x + 5, y + 5);
|
||||
// }
|
||||
if (showAdditionalStats) {
|
||||
const width = gl.drawingBufferWidth / (ratio * scale);
|
||||
const text =
|
||||
`entities ${this.entitiesDrawn}/${this.map.entitiesDrawable.length}\n` +
|
||||
`light enitities ${this.lightEntitiesDrawn}/${this.map.entitiesLight.length}\n` +
|
||||
`light sprite entities ${this.lightSpriteEntitiesDrawn}/${this.map.entitiesLightSprite.length}`;
|
||||
const size = measureText(text, fontMono);
|
||||
const x = width - Math.max(160, size.w);
|
||||
const y = 26;
|
||||
spriteBatch.drawRect(0x000000aa, x, y, 150, size.h + 10);
|
||||
drawText(spriteBatch, text, fontMono, WHITE, x + 5, y + 5);
|
||||
}
|
||||
|
||||
spriteBatch.restore();
|
||||
spriteBatch.end();
|
||||
@@ -1564,13 +1600,14 @@ export class PonyTownGame implements Game {
|
||||
const paletteTexture = this.paletteManager.texture!;
|
||||
const { width, height } = paletteTexture;
|
||||
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, normalSpriteSheet.texture!.width);
|
||||
gl.uniform2f(spriteShaderWithColor.uniforms.textureSize,
|
||||
normalSpriteSheet.texture!.width, normalSpriteSheet.texture!.height);
|
||||
bindTexture(gl, 0, normalSpriteSheet.texture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.drawRect(0x00000066, 20, 20, width, height);
|
||||
spriteBatch.end();
|
||||
|
||||
gl.uniform1f(spriteShader.uniforms.textureSize, width);
|
||||
gl.uniform2f(spriteShaderWithColor.uniforms.textureSize, width, height);
|
||||
bindTexture(gl, 0, paletteTexture);
|
||||
spriteBatch.begin();
|
||||
spriteBatch.drawImage(WHITE, 0, 0, width, height, 20, 20, width, height);
|
||||
@@ -1601,15 +1638,17 @@ export class PonyTownGame implements Game {
|
||||
drawFullScreenMessage(paletteBatch, this.camera, message, palettes.mainFont.white);
|
||||
}
|
||||
private drawMap(webgl: WebGL, map: WorldMap, viewMatrix: Matrix4, lighting: Float32Array, options: DrawOptions) {
|
||||
const { gl, paletteBatch, paletteShader } = webgl;
|
||||
const { gl, paletteBatch, paletteShader, paletteShaderWithDepth } = webgl;
|
||||
|
||||
const mapPaletteShader = options.useDepthBuffer ? paletteShaderWithDepth : paletteShader;
|
||||
|
||||
TIMING && timeStart('drawMap');
|
||||
if (this.tileSets && this.player) {
|
||||
gl.useProgram(paletteShader.program);
|
||||
gl.uniformMatrix4fv(paletteShader.uniforms.transform, false, viewMatrix);
|
||||
gl.uniform4fv(paletteShader.uniforms.lighting, lighting);
|
||||
gl.uniform1f(paletteShader.uniforms.pixelSize, this.paletteManager.pixelSize);
|
||||
gl.uniform1f(paletteShader.uniforms.textureSize, paletteSpriteSheet.texture!.width);
|
||||
gl.useProgram(mapPaletteShader.program);
|
||||
gl.uniformMatrix4fv(mapPaletteShader.uniforms.transform, false, viewMatrix);
|
||||
gl.uniform4fv(mapPaletteShader.uniforms.lighting, lighting);
|
||||
gl.uniform1f(mapPaletteShader.uniforms.pixelSize, this.paletteManager.pixelSize);
|
||||
gl.uniform1f(mapPaletteShader.uniforms.textureSize, 1.0 / paletteSpriteSheet.texture!.width);
|
||||
bindTexture(gl, 0, paletteSpriteSheet.texture);
|
||||
bindTexture(gl, 1, this.paletteManager.texture);
|
||||
paletteBatch.begin();
|
||||
@@ -1618,7 +1657,7 @@ export class PonyTownGame implements Game {
|
||||
paletteBatch.end();
|
||||
|
||||
if (this.highlightEntity && this.highlightEntity.draw) {
|
||||
gl.uniform4fv(paletteShader.uniforms.lighting, highlightColor);
|
||||
gl.uniform4fv(mapPaletteShader.uniforms.lighting, highlightColor);
|
||||
paletteBatch.begin();
|
||||
this.highlightEntity.draw(paletteBatch, this.drawOptions);
|
||||
paletteBatch.end();
|
||||
@@ -1680,16 +1719,29 @@ export class PonyTownGame implements Game {
|
||||
DEVELOPMENT && log(`scrollY: ${window.scrollY}`);
|
||||
}
|
||||
}
|
||||
private initializeFrameBuffer({ gl, frameBuffer }: WebGL, width: number, height: number) {
|
||||
const targetSize = getRenderTargetSize(width, height);
|
||||
private initializeFrameBuffers(
|
||||
{ gl, frameBuffer, frameBuffer2 }: WebGL, width: number, height: number, useDepthBuffer: boolean
|
||||
) {
|
||||
if (!frameBuffer || !frameBuffer2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frameBuffer && targetSize !== frameBuffer.width) {
|
||||
const hasDepth = frameBuffer.depthStencilRenderbuffer !== null;
|
||||
const isSizeChanged = width !== frameBuffer.width || height !== frameBuffer.height;
|
||||
const isDepthChanged = hasDepth !== useDepthBuffer;
|
||||
|
||||
if (isSizeChanged || isDepthChanged) {
|
||||
const maxSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
|
||||
const isSizeTooBig = maxSize != null && (width > maxSize || height > maxSize);
|
||||
|
||||
if (maxSize != null && targetSize > maxSize) {
|
||||
this.setScale(this.scale + 1);
|
||||
if (isSizeTooBig) {
|
||||
this.setScale(this.scale + 1); // should not happen, useDepthBuffer is also ignored if it does
|
||||
DEVELOPMENT && console.warn('Cannot resize framebuffer');
|
||||
} else {
|
||||
resizeFrameBuffer(gl, frameBuffer, targetSize, targetSize);
|
||||
disposeFrameBuffer(gl, frameBuffer);
|
||||
disposeFrameBuffer(gl, frameBuffer2);
|
||||
createFrameBuffer(gl, frameBuffer, width, height, false, useDepthBuffer, null);
|
||||
createFrameBuffer(gl, frameBuffer2, width, height, false, false, frameBuffer.depthStencilRenderbuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1742,7 +1794,7 @@ export class PonyTownGame implements Game {
|
||||
let value = '';
|
||||
|
||||
if (this.settings.browser.showStats) {
|
||||
const tris = spriteBatch.tris + paletteBatch.tris;
|
||||
const tris = spriteBatch.drawnTrisStats + paletteBatch.drawnTrisStats;
|
||||
const flush = paletteBatch.flushes;
|
||||
const sent = this.sent.toFixed();
|
||||
const recv = this.recv.toFixed();
|
||||
@@ -1772,9 +1824,9 @@ export class PonyTownGame implements Game {
|
||||
|
||||
TIMING && timeReset();
|
||||
|
||||
spriteBatch!.tris = 0;
|
||||
spriteBatch!.drawnTrisStats = 0;
|
||||
spriteBatch!.flushes = 0;
|
||||
paletteBatch!.tris = 0;
|
||||
paletteBatch!.drawnTrisStats = 0;
|
||||
paletteBatch!.flushes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,6 +522,12 @@ export const excite = createHeadAnimation('excite', 8, false, [
|
||||
...repeat(10, [0, 0, 1, 1, 5]),
|
||||
]);
|
||||
|
||||
export const excite_meno = createHeadAnimation('excite_meno', 8, false, [
|
||||
...repeat(2, [0, 0, 2, 2, 0]),
|
||||
[0, 1, 6, 6, 0],
|
||||
...repeat(10, [0, 0, 1, 1, 5])
|
||||
]);
|
||||
|
||||
export const surpriseSad = createHeadAnimation('surpriseSad', 8, false, [
|
||||
[0, 1, 15, 15, 8],
|
||||
...repeat(8, [0, 0, 15, 15, 8]),
|
||||
@@ -534,6 +540,24 @@ export const sneeze = createHeadAnimation('sneeze', 12, false, [
|
||||
...repeat(4, [0, 0, 18, 18, 7]),
|
||||
]);
|
||||
|
||||
export const happy_tongue = createHeadAnimation('happy_tongue', 12, false, [
|
||||
...repeat(3, [0, 0, 1, 1, 0]),
|
||||
[0, 1, 6, 6, 0],
|
||||
...repeat(8, [0, 0, 14, 14, 4]),
|
||||
]);
|
||||
|
||||
export const happy_tongue_meno = createHeadAnimation('happy_tongue_meno', 12, false, [
|
||||
...repeat(3, [0, 0, 2, 2, 0]),
|
||||
[0, 1, 6, 6, 0],
|
||||
...repeat(8, [0, 0, 12, 12, 4]),
|
||||
]);
|
||||
|
||||
export const happy_tongue_meno_2 = createHeadAnimation('happy_tongue_meno_2', 12, false, [
|
||||
...repeat(3, [0, 0, 2, 2, 0]),
|
||||
[0, 1, 6, 6, 0],
|
||||
...repeat(8, [0, 0, 11, 11, 4]),
|
||||
]);
|
||||
|
||||
export const headAnimations = [
|
||||
smile, nom, laugh, yawn, surprise, surpriseSad, sneeze, excite,
|
||||
];
|
||||
|
||||
@@ -214,13 +214,14 @@ export function initializeToys(paletteManager: PaletteManager) {
|
||||
}
|
||||
}
|
||||
|
||||
const zeroPoint = point(0, 0);
|
||||
// const zeroPoint = point(0, 0);
|
||||
const wakes = [
|
||||
{ ox: 21, oy: 60, behind: sprites.pony_wake_4, front: sprites.pony_wake_3 },
|
||||
{ ox: 24, oy: 60, behind: sprites.pony_wake_6, front: sprites.pony_wake_5 },
|
||||
{ ox: 18, oy: 51, behind: sprites.pony_wake_2, front: sprites.pony_wake_1 },
|
||||
];
|
||||
|
||||
// swimming effects of different size; add value per tail
|
||||
const wakeIndices = [0, 2, 1, 0, 2, 2, 2, 0, 2, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1];
|
||||
|
||||
function getWakeIndex(info: Info) {
|
||||
@@ -292,9 +293,10 @@ export function drawPony(batch: Batch, info: Info, state: State, ponyX: number,
|
||||
let hatOffset = at(HEAD_ACCESSORY_OFFSETS, info.mane ? info.mane.type : 0)!;
|
||||
const noMane = !info.mane || hasNoMane(info.mane.type);
|
||||
|
||||
if (info.headAccessory !== undefined && info.headAccessory.type === 20) {
|
||||
hatOffset = zeroPoint;
|
||||
}
|
||||
// temp front layer hat test
|
||||
// if (info.headAccessory !== undefined && info.headAccessory.type === 20) {
|
||||
// hatOffset = zeroPoint;
|
||||
//}
|
||||
|
||||
if (draw(options, NoDraw.Behind)) {
|
||||
// far wing
|
||||
@@ -639,13 +641,14 @@ export function drawHead(
|
||||
drawSet(batch, sprites.facialHair, info.facialHair, x, y, WHITE);
|
||||
}
|
||||
|
||||
const skipTopAndFrontMane = info.headAccessory !== undefined && info.headAccessory.type === 20;
|
||||
// temp front layer hat test
|
||||
// const skipTopAndFrontMane = info.headAccessory !== undefined && info.headAccessory.type === 20;
|
||||
|
||||
if (draw(options, NoDraw.FrontMane)) {
|
||||
drawSet(batch, sprites.backFrontManes, info.backMane, x, y + maneOffsetY, WHITE);
|
||||
}
|
||||
|
||||
if (draw(options, NoDraw.TopMane) && !skipTopAndFrontMane) {
|
||||
if (draw(options, NoDraw.TopMane) /* && !skipTopAndFrontMane*/ ) {
|
||||
drawSet(batch, sprites.topManes, info.mane, x, y, WHITE);
|
||||
}
|
||||
|
||||
@@ -682,7 +685,7 @@ export function drawHead(
|
||||
drawSet(batch, sprites.earAccessories, info.earAccessory, x + earAccessoryOffset.x, y + earAccessoryOffset.y, WHITE);
|
||||
}
|
||||
|
||||
if (draw(options, NoDraw.FrontMane) && !skipTopAndFrontMane) {
|
||||
if (draw(options, NoDraw.FrontMane) /* && !skipTopAndFrontMane */) {
|
||||
drawSet(batch, sprites.frontManes, info.mane, x, y + maneOffsetY, WHITE);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ export const tails = createCompleteSets(sprites.tails, 3);
|
||||
export const chest = createCompleteSets(sprites.chestAccessories, bodyFrames);
|
||||
export const chestBehind = createCompleteSets(sprites.chestAccessoriesBehind, bodyFrames);
|
||||
export const backAccessories = createCompleteSets(sprites.backAccessories, bodyFrames);
|
||||
sprites.neckAccessories.forEach(f => f && f.pop()); // TEMP: remove headphones
|
||||
export const neckAccessories = createCompleteSets(sprites.neckAccessories, bodyFrames);
|
||||
export const waistAccessories = createCompleteSets(sprites.waistAccessories, bodyFrames + 1);
|
||||
|
||||
@@ -145,10 +144,6 @@ function mergeSpriteSets(...sets: ColorExtraSets[]): ColorExtraSets {
|
||||
export const backLegSleeves: Sets = sprites.backLegSleeves
|
||||
.map(sets => sets && [undefined, undefined, undefined, undefined, undefined, ...sets]);
|
||||
|
||||
// TEMP: remove summer hat
|
||||
sprites.headAccessoriesBehind.pop();
|
||||
sprites.headAccessories.pop();
|
||||
|
||||
export const mergedManes = mergeSpriteSets(sprites.behindManes, sprites.topManes, sprites.frontManes)!;
|
||||
export const mergedBackManes = mergeSpriteSets(sprites.backBehindManes, sprites.backFrontManes)!;
|
||||
export const mergedFacialHair = mergeSpriteSets(sprites.facialHairBehind, sprites.facialHair)!;
|
||||
|
||||
+57
-54
@@ -1,35 +1,41 @@
|
||||
import { spriteShader, paletteLayersShader, lightShader } from '../generated/shaders';
|
||||
import { FrameBuffer, createFrameBuffer, disposeFrameBuffer } from '../graphics/webgl/frameBuffer';
|
||||
import { SpriteSheet, CommonPalettes, PaletteManager, Camera } from '../common/interfaces';
|
||||
import { Shader, createShader, disposeShader } from '../graphics/webgl/shader';
|
||||
import { PaletteSpriteBatch, PALETTE_BATCH_BYTES_PER_VERTEX } from '../graphics/paletteSpriteBatch';
|
||||
import { getWebGLContext, getRenderTargetSize, unbindAllTexturesAndBuffers } from '../graphics/webgl/webglUtils';
|
||||
import { mergeShader, spriteShader, paletteLayersShader, lightShader } from '../generated/shaders';
|
||||
import { FrameBuffer, disposeFrameBuffer, createFrameBuffer } from '../graphics/webgl/frameBuffer';
|
||||
import { CommonPalettes, PaletteManager, Camera } from '../common/interfaces';
|
||||
import { Shader, ShaderProgramData, disposeShaderProgramData } from '../graphics/webgl/shader';
|
||||
import { PaletteSpriteBatch } from '../graphics/paletteSpriteBatch';
|
||||
import { getWebGLContext, unbindAllTexturesAndBuffers } from '../graphics/webgl/webglUtils';
|
||||
import { createCommonPalettes } from '../graphics/graphicsUtils';
|
||||
import { createTexturesForSpriteSheets, disposeTexturesForSpriteSheets } from '../graphics/spriteSheetUtils';
|
||||
import * as sprites from '../generated/sprites';
|
||||
import { SpriteBatch } from '../graphics/spriteBatch';
|
||||
import { BATCH_SIZE_MAX } from '../common/constants';
|
||||
import { BATCH_VERTEX_CAPACITY_MAX } from '../common/constants';
|
||||
|
||||
export interface WebGL {
|
||||
gl: WebGLRenderingContext;
|
||||
frameBuffer: FrameBuffer | undefined;
|
||||
frameBufferSheet: SpriteSheet;
|
||||
spriteShader: Shader;
|
||||
lightShader: Shader;
|
||||
paletteShader: Shader;
|
||||
frameBuffer?: FrameBuffer;
|
||||
frameBuffer2?: FrameBuffer;
|
||||
mergeShader: ShaderProgramData;
|
||||
paletteShader: ShaderProgramData;
|
||||
paletteShaderWithDepth: ShaderProgramData;
|
||||
spriteShader: ShaderProgramData;
|
||||
spriteShaderWithColor: ShaderProgramData;
|
||||
lightShader: ShaderProgramData;
|
||||
spriteBatch: SpriteBatch;
|
||||
paletteBatch: PaletteSpriteBatch;
|
||||
palettes: CommonPalettes;
|
||||
failedFBO: boolean;
|
||||
failedDepthBuffer: boolean;
|
||||
renderer: string;
|
||||
indexBuffer: WebGLBuffer;
|
||||
}
|
||||
|
||||
const mergeShaderSource = mergeShader;
|
||||
const spriteShaderSource = spriteShader;
|
||||
const paletteShaderSource = paletteLayersShader;
|
||||
const lightShaderSource = lightShader;
|
||||
|
||||
function createIndices(capacity: number) {
|
||||
const numIndices = (capacity * 6) | 0;
|
||||
function createIndices(vertexCount: number) {
|
||||
const numIndices = (vertexCount * 6 / 4) | 0;
|
||||
const indices = new Uint16Array(numIndices);
|
||||
|
||||
for (let i = 0, j = 0; i < numIndices; j = (j + 4) | 0) {
|
||||
@@ -52,62 +58,48 @@ export function initWebGL(canvas: HTMLCanvasElement, paletteManager: PaletteMana
|
||||
export function initWebGLResources(gl: WebGLRenderingContext, paletteManager: PaletteManager, camera: Camera): WebGL {
|
||||
let renderer = '';
|
||||
let failedFBO = false;
|
||||
let frameBuffer: FrameBuffer | undefined;
|
||||
let frameBufferSheet: SpriteSheet = { texture: undefined, sprites: [], palette: false };
|
||||
let failedDepthBuffer = false;
|
||||
let frameBuffer = {} as FrameBuffer;
|
||||
let frameBuffer2 = {} as FrameBuffer;
|
||||
|
||||
gl.enable(gl.DEPTH_TEST); // no reason to not have it enabled at all times, depth reads/writes are controlled by other parameters
|
||||
gl.disable(gl.DITHER);
|
||||
|
||||
try {
|
||||
const size = getRenderTargetSize(camera.w, camera.h);
|
||||
frameBuffer = createFrameBuffer(gl, size, size);
|
||||
frameBufferSheet.texture = frameBuffer.texture;
|
||||
createFrameBuffer(gl, frameBuffer, camera.w, camera.h, false, true, null);
|
||||
createFrameBuffer(gl, frameBuffer2, camera.w, camera.h, true, false, frameBuffer.depthStencilRenderbuffer);
|
||||
} catch (e) {
|
||||
DEVELOPMENT && console.warn(e);
|
||||
failedFBO = true;
|
||||
failedDepthBuffer = true;
|
||||
}
|
||||
|
||||
if (!failedFBO) {
|
||||
failedDepthBuffer = frameBuffer!.depthStencilRenderbuffer === null;
|
||||
}
|
||||
|
||||
createTexturesForSpriteSheets(gl, sprites.spriteSheets);
|
||||
const palettes = createCommonPalettes(paletteManager);
|
||||
|
||||
const paletteShader = createShader(gl, paletteShaderSource);
|
||||
const spriteShader = createShader(gl, spriteShaderSource);
|
||||
const lightShader = createShader(gl, lightShaderSource);
|
||||
|
||||
const VERTICES_PER_SPRITE = 4;
|
||||
const buffer = new ArrayBuffer(BATCH_SIZE_MAX * VERTICES_PER_SPRITE * PALETTE_BATCH_BYTES_PER_VERTEX);
|
||||
const vertexBuffer = gl.createBuffer();
|
||||
|
||||
if (!vertexBuffer) {
|
||||
throw new Error(`Failed to allocate vertex buffer`);
|
||||
}
|
||||
const mergeShaderRaw = new Shader(mergeShaderSource);
|
||||
const paletteShaderRaw = new Shader(paletteShaderSource);
|
||||
const spriteShaderRaw = new Shader(spriteShaderSource);
|
||||
const lightShaderRaw = new Shader(lightShaderSource);
|
||||
|
||||
const indexBuffer = gl.createBuffer();
|
||||
|
||||
if (!indexBuffer) {
|
||||
throw new Error(`Failed to allocate index buffer`);
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, createIndices(BATCH_SIZE_MAX), gl.STATIC_DRAW);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, createIndices(BATCH_VERTEX_CAPACITY_MAX), gl.STATIC_DRAW);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
||||
|
||||
const vertexBuffer2 = gl.createBuffer();
|
||||
|
||||
if (!vertexBuffer2) {
|
||||
throw new Error(`Failed to allocate vertex buffer (2)`);
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer2);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
|
||||
|
||||
const spriteBatch = new SpriteBatch(gl, BATCH_SIZE_MAX, buffer, vertexBuffer2, indexBuffer);
|
||||
const paletteBatch = new PaletteSpriteBatch(gl, BATCH_SIZE_MAX, buffer, vertexBuffer, indexBuffer);
|
||||
const spriteBatch = new SpriteBatch(gl, BATCH_VERTEX_CAPACITY_MAX, indexBuffer);
|
||||
const paletteBatch = new PaletteSpriteBatch(gl, BATCH_VERTEX_CAPACITY_MAX, indexBuffer);
|
||||
spriteBatch.rectSprite = sprites.pixel;
|
||||
paletteBatch.rectSprite = sprites.pixel2;
|
||||
paletteBatch.defaultPalette = palettes.defaultPalette;
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
||||
|
||||
paletteManager.init(gl);
|
||||
|
||||
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
||||
@@ -116,9 +108,17 @@ export function initWebGLResources(gl: WebGLRenderingContext, paletteManager: Pa
|
||||
renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
||||
}
|
||||
|
||||
const mergeShader = mergeShaderRaw.compile(gl, []);
|
||||
const paletteShader = paletteShaderRaw.compile(gl, []);
|
||||
const paletteShaderWithDepth = paletteShaderRaw.compile(gl, ['DEPTH_BUFFERED']);
|
||||
const spriteShader = spriteShaderRaw.compile(gl, []);
|
||||
const spriteShaderWithColor = spriteShaderRaw.compile(gl, ['USE_COLOR']);
|
||||
const lightShader = lightShaderRaw.compile(gl, []);
|
||||
|
||||
return {
|
||||
gl, paletteShader, spriteShader, lightShader, spriteBatch, paletteBatch,
|
||||
frameBuffer, frameBufferSheet, palettes, failedFBO, renderer,
|
||||
gl, mergeShader: mergeShader, paletteShader: paletteShader, paletteShaderWithDepth: paletteShaderWithDepth,
|
||||
spriteShader: spriteShader, spriteShaderWithColor: spriteShaderWithColor, lightShader: lightShader,
|
||||
spriteBatch, paletteBatch, frameBuffer, frameBuffer2, palettes, failedFBO, failedDepthBuffer, renderer, indexBuffer
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,9 +128,12 @@ export function disposeWebGL(webgl: WebGL) {
|
||||
unbindAllTexturesAndBuffers(gl);
|
||||
disposeTexturesForSpriteSheets(gl, sprites.spriteSheets);
|
||||
disposeFrameBuffer(gl, webgl.frameBuffer);
|
||||
disposeShader(gl, webgl.lightShader);
|
||||
disposeShader(gl, webgl.spriteShader);
|
||||
disposeShader(gl, webgl.paletteShader);
|
||||
disposeShaderProgramData(gl, webgl.paletteShader);
|
||||
disposeShaderProgramData(gl, webgl.paletteShaderWithDepth);
|
||||
disposeShaderProgramData(gl, webgl.spriteShader);
|
||||
disposeShaderProgramData(gl, webgl.spriteShaderWithColor);
|
||||
disposeShaderProgramData(gl, webgl.lightShader);
|
||||
webgl.spriteBatch.dispose();
|
||||
webgl.paletteBatch.dispose();
|
||||
gl.deleteBuffer(webgl.indexBuffer);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '../client/ponyUtils';
|
||||
import { CM_SIZE } from './constants';
|
||||
|
||||
export const VERSION = 5; // old manes version is 3
|
||||
export const VERSION = 5; // previous: 3
|
||||
|
||||
const VERSION_BITS = 6; // max 63
|
||||
const COLORS_LENGTH_BITS = 10; // max 1024
|
||||
|
||||
@@ -11,10 +11,12 @@ export const WEEK = DAY * 7;
|
||||
export const MONTH = DAY * 30;
|
||||
export const YEAR = DAY * 365;
|
||||
|
||||
export const BATCH_SIZE_MAX = 10000;
|
||||
export const BATCH_VERTEX_CAPACITY_MAX = 16384;
|
||||
|
||||
export const MAX_VELOCITY = 16; // do not change
|
||||
|
||||
export const NEW_ACCOUNT_PONY_NAME = 'Pony';
|
||||
|
||||
export const PONY_TYPE = 1;
|
||||
export const PONY_SPEED_TROT = 4; // tiles per sec
|
||||
export const PONY_SPEED_WALK = 2; // tiles per sec
|
||||
@@ -37,6 +39,8 @@ export const MAP_SWITCH_DELAY = 1 * SECOND;
|
||||
export const MAP_SWITCHES_PER_UPDATE = 1;
|
||||
export const JOINS_PER_UPDATE = 1;
|
||||
|
||||
export const LIGHT_VOLUME_SCALE = 0.81;
|
||||
|
||||
export const DEFAULT_CHATLOG_OPACITY = 35;
|
||||
export const MAX_CHATLOG_RANGE = 13;
|
||||
export const MIN_CHATLOG_RANGE = 2;
|
||||
@@ -184,7 +188,7 @@ export const PAST_SUPPORTER_REWARDS = [
|
||||
export const GENERAL_RULES = [
|
||||
`Be kind to others`,
|
||||
`Don't spam`,
|
||||
`Don't use multiple accounts`,
|
||||
`Don't create multiple accounts`,
|
||||
`Don't modify the game with hacks or scripts`,
|
||||
`Don't encourage behaviour violating the rules`,
|
||||
`Violation of the rules may result in temporary or permanent ban`,
|
||||
|
||||
@@ -22,7 +22,7 @@ import { mockPaletteManager } from './ponyInfo';
|
||||
const entities: EntityDescriptor[] = [];
|
||||
|
||||
export function createBaseEntity(type: number, id: number, x: number, y: number): Entity {
|
||||
return { id, type, x, y, z: 0, vx: 0, vy: 0, order: 0, state: 0, playerState: 0, flags: 0, timestamp: 0 };
|
||||
return { id, type, x, y, z: 0, vx: 0, vy: 0, depth: 0, order: 0, state: 0, playerState: 0, flags: 0, timestamp: 0 };
|
||||
}
|
||||
|
||||
function createEntity(
|
||||
@@ -154,12 +154,14 @@ function mixOrder(order: number): MixinEntity {
|
||||
|
||||
const collectableInteractive = mixInteract(-8, -12, 16, 16, 1.5);
|
||||
|
||||
// entity with centered sprite and larger clickable area
|
||||
function collectable(name: string, sprite: PaletteRenderable, paletteIndex = 0, ...other: MixinEntity[]) {
|
||||
return doodad(name, sprite, Math.floor(sprite.color!.w / 2), sprite.color!.h - 1, paletteIndex,
|
||||
collectableInteractive,
|
||||
...other);
|
||||
}
|
||||
|
||||
// for ground details. puts origin point on the top of sprite, so you always go on top of it. collectables are able to spawn overlapping decals
|
||||
function decal(name: string, sprite: PaletteRenderable, palette = 0, ...other: MixinEntity[]) {
|
||||
return registerMix(name,
|
||||
mixDraw(sprite, Math.floor((sprite.color!.w + sprite.color!.ox) / 2), sprite.color!.oy, palette),
|
||||
@@ -174,6 +176,7 @@ function decalOffset(name: string, sprite: PaletteRenderable, dx: number, dy: nu
|
||||
...other);
|
||||
}
|
||||
|
||||
// for decorative objects
|
||||
function doodad(
|
||||
name: string, sprite: PaletteRenderable, ox: number, oy: number, palatte = 0, ...other: (MixinEntity | undefined)[]
|
||||
) {
|
||||
@@ -807,11 +810,6 @@ export const rock3B = doodad(n('rock-3b'), sprites.rock_3, 10, 11, 1,
|
||||
mixColliderRounded(-10, -4, 18, 5, 2, false),
|
||||
rockMinimap);
|
||||
|
||||
// other
|
||||
|
||||
export const well = doodad(n('well'), sprites.well, 30, 67, 0,
|
||||
mixColliderRect(-26, -20, 54, 30));
|
||||
|
||||
// water rocks
|
||||
|
||||
const waterRockFPS = WATER_FPS;
|
||||
@@ -1552,7 +1550,7 @@ const cloudSprite = sprites.cloud.shadow;
|
||||
|
||||
export const cloud = registerMix(n('cloud'),
|
||||
mixDrawShadow(sprites.cloud, Math.floor(cloudSprite.w / 2), cloudSprite.h, CLOUD_SHADOW_COLOR),
|
||||
mixFlags(EntityFlags.Decal | EntityFlags.Movable));
|
||||
mixFlags(EntityFlags.StaticY | EntityFlags.Decal | EntityFlags.Movable));
|
||||
|
||||
// vegetation
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ export type Batch = Float32Array;
|
||||
|
||||
export interface SpriteBatchBase {
|
||||
globalAlpha: number;
|
||||
depth: number;
|
||||
crop(x: number, y: number, w: number, h: number): void;
|
||||
clearCrop(): void;
|
||||
save(): void;
|
||||
@@ -105,11 +106,11 @@ export interface SpriteBatchBase {
|
||||
startBatch(): void;
|
||||
finishBatch(): Batch | undefined;
|
||||
releaseBatch(batch: Batch): void;
|
||||
flush(): void;
|
||||
}
|
||||
|
||||
export interface SpriteBatchCommons extends SpriteBatchBase {
|
||||
palette: boolean;
|
||||
depth?: number;
|
||||
drawRect(color: number, x: number, y: number, w: number, h: number): void;
|
||||
}
|
||||
|
||||
@@ -360,6 +361,7 @@ export interface SpriteSheet {
|
||||
texture: Texture2D | undefined;
|
||||
sprites: (Sprite | undefined)[];
|
||||
palette: boolean;
|
||||
isSingleChannel: boolean;
|
||||
}
|
||||
|
||||
export interface SpriteBorder {
|
||||
@@ -541,6 +543,7 @@ export interface Entity extends EntityPart {
|
||||
z: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
depth: number;
|
||||
// frame: number; // last update frame
|
||||
timestamp: number;
|
||||
|
||||
@@ -668,8 +671,15 @@ export interface AccountSettings {
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
export const enum GraphicsQuality {
|
||||
Low,
|
||||
Medium,
|
||||
High
|
||||
}
|
||||
|
||||
export interface BrowserSettings {
|
||||
lowGraphicsMode?: boolean;
|
||||
graphicsQuality?: GraphicsQuality;
|
||||
chatlogClosed?: boolean;
|
||||
chatlogTab?: string;
|
||||
chatlogWidth?: number;
|
||||
@@ -1641,6 +1651,7 @@ export interface DrawOptions {
|
||||
tileGrid: boolean;
|
||||
engine: Engine;
|
||||
season: Season;
|
||||
useDepthBuffer: boolean;
|
||||
error: (message: string) => void;
|
||||
}
|
||||
|
||||
@@ -1657,6 +1668,7 @@ export const defaultDrawOptions: DrawOptions = {
|
||||
tileGrid: false,
|
||||
engine: Engine.Default,
|
||||
season: Season.Summer,
|
||||
useDepthBuffer: true,
|
||||
error: () => { },
|
||||
};
|
||||
|
||||
|
||||
+13
-17
@@ -9,25 +9,21 @@ export function createMat4(): Matrix4 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function ortho(out: Matrix4, left: number, right: number, bottom: number, top: number, near: number, far: number) {
|
||||
export function ortho(out: Matrix4, left: number, right: number, bottom: number, top: number,
|
||||
near: number, far: number, transformZ: boolean
|
||||
) {
|
||||
const lr = 1 / (left - right);
|
||||
const bt = 1 / (bottom - top);
|
||||
const nf = 1 / (near - far);
|
||||
out[0] = -2 * lr;
|
||||
out[1] = 0;
|
||||
out[2] = 0;
|
||||
out[3] = 0;
|
||||
out[4] = 0;
|
||||
out[5] = -2 * bt;
|
||||
out[6] = 0;
|
||||
out[7] = 0;
|
||||
out[8] = 0;
|
||||
out[9] = 0;
|
||||
out[10] = 2 * nf;
|
||||
out[11] = 0;
|
||||
out[12] = (left + right) * lr;
|
||||
out[13] = (top + bottom) * bt;
|
||||
out[14] = (far + near) * nf;
|
||||
out[15] = 1;
|
||||
out[0] = -2 * lr; out[1] = 0; out[2] = 0; out[3] = 0;
|
||||
out[4] = 0; out[5] = -2 * bt; out[6] = 0; out[7] = 0;
|
||||
out[8] = 0; out[9] = 0; out[10] = 2 * nf; out[11] = 0;
|
||||
out[12] = (left + right) * lr; out[13] = (top + bottom) * bt; out[14] = (far + near) * nf; out[15] = 1;
|
||||
|
||||
if (!transformZ) {
|
||||
out[10] = 1;
|
||||
out[14] = 0;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { at, att, hasFlag, invalidEnum } from './utils';
|
||||
import { WHITE, BLACK, RED } from './colors';
|
||||
import { toScreenX, toWorldX, toWorldY, toScreenYWithZ } from './positionUtils';
|
||||
import { rect, addRects, addRect } from './rect';
|
||||
import { SECOND } from './constants';
|
||||
import { SECOND, LIGHT_VOLUME_SCALE } from './constants';
|
||||
import { mockPaletteManager } from './ponyInfo';
|
||||
import { releasePalette } from '../graphics/paletteManager';
|
||||
|
||||
@@ -679,16 +679,16 @@ export function mixLight(color: number, dx: number, dy: number, w: number, h: nu
|
||||
base.lightScale = 1;
|
||||
base.lightTarget = 1;
|
||||
base.lightScaleAdjust = 1;
|
||||
base.lightBounds = rect(-(dx + w / 2), -(dy + h / 2), w, h);
|
||||
const adjustedScale = base.lightScale * base.lightScaleAdjust * LIGHT_VOLUME_SCALE;
|
||||
base.lightBounds = rect(-(dx + w / 2), -(dy + h / 2), w * adjustedScale, h * adjustedScale);
|
||||
base.drawLight = function (batch: SpriteBatch) {
|
||||
if (!this.lightOn)
|
||||
return;
|
||||
|
||||
const x = toScreenX(this.x);
|
||||
const y = toScreenYWithZ(this.y, this.z);
|
||||
const s = this.lightScale! * this.lightScaleAdjust!;
|
||||
const width = w * s;
|
||||
const height = h * s;
|
||||
const width = w * adjustedScale;
|
||||
const height = h * adjustedScale;
|
||||
const color = this.lightColor!;
|
||||
|
||||
batch.drawImage(color, -1, -1, 2, 2, x - (dx + width / 2), y - (dy + height / 2), width, height);
|
||||
|
||||
@@ -71,6 +71,7 @@ export function createPony(
|
||||
z: 0,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
depth: 0,
|
||||
info,
|
||||
order: 0,
|
||||
timestamp: 0,
|
||||
|
||||
@@ -125,20 +125,20 @@ div(*ngIf="account")
|
||||
button.dropdown-item((click)="unmerge(m._id, m.data.account, m.data.merge)")
|
||||
| Split off #[b account]
|
||||
|
||||
h5 Support #[span.text-muted ({{support?.length || 0}})]
|
||||
table.table.table-sm(*ngIf="support?.length")
|
||||
thead
|
||||
tr
|
||||
th message
|
||||
th date
|
||||
tbody
|
||||
tr(*ngFor="let l of support")
|
||||
td
|
||||
fa-icon.mr-1([icon]="l.icon" [ngClass]="l.class")
|
||||
| {{l.message}}
|
||||
td
|
||||
time-field.mr-2([time]="l.date")
|
||||
span.text-muted ({{l.date | date:'MMM d'}})
|
||||
//- h5 Support #[span.text-muted ({{support?.length || 0}})]
|
||||
//- table.table.table-sm(*ngIf="support?.length")
|
||||
//- thead
|
||||
//- tr
|
||||
//- th message
|
||||
//- th date
|
||||
//- tbody
|
||||
//- tr(*ngFor="let l of support")
|
||||
//- td
|
||||
//- fa-icon.mr-1([icon]="l.icon" [ngClass]="l.class")
|
||||
//- | {{l.message}}
|
||||
//- td
|
||||
//- time-field.mr-2([time]="l.date")
|
||||
//- span.text-muted ({{l.date | date:'MMM d'}})
|
||||
|
||||
h5 Ban log #[span.text-muted ({{banLog?.length || 0}})]
|
||||
table.table.table-sm(*ngIf="banLog?.length")
|
||||
|
||||
@@ -38,9 +38,6 @@ ng-template(#duplicatesPopover)
|
||||
span.ml-3(title="Certificate expires in" [class.text-danger]="isOldCertificate")
|
||||
fa-icon.mr-2([icon]="certificateIcon")
|
||||
from-now([time]="status.certificateExpiration")
|
||||
span.ml-3(title="Last Patreon update" [class.text-danger]="isOldPatreon")
|
||||
fa-icon.mr-2([icon]="patreonIcon")
|
||||
from-now([time]="status.lastPatreonUpdate")
|
||||
|
||||
pagination(
|
||||
[totalItems]="filtered.length" [itemsPerPage]="itemsPerPage" [maxSize]="15" [(ngModel)]="currentPage"
|
||||
|
||||
@@ -85,17 +85,6 @@ ng-template(#alertModal)
|
||||
//-span.badge.badge-admin.badge-primary([title]="account.birthdate | date:'yyyy-MM-dd'")
|
||||
| {{age}}
|
||||
|
||||
span.badge.badge-admin.badge-supporter(
|
||||
*ngIf="isPatreonOrSupporter"
|
||||
[ngClass]="supporterClass"
|
||||
[class.declined]="account.supporterDeclinedSince && supporterLevel"
|
||||
[tooltip]="supporterTooltip")
|
||||
fa-icon([icon]="supporterIcon" [fixedWidth]="true")
|
||||
| {{supporterLevelString}}
|
||||
|
||||
span.badge.badge-admin.badge-supporter.badge-success(*ngIf="account.totalPledged" title="lifetime support")
|
||||
| ${{account.totalPledged}}
|
||||
|
||||
span.badge.badge-admin.badge-danger(*ngIf="account.ignoresCount" title="ignored by")
|
||||
| {{account.ignoresCount}}
|
||||
|
||||
|
||||
@@ -24,23 +24,23 @@ const accountDuplicates = new Map<string, DuplicatesInfo>();
|
||||
const predefinedAlerts = [
|
||||
{
|
||||
name: 'erp:flagged',
|
||||
message: `Your account has been flagged for inappropriate bahavior on PG rated server. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
message: `Your account has been flagged for inappropriate behavior on a PG rated server. `
|
||||
+ `Continuing that behavior may result in a permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'erp:timeout',
|
||||
message: `Your account has been timed out for inappropriate language and bahavior on PG rated server. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
message: `Your account has been timed out for inappropriate language and behavior on a PG rated server. `
|
||||
+ `Continuing that behavior may result in a permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'dups',
|
||||
message: `Your account has been flagged for making multiple accounts. `
|
||||
+ `Continuing that behavior may result in permanent ban.`
|
||||
+ `Continuing that behavior may result in a permanent ban.`
|
||||
},
|
||||
{
|
||||
name: 'under',
|
||||
message: `Your account has been reported for being underage, please do NOT play on 18+ server. `
|
||||
+ `Continuing that may result in permanent ban.`
|
||||
message: `Your account has been reported for being underage, please do NOT play on an 18+ server. `
|
||||
+ `Continuing that may result in a permanent ban.`
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2,38 +2,13 @@ h1(focusTitle) About
|
||||
|
||||
.row.text-large
|
||||
.col-lg-7
|
||||
p.lead.
|
||||
A game of ponies building a town
|
||||
p.lead A Pony Town custom server
|
||||
|
||||
p This game is a derivative work based on #[a(href="https://pony.town") Pony Town]. The developers of Pony Town are not responsible for it in any way.
|
||||
|
||||
h2 Contact
|
||||
p For all inquiries please send an email to #[b {{contactEmail}}]
|
||||
|
||||
//- div
|
||||
hr
|
||||
|
||||
h2.text-success Custom servers
|
||||
|
||||
p.
|
||||
You can #[a.text-success(href="...") download the files] and start your own custom {{title}} server.
|
||||
p.
|
||||
The package contains server files and instruction on steps required to start your own server.
|
||||
It also allows you to customize and modify the game to your liking.
|
||||
p.
|
||||
Feel free to use this to host a server for your friends, fandom, language group or just a general
|
||||
server for everyone.
|
||||
|
||||
a.d-block.mx-auto.my-4.btn.btn-lg.btn-outline-success.px-3(href="..." style="max-width: 400px;")
|
||||
| Download game files
|
||||
|
||||
p.
|
||||
All necessary instructions required to build and run the server are included in README.md file
|
||||
included in the package.
|
||||
p.
|
||||
Running the server requires basic knowledge of configuring and hosting web applications,
|
||||
it's not intended for beginner users.
|
||||
|
||||
hr
|
||||
|
||||
h2 Technology
|
||||
|
||||
p.
|
||||
|
||||
@@ -77,41 +77,6 @@ h1(focusTitle) Account settings
|
||||
| Account removed successfully
|
||||
|
||||
.col-md-6
|
||||
div(style="margin-bottom: 100px;")
|
||||
.clearfix(*ngIf="showSupporter")
|
||||
.form-group
|
||||
h3
|
||||
fa-icon.mr-2([icon]="starIcon" [ngClass]="supporterClass")
|
||||
| Thank you for your support
|
||||
|
||||
supporter-pony.float-right(#pony (mouseenter)="pony.excite()" (mouseleave)="pony.reset()")
|
||||
|
||||
.form-group
|
||||
p Your reward tier: #[b {{supporterTitle}}]
|
||||
p Your rewards include:
|
||||
ul
|
||||
li(*ngFor="let r of supporterRewards")
|
||||
| {{r}}
|
||||
|
||||
p(*ngIf="supporter > 0")
|
||||
| Use <code>/ss hello</code> command to use supporter chat color.
|
||||
|
||||
p(*ngIf="supporter > 1")
|
||||
| Use <code>/s1 hello</code> and <code>/s2 hello</code> commands to use lower tier supporter chat colors.
|
||||
|
||||
.clearfix(*ngIf="showSupporterInfo")
|
||||
.form-group
|
||||
h3 Supporter info
|
||||
|
||||
supporter-pony.float-right(#pony (mouseenter)="pony.excite()" (mouseleave)="pony.reset()")
|
||||
|
||||
.form-group.text-muted
|
||||
p.
|
||||
You've successfully connected your Patreon account to pixel.horse.
|
||||
If you support pixel.horse on Patreon your supporter info will show up here.
|
||||
p.
|
||||
It should take no longer than 10 minutes to register your Patreon pledges.
|
||||
|
||||
div(style="margin-bottom: 100px;")
|
||||
.form-group
|
||||
.float-right
|
||||
|
||||
@@ -35,7 +35,7 @@ export const routes: Routes = [
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
RouterModule,
|
||||
RouterModule.forRoot(routes, { anchorScrolling: 'enabled' }),
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
PopoverModule.forRoot(),
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
.text-right
|
||||
.d-flex.flex-wrap
|
||||
//- .text-nowrap.mr-2
|
||||
| © 2016-{{date | date:'yyyy'}}
|
||||
| © 2019-{{date | date:'yyyy'}}
|
||||
| #[a.text-muted.mr-1([href]="twitterLink" target="_blank") {{copyright}}] |
|
||||
.text-nowrap
|
||||
a.text-muted.mr-2(*ngIf="twitterLink" [href]="twitterLink" target="_blank" title="Twitter")
|
||||
|
||||
@@ -119,18 +119,21 @@ $chat-pad: 5px;
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#friends-box {
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
right: 45px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#swap-box {
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
right: 86px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#notifications {
|
||||
|
||||
@@ -8,8 +8,7 @@ import { GameService } from '../services/gameService';
|
||||
import { Model, Friend } from '../services/model';
|
||||
import { version, host, contactEmail, twitterLink, discordLink, copyrightName } from '../../client/data';
|
||||
import { PonyTownGame } from '../../client/game';
|
||||
import { faTwitter, faPatreon, faEnvelope, faCog, faHome, faGamepad, faInfoCircle, faHorseHead, faDiscord } from '../../client/icons';
|
||||
import { InstallService } from '../services/installService';
|
||||
import { faTwitter, faPatreon, faDiscord, faEnvelope, faCog, faHome, faInfoCircle, faHorseHead, faQuestionCircle } from '../../client/icons';
|
||||
import { OAuthProvider, Entity, FakeEntity, Pony } from '../../common/interfaces';
|
||||
import { registerServiceWorker, isBrowserOutdated, checkIframeKey } from '../../client/clientUtils';
|
||||
import { ErrorReporter } from '../services/errorReporter';
|
||||
@@ -50,7 +49,7 @@ export class App implements OnInit, OnDestroy {
|
||||
readonly discordIcon = faDiscord;
|
||||
readonly cogIcon = faCog;
|
||||
readonly homeIcon = faHome;
|
||||
readonly helpIcon = faGamepad;
|
||||
readonly helpIcon = faQuestionCircle;
|
||||
readonly aboutIcon = faInfoCircle;
|
||||
readonly charactersIcon = faHorseHead;
|
||||
readonly contactEmail = contactEmail;
|
||||
@@ -68,12 +67,11 @@ export class App implements OnInit, OnDestroy {
|
||||
private game: PonyTownGame,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private installService: InstallService,
|
||||
private errorReporter: ErrorReporter,
|
||||
) {
|
||||
}
|
||||
get canInstall() {
|
||||
return this.installService.canInstall;
|
||||
return false;
|
||||
}
|
||||
get loading() {
|
||||
return this.model.loading;
|
||||
|
||||
@@ -14,7 +14,7 @@ page-loader(*ngIf="!playing")
|
||||
h2.sr-only Character selection
|
||||
.mx-auto(style="max-width: 400px;")
|
||||
discord-button.mb-3
|
||||
install-button.mb-3
|
||||
visit-pt-button.mb-3
|
||||
character-select(
|
||||
[(error)]="error" [newButton]="true" [removeButton]="true" (change)="updateMuzzles()"
|
||||
(preview)="previewPony = $event")
|
||||
|
||||
@@ -5,7 +5,9 @@ h1(focusTitle) Help
|
||||
|
||||
section
|
||||
h2 Contact
|
||||
p For all inquiries please send an email to #[b {{contactEmail}}]
|
||||
p.
|
||||
For technical issues, see the #[a(routerLink="/help" fragment="issues") Common Issues] section. If it doesn't solve your problem,
|
||||
send an email to #[b {{contactEmail}}].
|
||||
|
||||
section
|
||||
h2 Controls
|
||||
@@ -153,15 +155,6 @@ h1(focusTitle) Help
|
||||
li <code>/removetoolbox</code> - removes toolbox from the house (need to be alone in the house or a party leader)
|
||||
li <code>/restoretoolbox</code> - restores toolbox to the house (need to be alone in the house or a party leader)
|
||||
|
||||
section
|
||||
h4 Supporter-only commands
|
||||
|
||||
ul.spaced-list
|
||||
li <code>/ss</code> - supporter colored text
|
||||
li <code>/s1</code> - tier 1 supporter colored text (for use by higher tier supporters)
|
||||
li <code>/s2</code> - tier 2 supporter colored text (for use by higher tier supporters)
|
||||
li <code>/s3</code> - tier 3 supporter colored text (for use by higher tier supporters)
|
||||
|
||||
section
|
||||
h2 Chat emojis
|
||||
p.
|
||||
@@ -177,7 +170,7 @@ h1(focusTitle) Help
|
||||
|
||||
.col-md-5
|
||||
section
|
||||
h2 Common issues
|
||||
h2#issues Common issues
|
||||
|
||||
section
|
||||
h4 Keys stuck when using iPad keyboard
|
||||
@@ -248,13 +241,13 @@ h1(focusTitle) Help
|
||||
using #[b Chrome] or #[b Edge] browsers for best performance.
|
||||
|
||||
section
|
||||
h4 Why can't I create multiple accounts
|
||||
h4#rules Why can't I create multiple accounts
|
||||
p.text-fading.
|
||||
This restriction is in-place to protect you and the game itself from other players in the game.
|
||||
This restriction is in place to protect you and the game itself from other players in the game.
|
||||
p.text-fading.
|
||||
Allowing multiple accounts for single person invalidates ignore and hide systems that
|
||||
allow you to ignore and hide other players that are harassing or bothering you in any way.
|
||||
It also invalidates mute and ban system that is in-place to protect you from players breaking
|
||||
It also invalidates mute and ban system that is in place to protect you from players breaking
|
||||
the game rules to bother or harass others.
|
||||
p.text-fading.
|
||||
Allowing use of multiple accounts at the same time would dramatically increase load on the
|
||||
@@ -264,13 +257,13 @@ h1(focusTitle) Help
|
||||
game anymore.
|
||||
p.text-fading.
|
||||
Use of multiple accounts also invalidates any limits (like character limit) that we put on the accounts.
|
||||
The limits are in-place to prevent large amount of players that we have at the moment from
|
||||
The limits are in place to prevent large amount of players that we have at the moment from
|
||||
exhausting our limited server resources.
|
||||
|
||||
section
|
||||
h4 Why can't I modify the game with hacks or scripts
|
||||
p.text-fading.
|
||||
This restriction is in-place to protect you and the game itself from other players in the game.
|
||||
This restriction is in place to protect you and the game itself from other players in the game.
|
||||
p.text-fading.
|
||||
Almost all third-party scripts used in-game were used to harass other players. Even when the
|
||||
creator of the script did not intend it to be used in that way, the limited knowledge of the
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
|
||||
import { emojis } from '../../../client/emoji';
|
||||
import { faArrowLeft, faArrowRight, faArrowUp, faArrowDown } from '../../../client/icons';
|
||||
import { contactEmail } from '../../../client/data';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'help',
|
||||
@@ -16,4 +17,15 @@ export class Help {
|
||||
readonly emotes = emojis.map(e => e.names[0]);
|
||||
readonly mac = /Macintosh/.test(navigator.userAgent);
|
||||
readonly contactEmail = contactEmail;
|
||||
|
||||
// allow scrolling to #issues and #rules fragments
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.route.fragment.subscribe(f => {
|
||||
const element = document.querySelector("#" + f);
|
||||
if (element) setTimeout(() => element.scrollIntoView(), 10);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ div(*ngIf="!playing")
|
||||
div(*ngIf="!!account")
|
||||
.form-group
|
||||
discord-button
|
||||
visit-pt-button
|
||||
.form-group(*ngIf="canInstall")
|
||||
install-button
|
||||
.form-group
|
||||
character-select([(error)]="error" [editButton]="true" (preview)="previewPony = $event")
|
||||
.form-group
|
||||
character-preview([pony]="previewInfo" [state]="state" [passive]="true" [name]="previewName" [tag]="previewTag")
|
||||
character-preview([pony]="previewInfo" [state]="state" [passive]="false" [name]="previewName" [tag]="previewTag")
|
||||
.form-group.text-center
|
||||
play-box([(error)]="error")
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Component } from '@angular/core';
|
||||
import { Model, getPonyTag } from '../../services/model';
|
||||
import { defaultPonyState } from '../../../client/ponyHelpers';
|
||||
import { GameService } from '../../services/gameService';
|
||||
import { InstallService } from '../../services/installService';
|
||||
import { OAuthProvider, PonyObject } from '../../../common/interfaces';
|
||||
import { stand } from '../../../client/ponyAnimations';
|
||||
|
||||
@Component({
|
||||
selector: 'home',
|
||||
@@ -14,10 +14,11 @@ export class Home {
|
||||
state = defaultPonyState();
|
||||
previewPony: PonyObject | undefined = undefined;
|
||||
error?: string;
|
||||
private animationTime = 0;
|
||||
private interval?: any;
|
||||
constructor(
|
||||
private gameService: GameService,
|
||||
private model: Model,
|
||||
private installService: InstallService,
|
||||
) {
|
||||
}
|
||||
get authError() {
|
||||
@@ -27,7 +28,7 @@ export class Home {
|
||||
return this.model.accountAlert;
|
||||
}
|
||||
get canInstall() {
|
||||
return this.installService.canInstall;
|
||||
return false;
|
||||
}
|
||||
get playing() {
|
||||
return this.gameService.playing;
|
||||
@@ -53,4 +54,21 @@ export class Home {
|
||||
signIn(provider: OAuthProvider) {
|
||||
this.model.signIn(provider);
|
||||
}
|
||||
ngOnInit() {
|
||||
let last = Date.now();
|
||||
this.interval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
this.update((now - last) / 1000);
|
||||
last = now;
|
||||
}, 1000 / 24);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
update(delta: number) {
|
||||
this.animationTime += delta;
|
||||
const animation = stand;
|
||||
this.state.animation = animation;
|
||||
this.state.animationFrame = Math.floor(this.animationTime * animation.fps) % animation.frames.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class InstallService {
|
||||
}
|
||||
}
|
||||
get canInstall() {
|
||||
return !!this.installEvent || (DEVELOPMENT && localStorage.getItem('install'));
|
||||
return false;
|
||||
}
|
||||
install() {
|
||||
if (!this.installEvent) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { ErrorReporter } from './errorReporter';
|
||||
import { randomString } from '../../common/stringUtils';
|
||||
import { StorageService } from './storageService';
|
||||
import { decompressPonyString, compressPonyString, decodePonyInfo } from '../../common/compressPony';
|
||||
import { SECOND, PLAYER_DESC_MAX_LENGTH } from '../../common/constants';
|
||||
import { SECOND, PLAYER_DESC_MAX_LENGTH, NEW_ACCOUNT_PONY_NAME } from '../../common/constants';
|
||||
import { canUseTag } from '../../common/tags';
|
||||
|
||||
export interface Friend extends FriendData {
|
||||
@@ -191,7 +191,11 @@ export class Model {
|
||||
this.ponies = account.ponies ? account.ponies.sort(comparePonies) : [];
|
||||
this.friends = undefined;
|
||||
|
||||
this.selectPony(getDefaultPony(this.ponies));
|
||||
let defaultPony = getDefaultPony(this.ponies);
|
||||
if (this.ponies.length === 0) {
|
||||
defaultPony.name = NEW_ACCOUNT_PONY_NAME;
|
||||
}
|
||||
this.selectPony(defaultPony);
|
||||
this.storage.setItem('vid', account.id);
|
||||
this.loading = false;
|
||||
this.accountAlert = account.alert;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
ng-template(#actionsModal)
|
||||
actions-modal((close)="closeActions()" [focusTrap]="true" (keydown)="$event.stopPropagation()")
|
||||
|
||||
.action-bar(
|
||||
[class.has-scroller]="hasScroller" [class.is-mobile]="mobile" [class.is-blurred]="blurred"
|
||||
#scroller (mousewheel)="scroll($event)" (wheel)="scroll($event)" (scroll)="true")
|
||||
@@ -14,6 +17,8 @@
|
||||
[draggablePad]="10"
|
||||
(draggableDrag)="drag(i)"
|
||||
(draggableDrop)="drop($event, i)")
|
||||
button.action-settings.game-button((click)="openActions()" style="touch-action: inherit;")
|
||||
fa-icon.action-icon([icon]="cogIcon" [fixedWidth]="true")
|
||||
.action-button-padding
|
||||
.scroller-label
|
||||
| scroll using this bar
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
.action-bar {
|
||||
overflow: hidden;
|
||||
padding-bottom: 7px;
|
||||
padding-bottom: 0px;
|
||||
padding-top: 7px;
|
||||
padding-right: 5px;
|
||||
max-width: calc(100vw - 50px);
|
||||
@@ -60,7 +60,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
action-button {
|
||||
.action-icon {
|
||||
font-size: 30px;
|
||||
position: relative;
|
||||
right: 6px;
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
action-button, .action-settings {
|
||||
pointer-events: auto;
|
||||
margin: 3px 6px;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, Input, ViewChild, ElementRef } from '@angular/core';
|
||||
import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { ButtonAction } from '../../../common/interfaces';
|
||||
import { PonyTownGame } from '../../../client/game';
|
||||
import { isMobile } from '../../../client/data';
|
||||
@@ -6,6 +7,7 @@ import { useAction, serializeActions } from '../../../client/buttonActions';
|
||||
import { SettingsService } from '../../services/settingsService';
|
||||
import { ACTIONS_LIMIT } from '../../../common/constants';
|
||||
import { last } from '../../../common/utils';
|
||||
import { faCog } from '../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'action-bar',
|
||||
@@ -14,11 +16,14 @@ import { last } from '../../../common/utils';
|
||||
})
|
||||
export class ActionBar {
|
||||
@ViewChild('scroller', { static: true }) scroller!: ElementRef;
|
||||
@ViewChild('actionsModal', { static: true }) actionsModal!: ElementRef;
|
||||
@Input() blurred = false;
|
||||
readonly cogIcon = faCog;
|
||||
activeAction: ButtonAction | undefined = undefined;
|
||||
shortcuts = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='];
|
||||
private modalRef?: BsModalRef;
|
||||
private _editable = false;
|
||||
constructor(private game: PonyTownGame, private settings: SettingsService) {
|
||||
constructor(private game: PonyTownGame, private settings: SettingsService, private modalService: BsModalService) {
|
||||
}
|
||||
@Input() get editable() {
|
||||
return this._editable;
|
||||
@@ -68,6 +73,20 @@ export class ActionBar {
|
||||
this.scroller.nativeElement.scrollLeft += delta * 20;
|
||||
}
|
||||
}
|
||||
openActions() {
|
||||
if (this.modalRef) {
|
||||
this.closeActions();
|
||||
}
|
||||
else {
|
||||
this.modalRef = this.modalService.show(this.actionsModal, { ignoreBackdropClick: true });
|
||||
}
|
||||
}
|
||||
closeActions() {
|
||||
if (this.modalRef) {
|
||||
this.modalRef.hide();
|
||||
this.modalRef = undefined;
|
||||
}
|
||||
}
|
||||
private updateFreeSlots() {
|
||||
const actions = this.actions;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { sampleMessages } from '../../../common/debugData';
|
||||
import { findEntityById } from '../../../common/worldMap';
|
||||
import { colorToRGBA, rgb2hsl, HSL, hsl2CSS } from '../../../common/color';
|
||||
import * as moment from 'moment';
|
||||
import { isMobile } from '../../../client/data';
|
||||
|
||||
interface IndexEntryUser {
|
||||
id: number;
|
||||
@@ -387,7 +388,10 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
|
||||
return (tab === 'local' || tab === 'party' || tab === 'whisper') ? tab : 'local';
|
||||
}
|
||||
get open() {
|
||||
return !this.settings.chatlogClosed;
|
||||
if (this.settings.chatlogClosed === undefined) {
|
||||
return !isMobile; // closed by default on mobile
|
||||
}
|
||||
return this.settings.chatlogClosed === false;
|
||||
}
|
||||
get width() {
|
||||
return this.settings.chatlogWidth || 500;
|
||||
@@ -645,7 +649,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
this.settings.chatlogClosed = !this.settings.chatlogClosed;
|
||||
this.settings.chatlogClosed = this.open;
|
||||
this.settingsService.saveBrowserSettings();
|
||||
this.updateOpen();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ $color-picker-box-offset: 5px;
|
||||
border: solid 1px $border-color;
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
|
||||
.disabled > & {
|
||||
opacity: 0.8;
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 70px;
|
||||
height: 80px;
|
||||
width: 100px;
|
||||
overflow: hidden;
|
||||
border-radius: $border-radius-lg - 2px;
|
||||
|
||||
> discord-pony {
|
||||
position: absolute;
|
||||
bottom: -95px;
|
||||
bottom: -80px;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export class InstallButton {
|
||||
constructor(private installService: InstallService) {
|
||||
}
|
||||
get canInstall() {
|
||||
return this.installService.canInstall;
|
||||
return false;
|
||||
}
|
||||
get isMobile() {
|
||||
return isMobile;
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
h5 General rules
|
||||
ul.text-muted.list-rules
|
||||
li(*ngFor="let r of rules") {{r}}
|
||||
p.text-muted For more details about the rules, see our #[a(routerLink="/help" fragment="rules") Help page].
|
||||
hr
|
||||
|
||||
h5 Notice
|
||||
h5 This is a Pony Town custom server
|
||||
p.text-muted
|
||||
| This game is very #[strong early in development]. There might be bugs and occasional downtimes.
|
||||
| The developers of Pony Town are not responsible for this server or the contents on it in any way.
|
||||
p.text-muted
|
||||
| Please do not redistribute any of the game files or code.
|
||||
| You can check out the original game #[a(href="https://pony.town") here].
|
||||
|
||||
@@ -12,32 +12,33 @@ ng-template(#actionsModal)
|
||||
button.game-button.dropdown-toggle.no-arrow(dropdownToggle (click)="false" title="Settings")
|
||||
fa-icon([icon]="cogIcon" [fixedWidth]="true")
|
||||
.dropdown-menu.dropdown-menu-right.settings-box-menu(*dropdownMenu)
|
||||
.dropdown-header.d-flex.justify-content-between
|
||||
.dropdown-header.d-flex.justify-content-between.mb-1
|
||||
span {{server}}
|
||||
span.clock-display {{time}}
|
||||
.dropdown-item.d-flex
|
||||
.dropdown-item.d-flex.mb-1
|
||||
fa-icon.mr-2([icon]="searchIcon" [fixedWidth]="true")
|
||||
.flex-grow-1 Scale (x{{scale}})
|
||||
button.btn.btn-xs.btn-outline-secondary((click)="zoomOut()" aria-label="Zoom out")
|
||||
fa-icon([icon]="minusIcon" [fixedWidth]="true")
|
||||
button.btn.btn-xs.btn-outline-secondary.ml-2((click)="zoomIn()" aria-label="Zoom in")
|
||||
fa-icon([icon]="plusIcon" [fixedWidth]="true")
|
||||
.dropdown-item.dropdown-item-static
|
||||
.dropdown-item.dropdown-item-static.mb-1
|
||||
a((click)="toggleVolume()" title="Toggle sound")
|
||||
fa-icon([icon]="volumeIcon" [fixedWidth]="true")
|
||||
//.flex-grow-1 Volume
|
||||
slider-bar([(value)]="volume" [step]="1" (started)="volumeStarted()")
|
||||
.text-muted.text-right(*ngIf="track" style="margin-top: -5px;")
|
||||
small
|
||||
| playing: #[b {{track}}]
|
||||
a.text-muted.ml-1((click)="nextTrack()" title="Next track")
|
||||
fa-icon([icon]="forwardIcon" [fixedWidth]="true")
|
||||
a.dropdown-item((click)="unhideAllHiddenPlayers()")
|
||||
a.dropdown-item((click)="unhideAllHiddenPlayers()").mb-1
|
||||
fa-icon.mr-2([icon]="emptyIcon" [fixedWidth]="true")
|
||||
| Unhide players (temporary)
|
||||
a.dropdown-item((click)="openSettings()")
|
||||
a.dropdown-item((click)="openSettings()").mb-1
|
||||
fa-icon.mr-2([icon]="emptyIcon" [fixedWidth]="true")
|
||||
| Settings
|
||||
a.dropdown-item((click)="openActions()")
|
||||
a.dropdown-item((click)="openActions()").mb-1
|
||||
fa-icon.mr-2([icon]="emptyIcon" [fixedWidth]="true")
|
||||
| Actions
|
||||
//-a.dropdown-item((click)="openInvites()" *ngIf="hasInvites")
|
||||
|
||||
@@ -30,14 +30,12 @@
|
||||
tab(title="Graphics" [icon]="graphicsIcon")
|
||||
div(*tabContent)
|
||||
.form-group
|
||||
custom-checkbox(
|
||||
*ngIf="!lockLowGraphicsMode" [(checked)]="browser.lowGraphicsMode"
|
||||
help="Can improve game performance in some cases")
|
||||
| Low graphics mode
|
||||
custom-checkbox(
|
||||
*ngIf="lockLowGraphicsMode" [checked]="true" [disabled]="true"
|
||||
help="Can improve game performance in some cases")
|
||||
| Low graphics mode
|
||||
label#graphics-quality-label Graphics quality
|
||||
div
|
||||
slider-bar.mr-2.mb-2(
|
||||
[(value)]="browser.graphicsQuality" [min]="0" [max]="maxGraphicsQuality" [step]="1"
|
||||
style="max-width: 300px" labelledBy="graphics-quality-label")
|
||||
| {{graphicsQualityText}}
|
||||
.form-group
|
||||
custom-checkbox(
|
||||
[(checked)]="browser.brightNight"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AccountSettings, BrowserSettings } from '../../../common/interfaces';
|
||||
import { AccountSettings, BrowserSettings, GraphicsQuality } from '../../../common/interfaces';
|
||||
import { SettingsService } from '../../services/settingsService';
|
||||
import {
|
||||
DEFAULT_CHATLOG_OPACITY, MAX_CHATLOG_RANGE, MIN_CHATLOG_RANGE, isChatlogRangeUnlimited, MAX_FILTER_WORDS_LENGTH
|
||||
@@ -9,7 +9,7 @@ import { StorageService } from '../../services/storageService';
|
||||
import { cloneDeep } from '../../../common/utils';
|
||||
import { PonyTownGame } from '../../../client/game';
|
||||
import { updateRangeIndicator, readFileAsText } from '../../../client/clientUtils';
|
||||
import { faSlidersH, faCommentSlash, faGamepad, faImage, faDownload, faUpload } from '../../../client/icons';
|
||||
import { faSlidersH, faCommentSlash, faGamepad, faImage, faDownload, faUpload, faComment } from '../../../client/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'settings-modal',
|
||||
@@ -19,8 +19,10 @@ import { faSlidersH, faCommentSlash, faGamepad, faImage, faDownload, faUpload }
|
||||
export class SettingsModal implements OnInit, OnDestroy {
|
||||
readonly maxChatlogRange = MAX_CHATLOG_RANGE;
|
||||
readonly minChatlogRange = MIN_CHATLOG_RANGE;
|
||||
readonly maxGraphicsQuality: number;
|
||||
readonly maxGraphicsQualityValue: GraphicsQuality;
|
||||
readonly gameIcon = faSlidersH;
|
||||
readonly chatIcon = faCommentSlash;
|
||||
readonly chatIcon = faComment;
|
||||
readonly filtersIcon = faCommentSlash;
|
||||
readonly controlsIcon = faGamepad;
|
||||
readonly graphicsIcon = faImage;
|
||||
@@ -38,6 +40,24 @@ export class SettingsModal implements OnInit, OnDestroy {
|
||||
private storage: StorageService,
|
||||
private game: PonyTownGame,
|
||||
) {
|
||||
if (game.webgl) {
|
||||
if (game.webgl.failedFBO) {
|
||||
this.maxGraphicsQuality = 0;
|
||||
this.maxGraphicsQualityValue = GraphicsQuality.Low;
|
||||
}
|
||||
else if (game.webgl.failedDepthBuffer) {
|
||||
this.maxGraphicsQuality = 1;
|
||||
this.maxGraphicsQualityValue = GraphicsQuality.Medium;
|
||||
}
|
||||
else {
|
||||
this.maxGraphicsQuality = 2;
|
||||
this.maxGraphicsQualityValue = GraphicsQuality.High;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.maxGraphicsQuality = 2;
|
||||
this.maxGraphicsQualityValue = GraphicsQuality.High;
|
||||
}
|
||||
}
|
||||
get pane() {
|
||||
return this.storage.getItem('settings-modal-pane') || 'game';
|
||||
@@ -45,13 +65,25 @@ export class SettingsModal implements OnInit, OnDestroy {
|
||||
set pane(value: string) {
|
||||
this.storage.setItem('settings-modal-pane', value);
|
||||
}
|
||||
get lockLowGraphicsMode() {
|
||||
return this.game.failedFBO;
|
||||
}
|
||||
get chatlogRangeText() {
|
||||
const range = this.account.chatlogRange;
|
||||
return isChatlogRangeUnlimited(range) ? 'entire screen' : `${range} tiles`;
|
||||
}
|
||||
get graphicsQualityText() {
|
||||
if (!this.game.webgl || (this.browser.graphicsQuality === undefined)) {
|
||||
return 'Undefined';
|
||||
}
|
||||
|
||||
if (this.game.webgl.failedFBO || (this.browser.graphicsQuality === GraphicsQuality.Low)) {
|
||||
return 'Low';
|
||||
}
|
||||
else if (this.game.webgl.failedDepthBuffer || (this.browser.graphicsQuality === GraphicsQuality.Medium)) {
|
||||
return 'Medium';
|
||||
}
|
||||
else {
|
||||
return 'High';
|
||||
}
|
||||
}
|
||||
ngOnInit() {
|
||||
this.accountBackup = cloneDeep(this.settingsService.account);
|
||||
this.browserBackup = cloneDeep(this.settingsService.browser);
|
||||
@@ -125,6 +157,10 @@ export class SettingsModal implements OnInit, OnDestroy {
|
||||
if (this.account.filterWords === undefined) {
|
||||
this.account.filterWords = '';
|
||||
}
|
||||
|
||||
if (this.browser.graphicsQuality === undefined) {
|
||||
this.browser.graphicsQuality = this.maxGraphicsQualityValue;
|
||||
}
|
||||
}
|
||||
export() {
|
||||
const account = { ...this.account, actions: undefined };
|
||||
|
||||
@@ -23,7 +23,6 @@ import { EmoteBox } from './emote-box/emote-box';
|
||||
import { SliderBar } from './slider-bar/slider-bar';
|
||||
import { SpriteBox } from './sprite-box/sprite-box';
|
||||
import { SpriteSelection } from './sprite-selection/sprite-selection';
|
||||
import { SupporterPony } from './supporter-pony/supporter-pony';
|
||||
import { DiscordButton } from './discord-button/discord-button';
|
||||
import { DiscordPony } from './discord-pony/discord-pony';
|
||||
import { SetSelection, SetOutlineHidden } from './set-selection/set-selection';
|
||||
@@ -74,6 +73,8 @@ import { dropdownDirectives } from './directives/dropdown';
|
||||
import { SaveActiveTab } from './directives/saveActiveTab';
|
||||
|
||||
import { SiteNamePipe } from './pipes/siteName';
|
||||
import { VisitPTButton } from './visit-pt-button/visit-pt-button';
|
||||
import { VisitPTPony } from './visit-pt-pony/visit-pt-pony';
|
||||
|
||||
const declarations = [
|
||||
ActionBar,
|
||||
@@ -90,9 +91,10 @@ const declarations = [
|
||||
SliderBar,
|
||||
SpriteBox,
|
||||
SpriteSelection,
|
||||
SupporterPony,
|
||||
DiscordButton,
|
||||
DiscordPony,
|
||||
VisitPTButton,
|
||||
VisitPTPony,
|
||||
SetSelection,
|
||||
SetOutlineHidden,
|
||||
CheckBox,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
a.btn.btn-lg.btn-pt.btn-outline-primary.btn-block.mb-2(
|
||||
*ngIf="enableVisitPTButton" (mouseenter)="pony.select()" (mouseout)="pony.reset()" [href]="ptLink" target="_blank"
|
||||
rel="noopener noreferrer")
|
||||
.pt-logo
|
||||
img.pixelart.logo(revSrc="images/logo-small.png")
|
||||
| {{'Visit Pony Town main server!'}}
|
||||
.visit-pt-pony
|
||||
visit-pt-pony(#pony [scale]="2")
|
||||
@@ -0,0 +1,51 @@
|
||||
@import '../../../../styles/partials/variables';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 320px) {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-pt {
|
||||
padding: 1.0rem;
|
||||
font-weight: 500;
|
||||
//height: 60px;
|
||||
}
|
||||
|
||||
.pt-logo {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 6px;
|
||||
width: 37px;
|
||||
height: 47px;
|
||||
|
||||
@media (max-width: 320px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.visit-pt-pony {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 80px;
|
||||
width: 100px;
|
||||
overflow: hidden;
|
||||
border-radius: $border-radius-lg - 2px;
|
||||
|
||||
> visit-pt-pony {
|
||||
position: absolute;
|
||||
bottom: -80px;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'visit-pt-button',
|
||||
templateUrl: 'visit-pt-button.pug',
|
||||
styleUrls: ['visit-pt-button.scss'],
|
||||
})
|
||||
export class VisitPTButton {
|
||||
readonly ptLink = 'http://pony.town';
|
||||
readonly enableVisitPTButton = true;
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
+42
-15
@@ -1,41 +1,42 @@
|
||||
import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core';
|
||||
import { defaultExpression } from '../../../client/ponyUtils';
|
||||
import { defaultPonyState } from '../../../client/ponyHelpers';
|
||||
import { SUPPORTER_PONY } from '../../../common/constants';
|
||||
import { Expression, Muzzle, HeadAnimation, Iris } from '../../../common/interfaces';
|
||||
import { excite } from '../../../client/ponyAnimations';
|
||||
import { Expression, Muzzle, HeadAnimation, BodyAnimation, Eye } from '../../../common/interfaces';
|
||||
import { excite_meno, happy_tongue_meno, stand, boop, happy_tongue_meno_2 } from '../../../client/ponyAnimations';
|
||||
import { FrameService, FrameLoop } from '../../services/frameService';
|
||||
import { CharacterPreview } from '../character-preview/character-preview';
|
||||
import { decompressPonyString } from '../../../common/compressPony';
|
||||
|
||||
const BLEP: Expression = {
|
||||
...defaultExpression,
|
||||
left: Eye.Neutral2,
|
||||
right: Eye.Neutral2,
|
||||
muzzle: Muzzle.Blep,
|
||||
};
|
||||
|
||||
const EXCITED: Expression = {
|
||||
...defaultExpression,
|
||||
left: Eye.Neutral2,
|
||||
right: Eye.Neutral2,
|
||||
muzzle: Muzzle.SmileOpen,
|
||||
};
|
||||
|
||||
const DERP: Expression = {
|
||||
...defaultExpression,
|
||||
muzzle: Muzzle.SmileOpen,
|
||||
leftIris: Iris.Up,
|
||||
};
|
||||
const MENO = 'DBWIzP8imd08//D19fVazSjcwf1GhLNEiMxENSovLy/NsIdJQDnGqYBiSztWQTM6LychEnE/KRX///9WPeE1HbSpBUEIIASwgEIAAAAAbIAgCFMY34AHAAjwgCMRQCVLY38IDhAAwmFBz4fAIxjKM6SjGlaxjWtYgjGI';
|
||||
|
||||
@Component({
|
||||
selector: 'supporter-pony',
|
||||
templateUrl: 'supporter-pony.pug',
|
||||
selector: 'visit-pt-pony',
|
||||
templateUrl: 'visit-pt-pony.pug',
|
||||
})
|
||||
export class SupporterPony implements OnInit, OnDestroy {
|
||||
export class VisitPTPony implements OnInit, OnDestroy {
|
||||
@ViewChild('characterPreview', { static: true }) characterPreview!: CharacterPreview;
|
||||
@Input() scale = 3;
|
||||
pony = decompressPonyString(SUPPORTER_PONY);
|
||||
pony = decompressPonyString(MENO);
|
||||
state = defaultPonyState();
|
||||
private expression?: Expression;
|
||||
private headAnimation?: HeadAnimation;
|
||||
private headTime = 0;
|
||||
private bodyAnimation?: BodyAnimation;
|
||||
private bodyTime = 0;
|
||||
private loop: FrameLoop;
|
||||
constructor(frameService: FrameService) {
|
||||
this.loop = frameService.create(delta => this.tick(delta));
|
||||
@@ -46,16 +47,29 @@ export class SupporterPony implements OnInit, OnDestroy {
|
||||
ngOnDestroy() {
|
||||
this.loop.destroy();
|
||||
}
|
||||
excite() {
|
||||
select() {
|
||||
this.headTime = 0;
|
||||
this.headAnimation = excite;
|
||||
this.expression = Math.random() < 0.2 ? DERP : EXCITED;
|
||||
this.bodyTime = 0;
|
||||
this.expression = EXCITED;
|
||||
this.bodyAnimation = Math.random() < 0.25 ? boop : stand;
|
||||
|
||||
const headRandom = Math.random();
|
||||
if (headRandom < 0.5) {
|
||||
this.headAnimation = excite_meno;
|
||||
}
|
||||
else if (headRandom < 0.8) {
|
||||
this.headAnimation = happy_tongue_meno;
|
||||
}
|
||||
else {
|
||||
this.headAnimation = happy_tongue_meno_2;
|
||||
}
|
||||
}
|
||||
reset() {
|
||||
this.expression = undefined;
|
||||
}
|
||||
private tick(delta: number) {
|
||||
this.headTime += delta;
|
||||
this.bodyTime += delta;
|
||||
|
||||
if (this.headAnimation) {
|
||||
const frame = Math.floor(this.headTime * this.headAnimation.fps);
|
||||
@@ -83,6 +97,19 @@ export class SupporterPony implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.bodyAnimation) {
|
||||
const frame = Math.floor(this.bodyTime * this.bodyAnimation.fps * 0.75);
|
||||
|
||||
if (frame >= this.bodyAnimation.frames.length && !this.bodyAnimation.loop) {
|
||||
this.bodyAnimation = undefined;
|
||||
this.state.animation = stand;
|
||||
this.state.animationFrame = 0;
|
||||
} else {
|
||||
this.state.animation = this.bodyAnimation;
|
||||
this.state.animationFrame = frame % this.bodyAnimation.frames.length;
|
||||
}
|
||||
}
|
||||
|
||||
this.state.expression = this.expression;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,6 @@ export class ToolsUI implements OnInit, OnDestroy {
|
||||
],
|
||||
};
|
||||
game.onClock.next('00:00');
|
||||
game.failedFBO = true;
|
||||
game.send = <T>(action: (server: any) => T) => action({
|
||||
action() { },
|
||||
select() { },
|
||||
|
||||
+2257
-2281
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
||||
import { Sprite, Batch, SpriteBatchBase, SpriteSheet } from '../common/interfaces';
|
||||
import { Sprite, Batch, SpriteBatchBase } from '../common/interfaces';
|
||||
import { WHITE } from '../common/colors';
|
||||
import { colorToFloat, colorToFloatAlpha } from '../common/color';
|
||||
import { BaseStateBatch } from './baseStateBatch';
|
||||
@@ -38,36 +38,45 @@ export function getColorFloat(color: number, alpha: number) {
|
||||
}
|
||||
|
||||
export abstract class BaseSpriteBatch extends BaseStateBatch implements SpriteBatchBase {
|
||||
tris = 0;
|
||||
depth = 1;
|
||||
drawnTrisStats = 0;
|
||||
flushes = 0;
|
||||
index = 0;
|
||||
spritesCount = 0;
|
||||
vertices!: Float32Array;
|
||||
verticesUint32!: Uint32Array;
|
||||
vao: VAO | undefined = undefined;
|
||||
rectSprite: Sprite | undefined = undefined;
|
||||
vertexBuffer: WebGLBuffer | undefined = undefined;
|
||||
indexBuffer: WebGLBuffer | undefined = undefined;
|
||||
spriteSheet: SpriteSheet | undefined = undefined;
|
||||
floatsPerSprite: number;
|
||||
batching = false;
|
||||
vao: VAO | undefined;
|
||||
vertexBuffer: WebGLBuffer | undefined;
|
||||
protected indexBuffer: WebGLBuffer;
|
||||
protected vertices!: Float32Array;
|
||||
protected spritesCapacity: number;
|
||||
private floatsPerSprite: number;
|
||||
private batching = false;
|
||||
private startBatchIndex = 0;
|
||||
private startBatchSprites = 0;
|
||||
constructor(
|
||||
public gl: WebGLRenderingContext,
|
||||
public capacity: number,
|
||||
buffer: ArrayBuffer,
|
||||
vertexBuffer: WebGLBuffer,
|
||||
public vertexCapacityMax: number,
|
||||
indexBuffer: WebGLBuffer,
|
||||
public attributes: VAOAttributeDefinition[],
|
||||
) {
|
||||
super();
|
||||
this.floatsPerSprite = getVAOAttributesSize(gl, attributes);
|
||||
this.vertices = new Float32Array(buffer, 0, capacity * this.floatsPerSprite);
|
||||
this.verticesUint32 = new Uint32Array(buffer, 0, capacity * this.floatsPerSprite);
|
||||
|
||||
const bytesPerVertex = getVAOAttributesSize(gl, attributes);
|
||||
this.vertices = new Float32Array(vertexCapacityMax * bytesPerVertex);
|
||||
this.spritesCapacity = (vertexCapacityMax / 4) | 0; // 4 vertices per sprite
|
||||
this.floatsPerSprite = bytesPerVertex | 0; // bytesPerVertex * 4 / sizeof(float)
|
||||
|
||||
const vertexBuffer = gl.createBuffer();
|
||||
if (!vertexBuffer) {
|
||||
throw new Error(`Failed to allocate vertex buffer`);
|
||||
}
|
||||
this.vertexBuffer = vertexBuffer;
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW);
|
||||
|
||||
this.indexBuffer = indexBuffer;
|
||||
this.vao = createVAO(gl, createVAOAttributes(gl, attributes, vertexBuffer), indexBuffer);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
}
|
||||
dispose() {
|
||||
disposeBuffers(this.gl, this);
|
||||
@@ -95,14 +104,13 @@ export abstract class BaseSpriteBatch extends BaseStateBatch implements SpriteBa
|
||||
|
||||
const batchSpriteCount = (batch.length / this.floatsPerSprite) | 0;
|
||||
|
||||
if (this.capacity < (this.spritesCount + batchSpriteCount)) {
|
||||
if (this.spritesCapacity < (this.spritesCount + batchSpriteCount)) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
this.vertices.set(batch, this.index);
|
||||
this.index += batch.length;
|
||||
this.spritesCount += batchSpriteCount;
|
||||
this.tris += batchSpriteCount * 2;
|
||||
}
|
||||
startBatch() {
|
||||
if (this.batching) {
|
||||
@@ -156,6 +164,7 @@ export abstract class BaseSpriteBatch extends BaseStateBatch implements SpriteBa
|
||||
this.vao.draw(this.gl.TRIANGLES, this.startBatchSprites * 6, 0);
|
||||
TIMING && timeEnd();
|
||||
|
||||
this.drawnTrisStats += this.startBatchSprites * 2;
|
||||
this.spritesCount -= this.startBatchSprites;
|
||||
this.index -= this.startBatchIndex;
|
||||
this.vertices.copyWithin(0, this.startBatchIndex, this.startBatchIndex + this.index);
|
||||
@@ -171,6 +180,7 @@ export abstract class BaseSpriteBatch extends BaseStateBatch implements SpriteBa
|
||||
this.vao.draw(this.gl.TRIANGLES, this.spritesCount * 6, 0);
|
||||
TIMING && timeEnd();
|
||||
|
||||
this.drawnTrisStats += this.spritesCount * 2;
|
||||
this.spritesCount = 0;
|
||||
this.index = 0;
|
||||
}
|
||||
@@ -184,19 +194,13 @@ function disposeBuffers(gl: WebGLRenderingContext, batch: BaseSpriteBatch) {
|
||||
if (batch.vao) {
|
||||
batch.vao.dispose();
|
||||
}
|
||||
|
||||
if (batch.vertexBuffer) {
|
||||
gl.deleteBuffer(batch.vertexBuffer);
|
||||
}
|
||||
|
||||
if (batch.indexBuffer) {
|
||||
gl.deleteBuffer(batch.indexBuffer);
|
||||
}
|
||||
} catch (e) {
|
||||
DEVELOPMENT && console.error(e);
|
||||
}
|
||||
|
||||
batch.vao = undefined;
|
||||
batch.vertexBuffer = undefined;
|
||||
batch.indexBuffer = undefined;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export function drawCanvas(
|
||||
}
|
||||
|
||||
export class ContextSpriteBatch extends BaseStateBatch implements PaletteSpriteBatch, SpriteBatch {
|
||||
depth = 1;
|
||||
pixelSize = 1;
|
||||
disableShading = false;
|
||||
ignoreColor = 0;
|
||||
@@ -137,6 +138,8 @@ export class ContextSpriteBatch extends BaseStateBatch implements PaletteSpriteB
|
||||
}
|
||||
releaseBatch() {
|
||||
}
|
||||
flush() {
|
||||
}
|
||||
}
|
||||
|
||||
const min = Math.min;
|
||||
|
||||
@@ -158,7 +158,7 @@ export class PaletteManager implements IPaletteManager {
|
||||
private initializeTexture(gl: WebGLRenderingContext, size: number) {
|
||||
try {
|
||||
if (!this.paletteTexture) {
|
||||
this.paletteTexture = createEmptyTexture(gl, size, size, gl.RGBA, gl.UNSIGNED_BYTE);
|
||||
this.paletteTexture = createEmptyTexture(gl, true, size, size, gl.RGBA, gl.UNSIGNED_BYTE);
|
||||
} else if (this.paletteTexture.width !== size) {
|
||||
resizeTexture(gl, this.paletteTexture, size, size);
|
||||
}
|
||||
|
||||
@@ -59,10 +59,12 @@ function pushQuad(
|
||||
*/
|
||||
|
||||
function pushQuad(
|
||||
vertices: Float32Array, //_verticesUint32: Uint32Array,
|
||||
vertices: Float32Array,
|
||||
transform: Matrix2D, index: number, type: number, color: number, palette: Palette,
|
||||
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number,
|
||||
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dz: number, dw: number, dh: number,
|
||||
) {
|
||||
index |= 0;
|
||||
|
||||
const c1 = types[type];
|
||||
|
||||
const y2 = dy + dh;
|
||||
@@ -83,63 +85,62 @@ function pushQuad(
|
||||
const t4 = transform[4];
|
||||
const t5 = transform[5];
|
||||
|
||||
// pushVertex(vertices, index, dx, dy, u1, v1, pu, pv, color, c1, transform);
|
||||
vertices[(index + 0) | 0] = t0 * dx + t2 * dy + t4;
|
||||
vertices[(index + 1) | 0] = t1 * dx + t3 * dy + t5;
|
||||
vertices[(index + 2) | 0] = u1;
|
||||
vertices[(index + 3) | 0] = v1;
|
||||
vertices[(index + 4) | 0] = pu;
|
||||
vertices[(index + 5) | 0] = pv;
|
||||
vertices[(index + 6) | 0] = color;
|
||||
vertices[(index + 7) | 0] = c1;
|
||||
vertices[index++] = t0 * dx + t2 * dy + t4;
|
||||
vertices[index++] = t1 * dx + t3 * dy + t5;
|
||||
vertices[index++] = dz;
|
||||
vertices[index++] = u1;
|
||||
vertices[index++] = v1;
|
||||
vertices[index++] = pu;
|
||||
vertices[index++] = pv;
|
||||
vertices[index++] = color;
|
||||
vertices[index++] = c1;
|
||||
|
||||
// pushVertex(vertices, index + 8, x2, dy, u2, v1, pu, pv, color, c1, transform);
|
||||
vertices[(index + 8) | 0] = t0 * x2 + t2 * dy + t4;
|
||||
vertices[(index + 9) | 0] = t1 * x2 + t3 * dy + t5;
|
||||
vertices[(index + 10) | 0] = u2;
|
||||
vertices[(index + 11) | 0] = v1;
|
||||
vertices[(index + 12) | 0] = pu;
|
||||
vertices[(index + 13) | 0] = pv;
|
||||
vertices[(index + 14) | 0] = color;
|
||||
vertices[(index + 15) | 0] = c1;
|
||||
vertices[index++] = t0 * x2 + t2 * dy + t4;
|
||||
vertices[index++] = t1 * x2 + t3 * dy + t5;
|
||||
vertices[index++] = dz;
|
||||
vertices[index++] = u2;
|
||||
vertices[index++] = v1;
|
||||
vertices[index++] = pu;
|
||||
vertices[index++] = pv;
|
||||
vertices[index++] = color;
|
||||
vertices[index++] = c1;
|
||||
|
||||
// pushVertex(vertices, index + 16, x2, y2, u2, v2, pu, pv, color, c1, transform);
|
||||
vertices[(index + 16) | 0] = t0 * x2 + t2 * y2 + t4;
|
||||
vertices[(index + 17) | 0] = t1 * x2 + t3 * y2 + t5;
|
||||
vertices[(index + 18) | 0] = u2;
|
||||
vertices[(index + 19) | 0] = v2;
|
||||
vertices[(index + 20) | 0] = pu;
|
||||
vertices[(index + 21) | 0] = pv;
|
||||
vertices[(index + 22) | 0] = color;
|
||||
vertices[(index + 23) | 0] = c1;
|
||||
vertices[index++] = t0 * x2 + t2 * y2 + t4;
|
||||
vertices[index++] = t1 * x2 + t3 * y2 + t5;
|
||||
vertices[index++] = dz;
|
||||
vertices[index++] = u2;
|
||||
vertices[index++] = v2;
|
||||
vertices[index++] = pu;
|
||||
vertices[index++] = pv;
|
||||
vertices[index++] = color;
|
||||
vertices[index++] = c1;
|
||||
|
||||
// pushVertex(vertices, index + 24, dx, y2, u1, v2, pu, pv, color, c1, transform);
|
||||
vertices[(index + 24) | 0] = t0 * dx + t2 * y2 + t4;
|
||||
vertices[(index + 25) | 0] = t1 * dx + t3 * y2 + t5;
|
||||
vertices[(index + 26) | 0] = u1;
|
||||
vertices[(index + 27) | 0] = v2;
|
||||
vertices[(index + 28) | 0] = pu;
|
||||
vertices[(index + 29) | 0] = pv;
|
||||
vertices[(index + 30) | 0] = color;
|
||||
vertices[(index + 31) | 0] = c1;
|
||||
vertices[index++] = t0 * dx + t2 * y2 + t4;
|
||||
vertices[index++] = t1 * dx + t3 * y2 + t5;
|
||||
vertices[index++] = dz;
|
||||
vertices[index++] = u1;
|
||||
vertices[index++] = v2;
|
||||
vertices[index++] = pu;
|
||||
vertices[index++] = pv;
|
||||
vertices[index++] = color;
|
||||
vertices[index++] = c1;
|
||||
|
||||
return index + 32;
|
||||
return index;
|
||||
}
|
||||
|
||||
// function colorWithAlpha(color: number, alpha: number) {
|
||||
// return ((color & 0xffffff00) | (((color & 0xff) * alpha) & 0xff)) >>> 0;
|
||||
// }
|
||||
|
||||
export const PALETTE_BATCH_BYTES_PER_VERTEX = 2 * 4 + 4 * 4 + 4 + 4;
|
||||
|
||||
export class PaletteSpriteBatch extends BaseSpriteBatch implements IPaletteSpriteBatch {
|
||||
depth = 1;
|
||||
palette = true;
|
||||
defaultPalette: Palette = createPalette(new Uint32Array(0));
|
||||
constructor(
|
||||
gl: WebGLRenderingContext, capacity: number, buffer: ArrayBuffer, vertexBuffer: WebGLBuffer, indexBuffer: WebGLBuffer
|
||||
gl: WebGLRenderingContext, vertexCapacityMax: number, indexBuffer: WebGLBuffer
|
||||
) {
|
||||
super(gl, capacity, buffer, vertexBuffer, indexBuffer, [
|
||||
{ name: 'position', size: 2 },
|
||||
super(gl, vertexCapacityMax, indexBuffer, [
|
||||
{ name: 'position', size: 3 },
|
||||
{ name: 'texcoord0', size: 4 }, //, type: gl.UNSIGNED_SHORT },
|
||||
{ name: 'color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
|
||||
{ name: 'color1', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
|
||||
@@ -149,37 +150,35 @@ export class PaletteSpriteBatch extends BaseSpriteBatch implements IPaletteSprit
|
||||
type: number, color: number, palette: Palette | undefined,
|
||||
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
|
||||
) {
|
||||
if (this.capacity <= this.spritesCount) {
|
||||
if (this.spritesCapacity <= this.spritesCount) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
this.index = pushQuad(
|
||||
this.vertices, //this.verticesUint32,
|
||||
this.vertices,
|
||||
this.transform, this.index, type, getColorFloat(color, this.globalAlpha),
|
||||
palette || this.defaultPalette, sx, sy, sw, sh, dx, dy, dw, dh,
|
||||
palette || this.defaultPalette, sx, sy, sw, sh, dx, dy, this.depth, dw, dh,
|
||||
);
|
||||
this.spritesCount++;
|
||||
this.tris += 2;
|
||||
}
|
||||
drawRect(color: number, x: number, y: number, w: number, h: number) {
|
||||
if (w !== 0 && h !== 0) {
|
||||
if (this.capacity <= this.spritesCount) {
|
||||
if (this.spritesCapacity <= this.spritesCount) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
const s = this.rectSprite || defaultRectSprite;
|
||||
this.index = pushQuad(
|
||||
this.vertices, //this.verticesUint32,
|
||||
this.vertices,
|
||||
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
|
||||
this.defaultPalette, s.x, s.y, s.w, s.h, x, y, w, h,
|
||||
this.defaultPalette, s.x, s.y, s.w, s.h, x, y, this.depth, w, h,
|
||||
);
|
||||
this.spritesCount++;
|
||||
this.tris += 2;
|
||||
}
|
||||
}
|
||||
drawSprite(s: Sprite, color: number, palette: Palette | undefined, x: number, y: number) {
|
||||
if (s.w !== 0 && s.h !== 0) {
|
||||
if (this.capacity <= this.spritesCount) {
|
||||
if (this.spritesCapacity <= this.spritesCount) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
@@ -222,21 +221,19 @@ export class PaletteSpriteBatch extends BaseSpriteBatch implements IPaletteSprit
|
||||
|
||||
if (w > 0 && h > 0) {
|
||||
this.index = pushQuad(
|
||||
this.vertices, //this.verticesUint32,
|
||||
this.vertices,
|
||||
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
|
||||
palette || this.defaultPalette, sx, sy, w, h, dx, dy, w, h,
|
||||
palette || this.defaultPalette, sx, sy, w, h, dx, dy, this.depth, w, h,
|
||||
);
|
||||
this.spritesCount++;
|
||||
this.tris += 2;
|
||||
}
|
||||
} else {
|
||||
this.index = pushQuad(
|
||||
this.vertices, //this.verticesUint32,
|
||||
this.vertices,
|
||||
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
|
||||
palette || this.defaultPalette, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h,
|
||||
palette || this.defaultPalette, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, this.depth, s.w, s.h,
|
||||
);
|
||||
this.spritesCount++;
|
||||
this.tris += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ varying vec2 textureCoord;
|
||||
varying vec4 vColor;
|
||||
|
||||
void main() {
|
||||
float d = clamp((1.0 - length(textureCoord)) + 0.1, 0.0, 1.0);
|
||||
float m = d * d * d;
|
||||
float d = clamp(1.1 - length(textureCoord), 0.0, 1.0);
|
||||
float m = d * d; // * d;
|
||||
|
||||
gl_FragColor = vec4(m, m, m, 1) * vColor;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// VERTEX
|
||||
|
||||
attribute vec2 position;
|
||||
attribute vec2 texcoords;
|
||||
|
||||
uniform mat4 transform;
|
||||
uniform vec2 textureSize;
|
||||
|
||||
varying vec2 textureCoord;
|
||||
|
||||
void main() {
|
||||
textureCoord = texcoords / textureSize;
|
||||
gl_Position = transform * vec4(position, 0, 1);
|
||||
}
|
||||
|
||||
// FRAGMENT
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform sampler2D sampler2;
|
||||
|
||||
varying vec2 textureCoord;
|
||||
|
||||
void main() {
|
||||
vec3 color1 = texture2D(sampler1, textureCoord).rgb;
|
||||
vec3 color2 = texture2D(sampler2, textureCoord).rgb;
|
||||
gl_FragColor = vec4(color1 * color2, 1.0);
|
||||
}
|
||||
@@ -1,26 +1,33 @@
|
||||
// VERTEX
|
||||
|
||||
attribute vec2 position;
|
||||
attribute vec3 position;
|
||||
attribute vec4 texcoords;
|
||||
attribute vec4 vertexColor;
|
||||
attribute vec4 vertexColor1;
|
||||
|
||||
uniform mat4 transform;
|
||||
uniform vec4 lighting;
|
||||
uniform float textureSize;
|
||||
|
||||
varying vec4 textureCoord;
|
||||
varying vec4 vColor;
|
||||
varying vec4 vColor1;
|
||||
|
||||
void main() {
|
||||
textureCoord = texcoords;
|
||||
textureCoord = vec4(texcoords.xy * textureSize, texcoords.zw);
|
||||
// float f = texcoords.z;
|
||||
// float fr = fract(texcoords.z);
|
||||
// textureCoord.z = fr;
|
||||
// textureCoord.w = (f - fr) / 1024.0;
|
||||
vColor = vertexColor * lighting;
|
||||
vColor1 = vertexColor1;
|
||||
gl_Position = transform * vec4(position, 0, 1);
|
||||
float depth = position.z;
|
||||
#ifdef DEPTH_BUFFERED
|
||||
if (vColor.a < 0.975) {
|
||||
depth = 0.0;
|
||||
}
|
||||
#endif
|
||||
gl_Position = transform * vec4(position.xy, depth, 1);
|
||||
}
|
||||
|
||||
// FRAGMENT
|
||||
@@ -30,14 +37,13 @@ precision mediump float;
|
||||
uniform sampler2D sampler1; // sprite
|
||||
uniform sampler2D sampler2; // palette
|
||||
uniform float pixelSize;
|
||||
uniform float textureSize;
|
||||
|
||||
varying vec4 textureCoord;
|
||||
varying vec4 vColor;
|
||||
varying vec4 vColor1;
|
||||
|
||||
void main() {
|
||||
vec4 sprite = texture2D(sampler1, textureCoord.xy / textureSize);
|
||||
vec4 sprite = texture2D(sampler1, textureCoord.xy);
|
||||
|
||||
float shade = clamp(sprite.g + vColor1.a, 0.0, 1.0);
|
||||
|
||||
@@ -48,4 +54,10 @@ void main() {
|
||||
vec4 palette = texture2D(sampler2, vec2(paletteCoord.x + paletteIndex * pixelSize, paletteCoord.y));
|
||||
|
||||
gl_FragColor = vec4(palette.xyz * shade, palette.w) * vColor;
|
||||
|
||||
#ifdef DEPTH_BUFFERED
|
||||
if (gl_FragColor.a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
// VERTEX
|
||||
|
||||
attribute vec2 position;
|
||||
attribute vec3 position;
|
||||
attribute vec2 texcoords;
|
||||
attribute vec4 vertexColor;
|
||||
|
||||
uniform mat4 transform;
|
||||
uniform vec4 lighting;
|
||||
uniform vec2 textureSize;
|
||||
|
||||
varying vec2 textureCoord;
|
||||
varying vec4 vColor;
|
||||
|
||||
void main() {
|
||||
textureCoord = texcoords;
|
||||
textureCoord = texcoords / textureSize;
|
||||
vColor = vertexColor * lighting;
|
||||
gl_Position = transform * vec4(position, 0, 1);
|
||||
gl_Position = transform * vec4(position, 1);
|
||||
}
|
||||
|
||||
// FRAGMENT
|
||||
@@ -21,11 +22,14 @@ void main() {
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform float textureSize;
|
||||
|
||||
varying vec2 textureCoord;
|
||||
varying vec4 vColor;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(sampler1, textureCoord / textureSize) * vColor;
|
||||
gl_FragColor = texture2D(sampler1, textureCoord);
|
||||
|
||||
#ifdef USE_COLOR
|
||||
gl_FragColor *= vColor;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import { Sprite, SpriteBatch as ISpriteBatch, Matrix2D } from '../common/interfa
|
||||
import { BaseSpriteBatch, getColorFloat } from './baseSpriteBatch';
|
||||
|
||||
function vertex(
|
||||
vertices: Float32Array, _verticesUint32: Uint32Array, index: number,
|
||||
x: number, y: number, u: number, v: number, c: number, transform: Matrix2D
|
||||
vertices: Float32Array, index: number,
|
||||
x: number, y: number, depth: number, u: number, v: number, c: number, transform: Matrix2D
|
||||
) {
|
||||
vertices[index++] = transform[0] * x + transform[2] * y + transform[4];
|
||||
vertices[index++] = transform[1] * x + transform[3] * y + transform[5];
|
||||
vertices[index++] = depth;
|
||||
vertices[index++] = u;
|
||||
vertices[index++] = v;
|
||||
vertices[index++] = c;
|
||||
@@ -18,16 +19,9 @@ function vertex(
|
||||
|
||||
export class SpriteBatch extends BaseSpriteBatch implements ISpriteBatch {
|
||||
palette = false;
|
||||
depth = 0;
|
||||
constructor(
|
||||
gl: WebGLRenderingContext,
|
||||
capacity: number,
|
||||
buffer: ArrayBuffer,
|
||||
vertexBuffer: WebGLBuffer,
|
||||
indexBuffer: WebGLBuffer
|
||||
) {
|
||||
super(gl, capacity, buffer, vertexBuffer, indexBuffer, [
|
||||
{ name: 'position', size: 2 },
|
||||
constructor(gl: WebGLRenderingContext, vertexCapacityMax: number, indexBuffer: WebGLBuffer) {
|
||||
super(gl, vertexCapacityMax, indexBuffer, [
|
||||
{ name: 'position', size: 3 },
|
||||
{ name: 'texcoord0', size: 2 }, // , type: gl.UNSIGNED_SHORT },
|
||||
{ name: 'color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
|
||||
]);
|
||||
@@ -36,7 +30,7 @@ export class SpriteBatch extends BaseSpriteBatch implements ISpriteBatch {
|
||||
color: number, sx: number, sy: number, sw: number, sh: number,
|
||||
dx: number, dy: number, dw: number, dh: number
|
||||
) {
|
||||
if (this.capacity <= this.spritesCount) {
|
||||
if (this.spritesCapacity <= this.spritesCount) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
@@ -51,19 +45,17 @@ export class SpriteBatch extends BaseSpriteBatch implements ISpriteBatch {
|
||||
const v2 = sy + sh;
|
||||
|
||||
const vertices = this.vertices;
|
||||
const verticesUint32 = this.verticesUint32;
|
||||
const transform = this.transform;
|
||||
|
||||
const index = this.index;
|
||||
|
||||
vertex(vertices, verticesUint32, index, dx, dy, u1, v1, c, transform);
|
||||
vertex(vertices, verticesUint32, index + 5, x2, dy, u2, v1, c, transform);
|
||||
vertex(vertices, verticesUint32, index + 10, x2, y2, u2, v2, c, transform);
|
||||
vertex(vertices, verticesUint32, index + 15, dx, y2, u1, v2, c, transform);
|
||||
vertex(vertices, index, dx, dy, this.depth, u1, v1, c, transform);
|
||||
vertex(vertices, index + 6, x2, dy, this.depth, u2, v1, c, transform);
|
||||
vertex(vertices, index + 12, x2, y2, this.depth, u2, v2, c, transform);
|
||||
vertex(vertices, index + 18, dx, y2, this.depth, u1, v2, c, transform);
|
||||
|
||||
this.index += 20;
|
||||
this.index += 24;
|
||||
this.spritesCount++;
|
||||
this.tris += 2;
|
||||
}
|
||||
drawRect(color: number, x: number, y: number, w: number, h: number) {
|
||||
if (w && h) {
|
||||
|
||||
@@ -4,7 +4,8 @@ import { createTexture, disposeTexture } from './webgl/texture2d';
|
||||
export function createTexturesForSpriteSheets(gl: WebGLRenderingContext, sheets: SpriteSheet[], texture = createTexture) {
|
||||
sheets.forEach(sheet => {
|
||||
if (sheet.data) {
|
||||
sheet.texture = texture(gl, sheet.data);
|
||||
let format = sheet.isSingleChannel ? gl.LUMINANCE : gl.RGBA;
|
||||
sheet.texture = texture(gl, sheet.data, format);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,28 +1,50 @@
|
||||
import { Texture2D, createEmptyTexture, resizeTexture } from './texture2d';
|
||||
import { Texture2D, createEmptyTexture } from './texture2d';
|
||||
|
||||
export interface FrameBuffer {
|
||||
handle: WebGLFramebuffer;
|
||||
texture: Texture2D;
|
||||
colorTexture: Texture2D;
|
||||
depthStencilRenderbuffer: WebGLRenderbuffer | null;
|
||||
width: number;
|
||||
height: number;
|
||||
owningDepthStencil: boolean;
|
||||
}
|
||||
|
||||
export function createFrameBuffer(gl: WebGLRenderingContext, width: number, height: number): FrameBuffer {
|
||||
export function createFrameBuffer(
|
||||
gl: WebGLRenderingContext, target: FrameBuffer, width: number, height: number, useLinearMagnify: boolean,
|
||||
createDepthStencil: boolean, depthStencilRenderbuffer: WebGLRenderbuffer | null
|
||||
) {
|
||||
const handle = gl.createFramebuffer();
|
||||
|
||||
if (!handle) {
|
||||
throw new Error('Failed to create frame buffer');
|
||||
}
|
||||
|
||||
const texture = createEmptyTexture(gl, width, height, gl.RGB);
|
||||
return { handle, texture, width, height };
|
||||
const resources = createFrameBufferResources(gl, width, height, createDepthStencil, useLinearMagnify);
|
||||
target.handle = handle;
|
||||
target.colorTexture = resources.colorTexture;
|
||||
target.depthStencilRenderbuffer = resources.depthStencilRenderbuffer;
|
||||
target.width = width;
|
||||
target.height = height;
|
||||
target.owningDepthStencil = createDepthStencil;
|
||||
|
||||
if (!createDepthStencil) {
|
||||
target.depthStencilRenderbuffer = depthStencilRenderbuffer;
|
||||
}
|
||||
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
|
||||
bindFrameBufferAttachments(gl, target);
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
}
|
||||
|
||||
export function disposeFrameBuffer(gl: WebGLRenderingContext | undefined, buffer: FrameBuffer | undefined) {
|
||||
export function disposeFrameBuffer(gl?: WebGLRenderingContext, buffer?: FrameBuffer) {
|
||||
try {
|
||||
if (gl && buffer) {
|
||||
gl.deleteFramebuffer(buffer.handle);
|
||||
gl.deleteTexture(buffer.texture.handle);
|
||||
if (buffer.colorTexture) {
|
||||
gl.deleteTexture(buffer.colorTexture.handle);
|
||||
}
|
||||
if (buffer.depthStencilRenderbuffer && buffer.owningDepthStencil) {
|
||||
gl.deleteRenderbuffer(buffer.depthStencilRenderbuffer);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
DEVELOPMENT && console.error(e);
|
||||
@@ -31,18 +53,52 @@ export function disposeFrameBuffer(gl: WebGLRenderingContext | undefined, buffer
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resizeFrameBuffer(gl: WebGLRenderingContext, frameBuffer: FrameBuffer, width: number, height: number) {
|
||||
resizeTexture(gl, frameBuffer.texture, width, height);
|
||||
frameBuffer.width = width;
|
||||
frameBuffer.height = height;
|
||||
}
|
||||
|
||||
export function bindFrameBuffer(gl: WebGLRenderingContext, { handle, texture }: FrameBuffer) {
|
||||
export function bindFrameBuffer(gl: WebGLRenderingContext, { handle }: FrameBuffer) {
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture.handle, 0);
|
||||
}
|
||||
|
||||
export function unbindFrameBuffer(gl: WebGLRenderingContext) {
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0);
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
}
|
||||
|
||||
function bindFrameBufferAttachments(gl: WebGLRenderingContext, { colorTexture, depthStencilRenderbuffer }: FrameBuffer) {
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture.handle, 0);
|
||||
if (depthStencilRenderbuffer) {
|
||||
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilRenderbuffer);
|
||||
}
|
||||
|
||||
gl.depthMask(true);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
gl.depthMask(false);
|
||||
|
||||
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
||||
throw new Error('Failed to set framebuffer attachments');
|
||||
}
|
||||
}
|
||||
|
||||
function createFrameBufferResources(
|
||||
gl: WebGLRenderingContext, width: number, height: number, createDepthBuffer: boolean, useLinearMagnify: boolean
|
||||
) {
|
||||
const colorTexture = createEmptyTexture(gl, false, width, height, gl.RGB);
|
||||
if (!colorTexture) {
|
||||
throw new Error('Failed to create frame buffer\'s color texture');
|
||||
}
|
||||
if (useLinearMagnify) {
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
}
|
||||
|
||||
let depthStencilRenderbuffer: WebGLRenderbuffer | null = null;
|
||||
if (createDepthBuffer) {
|
||||
depthStencilRenderbuffer = gl.createRenderbuffer();
|
||||
if (depthStencilRenderbuffer) {
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilRenderbuffer);
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
||||
}
|
||||
else {
|
||||
console.warn('depth/stencil is not available');
|
||||
}
|
||||
}
|
||||
|
||||
return { colorTexture, depthStencilRenderbuffer };
|
||||
}
|
||||
|
||||
@@ -293,9 +293,11 @@ function throwFBOError(gl: WebGL, status: number, message = '') {
|
||||
}
|
||||
|
||||
function initTexture(gl: WebGL, width: number, height: number, type: number, format: number, attachment: number) {
|
||||
const texture = createEmptyTexture(gl, width, height, format, type);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture.handle, 0);
|
||||
const texture = createEmptyTexture(gl, true, width, height, format, type);
|
||||
if (texture) {
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture.handle, 0);
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ class VAONative implements VAO {
|
||||
}
|
||||
dispose() {
|
||||
this.ext.deleteVertexArrayOES(this.handle);
|
||||
this.handle = {};
|
||||
}
|
||||
update(attributes: VAOAttributes[], elements: WebGLBuffer | null, elementsType?: number) {
|
||||
this.bind();
|
||||
|
||||
@@ -1,85 +1,98 @@
|
||||
export interface Shader {
|
||||
export interface ShaderProgramData {
|
||||
program: WebGLProgram;
|
||||
vertexShader: WebGLShader;
|
||||
fragmentShader: WebGLShader;
|
||||
uniforms: { [key: string]: WebGLUniformLocation; };
|
||||
}
|
||||
|
||||
export function createShader(gl: WebGLRenderingContext, source: string | { vertex: string; fragment: string; }): Shader {
|
||||
if (typeof source === 'string') {
|
||||
const index = source.indexOf('// FRAGMENT');
|
||||
export class Shader {
|
||||
private programs: { [key: string]: ShaderProgramData; } = {};
|
||||
private vertexCode: string;
|
||||
private fragmentCode: string;
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error(`Missing fragment shader separator`);
|
||||
constructor(source: string | { vertex: string; fragment: string; }) {
|
||||
if (typeof source === 'string') {
|
||||
const index = source.indexOf('// FRAGMENT');
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error(`Missing fragment shader separator`);
|
||||
}
|
||||
|
||||
source = {
|
||||
vertex: source.substring(0, index),
|
||||
fragment: source.substring(index),
|
||||
};
|
||||
}
|
||||
|
||||
source = {
|
||||
vertex: source.substring(0, index),
|
||||
fragment: source.substring(index),
|
||||
};
|
||||
this.vertexCode = source.vertex;
|
||||
this.fragmentCode = source.fragment;
|
||||
}
|
||||
|
||||
const vertexShader = createWebGLShader(gl, gl.VERTEX_SHADER, source.vertex);
|
||||
const fragmentShader = createWebGLShader(gl, gl.FRAGMENT_SHADER, source.fragment);
|
||||
const program = gl.createProgram();
|
||||
compile(gl: WebGLRenderingContext, defines: string[]) {
|
||||
defines.sort();
|
||||
let definesString = defines.reduce((prev, cur) => prev + cur, '');
|
||||
|
||||
if (!program) {
|
||||
throw new Error('Failed to create shader program');
|
||||
}
|
||||
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
|
||||
const attribs = source.vertex.match(/^attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/mg)!;
|
||||
|
||||
for (var i = 0; i < attribs.length; ++i) {
|
||||
const [, name] = /attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/.exec(attribs[i])!;
|
||||
gl.bindAttribLocation(program, i, name);
|
||||
}
|
||||
|
||||
gl.linkProgram(program);
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
throw new Error('Failed to link shader program');
|
||||
}
|
||||
|
||||
gl.useProgram(program);
|
||||
|
||||
const uniforms: any = {};
|
||||
const samplers: string[] = [];
|
||||
|
||||
for (let i = 0; i < gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); i++) {
|
||||
const info = gl.getActiveUniform(program, i)!;
|
||||
uniforms[info.name] = gl.getUniformLocation(program, info.name);
|
||||
|
||||
if (!uniforms[info.name]) {
|
||||
throw new Error(`Failed to get uniform location (${info.name})`);
|
||||
let data = this.programs[definesString];
|
||||
if (!data) {
|
||||
data = Shader.compileShader(gl, this.vertexCode, this.fragmentCode, defines);
|
||||
this.programs[definesString] = data;
|
||||
}
|
||||
|
||||
if (info.type === gl.SAMPLER_2D) {
|
||||
samplers.push(info.name);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
samplers.sort().forEach((name, i) => gl.uniform1i(uniforms[name], i));
|
||||
|
||||
gl.useProgram(null);
|
||||
private static compileShader(gl: WebGLRenderingContext, vertexCode: string, fragmentCode: string, defines: string[]) {
|
||||
let shaderDefines = defines.reduce((prev, cur) => prev + '#define ' + cur + '\n', '');
|
||||
|
||||
return { program, vertexShader, fragmentShader, uniforms };
|
||||
}
|
||||
const vertexShader = createWebGLShader(gl, gl.VERTEX_SHADER, shaderDefines + vertexCode);
|
||||
const fragmentShader = createWebGLShader(gl, gl.FRAGMENT_SHADER, shaderDefines + fragmentCode);
|
||||
const program = gl.createProgram();
|
||||
|
||||
export function disposeShader(gl: WebGLRenderingContext | undefined, shader: Shader | undefined) {
|
||||
try {
|
||||
if (gl && shader) {
|
||||
gl.deleteProgram(shader.program);
|
||||
gl.deleteShader(shader.vertexShader);
|
||||
gl.deleteShader(shader.fragmentShader);
|
||||
if (!program) {
|
||||
throw new Error('Failed to create shader program');
|
||||
}
|
||||
} catch (e) {
|
||||
DEVELOPMENT && console.error(e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
|
||||
const attribs = vertexCode.match(/^attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/mg)!;
|
||||
|
||||
for (var i = 0; i < attribs.length; ++i) {
|
||||
const [, name] = /attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/.exec(attribs[i])!;
|
||||
gl.bindAttribLocation(program, i, name);
|
||||
}
|
||||
|
||||
gl.linkProgram(program);
|
||||
gl.deleteShader(vertexShader);
|
||||
gl.deleteShader(fragmentShader);
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
throw new Error('Failed to link shader program');
|
||||
}
|
||||
|
||||
gl.useProgram(program);
|
||||
|
||||
const uniforms: any = {};
|
||||
const samplers: string[] = [];
|
||||
|
||||
for (let i = 0; i < gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); i++) {
|
||||
const info = gl.getActiveUniform(program, i)!;
|
||||
uniforms[info.name] = gl.getUniformLocation(program, info.name);
|
||||
|
||||
if (!uniforms[info.name]) {
|
||||
throw new Error(`Failed to get uniform location (${info.name})`);
|
||||
}
|
||||
|
||||
if (info.type === gl.SAMPLER_2D) {
|
||||
samplers.push(info.name);
|
||||
}
|
||||
}
|
||||
|
||||
samplers.sort().forEach((name, i) => gl.uniform1i(uniforms[name], i));
|
||||
|
||||
gl.useProgram(null);
|
||||
|
||||
return {program, uniforms};
|
||||
}
|
||||
}
|
||||
|
||||
function createWebGLShader(gl: WebGLRenderingContext, type: number, source: string) {
|
||||
@@ -98,3 +111,13 @@ function createWebGLShader(gl: WebGLRenderingContext, type: number, source: stri
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
export function disposeShaderProgramData(gl: WebGLRenderingContext, data: ShaderProgramData) {
|
||||
try {
|
||||
if (gl) {
|
||||
gl.deleteProgram(data.program);
|
||||
}
|
||||
} catch (e) {
|
||||
DEVELOPMENT && console.error(e);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { clearWebGLErrors, hasWebGLErrors, isWebGL2 } from './webglUtils';
|
||||
|
||||
type WebGL = WebGLRenderingContext;
|
||||
type Pixels = ImageBitmap | ImageData | HTMLVideoElement | HTMLImageElement | HTMLCanvasElement;
|
||||
|
||||
@@ -7,9 +9,12 @@ export interface Texture2D {
|
||||
height: number;
|
||||
format: number;
|
||||
type: number;
|
||||
internalFormat: number;
|
||||
}
|
||||
|
||||
export function createEmptyTexture(gl: WebGL, width: number, height: number, format?: number, type?: number): Texture2D {
|
||||
export function createEmptyTexture(
|
||||
gl: WebGL, isResizable: boolean, width: number, height: number, format?: number, type?: number, internalFormat?: number
|
||||
) {
|
||||
if (format === undefined) {
|
||||
format = gl.RGBA;
|
||||
}
|
||||
@@ -18,6 +23,10 @@ export function createEmptyTexture(gl: WebGL, width: number, height: number, for
|
||||
type = gl.UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
if (internalFormat === undefined) {
|
||||
internalFormat = format;
|
||||
}
|
||||
|
||||
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null;
|
||||
|
||||
if (maxTextureSize != null && (width < 0 || width > maxTextureSize || height < 0 || height > maxTextureSize)) {
|
||||
@@ -28,12 +37,36 @@ export function createEmptyTexture(gl: WebGL, width: number, height: number, for
|
||||
throw new Error('Floating point textures not supported on this platform');
|
||||
}
|
||||
|
||||
clearWebGLErrors(gl);
|
||||
const handle = createTextureHandle(gl);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null);
|
||||
return { handle, width, height, format, type };
|
||||
if (!isResizable && isWebGL2(gl)) {
|
||||
const gl2 = gl as WebGL2RenderingContext;
|
||||
let format2: number;
|
||||
if (internalFormat === gl.RGB) {
|
||||
format2 = gl2.RGB8;
|
||||
}
|
||||
else if (internalFormat === gl.RGBA) {
|
||||
format2 = gl2.RGBA8;
|
||||
}
|
||||
else {
|
||||
throw new Error('Cannot convert internal format into WebGL2 format');
|
||||
}
|
||||
gl2.texStorage2D(gl.TEXTURE_2D, 1, format2, width, height);
|
||||
}
|
||||
else {
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, null);
|
||||
}
|
||||
if (hasWebGLErrors(gl)) {
|
||||
console.warn('createEmptyTexture failed due to a WebGL error');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { handle, width, height, format, type, internalFormat };
|
||||
}
|
||||
|
||||
export function createTexture(gl: WebGL, data: Pixels, format?: number, type?: number): Texture2D {
|
||||
export function createTexture(
|
||||
gl: WebGL, data: Pixels, format?: number, type?: number, internalFormat?: number
|
||||
) {
|
||||
if (format === undefined) {
|
||||
format = gl.RGBA;
|
||||
}
|
||||
@@ -42,9 +75,19 @@ export function createTexture(gl: WebGL, data: Pixels, format?: number, type?: n
|
||||
type = gl.UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
if (internalFormat === undefined) {
|
||||
internalFormat = format;
|
||||
}
|
||||
|
||||
clearWebGLErrors(gl);
|
||||
const handle = createTextureHandle(gl);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, format, format, type, data);
|
||||
return { handle, width: data.width, height: data.height, format, type };
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
|
||||
if (hasWebGLErrors(gl)) {
|
||||
console.warn('createTexture failed due to a WebGL error');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { handle, width: data.width, height: data.height, format, type, internalFormat };
|
||||
}
|
||||
|
||||
export function disposeTexture(gl: WebGL | undefined, texture: Texture2D | undefined): undefined {
|
||||
@@ -68,7 +111,7 @@ export function resizeTexture(gl: WebGL, texture: Texture2D, width: number, heig
|
||||
width = width | 0;
|
||||
height = height | 0;
|
||||
|
||||
const { format, type } = texture;
|
||||
const { format, type, internalFormat } = texture;
|
||||
const maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null;
|
||||
|
||||
if (maxSize != null && (width < 0 || width > maxSize || height < 0 || height > maxSize)) {
|
||||
@@ -78,7 +121,7 @@ export function resizeTexture(gl: WebGL, texture: Texture2D, width: number, heig
|
||||
texture.width = width;
|
||||
texture.height = height;
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, null);
|
||||
}
|
||||
|
||||
function createTextureHandle(gl: WebGL) {
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { makeDebugContext } from 'webgl-debug';
|
||||
import { WEBGL_CREATION_ERROR } from '../../common/errors';
|
||||
|
||||
export function getRenderTargetSize(width: number, height: number) {
|
||||
const max = Math.max(width, height);
|
||||
let pow = 256;
|
||||
|
||||
while (pow < max) {
|
||||
pow *= 2;
|
||||
}
|
||||
|
||||
return pow;
|
||||
}
|
||||
|
||||
export function getWebGLContext(canvas: HTMLCanvasElement): WebGLRenderingContext {
|
||||
const options: WebGLContextAttributes = {
|
||||
alpha: false,
|
||||
preserveDrawingBuffer: false,
|
||||
premultipliedAlpha: false,
|
||||
antialias: false,
|
||||
depth: false
|
||||
};
|
||||
|
||||
const gl = canvas.getContext('webgl2', options)
|
||||
let gl = canvas.getContext('webgl2', options)
|
||||
|| canvas.getContext('webgl', options)
|
||||
|| canvas.getContext('experimental-webgl', options);
|
||||
|
||||
@@ -26,6 +18,20 @@ export function getWebGLContext(canvas: HTMLCanvasElement): WebGLRenderingContex
|
||||
throw new Error(WEBGL_CREATION_ERROR);
|
||||
}
|
||||
|
||||
// debug context will check every GL call for errors and will
|
||||
// emit a console message with a stack trace if there was an error,
|
||||
// but it can slow execution down by more than 2x depending
|
||||
// on the platform so it isn't on by default even in DEVELOPMENT
|
||||
const useDebugContext = false;
|
||||
|
||||
if (useDebugContext) {
|
||||
gl = makeDebugContext(gl);
|
||||
|
||||
if (!gl) {
|
||||
throw new Error(WEBGL_CREATION_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return gl;
|
||||
}
|
||||
|
||||
@@ -65,3 +71,11 @@ export function unbindAllTexturesAndBuffers(gl: WebGLRenderingContext) {
|
||||
DEVELOPMENT && console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearWebGLErrors(gl: WebGLRenderingContext) {
|
||||
while (hasWebGLErrors(gl));
|
||||
}
|
||||
|
||||
export function hasWebGLErrors(gl: WebGLRenderingContext) {
|
||||
return gl.getError() !== gl.NO_ERROR;
|
||||
}
|
||||
|
||||
@@ -372,10 +372,6 @@ export function createCommands(world: World): Command[] {
|
||||
const query = { account: client.account._id, name: { $regex: regex } };
|
||||
await swapCharacter(client, world, query);
|
||||
}),
|
||||
command(['s1'], '', 'sup1', shouldNotBeCalled),
|
||||
command(['s2'], '', 'sup2', shouldNotBeCalled),
|
||||
command(['s3'], '', 'sup3', shouldNotBeCalled),
|
||||
command(['ss'], '/ss - supporter text', 'sup1', shouldNotBeCalled),
|
||||
|
||||
// mod
|
||||
adminModChat(['m'], '/m - mod text', 'mod', MessageType.Mod),
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import '../lib';
|
||||
import { expect } from 'chai';
|
||||
import { stub } from 'sinon';
|
||||
import { getRenderTargetSize, isWebGL2, getWebGLContext } from '../../graphics/webgl/webglUtils';
|
||||
import { isWebGL2, getWebGLContext } from '../../graphics/webgl/webglUtils';
|
||||
import { WEBGL_CREATION_ERROR } from '../../common/errors';
|
||||
|
||||
describe('webglUtils', () => {
|
||||
describe('getRenderTargetSize()', () => {
|
||||
it('returns correct size for 150x200', () => {
|
||||
expect(getRenderTargetSize(150, 200)).equal(256);
|
||||
});
|
||||
|
||||
it('returns correct size for 256x512', () => {
|
||||
expect(getRenderTargetSize(256, 512)).equal(512);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWebGLContext()', () => {
|
||||
it('gets weblg2 context', () => {
|
||||
const context = {} as any;
|
||||
|
||||
@@ -13,11 +13,20 @@ describe('mat4', () => {
|
||||
});
|
||||
|
||||
describe('ortho()', () => {
|
||||
expect(ortho(createMat4(), 100, 200, 300, 400, 10, 20)).eql(new Float32Array([
|
||||
expect(ortho(createMat4(), 100, 200, 300, 400, 10, 20, true)).eql(new Float32Array([
|
||||
0.019999999552965164, 0, 0, 0,
|
||||
0, 0.019999999552965164, 0, 0,
|
||||
0, 0, -0.20000000298023224, 0,
|
||||
-3, -7, -3, 1
|
||||
]));
|
||||
});
|
||||
|
||||
describe('orthoNoZ()', () => {
|
||||
expect(ortho(createMat4(), 100, 200, 300, 400, 10, 20, false)).eql(new Float32Array([
|
||||
0.019999999552965164, 0, 0, 0,
|
||||
0, 0.019999999552965164, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
-3, -7, 0, 1
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,22 @@ describe('spriteSheetUtils', () => {
|
||||
const tex = {} as any;
|
||||
const createTexture = stub().returns(tex);
|
||||
const sheet: SpriteSheet[] = [
|
||||
{ sprites: [], src: 'foo', data, texture: undefined, palette: false },
|
||||
{ sprites: [], src: 'foo', data, texture: undefined, isSingleChannel: false, palette: false },
|
||||
];
|
||||
|
||||
createTexturesForSpriteSheets(gl, sheet, createTexture);
|
||||
|
||||
assert.calledWith(createTexture, gl, data);
|
||||
expect(sheet[0].texture).equal(tex);
|
||||
});
|
||||
|
||||
it('creates single channel texture from image', () => {
|
||||
const gl = {} as any;
|
||||
const data = createImageData();
|
||||
const tex = {} as any;
|
||||
const createTexture = stub().returns(tex);
|
||||
const sheet: SpriteSheet[] = [
|
||||
{ sprites: [], src: 'foo', data, texture: undefined, isSingleChannel: true, palette: false },
|
||||
];
|
||||
|
||||
createTexturesForSpriteSheets(gl, sheet, createTexture);
|
||||
@@ -29,7 +44,8 @@ describe('spriteSheetUtils', () => {
|
||||
it('handles empty sprites', () => {
|
||||
const createTexture = stub().returns({});
|
||||
const sheet: SpriteSheet[] = [
|
||||
{ sprites: [undefined] as any, src: 'foo', data: createImageData(), texture: undefined, palette: false },
|
||||
{ sprites: [undefined] as any, src: 'foo', data: createImageData(),
|
||||
isSingleChannel: false, texture: undefined, palette: false },
|
||||
];
|
||||
|
||||
createTexturesForSpriteSheets({} as any, sheet, createTexture);
|
||||
|
||||
@@ -38,14 +38,14 @@ export function mock<T>(ctor: new (...args: any[]) => T, fields: any = {}): T {
|
||||
|
||||
export function entity(id: number, x = 0, y = 0, type = 0, more: Partial<Entity> = {}): Entity {
|
||||
return {
|
||||
id, x, y, z: 0, vx: 0, vy: 0, type, order: 0, state: 0, playerState: 0, flags: 0, timestamp: 0,
|
||||
id, x, y, z: 0, vx: 0, vy: 0, depth: 0, type, order: 0, state: 0, playerState: 0, flags: 0, timestamp: 0,
|
||||
options: {}, ...more
|
||||
};
|
||||
}
|
||||
|
||||
export function serverEntity(id: number, x = 0, y = 0, type = 0, more: Partial<ServerEntity> = {}): ServerEntity {
|
||||
return {
|
||||
id, x, y, z: 0, vx: 0, vy: 0, type, order: 0, state: 0, playerState: 0, flags: 0, timestamp: 0,
|
||||
id, x, y, z: 0, vx: 0, vy: 0, depth: 0, type, order: 0, state: 0, playerState: 0, flags: 0, timestamp: 0,
|
||||
options: {}, ...more
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,8 +33,9 @@ import { sheets, Sheet } from '../common/sheets';
|
||||
import { colorToCSS } from '../common/color';
|
||||
import { bitWriter } from '../common/bitUtils';
|
||||
|
||||
const head0Indices = [0, 1, 2, 4, 7, 8, 9, 10, 12, 13, 14, 16, 18, 19]; // regular
|
||||
const head1Indices = [0, 1, 3, 5, 6, 8, 9, 11, 12, 13, 15, 17, 18, 19]; // clipped
|
||||
// face markings
|
||||
const head0Indices = [0, 1, 2, 4, 7, 8, 9, 10, 12, 13, 14, 16, 18, 19]; // regular (right facing)
|
||||
const head1Indices = [0, 1, 3, 5, 6, 8, 9, 11, 12, 13, 15, 17, 18, 19]; // clipped (left facing, displayed in char creator)
|
||||
|
||||
const MAX_PALETTE_SIZE = 128;
|
||||
const { assetsPath } = require('../../../config.json');
|
||||
@@ -293,7 +294,13 @@ function importSprites({ sprites, objects2 }: Result, sheet: Sheet) {
|
||||
|
||||
const animations = sets.map(({ layerName, name, mask, reverse, maskFile, mirror, mirrorOffsetX }) => {
|
||||
const layer = findLayerSafe(layerName, psd);
|
||||
let color = getLayerCanvasSafe('color', layer);
|
||||
|
||||
let color = getLayerCanvas('color', layer);
|
||||
if (!color) {
|
||||
color = createExtCanvas(psd.width, psd.height, layer.info);
|
||||
console.warn('Layer ' + layer.info + ' is empty');
|
||||
}
|
||||
|
||||
const extraCanvas = sheet.extra ? getLayerCanvas('extra', layer) : undefined;
|
||||
const patterns = getPatternCanvases(layer);
|
||||
|
||||
@@ -315,7 +322,7 @@ function importSprites({ sprites, objects2 }: Result, sheet: Sheet) {
|
||||
const { x, y } = importOffsets && importOffsets[frame] || { x: 0, y: 0 };
|
||||
const getAndPadBase = (canvas: ExtCanvas) => padCanvas(getImage(canvas, frame, type), -x, -y);
|
||||
const getAndPad = mirror ? (canvas: ExtCanvas) => mirrorCanvas(getAndPadBase(canvas), mirrorOffsetX) : getAndPadBase;
|
||||
const accessoryFrame = getAndPad(color);
|
||||
const accessoryFrame = getAndPad(color!);
|
||||
const extraFrame = extraCanvas && getAndPad(extraCanvas);
|
||||
|
||||
if (isCanvasEmpty(accessoryFrame)) {
|
||||
@@ -405,7 +412,7 @@ function importSprites({ sprites, objects2 }: Result, sheet: Sheet) {
|
||||
}
|
||||
|
||||
if (sheet.single) {
|
||||
objects2[`${name}: StaticSprites${hasExtra ? 'Extra' : ''}`] = frames[0];
|
||||
objects2[`${name}: StaticSprites${hasExtra ? 'Extra' : ''}`] = frames[0] || [];
|
||||
} else {
|
||||
objects2[`${name}: AnimatedSprites`] = frames;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ const palettes: Uint32Array[] = createPalettes('/*COLORS*/', [
|
||||
export const spriteSheets: SpriteSheet[] = [
|
||||
{
|
||||
src: '/*SPRITE_SHEET*/',
|
||||
data: undefined, texture: undefined, sprites: sprites, palette: false
|
||||
data: undefined, texture: undefined, sprites: sprites, isSingleChannel: false, palette: false
|
||||
},
|
||||
{
|
||||
src: '/*SPRITE_SHEET_PALETTE*/', srcA: '/*SPRITE_SHEET_PALETTE_ALPHA*/',
|
||||
data: undefined, texture: undefined, sprites: sprites2, palette: true
|
||||
data: undefined, texture: undefined, sprites: sprites2, isSingleChannel: false, palette: true
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
/// <reference path="my/fix.d.ts" />
|
||||
/// <reference path="my/canvas.d.ts" />
|
||||
/// <reference path="my/node-async.d.ts" />
|
||||
/// <reference path="my/patreon.d.ts" />
|
||||
/// <reference path="my/webgl-debug.d.ts" />
|
||||
Vendored
-62
@@ -1,62 +0,0 @@
|
||||
declare module 'patreon' {
|
||||
export interface PatronDataRelation {
|
||||
data: {
|
||||
id: string;
|
||||
type: string;
|
||||
};
|
||||
links: {
|
||||
related: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PatronDataEntry {
|
||||
attributes: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
amount_cents?: number;
|
||||
created_at?: string;
|
||||
declined_since?: string | null;
|
||||
patron_pays_fees?: boolean;
|
||||
pledge_cap_cents?: number;
|
||||
total_historical_amount_cents?: number;
|
||||
};
|
||||
id: string;
|
||||
relationships: {
|
||||
patron: PatronDataRelation;
|
||||
reward: PatronDataRelation;
|
||||
};
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface PatronData {
|
||||
rawJson: {
|
||||
data: PatronDataEntry[];
|
||||
included: PatronDataEntry[];
|
||||
links: {
|
||||
next?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface PatreonClient {
|
||||
(pathname: string): Promise<PatronData>;
|
||||
getStore(): any;
|
||||
setStore(store: { sync: () => void }): void;
|
||||
}
|
||||
|
||||
export interface PatreonTokens {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
expires_in: string;
|
||||
scope: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
export interface PatreonOAuthClient {
|
||||
getTokens(redirectCode: string, redirectUri: string): Promise<PatreonTokens>;
|
||||
refreshToken(refreshToken: string): Promise<any>;
|
||||
}
|
||||
|
||||
export function patreon(accessToken: string): PatreonClient;
|
||||
export function oauth(clientId: string, clientSecret: string): PatreonOAuthClient;
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
interface WebGLRenderingContext {
|
||||
readonly MAX_ELEMENT_INDEX: number;
|
||||
}
|
||||
|
||||
declare module "webgl-debug" {
|
||||
export function makeDebugContext(context: WebGLRenderingContext): WebGLRenderingContext;
|
||||
export function makeDebugContext(context: WebGLRenderingContext, callbackOnThrow: (err: string, funcName: string, args: any) => void): WebGLRenderingContext;
|
||||
}
|
||||
Reference in New Issue
Block a user