directional fixtures

This commit is contained in:
Norman Köhring 2025-03-22 22:43:27 +01:00
parent 72c5cd1d19
commit 3266ddb217
11 changed files with 106 additions and 46 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
public/Items/torchFloor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/Items/torchRight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Block, Direction, Item, InventoryItem } from './types.d'
import type { Block, BlockType, Direction, Item, InventoryItem, LightSource } from './types.d'
import { ref, computed, watch, onMounted, useTemplateRef } from 'vue'
import Help from './screens/help.vue'
import Inventory from './screens/inventory.vue'
@ -25,7 +25,7 @@ let updateLightMap = (() => {}) as ReturnType<typeof useLightMask>
pocket(getItem('tool_shovel_wood'))
pocket(getItem('tool_sword_wood'))
pocket(getItem('tool_pickaxe_wood'))
pocket(getItem('fixture_torch'))
pocket(getItem('fixture_torch'), 5)
let animationFrame = 0
let lastTick = 0
@ -47,22 +47,40 @@ const lightBarrier = computed<number[]>(() => {
return level.sunLight(floorX.value)
})
const lightSources = computed(() => [])
const mapUpdateCount = ref(0)
const mapGrid = computed<Block[][]>(() => {
const _update = mapUpdateCount.value // reactivity trigger
return level.grid(floorX.value, floorY.value, true)
})
const lightSources = computed(() => {
const _update = mapUpdateCount.value // reactivity trigger
const _floorX = floorX.value // reactivity trigger
const _floorY = floorY.value // reactivity trigger
const lightSources: LightSource[] = []
const grid = mapGrid.value
for (let y = 0; y < grid.length; y++) {
const row = grid[y]
for (let x = 0; x < row.length; x++) {
const block = row[x]
if (block.illumination) {
lightSources.push({
x, y,
strength: block.illumination,
color: block.color ?? '#FFE'
})
}
}
}
return lightSources
})
const arriving = ref(true)
const walking = ref(false)
const inventorySelection = ref<InventoryItem>(player.inventory[0])
const surroundings = computed<Record<Direction, Block>>(() => {
const _update = mapUpdateCount.value // reactivity trigger
const x = px.value
const y = py.value
const getSurroundings = (x: number, y: number) => {
const rows = mapGrid.value
const rowY = rows[y]
@ -76,6 +94,11 @@ const surroundings = computed<Record<Direction, Block>>(() => {
up: rowYp[x],
down: rowYn[x],
}
}
const surroundings = computed<Record<Direction, Block>>(() => {
const _update = mapUpdateCount.value // reactivity trigger
return getSurroundings(px.value, py.value)
})
const blocked = computed(() => {
const { left, right, up, down } = surroundings.value
@ -113,15 +136,26 @@ function dig(blockX: number, blockY: number, block: Block) {
}
function build(blockX: number, blockY: number, block: InventoryItem) {
const blockToBuild = block.builds
// the block doesn't do anything
if (!blockToBuild) return
let blockToBuild = block.builds
if (!blockToBuild) return // the block doesn't do anything?!
// While blocks are just filling the space completely, fixtures are attached
// to the closest surface. We check the surroundings, starting at with left
// and right, then bottom and top.
if (block.type === 'fixture') {
const { left, right, up, down } = getSurroundings(blockX, blockY)
if (!left.transparent) blockToBuild = `${blockToBuild}Left`
else if (!right.transparent) blockToBuild = `${blockToBuild}Right`
else if (!up.transparent) blockToBuild = `${blockToBuild}Ceiling`
else if (!down.transparent) blockToBuild = `${blockToBuild}Floor`
}
level.change({
change: 'exchange',
x: floorX.value + blockX,
y: floorY.value + blockY,
newType: blockToBuild
newType: blockToBuild as BlockType
})
mapUpdateCount.value = mapUpdateCount.value + 1
@ -130,10 +164,20 @@ 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, inventorySelection.value.type, 'in hand')
if (debug) {
console.debug(
`interact with ${block.type} at ${blockX},${blockY},`,
`with a ${inventorySelection.value.id} in hand`
)
}
// § 4 ArbZG
if (paused.value) return
// no spooky interaction at a distance
const distanceX = ~~(px.value - blockX)
const distanceY = ~~(py.value - blockY)
if (distanceX > 1 || distanceY > 1) return
const blockInHand = inventorySelection.value
const shovelInHand = blockInHand.id.indexOf('shovel') >= 0
const pickaxeInHand = blockInHand.id.indexOf('pickaxe') >= 0
@ -144,8 +188,6 @@ function interactWith(blockX: number, blockY: number, block: Block) {
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 (canBuild && hasSpace) {
build(blockX, blockY, blockInHand)
@ -227,8 +269,7 @@ onMounted(() => {
canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
const ctx = canvas.getContext('2d')!
updateLightMap = useLightMask(
ctx, floorX, floorY,
tx, ty, time,
ctx, floorY, tx, ty,
lightBarrier, lightSources,
)
} else {

View file

@ -98,7 +98,10 @@
.block.brickWall {
background-image: url(/Tiles/brick_grey.png);
}
.block.torch { background-image: url("/Items/torch.png"); }
.block.torchLeft { background-image: url("/Items/torchLeft.png"); }
.block.torchRight { background-image: url("/Items/torchRight.png"); }
.block.torchFloor { background-image: url("/Items/torchFloor.png"); }
.block.torchCeiling { background-image: url("/Items/torchCeiling.png"); }
#field {
user-select: none;

View file

@ -15,4 +15,4 @@
.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"); }
.item.fixture-torch { background-image: url("/Items/torchFloor.png"); }

View file

@ -26,11 +26,18 @@ export const blockTypes: Record<BlockType, Block> = {
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', fixture: true },
torch: { type: 'torch', hp: 1, walkable: true, transparent: true, drops: 'fixture_torch', illuminated: true, fixture: true },
brickWall: { type: 'brickWall', hp: 25, walkable: false, drops: 'block_gravel' },
torchLeft: { type: 'torchLeft', hp: 1, walkable: true, transparent: true, drops: 'fixture_torch', illumination: 1.0, color: '#FFE', fixture: true },
torchRight: { type: 'torchRight', hp: 1, walkable: true, transparent: true, drops: 'fixture_torch', illumination: 1.0, color: '#FFE', fixture: true },
torchCeiling: { type: 'torchCeiling', hp: 1, walkable: true, transparent: true, drops: 'fixture_torch', illumination: 1.0, color: '#FFE', fixture: true },
torchFloor: { type: 'torchFloor', hp: 1, walkable: true, transparent: true, drops: 'fixture_torch', illumination: 1.0, color: '#FEB', fixture: true },
}
export const softTerrain: BlockType[] = ['grass', 'soil', 'soilGravel', 'torch']
export const softTerrain: BlockType[] = [
'grass', 'soil', 'soilGravel',
'torchLeft', 'torchRight', 'torchCeiling', 'torchFloor',
]
export const hardTerrain: BlockType[] = ['stone', 'stoneGravel', 'bedrock', 'brickWall']
export const level = {

30
src/types.d.ts vendored
View file

@ -1,4 +1,5 @@
import type { ItemId } from './level/items'
export type { ItemId } from './level/items'
export type ItemQuality = 'wood' | 'iron' | 'diamond'
export type ItemType = 'tool' | 'block' | 'ore' | 'fixture'
@ -8,7 +9,9 @@ export interface Item {
type: ItemType
icon: string
quality?: ItemQuality
builds?: BlockType
// this should be ItemId | BlockType, but has to be string to avoid
// a circular type reference
builds?: string
}
export interface ToolItem extends Item {
@ -21,6 +24,8 @@ export interface BlockItem extends Item {
builds: BlockType
}
type Fixture<T extends string> = `${T}Left` | `${T}Right` | `${T}Ceiling` | `${T}Floor`;
export type BlockType =
| 'air' | 'grass'
| 'treeCrown' | 'treeLeaves' | 'treeTrunk' | 'treeRoot'
@ -28,17 +33,18 @@ export type BlockType =
| 'stone' | 'stoneGravel'
| 'bedrock' | 'cave'
| 'brickWall'
| 'torch'
| Fixture<'torch'>
export type Block = {
type: BlockType, // what is it?
hp: number, // how long do I need to hit it?
walkable: boolean, // can I walk through it?
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?
type: BlockType // what is it?
hp: number // how long do I need to hit it?
walkable: boolean // can I walk through it?
climbable?: boolean // can I climb it?
transparent?: boolean // can I see through it?
fixture?: boolean // is it built by the player?
illumination?: number // How many blocks wide is it glowing?
color?: string // How is it coloured?
drops?: ItemId // what do I get, when loot it?
}
// describes a changed block, eg digged or placed by the player
@ -54,11 +60,10 @@ type ChangedBlock = {
y: number
newType: BlockType
}
type Change = DamagedBlock | ChangedBlock
export type Change = DamagedBlock | ChangedBlock
export interface InventoryItem extends Item {
amount: number
quality: ItemQuality | null
}
export interface Moveable {
@ -85,4 +90,5 @@ export interface LightSource {
x: number
y: number
strength: number
color: string
}

View file

@ -6,11 +6,9 @@ type RefOrComputed<T> = Ref<T> | ComputedRef<T>
export default function useLightMask(
ctx: CanvasRenderingContext2D,
x: RefOrComputed<number>,
y: RefOrComputed<number>,
tx: RefOrComputed<number>,
ty: RefOrComputed<number>,
time: RefOrComputed<number>,
lightBarrier: RefOrComputed<number[]>,
lightSources: RefOrComputed<LightSource[]>,
) {
@ -24,8 +22,6 @@ export default function useLightMask(
const playerLightSize = B * 1.8
function drawPlayerLight(sizeMul:number) {
const t = time.value
const playerLight = ctx.createRadialGradient(
playerX - tx.value, playerY - ty.value, 0,
playerX - tx.value, playerY - ty.value, playerLightSize * sizeMul
@ -43,11 +39,19 @@ export default function useLightMask(
for (const src of lightSources.value) {
const x = src.x * B
const y = src.y * B
const strength = src.strength + (src.strength * Math.random()) / 10
const light = ctx.createRadialGradient(
x + BHalf, y - BHalf, 0,
x + BHalf, y - BHalf, strength * B,
)
const color = src.color
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")
light.addColorStop(0.0, color)
light.addColorStop(0.4, `${color}A`)
light.addColorStop(1, `${color}0`)
ctx.fillStyle = light
ctx.fillRect(0, 0, W, H)
}
}

View file

@ -11,7 +11,7 @@ const player = reactive<Player>({
inventory: [],
})
const pocket = (newItem: Item) => {
const pocket = (newItem: Item, amount = 1) => {
const existing = player.inventory.find(item => item.name === newItem.name)
if (existing) {
@ -19,9 +19,8 @@ const pocket = (newItem: Item) => {
return existing.amount
}
player.inventory.push({
quality: null,
amount: 1,
...newItem
...newItem,
amount,
})
return 1
}