1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-17 04:01:51 +00:00

Merge branch 'modloader' of https://github.com/tobspr/shapez.io into modloader

This commit is contained in:
Edward Badel 2022-01-16 13:13:37 -05:00
commit cd9434c410
18 changed files with 752 additions and 791 deletions

View File

@ -3,7 +3,16 @@
* If you are interested in adding more logic to the game, you should also check out
* the advanced example
*/
registerMod(() => {
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Add new basic building",
version: "1",
id: "add-building-basic",
description: "Shows how to add a new basic building",
};
class MetaDemoModBuilding extends shapez.ModMetaBuilding {
constructor() {
super("demoModBuilding");
@ -34,22 +43,7 @@ registerMod(() => {
}
}
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Add new basic building",
version: "1",
id: "add-building-basic",
description: "Shows how to add a new basic building",
},
modLoader
);
}
class Mod extends shapez.Mod {
init() {
// Register the new building
this.modInterface.registerNewBuilding({
@ -64,8 +58,7 @@ registerMod(() => {
metaClass: MetaDemoModBuilding,
});
}
};
});
}
////////////////////////////////////////////////////////////////////////

View File

@ -2,7 +2,16 @@
* This shows how to add a new extended building (A building which flips shapes).
* Be sure to check out the "add_building_basic" example first
*/
registerMod(() => {
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Add a flipper building",
version: "1",
id: "add-building-extended",
description:
"Shows how to add a new building with logic, in this case it flips/mirrors shapez from top to down",
};
// Declare a new type of item processor
shapez.enumItemProcessorTypes.flipper = "flipper";
@ -98,23 +107,7 @@ registerMod(() => {
}
}
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Add a flipper building",
version: "1",
id: "add-building-extended",
description:
"Shows how to add a new building with logic, in this case it flips/mirrors shapez from top to down",
},
modLoader
);
}
class Mod extends shapez.Mod {
init() {
// Register the new building
this.modInterface.registerNewBuilding({
@ -129,8 +122,7 @@ registerMod(() => {
metaClass: MetaModFlipperBuilding,
});
}
};
});
}
////////////////////////////////////////////////////////////////////////

View File

@ -1,25 +1,18 @@
/**
* This is the minimal structure of a mod
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Base",
version: "1",
id: "base",
description: "The most basic mod",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
// Start the modding here
}
};
});
}

View File

@ -1,24 +1,17 @@
/**
* This shows how to patch existing methods by making belts have a cost
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Patch Methods",
version: "1",
id: "patch-methods",
description:
"Shows how to patch existing methods to change the game by making the belts cost shapes",
},
modLoader
);
}
description: "Shows how to patch existing methods to change the game by making the belts cost shapes",
};
class Mod extends shapez.Mod {
init() {
// Define our currency
const CURRENCY = "CyCyCyCy:--------:CuCuCuCu";
@ -90,8 +83,7 @@ registerMod(() => {
return result;
});
}
};
});
}
const RESOURCES = {
"currency.png":

View File

@ -1,4 +1,3 @@
registerMod(() => {
class DemoModComponent extends shapez.Component {
static getId() {
return "DemoMod";
@ -80,7 +79,7 @@ registerMod(() => {
}
}
return class ModImpl extends shapez.Mod {
class Mod extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
@ -104,50 +103,9 @@ registerMod(() => {
// }
// `);
// Replace a builtin sprite
["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
this.modInterface.registerSprite(
"sprites/colors/" + color + ".png",
RESOURCES[color + ".png"]
);
});
// Add an atlas
this.modInterface.registerAtlas(RESOURCES["demoAtlas.png"], RESOURCES["demoAtlas.json"]);
// Add a new type of sub shape ("Line", short code "L")
this.modInterface.registerSubShapeType({
id: "line",
shortCode: "L",
weightComputation: distanceToOriginInChunks =>
Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)),
draw: ({ context, quadrantSize, layerScale }) => {
const quadrantHalfSize = quadrantSize / 2;
context.beginPath();
context.moveTo(-quadrantHalfSize, quadrantHalfSize);
context.arc(
-quadrantHalfSize,
quadrantHalfSize,
quadrantSize * layerScale,
-Math.PI * 0.25,
0
);
context.closePath();
},
});
// Modify the theme colors
this.signals.preprocessTheme.add(({ theme }) => {
theme.map.background = "#eee";
theme.items.outline = "#000";
});
// Modify the goal of the first level
this.signals.modifyLevelDefinitions.add(definitions => {
definitions[0].shape = "LuCuLuCu";
});
this.modInterface.registerTranslations("en", {
ingame: {
interactiveTutorial: {
@ -236,8 +194,7 @@ registerMod(() => {
`);
}
};
});
}
////////////////////////////////////////////////////////////////////////
// @notice: Later this part will be autogenerated

View File

@ -1,23 +1,16 @@
/**
* This example shows how to add custom css
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Add custom CSS",
version: "1",
id: "custom-css",
description: "Shows how to add custom css",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
// Notice that, since the UI is scaled dynamically, every pixel value
// should be wrapped in '$scaled()' (see below)
@ -40,8 +33,7 @@ registerMod(() => {
}
`);
}
};
});
}
const RESOURCES = {
"cat.png":

View File

@ -0,0 +1,61 @@
/**
* Shows how to draw overlays by visualizing which item processors are working
*/
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: custom drawing",
version: "1",
id: "base",
description: "Displays an indicator on every item processing building when its working",
};
class ItemProcessorStatusGameSystem extends shapez.GameSystem {
drawChunk(parameters, chunk) {
const contents = chunk.containedEntitiesByLayer.regular;
for (let i = 0; i < contents.length; ++i) {
const entity = contents[i];
const processorComp = entity.components.ItemProcessor;
if (!processorComp) {
continue;
}
const staticComp = entity.components.StaticMapEntity;
const context = parameters.context;
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
// Culling for better performance
if (parameters.visibleRect.containsCircle(center.x, center.y, 40)) {
// Circle
context.fillStyle = processorComp.ongoingCharges.length === 0 ? "#aaa" : "#53cf47";
context.strokeStyle = "#000";
context.lineWidth = 1;
context.beginCircle(center.x + 5, center.y + 5, 4);
context.fill();
context.stroke();
}
}
}
}
class Mod extends shapez.Mod {
init() {
// Register our game system
this.modInterface.registerGameSystem({
id: "item_processor_status",
systemClass: ItemProcessorStatusGameSystem,
// Specify at which point the update method will be called,
// in this case directly before the belt system. You can use
// before: "end" to make it the last system
before: "belt",
// Specify where our drawChunk method should be called, check out
// map_chunk_view
drawHooks: ["staticAfter"],
});
}
}

View File

@ -1,23 +1,17 @@
/**
* This shows how to add custom sub shapes
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Custom Sub Shapes",
version: "1",
id: "custom-sub-shapes",
description: "Shows how to add custom sub shapes",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
// Add a new type of sub shape ("Line", short code "L")
this.modInterface.registerSubShapeType({
@ -49,5 +43,4 @@ registerMod(() => {
definitions[0].shape = "LuLuLuLu";
});
}
};
});
}

View File

@ -1,23 +1,16 @@
/**
* This example shows how to add a new theme to the game
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Custom Game Theme",
version: "1",
id: "custom-theme",
description: "Shows how to add a custom game theme",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
this.modInterface.registerGameTheme({
id: "my-theme",
@ -25,8 +18,7 @@ registerMod(() => {
theme: RESOURCES["my-theme.json"],
});
}
};
});
}
const RESOURCES = {
"my-theme.json": {

View File

@ -1,23 +1,17 @@
/**
* This shows how to modify an existing building
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Modify existing building",
version: "1",
id: "modify-existing-building",
description: "Shows how to modify an existing building",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
// Make Rotator always unlocked
this.modInterface.replaceMethod(shapez.MetaRotaterBuilding, "getIsUnlocked", function () {
@ -32,5 +26,4 @@ registerMod(() => {
return [["Awesomeness", 5]];
});
}
};
});
}

View File

@ -2,23 +2,16 @@
* This example shows how to modify the builtin themes. If you want to create your own theme,
* be sure to check out the "custom_theme" example
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Modify Builtin Themes",
version: "1",
id: "modify-theme",
description: "Shows how to modify builtin themes",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
shapez.THEMES.light.map.background = "#eee";
shapez.THEMES.light.items.outline = "#000";
@ -26,5 +19,4 @@ registerMod(() => {
shapez.THEMES.dark.map.background = "#245";
shapez.THEMES.dark.items.outline = "#fff";
}
};
});
}

View File

@ -2,34 +2,24 @@
* This example shows how to replace builtin sprites, in this case
* the color sprites
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Replace builtin sprites",
version: "1",
id: "replace-builtin-sprites",
description: "Shows how to replace builtin sprites",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
// Replace a builtin sprite
["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
this.modInterface.registerSprite(
"sprites/colors/" + color + ".png",
RESOURCES[color + ".png"]
);
this.modInterface.registerSprite("sprites/colors/" + color + ".png", RESOURCES[color + ".png"]);
});
}
};
});
}
////////////////////////////////////////////////////////////////////////

View File

@ -1,23 +1,17 @@
/**
* Shows to add new translations
*/
registerMod(() => {
return class ModImpl extends shapez.Mod {
constructor(app, modLoader) {
super(
app,
{
const METADATA = {
website: "https://tobspr.io",
author: "tobspr",
name: "Mod Example: Translations",
version: "1",
id: "translations",
description: "Shows how to add and modify translations",
},
modLoader
);
}
};
class Mod extends shapez.Mod {
init() {
// Replace an existing translation in the english language
this.modInterface.registerTranslations("en", {
@ -67,5 +61,4 @@ registerMod(() => {
}
});
}
};
});
}

View File

@ -384,12 +384,20 @@ export class ShapeDefinition extends BasicSerializableObject {
context.strokeStyle = THEME.items.outline;
context.lineWidth = THEME.items.outlineWidth;
if (MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]) {
MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]({
context,
layerScale,
quadrantSize,
});
} else {
switch (subShape) {
case enumSubShape.rect: {
context.beginPath();
const dims = quadrantSize * layerScale;
context.rect(-quadrantHalfSize, quadrantHalfSize - dims, dims, dims);
context.fill();
context.stroke();
break;
}
case enumSubShape.star: {
@ -405,6 +413,8 @@ export class ShapeDefinition extends BasicSerializableObject {
context.lineTo(originX + dims - moveInwards, originY + dims);
context.lineTo(originX, originY + dims);
context.closePath();
context.fill();
context.stroke();
break;
}
@ -420,6 +430,8 @@ export class ShapeDefinition extends BasicSerializableObject {
context.lineTo(originX + dims, originY + dims);
context.lineTo(originX, originY + dims);
context.closePath();
context.fill();
context.stroke();
break;
}
@ -434,25 +446,17 @@ export class ShapeDefinition extends BasicSerializableObject {
0
);
context.closePath();
context.fill();
context.stroke();
break;
}
default: {
if (MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]) {
MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]({
context,
layerScale,
quadrantSize,
});
} else {
throw new Error("Unkown sub shape: " + subShape);
}
}
}
context.fill();
context.stroke();
context.rotate(-rotation);
context.translate(-centerQuadrantX, -centerQuadrantY);
}

4
src/js/globals.d.ts vendored
View File

@ -26,8 +26,6 @@ declare const shapez: any;
declare const ipcRenderer: any;
declare const registerMod: any;
// Polyfills
declare interface String {
replaceAll(search: string, replacement: string): string;
@ -98,7 +96,7 @@ declare interface Window {
cpmstarAPI: any;
// Mods
registerMod: any;
$shapez_registerMod: any;
anyModLoaded: any;
shapez: any;

View File

@ -7,28 +7,22 @@ import { MOD_SIGNALS } from "./mod_signals";
export class Mod {
/**
*
* @param {Application} app
* @param {object} metadata
* @param {string} metadata.name
* @param {string} metadata.version
* @param {string} metadata.author
* @param {string} metadata.website
* @param {string} metadata.description
* @param {string} metadata.id
*
* @param {ModLoader} modLoader
* @param {import("./modloader").ModMetadata} meta
*/
constructor(app, metadata, modLoader) {
constructor(app, modLoader, meta) {
this.app = app;
this.metadata = metadata;
this.modLoader = modLoader;
this.metadata = meta;
this.signals = MOD_SIGNALS;
this.modInterface = modLoader.modInterface;
}
init() {}
init() {
// to be overridden
}
get dialogs() {
return this.modInterface.dialogs;

View File

@ -229,19 +229,21 @@ export class ModInterface {
T.buildings[id] = {};
metaClass.getAllVariantCombinations().forEach(combination => {
const actualVariant = combination.variant || defaultBuildingVariant;
registerBuildingVariant(id, metaClass, actualVariant, combination.rotationVariant || 0);
const variant = combination.variant || defaultBuildingVariant;
const rotationVariant = combination.rotationVariant || 0;
const buildingIdentifier = id + (variant === defaultBuildingVariant ? "" : "-" + variant);
const uniqueTypeId = buildingIdentifier + (rotationVariant === 0 ? "" : "-" + rotationVariant);
registerBuildingVariant(uniqueTypeId, metaClass, variant, rotationVariant);
gBuildingVariants[id].metaInstance = metaInstance;
T.buildings[id][actualVariant] = {
T.buildings[id][variant] = {
name: combination.name || "Name",
description: combination.description || "Description",
};
const buildingIdentifier =
id + (actualVariant === defaultBuildingVariant ? "" : "-" + actualVariant);
if (combination.regularImageBase64) {
this.registerSprite(
"sprites/buildings/" + buildingIdentifier + ".png",
@ -256,7 +258,7 @@ export class ModInterface {
);
}
if (combination.tutorialImageBase64) {
this.setBuildingTutorialImage(id, actualVariant, combination.tutorialImageBase64);
this.setBuildingTutorialImage(id, variant, combination.tutorialImageBase64);
}
});

View File

@ -9,6 +9,17 @@ import { MOD_SIGNALS } from "./mod_signals";
const LOG = createLogger("mods");
/**
* @typedef {{
* name: string;
* version: string;
* author: string;
* website: string;
* description: string;
* id: string;
* }} ModMetadata
*/
export class ModLoader {
constructor() {
LOG.log("modloader created");
@ -23,7 +34,7 @@ export class ModLoader {
this.modInterface = new ModInterface(this);
/** @type {((Object) => (new (Application, ModLoader) => Mod))[]} */
/** @type {({ meta: ModMetadata, modClass: typeof Mod})[]} */
this.modLoadQueue = [];
this.initialized = false;
@ -73,10 +84,6 @@ export class ModLoader {
this.exposeExports();
window.registerMod = mod => {
this.modLoadQueue.push(mod);
};
if (G_IS_STANDALONE || G_IS_DEV) {
try {
let mods = [];
@ -101,34 +108,57 @@ export class ModLoader {
mods.push(await response.text());
}
window.$shapez_registerMod = (modClass, meta) => {
if (this.modLoadQueue.some(entry => entry.meta.id === meta.id)) {
console.warn(
"Not registering mod",
meta,
"since a mod with the same id is already loaded"
);
return;
}
this.modLoadQueue.push({
modClass,
meta,
});
};
mods.forEach(modCode => {
modCode += `
if (typeof Mod !== 'undefined') {
if (typeof METADATA !== 'object') {
throw new Error("No METADATA variable found");
}
window.$shapez_registerMod(Mod, METADATA);
}
`;
try {
const func = new Function(modCode);
func();
} catch (ex) {
console.error(ex);
alert("Failed to parse mod (launch with --dev for more info): " + ex);
alert("Failed to parse mod (launch with --dev for more info): \n\n" + ex);
}
});
delete window.$shapez_registerMod;
} catch (ex) {
alert("Failed to load mods (launch with --dev for more info): " + ex);
alert("Failed to load mods (launch with --dev for more info): \n\n" + ex);
}
}
this.initialized = true;
this.modLoadQueue.forEach(modClass => {
this.modLoadQueue.forEach(({ modClass, meta }) => {
try {
const mod = new (modClass())(this.app, this);
const mod = new modClass(this.app, this, meta);
mod.init();
this.mods.push(mod);
} catch (ex) {
console.error(ex);
alert("Failed to initialize mods (launch with --dev for more info): " + ex);
alert("Failed to initialize mods (launch with --dev for more info): \n\n" + ex);
}
});
this.modLoadQueue = [];
delete window.registerMod;
}
}