From 67530a48ab6a157f544438dcf6578ca9580a37ad Mon Sep 17 00:00:00 2001 From: j0code <42189560+j0code@users.noreply.github.com> Date: Sat, 23 Mar 2024 23:20:20 +0100 Subject: [PATCH] colored light - change 1 value lightLevel to 3 values rgb light - change torch color to orange - add froglights - add skyLightColor to world --- .../tiny/textures/block/ochre_froglight.png | Bin 0 -> 196 bytes .../textures/block/pearlescent_froglight.png | Bin 0 -> 196 bytes .../tiny/textures/block/verdant_froglight.png | Bin 0 -> 196 bytes public/blocks.yson | 5 +- src/Graphics.ts | 14 +++- src/block/Block.ts | 44 +++++++---- src/defs/BlockDef.ts | 13 ++-- src/texture/Texture.ts | 5 +- src/util/DebugScreen.ts | 4 +- src/util/LightColor.ts | 73 ++++++++++++++++++ src/world/World.ts | 9 ++- 11 files changed, 137 insertions(+), 30 deletions(-) create mode 100644 public/assets/tiny/textures/block/ochre_froglight.png create mode 100644 public/assets/tiny/textures/block/pearlescent_froglight.png create mode 100644 public/assets/tiny/textures/block/verdant_froglight.png create mode 100644 src/util/LightColor.ts diff --git a/public/assets/tiny/textures/block/ochre_froglight.png b/public/assets/tiny/textures/block/ochre_froglight.png new file mode 100644 index 0000000000000000000000000000000000000000..986956563b2eb15609ff7a70efb5826c53f04cc7 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPGa2=EDU{rCUPpI>Kxz2Efx{-l@Z z+HUU1yS^;qzV`9bT1mmFrzBj`RIl?aNT2CCX^~ryt28Tz2}ASMHlYQ_eP--o upU(5XP%*tq{OaS`t8doS|6E`FgSjI{_51GWr=9?A%s5+vkN;?jM_^cJi4>oS;^z% z626wS%~#()+H&XH)8cC%FRhgnoO(*aB~A4@&w})su9FtI1-VMIa+okQUu_dwaNK9c v9`@-x?+X>vtHiH9p1t~JP5saH)jya!VpPBHo_^{H&`JhRS3j3^P6z{9~zq`8k;r0cW zXHP!b(y_h1Vp+`7i98d5YSKMj978H@@t)Wy*zCaLdhmPy?81%|qxO;qkM1mYR`NKx zgs&xS^VRo{w%qylwD{V`OKT+sr=F5!d|)L9WuQ93~9SSKEXZ9QT>A vhkZKF`$EO^D)Fn2XRp3lQ~z^)^$+Hb7}f8)r=NNPw35No)z4*}Q$iB}`*2p6 literal 0 HcmV?d00001 diff --git a/public/blocks.yson b/public/blocks.yson index 6a85240..f32bf85 100644 --- a/public/blocks.yson +++ b/public/blocks.yson @@ -5,6 +5,9 @@ water: { type: "fluid", soundMaterial: "water" }, grass_block: { soundMaterial: "grass" }, chest: { type: "container", soundMaterial: "wood" }, - torch: { full: false, lightLevel: 15, soundMaterial: "wood" } + torch: { full: false, light: [15, 13, 0], soundMaterial: "wood" }, + ochre_froglight: { full: false, light: [13, 12, 8], soundMaterial: "wood" }, + verdant_froglight: { full: false, light: [9, 12, 9], soundMaterial: "wood" }, + pearlescent_froglight: { full: false, light: [15, 8, 11], soundMaterial: "wood" } } } diff --git a/src/Graphics.ts b/src/Graphics.ts index 0873007..228e7ed 100644 --- a/src/Graphics.ts +++ b/src/Graphics.ts @@ -1,3 +1,4 @@ +import LightColor from "./util/LightColor.js" import TextRenderer, { type TextRenderingOptions } from "./util/TextRenderer.js" export default class Graphics { @@ -15,7 +16,7 @@ export default class Graphics { })[] constructor(readonly canvas: HTMLCanvasElement | OffscreenCanvas, private readonly blockSize: number) { - this.ctx = canvas.getContext("2d")! + this.ctx = canvas.getContext("2d", { alpha: false })! this.filters = {} this.filterSaves = [] } @@ -87,8 +88,17 @@ export default class Graphics { return TextRenderer.drawText(this.ctx, text, 0, 0, options) } - drawImage(image: CanvasImageSource, w: number = 1, h: number = 1) { + drawImage(image: CanvasImageSource, w: number = 1, h: number = 1, light?: LightColor) { + this.ctx.save() + + if (light) { + this.ctx.fillStyle = light.toString() + this.ctx.fillRect(0, 0, w * this.blockSize, -h * this.blockSize) + this.ctx.globalCompositeOperation = "multiply" + } + this.ctx.drawImage(image, 0, 0, w * this.blockSize, -h * this.blockSize) + this.ctx.restore() } drawPartialImage(image: CanvasImageSource, sx: number, sy: number, sWidth: number, sHeight: number, dWidth: number = 1, dHeight: number = 1) { diff --git a/src/block/Block.ts b/src/block/Block.ts index 8206fba..e07a557 100644 --- a/src/block/Block.ts +++ b/src/block/Block.ts @@ -5,6 +5,7 @@ import BoundingBox from "../util/BoundingBox.js" import Dim2 from "../dim/Dim2.js" import Dim3 from "../dim/Dim3.js" import Graphics from "../Graphics.js" +import LightColor from "../util/LightColor.js" import World from "../world/World.js" export default class Block implements HasData { @@ -18,7 +19,7 @@ export default class Block implements HasData { protected readonly def: BlockDef - private readonly light: { sky: number, block: number } + private readonly light: { sky: number, block: LightColor } constructor(def: BlockDef | NamespacedId) { if (def instanceof BlockDef) this.def = def @@ -31,7 +32,7 @@ export default class Block implements HasData { } } - this.light = { sky: 0, block: 0 } + this.light = { sky: 0, block: new LightColor(0, 0, 0) } } get id() { @@ -59,11 +60,17 @@ export default class Block implements HasData { } get blockLight() { - return Math.max(this.light.block, this.def.lightLevel) + if (this.def.light) return this.light.block.max(this.def.light) + + return this.light.block } get lightLevel() { - return Math.max(this.skyLight, this.blockLight) + return Math.max(this.skyLight, this.blockLight.level) + } + + lightColor(world: World) { + return this.light.block.max(world.skyLight.scale(this.light.sky)) } hasInventory() { @@ -95,7 +102,8 @@ export default class Block implements HasData { this.light.block = updateBlockLight(world, this, x, y, z, this.light.block) } - draw(g: Graphics, x: number, y: number, z: number) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + draw(g: Graphics, world: World, x: number, y: number, z: number) { if (this.def.id == "tiny:air") { if (debug.showDebugScreen && debug.showAirLightLevel) overlayLightLevel(g, x, y, this.lightLevel) @@ -105,17 +113,21 @@ export default class Block implements HasData { g.save() g.translate(x, y) - let light = this.lightLevel / 15 + /* let light = this.lightLevel / 15 const FLUID_TRANSLUCENCY = 0.35 if (this.type == "fluid") g.globalAlpha = 1 - (1 - FLUID_TRANSLUCENCY) * light if (z < 0) light *= 0.75 + */ + + let light = this.lightColor(world) + if (z < 0) light = light.scale(12) // console.log(light) - g.brightness(light) - this.texture?.draw(g) + // g.brightness(light) + this.texture?.draw(g, 1, 1, false, light) g.restore() } @@ -180,9 +192,8 @@ function updateSkyLight(world: World, block: Block, x: number, y: number, z: num return skyLight } -function updateBlockLight(world: World, block: Block, x: number, y: number, z: number, blockLightBefore: number) { - const derivLight = [] - let blockLight: number = blockLightBefore +function updateBlockLight(world: World, block: Block, x: number, y: number, z: number, blockLightBefore: LightColor) { + const derivLight: LightColor[] = [] derivLight.push(deriveBlockLight(world, x-1, y, z)) derivLight.push(deriveBlockLight(world, x+1, y, z)) @@ -194,8 +205,9 @@ function updateBlockLight(world: World, block: Block, x: number, y: number, z: n derivLight.push(deriveBlockLight(world, x, y, z-1)) } - blockLight = Math.floor(Math.max(...derivLight, 0)) - if (blockLight != blockLightBefore) { + const blockLight = LightColor.max(derivLight) + + if (!blockLight.equals(blockLightBefore)) { world.scheduleBlockUpdate(x-1, y, z) world.scheduleBlockUpdate(x+1, y, z) world.scheduleBlockUpdate(x, y-1, z) @@ -217,12 +229,12 @@ function deriveSkyLight(world: World, x: number, y: number, z: number) { return other.skyLight - 1 // water } -function deriveBlockLight(world: World, x: number, y: number, z: number) { +function deriveBlockLight(world: World, x: number, y: number, z: number): LightColor { const other = world.getBlock(x, y, z) - if (!other || (other.isSolid() && other.full)) return 0 + if (!other || (other.isSolid() && other.full)) return new LightColor(0, 0, 0) - return other.blockLight - 1 + return other.blockLight.decrement() } function overlayLightLevel(g: Graphics, x: number, y: number, level: number) { diff --git a/src/defs/BlockDef.ts b/src/defs/BlockDef.ts index 9dd93ae..a203955 100644 --- a/src/defs/BlockDef.ts +++ b/src/defs/BlockDef.ts @@ -1,6 +1,7 @@ -import { equalsAny, isIntInRange, isObject, isPosInt, validateProperty } from "../util/typecheck.js" +import { equalsAny, isIntInRange, isObject, isPosInt, validateArray, validateProperty } from "../util/typecheck.js" import Base from "./Base.js" import { type Flatten } from "../util/interfaces.js" +import LightColor from "../util/LightColor.js" import Sound from "../sound/Sound.js" import TinyError from "../TinyError.js" import { getSound } from "../main.js" @@ -11,7 +12,7 @@ export default class BlockDef extends Base { readonly maxItemStack: number readonly full: boolean readonly soundMaterial: string - readonly lightLevel: number + readonly light: LightColor | null readonly inventorySlots: number | null readonly inventoryColumns: number | null @@ -46,8 +47,8 @@ export default class BlockDef extends Base { this.inventoryColumns = data.inventoryColumns } else this.inventorySlots = this.inventoryColumns = null - if (data.type == "block") this.lightLevel = data.lightLevel - else this.lightLevel = 0 + if (data.type == "block") this.light = new LightColor(...data.light) + else this.light = null } get assetsPath() { @@ -77,7 +78,7 @@ type BlockDefData = Flatten<{ full: boolean } & ({ type: "block", - lightLevel: number + light: [number, number, number] } | { type: "container", inventorySlots: number @@ -101,7 +102,7 @@ function validate(data: unknown): data is BlockDefData { } if (data.type == "block") { - validateProperty(data, "lightLevel", isIntInRange(0, 16), 0) + validateProperty(data, "light", validateArray(isIntInRange(0, 16)), [0, 0, 0]) } if (data.type == "container") { diff --git a/src/texture/Texture.ts b/src/texture/Texture.ts index 282f130..17f4f85 100644 --- a/src/texture/Texture.ts +++ b/src/texture/Texture.ts @@ -1,4 +1,5 @@ import Graphics from "../Graphics.js" +import LightColor from "../util/LightColor.js" import Subtexture from "./Subtexture.js" export default class Texture { @@ -44,10 +45,10 @@ export default class Texture { return this.#state == Texture.LOADED } - draw(g: Graphics, w?: number, h?: number, global: boolean = false) { + draw(g: Graphics, w?: number, h?: number, global: boolean = false, light?: LightColor) { if (!this.ready) return if (global) g.globalDrawImage(this.image, w, h) - else g.drawImage(this.image, w, h) + else g.drawImage(this.image, w, h, light) } getSubtexture(x: number, y: number, w: number, h: number) { diff --git a/src/util/DebugScreen.ts b/src/util/DebugScreen.ts index c8094a8..c309e76 100644 --- a/src/util/DebugScreen.ts +++ b/src/util/DebugScreen.ts @@ -48,14 +48,14 @@ function gameInfo(g: Graphics, world: World, player: Player) { lines.push(``) lines.push(`looking at block: ${mouseBlock.x}, ${mouseBlock.y}, ${lookingAt.z}`) lines.push(`${lookingAt.block.id}`) - lines.push(`light: ${lookingAt.block.lightLevel} (${lookingAt.block.skyLight} sky, ${lookingAt.block.blockLight} block)`) + lines.push(`light: ${lookingAt.block.lightColor(world).debug} (${lookingAt.block.skyLight} sky, ${lookingAt.block.blockLight.debug} block)`) } if (lookingAtFluid.block && lookingAtFluid.block.type == "fluid") { lines.push(``) lines.push(`looking at fluid: ${mouseBlock.x}, ${mouseBlock.y}, ${lookingAtFluid.z}`) lines.push(`${lookingAtFluid.block.id}`) - lines.push(`light: ${lookingAtFluid.block.lightLevel} (${lookingAtFluid.block.skyLight} sky, ${lookingAtFluid.block.blockLight} block)`) + lines.push(`light: ${lookingAtFluid.block.lightColor(world).debug} (${lookingAtFluid.block.skyLight} sky, ${lookingAtFluid.block.blockLight.debug} block)`) } const offset = g.drawText(lines[0], { drawBg: true, padding: 3, font: { size: 20 }, shadow: false }) diff --git a/src/util/LightColor.ts b/src/util/LightColor.ts new file mode 100644 index 0000000..21fe92e --- /dev/null +++ b/src/util/LightColor.ts @@ -0,0 +1,73 @@ +import { isIntInRange } from "./typecheck.js" + +const rangeCheck = isIntInRange(0, 16) + +export default class LightColor { + + static max(lights: LightColor[]) { + let maxRed = 0 + let maxGreen = 0 + let maxBlue = 0 + + lights.forEach(color => { + if (color.red > maxRed) maxRed = color.red + if (color.green > maxGreen) maxGreen = color.green + if (color.blue > maxBlue) maxBlue = color.blue + }) + + return new LightColor(maxRed, maxGreen, maxBlue) + } + + readonly red: number + readonly green: number + readonly blue: number + + constructor(red: number, green: number, blue: number) { + this.red = rangeCheck(red) ? red : 0 + this.green = rangeCheck(green) ? green : 0 + this.blue = rangeCheck(blue) ? blue : 0 + } + + get level() { + return Math.floor((this.red + this.green + this.blue) / 3) + } + + get debug() { + return `${this.red} ${this.green} ${this.blue}` + } + + max(other: LightColor) { + return new LightColor(Math.max(this.red, other.red), Math.max(this.green, other.green), Math.max(this.blue, other.blue)) + } + + multiply(other: LightColor) { + const red = (this.red * other.red) / 15 + const green = (this.green * other.green) / 15 + const blue = (this.blue * other.blue) / 15 + + return new LightColor(red, green, blue) + } + + scale(x: number) { + x /= 15 + + return new LightColor(Math.floor(this.red * x), Math.floor(this.green * x), Math.floor(this.blue * x)) + } + + decrement() { + return new LightColor(this.red - 1, this.green - 1, this.blue - 1) + } + + equals(other: LightColor) { + return this.red == other.red && this.green == other.green && this.blue == other.blue + } + + toString() { + const r = (this.red * 17).toString(16).padStart(2, "0") + const g = (this.green * 17).toString(16).padStart(2, "0") + const b = (this.blue * 17).toString(16).padStart(2, "0") + + return `#${r}${g}${b}` + } + +} diff --git a/src/world/World.ts b/src/world/World.ts index 7bc115a..700c6d9 100644 --- a/src/world/World.ts +++ b/src/world/World.ts @@ -4,6 +4,7 @@ import Player, { type PlayerData } from "../entity/Player.js" import { blockSize, cam, createBlock, createEntity, gameOffset, getFirstBlock } from "../gui/state/ingame.js" import Dim2 from "../dim/Dim2.js" import Graphics from "../Graphics.js" +import LightColor from "../util/LightColor.js" import { type NamespacedId } from "../util/interfaces.js" import { game } from "../main.js" import { isNamespacedId } from "../util/typecheck.js" @@ -77,6 +78,7 @@ export default class World { private blocks: Map<`${number},${number},${number}`, Block> private entities: Set private tickCount: number + private skyLightColor: LightColor readonly minX: number readonly maxX: number readonly minY: number @@ -92,6 +94,7 @@ export default class World { this.blocks = new Map() this.entities = new Set() this.tickCount = 0 + this.skyLightColor = new LightColor(0, 2, 3) for (let x = this.minX; x <= this.maxX; x++) { for (let y = this.minY; y <= this.maxY; y++) { @@ -109,6 +112,10 @@ export default class World { return this.tickCount } + get skyLight() { + return this.skyLightColor + } + validBlockPosition(x: number, y: number, z: number) { x = Math.floor(x) y = Math.floor(y) @@ -236,7 +243,7 @@ export default class World { for (let x = Math.max(this.minX, left); x <= Math.min(this.maxX, right); x++) { const frontBlock = getFirstBlock(this, x, y, undefined, block => block.full) - if (z >= frontBlock.z) this.getBlock(x, y, z)?.draw(g, x, y, z) + if (z >= frontBlock.z) this.getBlock(x, y, z)?.draw(g, this, x, y, z) } } }