Merge pull request #130 from ponydevs/archive-update-v0.53.2

Archive update v0.53.2
This commit is contained in:
Eliot Partridge
2019-09-18 19:22:30 -05:00
committed by GitHub
113 changed files with 3698 additions and 3719 deletions
+4
View File
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
+2
View File
@@ -272,6 +272,8 @@ $argentina-blue: #74acdf;
}
}
// TODO: make $action-bar-space 46px on desktop
.actions-modal-opened {
$action-bar-space: 80px;
+15 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+24
View File
@@ -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,
];
+10 -7
View File
@@ -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);
}
-5
View File
@@ -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
View File
@@ -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);
}
+1 -1
View File
@@ -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
+6 -2
View File
@@ -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`,
+5 -7
View File
@@ -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
+13 -1
View File
@@ -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
View File
@@ -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;
}
+5 -5
View File
@@ -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);
+1
View File
@@ -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.`
},
];
+3 -28
View File
@@ -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.
-35
View File
@@ -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
+1 -1
View File
@@ -35,7 +35,7 @@ export const routes: Routes = [
@NgModule({
imports: [
BrowserModule,
RouterModule,
RouterModule.forRoot(routes, { anchorScrolling: 'enabled' }),
FormsModule,
HttpClientModule,
PopoverModule.forRoot(),
+1 -1
View File
@@ -37,7 +37,7 @@
.text-right
.d-flex.flex-wrap
//- .text-nowrap.mr-2
| &copy; 2016-{{date | date:'yyyy'}}
| &copy; 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")
+3
View File
@@ -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 {
+3 -5
View File
@@ -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")
+9 -16
View File
@@ -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
+12
View File
@@ -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);
})
}
}
+2 -1
View File
@@ -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")
+21 -3
View File
@@ -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;
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ export class InstallService {
}
}
get canInstall() {
return !!this.installEvent || (DEVELOPMENT && localStorage.getItem('install'));
return false;
}
install() {
if (!this.installEvent) {
+6 -2
View File
@@ -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 };
+4 -2
View File
@@ -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() {
}
}
@@ -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
View File
File diff suppressed because one or more lines are too long
+28 -24
View File
@@ -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;
}
+3
View File
@@ -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;
+1 -1
View File
@@ -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);
}
+56 -59
View File
@@ -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;
}
}
}
+3 -2
View File
@@ -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;
}
+29
View File
@@ -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);
}
+17 -5
View File
@@ -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
}
+9 -5
View File
@@ -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
}
+12 -20
View File
@@ -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) {
+2 -1
View File
@@ -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);
}
});
}
+73 -17
View File
@@ -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 };
}
+5 -3
View File
@@ -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;
}
+1
View File
@@ -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();
+86 -63
View File
@@ -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);
}
}
+51 -8
View File
@@ -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) {
+26 -12
View File
@@ -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;
}
-4
View File
@@ -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 -11
View File
@@ -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;
+10 -1
View File
@@ -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
]));
});
});
+18 -2
View File
@@ -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);
+2 -2
View File
@@ -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
};
}
+12 -5
View File
@@ -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;
}
+2 -2
View File
@@ -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
},
];
+1 -1
View File
@@ -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" />
-62
View File
@@ -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;
}
+8
View File
@@ -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;
}