|
| 1 | +/* global Player, tileset, Vigilance */ |
| 2 | + |
| 3 | +const ctxObjects = document.getElementById('objects').getContext('2d') |
| 4 | +const ctxSight = document.getElementById('sight').getContext('2d') |
| 5 | +const [drawCollitions, noShadows] = [false, true] |
| 6 | + |
| 7 | +// eslint-disable-next-line no-unused-vars |
| 8 | +const game = { |
| 9 | + objects: {}, |
| 10 | + insideMap: [], |
| 11 | + collitionMap: [], |
| 12 | + timer: null, |
| 13 | + ticker () { |
| 14 | + if (this.timer) clearInterval(this.timer) |
| 15 | + Vigilance.ctx.clearRect(0, 0, 512, 512) |
| 16 | + Vigilance.instances |
| 17 | + .filter(vg => vg.props.start) |
| 18 | + .forEach(vg => { |
| 19 | + vg.frame() |
| 20 | + this.generateSight(vg) |
| 21 | + }) |
| 22 | + this.timer = setInterval(() => { |
| 23 | + this.ticker() |
| 24 | + }, 800) |
| 25 | + }, |
| 26 | + setPrototypes (objects) { |
| 27 | + this.prototypes = objects.filter(({ type }) => type === 'prototype') |
| 28 | + }, |
| 29 | + setObjects (objects) { |
| 30 | + this.objects = objects.filter(({ type }) => type !== 'prototype') |
| 31 | + this.objects |
| 32 | + .map(object => { |
| 33 | + if (!object.properties) object.properties = {} |
| 34 | + this.prototypes.find(obj => obj.name === object.type).properties |
| 35 | + .forEach(({ name, value }) => { |
| 36 | + object.properties[name] = JSON.parse(value) |
| 37 | + }) |
| 38 | + return object |
| 39 | + }) |
| 40 | + .forEach(object => { |
| 41 | + this.drawObject(object) |
| 42 | + }) |
| 43 | + }, |
| 44 | + drawObject (object) { |
| 45 | + if (object.type === 'door') this.toggleVisibility(object) |
| 46 | + const proto = object.properties |
| 47 | + object.state = !!object.state |
| 48 | + object.col = { |
| 49 | + x: object.x + proto.col.x, |
| 50 | + y: object.y + proto.col.y, |
| 51 | + w: proto.col.w, |
| 52 | + h: proto.col.h |
| 53 | + } |
| 54 | + object.collitionIndex = this.computePosition(object.col.x, object.col.y).collitionIndex - 1 |
| 55 | + ctxObjects.clearRect(object.x, object.y, object.width, object.height) |
| 56 | + ctxObjects.drawImage( |
| 57 | + tileset, |
| 58 | + proto[object.state].x, // source x |
| 59 | + proto[object.state].y, // source y |
| 60 | + object.width, // source width |
| 61 | + object.height, // source height |
| 62 | + object.x, // desination x |
| 63 | + object.y, // destination y |
| 64 | + object.width, // destination width |
| 65 | + object.height // destination height |
| 66 | + ) |
| 67 | + if (drawCollitions) { |
| 68 | + // draw collition on object |
| 69 | + ctxObjects.rect( |
| 70 | + object.col.x, |
| 71 | + object.col.y, |
| 72 | + object.col.w, |
| 73 | + object.col.h |
| 74 | + ) |
| 75 | + ctxObjects.stroke() |
| 76 | + } |
| 77 | + }, |
| 78 | + computePosition (x, y) { |
| 79 | + const computed = { x: Math.floor(x / 16) + 2, y: Math.ceil(y / 16) + 2 } |
| 80 | + computed.collitionIndex = (computed.x - 1) + (computed.y - 1) * 32 |
| 81 | + return computed |
| 82 | + }, |
| 83 | + activateObject (x, y) { |
| 84 | + const ply = Player.getCurrent() |
| 85 | + const obj = this.objects.find(itm => { |
| 86 | + return itm.col.x <= x && |
| 87 | + itm.col.x + itm.col.w >= x && |
| 88 | + itm.col.y <= y && |
| 89 | + itm.col.y + itm.col.h >= y |
| 90 | + }) |
| 91 | + |
| 92 | + let direction = { x: 32, y: 0 } |
| 93 | + if (x < (ply.position.x + 8)) direction.x = -32 |
| 94 | + if (y < (ply.position.y + 32)) direction = { x: 0, y: -48 } |
| 95 | + if (y > (ply.position.y + 32)) direction = { x: 0, y: 48 } |
| 96 | + |
| 97 | + if (!obj) return false |
| 98 | + ply.interactingWith = obj |
| 99 | + if (obj.state === true) { |
| 100 | + ply.move({ x: ply.position.x + direction.x, y: ply.position.y + direction.y }, true) |
| 101 | + if (obj.type === 'door') obj.state = false |
| 102 | + this.drawObject(obj) |
| 103 | + return false |
| 104 | + } |
| 105 | + |
| 106 | + obj.state = true |
| 107 | + this.drawObject(obj) |
| 108 | + return false |
| 109 | + }, |
| 110 | + toggleVisibility (obj) { |
| 111 | + const { collitionIndex } = this.computePosition(obj.x - 16, obj.y - 16) |
| 112 | + this.obstaclesMap[collitionIndex] = obj.state ? 0 : -1 |
| 113 | + this.setSight() |
| 114 | + }, |
| 115 | + generateShadow (collitionIndex, pl) { |
| 116 | + const shadow = new Array(1024).fill(0) |
| 117 | + const playerX = Math.floor(collitionIndex % 32) |
| 118 | + const playerY = Math.floor(collitionIndex / 32) |
| 119 | + for (let y = 0; y < 32; y++) { |
| 120 | + for (let x = 0; x < 32; x++) { |
| 121 | + // Calculate angle and distance from player to ever other item in obstaclesMap |
| 122 | + const dx = x - playerX |
| 123 | + const dy = y - playerY |
| 124 | + const angle = Math.atan2(dy, dx) |
| 125 | + const distance = Math.sqrt(dx ** 2 + dy ** 2) |
| 126 | + // Verificar si hay obstrucciones en la línea de vista |
| 127 | + let visibleObstacle = false |
| 128 | + for (let d = 1; d <= distance; d++) { |
| 129 | + const xObstruction = Math.round(playerX + d * Math.cos(angle)) |
| 130 | + const yObstruction = Math.round(playerY + d * Math.sin(angle)) |
| 131 | + const obstructionPosition = yObstruction * 32 + xObstruction |
| 132 | + if (xObstruction >= 0 && xObstruction < 32 && yObstruction >= 0 && yObstruction < 32) { |
| 133 | + if (this.obstaclesMap[obstructionPosition] !== 0) { |
| 134 | + if (!visibleObstacle) { |
| 135 | + shadow[obstructionPosition] = 1 |
| 136 | + visibleObstacle = true |
| 137 | + } |
| 138 | + break |
| 139 | + } else { |
| 140 | + shadow[obstructionPosition] = 1 |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | + } |
| 145 | + } |
| 146 | + ctxSight.clearRect(pl.x + 8, pl.y + 16, 16, 16) |
| 147 | + shadow.forEach((val, index) => { |
| 148 | + if (val === 0) return |
| 149 | + ctxSight.clearRect((index % 32) * 16, Math.floor(index / 32) * 16, 16, 16) |
| 150 | + }) |
| 151 | + ctxSight.closePath() |
| 152 | + }, |
| 153 | + generateSight (vg) { |
| 154 | + const mapa = this.collitionMap |
| 155 | + const filas = 32 |
| 156 | + const columnas = 32 |
| 157 | + const tamanoMapa = filas * columnas |
| 158 | + const sombra = new Array(tamanoMapa).fill(0) |
| 159 | + let [x, y] = [vg.position.x, vg.position.y] |
| 160 | + |
| 161 | + const apertura = 60 / 2 |
| 162 | + let rangoAngulos |
| 163 | + if (['down', 'left', 'up'].includes(vg.direction)) x -= 1 |
| 164 | + if (vg.direction === 'right') rangoAngulos = [360 - apertura, apertura] |
| 165 | + if (vg.direction === 'down') rangoAngulos = [90 - apertura, 90 + apertura] |
| 166 | + if (vg.direction === 'left') rangoAngulos = [180 - apertura, 180 + apertura] |
| 167 | + if (vg.direction === 'up') rangoAngulos = [270 - apertura, 270 + apertura] |
| 168 | + |
| 169 | + const posicionUnidad = this.computePosition(x, y).collitionIndex |
| 170 | + |
| 171 | + const xUnidad = Math.floor(posicionUnidad % columnas) |
| 172 | + const yUnidad = Math.floor(posicionUnidad / columnas) |
| 173 | + |
| 174 | + for (let y = 0; y < filas; y++) { |
| 175 | + for (let x = 0; x < columnas; x++) { |
| 176 | + // Calcular ángulo y distancia desde la unidad hasta la posición actual |
| 177 | + const dx = x - xUnidad |
| 178 | + const dy = y - yUnidad |
| 179 | + const angulo = Math.atan2(dy, dx) |
| 180 | + |
| 181 | + // Convertir el ángulo a grados |
| 182 | + const anguloGrados = (angulo * 180) / Math.PI |
| 183 | + |
| 184 | + // Mapear el ángulo al rango [0, 360) |
| 185 | + const anguloMapeado = (anguloGrados + 360) % 360 |
| 186 | + |
| 187 | + // Verificar si el ángulo está dentro del rango especificado |
| 188 | + let anguloEnRango = false |
| 189 | + if (rangoAngulos[0] <= rangoAngulos[1]) { |
| 190 | + anguloEnRango = rangoAngulos[0] <= anguloMapeado && anguloMapeado <= rangoAngulos[1] |
| 191 | + } else { |
| 192 | + anguloEnRango = |
| 193 | + anguloMapeado >= rangoAngulos[0] || anguloMapeado <= rangoAngulos[1] |
| 194 | + } |
| 195 | + |
| 196 | + if (anguloEnRango) { |
| 197 | + const distancia = Math.sqrt((dx ** 2) + (dy ** 2)) |
| 198 | + |
| 199 | + // Verificar si hay obstrucciones en la línea de vista |
| 200 | + let visibleObstacle = false |
| 201 | + for (let d = 1; d <= distancia; d++) { |
| 202 | + const xObstruccion = Math.round(xUnidad + d * Math.cos(angulo)) |
| 203 | + const yObstruccion = Math.round(yUnidad + d * Math.sin(angulo)) |
| 204 | + const posicionObstruccion = yObstruccion * columnas + xObstruccion |
| 205 | + |
| 206 | + if ( |
| 207 | + xObstruccion >= 0 && |
| 208 | + xObstruccion < columnas && |
| 209 | + yObstruccion >= 0 && |
| 210 | + yObstruccion < filas |
| 211 | + ) { |
| 212 | + if (mapa[posicionObstruccion] !== 0) { |
| 213 | + if (!visibleObstacle) { |
| 214 | + sombra[posicionObstruccion] = 1 |
| 215 | + visibleObstacle = true |
| 216 | + } |
| 217 | + break |
| 218 | + } else { |
| 219 | + sombra[posicionObstruccion] = 1 |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + } |
| 226 | + |
| 227 | + Vigilance.ctx.clearRect(vg.x + 8, vg.y + 16, 16, 16) |
| 228 | + sombra.forEach((val, index) => { |
| 229 | + if (val === 0) return |
| 230 | + Vigilance.ctx.rect((index % 32) * 16, Math.floor(index / 32) * 16, 16, 16) |
| 231 | + }) |
| 232 | + Vigilance.ctx.fillStyle = `${this.palette[11]}66` |
| 233 | + Vigilance.ctx.fill() |
| 234 | + Vigilance.ctx.closePath() |
| 235 | + }, |
| 236 | + setSight () { |
| 237 | + if (noShadows) return |
| 238 | + const players = Player.instances.map(({ position }) => position) |
| 239 | + ctxSight.beginPath() |
| 240 | + ctxSight.fillStyle = this.palette[0] |
| 241 | + ctxSight.rect(0, 0, 512, 512) |
| 242 | + ctxSight.fill() |
| 243 | + ctxSight.closePath() |
| 244 | + players.forEach(pl => { |
| 245 | + this.generateShadow(this.computePosition(pl.x, pl.y).collitionIndex, pl) |
| 246 | + }) |
| 247 | + } |
| 248 | +} |
0 commit comments