|
|
|
import { DrawParameters } from "./draw_parameters";
|
|
|
|
import { Rectangle } from "./rectangle";
|
|
|
|
import { round3Digits } from "./utils";
|
|
|
|
|
|
|
|
export const ORIGINAL_SPRITE_SCALE = "0.75";
|
|
|
|
export const FULL_CLIP_RECT = new Rectangle(0, 0, 1, 1);
|
|
|
|
|
|
|
|
const EXTRUDE = 0.1;
|
|
|
|
|
|
|
|
export class BaseSprite {
|
|
|
|
/**
|
|
|
|
* Returns the raw handle
|
|
|
|
* @returns {HTMLImageElement|HTMLCanvasElement}
|
|
|
|
*/
|
|
|
|
getRawTexture() {
|
|
|
|
abstract;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the sprite
|
|
|
|
* @param {CanvasRenderingContext2D} context
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {number} w
|
|
|
|
* @param {number} h
|
|
|
|
*/
|
|
|
|
draw(context, x, y, w, h) {
|
|
|
|
// eslint-disable-line no-unused-vars
|
|
|
|
abstract;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Position of a sprite within an atlas
|
|
|
|
*/
|
|
|
|
export class SpriteAtlasLink {
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {object} param0
|
|
|
|
* @param {number} param0.packedX
|
|
|
|
* @param {number} param0.packedY
|
|
|
|
* @param {number} param0.packOffsetX
|
|
|
|
* @param {number} param0.packOffsetY
|
|
|
|
* @param {number} param0.packedW
|
|
|
|
* @param {number} param0.packedH
|
|
|
|
* @param {number} param0.w
|
|
|
|
* @param {number} param0.h
|
|
|
|
* @param {HTMLImageElement|HTMLCanvasElement} param0.atlas
|
|
|
|
*/
|
|
|
|
constructor({ w, h, packedX, packedY, packOffsetX, packOffsetY, packedW, packedH, atlas }) {
|
|
|
|
this.packedX = packedX;
|
|
|
|
this.packedY = packedY;
|
|
|
|
this.packedW = packedW;
|
|
|
|
this.packedH = packedH;
|
|
|
|
this.packOffsetX = packOffsetX;
|
|
|
|
this.packOffsetY = packOffsetY;
|
|
|
|
this.atlas = atlas;
|
|
|
|
this.w = w;
|
|
|
|
this.h = h;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class AtlasSprite extends BaseSprite {
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {string} spriteName
|
|
|
|
*/
|
|
|
|
constructor(spriteName = "sprite") {
|
|
|
|
super();
|
|
|
|
/** @type {Object.<string, SpriteAtlasLink>} */
|
|
|
|
this.linksByResolution = {};
|
|
|
|
this.spriteName = spriteName;
|
Mod Support - 1.5.0 Update (#1361)
* initial modloader draft
* modloader features
* Refactor mods to use signals
* Add support for modifying and registering new transltions
* Minor adjustments
* Support for string building ids for mods
* Initial support for adding new buildings
* Refactor how mods are loaded to resolve circular dependencies and prepare for future mod loading
* Lazy Load mods to make sure all dependencies are loaded
* Expose all exported members automatically to mods
* Fix duplicate exports
* Allow loading mods from standalone
* update changelog
* Fix mods folder incorrect path
* Fix modloading in standalone
* Fix sprites not getting replaced, update demo mod
* Load dev mod via raw loader
* Improve mod developing so mods are directly ready to be deployed, load mods from local file server
* Proper mods ui
* Allow mods to register game systems and draw stuff
* Change mods path
* Fix sprites not loading
* Minor adjustments, closes #1333
* Add support for loading atlases via mods
* Add support for loading mods from external sources in DEV
* Add confirmation when loading mods
* Fix circular dependency
* Minor Keybindings refactor, add support for keybindings to mods, add support for dialogs to mods
* Add some mod signals
* refactor game loading states
* Make shapez exports global
* Start to make mods safer
* Refactor file system electron event handling
* Properly isolate electron renderer process
* Update to latest electron
* Show errors when loading mods
* Update confirm dialgo
* Minor restructure, start to add mod examples
* Allow adding custom themesw
* Add more examples and allow defining custom item processor operations
* Add interface to register new buildings
* Fixed typescript type errors (#1335)
* Refactor building registry, make it easier for mods to add new buildings
* Allow overriding existing methods
* Add more examples and more features
* More mod examples
* Make mod loading simpler
* Add example how to add custom drawings
* Remove unused code
* Minor modloader adjustments
* Support for rotation variants in mods (was broken previously)
* Allow mods to replace builtin sub shapes
* Add helper methods to extend classes
* Fix menu bar on mac os
* Remember window state
* Add support for paste signals
* Add example how to add custom components and systems
* Support for mod settings
* Add example for adding a new item type
* Update class extensions
* Minor adjustments
* Fix typo
* Add notification blocks mod example
* Add small tutorial
* Update readme
* Add better instructions
* Update JSDoc for Replacing Methods (#1336)
* upgraded types for overriding methods
* updated comments
Co-authored-by: Edward Badel <you@example.com>
* Direction lock now indicates when there is a building inbetween
* Fix mod examples
* Fix linter error
* Game state register (#1341)
* Added a gamestate register helper
Added a gamestate register helper
* Update mod_interface.js
* export build options
* Fix runBeforeMethod and runAfterMethod
* Minor game system code cleanup
* Belt path drawing optimization
* Fix belt path optimization
* Belt drawing improvements, again
* Do not render belts in statics disabled view
* Allow external URL to load more than one mod (#1337)
* Allow external URL to load more than one mod
Instead of loading the text returned from the remote server, load a JSON object with a `mods` field, containing strings of all the mods. This lets us work on more than one mod at a time or without separate repos. This will break tooling such as `create-shapezio-mod` though.
* Update modloader.js
* Prettier fixes
* Added link to create-shapezio-mod npm page (#1339)
Added link to create-shapezio-mod npm page: https://www.npmjs.com/package/create-shapezio-mod
* allow command line switch to load more than one mod (#1342)
* Fixed class handle type (#1345)
* Fixed class handle type
* Fixed import game state
* Minor adjustments
* Refactor item acceptor to allow only single direction slots
* Allow specifying minimumGameVersion
* Add sandbox example
* Replaced concatenated strings with template literals (#1347)
* Mod improvements
* Make wired pins component optional on the storage
* Fix mod examples
* Bind `this` for method overriding JSDoc (#1352)
* fix entity debugger reaching HTML elements (#1353)
* Store mods in savegame and show warning when it differs
* Closes #1357
* Fix All Shapez Exports Being Const (#1358)
* Allowed setting of variables inside webpack modules
* remove console log
* Fix stringification of things inside of eval
Co-authored-by: Edward Badel <you@example.com>
* Fix building placer intersection warning
* Add example for storing data in the savegame
* Fix double painter bug (#1349)
* Add example on how to extend builtin buildings
* update readme
* Disable steam achievements when playing with mods
* Update translations
Co-authored-by: Thomas (DJ1TJOO) <44841260+DJ1TJOO@users.noreply.github.com>
Co-authored-by: Bagel03 <70449196+Bagel03@users.noreply.github.com>
Co-authored-by: Edward Badel <you@example.com>
Co-authored-by: Emerald Block <69981203+EmeraldBlock@users.noreply.github.com>
Co-authored-by: saile515 <63782477+saile515@users.noreply.github.com>
Co-authored-by: Sense101 <67970865+Sense101@users.noreply.github.com>
3 years ago
|
|
|
|
|
|
|
this.frozen = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
getRawTexture() {
|
|
|
|
return this.linksByResolution[ORIGINAL_SPRITE_SCALE].atlas;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the sprite onto a regular context using no contexts
|
|
|
|
* @see {BaseSprite.draw}
|
|
|
|
*/
|
|
|
|
draw(context, x, y, w, h) {
|
|
|
|
if (G_IS_DEV) {
|
|
|
|
assert(context instanceof CanvasRenderingContext2D, "Not a valid context");
|
|
|
|
}
|
|
|
|
|
|
|
|
const link = this.linksByResolution[ORIGINAL_SPRITE_SCALE];
|
|
|
|
|
|
|
|
assert(
|
|
|
|
link,
|
|
|
|
"Link not known: " +
|
|
|
|
ORIGINAL_SPRITE_SCALE +
|
|
|
|
" (having " +
|
|
|
|
Object.keys(this.linksByResolution) +
|
|
|
|
")"
|
|
|
|
);
|
|
|
|
|
|
|
|
const width = w || link.w;
|
|
|
|
const height = h || link.h;
|
|
|
|
|
|
|
|
const scaleW = width / link.w;
|
|
|
|
const scaleH = height / link.h;
|
|
|
|
|
|
|
|
context.drawImage(
|
|
|
|
link.atlas,
|
|
|
|
|
|
|
|
link.packedX,
|
|
|
|
link.packedY,
|
|
|
|
link.packedW,
|
|
|
|
link.packedH,
|
|
|
|
|
|
|
|
x + link.packOffsetX * scaleW,
|
|
|
|
y + link.packOffsetY * scaleH,
|
|
|
|
link.packedW * scaleW,
|
|
|
|
link.packedH * scaleH
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {DrawParameters} parameters
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {number} size
|
|
|
|
* @param {boolean=} clipping
|
|
|
|
*/
|
|
|
|
drawCachedCentered(parameters, x, y, size, clipping = true) {
|
|
|
|
this.drawCached(parameters, x - size / 2, y - size / 2, size, size, clipping);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {CanvasRenderingContext2D} context
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {number} size
|
|
|
|
*/
|
|
|
|
drawCentered(context, x, y, size) {
|
|
|
|
this.draw(context, x - size / 2, y - size / 2, size, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the sprite
|
|
|
|
* @param {DrawParameters} parameters
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {number} w
|
|
|
|
* @param {number} h
|
|
|
|
* @param {boolean=} clipping Whether to perform culling
|
|
|
|
*/
|
|
|
|
drawCached(parameters, x, y, w = null, h = null, clipping = true) {
|
|
|
|
if (G_IS_DEV) {
|
|
|
|
assert(parameters instanceof DrawParameters, "Not a valid context");
|
|
|
|
assert(!!w && w > 0, "Not a valid width:" + w);
|
|
|
|
assert(!!h && h > 0, "Not a valid height:" + h);
|
|
|
|
}
|
|
|
|
|
|
|
|
const visibleRect = parameters.visibleRect;
|
|
|
|
|
|
|
|
const scale = parameters.desiredAtlasScale;
|
|
|
|
const link = this.linksByResolution[scale];
|
|
|
|
|
|
|
|
if (!link) {
|
|
|
|
assert(false, `Link not known: ${scale} (having ${Object.keys(this.linksByResolution)})`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const scaleW = w / link.w;
|
|
|
|
const scaleH = h / link.h;
|
|
|
|
|
|
|
|
let destX = x + link.packOffsetX * scaleW;
|
|
|
|
let destY = y + link.packOffsetY * scaleH;
|
|
|
|
let destW = link.packedW * scaleW;
|
|
|
|
let destH = link.packedH * scaleH;
|
|
|
|
|
|
|
|
let srcX = link.packedX;
|
|
|
|
let srcY = link.packedY;
|
|
|
|
let srcW = link.packedW;
|
|
|
|
let srcH = link.packedH;
|
|
|
|
|
|
|
|
let intersection = null;
|
|
|
|
|
|
|
|
if (clipping) {
|
|
|
|
const rect = new Rectangle(destX, destY, destW, destH);
|
|
|
|
intersection = rect.getIntersection(visibleRect);
|
|
|
|
if (!intersection) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
srcX += (intersection.x - destX) / scaleW;
|
|
|
|
srcY += (intersection.y - destY) / scaleH;
|
|
|
|
|
|
|
|
srcW *= intersection.w / destW;
|
|
|
|
srcH *= intersection.h / destH;
|
|
|
|
|
|
|
|
destX = intersection.x;
|
|
|
|
destY = intersection.y;
|
|
|
|
|
|
|
|
destW = intersection.w;
|
|
|
|
destH = intersection.h;
|
|
|
|
}
|
|
|
|
|
|
|
|
parameters.context.drawImage(
|
|
|
|
link.atlas,
|
|
|
|
|
|
|
|
// atlas src pos
|
|
|
|
srcX,
|
|
|
|
srcY,
|
|
|
|
|
|
|
|
// atlas src size
|
|
|
|
srcW,
|
|
|
|
srcH,
|
|
|
|
|
|
|
|
// dest pos and size
|
|
|
|
destX - EXTRUDE,
|
|
|
|
destY - EXTRUDE,
|
|
|
|
destW + 2 * EXTRUDE,
|
|
|
|
destH + 2 * EXTRUDE
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws a subset of the sprite. Does NO culling
|
|
|
|
* @param {DrawParameters} parameters
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {number} w
|
|
|
|
* @param {number} h
|
|
|
|
* @param {Rectangle=} clipRect The rectangle in local space (0 ... 1) to draw of the image
|
|
|
|
*/
|
|
|
|
drawCachedWithClipRect(parameters, x, y, w = null, h = null, clipRect = FULL_CLIP_RECT) {
|
|
|
|
if (G_IS_DEV) {
|
|
|
|
assert(parameters instanceof DrawParameters, "Not a valid context");
|
|
|
|
assert(!!w && w > 0, "Not a valid width:" + w);
|
|
|
|
assert(!!h && h > 0, "Not a valid height:" + h);
|
|
|
|
assert(clipRect, "No clip rect given!");
|
|
|
|
}
|
|
|
|
|
|
|
|
const scale = parameters.desiredAtlasScale;
|
|
|
|
const link = this.linksByResolution[scale];
|
|
|
|
|
|
|
|
if (!link) {
|
|
|
|
assert(false, `Link not known: ${scale} (having ${Object.keys(this.linksByResolution)})`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const scaleW = w / link.w;
|
|
|
|
const scaleH = h / link.h;
|
|
|
|
|
|
|
|
let destX = x + link.packOffsetX * scaleW + clipRect.x * w;
|
|
|
|
let destY = y + link.packOffsetY * scaleH + clipRect.y * h;
|
|
|
|
let destW = link.packedW * scaleW * clipRect.w;
|
|
|
|
let destH = link.packedH * scaleH * clipRect.h;
|
|
|
|
|
|
|
|
let srcX = link.packedX + clipRect.x * link.packedW;
|
|
|
|
let srcY = link.packedY + clipRect.y * link.packedH;
|
|
|
|
let srcW = link.packedW * clipRect.w;
|
|
|
|
let srcH = link.packedH * clipRect.h;
|
|
|
|
|
|
|
|
parameters.context.drawImage(
|
|
|
|
link.atlas,
|
|
|
|
|
|
|
|
// atlas src pos
|
|
|
|
srcX,
|
|
|
|
srcY,
|
|
|
|
|
|
|
|
// atlas src siize
|
|
|
|
srcW,
|
|
|
|
srcH,
|
|
|
|
|
|
|
|
// dest pos and size
|
|
|
|
destX - EXTRUDE,
|
|
|
|
destY - EXTRUDE,
|
|
|
|
destW + 2 * EXTRUDE,
|
|
|
|
destH + 2 * EXTRUDE
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Renders into an html element
|
|
|
|
* @param {HTMLElement} element
|
|
|
|
* @param {number} w
|
|
|
|
* @param {number} h
|
|
|
|
*/
|
|
|
|
renderToHTMLElement(element, w = 1, h = 1) {
|
|
|
|
element.style.position = "relative";
|
|
|
|
element.innerHTML = this.getAsHTML(w, h);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the html to render as icon
|
|
|
|
* @param {number} w
|
|
|
|
* @param {number} h
|
|
|
|
*/
|
|
|
|
getAsHTML(w, h) {
|
|
|
|
const link = this.linksByResolution["0.5"];
|
|
|
|
|
|
|
|
// Find out how much we have to scale it so that it fits
|
|
|
|
const scaleX = w / link.w;
|
|
|
|
const scaleY = h / link.h;
|
|
|
|
|
|
|
|
// Find out how big the scaled atlas is
|
|
|
|
const atlasW = link.atlas.width * scaleX;
|
|
|
|
const atlasH = link.atlas.height * scaleY;
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
const srcSafe = link.atlas.src.replaceAll("\\", "/");
|
|
|
|
|
|
|
|
// Find out how big we render the sprite
|
|
|
|
const widthAbsolute = scaleX * link.packedW;
|
|
|
|
const heightAbsolute = scaleY * link.packedH;
|
|
|
|
|
|
|
|
// Compute the position in the relative container
|
|
|
|
const leftRelative = (link.packOffsetX * scaleX) / w;
|
|
|
|
const topRelative = (link.packOffsetY * scaleY) / h;
|
|
|
|
const widthRelative = widthAbsolute / w;
|
|
|
|
const heightRelative = heightAbsolute / h;
|
|
|
|
|
|
|
|
// Scale the atlas relative to the width and height of the element
|
|
|
|
const bgW = atlasW / widthAbsolute;
|
|
|
|
const bgH = atlasH / heightAbsolute;
|
|
|
|
|
|
|
|
// Figure out what the position of the atlas is
|
|
|
|
const bgX = link.packedX * scaleX;
|
|
|
|
const bgY = link.packedY * scaleY;
|
|
|
|
|
|
|
|
// Fuck you, whoever thought its a good idea to make background-position work like it does now
|
|
|
|
const bgXRelative = -bgX / (widthAbsolute - atlasW);
|
|
|
|
const bgYRelative = -bgY / (heightAbsolute - atlasH);
|
|
|
|
|
|
|
|
return `
|
|
|
|
<span class="spritesheetImage" style="
|
|
|
|
background-image: url('${srcSafe}');
|
|
|
|
left: ${round3Digits(leftRelative * 100.0)}%;
|
|
|
|
top: ${round3Digits(topRelative * 100.0)}%;
|
|
|
|
width: ${round3Digits(widthRelative * 100.0)}%;
|
|
|
|
height: ${round3Digits(heightRelative * 100.0)}%;
|
|
|
|
background-repeat: repeat;
|
|
|
|
background-position: ${round3Digits(bgXRelative * 100.0)}% ${round3Digits(
|
|
|
|
bgYRelative * 100.0
|
|
|
|
)}%;
|
|
|
|
background-size: ${round3Digits(bgW * 100.0)}% ${round3Digits(bgH * 100.0)}%;
|
|
|
|
"></span>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class RegularSprite extends BaseSprite {
|
|
|
|
constructor(sprite, w, h) {
|
|
|
|
super();
|
|
|
|
this.w = w;
|
|
|
|
this.h = h;
|
|
|
|
this.sprite = sprite;
|
|
|
|
}
|
|
|
|
|
|
|
|
getRawTexture() {
|
|
|
|
return this.sprite;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the sprite, do *not* use this for sprites which are rendered! Only for drawing
|
|
|
|
* images into buffers
|
|
|
|
* @param {CanvasRenderingContext2D} context
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {number} w
|
|
|
|
* @param {number} h
|
|
|
|
*/
|
|
|
|
draw(context, x, y, w, h) {
|
|
|
|
assert(context, "No context given");
|
|
|
|
assert(x !== undefined, "No x given");
|
|
|
|
assert(y !== undefined, "No y given");
|
|
|
|
assert(w !== undefined, "No width given");
|
|
|
|
assert(h !== undefined, "No height given");
|
|
|
|
context.drawImage(this.sprite, x, y, w, h);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the sprite, do *not* use this for sprites which are rendered! Only for drawing
|
|
|
|
* images into buffers
|
|
|
|
* @param {CanvasRenderingContext2D} context
|
|
|
|
* @param {number} x
|
|
|
|
* @param {number} y
|
|
|
|
* @param {number} w
|
|
|
|
* @param {number} h
|
|
|
|
*/
|
|
|
|
drawCentered(context, x, y, w, h) {
|
|
|
|
assert(context, "No context given");
|
|
|
|
assert(x !== undefined, "No x given");
|
|
|
|
assert(y !== undefined, "No y given");
|
|
|
|
assert(w !== undefined, "No width given");
|
|
|
|
assert(h !== undefined, "No height given");
|
|
|
|
context.drawImage(this.sprite, x - w / 2, y - h / 2, w, h);
|
|
|
|
}
|
|
|
|
}
|