123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- import { AsyncDataWriter } from "../../libraries/AsyncDataWriter.js";
- import { Camera } from "../../libraries/Camera.js";
- import { TileMap } from "../../libraries/components/TileMap.js";
- import { Point } from "../../libraries/spatial/Point.js";
- import { SpritesheetAtlas } from "../../libraries/SpritesheetAtlas.js";
- import { TweenManager, Tween, Easing } from "../../libraries/Tween.js";
- export class LevelEditorState {
- constructor(view) {
- this.stateMachine = view.stateMachine
- this.canvasBounds = null
- this.camera = new Camera()
- this.camera.scale = new Point(2, 2)
- this.tweenManager = new TweenManager()
- this.keysPressed = {}
- this.map = new TileMap()
- this.primaryTile = { x: 0, y: 0, atlas: 0 }
- this.secondaryTile = { x: 1, y: 0, atlas: 0 }
- this.history = []
- this.placementMode = "tile"
- }
- init(self) {
- let params = new URLSearchParams(window.location.search)
- let roomPath = params.get('room') ?? "room1"
- this.map.loadFromApi(roomPath).then(() => {
- this.map.mapData.spritesheets.forEach((sheetpath) => {
- window.open(`./sheetviewer.html?sheet=${sheetpath}`, `Spritesheet Viewer ${sheetpath}`, 'height=400,width=600')
- })
- })
- this.history = []
- }
- draw(ctx, scaledCanvas) {
- this.canvasBounds = scaledCanvas.bounds;
- if (!this.map.loaded) {
- return
- }
- this.camera.draw(ctx, scaledCanvas, () => {
- if (this.map.mapData) {
- ctx.strokeStyle = "#000000"
- ctx.beginPath()
- ctx.rect(
- this.map.mapData.bounds[0] * 16 - 8,
- this.map.mapData.bounds[1] * 16 - 8,
- this.map.mapData.bounds[2] * 16,
- this.map.mapData.bounds[3] * 16)
- ctx.stroke()
- }
-
-
- let mode = localStorage.getItem("teetopia-editor-placement") ?? "tile"
- switch (mode) {
- case "wall":
- this.map.draw(ctx, scaledCanvas)
- this.map.drawDecals(ctx, scaledCanvas)
- this.map.drawOverDecals(ctx, scaledCanvas)
- this.map.drawCollision(ctx, scaledCanvas)
- break;
- case "door":
- this.map.draw(ctx, scaledCanvas)
- this.map.drawDecals(ctx, scaledCanvas)
- this.map.drawOverDecals(ctx, scaledCanvas)
- this.map.drawDoors(ctx, scaledCanvas)
- break
- case "tile":
- this.map.draw(ctx, scaledCanvas)
- this.map.drawDecals(ctx, scaledCanvas)
- this.map.drawOverDecals(ctx, scaledCanvas)
- this.map.drawCoords(ctx, scaledCanvas)
- break
- case "decal-bottom":
- this.map.draw(ctx, scaledCanvas)
- this.map.drawDecals(ctx, scaledCanvas)
- this.map.drawDecalCoords(ctx, scaledCanvas)
- break
- case "decal-top":
- this.map.draw(ctx, scaledCanvas)
- this.map.drawOverDecals(ctx, scaledCanvas)
- this.map.drawDecalCoords(ctx, scaledCanvas)
- break
- default:
- break;
- }
-
- })
- ctx.save()
- ctx.scale(2, 2)
- ctx.translate(16, 16)
- ctx.strokeStyle = "green"
- ctx.beginPath()
- ctx.rect(-8, -8, 16, 16)
- ctx.closePath()
- ctx.stroke()
- ctx.fillStyle = "white"
- ctx.textAlign = "left"
- ctx.font = "18px Arial"
- ctx.fillText(this.placementMode, 16, 6)
- let primaryAtlas = this.map.getAtlas(this.primaryTile.atlas)
- if (primaryAtlas) {
- let sprite = primaryAtlas.getDrawableSprite(this.primaryTile.x, this.primaryTile.y)
- sprite.draw(ctx, scaledCanvas)
- ctx.textAlign = "right"
- ctx.font = "6px Arial"
- ctx.fillStyle = "rgba(255,255,255,0.4)"
- ctx.fillText(`${this.primaryTile.x},${this.primaryTile.y}`, 8, 8)
- }
- ctx.restore()
- ctx.save()
- ctx.scale(2, 2)
- ctx.translate(16, 48)
- ctx.strokeStyle = "blue"
- ctx.beginPath()
- ctx.rect(-8, -8, 16, 16)
- ctx.closePath()
- ctx.stroke()
- let secondAtlas = this.map.getAtlas(this.secondaryTile.atlas)
- if (secondAtlas) {
- let secondarySprite = secondAtlas.getDrawableSprite(this.secondaryTile.x, this.secondaryTile.y)
- secondarySprite.draw(ctx, scaledCanvas)
- ctx.textAlign = "right"
- ctx.font = "6px Arial"
- ctx.fillStyle = "rgba(255,255,255,0.4)"
- ctx.fillText(`${this.secondaryTile.x},${this.secondaryTile.y}`, 8, 8)
- }
- ctx.restore()
- }
- update(delta) {
- if (this.keysPressed['w'] || this.keysPressed['ArrowUp']) {
- this.camera.position.y -= 16
- }
- if (this.keysPressed['a'] || this.keysPressed['ArrowLeft']) {
- this.camera.position.x -= 16
- }
- if (this.keysPressed['s'] || this.keysPressed['ArrowDown']) {
- this.camera.position.y += 16
- }
- if (this.keysPressed['d'] || this.keysPressed['ArrowRight']) {
- this.camera.position.x += 16
- }
- this.camera.update(delta);
- this.tweenManager.update()
- if (!this.map.mapData) {
- return
- }
- let tileString = localStorage.getItem('teetopia-editor-primary') ?? `0,0,${this.map.mapData.spritesheets[0]}`
- let tile = tileString.split(",")
- this.primaryTile.x = parseInt(tile[0])
- this.primaryTile.y = parseInt(tile[1])
- let primaryAtlas = parseInt(this.map.mapData.spritesheets.indexOf(tile[2]))
- if (primaryAtlas == -1) {
- this.map.mapData.spritesheets.push(tile[2])
- primaryAtlas = this.map.mapData.spritesheets.length - 1
- this.map.reloadAtlas(primaryAtlas)
- }
- this.primaryTile.atlas = primaryAtlas
- let secondTileString = localStorage.getItem('teetopia-editor-secondary') ?? `1,0,${this.map.mapData.spritesheets[0]}`
- let secondTile = secondTileString.split(",")
- this.secondaryTile.x = parseInt(secondTile[0])
- this.secondaryTile.y = parseInt(secondTile[1])
- let secondaryAtlas = parseInt(this.map.mapData.spritesheets.indexOf(secondTile[2]))
- if (secondaryAtlas == -1) {
- this.map.mapData.spritesheets.push(secondTile[2])
- secondaryAtlas = this.map.mapData.spritesheets.length - 1
- this.map.reloadAtlas(secondaryAtlas)
- }
- this.secondaryTile.atlas = secondaryAtlas
- this.placementMode = localStorage.getItem('teetopia-editor-placement') ?? "tile"
- }
- enter() {
- this.init(this.stateMachine.getState("view"))
- this.registeredEvents = {}
- this.registeredEvents["resize"] = this.onResize
- this.registeredEvents["keydown"] = this.onKeyDown
- this.registeredEvents["keyup"] = this.onKeyUp
- this.registeredEvents["touchstart"] = this.onTouchStart
- this.registeredEvents['click'] = this.onClick
- this.registeredEvents['mousemove'] = this.onMouseMove
- this.registeredEvents['contextmenu'] = this.onClick
- for (let index in this.registeredEvents) {
- window.addEventListener(index, this.registeredEvents[index].bind(this))
- }
- }
- leave() {
- for (let index in this.registeredEvents) {
- window.removeEventListener(index, this.registeredEvents[index])
- }
- this.tweenManager.clear()
- }
- onFinish() {
- }
- onResize() {
- }
- onKeyDown(event) {
- this.keysPressed[event.key] = true
- switch (event.key) {
- case "s":
- if (event.ctrlKey) {
- event.preventDefault()
- this.saveRoom()
- this.keysPressed[event.key] = false
- return false
- }
- break
- }
- }
- async saveRoom() {
- let params = new URLSearchParams(window.location.search)
- let roomPath = params.get('room')
- let sessionInfoString = localStorage.getItem('teetopia-session');
- if (!sessionInfoString) {
- alert("You must be logged in to save a room.")
- return
- }
- let sessionInfo = JSON.parse(sessionInfoString)
- console.log(sessionInfo)
- let request = {
- token: sessionInfo.token,
- roomData: this.map.mapData,
- }
- if (request.roomData.owner == "") {
- request.roomData.owner = sessionInfo.email;
- }
- let result;
- try {
- result = await new AsyncDataWriter().post(`./api/room/${roomPath}/save`, request)
- } catch (e) {
- console.error(result, e);
- }
- alert(result.responseMessage)
- }
- onKeyUp(event) {
- event.preventDefault()
- this.keysPressed[event.key] = false
- switch (event.key) {
- case "Escape":
- window.location = "./index.html"
- break
- case "Control":
- break;
- case "z":
- if (event.ctrlKey) {
- if (this.history.length == 0) {
- return
- }
- let lastStep = this.history.pop()
- let mapTile = this.map.getTile(lastStep.position.x, lastStep.position.y)
- mapTile[0] = lastStep.previous[0]
- mapTile[1] = lastStep.previous[1]
- mapTile[2] = lastStep.previous[2]
- }
- break
- case "1":
- localStorage.setItem("teetopia-editor-placement", "tile")
- break;
- case "2":
- localStorage.setItem("teetopia-editor-placement", "decal-bottom")
- break;
- case "3":
- localStorage.setItem("teetopia-editor-placement", "decal-top")
- break;
- case "4":
- localStorage.setItem("teetopia-editor-placement", "wall")
- break;
- case "5":
- localStorage.setItem("teetopia-editor-placement", "door")
- break;
- default:
- // console.log(event.key, event)
- }
- }
- onTouchStart(event) {
- let touchPosition = new Point(event.changedTouches[0].clientX, event.changedTouches[0].clientY)
- let worldTouchPosition = this.camera.screenToWorld(touchPosition)
- let delta = worldTouchPosition.difference(this.player.position)
- if (Math.abs(delta.x) > Math.abs(delta.y)) {
- if (Math.sign(delta.x) < 0) {
- this.keysPressed["ArrowLeft"] = true
- } else {
- this.keysPressed["ArrowRight"] = true
- }
- } else if (Math.abs(delta.x) < Math.abs(delta.y)) {
- if (Math.sign(delta.y) < 0) {
- this.keysPressed["ArrowUp"] = true
- } else {
- this.keysPressed["ArrowDown"] = true
- }
- }
- }
- onClick(event) {
- event.preventDefault()
- let clickPosition = new Point(event.clientX, event.clientY)
- let worldClickPosition = this.camera.screenToWorld(clickPosition)
- worldClickPosition.snapToGrid({ width: 16, height: 16, margin: 1 })
- worldClickPosition.scale(1 / 16, 1 / 16)
- let mode = localStorage.getItem("teetopia-editor-placement") ?? "tile"
- switch (mode) {
- case "door":
- if (
- worldClickPosition.x > this.map.mapData.bounds[0] + this.map.mapData.bounds[2] ||
- worldClickPosition.y > this.map.mapData.bounds[1] + this.map.mapData.bounds[3] ||
- worldClickPosition.x < this.map.mapData.bounds[0] ||
- worldClickPosition.y < this.map.mapData.bounds[1]
- ) {
- return;
- }
- if (event.button == 0) {
- let shouldPlace = confirm(`Do you want to place an entrance at ${worldClickPosition.x}, ${worldClickPosition.y}?`)
- if (shouldPlace) {
- this.map.mapData.exits.push([worldClickPosition.x, worldClickPosition.y])
- alert(`Entrance placed at ${worldClickPosition.x}, ${worldClickPosition.y}`)
- }
- } else if (event.button == 2) {
- let roomFile = prompt("Please input the name of the desired room:", "room1")
- let roomExit = prompt("Please select an exit:", 0)
- let tile = this.map.getTile(worldClickPosition.x, worldClickPosition.y)
- tile[2][1] = [roomFile, roomExit]
- }
- break
- case "wall":
- try {
- let tile = this.map.getTile(worldClickPosition.x, worldClickPosition.y)
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- if (event.button == 2) {
- tile[2][0] = 0
- } else {
- tile[2][0] = 1
- }
- } catch (e) { }
- break;
- case "decal-bottom":
- try {
- let tile = this.map.getTile(worldClickPosition.x, worldClickPosition.y)
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- if (event.button == 2) {
- tile[3].pop()
- } else {
- tile[3].push([this.primaryTile.x, this.primaryTile.y, this.primaryTile.atlas])
- }
- } catch (e) { }
- break;
- case "decal-top":
- try {
- let tile = this.map.getTile(worldClickPosition.x, worldClickPosition.y)
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- if (event.button == 2) {
- tile[4].pop()
- } else {
- tile[4].push([this.primaryTile.x, this.primaryTile.y, this.primaryTile.atlas])
- }
- } catch (e) { }
- break;
- case "tile":
- default:
- try {
- let tile = this.map.getTile(worldClickPosition.x, worldClickPosition.y)
- if (!tile) {
- let newTile = []
- newTile[0] = [worldClickPosition.x, worldClickPosition.y]
- if (event.button == 2) {
- newTile[1] = [this.secondaryTile.x, this.secondaryTile.y, this.secondaryTile.atlas]
- } else {
- newTile[1] = [this.primaryTile.x, this.primaryTile.y, this.primaryTile.atlas]
- }
- newTile[2] = [0]
- newTile[3] = []
- newTile[4] = []
- this.map.mapData.tiles.push(newTile)
- return
- }
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- if (event.button == 2) {
- tile[1] = [this.secondaryTile.x, this.secondaryTile.y, this.secondaryTile.atlas]
- } else {
- tile[1] = [this.primaryTile.x, this.primaryTile.y, this.primaryTile.atlas]
- }
- } catch (e) {
- if ((
- worldClickPosition.x > this.map.mapData.bounds[0] + this.map.mapData.bounds[2] ||
- worldClickPosition.y > this.map.mapData.bounds[1] + this.map.mapData.bounds[3]
- ) && confirm("Do you want to expand the map?")) {
- // if (worldClickPosition.x < this.map.mapData.bounds[0]) {
- // this.map.mapData.bounds[2] = (this.map.mapData.bounds[2] - worldClickPosition.x) + this.map.mapData.bounds[0]
- // this.map.mapData.bounds[0] = worldClickPosition.x
- // }
- if (worldClickPosition.x > this.map.mapData.bounds[0] + this.map.mapData.bounds[2]) {
- this.map.mapData.bounds[2] = worldClickPosition.x - this.map.mapData.bounds[0]
- }
- // if (worldClickPosition.y < this.map.mapData.bounds[1]) {
- // this.map.mapData.bounds[3] = (this.map.mapData.bounds[3] - worldClickPosition.y) + this.map.mapData.bounds[1]
- // this.map.mapData.bounds[1] = worldClickPosition.y
- // }
- if (worldClickPosition.y > this.map.mapData.bounds[1] + this.map.mapData.bounds[3]) {
- this.map.mapData.bounds[3] = worldClickPosition.y - this.map.mapData.bounds[1]
- }
- }
- }
- break;
- }
- }
- onMouseMove(event) {
- event.preventDefault()
- if (event.buttons == 0) {
- return
- }
- let clickPosition = new Point(event.clientX, event.clientY)
- let worldClickPosition = this.camera.screenToWorld(clickPosition)
- worldClickPosition.snapToGrid({ width: 16, height: 16, margin: 1 })
- worldClickPosition.scale(1 / 16, 1 / 16)
- let mode = localStorage.getItem("teetopia-editor-placement") ?? "tile"
- switch (mode) {
- case "door":
- case "decal-bottom":
- case "decal-top":
- break;
- case "wall":
- try {
- let tile = this.map.getTile(worldClickPosition.x, worldClickPosition.y)
- if (event.buttons == 2) {
- if (tile[2][0] == 1) {
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- tile[2][0] = 0
- }
- } else if (event.buttons == 1) {
- if (tile[2][0] == 0) {
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- tile[2][0] = 1
- }
- }
- } catch (e) { }
- break;
- case "tile":
- default:
- try {
- let tile = this.map.getTile(worldClickPosition.x, worldClickPosition.y)
- if (!tile) {
- let newTile = []
- newTile[0] = [worldClickPosition.x, worldClickPosition.y]
- if (event.button == 2) {
- newTile[1] = [this.secondaryTile.x, this.secondaryTile.y, this.secondaryTile.atlas]
- } else {
- newTile[1] = [this.primaryTile.x, this.primaryTile.y, this.primaryTile.atlas]
- }
- newTile[2] = [0]
- newTile[3] = []
- newTile[4] = []
- this.map.mapData.tiles.push(newTile)
- return
- }
- if (event.buttons == 2) {
- if (tile[1][0] != this.secondaryTile.x ||
- tile[1][1] != this.secondaryTile.y ||
- tile[1][2] != this.secondaryTile.atlas) {
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- tile[1] = [this.secondaryTile.x, this.secondaryTile.y, this.secondaryTile.atlas]
- }
- } else if (event.buttons == 1) {
- if (tile[1][0] != this.primaryTile.x ||
- tile[1][1] != this.primaryTile.y ||
- tile[1][2] != this.primaryTile.atlas) {
- let copiedTile = JSON.parse(JSON.stringify(tile))
- this.history.push({ "position": worldClickPosition, "previous": copiedTile })
- tile[1] = [this.primaryTile.x, this.primaryTile.y, this.primaryTile.atlas]
- }
- }
- } catch (e) {
- }
- break;
- }
- }
- }
|