From 3266ddb21799ed2e0da3d3c620cb44c4647db3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20K=C3=B6hring?= Date: Sat, 22 Mar 2025 22:43:27 +0100 Subject: [PATCH] directional fixtures --- public/Items/torchCeiling.png | Bin 0 -> 2144 bytes public/Items/torchFloor.png | Bin 0 -> 1809 bytes public/Items/{torch.png => torchLeft.png} | Bin public/Items/torchRight.png | Bin 0 -> 1253 bytes src/App.vue | 75 +++++++++++++++++----- src/assets/field.css | 5 +- src/assets/items.css | 2 +- src/level/def.ts | 13 +++- src/types.d.ts | 30 +++++---- src/util/useLightMask.ts | 20 +++--- src/util/usePlayer.ts | 7 +- 11 files changed, 106 insertions(+), 46 deletions(-) create mode 100644 public/Items/torchCeiling.png create mode 100644 public/Items/torchFloor.png rename public/Items/{torch.png => torchLeft.png} (100%) create mode 100644 public/Items/torchRight.png diff --git a/public/Items/torchCeiling.png b/public/Items/torchCeiling.png new file mode 100644 index 0000000000000000000000000000000000000000..0dafe8966fd9195c126fcb27af0699bdfc751af2 GIT binary patch literal 2144 zcmbuAc{CL49>w`L zH%wc3cFRhVO5dq-g3-1$2wuY-_kqxgankx!X_sC=#RAWcoa;re$WrH!W0!k)g3w@B zZYQ7kfqN=wUbV3M+f{J$ioDvqVA`F_N5>Qv5uK#GhgE^4RvH`Uk?8sc$lO5f`e{U1 zl&W{A&NxZo)wk8Y$&Dz={ti~5Ee_#7xy0w@aZi)r)0X@ z$>bQ600p~=5)8J`NSXwHcW1*Zv(WwLlCJuR8}>37d62{NeItPKnPvX1?M4hn5e{%^ zkRL(1?Ij;qOgZt4oOfpIW0a4 zaA5AS7iS!(fZbY(DqdLYwLbvapI#yS*BtAvHp|-v^(4ufp{&7|kQ=S}nd}TjU+YHo zzcLtfaFfhxtF6hBH=@3xayn3H9{lZ?kY<@hxv+;*U6hmhNds2$VCH7k^4vp$?GF!0 z?yR4Dt7Q6W^KbUY#vVws09mtHP1}ly5(e*6IG8IOJsS9xx_27I?cH{Jcb=Xe{dmpK zCrbNz)6S;P7R1V(U;qAPWgH*OPQpoXZb}xa7Tx~5vMJx%7;y*eGvK*C*9@HeL&Fm> zVGXqz$^9?utKMW;h$BAPO?{boSDM6*E?ObUtsMK=y1liv@Kl?1UIns}bLLMNZ>py5 z^?8?W?(?)18~0S7K^=6EhFNpFhb-By8j9sA4(cXFnT_Bygi4bUpW~Xa={?v?pJ86K z7t|o~n~iL(PDF-G@CV7Ok(7?OlL;g~-(LOb)g0F7q=X zi>V6u(S2oIIu&xn<++otpAZ?9tr+c6j}F?X$kLfkB2hsb`hzZ((XO-f(Tg0~k%ILX`LVFW*R(@YCzN~3S`7PB)U+cdjc z)nQRsE-lJIWZU2Go-(gFBi#KDy?C>L>~b|Bl&<0 zr4x>GHlrzlK2qWP+VjpL1(+$^aX2;7f zR2WFjNZfd916~M}Ly5g7lZpA}oWZ@91>2rdUf<=GZ<`Chn4&x&o})w*&OSUf;W>1K zc=@;}FvK@Rgkc9@I8yPw6uBANNE$Yo5%&GDqOKmyV9{sGDo7uNZc(Y}ui z(GSCyEx`27!vzoybJtCDACtQaouAd#H++%Y9b^qB4;UG0PlNGZeC6Hh+ky@c^|Lh82mrdS%b088Zo2?M&=V`|A zMrZ59`XOYo9c3@i@Un%qf~N{RsKYT2X{Tv!G&z$R3r-On8q^|GSwtUV~SZ`@Jd#o4W-2TnhHp zC*1Vq3eZAo>8K%3t7&OEBK7n&we^vwPa%=|NF-e>jORaqz~6kXTn_u+K)U_AW-g%o o?+U?J0)2_WmjZ+Si_to*uch;Mkw+<&o?Hf4ncJc(Oz|=Q0z$;vyZ`_I literal 0 HcmV?d00001 diff --git a/public/Items/torchFloor.png b/public/Items/torchFloor.png new file mode 100644 index 0000000000000000000000000000000000000000..166d00bd6705364b5c6740de3de6ab4c1eb30214 GIT binary patch literal 1809 zcmbuA={uW=0>$4qBDLOT6bYqixvF+P9VNsdb{AvC}b1Eg|W|)E>&Xu|!<=Z@3@MInVDr&-r|^Jlq`RWmIJV0FZZfvhzAl z+W#a4JN6HlprPY{hT7n50H7gHRum+8%wUX{;}vjvK%aUHXG5I4@Bol_{&04&eh&c^%x%=!!b!<&9eebmKnKh)g#+%;RqFmh9q z@J?3hiZ?XZ>R&%2qbq-$UU7g&-;#6YlNi#f9tDitc25f5P3>ItPtN&-mC3w}oam6u zzhlwXH+2r$nU)`@-yCbt#i&ynS=p~;$^gwvi9wMzA z+`!r#eCb~eVJq>t?2Q^xTl(_|aS<&L!x7mYB>V(MfGDt=D7bM@4XvLkIa1>F@p*!- z`)mWA2Bm`2F2C4j8MyopVDlJ!PJ2edxX6t%xKeXvNT8F@Ln8!Qo@Jx8bZ2R{HB6e&ca@PYT+2N_fx;c!?DRH*Umg8L?zT3fGpL7_(^fw54z%) zpGU3>PiDTDr6w(|My*qKIlfkSu9YdQmk>VYWZk*cSj_!x#{LfDa7V)3G%&LH1KDNj z&Cc%VwY-$^33y|0XX2``B{KbVT~vnlM)%(hzA=kt!er~KmtNLqJ;3V=(#)LVWwy_e z3;KIMa>ac!trrbb1-!Yr;kOv!1h^sV@dw>WR4ci=$FvlHR%Z4W+JV;_TRduBmv> zc8gzkOsDh}eOqm7EB^@7c>>h5j(+tr0hIgrK?=Cv25$`c(;%YM+-~HK|83>k)|-Q~ zs8BgwsAsKCL7`2SH4;dnsHxF|$dxLfHadi|oPn)|dz(q;sM}azRHq^@){`E%Kg|?Y zf7eU_2RhC5>d_8dv2hEuCjxb0KJnXQXJ8`u?%M45O9JxH^&;q*yx->MbAowiJ&3%n zgD@6be$|!9m#q#^ow|N6WTSyN;1BWdals|TM=U*g#e=&-n2*tr5v}L%-HSh0!8|k( zq&b34UGAbpUYLdx;G+jYe%+<)U&Wf3#v*}1i1$0BY1}SV-wl}~VI$3SSs2^rJ6F6> z58cY$ivh<-vCAP0st)51c6KjtW>|&7LQl=1Tl{l&QOma1quyy(t^f^ShzevXRfp8_`mrqd}gKhxBz zcsZ4pZPLWptNwI)5krm_{wZ8REBjWpZ2sY3c(w$qLad8qT1|i&2gDqb3nYt=v79J`V59 z=E)j~Je8TR?KTsXpp!)$&b|UJ*MR-kYE-NiZ9mw&?8wy+{Di0*`sMcmfE=^2wPT(c ccYTKndK6<_w*VsH__F|KdpEncS4i~#0tmE2EC2ui literal 0 HcmV?d00001 diff --git a/public/Items/torch.png b/public/Items/torchLeft.png similarity index 100% rename from public/Items/torch.png rename to public/Items/torchLeft.png diff --git a/public/Items/torchRight.png b/public/Items/torchRight.png new file mode 100644 index 0000000000000000000000000000000000000000..945043b84f093199ba505fc83a0419bfc48c9f82 GIT binary patch literal 1253 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&DI|LY`7$t6sR6}X7#MzmnTu$sZZAYL$MSD+0817l%;Plzi}!T*1gv7rA`|9|TSiT_{r?N!^K4=o_{ z9!x^mNaX+jtL{A93{>#{|JpSh_W>Ep=FEg>SygWQ{8RJy56!DeZU4V-SyO8N<73N~ zY6qY!gak5x=FjeI+g$Cixz_Q)t5%@mcaJ;zC(T(=Wb^P<_ltX7y%XlFF16cT?y{xI z0f_duxE*ZsIN0HJsNHLGmBY45$F0>)huS=W>@yuM2iv^1RypjfbA}iQ_QJgXKYRcG zUjel1|L^|)|7ZOFGwJWg*8l&f{Qtk?|Nq7RKeqq>zu^CuPN0{sJ(|OC{{PBy_f@5K z&WHZ5Ds#@=_AoR67C#VdB&c8!GIdf9RgsJ9AT|!}hfXx@vc$+m1TtgKeJMYMr*$IPGuo*w^B|z0Udn|NkWk(YnAu_bv(Y3ufSD&xHQ zf6sq@etdlX{Cs=)_3Qc1*T-*W%$x3Z7icVFlDE4{mBrQ8EFg!y#M9T6{Ux&~rv#(A z>ALGc>2IDcjv*Dd-d;}+7j~35`jL@QGw4WLN0SEEG#9~%39LCAW^j6J=H2GF=A_cs z+to(a(I0HjKRx=lzVWy9>^GBzmr5k}7#;%>8=F@w6SJycvd)g~;=z|J8{X_^*FO4ark>@2n=A|N{|HZ9`g_g;;TN8azj9eB%Hs;+V_%%*xU>4h zzvh>dMPlBZ=kRje@ay6X?@0_x_0r`I{aJqgn1r!{_KrzR`$C?d4X+LjGkEd*`o5ii zOS9ijSo2`k?f?G{zx}@@EUq9nyY_Kf>A&mw+w+Sn0}rgdUG?K!hvBpSUSY92vU`_b zIdNl`?`csN)^#;{%o`q8G}=6{KUmvxU=gcx-p)6yhn_14h8*Huu|TLT@efbRdB-cw z20pBZ*fn1*6#jEmc8SM^ovqd$?G7JKaBeGn?qIu)tE_=NbN>U*m<9`vi3=XF3TH4r zQE+AYzj?y!7S2-#iVHYcf@jWeUCwb}^YfXDc`QBrHoT5MCa&Q4hh9`WYT5I$t&lWv(6n$k$Owrf za7-B -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 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(() => { return level.sunLight(floorX.value) }) -const lightSources = computed(() => []) - const mapUpdateCount = ref(0) const mapGrid = computed(() => { 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(player.inventory[0]) -const surroundings = computed>(() => { - 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>(() => { up: rowYp[x], down: rowYn[x], } +} + +const surroundings = computed>(() => { + 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 { diff --git a/src/assets/field.css b/src/assets/field.css index 3e08e70..aa4eade 100644 --- a/src/assets/field.css +++ b/src/assets/field.css @@ -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; diff --git a/src/assets/items.css b/src/assets/items.css index de56871..06db695 100644 --- a/src/assets/items.css +++ b/src/assets/items.css @@ -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"); } diff --git a/src/level/def.ts b/src/level/def.ts index cff65d5..703a4b0 100644 --- a/src/level/def.ts +++ b/src/level/def.ts @@ -26,11 +26,18 @@ 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', 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 = { diff --git a/src/types.d.ts b/src/types.d.ts index 065c38d..44344c3 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -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}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 } diff --git a/src/util/useLightMask.ts b/src/util/useLightMask.ts index d5e21d0..cf06ce4 100644 --- a/src/util/useLightMask.ts +++ b/src/util/useLightMask.ts @@ -6,11 +6,9 @@ type RefOrComputed = Ref | ComputedRef export default function useLightMask( ctx: CanvasRenderingContext2D, - x: RefOrComputed, y: RefOrComputed, tx: RefOrComputed, ty: RefOrComputed, - time: RefOrComputed, lightBarrier: RefOrComputed, lightSources: RefOrComputed, ) { @@ -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) } } diff --git a/src/util/usePlayer.ts b/src/util/usePlayer.ts index 1b63c1a..8d7864e 100644 --- a/src/util/usePlayer.ts +++ b/src/util/usePlayer.ts @@ -11,7 +11,7 @@ const player = reactive({ 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 }