You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

221 lines
6.3 KiB
Vue

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import Help from './screens/help.vue'
import Inventory from './screens/inventory.vue'
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT, type Block, blockTypes } from './level/def'
import createLevel from './level'
import useTime from './util/useTime'
import useInput from './util/useInput'
import usePlayer from './util/usePlayer'
import useLightMap from './util/useLightMap'
const { updateTime, time, timeOfDay, clock } = useTime()
const { player, direction, dx, dy } = usePlayer()
const { inputX, inputY, running, paused, help, inventory } = useInput()
const level = createLevel(STAGE_WIDTH + 2, STAGE_HEIGHT + 2)
const lightMapEl = ref<HTMLCanvasElement | undefined>(undefined)
let updateLightMap: ReturnType<typeof useLightMap>
player.inventory.push(
{ name: 'Shovel', type: 'tool', icon: 'shovel', quality: 'bronze', amount: 1 },
{ name: 'Sword', type: 'weapon', icon: 'sword', quality: 'bronze', amount: 2 },
{ name: 'Pick Axe', type: 'tool', icon: 'pick', quality: 'bronze', amount: 1 },
)
let animationFrame = 0
let lastTick = 0
const x = ref(0)
const y = ref(0)
const floorX = computed(() => Math.floor(x.value))
const floorY = computed(() => Math.floor(y.value))
const tx = computed(() => (x.value - floorX.value) * -BLOCK_SIZE)
const ty = computed(() => (y.value - floorY.value) * -BLOCK_SIZE)
const rows = computed(() => level.grid(floorX.value, floorY.value))
const lightBarrier = computed(() => level.sunLight(floorX.value))
const arriving = ref(true)
const walking = ref(false)
const inventorySelection = ref<InventoryItem>(player.inventory[0])
type Surroundings = {
at: Block,
left: Block,
right: Block,
up: Block,
down: Block,
}
const surroundings = computed<Surroundings>(() => {
const px = player.x
const py = player.y
const row = rows.value
return {
at: row[py][px],
left: row[py][px - 1],
right: row[py][px + 1],
up: row[py - 1][px],
down: row[py + 1][px],
}
})
const blocked = computed(() => {
const { left, right, up, down } = surroundings.value
return {
left: !left.walkable,
right: !right.walkable,
up: !up.walkable,
down: !down.walkable,
}
})
// TODO
const damagedBlocks = ref([])
function dig(blockX: number, blockY: number, oldBlockType: BlockType) {
// § 4 ArbZG
if (paused.value) return
// TODO: temporary filter
if (oldBlockType === 'air' || oldBlockType === 'cave') return
// when we finally dig that block
level.change({ type: 'exchange', x: floorX.value + blockX, y: floorY.value + blockY, newType: 'air' })
// This feels like cheating, but it makes Vue recalculate floorX
// which then recalculates the blocks, so that the changes are
// applied. Otherwise, they wouldn't be visible before moving
x.value = x.value + 0.01
x.value = x.value - 0.01
}
let lastTimeUpdate = 0
const move = (thisTick: number): void => {
animationFrame = requestAnimationFrame(move)
// do nothing when paused
if (paused.value) {
lastTick = thisTick // reset tick, to avoid huge tickDelta
return
}
const tickDelta = thisTick - lastTick
lastTimeUpdate += tickDelta
// update in-game time every 60ms by 0.1
// then a day needs 10000 updates, and it takes about 10 minutes
if (lastTimeUpdate > 60) {
updateTime()
lastTimeUpdate = 0
}
6 years ago
player.vx = inputX.value
player.vy = inputY.value
6 years ago
if (inputX.value) player.lastDir = inputX.value
let dx_ = dx.value
let dy_ = dy.value
if (running.value) dx_ *= 2
if (dx_ > 0 && blocked.value.right) dx_ = 0
else if (dx_ < 0 && blocked.value.left) dx_ = 0
if (dy_ > 0 && blocked.value.down) dy_ = 0
else if (dy_ < 0 && blocked.value.up) dy_ = 0
const optimal = 16 // 16ms per tick => 60 FPS
const movementMultiplier = (tickDelta / optimal) * 2
const fallMultiplier = movementMultiplier * 2 // TODO: accelerated fall?
if (arriving.value && dy_ === 0) {
arriving.value = false
}
walking.value = !!dx_
if (dy_ <= 0) x.value += dx_ * movementMultiplier
if (dy_ < 0 || arriving.value) {
y.value += dy_ * movementMultiplier
} else {
y.value += dy_ * fallMultiplier
}
updateLightMap()
lastTick = thisTick
6 years ago
}
function calcBrightness(level: number, row: number) {
const barrier = lightBarrier.value[row]
const barrierLeft = lightBarrier.value[row - 1]
const barrierRight = lightBarrier.value[row + 1]
let delta = barrier - level - (floorY.value - 3)
const deltaL = Math.min(3, barrierLeft - level - (floorY.value - 3))
const deltaR = Math.min(3, barrierRight - level - (floorY.value - 3))
if (delta > 3) delta = 3
else if (delta < 0) delta = 0
if (deltaR > delta || deltaL > delta) delta = Math.max(deltaL, deltaR) - 1
return `sun-${delta}`
}
onMounted(() => {
const canvas = lightMapEl.value!
canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT
canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
const ctx = canvas.getContext('2d')!
updateLightMap = useLightMap(ctx, floorX, floorY, tx, ty, time, lightBarrier)
lastTick = performance.now()
move(lastTick)
})
6 years ago
</script>
<template>
<div id="field" :class="timeOfDay">
<div id="blocks" :style="{transform: `translate(${tx}px, ${ty}px)`}">
<template v-for="(row, y) in rows">
<div v-for="(block, x) in row"
:class="['block', block.type]"
@click="dig(x, y, block.type)"
/>
</template>
</div>
<div id="player" :class="[direction, { walking }]" @click="inventory = !inventory">
<div class="head"></div>
<div class="body"></div>
<div class="legs">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="arms">
<div v-if="inventorySelection"
:class="['item', `${inventorySelection.type}-${inventorySelection.icon}-${inventorySelection.quality}`]"
></div>
</div>
</div>
<canvas id="light-mask" ref="lightMapEl" :style="{transform: `translate(${tx}px, ${ty}px)`}" />
<div id="beam" v-if="arriving"></div>
<div id="level-indicator">
x:{{ floorX }}, y:{{ floorY }}
<template v-if="paused">(PAUSED)</template>
<template v-else>({{ clock }})</template>
</div>
<Inventory :shown="inventory"
:items="player.inventory"
@selection="inventorySelection = $event"
/>
<Help v-show="help" />
</div>
</template>