diff --git a/src/js/game/map_chunk.js b/src/js/game/map_chunk.js index 54af1125..0c5390e4 100644 --- a/src/js/game/map_chunk.js +++ b/src/js/game/map_chunk.js @@ -10,6 +10,7 @@ import { COLOR_ITEM_SINGLETONS } from "./items/color_item"; import { GameRoot } from "./root"; import { enumSubShape } from "./shape_definition"; import { Rectangle } from "../core/rectangle"; +import { MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS } from "../mods/mod_interface"; const logger = createLogger("map_chunk"); @@ -192,6 +193,10 @@ export class MapChunk { [enumSubShape.windmill]: Math.round(6 + clamp(distanceToOriginInChunks / 2, 0, 20)), }; + for (const key in MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS) { + weights[key] = MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS[key](distanceToOriginInChunks); + } + if (distanceToOriginInChunks < 7) { // Initial chunks can not spawn the good stuff weights[enumSubShape.star] = 0; diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 429c1515..c8d6c9e5 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -38,6 +38,7 @@ import { HUDSandboxController } from "../hud/parts/sandbox_controller"; import { queryParamOptions } from "../../core/query_parameters"; import { MetaBlockBuilding } from "../buildings/block"; import { MetaItemProducerBuilding } from "../buildings/item_producer"; +import { MODS } from "../../mods/modloader"; /** @typedef {{ * shape: string, @@ -521,6 +522,8 @@ export function generateLevelDefinitions(limitedVersion = false) { }); } + MODS.callHook("modifyLevelDefinitions", levelDefinitions); + return levelDefinitions; } diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js index b09d73c5..acc0bd1c 100644 --- a/src/js/game/shape_definition.js +++ b/src/js/game/shape_definition.js @@ -3,6 +3,7 @@ import { globalConfig } from "../core/config"; import { smoothenDpi } from "../core/dpi_manager"; import { DrawParameters } from "../core/draw_parameters"; import { Vector } from "../core/vector"; +import { MODS_ADDITIONAL_SUB_SHAPE_DRAWERS } from "../mods/mod_interface"; import { BasicSerializableObject, types } from "../savegame/serialization"; import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors"; import { THEME } from "./theme"; @@ -366,18 +367,11 @@ export class ShapeDefinition extends BasicSerializableObject { context.strokeStyle = THEME.items.outline; context.lineWidth = THEME.items.outlineWidth; - const insetPadding = 0.0; - switch (subShape) { case enumSubShape.rect: { context.beginPath(); const dims = quadrantSize * layerScale; - context.rect( - insetPadding + -quadrantHalfSize, - -insetPadding + quadrantHalfSize - dims, - dims, - dims - ); + context.rect(-quadrantHalfSize, quadrantHalfSize - dims, dims, dims); break; } @@ -385,8 +379,8 @@ export class ShapeDefinition extends BasicSerializableObject { context.beginPath(); const dims = quadrantSize * layerScale; - let originX = insetPadding - quadrantHalfSize; - let originY = -insetPadding + quadrantHalfSize - dims; + let originX = -quadrantHalfSize; + let originY = quadrantHalfSize - dims; const moveInwards = dims * 0.4; context.moveTo(originX, originY + moveInwards); @@ -401,8 +395,8 @@ export class ShapeDefinition extends BasicSerializableObject { context.beginPath(); const dims = quadrantSize * layerScale; - let originX = insetPadding - quadrantHalfSize; - let originY = -insetPadding + quadrantHalfSize - dims; + let originX = -quadrantHalfSize; + let originY = quadrantHalfSize - dims; const moveInwards = dims * 0.4; context.moveTo(originX, originY + moveInwards); context.lineTo(originX + dims, originY); @@ -414,10 +408,10 @@ export class ShapeDefinition extends BasicSerializableObject { case enumSubShape.circle: { context.beginPath(); - context.moveTo(insetPadding + -quadrantHalfSize, -insetPadding + quadrantHalfSize); + context.moveTo(-quadrantHalfSize, quadrantHalfSize); context.arc( - insetPadding + -quadrantHalfSize, - -insetPadding + quadrantHalfSize, + -quadrantHalfSize, + quadrantHalfSize, quadrantSize * layerScale, -Math.PI * 0.5, 0 @@ -427,7 +421,15 @@ export class ShapeDefinition extends BasicSerializableObject { } default: { - assertAlways(false, "Unkown sub shape: " + subShape); + if (MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]) { + MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]({ + context, + layerScale, + quadrantSize, + }); + } else { + throw new Error("Unkown sub shape: " + subShape); + } } } diff --git a/src/js/mods/demo_mod.js b/src/js/mods/demo_mod.js index c5003da0..7dca4bad 100644 --- a/src/js/mods/demo_mod.js +++ b/src/js/mods/demo_mod.js @@ -12,20 +12,50 @@ export class DemoMod extends Mod { } hook_init() { + // Add some custom css this.interface.registerCss(` - * { - color: red !important; - } + * { + color: red !important; + } `); + // Replace a builtin sprite this.interface.registerSprite( "sprites/colors/red.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB2AAAAdgB+lymcgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAATkSURBVHic7ZpfiFRVHMc/v5m99+6qWYhCYKFGVJpPFYgK4UNQb1GwODO5M2OLUmASItGD6OJDYX8IIqPMdWdmdWZkE6GnnozqJWkxENMyg/75tP31z+7MnZ3760GrZf7fmXuvaPfzMvA7v3PO9/e7555z5pwLISEhISEhISH/U6SXyjo4aFaM+atUndUISwEUSmjkW5XouYH86I9eiJxJDC8Tra4EHhBxrGtW+UWQr43K1bMyMWF323ZXCSglko8JJFF5EljYwvUMytFqVcfmTeQuuuljejC5NBqVzQgx4MEWrpdAjjtIbqAwdsJNH+AyAXYi9TDKGwobXPYzI8J7hil7JZP5s5WjptN3VGzdrcpzwICbTgQ9gbLTLOa+6rxOh9iJ1FZV3gEMN6Jqevspok7SKIx/2qi4Ek+udZDDwD1d9wGziO6y8rl9nUlqg4LY8dQosLkHUXOpCmwxC9mxuUY7ln5WRQ8AUS86UWXUKma3CGgrv0i7hirx1Ot4FzxAVGG0lEhu+8dQiqdfUNGDeBQ8gAjDdiL1Wlu/VoXleHITyLhXompwVHkKQITjdPAwukOHrELucLPSpgnQeHxxGfOcwGJ/hAFw5frvAr86UJiysFdJofBro/KmWS+rudfn4OFa4L4FDyCwpCLmSIvyenRw6+12X/kiMN8vYQFzxZy17pKJA3/VFjQcAWWjNMStEzzAgnK0vKlRQcMEiMoT/uoJHhEeb2SvS4Beey3W+q4oeNbryEhdvHWGUiy9DFgUiKRgWVQ6//Pdtca6BESVJcHoCZ4os3WrWl0CnGj1Vnz6ADg47RNANdJy73xzI9VaS/2kEOW3YMQET6Qa/b3OVmsw+qPnAScQRcHiGH32hVpjXQLk0KHLwDeBSAqWs3LkyKVaY+ONEPKx/3oCRmgYU+M/Q1ot+CrmBiCO5hvZGybALI5PIpzyV1KAKF82OydsfgihvOKboKARaRpL0wSY9684fouMgknz/uUfNStseSRmx4YeUYmcxLfjKt9xJCLrzSOZL5o5tAzMLI5PqvK+97qCQZR3WwUPHTxZq192AKc9UxUcZ43q9EvtnNomQDKZEugQMOOJrCAQpnGcjTIx0VZzR++2VcidBoa4ObbICjJsHR0/04lzx5ObVcgeQ9nTva6AEN1l5TPFjt3dtm/H028qusNtvSAQYb+Zz25r7/kfrpc3o5DZqegHbuv5j2SN+1Zsd1vLdQIE1JqdeR4Ya+scHGPm7NVhGRlxPUd1/YWIgtiJ9B5Ub+i8IMLbRj77Yrtb4Kb1exVQiqW2i/AWwe8WFXjZKmTb3gC3oucEANiJVFKVg/Ty8YQ7KqIybBYzPd9ce5IAgMozqfWOw4fAnV612QiFKRXdOJDPfeJFe54lAK5/2NQXOQa6xst2/0U45dD3tFdfn4HH7+28idxF02IDvqwQesj8Y+E6L4MHj0fAXK7PC/vp/f7/sgg7zXz2gBe6avEtAQAzsfTyiGie7i9bJ1Uk0Z/PfOelrrn4unQNFDM/mLPTG1DZB9TdyrSgCrxq3mat8zN48HkEzMWODa1RiYwBK9u4fh9R2WwUM58HoSuwzYtZHD9pWvIQIrsVpmrLFaYQ2W1asjqo4CHAETAXHRwcKBv9j+JE7gUg4lywKqXPOjnACAkJCQkJCQkJ8Yi/AfA6e2lfA0oPAAAAAElFTkSuQmCC" ); + + // Add a new type of sub shape ("Line", short code "L") + this.interface.registerSubShapeType({ + id: "line", + shortCode: "L", + weightComputation: distanceToOriginInChunks => + Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)), + + shapeDrawer: ({ 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(); + }, + }); } hook_preprocessTheme({ id, theme }) { + // Modify the theme colors theme.map.background = "#eee"; theme.items.outline = "#000"; } + + hook_modifyLevelDefinitions(definitions) { + // Modify the goal of the first level + definitions[0].shape = "LuCuLuCu"; + } } diff --git a/src/js/mods/mod_interface.js b/src/js/mods/mod_interface.js index 47bff484..16de4d67 100644 --- a/src/js/mods/mod_interface.js +++ b/src/js/mods/mod_interface.js @@ -6,9 +6,28 @@ import { ModLoader } from "./modloader"; import { createLogger } from "../core/logging"; import { AtlasSprite, SpriteAtlasLink } from "../core/sprites"; import { Mod } from "./mod"; +import { enumShortcodeToSubShape, enumSubShape, enumSubShapeToShortcode } from "../game/shape_definition"; const LOG = createLogger("mod-interface"); +/** + * @type {Object number>} + */ +export const MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS = {}; + +/** + * @typedef {{ + * context: CanvasRenderingContext2D, + * quadrantSize: number, + * layerScale: number, + * }} SubShapeDrawOptions + */ + +/** + * @type {Object void>} + */ +export const MODS_ADDITIONAL_SUB_SHAPE_DRAWERS = {}; + export class ModInterface { /** * @@ -74,4 +93,24 @@ export class ModInterface { } this.modLoader.lazySprites.set(spriteId, sprite); } + + /** + * + * @param {object} param0 + * @param {string} param0.id + * @param {string} param0.shortCode + * @param {(distanceToOriginInChunks: number) => number} param0.weightComputation + * @param {(options: SubShapeDrawOptions) => void} param0.shapeDrawer + */ + registerSubShapeType({ id, shortCode, weightComputation, shapeDrawer }) { + if (shortCode.length !== 1) { + throw new Error("Bad short code: " + shortCode); + } + enumSubShape[id] = id; + enumSubShapeToShortcode[id] = shortCode; + enumShortcodeToSubShape[shortCode] = id; + + MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS[id] = weightComputation; + MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[id] = shapeDrawer; + } }