diff --git a/public/Items/torch.png b/public/Items/torch.png new file mode 100644 index 0000000..b8d80b2 Binary files /dev/null and b/public/Items/torch.png differ diff --git a/src/App.vue b/src/App.vue index fe1ad38..749769f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,7 +5,7 @@ import Help from './screens/help.vue' import Inventory from './screens/inventory.vue' import Background from './Background.vue' -import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def' +import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT, softTerrain, hardTerrain } from './level/def' import { getItem, getItemClass } from './level/items' import createLevel from './level' @@ -25,11 +25,12 @@ let updateLightMap = (() => {}) as ReturnType pocket(getItem('tool_shovel_wood')) pocket(getItem('tool_sword_wood')) pocket(getItem('tool_pickaxe_wood')) +pocket(getItem('fixture_torch')) let animationFrame = 0 let lastTick = 0 -const debug = ref(false) +const debug = ref(true) const x = ref(0) const y = ref(0) const floorX = computed(() => Math.floor(x.value)) @@ -46,6 +47,8 @@ const lightBarrier = computed(() => { return level.sunLight(floorX.value) }) +const lightSources = computed(() => []) + const mapUpdateCount = ref(0) const mapGrid = computed(() => { const _update = mapUpdateCount.value // reactivity trigger @@ -127,20 +130,29 @@ function build(blockX: number, blockY: number, block: InventoryItem) { } function interactWith(blockX: number, blockY: number, block: Block) { - if (debug) console.debug('interact with', blockX, blockY, block.type) + if (debug) console.debug('interact with', blockX, blockY, block.type, inventorySelection.value.type, 'in hand') // ยง 4 ArbZG if (paused.value) return - const blockInHand = inventorySelection.value.type === 'block' - const toolInHand = inventorySelection.value.type === 'tool' - const emptyBlock = block.type === 'air' || block.type === 'cave' + const blockInHand = inventorySelection.value + const shovelInHand = blockInHand.id.indexOf('shovel') >= 0 + const pickaxeInHand = blockInHand.id.indexOf('pickaxe') >= 0 + + const canBuild = !!blockInHand.builds + const hasTool = blockInHand.type === 'tool' + const hasSpace = block.type === 'air' || block.type === 'cave' + const canUseShovel = softTerrain.indexOf(block.type) >= 0 + const canUsePickaxe = hardTerrain.indexOf(block.type) >= 0 + + console.log({ canBuild, hasSpace, hasTool, blockInHand }) // put the selected block - if (blockInHand && emptyBlock) { - build(blockX, blockY, inventorySelection.value) + if (canBuild && hasSpace) { + build(blockX, blockY, blockInHand) // dig a block with shovel or pick axe - } else if (toolInHand && !emptyBlock) { - dig(blockX, blockY, block) + } else if (hasTool && !hasSpace) { + if (shovelInHand && canUseShovel) dig(blockX, blockY, block) + else if (pickaxeInHand && canUsePickaxe) dig(blockX, blockY, block) } } @@ -214,7 +226,11 @@ onMounted(() => { canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH const ctx = canvas.getContext('2d')! - updateLightMap = useLightMask(ctx, floorX, floorY, tx, ty, time, lightBarrier) + updateLightMap = useLightMask( + ctx, floorX, floorY, + tx, ty, time, + lightBarrier, lightSources, + ) } else { console.warn('lightmap deactivated') } @@ -265,7 +281,13 @@ onMounted(() => {
x:{{ floorX }}, y:{{ floorY }} - +
diff --git a/src/Background.vue b/src/Background.vue index 99c55e2..2282b26 100644 --- a/src/Background.vue +++ b/src/Background.vue @@ -21,18 +21,15 @@ onMounted(() => { const drawBackground = useBackground( canvasEl, - ~~(STAGE_WIDTH * BLOCK_SIZE / 2.0), - ~~(STAGE_HEIGHT * BLOCK_SIZE / 2.0), + ~~(STAGE_WIDTH * BLOCK_SIZE / 1.0), + ~~(STAGE_HEIGHT * BLOCK_SIZE / 1.0), ) - watch(props, () => { - console.log('drawing background', sunY.value) - drawBackground(props.x, sunY.value) - }, { immediate: true }) + watch(props, () => drawBackground(props.x, sunY.value), { immediate: true }) }) diff --git a/src/assets/field.css b/src/assets/field.css index f824ed2..3e08e70 100644 --- a/src/assets/field.css +++ b/src/assets/field.css @@ -98,6 +98,7 @@ .block.brickWall { background-image: url(/Tiles/brick_grey.png); } +.block.torch { background-image: url("/Items/torch.png"); } #field { user-select: none; @@ -112,17 +113,17 @@ .morning0 .block, .morning0 #player { - filter: saturate(50%); + filter: saturate(50%) brightness(0.6); } .morning1 .block, .morning1 #player { - filter: saturate(100%); + filter: saturate(100%) brightness(0.8); } .morning2 .block, .morning2 #player { - filter: saturate(120%); + filter: saturate(120%) brightness(0.9); } .evening0 .block, @@ -132,17 +133,17 @@ .evening1 .block, .evening1 #player { - filter: saturate(70%); + filter: saturate(70%) brightness(0.8); } .evening2 .block, .evening2 #player { - filter: saturate(50%); + filter: saturate(50%) brightness(0.6); } .night .block, .night #player { - filter: saturate(30%); + filter: saturate(30%) brightness(0.4); } #blocks { diff --git a/src/assets/items.css b/src/assets/items.css index 7cf02fc..de56871 100644 --- a/src/assets/items.css +++ b/src/assets/items.css @@ -14,3 +14,5 @@ .item.block-dirt { background-image: url("/Tiles/dirt.png"); } .item.block-stone { background-image: url("/Tiles/stone.png"); } .item.block-gravel { background-image: url("/Tiles/gravel_stone.png"); } + +.item.fixture-torch { background-image: url("/Items/torch.png"); } diff --git a/src/level/def.ts b/src/level/def.ts index b420a7c..cff65d5 100644 --- a/src/level/def.ts +++ b/src/level/def.ts @@ -26,9 +26,13 @@ export const blockTypes: Record = { stone: { type: 'stone', hp: 10, walkable: false, drops: 'block_stone' }, bedrock: { type: 'bedrock', hp: 25, walkable: false, drops: 'block_stone' }, // Built Blocks - brickWall: { type: 'brickWall', hp: 25, walkable: false, drops: 'block_gravel' }, + brickWall: { type: 'brickWall', hp: 25, walkable: false, drops: 'block_gravel', fixture: true }, + torch: { type: 'torch', hp: 1, walkable: true, transparent: true, drops: 'fixture_torch', illuminated: true, fixture: true }, } +export const softTerrain: BlockType[] = ['grass', 'soil', 'soilGravel', 'torch'] +export const hardTerrain: BlockType[] = ['stone', 'stoneGravel', 'bedrock', 'brickWall'] + export const level = { treeTop: 9, ground: 14, diff --git a/src/level/index.ts b/src/level/index.ts index 9ebcd7b..1019f1c 100644 --- a/src/level/index.ts +++ b/src/level/index.ts @@ -3,10 +3,10 @@ import { createNoise2D, type NoiseFunction2D } from 'simplex-noise' import createBlockGenerator from './blockGen' import createBlockExtender from './blockExt' -import { blockTypes, blockTypes as T } from './def' +import { level as L, blockTypes, blockTypes as T } from './def' import type { Block, Change } from '../types.d' -const MAX_LIGHT = 100 // maximum level where light shines +const MAX_LIGHT = L.underground // maximum level where light shines export default function createLevel(width: number, height: number, seed = 'extremely random seed') { const prng = alea(seed) diff --git a/src/level/items.ts b/src/level/items.ts index 75061db..5c7b46a 100644 --- a/src/level/items.ts +++ b/src/level/items.ts @@ -26,6 +26,8 @@ export const items = { ore_ruby: { id: 'ore_ruby', name: 'ruby', type: 'ore', icon: 'ore_ruby' } as Item, ore_diamond: { id: 'ore_diamond', name: 'diamond', type: 'ore', icon: 'ore_diamond' } as Item, ore_emerald: { id: 'ore_emerald', name: 'emerald', type: 'ore', icon: 'ore_emerald' } as Item, + + fixture_torch: { id: 'fixture_torch', name: 'Torch', type: 'fixture', icon: 'torch', builds: 'torch' } as Item, } as const export type ItemId = keyof typeof items diff --git a/src/types.d.ts b/src/types.d.ts index 9bb3738..065c38d 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,6 +1,6 @@ import type { ItemId } from './level/items' export type ItemQuality = 'wood' | 'iron' | 'diamond' -export type ItemType = 'tool' | 'block' | 'ore' +export type ItemType = 'tool' | 'block' | 'ore' | 'fixture' export interface Item { id: string @@ -28,6 +28,7 @@ export type BlockType = | 'stone' | 'stoneGravel' | 'bedrock' | 'cave' | 'brickWall' + | 'torch' export type Block = { type: BlockType, // what is it? @@ -36,6 +37,7 @@ export type Block = { climbable?: boolean, // can I climb it? transparent?: boolean, // can I see through it? illuminated?: boolean, // is it glowing? + fixture?: boolean, // is it built by the player? drops?: ItemId, // what do I get, when loot it? } @@ -78,3 +80,9 @@ export interface Player extends Moveable { export type Direction = 'at' | 'left' | 'right' | 'up' | 'down' + +export interface LightSource { + x: number + y: number + strength: number +} diff --git a/src/util/useLightMask.ts b/src/util/useLightMask.ts index 4c3a23e..d5e21d0 100644 --- a/src/util/useLightMask.ts +++ b/src/util/useLightMask.ts @@ -1,4 +1,5 @@ import type { Ref, ComputedRef } from 'vue' +import type { LightSource } from '../types.d' import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from '../level/def' type RefOrComputed = Ref | ComputedRef @@ -11,37 +12,17 @@ export default function useLightMask( ty: RefOrComputed, time: RefOrComputed, lightBarrier: RefOrComputed, + lightSources: RefOrComputed, ) { const W = ((STAGE_WIDTH + 2) * BLOCK_SIZE) const H = ((STAGE_HEIGHT + 2) * BLOCK_SIZE) const B = BLOCK_SIZE - 4 // no idea why there is a difference, but it is 4px + const BHalf = B / 2 const playerX = (W - B) / 2 + B / 4 - const playerY = H / 2 - B / 2 + const playerY = H / 2 - BHalf const playerLightSize = B * 1.8 - function getAmbientLightColor() { - const t = time.value - - // Night time (pale bluish dark: hslpicker.com/#2b293d ) - if (t > 900 || t < 100) { - return `hsl(245, 20%, 20%)` - } - // Morning hours (gradually more reddish hue) - if (t < 250) { - const s = Math.round((t - 100) / 1.5) // 0-100% - const l = Math.round((t - 100) / 1.875) + 20 // 20-100% - return `hsl(0, ${s}%, ${l}%)` - } - // Evening hours (from neutral white to bluish hue with low saturation) - if (t > 700) { - const s = 100 - Math.round((t - 700) / 2.5) // 100-20% - return `hsl(245, ${s}%, ${s}%)` - } - // day (neutral white) - return `hsl(0, 0%, 100%)` - } - function drawPlayerLight(sizeMul:number) { const t = time.value @@ -50,67 +31,53 @@ export default function useLightMask( playerX - tx.value, playerY - ty.value, playerLightSize * sizeMul ) - // Add color stops for a light around the player - // Night time, lets tone down the light a bit - if (t > 900 || t < 100) { - playerLight.addColorStop(0.7, "#AA7A"); - playerLight.addColorStop(1, "#AA70"); - - // Morning - } else if (t < 150) { - playerLight.addColorStop(0.7, "#CCAA"); - playerLight.addColorStop(1, "#CCA0"); - - // Day (neutral white) - } else { - playerLight.addColorStop(0.7, "#FFFA"); - playerLight.addColorStop(1, "#FFF0"); - } + playerLight.addColorStop(0.7, "#FFFA"); + playerLight.addColorStop(1, "#FFF0"); // Set the fill style and draw a rectangle ctx.fillStyle = playerLight; ctx.fillRect(0, 0, W, H) } - function drawLights() { - // used for everything above ground - const ambientLight = getAmbientLightColor() + function drawLightSources() { + for (const src of lightSources.value) { + const x = src.x * B + const y = src.y * B + + const light = ctx.createRadialGradient(x, y, 0, x, y, src.strength) + light.addColorStop(0.0, "#CC8F") + light.addColorStop(0.4, "#CC8A") + light.addColorStop(1, "#CC80") + } + } + + function drawShadows() { const barrier = lightBarrier.value - ctx.fillStyle = ambientLight - for (let col = 0; col < W / B; col++) { + for (let col = 0; col < barrier.length; col++) { const level = (barrier[col] - y.value) * B - const sw = B - const sh = level - const sx = col * sw - const sy = 0 + const x = B*col - ctx.fillRect(sx, sy, sw, sh) + // gradient for the shadow that is cast down from the surface + const gradient = ctx.createLinearGradient(0, 0, 0, H) + gradient.addColorStop(0, '#FFF') + gradient.addColorStop(Math.min(level / H, 1), '#FFF') + gradient.addColorStop(Math.min((level + B) / H, 1), '#000') + ctx.fillStyle = gradient + ctx.fillRect(x, 0, B, H) } - - // make light columns wider to illuminate surrounding blocks - const extra = Math.floor(B / 2) - ctx.fillStyle = ambientLight.slice(0, -1) + ', 40%)' - for (let col = 0; col < W / B; col++) { - const level = (barrier[col] - y.value) * B - const sw = B - const sh = level - const sx = col * sw - const sy = 0 - - ctx.fillRect(sx - extra, sy - extra, sw + extra * 2, sh + extra * 2) - } - - // TODO: draw light for candles and torches } return function update() { // first, throw the world in complete darkness - ctx.fillStyle = '#000000' + ctx.fillStyle = '#FFFFFF' ctx.fillRect(0, 0, W, H) - // second, find and bring light into the world - drawLights() + // second, hide what is beneath + drawShadows() + + // third, fight the darkness + drawLightSources() // finally, draw the players light // with a size multiplicator which might be later used to diff --git a/src/util/useTime.ts b/src/util/useTime.ts index 70c94cc..0316f15 100644 --- a/src/util/useTime.ts +++ b/src/util/useTime.ts @@ -46,7 +46,7 @@ export function calcSunAngle(tick: number): number { export default function useTime() { // the day is split in 1000 parts, so we start in the morning - const time = ref(250) + const time = ref(240) function updateTime() { time.value = (time.value + 0.1) % 1000