added floorplan editor

This commit is contained in:
dank074 2021-10-31 15:42:46 -05:00
parent 710f0f1e01
commit b7f1a11acc
34 changed files with 864 additions and 9 deletions

View File

@ -4,6 +4,7 @@
"camera.url": "https://nitro.nitrots.co/camera",
"thumbnails.url": "https://nitro.nitrots.co/camera/thumbnail/%thumbnail%.png",
"url.prefix": "http://localhost:3000",
"floorplan.tile.url": "${url.prefix}/floorplan-editor/tiles.json",
"chat.viewer.height.percentage": 0.40,
"widget.dimmer.colorwheel": false,
"hotelview": {

View File

@ -1,6 +1,8 @@
import { AvatarExpressionEnum, HabboClubLevelEnum, NitroEvent, RoomControllerLevel, RoomSessionChatEvent, RoomSettingsComposer, RoomWidgetEnum, RoomZoomEvent, TextureUtils } from '@nitrots/nitro-renderer';
import { GetConfiguration, GetNitroInstance } from '../../..';
import { GetRoomEngine, GetSessionDataManager } from '../../../..';
import { FloorplanEditorEvent } from '../../../../../events/floorplan-editor/FloorplanEditorEvent';
import { dispatchUiEvent } from '../../../../../hooks';
import { SendMessageHook } from '../../../../../hooks/messages';
import { RoomWidgetFloodControlEvent, RoomWidgetUpdateEvent } from '../events';
import { RoomWidgetChatMessage, RoomWidgetChatSelectAvatarMessage, RoomWidgetChatTypingMessage, RoomWidgetMessage, RoomWidgetRequestWidgetMessage } from '../messages';
@ -143,7 +145,8 @@ export class RoomWidgetChatInputHandler extends RoomWidgetHandler
case ':bcfloor':
if(this.container.roomSession.controllerLevel >= RoomControllerLevel.ROOM_OWNER)
{
this.container.processWidgetMessage(new RoomWidgetRequestWidgetMessage(RoomWidgetRequestWidgetMessage.FLOOR_EDITOR));
//this.container.processWidgetMessage(new RoomWidgetRequestWidgetMessage(RoomWidgetRequestWidgetMessage.FLOOR_EDITOR));
dispatchUiEvent(new FloorplanEditorEvent(FloorplanEditorEvent.SHOW_FLOORPLAN_EDITOR));
}
return null;

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View File

@ -5,5 +5,4 @@ export class ChatHistoryEvent extends NitroEvent
public static SHOW_CHAT_HISTORY: string = 'CHE_SHOW_CHAT_HISTORY';
public static HIDE_CHAT_HISTORY: string = 'CHE_HIDE_CHAT_HISTORY';
public static TOGGLE_CHAT_HISTORY: string = 'CHE_TOGGLE_CHAT_HISTORY';
public static CHAT_HISTORY_CHANGED: string = 'CHE_CHAT_HISTORY_CHANGED';
}

View File

@ -0,0 +1,8 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class FloorplanEditorEvent extends NitroEvent
{
public static SHOW_FLOORPLAN_EDITOR: string = 'FPEE_SHOW_FLOORPLAN_EDITOR';
public static HIDE_FLOORPLAN_EDITOR: string = 'FPEE_HIDE_FLOORPLAN_EDITOR';
public static TOGGLE_FLOORPLAN_EDITOR: string = 'FPEE_TOGGLE_FLOORPLAN_EDITOR';
}

View File

@ -22,3 +22,4 @@
@import './user-profile/UserProfileVew';
@import './chat-history/ChatHistoryView';
@import './help/HelpView';
@import './floorplan-editor/FloorplanEditorView';

View File

@ -11,8 +11,6 @@ import { RoomHistoryState } from './common/RoomHistoryState';
import { ChatHistoryContextProvider } from './context/ChatHistoryContext';
import { ChatEntryType } from './context/ChatHistoryContext.types';
export const ChatHistoryView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
@ -52,18 +50,15 @@ export const ChatHistoryView: FC<{}> = props =>
case ChatHistoryEvent.TOGGLE_CHAT_HISTORY:
setIsVisible(!isVisible);
break;
case ChatHistoryEvent.CHAT_HISTORY_CHANGED:
break;
}
}, [isVisible]);
useUiEvent(ChatHistoryEvent.HIDE_CHAT_HISTORY, onChatHistoryEvent);
useUiEvent(ChatHistoryEvent.SHOW_CHAT_HISTORY, onChatHistoryEvent);
useUiEvent(ChatHistoryEvent.TOGGLE_CHAT_HISTORY, onChatHistoryEvent);
useUiEvent(ChatHistoryEvent.CHAT_HISTORY_CHANGED, onChatHistoryEvent);
const cache = useMemo(() =>
{
{
return new CellMeasurerCache({
defaultHeight: 25,
fixedWidth: true,

View File

@ -0,0 +1,37 @@
.nitro-floorplan-editor
{
width: 760px;
height: 575px;
.editor-area
{
width: 100%;
height: 300px;
overflow-x: scroll;
}
.set-tile
{
background-image: url('../../assets/images/floorplaneditor/icon-tile-set.png');
}
.unset-tile
{
background-image: url('../../assets/images/floorplaneditor/icon-tile-unset.png');
}
.increase-height
{
background-image: url('../../assets/images/floorplaneditor/icon-tile-up.png');
}
.decrease-height
{
background-image: url('../../assets/images/floorplaneditor/icon-tile-down.png');
}
.set-door
{
background-image: url('../../assets/images/floorplaneditor/icon-door.png');
}
}

View File

@ -0,0 +1,119 @@
import { FloorHeightMapEvent, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { LocalizeText } from '../../api';
import { FloorplanEditorEvent } from '../../events/floorplan-editor/FloorplanEditorEvent';
import { CreateMessageHook, SendMessageHook, useUiEvent } from '../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
import { FloorplanEditor } from './common/FloorplanEditor';
import { convertNumbersForSaving, convertSettingToNumber } from './common/Utils';
import { FloorplanEditorContextProvider } from './context/FloorplanEditorContext';
import { IFloorplanSettings, initialFloorplanSettings } from './context/FloorplanEditorContext.types';
import { FloorplanCanvasView } from './views/FloorplanCanvasView';
import { FloorplanOptionsView } from './views/FloorplanOptionsView';
export const FloorplanEditorView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState(false);
const [floorplanSettings, setFloorplanSettings ] = useState<IFloorplanSettings>(initialFloorplanSettings);
const onFloorplanEditorEvent = useCallback((event: FloorplanEditorEvent) =>
{
switch(event.type)
{
case FloorplanEditorEvent.HIDE_FLOORPLAN_EDITOR:
setIsVisible(false);
break;
case FloorplanEditorEvent.SHOW_FLOORPLAN_EDITOR:
setIsVisible(true);
break;
case FloorplanEditorEvent.TOGGLE_FLOORPLAN_EDITOR:
setIsVisible(!isVisible);
break;
}
}, [isVisible]);
useUiEvent(FloorplanEditorEvent.HIDE_FLOORPLAN_EDITOR, onFloorplanEditorEvent);
useUiEvent(FloorplanEditorEvent.SHOW_FLOORPLAN_EDITOR, onFloorplanEditorEvent);
useUiEvent(FloorplanEditorEvent.TOGGLE_FLOORPLAN_EDITOR, onFloorplanEditorEvent);
useEffect(() =>
{
FloorplanEditor.instance.initialize();
}, []);
const onFloorHeightMapEvent = useCallback((event: FloorHeightMapEvent) =>
{
const parser = event.getParser();
if(!parser) return;
const settings = Object.assign({}, floorplanSettings);
settings.tilemap = parser.model;
settings.wallHeight = parser.wallHeight + 1;
setFloorplanSettings(settings);
}, [floorplanSettings]);
CreateMessageHook(FloorHeightMapEvent, onFloorHeightMapEvent);
const onRoomVisualizationSettingsEvent = useCallback((event: RoomVisualizationSettingsEvent) =>
{
const parser = event.getParser();
if(!parser) return;
const settings = Object.assign({}, floorplanSettings);
settings.thicknessFloor = convertSettingToNumber(parser.thicknessFloor)
settings.thicknessWall = convertSettingToNumber(parser.thicknessWall);
setFloorplanSettings(settings);
}, [floorplanSettings]);
CreateMessageHook(RoomVisualizationSettingsEvent, onRoomVisualizationSettingsEvent);
const saveFloorChanges = useCallback(() =>
{
SendMessageHook(new UpdateFloorPropertiesMessageComposer(
FloorplanEditor.instance.getCurrentTilemapString(),
floorplanSettings.entryPoint[0],
floorplanSettings.entryPoint[1],
floorplanSettings.entryPointDir,
convertNumbersForSaving(floorplanSettings.thicknessWall),
convertNumbersForSaving(floorplanSettings.thicknessFloor),
floorplanSettings.wallHeight - 1
));
}, [floorplanSettings.entryPoint, floorplanSettings.entryPointDir, floorplanSettings.thicknessFloor, floorplanSettings.thicknessWall, floorplanSettings.wallHeight]);
return (
<>
<FloorplanEditorContextProvider value={ { floorplanSettings, setFloorplanSettings } }>
{isVisible &&
<NitroCardView className="nitro-floorplan-editor">
<NitroCardHeaderView headerText={LocalizeText('floor.plan.editor.title')} onCloseClick={() => setIsVisible(false)} />
<NitroCardContentView>
<div className="row">
<div className="col">
<FloorplanOptionsView />
</div>
</div>
<div className="row">
<div className="col">
<FloorplanCanvasView />
</div>
</div>
<div className="row justify-content-between mt-2">
<div className="btn-group col-auto">
<button className="btn btn-primary">Revert changes</button>
</div>
<div className="btn-group col-auto" role="group" aria-label="First group">
<button className="btn btn-primary">Show Preview</button>
<button className="btn btn-primary">Import/Export</button>
<button className="btn btn-primary" onClick={saveFloorChanges}>Save</button>
</div>
</div>
</NitroCardContentView>
</NitroCardView>
}
</FloorplanEditorContextProvider>
</>
);
}

View File

@ -0,0 +1,39 @@
import { FloorAction, HEIGHT_SCHEME } from './Constants';
export class ActionSettings
{
private _currentAction: number;
private _currentHeight: string;
constructor()
{
this._currentAction = FloorAction.SET;
this._currentHeight = HEIGHT_SCHEME[1];
}
public get currentAction(): number
{
return this._currentAction;
}
public set currentAction(value: number)
{
this._currentAction = value;
}
public get currentHeight(): string
{
return this._currentHeight;
}
public set currentHeight(value: string)
{
this._currentHeight = value;
}
public clear(): void
{
this._currentAction = FloorAction.SET;
this._currentHeight = HEIGHT_SCHEME[1];
}
}

View File

@ -0,0 +1,13 @@
export const TILE_SIZE = 32;
export const MAX_NUM_TILE_PER_AXIS = 64;
export const HEIGHT_SCHEME: string = 'x0123456789abcdefghijklmnopq';
export class FloorAction
{
public static readonly DOOR = 0;
public static readonly UP = 1;
public static readonly DOWN = 2;
public static readonly SET = 3;
public static readonly UNSET = 4;
}

View File

@ -0,0 +1,412 @@
import { NitroPoint, NitroTilemap, PixiApplicationProxy, PixiInteractionEventProxy, POINT_STRUCT_SIZE } from '@nitrots/nitro-renderer';
import { GetConfiguration } from '../../../api';
import { ActionSettings } from './ActionSettings';
import { FloorAction, HEIGHT_SCHEME, MAX_NUM_TILE_PER_AXIS, TILE_SIZE } from './Constants';
import { Tile } from './Tile';
import { getScreenPositionForTile, getTileFromScreenPosition } from './Utils';
export class FloorplanEditor extends PixiApplicationProxy
{
private static _instance: FloorplanEditor = new FloorplanEditor();
private static readonly TILE_BLOCKED = 'r_blocked';
private static readonly TILE_DOOR = 'r_door';
private _tilemap: Tile[][];
private _width: number;
private _height: number;
private _isHolding: boolean;
private _doorLocation: NitroPoint;
private _lastUsedTile: NitroPoint;
private _tilemapRenderer: NitroTilemap;
private _actionSettings: ActionSettings;
private _isInitialized: boolean;
private constructor()
{
const width = TILE_SIZE * MAX_NUM_TILE_PER_AXIS + 20;
const height = (TILE_SIZE * MAX_NUM_TILE_PER_AXIS) / 2 + 100;
super({
width: width,
height: height,
backgroundColor: 0x2b2b2b,
antialias: true,
autoDensity: true,
resolution: 1,
sharedLoader: true,
sharedTicker: true
});
this._tilemap = [];
this._doorLocation = new NitroPoint(0, 0);
this._width = 0;
this._height = 0;
this._isHolding = false;
this._lastUsedTile = new NitroPoint(-1,-1);
this._actionSettings = new ActionSettings();
}
public initialize(): void
{
if(!this._isInitialized)
{
this.loader.add('tiles', GetConfiguration<string>('floorplan.tile.url'));
this.loader.load((_, resources) =>
{
this._tilemapRenderer = new NitroTilemap(resources['tiles'].spritesheet.baseTexture);
this.registerEventListeners();
this.stage.addChild(this._tilemapRenderer);
});
this._isInitialized = true;
}
}
private registerEventListeners(): void
{
//this._tilemapRenderer.interactive = true;
const tempPoint = new NitroPoint();
// @ts-ignore
this._tilemapRenderer.containsPoint = (position) =>
{
this._tilemapRenderer.worldTransform.applyInverse(position, tempPoint);
return this.tileHitDettection(tempPoint, false);
};
this._tilemapRenderer.on('pointerup', () =>
{
this._isHolding = false;
});
this._tilemapRenderer.on('pointerout', () =>
{
this._isHolding = false;
});
this._tilemapRenderer.on('pointerdown', (event: PixiInteractionEventProxy) =>
{
if(!(event.data.originalEvent instanceof PointerEvent)) return;
const pointerEvent = event.data.originalEvent;
if(pointerEvent.button === 2) return;
const location = event.data.global;
this.tileHitDettection(location, true);
});
this._tilemapRenderer.on('click', (event: PixiInteractionEventProxy) =>
{
if(!(event.data.originalEvent instanceof PointerEvent)) return;
const pointerEvent = event.data.originalEvent;
if(pointerEvent.button === 2) return;
const location = event.data.global;
this.tileHitDettection(location, true, true);
});
}
private tileHitDettection(tempPoint: NitroPoint, setHolding: boolean, isClick: boolean = false): boolean
{
// @ts-ignore
const buffer = this._tilemapRenderer.pointsBuf;
const bufSize = POINT_STRUCT_SIZE;
const len = buffer.length;
if(setHolding)
{
this._isHolding = true;
}
for(let j = 0; j < len; j += bufSize)
{
const bufIndex = j + bufSize;
const data = buffer.slice(j, bufIndex);
const width = data[4];
const height = data[5];
const mousePositionX = Math.floor(tempPoint.x);
const mousePositionY = Math.floor(tempPoint.y);
const tileStartX = data[2];
const tileStartY = data[3];
const centreX = tileStartX + (width / 2);
const centreY = tileStartY + (height / 2);
const dx = Math.abs(mousePositionX - centreX - 2);
const dy = Math.abs(mousePositionY - centreY - 2);
const solution = (dx / (width * 0.5) + dy / (height * 0.5) <= 1);//todo: improve this
if(solution)
{
if(this._isHolding)
{
const [realX, realY] = getTileFromScreenPosition(tileStartX, tileStartY);
if(isClick)
{
this.onClick(realX, realY);
}
else if(this._lastUsedTile.x !== realX || this._lastUsedTile.y !== realY)
{
this._lastUsedTile.x = realX;
this._lastUsedTile.y = realY;
this.onClick(realX, realY);
}
}
return true;
}
}
return false;
}
private onClick(x: number, y: number): void
{
const tile = this._tilemap[y][x];
const heightIndex = HEIGHT_SCHEME.indexOf(tile.height);
let futureHeightIndex = 0;
switch(this._actionSettings.currentAction)
{
case FloorAction.DOOR:
if(tile.height !== 'x')
{
this._doorLocation.x = x;
this._doorLocation.y = y;
this.renderTiles();
}
return;
case FloorAction.UP:
futureHeightIndex = heightIndex + 1;
break;
case FloorAction.DOWN:
futureHeightIndex = heightIndex - 1;
break;
case FloorAction.SET:
futureHeightIndex = HEIGHT_SCHEME.indexOf(this._actionSettings.currentHeight);
break;
case FloorAction.UNSET:
futureHeightIndex = 0;
break;
}
if(futureHeightIndex === -1) return;
if(heightIndex === futureHeightIndex) return;
if(futureHeightIndex > 0)
{
if((x + 1) > this._width) this._width = x + 1;
if( (y + 1) > this._height) this._height = y + 1;
}
const newHeight = HEIGHT_SCHEME[futureHeightIndex];
if(!newHeight) return;
if(tile.isBlocked) return;
this._tilemap[y][x].height = newHeight;
this.renderTiles();
}
public renderTiles(): void
{
this.tilemapRenderer.clear();
for(let y = 0; y < this._tilemap.length; y++)
{
for(let x = 0; x < this.tilemap[y].length; x++)
{
const tile = this.tilemap[y][x];
let assetName = tile.height;
if(this._doorLocation.x === x && this._doorLocation.y === y)
assetName = FloorplanEditor.TILE_DOOR;
if(tile.isBlocked) assetName = FloorplanEditor.TILE_BLOCKED;
//if((tile.height === 'x') || tile.height === 'X') continue;
const [positionX, positionY ] = getScreenPositionForTile(x, y);
this._tilemapRenderer.tile(`${assetName}.png`, positionX, positionY);
}
}
}
public setTilemap(map: string, blockedTiles: boolean[][]): void
{
this._tilemap = [];
const roomMapStringSplit = map.split('\r');
let width = 0;
let height = roomMapStringSplit.length;
// find the map width, height
for(let y = 0; y < height; y++)
{
const originalRow = roomMapStringSplit[y];
if(originalRow.length === 0)
{
roomMapStringSplit.splice(y, 1);
height = roomMapStringSplit.length;
y--;
continue;
}
if(originalRow.length > width)
{
width = originalRow.length;
}
}
// fill map with room heightmap tiles
for(let y = 0; y < height; y++)
{
this._tilemap[y] = [];
const rowString = roomMapStringSplit[y];
for(let x = 0; x < width; x++)
{
const blocked = (blockedTiles[y] && blockedTiles[y][x]) || false;
const char = rowString[x];
if(((!(char === 'x')) && (!(char === 'X')) && char))
{
this._tilemap[y][x] = new Tile(char, blocked);
}
else
{
this._tilemap[y][x] = new Tile('x', blocked);
}
}
for(let x = width; x < MAX_NUM_TILE_PER_AXIS; x++)
{
this.tilemap[y][x] = new Tile('x', false);
}
}
// fill remaining map with empty tiles
for(let y = height; y < MAX_NUM_TILE_PER_AXIS; y++)
{
if(!this.tilemap[y]) this.tilemap[y] = [];
for(let x = 0; x < MAX_NUM_TILE_PER_AXIS; x++)
{
this.tilemap[y][x] = new Tile('x', false);
}
}
this._width = width;
this._height = height;
}
public getCurrentTilemapString(): string
{
const highestTile = this._tilemap[this._height - 1][this._width - 1];
if(highestTile.height === 'x')
{
this._width = -1;
this._height = -1;
for(let y = MAX_NUM_TILE_PER_AXIS - 1; y >= 0; y--)
{
if(!this._tilemap[y]) continue;
for(let x = MAX_NUM_TILE_PER_AXIS - 1; x >= 0; x--)
{
if(!this._tilemap[y][x]) continue;
const tile = this._tilemap[y][x];
if(tile.height !== 'x')
{
if( (x + 1) > this._width)
this._width = x + 1;
if( (y + 1) > this._height)
this._height = y + 1;
}
}
}
}
const rows = [];
for(let y = 0; y < this._height; y++)
{
const row = [];
for(let x = 0; x < this._width; x++)
{
const tile = this._tilemap[y][x];
row[x] = tile.height;
}
rows[y] = row.join('');
}
return rows.join('\r');
}
public clear(): void
{
this._tilemapRenderer.interactive = false;
this._tilemap = [];
this._doorLocation.set(-1, -1);
this._width = 0;
this._height = 0;
this._isHolding = false;
this._lastUsedTile.set(-1, -1);
this._actionSettings.clear();
this._tilemapRenderer.clear();
}
public get tilemapRenderer(): NitroTilemap
{
return this._tilemapRenderer;
}
public get tilemap(): Tile[][]
{
return this._tilemap;
}
public get doorLocation(): NitroPoint
{
return this._doorLocation;
}
public set doorLocation(value: NitroPoint)
{
this._doorLocation = value;
}
public static get instance(): FloorplanEditor
{
if(!FloorplanEditor._instance)
{
FloorplanEditor._instance = new FloorplanEditor();
}
return FloorplanEditor._instance;
}
}

View File

@ -0,0 +1,31 @@
export class Tile
{
private _height: string;
private _isBlocked: boolean;
constructor(height: string, isBlocked: boolean)
{
this._height = height;
this._isBlocked = isBlocked;
}
public get height(): string
{
return this._height;
}
public set height(height: string)
{
this._height = height;
}
public get isBlocked(): boolean
{
return this._isBlocked;
}
public set isBlocked(val: boolean)
{
this._isBlocked = val;
}
}

View File

@ -0,0 +1,53 @@
import { TILE_SIZE } from './Constants';
export const getScreenPositionForTile = (x: number, y: number): [number , number] =>
{
let positionX = (x * TILE_SIZE / 2) - (y * TILE_SIZE / 2);
const positionY = (x * TILE_SIZE / 4) + (y * TILE_SIZE / 4);
positionX = positionX + 1024; // center the map in the canvas
return [positionX, positionY];
}
export const getTileFromScreenPosition = (x: number, y: number): [number, number] =>
{
const translatedX = x - 1024; // after centering translation
const realX = ((translatedX /(TILE_SIZE / 2)) + (y / (TILE_SIZE / 4))) / 2;
const realY = ((y /(TILE_SIZE / 4)) - (translatedX / (TILE_SIZE / 2))) / 2;
return [realX, realY];
}
export const convertNumbersForSaving = (value: number): number =>
{
value = parseInt(value.toString());
switch(value)
{
case 0:
return -2;
case 1:
return -1;
case 3:
return 1;
default:
return 0;
}
}
export const convertSettingToNumber = (value: number): number =>
{
switch(value)
{
case 0.25:
return 0;
case 0.5:
return 1;
case 2:
return 3;
default:
return 2;
}
}

View File

@ -0,0 +1,14 @@
import { createContext, FC, useContext } from 'react';
import { FloorplanEditorContextProps, IFloorplanEditorContext } from './FloorplanEditorContext.types';
const FloorplanEditorContext = createContext<IFloorplanEditorContext>({
floorplanSettings: null,
setFloorplanSettings: null
});
export const FloorplanEditorContextProvider: FC<FloorplanEditorContextProps> = props =>
{
return <FloorplanEditorContext.Provider value={ props.value }>{ props.children }</FloorplanEditorContext.Provider>
}
export const useFloorplanEditorContext = () => useContext(FloorplanEditorContext);

View File

@ -0,0 +1,32 @@
import { ProviderProps } from 'react';
export interface IFloorplanEditorContext
{
floorplanSettings: IFloorplanSettings;
setFloorplanSettings: React.Dispatch<React.SetStateAction<IFloorplanSettings>>;
}
export interface IFloorplanSettings {
tilemap: string;
reservedTiles: boolean[][];
entryPoint: [number, number];
entryPointDir: number;
wallHeight: number;
thicknessWall: number;
thicknessFloor: number;
}
export const initialFloorplanSettings: IFloorplanSettings = {
tilemap: '',
reservedTiles: [],
entryPoint: [0, 0],
entryPointDir: 2,
wallHeight: -1,
thicknessWall: 1,
thicknessFloor: 1
}
export interface FloorplanEditorContextProps extends ProviderProps<IFloorplanEditorContext>
{
}

View File

@ -0,0 +1,72 @@
import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, NitroPoint, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { CreateMessageHook, SendMessageHook } from '../../../hooks';
import { FloorplanEditor } from '../common/FloorplanEditor';
import { useFloorplanEditorContext } from '../context/FloorplanEditorContext';
export const FloorplanCanvasView: FC<{}> = props =>
{
const { floorplanSettings = null, setFloorplanSettings = null } = useFloorplanEditorContext();
const [ occupiedTilesReceived , setOccupiedTilesReceived ] = useState(false);
const [ entryTileReceived, setEntryTileReceived ] = useState(false);
const elementRef = useRef<HTMLDivElement>(null);
useEffect(() =>
{
SendMessageHook(new GetRoomEntryTileMessageComposer());
SendMessageHook(new GetOccupiedTilesMessageComposer());
FloorplanEditor.instance.tilemapRenderer.interactive = true;
elementRef.current.appendChild(FloorplanEditor.instance.renderer.view);
return ( () =>
{
FloorplanEditor.instance.clear();
});
}, []);
const onRoomOccupiedTilesMessageEvent = useCallback((event: RoomOccupiedTilesMessageEvent) =>
{
const parser = event.getParser();
if(!parser) return;
const settings = Object.assign({}, floorplanSettings);
settings.reservedTiles = parser.blockedTilesMap;
setFloorplanSettings(settings);
FloorplanEditor.instance.setTilemap(floorplanSettings.tilemap, parser.blockedTilesMap);
setOccupiedTilesReceived(true);
elementRef.current.scrollTo(FloorplanEditor.instance.view.width / 3, 0);
}, [floorplanSettings, setFloorplanSettings]);
CreateMessageHook(RoomOccupiedTilesMessageEvent, onRoomOccupiedTilesMessageEvent);
const onRoomEntryTileMessageEvent = useCallback((event: RoomEntryTileMessageEvent) =>
{
const parser = event.getParser();
if(!parser) return;
const settings = Object.assign({}, floorplanSettings);
settings.entryPoint = [parser.x, parser.y];
settings.entryPointDir = parser.direction;
setFloorplanSettings(settings);
FloorplanEditor.instance.doorLocation = new NitroPoint(settings.entryPoint[0], settings.entryPoint[1]);
setEntryTileReceived(true);
}, [floorplanSettings, setFloorplanSettings]);
CreateMessageHook(RoomEntryTileMessageEvent, onRoomEntryTileMessageEvent);
useEffect(() =>
{
if(entryTileReceived && occupiedTilesReceived)
FloorplanEditor.instance.renderTiles();
}, [entryTileReceived, occupiedTilesReceived])
return (
<div ref={elementRef} className="editor-area" />
);
}

View File

@ -0,0 +1,20 @@
import { FC } from 'react';
import { NitroCardGridItemView, NitroCardGridView } from '../../../layout';
import { useFloorplanEditorContext } from '../context/FloorplanEditorContext';
export const FloorplanOptionsView: FC<{}> = props =>
{
const { floorplanSettings = null, setFloorplanSettings = null } = useFloorplanEditorContext();
return (
<>
<NitroCardGridView columns={5}>
<NitroCardGridItemView className="set-tile" />
<NitroCardGridItemView className="unset-tile" />
<NitroCardGridItemView className="increase-height" />
<NitroCardGridItemView className="decrease-height" />
<NitroCardGridItemView className="set-door" />
</NitroCardGridView>
</>
);
}

View File

@ -8,6 +8,7 @@ import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
import { CameraWidgetView } from '../camera/CameraWidgetView';
import { CatalogView } from '../catalog/CatalogView';
import { ChatHistoryView } from '../chat-history/ChatHistoryView';
import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView';
import { FriendsView } from '../friends/FriendsView';
import { GroupsView } from '../groups/GroupsView';
import { HelpView } from '../help/HelpView';
@ -73,6 +74,7 @@ export const MainView: FC<MainViewProps> = props =>
<GroupsView />
<CameraWidgetView />
<HelpView />
<FloorplanEditorView />
</div>
);
}

View File

@ -3,6 +3,7 @@ import classNames from 'classnames';
import { FC, useCallback, useEffect, useState } from 'react';
import { GetConfiguration, GetGroupInformation, GetSessionDataManager, LocalizeText } from '../../../../api';
import { NavigatorEvent } from '../../../../events';
import { FloorplanEditorEvent } from '../../../../events/floorplan-editor/FloorplanEditorEvent';
import { RoomWidgetThumbnailEvent } from '../../../../events/room-widgets/thumbnail';
import { dispatchUiEvent } from '../../../../hooks/events';
import { SendMessageHook } from '../../../../hooks/messages';
@ -98,6 +99,9 @@ export const NavigatorRoomInfoView: FC<NavigatorRoomInfoViewProps> = props =>
setIsRoomMuted(value => !value);
SendMessageHook(new RoomMuteComposer());
return;
case 'open_floorplan_editor':
dispatchUiEvent(new FloorplanEditorEvent(FloorplanEditorEvent.TOGGLE_FLOORPLAN_EDITOR));
return;
case 'close':
onCloseClick();
return;
@ -155,7 +159,7 @@ export const NavigatorRoomInfoView: FC<NavigatorRoomInfoViewProps> = props =>
</div>
{ hasPermission('settings') && <>
<button className="btn btn-sm btn-primary w-100 mb-1" onClick={ () => processAction('open_room_settings') }>{ LocalizeText('navigator.room.popup.info.room.settings') }</button>
<button className="btn btn-sm btn-primary w-100 mb-1" disabled={ true }>{ LocalizeText('open.floor.plan.editor') }</button>
<button className="btn btn-sm btn-primary w-100 mb-1" onClick={ () => processAction('open_floorplan_editor') }>{ LocalizeText('open.floor.plan.editor') }</button>
</> }
{ hasPermission('staff_pick') && <button className="btn btn-sm btn-primary w-100 mb-1" onClick={ () => processAction('toggle_pick') }>{ LocalizeText(isRoomPicked ? 'navigator.staffpicks.unpick' : 'navigator.staffpicks.pick') }</button> }
<button className="btn btn-sm btn-danger w-100 mb-1" disabled={ true }>{ LocalizeText('help.emergency.main.report.room') }</button>