forked from noxious/server
Added models to store extra data in RAM
This commit is contained in:
parent
32b390bb20
commit
636aa6cc55
6
package-lock.json
generated
6
package-lock.json
generated
@ -706,9 +706,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn-walk": {
|
"node_modules/acorn-walk": {
|
||||||
"version": "8.3.3",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||||
"integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
|
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.11.0"
|
"acorn": "^8.11.0"
|
||||||
|
@ -16,7 +16,7 @@ export default class CharacterMoveEvent {
|
|||||||
private characterMoveService: CharacterMoveService
|
private characterMoveService: CharacterMoveService
|
||||||
private zoneEventTileService: ZoneEventTileService
|
private zoneEventTileService: ZoneEventTileService
|
||||||
private movementValidator: MovementValidator
|
private movementValidator: MovementValidator
|
||||||
private nextPath: {[index: number]: {x: number, y: number}[]} = [];
|
private nextPath: { [index: number]: { x: number; y: number }[] } = []
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly io: Server,
|
private readonly io: Server,
|
||||||
@ -38,16 +38,15 @@ export default class CharacterMoveEvent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const path = await this.characterMoveService.calculatePath(character, positionX, positionY)
|
const path = await this.characterMoveService.calculatePath(character, positionX, positionY)
|
||||||
if (!path) {
|
if (!path) {
|
||||||
this.io.in(character.zoneId.toString()).emit('character:moveError', 'No valid path found')
|
this.io.in(character.zoneId.toString()).emit('character:moveError', 'No valid path found')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if(character.isMoving && !character.resetMovement) {
|
if (character.isMoving && !character.resetMovement) {
|
||||||
character.resetMovement = true;
|
character.resetMovement = true
|
||||||
this.nextPath[character.id] = path;
|
this.nextPath[character.id] = path
|
||||||
} else {
|
} else {
|
||||||
await this.moveAlongPath(character, path)
|
await this.moveAlongPath(character, path)
|
||||||
}
|
}
|
||||||
@ -55,26 +54,26 @@ export default class CharacterMoveEvent {
|
|||||||
|
|
||||||
private async moveAlongPath(character: ExtendedCharacter, path: Array<{ x: number; y: number }>): Promise<void> {
|
private async moveAlongPath(character: ExtendedCharacter, path: Array<{ x: number; y: number }>): Promise<void> {
|
||||||
for (let i = 0; i < path.length - 1; i++) {
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
const start = path[i];
|
const start = path[i]
|
||||||
const end = path[i + 1];
|
const end = path[i + 1]
|
||||||
|
|
||||||
if (!(await this.movementValidator.isValidMove(character, end))) {
|
if (!(await this.movementValidator.isValidMove(character, end))) {
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if(character.isMoving && character.resetMovement) {
|
if (character.isMoving && character.resetMovement) {
|
||||||
character.isMoving = false;
|
character.isMoving = false
|
||||||
character.resetMovement = false;
|
character.resetMovement = false
|
||||||
const nextPath = this.nextPath[character.id];
|
const nextPath = this.nextPath[character.id]
|
||||||
this.moveAlongPath(character, nextPath);
|
this.moveAlongPath(character, nextPath)
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!character.isMoving) {
|
if (!character.isMoving) {
|
||||||
character.isMoving = true;
|
character.isMoving = true
|
||||||
}
|
}
|
||||||
|
|
||||||
character.rotation = Rotation.calculate(start.x, start.y, end.x, end.y);
|
character.rotation = Rotation.calculate(start.x, start.y, end.x, end.y)
|
||||||
|
|
||||||
const zoneEventTile = await prisma.zoneEventTile.findFirst({
|
const zoneEventTile = await prisma.zoneEventTile.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@ -82,33 +81,33 @@ export default class CharacterMoveEvent {
|
|||||||
positionX: Math.floor(end.x),
|
positionX: Math.floor(end.x),
|
||||||
positionY: Math.floor(end.y)
|
positionY: Math.floor(end.y)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
if (zoneEventTile) {
|
if (zoneEventTile) {
|
||||||
if (zoneEventTile.type === 'BLOCK') {
|
if (zoneEventTile.type === 'BLOCK') {
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zoneEventTile.type === 'TELEPORT') {
|
if (zoneEventTile.type === 'TELEPORT') {
|
||||||
const teleportTile = await prisma.zoneEventTile.findFirst({
|
const teleportTile = (await prisma.zoneEventTile.findFirst({
|
||||||
where: { id: zoneEventTile.id },
|
where: { id: zoneEventTile.id },
|
||||||
include: { teleport: true }
|
include: { teleport: true }
|
||||||
}) as ZoneEventTileWithTeleport;
|
})) as ZoneEventTileWithTeleport
|
||||||
|
|
||||||
if (teleportTile) {
|
if (teleportTile) {
|
||||||
await this.handleZoneEventTile(teleportTile);
|
await this.handleZoneEventTile(teleportTile)
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.characterMoveService.updatePosition(character, end);
|
await this.characterMoveService.updatePosition(character, end)
|
||||||
this.io.in(character.zoneId.toString()).emit('character:move', character);
|
this.io.in(character.zoneId.toString()).emit('character:move', character)
|
||||||
|
|
||||||
await this.characterMoveService.applyMovementDelay();
|
await this.characterMoveService.applyMovementDelay()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.finalizeMovement(character);
|
this.finalizeMovement(character)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise<void> {
|
private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise<void> {
|
||||||
|
@ -3,15 +3,10 @@ import ZoneRepository from '../repositories/zoneRepository'
|
|||||||
import ZoneService from '../services/zoneService'
|
import ZoneService from '../services/zoneService'
|
||||||
import zoneRepository from '../repositories/zoneRepository'
|
import zoneRepository from '../repositories/zoneRepository'
|
||||||
import logger from '../utilities/logger'
|
import logger from '../utilities/logger'
|
||||||
|
import LoadedZone from '../models/zone/loadedZone'
|
||||||
type TLoadedZone = {
|
|
||||||
zone: Zone
|
|
||||||
characters: Character[]
|
|
||||||
grid: number[][]
|
|
||||||
}
|
|
||||||
|
|
||||||
class ZoneManager {
|
class ZoneManager {
|
||||||
private loadedZones: TLoadedZone[] = []
|
private loadedZones: LoadedZone[] = []
|
||||||
|
|
||||||
// Method to initialize zoneEditor manager
|
// Method to initialize zoneEditor manager
|
||||||
public async boot() {
|
public async boot() {
|
||||||
@ -31,149 +26,32 @@ class ZoneManager {
|
|||||||
|
|
||||||
// Method to handle individual zoneEditor loading
|
// Method to handle individual zoneEditor loading
|
||||||
public async loadZone(zone: Zone) {
|
public async loadZone(zone: Zone) {
|
||||||
const grid = await this.getGrid(zone.id) // Create the grid for the zoneEditor
|
const loadedZone = new LoadedZone(zone)
|
||||||
this.loadedZones.push({
|
this.loadedZones.push(loadedZone)
|
||||||
zone,
|
|
||||||
characters: [],
|
|
||||||
grid
|
|
||||||
})
|
|
||||||
logger.info(`Zone ID ${zone.id} loaded`)
|
logger.info(`Zone ID ${zone.id} loaded`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to handle individual zoneEditor unloading
|
// Method to handle individual zoneEditor unloading
|
||||||
public unloadZone(zoneId: number) {
|
public unloadZone(zoneId: number) {
|
||||||
this.loadedZones = this.loadedZones.filter((loadedZone) => {
|
this.loadedZones = this.loadedZones.filter((loadedZone) => loadedZone.getZone().id !== zoneId)
|
||||||
return loadedZone.zone.id !== zoneId
|
|
||||||
})
|
|
||||||
logger.info(`Zone ID ${zoneId} unloaded`)
|
logger.info(`Zone ID ${zoneId} unloaded`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getter for loaded zones
|
// Getter for loaded zones
|
||||||
public getLoadedZones(): TLoadedZone[] {
|
public getLoadedZones(): LoadedZone[] {
|
||||||
return this.loadedZones
|
return this.loadedZones
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if position is walkable
|
|
||||||
private isPositionWalkable(zoneId: number, x: number, y: number): boolean {
|
|
||||||
const loadedZone = this.loadedZones.find((lz) => lz.zone.id === zoneId)
|
|
||||||
if (!loadedZone) {
|
|
||||||
console.log(`Zone ${zoneId} not found in loadedZones`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!loadedZone.grid) {
|
|
||||||
console.log(`Grid for zone ${zoneId} is undefined`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!loadedZone.grid[y]) {
|
|
||||||
console.log(`Row ${y} in grid for zone ${zoneId} is undefined`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return loadedZone.grid[y][x] === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
public addCharacterToZone(zoneId: number, character: Character) {
|
public addCharacterToZone(zoneId: number, character: Character) {
|
||||||
console.log(`Adding character ${character.id} to zone ${zoneId}`)
|
|
||||||
console.log(`Character position: x=${character.positionX}, y=${character.positionY}`)
|
|
||||||
|
|
||||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
|
||||||
return loadedZone.zone.id === zoneId
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!loadedZone) {
|
|
||||||
console.log(`Zone ${zoneId} not found in loadedZones`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isPositionWalkable(zoneId, character.positionX, character.positionY)) {
|
|
||||||
loadedZone.characters.push(character)
|
|
||||||
console.log(`Character ${character.id} added to zone ${zoneId}`)
|
|
||||||
} else {
|
|
||||||
// set position to 0,0 if not walkable
|
|
||||||
console.log(`Position (${character.positionX}, ${character.positionY}) is not walkable in zone ${zoneId}`)
|
|
||||||
character.positionX = 0
|
|
||||||
character.positionY = 0
|
|
||||||
loadedZone.characters.push(character)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeCharacterFromZone(zoneId: number, character: Character) {
|
public removeCharacterFromZone(zoneId: number, character: Character) {
|
||||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
|
||||||
return loadedZone.zone.id === zoneId
|
|
||||||
})
|
|
||||||
if (loadedZone) {
|
|
||||||
loadedZone.characters = loadedZone.characters.filter((loadedCharacter) => {
|
|
||||||
return loadedCharacter.id !== character.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateCharacterInZone(zoneId: number, character: Character) {
|
|
||||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
|
||||||
return loadedZone.zone.id === zoneId
|
|
||||||
})
|
|
||||||
if (loadedZone) {
|
|
||||||
const characterIndex = loadedZone.characters.findIndex((loadedCharacter) => {
|
|
||||||
return loadedCharacter.id === character.id
|
|
||||||
})
|
|
||||||
if (characterIndex !== -1) {
|
|
||||||
loadedZone.characters[characterIndex] = character
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCharactersInZone(zoneId: number): Character[] {
|
|
||||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
|
||||||
return loadedZone.zone.id === zoneId
|
|
||||||
})
|
|
||||||
return loadedZone ? loadedZone.characters : []
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getGrid(zoneId: number): Promise<number[][]> {
|
|
||||||
const zone = this.loadedZones.find((z) => z.zone.id === zoneId)
|
|
||||||
if (zone) return zone.grid
|
|
||||||
|
|
||||||
const loadedZone = await ZoneRepository.getById(zoneId)
|
|
||||||
if (!loadedZone) return []
|
|
||||||
|
|
||||||
let grid: number[][] = Array.from({ length: loadedZone.height }, () => Array.from({ length: loadedZone.width }, () => 0))
|
|
||||||
|
|
||||||
const eventTiles = await zoneRepository.getEventTiles(zoneId)
|
|
||||||
|
|
||||||
// Set the grid values based on the event tiles, these are strings
|
|
||||||
eventTiles.forEach((eventTile) => {
|
|
||||||
if (eventTile.type === 'BLOCK') {
|
|
||||||
grid[eventTile.positionY][eventTile.positionX] = 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return grid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async moveCharacterBetweenZones(oldZoneId: number, newZoneId: number, character: Character): Promise<void> {
|
public async moveCharacterBetweenZones(oldZoneId: number, newZoneId: number, character: Character): Promise<void> {
|
||||||
// Find the old and new zones
|
|
||||||
const oldZone = this.loadedZones.find(zone => zone.zone.id === oldZoneId);
|
|
||||||
const newZone = this.loadedZones.find(zone => zone.zone.id === newZoneId);
|
|
||||||
|
|
||||||
if (!oldZone || !newZone) {
|
|
||||||
logger.error(`Unable to move character ${character.id}: zones not found`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove character from old zone
|
|
||||||
oldZone.characters = oldZone.characters.filter(c => c.id !== character.id);
|
|
||||||
|
|
||||||
// Check if the new position is walkable
|
|
||||||
if (this.isPositionWalkable(newZoneId, character.positionX, character.positionY)) {
|
|
||||||
newZone.characters.push(character);
|
|
||||||
} else {
|
|
||||||
// Set position to 0,0 if not walkable
|
|
||||||
logger.warn(`Position (${character.positionX}, ${character.positionY}) is not walkable in zone ${newZoneId}. Moving character to (0,0).`);
|
|
||||||
character.positionX = 0;
|
|
||||||
character.positionY = 0;
|
|
||||||
newZone.characters.push(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Character ${character.id} moved from zone ${oldZoneId} to zone ${newZoneId}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
src/models/zone/loadedZone.ts
Normal file
60
src/models/zone/loadedZone.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { Character, Tile, Zone } from '@prisma/client'
|
||||||
|
import ZoneCharacter from './zoneCharacter'
|
||||||
|
import zoneRepository from '../../repositories/zoneRepository'
|
||||||
|
import { ExtendedCharacter } from '../../utilities/types'
|
||||||
|
import ZoneManager from '../../managers/zoneManager'
|
||||||
|
|
||||||
|
class LoadedZone {
|
||||||
|
private readonly zone: Zone
|
||||||
|
private characters: ZoneCharacter[] = []
|
||||||
|
// private readonly npcs: ZoneNPC[] = []
|
||||||
|
private readonly grid: number[][] = []
|
||||||
|
|
||||||
|
constructor(zone: Zone) {
|
||||||
|
this.zone = zone
|
||||||
|
}
|
||||||
|
|
||||||
|
public getZone(): Zone {
|
||||||
|
return this.zone
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCharacters(): ZoneCharacter[] {
|
||||||
|
return this.characters
|
||||||
|
}
|
||||||
|
|
||||||
|
public addCharacter(character: Character): void {
|
||||||
|
const zoneCharacter = new ZoneCharacter(character)
|
||||||
|
this.characters.push(zoneCharacter)
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeCharacter(character: Character): void {
|
||||||
|
this.characters = this.characters.filter((zoneCharacter) => zoneCharacter.getCharacter().id !== character.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getGrid(): Promise<number[][]> {
|
||||||
|
let grid: number[][] = Array.from({ length: this.zone..height }, () => Array.from({ length: this.zone.width }, () => 0))
|
||||||
|
|
||||||
|
const eventTiles = await zoneRepository.getEventTiles(this.zone.id)
|
||||||
|
|
||||||
|
// Set the grid values based on the event tiles, these are strings
|
||||||
|
eventTiles.forEach((eventTile) => {
|
||||||
|
if (eventTile.type === 'BLOCK') {
|
||||||
|
grid[eventTile.positionY][eventTile.positionX] = 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return grid
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isPositionWalkable(position: { x: number; y: number }): Promise<boolean> {
|
||||||
|
const grid = await this.getGrid()
|
||||||
|
if (!grid?.length) return false
|
||||||
|
|
||||||
|
const gridX = Math.floor(position.x)
|
||||||
|
const gridY = Math.floor(position.y)
|
||||||
|
|
||||||
|
return grid[gridY]?.[gridX] === 1 || grid[gridY]?.[Math.ceil(position.x)] === 1 || grid[Math.ceil(position.y)]?.[gridX] === 1 || grid[Math.ceil(position.y)]?.[Math.ceil(position.x)] === 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadedZone
|
@ -1,6 +1,6 @@
|
|||||||
import { Character } from '@prisma/client'
|
import { Character } from '@prisma/client'
|
||||||
|
|
||||||
class ZoneCharacter {
|
export default class ZoneCharacter {
|
||||||
private readonly character: Character
|
private readonly character: Character
|
||||||
private isMoving: boolean = false
|
private isMoving: boolean = false
|
||||||
|
|
@ -7,13 +7,13 @@ import { Server } from 'socket.io'
|
|||||||
|
|
||||||
export class ZoneEventTileService {
|
export class ZoneEventTileService {
|
||||||
public async handleTeleport(io: Server, socket: TSocket, character: ExtendedCharacter, teleport: ZoneEventTileTeleport): Promise<void> {
|
public async handleTeleport(io: Server, socket: TSocket, character: ExtendedCharacter, teleport: ZoneEventTileTeleport): Promise<void> {
|
||||||
if (teleport.toZoneId === character.zoneId) return;
|
if (teleport.toZoneId === character.zoneId) return
|
||||||
|
|
||||||
const zone = await ZoneRepository.getById(teleport.toZoneId);
|
const zone = await ZoneRepository.getById(teleport.toZoneId)
|
||||||
if (!zone) return;
|
if (!zone) return
|
||||||
|
|
||||||
const oldZoneId = character.zoneId;
|
const oldZoneId = character.zoneId
|
||||||
const newZoneId = teleport.toZoneId;
|
const newZoneId = teleport.toZoneId
|
||||||
|
|
||||||
// Update character in database
|
// Update character in database
|
||||||
await prisma.character.update({
|
await prisma.character.update({
|
||||||
@ -23,28 +23,28 @@ export class ZoneEventTileService {
|
|||||||
positionX: teleport.toPositionX,
|
positionX: teleport.toPositionX,
|
||||||
positionY: teleport.toPositionY
|
positionY: teleport.toPositionY
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// Update local character object
|
// Update local character object
|
||||||
character.zoneId = newZoneId;
|
character.zoneId = newZoneId
|
||||||
character.positionX = teleport.toPositionX;
|
character.positionX = teleport.toPositionX
|
||||||
character.positionY = teleport.toPositionY;
|
character.positionY = teleport.toPositionY
|
||||||
|
|
||||||
// Atomic operation in ZoneManager
|
// Atomic operation in ZoneManager
|
||||||
await ZoneManager.moveCharacterBetweenZones(oldZoneId, newZoneId, character as Character);
|
await ZoneManager.moveCharacterBetweenZones(oldZoneId, newZoneId, character as Character)
|
||||||
|
|
||||||
// Emit events
|
// Emit events
|
||||||
io.to(oldZoneId.toString()).emit('zone:character:leave', character.id);
|
io.to(oldZoneId.toString()).emit('zone:character:leave', character.id)
|
||||||
io.to(newZoneId.toString()).emit('zone:character:join', character);
|
io.to(newZoneId.toString()).emit('zone:character:join', character)
|
||||||
|
|
||||||
// Update socket rooms
|
// Update socket rooms
|
||||||
socket.leave(oldZoneId.toString());
|
socket.leave(oldZoneId.toString())
|
||||||
socket.join(newZoneId.toString());
|
socket.join(newZoneId.toString())
|
||||||
|
|
||||||
// Send teleport information to the client
|
// Send teleport information to the client
|
||||||
socket.emit('zone:teleport', {
|
socket.emit('zone:teleport', {
|
||||||
zone,
|
zone,
|
||||||
characters: ZoneManager.getCharactersInZone(zone.id)
|
characters: ZoneManager.getCharactersInZone(zone.id)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,5 @@ import { ExtendedCharacter } from '../types'
|
|||||||
import ZoneManager from '../../managers/zoneManager'
|
import ZoneManager from '../../managers/zoneManager'
|
||||||
|
|
||||||
export class MovementValidator {
|
export class MovementValidator {
|
||||||
public async isValidMove(character: ExtendedCharacter, position: { x: number; y: number }): Promise<boolean> {
|
|
||||||
const grid = await ZoneManager.getGrid(character.zoneId)
|
|
||||||
if (!grid?.length) return false
|
|
||||||
|
|
||||||
return !this.isObstacle(position, grid)
|
|
||||||
}
|
|
||||||
|
|
||||||
private isObstacle({ x, y }: { x: number; y: number }, grid: number[][]): boolean {
|
|
||||||
const gridX = Math.floor(x)
|
|
||||||
const gridY = Math.floor(y)
|
|
||||||
return grid[gridY]?.[gridX] === 1 || grid[gridY]?.[Math.ceil(x)] === 1 || grid[Math.ceil(y)]?.[gridX] === 1 || grid[Math.ceil(y)]?.[Math.ceil(x)] === 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user