Fix chunk rendering, improve load times, standardize editor units.
BIN
app/res/tileset/hole.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
app/res/ui/icon/detail.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 654 B After Width: | Height: | Size: 654 B |
Before Width: | Height: | Size: 767 B |
@ -49,12 +49,16 @@ export default function App() {
|
||||
<div class='App-Main'>
|
||||
<AppSidebar />
|
||||
<Switch>
|
||||
<Redirect exact path='/' to='/campaigns' />
|
||||
|
||||
<Route path='/assets/collection/:user/:id' component={Routes.Collection} />
|
||||
<Route path='/asset/:user/:id' component={Routes.Asset} />
|
||||
<Route path='/assets' component={Routes.Assets} />
|
||||
|
||||
<Route path='/campaigns' component={Routes.Campaigns} />
|
||||
<Route path='/campaign/:id?' component={Routes.Campaign} />
|
||||
<Route path='/campaigns' component={Routes.Campaigns} />
|
||||
|
||||
<Redirect to='/campaigns' />
|
||||
<Redirect to='/' />
|
||||
</Switch>
|
||||
</div>
|
||||
</Route>
|
||||
|
57
app/src/components/route/AssetRoute.tsx
Normal file
@ -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 <Redirect to='/assets/' />;
|
||||
|
||||
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 (
|
||||
<div class='AssetRoute Page'>
|
||||
<aside class='Page-Sidebar'>
|
||||
<h2 class='Page-SidebarTitle'>{currentAsset.name}</h2>
|
||||
</aside>
|
||||
<main class='AssetRoute-Main'>
|
||||
<Switch>
|
||||
<Route>
|
||||
<img src={'/app/asset/' + currentAsset.path} role='presentational' alt=''/>
|
||||
<Button onClick={handleDeleteAsset} label='Delete'/>
|
||||
</Route>
|
||||
{/* <Route exact path='/campaign/:id'><CampaignOverview campaign={currentCampaign} /></Route>
|
||||
<Route exact path='/campaign/:id/players'>
|
||||
<PlayerList players={[
|
||||
{ name: 'Player', sprite: '/app/static/token/baby_blue_dragon.png' },
|
||||
{ name: 'Player', sprite: '/app/static/token/cadin_1.png' },
|
||||
{ name: 'Player', sprite: '/app/static/token/dragonfolk_1.png' },
|
||||
{ name: 'Player', sprite: '/app/static/token/druid_male.png' },
|
||||
{ name: 'Player', sprite: '/app/static/token/naexi_human_yklwa.png' }
|
||||
]}/>
|
||||
</Route>
|
||||
<Route exact path='/campaign/:id/maps'><MapList maps={currentCampaign.maps} /></Route>
|
||||
<Route exact path='/campaign/:id/maps/new'><NewMapForm /></Route>
|
||||
<Route exact path='/campaign/:id/assets'></Route>
|
||||
|
||||
<Redirect to={`/campaign/${id}`} />*/}
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<div class='AssetsRoute Page'>
|
||||
<aside class='Page-Sidebar'>
|
||||
<h2 class='Page-SidebarTitle'>Assets</h2>
|
||||
|
||||
{/* <Link className='Page-SidebarCategory' activeClassName='Active' exact to='/assets/'>Featured Assets</Link>
|
||||
<Link className='Page-SidebarCategory' activeClassName='Active' exact to='/assets/subscribed'>Subscribed Assets</Link>*/}
|
||||
<Link className='Page-SidebarCategory' activeClassName='Active' to='/assets/uploaded'>Uploaded Assets</Link>
|
||||
<Link className='Page-SidebarCategory' activeClassName='Active' exact to='/assets/'>My Assets</Link>
|
||||
<Link className='Page-SidebarCategory' activeClassName='Active' to='/assets/collections/'>My Collections</Link>
|
||||
</aside>
|
||||
<main class='AssetsRoute-Main'>
|
||||
<Switch>
|
||||
{/* <Route exact path='/assets/'>
|
||||
<h1>Storefront</h1>
|
||||
<Route exact path='/assets/'>
|
||||
<AssetList assets={(assets || []).filter(a => a.user === user!.user)}
|
||||
onClick={(user, identifier) => history.push(`/asset/${user}/${identifier}`)}
|
||||
onNew={() => history.push('/assets/new')} />
|
||||
</Route>
|
||||
<Route exact path='/assets/subscribed'>
|
||||
<h1>Subscribed</h1>
|
||||
</Route>*/}
|
||||
<Route exact path='/assets/uploaded'><MyAssetsList/></Route>
|
||||
|
||||
<Route exact path='/assets/collections'>
|
||||
<AssetCollectionList collections={collections || []}
|
||||
onClick={(user, identifier) => history.push(`/assets/collection/${user}/${identifier}`)} />
|
||||
</Route>
|
||||
|
||||
<Route exact path='/assets/new'><NewAssetForm/></Route>
|
||||
<Redirect to='/assets/uploaded' />
|
||||
{/* <Redirect to='/assets/uploaded' />*/}
|
||||
</Switch>
|
||||
</main>
|
||||
</div>
|
||||
|
59
app/src/components/route/CollectionRoute.tsx
Normal file
@ -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<boolean>(false);
|
||||
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const currentCollection = (collections ?? []).filter(c => c.identifier === id)[0];
|
||||
if (!currentCollection) return <Redirect to='/assets/collections/' />;
|
||||
|
||||
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 (
|
||||
<div class='CollectionRoute Page'>
|
||||
<aside class='Page-Sidebar'>
|
||||
<h2 class='Page-SidebarTitle'>{currentCollection.name}</h2>
|
||||
|
||||
{/* <Link className='Page-SidebarCategory' activeClassName='Active' exact to={`/campaign/${id}`}>Overview</Link>
|
||||
<Link className='Page-SidebarCategory' activeClassName='Active' to={`/campaign/${id}/players`}>Players</Link>
|
||||
<Link className='Page-SidebarCategory' activeClassName='Active' to={`/campaign/${id}/maps`}>Maps</Link>
|
||||
<Link className='Page-SidebarCategory' activeClassName='Active' to={`/campaign/${id}/assets`}>Assets</Link>*/}
|
||||
</aside>
|
||||
<main class='CollectionRoute-Main'>
|
||||
{!adding && <AssetList assets={collectionAssets} onClick={() => {/**/}} onNew={handleAddingAsset} newText='Add Asset' />}
|
||||
{adding && <Preact.Fragment>
|
||||
<h3>Add Asset</h3>
|
||||
<AssetList assets={assets || []} onClick={handleAddAsset} />
|
||||
</Preact.Fragment>}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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';
|
||||
|
87
app/src/components/view/AssetCollectionList.sass
Normal file
@ -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
|
40
app/src/components/view/AssetCollectionList.tsx
Normal file
@ -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 (
|
||||
<div class='AssetCollectionList'>
|
||||
{collections === undefined && <p>Loading Collections...</p>}
|
||||
{collections !== undefined &&
|
||||
<Preact.Fragment>
|
||||
<ul class='AssetCollectionList-Grid'>
|
||||
{collections.map(c => <li class='AssetCollectionList-CollectionWrap'>
|
||||
<button class='AssetCollectionList-Collection' onClick={() => onClick(c.user, c.identifier)}>
|
||||
<div class='AssetCollectionList-CollectionInner'>
|
||||
<div class='AssetCollectionList-CollectionPreview'>
|
||||
</div>
|
||||
<p class='AssetCollectionList-CollectionTitle'>{c.name || 'Untitled'}</p>
|
||||
</div>
|
||||
</button>
|
||||
</li>)}
|
||||
<li class='AssetCollectionList-CollectionWrap'>
|
||||
{onNew && <button onClick={onNew} className='AssetCollectionList-NewCollection'>
|
||||
<img src='/app/static/ui/icon/asset_new.png' alt=''/>
|
||||
<p>Create New Collection</p>
|
||||
</button>}
|
||||
</li>
|
||||
</ul>
|
||||
</Preact.Fragment>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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
|
43
app/src/components/view/AssetList.tsx
Normal file
@ -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 (
|
||||
<div class='AssetList'>
|
||||
{assets === undefined && <p>Loading Assets...</p>}
|
||||
{assets !== undefined &&
|
||||
<Preact.Fragment>
|
||||
<ul class='AssetList-Grid'>
|
||||
{assets.map(a => <li class='AssetList-AssetWrap'>
|
||||
<button class='AssetList-Asset' onClick={() => onClick(a.user, a.identifier)}>
|
||||
<div class='AssetList-AssetInner'>
|
||||
<div class='AssetList-AssetPreview'>
|
||||
<img src={'/app/asset/' + a.path} role='presentational' alt='' loading='lazy'/>
|
||||
</div>
|
||||
<p class='AssetList-AssetTitle'>{a.name || 'Untitled'}</p>
|
||||
</div>
|
||||
</button>
|
||||
</li>)}
|
||||
<li class='AssetList-AssetWrap'>
|
||||
{onNew && <button onClick={onNew} className='AssetList-NewAsset'>
|
||||
<img src='/app/static/ui/icon/asset_new.png' alt=''/>
|
||||
<p>{newText ?? 'Upload Asset'}</p>
|
||||
</button>}
|
||||
</li>
|
||||
</ul>
|
||||
</Preact.Fragment>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<div class='AssetsList'>
|
||||
{assets === undefined && <p>Loading Assets...</p>}
|
||||
{assets !== undefined &&
|
||||
<Preact.Fragment>
|
||||
<ul class='AssetsList-Grid'>
|
||||
{assets.map(a => <li class='AssetsList-AssetWrap'>
|
||||
<Link className='AssetsList-Asset' to={`/campaign/${a.identifier}`}>
|
||||
<div class='AssetsList-AssetInner'>
|
||||
<div class='AssetsList-AssetPreview'>
|
||||
<img src={'/app/asset/' + a.path} alt='' />
|
||||
</div>
|
||||
<p class='AssetsList-AssetTitle'>{a.name || 'Untitled'}</p>
|
||||
</div>
|
||||
</Link>
|
||||
</li>)}
|
||||
<li class='AssetsList-AssetWrap'>
|
||||
<Link className='AssetsList-NewAsset' to='/assets/new'>
|
||||
<img src='/app/static/ui/icon/asset_new.png' alt=''/>
|
||||
<p>Upload Asset</p>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Preact.Fragment>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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
|
||||
|
@ -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<File | null>(null);
|
||||
@ -25,7 +25,7 @@ export default function NewAssetForm() {
|
||||
const [ name, setName ] = useState<string>('');
|
||||
const [ identifier, setIdentifier ] = useState<string>('');
|
||||
|
||||
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() {
|
||||
<div class='NewAssetForm'>
|
||||
<h2 class='NewAssetForm-Title'>New Asset</h2>
|
||||
|
||||
<div class='NewAssetForm-Col2'>
|
||||
<div class='NewAssetForm-Col2-60'>
|
||||
<div>
|
||||
<Label label='Asset Type' />
|
||||
<ButtonGroup>
|
||||
<Button icon='token' label='Token' inactive={type !== 'token'} onClick={() => handleSetType('token')}/>
|
||||
<Button icon='ground' label='Ground' inactive={type !== 'ground'} onClick={() => handleSetType('ground')}/>
|
||||
<Button icon='detail' label='Detail' inactive={type !== 'detail'} onClick={() => handleSetType('detail')}/>
|
||||
<Button icon='floor' label='Floor' inactive={type !== 'floor'} onClick={() => handleSetType('floor')}/>
|
||||
<Button icon='wall' label='Wall' inactive={type !== 'wall'} onClick={() => handleSetType('wall')}/>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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'));
|
@ -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++;
|
||||
}
|
||||
}
|
@ -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<void> {
|
||||
return new Promise<void>(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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
99
app/src/editor/map/Map.ts
Executable file
@ -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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
114
app/src/editor/map/MapChunk.ts
Executable file
@ -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);
|
||||
}
|
||||
}
|
250
app/src/editor/map/MapLayer.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
49
app/src/editor/map/TileStore.ts
Executable file
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -1,7 +1 @@
|
||||
enum Layer {
|
||||
floor = 0,
|
||||
wall = 1,
|
||||
overlay = 2
|
||||
};
|
||||
|
||||
export default Layer;
|
||||
export type Layer = 'floor' | 'wall' | 'detail';
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.3 KiB |
BIN
assets/1d47e7bf3a7e92475630090704215c3d.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
assets/1d8c662852f88f8b0eab68764b6075a9.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 407 B |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
BIN
assets/5cafce367161af38becdcfbd4e965baf.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 958 B |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 843 B |
Before Width: | Height: | Size: 843 B After Width: | Height: | Size: 843 B |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1004 B |
Before Width: | Height: | Size: 4.8 KiB |
@ -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[];
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<DB.Asset[]> {
|
||||
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<DB.AssetCollection[]> {
|
||||
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<void> {
|
||||
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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|