Compare commits
No commits in common. "3266ddb21799ed2e0da3d3c620cb44c4647db3da" and "8f281fbdf9c17af948280397e19c10e60b29d2e8" have entirely different histories.
3266ddb217
...
8f281fbdf9
15 changed files with 123 additions and 186 deletions
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
105
src/App.vue
105
src/App.vue
|
@ -1,11 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Block, BlockType, Direction, Item, InventoryItem, LightSource } from './types.d'
|
import type { Block, Direction, Item, InventoryItem } from './types.d'
|
||||||
import { ref, computed, watch, onMounted, useTemplateRef } from 'vue'
|
import { ref, computed, watch, onMounted, useTemplateRef } from 'vue'
|
||||||
import Help from './screens/help.vue'
|
import Help from './screens/help.vue'
|
||||||
import Inventory from './screens/inventory.vue'
|
import Inventory from './screens/inventory.vue'
|
||||||
import Background from './Background.vue'
|
import Background from './Background.vue'
|
||||||
|
|
||||||
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT, softTerrain, hardTerrain } from './level/def'
|
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def'
|
||||||
import { getItem, getItemClass } from './level/items'
|
import { getItem, getItemClass } from './level/items'
|
||||||
import createLevel from './level'
|
import createLevel from './level'
|
||||||
|
|
||||||
|
@ -25,12 +25,11 @@ let updateLightMap = (() => {}) as ReturnType<typeof useLightMask>
|
||||||
pocket(getItem('tool_shovel_wood'))
|
pocket(getItem('tool_shovel_wood'))
|
||||||
pocket(getItem('tool_sword_wood'))
|
pocket(getItem('tool_sword_wood'))
|
||||||
pocket(getItem('tool_pickaxe_wood'))
|
pocket(getItem('tool_pickaxe_wood'))
|
||||||
pocket(getItem('fixture_torch'), 5)
|
|
||||||
|
|
||||||
let animationFrame = 0
|
let animationFrame = 0
|
||||||
let lastTick = 0
|
let lastTick = 0
|
||||||
|
|
||||||
const debug = ref(true)
|
const debug = ref(false)
|
||||||
const x = ref(0)
|
const x = ref(0)
|
||||||
const y = ref(0)
|
const y = ref(0)
|
||||||
const floorX = computed(() => Math.floor(x.value))
|
const floorX = computed(() => Math.floor(x.value))
|
||||||
|
@ -52,35 +51,15 @@ const mapGrid = computed<Block[][]>(() => {
|
||||||
const _update = mapUpdateCount.value // reactivity trigger
|
const _update = mapUpdateCount.value // reactivity trigger
|
||||||
return level.grid(floorX.value, floorY.value, true)
|
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 arriving = ref(true)
|
||||||
const walking = ref(false)
|
const walking = ref(false)
|
||||||
const inventorySelection = ref<InventoryItem>(player.inventory[0])
|
const inventorySelection = ref<InventoryItem>(player.inventory[0])
|
||||||
|
|
||||||
const getSurroundings = (x: number, y: number) => {
|
const surroundings = computed<Record<Direction, Block>>(() => {
|
||||||
|
const _update = mapUpdateCount.value // reactivity trigger
|
||||||
|
const x = px.value
|
||||||
|
const y = py.value
|
||||||
const rows = mapGrid.value
|
const rows = mapGrid.value
|
||||||
|
|
||||||
const rowY = rows[y]
|
const rowY = rows[y]
|
||||||
|
@ -94,11 +73,6 @@ const getSurroundings = (x: number, y: number) => {
|
||||||
up: rowYp[x],
|
up: rowYp[x],
|
||||||
down: rowYn[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 blocked = computed(() => {
|
||||||
const { left, right, up, down } = surroundings.value
|
const { left, right, up, down } = surroundings.value
|
||||||
|
@ -136,26 +110,15 @@ function dig(blockX: number, blockY: number, block: Block) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function build(blockX: number, blockY: number, block: InventoryItem) {
|
function build(blockX: number, blockY: number, block: InventoryItem) {
|
||||||
let blockToBuild = block.builds
|
const blockToBuild = block.builds
|
||||||
if (!blockToBuild) return // the block doesn't do anything?!
|
// the block doesn't do anything
|
||||||
|
if (!blockToBuild) return
|
||||||
// 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({
|
level.change({
|
||||||
change: 'exchange',
|
change: 'exchange',
|
||||||
x: floorX.value + blockX,
|
x: floorX.value + blockX,
|
||||||
y: floorY.value + blockY,
|
y: floorY.value + blockY,
|
||||||
newType: blockToBuild as BlockType
|
newType: blockToBuild
|
||||||
})
|
})
|
||||||
mapUpdateCount.value = mapUpdateCount.value + 1
|
mapUpdateCount.value = mapUpdateCount.value + 1
|
||||||
|
|
||||||
|
@ -164,37 +127,20 @@ function build(blockX: number, blockY: number, block: InventoryItem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function interactWith(blockX: number, blockY: number, block: Block) {
|
function interactWith(blockX: number, blockY: number, block: Block) {
|
||||||
if (debug) {
|
if (debug) console.debug('interact with', blockX, blockY, block.type)
|
||||||
console.debug(
|
|
||||||
`interact with ${block.type} at ${blockX},${blockY},`,
|
|
||||||
`with a ${inventorySelection.value.id} in hand`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// § 4 ArbZG
|
// § 4 ArbZG
|
||||||
if (paused.value) return
|
if (paused.value) return
|
||||||
|
|
||||||
// no spooky interaction at a distance
|
const blockInHand = inventorySelection.value.type === 'block'
|
||||||
const distanceX = ~~(px.value - blockX)
|
const toolInHand = inventorySelection.value.type === 'tool'
|
||||||
const distanceY = ~~(py.value - blockY)
|
const emptyBlock = block.type === 'air' || block.type === 'cave'
|
||||||
if (distanceX > 1 || distanceY > 1) return
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
// put the selected block
|
// put the selected block
|
||||||
if (canBuild && hasSpace) {
|
if (blockInHand && emptyBlock) {
|
||||||
build(blockX, blockY, blockInHand)
|
build(blockX, blockY, inventorySelection.value)
|
||||||
// dig a block with shovel or pick axe
|
// dig a block with shovel or pick axe
|
||||||
} else if (hasTool && !hasSpace) {
|
} else if (toolInHand && !emptyBlock) {
|
||||||
if (shovelInHand && canUseShovel) dig(blockX, blockY, block)
|
dig(blockX, blockY, block)
|
||||||
else if (pickaxeInHand && canUsePickaxe) dig(blockX, blockY, block)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,10 +214,7 @@ onMounted(() => {
|
||||||
canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT
|
canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT
|
||||||
canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
|
canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
|
||||||
const ctx = canvas.getContext('2d')!
|
const ctx = canvas.getContext('2d')!
|
||||||
updateLightMap = useLightMask(
|
updateLightMap = useLightMask(ctx, floorX, floorY, tx, ty, time, lightBarrier)
|
||||||
ctx, floorY, tx, ty,
|
|
||||||
lightBarrier, lightSources,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('lightmap deactivated')
|
console.warn('lightmap deactivated')
|
||||||
}
|
}
|
||||||
|
@ -322,13 +265,7 @@ onMounted(() => {
|
||||||
<div id="beam" v-if="arriving"></div>
|
<div id="beam" v-if="arriving"></div>
|
||||||
<div id="level-indicator">
|
<div id="level-indicator">
|
||||||
x:{{ floorX }}, y:{{ floorY }}
|
x:{{ floorX }}, y:{{ floorY }}
|
||||||
<template v-if="paused">
|
<template v-if="paused">(PAUSED)</template>
|
||||||
<template v-if="debug">
|
|
||||||
({{ clock }})<br/>
|
|
||||||
time: <input type="number" max="0" min="1000" v-model="time" />
|
|
||||||
</template>
|
|
||||||
<template v-else>(PAUSED)</template>
|
|
||||||
</template>
|
|
||||||
<template v-else>({{ clock }})</template>
|
<template v-else>({{ clock }})</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,18 @@ onMounted(() => {
|
||||||
|
|
||||||
const drawBackground = useBackground(
|
const drawBackground = useBackground(
|
||||||
canvasEl,
|
canvasEl,
|
||||||
~~(STAGE_WIDTH * BLOCK_SIZE / 1.0),
|
~~(STAGE_WIDTH * BLOCK_SIZE / 2.0),
|
||||||
~~(STAGE_HEIGHT * BLOCK_SIZE / 1.0),
|
~~(STAGE_HEIGHT * BLOCK_SIZE / 2.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(props, () => drawBackground(props.x, sunY.value), { immediate: true })
|
watch(props, () => {
|
||||||
|
console.log('drawing background', sunY.value)
|
||||||
|
drawBackground(props.x, sunY.value)
|
||||||
|
}, { immediate: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<canvas ref="canvas" id="background" />
|
<canvas ref="canvas" id="background"></canvas>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -98,10 +98,6 @@
|
||||||
.block.brickWall {
|
.block.brickWall {
|
||||||
background-image: url(/Tiles/brick_grey.png);
|
background-image: url(/Tiles/brick_grey.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 {
|
#field {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -116,17 +112,17 @@
|
||||||
|
|
||||||
.morning0 .block,
|
.morning0 .block,
|
||||||
.morning0 #player {
|
.morning0 #player {
|
||||||
filter: saturate(50%) brightness(0.6);
|
filter: saturate(50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.morning1 .block,
|
.morning1 .block,
|
||||||
.morning1 #player {
|
.morning1 #player {
|
||||||
filter: saturate(100%) brightness(0.8);
|
filter: saturate(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.morning2 .block,
|
.morning2 .block,
|
||||||
.morning2 #player {
|
.morning2 #player {
|
||||||
filter: saturate(120%) brightness(0.9);
|
filter: saturate(120%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.evening0 .block,
|
.evening0 .block,
|
||||||
|
@ -136,17 +132,17 @@
|
||||||
|
|
||||||
.evening1 .block,
|
.evening1 .block,
|
||||||
.evening1 #player {
|
.evening1 #player {
|
||||||
filter: saturate(70%) brightness(0.8);
|
filter: saturate(70%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.evening2 .block,
|
.evening2 .block,
|
||||||
.evening2 #player {
|
.evening2 #player {
|
||||||
filter: saturate(50%) brightness(0.6);
|
filter: saturate(50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.night .block,
|
.night .block,
|
||||||
.night #player {
|
.night #player {
|
||||||
filter: saturate(30%) brightness(0.4);
|
filter: saturate(30%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#blocks {
|
#blocks {
|
||||||
|
|
|
@ -14,5 +14,3 @@
|
||||||
.item.block-dirt { background-image: url("/Tiles/dirt.png"); }
|
.item.block-dirt { background-image: url("/Tiles/dirt.png"); }
|
||||||
.item.block-stone { background-image: url("/Tiles/stone.png"); }
|
.item.block-stone { background-image: url("/Tiles/stone.png"); }
|
||||||
.item.block-gravel { background-image: url("/Tiles/gravel_stone.png"); }
|
.item.block-gravel { background-image: url("/Tiles/gravel_stone.png"); }
|
||||||
|
|
||||||
.item.fixture-torch { background-image: url("/Items/torchFloor.png"); }
|
|
||||||
|
|
|
@ -27,19 +27,8 @@ export const blockTypes: Record<BlockType, Block> = {
|
||||||
bedrock: { type: 'bedrock', hp: 25, walkable: false, drops: 'block_stone' },
|
bedrock: { type: 'bedrock', hp: 25, walkable: false, drops: 'block_stone' },
|
||||||
// Built Blocks
|
// Built Blocks
|
||||||
brickWall: { type: 'brickWall', hp: 25, walkable: false, drops: 'block_gravel' },
|
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',
|
|
||||||
'torchLeft', 'torchRight', 'torchCeiling', 'torchFloor',
|
|
||||||
]
|
|
||||||
export const hardTerrain: BlockType[] = ['stone', 'stoneGravel', 'bedrock', 'brickWall']
|
|
||||||
|
|
||||||
export const level = {
|
export const level = {
|
||||||
treeTop: 9,
|
treeTop: 9,
|
||||||
ground: 14,
|
ground: 14,
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { createNoise2D, type NoiseFunction2D } from 'simplex-noise'
|
||||||
import createBlockGenerator from './blockGen'
|
import createBlockGenerator from './blockGen'
|
||||||
import createBlockExtender from './blockExt'
|
import createBlockExtender from './blockExt'
|
||||||
|
|
||||||
import { level as L, blockTypes, blockTypes as T } from './def'
|
import { blockTypes, blockTypes as T } from './def'
|
||||||
import type { Block, Change } from '../types.d'
|
import type { Block, Change } from '../types.d'
|
||||||
|
|
||||||
const MAX_LIGHT = L.underground // maximum level where light shines
|
const MAX_LIGHT = 100 // maximum level where light shines
|
||||||
|
|
||||||
export default function createLevel(width: number, height: number, seed = 'extremely random seed') {
|
export default function createLevel(width: number, height: number, seed = 'extremely random seed') {
|
||||||
const prng = alea(seed)
|
const prng = alea(seed)
|
||||||
|
|
|
@ -26,8 +26,6 @@ export const items = {
|
||||||
ore_ruby: { id: 'ore_ruby', name: 'ruby', type: 'ore', icon: 'ore_ruby' } as Item,
|
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_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,
|
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
|
} as const
|
||||||
|
|
||||||
export type ItemId = keyof typeof items
|
export type ItemId = keyof typeof items
|
||||||
|
|
36
src/types.d.ts
vendored
36
src/types.d.ts
vendored
|
@ -1,7 +1,6 @@
|
||||||
import type { ItemId } from './level/items'
|
import type { ItemId } from './level/items'
|
||||||
export type { ItemId } from './level/items'
|
|
||||||
export type ItemQuality = 'wood' | 'iron' | 'diamond'
|
export type ItemQuality = 'wood' | 'iron' | 'diamond'
|
||||||
export type ItemType = 'tool' | 'block' | 'ore' | 'fixture'
|
export type ItemType = 'tool' | 'block' | 'ore'
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
id: string
|
id: string
|
||||||
|
@ -9,9 +8,7 @@ export interface Item {
|
||||||
type: ItemType
|
type: ItemType
|
||||||
icon: string
|
icon: string
|
||||||
quality?: ItemQuality
|
quality?: ItemQuality
|
||||||
// this should be ItemId | BlockType, but has to be string to avoid
|
builds?: BlockType
|
||||||
// a circular type reference
|
|
||||||
builds?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolItem extends Item {
|
export interface ToolItem extends Item {
|
||||||
|
@ -24,8 +21,6 @@ export interface BlockItem extends Item {
|
||||||
builds: BlockType
|
builds: BlockType
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fixture<T extends string> = `${T}Left` | `${T}Right` | `${T}Ceiling` | `${T}Floor`;
|
|
||||||
|
|
||||||
export type BlockType =
|
export type BlockType =
|
||||||
| 'air' | 'grass'
|
| 'air' | 'grass'
|
||||||
| 'treeCrown' | 'treeLeaves' | 'treeTrunk' | 'treeRoot'
|
| 'treeCrown' | 'treeLeaves' | 'treeTrunk' | 'treeRoot'
|
||||||
|
@ -33,18 +28,15 @@ export type BlockType =
|
||||||
| 'stone' | 'stoneGravel'
|
| 'stone' | 'stoneGravel'
|
||||||
| 'bedrock' | 'cave'
|
| 'bedrock' | 'cave'
|
||||||
| 'brickWall'
|
| 'brickWall'
|
||||||
| Fixture<'torch'>
|
|
||||||
|
|
||||||
export type Block = {
|
export type Block = {
|
||||||
type: BlockType // what is it?
|
type: BlockType, // what is it?
|
||||||
hp: number // how long do I need to hit it?
|
hp: number, // how long do I need to hit it?
|
||||||
walkable: boolean // can I walk through it?
|
walkable: boolean, // can I walk through it?
|
||||||
climbable?: boolean // can I climb it?
|
climbable?: boolean, // can I climb it?
|
||||||
transparent?: boolean // can I see through it?
|
transparent?: boolean, // can I see through it?
|
||||||
fixture?: boolean // is it built by the player?
|
illuminated?: boolean, // is it glowing?
|
||||||
illumination?: number // How many blocks wide is it glowing?
|
drops?: ItemId, // what do I get, when loot it?
|
||||||
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
|
// describes a changed block, eg digged or placed by the player
|
||||||
|
@ -60,10 +52,11 @@ type ChangedBlock = {
|
||||||
y: number
|
y: number
|
||||||
newType: BlockType
|
newType: BlockType
|
||||||
}
|
}
|
||||||
export type Change = DamagedBlock | ChangedBlock
|
type Change = DamagedBlock | ChangedBlock
|
||||||
|
|
||||||
export interface InventoryItem extends Item {
|
export interface InventoryItem extends Item {
|
||||||
amount: number
|
amount: number
|
||||||
|
quality: ItemQuality | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Moveable {
|
export interface Moveable {
|
||||||
|
@ -85,10 +78,3 @@ export interface Player extends Moveable {
|
||||||
|
|
||||||
export type Direction = 'at' | 'left' | 'right' | 'up' | 'down'
|
export type Direction = 'at' | 'left' | 'right' | 'up' | 'down'
|
||||||
|
|
||||||
|
|
||||||
export interface LightSource {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
strength: number
|
|
||||||
color: string
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,87 +1,116 @@
|
||||||
import type { Ref, ComputedRef } from 'vue'
|
import type { Ref, ComputedRef } from 'vue'
|
||||||
import type { LightSource } from '../types.d'
|
|
||||||
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from '../level/def'
|
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from '../level/def'
|
||||||
|
|
||||||
type RefOrComputed<T> = Ref<T> | ComputedRef<T>
|
type RefOrComputed<T> = Ref<T> | ComputedRef<T>
|
||||||
|
|
||||||
export default function useLightMask(
|
export default function useLightMask(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
x: RefOrComputed<number>,
|
||||||
y: RefOrComputed<number>,
|
y: RefOrComputed<number>,
|
||||||
tx: RefOrComputed<number>,
|
tx: RefOrComputed<number>,
|
||||||
ty: RefOrComputed<number>,
|
ty: RefOrComputed<number>,
|
||||||
|
time: RefOrComputed<number>,
|
||||||
lightBarrier: RefOrComputed<number[]>,
|
lightBarrier: RefOrComputed<number[]>,
|
||||||
lightSources: RefOrComputed<LightSource[]>,
|
|
||||||
) {
|
) {
|
||||||
const W = ((STAGE_WIDTH + 2) * BLOCK_SIZE)
|
const W = ((STAGE_WIDTH + 2) * BLOCK_SIZE)
|
||||||
const H = ((STAGE_HEIGHT + 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 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 playerX = (W - B) / 2 + B / 4
|
||||||
const playerY = H / 2 - BHalf
|
const playerY = H / 2 - B / 2
|
||||||
const playerLightSize = B * 1.8
|
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) {
|
function drawPlayerLight(sizeMul:number) {
|
||||||
|
const t = time.value
|
||||||
|
|
||||||
const playerLight = ctx.createRadialGradient(
|
const playerLight = ctx.createRadialGradient(
|
||||||
playerX - tx.value, playerY - ty.value, 0,
|
playerX - tx.value, playerY - ty.value, 0,
|
||||||
playerX - tx.value, playerY - ty.value, playerLightSize * sizeMul
|
playerX - tx.value, playerY - ty.value, playerLightSize * sizeMul
|
||||||
)
|
)
|
||||||
|
|
||||||
playerLight.addColorStop(0.7, "#FFFA");
|
// Add color stops for a light around the player
|
||||||
playerLight.addColorStop(1, "#FFF0");
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
// Set the fill style and draw a rectangle
|
// Set the fill style and draw a rectangle
|
||||||
ctx.fillStyle = playerLight;
|
ctx.fillStyle = playerLight;
|
||||||
ctx.fillRect(0, 0, W, H)
|
ctx.fillRect(0, 0, W, H)
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawLightSources() {
|
function drawLights() {
|
||||||
for (const src of lightSources.value) {
|
// used for everything above ground
|
||||||
const x = src.x * B
|
const ambientLight = getAmbientLightColor()
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawShadows() {
|
|
||||||
const barrier = lightBarrier.value
|
const barrier = lightBarrier.value
|
||||||
|
|
||||||
for (let col = 0; col < barrier.length; col++) {
|
ctx.fillStyle = ambientLight
|
||||||
|
for (let col = 0; col < W / B; col++) {
|
||||||
const level = (barrier[col] - y.value) * B
|
const level = (barrier[col] - y.value) * B
|
||||||
const x = B*col
|
const sw = B
|
||||||
|
const sh = level
|
||||||
|
const sx = col * sw
|
||||||
|
const sy = 0
|
||||||
|
|
||||||
// gradient for the shadow that is cast down from the surface
|
ctx.fillRect(sx, sy, sw, sh)
|
||||||
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() {
|
return function update() {
|
||||||
// first, throw the world in complete darkness
|
// first, throw the world in complete darkness
|
||||||
ctx.fillStyle = '#FFFFFF'
|
ctx.fillStyle = '#000000'
|
||||||
ctx.fillRect(0, 0, W, H)
|
ctx.fillRect(0, 0, W, H)
|
||||||
|
|
||||||
// second, hide what is beneath
|
// second, find and bring light into the world
|
||||||
drawShadows()
|
drawLights()
|
||||||
|
|
||||||
// third, fight the darkness
|
|
||||||
drawLightSources()
|
|
||||||
|
|
||||||
// finally, draw the players light
|
// finally, draw the players light
|
||||||
// with a size multiplicator which might be later used to
|
// with a size multiplicator which might be later used to
|
||||||
|
|
|
@ -11,7 +11,7 @@ const player = reactive<Player>({
|
||||||
inventory: [],
|
inventory: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const pocket = (newItem: Item, amount = 1) => {
|
const pocket = (newItem: Item) => {
|
||||||
const existing = player.inventory.find(item => item.name === newItem.name)
|
const existing = player.inventory.find(item => item.name === newItem.name)
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
@ -19,8 +19,9 @@ const pocket = (newItem: Item, amount = 1) => {
|
||||||
return existing.amount
|
return existing.amount
|
||||||
}
|
}
|
||||||
player.inventory.push({
|
player.inventory.push({
|
||||||
...newItem,
|
quality: null,
|
||||||
amount,
|
amount: 1,
|
||||||
|
...newItem
|
||||||
})
|
})
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ export function calcSunAngle(tick: number): number {
|
||||||
|
|
||||||
export default function useTime() {
|
export default function useTime() {
|
||||||
// the day is split in 1000 parts, so we start in the morning
|
// the day is split in 1000 parts, so we start in the morning
|
||||||
const time = ref(240)
|
const time = ref(250)
|
||||||
|
|
||||||
function updateTime() {
|
function updateTime() {
|
||||||
time.value = (time.value + 0.1) % 1000
|
time.value = (time.value + 0.1) % 1000
|
||||||
|
|
Loading…
Add table
Reference in a new issue