From ef43022f19fe82d1e1d775b5d93f2c1334ab1fea Mon Sep 17 00:00:00 2001 From: Dimava Date: Mon, 8 Jun 2020 13:46:37 +0300 Subject: [PATCH] Rewrite color creation model --- src/js/game/colors.js | 332 ++++++++++++++++++++------------ src/js/game/custom/colors.js | 29 +++ src/js/game/items/color_item.js | 2 +- src/js/game/map_chunk.js | 15 +- 4 files changed, 255 insertions(+), 123 deletions(-) create mode 100644 src/js/game/custom/colors.js diff --git a/src/js/game/colors.js b/src/js/game/colors.js index 6d483771..e42bafcd 100644 --- a/src/js/game/colors.js +++ b/src/js/game/colors.js @@ -1,3 +1,5 @@ +import { customColors } from "./custom/colors"; + /** @enum {string} */ export const enumColors = { red: "red", @@ -5,7 +7,7 @@ export const enumColors = { blue: "blue", yellow: "yellow", - purple: "purple", + magenta: "magenta", cyan: "cyan", white: "white", @@ -19,7 +21,7 @@ export const enumColorToShortcode = { [enumColors.blue]: "b", [enumColors.yellow]: "y", - [enumColors.purple]: "p", + [enumColors.magenta]: "p", [enumColors.cyan]: "c", [enumColors.white]: "w", @@ -28,140 +30,234 @@ export const enumColorToShortcode = { /** @enum {enumColors} */ export const enumShortcodeToColor = {}; -for (const key in enumColorToShortcode) { - enumShortcodeToColor[enumColorToShortcode[key]] = key; -} /** @enum {string} */ -export const enumColorsToHexCode = { - [enumColors.red]: "#ff666a", - [enumColors.green]: "#78ff66", - [enumColors.blue]: "#66a7ff", - - // red + green - [enumColors.yellow]: "#fcf52a", - - // red + blue - [enumColors.purple]: "#dd66ff", - - // blue + green - [enumColors.cyan]: "#00fcff", - - // blue + green + red - [enumColors.white]: "#ffffff", - - [enumColors.uncolored]: "#aaaaaa", -}; +export const enumColorsToHexCode = {}; const c = enumColors; -/** @enum {Object.} */ -export const enumColorMixingResults = { - // 255, 0, 0 - [c.red]: { - [c.green]: c.yellow, - [c.blue]: c.purple, +/** @enum {Object.} */ +export const enumColorMixingResults = {}; - [c.yellow]: c.yellow, - [c.purple]: c.purple, - [c.cyan]: c.white, +/** + * @typedef {Object} ColorData + * @property {string} id + * @property {string} code + * @property {string} hex + * @property {string[][] | string[]} [mixingFrom] + * @property {Object.} [mixing] + * @property {boolean} [spawnable] + * @property {number} [minDistance] + */ - [c.white]: c.white, +/** @enum {ColorData} */ +export const allColorData = { + uncolored: { + id: "uncolored", + code: "u", + hex: "#aaaaaa", + mixing: { + any: "any", + }, }, - - // 0, 255, 0 - [c.green]: { - [c.blue]: c.cyan, - - [c.yellow]: c.yellow, - [c.purple]: c.white, - [c.cyan]: c.cyan, - - [c.white]: c.white, + red: { + id: "red", + code: "r", + hex: "#ff666a", + // no recipes + spawnable: true, }, - - // 0, 255, 0 - [c.blue]: { - [c.yellow]: c.white, - [c.purple]: c.purple, - [c.cyan]: c.cyan, - - [c.white]: c.white, + green: { + id: "green", + code: "g", + hex: "#78ff66", + // no recipes + spawnable: true, }, - - // 255, 255, 0 - [c.yellow]: { - [c.purple]: c.white, - [c.cyan]: c.white, + blue: { + id: "blue", + code: "b", + hex: "#66a7ff", + // no recipes + spawnable: true, + minDistance: 3, }, - - // 255, 0, 255 - [c.purple]: { - [c.cyan]: c.white, + cyan: { + id: "cyan", + code: "c", + hex: "#00fcff", + mixingFrom: ["green", "blue"], + mixing: { + green: "this", + blue: "this", + }, }, - - // 0, 255, 255 - [c.cyan]: {}, - - //// SPECIAL COLORS - - // 255, 255, 255 - [c.white]: { - // auto + magenta: { + id: "magenta", + code: "p", + hex: "#dd66ff", + mixingFrom: ["red", "blue"], + mixing: { + red: "this", + blue: "this", + }, }, - - // X, X, X - [c.uncolored]: { - // auto + yellow: { + id: "yellow", + code: "y", + hex: "#fcf52a", + mixingFrom: ["red", "green"], + mixing: { + red: "this", + green: "this", + }, + }, + white: { + id: "white", + code: "w", + hex: "#ffffff", + mixing: { + any: "white", + }, + mixingFrom: [ + ["red", "cyan"], + ["green", "magenta"], + ["blue", "yellow"], + ["cyan", "magenta"], + ["cyan", "yellow"], + ["magenta", "yellow"], + ], }, }; -// Create same color lookups -for (const color in enumColors) { - enumColorMixingResults[color][color] = color; - - // Anything with white is white again - enumColorMixingResults[color][c.white] = c.white; - - // Anything with uncolored is the same color - enumColorMixingResults[color][c.uncolored] = color; +/** + * @param {Object} colorData + * @property {string} colorData.id + * @property {string} colorData.code + * @property {string} colorData.hex + * @property {Object.} [colorData.mixing] + * @property {string[2][] | string[2]} [colorData.mixingFrom] + */ +function registerColor(colorData) { + allColorData[colorData.id] = colorData; } -// Create reverse lookup and check color mixing lookups -for (const colorA in enumColorMixingResults) { - for (const colorB in enumColorMixingResults[colorA]) { - const resultColor = enumColorMixingResults[colorA][colorB]; - if (!enumColorMixingResults[colorB]) { - enumColorMixingResults[colorB] = { - [colorA]: resultColor, - }; - } else { - const existingResult = enumColorMixingResults[colorB][colorA]; - if (existingResult && existingResult !== resultColor) { - assertAlways( - false, - "invalid color mixing configuration, " + - colorA + - " + " + - colorB + - " is " + - resultColor + - " but " + - colorB + - " + " + - colorA + - " is " + - existingResult - ); +export let allowColorMixingMismatch = false; +export let allowColorMixingOverride = false; +export let allowColorMixingMissingSource = false; +export let allowColorMixingMissingTarget = false; + +for (let data of customColors) { + registerColor(data); +} + +const mix = enumColorMixingResults; + +initColors(); + +export function initColors() { + + for (let c1 in allColorData) { + let data = allColorData[c1]; + assert(data); + assert(data.id == c1); + assert(data.code.toLowerCase() == data.code); + if (data.disabled) { + continue; + } + if (data.spawnable && !data.minDistance) { + data.minDistance = 0; + } + enumColors[c1] = c1; + enumColorToShortcode[c1] = data.code; + enumShortcodeToColor[data.code] = c1; + enumColorsToHexCode[c1] = data.hex; + if (!mix[c1]) { + mix[c1] = {}; + } + let mixing = mix[c1]; + if (data.mixing) { + for (let c2 in data.mixing) { + if (c2 == "any") { + continue; + } + let result = data.mixing[c2] == "this" ? c1 : data.mixing[c2] == "any" ? c2 : data.mixing[c2]; + if (mixing[c2] && mixing[c2] != result) { + if (!allowColorMixingOverride) { + assertAlways( + false, + `Color mixing recipe overrides are not implemented (${c1}+${c2}=${mixing[c2]}->${result})` + ); + } + } + mixing[c2] = result; + } + } + } + for (let id in allColorData) { + let data = allColorData[id]; + let mixingFrom = !data.mixingFrom + ? [] + : data.mixingFrom[0] instanceof Array + ? data.mixingFrom + : [data.mixingFrom]; + for (let [c1, c2] of mixingFrom) { + if (!c[c1] || !c[c2]) { + if (!allowColorMixingMissingSource) { + assertAlways(false, `Color mixing recipe source is not known (${c1}+${c2}=${id})`); + } + continue; + } + if (mix[c1][c2] && mix[c1][c2] != id) { + // TODO + throw "wut"; + } + if (mix[c2][c1] && mix[c2][c1] != id) { + // TODO + throw "wut"; + } + mix[c1][c2] = id; + } + } + for (let c1 in c) { + for (let c2 in c) { + if (mix[c1][c2] != mix[c2][c1]) { + if (mix[c1][c2] && mix[c2][c1] && !allowColorMixingMismatch) { + assertAlways( + false, + `Color mixing recipe result mismatch (${c1}+${c2}=${mix[c1][c2]}/${mix[c2][c1]}}` + ); + } + mix[c1][c2] = mix[c2][c1] = mix[c1][c2] || mix[c2][c1]; + } + } + } + for (let c1 in c) { + if (!mix[c1][c1]) { + mix[c1][c1] = c1; + } + } + let anyPairs = {}; + for (let c1 in c) { + let mixing = allColorData[c1].mixing; + if (!mixing || !mixing.any) { + continue; + } + for (let c2 in c) { + if (mix[c1][c2] || mix[c2][c1]) { + continue; + } + if (anyPairs[`${c1}+${c2}`]) { + throw "wut"; + } + anyPairs[`${c1}+${c2}`] = anyPairs[`${c2}+${c1}`] = true; + mix[c1][c2] = mix[c2][c1] = mixing.any == "any" ? c2 : mixing.any; + } + } + for (let c1 in c) { + for (let c2 in c) { + if (!mix[c1][c2]) { + assertAlways(false, "Color mixing of", c1, "with", c2, "is not defined"); } - enumColorMixingResults[colorB][colorA] = resultColor; - } - } -} - -for (const colorA in enumColorMixingResults) { - for (const colorB in enumColorMixingResults) { - if (!enumColorMixingResults[colorA][colorB]) { - assertAlways(false, "Color mixing of", colorA, "with", colorB, "is not defined"); } } } diff --git a/src/js/game/custom/colors.js b/src/js/game/custom/colors.js new file mode 100644 index 00000000..a3fea39d --- /dev/null +++ b/src/js/game/custom/colors.js @@ -0,0 +1,29 @@ +/** @enum {string} */ +export const customColors = []; + +/** + * @param {Object} colorData + * @param {string} colorData.id + * @param {string} colorData.code + * @param {string} colorData.hex + * @param {string[][] | string[]} [colorData.mixingFrom] + * @param {Object.} [colorData.mixing] + * @param {boolean} [colorData.spawnable] + * @param {number} [colorData.minDistance] + */ +export function registerCustomColor(colorData) { + customColors.push(colorData); +} + +registerCustomColor({ + id: "black", + code: "k", + hex: "#333333", + mixing: { + white: "uncolored", + uncolored: "uncolored", + any: "black", + }, + spawnable: true, + minDistance: 5, +}); diff --git a/src/js/game/items/color_item.js b/src/js/game/items/color_item.js index b2a3cd74..c88b1433 100644 --- a/src/js/game/items/color_item.js +++ b/src/js/game/items/color_item.js @@ -33,7 +33,7 @@ export class ColorItem extends BaseItem { } getBackgroundColorAsResource() { - return THEME.map.resources[this.color]; + return THEME.map.resources[this.color] || THEME.map.resources.shape; } /** diff --git a/src/js/game/map_chunk.js b/src/js/game/map_chunk.js index bffaf9e8..5ebdd975 100644 --- a/src/js/game/map_chunk.js +++ b/src/js/game/map_chunk.js @@ -8,7 +8,7 @@ import { createLogger } from "../core/logging"; import { clamp, fastArrayDeleteValueIfContained, make2DUndefinedArray } from "../core/utils"; import { Vector } from "../core/vector"; import { BaseItem } from "./base_item"; -import { enumColors } from "./colors"; +import { enumColors, allColorData } from "./colors"; import { Entity } from "./entity"; import { ColorItem } from "./items/color_item"; import { ShapeItem } from "./items/shape_item"; @@ -136,9 +136,16 @@ export class MapChunk { */ internalGenerateColorPatch(rng, colorPatchSize, distanceToOriginInChunks) { // First, determine available colors - let availableColors = [enumColors.red, enumColors.green]; - if (distanceToOriginInChunks > 2) { - availableColors.push(enumColors.blue); + let availableColors = []; + for (let c in enumColors) { + c = enumColors[c]; + if ( + allColorData[c] && + allColorData[c].spawnable && + allColorData[c].minDistance <= distanceToOriginInChunks + ) { + availableColors.push(c); + } } this.internalGeneratePatch(rng, colorPatchSize, new ColorItem(rng.choice(availableColors))); }