diff --git a/app/res/tileset/hole.png b/app/res/tileset/hole.png new file mode 100644 index 0000000..80d4bf4 Binary files /dev/null and b/app/res/tileset/hole.png differ diff --git a/app/res/tileset/new_wall_stone.png b/app/res/tileset/new_wall_stone.png index ef07fac..3b6ded9 100644 Binary files a/app/res/tileset/new_wall_stone.png and b/app/res/tileset/new_wall_stone.png differ diff --git a/app/res/ui/icon/detail.png b/app/res/ui/icon/detail.png new file mode 100644 index 0000000..4a9d59c Binary files /dev/null and b/app/res/ui/icon/detail.png differ diff --git a/app/res/ui/icon/ground.png b/app/res/ui/icon/floor.png similarity index 100% rename from app/res/ui/icon/ground.png rename to app/res/ui/icon/floor.png diff --git a/app/res/ui/icon/virtual_dungeon.png b/app/res/ui/icon/virtual_dungeon.png deleted file mode 100644 index 3f865db..0000000 Binary files a/app/res/ui/icon/virtual_dungeon.png and /dev/null differ diff --git a/app/src/components/App.tsx b/app/src/components/App.tsx index 4e4c49a..6bab52d 100644 --- a/app/src/components/App.tsx +++ b/app/src/components/App.tsx @@ -49,12 +49,16 @@ export default function App() {
+ + + + - + - +
diff --git a/app/src/components/route/AssetRoute.tsx b/app/src/components/route/AssetRoute.tsx new file mode 100644 index 0000000..6271108 --- /dev/null +++ b/app/src/components/route/AssetRoute.tsx @@ -0,0 +1,57 @@ +import * as Preact from 'preact'; +import { useAppData } from '../../Hooks'; +import { Switch, Route, Redirect, useParams, useHistory } from 'react-router-dom'; + +import Button from '../Button'; + +export default function AssetRoute() { + const [ { assets } ] = useAppData('assets'); + if (!assets) return null; + + const history = useHistory(); + const { id } = useParams<{ id: string }>(); + const currentAsset = (assets ?? []).filter(a => a.identifier === id)[0]; + + if (!currentAsset) return ; + + const handleDeleteAsset = () => { + fetch('/data/asset/delete', { + method: 'POST', cache: 'no-cache', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ identifier: id }) + }); + + history.push('/assets'); + }; + + return ( +
+ +
+ + + +
+
+ ); +} diff --git a/app/src/components/route/AssetsRoute.tsx b/app/src/components/route/AssetsRoute.tsx index 4a5c339..1915ce3 100644 --- a/app/src/components/route/AssetsRoute.tsx +++ b/app/src/components/route/AssetsRoute.tsx @@ -1,30 +1,37 @@ import * as Preact from 'preact'; -import { Switch, Route, Redirect, NavLink as Link } from 'react-router-dom'; +import { useAppData } from '../../Hooks'; +import { Switch, Route, NavLink as Link, useHistory } from 'react-router-dom'; +import AssetList from '../view/AssetList'; import NewAssetForm from '../view/NewAssetForm'; -import MyAssetsList from '../view/MyAssetsList'; +import AssetCollectionList from '../view/AssetCollectionList'; export default function AssetsRoute() { + const history = useHistory(); + const [ { assets, collections, user } ] = useAppData([ 'assets', 'collections', 'user' ]); return (
- {/* -

Storefront

+ + a.user === user!.user)} + onClick={(user, identifier) => history.push(`/asset/${user}/${identifier}`)} + onNew={() => history.push('/assets/new')} /> - -

Subscribed

-
*/} - + + + history.push(`/assets/collection/${user}/${identifier}`)} /> + + - + {/* */}
diff --git a/app/src/components/route/CollectionRoute.tsx b/app/src/components/route/CollectionRoute.tsx new file mode 100644 index 0000000..55b38f1 --- /dev/null +++ b/app/src/components/route/CollectionRoute.tsx @@ -0,0 +1,59 @@ +import * as Preact from 'preact'; +import { useState } from 'preact/hooks'; +import { useAppData } from '../../Hooks'; +import { Redirect, useParams } from 'react-router-dom'; + +import AssetList from '../view/AssetList'; + +export default function CollectionRoute() { + const [ { collections, assets },, mergeData ] = useAppData([ 'collections', 'assets' ]); + if (!collections) return null; + + const [ adding, setAdding ] = useState(false); + + const { id } = useParams<{ id: string }>(); + const currentCollection = (collections ?? []).filter(c => c.identifier === id)[0]; + if (!currentCollection) return ; + + const collectionAssets = (assets || []).filter(({ user, identifier }) => currentCollection.items.includes(user + ':' + identifier)); + + const handleAddingAsset = () => { + setAdding(true); + }; + + const handleAddAsset = async (user: string, identifier: string) => { + const res = await fetch('/data/collection/add', { + method: 'POST', cache: 'no-cache', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ collection: currentCollection.identifier, asset: user + ':' + identifier }) + }); + + if (res.status !== 200) console.error(await res.text()); + else { + const data = await res.json(); + await mergeData(data); + } + + setAdding(false); + }; + + return ( +
+ +
+ {!adding && {/**/}} onNew={handleAddingAsset} newText='Add Asset' />} + {adding && +

Add Asset

+ +
} +
+
+ ); +} diff --git a/app/src/components/route/Routes.ts b/app/src/components/route/Routes.ts index 2d3e7e6..46f12d4 100644 --- a/app/src/components/route/Routes.ts +++ b/app/src/components/route/Routes.ts @@ -8,6 +8,8 @@ export { default as Map } from './MapRoute'; export { default as Campaign } from './CampaignRoute'; export { default as Campaigns } from './CampaignsRoute'; +export { default as Asset } from './AssetRoute'; export { default as Assets } from './AssetsRoute'; +export { default as Collection } from './CollectionRoute'; export { default as Editor } from './EditorRoute'; diff --git a/app/src/components/view/AssetCollectionList.sass b/app/src/components/view/AssetCollectionList.sass new file mode 100644 index 0000000..32254a2 --- /dev/null +++ b/app/src/components/view/AssetCollectionList.sass @@ -0,0 +1,87 @@ +@use '../../style/text' +@use '../../style/grid' +@use '../../style/def' as * + +.AssetCollectionList + max-width: 1000px + display: block + margin: 0 auto + margin-top: 56px + + .AssetCollectionList-Grid + @include grid.auto_width(160px, 16px) + + .AssetCollectionList-CollectionWrap + .AssetCollectionList-Collection + width: 100% + height: 0 + border: none + display: grid + user-select: none + position: relative + padding-bottom: 100% + + overflow: hidden + border-radius: 4px + text-decoration: none + background-color: $neutral-200 + box-shadow: 0px 2px 12px 0px transparentize($neutral-000, 0.9) + + .AssetCollectionList-CollectionInner + position: absolute + top: 0 + left: 0 + width: 100% + height: 100% + display: grid + grid-template-rows: 1fr auto + + .AssetCollectionList-CollectionPreview + position: relative + padding: 8px + overflow: hidden + background-color: $neutral-100 + + img + width: 100% + height: 100% + + user-select: none + object-fit: contain + pointer-events: none + image-rendering: pixelated + + .AssetCollectionList-CollectionTitle + @include text.line_clamp + + margin: 0 + font-size: 18px + padding: 12px + + .AssetCollectionList-NewCollection + width: 100% + display: flex + user-select: none + flex-direction: column + justify-content: center + align-items: center + + overflow: hidden + border-radius: 4px + background-color: $neutral-100 + box-shadow: 0px 2px 12px 0px transparentize($neutral-000, 0.9) + + border: 3px dotted $neutral-300 + + min-height: 180px + height: 100% + + img + width: 80px + height: 80px + image-rendering: pixelated + + p + font-size: 18px + margin: 0 + margin-top: 4px diff --git a/app/src/components/view/AssetCollectionList.tsx b/app/src/components/view/AssetCollectionList.tsx new file mode 100644 index 0000000..b0da310 --- /dev/null +++ b/app/src/components/view/AssetCollectionList.tsx @@ -0,0 +1,40 @@ +import * as Preact from 'preact'; + +import './AssetCollectionList.sass'; + +import { AssetCollection } from '../../../../common/DBStructs'; + +interface Props { + collections: AssetCollection[]; + onNew?: () => void; + onClick: (user: string, identifier: string) => void; +} + +export default function AssetCollectionList({ collections, onNew, onClick }: Props) { + return ( +
+ {collections === undefined &&

Loading Collections...

} + {collections !== undefined && + +
    + {collections.map(c =>
  • + +
  • )} +
  • + {onNew && } +
  • +
+
+ } +
+ ); +} diff --git a/app/src/components/view/MyAssetsList.sass b/app/src/components/view/AssetList.sass similarity index 86% rename from app/src/components/view/MyAssetsList.sass rename to app/src/components/view/AssetList.sass index e1ddb42..0554905 100644 --- a/app/src/components/view/MyAssetsList.sass +++ b/app/src/components/view/AssetList.sass @@ -2,18 +2,20 @@ @use '../../style/grid' @use '../../style/def' as * -.AssetsList +.AssetList max-width: 1000px display: block margin: 0 auto margin-top: 56px - .AssetsList-Grid + .AssetList-Grid @include grid.auto_width(160px, 16px) - .AssetsList-AssetWrap - .AssetsList-Asset + .AssetList-AssetWrap + .AssetList-Asset + width: 100% height: 0 + border: none display: grid user-select: none position: relative @@ -25,7 +27,7 @@ background-color: $neutral-200 box-shadow: 0px 2px 12px 0px transparentize($neutral-000, 0.9) - .AssetsList-AssetInner + .AssetList-AssetInner position: absolute top: 0 left: 0 @@ -34,7 +36,7 @@ display: grid grid-template-rows: 1fr auto - .AssetsList-AssetPreview + .AssetList-AssetPreview position: relative padding: 8px overflow: hidden @@ -49,14 +51,15 @@ pointer-events: none image-rendering: pixelated - .AssetsList-AssetTitle + .AssetList-AssetTitle @include text.line_clamp margin: 0 font-size: 18px padding: 12px - .AssetsList-NewAsset + .AssetList-NewAsset + width: 100% display: flex user-select: none flex-direction: column diff --git a/app/src/components/view/AssetList.tsx b/app/src/components/view/AssetList.tsx new file mode 100644 index 0000000..ee72f9b --- /dev/null +++ b/app/src/components/view/AssetList.tsx @@ -0,0 +1,43 @@ +import * as Preact from 'preact'; + +import './AssetList.sass'; + +import { Asset } from '../../../../common/DBStructs'; + +interface Props { + assets: Asset[]; + + newText?: string; + onNew?: () => void; + onClick: (user: string, identifier: string) => void; +} + +export default function AssetList({ assets, newText, onNew, onClick }: Props) { + return ( +
+ {assets === undefined &&

Loading Assets...

} + {assets !== undefined && + +
    + {assets.map(a =>
  • + +
  • )} +
  • + {onNew && } +
  • +
+
+ } +
+ ); +} diff --git a/app/src/components/view/MyAssetsList.tsx b/app/src/components/view/MyAssetsList.tsx deleted file mode 100644 index bbdfbe3..0000000 --- a/app/src/components/view/MyAssetsList.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as Preact from 'preact'; -import { useAppData } from '../../Hooks'; -import { NavLink as Link } from 'react-router-dom'; - -import './MyAssetsList.sass'; - -export default function MyAssetsList() { - const [ { assets } ] = useAppData('assets'); - - return ( -
- {assets === undefined &&

Loading Assets...

} - {assets !== undefined && - -
    - {assets.map(a =>
  • - -
    -
    - -
    -

    {a.name || 'Untitled'}

    -
    - -
  • )} -
  • - - -

    Upload Asset

    - -
  • -
-
- } -
- ); -} diff --git a/app/src/components/view/NewAssetForm.sass b/app/src/components/view/NewAssetForm.sass index ef1e1a3..aa9dfda 100644 --- a/app/src/components/view/NewAssetForm.sass +++ b/app/src/components/view/NewAssetForm.sass @@ -25,6 +25,13 @@ display: grid grid-gap: 16px grid-template-columns: 1fr 1fr + + .NewAssetForm-Col2-60 + @extend .NewAssetForm-Col2 + grid-template-columns: 5fr 4fr + + & > div:nth-child(2) + text-align: right .NewAssetForm-UploadWrap @extend %material_button diff --git a/app/src/components/view/NewAssetForm.tsx b/app/src/components/view/NewAssetForm.tsx index 9dc0cb4..09b6581 100644 --- a/app/src/components/view/NewAssetForm.tsx +++ b/app/src/components/view/NewAssetForm.tsx @@ -16,7 +16,7 @@ export default function NewAssetForm() { const [ queryState, setQueryState ] = useState<'idle' | 'querying'>('idle'); - const [ type, setType ] = useState<'wall' | 'ground' | 'token'>('token'); + const [ type, setType ] = useState<'wall' | 'floor' | 'detail' | 'token'>('token'); const [ tokenType, setTokenType ] = useState<1 | 4 | 8>(4); const [ file, setFile ] = useState(null); @@ -25,7 +25,7 @@ export default function NewAssetForm() { const [ name, setName ] = useState(''); const [ identifier, setIdentifier ] = useState(''); - const handleSetType = (type: 'wall' | 'ground' | 'token') => { + const handleSetType = (type: 'wall' | 'floor' | 'detail' | 'token') => { setType(type); }; @@ -69,12 +69,9 @@ export default function NewAssetForm() { body: data }); - if (res.status === 202) { - console.log('hellyea!'); - history.push('/assets'); - } + if (res.status === 200) history.push('/assets'); else { - console.log('hellnah', await res.text()); + console.error(await res.text()); setQueryState('idle'); } }; @@ -83,12 +80,13 @@ export default function NewAssetForm() {

New Asset

-
+
diff --git a/app/src/editor/ArchitectMode.ts b/app/src/editor/ArchitectMode.ts index 944fc89..426c9c5 100755 --- a/app/src/editor/ArchitectMode.ts +++ b/app/src/editor/ArchitectMode.ts @@ -1,7 +1,7 @@ import type MapScene from './scene/MapScene'; -import Layer from './util/Layer'; import { Vec2 } from './util/Vec'; +import { Layer } from './util/Layer'; export default class ArchitectMode { scene: MapScene; @@ -16,7 +16,7 @@ export default class ArchitectMode { pointerPrimaryDown: boolean = false; activeTileset: number = 0; - activeLayer: Layer = Layer.wall; + activeLayer: Layer = 'wall'; manipulated: {pos: Vec2; layer: Layer; lastTile: number; tile: number}[] = []; @@ -25,19 +25,18 @@ export default class ArchitectMode { } init() { - // Create cursor hover sprite this.cursor = this.scene.add.sprite(0, 0, 'cursor'); - this.cursor.setScale(4, 4); this.cursor.setDepth(1000); this.cursor.setOrigin(0, 0); + this.cursor.setScale(1 / 16); } update() { this.active = true; this.cursor!.setVisible(true); - let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x / 64), Math.floor(this.scene.view.cursorWorld.y / 64)); - this.cursor!.setPosition(selectedTilePos.x * 64, selectedTilePos.y * 64); + let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x), Math.floor(this.scene.view.cursorWorld.y)); + this.cursor!.setPosition(selectedTilePos.x, selectedTilePos.y); this.cursor!.setVisible((selectedTilePos.x >= 0 && selectedTilePos.y >= 0 && selectedTilePos.x < this.scene.map.size.x && selectedTilePos.y < this.scene.map.size.y)); @@ -91,7 +90,7 @@ export default class ArchitectMode { if (Math.abs(b.x - a.x) > Math.abs(b.y - a.y)) b.y = a.y; else b.x = a.x; - this.cursor!.setPosition(b.x * 64, b.y * 64); + this.cursor!.setPosition(b.x, b.y); this.primitives.forEach((v) => v.destroy()); this.primitives = []; @@ -100,21 +99,19 @@ export default class ArchitectMode { this.primitives.forEach((v) => { v.setOrigin(0, 0); - v.setScale(64, 64); - v.setLineWidth(0.03); v.setDepth(300); + v.setLineWidth(0.03); }); - this.primitives.push(this.scene.add.sprite(this.startTilePos.x * 64, this.startTilePos.y * 64, - 'cursor') as any as Phaser.GameObjects.Line); + this.primitives.push(this.scene.add.sprite(this.startTilePos.x, this.startTilePos.y, 'cursor') as any); this.primitives[1].setOrigin(0, 0); - this.primitives[1].setScale(4, 4); + this.primitives[1].setScale(1 / 16); this.primitives[1].setAlpha(0.5); } else if (!this.scene.i.mouseLeftDown() && !this.scene.i.mouseRightDown() && this.pointerDown) { - let a = new Vec2(this.startTilePos.x * 64, this.startTilePos.y * 64); - let b = new Vec2(selectedTilePos.x * 64, selectedTilePos.y * 64); + let a = new Vec2(this.startTilePos.x, this.startTilePos.y); + let b = new Vec2(selectedTilePos.x, selectedTilePos.y); if (Math.abs(b.x - a.x) > Math.abs(b.y - a.y)) b.y = a.y; else b.x = a.x; @@ -125,12 +122,12 @@ export default class ArchitectMode { change.y /= normalizeFactor; while (Math.abs(b.x - a.x) >= 1 || Math.abs(b.y - a.y) >= 1) { - this.placeTileAndPushManip(new Vec2(Math.floor(a.x / 64), Math.floor(a.y / 64)), this.pointerPrimaryDown); + this.placeTileAndPushManip(new Vec2(Math.floor(a.x), Math.floor(a.y)), this.pointerPrimaryDown); a.x += change.x; a.y += change.y; } - this.placeTileAndPushManip(new Vec2(b.x / 64, b.y / 64), this.pointerPrimaryDown); + this.placeTileAndPushManip(new Vec2(b.x, b.y), this.pointerPrimaryDown); this.primitives.forEach((v) => v.destroy()); this.primitives = []; } @@ -154,7 +151,6 @@ export default class ArchitectMode { this.primitives.forEach((v) => { v.setOrigin(0, 0); - v.setScale(64, 64); v.setLineWidth(0.03); v.setDepth(300); }); @@ -180,14 +176,14 @@ export default class ArchitectMode { let change = new Vec2(this.scene.view.cursorWorld.x - this.scene.view.lastCursorWorld.x, this.scene.view.cursorWorld.y - this.scene.view.lastCursorWorld.y); - let normalizeFactor = Math.sqrt(change.x * change.x + change.y * change.y); + let normalizeFactor = Math.sqrt(change.x * change.x + change.y * change.y) * 10; change.x /= normalizeFactor; change.y /= normalizeFactor; let place = new Vec2(this.scene.view.lastCursorWorld.x, this.scene.view.lastCursorWorld.y); - while (Math.abs(this.scene.view.cursorWorld.x - place.x) >= 1 || Math.abs(this.scene.view.cursorWorld.y - place.y) >= 1) { - this.placeTileAndPushManip(new Vec2(Math.floor(place.x / 64), Math.floor(place.y / 64)), this.scene.i.mouseLeftDown()); + while (Math.abs(this.scene.view.cursorWorld.x - place.x) >= 0.1 || Math.abs(this.scene.view.cursorWorld.y - place.y) >= 0.1) { + this.placeTileAndPushManip(new Vec2(Math.floor(place.x), Math.floor(place.y)), this.scene.i.mouseLeftDown()); place.x += change.x; place.y += change.y; } @@ -198,12 +194,12 @@ export default class ArchitectMode { placeTileAndPushManip(manipPos: Vec2, solid: boolean) { let tile = solid ? this.activeTileset : -1; - let layer = (tile === -1 && this.activeLayer === Layer.floor) ? Layer.wall : this.activeLayer; + let layer = (tile === -1 && this.activeLayer === 'floor') ? 'wall' : this.activeLayer; - let lastTile = this.scene.map.getTileset(layer, manipPos.x, manipPos.y); + let lastTile = this.scene.map.activeLayer.getTile(layer, manipPos.x, manipPos.y); if (tile === lastTile) return; - this.scene.map.setTile(layer, tile, manipPos.x, manipPos.y); + this.scene.map.activeLayer.setTile(layer, tile, manipPos.x, manipPos.y); this.manipulated.push({ pos: manipPos, @@ -211,7 +207,6 @@ export default class ArchitectMode { lastTile: lastTile, tile: tile }); - } cleanup() { diff --git a/app/src/editor/MapChunk.ts b/app/src/editor/MapChunk.ts deleted file mode 100755 index fc5c305..0000000 --- a/app/src/editor/MapChunk.ts +++ /dev/null @@ -1,96 +0,0 @@ -import MapData from './MapData'; - -import Layer from './util/Layer'; -import { Vec2 } from './util/Vec'; - -export default class MapChunk { - static CHUNK_SIZE = 16; - static TILE_SIZE = 16; - static DIRTY_LIMIT = 32; - - private pos: Vec2; - private map: MapData; - private canvas: Phaser.GameObjects.RenderTexture; - - private dirtyList: Vec2[] = []; - private fullyDirty: boolean = false; - - constructor(map: MapData, x: number, y: number) { - this.pos = new Vec2(x, y); - this.canvas = map.scene.add.renderTexture( - x * MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE * 4, y * MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE * 4, - MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE, MapChunk.CHUNK_SIZE * MapChunk.TILE_SIZE); - - this.map = map; - this.canvas.setScale(4); - this.canvas.setOrigin(0, 0); - - for (let i = 0; i < MapChunk.CHUNK_SIZE * MapChunk.CHUNK_SIZE; i++) { - let x = i % MapChunk.CHUNK_SIZE; - let y = Math.floor(i / MapChunk.CHUNK_SIZE); - - let mX = x + this.pos.x * MapChunk.CHUNK_SIZE; - let mY = y + this.pos.y * MapChunk.CHUNK_SIZE; - if (mX >= this.map.size.x || mY >= this.map.size.y) continue; - - this.drawTile(x, y); - } - } - - dirty(pos: Vec2): void { - if (!this.fullyDirty) { - for (let v of this.dirtyList) if (v.equals(pos)) return; - this.dirtyList.push(pos); - - if (this.dirtyList.length > MapChunk.DIRTY_LIMIT) { - this.fullyDirty = true; - this.dirtyList = []; - } - } - } - - rebuild(): boolean { - if (this.fullyDirty) { - for (let i = 0; i < MapChunk.CHUNK_SIZE * MapChunk.CHUNK_SIZE; i++) { - let x = i % MapChunk.CHUNK_SIZE; - let y = Math.floor(i / MapChunk.CHUNK_SIZE); - - let mX = x + this.pos.x * MapChunk.CHUNK_SIZE; - let mY = y + this.pos.y * MapChunk.CHUNK_SIZE; - if (mX >= this.map.size.x || mY >= this.map.size.y) continue; - - this.drawTile(x, y); - } - this.fullyDirty = false; - return true; - } - - if (this.dirtyList.length === 0) return false; - - for (let elem of this.dirtyList) this.drawTile(elem.x, elem.y); - this.dirtyList = []; - return true; - } - - private drawTile(x: number, y: number): void { - let mX = x + this.pos.x * MapChunk.CHUNK_SIZE; - let mY = y + this.pos.y * MapChunk.CHUNK_SIZE; - - let wallTile = this.map.getTile(Layer.wall, mX, mY); - if (this.map.getTileset(Layer.wall, mX, mY) === -1 || (wallTile < 54 || wallTile > 60)) { - this.canvas.drawFrame(this.map.manager.groundLocations[this.map.getTileset(Layer.floor, mX, mY)].identifier, - this.map.getTile(Layer.floor, mX, mY), x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - - if (this.map.getTileset(Layer.overlay, mX, mY) !== -1) - this.canvas.drawFrame(this.map.manager.overlayLocations[this.map.getTileset(Layer.overlay, mX, mY)].identifier, - this.map.getTile(Layer.overlay, mX, mY), x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - } - - if (this.map.getTileset(Layer.wall, mX, mY) !== -1) - this.canvas.drawFrame(this.map.manager.wallLocations[this.map.getTileset(Layer.wall, mX, mY)].identifier, - this.map.getTile(Layer.wall, mX, mY), x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - - if ((x % 2 === 0 && y % 2 === 0) || (x % 2 !== 0 && y % 2 !== 0)) - this.canvas.drawFrame('grid_tile', 0, x * MapChunk.TILE_SIZE, y * MapChunk.TILE_SIZE); - } -} diff --git a/app/src/editor/MapData.ts b/app/src/editor/MapData.ts deleted file mode 100755 index f3d0cae..0000000 --- a/app/src/editor/MapData.ts +++ /dev/null @@ -1,198 +0,0 @@ -import MapChunk from './MapChunk'; -import * as SmartTiler from './SmartTiler'; -import type MapScene from './scene/MapScene'; -import TilesetManager from './TilesetManager'; - -import Layer from './util/Layer'; -import { Vec2 } from './util/Vec'; -import { Asset } from './util/Asset'; -import { clamp } from './util/Helpers'; - -export default class MapData { - scene: MapScene; - size: Vec2 = new Vec2(0, 0); - - savedMapData: number[][] = []; - - manager: TilesetManager; - private layers: {[key: number]: { tiles: number[][]; tilesets: number[][] }} = {}; - private chunks: MapChunk[][] = []; - - constructor(scene: MapScene) { - this.scene = scene; - this.manager = new TilesetManager(scene); - } - - init(size: Vec2, assets: Asset[]) { - this.size = size; - this.manager.init(assets); - - this.registerLayer(Layer.floor, () => Math.floor(Math.random() * 6) + 54, 0); - this.registerLayer(Layer.wall, 0, -1); - this.registerLayer(Layer.overlay, 0, -1); - - for (let i = 0; i < Math.ceil(size.y / MapChunk.CHUNK_SIZE); i++) { - this.chunks[i] = []; - for (let j = 0; j < Math.ceil(size.x / MapChunk.CHUNK_SIZE); j++) { - this.chunks[i][j] = new MapChunk(this, j, i); - } - } - } - - update(): void { - let start = Date.now(); - for (let arr of this.chunks) for (let chunk of arr) { - chunk.rebuild(); - if (Date.now() - start > 10) break; - } - - if (this.scene.i.keyPressed('S')) this.saveMap(); - if (this.scene.i.keyPressed('L')) this.loadMap(this.savedMapData); - } - - setTile(layer: Layer, tileset: number, xx: number | Vec2, yy?: number): boolean { - let x: number, y: number; - if (xx instanceof Vec2) { x = xx.x; y = xx.y; } - else { x = xx; y = yy!; } - - if (x < 0 || y < 0 || x >= this.size.x || y >= this.size.y) return false; - - let oldTileset = this.getTileset(layer, x, y); - if (oldTileset === tileset) return false; - this.setTileset(layer, x, y, tileset); - this.smartTile(x, y); - - return true; - } - - setTileset(key: number, x: number | Vec2, a?: number, b?: number): void { - if (x instanceof Vec2) this.layers[key].tilesets[x.y][x.x] = a!; - else this.layers[key].tilesets[a!][x] = b!; - } - - getTile(key: number, xx: number | Vec2, yy?: number): number { - let x: number, y: number; - if (xx instanceof Vec2) { x = xx.x; y = xx.y; } - else { x = xx; y = yy!; } - - return this.layers[key].tiles[clamp(y, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; - } - - getTileset(key: number, xx: number | Vec2, yy?: number): number { - let x: number, y: number; - if (xx instanceof Vec2) { x = xx.x; y = xx.y; } - else { x = xx; y = yy!; } - - return this.layers[key].tilesets[clamp(y, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; - } - - private smartTile(x: number, y: number): void { - for (let i = clamp(x - 1, this.size.x - 1, 0); i <= clamp(x + 1, this.size.x - 1, 0); i++) { - for (let j = clamp(y - 1, this.size.y - 1, 0); j <= clamp(y + 1, this.size.y - 1, 0); j++) { - const solids = this.getTilesetsAt(Layer.wall, i, j).map(i => i !== -1); - - // const bits = SmartTiler.bitsToIndices(solids); - // if (i === x && j === y) console.log(bits, Math.floor(bits / 16), bits % 16); - - const wall = SmartTiler.wall(solids, this.getTile(Layer.wall, i, j)); - if (wall !== -1) this.setTileRaw(Layer.wall, i, j, wall); - - const floor = SmartTiler.floor(solids, this.getTile(Layer.floor, i, j)); - if (floor !== -1) this.setTileRaw(Layer.floor, i, j, floor); - - const overlay = SmartTiler.overlay(this.getTilesetsAt(Layer.overlay, i, j) - .map(t => t === this.getTileset(Layer.overlay, i, j)), this.getTileset(Layer.overlay, i, j)); - if (overlay !== -1) this.setTileRaw(Layer.overlay, i, j, overlay); - } - } - - // this.scene.lighting.tileUpdatedAt(x, y); - } - - private setTileRaw(key: number, x: number | Vec2, a?: number, b?: number, c?: number): void { - if (x instanceof Vec2) { - this.layers[key].tiles[x.y][x.x] = a!; - if (b !== undefined) this.setTileset(key, x, b); - - this.chunks[Math.floor(x.y / MapChunk.CHUNK_SIZE)][Math.floor(x.x / MapChunk.CHUNK_SIZE)] - .dirty(new Vec2(x.x % MapChunk.CHUNK_SIZE, x.y % MapChunk.CHUNK_SIZE)); - } - else { - this.layers[key].tiles[a!][x] = b!; - if (c !== undefined) this.setTileset(key, x, a, c); - - this.chunks[Math.floor(a! / MapChunk.CHUNK_SIZE)][Math.floor(x / MapChunk.CHUNK_SIZE)] - .dirty(new Vec2(x % MapChunk.CHUNK_SIZE, a! % MapChunk.CHUNK_SIZE)); - } - } - - private getTilesetsAt(layer: Layer, x: number, y: number): number[] { - let tilesets: number[] = []; - for (let i = -1; i <= 1; i++) - for (let j = -1; j <= 1; j++) - tilesets.push(this.getTileset(layer, clamp(x + j, 0, this.size.x - 1), clamp(y + i, 0, this.size.y - 1))); - return tilesets; - } - - private registerLayer(key: number, startTile: any = 0, startTileset: number = -1): void { - let layer: {tiles: number[][]; tilesets: number[][]} = { tiles: [], tilesets: [] }; - - for (let i = 0; i < this.size.y; i++) { - layer.tiles[i] = []; - layer.tilesets[i] = []; - for (let j = 0; j < this.size.x; j++) { - let tile = typeof(startTile) === 'number' ? startTile : startTile(); - layer.tiles[i][j] = tile; - layer.tilesets[i][j] = startTileset; - } - } - - this.layers[key] = layer; - } - - private saveMap() { - - let mapData: number[][] = []; - - for (let k = 0; k < 3; k++) { - let tile = 0; - let count = 0; - mapData[k] = []; - - for (let i = 0; i < this.size.x * this.size.y; i++) { - let x = i % this.size.x; - let y = Math.floor(i / this.size.x); - - if (this.getTileset(k, x, y) === tile) count++; - else { - if (i !== 0) { - mapData[k].push(tile); - mapData[k].push(count); - } - tile = this.getTileset(k, x, y); - count = 1; - } - } - } - - this.savedMapData = mapData; - } - - private loadMap(dat: number[][]) { - for (let k = 0; k < 3; k++) { - let offset = 0; - for (let i = 0; i < dat[k].length / 2; i++) { - let tile = dat[k][i * 2]; - let count = dat[k][i * 2 + 1]; - - for (let t = 0; t < count; t++) { - let x = (offset + t) % this.size.x; - let y = Math.floor((offset + t) / this.size.x); - - this.setTile(k, tile, x, y); - } - offset += count; - } - } - } -} diff --git a/app/src/editor/SmartTiler.ts b/app/src/editor/SmartTiler.ts deleted file mode 100755 index 6b0d847..0000000 --- a/app/src/editor/SmartTiler.ts +++ /dev/null @@ -1,68 +0,0 @@ -const wallField = [ - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 14, 14, 11, 11, 14, 14, 11, 11, 1, 1, 13, 48, 1, 1, 13, 48, 0, 0, 12, 12, 0, 0, 47, 47, 3, 3, 25, 46, 3, 3, 45, 20, - 14, 14, 11, 11, 14, 14, 11, 11, 8, 8, 39, 23, 8, 8, 39, 23, 0, 0, 12, 12, 0, 0, 47, 47, 41, 41, 37, 29, 41, 41, 51, 18, - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 33, 33, 6, 6, 33, 33, 6, 6, 5, 5, 10, 17, 5, 5, 10, 17, 15, 15, 9, 9, 15, 15, 16, 16, 2, 2, 4, 50, 2, 2, 49, 32, - 14, 14, 11, 11, 14, 14, 11, 11, 1, 1, 13, 48, 1, 1, 13, 48, 7, 7, 38, 38, 7, 7, 22, 22, 40, 40, 36, 42, 40, 40, 30, 19, - 14, 14, 11, 11, 14, 14, 11, 11, 8, 8, 39, 23, 8, 8, 39, 23, 7, 7, 38, 38, 7, 7, 22, 22, 31, 31, 21, 27, 31, 31, 28, 54 -]; - -const floorField = [ - 54, 20, 19, 19, 18, 4, 19, 19, 11, 11, 3, 3, 51, 51, 3, 3, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, - 2, 12, 32, 32, 34, 6, 32, 32, 11, 11, 3, 3, 51, 51, 3, 3, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, - 0, 33, 31, 31, 14, 7, 31, 31, 42, 42, 27, 27, 36, 36, 27, 27, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, - 22, 15, 28, 28, 16, 37, 28, 28, 42, 42, 27, 27, 36, 36, 27, 27, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, - 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49 -]; - -export function bitsToIndices(walls: boolean[]) { - return ( - (+walls[0] << 0) + - (+walls[1] << 1) + - (+walls[2] << 2) + - (+walls[3] << 3) + - (+walls[5] << 4) + - (+walls[6] << 5) + - (+walls[7] << 6) + - (+walls[8] << 7)); -} - -export function wall(walls: boolean[], current: number): number { - if (current === -1) return -1; - const ind = wallField[bitsToIndices(walls)]; - if (ind < 54) return ind; - return 54 + Math.floor(Math.random() * 6); -} - -export function overlay(overlays: boolean[], current: number): number { - if (current === -1) return -1; - const ind = floorField[bitsToIndices(overlays)]; - if (ind < 54) return ind; - return 54 + Math.floor(Math.random() * 6); -} - -export function floor(walls: boolean[], current: number): number { - if (current === -1) return -1; - const ind = floorField[bitsToIndices(walls)]; - if (ind < 54) return ind; - return 54 + Math.floor(Math.random() * 6); -} - -// const l = []; -// for (let i = 0; i < (1 << 8); ++i ) { -// let arr = [ i & 0x001, i & 0x002, i & 0x004, i & 0x008, 0, -// i & 0x010, i & 0x020, i & 0x040, i & 0x080 ].map(a => !!a); -// l.push(overlay(arr, 1)); -// } - -// let lines = []; -// for (let i = 0; i < 16; i++) { -// lines.push(l.slice(i * 16, (i + 1) * 16).map(n => n < 10 ? n + ', ' : n + ',').join(' ')); -// } - -// console.log(lines.join('\n')); diff --git a/app/src/editor/TilesetManager.ts b/app/src/editor/TilesetManager.ts deleted file mode 100755 index 458705c..0000000 --- a/app/src/editor/TilesetManager.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type MapScene from './scene/MapScene'; - -import Layer from './util/Layer'; -import { Asset } from './util/Asset'; - -export default class TilesetManager { - scene: MapScene; - - wallLocations: {[index: number]: { res: number; ind: number; identifier: string }} = {}; - groundLocations: {[index: number]: { res: number; ind: number; identifier: string }} = {}; - overlayLocations: {[index: number]: { res: number; ind: number; identifier: string }} = {}; - indexes: {[tileset_key: string]: number} = {}; - - private currentWallInd: number = 0; - private currentGroundInd: number = 0; - private currentOverlayInd: number = 0; - - constructor(scene: MapScene) { - this.scene = scene; - } - - init(assets: Asset[]) { - for (let tileset of assets.filter(a => a.type === 'wall' )) this.addTileset(tileset.identifier, Layer.wall); - for (let tileset of assets.filter(a => a.type === 'ground' )) this.addTileset(tileset.identifier, Layer.floor); - // for (let tileset of assets.filter(a => a.type === 'overlay')) this.addTileset(tileset.key, Layer.overlay); - } - - private addTileset(identifier: string, layer: Layer): void { - let res = this.scene.textures.get(identifier).getSourceImage(0).width / 9; - let ind = (layer === Layer.wall ? this.currentWallInd : layer === Layer.floor ? this.currentGroundInd : this.currentOverlayInd); - this[layer === Layer.wall ? 'wallLocations' : layer === Layer.floor ? - 'groundLocations' : 'overlayLocations'][ind] = { res, ind, identifier }; - this.indexes[identifier] = ind; - - if (layer === Layer.wall) this.currentWallInd++; - else if (layer === Layer.floor) this.currentGroundInd++; - else this.currentOverlayInd++; - } -} diff --git a/app/src/editor/TilesetPatcher.ts b/app/src/editor/TilesetPatcher.ts index add76fa..b882805 100644 --- a/app/src/editor/TilesetPatcher.ts +++ b/app/src/editor/TilesetPatcher.ts @@ -5,154 +5,160 @@ import { Vec2, Vec4 } from './util/Vec'; export default class TilesetPatcher { constructor(private scene: Phaser.Scene) {} - patch(tileset_key: string, tile_size: number): void { - const s = Date.now(); + async patch(tileset_key: string, tile_size: number): Promise { + return new Promise(resolve => { + const s = Date.now(); - const canvas = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, 10 * tile_size, 5 * tile_size); - canvas.draw(tileset_key); + const canvas = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, 10 * tile_size, 5 * tile_size); + canvas.draw(tileset_key); - let part: Phaser.GameObjects.Sprite | Phaser.GameObjects.RenderTexture - = new Phaser.GameObjects.Sprite(this.scene, 0, 0, tileset_key); - part.setOrigin(0, 0); + let part: Phaser.GameObjects.Sprite | Phaser.GameObjects.RenderTexture + = new Phaser.GameObjects.Sprite(this.scene, 0, 0, tileset_key, '__BASE'); + part.setOrigin(0, 0); - function draw(source: Vec4, dest: Vec2) { - part.setCrop(source.x * tile_size, source.y * tile_size, (source.z - source.x) * tile_size, (source.w - source.y) * tile_size); - part.setPosition((dest.x - source.x) * tile_size, (dest.y - source.y) * tile_size); - canvas.draw(part); - } + function draw(source: Vec4, dest: Vec2) { + part.setCrop(source.x * tile_size, source.y * tile_size, (source.z - source.x) * tile_size, (source.w - source.y) * tile_size); + part.setPosition((dest.x - source.x) * tile_size, (dest.y - source.y) * tile_size); + canvas.draw(part); + } - // End Pieces and Walls + // End Pieces and Walls - draw(new Vec4(2, 0, 3, 0.5), new Vec2(7, 0)); - draw(new Vec4(2, 1.5, 3, 2), new Vec2(7, 0.5)); + draw(new Vec4(2, 0, 3, 0.5), new Vec2(7, 0)); + draw(new Vec4(2, 1.5, 3, 2), new Vec2(7, 0.5)); - draw(new Vec4(2, 0, 2.5, 1), new Vec2(8, 0)); - draw(new Vec4(3.5, 0, 4, 1), new Vec2(8.5, 0)); + draw(new Vec4(2, 0, 2.5, 1), new Vec2(8, 0)); + draw(new Vec4(3.5, 0, 4, 1), new Vec2(8.5, 0)); - draw(new Vec4(1, 0, 2, 0.5), new Vec2(9, 0)); - draw(new Vec4(0, 1.5, 1, 2), new Vec2(9, 0.5)); + draw(new Vec4(1, 0, 2, 0.5), new Vec2(9, 0)); + draw(new Vec4(0, 1.5, 1, 2), new Vec2(9, 0.5)); - draw(new Vec4(2, 1, 2.5, 2), new Vec2(7, 1)); - draw(new Vec4(3.5, 1, 4, 2), new Vec2(7.5, 1)); + draw(new Vec4(2, 1, 2.5, 2), new Vec2(7, 1)); + draw(new Vec4(3.5, 1, 4, 2), new Vec2(7.5, 1)); - draw(new Vec4(3, 0, 4, 0.5), new Vec2(8, 1)); - draw(new Vec4(3, 1.5, 4, 2), new Vec2(8, 1.5)); + draw(new Vec4(3, 0, 4, 0.5), new Vec2(8, 1)); + draw(new Vec4(3, 1.5, 4, 2), new Vec2(8, 1.5)); - draw(new Vec4(0, 0, 0.5, 1), new Vec2(9, 1)); - draw(new Vec4(1.5, 1, 2, 2), new Vec2(9.5, 1)); + draw(new Vec4(0, 0, 0.5, 1), new Vec2(9, 1)); + draw(new Vec4(1.5, 1, 2, 2), new Vec2(9.5, 1)); - // Advanced Corners (Orange) + // Advanced Corners (Orange) - draw(new Vec4(6, 1, 7, 1.5), new Vec2(0, 2)); - draw(new Vec4(6, 0.5, 7, 1), new Vec2(0, 2.5)); + draw(new Vec4(6, 1, 7, 1.5), new Vec2(0, 2)); + draw(new Vec4(6, 0.5, 7, 1), new Vec2(0, 2.5)); - draw(new Vec4(6, 1, 6.5, 2), new Vec2(1, 2)); - draw(new Vec4(5.5, 1, 6, 2), new Vec2(1.5, 2)); + draw(new Vec4(6, 1, 6.5, 2), new Vec2(1, 2)); + draw(new Vec4(5.5, 1, 6, 2), new Vec2(1.5, 2)); - draw(new Vec4(5, 0, 6, 1), new Vec2(2, 2)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(2, 2)); + draw(new Vec4(5, 0, 6, 1), new Vec2(2, 2)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(2, 2)); - draw(new Vec4(6, 0, 6.5, 1), new Vec2(0, 3)); - draw(new Vec4(5.5, 0, 6, 1), new Vec2(0.5, 3)); + draw(new Vec4(6, 0, 6.5, 1), new Vec2(0, 3)); + draw(new Vec4(5.5, 0, 6, 1), new Vec2(0.5, 3)); - draw(new Vec4(5, 1, 6, 1.5), new Vec2(1, 3)); - draw(new Vec4(5, 0.5, 6, 1), new Vec2(1, 3.5)); + draw(new Vec4(5, 1, 6, 1.5), new Vec2(1, 3)); + draw(new Vec4(5, 0.5, 6, 1), new Vec2(1, 3.5)); - draw(new Vec4(6, 0, 7, 1), new Vec2(2, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(2.5, 3)); + draw(new Vec4(6, 0, 7, 1), new Vec2(2, 3)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(2.5, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(0.5, 4)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(0, 4)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(0, 4.5)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(0.5, 4.5)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(0.5, 4)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(0, 4)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(0, 4.5)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(0.5, 4.5)); - /** - * So here's why this is horrible: - * - Phaser doesn't let you copy a region of a RenderTexture to the same RenderTexture. - * - Creating a new RenderTexture and drawing the original directly onto it flips it upside down for some reason? - * - Scaling that upside-down RenderTexture to upside-right fucks with the draw() function's positioning. - * - * In other words, yeah, it's fucked man. The janky solution below is the only way I've found to make it work, - * so if future-Auri is looking at this and making a snarky comment to her friends about how she could do it - * *so much better*, please, just don't. Don't do it. - */ + /** + * So here's why this is horrible: + * - Phaser doesn't let you copy a region of a RenderTexture to the same RenderTexture. + * - Creating a new RenderTexture and drawing the original directly onto it flips it upside down for some reason? + * - Scaling that upside-down RenderTexture to upside-right fucks with the draw() function's positioning. + * + * In other words, yeah, it's fucked man. The janky solution below is the only way I've found to make it work, + * so if future-Auri is looking at this and making a snarky comment to her friends about how she could do it + * *so much better*, please, just don't. Don't do it. + */ - part.setCrop(); - part = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, canvas.width, canvas.height); - part.setOrigin(0, 0); - const temp = new Phaser.GameObjects.Sprite(this.scene, 0, 0, canvas.texture); - temp.setOrigin(0, 0); - part.draw(temp); + part.setCrop(); + part = new Phaser.GameObjects.RenderTexture(this.scene, 0, 0, canvas.width, canvas.height); + part.setOrigin(0, 0); + const temp = new Phaser.GameObjects.Sprite(this.scene, 0, 0, canvas.texture); + temp.setOrigin(0, 0); + part.draw(temp); - // Derived Forms (Pink) + // Derived Forms (Pink) - draw(new Vec4(2, 0, 3, 1), new Vec2(3, 2)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(3.5, 2.5)); + draw(new Vec4(2, 0, 3, 1), new Vec2(3, 2)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(3.5, 2.5)); - draw(new Vec4(3, 0, 4, 1), new Vec2(4, 2)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(4, 2.5)); + draw(new Vec4(3, 0, 4, 1), new Vec2(4, 2)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(4, 2.5)); - draw(new Vec4(1, 0, 2, 1), new Vec2(5, 2)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(5.5, 2.5)); + draw(new Vec4(1, 0, 2, 1), new Vec2(5, 2)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(5.5, 2.5)); - draw(new Vec4(1, 0, 2, 1), new Vec2(6, 2)); - draw(new Vec4(0, 3.5, 1, 4), new Vec2(6, 2.5)); + draw(new Vec4(1, 0, 2, 1), new Vec2(6, 2)); + draw(new Vec4(0, 3.5, 1, 4), new Vec2(6, 2.5)); - draw(new Vec4(1, 0, 2, 1), new Vec2(7, 2)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 2.5)); + draw(new Vec4(1, 0, 2, 1), new Vec2(7, 2)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 2.5)); - draw(new Vec4(0, 0, 1, 1), new Vec2(8, 2)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(8.5, 2.5)); + draw(new Vec4(0, 0, 1, 1), new Vec2(8, 2)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(8.5, 2.5)); - draw(new Vec4(1, 1, 2, 2), new Vec2(9, 2)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(9, 2.5)); + draw(new Vec4(1, 1, 2, 2), new Vec2(9, 2)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(9, 2.5)); - draw(new Vec4(2, 1, 3, 2), new Vec2(3, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(3.5, 3)); + draw(new Vec4(2, 1, 3, 2), new Vec2(3, 3)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(3.5, 3)); - draw(new Vec4(3, 1, 4, 2), new Vec2(4, 3)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(4, 3)); + draw(new Vec4(3, 1, 4, 2), new Vec2(4, 3)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(4, 3)); - draw(new Vec4(0, 1, 1, 2), new Vec2(5, 3)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(5.5, 3)); + draw(new Vec4(0, 1, 1, 2), new Vec2(5, 3)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(5.5, 3)); - draw(new Vec4(0, 1, 1, 2), new Vec2(6, 3)); - draw(new Vec4(1, 2, 2, 2.5), new Vec2(6, 3)); + draw(new Vec4(0, 1, 1, 2), new Vec2(6, 3)); + draw(new Vec4(1, 2, 2, 2.5), new Vec2(6, 3)); - draw(new Vec4(0, 1, 1, 2), new Vec2(7, 3)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(7, 3)); + draw(new Vec4(0, 1, 1, 2), new Vec2(7, 3)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(7, 3)); - draw(new Vec4(0, 0, 1, 1), new Vec2(8, 3)); - draw(new Vec4(1.5, 3, 2, 4), new Vec2(8.5, 3)); + draw(new Vec4(0, 0, 1, 1), new Vec2(8, 3)); + draw(new Vec4(1.5, 3, 2, 4), new Vec2(8.5, 3)); - draw(new Vec4(1, 1, 2, 2), new Vec2(9, 3)); - draw(new Vec4(0, 2, 0.5, 3), new Vec2(9, 3)); + draw(new Vec4(1, 1, 2, 2), new Vec2(9, 3)); + draw(new Vec4(0, 2, 0.5, 3), new Vec2(9, 3)); - draw(new Vec4(0, 2, 1, 3), new Vec2(4, 4)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(4.5, 4)); + draw(new Vec4(0, 2, 1, 3), new Vec2(4, 4)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(4.5, 4)); - draw(new Vec4(1, 3, 2, 4), new Vec2(5, 4)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(5, 4)); + draw(new Vec4(1, 3, 2, 4), new Vec2(5, 4)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(5, 4)); - draw(new Vec4(0, 2, 1, 3), new Vec2(6, 4)); - draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(6.5, 4.5)); + draw(new Vec4(0, 2, 1, 3), new Vec2(6, 4)); + draw(new Vec4(5.5, 0.5, 6, 1), new Vec2(6.5, 4.5)); - draw(new Vec4(1, 3, 2, 4), new Vec2(7, 4)); - draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 4.5)); + draw(new Vec4(1, 3, 2, 4), new Vec2(7, 4)); + draw(new Vec4(6, 0.5, 6.5, 1), new Vec2(7, 4.5)); - draw(new Vec4(0, 0, 1, 1), new Vec2(8, 4)); - draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(8.5, 4)); + draw(new Vec4(0, 0, 1, 1), new Vec2(8, 4)); + draw(new Vec4(5.5, 1, 6, 1.5), new Vec2(8.5, 4)); - draw(new Vec4(1, 1, 2, 2), new Vec2(9, 4)); - draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(9, 4)); + draw(new Vec4(1, 1, 2, 2), new Vec2(9, 4)); + draw(new Vec4(6, 1, 6.5, 1.5), new Vec2(9, 4)); - this.scene.textures.removeKey(tileset_key); - canvas.saveTexture(tileset_key); + canvas.snapshot((img: any) => { + this.scene.textures.removeKey(tileset_key); + this.scene.textures.addSpriteSheet(tileset_key, img, { frameWidth: tile_size, frameHeight: tile_size }); - console.log(`Patched '${tileset_key}' in ${Date.now() - s} ms.`); + console.log(`Patched '${tileset_key}' in ${Date.now() - s} ms.`); + + resolve(); + }); + }); } } diff --git a/app/src/editor/Token.ts b/app/src/editor/Token.ts index 8f0ecb2..495747d 100755 --- a/app/src/editor/Token.ts +++ b/app/src/editor/Token.ts @@ -1,4 +1,3 @@ -import { Vec2 } from './util/Vec'; import { generateId } from './util/Helpers'; export interface SerializedToken { @@ -10,8 +9,8 @@ export interface SerializedToken { } export default class Token extends Phaser.GameObjects.Container { - sprite: Phaser.GameObjects.Sprite | null = null; - shadow: Phaser.GameObjects.Sprite | null = null; + sprite: Phaser.GameObjects.Sprite; + shadow: Phaser.GameObjects.Sprite; currentFrame: number = 0; @@ -25,6 +24,20 @@ export default class Token extends Phaser.GameObjects.Container { constructor(scene: Phaser.Scene, x: number, y: number, tex: string) { super(scene, x, y); + + this.shadow = new Phaser.GameObjects.Sprite(this.scene, 0, 0, ''); + this.shadow.setOrigin(0, 0); + this.shadow.setScale(1 / this.shadow.width, 0.25 / this.shadow.height); + this.shadow.setTint(0x000000); + this.shadow.setAlpha(0.1, 0.1, 0.3, 0.3); + this.list.push(this.shadow); + + this.sprite = new Phaser.GameObjects.Sprite(this.scene, 0, 0, ''); + this.sprite.setOrigin(0, 0); + this.sprite.setScale(1 / this.sprite.width, 1 / this.sprite.height); + this.setPosition(this.x, this.y); + this.list.push(this.sprite); + this.setTexture(tex); this.uuid = generateId(32); @@ -37,28 +50,16 @@ export default class Token extends Phaser.GameObjects.Container { } setTexture(tex: string) { - if (this.shadow != null) this.shadow.setTexture(tex); - else { - this.shadow = new Phaser.GameObjects.Sprite(this.scene, -4, -4, tex); - this.shadow.setOrigin(0, 0); - this.shadow.setScale(4, 1); - this.shadow.setTint(0x000000); - this.shadow.setAlpha(0.1, 0.1, 0.3, 0.3); - this.list.push(this.shadow); - } + this.shadow.setTexture(tex); + this.sprite.setTexture(tex); - this.width = this.shadow.width * 4; - this.height = this.shadow.height * 4; - this.shadow.y = this.height - 26; + this.shadow.setScale(1 / this.shadow.width, 0.25 / this.shadow.height); + this.sprite.setScale(1 / this.sprite.width, 1 / this.sprite.height); - if (this.sprite != null) this.sprite.setTexture(tex); - else { - this.sprite = new Phaser.GameObjects.Sprite(this.scene, -4, -4, tex); - this.sprite.setOrigin(0, 0); - this.sprite.setScale(4, 4); - this.setPosition(this.x / 4, this.y / 4); - this.list.push(this.sprite); - } + this.shadow.y = this.sprite.displayHeight - this.shadow.displayHeight - 0.025; + + this.width = this.sprite.displayWidth; + this.height = this.sprite.displayHeight; } setFrame(frame: number): void { @@ -83,11 +84,14 @@ export default class Token extends Phaser.GameObjects.Container { this.hovered = hovered; if (!hovered && !this.selected) { - this.sprite.resetPipeline(); + // this.sprite.resetPipeline(); + this.sprite.setTint(0xffffff); return; } - if (!this.selected) this.sprite.setPipeline('brighten'); + if (!this.selected) this.sprite.setTint(0x999999); + + // if (!this.selected) this.sprite.setPipeline('brighten'); } setSelected(selected: boolean) { @@ -96,33 +100,27 @@ export default class Token extends Phaser.GameObjects.Container { this.selected = selected; if (!selected) { - if (!this.hovered) this.sprite.resetPipeline(); - else this.sprite.setPipeline('brighten'); + // if (!this.hovered) this.sprite.resetPipeline(); + // else this.sprite.setPipeline('brighten'); + if (!this.hovered) this.sprite.setTint(0xffffff); + else this.sprite.setTint(0x999999); } else { - this.sprite.setPipeline('outline'); + // this.sprite.setPipeline('outline'); + this.sprite.setTint(0x000000); // @ts-ignore // this.sprite.pipeline.setFloat1('tex_size', this.sprite.texture.source[0].width); } } - setPosition(x?: number, y?: number, z?: number, w?: number): this { - Phaser.GameObjects.Container.prototype.setPosition.call(this, (x || 0) * 4, (y || 0) * 4, z, w); - return this; - } - - getPosition(): Vec2 { - return new Vec2(this.x / 4, this.y / 4); - } - // Serialization Methods serialize(): string { return JSON.stringify(({ uuid: this.uuid, sprite: this.sprite ? this.sprite.texture.key : '', frame: this.currentFrame, - x: this.x / 4, - y: this.y / 4 + x: this.x, + y: this.y } as SerializedToken)); } diff --git a/app/src/editor/TokenMode.ts b/app/src/editor/TokenMode.ts index 69f645d..00655cc 100755 --- a/app/src/editor/TokenMode.ts +++ b/app/src/editor/TokenMode.ts @@ -31,7 +31,7 @@ export default class TokenMode { init() { // Create cursor hover sprite this.cursor = this.scene.add.sprite(0, 0, 'cursor'); - this.cursor.setScale(4, 4); + this.cursor.setScale(1 / 16, 1 / 16); this.cursor.setDepth(1000); this.cursor.setOrigin(0, 0); this.cursor.setVisible(false); @@ -58,15 +58,15 @@ export default class TokenMode { update() { this.active = true; - let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x / 64), Math.floor(this.scene.view.cursorWorld.y / 64)); + let selectedTilePos = new Vec2(Math.floor(this.scene.view.cursorWorld.x), Math.floor(this.scene.view.cursorWorld.y)); if (this.movingTokens) this.moving(); if (!this.movingTokens) this.selecting(); if (this.selectedTokens.length > 0 && !this.movingTokens) this.tokenMoveControls(); - this.tokenPreview!.setPosition(selectedTilePos.x * 16, selectedTilePos.y * 16); - this.cursor!.setPosition(selectedTilePos.x * 64, selectedTilePos.y * 64); + this.tokenPreview!.setPosition(selectedTilePos.x, selectedTilePos.y); + this.cursor!.setPosition(selectedTilePos.x, selectedTilePos.y); if (this.selectedTokenType === '') this.tokenPreview!.setVisible(false); if (this.selectedTokenType !== '') this.cursor!.setVisible(false); @@ -80,7 +80,7 @@ export default class TokenMode { updateRectangleSelect() { const cursor = this.scene.view.cursorWorld; - let selectedTilePos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + let selectedTilePos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); this.primitives.forEach((v) => v.destroy()); this.primitives = []; @@ -97,7 +97,6 @@ export default class TokenMode { this.primitives.forEach((v) => { v.setOrigin(0, 0); - v.setScale(64, 64); v.setLineWidth(0.03); v.setDepth(300); }); @@ -109,14 +108,14 @@ export default class TokenMode { this.movingTokens = true; const cursor = this.scene.view.cursorWorld; - this.tileGrabPos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + this.tileGrabPos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); this.prevSerialized = []; this.selectedTokens.forEach(t => this.prevSerialized.push(t.serialize())); } createToken(): Token { - let token = new Token(this.scene, Math.floor(this.scene.view.cursorWorld.x / 4 / 16) * 16, - Math.floor(this.scene.view.cursorWorld.y / 4 / 16) * 16, this.selectedTokenType); + let token = new Token(this.scene, Math.floor(this.scene.view.cursorWorld.x), + Math.floor(this.scene.view.cursorWorld.y), this.selectedTokenType); this.scene.add.existing(token); this.scene.tokens.push(token); @@ -150,16 +149,16 @@ export default class TokenMode { private tokenMoveControls(): void { if (this.scene.i.keyPressed('UP')) { - this.moveToken(0, -16, 2); + this.moveToken(0, -1, 2); } if (this.scene.i.keyPressed('LEFT')) { - this.moveToken(-16, 0, 1); + this.moveToken(-1, 0, 1); } if (this.scene.i.keyPressed('DOWN')) { - this.moveToken(0, 16, 0); + this.moveToken(0, 1, 0); } if (this.scene.i.keyPressed('RIGHT')) { - this.moveToken(16, 0, 3); + this.moveToken(1, 0, 3); } } @@ -175,8 +174,8 @@ export default class TokenMode { let prevSerialized: string[] = []; this.selectedTokens.forEach((token) => { prevSerialized.push(token.serialize()); - token.x += x * 4; - token.y += y * 4; + token.x += x; + token.y += y; token.setFrame(frame); }); @@ -201,7 +200,7 @@ export default class TokenMode { for (let i = this.scene.tokens.length - 1; i >= 0; i--) { let token = this.scene.tokens[i]; - if (cursor.x >= token.x && cursor.y >= token.y && cursor.x <= token.x + token.width - 8 && cursor.y <= token.y + token.height - 8) { + if (cursor.x >= token.x && cursor.y >= token.y && cursor.x <= token.x + token.width && cursor.y <= token.y + token.height) { this.hoveredToken = token; break; } @@ -237,7 +236,7 @@ export default class TokenMode { } // Start a rectangle selection else { - this.startTilePos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + this.startTilePos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); } } // Selecting existing token to move @@ -251,7 +250,7 @@ export default class TokenMode { } else { this.selectedTokens.forEach(t => t.setSelected(false)); - this.selectedTokens = [this.hoveredToken]; + this.selectedTokens = [ this.hoveredToken ]; this.clickedLastFrame = true; clickedAddedThisFrame = true; this.hoveredToken.setSelected(true); @@ -266,7 +265,7 @@ export default class TokenMode { this.primitives.forEach((v) => v.destroy()); this.primitives = []; - let selectedTilePos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + let selectedTilePos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); let a = new Vec2(Math.min(this.startTilePos.x, selectedTilePos.x), Math.min(this.startTilePos.y, selectedTilePos.y)); let b = new Vec2(Math.max(this.startTilePos.x, selectedTilePos.x), Math.max(this.startTilePos.y, selectedTilePos.y)); @@ -278,7 +277,7 @@ export default class TokenMode { } for (let token of this.scene.tokens) { - let tokenTilePos = new Vec2(Math.floor(token.x / 64), Math.floor(token.y / 64)); + let tokenTilePos = new Vec2(Math.floor(token.x), Math.floor(token.y)); if (tokenTilePos.x >= a.x && tokenTilePos.y >= a.y && tokenTilePos.x <= b.x && tokenTilePos.y <= b.y) { let selected = this.scene.i.keyDown('CTRL') ? !this.selectedIncludes(token) : true; @@ -360,13 +359,13 @@ export default class TokenMode { return; } - let newTileGrabPos = new Vec2(Math.floor(cursor.x / 64), Math.floor(cursor.y / 64)); + let newTileGrabPos = new Vec2(Math.floor(cursor.x), Math.floor(cursor.y)); let offset = new Vec2(newTileGrabPos.x - this.tileGrabPos.x, newTileGrabPos.y - this.tileGrabPos.y); if (offset.x === 0 && offset.y === 0) return; this.movedTokens = true; this.tileGrabPos = newTileGrabPos; - this.selectedTokens.forEach(tkn => tkn.setPosition(tkn.x / 4 + offset.x * 16, tkn.y / 4 + offset.y * 16)); + this.selectedTokens.forEach(tkn => tkn.setPosition(tkn.x + offset.x, tkn.y + offset.y)); } } } diff --git a/app/src/editor/WorldView.ts b/app/src/editor/WorldView.ts index 623af05..aa007d9 100755 --- a/app/src/editor/WorldView.ts +++ b/app/src/editor/WorldView.ts @@ -12,8 +12,8 @@ export default class WorldView { cursorWorld: Vec2 = new Vec2(); lastCursorWorld: Vec2 = new Vec2(); - zoomLevels: number[] = [10, 17, 25, 33, 40, 50, 60, 67, 75, 80, 90, 100, 110, 125, 150, 175, 200, 250, 300, 400, 500]; - zoomLevel = 11; + zoomLevels: number[] = [ 5, 6, 8, 10, 15, 20, 25, 33, 40, 50, 60, 75, 100, 125, 150, 200, 300 ]; + zoomLevel = 9; constructor(scene: MapScene) { this.scene = scene; @@ -22,11 +22,26 @@ export default class WorldView { init(): void { this.camera = this.scene.cameras.main; this.camera.setBackgroundColor('#090d24'); + this.camera.setZoom(this.zoomLevels[this.zoomLevel]); + this.camera.setScroll(-this.camera.width / 2.2, -this.camera.height / 2.2); this.scene.i.bindScrollEvent((delta: number) => { if (!this.scene.token.movingTokens && !this.scene.ui.uiActive) { + + const lastZoom = this.zoomLevels[this.zoomLevel]; this.zoomLevel = clamp(this.zoomLevel + delta, 0, this.zoomLevels.length - 1); - this.camera!.setZoom(this.zoomLevels[this.zoomLevel] / 100); + const zoom = this.zoomLevels[this.zoomLevel]; + + this.scene.tweens.add({ + targets: this.camera, + zoom: { from: lastZoom, to: zoom }, + ease: 'Cubic', + duration: 150, + repeat: 0 + }); + + // this.scene.tweens.add({ targets: this.camera!, duration: 150, props: { zoom: this.zoomLevels[this.zoomLevel / 100]}}); + // this.camera!.setZoom(this.zoomLevels[this.zoomLevel] / 100); } }); } @@ -47,8 +62,8 @@ export default class WorldView { private pan() { if (this.scene.input.activePointer.middleButtonDown()) { - this.camera!.scrollX += Math.round((this.lastCursorScreen.x - this.cursorScreen.x) / this.camera!.zoom); - this.camera!.scrollY += Math.round((this.lastCursorScreen.y - this.cursorScreen.y) / this.camera!.zoom); + this.camera!.scrollX += (this.lastCursorScreen.x - this.cursorScreen.x) / this.camera!.zoom; + this.camera!.scrollY += (this.lastCursorScreen.y - this.cursorScreen.y) / this.camera!.zoom; } } } diff --git a/app/src/editor/history/HistoryElement.ts b/app/src/editor/history/HistoryElement.ts index 1852064..607c4ff 100755 --- a/app/src/editor/history/HistoryElement.ts +++ b/app/src/editor/history/HistoryElement.ts @@ -1,8 +1,8 @@ import Token from '../Token'; import type MapScene from '../scene/MapScene'; -import Layer from '../util/Layer'; import { Vec2 } from '../util/Vec'; +import { Layer } from '../util/Layer'; export default class HistoryElement { scene: MapScene; @@ -19,7 +19,7 @@ export default class HistoryElement { console.log('Undo', this.type); if (this.type === 'tile') { for (let tile of this.data as {pos: Vec2; layer: Layer; lastTile: number; tile: number}[]) { - this.scene.map.setTile(tile.layer, tile.lastTile, tile.pos.x, tile.pos.y); + this.scene.map.activeLayer.setTile(tile.layer, tile.lastTile, tile.pos.x, tile.pos.y); this.scene.lighting.tileUpdatedAt(tile.pos.x, tile.pos.y); } } @@ -69,7 +69,7 @@ export default class HistoryElement { console.log('Redo', this.type); if (this.type === 'tile') { for (let tile of this.data as {pos: Vec2; layer: Layer; lastTile: number; tile: number}[]) { - this.scene.map.setTile(tile.layer, tile.tile, tile.pos.x, tile.pos.y); + this.scene.map.activeLayer.setTile(tile.layer, tile.tile, tile.pos.x, tile.pos.y); this.scene.lighting.tileUpdatedAt(tile.pos.x, tile.pos.y); } } diff --git a/app/src/editor/interface/components/UITileSidebar.ts b/app/src/editor/interface/components/UITileSidebar.ts index d0f42cb..840c03a 100755 --- a/app/src/editor/interface/components/UITileSidebar.ts +++ b/app/src/editor/interface/components/UITileSidebar.ts @@ -1,14 +1,13 @@ import UISidebar from './UISidebar'; import type MapScene from '../../scene/MapScene'; -import Layer from '../../util/Layer'; import { Asset } from '../../util/Asset'; export default class UITileSidebar extends UISidebar { walls: string[] = []; grounds: string[] = []; - overlays: string[] = []; + details: string[] = []; constructor(scene: MapScene, x: number, y: number, assets: Asset[]) { super(scene, x, y); @@ -30,18 +29,18 @@ export default class UITileSidebar extends UISidebar { this.list.push(add_ground); this.sprites.push(add_ground); - for (let tileset of assets.filter((a) => a.type === 'ground')) + for (let tileset of assets.filter((a) => a.type === 'floor')) this.addGround(tileset.identifier); - let add_overlay = new Phaser.GameObjects.Sprite(this.scene, 9 + x * 21 * 3, 9 + 9 * 21 * 3, 'ui_sidebar_browse'); - add_overlay.setName('add_overlay'); - add_overlay.setScale(3); - add_overlay.setOrigin(0, 0); - this.list.push(add_overlay); - this.sprites.push(add_overlay); + let add_detail = new Phaser.GameObjects.Sprite(this.scene, 9 + x * 21 * 3, 9 + 9 * 21 * 3, 'ui_sidebar_browse'); + add_detail.setName('add_detail'); + add_detail.setScale(3); + add_detail.setOrigin(0, 0); + this.list.push(add_detail); + this.sprites.push(add_detail); - for (let tileset of assets.filter((a) => a.type === 'ground')) - this.addOverlay(tileset.identifier); + for (let tileset of assets.filter((a) => a.type === 'detail')) + this.addDetail(tileset.identifier); for (let i = 0; i < 12; i++) { if (i % 4 !== 0) this.backgrounds[i].setFrame(0); @@ -50,21 +49,21 @@ export default class UITileSidebar extends UISidebar { elemClick(x: number, y: number): void { if (y < 4) { - this.scene.architect.activeTileset = this.scene.map.manager.indexes[this.walls[x + y * 3]]; - this.scene.architect.activeLayer = Layer.wall; + this.scene.architect.activeTileset = this.scene.map.tileStore.indices[this.walls[x + y * 3]]; + this.scene.architect.activeLayer = 'wall'; } else if (y < 8) { - this.scene.architect.activeTileset = this.scene.map.manager.indexes[this.grounds[x + (y - 4) * 3]]; - this.scene.architect.activeLayer = Layer.floor; + this.scene.architect.activeTileset = this.scene.map.tileStore.indices[this.grounds[x + (y - 4) * 3]]; + this.scene.architect.activeLayer = 'floor'; } else { - this.scene.architect.activeTileset = this.scene.map.manager.indexes[this.overlays[x + (y - 8) * 3]]; - this.scene.architect.activeLayer = Layer.overlay; + this.scene.architect.activeTileset = this.scene.map.tileStore.indices[this.details[x + (y - 8) * 3]]; + this.scene.architect.activeLayer = 'detail'; } } private addWall(tileset: string): void { - this.addTilesetSprite(tileset, this.walls.length % 3, Math.floor(this.walls.length / 3) + 1, 17); + this.addTilesetSprite(tileset, this.walls.length % 3, Math.floor(this.walls.length / 3) + 1, 13); (this.getByName('add_wall') as Phaser.GameObjects.Sprite).x = 9 + ((this.walls.length + 1) % 3 * 21 * 3); (this.getByName('add_wall') as Phaser.GameObjects.Sprite).y = 9 + (Math.floor((this.walls.length + 1) / 3 + 1) * 21 * 3); this.walls.push(tileset); @@ -77,11 +76,11 @@ export default class UITileSidebar extends UISidebar { this.grounds.push(tileset); } - private addOverlay(tileset: string): void { - this.addTilesetSprite(tileset, this.overlays.length % 3, Math.floor(this.overlays.length / 3) + 9, 33); - (this.getByName('add_overlay') as Phaser.GameObjects.Sprite).x = 9 + ((this.overlays.length + 1) % 3 * 21 * 3); - (this.getByName('add_overlay') as Phaser.GameObjects.Sprite).y = 9 + (Math.floor((this.overlays.length + 1) / 3 + 9) * 21 * 3); - this.overlays.push(tileset); + private addDetail(tileset: string): void { + this.addTilesetSprite(tileset, this.details.length % 3, Math.floor(this.details.length / 3) + 9, 33); + (this.getByName('add_detail') as Phaser.GameObjects.Sprite).x = 9 + ((this.details.length + 1) % 3 * 21 * 3); + (this.getByName('add_detail') as Phaser.GameObjects.Sprite).y = 9 + (Math.floor((this.details.length + 1) / 3 + 9) * 21 * 3); + this.details.push(tileset); } private addTilesetSprite(key: string, x: number, y: number, frame: number) { diff --git a/app/src/editor/interface/components/UITokenSidebar.ts b/app/src/editor/interface/components/UITokenSidebar.ts index 5148a3c..ee7c8fc 100755 --- a/app/src/editor/interface/components/UITokenSidebar.ts +++ b/app/src/editor/interface/components/UITokenSidebar.ts @@ -74,7 +74,7 @@ export default class UITokenSidebar extends UISidebar { let token = new Token(this.scene, 0, 0, sprite); Phaser.GameObjects.Sprite.prototype.setPosition.call(token, 12 + x * 21 * 3, 12 + y * 21 * 3); - token.setScale(3 / 4); + token.setScale(3); this.sprites.push(token); this.list.push(token); diff --git a/app/src/editor/lighting/LightSource.ts b/app/src/editor/lighting/LightSource.ts index bf78298..6b5bea0 100755 --- a/app/src/editor/lighting/LightSource.ts +++ b/app/src/editor/lighting/LightSource.ts @@ -1,6 +1,5 @@ import type Lighting from './Lighting'; -import Layer from '../util/Layer'; import { Vec2 } from '../util/Vec'; export default class LightSource { @@ -36,7 +35,7 @@ export default class LightSource { let dir = new Vec2(Math.cos(i * 1.25 * (Math.PI / 180)) / 32, Math.sin(i * 1.25 * (Math.PI / 180)) / 32); let dist = 0; - while (this.light.scene.map.getTileset(Layer.wall, Math.floor(start.x + ray.x), Math.floor(start.y + ray.y)) === -1 && + while (this.light.scene.map.activeLayer.getTile('wall', Math.floor(start.x + ray.x), Math.floor(start.y + ray.y)) === -1 && (dist = Math.sqrt(Math.pow(ray.x, 2) + Math.pow(ray.y, 2))) < this.radius / 16) { ray.x += dir.x; diff --git a/app/src/editor/lighting/Lighting.ts b/app/src/editor/lighting/Lighting.ts index f2541c3..8b8a40b 100755 --- a/app/src/editor/lighting/Lighting.ts +++ b/app/src/editor/lighting/Lighting.ts @@ -1,4 +1,4 @@ -import MapChunk from '../MapChunk'; +import { CHUNK_SIZE } from '../map/MapChunk'; import type MapScene from '../scene/MapScene'; import LightChunk from './LightChunk'; @@ -23,9 +23,9 @@ export default class Lighting { init(size: Vec2) { this.size = size; - for (let i = 0; i < Math.ceil(size.y / (MapChunk.CHUNK_SIZE * 2)); i++) { + for (let i = 0; i < Math.ceil(size.y / (CHUNK_SIZE * 2)); i++) { this.chunks[i] = []; - for (let j = 0; j < Math.ceil(size.x / (MapChunk.CHUNK_SIZE * 2)); j++) { + for (let j = 0; j < Math.ceil(size.x / (CHUNK_SIZE * 2)); j++) { this.chunks[i][j] = new LightChunk(this, j, i); } } diff --git a/app/src/editor/map/Map.ts b/app/src/editor/map/Map.ts new file mode 100755 index 0000000..7184cc6 --- /dev/null +++ b/app/src/editor/map/Map.ts @@ -0,0 +1,99 @@ +import * as Phaser from 'phaser'; + +import MapLayer from './MapLayer'; +import TileStore from './TileStore'; +import MapChunk, { CHUNK_SIZE } from './MapChunk'; + +import { Vec2 } from '../util/Vec'; +import { Asset } from '../util/Asset'; + + + +export default class MapData { + tileStore: TileStore = new TileStore(); + size: Vec2 = new Vec2(0, 0); + + activeLayer: MapLayer = {} as MapLayer; + private layers: MapLayer[] = []; + + private chunks: MapChunk[][] = []; + + init(scene: Phaser.Scene, size: Vec2, assets: Asset[]) { + this.size = size; + this.tileStore.init(scene.textures, assets); + + this.layers.push(new MapLayer(size, this.handleDirty)); + this.activeLayer = this.layers[0]; + + for (let i = 0; i < Math.ceil(size.y / CHUNK_SIZE); i++) { + this.chunks[i] = []; + for (let j = 0; j < Math.ceil(size.x / CHUNK_SIZE); j++) { + this.chunks[i][j] = new MapChunk(scene, new Vec2(j, i), this.activeLayer, this.tileStore); + } + } + } + + update(): void { + let start = Date.now(); + + for (let arr of this.chunks) { + for (let chunk of arr) { + chunk.redraw(); + if (Date.now() - start > 10) break; + } + } + + // if (this.scene.i.keyPressed('S')) this.saveMap(); + // if (this.scene.i.keyPressed('L')) this.loadMap(this.savedMapData); + } + + private handleDirty = (x: number, y: number) => { + this.chunks[Math.floor(y / CHUNK_SIZE)][Math.floor(x / CHUNK_SIZE)].setDirty(new Vec2(x % CHUNK_SIZE, y % CHUNK_SIZE)); + }; + + // private saveMap() { + + // let mapData: number[][] = []; + + // for (let k = 0; k < 3; k++) { + // let tile = 0; + // let count = 0; + // mapData[k] = []; + + // for (let i = 0; i < this.size.x * this.size.y; i++) { + // let x = i % this.size.x; + // let y = Math.floor(i / this.size.x); + + // if (this.getTileset(k, x, y) === tile) count++; + // else { + // if (i !== 0) { + // mapData[k].push(tile); + // mapData[k].push(count); + // } + // tile = this.getTileset(k, x, y); + // count = 1; + // } + // } + // } + + // this.savedMapData = mapData; + // } + + // private loadMap(dat: number[][]) { + // for (let k = 0; k < 3; k++) { + // let offset = 0; + // for (let i = 0; i < dat[k].length / 2; i++) { + // let tile = dat[k][i * 2]; + // let count = dat[k][i * 2 + 1]; + + // for (let t = 0; t < count; t++) { + // let x = (offset + t) % this.size.x; + // let y = Math.floor((offset + t) / this.size.x); + + // this.setTile(k, tile, x, y); + // } + // offset += count; + // } + // } + // } +} diff --git a/app/src/editor/map/MapChunk.ts b/app/src/editor/map/MapChunk.ts new file mode 100755 index 0000000..37b4598 --- /dev/null +++ b/app/src/editor/map/MapChunk.ts @@ -0,0 +1,114 @@ +import * as Phaser from 'phaser'; + +import MapLayer from './MapLayer'; +import TileStore from './TileStore'; + +import { Vec2 } from '../util/Vec'; + +export const TILE_SIZE = 16; +export const CHUNK_SIZE = 32; +export const DIRTY_LIMIT = (CHUNK_SIZE * CHUNK_SIZE) / 2; + + +/** + * A visual representation of a chunk of a MapLayer. + */ + +export default class MapChunk extends Phaser.GameObjects.RenderTexture { + private dirtyList: Vec2[] = []; + private fullyDirty: boolean = true; + + constructor(scene: Phaser.Scene, private pos: Vec2, private layer: MapLayer, private tileStore: TileStore) { + super(scene, CHUNK_SIZE * pos.x - 2 / TILE_SIZE, CHUNK_SIZE * pos.y - 2 / TILE_SIZE, + CHUNK_SIZE * TILE_SIZE + 4, CHUNK_SIZE * TILE_SIZE + 4); + this.setScale(1 / TILE_SIZE); + this.setOrigin(0, 0); + + scene.add.existing(this); + } + + + /** + * Indicates that a position on the chunk is dirty so it will be re-rendered. + * + * @param {Vec2} pos - The position that is dirtied. + */ + + setDirty(pos: Vec2): void { + if (!this.fullyDirty) { + for (let v of this.dirtyList) if (v.equals(pos)) return; + this.dirtyList.push(pos); + + if (this.dirtyList.length > DIRTY_LIMIT) { + this.fullyDirty = true; + this.dirtyList = []; + } + } + } + + + /** + * Redraws all dirty tiles on the chunk. + * + * @returns {boolean} - A boolean indicating if tiles have changed since the last render. + */ + + redraw(): boolean { + if (this.fullyDirty) { + for (let i = 0; i < CHUNK_SIZE * CHUNK_SIZE; i++) { + let x = i % CHUNK_SIZE; + let y = Math.floor(i / CHUNK_SIZE); + + if (x + this.pos.x * CHUNK_SIZE >= this.layer.size.x || + y + this.pos.y * CHUNK_SIZE >= this.layer.size.y) continue; + + this.drawTile(x, y); + } + + this.fullyDirty = false; + return true; + } + + if (this.dirtyList.length === 0) return false; + + for (let elem of this.dirtyList) this.drawTile(elem.x, elem.y); + this.dirtyList = []; + + return true; + } + + + /** + * Redraws the tile at the specified position, + * based on the current data on the MapLayer. + * + * @param {number} x - The x position to draw at. + * @param {number} y - The y position to draw at. + */ + + private drawTile(x: number, y: number): void { + let mX = x + this.pos.x * CHUNK_SIZE; + let mY = y + this.pos.y * CHUNK_SIZE; + + let wallTile = this.layer.getTile('wall', mX, mY); + let wallTileIndex = this.layer.getTileIndex('wall', mX, mY); + + let floorTile = this.layer.getTile('floor', mX, mY); + let floorTileIndex = this.layer.getTileIndex('floor', mX, mY); + + let detailTile = this.layer.getTile('detail', mX, mY); + let detailTileIndex = this.layer.getTileIndex('detail', mX, mY); + + if (floorTile !== -1) + this.drawFrame(this.tileStore.floorTiles[floorTile].identifier, floorTileIndex, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + + if (detailTile !== -1) + this.drawFrame(this.tileStore.detailTiles[detailTile].identifier, detailTileIndex, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + + if (wallTile !== -1) + this.drawFrame(this.tileStore.wallTiles[wallTile].identifier, wallTileIndex, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + + if ((x % 2 === 0 && y % 2 === 0) || (x % 2 !== 0 && y % 2 !== 0)) + this.drawFrame('grid_tile', 0, x * TILE_SIZE + 2, y * TILE_SIZE + 2); + } +} diff --git a/app/src/editor/map/MapLayer.ts b/app/src/editor/map/MapLayer.ts new file mode 100644 index 0000000..66d2239 --- /dev/null +++ b/app/src/editor/map/MapLayer.ts @@ -0,0 +1,250 @@ +import { Vec2 } from '../util/Vec'; +import { Layer } from '../util/Layer'; +import { clamp } from '../util/Helpers'; + + +const WALL_FIELD = [ + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 8, 8, 19, 19, 8, 8, 19, 19, 24, 24, 39, 29, 24, 24, 39, 29, 23, 23, 38, 38, 23, 23, 28, 28, 26, 26, 40, 47, 26, 26, 46, 30, + 8, 8, 19, 19, 8, 8, 19, 19, 3, 3, 49, 11, 3, 3, 49, 11, 23, 23, 38, 38, 23, 23, 28, 28, 25, 25, 45, 31, 25, 25, 22, 5, + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 4, 4, 17, 17, 4, 4, 17, 17, 18, 18, 34, 13, 18, 18, 34, 13, 7, 7, 33, 33, 7, 7, 12, 12, 9, 9, 36, 35, 9, 9, 37, 10, + 8, 8, 19, 19, 8, 8, 19, 19, 24, 24, 39, 29, 24, 24, 39, 29, 2, 2, 48, 48, 2, 2, 0, 0, 27, 27, 44, 32, 27, 27, 20, 6, + 8, 8, 19, 19, 8, 8, 19, 19, 3, 3, 49, 11, 3, 3, 49, 11, 2, 2, 48, 48, 2, 2, 0, 0, 1, 1, 21, 15, 1, 1, 16, 14 +]; + +const FLOOR_FIELD = [ + 54, 20, 19, 19, 18, 4, 19, 19, 11, 11, 3, 3, 51, 51, 3, 3, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, + 2, 12, 32, 32, 34, 6, 32, 32, 11, 11, 3, 3, 51, 51, 3, 3, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, + 0, 33, 31, 31, 14, 7, 31, 31, 42, 42, 27, 27, 36, 36, 27, 27, 9, 52, 5, 5, 9, 52, 5, 5, 39, 39, 30, 30, 39, 39, 30, 30, + 22, 15, 28, 28, 16, 37, 28, 28, 42, 42, 27, 27, 36, 36, 27, 27, 43, 38, 29, 29, 43, 38, 29, 29, 39, 39, 30, 30, 39, 39, 30, 30, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49, + 1, 41, 17, 17, 40, 46, 17, 17, 21, 21, 8, 8, 45, 45, 8, 8, 23, 47, 26, 26, 23, 47, 26, 26, 48, 48, 49, 49, 48, 48, 49, 49 +]; + +type LayerData = { tiles: number[][]; tilesets: number[][] }; + +export default class MapLayer { + private data: { [ key in Layer ]: LayerData } = { + wall: { tiles: [], tilesets: [] }, floor: { tiles: [], tilesets: [] }, detail: { tiles: [], tilesets: [] } }; + + constructor(public size: Vec2, private onDirty: (x: number, y: number) => void) { + const createLayerData = (startTile: number | (() => number), startTileset: number): LayerData => { + let layer: LayerData = { tiles: [], tilesets: [] }; + + for (let i = 0; i < this.size.y; i++) { + layer.tiles[i] = []; + layer.tilesets[i] = []; + for (let j = 0; j < this.size.x; j++) { + let tile = typeof(startTile) === 'number' ? startTile : startTile(); + layer.tiles[i][j] = tile; + layer.tilesets[i][j] = startTileset; + } + } + + return layer; + }; + + this.data.wall = createLayerData(0, -1); + this.data.floor = createLayerData(() => Math.floor(Math.random() * 6) + 54, 0); + this.data.detail = createLayerData(0, -1); + } + + + /** + * Convert a 3x3 array of wall states into a numeric value between 0 and 255. + * + * @param {boolean} walls - The walls + */ + + static bitsToIndices(walls: boolean[]): number { + return ( + (+walls[0] << 0) + + (+walls[1] << 1) + + (+walls[2] << 2) + + (+walls[3] << 3) + + (+walls[5] << 4) + + (+walls[6] << 5) + + (+walls[7] << 6) + + (+walls[8] << 7)); + } + + + /** + * Returns a tile index for a wall based on it's surrounding walls. + * + * @param {boolean[]} walls - Surrounding walls boolean array. + * @param {number} current - The current wall value. + */ + + static wall(walls: boolean[], current: number): number { + if (current === -1) return -1; + const ind = WALL_FIELD[MapLayer.bitsToIndices(walls)]; + if (ind < 54) return ind; + return 54 + Math.floor(Math.random() * 6); + } + + + /** + * Returns a tile index for a floor based on it's surrounding walls. + * + * @param {boolean[]} walls - Surrounding walls boolean array. + * @param {number} current - The current floor value. + */ + + static floor(walls: boolean[], current: number): number { + if (current === -1) return -1; + const ind = FLOOR_FIELD[MapLayer.bitsToIndices(walls)]; + if (ind < 54) return ind; + return 54 + Math.floor(Math.random() * 6); + } + + + /** + * Returns a tile index for a detail based on it's surrounding details. + * + * @param {boolean[]} details - Surrounding details boolean array. + * @param {number} current - The current floor value. + */ + + static detail(details: boolean[], current: number): number { + if (current === -1) return -1; + const ind = WALL_FIELD[MapLayer.bitsToIndices(details)]; + if (ind < 54) return ind; + return 54 + Math.floor(Math.random() * 6); + } + + + /** + * Sets a tile to the tileset provided, automatically smart-tiling as needed. + * + * @param {Layer} layer - The internal layer to set the tile at. + * @param {number} tileset - The tileset to set the tile to. + * @param {number | Vec2} x - Either the x value of the position to set the tile at, or a vector for the full position. + * @param {number} y - The y value of the position if the x value is a number. + * + * @returns {boolean} - True if the tileset was changed, false otherwise. + */ + + setTile(layer: Layer, tileset: number, x: number | Vec2, y?: number): boolean { + if (x instanceof Vec2) { y = x.y; x = x.x; } + if (x < 0 || y! < 0 || x >= this.size.x || y! >= this.size.y) return false; + + if (this.setTileset(layer, x, y!, tileset)) { + // this.setTileIndex(layer, x, y!, 3); + this.autoTile(x, y!); + return true; + } + + return false; + } + + + /** + * Gets the tileset at the specified position. + * + * @param layer - The internal layer to get the tileset at. + * @param {number} x - Either the x value of the position to get the tile set at, or a vector for the full position. + * @param {number} y - The y value of the position if the x value is a number. + */ + + getTile(layer: Layer, x: number | Vec2, y?: number): number { + if (x instanceof Vec2) { y = x.y; x = x.x; } + return this.data[layer].tilesets[clamp(y!, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; + } + + + /** + * Gets the current tile index at a position. + * + * @param {Layer} layer - The internal layer to get the tile at. + * @param {number | Vec2} x - Either the x value of the position to get the tile at, or a vector for the full position. + * @param {number} y - The y value of the position if the x value is a number. + * + * @returns {number} - The tile index at the position specified. + */ + + getTileIndex(layer: Layer, x: number | Vec2, y?: number): number { + if (x instanceof Vec2) { y = x.y; x = x.x; } + return this.data[layer].tiles[clamp(y!, 0, this.size.y - 1)][clamp(x, 0, this.size.x - 1)]; + } + + + /** + * Sets a tile to the one provided. + * + * @param {Layer} layer - The layer to set the tileset at. + * @param {number | Vec2} x - Either the x value of the position to set the tileset at, or a vector for the full position. + * @param {number} y - Either the y value of the position if x is a number, or the tileset to set. + * @param {number} tile - The tileset to set if the x value is a number. + * + * @returns {boolean} - True if the tileset was changed, false otherwise. + */ + + private setTileset(layer: Layer, x: number | Vec2, y: number, tile?: number): boolean { + if (x instanceof Vec2) { tile = y; y = x.y; x = x.x; }; + + const oldTileset = this.getTile(layer, x, y); + if (oldTileset === tile!) return false; + + this.data[layer].tilesets[y][x] = tile!; + return true; + } + + + /** + * Sets the tile at the specified position to the index provided. + */ + + private setTileIndex(layer: Layer, x: number, y: number, index: number): void { + this.data[layer].tiles[y][x] = index; + this.onDirty(x, y); + } + + + /** + * Automatically updates the tile indexes surrounding a position. + * + * @param {number} x - The x value of the position to center around. + * @param {number} y - The y value of the position to center around. + */ + + private autoTile(x: number, y: number): void { + for (let i = clamp(x - 1, this.size.x - 1, 0); i <= clamp(x + 1, this.size.x - 1, 0); i++) { + for (let j = clamp(y - 1, this.size.y - 1, 0); j <= clamp(y + 1, this.size.y - 1, 0); j++) { + const solids = this.getTilesAround('wall', i, j).map(i => i !== -1); + + const wall = MapLayer.wall(solids, this.getTileIndex('wall', i, j)); + if (wall !== -1) this.setTileIndex('wall', i, j, wall); + + const floor = MapLayer.floor(solids, this.getTileIndex('floor', i, j)); + if (floor !== -1) this.setTileIndex('floor', i, j, floor); + + const detail = MapLayer.detail(this.getTilesAround('detail', i, j).map(i => i !== -1), -1); + if (detail !== -1) this.setTileIndex('detail', i, j, detail); + } + } + } + + + /** + * Gets the 9 tiles in a 3x3 grid around the position specified. + * + * @param {Layer} layer - The internal layer to get the tileset at. + * @param {number} x - The x value of the position to center around. + * @param {number} y - The y value of the position to center around. + * + * @returns {number[]} a nine-element long array of the tiles around. + */ + + private getTilesAround(layer: Layer, x: number, y: number): number[] { + let tilesets: number[] = []; + for (let i = -1; i <= 1; i++) + for (let j = -1; j <= 1; j++) + tilesets.push(this.getTile(layer, clamp(x + j, 0, this.size.x - 1), clamp(y + i, 0, this.size.y - 1))); + return tilesets; + } +} diff --git a/app/src/editor/map/TileStore.ts b/app/src/editor/map/TileStore.ts new file mode 100755 index 0000000..70500db --- /dev/null +++ b/app/src/editor/map/TileStore.ts @@ -0,0 +1,49 @@ +import * as Phaser from 'phaser'; + +import { Layer } from '../util/Layer'; +import { Asset, AssetType } from '../util/Asset'; + +interface TileInfo { + res: number; + ind: number; + identifier: string; +} + +/** + * Stores a map of tileset indexes to tiles. + */ + +export default class TileStore { + indices: { [tileset_key: string]: number } = {}; + wallTiles: { [index: number]: TileInfo } = {}; + floorTiles: { [index: number]: TileInfo } = {}; + detailTiles: { [index: number]: TileInfo } = {}; + + private currentInd: { [layer in Layer]: number } = { wall: 0, floor: 0, detail: 0 }; + + + /** + * Initializes tilesets from a list of assets. + */ + + init(textures: Phaser.Textures.TextureManager, assets: Asset[]) { + for (const tileset of assets.filter(a => a.type !== 'token')) + this.addTileset(textures, tileset.type, tileset.identifier); + } + + + /** + * Adds the specified tileset to the map. + */ + + private addTileset(textures: Phaser.Textures.TextureManager, layer: AssetType, identifier: string): void { + const ind = this.currentInd[layer as Layer]++; + const res = textures.get(identifier).getSourceImage(0).width / 9; + + if (layer === 'wall') this.wallTiles[ind] = { res, ind, identifier }; + else if (layer === 'floor') this.floorTiles[ind] = { res, ind, identifier }; + else if (layer === 'detail') this.detailTiles[ind] = { res, ind, identifier }; + + this.indices[identifier] = ind; + } +} diff --git a/app/src/editor/scene/LoadScene.ts b/app/src/editor/scene/LoadScene.ts index 5ec6474..be9fb06 100755 --- a/app/src/editor/scene/LoadScene.ts +++ b/app/src/editor/scene/LoadScene.ts @@ -1,7 +1,9 @@ import * as Phaser from 'phaser'; -import EditorData from '../EditorData'; +import TilesetPatcher from '../TilesetPatcher'; + import { Asset } from '../util/Asset'; +import EditorData from '../EditorData'; export default class LoadScene extends Phaser.Scene { loaderOutline: Phaser.GameObjects.Sprite | null = null; @@ -24,8 +26,6 @@ export default class LoadScene extends Phaser.Scene { this.load.image('cursor', '/app/static/cursor.png'); this.load.image('grid_tile', '/app/static/grid_tile.png'); - this.load.image('tileset_partial', '/app/static/tileset/water_new.png'); - this.load.image('tileset_template', '/app/static/tileset_template.png'); this.load.image('ui_button_grid', '/app/static/ui/button_grid.png'); this.load.spritesheet('ui_button_side_menu', '/app/static/ui/button_side_menu.png', {frameWidth: 21, frameHeight: 18}); this.load.spritesheet('ui_history_manipulation', '/app/static/ui/history_manipulation.png', {frameWidth: 39, frameHeight: 18}); @@ -43,17 +43,22 @@ export default class LoadScene extends Phaser.Scene { this.assets = JSON.parse(this.cache.text.get('assets')); for (let asset of this.assets) { - if (asset.tileSize) this.load.spritesheet(asset.identifier, '/app/asset/' + asset.path, - { frameWidth: asset.tileSize, frameHeight: asset.tileSize }); - else this.load.image(asset.identifier, asset.path); + if (asset.tileSize && asset.type !== 'wall' && asset.type !== 'detail') + this.load.spritesheet(asset.identifier, '/app/asset/' + asset.path, { frameWidth: asset.tileSize, frameHeight: asset.tileSize }); + else this.load.image(asset.identifier, '/app/asset/' + asset.path); } } create(): void { - this.game.scene.start('MapScene', { ...this.editorData, data: JSON.parse(this.cache.text.get('data')), assets: this.assets }); - this.cache.text.remove('assets'); - this.game.scene.stop('LoadScene'); - this.game.scene.swapPosition('MapScene', 'LoadScene'); + const t = new TilesetPatcher(this); + Promise.all(this.assets.filter(a => a.type === 'wall' || a.type === 'detail') + .map(async (a) => await t.patch(a.identifier, a.tileSize))) + .then(() => { + this.game.scene.start('MapScene', { ...this.editorData, data: JSON.parse(this.cache.text.get('data')), assets: this.assets }); + this.cache.text.remove('assets'); + this.game.scene.stop('LoadScene'); + this.game.scene.swapPosition('MapScene', 'LoadScene'); + }); } private setup(): void { @@ -66,8 +71,8 @@ export default class LoadScene extends Phaser.Scene { this.add.sprite(this.cameras.main.width / 2, this.cameras.main.height - 140, 'logo'); this.load.on('progress', (val: number) => { - this.loaderFilled!.setCrop(0, this.loaderFilled!.height - this.loaderFilled!.height * val, - this.loaderFilled!.width, this.loaderFilled!.height * val); + this.loaderFilled!.setCrop(0, this.loaderFilled!.height - this.loaderFilled!.height * val, + this.loaderFilled!.width, this.loaderFilled!.height * val); }); } } diff --git a/app/src/editor/scene/MapScene.ts b/app/src/editor/scene/MapScene.ts index 229fb9e..33a1dfb 100755 --- a/app/src/editor/scene/MapScene.ts +++ b/app/src/editor/scene/MapScene.ts @@ -9,10 +9,9 @@ import WorldView from '../WorldView'; import TokenMode from '../TokenMode'; import ArchitectMode from '../ArchitectMode'; +import Map from '../map/Map'; import Token from '../Token'; -import MapData from '../MapData'; import Lighting from '../lighting/Lighting'; -import TilesetPatcher from '../TilesetPatcher'; // import OutlinePipeline from '../shader/OutlinePipeline'; // import BrightenPipeline from '../shader/BrightenPipeline'; @@ -34,7 +33,7 @@ export default class MapScene extends Phaser.Scene { size: Vec2 = new Vec2(); - map: MapData = new MapData(this); + map: Map = new Map(); lighting: Lighting = new Lighting(this); mode: number = 0; @@ -48,18 +47,11 @@ export default class MapScene extends Phaser.Scene { // webRenderer.pipelines.add('outline', new OutlinePipeline(this.game)); // webRenderer.pipelines.add('brighten', new BrightenPipeline(this.game)); - const t = new TilesetPatcher(this); - t.patch('tileset_partial', 16); - - const s = this.add.sprite(300, 300, 'tileset_partial'); - s.setOrigin(0, 0); - s.setScale(4); - this.i.init(); this.view.init(); this.size = new Vec2(data.data.size); - this.map.init(this.size, this.assets!); + this.map.init(this, this.size, this.assets!); this.ui.init(this.assets!); this.architect.init(); diff --git a/app/src/editor/util/Asset.ts b/app/src/editor/util/Asset.ts index 1a8ab86..9e43fcb 100755 --- a/app/src/editor/util/Asset.ts +++ b/app/src/editor/util/Asset.ts @@ -1,6 +1,6 @@ import { Vec2 } from './Vec'; -export type AssetType = 'ground' | 'wall' | 'token'; +export type AssetType = 'floor' | 'detail' | 'wall' | 'token'; export interface Asset { type: AssetType; diff --git a/app/src/editor/util/Layer.ts b/app/src/editor/util/Layer.ts index 88d5729..87fd5df 100644 --- a/app/src/editor/util/Layer.ts +++ b/app/src/editor/util/Layer.ts @@ -1,7 +1 @@ -enum Layer { - floor = 0, - wall = 1, - overlay = 2 -}; - -export default Layer; +export type Layer = 'floor' | 'wall' | 'detail'; diff --git a/assets/auri_16x_fantasy_floor_rock.png b/assets/1212b3f83385f518d41fd5d60924617f.png similarity index 100% rename from assets/auri_16x_fantasy_floor_rock.png rename to assets/1212b3f83385f518d41fd5d60924617f.png diff --git a/assets/129e90e0cc9499733a12c1604429fa62.png b/assets/129e90e0cc9499733a12c1604429fa62.png deleted file mode 100644 index c5c8e59..0000000 Binary files a/assets/129e90e0cc9499733a12c1604429fa62.png and /dev/null differ diff --git a/assets/19f80922c8ec85b838aecbb58e83ebb5.png b/assets/19f80922c8ec85b838aecbb58e83ebb5.png deleted file mode 100644 index d4471e9..0000000 Binary files a/assets/19f80922c8ec85b838aecbb58e83ebb5.png and /dev/null differ diff --git a/assets/1d47e7bf3a7e92475630090704215c3d.png b/assets/1d47e7bf3a7e92475630090704215c3d.png new file mode 100644 index 0000000..80d4bf4 Binary files /dev/null and b/assets/1d47e7bf3a7e92475630090704215c3d.png differ diff --git a/assets/1d8c662852f88f8b0eab68764b6075a9.png b/assets/1d8c662852f88f8b0eab68764b6075a9.png new file mode 100644 index 0000000..3b6ded9 Binary files /dev/null and b/assets/1d8c662852f88f8b0eab68764b6075a9.png differ diff --git a/assets/2506d31b947fda4295475c191f2dd0db.png b/assets/2506d31b947fda4295475c191f2dd0db.png deleted file mode 100644 index f5e2e29..0000000 Binary files a/assets/2506d31b947fda4295475c191f2dd0db.png and /dev/null differ diff --git a/assets/4d382cb3f4956f5b3acacb84cc75b242.png b/assets/4d382cb3f4956f5b3acacb84cc75b242.png deleted file mode 100644 index 11827f2..0000000 Binary files a/assets/4d382cb3f4956f5b3acacb84cc75b242.png and /dev/null differ diff --git a/assets/51bb14491a3ec884269416faac03cafa.png b/assets/51bb14491a3ec884269416faac03cafa.png deleted file mode 100644 index f257d86..0000000 Binary files a/assets/51bb14491a3ec884269416faac03cafa.png and /dev/null differ diff --git a/assets/595347ff1a6fe6a52fbde2255860fdd7.png b/assets/595347ff1a6fe6a52fbde2255860fdd7.png deleted file mode 100644 index 13d9fc7..0000000 Binary files a/assets/595347ff1a6fe6a52fbde2255860fdd7.png and /dev/null differ diff --git a/assets/5cafce367161af38becdcfbd4e965baf.png b/assets/5cafce367161af38becdcfbd4e965baf.png new file mode 100644 index 0000000..f853645 Binary files /dev/null and b/assets/5cafce367161af38becdcfbd4e965baf.png differ diff --git a/assets/65172fd93b3361a53f63e6a964f47152.png b/assets/65172fd93b3361a53f63e6a964f47152.png deleted file mode 100644 index 535d8ab..0000000 Binary files a/assets/65172fd93b3361a53f63e6a964f47152.png and /dev/null differ diff --git a/assets/6900ec9f541e1d7d010aee1f57c7f1a9.png b/assets/6900ec9f541e1d7d010aee1f57c7f1a9.png deleted file mode 100644 index b82b259..0000000 Binary files a/assets/6900ec9f541e1d7d010aee1f57c7f1a9.png and /dev/null differ diff --git a/assets/8a344775bc45eaae8c7a399985f336e9.png b/assets/8a344775bc45eaae8c7a399985f336e9.png deleted file mode 100644 index d4471e9..0000000 Binary files a/assets/8a344775bc45eaae8c7a399985f336e9.png and /dev/null differ diff --git a/assets/99f8ad5dbf19c983e8decd61a1ab0efc.png b/assets/8aad9d92a9a622d664c3362c69154aca.png similarity index 100% rename from assets/99f8ad5dbf19c983e8decd61a1ab0efc.png rename to assets/8aad9d92a9a622d664c3362c69154aca.png diff --git a/assets/a532af9feb981f9302ecf865b5d9c7d0.png b/assets/a532af9feb981f9302ecf865b5d9c7d0.png deleted file mode 100644 index f257d86..0000000 Binary files a/assets/a532af9feb981f9302ecf865b5d9c7d0.png and /dev/null differ diff --git a/assets/auri_16x_fantasy_cadin_1.png b/assets/auri_16x_fantasy_cadin_1.png deleted file mode 100644 index 2009e55..0000000 Binary files a/assets/auri_16x_fantasy_cadin_1.png and /dev/null differ diff --git a/assets/auri_16x_fantasy_wall_dungeon.png b/assets/auri_16x_fantasy_wall_dungeon.png deleted file mode 100644 index ff157c1..0000000 Binary files a/assets/auri_16x_fantasy_wall_dungeon.png and /dev/null differ diff --git a/assets/c57c07c340da5af1c066def99abf7a25.png b/assets/c57c07c340da5af1c066def99abf7a25.png deleted file mode 100644 index 190b845..0000000 Binary files a/assets/c57c07c340da5af1c066def99abf7a25.png and /dev/null differ diff --git a/assets/c58cb184401388ad65c9e3509e83b706.png b/assets/c58cb184401388ad65c9e3509e83b706.png deleted file mode 100644 index 8822e9b..0000000 Binary files a/assets/c58cb184401388ad65c9e3509e83b706.png and /dev/null differ diff --git a/assets/cc88be196a49a85b30d8f075cface18e.png b/assets/cc88be196a49a85b30d8f075cface18e.png deleted file mode 100644 index 4d82d6c..0000000 Binary files a/assets/cc88be196a49a85b30d8f075cface18e.png and /dev/null differ diff --git a/assets/d365e29f01067ade7b15566e97656999.png b/assets/d365e29f01067ade7b15566e97656999.png deleted file mode 100644 index f257d86..0000000 Binary files a/assets/d365e29f01067ade7b15566e97656999.png and /dev/null differ diff --git a/assets/d38c5fc89edfc8feb26af435c84bda95.png b/assets/d38c5fc89edfc8feb26af435c84bda95.png deleted file mode 100644 index 8ed64bb..0000000 Binary files a/assets/d38c5fc89edfc8feb26af435c84bda95.png and /dev/null differ diff --git a/assets/7ff3add8ef026bdf6e0dad0d5ba85642.png b/assets/df059aba8e6c8d0afc77acf3a9713c27.png similarity index 100% rename from assets/7ff3add8ef026bdf6e0dad0d5ba85642.png rename to assets/df059aba8e6c8d0afc77acf3a9713c27.png diff --git a/assets/e8e61062f0889dffca9a1439d9678201.png b/assets/e8e61062f0889dffca9a1439d9678201.png deleted file mode 100644 index 4d82d6c..0000000 Binary files a/assets/e8e61062f0889dffca9a1439d9678201.png and /dev/null differ diff --git a/assets/f975160fe390339d57d077a6ff0085aa.png b/assets/f975160fe390339d57d077a6ff0085aa.png deleted file mode 100644 index a4f7aba..0000000 Binary files a/assets/f975160fe390339d57d077a6ff0085aa.png and /dev/null differ diff --git a/assets/fcb05842c62423b61ca44e39e68380d9.png b/assets/fcb05842c62423b61ca44e39e68380d9.png deleted file mode 100644 index f5e2e29..0000000 Binary files a/assets/fcb05842c62423b61ca44e39e68380d9.png and /dev/null differ diff --git a/common/AppData.ts b/common/AppData.ts index 58028bc..b9bb4b3 100644 --- a/common/AppData.ts +++ b/common/AppData.ts @@ -1,9 +1,10 @@ import * as DB from './DBStructs'; -export type AppDataSpecifier = 'user' | 'campaigns' | 'assets'; +export type AppDataSpecifier = 'user' | 'campaigns' | 'collections' | 'assets'; export interface AppData { user: { user: string, name: string }; + collections: DB.AssetCollection[]; campaigns: DB.Campaign[]; assets: DB.Asset[]; } diff --git a/common/DBStructs.ts b/common/DBStructs.ts index de9c213..1a8f485 100755 --- a/common/DBStructs.ts +++ b/common/DBStructs.ts @@ -41,7 +41,7 @@ export interface Map { tiles: string; } -export type AssetType = 'wall' | 'ground' | 'token'; +export type AssetType = 'wall' | 'detail' | 'ground' | 'token'; export interface AssetCollection { _id?: ObjectID; diff --git a/server/src/Database.ts b/server/src/Database.ts index f856cdf..2824a8f 100755 --- a/server/src/Database.ts +++ b/server/src/Database.ts @@ -14,6 +14,7 @@ const sizeOf = promisify(sizeOfRaw); const logger = log4js.getLogger(); const PERSONAL_IDENTIFIER = '_'; +const ASSET_PATH = path.join(path.dirname(path.dirname(__dirname)), 'assets'); export const uploadLimit = 2 * 1024 * 1024; export const accountLimit = 5 * 1024 * 1024; @@ -25,7 +26,7 @@ interface BaseAssetData { } interface TilesetData { - type: 'ground' | 'wall' + type: 'floor' | 'wall' | 'detail' } interface TokenData { @@ -310,11 +311,43 @@ export default class Database { /** - * Get a users's uploaded assets. + * Gets a users's uploaded assets. */ async getUserAssets(user: string): Promise { - return await this.db!.collection('assets').find({ user: user }).toArray(); + return await this.db!.collection('assets').find({ user }).toArray(); + } + + + /** + * Gets a user's collections. + */ + + + async getUserCollections(user: string): Promise { + return await this.db!.collection('collections').find({ user }).toArray(); + } + + + /** + * Adds an asset to a user's collection + * + * @param user - The user that owns the collection. + * @param collection - The collection to insert an item into. + * @param asset - An Asset string to add to the collection. + */ + + + async addCollectionAsset(user: string, collection: string, asset: string) { + const res = await this.db!.collection('assets').find({ + user: asset.slice(0, asset.indexOf(':')), + identifier: asset.slice(asset.indexOf(':') + 1) + }); + if (!res) throw 'Asset doesn\'t exist.'; + + const matched = await this.db!.collection('collections') + .updateOne({ user: user, identifier: collection }, { $addToSet: { items: asset }}); + if (!matched.matchedCount) throw 'Collection doesn\'t exist.'; } @@ -342,7 +375,7 @@ export default class Database { let assetName = '', assetPath = ''; while (true) { assetName = crypto.createHash('md5').update(data.identifier + await crypto.randomBytes(8)).digest('hex') + '.png'; - assetPath = path.join(path.dirname(path.dirname(__dirname)), 'assets', assetName); + assetPath = path.join(ASSET_PATH, assetName); try { await fs.access(assetPath, fsc.R_OK | fsc.W_OK); } catch (e) { if (e.code === 'ENOENT') break; } } @@ -378,6 +411,26 @@ export default class Database { } + /** + * Deletes a user's asset, removing it from the filesystem. + * + * @param {string} user - The user identifier. + * @param {string} identifier - The asset identifier. + */ + + async deleteAsset(user: string, identifier: string): Promise { + const asset: DB.Asset | null = await this.db!.collection('assets').findOne({ user, identifier }); + if (!asset) return; + + try { await fs.unlink(path.join(ASSET_PATH, asset.path)) } catch {} + await this.db!.collection('assets').remove({ user, identifier }); + + const query = user + ':' + identifier; + await this.db!.collection('collections').updateMany( + { items: { $all: [ query ] } }, { $pull: { items: query } }); + } + + /** * Creates and returns an authentication token for a user using a username / password pair. * Throws if the username and password do not refer to a valid user. diff --git a/server/src/routers/DataRouter.ts b/server/src/routers/DataRouter.ts index eac0d4b..e75a8fe 100755 --- a/server/src/routers/DataRouter.ts +++ b/server/src/routers/DataRouter.ts @@ -35,12 +35,17 @@ export default class DataRouter extends Router { case 'assets': data.assets = await this.db.getUserAssets(user); break; + + case 'collections': + data.collections = await this.db.getUserCollections(user); + break; } })); return data; }; + /** * Attempts to authenticate a user from a username and password. */ @@ -97,7 +102,7 @@ export default class DataRouter extends Router { /** - * Creates a new campaign. + * Creates a new map within a campaign. */ this.router.post('/map/new', this.authRoute(async (user, req, res) => { @@ -118,16 +123,16 @@ export default class DataRouter extends Router { })); this.router.post('/asset/upload/', this.authRoute(async (user, req, res) => { - const type: 'ground' | 'token' | 'wall' = req.body.type; + const type: 'floor' | 'token' | 'detail' | 'wall' = req.body.type; const tokenType: '1' | '4' | '8' = req.body.tokenType; const name = req.body.name; const identifier = req.body.identifier; - if (typeof name != 'string' || typeof identifier != 'string' || - (type != 'token' && type != 'ground' && type != 'wall') || + if (typeof name !== 'string' || typeof identifier !== 'string' || + (type !== 'token' && type !== 'floor' && type !== 'wall' && type !== 'detail') || (type === 'token' && tokenType !== '1' && tokenType !== '4' && tokenType !== '8')) - return res.status(400) + return res.sendStatus(400); const file = req.files?.file; if (!file || Array.isArray(file)) return res.sendStatus(400); @@ -144,6 +149,33 @@ export default class DataRouter extends Router { })); + /** + * Deletes an asset from the database & filesystem. + */ + + this.router.post('/asset/delete/', this.authRoute(async (user, req, res) => { + const identifier = req.body.identifier; + + if (typeof identifier !== 'string') res.sendStatus(400); + else { + await this.db.deleteAsset(user, identifier); + res.sendStatus(200); + } + })); + + + /** + * Adds an asset to a collection. + */ + + this.router.post('/collection/add', this.authRoute(async (user, req, res) => { + if (typeof req.body.collection !== 'string' || typeof req.body.asset !== 'string') throw 'Invalid parameters.'; + + await this.db.addCollectionAsset(user, req.body.collection, req.body.asset); + res.send(await getAppData(user, 'collections')); + })); + + this.app.use('/data', this.router); } }