From 79f362ecec30aa1a06980593265b5c7e1bd57f83 Mon Sep 17 00:00:00 2001 From: Nubo <69615769+Nubo318@users.noreply.github.com> Date: Tue, 22 Aug 2023 22:00:31 -0600 Subject: [PATCH 01/10] Ketchup Mod Version 1.3.2 Nothing big, just changing some stuff to make more sense in the newest version. --- mods/ketchup_mod.js | 81 +++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/mods/ketchup_mod.js b/mods/ketchup_mod.js index 66f21126..c0cb6766 100644 --- a/mods/ketchup_mod.js +++ b/mods/ketchup_mod.js @@ -30,8 +30,7 @@ elements.ketchup.reactions = { "mayonnaise": { "elem1": null, "elem2": "fry_sauce" }, "plague": { "elem1": "poisoned_ketchup", "elem2": null}, "infection": { "elem1": "poisoned_ketchup", "elem2": null}, - "radiation": { "elem1": "poisoned_ketchup", chance:025}, - "fallout": { "elem1": "poisoned_ketchup", chance:025}, + "fallout": { "elem1": "poisoned_ketchup", chance:25}, "gloomwind": { "elem1": "poisoned_ketchup", "elem2": null}, }; @@ -61,7 +60,10 @@ elements.smoke.reactions = { "pyrocumulus": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, "ketchup_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0.15] }, "poisoned_ketchup_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0.15] }, -}; +}; + +// fixing radiation reactions +elements.radiation.reactions.ketchup = { "elem1": "poisoned_ketchup", chance:25} // elements elements.frozen_ketchup = { @@ -76,8 +78,8 @@ elements.frozen_ketchup = { reactions: { "plague": { "elem1": "frozen_poisoned_ketchup", "elem2": null}, "infection": { "elem1": "frozen_poisoned_ketchup", "elem2": null}, - "radiation": { "elem1": "frozen_poisoned_ketchup", chance:025}, - "fallout": { "elem1": "frozen_poisoned_ketchup", chance:025}, + "radiation": { "elem1": "frozen_poisoned_ketchup", chance:25}, + "fallout": { "elem1": "frozen_poisoned_ketchup", chance:25}, "gloomwind": { "elem1": "frozen_poisoned_ketchup", "elem2": null}, }, }; @@ -128,8 +130,8 @@ elements.ketchup_cloud = { reactions: { "plague": { "elem1": "poisoned_ketchup_cloud", "elem2": null}, "infection": { "elem1": "poisoned_ketchup_cloud"}, - "radiation": { "elem1": "poisoned_ketchup_cloud", chance:025}, - "fallout": { "elem1": "poisoned_ketchup_cloud", chance:025}, + "radiation": { "elem1": "poisoned_ketchup_cloud", chance:25}, + "fallout": { "elem1": "poisoned_ketchup_cloud", chance:25}, "gloomwind": { "elem1": "poisoned_ketchup_cloud", "elem2": null}, }, conduct: 0.03, @@ -161,8 +163,8 @@ elements.ketchup_snow = { reactions: { "plague": { "elem1": "poisoned_ketchup_snow", "elem2": null}, "infection": { "elem1": "poisoned_ketchup_snow", "elem2": null}, - "radiation": { "elem1": "poisoned_ketchup_snow", chance:025}, - "fallout": { "elem1": "poisoned_ketchup_snow", chance:025}, + "radiation": { "elem1": "poisoned_ketchup_snow", chance:25}, + "fallout": { "elem1": "poisoned_ketchup_snow", chance:25}, "gloomwind": { "elem1": "poisoned_ketchup_snow", "elem2": null}, }, }; @@ -182,8 +184,8 @@ elements.ketchup_snow_cloud = { reactions: { "plague": { "elem1": "poisoned_ketchup_snow_cloud", "elem2": null}, "infection": { "elem1": "poisoned_ketchup_snow_cloud"}, - "radiation": { "elem1": "poisoned_ketchup_snow_cloud", chance:025}, - "fallout": { "elem1": "poisoned_ketchup_snow_cloud", chance:025}, + "radiation": { "elem1": "poisoned_ketchup_snow_cloud", chance:25}, + "fallout": { "elem1": "poisoned_ketchup_snow_cloud", chance:25}, "gloomwind": { "elem1": "poisoned_ketchup_snow_cloud", "elem2": null}, }, }; @@ -239,8 +241,8 @@ elements.ketchup_gas = { "plague": { "elem1": "poisoned_ketchup_gas", "elem2": null}, "ketchup_gas": { "elem1": null, "elem2": "ketchup_cloud", "chance":0.3, "y":[0,15] }, "infection": { "elem1": "poisoned_ketchup_gas"}, - "radiation": { "elem1": "poisoned_ketchup_gas", chance:025}, - "fallout": { "elem1": "poisoned_ketchup_gas", chance:025}, + "radiation": { "elem1": "poisoned_ketchup_gas", chance:25}, + "fallout": { "elem1": "poisoned_ketchup_gas", chance:25}, "gloomwind": { "elem1": "poisoned_ketchup_gas", "elem2": null}, }, }; @@ -271,8 +273,8 @@ elements.ketchup_powder = { reactions: { "plague": { "elem1": "poisoned_ketchup_powder", "elem2": null}, "infection": { "elem1": "poisoned_ketchup_powder", "elem2": null}, - "radiation": { "elem1": "poisoned_ketchup_powder", chance:025}, - "fallout": { "elem1": "poisoned_ketchup_powder", chance:025}, + "radiation": { "elem1": "poisoned_ketchup_powder", chance:25}, + "fallout": { "elem1": "poisoned_ketchup_powder", chance:25}, "gloomwind": { "elem1": "poisoned_ketchup_powder", "elem2": null}, }, state: "solid", @@ -285,44 +287,6 @@ elements.poisoned_ketchup_powder = { state: "solid", category: "powders", }; -elements.tomato = { - color: "#B11E0C", - behavior: behaviors.STURDYPOWDER, - category: "food", - density: 470, - state: "solid", - tempHigh: 400, - stateHigh: "ash", - reactions: { - "rock": { "elem1": "tomato_sauce", "elem2": "rock" }, - }, - burn: 40, - burnTime: 30, - burnInto: "ash", -}; -elements.tomato_sauce = { - color: "#B72003", - behavior: behaviors.LIQUID, - category: "liquids", - density: 1031, - state: "liquid", - reactions: { - "sugar": { "elem1": "sugary_tomato_sauce", "elem2": null }, - }, - viscosity: 25000, -}; -elements.sugary_tomato_sauce = { - color: "#b53921", - behavior: behaviors.LIQUID, - category: "liquids", - density: 1031, - state: "liquid", - reactions: { - "vinegar": { "elem1": "ketchup", "elem2": null }, - }, - viscosity: 25000, - hidden: true, -}; elements.cumin = { color: "#8B7778", behavior: behaviors.POWDER, @@ -444,8 +408,15 @@ runAfterLoad(function() { /* Changelog -Mod made primarily by Nubo318. Contributors include deviantEquinox and Lily129. -Version 1.3.1 +Mod made by Nubo318. Contributors include DeviantEquinox and An Orbit. +Version 1.3.2 + +Version 1.3.2 (22nd of August 2023) +- Removed some elements due to their inclusion or some form of it in the vanilla game, including: + - Tomato + - Tomato Sauce + - Sugary Tomato Sauce +- Removed a vanilla reaction which turned ketchup into sauce when exposed to radiation Version 1.3.1 (20th of January 2022) ~ Ketchup fairies are now killed by iron and silver From d9daf5fcb24d206b1a6f4ff3ad38c4a62165ed2d Mon Sep 17 00:00:00 2001 From: Nubo <69615769+Nubo318@users.noreply.github.com> Date: Wed, 23 Aug 2023 23:04:05 -0600 Subject: [PATCH 02/10] Ketchup Mod 1.3.3 The mod is now up to date in terms of how things work --- mods/ketchup_mod.js | 181 ++++++++++++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 58 deletions(-) diff --git a/mods/ketchup_mod.js b/mods/ketchup_mod.js index c0cb6766..dde0078a 100644 --- a/mods/ketchup_mod.js +++ b/mods/ketchup_mod.js @@ -30,56 +30,51 @@ elements.ketchup.reactions = { "mayonnaise": { "elem1": null, "elem2": "fry_sauce" }, "plague": { "elem1": "poisoned_ketchup", "elem2": null}, "infection": { "elem1": "poisoned_ketchup", "elem2": null}, - "fallout": { "elem1": "poisoned_ketchup", chance:25}, + "fallout": { "elem1": "poisoned_ketchup", "chance":25}, "gloomwind": { "elem1": "poisoned_ketchup", "elem2": null}, - }; +}; // making ketchup dirty elements.dirt.reactions = { "ketchup": { "elem1": null, "elem2": "dirty_ketchup", "oneway":true}, }; -elements.ash.reactions = { - "ketchup": { "elem1": null, "elem2": "dirty_ketchup", "oneway":true}, - "steam": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, - "rain_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, - "snow_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, - "acid_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,15] }, - "pyrocumulus": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, -}; +elements.ash.reactions.ketchup = { "elem1": null, "elem2": "dirty_ketchup", "oneway":true}, elements.dust.reactions = { "ketchup": { "elem1": null, "elem2": "dirty_ketchup", "oneway":true}, }; -// making it so ketchup clouds can react with smoke to make pyrocumulus -elements.smoke.reactions = { - "steam": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, - "rain_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, - "snow_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, - "acid_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,15] }, - "fire_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,15] }, - "pyrocumulus": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }, - "ketchup_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0.15] }, - "poisoned_ketchup_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0.15] }, -}; +// pyrocumulus reactions +elements.smoke.reactions.ketchup_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.smoke.reactions.poisoned_ketchup_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.smoke.reactions.ketchup_snow_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.smoke.reactions.poisoned_ketchup_snow_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.smoke.reactions.ketchup_rain_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.smoke.reactions.poisoned_ketchup_rain_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.ash.reactions.ketchup_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.ash.reactions.poisoned_ketchup_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.ash.reactions.ketchup_snow_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.ash.reactions.poisoned_ketchup_snow_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.ash.reactions.ketchup_rain_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, +elements.ash.reactions.poisoned_ketchup_rain_cloud = { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15], "setting":"clouds" }, // fixing radiation reactions -elements.radiation.reactions.ketchup = { "elem1": "poisoned_ketchup", chance:25} +elements.radiation.reactions.ketchup = { "elem1": null, "elem2": "poisoned_ketchup", "chance":25} // elements elements.frozen_ketchup = { color: "#d44737", behavior: behaviors.WALL, - temp: 0, + temp: -5, category:"solids", - tempHigh: -3, + tempHigh: 5, stateHigh: "ketchup", state: "solid", density: 917, reactions: { "plague": { "elem1": "frozen_poisoned_ketchup", "elem2": null}, "infection": { "elem1": "frozen_poisoned_ketchup", "elem2": null}, - "radiation": { "elem1": "frozen_poisoned_ketchup", chance:25}, - "fallout": { "elem1": "frozen_poisoned_ketchup", chance:25}, + "radiation": { "elem1": "frozen_poisoned_ketchup", "chance":25}, + "fallout": { "elem1": "frozen_poisoned_ketchup", "chance":25}, "gloomwind": { "elem1": "frozen_poisoned_ketchup", "elem2": null}, }, }; @@ -94,13 +89,14 @@ elements.poisoned_ketchup = { category:"liquids", state: "liquid", density: 1140, + stain: 0.05, }; elements.frozen_poisoned_ketchup = { color: "#d43754", behavior: behaviors.POISONED_WALL, - temp: 0, + temp: -5, category:"solids", - tempHigh: 3, + tempHigh: 5, stateHigh: "poisoned_ketchup", state: "solid", density: 917, @@ -115,51 +111,93 @@ elements.ketchup_spout = { category:"special", }; elements.ketchup_cloud = { - color: "#6e413b", + color: "#ad655c", behavior: [ "XX|XX|XX", - "M1%5|XX|M1%5", - "XX|CR:ketchup%1|XX", + "XX|CO:1%5|M1%2.5 AND BO", + "XX|XX|XX", ], category:"gases", - temp: 80, - tempLow: 0, - stateLow: "ketchup_snow_cloud", + temp: 110, + tempLow: 100, + stateLow: "ketchup_rain_cloud", state: "gas", - density: 1, + density: 0.5, reactions: { "plague": { "elem1": "poisoned_ketchup_cloud", "elem2": null}, "infection": { "elem1": "poisoned_ketchup_cloud"}, - "radiation": { "elem1": "poisoned_ketchup_cloud", chance:25}, - "fallout": { "elem1": "poisoned_ketchup_cloud", chance:25}, + "radiation": { "elem1": "poisoned_ketchup_cloud", "chance":25}, + "fallout": { "elem1": "poisoned_ketchup_cloud", "chance":25}, "gloomwind": { "elem1": "poisoned_ketchup_cloud", "elem2": null}, + "ketchup_rain_cloud": { "elem1":"ketchup_rain_cloud", "temp1":-20 }, }, conduct: 0.03, + ignoreAir: true, +}; +elements.ketchup_rain_cloud = { + color: "#6e413b", + behavior: [ + "XX|XX|XX", + "XX|CH:ketchup%0.05|M1%2.5 AND BO", + "XX|XX|XX|", + ], + category: "gases", + temp: 70, + tempHigh: 100, + stateHigh: "ketchup_cloud", + tempLow: 0, + stateLow: "ketchup_snow_cloud", + state: "gas", + density: "0.5", + ignoreAir: true, + conduct: 0.03, }; elements.poisoned_ketchup_cloud = { + color: "#a8596b", + behavior: [ + "XX|XX|XX", + "XX|CO:1%5|M1%2.5 AND BO", + "XX|XX|XX", + ], + reactions: { + "poisoned_ketchup_rain_cloud": { "elem1":"poisoned_ketchup_rain_cloud", "temp1": -20 }, + }, + category: "gases", + temp: 110, + tempLow: 100, + stateLow: "poisoned_ketchup_rain_cloud", + state: "gas", + density: 0.5, + conduct: 0.03, + ignoreAir: true, +}; +elements.poisoned_ketchup_rain_cloud = { color: "#633640", behavior: [ "XX|XX|XX", - "M1%5|XX|M1%5", - "XX|CR:poisoned_ketchup%1|XX", + "XX|CH:poisoned_ketchup%0.05|M1%2.5 AND BO", + "XX|XX|XX", ], - category:"gases", - temp: 80, + category: "gases", + temp: 70, + tempHigh: 100, + stateHigh: "poisoned_ketchup_cloud", tempLow: 0, stateLow: "poisoned_ketchup_snow_cloud", state: "gas", - density: 1, + density: 0.5, + ignoreAir: true, conduct: 0.03, }; elements.ketchup_snow = { color: "#ed7a6d", behavior: behaviors.POWDER, - temp: 0, - tempHigh: 5, + temp: -5, + tempHigh: 18, stateHigh: "ketchup", - category: "land", + category: "land", state: "solid", - density: "100", + density: 100, reactions: { "plague": { "elem1": "poisoned_ketchup_snow", "elem2": null}, "infection": { "elem1": "poisoned_ketchup_snow", "elem2": null}, @@ -172,7 +210,7 @@ elements.ketchup_snow_cloud = { color: "#755652", behavior: [ "XX|XX|XX", - "M1%5|CH:ketchup_snow%0.05|M1%5", + "XX|CH:ketchup_snow%0.05|M1%2.5 AND BO", "XX|XX|XX", ], category:"gases", @@ -180,7 +218,7 @@ elements.ketchup_snow_cloud = { tempHigh: 30, stateHigh: "ketchup_cloud", state: "gas", - density: 2, + density: 0.55, reactions: { "plague": { "elem1": "poisoned_ketchup_snow_cloud", "elem2": null}, "infection": { "elem1": "poisoned_ketchup_snow_cloud"}, @@ -188,22 +226,23 @@ elements.ketchup_snow_cloud = { "fallout": { "elem1": "poisoned_ketchup_snow_cloud", chance:25}, "gloomwind": { "elem1": "poisoned_ketchup_snow_cloud", "elem2": null}, }, + ignoreAir: true, }; elements.poisoned_ketchup_snow = { color: "#d1697f", behavior: behaviors.POISONED_POWDER, - temp: 0, - tempHigh: 5, + temp: -5, + tempHigh: 18, stateHigh: "poisoned_ketchup", category: "land", state: "solid", - density: "100", + density: 100, }; elements.poisoned_ketchup_snow_cloud = { color: "#6e4e55", behavior: [ "XX|XX|XX", - "M1%5|CH:poisoned_ketchup_snow%0.05|M1%5", + "XX|CH:poisoned_ketchup_snow%0.05|M1%2.5 AND BO", "XX|XX|XX", ], category:"gases", @@ -211,7 +250,8 @@ elements.poisoned_ketchup_snow_cloud = { tempHigh: 30, stateHigh: "poisoned_ketchup_cloud", state: "gas", - density: 2, + density: 0.55, + ignoreAir: true, }; elements.mayonnaise = { color: "#F2EEE9", @@ -220,6 +260,8 @@ elements.mayonnaise = { category:"liquids", state: "liquid", density: 1000, + stain: 0.05, + isFood: true, }; elements.mustard = { color: "#D8AD01", @@ -228,18 +270,23 @@ elements.mustard = { category:"liquids", state: "liquid", density: 1052, + stain: 0.05, + isFood: true, }; elements.ketchup_gas = { color: "#ffb5ad", behavior: behaviors.GAS, + temp: 150, density: 0.6, state: "gas", - tempLow: 100, + tempLow: 95, stateLow: "ketchup", category: "gases", reactions: { "plague": { "elem1": "poisoned_ketchup_gas", "elem2": null}, - "ketchup_gas": { "elem1": null, "elem2": "ketchup_cloud", "chance":0.3, "y":[0,15] }, + "ketchup_gas": { "elem1": null, "elem2": "ketchup_cloud", "chance":0.3, "y":[0,15], "setting":"clouds" }, + "ketchup_cloud": { "elem1": "ketchup_cloud", "chance":0.4, "y":[0, 12], "setting":"clouds" }, + "ketchup_rain_cloud": { "elem1": "ketchup_rain_cloud", "chance":0.4, "y":[0, 12], "setting":"clouds" }, "infection": { "elem1": "poisoned_ketchup_gas"}, "radiation": { "elem1": "poisoned_ketchup_gas", chance:25}, "fallout": { "elem1": "poisoned_ketchup_gas", chance:25}, @@ -249,13 +296,16 @@ elements.ketchup_gas = { elements.poisoned_ketchup_gas = { color: "#e096a6", behavior: behaviors.POISONED_GAS, + temp: 150, density: 0.6, state: "gas", - tempLow: 100, + tempLow: 95, stateLow: "poisoned_ketchup", category: "gases", reactions: { - "poisoned_ketchup_gas": { "elem1": null, "elem2": "poisoned_ketchup_cloud", "chance":0.3, "y":[0,15] }, + "poisoned_ketchup_gas": { "elem1": null, "elem2": "poisoned_ketchup_cloud", "chance":0.3, "y":[0,15], "setting":"clouds" }, + "poisoned_ketchup_cloud": { "elem1": "poisoned_ketchup_cloud", "chance":0.4, "y":[0, 12], "setting":"clouds" }, + "ketchup_rain_cloud": { "elem1": "poisoned_ketchup_rain_cloud", "chance":0.4, "y":[0, 12], "setting":"clouds" }, }, }; elements.fry_sauce = { @@ -265,6 +315,8 @@ elements.fry_sauce = { category: "liquids", state: "liquid", density: 1149, + stain: 0.05, + isFood: true, }; elements.ketchup_powder = { color: "#E06320", @@ -279,6 +331,7 @@ elements.ketchup_powder = { }, state: "solid", category: "powders", + isFood: true, }; elements.poisoned_ketchup_powder = { color: "#e0204a", @@ -298,6 +351,7 @@ elements.cumin = { burn: 40, burnTime: 40, burnInto: "ash", + isFood: true, }; elements.eketchup_spout = { name: "E-Ketchup Spout", @@ -328,6 +382,7 @@ elements.antiketchup = { category:"special", state: "liquid", density: 1092, + stain: 0.05, }; elements.dirty_ketchup = { color: "#851a0d", @@ -341,6 +396,7 @@ elements.dirty_ketchup = { stateLow: "frozen_ketchup", density: 1140, hidden: true, + stain: 0.05, }; elements.ketchup_gold = { color: ["#eb8a8a", "#bf3939", "#ff6161"], @@ -409,7 +465,16 @@ runAfterLoad(function() { /* Changelog Mod made by Nubo318. Contributors include DeviantEquinox and An Orbit. -Version 1.3.2 +Version 1.3.3 + +Version 1.3.3 (23rd of August 2023) ++ All liquids added on this mod can now stain stuff, with the exception of molten metals ++ Certain elements can now be mixed with dough and batter +~ Fixed reactions that turned clouds into pyrocumulus when in contact with with smoke or ash +~ Optimized the way in which new reactions of vanilla elements are coded +~ Changed the initial temperature of multiple elements +~ Fixed a bug that caused Ketchup Snow and its poisoned variant to not display their info properly +~ Ketchup clouds now work more similarly to vanilla clouds Version 1.3.2 (22nd of August 2023) - Removed some elements due to their inclusion or some form of it in the vanilla game, including: From 761fe80e6b4dd367f9daf5760d9bc47600f7199d Mon Sep 17 00:00:00 2001 From: GGodPL <46885632+GGodPL@users.noreply.github.com> Date: Tue, 29 Aug 2023 01:39:16 +0200 Subject: [PATCH 03/10] Add betterSettings, editTools mods --- betterSettings.js | 291 ++++++++++++++++++++++ editTools.js | 612 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 903 insertions(+) create mode 100644 betterSettings.js create mode 100644 editTools.js diff --git a/betterSettings.js b/betterSettings.js new file mode 100644 index 00000000..8baba711 --- /dev/null +++ b/betterSettings.js @@ -0,0 +1,291 @@ +const settingType = { + COLOR: [0, "#ff0000"], + TEXT: [1, ""], + NUMBER: [2, 0], + BOOLEAN: [3, false], + SELECT: [4, null] +} +class Setting { + constructor (name, storageName, type, disabled = false, defaultValue = null) { + this.tabName = null; + this.name = name; + this.storageName = storageName; + this.type = type[0]; + this.disabled = disabled; + this.defaultValue = defaultValue ?? type[1]; + } + + set(value) { + this.value = value; + const settings = JSON.parse(localStorage.getItem(`${this.tabName}/settings`)) ?? {}; + settings[this.name] = value; + localStorage.setItem(`${this.tabName}/settings`, JSON.stringify(settings)); + } + + update() { + this.value = (JSON.parse(localStorage.getItem(`${this.tabName}/settings`)) ?? {})[this.name] ?? this.defaultValue; + } + + get() { + this.update(); + return this.value; + } + + enable() { + this.disabled = false; + } + + disable() { + this.disabled = true; + } + + #parseColor(colorString) { + if (colorString instanceof Array) return parseColor(colorString[0]); + if (typeof colorString != "string") return "#ffffff"; + if (colorString.startsWith("rgb(")) { + const color = colorString.replace("rgb(", "").replace(")", ""); + return `#${color.split(",").map(a => parseInt(a).toString(16)).join("")}`; + } else { + if (colorString.startsWith("#")) { + const color = colorString.slice(1); + if (color.length == 3) return `#${color.repeat(2)}`; + else if (color.length == 2) return `#${color.repeat(3)}`; + else if (color.length >= 6) return `#${color.slice(0, 6)}`; + else return `#${color}`; + } + } + } + + build() { + const value = this.get(); + const id = "betterSettings/" + this.modName + "/" + this.storageName; + const span = document.createElement("span"); + span.className = "setting-span"; + span.title = 'Default: "' + this.defaultValue + '"' + (this.disabled ? ". This setting is disabled." : ""); + span.innerText = this.name + " "; + const element = document.createElement("input"); + switch (this.type) { + case 0: { + element.type = "color"; + element.disabled = this.disabled; + element.id = id; + element.value = value; + element.onchange = (ev) => { + this.set(this.#parseColor(ev.target.value)); + } + break; + } + case 1: { + element.type = "text"; + element.disabled = this.disabled; + element.id = id; + element.value = value; + element.onchange = (ev) => { + this.set(ev.target.value); + } + break; + } + case 2: { + element.type = "number"; + element.disabled = this.disabled; + element.id = id; + element.value = value; + element.onchange = (ev) => { + this.set(parseFloat(ev.target.value)); + } + break; + } + case 3: { + element.type = "input"; + element.className = "toggleInput"; + element.disabled = this.disabled; + element.id = id; + element.value = value ? "ON" : "OFF"; + element.setAttribute("state", value ? "1" : "0"); + element.onclick = (ev) => { + ev.target.value = ev.target.value == "ON" ? "OFF" : "ON"; + ev.target.setAttribute("state", ev.target.getAttribute("state") == "1" ? "0" : "1"); + this.set(ev.target.value == "ON"); + } + break; + } + } + span.appendChild(element); + return span; + } +} + +class SelectSetting extends Setting { + constructor (name, storageName, values, disabled = false, defaultValue = null) { + super(name, storageName, settingType.SELECT, disabled, defaultValue ?? values[0][1]); + this.values = values; + } + + build() { + const value = this.get(); + const id = "betterSettings/" + this.modName + "/" + this.storageName; + let selected = false; + const span = document.createElement("span"); + span.className = "setting-span"; + span.title = "Default: " + this.defaultValue; + span.innerText = this.name; + const element = document.createElement("select"); + element.id = id; + for (const val of this.values) { + const option = document.createElement("option"); + option.value = val[0]; + option.innerText = val[1]; + if (val[0] == value && !selected) { + option.selected = true; + selected = true; + } + element.appendChild(option); + } + element.onchange = (ev) => { + this.set(ev.target.value); + } + span.appendChild(element); + return span; + } +} + +class SettingsTab { + constructor (tabName) { + this.categories = new Map(); + this.registry = new Map(); + this.tabName = tabName; + } + + registerSetting(setting, category = "General") { + setting.tabName = this.tabName.toLowerCase().replace(/ /, "_"); + setting.update(); + if (this.categories.has(category)) this.categories.get(category).push(setting); + else this.categories.set(category, [setting]); + this.registry.set(setting.storageName, setting); + } + + registerSettings(category = "General", ...settings) { + for (const setting of settings) { + this.registerSetting(setting, category); + } + } + + set(name, value) { + this.registry.get(name)?.set(value); + } + + get(name) { + return this.registry.get(name)?.get(); + } + + build() { + const result = document.createElement("div"); + for (const key of this.categories.keys()) { + const category = document.createElement("div"); + const title = document.createElement("span"); + title.innerText = key; + title.className = "betterSettings-categoryTitle"; + category.appendChild(title); + for (const setting of this.categories.get(key)) { + if (setting instanceof Setting) category.appendChild(setting.build()); + } + result.append(category, document.createElement("br")); + } + return result; + } +} + +class SettingsManager { + constructor () { + this.settings = new Map(); + } + + registerTab(settingsTab) { + this.settings.set(settingsTab.tabName, settingsTab); + } + + getSettings() { + return this.settings; + } +} + +const settingsManager = new SettingsManager(); +{ +const injectCss = () => { + const css = `.modSelectSettingsButton { + padding: 10px; + cursor: pointer; + } + .modSelectSettingsButton[current=true] { + background-color: rgb(71, 71, 71); + } + .modSelectSettingsButton:hover { + background-color: rgb(51, 51, 51); + } + #modSelectControls { + margin-bottom: 10px; + position: relative; + display: flex; + overflow-x: scroll; + } + .betterSettings-categoryTitle { + font-size: 1.25em; + }`; + const style = document.createElement("style"); + style.innerHTML = css; + document.head.appendChild(style); +} + +const inject = () => { + const settingsMenu = document.getElementById("settingsMenu"); + const menuText = settingsMenu.querySelector(".menuText"); + const menuTextChildren = menuText.children; + const generalDiv = document.createElement("div"); + generalDiv.id = "betterSettings/div/general"; + while (menuTextChildren.length > 0) { + generalDiv.appendChild(menuTextChildren[0]); + } + menuText.appendChild(generalDiv); + const controls = document.createElement("div"); + controls.id = "modSelectControls"; + const generalButton = document.createElement("button"); + generalButton.setAttribute("current", true); + generalButton.id = "betterSettings/button/general"; + generalButton.className = "modSelectSettingsButton"; + generalButton.innerText = "General"; + generalButton.onclick = (ev) => { + for (const element of controls.children) { + element.setAttribute("current", false); + document.getElementById(element.id.replace("button", "div")).style.display = "none"; + } + ev.target.setAttribute("current", true); + document.getElementById("betterSettings/div/general").style.display = ""; + } + controls.appendChild(generalButton); + const wrapper = document.createElement("div"); + wrapper.appendChild(generalDiv); + for (const mod of settingsManager.getSettings().keys()) { + const modButton = document.createElement("button"); + modButton.setAttribute("current", false); + modButton.id = "betterSettings/button/" + mod; + modButton.className = "modSelectSettingsButton"; + modButton.innerText = mod; + modButton.onclick = (ev) => { + for (const element of controls.children) { + element.setAttribute("current", false); + document.getElementById(element.id.replace("button", "div")).style.display = "none"; + } + ev.target.setAttribute("current", true); + document.getElementById("betterSettings/div/" + mod).style.display = ""; + } + controls.appendChild(modButton); + const modDiv = document.createElement("div"); + modDiv.style.display = "none"; + modDiv.id = "betterSettings/div/" + mod; + modDiv.appendChild(settingsManager.getSettings().get(mod).build()); + wrapper.appendChild(modDiv); + } + menuText.append(controls, wrapper); +} +runAfterLoadList.push(inject, injectCss); +} \ No newline at end of file diff --git a/editTools.js b/editTools.js new file mode 100644 index 00000000..94ec191f --- /dev/null +++ b/editTools.js @@ -0,0 +1,612 @@ +if (!enabledMods.includes("mods/betterSettings.js")) { enabledMods.unshift("mods/betterSettings.js"); localStorage.setItem("enabledMods", JSON.stringify(enabledMods)); window.location.reload() }; + +const settingsTab = new SettingsTab("Edit tools"); + +const element = new Setting("Element", "element", settingType.TEXT, false, "wall"); +const replace = new Setting("Replace pixels", "replace", settingType.BOOLEAN, false, true); + +const filter = new Setting("Filter", "filter", settingType.BOOLEAN, false, false); +const filteredElement = new Setting("Filtered Element", "filterElement", settingType.TEXT, false, ""); + +const transparentSelection = new Setting("Transparent selection", "transparentSelection", settingType.BOOLEAN, false, true); + +settingsTab.registerSettings("Box tools", element, replace); +settingsTab.registerSettings("Filter settings", filter, filteredElement); +settingsTab.registerSettings("Selection settings", transparentSelection); + +settingsManager.registerTab(settingsTab); + +runAfterLoadList.push(() => { + // the game doesn't load without the setTimeout + setTimeout(() => { + document.getElementById("elementButton-select").style.backgroundColor = "transparent"; + document.getElementById("elementButton-select").style.border = "2px dashed rgba(255, 255, 255, 0.75)"; + document.getElementById("elementButton-select").classList = ["elementButton"]; + }, 1) +}) + +// current selection +let selection_ = { + start: {}, + end: {} +} +// selection position, used for moveSelection +let selectionPosition = {}; +// copy of the selection that is being moved (pixels), used for moveSelection +let selectionMoved = []; +// offsets of the mouse relative to selection position, used for moveSelection +let selectionOffsets = {}; + +// current box, used in box and rectangle +let box = { + start: {}, + end: {} +} + +// whether the next mouseUp even should trigger +let skip = false; +// whether user is currently holding +let holding = false; +// mobile device shift equivalent +let lockSelection = false; + +// current clipboard, used for cutting, copying and pasting +let clipboard = []; + +// createPixel but with color argument +const createPixelColor = (element, x, y, color) => { + const pixel = new Pixel(x, y, element); + pixel.color = color; + currentPixels.push(pixel); + checkUnlock(element); +} + +// replaces a pixel at x, y position with replacement element specified in the settings +const replacePixel = (x, y) => { + if (outOfBounds(x, y)) return; + if (pixelMap[x][y]) { + if (!replace.get()) return; + deletePixel(x, y); + } + createPixel(element.get(), x, y); +} + +// checks whether position pos is in bounds +const inBounds = (bounds, pos) => { + bounds = { + start: { + x: Math.min(bounds.start.x, bounds.end.x), + y: Math.min(bounds.start.y, bounds.end.y) + }, + end: { + x: Math.max(bounds.start.x, bounds.end.x), + y: Math.max(bounds.start.y, bounds.end.y) + } + } + return pos.x >= bounds.start.x && pos.x <= bounds.end.x && pos.y >= bounds.start.y && pos.y <= bounds.end.y; +} + +// generates a selection based on start and end positions +const select = (start, end) => { + const res = []; + for (let i = 0; i <= end.x - start.x; i++) { + res[i] = []; + for (let j = 0; j <= end.y - start.y; j++) { + res[i][j] = pixelMap[i + start.x][j + start.y]; + } + } + return res; +} + +elements.select = { + name: "Select", + category: "editTools", + maxSize: 1, + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) { + skip = true; + return; + } + skip = false; + holding = true; + selection_.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + selection_.end = mousePos; + if (selection_.start == selection_.end) selection_ = {}; + holding = false; + }, + tool: (_) => {}, + perTick: () => { + if (!selection_) return; + if (holding) { + selection_.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(selection_.start.x * pixelSize), + y: Math.round(selection_.start.y * pixelSize) + } + const end = { + x: Math.round(selection_.end.x * pixelSize), + y: Math.round((selection_.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "white"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } +} + +elements.box = { + name: "Box", + category: "editTools", + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y) || showingMenu) { + skip = true; + return; + } + skip = false; + holding = true; + box.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + box.end = mousePos; + holding = false; + for (let i = -Math.floor(mouseSize / 2); i < Math.abs(box.start.x - box.end.x) + Math.floor(mouseSize / 2); i++) { + const x = Math.min(box.start.x, box.end.x) + i; + for (let j = -Math.floor(mouseSize / 2); j <= Math.floor(mouseSize / 2); j++) { + replacePixel(x, box.start.y + (box.start.y > box.end.y ? -j : j)); + replacePixel(x, box.end.y + (box.end.y > box.start.y ? -j : j)); + } + } + for (let i = -Math.floor(mouseSize / 2); i <= Math.abs(box.start.y - box.end.y) + Math.floor(mouseSize / 2); i++) { + const y = Math.min(box.start.y, box.end.y) + i; + for (let j = -Math.floor(mouseSize / 2); j <= Math.floor(mouseSize / 2); j++) { + replacePixel(box.start.x + (box.start.x > box.end.x ? -j : j), y); + replacePixel(box.end.x + (box.end.x > box.start.x ? -j : j), y); + } + } + }, + tool: (_) => {}, + perTick: () => { + if (holding) { + if (shiftDown || lockSelection) { + box.end = { + y: mousePos.y, + x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) + }; + mousePos = box.end; + } else { + box.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(box.start.x * pixelSize), + y: Math.round(box.start.y * pixelSize) + } + const end = { + x: Math.round(box.end.x * pixelSize), + y: Math.round((box.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "yellow"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } + } +} + +elements.cut = { + name: "Cut", + category: "editTools", + maxSize: 0, + tool: (_) => {}, + onSelect: () => { + if (!selection_.start || !selection_.end) return alert("No selection made"); + const selected = select({ + x: Math.min(selection_.start.x, selection_.end.x), + y: Math.min(selection_.start.y, selection_.end.y) + }, { + x: Math.max(selection_.start.x, selection_.end.x), + y: Math.max(selection_.start.y, selection_.end.y) + }) + clipboard = selected; + for (const i of selected) { + for (const pixel of i) { + if (!pixel) continue; + deletePixel(pixel.x, pixel.y); + } + } + selectElement("unknown"); + } +} +elements.copy = { + name: "Copy", + category: "editTools", + maxSize: 0, + tool: (_) => {}, + onSelect: () => { + if (!selection_.start || !selection_.end) return alert("No selection made"); + const selected = select({ + x: Math.min(selection_.start.x, selection_.end.x), + y: Math.min(selection_.start.y, selection_.end.y) + }, { + x: Math.max(selection_.start.x, selection_.end.x), + y: Math.max(selection_.start.y, selection_.end.y) + }) + clipboard = selected; + selectElement("unknown"); + } +} + +elements.selectionMove = { + name: "Move selection", + category: "editTools", + maxSize: 0, + tool: (_) => {}, + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) return; + if (!selection_.start || !selection_.end) return; + if (!inBounds(selection_, mousePos)) return; + selectionOffsets = { + x: mousePos.x - Math.min(selection_.start.x, selection_.end.x), + y: mousePos.y - Math.min(selection_.start.y, selection_.end.y) + } + const selected = select({ + x: Math.min(selection_.start.x, selection_.end.x), + y: Math.min(selection_.start.y, selection_.end.y) + }, { + x: Math.max(selection_.start.x, selection_.end.x), + y: Math.max(selection_.start.y, selection_.end.y) + }) + selectionPosition = { + x: selection_.start.x, + y: selection_.start.y + } + for (const i of selected) { + for (const pixel of i) { + if (!pixel) continue; + deletePixel(pixel.x, pixel.y); + } + } + selectionMoved = selected; + }, + perTick: () => { + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + ctx.globalAlpha = 1; + const start = { + x: Math.round(selection_.start.x * pixelSize), + y: Math.round(selection_.start.y * pixelSize) + } + const end = { + x: Math.round(selection_.end.x * pixelSize), + y: Math.round((selection_.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "white"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + if (!selectionMoved || selectionMoved.length == 0) return; + selectionPosition = { + x: mousePos.x - selectionOffsets.x, + y: mousePos.y - selectionOffsets.y + } + selection_ = { + start: { + x: selectionPosition.x, + y: selectionPosition.y + }, + end: { + x: selectionPosition.x + selectionMoved.length - 1, + y: selectionPosition.y + selectionMoved[0].length - 1 + } + } + ctx.globalAlpha = 0.5; + for (let i = 0; i < selectionMoved.length; i++) { + for (let j = 0; j < selectionMoved[i].length; j++) { + const x = selectionPosition.x + i; + const y = selectionPosition.y + j; + if (!selectionMoved[i][j]) continue; + ctx.globalCompositeOperation = 'destination-over'; + ctx.fillStyle = selectionMoved[i][j].color; + ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); + } + } + }, + onMouseUp: () => { + if (outOfBounds(mousePos.x, mousePos.y)) return; + for (let i = 0; i < selectionMoved.length; i++) { + for (let j = 0; j < selectionMoved[i].length; j++) { + const x = selectionPosition.x + i; + const y = selectionPosition.y + j; + if (!selectionMoved[i][j] && transparentSelection.get()) continue; + if (pixelMap[x][y]) deletePixel(x, y); + createPixelColor(selectionMoved[i][j].element, x, y, selectionMoved[i][j].color); + } + } + selectionMoved = []; + } +} + +elements.paste = { + name: "Paste", + category: "editTools", + tool: (_) => {}, + maxSize: 1, + onMouseDown: () => { + if (!clipboard) return alert("Nothing left to paste"); + for (let i = 0; i < clipboard.length; i++) { + for (let j = 0; j < clipboard[i].length; j++) { + const x = mousePos.x + i; + const y = mousePos.y + j; + if (outOfBounds(x, y) || (!clipboard[i][j] && transparentSelection.get())) continue; + if (!pixelMap[x][y]) createPixelColor(clipboard[i][j].element, x, y, clipboard[i][j].color); + else { + deletePixel(x, y); + createPixelColor(clipboard[i][j].element, x, y, clipboard[i][j].color); + } + } + } + } +} + +elements.rectangle = { + name: "Rectangle", + category: "editTools", + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) { + skip = true; + return; + } + skip = false; + holding = true; + box.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + box.end = mousePos; + holding = false; + for (let i = -Math.floor(mouseSize / 2); i <= Math.abs(box.start.x - box.end.x) + Math.floor(mouseSize / 2); i++) { + for (let j = -Math.floor(mouseSize / 2); j <= Math.abs(box.start.y - box.end.y) + Math.floor(mouseSize / 2); j++) { + replacePixel(Math.min(box.start.x, box.end.x) + i, Math.min(box.start.y, box.end.y) + j); + } + } + }, + tool: (_) => {}, + perTick: () => { + if (holding) { + if (shiftDown || lockSelection) { + box.end = { + y: mousePos.y, + x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) + }; + mousePos = box.end; + } else { + box.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(box.start.x * pixelSize), + y: Math.round(box.start.y * pixelSize) + } + const end = { + x: Math.round(box.end.x * pixelSize), + y: Math.round((box.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "yellow"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } + } +} + +// Ellipse Midpoint Algorithm +// https://stackoverflow.com/questions/15474122/is-there-a-midpoint-ellipse-algorithm +// https://web.archive.org/web/20160305234351/http://geofhagopian.net/sablog/Slog-october/slog-10-25-05.htm +function ellipsePlotPoints(xc, yc, x, y) { + replacePixel(xc + x, yc + y); + replacePixel(xc - x, yc + y); + replacePixel(xc + x, yc - y); + replacePixel(xc - x, yc - y); +} + +function ellipse(xc, yc, w, h) { + const a2 = Math.pow(w, 2); + const b2 = Math.pow(h, 2); + const twoa2 = 2 * a2; + const twob2 = 2 * b2; + let p; + let x = 0; + let y = h; + let px = 0; + let py = twoa2 * y; + + /* Plot the initial point in each quadrant. */ + ellipsePlotPoints(xc, yc, x, y); + + p = Math.round(b2 - (a2 * b) + (0.25 * a2)); + while (px < py) { + x++; + px += twob2; + if (p < 0) p += b2 + px; + else { + y--; + py -= twoa2; + p += b2 + px - py; + } + ellipsePlotPoints(xc, yc, x, y); + } + + p = Math.round(b2 * (x + 0.5) * (x + 0.5) + a2 * (y - 1) * (y - 1) - a2 * b2); + while (y > 0) { + y--; + py -= twoa2; + if (p > 0) p += a2 - py; + else { + x++; + px += twob2; + p += a2 - py + px; + } + ellipsePlotPoints(xc, yc, x, y); + } +} + +elements.ellipse = { + name: "Ellipse", + category: "editTools", + tool: (_) => {}, + maxSize: 1, + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) { + skip = true; + return; + } + skip = false; + holding = true; + box.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + box.end = mousePos; + holding = false; + const w = Math.abs(box.end.x - box.start.x); + const h = Math.abs(box.end.y - box.start.y); + const x = Math.min(box.start.x, box.end.x) + Math.floor(w / 2); + const y = Math.min(box.start.y, box.end.y) + Math.floor(h / 2); + ellipse(x, y, Math.floor(w / 2), Math.floor(h / 2)); + }, + tool: (_) => {}, + perTick: () => { + if (holding) { + if (shiftDown || lockSelection) { + box.end = { + y: mousePos.y, + x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) + }; + mousePos = box.end; + } else { + box.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(box.start.x * pixelSize), + y: Math.round(box.start.y * pixelSize) + } + const end = { + x: Math.round(box.end.x * pixelSize), + y: Math.round((box.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "yellow"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } + } +} + +document.addEventListener("mousedown", (ev) => { + if (elements[currentElement].onMouseDown) { + elements[currentElement].onMouseDown(); + } +}) + +// mouse2 overwrite for delete filter +// no mouse1 overwrite for replace functionality, I don't think I should be overwriting such big functions +// maybe in a future update +mouse2Action = (e,mouseX=undefined,mouseY=undefined,startPos) => { + // Erase pixel at mouse position + if (mouseX == undefined && mouseY == undefined) { + var canvas = document.getElementById("game"); + var ctx = canvas.getContext("2d"); + lastPos = mousePos; + mousePos = getMousePos(canvas, e); + var mouseX = mousePos.x; + var mouseY = mousePos.y; + } + // If the current element is "pick" or "lookup", coords = [mouseX,mouseY] + if (currentElement == "pick" || currentElement == "lookup") { + var coords = [[mouseX,mouseY]]; + } + else if (!isMobile) { + startPos = startPos || lastPos + var coords = lineCoords(startPos.x,startPos.y,mouseX,mouseY); + } + else { + var coords = mouseRange(mouseX,mouseY); + } + // For each x,y in coords + for (var i = 0; i < coords.length; i++) { + var x = coords[i][0]; + var y = coords[i][1]; + + if (!isEmpty(x, y)) { + if (outOfBounds(x,y)) { + continue + } + var pixel = pixelMap[x][y]; + // filter + if (filter.get() && pixel.element != filteredElement.get()) continue; + delete pixelMap[x][y]; + // Remove pixel from currentPixels + for (var j = 0; j < currentPixels.length; j++) { + if (currentPixels[j].x == x && currentPixels[j].y == y) { + currentPixels.splice(j, 1); + break; + } + } + } + } +} + +// Mobile check +// https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser +// http://detectmobilebrowsers.com/ +window.mobileAndTabletCheck = () => { + let check = false; + (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); + return check; +}; + +// if user is on mobile, add lock selection tool +if (window.mobileAndTabletCheck()) { + elements.lockSelection = { + name: "Lock selection", + category: "editTools", + tool: (_) => {}, + onSelect: () => { + // unselect so you can click it multiple times + selectElement("unknown"); + document.getElementById("elementButton-lockSelection").innerText = lockSelection ? "Lock selection" : "Unlock selection"; + lockSelection = !lockSelection; + } + } +} \ No newline at end of file From 956dc96e583a4e701d26abc37d21e15c974eab74 Mon Sep 17 00:00:00 2001 From: GGodPL <46885632+GGodPL@users.noreply.github.com> Date: Tue, 29 Aug 2023 01:39:18 +0200 Subject: [PATCH 04/10] add betterSettings and editTools mods From 98b18e5994ac939fc23dd82c1eb4f9b3e1c5ca76 Mon Sep 17 00:00:00 2001 From: GGod Date: Tue, 29 Aug 2023 01:50:56 +0200 Subject: [PATCH 05/10] Revert "Add betterSettings, editTools mods" This reverts commit 761fe80e6b4dd367f9daf5760d9bc47600f7199d. --- betterSettings.js | 291 ---------------------- editTools.js | 612 ---------------------------------------------- 2 files changed, 903 deletions(-) delete mode 100644 betterSettings.js delete mode 100644 editTools.js diff --git a/betterSettings.js b/betterSettings.js deleted file mode 100644 index 8baba711..00000000 --- a/betterSettings.js +++ /dev/null @@ -1,291 +0,0 @@ -const settingType = { - COLOR: [0, "#ff0000"], - TEXT: [1, ""], - NUMBER: [2, 0], - BOOLEAN: [3, false], - SELECT: [4, null] -} -class Setting { - constructor (name, storageName, type, disabled = false, defaultValue = null) { - this.tabName = null; - this.name = name; - this.storageName = storageName; - this.type = type[0]; - this.disabled = disabled; - this.defaultValue = defaultValue ?? type[1]; - } - - set(value) { - this.value = value; - const settings = JSON.parse(localStorage.getItem(`${this.tabName}/settings`)) ?? {}; - settings[this.name] = value; - localStorage.setItem(`${this.tabName}/settings`, JSON.stringify(settings)); - } - - update() { - this.value = (JSON.parse(localStorage.getItem(`${this.tabName}/settings`)) ?? {})[this.name] ?? this.defaultValue; - } - - get() { - this.update(); - return this.value; - } - - enable() { - this.disabled = false; - } - - disable() { - this.disabled = true; - } - - #parseColor(colorString) { - if (colorString instanceof Array) return parseColor(colorString[0]); - if (typeof colorString != "string") return "#ffffff"; - if (colorString.startsWith("rgb(")) { - const color = colorString.replace("rgb(", "").replace(")", ""); - return `#${color.split(",").map(a => parseInt(a).toString(16)).join("")}`; - } else { - if (colorString.startsWith("#")) { - const color = colorString.slice(1); - if (color.length == 3) return `#${color.repeat(2)}`; - else if (color.length == 2) return `#${color.repeat(3)}`; - else if (color.length >= 6) return `#${color.slice(0, 6)}`; - else return `#${color}`; - } - } - } - - build() { - const value = this.get(); - const id = "betterSettings/" + this.modName + "/" + this.storageName; - const span = document.createElement("span"); - span.className = "setting-span"; - span.title = 'Default: "' + this.defaultValue + '"' + (this.disabled ? ". This setting is disabled." : ""); - span.innerText = this.name + " "; - const element = document.createElement("input"); - switch (this.type) { - case 0: { - element.type = "color"; - element.disabled = this.disabled; - element.id = id; - element.value = value; - element.onchange = (ev) => { - this.set(this.#parseColor(ev.target.value)); - } - break; - } - case 1: { - element.type = "text"; - element.disabled = this.disabled; - element.id = id; - element.value = value; - element.onchange = (ev) => { - this.set(ev.target.value); - } - break; - } - case 2: { - element.type = "number"; - element.disabled = this.disabled; - element.id = id; - element.value = value; - element.onchange = (ev) => { - this.set(parseFloat(ev.target.value)); - } - break; - } - case 3: { - element.type = "input"; - element.className = "toggleInput"; - element.disabled = this.disabled; - element.id = id; - element.value = value ? "ON" : "OFF"; - element.setAttribute("state", value ? "1" : "0"); - element.onclick = (ev) => { - ev.target.value = ev.target.value == "ON" ? "OFF" : "ON"; - ev.target.setAttribute("state", ev.target.getAttribute("state") == "1" ? "0" : "1"); - this.set(ev.target.value == "ON"); - } - break; - } - } - span.appendChild(element); - return span; - } -} - -class SelectSetting extends Setting { - constructor (name, storageName, values, disabled = false, defaultValue = null) { - super(name, storageName, settingType.SELECT, disabled, defaultValue ?? values[0][1]); - this.values = values; - } - - build() { - const value = this.get(); - const id = "betterSettings/" + this.modName + "/" + this.storageName; - let selected = false; - const span = document.createElement("span"); - span.className = "setting-span"; - span.title = "Default: " + this.defaultValue; - span.innerText = this.name; - const element = document.createElement("select"); - element.id = id; - for (const val of this.values) { - const option = document.createElement("option"); - option.value = val[0]; - option.innerText = val[1]; - if (val[0] == value && !selected) { - option.selected = true; - selected = true; - } - element.appendChild(option); - } - element.onchange = (ev) => { - this.set(ev.target.value); - } - span.appendChild(element); - return span; - } -} - -class SettingsTab { - constructor (tabName) { - this.categories = new Map(); - this.registry = new Map(); - this.tabName = tabName; - } - - registerSetting(setting, category = "General") { - setting.tabName = this.tabName.toLowerCase().replace(/ /, "_"); - setting.update(); - if (this.categories.has(category)) this.categories.get(category).push(setting); - else this.categories.set(category, [setting]); - this.registry.set(setting.storageName, setting); - } - - registerSettings(category = "General", ...settings) { - for (const setting of settings) { - this.registerSetting(setting, category); - } - } - - set(name, value) { - this.registry.get(name)?.set(value); - } - - get(name) { - return this.registry.get(name)?.get(); - } - - build() { - const result = document.createElement("div"); - for (const key of this.categories.keys()) { - const category = document.createElement("div"); - const title = document.createElement("span"); - title.innerText = key; - title.className = "betterSettings-categoryTitle"; - category.appendChild(title); - for (const setting of this.categories.get(key)) { - if (setting instanceof Setting) category.appendChild(setting.build()); - } - result.append(category, document.createElement("br")); - } - return result; - } -} - -class SettingsManager { - constructor () { - this.settings = new Map(); - } - - registerTab(settingsTab) { - this.settings.set(settingsTab.tabName, settingsTab); - } - - getSettings() { - return this.settings; - } -} - -const settingsManager = new SettingsManager(); -{ -const injectCss = () => { - const css = `.modSelectSettingsButton { - padding: 10px; - cursor: pointer; - } - .modSelectSettingsButton[current=true] { - background-color: rgb(71, 71, 71); - } - .modSelectSettingsButton:hover { - background-color: rgb(51, 51, 51); - } - #modSelectControls { - margin-bottom: 10px; - position: relative; - display: flex; - overflow-x: scroll; - } - .betterSettings-categoryTitle { - font-size: 1.25em; - }`; - const style = document.createElement("style"); - style.innerHTML = css; - document.head.appendChild(style); -} - -const inject = () => { - const settingsMenu = document.getElementById("settingsMenu"); - const menuText = settingsMenu.querySelector(".menuText"); - const menuTextChildren = menuText.children; - const generalDiv = document.createElement("div"); - generalDiv.id = "betterSettings/div/general"; - while (menuTextChildren.length > 0) { - generalDiv.appendChild(menuTextChildren[0]); - } - menuText.appendChild(generalDiv); - const controls = document.createElement("div"); - controls.id = "modSelectControls"; - const generalButton = document.createElement("button"); - generalButton.setAttribute("current", true); - generalButton.id = "betterSettings/button/general"; - generalButton.className = "modSelectSettingsButton"; - generalButton.innerText = "General"; - generalButton.onclick = (ev) => { - for (const element of controls.children) { - element.setAttribute("current", false); - document.getElementById(element.id.replace("button", "div")).style.display = "none"; - } - ev.target.setAttribute("current", true); - document.getElementById("betterSettings/div/general").style.display = ""; - } - controls.appendChild(generalButton); - const wrapper = document.createElement("div"); - wrapper.appendChild(generalDiv); - for (const mod of settingsManager.getSettings().keys()) { - const modButton = document.createElement("button"); - modButton.setAttribute("current", false); - modButton.id = "betterSettings/button/" + mod; - modButton.className = "modSelectSettingsButton"; - modButton.innerText = mod; - modButton.onclick = (ev) => { - for (const element of controls.children) { - element.setAttribute("current", false); - document.getElementById(element.id.replace("button", "div")).style.display = "none"; - } - ev.target.setAttribute("current", true); - document.getElementById("betterSettings/div/" + mod).style.display = ""; - } - controls.appendChild(modButton); - const modDiv = document.createElement("div"); - modDiv.style.display = "none"; - modDiv.id = "betterSettings/div/" + mod; - modDiv.appendChild(settingsManager.getSettings().get(mod).build()); - wrapper.appendChild(modDiv); - } - menuText.append(controls, wrapper); -} -runAfterLoadList.push(inject, injectCss); -} \ No newline at end of file diff --git a/editTools.js b/editTools.js deleted file mode 100644 index 94ec191f..00000000 --- a/editTools.js +++ /dev/null @@ -1,612 +0,0 @@ -if (!enabledMods.includes("mods/betterSettings.js")) { enabledMods.unshift("mods/betterSettings.js"); localStorage.setItem("enabledMods", JSON.stringify(enabledMods)); window.location.reload() }; - -const settingsTab = new SettingsTab("Edit tools"); - -const element = new Setting("Element", "element", settingType.TEXT, false, "wall"); -const replace = new Setting("Replace pixels", "replace", settingType.BOOLEAN, false, true); - -const filter = new Setting("Filter", "filter", settingType.BOOLEAN, false, false); -const filteredElement = new Setting("Filtered Element", "filterElement", settingType.TEXT, false, ""); - -const transparentSelection = new Setting("Transparent selection", "transparentSelection", settingType.BOOLEAN, false, true); - -settingsTab.registerSettings("Box tools", element, replace); -settingsTab.registerSettings("Filter settings", filter, filteredElement); -settingsTab.registerSettings("Selection settings", transparentSelection); - -settingsManager.registerTab(settingsTab); - -runAfterLoadList.push(() => { - // the game doesn't load without the setTimeout - setTimeout(() => { - document.getElementById("elementButton-select").style.backgroundColor = "transparent"; - document.getElementById("elementButton-select").style.border = "2px dashed rgba(255, 255, 255, 0.75)"; - document.getElementById("elementButton-select").classList = ["elementButton"]; - }, 1) -}) - -// current selection -let selection_ = { - start: {}, - end: {} -} -// selection position, used for moveSelection -let selectionPosition = {}; -// copy of the selection that is being moved (pixels), used for moveSelection -let selectionMoved = []; -// offsets of the mouse relative to selection position, used for moveSelection -let selectionOffsets = {}; - -// current box, used in box and rectangle -let box = { - start: {}, - end: {} -} - -// whether the next mouseUp even should trigger -let skip = false; -// whether user is currently holding -let holding = false; -// mobile device shift equivalent -let lockSelection = false; - -// current clipboard, used for cutting, copying and pasting -let clipboard = []; - -// createPixel but with color argument -const createPixelColor = (element, x, y, color) => { - const pixel = new Pixel(x, y, element); - pixel.color = color; - currentPixels.push(pixel); - checkUnlock(element); -} - -// replaces a pixel at x, y position with replacement element specified in the settings -const replacePixel = (x, y) => { - if (outOfBounds(x, y)) return; - if (pixelMap[x][y]) { - if (!replace.get()) return; - deletePixel(x, y); - } - createPixel(element.get(), x, y); -} - -// checks whether position pos is in bounds -const inBounds = (bounds, pos) => { - bounds = { - start: { - x: Math.min(bounds.start.x, bounds.end.x), - y: Math.min(bounds.start.y, bounds.end.y) - }, - end: { - x: Math.max(bounds.start.x, bounds.end.x), - y: Math.max(bounds.start.y, bounds.end.y) - } - } - return pos.x >= bounds.start.x && pos.x <= bounds.end.x && pos.y >= bounds.start.y && pos.y <= bounds.end.y; -} - -// generates a selection based on start and end positions -const select = (start, end) => { - const res = []; - for (let i = 0; i <= end.x - start.x; i++) { - res[i] = []; - for (let j = 0; j <= end.y - start.y; j++) { - res[i][j] = pixelMap[i + start.x][j + start.y]; - } - } - return res; -} - -elements.select = { - name: "Select", - category: "editTools", - maxSize: 1, - onMouseDown: () => { - if (outOfBounds(mousePos.x, mousePos.y)) { - skip = true; - return; - } - skip = false; - holding = true; - selection_.start = mousePos; - }, - onMouseUp: () => { - if (skip) return; - selection_.end = mousePos; - if (selection_.start == selection_.end) selection_ = {}; - holding = false; - }, - tool: (_) => {}, - perTick: () => { - if (!selection_) return; - if (holding) { - selection_.end = mousePos; - } - const canvas = document.getElementById("game"); - const ctx = canvas.getContext("2d"); - const start = { - x: Math.round(selection_.start.x * pixelSize), - y: Math.round(selection_.start.y * pixelSize) - } - const end = { - x: Math.round(selection_.end.x * pixelSize), - y: Math.round((selection_.end.y + 1) * pixelSize) - } - const {strokeStyle, lineWidth} = ctx; - ctx.setLineDash([8, 8]); - ctx.lineWidth = 2; - ctx.strokeStyle = "white"; - ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); - ctx.strokeStyle = strokeStyle; - ctx.lineWidth = lineWidth; - ctx.setLineDash([]); - } -} - -elements.box = { - name: "Box", - category: "editTools", - onMouseDown: () => { - if (outOfBounds(mousePos.x, mousePos.y) || showingMenu) { - skip = true; - return; - } - skip = false; - holding = true; - box.start = mousePos; - }, - onMouseUp: () => { - if (skip) return; - box.end = mousePos; - holding = false; - for (let i = -Math.floor(mouseSize / 2); i < Math.abs(box.start.x - box.end.x) + Math.floor(mouseSize / 2); i++) { - const x = Math.min(box.start.x, box.end.x) + i; - for (let j = -Math.floor(mouseSize / 2); j <= Math.floor(mouseSize / 2); j++) { - replacePixel(x, box.start.y + (box.start.y > box.end.y ? -j : j)); - replacePixel(x, box.end.y + (box.end.y > box.start.y ? -j : j)); - } - } - for (let i = -Math.floor(mouseSize / 2); i <= Math.abs(box.start.y - box.end.y) + Math.floor(mouseSize / 2); i++) { - const y = Math.min(box.start.y, box.end.y) + i; - for (let j = -Math.floor(mouseSize / 2); j <= Math.floor(mouseSize / 2); j++) { - replacePixel(box.start.x + (box.start.x > box.end.x ? -j : j), y); - replacePixel(box.end.x + (box.end.x > box.start.x ? -j : j), y); - } - } - }, - tool: (_) => {}, - perTick: () => { - if (holding) { - if (shiftDown || lockSelection) { - box.end = { - y: mousePos.y, - x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) - }; - mousePos = box.end; - } else { - box.end = mousePos; - } - const canvas = document.getElementById("game"); - const ctx = canvas.getContext("2d"); - const start = { - x: Math.round(box.start.x * pixelSize), - y: Math.round(box.start.y * pixelSize) - } - const end = { - x: Math.round(box.end.x * pixelSize), - y: Math.round((box.end.y + 1) * pixelSize) - } - const {strokeStyle, lineWidth} = ctx; - ctx.setLineDash([8, 8]); - ctx.lineWidth = 2; - ctx.strokeStyle = "yellow"; - ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); - ctx.strokeStyle = strokeStyle; - ctx.lineWidth = lineWidth; - ctx.setLineDash([]); - } - } -} - -elements.cut = { - name: "Cut", - category: "editTools", - maxSize: 0, - tool: (_) => {}, - onSelect: () => { - if (!selection_.start || !selection_.end) return alert("No selection made"); - const selected = select({ - x: Math.min(selection_.start.x, selection_.end.x), - y: Math.min(selection_.start.y, selection_.end.y) - }, { - x: Math.max(selection_.start.x, selection_.end.x), - y: Math.max(selection_.start.y, selection_.end.y) - }) - clipboard = selected; - for (const i of selected) { - for (const pixel of i) { - if (!pixel) continue; - deletePixel(pixel.x, pixel.y); - } - } - selectElement("unknown"); - } -} -elements.copy = { - name: "Copy", - category: "editTools", - maxSize: 0, - tool: (_) => {}, - onSelect: () => { - if (!selection_.start || !selection_.end) return alert("No selection made"); - const selected = select({ - x: Math.min(selection_.start.x, selection_.end.x), - y: Math.min(selection_.start.y, selection_.end.y) - }, { - x: Math.max(selection_.start.x, selection_.end.x), - y: Math.max(selection_.start.y, selection_.end.y) - }) - clipboard = selected; - selectElement("unknown"); - } -} - -elements.selectionMove = { - name: "Move selection", - category: "editTools", - maxSize: 0, - tool: (_) => {}, - onMouseDown: () => { - if (outOfBounds(mousePos.x, mousePos.y)) return; - if (!selection_.start || !selection_.end) return; - if (!inBounds(selection_, mousePos)) return; - selectionOffsets = { - x: mousePos.x - Math.min(selection_.start.x, selection_.end.x), - y: mousePos.y - Math.min(selection_.start.y, selection_.end.y) - } - const selected = select({ - x: Math.min(selection_.start.x, selection_.end.x), - y: Math.min(selection_.start.y, selection_.end.y) - }, { - x: Math.max(selection_.start.x, selection_.end.x), - y: Math.max(selection_.start.y, selection_.end.y) - }) - selectionPosition = { - x: selection_.start.x, - y: selection_.start.y - } - for (const i of selected) { - for (const pixel of i) { - if (!pixel) continue; - deletePixel(pixel.x, pixel.y); - } - } - selectionMoved = selected; - }, - perTick: () => { - const canvas = document.getElementById("game"); - const ctx = canvas.getContext("2d"); - ctx.globalAlpha = 1; - const start = { - x: Math.round(selection_.start.x * pixelSize), - y: Math.round(selection_.start.y * pixelSize) - } - const end = { - x: Math.round(selection_.end.x * pixelSize), - y: Math.round((selection_.end.y + 1) * pixelSize) - } - const {strokeStyle, lineWidth} = ctx; - ctx.setLineDash([8, 8]); - ctx.lineWidth = 2; - ctx.strokeStyle = "white"; - ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); - ctx.strokeStyle = strokeStyle; - ctx.lineWidth = lineWidth; - ctx.setLineDash([]); - if (!selectionMoved || selectionMoved.length == 0) return; - selectionPosition = { - x: mousePos.x - selectionOffsets.x, - y: mousePos.y - selectionOffsets.y - } - selection_ = { - start: { - x: selectionPosition.x, - y: selectionPosition.y - }, - end: { - x: selectionPosition.x + selectionMoved.length - 1, - y: selectionPosition.y + selectionMoved[0].length - 1 - } - } - ctx.globalAlpha = 0.5; - for (let i = 0; i < selectionMoved.length; i++) { - for (let j = 0; j < selectionMoved[i].length; j++) { - const x = selectionPosition.x + i; - const y = selectionPosition.y + j; - if (!selectionMoved[i][j]) continue; - ctx.globalCompositeOperation = 'destination-over'; - ctx.fillStyle = selectionMoved[i][j].color; - ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); - } - } - }, - onMouseUp: () => { - if (outOfBounds(mousePos.x, mousePos.y)) return; - for (let i = 0; i < selectionMoved.length; i++) { - for (let j = 0; j < selectionMoved[i].length; j++) { - const x = selectionPosition.x + i; - const y = selectionPosition.y + j; - if (!selectionMoved[i][j] && transparentSelection.get()) continue; - if (pixelMap[x][y]) deletePixel(x, y); - createPixelColor(selectionMoved[i][j].element, x, y, selectionMoved[i][j].color); - } - } - selectionMoved = []; - } -} - -elements.paste = { - name: "Paste", - category: "editTools", - tool: (_) => {}, - maxSize: 1, - onMouseDown: () => { - if (!clipboard) return alert("Nothing left to paste"); - for (let i = 0; i < clipboard.length; i++) { - for (let j = 0; j < clipboard[i].length; j++) { - const x = mousePos.x + i; - const y = mousePos.y + j; - if (outOfBounds(x, y) || (!clipboard[i][j] && transparentSelection.get())) continue; - if (!pixelMap[x][y]) createPixelColor(clipboard[i][j].element, x, y, clipboard[i][j].color); - else { - deletePixel(x, y); - createPixelColor(clipboard[i][j].element, x, y, clipboard[i][j].color); - } - } - } - } -} - -elements.rectangle = { - name: "Rectangle", - category: "editTools", - onMouseDown: () => { - if (outOfBounds(mousePos.x, mousePos.y)) { - skip = true; - return; - } - skip = false; - holding = true; - box.start = mousePos; - }, - onMouseUp: () => { - if (skip) return; - box.end = mousePos; - holding = false; - for (let i = -Math.floor(mouseSize / 2); i <= Math.abs(box.start.x - box.end.x) + Math.floor(mouseSize / 2); i++) { - for (let j = -Math.floor(mouseSize / 2); j <= Math.abs(box.start.y - box.end.y) + Math.floor(mouseSize / 2); j++) { - replacePixel(Math.min(box.start.x, box.end.x) + i, Math.min(box.start.y, box.end.y) + j); - } - } - }, - tool: (_) => {}, - perTick: () => { - if (holding) { - if (shiftDown || lockSelection) { - box.end = { - y: mousePos.y, - x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) - }; - mousePos = box.end; - } else { - box.end = mousePos; - } - const canvas = document.getElementById("game"); - const ctx = canvas.getContext("2d"); - const start = { - x: Math.round(box.start.x * pixelSize), - y: Math.round(box.start.y * pixelSize) - } - const end = { - x: Math.round(box.end.x * pixelSize), - y: Math.round((box.end.y + 1) * pixelSize) - } - const {strokeStyle, lineWidth} = ctx; - ctx.setLineDash([8, 8]); - ctx.lineWidth = 2; - ctx.strokeStyle = "yellow"; - ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); - ctx.strokeStyle = strokeStyle; - ctx.lineWidth = lineWidth; - ctx.setLineDash([]); - } - } -} - -// Ellipse Midpoint Algorithm -// https://stackoverflow.com/questions/15474122/is-there-a-midpoint-ellipse-algorithm -// https://web.archive.org/web/20160305234351/http://geofhagopian.net/sablog/Slog-october/slog-10-25-05.htm -function ellipsePlotPoints(xc, yc, x, y) { - replacePixel(xc + x, yc + y); - replacePixel(xc - x, yc + y); - replacePixel(xc + x, yc - y); - replacePixel(xc - x, yc - y); -} - -function ellipse(xc, yc, w, h) { - const a2 = Math.pow(w, 2); - const b2 = Math.pow(h, 2); - const twoa2 = 2 * a2; - const twob2 = 2 * b2; - let p; - let x = 0; - let y = h; - let px = 0; - let py = twoa2 * y; - - /* Plot the initial point in each quadrant. */ - ellipsePlotPoints(xc, yc, x, y); - - p = Math.round(b2 - (a2 * b) + (0.25 * a2)); - while (px < py) { - x++; - px += twob2; - if (p < 0) p += b2 + px; - else { - y--; - py -= twoa2; - p += b2 + px - py; - } - ellipsePlotPoints(xc, yc, x, y); - } - - p = Math.round(b2 * (x + 0.5) * (x + 0.5) + a2 * (y - 1) * (y - 1) - a2 * b2); - while (y > 0) { - y--; - py -= twoa2; - if (p > 0) p += a2 - py; - else { - x++; - px += twob2; - p += a2 - py + px; - } - ellipsePlotPoints(xc, yc, x, y); - } -} - -elements.ellipse = { - name: "Ellipse", - category: "editTools", - tool: (_) => {}, - maxSize: 1, - onMouseDown: () => { - if (outOfBounds(mousePos.x, mousePos.y)) { - skip = true; - return; - } - skip = false; - holding = true; - box.start = mousePos; - }, - onMouseUp: () => { - if (skip) return; - box.end = mousePos; - holding = false; - const w = Math.abs(box.end.x - box.start.x); - const h = Math.abs(box.end.y - box.start.y); - const x = Math.min(box.start.x, box.end.x) + Math.floor(w / 2); - const y = Math.min(box.start.y, box.end.y) + Math.floor(h / 2); - ellipse(x, y, Math.floor(w / 2), Math.floor(h / 2)); - }, - tool: (_) => {}, - perTick: () => { - if (holding) { - if (shiftDown || lockSelection) { - box.end = { - y: mousePos.y, - x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) - }; - mousePos = box.end; - } else { - box.end = mousePos; - } - const canvas = document.getElementById("game"); - const ctx = canvas.getContext("2d"); - const start = { - x: Math.round(box.start.x * pixelSize), - y: Math.round(box.start.y * pixelSize) - } - const end = { - x: Math.round(box.end.x * pixelSize), - y: Math.round((box.end.y + 1) * pixelSize) - } - const {strokeStyle, lineWidth} = ctx; - ctx.setLineDash([8, 8]); - ctx.lineWidth = 2; - ctx.strokeStyle = "yellow"; - ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); - ctx.strokeStyle = strokeStyle; - ctx.lineWidth = lineWidth; - ctx.setLineDash([]); - } - } -} - -document.addEventListener("mousedown", (ev) => { - if (elements[currentElement].onMouseDown) { - elements[currentElement].onMouseDown(); - } -}) - -// mouse2 overwrite for delete filter -// no mouse1 overwrite for replace functionality, I don't think I should be overwriting such big functions -// maybe in a future update -mouse2Action = (e,mouseX=undefined,mouseY=undefined,startPos) => { - // Erase pixel at mouse position - if (mouseX == undefined && mouseY == undefined) { - var canvas = document.getElementById("game"); - var ctx = canvas.getContext("2d"); - lastPos = mousePos; - mousePos = getMousePos(canvas, e); - var mouseX = mousePos.x; - var mouseY = mousePos.y; - } - // If the current element is "pick" or "lookup", coords = [mouseX,mouseY] - if (currentElement == "pick" || currentElement == "lookup") { - var coords = [[mouseX,mouseY]]; - } - else if (!isMobile) { - startPos = startPos || lastPos - var coords = lineCoords(startPos.x,startPos.y,mouseX,mouseY); - } - else { - var coords = mouseRange(mouseX,mouseY); - } - // For each x,y in coords - for (var i = 0; i < coords.length; i++) { - var x = coords[i][0]; - var y = coords[i][1]; - - if (!isEmpty(x, y)) { - if (outOfBounds(x,y)) { - continue - } - var pixel = pixelMap[x][y]; - // filter - if (filter.get() && pixel.element != filteredElement.get()) continue; - delete pixelMap[x][y]; - // Remove pixel from currentPixels - for (var j = 0; j < currentPixels.length; j++) { - if (currentPixels[j].x == x && currentPixels[j].y == y) { - currentPixels.splice(j, 1); - break; - } - } - } - } -} - -// Mobile check -// https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser -// http://detectmobilebrowsers.com/ -window.mobileAndTabletCheck = () => { - let check = false; - (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); - return check; -}; - -// if user is on mobile, add lock selection tool -if (window.mobileAndTabletCheck()) { - elements.lockSelection = { - name: "Lock selection", - category: "editTools", - tool: (_) => {}, - onSelect: () => { - // unselect so you can click it multiple times - selectElement("unknown"); - document.getElementById("elementButton-lockSelection").innerText = lockSelection ? "Lock selection" : "Unlock selection"; - lockSelection = !lockSelection; - } - } -} \ No newline at end of file From 01a5877dfcc0a8a83c126667a98eb56247237733 Mon Sep 17 00:00:00 2001 From: GGod Date: Tue, 29 Aug 2023 01:52:05 +0200 Subject: [PATCH 06/10] add betterSettings and editTools mods --- mods/betterSettings.js | 291 +++++++++++++++++++ mods/editTools.js | 613 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 904 insertions(+) create mode 100644 mods/betterSettings.js create mode 100644 mods/editTools.js diff --git a/mods/betterSettings.js b/mods/betterSettings.js new file mode 100644 index 00000000..811946a7 --- /dev/null +++ b/mods/betterSettings.js @@ -0,0 +1,291 @@ +const settingType = { + COLOR: [0, "#ff0000"], + TEXT: [1, ""], + NUMBER: [2, 0], + BOOLEAN: [3, false], + SELECT: [4, null] +} +class Setting { + constructor (name, storageName, type, disabled = false, defaultValue = null) { + this.tabName = null; + this.name = name; + this.storageName = storageName; + this.type = type[0]; + this.disabled = disabled; + this.defaultValue = defaultValue ?? type[1]; + } + + set(value) { + this.value = value; + const settings = JSON.parse(localStorage.getItem(`${this.tabName}/settings`)) ?? {}; + settings[this.name] = value; + localStorage.setItem(`${this.tabName}/settings`, JSON.stringify(settings)); + } + + update() { + this.value = (JSON.parse(localStorage.getItem(`${this.tabName}/settings`)) ?? {})[this.name] ?? this.defaultValue; + } + + get() { + this.update(); + return this.value; + } + + enable() { + this.disabled = false; + } + + disable() { + this.disabled = true; + } + + #parseColor(colorString) { + if (colorString instanceof Array) return parseColor(colorString[0]); + if (typeof colorString != "string") return "#ffffff"; + if (colorString.startsWith("rgb(")) { + const color = colorString.replace("rgb(", "").replace(")", ""); + return `#${color.split(",").map(a => parseInt(a).toString(16)).join("")}`; + } else { + if (colorString.startsWith("#")) { + const color = colorString.slice(1); + if (color.length == 3) return `#${color.repeat(2)}`; + else if (color.length == 2) return `#${color.repeat(3)}`; + else if (color.length >= 6) return `#${color.slice(0, 6)}`; + else return `#${color}`; + } + } + } + + build() { + const value = this.get(); + const id = "betterSettings/" + this.modName + "/" + this.storageName; + const span = document.createElement("span"); + span.className = "setting-span"; + span.title = 'Default: "' + this.defaultValue + '"' + (this.disabled ? ". This setting is disabled." : ""); + span.innerText = this.name + " "; + const element = document.createElement("input"); + switch (this.type) { + case 0: { + element.type = "color"; + element.disabled = this.disabled; + element.id = id; + element.value = value; + element.onchange = (ev) => { + this.set(this.#parseColor(ev.target.value)); + } + break; + } + case 1: { + element.type = "text"; + element.disabled = this.disabled; + element.id = id; + element.value = value; + element.onchange = (ev) => { + this.set(ev.target.value); + } + break; + } + case 2: { + element.type = "number"; + element.disabled = this.disabled; + element.id = id; + element.value = value; + element.onchange = (ev) => { + this.set(parseFloat(ev.target.value)); + } + break; + } + case 3: { + element.type = "input"; + element.className = "toggleInput"; + element.disabled = this.disabled; + element.id = id; + element.value = value ? "ON" : "OFF"; + element.setAttribute("state", value ? "1" : "0"); + element.onclick = (ev) => { + ev.target.value = ev.target.value == "ON" ? "OFF" : "ON"; + ev.target.setAttribute("state", ev.target.getAttribute("state") == "1" ? "0" : "1"); + this.set(ev.target.value == "ON"); + } + break; + } + } + span.appendChild(element); + return span; + } +} + +class SelectSetting extends Setting { + constructor (name, storageName, values, disabled = false, defaultValue = null) { + super(name, storageName, settingType.SELECT, disabled, defaultValue ?? values[0][1]); + this.values = values; + } + + build() { + const value = this.get(); + const id = "betterSettings/" + this.modName + "/" + this.storageName; + let selected = false; + const span = document.createElement("span"); + span.className = "setting-span"; + span.title = "Default: " + this.defaultValue; + span.innerText = this.name; + const element = document.createElement("select"); + element.id = id; + for (const val of this.values) { + const option = document.createElement("option"); + option.value = val[0]; + option.innerText = val[1]; + if (val[0] == value && !selected) { + option.selected = true; + selected = true; + } + element.appendChild(option); + } + element.onchange = (ev) => { + this.set(ev.target.value); + } + span.appendChild(element); + return span; + } +} + +class SettingsTab { + constructor (tabName) { + this.categories = new Map(); + this.registry = new Map(); + this.tabName = tabName; + } + + registerSetting(setting, category = "General") { + setting.tabName = this.tabName.toLowerCase().replace(/ /, "_"); + setting.update(); + if (this.categories.has(category)) this.categories.get(category).push(setting); + else this.categories.set(category, [setting]); + this.registry.set(setting.storageName, setting); + } + + registerSettings(category = "General", ...settings) { + for (const setting of settings) { + this.registerSetting(setting, category); + } + } + + set(name, value) { + this.registry.get(name)?.set(value); + } + + get(name) { + return this.registry.get(name)?.get(); + } + + build() { + const result = document.createElement("div"); + for (const key of this.categories.keys()) { + const category = document.createElement("div"); + const title = document.createElement("span"); + title.innerText = key; + title.className = "betterSettings-categoryTitle"; + category.appendChild(title); + for (const setting of this.categories.get(key)) { + if (setting instanceof Setting) category.appendChild(setting.build()); + } + result.append(category, document.createElement("br")); + } + return result; + } +} + +class SettingsManager { + constructor () { + this.settings = new Map(); + } + + registerTab(settingsTab) { + this.settings.set(settingsTab.tabName, settingsTab); + } + + getSettings() { + return this.settings; + } +} + +const settingsManager = new SettingsManager(); +{ +const injectCss = () => { + const css = `.modSelectSettingsButton { + padding: 10px; + cursor: pointer; + } + .modSelectSettingsButton[current=true] { + background-color: rgb(71, 71, 71); + } + .modSelectSettingsButton:hover { + background-color: rgb(51, 51, 51); + } + #modSelectControls { + margin-bottom: 10px; + position: relative; + display: flex; + overflow-x: scroll; + } + .betterSettings-categoryTitle { + font-size: 1.25em; + }`; + const style = document.createElement("style"); + style.innerHTML = css; + document.head.appendChild(style); +} + +const inject = () => { + const settingsMenu = document.getElementById("settingsMenu"); + const menuText = settingsMenu.querySelector(".menuText"); + const menuTextChildren = menuText.children; + const generalDiv = document.createElement("div"); + generalDiv.id = "betterSettings/div/general"; + while (menuTextChildren.length > 0) { + generalDiv.appendChild(menuTextChildren[0]); + } + menuText.appendChild(generalDiv); + const controls = document.createElement("div"); + controls.id = "modSelectControls"; + const generalButton = document.createElement("button"); + generalButton.setAttribute("current", true); + generalButton.id = "betterSettings/button/general"; + generalButton.className = "modSelectSettingsButton"; + generalButton.innerText = "General"; + generalButton.onclick = (ev) => { + for (const element of controls.children) { + element.setAttribute("current", false); + document.getElementById(element.id.replace("button", "div")).style.display = "none"; + } + ev.target.setAttribute("current", true); + document.getElementById("betterSettings/div/general").style.display = ""; + } + controls.appendChild(generalButton); + const wrapper = document.createElement("div"); + wrapper.appendChild(generalDiv); + for (const mod of settingsManager.getSettings().keys()) { + const modButton = document.createElement("button"); + modButton.setAttribute("current", false); + modButton.id = "betterSettings/button/" + mod; + modButton.className = "modSelectSettingsButton"; + modButton.innerText = mod; + modButton.onclick = (ev) => { + for (const element of controls.children) { + element.setAttribute("current", false); + document.getElementById(element.id.replace("button", "div")).style.display = "none"; + } + ev.target.setAttribute("current", true); + document.getElementById("betterSettings/div/" + mod).style.display = ""; + } + controls.appendChild(modButton); + const modDiv = document.createElement("div"); + modDiv.style.display = "none"; + modDiv.id = "betterSettings/div/" + mod; + modDiv.appendChild(settingsManager.getSettings().get(mod).build()); + wrapper.appendChild(modDiv); + } + menuText.append(controls, wrapper); +} +runAfterLoadList.push(inject, injectCss); +} \ No newline at end of file diff --git a/mods/editTools.js b/mods/editTools.js new file mode 100644 index 00000000..2c9af74c --- /dev/null +++ b/mods/editTools.js @@ -0,0 +1,613 @@ +if (!enabledMods.includes("mods/betterSettings.js")) { enabledMods.unshift("mods/betterSettings.js"); localStorage.setItem("enabledMods", JSON.stringify(enabledMods)); window.location.reload() }; + +const settingsTab = new SettingsTab("Edit tools"); + +const element = new Setting("Element", "element", settingType.TEXT, false, "wall"); +const replace = new Setting("Replace pixels", "replace", settingType.BOOLEAN, false, true); + +const filter = new Setting("Filter", "filter", settingType.BOOLEAN, false, false); +const filteredElement = new Setting("Filtered Element", "filterElement", settingType.TEXT, false, ""); + +const transparentSelection = new Setting("Transparent selection", "transparentSelection", settingType.BOOLEAN, false, true); + +settingsTab.registerSettings("Box tools", element, replace); +settingsTab.registerSettings("Filter settings", filter, filteredElement); +settingsTab.registerSettings("Selection settings", transparentSelection); + +settingsManager.registerTab(settingsTab); + +runAfterLoadList.push(() => { + // the game doesn't load without the setTimeout + setTimeout(() => { + document.getElementById("elementButton-select").style.backgroundColor = "transparent"; + document.getElementById("elementButton-select").style.border = "2px dashed rgba(255, 255, 255, 0.75)"; + document.getElementById("elementButton-select").classList = ["elementButton"]; + }, 1) +}) + +// current selection +let selection_ = { + start: {}, + end: {} +} +// selection position, used for moveSelection +let selectionPosition = {}; +// copy of the selection that is being moved (pixels), used for moveSelection +let selectionMoved = []; +// offsets of the mouse relative to selection position, used for moveSelection +let selectionOffsets = {}; + +// current box, used in box and rectangle +let box = { + start: {}, + end: {} +} + +// whether the next mouseUp even should trigger +let skip = false; +// whether user is currently holding +let holding = false; +// mobile device shift equivalent +let lockSelection = false; + +// current clipboard, used for cutting, copying and pasting +let clipboard = []; + +// createPixel but with color argument +const createPixelColor = (element, x, y, color) => { + const pixel = new Pixel(x, y, element); + pixel.color = color; + currentPixels.push(pixel); + checkUnlock(element); +} + +// replaces a pixel at x, y position with replacement element specified in the settings +const replacePixel = (x, y) => { + if (outOfBounds(x, y)) return; + if (pixelMap[x][y]) { + if (!replace.get()) return; + deletePixel(x, y); + } + createPixel(element.get(), x, y); +} + +// checks whether position pos is in bounds +const inBounds = (bounds, pos) => { + bounds = { + start: { + x: Math.min(bounds.start.x, bounds.end.x), + y: Math.min(bounds.start.y, bounds.end.y) + }, + end: { + x: Math.max(bounds.start.x, bounds.end.x), + y: Math.max(bounds.start.y, bounds.end.y) + } + } + return pos.x >= bounds.start.x && pos.x <= bounds.end.x && pos.y >= bounds.start.y && pos.y <= bounds.end.y; +} + +// generates a selection based on start and end positions +const select = (start, end) => { + const res = []; + for (let i = 0; i <= end.x - start.x; i++) { + res[i] = []; + for (let j = 0; j <= end.y - start.y; j++) { + res[i][j] = pixelMap[i + start.x][j + start.y]; + } + } + return res; +} + +elements.select = { + name: "Select", + category: "editTools", + maxSize: 1, + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) { + skip = true; + return; + } + skip = false; + holding = true; + selection_.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + selection_.end = mousePos; + if (selection_.start == selection_.end) selection_ = {}; + holding = false; + }, + tool: (_) => {}, + perTick: () => { + if (!selection_ || !selection_.start || !selection_.end) return; + if (holding) { + selection_.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(selection_.start.x * pixelSize), + y: Math.round(selection_.start.y * pixelSize) + } + const end = { + x: Math.round(selection_.end.x * pixelSize), + y: Math.round((selection_.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "white"; + ctx.globalCompositeOperation = "destination-over"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } +} + +elements.box = { + name: "Box", + category: "editTools", + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y) || showingMenu) { + skip = true; + return; + } + skip = false; + holding = true; + box.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + box.end = mousePos; + holding = false; + for (let i = -Math.floor(mouseSize / 2); i < Math.abs(box.start.x - box.end.x) + Math.floor(mouseSize / 2); i++) { + const x = Math.min(box.start.x, box.end.x) + i; + for (let j = -Math.floor(mouseSize / 2); j <= Math.floor(mouseSize / 2); j++) { + replacePixel(x, box.start.y + (box.start.y > box.end.y ? -j : j)); + replacePixel(x, box.end.y + (box.end.y > box.start.y ? -j : j)); + } + } + for (let i = -Math.floor(mouseSize / 2); i <= Math.abs(box.start.y - box.end.y) + Math.floor(mouseSize / 2); i++) { + const y = Math.min(box.start.y, box.end.y) + i; + for (let j = -Math.floor(mouseSize / 2); j <= Math.floor(mouseSize / 2); j++) { + replacePixel(box.start.x + (box.start.x > box.end.x ? -j : j), y); + replacePixel(box.end.x + (box.end.x > box.start.x ? -j : j), y); + } + } + }, + tool: (_) => {}, + perTick: () => { + if (holding) { + if (shiftDown || lockSelection) { + box.end = { + y: mousePos.y, + x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) + }; + mousePos = box.end; + } else { + box.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(box.start.x * pixelSize), + y: Math.round(box.start.y * pixelSize) + } + const end = { + x: Math.round(box.end.x * pixelSize), + y: Math.round((box.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "yellow"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } + } +} + +elements.cut = { + name: "Cut", + category: "editTools", + maxSize: 0, + tool: (_) => {}, + onSelect: () => { + if (!selection_.start || !selection_.end) return alert("No selection made"); + const selected = select({ + x: Math.min(selection_.start.x, selection_.end.x), + y: Math.min(selection_.start.y, selection_.end.y) + }, { + x: Math.max(selection_.start.x, selection_.end.x), + y: Math.max(selection_.start.y, selection_.end.y) + }) + clipboard = selected; + for (const i of selected) { + for (const pixel of i) { + if (!pixel) continue; + deletePixel(pixel.x, pixel.y); + } + } + selectElement("unknown"); + } +} +elements.copy = { + name: "Copy", + category: "editTools", + maxSize: 0, + tool: (_) => {}, + onSelect: () => { + if (!selection_.start || !selection_.end) return alert("No selection made"); + const selected = select({ + x: Math.min(selection_.start.x, selection_.end.x), + y: Math.min(selection_.start.y, selection_.end.y) + }, { + x: Math.max(selection_.start.x, selection_.end.x), + y: Math.max(selection_.start.y, selection_.end.y) + }) + clipboard = selected; + selectElement("unknown"); + } +} + +elements.selectionMove = { + name: "Move selection", + category: "editTools", + maxSize: 0, + tool: (_) => {}, + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) return; + if (!selection_.start || !selection_.end) return; + if (!inBounds(selection_, mousePos)) return; + selectionOffsets = { + x: mousePos.x - Math.min(selection_.start.x, selection_.end.x), + y: mousePos.y - Math.min(selection_.start.y, selection_.end.y) + } + const selected = select({ + x: Math.min(selection_.start.x, selection_.end.x), + y: Math.min(selection_.start.y, selection_.end.y) + }, { + x: Math.max(selection_.start.x, selection_.end.x), + y: Math.max(selection_.start.y, selection_.end.y) + }) + selectionPosition = { + x: selection_.start.x, + y: selection_.start.y + } + for (const i of selected) { + for (const pixel of i) { + if (!pixel) continue; + deletePixel(pixel.x, pixel.y); + } + } + selectionMoved = selected; + }, + perTick: () => { + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + ctx.globalAlpha = 1; + const start = { + x: Math.round(selection_.start.x * pixelSize), + y: Math.round(selection_.start.y * pixelSize) + } + const end = { + x: Math.round(selection_.end.x * pixelSize), + y: Math.round((selection_.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "white"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + if (!selectionMoved || selectionMoved.length == 0) return; + selectionPosition = { + x: mousePos.x - selectionOffsets.x, + y: mousePos.y - selectionOffsets.y + } + selection_ = { + start: { + x: selectionPosition.x, + y: selectionPosition.y + }, + end: { + x: selectionPosition.x + selectionMoved.length - 1, + y: selectionPosition.y + selectionMoved[0].length - 1 + } + } + ctx.globalAlpha = 0.5; + for (let i = 0; i < selectionMoved.length; i++) { + for (let j = 0; j < selectionMoved[i].length; j++) { + const x = selectionPosition.x + i; + const y = selectionPosition.y + j; + if (!selectionMoved[i][j]) continue; + ctx.globalCompositeOperation = 'destination-over'; + ctx.fillStyle = selectionMoved[i][j].color; + ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); + } + } + }, + onMouseUp: () => { + if (outOfBounds(mousePos.x, mousePos.y)) return; + for (let i = 0; i < selectionMoved.length; i++) { + for (let j = 0; j < selectionMoved[i].length; j++) { + const x = selectionPosition.x + i; + const y = selectionPosition.y + j; + if (!selectionMoved[i][j] && transparentSelection.get()) continue; + if (pixelMap[x][y]) deletePixel(x, y); + createPixelColor(selectionMoved[i][j].element, x, y, selectionMoved[i][j].color); + } + } + selectionMoved = []; + } +} + +elements.paste = { + name: "Paste", + category: "editTools", + tool: (_) => {}, + maxSize: 1, + onMouseDown: () => { + if (!clipboard) return alert("Nothing left to paste"); + for (let i = 0; i < clipboard.length; i++) { + for (let j = 0; j < clipboard[i].length; j++) { + const x = mousePos.x + i; + const y = mousePos.y + j; + if (outOfBounds(x, y) || (!clipboard[i][j] && transparentSelection.get())) continue; + if (!pixelMap[x][y]) createPixelColor(clipboard[i][j].element, x, y, clipboard[i][j].color); + else { + deletePixel(x, y); + createPixelColor(clipboard[i][j].element, x, y, clipboard[i][j].color); + } + } + } + } +} + +elements.rectangle = { + name: "Rectangle", + category: "editTools", + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) { + skip = true; + return; + } + skip = false; + holding = true; + box.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + box.end = mousePos; + holding = false; + for (let i = -Math.floor(mouseSize / 2); i <= Math.abs(box.start.x - box.end.x) + Math.floor(mouseSize / 2); i++) { + for (let j = -Math.floor(mouseSize / 2); j <= Math.abs(box.start.y - box.end.y) + Math.floor(mouseSize / 2); j++) { + replacePixel(Math.min(box.start.x, box.end.x) + i, Math.min(box.start.y, box.end.y) + j); + } + } + }, + tool: (_) => {}, + perTick: () => { + if (holding) { + if (shiftDown || lockSelection) { + box.end = { + y: mousePos.y, + x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) + }; + mousePos = box.end; + } else { + box.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(box.start.x * pixelSize), + y: Math.round(box.start.y * pixelSize) + } + const end = { + x: Math.round(box.end.x * pixelSize), + y: Math.round((box.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "yellow"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } + } +} + +// Ellipse Midpoint Algorithm +// https://stackoverflow.com/questions/15474122/is-there-a-midpoint-ellipse-algorithm +// https://web.archive.org/web/20160305234351/http://geofhagopian.net/sablog/Slog-october/slog-10-25-05.htm +function ellipsePlotPoints(xc, yc, x, y) { + replacePixel(xc + x, yc + y); + replacePixel(xc - x, yc + y); + replacePixel(xc + x, yc - y); + replacePixel(xc - x, yc - y); +} + +function ellipse(xc, yc, w, h) { + const a2 = Math.pow(w, 2); + const b2 = Math.pow(h, 2); + const twoa2 = 2 * a2; + const twob2 = 2 * b2; + let p; + let x = 0; + let y = h; + let px = 0; + let py = twoa2 * y; + + /* Plot the initial point in each quadrant. */ + ellipsePlotPoints(xc, yc, x, y); + + p = Math.round(b2 - (a2 * b) + (0.25 * a2)); + while (px < py) { + x++; + px += twob2; + if (p < 0) p += b2 + px; + else { + y--; + py -= twoa2; + p += b2 + px - py; + } + ellipsePlotPoints(xc, yc, x, y); + } + + p = Math.round(b2 * (x + 0.5) * (x + 0.5) + a2 * (y - 1) * (y - 1) - a2 * b2); + while (y > 0) { + y--; + py -= twoa2; + if (p > 0) p += a2 - py; + else { + x++; + px += twob2; + p += a2 - py + px; + } + ellipsePlotPoints(xc, yc, x, y); + } +} + +elements.ellipse = { + name: "Ellipse", + category: "editTools", + tool: (_) => {}, + maxSize: 1, + onMouseDown: () => { + if (outOfBounds(mousePos.x, mousePos.y)) { + skip = true; + return; + } + skip = false; + holding = true; + box.start = mousePos; + }, + onMouseUp: () => { + if (skip) return; + box.end = mousePos; + holding = false; + const w = Math.abs(box.end.x - box.start.x); + const h = Math.abs(box.end.y - box.start.y); + const x = Math.min(box.start.x, box.end.x) + Math.floor(w / 2); + const y = Math.min(box.start.y, box.end.y) + Math.floor(h / 2); + ellipse(x, y, Math.floor(w / 2), Math.floor(h / 2)); + }, + tool: (_) => {}, + perTick: () => { + if (holding) { + if (shiftDown || lockSelection) { + box.end = { + y: mousePos.y, + x: box.start.x + Math.abs(mousePos.y - box.start.y) * (box.start.x > mousePos.x ? -1 : 1) + }; + mousePos = box.end; + } else { + box.end = mousePos; + } + const canvas = document.getElementById("game"); + const ctx = canvas.getContext("2d"); + const start = { + x: Math.round(box.start.x * pixelSize), + y: Math.round(box.start.y * pixelSize) + } + const end = { + x: Math.round(box.end.x * pixelSize), + y: Math.round((box.end.y + 1) * pixelSize) + } + const {strokeStyle, lineWidth} = ctx; + ctx.setLineDash([8, 8]); + ctx.lineWidth = 2; + ctx.strokeStyle = "yellow"; + ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.setLineDash([]); + } + } +} + +document.addEventListener("mousedown", (ev) => { + if (elements[currentElement].onMouseDown) { + elements[currentElement].onMouseDown(); + } +}) + +// mouse2 overwrite for delete filter +// no mouse1 overwrite for replace functionality, I don't think I should be overwriting such big functions +// maybe in a future update +mouse2Action = (e,mouseX=undefined,mouseY=undefined,startPos) => { + // Erase pixel at mouse position + if (mouseX == undefined && mouseY == undefined) { + var canvas = document.getElementById("game"); + var ctx = canvas.getContext("2d"); + lastPos = mousePos; + mousePos = getMousePos(canvas, e); + var mouseX = mousePos.x; + var mouseY = mousePos.y; + } + // If the current element is "pick" or "lookup", coords = [mouseX,mouseY] + if (currentElement == "pick" || currentElement == "lookup") { + var coords = [[mouseX,mouseY]]; + } + else if (!isMobile) { + startPos = startPos || lastPos + var coords = lineCoords(startPos.x,startPos.y,mouseX,mouseY); + } + else { + var coords = mouseRange(mouseX,mouseY); + } + // For each x,y in coords + for (var i = 0; i < coords.length; i++) { + var x = coords[i][0]; + var y = coords[i][1]; + + if (!isEmpty(x, y)) { + if (outOfBounds(x,y)) { + continue + } + var pixel = pixelMap[x][y]; + // filter + if (filter.get() && pixel.element != filteredElement.get()) continue; + delete pixelMap[x][y]; + // Remove pixel from currentPixels + for (var j = 0; j < currentPixels.length; j++) { + if (currentPixels[j].x == x && currentPixels[j].y == y) { + currentPixels.splice(j, 1); + break; + } + } + } + } +} + +// Mobile check +// https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser +// http://detectmobilebrowsers.com/ +window.mobileAndTabletCheck = () => { + let check = false; + (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); + return check; +}; + +// if user is on mobile, add lock selection tool +if (window.mobileAndTabletCheck()) { + elements.lockSelection = { + name: "Lock selection", + category: "editTools", + tool: (_) => {}, + onSelect: () => { + // unselect so you can click it multiple times + selectElement("unknown"); + document.getElementById("elementButton-lockSelection").innerText = lockSelection ? "Lock selection" : "Unlock selection"; + lockSelection = !lockSelection; + } + } +} \ No newline at end of file From 06f59eeca86f2bf1a64a3d5e13d3d1e7e347a97b Mon Sep 17 00:00:00 2001 From: GGod Date: Tue, 29 Aug 2023 01:57:36 +0200 Subject: [PATCH 07/10] Update editTools.js --- mods/editTools.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mods/editTools.js b/mods/editTools.js index 2c9af74c..83d57e5d 100644 --- a/mods/editTools.js +++ b/mods/editTools.js @@ -19,9 +19,12 @@ settingsManager.registerTab(settingsTab); runAfterLoadList.push(() => { // the game doesn't load without the setTimeout setTimeout(() => { - document.getElementById("elementButton-select").style.backgroundColor = "transparent"; - document.getElementById("elementButton-select").style.border = "2px dashed rgba(255, 255, 255, 0.75)"; - document.getElementById("elementButton-select").classList = ["elementButton"]; + // well apparently the game crashes anyway + try { + document.getElementById("elementButton-select").style.backgroundColor = "transparent"; + document.getElementById("elementButton-select").style.border = "2px dashed rgba(255, 255, 255, 0.75)"; + document.getElementById("elementButton-select").classList = ["elementButton"]; + } catch (_) {} }, 1) }) From 1a63fa81fe8ba503e535ed0f12711bd29033e694 Mon Sep 17 00:00:00 2001 From: GGod Date: Tue, 29 Aug 2023 02:05:27 +0200 Subject: [PATCH 08/10] Update editTools.js --- mods/editTools.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/mods/editTools.js b/mods/editTools.js index 83d57e5d..0d5e4dbd 100644 --- a/mods/editTools.js +++ b/mods/editTools.js @@ -16,18 +16,6 @@ settingsTab.registerSettings("Selection settings", transparentSelection); settingsManager.registerTab(settingsTab); -runAfterLoadList.push(() => { - // the game doesn't load without the setTimeout - setTimeout(() => { - // well apparently the game crashes anyway - try { - document.getElementById("elementButton-select").style.backgroundColor = "transparent"; - document.getElementById("elementButton-select").style.border = "2px dashed rgba(255, 255, 255, 0.75)"; - document.getElementById("elementButton-select").classList = ["elementButton"]; - } catch (_) {} - }, 1) -}) - // current selection let selection_ = { start: {}, From 2eab7fd75dd597e9f2f28835d67f1ebcff81a4b2 Mon Sep 17 00:00:00 2001 From: GGod Date: Tue, 29 Aug 2023 02:17:53 +0200 Subject: [PATCH 09/10] i absolutely forgot to implement non-transparent selections --- mods/editTools.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mods/editTools.js b/mods/editTools.js index 0d5e4dbd..19e120a0 100644 --- a/mods/editTools.js +++ b/mods/editTools.js @@ -331,7 +331,7 @@ elements.selectionMove = { const y = selectionPosition.y + j; if (!selectionMoved[i][j] && transparentSelection.get()) continue; if (pixelMap[x][y]) deletePixel(x, y); - createPixelColor(selectionMoved[i][j].element, x, y, selectionMoved[i][j].color); + if (selectionMoved[i][j]) createPixelColor(selectionMoved[i][j].element, x, y, selectionMoved[i][j].color); } } selectionMoved = []; @@ -350,6 +350,10 @@ elements.paste = { const x = mousePos.x + i; const y = mousePos.y + j; if (outOfBounds(x, y) || (!clipboard[i][j] && transparentSelection.get())) continue; + if (!clipboard[i][j]) { + if (pixelMap[x][y]) deletePixel(x, y); + continue; + } if (!pixelMap[x][y]) createPixelColor(clipboard[i][j].element, x, y, clipboard[i][j].color); else { deletePixel(x, y); From f545558f13bc91c6d851b0864b7df2b157fe29be Mon Sep 17 00:00:00 2001 From: GGod Date: Wed, 30 Aug 2023 21:58:05 +0200 Subject: [PATCH 10/10] add peta mod --- mods/peta.js | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 mods/peta.js diff --git a/mods/peta.js b/mods/peta.js new file mode 100644 index 00000000..7f7a09e5 --- /dev/null +++ b/mods/peta.js @@ -0,0 +1,214 @@ +{ +const inject = () => { + const gameDiv = document.getElementById("gameDiv"); + const parent = document.createElement("div"); + parent.id = "popUpParent"; + parent.style.display = "none"; + const inner = document.createElement("div"); + inner.className = "popUp"; + inner.id = "petaPopup"; + const title = document.createElement("span"); + title.id = "popUpTitle"; + title.style.fontSize = "1.5em"; + title.innerText = "title"; + inner.appendChild(title); + const closeButton = document.createElement("button"); + closeButton.innerText = "-"; + closeButton.id = "popUpCloseButton"; + closeButton.className = "XButton"; + closeButton.onclick = () => { + closePopUp(); + } + inner.appendChild(closeButton); + const text = document.createElement("div"); + text.id = "popUpText"; + text.style.marginTop = "20px"; + text.style.lineHeight = "1.5em"; + inner.appendChild(text); + const progress = document.createElement("div"); + progress.className = "progressBar"; + const progressInner = document.createElement("div"); + progressInner.className = "progressBarInside"; + progressInner.id = "popUpProgressBar"; + progress.appendChild(progressInner); + inner.appendChild(progress); + parent.appendChild(inner); + gameDiv.appendChild(parent); +} + +const injectCss = () => { + const style = `.popUp { + position: absolute; + border: 1px solid #fff; + left: 50%; + top: 20%; + transform: translate(-50%, 0%); + width: 70%; + height: 30%; + padding: 10px; + background-color: rgb(31, 31, 31); + overflow-x: hidden; + z-index: 12; + } + .progressBar { + width: 100%; + position: absolute; + bottom: 0; + left: 0; + height: 20px; + } + .progressBarInside { + width: 100%; + height: 20px; + background-color: white; + }`; + const styleElement = document.createElement("style"); + styleElement.innerHTML = style; + document.head.appendChild(styleElement); +} + +function closePopUp() { + const popUp = document.getElementById("popUpParent"); + let alpha = 100; + const interval = setInterval(frame, 5); + function frame() { + if (alpha <= 0) { + clearInterval(interval); + popUp.style.display = "none"; + popUp.style.opacity = 1; + } else { + popUp.style.opacity = alpha / 100; + alpha -= 5; + } + } + showingPopUp = false; +} + +let intervalTime = 30; + +function destroyGame() { + selectElement("unknown"); + for (const i of pixelMap) { + for (const pixel of i) { + if (pixel) { + deletePixel(pixel.x, pixel.y); + } + } + } + for (const element of Object.keys(elements)) { + delete elements[element]; + const elem = document.getElementById("elementButton-" + element); + if (elem) elem.remove(); + } + for (let j = 0; j < document.getElementById("categoryControls").children.length; j++) { + const i = parseInt(j); + document.getElementById("categoryControls").children.item(i).innerText = ["DONT", "MESS", "WITH", "PETA"][i % 4]; + } + document.getElementById("toolControls").remove(); + // breaks stuff + mouse1Action = () => {}; + mouseAction = () => {}; + updateStats = () => {}; + pixelMap = []; + document.getElementById('stats').innerHTML = "xNONE,yNONE Pxls:-8 -Infinity tps NONE NO_VIEW THAT'S WHAT YOU GET"; +} + +let showingPopUp = false; + +function showPopUp(text, title) { + const titleElement = document.getElementById("popUpTitle"); + titleElement.innerText = title; + const textElement = document.getElementById("popUpText"); + textElement.innerText = text; + const popUp = document.getElementById("popUpParent"); + popUp.style.display = "block"; + let width = 100; + showingPopUp = true; + const progressBar = document.getElementById("popUpProgressBar"); + const interval = setInterval(frame, intervalTime); + function frame() { + if (width <= 0 || !showingPopUp) { + clearInterval(interval); + setTimeout(() => { + closePopUp(); + }, intervalTime); + progressBar.style.width = "0%"; + } else { + width--; + progressBar.style.width = width + "%"; + } + } +} + +let warnings = 0; +let happened = false; +const forbiddenElements = ["meat", "rotten_meat", "cooked_meat", "frozen_meat", "milk", "cream", "fruit_milk", "pilk", "yogurt", "frozen_yogurt", "ice_cream", "egg", "yolk", "hard_yolk", "chocolate_milk", "eggnog"] + +const messages = ["What are you trying to do?! That behavior is unnacceptable. Those animals didn't do anything to deserve this. You have {warning} warnings left before we delete half of your elements.", + "You really think we can't do anything huh? Fuck around and find out. {warning} warnings left", + "We have no words for you, how dare you?! {warning} warnings left", + "That is completely unbelievable. {warning} warnings left", + "That is your last chance. If you try it one more time, we WILL remove half of your elements. Do you really want to do it?", + "You already lost half of your elements. Can't you just leave animals alone and use vegan products instead? We are left with no choice, we have to give you a warning. {warning} warnings remaining.", + "You are leaving us with no choice. If you continue this behavior we will make your game completely unplayable. Do you really want to do this? {warning} warnings remaing.", + "Are you even listening? Or are you just skipping our messages like there's nothing there. We are increasing the pop-up length and removing the close button, maybe now you will listen to us. {warning} warnings remaining.", + "You only ever think about yourself, don't you? Do you really think that what you're doing currently is morally justifiable? You are just a coward, you can't admit that what you are doing is evil. Disgusting behavior. {warning} warnings remaining.", + "That is your last chance. It's completely impossible to educate you, so if you try it one more time WE WILL TAKE ACTION! DO YOU UNDERSTAND THAT?!"]; + +selectElement = (element) => { + if (showingPopUp) return; + if (forbiddenElements.includes(element)) { + console.log(warnings) + if (warnings == 5 && !happened) { + showPopUp("As you wish.", "From PETA"); + happened = true; + let possibleElements = Object.keys(elements).filter(e => !forbiddenElements.includes(e) && currentElement != e); + const initialLength = Math.floor(possibleElements.length / 2); + while (possibleElements.length > initialLength) { + const max = possibleElements.length; + const randomElement = Math.floor(Math.random() * max); + const element_ = possibleElements[randomElement]; + const elem = document.getElementById("elementButton-" + element_); + if (elem) elem.remove(); + possibleElements = possibleElements.filter(e => e != element_); + } + } else if (warnings == 10) { + destroyGame(); + } else { + if (warnings == 7) { + document.getElementById("popUpCloseButton").remove(); + intervalTime = 60; + } + showPopUp(messages[warnings].replace("{warning}", happened ? 10 - (warnings) : 5 - warnings), "From PETA"); + warnings++; + } + return; + } + if (elements[currentElement].onUnselect) { + elements[currentElement].onUnselect(); + } + var e1 = document.getElementById("elementButton-"+currentElement); + if (e1 != null) { e1.setAttribute("current","false"); } + currentElement = element; + if (elements[element].customColor) { + // show the colorSelector + document.getElementById("colorSelector").style.display = "block"; + } + else { + // hide the colorSelector + document.getElementById("colorSelector").style.display = "none"; + } + if (elements[element].onSelect) { + elements[element].onSelect(); + } + var e2 = document.getElementById("elementButton-"+element); + if (!e2) { return; } + e2.setAttribute("current","true"); + // if e2 has the class "notify", remove it + if (e2.classList.contains("notify")) { + e2.classList.remove("notify"); + } +} + +runAfterLoadList.push(inject, injectCss); +} \ No newline at end of file