init(🎉): new habbo renderer
This commit is contained in:
commit
29675c7c0b
10
.editorconfig
Executable file
10
.editorconfig
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
# editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
.parcel-cache
|
||||||
|
dist
|
||||||
|
.github
|
8
.parcelrc
Normal file
8
.parcelrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@parcel/config-default",
|
||||||
|
"transformers": {
|
||||||
|
"*.ts": [
|
||||||
|
"@parcel/transformer-typescript-tsc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": true
|
||||||
|
}
|
||||||
|
}
|
47
package.json
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "renderer",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/types.d.ts",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "parcel public/index.html --no-cache --open",
|
||||||
|
"build": "parcelb",
|
||||||
|
"lint:typescript": "eslint \"**/*.ts\" --ignore-path \".gitignore\" && tsc --noemit",
|
||||||
|
"lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\""
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@pixi/color": "7.2.4",
|
||||||
|
"@pixi/layers": "2.1.0",
|
||||||
|
"@pixi/utils": "^7.2.4",
|
||||||
|
"gsap": "^3.12.2",
|
||||||
|
"pixi.js": "^7.2.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@parcel/config-default": "^2.9.3",
|
||||||
|
"@parcel/transformer-typescript-tsc": "^2.9.3",
|
||||||
|
"@types/node": "^20.4.3",
|
||||||
|
"@walidoux/eslint-config": "1.0.3",
|
||||||
|
"@walidoux/prettier-config": "1.0.3",
|
||||||
|
"parcel": "^2.9.3",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"punycode": "1.4.1",
|
||||||
|
"querystring-es3": "^0.2.1",
|
||||||
|
"timers-browserify": "^2.0.12",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"eslint": "8.45.0",
|
||||||
|
"prettier": "2.8.8",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"url": "0.11.1"
|
||||||
|
},
|
||||||
|
"prettier": "@walidoux/prettier-config",
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"@walidoux/eslint-config"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
4645
pnpm-lock.yaml
Normal file
4645
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
31
public/index.html
Normal file
31
public/index.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Scuti Renderer</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
div#stats {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 500;
|
||||||
|
width: max(200px, 10vw, 10vh);
|
||||||
|
height: max(100px, 6vh, 6vw);
|
||||||
|
opacity: 0.8;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script type="module" src="script4.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
61
public/script.js
Normal file
61
public/script.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Scuti } from '../src/Scuti';
|
||||||
|
import { Room } from '../src/objects/rooms/Room';
|
||||||
|
import { FloorMaterial } from '../src/objects/rooms/materials/FloorMaterial';
|
||||||
|
import { WallMaterial } from '../src/objects/rooms/materials/WallMaterial';
|
||||||
|
import { Avatar } from '../src/objects/avatars/Avatar';
|
||||||
|
import { AvatarAction } from '../src/objects/avatars/actions/AvatarAction';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const renderer = new Scuti({
|
||||||
|
canvas: document.getElementById('app'),
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
resources: './resources'
|
||||||
|
});
|
||||||
|
await renderer.loadResources();
|
||||||
|
|
||||||
|
const tileMap =
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'00000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n';
|
||||||
|
|
||||||
|
const room = new Room(renderer, {
|
||||||
|
tileMap: tileMap,
|
||||||
|
/*floorMaterial: new FloorMaterial(renderer, 110),
|
||||||
|
wallMaterial: new WallMaterial(renderer, 1501)*/
|
||||||
|
//floorMaterial: new FloorMaterial(renderer, 307),
|
||||||
|
floorMaterial: new FloorMaterial(renderer, 110),
|
||||||
|
wallMaterial: new WallMaterial(renderer, 1601)
|
||||||
|
});
|
||||||
|
avatar(room, 2, 1, 0, 0);
|
||||||
|
avatar(room, 4, 1, 0, 1);
|
||||||
|
avatar(room, 6, 1, 0, 2);
|
||||||
|
avatar(room, 8, 1, 0, 3);
|
||||||
|
avatar(room, 10, 1, 0, 4);
|
||||||
|
avatar(room, 12, 1, 0, 5);
|
||||||
|
avatar(room, 14, 1, 0, 6);
|
||||||
|
avatar(room, 16, 1, 0, 7);
|
||||||
|
})();
|
||||||
|
|
||||||
|
function avatar(room, x, y, z, direction) {
|
||||||
|
const avatar = new Avatar({
|
||||||
|
figure: 'hr-100-61.hd-180-7.ch-210-66.lg-270-82.sh-290-80',
|
||||||
|
position: {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
z: z
|
||||||
|
},
|
||||||
|
bodyDirection: direction,
|
||||||
|
headDirection: direction,
|
||||||
|
actions: [AvatarAction.Talk, AvatarAction.Wave, AvatarAction.Walk, AvatarAction.CarryItem],
|
||||||
|
handItem: 55
|
||||||
|
});
|
||||||
|
room.objects.add(avatar);
|
||||||
|
}
|
427
public/script2.js
Normal file
427
public/script2.js
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
import { Scuti } from '../src/Scuti';
|
||||||
|
import { Room } from '../src/objects/rooms/Room';
|
||||||
|
import { FloorMaterial } from '../src/objects/rooms/materials/FloorMaterial';
|
||||||
|
import { WallMaterial } from '../src/objects/rooms/materials/WallMaterial';
|
||||||
|
import { FloorFurniture } from '../src/objects/furnitures/FloorFurniture';
|
||||||
|
import { WallFurniture } from '../src/objects/furnitures/WallFurniture';
|
||||||
|
import { Avatar } from '../src/objects/avatars/Avatar';
|
||||||
|
import { AvatarAction } from '../src/objects/avatars/actions/AvatarAction';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const renderer = new Scuti({
|
||||||
|
canvas: document.getElementById('app'),
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
resources: './resources'
|
||||||
|
});
|
||||||
|
await renderer.loadResources();
|
||||||
|
|
||||||
|
const tileMap =
|
||||||
|
'xxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'x222221111111111111x\n' +
|
||||||
|
'x222221111111111111x\n' +
|
||||||
|
'2222221111111111111x\n' +
|
||||||
|
'x222221111111111111x\n' +
|
||||||
|
'x222221111111111111x\n' +
|
||||||
|
'x222221111111111111x\n' +
|
||||||
|
'xxxxxxxx1111xxxxxxxx\n' +
|
||||||
|
'xxxxxxxx0000xxxxxxxx\n' +
|
||||||
|
'x000000x0000x000000x\n' +
|
||||||
|
'x000000x0000x000000x\n' +
|
||||||
|
'x00000000000x000000x\n' +
|
||||||
|
'x00000000000x000000x\n' +
|
||||||
|
'x000000000000000000x\n' +
|
||||||
|
'x000000000000000000x\n' +
|
||||||
|
'xxxxxxxx00000000000x\n' +
|
||||||
|
'x000000x00000000000x\n' +
|
||||||
|
'x000000x0000xxxxxxxx\n' +
|
||||||
|
'x00000000000x000000x\n' +
|
||||||
|
'x00000000000x000000x\n' +
|
||||||
|
'x00000000000x000000x\n' +
|
||||||
|
'x00000000000x000000x\n' +
|
||||||
|
'xxxxxxxx0000x000000x\n' +
|
||||||
|
'x000000x0000x000000x\n' +
|
||||||
|
'x000000x0000x000000x\n' +
|
||||||
|
'x000000000000000000x\n' +
|
||||||
|
'x000000000000000000x\n' +
|
||||||
|
'x000000000000000000x\n' +
|
||||||
|
'x000000000000000000x\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxx\n';
|
||||||
|
|
||||||
|
const tileMap1 =
|
||||||
|
'xxxxxx\n' +
|
||||||
|
'x4444432110011111x\n' +
|
||||||
|
'x444443211001xx00x\n' +
|
||||||
|
'0000000011001xx00x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0001000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x000000000xx00000x\n' +
|
||||||
|
'x000000000x000000x\n' +
|
||||||
|
'00000000000001000x\n' +
|
||||||
|
'x0000432100011110x\n' +
|
||||||
|
'x0000000000001000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000100000000x\n' +
|
||||||
|
'x0000001110000000x\n' +
|
||||||
|
'x0000011211000000x\n' +
|
||||||
|
'x0000012221000000x\n' +
|
||||||
|
'x000111232111100x\n' +
|
||||||
|
'x0001112321111000x\n' +
|
||||||
|
'x0000012321000000x\n' +
|
||||||
|
'x0000012221000000x\n' +
|
||||||
|
'x0000011211000000x\n' +
|
||||||
|
'x00000011100xxxxxx\n' +
|
||||||
|
'x00000001000xxxxxx\n' +
|
||||||
|
'x00000000000xxxxxx\n' +
|
||||||
|
'x00000000000xxxxxx\n';
|
||||||
|
|
||||||
|
const tileMap2 =
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'00000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n';
|
||||||
|
|
||||||
|
const tileMap3 =
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'x222222222222222222222222222x\n' +
|
||||||
|
'x222222222222222222222222222x\n' +
|
||||||
|
'2222222222222222222222222222x\n' +
|
||||||
|
'x222222222222222222222222222x\n' +
|
||||||
|
'x2222xxxxxx222222xxxxxxx2222x\n' +
|
||||||
|
'x2222xxxxxx111111xxxxxxx2222x\n' +
|
||||||
|
'x2222xx111111111111111xx2222x\n' +
|
||||||
|
'x2222xx111111111111111xx2222x\n' +
|
||||||
|
'x2222xx11xxx1111xxxx11xx2222x\n' +
|
||||||
|
'x2222xx11xxx0000xxxx11xx2222x\n' +
|
||||||
|
'x22222111x00000000xx11xx2222x\n' +
|
||||||
|
'x22222111x00000000xx11xx2222x\n' +
|
||||||
|
'x22222111x00000000xx11xx2222x\n' +
|
||||||
|
'x22222111x00000000xx11xx2222x\n' +
|
||||||
|
'x22222111x00000000xx11xx2222x\n' +
|
||||||
|
'x22222111x00000000xx11xx2222x\n' +
|
||||||
|
'x2222xx11xxxxxxxxxxx11xx2222x\n' +
|
||||||
|
'x2222xx11xxxxxxxxxxx11xx2222x\n' +
|
||||||
|
'x2222xx111111111111111xx2222x\n' +
|
||||||
|
'x2222xx111111111111111xx2222x\n' +
|
||||||
|
'x2222xxxxxxxxxxxxxxxxxxx2222x\n' +
|
||||||
|
'x2222xxxxxxxxxxxxxxxxxxx2222x\n' +
|
||||||
|
'x222222222222222222222222222x\n' +
|
||||||
|
'x222222222222222222222222222x\n' +
|
||||||
|
'x222222222222222222222222222x\n' +
|
||||||
|
'x222222222222222222222222222x\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
|
||||||
|
|
||||||
|
const tileMap4 =
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeedcba9888888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xdxxxxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'xcxxxxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'xbxxxxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'xaxxxxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'aaaaaaaaaaaaaaaaaxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaa98766666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxx5xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxx4xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxx3xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx9xxx3333333333xxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx8xxx3333333333xxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx7xxx3333333333xxxx\n' +
|
||||||
|
'xxx777777777xxxx6xxx3333333333xxxx\n' +
|
||||||
|
'xxx777777777xxxx5xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxx777777777xxxx4xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxx777777777xxxx3xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxx777777777xxxx2xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xfffffffffxxxxxx1xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx111111111111111111\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
|
||||||
|
|
||||||
|
const tileMap5 =
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'x2222xx1111111111xx11111111\n' +
|
||||||
|
'x2222xx1111111111xx11111111\n' +
|
||||||
|
'222222111111111111111111111\n' +
|
||||||
|
'x22222111111111111111111111\n' +
|
||||||
|
'x22222111111111111111111111\n' +
|
||||||
|
'x22222111111111111111111111\n' +
|
||||||
|
'x2222xx1111111111xx11111111\n' +
|
||||||
|
'x2222xx1111111111xx11111111\n' +
|
||||||
|
'x2222xx1111111111xxxx1111xx\n' +
|
||||||
|
'x2222xx1111111111xxxx0000xx\n' +
|
||||||
|
'xxxxxxx1111111111xx00000000\n' +
|
||||||
|
'xxxxxxx1111111111xx00000000\n' +
|
||||||
|
'x22222111111111111000000000\n' +
|
||||||
|
'x22222111111111111000000000\n' +
|
||||||
|
'x22222111111111111000000000\n' +
|
||||||
|
'x22222111111111111000000000\n' +
|
||||||
|
'x2222xx1111111111xx00000000\n' +
|
||||||
|
'x2222xx1111111111xx00000000\n' +
|
||||||
|
'x2222xxxx1111xxxxxxxxxxxxxx\n' +
|
||||||
|
'x2222xxxx0000xxxxxxxxxxxxxx\n' +
|
||||||
|
'x2222x0000000000xxxxxxxxxxx\n' +
|
||||||
|
'x2222x0000000000xxxxxxxxxxx\n' +
|
||||||
|
'x2222x0000000000xxxxxxxxxxx\n' +
|
||||||
|
'x2222x0000000000xxxxxxxxxxx\n' +
|
||||||
|
'x2222x0000000000xxxxxxxxxxx\n' +
|
||||||
|
'x2222x0000000000xxxxxxxxxxx';
|
||||||
|
|
||||||
|
const room = new Room(renderer, {
|
||||||
|
tileMap: tileMap5,
|
||||||
|
/*floorMaterial: new FloorMaterial(renderer, 110),
|
||||||
|
wallMaterial: new WallMaterial(renderer, 1501)*/
|
||||||
|
//floorMaterial: new FloorMaterial(renderer, 307),
|
||||||
|
floorMaterial: new FloorMaterial(renderer, 110),
|
||||||
|
wallMaterial: new WallMaterial(renderer, 1601)
|
||||||
|
});
|
||||||
|
/*setTimeout(() => {
|
||||||
|
room.wallMaterial = new WallMaterial(renderer, 1701);
|
||||||
|
room.floorMaterial = new FloorMaterial(renderer, 301);
|
||||||
|
room.wallThickness = 8;
|
||||||
|
room.floorThickness = 8;
|
||||||
|
room.wallHeight = 6;
|
||||||
|
}, 5000);*/
|
||||||
|
const furniture = new FloorFurniture({
|
||||||
|
id: 1619,
|
||||||
|
position: {
|
||||||
|
x: 7,
|
||||||
|
y: 5,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 4,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
const furniture2 = new FloorFurniture({
|
||||||
|
id: 3895,
|
||||||
|
position: {
|
||||||
|
x: 7,
|
||||||
|
y: 5,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 4,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
const wallFurniture = new WallFurniture({
|
||||||
|
position: {
|
||||||
|
x: 1,
|
||||||
|
y: 0,
|
||||||
|
offsetX: 8,
|
||||||
|
offsetY: 36
|
||||||
|
},
|
||||||
|
state: 0,
|
||||||
|
id: 4625,
|
||||||
|
direction: 2
|
||||||
|
});
|
||||||
|
const wallFurniture2 = new WallFurniture({
|
||||||
|
position: {
|
||||||
|
x: 1,
|
||||||
|
y: 3,
|
||||||
|
offsetX: 8,
|
||||||
|
offsetY: 36
|
||||||
|
},
|
||||||
|
state: 0,
|
||||||
|
id: 4625,
|
||||||
|
direction: 2
|
||||||
|
});
|
||||||
|
const wallFurniture3 = new WallFurniture({
|
||||||
|
position: {
|
||||||
|
x: 4,
|
||||||
|
y: 0,
|
||||||
|
offsetX: 14,
|
||||||
|
offsetY: 41
|
||||||
|
},
|
||||||
|
id: 4066,
|
||||||
|
direction: 4,
|
||||||
|
state: 3
|
||||||
|
});
|
||||||
|
const avatar = new Avatar({
|
||||||
|
figure: 'hr-100-61.hd-180-7.ch-210-66.lg-270-82.sh-290-80',
|
||||||
|
position: {
|
||||||
|
x: 4,
|
||||||
|
y: 4,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
bodyDirection: 2,
|
||||||
|
headDirection: 2,
|
||||||
|
actions: [
|
||||||
|
//AvatarAction.Idle,
|
||||||
|
//AvatarAction.Walk,
|
||||||
|
AvatarAction.Talk,
|
||||||
|
AvatarAction.Wave,
|
||||||
|
AvatarAction.Walk,
|
||||||
|
AvatarAction.CarryItem
|
||||||
|
],
|
||||||
|
handItem: 55
|
||||||
|
});
|
||||||
|
|
||||||
|
let hd = [180, 185, 190, 195, 200, 205];
|
||||||
|
let hr = [100, 105, 110, 115, 125, 135, 145, 155, 165, 170];
|
||||||
|
let ch = [210, 215, 220, 225, 230, 235, 240, 245, 250, 255];
|
||||||
|
let sh = [290, 295, 300, 305, 725, 730, 735, 740, 905, 906, 907, 908];
|
||||||
|
let ha = [
|
||||||
|
1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019,
|
||||||
|
1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027
|
||||||
|
];
|
||||||
|
let lg = [270, 275, 280, 285, 281, 695, 696, 716, 700, 705, 710, 715, 720, 827];
|
||||||
|
let wa = [2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012];
|
||||||
|
let ea = [1401, 1402, 1403, 1404, 1405, 1406];
|
||||||
|
let color = [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
|
||||||
|
];
|
||||||
|
let color2 = [
|
||||||
|
31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
|
||||||
|
60, 61
|
||||||
|
];
|
||||||
|
let color3 = [
|
||||||
|
62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
|
||||||
|
91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110
|
||||||
|
];
|
||||||
|
let actions = [
|
||||||
|
AvatarAction.Default,
|
||||||
|
AvatarAction.Walk,
|
||||||
|
AvatarAction.GestureSmile,
|
||||||
|
AvatarAction.Wave,
|
||||||
|
AvatarAction.GestureAngry,
|
||||||
|
AvatarAction.GestureSurprised,
|
||||||
|
AvatarAction.Respect,
|
||||||
|
AvatarAction.CarryItem,
|
||||||
|
AvatarAction.UseItem
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let y = 0; y < 2; y++) {
|
||||||
|
for (let x = 0; x < 2; x++) {
|
||||||
|
let figure =
|
||||||
|
'hr-' +
|
||||||
|
hr[Math.floor(Math.random() * hr.length)] +
|
||||||
|
'-' +
|
||||||
|
color2[Math.floor(Math.random() * color2.length)] +
|
||||||
|
'.hd-' +
|
||||||
|
hd[Math.floor(Math.random() * hd.length)] +
|
||||||
|
'-' +
|
||||||
|
color[Math.floor(Math.random() * color.length)] +
|
||||||
|
'.ch-' +
|
||||||
|
ch[Math.floor(Math.random() * ch.length)] +
|
||||||
|
'-' +
|
||||||
|
color3[Math.floor(Math.random() * color3.length)] +
|
||||||
|
'.lg-' +
|
||||||
|
lg[Math.floor(Math.random() * lg.length)] +
|
||||||
|
'-' +
|
||||||
|
color3[Math.floor(Math.random() * color3.length)] +
|
||||||
|
'.sh-' +
|
||||||
|
sh[Math.floor(Math.random() * sh.length)] +
|
||||||
|
'-' +
|
||||||
|
color3[Math.floor(Math.random() * color3.length)] +
|
||||||
|
'.ha-' +
|
||||||
|
ha[Math.floor(Math.random() * ha.length)] +
|
||||||
|
'-' +
|
||||||
|
color3[Math.floor(Math.random() * color3.length)] +
|
||||||
|
'.wa-' +
|
||||||
|
wa[Math.floor(Math.random() * wa.length)] +
|
||||||
|
'-' +
|
||||||
|
color3[Math.floor(Math.random() * color3.length)] +
|
||||||
|
'.ea-' +
|
||||||
|
ea[Math.floor(Math.random() * ea.length)] +
|
||||||
|
'-' +
|
||||||
|
color3[Math.floor(Math.random() * color3.length)];
|
||||||
|
let randomAvatar = new Avatar({
|
||||||
|
position: {
|
||||||
|
x: x + 1,
|
||||||
|
y: y,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
bodyDirection: 2,
|
||||||
|
headDirection: 2,
|
||||||
|
figure: figure,
|
||||||
|
actions: [actions[Math.floor(Math.random() * actions.length)]]
|
||||||
|
});
|
||||||
|
//room.objects.add(randomAvatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
room.objects.add(avatar);
|
||||||
|
avatar.onPointerDown = (event) => {
|
||||||
|
console.log('down', event);
|
||||||
|
};
|
||||||
|
avatar.onPointerUp = (event) => {
|
||||||
|
console.log('up', event);
|
||||||
|
};
|
||||||
|
avatar.onPointerMove = (event) => {
|
||||||
|
console.log('move', event);
|
||||||
|
};
|
||||||
|
avatar.onPointerOut = (event) => {
|
||||||
|
console.log('out', event);
|
||||||
|
};
|
||||||
|
avatar.onPointerOver = (event) => {
|
||||||
|
console.log('over', event);
|
||||||
|
};
|
||||||
|
avatar.onDoubleClick = (event) => {
|
||||||
|
console.log('doubleclick', event);
|
||||||
|
};
|
||||||
|
//room.objects.add(furniture2);
|
||||||
|
room.visualization.onTileClick = (position) => {
|
||||||
|
console.log('click', position);
|
||||||
|
/*if(furniture.direction === 4) {
|
||||||
|
furniture.direction = 2
|
||||||
|
} else {
|
||||||
|
furniture.direction = 4;
|
||||||
|
}*/
|
||||||
|
avatar.roomPosition = {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
z: position.z
|
||||||
|
};
|
||||||
|
wallFurniture.pos = {
|
||||||
|
x: 1,
|
||||||
|
y: Math.floor(Math.random() * (10 - 1 + 1) + 1),
|
||||||
|
offsetX: 8,
|
||||||
|
offsetY: 36
|
||||||
|
};
|
||||||
|
};
|
||||||
|
room.visualization.onTileOver = (position) => {
|
||||||
|
console.log('over', position);
|
||||||
|
};
|
||||||
|
room.visualization.onTileOut = (position) => {
|
||||||
|
console.log('out', position);
|
||||||
|
};
|
||||||
|
room.objects.add(furniture);
|
||||||
|
furniture.onDoubleClick = (event) => {
|
||||||
|
console.log('dblclick furni', event);
|
||||||
|
};
|
||||||
|
//room.addRoomObject(wallFurniture);
|
||||||
|
//room.addRoomObject(wallFurniture2);
|
||||||
|
//room.addRoomObject(wallFurniture3);
|
||||||
|
})();
|
443
public/script3.js
Normal file
443
public/script3.js
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
import { Scuti } from '../src/Scuti';
|
||||||
|
import { Room } from '../src/objects/rooms/Room';
|
||||||
|
import { FloorMaterial } from '../src/objects/rooms/materials/FloorMaterial';
|
||||||
|
import { WallMaterial } from '../src/objects/rooms/materials/WallMaterial';
|
||||||
|
import { FloorFurniture } from '../src/objects/furnitures/FloorFurniture';
|
||||||
|
import { WallFurniture } from '../src/objects/furnitures/WallFurniture';
|
||||||
|
import { Avatar } from '../src/objects/avatars/Avatar';
|
||||||
|
import { AvatarAction } from '../src/objects/avatars/actions/AvatarAction';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const renderer = new Scuti({
|
||||||
|
canvas: document.getElementById('app'),
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
resources: 'http://localhost:8081/'
|
||||||
|
});
|
||||||
|
await renderer.loadResources('http://localhost:8081/');
|
||||||
|
|
||||||
|
const tileMap =
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeedcba9888888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xeeeeeeeeeeeeeeeexxxxxx88888888888\n' +
|
||||||
|
'xdxxxxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'xcaaaxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'xbaaaxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'xaaaaxxxxxxxxxxxxxxxxxx88888888888\n' +
|
||||||
|
'aaaaaaaaaaaaaaaaaxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxx6666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaa98766666666666666\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxx5xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxx4xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxxxxxxxxxxx3xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xaaaaaaaaaaaaaaaaxxx3333333333xxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx9xxx3333333333xxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx8xxx3333333333xxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx7xxx3333333333xxxx\n' +
|
||||||
|
'xxx777777777xxxx6xxx3333333333xxxx\n' +
|
||||||
|
'xxx777777777xxxx5xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxx777777777xxxx4xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxx777777777xxxx3xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxx777777777xxxx2xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xfffffffffxxxxxx1xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xfffffffffxxxxxx111111111111111111\n' +
|
||||||
|
'xxxxxxxxxxxxxxxx111111111111111111\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
|
||||||
|
|
||||||
|
const tileMapUpdated =
|
||||||
|
'x6543210000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'00000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n' +
|
||||||
|
'x0000000000000000x\n';
|
||||||
|
|
||||||
|
const room = new Room(renderer, {
|
||||||
|
tileMap:
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxxxxxxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxx00xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxx00xxxxxxxxxxxxxxxxx\n' +
|
||||||
|
'xxxxx00xxxxx00xxxxxxxxxx\n' +
|
||||||
|
'xxxxxxxxxxx00000000xxxxx\n' +
|
||||||
|
'xxxxxxxxxxx00000000xxxxx\n' +
|
||||||
|
'xxxxxxxxxxx00000000xxxxx\n' +
|
||||||
|
'xxxxxxxxxxx00000000xxxxx\n' +
|
||||||
|
'xxxxxxxxxxx00000000xxxxx\n' +
|
||||||
|
'xxxxxxxxxxx00000000xxxxx\n' +
|
||||||
|
'xxxxxxxxxxx000000000000x\n' +
|
||||||
|
'xxxxxxxxxxx000000000000x\n' +
|
||||||
|
'xxxxxxxxxxx000000000000x\n' +
|
||||||
|
'xxxxxxxxxx0000000000000x\n' +
|
||||||
|
'xxxxxxxxxxx0000000000000\n' +
|
||||||
|
'xxxxxxxxxxx0000000000000\n' +
|
||||||
|
'x66666xxxxx000000000000x\n' +
|
||||||
|
'x66666xxxxx000000000000x\n' +
|
||||||
|
'x6666xxxxxx000000000000x\n' +
|
||||||
|
'x6666xxxxxx000000000000x\n' +
|
||||||
|
'x6666xxxxxx00000000xxxxx\n' +
|
||||||
|
'x6666xxxxxx00000000xxxxx\n' +
|
||||||
|
'x6666xxxxxx00000000xxxxx\n' +
|
||||||
|
'x6666xxxxxx00000000xxxxx\n' +
|
||||||
|
'x6666xxxxxx00000000xxxxx\n' +
|
||||||
|
'x6666xxxxxx00000000xxxxx\n' +
|
||||||
|
'xxxxxxxxxxxx00xxxxxxxxxx',
|
||||||
|
/*floorMaterial: new FloorMaterial(renderer, 110),
|
||||||
|
wallMaterial: new WallMaterial(renderer, 1501)*/
|
||||||
|
//floorMaterial: new FloorMaterial(renderer, 307),
|
||||||
|
floorMaterial: new FloorMaterial(renderer, 110),
|
||||||
|
wallMaterial: new WallMaterial(renderer, 1601)
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
//room.tileMap = tileMapUpdated;
|
||||||
|
//room.camera._centerCamera();
|
||||||
|
}, 2000);
|
||||||
|
const avatar = new Avatar({
|
||||||
|
//figure: "hr-100-61.hd-180-7.ch-210-66.lg-270-82.sh-290-80",
|
||||||
|
// police figure: "hr-892-46.hd-209-8.ch-225-81.lg-270-64.sh-300-64.ca-1804-64.wa-2012",
|
||||||
|
figure: 'hd-209-14.ch-3688-1408.lg-280-1408.sh-290-1408.ha-1008.ea-3578.ca-1806-82.cc-3360-1408',
|
||||||
|
//figure: "hd-180-1.ch-255-66.lg-280-110.sh-305-62.ha-1012-110.hr-828-61",
|
||||||
|
position: {
|
||||||
|
x: 4,
|
||||||
|
y: 4,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
bodyDirection: 6,
|
||||||
|
headDirection: 6,
|
||||||
|
actions: [
|
||||||
|
//AvatarAction.Idle,
|
||||||
|
//AvatarAction.Walk,
|
||||||
|
AvatarAction.Talk,
|
||||||
|
AvatarAction.Wave,
|
||||||
|
//AvatarAction.Walk,
|
||||||
|
AvatarAction.CarryItem,
|
||||||
|
AvatarAction.Sit
|
||||||
|
],
|
||||||
|
handItem: 55
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
avatar.addAction(AvatarAction.Walk);
|
||||||
|
}, 5000);
|
||||||
|
setTimeout(() => {
|
||||||
|
avatar.removeAction(AvatarAction.Walk);
|
||||||
|
}, 7000);
|
||||||
|
setTimeout(() => {
|
||||||
|
avatar.addAction(AvatarAction.Walk);
|
||||||
|
}, 9000);
|
||||||
|
room.objects.add(avatar);
|
||||||
|
room.tiles.onPointerDown = (position) => {
|
||||||
|
console.log('click', position);
|
||||||
|
avatar.roomPosition = position.position;
|
||||||
|
};
|
||||||
|
room.tiles.onDoubleClick = (position) => {
|
||||||
|
console.log('dblclick', position);
|
||||||
|
};
|
||||||
|
room.tiles.onPointerOver = (event) => {};
|
||||||
|
dice(room, 5, 5, 2);
|
||||||
|
dice(room, 5, 6, 1);
|
||||||
|
dice(room, 6, 5, 2);
|
||||||
|
dice(room, 7, 5, 2);
|
||||||
|
dice(room, 7, 6, 1);
|
||||||
|
const wallFurniture = new WallFurniture({
|
||||||
|
id: 4054,
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0
|
||||||
|
},
|
||||||
|
direction: 4,
|
||||||
|
state: 0
|
||||||
|
});
|
||||||
|
const wallFurniture2 = new WallFurniture({
|
||||||
|
position: {
|
||||||
|
x: 1,
|
||||||
|
y: 0,
|
||||||
|
offsetX: 8,
|
||||||
|
offsetY: 36
|
||||||
|
},
|
||||||
|
state: 0,
|
||||||
|
id: 4625,
|
||||||
|
direction: 2
|
||||||
|
});
|
||||||
|
const furniture = new FloorFurniture({
|
||||||
|
id: 1619,
|
||||||
|
position: {
|
||||||
|
x: 8,
|
||||||
|
y: 5,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
setTimeout(() => {}, 2000);
|
||||||
|
document.body.addEventListener('keyup', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
room.camera.centerCamera(furniture);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const furniture2 = new FloorFurniture({
|
||||||
|
id: 8916,
|
||||||
|
position: {
|
||||||
|
x: 8,
|
||||||
|
y: 10,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
const furniture3 = new FloorFurniture({
|
||||||
|
id: 8916,
|
||||||
|
position: {
|
||||||
|
x: 10,
|
||||||
|
y: 10,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 0
|
||||||
|
});
|
||||||
|
furniture.onPointerDown = () => {
|
||||||
|
if (furniture.selected) {
|
||||||
|
furniture.selected = false;
|
||||||
|
} else {
|
||||||
|
furniture.selected = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const guildGate = new FloorFurniture({
|
||||||
|
id: 4389,
|
||||||
|
position: {
|
||||||
|
x: 11,
|
||||||
|
y: 5,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
const background1 = new FloorFurniture({
|
||||||
|
id: 3996,
|
||||||
|
position: {
|
||||||
|
x: 20,
|
||||||
|
y: 19,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 1,
|
||||||
|
state: 0
|
||||||
|
});
|
||||||
|
const background2 = new FloorFurniture({
|
||||||
|
id: 3996,
|
||||||
|
position: {
|
||||||
|
x: 12,
|
||||||
|
y: 11,
|
||||||
|
z: 1
|
||||||
|
},
|
||||||
|
direction: 1,
|
||||||
|
state: 0
|
||||||
|
});
|
||||||
|
const background3 = new FloorFurniture({
|
||||||
|
id: 3996,
|
||||||
|
position: {
|
||||||
|
x: 12,
|
||||||
|
y: 11,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 1,
|
||||||
|
state: 0
|
||||||
|
});
|
||||||
|
const background4 = new FloorFurniture({
|
||||||
|
id: 3996,
|
||||||
|
position: {
|
||||||
|
x: 11,
|
||||||
|
y: 10,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 1,
|
||||||
|
state: 0
|
||||||
|
});
|
||||||
|
const background5 = new FloorFurniture({
|
||||||
|
id: 3996,
|
||||||
|
position: {
|
||||||
|
x: 8,
|
||||||
|
y: 5,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 1,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(furniture.position);
|
||||||
|
/*furniture.move({
|
||||||
|
x: 10,
|
||||||
|
y: 6,
|
||||||
|
z: 0
|
||||||
|
}, 0);*/
|
||||||
|
//
|
||||||
|
furniture.rotate(4);
|
||||||
|
//furniture.direction = 4;
|
||||||
|
furniture.state = 0;
|
||||||
|
guildGate.state = 101;
|
||||||
|
guildGate.visualization.secondaryColor = 0xffff00;
|
||||||
|
guildGate.visualization.primaryColor = 0x00ffff;
|
||||||
|
}, 5000);
|
||||||
|
const wallFurniture3 = new WallFurniture({
|
||||||
|
position: {
|
||||||
|
x: 4,
|
||||||
|
y: 0,
|
||||||
|
offsetX: 14,
|
||||||
|
offsetY: 41
|
||||||
|
},
|
||||||
|
id: 4066,
|
||||||
|
direction: 4,
|
||||||
|
state: 3
|
||||||
|
});
|
||||||
|
background1.onLoad = () => {
|
||||||
|
background1.move(
|
||||||
|
{
|
||||||
|
x: 20,
|
||||||
|
y: 19,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
background1.visualization.offsetX = -720;
|
||||||
|
background1.visualization.offsetY = 190;
|
||||||
|
background1.visualization.offsetZ = 8700;
|
||||||
|
background1.visualization.imageUrl = '/images/room_ads/wl15/wl15_a.png';
|
||||||
|
};
|
||||||
|
room.objects.add(background1);
|
||||||
|
background2.onLoad = () => {
|
||||||
|
background2.move(
|
||||||
|
{
|
||||||
|
x: 12,
|
||||||
|
y: 11,
|
||||||
|
z: 1
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
background2.visualization.offsetX = -253;
|
||||||
|
background2.visualization.offsetY = 446;
|
||||||
|
background2.visualization.offsetZ = 8700;
|
||||||
|
background2.visualization.imageUrl = '/images/room_ads/wl15/wl15_d.png';
|
||||||
|
};
|
||||||
|
room.objects.add(background2);
|
||||||
|
background3.onLoad = () => {
|
||||||
|
background3.move(
|
||||||
|
{
|
||||||
|
x: 12,
|
||||||
|
y: 11,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
background3.visualization.offsetX = -704;
|
||||||
|
background3.visualization.offsetY = 155;
|
||||||
|
background3.visualization.offsetZ = 8700;
|
||||||
|
background3.visualization.imageUrl = '/images/room_ads/wl15/wl15_c.png';
|
||||||
|
};
|
||||||
|
room.objects.add(background3);
|
||||||
|
background4.onLoad = () => {
|
||||||
|
background4.move(
|
||||||
|
{
|
||||||
|
x: 11,
|
||||||
|
y: 10,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
background4.visualization.offsetX = -253;
|
||||||
|
background4.visualization.offsetY = 187;
|
||||||
|
background4.visualization.offsetZ = 9995;
|
||||||
|
background4.visualization.imageUrl = '/images/room_ads/wl15/wl15_b.png';
|
||||||
|
};
|
||||||
|
room.objects.add(background4);
|
||||||
|
//background.visualization.imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/2367px-Vue.js_Logo_2.svg.png";
|
||||||
|
room.objects.add(wallFurniture);
|
||||||
|
setInterval(() => {
|
||||||
|
wallFurniture3.state += 1;
|
||||||
|
wallFurniture3.destroy();
|
||||||
|
}, 3000);
|
||||||
|
room.objects.add(wallFurniture3);
|
||||||
|
room.objects.add(furniture);
|
||||||
|
room.objects.add(furniture2);
|
||||||
|
room.objects.add(furniture3);
|
||||||
|
room.objects.add(guildGate);
|
||||||
|
|
||||||
|
/*const furniture = new FloorFurniture({
|
||||||
|
id: 1619,
|
||||||
|
position: {
|
||||||
|
x: 7,
|
||||||
|
y: 5,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 4,
|
||||||
|
state: 1
|
||||||
|
});*/
|
||||||
|
})();
|
||||||
|
|
||||||
|
function dice(room, x, y, z) {
|
||||||
|
let furni5 = new FloorFurniture({
|
||||||
|
position: {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
z: z
|
||||||
|
},
|
||||||
|
//direction: randomRotation[Math.floor(Math.random() * randomRotation.length)],
|
||||||
|
direction: 0,
|
||||||
|
//id: furniId[Math.floor(Math.random() * furniId.length)],
|
||||||
|
id: 284,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
room.objects.add(furni5);
|
||||||
|
let timeout = undefined;
|
||||||
|
furni5.onDoubleClick = (event) => {
|
||||||
|
console.log(event);
|
||||||
|
//if(furni5.infos.logic === "furniture_dice") {
|
||||||
|
console.log('clicked furni5', event);
|
||||||
|
if (event.tag === 'activate') {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
furni5.state = -1;
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
furni5.state = Math.floor(Math.random() * 6) + 1;
|
||||||
|
}, 1000);
|
||||||
|
/*setTimeout(() => {
|
||||||
|
furni5.state = 0
|
||||||
|
}, 2000);*/
|
||||||
|
} else {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
furni5.state = 0;
|
||||||
|
}
|
||||||
|
//x@}
|
||||||
|
};
|
||||||
|
}
|
166
public/script4.js
Normal file
166
public/script4.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { Scuti } from '../src/Scuti';
|
||||||
|
import { Room } from '../src/objects/rooms/Room';
|
||||||
|
import { FloorMaterial } from '../src/objects/rooms/materials/FloorMaterial';
|
||||||
|
import { WallMaterial } from '../src/objects/rooms/materials/WallMaterial';
|
||||||
|
import { FloorFurniture } from '../src/objects/furnitures/FloorFurniture';
|
||||||
|
import {WiredSelectionFilter} from "../src/objects/filters/WiredSelectionFilter";
|
||||||
|
import {WallFurniture} from "../src";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const renderer = new Scuti({
|
||||||
|
canvas: document.getElementById('app'),
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
resources: 'https://kozennnn.github.io/scuti-resources/'
|
||||||
|
});
|
||||||
|
await renderer.loadResources('https://kozennnn.github.io/scuti-resources/');
|
||||||
|
|
||||||
|
const tileMap = 'x1110001\n' + 'x0000000\n' + '00000000\n' + 'x0000000\n' + 'x0000000\n';
|
||||||
|
|
||||||
|
const room = new Room(renderer, {
|
||||||
|
tileMap: tileMap,
|
||||||
|
floorMaterial: new FloorMaterial(renderer, 110),
|
||||||
|
wallMaterial: new WallMaterial(renderer, 2301)
|
||||||
|
});
|
||||||
|
const furniture = new FloorFurniture({
|
||||||
|
//id: 4950,
|
||||||
|
//id: 1619,
|
||||||
|
id: 4967,
|
||||||
|
position: {
|
||||||
|
x: 5,
|
||||||
|
y: 4,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
room.objects.add(furniture);
|
||||||
|
furniture.onPointerDown = () => {
|
||||||
|
console.log('clicked');
|
||||||
|
};
|
||||||
|
const furniture3 = new FloorFurniture({
|
||||||
|
id: 8916,
|
||||||
|
position: {
|
||||||
|
x: 10,
|
||||||
|
y: 10,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
const furniture2 = new FloorFurniture({
|
||||||
|
id: 8916,
|
||||||
|
position: {
|
||||||
|
x: 8,
|
||||||
|
y: 10,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
const wallFurniture = new WallFurniture({
|
||||||
|
id: 4625,
|
||||||
|
position: {
|
||||||
|
x: -1,
|
||||||
|
y: 2,
|
||||||
|
offsetX: 2,
|
||||||
|
offsetY: -25
|
||||||
|
},
|
||||||
|
direction: 2,
|
||||||
|
state: 2
|
||||||
|
});
|
||||||
|
const wallFurniture2 = new WallFurniture({
|
||||||
|
id: 4032,
|
||||||
|
position: {
|
||||||
|
x: 3,
|
||||||
|
y: -1,
|
||||||
|
offsetX: 4,
|
||||||
|
offsetY: -30
|
||||||
|
},
|
||||||
|
direction: 4,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
room.objects.add(furniture3);
|
||||||
|
room.objects.add(furniture2);
|
||||||
|
room.objects.add(wallFurniture);
|
||||||
|
room.objects.add(wallFurniture2);
|
||||||
|
setTimeout(() => wallFurniture.move({
|
||||||
|
x: -1,
|
||||||
|
y: 3,
|
||||||
|
offsetX: 2,
|
||||||
|
offsetY: -25
|
||||||
|
}), 3000);
|
||||||
|
setTimeout(() => wallFurniture.move({
|
||||||
|
x: -1,
|
||||||
|
y: 5,
|
||||||
|
offsetX: 2,
|
||||||
|
offsetY: -25
|
||||||
|
}), 5000);
|
||||||
|
//setTimeout(() => room.objects.add(furniture), 6000);
|
||||||
|
furniture3.onLoadComplete = () => {
|
||||||
|
console.log('loaded!');
|
||||||
|
};
|
||||||
|
room.tiles.onPointerDown = (event) => {
|
||||||
|
furniture.move(event.position);
|
||||||
|
//room.tileMap = tileMap;
|
||||||
|
};
|
||||||
|
//dice(room, 5, 5, 2);
|
||||||
|
document.onkeydown = (e) => {
|
||||||
|
e = e || window.event;
|
||||||
|
|
||||||
|
if (e.keyCode == '38') {
|
||||||
|
if (room.camera.zoomLevel <= 1) {
|
||||||
|
room.camera.zoomLevel = room.camera.zoomLevel * 2;
|
||||||
|
} else {
|
||||||
|
room.camera.zoomLevel += 1;
|
||||||
|
}
|
||||||
|
} else if (e.keyCode == '40') {
|
||||||
|
if (room.camera.zoomLevel <= 1) {
|
||||||
|
room.camera.zoomLevel = room.camera.zoomLevel / 2;
|
||||||
|
} else {
|
||||||
|
room.camera.zoomLevel -= 1;
|
||||||
|
}
|
||||||
|
} else if (e.keyCode == '37') {
|
||||||
|
furniture.rotate(4);
|
||||||
|
} else if (e.keyCode == '39') {
|
||||||
|
const filter = new WiredSelectionFilter(0xffffff, 0x999999);
|
||||||
|
furniture.addFilter(filter);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
function dice(room, x, y, z) {
|
||||||
|
let furni5 = new FloorFurniture({
|
||||||
|
position: {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
z: z
|
||||||
|
},
|
||||||
|
//direction: randomRotation[Math.floor(Math.random() * randomRotation.length)],
|
||||||
|
direction: 0,
|
||||||
|
//id: furniId[Math.floor(Math.random() * furniId.length)],
|
||||||
|
id: 284,
|
||||||
|
state: 1
|
||||||
|
});
|
||||||
|
room.objects.add(furni5);
|
||||||
|
let timeout = undefined;
|
||||||
|
furni5.onDoubleClick = (event) => {
|
||||||
|
console.log(event);
|
||||||
|
//if(furni5.infos.logic === "furniture_dice") {
|
||||||
|
console.log('clicked furni5', event);
|
||||||
|
if (event.tag === 'activate') {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
furni5.state = -1;
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
furni5.state = Math.floor(Math.random() * 6) + 1;
|
||||||
|
}, 1000);
|
||||||
|
/*setTimeout(() => {
|
||||||
|
furni5.state = 0
|
||||||
|
}, 2000);*/
|
||||||
|
} else {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
furni5.state = 0;
|
||||||
|
}
|
||||||
|
//x@}
|
||||||
|
};
|
||||||
|
}
|
126
src/Scuti.ts
Normal file
126
src/Scuti.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { Application, BaseTexture, Container, SCALE_MODES, settings } from 'pixi.js'
|
||||||
|
import { PixiPlugin } from 'gsap/PixiPlugin'
|
||||||
|
import { gsap } from 'gsap'
|
||||||
|
import { Stage } from '@pixi/layers'
|
||||||
|
|
||||||
|
import { Logger } from './utilities/Logger'
|
||||||
|
import type { IRendererConfiguration } from './types/Configuration'
|
||||||
|
import { AssetLoader } from './utilities/AssetLoader'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience class to create a new Scuti renderer.
|
||||||
|
*
|
||||||
|
* This class automatically load all the needed resources and initialise the PixiJS application.
|
||||||
|
* @example
|
||||||
|
* import { Scuti } from 'scuti-renderer';
|
||||||
|
*
|
||||||
|
* // Create the renderer
|
||||||
|
* const renderer = new Scuti({
|
||||||
|
* canvas: document.getElementById("app"),
|
||||||
|
* width: window.innerWidth,
|
||||||
|
* height: window.innerHeight,
|
||||||
|
* resources: './resources'
|
||||||
|
* });
|
||||||
|
* await renderer.loadResources();
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Scuti {
|
||||||
|
/**
|
||||||
|
* The canvas that will be used to render the PixiJS canvas.
|
||||||
|
*
|
||||||
|
* @member {HTMLElement}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _canvas: HTMLElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PixiJS application instance that will be used to render everything.
|
||||||
|
*
|
||||||
|
* @member {Application}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _application: Application
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The renderer logger instance.
|
||||||
|
*
|
||||||
|
* @member {Logger}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _logger: Logger = new Logger('Scuti')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {IRendererConfiguration} [configuration] - The renderer configuration.
|
||||||
|
* @param {HTMLElement} [configuration.canvas] - The canvas that will be used to render everything.
|
||||||
|
* @param {number} [configuration.width] - The width of the render part.
|
||||||
|
* @param {number} [configuration.height] - The height of the render part.
|
||||||
|
* @param {string} [configuration.resources] - The URL of the resource server.
|
||||||
|
**/
|
||||||
|
constructor(configuration: IRendererConfiguration) {
|
||||||
|
this._logger.info('⚡ Scuti Renderer - v1.0.0')
|
||||||
|
|
||||||
|
/** Change the PixiJS settings and default settings */
|
||||||
|
settings.RESOLUTION = 1
|
||||||
|
Container.defaultSortableChildren = true
|
||||||
|
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST
|
||||||
|
|
||||||
|
/** Register the plugins */
|
||||||
|
gsap.registerPlugin(PixiPlugin)
|
||||||
|
|
||||||
|
/** Create the PixiJS application */
|
||||||
|
this._application = new Application({
|
||||||
|
width: configuration.width,
|
||||||
|
height: configuration.height,
|
||||||
|
resolution: 1,
|
||||||
|
antialias: false
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Support for PIXI.js dev-tool */
|
||||||
|
if (process.env.NODE_ENV === 'development') (globalThis as any).__PIXI_APP__ = this._application
|
||||||
|
this._application.stage = new Stage()
|
||||||
|
this._canvas = configuration.canvas
|
||||||
|
|
||||||
|
/** Append it to the canvas */
|
||||||
|
this._canvas.append(this._application.view as unknown as Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It loads all the resources that are essentials for rendering rooms and objects.
|
||||||
|
* It's necessary to call this method just after the instanciation of this class.
|
||||||
|
*
|
||||||
|
* @member {Promise<void>}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public async loadResources(domain: string = 'http://127.0.0.1:8081/'): Promise<void> {
|
||||||
|
AssetLoader.domain = domain
|
||||||
|
|
||||||
|
/** And now load them */
|
||||||
|
await Promise.all([
|
||||||
|
AssetLoader.load('room/materials', 'generic/room/room_data.json'),
|
||||||
|
AssetLoader.load('room/room', 'generic/room/room.json'),
|
||||||
|
AssetLoader.load('room/cursors', 'generic/tile_cursor/tile_cursor.json'),
|
||||||
|
AssetLoader.load('furnitures/floor/placeholder', 'generic/place_holder/place_holder_furniture.json'),
|
||||||
|
AssetLoader.load('furnitures/wall/placeholder', 'generic/place_holder/place_holder_wall_item.json'),
|
||||||
|
AssetLoader.load('furnitures/furnidata', 'gamedata/furnidata.json'),
|
||||||
|
AssetLoader.load('figures/figuredata', 'gamedata/figuredata.json'),
|
||||||
|
AssetLoader.load('figures/figuremap', 'gamedata/figuremap.json'),
|
||||||
|
AssetLoader.load('figures/draworder', 'gamedata/draworder.json'),
|
||||||
|
AssetLoader.load('figures/actions', 'generic/HabboAvatarActions.json'),
|
||||||
|
AssetLoader.load('figures/partsets', 'generic/HabboAvatarPartSets.json'),
|
||||||
|
AssetLoader.load('figures/animations', 'generic/HabboAvatarAnimations.json')
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the PixiJS application instance.
|
||||||
|
*
|
||||||
|
* @member {Application}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get application(): Application {
|
||||||
|
return this._application
|
||||||
|
}
|
||||||
|
}
|
16
src/enums/Direction.ts
Normal file
16
src/enums/Direction.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Direction enum regroup the 8 directions that an avatar can handle on Habbo.
|
||||||
|
*
|
||||||
|
* @enum
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export enum Direction {
|
||||||
|
NORTH /** Direction 0 */,
|
||||||
|
NORTH_EAST /** Direction 1 */,
|
||||||
|
EAST /** Direction 2 */,
|
||||||
|
SOUTH_EAST /** Direction 3 */,
|
||||||
|
SOUTH /** Direction 4 */,
|
||||||
|
SOUTH_WEST /** Direction 5 */,
|
||||||
|
WEST /** Direction 6 */,
|
||||||
|
NORTH_WEST /** Direction 7 */
|
||||||
|
}
|
11
src/enums/StairType.ts
Normal file
11
src/enums/StairType.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* StairType enum regroup the 3 differents type of stairs available on Habbo.
|
||||||
|
*
|
||||||
|
* @enum
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export enum StairType {
|
||||||
|
INNER_CORNER_STAIR,
|
||||||
|
OUTER_CORNER_STAIR,
|
||||||
|
STAIR
|
||||||
|
}
|
12
src/enums/WallType.ts
Normal file
12
src/enums/WallType.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* WallType enum regroup the 3 differents type of walls available on Habbo.
|
||||||
|
*
|
||||||
|
* @enum
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export enum WallType {
|
||||||
|
CORNER_WALL,
|
||||||
|
LEFT_WALL,
|
||||||
|
RIGHT_WALL,
|
||||||
|
DOOR_WALL
|
||||||
|
}
|
8
src/index.ts
Normal file
8
src/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export { Scuti } from './Scuti';
|
||||||
|
export { Room } from './objects/rooms/Room';
|
||||||
|
export { FloorMaterial } from './objects/rooms/materials/FloorMaterial';
|
||||||
|
export { FloorFurniture } from './objects/furnitures/FloorFurniture';
|
||||||
|
export { WallMaterial } from './objects/rooms/materials/WallMaterial';
|
||||||
|
export { WallFurniture } from './objects/furnitures/WallFurniture';
|
||||||
|
export { Avatar } from './objects/avatars/Avatar';
|
||||||
|
export { AvatarAction } from './objects/avatars/actions/AvatarAction';
|
98
src/objects/avatars/Avatar.ts
Normal file
98
src/objects/avatars/Avatar.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { gsap } from 'gsap';
|
||||||
|
|
||||||
|
import type { Direction } from '../../enums/Direction';
|
||||||
|
import { AvatarAction } from './actions/AvatarAction';
|
||||||
|
import { AvatarActionManager } from './actions/AvatarActionManager';
|
||||||
|
import { AvatarAnimationManager } from './animations/AvatarAnimationManager';
|
||||||
|
import type { AvatarBodyPart } from './visualizations/AvatarBodyPart';
|
||||||
|
import type { IAvatarConfig, IAvatarPosition } from '../../types/Avatar';
|
||||||
|
import { RoomObject } from '../rooms/objects/RoomObject';
|
||||||
|
import { AvatarVisualization } from './visualizations/AvatarVisualization';
|
||||||
|
import { AvatarFigure } from './AvatarFigure';
|
||||||
|
|
||||||
|
export class Avatar extends RoomObject {
|
||||||
|
private readonly _figure: AvatarFigure;
|
||||||
|
private _bodyDirection: Direction;
|
||||||
|
private _headDirection: Direction;
|
||||||
|
private _actions: AvatarAction[];
|
||||||
|
private readonly _actionManager: AvatarActionManager;
|
||||||
|
private readonly _animationManager: AvatarAnimationManager;
|
||||||
|
private readonly _bodyParts: AvatarBodyPart[] = [];
|
||||||
|
|
||||||
|
constructor(config: IAvatarConfig) {
|
||||||
|
super(config);
|
||||||
|
|
||||||
|
this._headDirection = config.headDirection;
|
||||||
|
this._bodyDirection = config.bodyDirection;
|
||||||
|
this._actions = config.actions;
|
||||||
|
|
||||||
|
this._figure = new AvatarFigure(this, config.figure);
|
||||||
|
this._visualization = new AvatarVisualization(this);
|
||||||
|
this._actionManager = new AvatarActionManager(AvatarAction.Default);
|
||||||
|
this._animationManager = new AvatarAnimationManager(config.actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addAction(action: AvatarAction): void {
|
||||||
|
if (!Boolean(this._animationManager.getAnimation(action))) this._animationManager.registerAnimation(action);
|
||||||
|
if (!this._actions.includes(action)) this._actions.push(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeAction(action: AvatarAction): void {
|
||||||
|
this._actions = this._actions.filter((fAction) => {
|
||||||
|
return fAction !== action;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
move(position: IAvatarPosition, duration: number = 0.5): void {
|
||||||
|
if (this._visualization === undefined) return;
|
||||||
|
gsap.to(this.position, {
|
||||||
|
x: 32 * position.x - 32 * position.y,
|
||||||
|
y: 16 * position.x + 16 * position.y - 32 * position.z,
|
||||||
|
duration,
|
||||||
|
ease: 'linear',
|
||||||
|
onUpdate: () => this._visualization.updatePosition()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get actions(): AvatarAction[] {
|
||||||
|
return this._actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set actions(actions: AvatarAction[]) {
|
||||||
|
actions.forEach((action) => this._animationManager.registerAnimation(action));
|
||||||
|
actions.forEach((action) => this.actions.push(action));
|
||||||
|
this._actions = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get headDirection(): Direction {
|
||||||
|
return this._headDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set headDirection(direction: Direction) {
|
||||||
|
this._headDirection = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get bodyDirection(): Direction {
|
||||||
|
return this._bodyDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set bodyDirection(direction: Direction) {
|
||||||
|
this._bodyDirection = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get actionManager(): AvatarActionManager {
|
||||||
|
return this._actionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get bodyParts(): AvatarBodyPart[] {
|
||||||
|
return this._bodyParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get animationManager(): AvatarAnimationManager {
|
||||||
|
return this._animationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get figure(): AvatarFigure {
|
||||||
|
return this._figure;
|
||||||
|
}
|
||||||
|
}
|
79
src/objects/avatars/AvatarFigure.ts
Normal file
79
src/objects/avatars/AvatarFigure.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { Assets } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Figure, IAvatarPart, IFigureData, IFigureMap } from '../../types';
|
||||||
|
import type { Avatar } from './Avatar';
|
||||||
|
import { AvatarBodyPart } from './visualizations/AvatarBodyPart';
|
||||||
|
|
||||||
|
export class AvatarFigure {
|
||||||
|
private readonly _avatar: Avatar;
|
||||||
|
private _figure!: Figure;
|
||||||
|
|
||||||
|
constructor(avatar: Avatar, figure: string) {
|
||||||
|
this._avatar = avatar;
|
||||||
|
|
||||||
|
this._parseFigure(figure);
|
||||||
|
this._parseBodyParts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _parseFigure(figure: string): void {
|
||||||
|
// todo!(): Handle split errors for the string
|
||||||
|
|
||||||
|
this._figure = new Map(
|
||||||
|
figure.split('.').map((part) => {
|
||||||
|
const data = part.split('-');
|
||||||
|
return [
|
||||||
|
data[0],
|
||||||
|
{
|
||||||
|
setId: Number(data[1]),
|
||||||
|
colors: data.splice(2, 2).map((color) => Number(color))
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _parseBodyParts(): void {
|
||||||
|
return this._figure.forEach((set, type) => {
|
||||||
|
const parts = this._getParts(type, set.setId);
|
||||||
|
|
||||||
|
return this._avatar.bodyParts.push(
|
||||||
|
new AvatarBodyPart(this._avatar, {
|
||||||
|
type,
|
||||||
|
setId: set.setId,
|
||||||
|
colors: set.colors,
|
||||||
|
parts,
|
||||||
|
actions: this._avatar.actions
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getParts(type: string, setId: number): IAvatarPart[] {
|
||||||
|
const figureData = Assets.get<IFigureData>('figures/figuredata');
|
||||||
|
const figureMap = Assets.get<IFigureMap>('figures/figuremap');
|
||||||
|
let parts: IAvatarPart[] = [];
|
||||||
|
|
||||||
|
if (!Boolean(figureData.settype[type]?.set[setId])) return parts;
|
||||||
|
|
||||||
|
const hiddenLayers = figureData.settype[type].set[setId].hiddenLayers;
|
||||||
|
const set = figureData.settype[type].set[setId];
|
||||||
|
|
||||||
|
set?.parts.forEach((part) => {
|
||||||
|
const libId = figureMap.parts[part.type][String(part.id)];
|
||||||
|
const lib = figureMap.libs[libId];
|
||||||
|
//console.log(part.type, libId);
|
||||||
|
part.lib = lib;
|
||||||
|
parts.push(part);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hiddenLayers !== undefined) {
|
||||||
|
parts = parts.filter((part) => !hiddenLayers.includes(part.type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get figure(): Figure {
|
||||||
|
return this._figure;
|
||||||
|
}
|
||||||
|
}
|
30
src/objects/avatars/actions/AvatarAction.ts
Normal file
30
src/objects/avatars/actions/AvatarAction.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export enum AvatarAction {
|
||||||
|
Default = 'Default',
|
||||||
|
Walk = 'Move',
|
||||||
|
Sleep = 'Sleep',
|
||||||
|
GestureSurprised = 'GestureSurprised',
|
||||||
|
GestureAngry = 'GestureAngry',
|
||||||
|
GestureSad = 'GestureSad',
|
||||||
|
GestureSmile = 'GestureSmile',
|
||||||
|
Gesture = 'Gesture',
|
||||||
|
Talk = 'Talk',
|
||||||
|
CarryItem = 'CarryItem',
|
||||||
|
UseItem = 'UseItem',
|
||||||
|
Dance = 'Dance',
|
||||||
|
AvatarEffect = 'AvatarEffect',
|
||||||
|
Idle = 'Idle',
|
||||||
|
Laugh = 'Laugh',
|
||||||
|
Blow = 'Blow',
|
||||||
|
Sign = 'Sign',
|
||||||
|
Wave = 'Wave',
|
||||||
|
Respect = 'Respect',
|
||||||
|
RideJump = 'RideJump',
|
||||||
|
SnowboardSquat = 'SnowboardSquat',
|
||||||
|
SnowboardUp = 'SnowboardUp',
|
||||||
|
SnowboardOllie = 'SnowboardOllie',
|
||||||
|
Snowboard360 = 'Snowboard360',
|
||||||
|
Sit = 'Sit',
|
||||||
|
Swim = 'Swim',
|
||||||
|
Float = 'Float',
|
||||||
|
Lay = 'Lay'
|
||||||
|
}
|
48
src/objects/avatars/actions/AvatarActionManager.ts
Normal file
48
src/objects/avatars/actions/AvatarActionManager.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Assets } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { AvatarAction } from './AvatarAction';
|
||||||
|
import type { IActionDefinition, IAvatarPartSets } from '../../../types/Avatar';
|
||||||
|
|
||||||
|
export class AvatarActionManager {
|
||||||
|
private readonly _avatarActionsLib: IActionDefinition[] = Assets.get('figures/actions');
|
||||||
|
private readonly _avatarPartSetsLib: IAvatarPartSets = Assets.get('figures/partsets');
|
||||||
|
|
||||||
|
constructor(private readonly _defaultAction: AvatarAction) {}
|
||||||
|
|
||||||
|
public filterActions(actions: AvatarAction[], partType: string): AvatarAction[] {
|
||||||
|
return actions.filter((action: AvatarAction) => {
|
||||||
|
const actionDefinition: IActionDefinition = this._avatarActionsLib[action];
|
||||||
|
return (
|
||||||
|
actionDefinition !== undefined &&
|
||||||
|
this._avatarPartSetsLib.activePartSets[actionDefinition.activepartset].includes(partType)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public sortActions(actions: AvatarAction[]): AvatarAction[] {
|
||||||
|
if (actions.length === 0) return [this._defaultAction];
|
||||||
|
return actions.sort((a: AvatarAction, b: AvatarAction) => {
|
||||||
|
const actionDefinitionA: IActionDefinition = this._avatarActionsLib[a];
|
||||||
|
const actionDefinitionB: IActionDefinition = this._avatarActionsLib[b];
|
||||||
|
if (Number(actionDefinitionA.precedence) < Number(actionDefinitionB.precedence)) return -1;
|
||||||
|
if (Number(actionDefinitionA.precedence) > Number(actionDefinitionB.precedence)) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getActionDefinition(action: AvatarAction): IActionDefinition {
|
||||||
|
return this._avatarActionsLib[action];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get defaultAction(): AvatarAction {
|
||||||
|
return this._defaultAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get definitions(): IActionDefinition[] {
|
||||||
|
return this._avatarActionsLib;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get partSets(): IAvatarPartSets {
|
||||||
|
return this._avatarPartSetsLib;
|
||||||
|
}
|
||||||
|
}
|
15
src/objects/avatars/animations/AvatarAnimation.ts
Normal file
15
src/objects/avatars/animations/AvatarAnimation.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import type { IAnimationDefinition, IAnimationFrameData } from '../../../types/Avatar';
|
||||||
|
import type { AvatarAction } from '../actions/AvatarAction';
|
||||||
|
|
||||||
|
export class AvatarAnimation {
|
||||||
|
constructor(_action: AvatarAction, private readonly _definition: IAnimationDefinition) {}
|
||||||
|
|
||||||
|
public getFrame(frame: number, type: string): IAnimationFrameData {
|
||||||
|
// @ts-expect-error
|
||||||
|
return this._definition.frames[frame].bodyparts[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFrameCount(): number {
|
||||||
|
return this._definition.frames.length;
|
||||||
|
}
|
||||||
|
}
|
33
src/objects/avatars/animations/AvatarAnimationManager.ts
Normal file
33
src/objects/avatars/animations/AvatarAnimationManager.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Assets } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { IAvatarPartSets, IAnimationFrameData } from '../../../types/Avatar';
|
||||||
|
import type { AvatarAction } from '../actions/AvatarAction';
|
||||||
|
import { AvatarAnimation } from './AvatarAnimation';
|
||||||
|
|
||||||
|
export class AvatarAnimationManager {
|
||||||
|
private readonly _animations = new Map<AvatarAction, AvatarAnimation>();
|
||||||
|
private readonly _avatarAnimationsLib = Assets.get<IAvatarPartSets>('figures/animations');
|
||||||
|
|
||||||
|
constructor(actions: AvatarAction[]) {
|
||||||
|
actions.forEach((action) => this.registerAnimation(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerAnimation(action: AvatarAction): void {
|
||||||
|
if (this._avatarAnimationsLib[action] === undefined) return;
|
||||||
|
this._animations.set(action, new AvatarAnimation(action, this._avatarAnimationsLib[action]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnimation(action: AvatarAction): AvatarAnimation {
|
||||||
|
return this._animations.get(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLayerData(action: AvatarAction, frame: number, type: string): IAnimationFrameData {
|
||||||
|
const animation = this.getAnimation(action);
|
||||||
|
if (animation === undefined) return;
|
||||||
|
return animation.getFrame(frame, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get animations(): IAvatarPartSets {
|
||||||
|
return this._avatarAnimationsLib;
|
||||||
|
}
|
||||||
|
}
|
231
src/objects/avatars/visualizations/AvatarBodyPart.ts
Normal file
231
src/objects/avatars/visualizations/AvatarBodyPart.ts
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import { Assets } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { IAvatarPart, IBodyPartConfiguration } from '../../../types/Avatar';
|
||||||
|
import { AssetLoader } from '../../../utilities/AssetLoader';
|
||||||
|
import { ZOrder } from '../../../utilities/ZOrder';
|
||||||
|
import { AvatarAction } from '../actions/AvatarAction';
|
||||||
|
import type { Avatar } from '../Avatar';
|
||||||
|
import { AvatarLayer } from './AvatarLayer';
|
||||||
|
|
||||||
|
export class AvatarBodyPart {
|
||||||
|
private readonly _avatar: Avatar;
|
||||||
|
|
||||||
|
private readonly _type: string; // hr - hd - ch - lg - sh
|
||||||
|
|
||||||
|
private readonly _setId: number;
|
||||||
|
|
||||||
|
private readonly _colors: number[];
|
||||||
|
|
||||||
|
private readonly _parts: IAvatarPart[]; // lib.id, etc...
|
||||||
|
|
||||||
|
private _actions: AvatarAction[];
|
||||||
|
|
||||||
|
private readonly _frames: Map<number, Map<string, { action: AvatarAction; frame: number; repeat: number }>> =
|
||||||
|
new Map();
|
||||||
|
|
||||||
|
private areAllAssetsLoaded = false;
|
||||||
|
|
||||||
|
constructor(avatar: Avatar, config: IBodyPartConfiguration) {
|
||||||
|
this._avatar = avatar;
|
||||||
|
this._type = config.type;
|
||||||
|
this._setId = config.setId;
|
||||||
|
this._colors = config.colors;
|
||||||
|
this._parts = config.parts;
|
||||||
|
this._actions = config.actions;
|
||||||
|
|
||||||
|
const assets: Array<Promise<void>> = [];
|
||||||
|
|
||||||
|
this._parts.forEach((part: IAvatarPart) => {
|
||||||
|
if (part.lib != null)
|
||||||
|
assets.push(AssetLoader.load('figures/' + part.lib.id, 'figure/' + part.lib.id + '/' + part.lib.id + '.json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(assets)
|
||||||
|
.then(() => (this.areAllAssetsLoaded = true))
|
||||||
|
.catch((error) => this._avatar.logger.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _draw(): void {
|
||||||
|
if (!this.areAllAssetsLoaded) return;
|
||||||
|
this._parts.forEach((part) => this._createPart(part));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createPart(part: IAvatarPart): void {
|
||||||
|
if (!this._frames.has(part.id)) this._frames.set(part.id, new Map());
|
||||||
|
|
||||||
|
if (part.lib == null) return;
|
||||||
|
|
||||||
|
const spritesheet = Assets.get('figures/' + part.lib.id);
|
||||||
|
|
||||||
|
Object.keys(spritesheet.data.partsType).forEach((type) => {
|
||||||
|
// We register the part type if it's not already registered
|
||||||
|
if (!this._frames.get(part.id).has(type))
|
||||||
|
this._frames.get(part.id).set(type, {
|
||||||
|
action: AvatarAction.Default,
|
||||||
|
frame: 0,
|
||||||
|
repeat: 0
|
||||||
|
});
|
||||||
|
let direction = this._avatar.bodyDirection;
|
||||||
|
|
||||||
|
// We get the actions, check if it's valid and if the action is included in the active part set
|
||||||
|
const sortedActions = this._avatar.actionManager.filterActions(this._actions, type);
|
||||||
|
|
||||||
|
let finalAction: AvatarAction = this._avatar.actionManager.sortActions(sortedActions)[0];
|
||||||
|
|
||||||
|
// If this part type is in the head part set, we put the direction equal to the head direction
|
||||||
|
if (this._isHeadPart(type)) direction = this._avatar.headDirection;
|
||||||
|
|
||||||
|
// We get the animation gesture and frame
|
||||||
|
const frameData = this._avatar.animationManager.getLayerData(
|
||||||
|
finalAction,
|
||||||
|
this._frames.get(part.id).get(type).frame,
|
||||||
|
type
|
||||||
|
);
|
||||||
|
let gesture: string = this._avatar.actionManager.getActionDefinition(finalAction).assetpartdefinition;
|
||||||
|
let frame: number = 0;
|
||||||
|
let flip: boolean = false;
|
||||||
|
if (frameData !== undefined) {
|
||||||
|
this._frames.get(part.id).get(type).action = finalAction;
|
||||||
|
frame = frameData.frame;
|
||||||
|
gesture = frameData.assetpartdefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([4, 5, 6].includes(direction)) {
|
||||||
|
flip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const zDirection = direction;
|
||||||
|
|
||||||
|
let tempDirection: number = direction;
|
||||||
|
if ([4, 5, 6].includes(tempDirection)) tempDirection = 6 - tempDirection;
|
||||||
|
|
||||||
|
// If the texture don't exist we reinitalise the gesture and the final action
|
||||||
|
if (
|
||||||
|
spritesheet.textures[
|
||||||
|
// Skipping type checking because we cannot convert
|
||||||
|
part.lib.id + '_h_' + gesture + '_' + type + '_' + part.id + '_' + tempDirection + '_' + frame
|
||||||
|
] === undefined
|
||||||
|
) {
|
||||||
|
gesture = 'std';
|
||||||
|
finalAction = AvatarAction.Default;
|
||||||
|
this._frames.get(part.id).get(type).action = finalAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
((gesture === 'wav' && (type === 'lh' || type === 'ls' || type === 'lc' || type === 'lcs')) ||
|
||||||
|
(gesture === 'drk' && (type === 'rh' || type === 'rs' || type === 'rcs')) ||
|
||||||
|
(gesture === 'blw' && type === 'rh') ||
|
||||||
|
(gesture === 'sig' && type === 'lh') ||
|
||||||
|
(gesture === 'respect' && type === 'lh')) &&
|
||||||
|
[4, 5, 6].includes(this._avatar.bodyDirection)
|
||||||
|
) {
|
||||||
|
flip = !flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(gesture === 'crr' || gesture === 'respect' || gesture === 'sig') &&
|
||||||
|
[4, 5, 6].includes(this._avatar.bodyDirection)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
this._avatar.actionManager.partSets.partSets[type] !== undefined &&
|
||||||
|
this._avatar.actionManager.partSets.partSets[type]['flipped-set-type'] !== undefined &&
|
||||||
|
[4, 5, 6, 7].includes(direction)
|
||||||
|
) {
|
||||||
|
type = this._avatar.actionManager.partSets.partSets[type]['flipped-set-type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const zOrder = ZOrder.avatar(this._avatar.position, this._getDrawOrder(type, gesture, direction));
|
||||||
|
|
||||||
|
// We create the layer
|
||||||
|
if (
|
||||||
|
spritesheet.textures[
|
||||||
|
// Skipping type checking because we cannot convert
|
||||||
|
part.lib.id + '_h_' + gesture + '_' + type + '_' + part.id + '_' + tempDirection + '_' + frame
|
||||||
|
] !== undefined
|
||||||
|
) {
|
||||||
|
const layer = new AvatarLayer(this._avatar, {
|
||||||
|
type,
|
||||||
|
part,
|
||||||
|
gesture,
|
||||||
|
tint:
|
||||||
|
part.colorable === 1 && type !== 'ey' ? this._getColor(this._type, this._colors[part.index]) : undefined,
|
||||||
|
z: zOrder,
|
||||||
|
flip,
|
||||||
|
direction,
|
||||||
|
frame
|
||||||
|
});
|
||||||
|
/*let tempType: string = type;
|
||||||
|
if(this._avatar.actionManager.partSets.partSets[type] !== undefined && this._avatar.actionManager.partSets.partSets[type]["flipped-set-type"] !== undefined && [4, 5, 6, 7].includes(direction)) {
|
||||||
|
tempType = this._avatar.actionManager.partSets.partSets[type]["flipped-set-type"];
|
||||||
|
if(spritesheet.data.frames[part.lib.id + "_h_std_" + tempType + "_" + part.id + "_" + tempDirection + "_0"] !== undefined) {
|
||||||
|
layer.x = spritesheet.data.frames[part.lib.id + "_h_std_" + tempType + "_" + part.id + "_" + tempDirection + "_0"].spriteSourceSize.x;
|
||||||
|
layer.y = spritesheet.data.frames[part.lib.id + "_h_std_" + tempType + "_" + part.id + "_" + tempDirection + "_0"].spriteSourceSize.y;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
this._avatar.addChild(layer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isHeadPart(type: string): boolean {
|
||||||
|
return this._avatar.actionManager.partSets.activePartSets.head.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// types must be changed here
|
||||||
|
private _getColor(type: string, colorId: number): number {
|
||||||
|
const figureData: [] = Assets.get('figures/figuredata');
|
||||||
|
const paletteId = figureData.settype[type].paletteid;
|
||||||
|
const palette = figureData.palette[String(paletteId)];
|
||||||
|
|
||||||
|
if (palette[String(colorId)] === undefined) return Number('0xFFFFFF');
|
||||||
|
|
||||||
|
return Number('0x' + String(palette[String(colorId)].color));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDrawOrder(type: string, action: string, direction: number): number {
|
||||||
|
const drawOrder: [] = Assets.get('figures/draworder');
|
||||||
|
const drawOrderList = Object.entries(
|
||||||
|
drawOrder[drawOrder[action] !== undefined ? action : 'std'][direction] ?? drawOrder.std[direction]
|
||||||
|
).find((entry) => {
|
||||||
|
return entry[1] === type;
|
||||||
|
});
|
||||||
|
return drawOrderList !== undefined ? Number(drawOrderList[0]) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateParts(): void {
|
||||||
|
this._frames.forEach((types, partId) => {
|
||||||
|
types.forEach((value, type) => {
|
||||||
|
const animation = this._avatar.animationManager.getAnimation(value.action);
|
||||||
|
const frameData = this._avatar.animationManager.getLayerData(value.action, value.frame, type);
|
||||||
|
if (frameData !== undefined) {
|
||||||
|
const currentFrame = this._frames.get(partId).get(type);
|
||||||
|
if (frameData.repeats !== undefined) {
|
||||||
|
if (currentFrame.repeat >= Number(frameData.repeats)) {
|
||||||
|
currentFrame.repeat = 0;
|
||||||
|
currentFrame.frame = currentFrame.frame >= animation.getFrameCount() - 1 ? 0 : currentFrame.frame + 1;
|
||||||
|
} else {
|
||||||
|
currentFrame.repeat += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentFrame.frame = currentFrame.frame >= animation.getFrameCount() - 1 ? 0 : currentFrame.frame + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get actions(): AvatarAction[] {
|
||||||
|
return this._actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set actions(actions: AvatarAction[]) {
|
||||||
|
this._actions = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get frames(): Map<number, Map<string, { action: AvatarAction; frame: number; repeat: number }>> {
|
||||||
|
return this._frames;
|
||||||
|
}
|
||||||
|
}
|
110
src/objects/avatars/visualizations/AvatarLayer.ts
Normal file
110
src/objects/avatars/visualizations/AvatarLayer.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { Assets, Container } from 'pixi.js';
|
||||||
|
import { Color } from '@pixi/color';
|
||||||
|
|
||||||
|
import type { Avatar } from '../Avatar';
|
||||||
|
import type { IAvatarLayerConfiguration, IAvatarPart } from '../../../types/Avatar';
|
||||||
|
import { HitSprite } from '../../interactions/HitSprite';
|
||||||
|
import type { Direction } from '../../../enums/Direction';
|
||||||
|
|
||||||
|
export class AvatarLayer extends Container {
|
||||||
|
private readonly _avatar: Avatar;
|
||||||
|
|
||||||
|
private readonly _type: string;
|
||||||
|
private readonly _part: IAvatarPart;
|
||||||
|
private readonly _gesture: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer tint
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tint: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer z
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _z: number;
|
||||||
|
|
||||||
|
private readonly _direction: Direction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the layer flipped
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _flip: boolean;
|
||||||
|
|
||||||
|
private readonly _frame: number;
|
||||||
|
|
||||||
|
private readonly _alpha: number;
|
||||||
|
|
||||||
|
constructor(avatar: Avatar, configuration: IAvatarLayerConfiguration) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._avatar = avatar;
|
||||||
|
this._type = configuration.type;
|
||||||
|
this._part = configuration.part;
|
||||||
|
this._gesture = configuration.gesture;
|
||||||
|
this._tint = configuration.tint;
|
||||||
|
this._z = configuration.z;
|
||||||
|
this._flip = configuration.flip;
|
||||||
|
this._direction = configuration.direction;
|
||||||
|
this._frame = configuration.frame;
|
||||||
|
this._alpha = configuration.alpha;
|
||||||
|
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _draw(): void {
|
||||||
|
let tempDirection = this._direction;
|
||||||
|
if (this._flip && [4, 5, 6].includes(tempDirection)) this.scale.x = -1;
|
||||||
|
if (this._flip && [4, 5, 6].includes(tempDirection)) this.x = 64;
|
||||||
|
if ([4, 5, 6].includes(tempDirection) && this._flip) {
|
||||||
|
tempDirection = 6 - tempDirection;
|
||||||
|
}
|
||||||
|
// const avatarActions: IActionDefinition[] = Assets.get('figures/actions')
|
||||||
|
// if(this._type === "ls" || this._type === "lh" || this._type === "lc") console.log(2, this._part.lib.id + "_h_" + this._gesture + "_" + this._type + "_" + this._part.id + "_" + tempDirection + "_" + this._frame);
|
||||||
|
const sprite = new HitSprite(
|
||||||
|
Assets.get('figures/' + this._part.lib.id).textures[
|
||||||
|
this._part.lib.id +
|
||||||
|
'_h_' +
|
||||||
|
this._gesture +
|
||||||
|
'_' +
|
||||||
|
this._type +
|
||||||
|
'_' +
|
||||||
|
String(this._part.id) +
|
||||||
|
'_' +
|
||||||
|
String(tempDirection) +
|
||||||
|
'_' +
|
||||||
|
String(this._frame)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if (this._tint !== undefined) sprite.tint = new Color(this._tint).premultiply(1).toNumber();
|
||||||
|
if (this._avatar.room !== undefined) this.parentLayer = this._avatar.room.objects.layer;
|
||||||
|
if (this._z !== undefined) this.zOrder = this._z;
|
||||||
|
if (this._alpha !== undefined) sprite.alpha = this._alpha;
|
||||||
|
//sprite.animationSpeed = 0.167;
|
||||||
|
//sprite.play();
|
||||||
|
sprite.interactive = true;
|
||||||
|
sprite.on('pointerdown', (event) => {
|
||||||
|
return this._avatar.eventManager.handlePointerDown({ event });
|
||||||
|
});
|
||||||
|
sprite.on('pointerup', (event) => {
|
||||||
|
return this._avatar.eventManager.handlePointerUp({ event });
|
||||||
|
});
|
||||||
|
sprite.on('pointermove', (event) => {
|
||||||
|
return this._avatar.eventManager.handlePointerMove({ event });
|
||||||
|
});
|
||||||
|
sprite.on('pointerout', (event) => {
|
||||||
|
return this._avatar.eventManager.handlePointerOut({ event });
|
||||||
|
});
|
||||||
|
sprite.on('pointerover', (event) => {
|
||||||
|
return this._avatar.eventManager.handlePointerOver({ event });
|
||||||
|
});
|
||||||
|
|
||||||
|
this._avatar.addChild(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get avatar(): Avatar {
|
||||||
|
return this._avatar;
|
||||||
|
}
|
||||||
|
}
|
134
src/objects/avatars/visualizations/AvatarVisualization.ts
Normal file
134
src/objects/avatars/visualizations/AvatarVisualization.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import type { IAvatarPosition, Nullable } from '../../../types';
|
||||||
|
import { AssetLoader } from '../../../utilities/AssetLoader';
|
||||||
|
import { RoomObjectVisualization } from '../../rooms/objects/RoomObjectVisualization';
|
||||||
|
import { AvatarAction } from '../actions/AvatarAction';
|
||||||
|
import type { Avatar } from '../Avatar';
|
||||||
|
import { AvatarLayer } from './AvatarLayer';
|
||||||
|
|
||||||
|
export class AvatarVisualization extends RoomObjectVisualization {
|
||||||
|
private readonly _avatar: Avatar;
|
||||||
|
|
||||||
|
private readonly _handItem: Nullable<number>;
|
||||||
|
|
||||||
|
constructor(avatar: Avatar) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._avatar = avatar;
|
||||||
|
this._loadAssets();
|
||||||
|
|
||||||
|
this._avatar.onRoomAdded = (room) => {
|
||||||
|
if (this.loaded) room.visualization.animationTicker.add(() => this.render());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadAssets(): void {
|
||||||
|
const assets: Array<Promise<void>> = [];
|
||||||
|
|
||||||
|
if (this._avatar.onLoad !== undefined) this._avatar.onLoad();
|
||||||
|
|
||||||
|
if (this._hasHandItem())
|
||||||
|
assets.push(AssetLoader.load('figures/hh_human_item', 'figure/hh_human_item/hh_human_item.json'));
|
||||||
|
assets.push(AssetLoader.load('figures/hh_human_body', 'figure/hh_human_body/hh_human_body.json'));
|
||||||
|
|
||||||
|
Promise.all(assets)
|
||||||
|
.then(() => {
|
||||||
|
if (this._avatar.onLoadComplete !== undefined) this._avatar.onLoadComplete();
|
||||||
|
this.loaded = true;
|
||||||
|
|
||||||
|
if (this.placeholder !== undefined) this.placeholder.destroy();
|
||||||
|
if (this._avatar.room != null) this._avatar.room.visualization.animationTicker.add(() => this.render());
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.logger.error('Unable to load the assets');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): void {
|
||||||
|
this.destroy();
|
||||||
|
|
||||||
|
if (!this.loaded) return;
|
||||||
|
if (this._hasHandItem()) this._createHandItem();
|
||||||
|
|
||||||
|
this._createShadow();
|
||||||
|
|
||||||
|
this._avatar.bodyParts.forEach((bodyPart) => {
|
||||||
|
bodyPart.actions = this._avatar.actions;
|
||||||
|
bodyPart.updateParts();
|
||||||
|
});
|
||||||
|
|
||||||
|
const position = this._avatar.position as IAvatarPosition;
|
||||||
|
|
||||||
|
this._avatar.x = 32 * position.x - 32 * position.y;
|
||||||
|
this._avatar.y = 16 * position.x + 16 * position.y - 32 * position.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo!(): destroy avatar's bodyparts
|
||||||
|
destroy(): void {
|
||||||
|
// if (this.placeholder.parent !== null) this.placeholder.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo!(): add figure placeholder
|
||||||
|
renderPlaceholder(): void {
|
||||||
|
// const position = this._avatar.position as IAvatarPosition;
|
||||||
|
// this.placeholder = new Sprite(Assets.get('furnitures/floor/placeholder').textures['place_holder_furniture_64.png']);
|
||||||
|
// if (this._avatar.room != null) this._avatar.addChild(this.placeholder);
|
||||||
|
// this.placeholder.x = 32 + 32 * position.x - 32 * position.y - 32;
|
||||||
|
// this.placeholder.y = 16 * position.x + 16 * position.y - 32 * position.z - 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePosition(): void {}
|
||||||
|
|
||||||
|
private _createShadow(): void {
|
||||||
|
this._avatar.addChild(
|
||||||
|
new AvatarLayer(this._avatar, {
|
||||||
|
type: 'sd',
|
||||||
|
part: { id: 1, lib: { id: 'hh_human_body' } },
|
||||||
|
gesture: 'std',
|
||||||
|
tint: undefined,
|
||||||
|
z: 0,
|
||||||
|
flip: true,
|
||||||
|
direction: 0,
|
||||||
|
frame: 0,
|
||||||
|
alpha: 0.1
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createHandItem(): void {
|
||||||
|
const item = this._handItem;
|
||||||
|
const handItemCarryId = this._avatar.actionManager.getActionDefinition(AvatarAction.CarryItem).params[String(item)];
|
||||||
|
const handItemUseId = this._avatar.actionManager.getActionDefinition(AvatarAction.UseItem).params[String(item)];
|
||||||
|
|
||||||
|
if (this._avatar.actions.includes(AvatarAction.UseItem) && handItemUseId !== undefined) {
|
||||||
|
this._avatar.addChild(
|
||||||
|
new AvatarLayer(this._avatar, {
|
||||||
|
type: 'ri',
|
||||||
|
part: { id: handItemUseId, lib: { id: 'hh_human_item' } },
|
||||||
|
gesture: 'drk',
|
||||||
|
tint: undefined,
|
||||||
|
z: 1000,
|
||||||
|
flip: false,
|
||||||
|
direction: this._avatar.bodyDirection,
|
||||||
|
frame: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (this._avatar.actions.includes(AvatarAction.CarryItem) && handItemCarryId !== undefined) {
|
||||||
|
this._avatar.addChild(
|
||||||
|
new AvatarLayer(this._avatar, {
|
||||||
|
type: 'ri',
|
||||||
|
part: { id: handItemCarryId, lib: { id: 'hh_human_item' } },
|
||||||
|
gesture: 'crr',
|
||||||
|
tint: undefined,
|
||||||
|
z: 1000,
|
||||||
|
flip: false,
|
||||||
|
direction: this._avatar.bodyDirection,
|
||||||
|
frame: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hasHandItem(): boolean {
|
||||||
|
return this._handItem !== 0 || this._handItem !== undefined;
|
||||||
|
}
|
||||||
|
}
|
52
src/objects/filters/WiredSelectionFilter.ts
Normal file
52
src/objects/filters/WiredSelectionFilter.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Color, Filter } from 'pixi.js';
|
||||||
|
|
||||||
|
/** The shader vertex */
|
||||||
|
const vertex = `
|
||||||
|
attribute vec2 aVertexPosition;
|
||||||
|
attribute vec2 aTextureCoord;
|
||||||
|
uniform mat3 projectionMatrix;
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
|
||||||
|
vTextureCoord = aTextureCoord;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/** The shader fragment */
|
||||||
|
const fragment = `
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
uniform vec3 lineColor;
|
||||||
|
uniform vec3 backgroundColor;
|
||||||
|
void main(void) {
|
||||||
|
vec4 currentColor = texture2D(uSampler, vTextureCoord);
|
||||||
|
vec3 colorLine = lineColor * currentColor.a;
|
||||||
|
vec3 colorOverlay = backgroundColor * currentColor.a;
|
||||||
|
if(currentColor.r == 0.0 && currentColor.g == 0.0 && currentColor.b == 0.0 && currentColor.a > 0.0) {
|
||||||
|
gl_FragColor = vec4(colorLine.r, colorLine.g, colorLine.b, currentColor.a);
|
||||||
|
} else if(currentColor.a > 0.0) {
|
||||||
|
gl_FragColor = vec4(colorOverlay.r, colorOverlay.g, colorOverlay.b, currentColor.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WiredSelectionFilter class that aim to reproduce the wired selection effect.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class WiredSelectionFilter extends Filter {
|
||||||
|
/**
|
||||||
|
* @param {number} [lineColor] - The color of the furniture border when selected.
|
||||||
|
* @param {number} [backgroundColor] - The main color of the furniture when selected.
|
||||||
|
**/
|
||||||
|
constructor(lineColor: number, backgroundColor: number) {
|
||||||
|
super(vertex, fragment);
|
||||||
|
|
||||||
|
/** Set the colors */
|
||||||
|
this.uniforms.lineColor = new Color(lineColor).toRgbArray();
|
||||||
|
this.uniforms.backgroundColor = new Color(backgroundColor).toRgbArray();
|
||||||
|
}
|
||||||
|
}
|
65
src/objects/furnitures/FloorFurniture.ts
Normal file
65
src/objects/furnitures/FloorFurniture.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { gsap } from 'gsap';
|
||||||
|
|
||||||
|
import type { IFloorFurnitureConfiguration, IFloorPosition } from '../../types/Furniture';
|
||||||
|
import { FurnitureData } from './visualizations/FurnitureData';
|
||||||
|
import { FurnitureVisualization } from './visualizations/FurnitureVisualization';
|
||||||
|
import { RoomObject } from '../rooms/objects/RoomObject';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FloorFurniture class that aim to reproduce the floor furnitures on Habbo.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class FloorFurniture extends RoomObject {
|
||||||
|
/**
|
||||||
|
* The furniture id that represent the one in furnidata.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {IFloorFurnitureConfiguration} [config] - The furniture configuration.
|
||||||
|
*/
|
||||||
|
constructor(config: IFloorFurnitureConfiguration) {
|
||||||
|
super(config);
|
||||||
|
|
||||||
|
this._id = config.id;
|
||||||
|
this._state = config.state ?? 0;
|
||||||
|
this._data = new FurnitureData(this);
|
||||||
|
this._visualization = new FurnitureVisualization(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture id from the furni data.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get id(): number {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the furniture at the given position and in time.
|
||||||
|
*
|
||||||
|
* @param {IFloorPosition} [position] - The position where we want to move the furniture.
|
||||||
|
* @param {number} [duration] - The time to move the furniture to the given position.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
move(position: IFloorPosition, duration: number = 0.5): void {
|
||||||
|
if (this._visualization === undefined) return;
|
||||||
|
gsap.to(this.position, {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
z: position.z,
|
||||||
|
duration,
|
||||||
|
ease: 'linear',
|
||||||
|
onUpdate: () => this._visualization.updatePosition()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
64
src/objects/furnitures/WallFurniture.ts
Normal file
64
src/objects/furnitures/WallFurniture.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { gsap } from 'gsap';
|
||||||
|
|
||||||
|
import type { IWallFurniConfig, IWallPosition } from '../../types/Furniture';
|
||||||
|
import { RoomObject } from '../rooms/objects/RoomObject';
|
||||||
|
import { FurnitureData } from './visualizations/FurnitureData';
|
||||||
|
import { FurnitureVisualization } from './visualizations/FurnitureVisualization';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WallFurniture class that aim to reproduce the wall furnitures on Habbo.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class WallFurniture extends RoomObject {
|
||||||
|
/**
|
||||||
|
* The furniture's id that represent the one in furnidata.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {IWallFurniConfig} [config] - The furniture configuration.
|
||||||
|
*/
|
||||||
|
constructor(config: IWallFurniConfig) {
|
||||||
|
super(config);
|
||||||
|
|
||||||
|
this._id = config.id;
|
||||||
|
this._state = config.state ?? 0;
|
||||||
|
this._data = new FurnitureData(this);
|
||||||
|
this._visualization = new FurnitureVisualization(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture id from the furni data.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get id(): number {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the furniture at the given position and in time.
|
||||||
|
*
|
||||||
|
* @param {IWallPosition} [position] - The position where we want to move the furniture.
|
||||||
|
* @param {number} [duration] - The time to move the furniture to the given position.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
move(position: IWallPosition, duration: number = 0.5): void {
|
||||||
|
if (this._visualization === undefined) return;
|
||||||
|
gsap.to(this.position, {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
duration,
|
||||||
|
ease: 'linear',
|
||||||
|
onUpdate: () => this._visualization.updatePosition()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
97
src/objects/furnitures/visualizations/FurnitureData.ts
Normal file
97
src/objects/furnitures/visualizations/FurnitureData.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { Assets } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { ISharedFurniData } from '../../../types/Furniture';
|
||||||
|
import { FloorFurniture } from '../FloorFurniture';
|
||||||
|
import type { WallFurniture } from '../WallFurniture';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FurnitureData class that manage the data of a furniture.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class FurnitureData {
|
||||||
|
/**
|
||||||
|
* The furniture instance that we want to retrieve data.
|
||||||
|
*
|
||||||
|
* @member {FloorFurniture | WallFurniture}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _furniture: FloorFurniture | WallFurniture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The furniture data.
|
||||||
|
*
|
||||||
|
* @member {ISharedFurniData}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _data!: ISharedFurniData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FloorFurniture | WallFurniture} [furniture] - The furniture instance.
|
||||||
|
*/
|
||||||
|
constructor(furniture: FloorFurniture | WallFurniture) {
|
||||||
|
this._furniture = furniture;
|
||||||
|
|
||||||
|
this._load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the furniture data.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _load(): void {
|
||||||
|
const furniType = this._furniture instanceof FloorFurniture ? 'floorItems' : 'wallItems';
|
||||||
|
this._data = Assets.get('furnitures/furnidata')[furniType].find((item: ISharedFurniData) => {
|
||||||
|
return item.id === this._furniture.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture id.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get id(): number {
|
||||||
|
return this._data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture class name.
|
||||||
|
*
|
||||||
|
* @member {string}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get className(): string {
|
||||||
|
return this._data.className;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture base name.
|
||||||
|
*
|
||||||
|
* @member {string}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get baseName(): string {
|
||||||
|
if (!Boolean(this._data.className.includes('*'))) return this._data.className;
|
||||||
|
return this._data.className.split('*')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture color id.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get color(): number | null {
|
||||||
|
if (!Boolean(this._data.className.includes('*'))) return null;
|
||||||
|
return Number(this._data.className.split('*')[1]);
|
||||||
|
}
|
||||||
|
}
|
202
src/objects/furnitures/visualizations/FurnitureLayer.ts
Normal file
202
src/objects/furnitures/visualizations/FurnitureLayer.ts
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import type { BLEND_MODES } from 'pixi.js';
|
||||||
|
import { Assets } from 'pixi.js';
|
||||||
|
import { Color } from '@pixi/color';
|
||||||
|
|
||||||
|
import type { FloorFurniture } from '../FloorFurniture';
|
||||||
|
import type { IFurnitureLayerConfiguration } from '../../../types/Furniture';
|
||||||
|
import { HitSprite } from '../../interactions/HitSprite';
|
||||||
|
import type { WallFurniture } from '../WallFurniture';
|
||||||
|
import type { Direction } from '../../../enums/Direction';
|
||||||
|
import { WiredSelectionFilter } from '../../filters/WiredSelectionFilter';
|
||||||
|
|
||||||
|
/** The wired selection filter */
|
||||||
|
const WIRED_SELECTION_FILTER = new WiredSelectionFilter(0xffffff, 0x999999);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FurnitureLayer class.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class FurnitureLayer extends HitSprite {
|
||||||
|
/**
|
||||||
|
* The furniture instance.
|
||||||
|
*
|
||||||
|
* @member {FloorFurniture | WallFurniture}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _furniture: FloorFurniture | WallFurniture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer id.
|
||||||
|
*
|
||||||
|
* @member {number | string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _layer: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer alpha.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _alpha: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer tint.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tint: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer z index.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _z: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer blend mode.
|
||||||
|
*
|
||||||
|
* @member {BLEND_MODES}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _blendMode: BLEND_MODES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the layer flipped.
|
||||||
|
*
|
||||||
|
* @member {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _flip: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer frame id.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _frame: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the layer interactive.
|
||||||
|
*
|
||||||
|
* @member {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _ignoreMouse: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer direction.
|
||||||
|
*
|
||||||
|
* @member {Direction}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _direction: Direction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layer tag.
|
||||||
|
*
|
||||||
|
* @member {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tag: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FloorFurniture | WallFurniture} [furniture] - The furniture instance.
|
||||||
|
* @param {IFurnitureLayerConfiguration} [config] - The layer configuration.
|
||||||
|
*/
|
||||||
|
constructor(furniture: FloorFurniture | WallFurniture, config: IFurnitureLayerConfiguration) {
|
||||||
|
super(undefined);
|
||||||
|
|
||||||
|
this._furniture = furniture;
|
||||||
|
this._layer = config.layer;
|
||||||
|
this._alpha = config.alpha;
|
||||||
|
this._tint = config.tint;
|
||||||
|
this._z = config.z;
|
||||||
|
this._blendMode = config.blendMode;
|
||||||
|
this._flip = config.flip;
|
||||||
|
this._frame = config.frame;
|
||||||
|
this._ignoreMouse = config.ignoreMouse;
|
||||||
|
this._direction = config.direction;
|
||||||
|
this._tag = config.tag;
|
||||||
|
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the part.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _draw(): void {
|
||||||
|
this.filters = [];
|
||||||
|
this.texture = Assets.get('furnitures/' + this._furniture.data.baseName).textures[
|
||||||
|
this._furniture.data.baseName +
|
||||||
|
'_' +
|
||||||
|
this._furniture.data.baseName +
|
||||||
|
'_64_' +
|
||||||
|
String.fromCharCode(97 + Number(this._layer)) +
|
||||||
|
'_' +
|
||||||
|
String(this._direction) +
|
||||||
|
'_' +
|
||||||
|
String(this._frame)
|
||||||
|
];
|
||||||
|
/*console.log(
|
||||||
|
this._furniture.data.baseName +
|
||||||
|
'_' +
|
||||||
|
this._furniture.data.baseName +
|
||||||
|
'_64_' +
|
||||||
|
String.fromCharCode(97 + Number(this._layer)) +
|
||||||
|
'_' +
|
||||||
|
String(this._direction) +
|
||||||
|
'_' +
|
||||||
|
String(this._frame)
|
||||||
|
);*/
|
||||||
|
if (this._tint !== undefined) this.tint = new Color(this._tint).premultiply(1).toNumber();
|
||||||
|
if (this._blendMode !== undefined) this.blendMode = this._blendMode;
|
||||||
|
if (this._alpha !== undefined) this.alpha = this._alpha;
|
||||||
|
if (this._flip) this.scale.x = -1;
|
||||||
|
//if (this._furniture.room !== undefined) this.parentLayer = this._furniture.room.objects.layer;
|
||||||
|
if (this._z !== undefined) this.zIndex = this._z;
|
||||||
|
if (this._ignoreMouse !== null && !this._ignoreMouse) this.interactive = true;
|
||||||
|
if (this._furniture.selected) this.filters.push(WIRED_SELECTION_FILTER);
|
||||||
|
|
||||||
|
this.on('pointerdown', (event) => {
|
||||||
|
return this._furniture.eventManager.handlePointerDown({ event, tag: this._tag });
|
||||||
|
});
|
||||||
|
this.on('pointerup', (event) => {
|
||||||
|
return this._furniture.eventManager.handlePointerUp({ event, tag: this._tag });
|
||||||
|
});
|
||||||
|
this.on('pointermove', (event) => {
|
||||||
|
return this._furniture.eventManager.handlePointerMove({ event, tag: this._tag });
|
||||||
|
});
|
||||||
|
this.on('pointerout', (event) => {
|
||||||
|
return this._furniture.eventManager.handlePointerOut({ event, tag: this._tag });
|
||||||
|
});
|
||||||
|
this.on('pointerover', (event) => {
|
||||||
|
return this._furniture.eventManager.handlePointerOver({ event, tag: this._tag });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture instance.
|
||||||
|
*
|
||||||
|
* @member {FloorFurniture | WallFurniture}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get furniture(): FloorFurniture | WallFurniture {
|
||||||
|
return this._furniture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get layer(): number {
|
||||||
|
return this._layer;
|
||||||
|
}
|
||||||
|
}
|
283
src/objects/furnitures/visualizations/FurnitureVisualization.ts
Normal file
283
src/objects/furnitures/visualizations/FurnitureVisualization.ts
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
import type { Spritesheet } from 'pixi.js';
|
||||||
|
import { Assets, BLEND_MODES, Sprite } from 'pixi.js';
|
||||||
|
|
||||||
|
import { Direction } from '../../../enums/Direction';
|
||||||
|
import { ZOrder } from '../../../utilities/ZOrder';
|
||||||
|
import { AssetLoader } from '../../../utilities/AssetLoader';
|
||||||
|
import { FurnitureLayer } from './FurnitureLayer';
|
||||||
|
import { RoomObjectVisualization } from '../../rooms/objects/RoomObjectVisualization';
|
||||||
|
import type {
|
||||||
|
IFloorPosition,
|
||||||
|
IFurnitureLayerData,
|
||||||
|
IFurnitureProperty,
|
||||||
|
IFurnitureVisualization,
|
||||||
|
IWallPosition
|
||||||
|
} from '../../../types/Furniture';
|
||||||
|
import { WallFurniture } from '../WallFurniture';
|
||||||
|
import { FloorFurniture } from '../FloorFurniture';
|
||||||
|
|
||||||
|
export class FurnitureVisualization extends RoomObjectVisualization {
|
||||||
|
// updateLayerPosition and layerData private
|
||||||
|
private readonly _frames: Map<number, number> = new Map();
|
||||||
|
|
||||||
|
public _layers = new Map<number, FurnitureLayer>();
|
||||||
|
|
||||||
|
public _spritesheet!: Spritesheet;
|
||||||
|
|
||||||
|
public _properties!: IFurnitureProperty;
|
||||||
|
|
||||||
|
public _furniture: FloorFurniture | WallFurniture;
|
||||||
|
|
||||||
|
constructor(furniture: FloorFurniture | WallFurniture) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._furniture = furniture;
|
||||||
|
this._loadAssets();
|
||||||
|
|
||||||
|
this._furniture.onRoomAdded = (room) => {
|
||||||
|
if (this.loaded) room.visualization.animationTicker.add(() => this._update());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadAssets(): void {
|
||||||
|
const name = this._furniture.data.baseName;
|
||||||
|
|
||||||
|
if (this._furniture.onLoad !== undefined) this._furniture.onLoad();
|
||||||
|
AssetLoader.load('furnitures/' + name, 'furniture/' + name + '/' + name + '.json')
|
||||||
|
.then(() => {
|
||||||
|
if (this._furniture.onLoadComplete !== undefined) this._furniture.onLoadComplete();
|
||||||
|
|
||||||
|
this._spritesheet = Assets.get('furnitures/' + name);
|
||||||
|
// @ts-expect-error
|
||||||
|
this._properties = this._spritesheet.data.furniProperty;
|
||||||
|
this.loaded = true;
|
||||||
|
|
||||||
|
if (this.placeholder !== undefined) this.placeholder.destroy();
|
||||||
|
if (this._furniture.room != null) this._furniture.room.visualization.animationTicker.add(() => this._update());
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.logger.error(
|
||||||
|
'Unable to load the assets "' +
|
||||||
|
name +
|
||||||
|
'". It can be an invalid file, an invalid json format or just it don\t exist!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _update(): void {
|
||||||
|
const visualization = this._properties.visualization;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._properties.visualization.layerCount; i++) {
|
||||||
|
const frame = visualization.animation[String(this._furniture.state)];
|
||||||
|
|
||||||
|
if (frame !== undefined && frame[i] !== undefined) {
|
||||||
|
const frameSequence = frame[i].frameSequence;
|
||||||
|
const currentFrame = this._frames.get(i) ?? 0;
|
||||||
|
|
||||||
|
if (frameSequence.length > 1) {
|
||||||
|
if (frameSequence.length - 1 > currentFrame) {
|
||||||
|
this._frames.set(i, currentFrame + 1);
|
||||||
|
} else this._frames.set(i, 0);
|
||||||
|
|
||||||
|
this._renderLayer(i, this._frames.get(i) ?? 0);
|
||||||
|
} else {
|
||||||
|
if (this._layers.get(i) == null) this._renderLayer(i, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this._layers.get(i) == null) this._renderLayer(i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderLayer(layer: number, frame: number): void {
|
||||||
|
const visualization = this._properties.visualization;
|
||||||
|
const furnitureLayer = this._layers.get(layer);
|
||||||
|
const frames = visualization.animation[this._furniture.state];
|
||||||
|
|
||||||
|
if (furnitureLayer != null) furnitureLayer.destroy();
|
||||||
|
|
||||||
|
if (this._properties.infos.visualization === 'furniture_animated' && frames === undefined) {
|
||||||
|
this._furniture.state = Number(Object.keys(visualization.animation)[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layerData = this.layerData(layer, frame);
|
||||||
|
|
||||||
|
if (!visualization.directions.includes(this._furniture.direction)) {
|
||||||
|
this._furniture.rotate(visualization.directions[0], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frames !== undefined && frames[layer] !== undefined && frames[layer].frameSequence.length > 1)
|
||||||
|
layerData.frame = frame;
|
||||||
|
|
||||||
|
if (frames !== undefined && frames[layer] !== undefined)
|
||||||
|
layerData.frame = frames[layer].frameSequence[layerData.frame] ?? 0;
|
||||||
|
|
||||||
|
const layerContainer = new FurnitureLayer(this._furniture, layerData);
|
||||||
|
|
||||||
|
layerContainer.zIndex = layerData.z;
|
||||||
|
this._layers.set(layer, layerContainer);
|
||||||
|
|
||||||
|
if (this._furniture.room != null) {
|
||||||
|
// @ts-expect-error
|
||||||
|
this._furniture.addChild(layerContainer);
|
||||||
|
this.updateLayerPosition(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
layerContainer.filters = this._furniture.filters; // TODO: Move this to global
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (this.placeholder.parent !== null) this.placeholder.destroy();
|
||||||
|
this._layers.forEach((layer) => layer.destroy());
|
||||||
|
this._layers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): void {
|
||||||
|
this.destroy();
|
||||||
|
this.directions = this._properties.visualization.directions;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._properties.visualization.layerCount; i++) {
|
||||||
|
this._renderLayer(i, this._frames.get(i) ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePosition(): void {
|
||||||
|
if (this._furniture instanceof FloorFurniture) {
|
||||||
|
const position = this._furniture.position as IFloorPosition;
|
||||||
|
|
||||||
|
return this._layers.forEach((layer) => {
|
||||||
|
layer.x = layer.texture.orig.x + 32 + 32 * position.x - 32 * position.y;
|
||||||
|
layer.y = layer.texture.orig.y + 16 * position.x + 16 * position.y - 32 * position.z;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const position = this._furniture.position as IWallPosition;
|
||||||
|
|
||||||
|
// TODO: Refactor wall items
|
||||||
|
return this._layers.forEach((layer) => {
|
||||||
|
if (this._furniture.direction === Direction.EAST) {
|
||||||
|
layer.x = layer.texture.orig.x + 32 + 32 * position.x - 32 * position.y + position.offsetX * 2 - 1;
|
||||||
|
layer.y = layer.texture.orig.y + 16 * position.x + 16 * position.y - 32 + position.offsetY * 2 + 31;
|
||||||
|
} else if (this._furniture.direction === Direction.SOUTH) {
|
||||||
|
layer.x = layer.texture.orig.x + 32 + 32 * position.x - 32 * position.y + position.offsetX * 2 - 32;
|
||||||
|
layer.y = layer.texture.orig.x + 16 * position.x + 16 * position.y - 32 + position.offsetY * 2 + 31;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLayerPosition(layer: number): void {
|
||||||
|
const furnitureLayer = this._layers.get(layer);
|
||||||
|
if (furnitureLayer == null) return;
|
||||||
|
if (this._furniture instanceof FloorFurniture) {
|
||||||
|
const position = this._furniture.position as IFloorPosition;
|
||||||
|
|
||||||
|
furnitureLayer.x = furnitureLayer.texture.orig.x + 32 + 32 * position.x - 32 * position.y;
|
||||||
|
furnitureLayer.y = furnitureLayer.texture.orig.y + 16 * position.x + 16 * position.y - 32 * position.z;
|
||||||
|
} else {
|
||||||
|
const position = this._furniture.position as IWallPosition;
|
||||||
|
|
||||||
|
if (this._furniture.direction === Direction.EAST) {
|
||||||
|
furnitureLayer.x =
|
||||||
|
furnitureLayer.texture.orig.x + 32 + 32 * position.x - 32 * position.y + position.offsetX * 2 - 1;
|
||||||
|
furnitureLayer.y =
|
||||||
|
furnitureLayer.texture.orig.y + 16 * position.x + 16 * position.y - 32 + position.offsetY * 2 + 31;
|
||||||
|
} else if (this._furniture.direction === Direction.SOUTH) {
|
||||||
|
furnitureLayer.x =
|
||||||
|
furnitureLayer.texture.orig.x + 32 + 32 * position.x - 32 * position.y + position.offsetX * 2 - 32;
|
||||||
|
furnitureLayer.y =
|
||||||
|
furnitureLayer.texture.orig.x + 16 * position.x + 16 * position.y - 32 + position.offsetY * 2 + 31;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlaceholder(): void {
|
||||||
|
if (this._furniture instanceof FloorFurniture) {
|
||||||
|
const position = this._furniture.position as IFloorPosition;
|
||||||
|
|
||||||
|
this.placeholder = new Sprite(
|
||||||
|
Assets.get('furnitures/floor/placeholder').textures['place_holder_furniture_64.png']
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._furniture.room != null) this._furniture.addChild(this.placeholder);
|
||||||
|
|
||||||
|
this.placeholder.x = 32 + 32 * position.x - 32 * position.y - 32;
|
||||||
|
this.placeholder.y = 16 * position.x + 16 * position.y - 32 * position.z - 50;
|
||||||
|
} else {
|
||||||
|
const position = this._furniture.position as IWallPosition;
|
||||||
|
|
||||||
|
this.placeholder = new Sprite(
|
||||||
|
Assets.get('furnitures/wall/placeholder').textures['place_holder_wall_item_64.png']
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._furniture.room != null) this._furniture.addChild(this.placeholder);
|
||||||
|
if (this._furniture.direction === Direction.EAST) {
|
||||||
|
this.placeholder.x = 32 + 32 * position.x - 32 * position.y + position.offsetX * 2 - 1;
|
||||||
|
this.placeholder.y = 16 * position.x + 16 * position.y - 32 + position.offsetY * 2 + 31 - 50;
|
||||||
|
} else if (this._furniture.direction === Direction.SOUTH) {
|
||||||
|
this.placeholder.scale.x = -1;
|
||||||
|
this.placeholder.x = 32 + 32 * position.x - 32 * position.y + position.offsetX * 2 - 32;
|
||||||
|
this.placeholder.y = 16 * position.x + 16 * position.y - 32 + position.offsetY * 2 + 31 - 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layerData(layer: number, frame: number = 0): IFurnitureLayerData {
|
||||||
|
const spritesheet = Assets.get<Spritesheet>('furnitures/' + this._furniture.data.baseName);
|
||||||
|
// @ts-expect-error
|
||||||
|
const visualization = spritesheet.data.furniProperty.visualization as IFurnitureVisualization;
|
||||||
|
const layerData: IFurnitureLayerData = {
|
||||||
|
layer,
|
||||||
|
alpha: 1,
|
||||||
|
z: 0,
|
||||||
|
blendMode: BLEND_MODES.NORMAL,
|
||||||
|
flip: false,
|
||||||
|
frame: 0,
|
||||||
|
ignoreMouse: false,
|
||||||
|
direction: Direction.NORTH
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!visualization.directions.includes(this._furniture.direction))
|
||||||
|
this._furniture.rotate(visualization.directions[0], 0);
|
||||||
|
|
||||||
|
layerData.direction = this._furniture.direction;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._furniture.data.color !== null &&
|
||||||
|
visualization.colors[this._furniture.data.color] !== undefined &&
|
||||||
|
visualization.colors[this._furniture.data.color][layer] !== undefined
|
||||||
|
)
|
||||||
|
layerData.tint = Number('0x' + String(visualization.colors[this._furniture.data.color][layer]));
|
||||||
|
|
||||||
|
const visualizationLayerData = visualization.layers[layer];
|
||||||
|
|
||||||
|
if (visualizationLayerData !== undefined) {
|
||||||
|
if (visualizationLayerData.z !== undefined) layerData.z = visualizationLayerData.z;
|
||||||
|
if (visualizationLayerData.alpha !== undefined) layerData.alpha = visualizationLayerData.alpha / 255;
|
||||||
|
if (visualizationLayerData.ink !== undefined) layerData.blendMode = BLEND_MODES[visualizationLayerData.ink];
|
||||||
|
if (visualization.layers[layer].ignoreMouse !== undefined)
|
||||||
|
layerData.ignoreMouse = visualizationLayerData.ignoreMouse;
|
||||||
|
if (visualization.layers[layer].tag !== undefined) layerData.tag = visualizationLayerData.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = [
|
||||||
|
this._furniture.data.baseName,
|
||||||
|
this._furniture.data.baseName,
|
||||||
|
64,
|
||||||
|
String.fromCharCode(97 + Number(layer)),
|
||||||
|
this._furniture.direction,
|
||||||
|
frame
|
||||||
|
].join('_');
|
||||||
|
|
||||||
|
if (this._furniture instanceof FloorFurniture)
|
||||||
|
layerData.z = ZOrder.floorItem(this._furniture.position as IFloorPosition, layerData.z);
|
||||||
|
|
||||||
|
if (this._furniture instanceof WallFurniture)
|
||||||
|
layerData.z = ZOrder.wallItem(this._furniture.position as IWallPosition, layerData.z);
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
if (spritesheet.data.frames[name] !== undefined) layerData.flip = spritesheet.data.frames[name].flipH;
|
||||||
|
|
||||||
|
return layerData;
|
||||||
|
}
|
||||||
|
}
|
416
src/objects/interactions/EventManager.ts
Normal file
416
src/objects/interactions/EventManager.ts
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
import type { IInteractionEvent } from '../../types/Interaction';
|
||||||
|
import type { Room } from '../rooms/Room';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InteractionManager class for interaction handling.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class EventManager {
|
||||||
|
/**
|
||||||
|
* A boolean indicating if the user have clicked at least one time, indicating that the second click is a double click.
|
||||||
|
*
|
||||||
|
* @member {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _isDoubleClicking = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The double click timeout that set the _isDoubleClicking boolean value to false after 350ms.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _doubleClickTimeout!: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pointer down event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onPointerDown!: (event: IInteractionEvent) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pointer up event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onPointerUp!: (event: IInteractionEvent) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pointer move event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onPointerMove!: (event: IInteractionEvent) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pointer out event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onPointerOut!: (event: IInteractionEvent) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pointer ouver event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onPointerOver!: (event: IInteractionEvent) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pointer double click event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onDoubleClick!: (event: IInteractionEvent) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The assets starting load event.
|
||||||
|
*
|
||||||
|
* @member {() => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onLoad!: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The assets ending load event.
|
||||||
|
*
|
||||||
|
* @member {() => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onLoadComplete!: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room add event.
|
||||||
|
*
|
||||||
|
* @member {(room: Room) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onRoomAdded!: (room: Room) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room remove event.
|
||||||
|
*
|
||||||
|
* @member {(room: Room) => void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _onRoomRemoved!: (room: Room) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer down event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerDown(): (event: IInteractionEvent) => void {
|
||||||
|
return this._onPointerDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerDown(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._onPointerDown = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer up event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerUp(): (event: IInteractionEvent) => void {
|
||||||
|
return this._onPointerUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerUp(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._onPointerUp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer move event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerMove(): (event: IInteractionEvent) => void {
|
||||||
|
return this._onPointerMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerMove(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._onPointerMove = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer out event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerOut(): (event: IInteractionEvent) => void {
|
||||||
|
return this._onPointerOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerOut(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._onPointerOut = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer over event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerOver(): (event: IInteractionEvent) => void {
|
||||||
|
return this._onPointerOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerOver(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._onPointerOver = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer double click event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onDoubleClick(): (event: IInteractionEvent) => void {
|
||||||
|
return this._onDoubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onDoubleClick(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._onDoubleClick = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the assets starting load event.
|
||||||
|
*
|
||||||
|
* @member {() => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onLoad(): () => void {
|
||||||
|
return this._onLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {() => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onLoad(value: () => void) {
|
||||||
|
this._onLoad = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the assets ending load event.
|
||||||
|
*
|
||||||
|
* @member {() => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onLoadComplete(): () => void {
|
||||||
|
return this._onLoadComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {() => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onLoadComplete(value: () => void) {
|
||||||
|
this._onLoadComplete = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room add event.
|
||||||
|
*
|
||||||
|
* @member {(room: Room) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onRoomAdded(): (room: Room) => void {
|
||||||
|
return this._onRoomAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(room: Room) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onRoomAdded(value: (room: Room) => void) {
|
||||||
|
this._onRoomAdded = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room remove event.
|
||||||
|
*
|
||||||
|
* @member {(room: Room) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onRoomRemoved(): (room: Room) => void {
|
||||||
|
return this._onRoomRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(room: Room) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onRoomRemoved(value: (room: Room) => void) {
|
||||||
|
this._onRoomRemoved = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the pointer down event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handlePointerDown(event: IInteractionEvent): void {
|
||||||
|
if (!this._isDoubleClicking) {
|
||||||
|
if (this.onPointerDown !== undefined) this._onPointerDown(event);
|
||||||
|
this._isDoubleClicking = true;
|
||||||
|
this._doubleClickTimeout = window.setTimeout(() => {
|
||||||
|
return (this._isDoubleClicking = false);
|
||||||
|
}, 350);
|
||||||
|
} else {
|
||||||
|
if (this.onDoubleClick !== undefined) this._onDoubleClick(event);
|
||||||
|
this._isDoubleClicking = false;
|
||||||
|
window.clearTimeout(this._doubleClickTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the pointer up event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handlePointerUp(event: IInteractionEvent): void {
|
||||||
|
if (this.onPointerUp !== undefined) this._onPointerUp(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the pointer move event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handlePointerMove(event: IInteractionEvent): void {
|
||||||
|
if (this.onPointerMove !== undefined) this._onPointerMove(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the pointer out event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handlePointerOut(event: IInteractionEvent): void {
|
||||||
|
if (this.onPointerOut !== undefined) this._onPointerOut(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the pointer over event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handlePointerOver(event: IInteractionEvent): void {
|
||||||
|
if (this.onPointerOver !== undefined) this._onPointerOver(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the assets load start event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handleLoad(): void {
|
||||||
|
if (this.onLoad !== undefined) this._onLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the assets load end event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handleLoadComplete(): void {
|
||||||
|
if (this.onLoadComplete !== undefined) this._onLoadComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the room add event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handleRoomAdded(room: Room): void {
|
||||||
|
if (this.onRoomAdded !== undefined) this._onRoomAdded(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the room remove event.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public handleRoomRemoved(room: Room): void {
|
||||||
|
if (this.onRoomRemoved !== undefined) this._onRoomRemoved(room);
|
||||||
|
}
|
||||||
|
}
|
71
src/objects/interactions/HitSprite.ts
Normal file
71
src/objects/interactions/HitSprite.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import type { IPointData } from 'pixi.js';
|
||||||
|
import { Sprite } from 'pixi.js';
|
||||||
|
|
||||||
|
import { HitTexture } from './HitTexture';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HitSprite class that manage the interactions with sprite transparency.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class HitSprite extends Sprite {
|
||||||
|
/**
|
||||||
|
* The sprite interactivity.
|
||||||
|
*
|
||||||
|
* @member {boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public interactive!: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global sprite position in the canvas.
|
||||||
|
*
|
||||||
|
* @member {{ x: number, y: number }}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public getGlobalPosition: () => { x: number; y: number };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hit texture that contains the hit map data.
|
||||||
|
*
|
||||||
|
* @member {HitTexture}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _hitTexture!: HitTexture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean indicating if the pointer is on the sprite.
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public containsPoint(point: IPointData): boolean {
|
||||||
|
/** The sprite is not interactive, so we stop here */
|
||||||
|
if (!this.interactive) return false;
|
||||||
|
|
||||||
|
if (this.texture.trim === undefined) return false;
|
||||||
|
|
||||||
|
const width = this.texture.orig.width;
|
||||||
|
const height = this.texture.orig.height;
|
||||||
|
|
||||||
|
const x1 = this.getGlobalPosition().x + this.texture.trim.x;
|
||||||
|
let y1 = 0;
|
||||||
|
let flag = false;
|
||||||
|
|
||||||
|
/** Check if the pointer is out of bound of the sprite */
|
||||||
|
if (point.x >= x1 && point.x < x1 + width) {
|
||||||
|
y1 = this.getGlobalPosition().y + this.texture.trim.y;
|
||||||
|
if (point.y >= y1 && point.y < y1 + height) flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return false if the pointer is out of bound */
|
||||||
|
if (!flag) return false;
|
||||||
|
|
||||||
|
/** Create the hit texture */
|
||||||
|
if (this._hitTexture == null) this._hitTexture = new HitTexture(this);
|
||||||
|
|
||||||
|
/** Check the hit map of the hit texture if the pointer is on a transparent pixel or not */
|
||||||
|
return this._hitTexture.hit(point.x - x1, point.y - y1, this.scale.x === -1);
|
||||||
|
}
|
||||||
|
}
|
290
src/objects/interactions/HitTexture.ts
Normal file
290
src/objects/interactions/HitTexture.ts
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
import type { BaseTexture, DisplayObject, ICanvas, IRenderer, Resource } from 'pixi.js';
|
||||||
|
import { BaseImageResource, Rectangle, RenderTexture, Sprite, Texture } from 'pixi.js';
|
||||||
|
import { CanvasRenderTarget } from '@pixi/utils';
|
||||||
|
|
||||||
|
import type { HitSprite } from './HitSprite';
|
||||||
|
import { FurnitureLayer } from '../furnitures/visualizations/FurnitureLayer';
|
||||||
|
import { AvatarLayer } from '../avatars/visualizations/AvatarLayer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HitTexture class create an hit map from a texture to manage interactions.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class HitTexture {
|
||||||
|
/**
|
||||||
|
* The hit sprite of the hit texture.
|
||||||
|
*
|
||||||
|
* @member {HitSprite}
|
||||||
|
* @readonly
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _sprite: HitSprite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hit texture base texture.
|
||||||
|
*
|
||||||
|
* @member {Texture}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _texture: Texture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hit map array.
|
||||||
|
*
|
||||||
|
* @member {Uint32Array}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _hitMap!: Uint32Array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HitSprite} [sprite] - The hit sprite that we want to retrieve the texture.
|
||||||
|
*/
|
||||||
|
constructor(sprite: HitSprite) {
|
||||||
|
this._sprite = sprite;
|
||||||
|
this._texture = this._generateTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the cached hit map of the sprite.
|
||||||
|
*
|
||||||
|
* @return {Uint32Array} - A Uint32Array that is the cached hit map of the sprite.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _getHitMap(): Uint32Array {
|
||||||
|
if (!Boolean(this._hitMap)) this._hitMap = this._generateHitMap(this._texture.baseTexture);
|
||||||
|
return this._hitMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the hit map from a specified base texture.
|
||||||
|
*
|
||||||
|
* @param {BaseTexture} baseTexture - The base texture that we want to have the hit map.
|
||||||
|
* @return {Uint32Array} - An Uint32Array that is the hit map of the specified baseTexture.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _generateHitMap(baseTexture: BaseTexture<Resource>): Uint32Array {
|
||||||
|
const { height: imageHeight, width: imageWidth } = baseTexture.resource;
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
canvas.width = imageWidth;
|
||||||
|
canvas.height = imageHeight;
|
||||||
|
// @ts-expect-error
|
||||||
|
context.drawImage(baseTexture.resource.source, 0, 0);
|
||||||
|
|
||||||
|
let w = canvas.width;
|
||||||
|
let h = canvas.height;
|
||||||
|
if (w > canvas.width) w = canvas.width;
|
||||||
|
if (h > canvas.height) h = canvas.height;
|
||||||
|
|
||||||
|
if (w === 0) return new Uint32Array();
|
||||||
|
|
||||||
|
const imageData = context.getImageData(0, 0, w, h);
|
||||||
|
const threshold = 255;
|
||||||
|
const hitmap = new Uint32Array(Math.ceil((w * h) / 32));
|
||||||
|
for (let i = 0; i < w * h; i++) {
|
||||||
|
const ind1 = i % 32;
|
||||||
|
const ind2 = (i / 32) | 0;
|
||||||
|
if (imageData.data[i * 4 + 3] >= threshold) {
|
||||||
|
hitmap[ind2] = hitmap[ind2] | (1 << ind1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a sprite texture and not the texture of the entire spritesheet.
|
||||||
|
*
|
||||||
|
* @return {Texture} - A texture of the sprite (not all the spritesheet).
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _generateTexture(): Texture {
|
||||||
|
const sprite = new Sprite(this._sprite.texture.clone());
|
||||||
|
let renderTexture = {} as RenderTexture;
|
||||||
|
|
||||||
|
sprite.x = this._sprite.getGlobalPosition().x + this._sprite.texture.trim.x;
|
||||||
|
sprite.y = this._sprite.getGlobalPosition().y + this._sprite.texture.trim.y;
|
||||||
|
sprite.texture.trim.x = 0;
|
||||||
|
sprite.texture.trim.y = 0;
|
||||||
|
|
||||||
|
if (this._sprite instanceof FurnitureLayer) {
|
||||||
|
this._sprite.furniture.room.engine.application.stage.addChild(sprite);
|
||||||
|
renderTexture = this._sprite.furniture.room.engine.application.renderer.generateTexture(sprite);
|
||||||
|
} else if (this._sprite.parent instanceof AvatarLayer) {
|
||||||
|
this._sprite.parent.avatar.room.engine.application.stage.addChild(sprite);
|
||||||
|
renderTexture = this._sprite.parent.avatar.room.engine.application.renderer.generateTexture(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = this._image(renderTexture);
|
||||||
|
const baseTexture = renderTexture.baseTexture;
|
||||||
|
|
||||||
|
renderTexture.baseTexture.resource = new BaseImageResource(image);
|
||||||
|
renderTexture.destroy();
|
||||||
|
sprite.destroy();
|
||||||
|
|
||||||
|
return new Texture(baseTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return a boolean that indicate if we hit the sprite.
|
||||||
|
*
|
||||||
|
* @param {number} x - The x coordinate of the hit point
|
||||||
|
* @param {number} y - The y coordinate of the hit point
|
||||||
|
* @param {boolean} flip - If the sprite is flipped
|
||||||
|
* @return {boolean} - A boolean indicating if we hit the sprite.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public hit(x: number, y: number, flip: boolean): boolean {
|
||||||
|
const hitmap = this._getHitMap();
|
||||||
|
const dx = flip
|
||||||
|
? this._texture.baseTexture.realWidth - Math.round(x * this._texture.baseTexture.resolution)
|
||||||
|
: Math.round(x * this._texture.baseTexture.resolution);
|
||||||
|
const dy = Math.round(y * this._texture.baseTexture.resolution);
|
||||||
|
const ind = dx + dy * this._texture.baseTexture.realWidth;
|
||||||
|
const ind1 = ind % 32;
|
||||||
|
const ind2 = (ind / 32) | 0;
|
||||||
|
|
||||||
|
return (hitmap[ind2] & (1 << ind1)) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return a HTML Image of the target.
|
||||||
|
*
|
||||||
|
* @param {DisplayObject|RenderTexture} target - A displayObject or renderTexture
|
||||||
|
* to convert. If left empty will use the main renderer
|
||||||
|
* @param {string} [format] - Image format, e.g. "image/jpeg" or "image/webp".
|
||||||
|
* @param {number} [quality] - JPEG or Webp compression from 0 to 1. Default is 0.92.
|
||||||
|
* @return {HTMLImageElement} HTML Image of the target.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _image(target: DisplayObject | RenderTexture, format?: string, quality?: number): HTMLImageElement {
|
||||||
|
const image = new Image();
|
||||||
|
image.src = this._base64(target, format, quality);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return a a base64 encoded string of this target. It works by calling
|
||||||
|
* `Extract.getCanvas` and then running toDataURL on that.
|
||||||
|
*
|
||||||
|
* @param {DisplayObject|RenderTexture} target - A displayObject or renderTexture
|
||||||
|
* to convert. If left empty will use the main renderer
|
||||||
|
* @param {string} [format] - Image format, e.g. "image/jpeg" or "image/webp".
|
||||||
|
* @param {number} [quality] - JPEG or Webp compression from 0 to 1. Default is 0.92.
|
||||||
|
* @return {string} A base64 encoded string of the texture.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _base64(target: DisplayObject | RenderTexture, format?: string, quality?: number): string {
|
||||||
|
return this._canvas(target).toDataURL(format, quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Canvas element, renders this target to it and then returns it.
|
||||||
|
*
|
||||||
|
* @param {DisplayObject|RenderTexture} target - A displayObject or renderTexture
|
||||||
|
* to convert. If left empty will use the main renderer
|
||||||
|
* @return {HTMLCanvasElement} A Canvas element with the texture rendered on.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _canvas(target: DisplayObject | RenderTexture): ICanvas {
|
||||||
|
const TEMP_RECT = new Rectangle();
|
||||||
|
const BYTES_PER_PIXEL = 4;
|
||||||
|
let renderer = {} as IRenderer<ICanvas>;
|
||||||
|
|
||||||
|
if (this._sprite instanceof FurnitureLayer) {
|
||||||
|
renderer = this._sprite.furniture.room.engine.application.renderer;
|
||||||
|
} else if (this._sprite.parent instanceof AvatarLayer) {
|
||||||
|
renderer = this._sprite.parent.avatar.room.engine.application.renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolution: number;
|
||||||
|
let frame: Rectangle;
|
||||||
|
let flipY: boolean;
|
||||||
|
let renderTexture: RenderTexture | undefined;
|
||||||
|
let generated = false;
|
||||||
|
|
||||||
|
if (Boolean(target)) {
|
||||||
|
if (target instanceof RenderTexture) {
|
||||||
|
renderTexture = target;
|
||||||
|
} else {
|
||||||
|
renderTexture = renderer.generateTexture(target);
|
||||||
|
generated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boolean(renderTexture)) {
|
||||||
|
resolution = renderTexture.baseTexture.resolution;
|
||||||
|
frame = renderTexture.frame;
|
||||||
|
flipY = false;
|
||||||
|
renderer.renderTexture.bind(renderTexture);
|
||||||
|
} else {
|
||||||
|
resolution = renderer.resolution;
|
||||||
|
|
||||||
|
flipY = true;
|
||||||
|
|
||||||
|
frame = TEMP_RECT;
|
||||||
|
frame.width = renderer.width;
|
||||||
|
frame.height = renderer.height;
|
||||||
|
renderer.renderTexture.bind(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = Math.floor(frame.width * resolution + 1e-4);
|
||||||
|
const height = Math.floor(frame.height * resolution + 1e-4);
|
||||||
|
const webglPixels = new Uint8Array(BYTES_PER_PIXEL * width * height);
|
||||||
|
let canvasBuffer = new CanvasRenderTarget(width, height, 1);
|
||||||
|
|
||||||
|
/** Read pixels to the array */
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webglPixels);
|
||||||
|
|
||||||
|
/** Add the pixels to the canvas */
|
||||||
|
const canvasData = canvasBuffer.context.getImageData(0, 0, width, height);
|
||||||
|
this.arrayPostDivide(webglPixels, canvasData.data);
|
||||||
|
canvasBuffer.context.putImageData(canvasData, 0, 0);
|
||||||
|
|
||||||
|
/** Pulling pixels */
|
||||||
|
if (flipY) {
|
||||||
|
const target = new CanvasRenderTarget(canvasBuffer.width, canvasBuffer.height, 1);
|
||||||
|
target.context.scale(1, -1);
|
||||||
|
|
||||||
|
/** We can't render to itself because we should be empty before render. */
|
||||||
|
target.context.drawImage(canvasBuffer.canvas, 0, -height);
|
||||||
|
|
||||||
|
canvasBuffer.destroy();
|
||||||
|
canvasBuffer = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generated) renderTexture.destroy(true);
|
||||||
|
|
||||||
|
/** Send the canvas back... */
|
||||||
|
return canvasBuffer.canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes premultiplied pixel data and produces regular pixel data
|
||||||
|
* @private
|
||||||
|
* @param pixels - array of pixel data
|
||||||
|
* @param out - output array
|
||||||
|
*/
|
||||||
|
private arrayPostDivide(
|
||||||
|
pixels: number[] | Uint8Array | Uint8ClampedArray,
|
||||||
|
out: number[] | Uint8Array | Uint8ClampedArray
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < pixels.length; i += 4) {
|
||||||
|
const alpha = (out[i + 3] = pixels[i + 3]);
|
||||||
|
|
||||||
|
if (alpha !== 0) {
|
||||||
|
out[i] = Math.round(Math.min((pixels[i] * 255) / alpha, 255));
|
||||||
|
out[i + 1] = Math.round(Math.min((pixels[i + 1] * 255) / alpha, 255));
|
||||||
|
out[i + 2] = Math.round(Math.min((pixels[i + 2] * 255) / alpha, 255));
|
||||||
|
} else {
|
||||||
|
out[i] = pixels[i];
|
||||||
|
out[i + 1] = pixels[i + 1];
|
||||||
|
out[i + 2] = pixels[i + 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
333
src/objects/rooms/Room.ts
Normal file
333
src/objects/rooms/Room.ts
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
import { Container } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Scuti } from '../../Scuti';
|
||||||
|
import { RoomVisualization } from './RoomVisualization';
|
||||||
|
import { RoomTileMap } from './RoomTileMap';
|
||||||
|
import type { Material } from './materials/Material';
|
||||||
|
import { WallMaterial } from './materials/WallMaterial';
|
||||||
|
import { FloorMaterial } from './materials/FloorMaterial';
|
||||||
|
import { RoomCamera } from './RoomCamera';
|
||||||
|
import type { EventManager } from '../interactions/EventManager';
|
||||||
|
import type { IRoomConfig } from '../../types/Room';
|
||||||
|
import type { RoomPartLayer } from './layers/RoomPartLayer';
|
||||||
|
import type { RoomObjectLayer } from './layers/RoomObjectLayer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room class for rendering rooms like the ones on Habbo Hotel.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Room extends Container {
|
||||||
|
/**
|
||||||
|
* The game engine instance that the room will be using to render objects.
|
||||||
|
*
|
||||||
|
* @member {Scuti}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _engine: Scuti;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room tile map where every informations about the room model is stored.
|
||||||
|
*
|
||||||
|
* @member {RoomTileMap}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _tileMap: RoomTileMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wall material that will be applied in the room, it contains the color and the texture of the wall.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _wallMaterial: Material;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The floor material that will be applied in the room, it contains the color and the texture of the wall.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _floorMaterial: Material;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wall thickness of the room.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _wallThickness: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The floor thickness of the room.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _floorThickness: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wall height of the room, the height is added to the base height of the room.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _wallHeight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room view instance, where all the objects like furnitures, avatars or the tiles, walls and stairs are stored.
|
||||||
|
*
|
||||||
|
* @member {RoomVisualization}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _visualization: RoomVisualization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room camera, it manage the room dragging and centering the room when it's out of bounds.
|
||||||
|
*
|
||||||
|
* @member {RoomCamera}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _camera: RoomCamera;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Scuti} [engine] - The Scuti instance that will be used to render the room.
|
||||||
|
* @param {IRoomConfig} [config] - The room configuration.
|
||||||
|
* @param {string} [config.tilemap] - The room tile map that will be parsed.
|
||||||
|
* @param {Material} [config.floorMaterial] - The room floor material that will be applied.
|
||||||
|
* @param {number} [config.floorThickness] - The room floor thickness.
|
||||||
|
* @param {Material} [config.wallMaterial] - The room wall material that will be applied.
|
||||||
|
* @param {number} [config.wallHeight] - The room wall height.
|
||||||
|
* @param {number} [config.wallThickness] - The room wall thickness.
|
||||||
|
**/
|
||||||
|
constructor(engine: Scuti, config: IRoomConfig) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
/** Store variables */
|
||||||
|
this._engine = engine;
|
||||||
|
this._wallMaterial = config.wallMaterial ?? new WallMaterial(this._engine);
|
||||||
|
this._floorMaterial = config.floorMaterial ?? new FloorMaterial(this._engine);
|
||||||
|
this._wallThickness = config.wallThickness ?? 8;
|
||||||
|
this._floorThickness = config.floorThickness ?? 8;
|
||||||
|
this._wallHeight = config.wallHeight ?? 0;
|
||||||
|
|
||||||
|
/** Initialise everything */
|
||||||
|
this._tileMap = new RoomTileMap(config.tileMap);
|
||||||
|
this._visualization = new RoomVisualization(this);
|
||||||
|
this._camera = new RoomCamera(this);
|
||||||
|
|
||||||
|
/** Add the room view and then the room camera to the PixiJS application */
|
||||||
|
this.addChild(this._visualization);
|
||||||
|
this._engine.application.stage.addChild(this._camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the game engine instance.
|
||||||
|
*
|
||||||
|
* @member {Scuti}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get engine(): Scuti {
|
||||||
|
return this._engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room view instance.
|
||||||
|
*
|
||||||
|
* @member {RoomVisualization}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get visualization(): RoomVisualization {
|
||||||
|
return this._visualization;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room tile map instance.
|
||||||
|
*
|
||||||
|
* @member {RoomTileMap}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get tileMap(): RoomTileMap {
|
||||||
|
return this._tileMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the room tileMap.
|
||||||
|
*
|
||||||
|
* @param {string} [tileMap] - The new room tileMap.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set tileMap(tileMap: RoomTileMap) {
|
||||||
|
this._tileMap = tileMap;
|
||||||
|
this._visualization.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the wall material instance.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get wallMaterial(): Material {
|
||||||
|
return this._wallMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the wall material and rerender the room.
|
||||||
|
*
|
||||||
|
* @param {Material} [material] - The room wall material that will be applied.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set wallMaterial(material: Material) {
|
||||||
|
this._wallMaterial = material;
|
||||||
|
this._visualization.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the floor material instance.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get floorMaterial(): Material {
|
||||||
|
return this._floorMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the floor material and rerender the room.
|
||||||
|
*
|
||||||
|
* @param {Material} [material] - The room floor material that will be applied.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set floorMaterial(material: Material) {
|
||||||
|
this._floorMaterial = material;
|
||||||
|
this._visualization.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the wall thickness.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get wallThickness(): number {
|
||||||
|
return this._wallThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the wall thickness and rerender the room.
|
||||||
|
*
|
||||||
|
* @param {number} [thickness] - The room wall thickness that will be applied.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set wallThickness(thickness: number) {
|
||||||
|
this._wallThickness = thickness;
|
||||||
|
this._visualization.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the floor thickness.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get floorThickness(): number {
|
||||||
|
return this._floorThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the floor thickness and rerender the room.
|
||||||
|
*
|
||||||
|
* @param {number} [thickness] - The room floor thickness that will be applied.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set floorThickness(thickness: number) {
|
||||||
|
this._floorThickness = thickness;
|
||||||
|
this._visualization.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the wall height.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get wallHeight(): number {
|
||||||
|
return this._wallHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the wall height and rerender the room.
|
||||||
|
*
|
||||||
|
* @param {number} [height] - The room wall height that will be applied.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set wallHeight(height: number) {
|
||||||
|
this._wallHeight = height;
|
||||||
|
this._visualization.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the tile event manager.
|
||||||
|
*
|
||||||
|
* @member {EventManager}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get tiles(): EventManager {
|
||||||
|
return this._visualization.partLayer.tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the wall event manager.
|
||||||
|
*
|
||||||
|
* @member {EventManager}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get walls(): EventManager {
|
||||||
|
return this._visualization.partLayer.walls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the object layer container.
|
||||||
|
*
|
||||||
|
* @member {RoomObjectLayer}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get objects(): RoomObjectLayer {
|
||||||
|
return this._visualization.objectLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the part layer container.
|
||||||
|
*
|
||||||
|
* @member {RoomPartLayer}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get parts(): RoomPartLayer {
|
||||||
|
return this._visualization.partLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room camera.
|
||||||
|
*
|
||||||
|
* @member {RoomCamera}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get camera(): RoomCamera {
|
||||||
|
return this._camera;
|
||||||
|
}
|
||||||
|
}
|
269
src/objects/rooms/RoomCamera.ts
Normal file
269
src/objects/rooms/RoomCamera.ts
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import { Container, EventBoundary, FederatedPointerEvent } from 'pixi.js';
|
||||||
|
import { Expo, gsap } from 'gsap';
|
||||||
|
|
||||||
|
import type { Room } from './Room';
|
||||||
|
import type { Tile } from './parts/Tile';
|
||||||
|
import type { Stair } from './parts/Stair';
|
||||||
|
import type { RoomObject } from './objects/RoomObject';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomCamera class that manage things like the room dragging or detecting if the room is out of bounds.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class RoomCamera extends Container {
|
||||||
|
/**
|
||||||
|
* The room instance that will be managed by the camera.
|
||||||
|
*
|
||||||
|
* @member {Room}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _room: Room;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean indicating if the room is being dragged.
|
||||||
|
*
|
||||||
|
* @member {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _dragging!: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current selected tile.
|
||||||
|
*
|
||||||
|
* @member {Tile | Stair}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _selectedTile!: Tile | Stair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current zoom level.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _zoomLevel: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [room] - The room instance that will be managed by this camera.
|
||||||
|
*/
|
||||||
|
constructor(room: Room) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._room = room;
|
||||||
|
this._zoomLevel = 1;
|
||||||
|
|
||||||
|
this.addChild(this._room);
|
||||||
|
|
||||||
|
/** Handle interactions */
|
||||||
|
this._room.engine.application.renderer.events.domElement.addEventListener('pointerdown', this._dragStart);
|
||||||
|
this._room.engine.application.renderer.events.domElement.addEventListener('pointerup', this._dragEnd);
|
||||||
|
this._room.engine.application.renderer.events.domElement.addEventListener('pointermove', this._dragMove);
|
||||||
|
|
||||||
|
/** Handle tile interactions */
|
||||||
|
this._room.engine.application.renderer.events.domElement.addEventListener('pointerdown', this._tilePointerDown);
|
||||||
|
this._room.engine.application.renderer.events.domElement.addEventListener('pointerup', this._tilePointerUp);
|
||||||
|
this._room.engine.application.renderer.events.domElement.addEventListener('pointermove', this._tilePointerMove);
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
'wheel',
|
||||||
|
(event) => {
|
||||||
|
if (event.ctrlKey) event.preventDefault();
|
||||||
|
|
||||||
|
const delta = Math.sign(event.deltaY);
|
||||||
|
const zoomLevel = parseFloat((-delta / 8).toFixed(2));
|
||||||
|
if (this.zoomLevel + zoomLevel <= 0.8 || this.zoomLevel + zoomLevel >= 2.8) return;
|
||||||
|
|
||||||
|
this.zoomLevel += zoomLevel;
|
||||||
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this._room.engine.application.view.height = window.innerHeight;
|
||||||
|
this._room.engine.application.view.width = window.innerWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._updateBounds();
|
||||||
|
this.centerCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the room container bounds.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _updateBounds(): void {
|
||||||
|
this.pivot.x = this._room.visualization.getBounds().x;
|
||||||
|
this.pivot.y = this._room.visualization.getBounds().y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tween the room container at the center of the PixiJS view.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public centerCamera(object?: RoomObject): void {
|
||||||
|
if (object === null || object === undefined)
|
||||||
|
gsap.to(this, {
|
||||||
|
x: Math.floor(this._room.engine.application.view.width / 2 - this._room.visualization.width / 2),
|
||||||
|
y: Math.floor(this._room.engine.application.view.height / 2 - this._room.visualization.height / 2),
|
||||||
|
duration: 0.8,
|
||||||
|
ease: 'easeOut'
|
||||||
|
});
|
||||||
|
// TODO: Reimplement this part with the new room object
|
||||||
|
/*else {
|
||||||
|
const globalPos: Point = object.getGlobalPosition(new Point(object.width / 2, object.height / 2));
|
||||||
|
const diffX: number = globalPos.x - this._room.engine.application.view.width / 2;
|
||||||
|
const diffY: number = globalPos.y - this._room.engine.application.view.height / 2;
|
||||||
|
gsap.to(this._roomContainer, {
|
||||||
|
x: Math.floor(this._roomContainer.x - diffX),
|
||||||
|
y: Math.floor(this._roomContainer.y - diffY),
|
||||||
|
duration: 0.8,
|
||||||
|
ease: 'easeOut'
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called when the user start dragging the room.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _dragStart = (): void => {
|
||||||
|
// console.log(event.movementX);
|
||||||
|
this._dragging = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called when the user stop dragging the room.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _dragEnd = (): void => {
|
||||||
|
this._dragging = false;
|
||||||
|
if (this._isOutOfBounds()) this.centerCamera();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called when the user is moving the dragged room in the canvas.
|
||||||
|
*
|
||||||
|
* @param {PointerEvent} [event] - The mouse event.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _dragMove = (event: PointerEvent): void => {
|
||||||
|
if (!this._dragging) return;
|
||||||
|
this.x = Math.floor(this.x + event.movementX * (1 / this._zoomLevel));
|
||||||
|
this.y = Math.floor(this.y + event.movementY * (1 / this._zoomLevel));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate if the room container is out of bounds of the PixiJS view.
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _isOutOfBounds(): boolean {
|
||||||
|
/** Out of bounds on the right */
|
||||||
|
if (this.x > this._room.engine.application.view.width) return true;
|
||||||
|
/** Out of bounds on the left */
|
||||||
|
if (this.x + this.width < 0) return true;
|
||||||
|
/** Out of bounds on the bottom */
|
||||||
|
if (this.y > this._room.engine.application.view.height) return true;
|
||||||
|
/** Out of bounds on the top */
|
||||||
|
if (this.y + this.height < 0) return true;
|
||||||
|
/** It is not out of bounds */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage pointer down event on the canvas for tile interaction.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tilePointerDown = (event: PointerEvent): void => {
|
||||||
|
const tile = this._room.parts.getFromGlobal({ x: event.clientX, y: event.clientY });
|
||||||
|
|
||||||
|
if (tile != null) tile.emit('pointerdown', new FederatedPointerEvent(new EventBoundary()));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage pointer up event on the canvas for tile interaction.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tilePointerUp = (event: PointerEvent): void => {
|
||||||
|
const tile = this._room.parts.getFromGlobal({ x: event.clientX, y: event.clientY });
|
||||||
|
|
||||||
|
if (tile != null) tile.emit('pointerup', new FederatedPointerEvent(new EventBoundary()));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage pointer move event on the canvas for tile interaction.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tilePointerMove = (event: PointerEvent): void => {
|
||||||
|
const objectPart = this._room.parts.getFromGlobal({ x: event.clientX, y: event.clientY });
|
||||||
|
|
||||||
|
if (objectPart == null) return;
|
||||||
|
if (this._selectedTile === objectPart) {
|
||||||
|
objectPart.emit('pointermove', new FederatedPointerEvent(new EventBoundary()));
|
||||||
|
} else {
|
||||||
|
if (this._selectedTile != null)
|
||||||
|
this._selectedTile.emit('pointerout', new FederatedPointerEvent(new EventBoundary()));
|
||||||
|
if (objectPart != null) objectPart.emit('pointerover', new FederatedPointerEvent(new EventBoundary()));
|
||||||
|
this._selectedTile = objectPart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zoom the room container.
|
||||||
|
*
|
||||||
|
* @param {number} [zoomLevel] - The zoom ratio as a number.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set zoomLevel(zoomLevel: number) {
|
||||||
|
const origWidth = this.width / this._zoomLevel;
|
||||||
|
const origHeight = this.height / this._zoomLevel;
|
||||||
|
|
||||||
|
this._zoomLevel = zoomLevel;
|
||||||
|
|
||||||
|
// pivot's container property must be changed
|
||||||
|
// because by default things are centered and scaled from the top corner
|
||||||
|
// this.pivot.x = ... / this.pivot.y = ...
|
||||||
|
gsap.set(this.scale, { x: zoomLevel, y: zoomLevel, ease: Expo.easeOut });
|
||||||
|
|
||||||
|
const diffWidth = origWidth - this.width;
|
||||||
|
const diffHeight = origHeight - this.height;
|
||||||
|
const offsetX = this._room.engine.application.view.width / 2 - (this.x + origWidth / 2);
|
||||||
|
const offsetY = this._room.engine.application.view.height / 2 - (this.y + origHeight / 2);
|
||||||
|
|
||||||
|
gsap.to(this, {
|
||||||
|
x: this.x + Math.floor(diffWidth / 2 + offsetX),
|
||||||
|
y: this.y + Math.floor(diffHeight / 2 + offsetY),
|
||||||
|
duration: 0.8,
|
||||||
|
ease: 'easeOut'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the zoom level of the room container.
|
||||||
|
*
|
||||||
|
* @member {Application}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get zoomLevel(): number {
|
||||||
|
return this._zoomLevel;
|
||||||
|
}
|
||||||
|
}
|
325
src/objects/rooms/RoomTileMap.ts
Normal file
325
src/objects/rooms/RoomTileMap.ts
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
import type { IPosition2D, ITileInfo, TileMap } from '../../types/Room';
|
||||||
|
import { WallType } from '../../enums/WallType';
|
||||||
|
import { StairType } from '../../enums/StairType';
|
||||||
|
import { Direction } from '../../enums/Direction';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomTileMap class that manage all the things about the room model.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class RoomTileMap {
|
||||||
|
/**
|
||||||
|
* The room tile map where every informations about the room model is stored.
|
||||||
|
*
|
||||||
|
* @member {TileMap}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tileMap: TileMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} [tileMap] - The room tile map string that need to be parsed.
|
||||||
|
*/
|
||||||
|
constructor(tileMap: string) {
|
||||||
|
/** Parse the tile map string to convert it into a matrix */
|
||||||
|
this._tileMap = this._parse(tileMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given tile map to convert it into a matrix.
|
||||||
|
*
|
||||||
|
* @param {string} [tileMap] - The tile map string that we want to convert into a matrix.
|
||||||
|
* @return {TileMap}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _parse(tileMap: string): TileMap {
|
||||||
|
tileMap = tileMap.replace(/ /g, '');
|
||||||
|
tileMap = tileMap.replace(/\n\n/g, '\n');
|
||||||
|
return tileMap.split(/\r?\n/).map((line) => {
|
||||||
|
return line.split('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room tile map matrix.
|
||||||
|
*
|
||||||
|
* @member {TileMap}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get tileMap(): TileMap {
|
||||||
|
return this._tileMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the 2D position into it's tile type character.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile position that we want to have the type.
|
||||||
|
* @return {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public getTile(position: IPosition2D): string {
|
||||||
|
return position.x < 0 ||
|
||||||
|
position.y < 0 ||
|
||||||
|
this._tileMap[position.y] === undefined ||
|
||||||
|
this._tileMap[position.y][position.x] === undefined
|
||||||
|
? 'x'
|
||||||
|
: this._tileMap[position.y][position.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the tile character into a number that is the height of the tile.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile position that we want to have the height.
|
||||||
|
* @return {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public getTileHeight(position: IPosition2D): number {
|
||||||
|
const tile = this.getTile(position);
|
||||||
|
return tile === 'x' ? 0 : isNaN(Number(tile)) ? tile.charCodeAt(0) - 96 + 9 : Number(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return informations about the tile (if it's a stair, a door, if there is walls, ...).
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile position that we want to have informations.
|
||||||
|
* @return {ITileInfo}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public getTileInfo(position: IPosition2D): ITileInfo {
|
||||||
|
return {
|
||||||
|
tile: this.isTile(position),
|
||||||
|
door: this.isDoor(position),
|
||||||
|
height: this.getTileHeight(position),
|
||||||
|
stairType: this._getStairType(position),
|
||||||
|
wallType: this._getWallType(position)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return walls informations about the given tile, like if it's a left wall, a corner wall, ...
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile position where we want to have the wall informations.
|
||||||
|
* @return {WallType}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _getWallType(position: IPosition2D): WallType | undefined {
|
||||||
|
const topLeftTile: IPosition2D = { x: position.x - 1, y: position.y - 1 };
|
||||||
|
const topTile: IPosition2D = { x: position.x, y: position.y - 1 };
|
||||||
|
const midLeftTile: IPosition2D = { x: position.x - 1, y: position.y };
|
||||||
|
|
||||||
|
if (this.isDoor(position)) return;
|
||||||
|
|
||||||
|
if (!this.isTile(topLeftTile) && !this.isTile(topTile) && !this.isTile(midLeftTile) && this.isTile(position))
|
||||||
|
return WallType.CORNER_WALL;
|
||||||
|
if (!this.isTile(midLeftTile) && this.isTile(position)) return WallType.LEFT_WALL;
|
||||||
|
if (!this.isTile(topTile) && this.isTile(position)) return WallType.RIGHT_WALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return stairs informations about the given tile, like if it's a normal stair, a corner stair, ...
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile position where we want to have the stair informations.
|
||||||
|
* @return {{ type: StairType, direction: Direction }}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _getStairType(position: IPosition2D): { type: StairType; direction: Direction } | undefined {
|
||||||
|
const topLeftTile: IPosition2D = { x: position.x - 1, y: position.y - 1 };
|
||||||
|
const topTile: IPosition2D = { x: position.x, y: position.y - 1 };
|
||||||
|
const topRightTile: IPosition2D = { x: position.x + 1, y: position.y - 1 };
|
||||||
|
|
||||||
|
const midLeftTile: IPosition2D = { x: position.x - 1, y: position.y };
|
||||||
|
const midRightTile: IPosition2D = { x: position.x + 1, y: position.y };
|
||||||
|
|
||||||
|
const botLeftTile: IPosition2D = { x: position.x - 1, y: position.y + 1 };
|
||||||
|
const botTile: IPosition2D = { x: position.x, y: position.y + 1 };
|
||||||
|
const botRightTile: IPosition2D = { x: position.x + 1, y: position.y + 1 };
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(topRightTile) &&
|
||||||
|
this._getTileDifference(topRightTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midRightTile, position) === 1 &&
|
||||||
|
this._getTileDifference(topTile, position) === 1
|
||||||
|
)
|
||||||
|
return { type: StairType.INNER_CORNER_STAIR, direction: Direction.NORTH_EAST };
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(botRightTile) &&
|
||||||
|
this._getTileDifference(botRightTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midRightTile, position) === 1 &&
|
||||||
|
this._getTileDifference(botTile, position) === 1
|
||||||
|
)
|
||||||
|
return { type: StairType.INNER_CORNER_STAIR, direction: Direction.SOUTH_EAST };
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(botLeftTile) &&
|
||||||
|
this._getTileDifference(botLeftTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midLeftTile, position) === 1 &&
|
||||||
|
this._getTileDifference(botTile, position) === 1
|
||||||
|
)
|
||||||
|
return { type: StairType.INNER_CORNER_STAIR, direction: Direction.SOUTH_WEST };
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(topLeftTile) &&
|
||||||
|
this._getTileDifference(topLeftTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midLeftTile, position) === 1 &&
|
||||||
|
this._getTileDifference(topTile, position) === 1
|
||||||
|
)
|
||||||
|
return { type: StairType.INNER_CORNER_STAIR, direction: Direction.NORTH_WEST };
|
||||||
|
if (this.isTile(position) && this.isTile(topTile) && this._getTileDifference(topTile, position) === 1)
|
||||||
|
return { type: StairType.STAIR, direction: Direction.NORTH };
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(topRightTile) &&
|
||||||
|
this._getTileDifference(topRightTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midRightTile, position) === 0 &&
|
||||||
|
this._getTileDifference(topTile, position) === 0
|
||||||
|
)
|
||||||
|
return { type: StairType.OUTER_CORNER_STAIR, direction: Direction.NORTH_EAST };
|
||||||
|
if (this.isTile(position) && this.isTile(midRightTile) && this._getTileDifference(midRightTile, position) === 1)
|
||||||
|
return { type: StairType.STAIR, direction: Direction.EAST };
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(botRightTile) &&
|
||||||
|
this._getTileDifference(botRightTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midRightTile, position) === 0 &&
|
||||||
|
this._getTileDifference(botTile, position) === 0
|
||||||
|
)
|
||||||
|
return { type: StairType.OUTER_CORNER_STAIR, direction: Direction.SOUTH_EAST };
|
||||||
|
if (this.isTile(position) && this.isTile(botTile) && this._getTileDifference(botTile, position) === 1)
|
||||||
|
return { type: StairType.STAIR, direction: Direction.SOUTH };
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(botLeftTile) &&
|
||||||
|
this._getTileDifference(botLeftTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midLeftTile, position) === 0 &&
|
||||||
|
this._getTileDifference(botTile, position) === 0
|
||||||
|
)
|
||||||
|
return { type: StairType.OUTER_CORNER_STAIR, direction: Direction.SOUTH_WEST };
|
||||||
|
if (this.isTile(position) && this.isTile(midLeftTile) && this._getTileDifference(midLeftTile, position) === 1)
|
||||||
|
return { type: StairType.STAIR, direction: Direction.WEST };
|
||||||
|
if (
|
||||||
|
this.isTile(position) &&
|
||||||
|
this.isTile(topLeftTile) &&
|
||||||
|
this._getTileDifference(topLeftTile, position) === 1 &&
|
||||||
|
this._getTileDifference(midLeftTile, position) === 0 &&
|
||||||
|
this._getTileDifference(topTile, position) === 0
|
||||||
|
)
|
||||||
|
return { type: StairType.OUTER_CORNER_STAIR, direction: Direction.NORTH_WEST };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the height differencte between two tile position.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position1] - The tile position that we want to compare the height.
|
||||||
|
* @param {IPosition2D} [position2] - The tile position that we want to compare the height.
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _getTileDifference(position1: IPosition2D, position2: IPosition2D): number {
|
||||||
|
return Number(this.getTileHeight(position1)) - Number(this.getTileHeight(position2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean that indicate if the tile position given refer to an existing tile.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile position that we want to see if it exist.
|
||||||
|
* @return {boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public isTile(position: IPosition2D): boolean {
|
||||||
|
return this.getTile(position) !== 'x';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean that indicate if the given tile is a door.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile position that we want to see if it's a door.
|
||||||
|
* @return {boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public isDoor(position: IPosition2D): boolean {
|
||||||
|
const topLeftTile: IPosition2D = { x: position.x - 1, y: position.y - 1 };
|
||||||
|
const topTile: IPosition2D = { x: position.x, y: position.y - 1 };
|
||||||
|
|
||||||
|
const midLeftTile: IPosition2D = { x: position.x - 1, y: position.y };
|
||||||
|
const midTile: IPosition2D = { x: position.x, y: position.y };
|
||||||
|
|
||||||
|
const botLeftTile: IPosition2D = { x: position.x - 1, y: position.y + 1 };
|
||||||
|
const botTile: IPosition2D = { x: position.x, y: position.y + 1 };
|
||||||
|
|
||||||
|
return (
|
||||||
|
!this.isTile(topTile) &&
|
||||||
|
!this.isTile(topLeftTile) &&
|
||||||
|
!this.isTile(midLeftTile) &&
|
||||||
|
!this.isTile(botLeftTile) &&
|
||||||
|
!this.isTile(botTile) &&
|
||||||
|
this.isTile(midTile)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the max Z value of this tile map.
|
||||||
|
*
|
||||||
|
* @return {number}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get maxZ(): number {
|
||||||
|
let z = 0;
|
||||||
|
for (let y = 0; y < this._tileMap.length; y++) {
|
||||||
|
for (let x = 0; x < this._tileMap[y].length; x++) {
|
||||||
|
const height = this.getTileHeight({ x, y });
|
||||||
|
if (height > z) z = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate if the given tile position have a left or a right wall.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The given tile position that we wan't to check if it have walls.
|
||||||
|
* @return {{ x: boolean, y: boolean }}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public hasWall(position: IPosition2D): { x: boolean; y: boolean } {
|
||||||
|
// TODO: Integrate it in _getWallType()
|
||||||
|
let wallX = false;
|
||||||
|
let wallY = false;
|
||||||
|
for (let i = position.y - 1; i >= 0; i--) {
|
||||||
|
const wall: WallType | undefined = this._getWallType({ x: position.x, y: i });
|
||||||
|
if (wall) {
|
||||||
|
if (wall === WallType.RIGHT_WALL || wall === (WallType.CORNER_WALL as WallType)) {
|
||||||
|
wallY = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let j = position.x - 1; j >= 0; j--) {
|
||||||
|
const wall2: WallType | undefined = this._getWallType({ x: j, y: i });
|
||||||
|
if (wall2) {
|
||||||
|
if (wall2 === WallType.LEFT_WALL || wall2 === (WallType.CORNER_WALL as WallType)) {
|
||||||
|
wallY = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = position.x - 1; i >= 0; i--) {
|
||||||
|
const wall: WallType | undefined = this._getWallType({ x: i, y: position.y });
|
||||||
|
if (wall) {
|
||||||
|
if (wall === WallType.LEFT_WALL || wall === (WallType.CORNER_WALL as WallType)) {
|
||||||
|
wallX = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let j = position.y - 1; j >= 0; j--) {
|
||||||
|
const wall2: WallType | undefined = this._getWallType({ x: i, y: j });
|
||||||
|
if (wall2) {
|
||||||
|
if (wall2 === WallType.RIGHT_WALL || wall2 === (WallType.CORNER_WALL as WallType)) {
|
||||||
|
wallX = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { x: wallX, y: wallY };
|
||||||
|
}
|
||||||
|
}
|
404
src/objects/rooms/RoomVisualization.ts
Normal file
404
src/objects/rooms/RoomVisualization.ts
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
import { Container, Ticker } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Room } from './Room';
|
||||||
|
import type { IPosition3D, ITileInfo } from '../../types/Room';
|
||||||
|
import { Tile } from './parts/Tile';
|
||||||
|
import { Wall } from './parts/Wall';
|
||||||
|
import { Stair } from './parts/Stair';
|
||||||
|
import { WallType } from '../../enums/WallType';
|
||||||
|
import type { StairType } from '../../enums/StairType';
|
||||||
|
import { Cursor } from './parts/Cursor';
|
||||||
|
import { RoomObjectLayer } from './layers/RoomObjectLayer';
|
||||||
|
import { RoomPartLayer } from './layers/RoomPartLayer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomView class that manage all the rendering part of the room.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class RoomVisualization extends Container {
|
||||||
|
/**
|
||||||
|
* The room instance that will be managed by the camera.
|
||||||
|
*
|
||||||
|
* @member {Room}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _room: Room;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The container that will contains all the objects like avatars or furnitures.
|
||||||
|
*
|
||||||
|
* @member {RoomObjectLayer}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _objectLayer: RoomObjectLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The container that will contains all the parts like tiles, walls and stairs.
|
||||||
|
*
|
||||||
|
* @member {RoomPartLayer}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _partLayer: RoomPartLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List containing all the walls instances.
|
||||||
|
*
|
||||||
|
* @member {Wall}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _walls: Wall[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List containing all the tiles and stairs instances.
|
||||||
|
*
|
||||||
|
* @member {Tile | Stair}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _tiles: Array<Tile | Stair> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infos related to the door tile.
|
||||||
|
*
|
||||||
|
* @member {ITileInfo}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _doorTile!: ITileInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room tile cursor instance.
|
||||||
|
*
|
||||||
|
* @member {Cursor}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _cursor!: Cursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room animation ticker instance that will manage all the objects animations
|
||||||
|
*
|
||||||
|
* @member {Ticker}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _animationTicker = new Ticker();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [room] - The room instance that we want to visualize.
|
||||||
|
*/
|
||||||
|
constructor(room: Room) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._room = room;
|
||||||
|
this._objectLayer = new RoomObjectLayer(this._room);
|
||||||
|
this._partLayer = new RoomPartLayer(this._room);
|
||||||
|
|
||||||
|
/** Start the animation ticker */
|
||||||
|
this._animationTicker.maxFPS = 4;
|
||||||
|
this._animationTicker.start();
|
||||||
|
|
||||||
|
/** Render everything */
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the room visualization with all the tiles and walls.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _draw(): void {
|
||||||
|
this._destroyParts();
|
||||||
|
this._destroyCursor();
|
||||||
|
|
||||||
|
for (let y = 0; y < this._room.tileMap.tileMap.length; y++) {
|
||||||
|
for (let x = 0; x < this._room.tileMap.tileMap[y].length; x++) {
|
||||||
|
const tileInfo = this._room.tileMap.getTileInfo({ x, y });
|
||||||
|
|
||||||
|
// todo: avoid duplicate tile doors
|
||||||
|
if (tileInfo.door && this._doorTile != null) tileInfo.door = false;
|
||||||
|
if (tileInfo.door && this._doorTile == null) this._doorTile = tileInfo;
|
||||||
|
|
||||||
|
this._createPart(tileInfo, { x, y, z: tileInfo.height });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy all the parts (tiles, walls, stairs, ...).
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _destroyParts(): void {
|
||||||
|
[...this._tiles, ...this._walls].forEach((part) => part.destroy());
|
||||||
|
this._tiles = [];
|
||||||
|
this._walls = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rerender all the room visualization.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
public update(): void {
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a room part and add it into the visualization.
|
||||||
|
*
|
||||||
|
* @param {ITileInfo} [tileInfo] - The tile informations where we want to create the part.
|
||||||
|
* @param {IPosition3D} [position] - And the position.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _createPart(tileInfo: ITileInfo, position: IPosition3D): void {
|
||||||
|
if (tileInfo.wallType !== null || tileInfo.door) {
|
||||||
|
if (
|
||||||
|
tileInfo.wallType === WallType.CORNER_WALL &&
|
||||||
|
!this._room.tileMap.hasWall(position).x &&
|
||||||
|
!this._room.tileMap.hasWall(position).y
|
||||||
|
) {
|
||||||
|
this._createWall(position, WallType.CORNER_WALL);
|
||||||
|
this._createWall(position, WallType.LEFT_WALL);
|
||||||
|
this._createWall(position, WallType.RIGHT_WALL);
|
||||||
|
} else if (tileInfo.wallType === WallType.CORNER_WALL && !this._room.tileMap.hasWall(position).x) {
|
||||||
|
this._createWall(position, WallType.LEFT_WALL);
|
||||||
|
} else if (tileInfo.wallType === WallType.CORNER_WALL && !this._room.tileMap.hasWall(position).y) {
|
||||||
|
this._createWall(position, WallType.RIGHT_WALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tileInfo.wallType === WallType.LEFT_WALL && !this._room.tileMap.hasWall(position).y)
|
||||||
|
this._createWall(position, WallType.LEFT_WALL);
|
||||||
|
if (tileInfo.wallType === WallType.RIGHT_WALL && !this._room.tileMap.hasWall(position).y)
|
||||||
|
this._createWall(position, WallType.RIGHT_WALL);
|
||||||
|
|
||||||
|
if (tileInfo.door) this._createWall(position, WallType.DOOR_WALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tileInfo.stairType != null) {
|
||||||
|
position.direction = tileInfo.stairType.direction;
|
||||||
|
this._createStair(position, tileInfo.stairType.type);
|
||||||
|
} else if (tileInfo.door) {
|
||||||
|
this._createDoor(position);
|
||||||
|
} else if (tileInfo.tile) {
|
||||||
|
this._createTile(position, tileInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the current cursor and draw a new one at the new position.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The cursor position.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _createCursor(position: IPosition3D): void {
|
||||||
|
if (this._cursor != null) {
|
||||||
|
this._cursor.visible = true;
|
||||||
|
return this._cursor.moveTo(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._destroyCursor();
|
||||||
|
const cursor = new Cursor(this._room, { position });
|
||||||
|
this.addChild(cursor);
|
||||||
|
this._cursor = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the room cursor
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _destroyCursor(): void {
|
||||||
|
if (this._cursor != null) this._cursor.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tile.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The tile position.
|
||||||
|
* @param {ITileInfo} [tileInfo]
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _createTile(position: IPosition3D, tileInfo: ITileInfo): void {
|
||||||
|
const tile = new Tile(
|
||||||
|
this._room,
|
||||||
|
{ position, material: this._room.floorMaterial, thickness: this._room.floorThickness },
|
||||||
|
tileInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Register interactions */
|
||||||
|
tile.onPointerDown = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerDown != null) this._partLayer.tiles.onPointerDown(event);
|
||||||
|
};
|
||||||
|
tile.onPointerUp = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerUp != null) this._partLayer.tiles.onPointerUp(event);
|
||||||
|
};
|
||||||
|
tile.onPointerMove = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerMove != null) this._partLayer.tiles.onPointerMove(event);
|
||||||
|
};
|
||||||
|
tile.onPointerOut = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerOut != null) this._partLayer.tiles.onPointerOut(event);
|
||||||
|
this._destroyCursor();
|
||||||
|
};
|
||||||
|
tile.onPointerOver = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerOver != null) this._partLayer.tiles.onPointerOver(event);
|
||||||
|
this._createCursor(position);
|
||||||
|
};
|
||||||
|
tile.onDoubleClick = (event) => {
|
||||||
|
if (this._partLayer.tiles.onDoubleClick != null) this._partLayer.tiles.onDoubleClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addChild(tile);
|
||||||
|
this._tiles.push(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a door.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The door position.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _createDoor(position: IPosition3D): void {
|
||||||
|
const tile = new Tile(this._room, { position, material: this._room.floorMaterial });
|
||||||
|
|
||||||
|
/** Register interactions */
|
||||||
|
tile.onPointerDown = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerDown != null) this._partLayer.tiles.onPointerDown(event);
|
||||||
|
};
|
||||||
|
tile.onPointerUp = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerUp != null) this._partLayer.tiles.onPointerUp(event);
|
||||||
|
};
|
||||||
|
tile.onPointerMove = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerMove != null) this._partLayer.tiles.onPointerMove(event);
|
||||||
|
};
|
||||||
|
tile.onPointerOut = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerOut != null) this._partLayer.tiles.onPointerOut(event);
|
||||||
|
this._destroyCursor();
|
||||||
|
};
|
||||||
|
tile.onPointerOver = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerOver != null) this._partLayer.tiles.onPointerOver(event);
|
||||||
|
this._createCursor(position);
|
||||||
|
};
|
||||||
|
tile.onDoubleClick = (event) => {
|
||||||
|
if (this._partLayer.tiles.onDoubleClick != null) this._partLayer.tiles.onDoubleClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addChild(tile);
|
||||||
|
this._tiles.push(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wall.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The wall position.
|
||||||
|
* @param {WallType} [type] - The wall type.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _createWall(position: IPosition3D, type: WallType): void {
|
||||||
|
const wall = new Wall(this._room, {
|
||||||
|
position,
|
||||||
|
material: this._room.wallMaterial,
|
||||||
|
thickness: this._room.wallThickness,
|
||||||
|
height: this._room.wallHeight,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo!(): register event interactions for walls */
|
||||||
|
|
||||||
|
this.addChild(wall);
|
||||||
|
this._walls.push(wall);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create stairs.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The stairs position.
|
||||||
|
* @param {StairType} [type] - The stairs type.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _createStair(position: IPosition3D, type: StairType): void {
|
||||||
|
const stair = new Stair(this._room, {
|
||||||
|
position,
|
||||||
|
material: this._room.floorMaterial,
|
||||||
|
thickness: this._room.floorThickness,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Register interactions */
|
||||||
|
stair.onPointerDown = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerDown != null) this._partLayer.tiles.onPointerDown(event);
|
||||||
|
};
|
||||||
|
stair.onPointerUp = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerUp != null) this._partLayer.tiles.onPointerUp(event);
|
||||||
|
};
|
||||||
|
stair.onPointerMove = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerMove != null) this._partLayer.tiles.onPointerMove(event);
|
||||||
|
};
|
||||||
|
stair.onPointerOut = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerOut != null) this._partLayer.tiles.onPointerOut(event);
|
||||||
|
this._destroyCursor();
|
||||||
|
};
|
||||||
|
stair.onPointerOver = (event) => {
|
||||||
|
if (this._partLayer.tiles.onPointerOver != null) this._partLayer.tiles.onPointerOver(event);
|
||||||
|
this._createCursor(position);
|
||||||
|
};
|
||||||
|
stair.onDoubleClick = (event) => {
|
||||||
|
if (this._partLayer.tiles.onDoubleClick != null) this._partLayer.tiles.onDoubleClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addChild(stair);
|
||||||
|
this._tiles.push(stair);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room visualization room instance.
|
||||||
|
*
|
||||||
|
* @member {Room}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get room(): Room {
|
||||||
|
return this._room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the object layer container.
|
||||||
|
*
|
||||||
|
* @member {RoomObjectLayer}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get objectLayer(): RoomObjectLayer {
|
||||||
|
return this._objectLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the part layer container.
|
||||||
|
*
|
||||||
|
* @member {RoomObjectLayer}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get partLayer(): RoomPartLayer {
|
||||||
|
return this._partLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room animation ticker instance.
|
||||||
|
*
|
||||||
|
* @member {Ticker}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get animationTicker(): Ticker {
|
||||||
|
return this._animationTicker;
|
||||||
|
}
|
||||||
|
}
|
72
src/objects/rooms/layers/RoomObjectLayer.ts
Normal file
72
src/objects/rooms/layers/RoomObjectLayer.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import type { RoomObject } from '../objects/RoomObject';
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomObjectLayer class that manage all the room objects.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class RoomObjectLayer {
|
||||||
|
/**
|
||||||
|
* The room instance that will be managed by the camera.
|
||||||
|
*
|
||||||
|
* @member {Room}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _room: Room;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object list.
|
||||||
|
*
|
||||||
|
* @member {RoomObject[]}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _objects: RoomObject[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [room] - The room instance that we want to visualize.
|
||||||
|
*/
|
||||||
|
constructor(room: Room) {
|
||||||
|
this._room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the given room object into the object layer of the room.
|
||||||
|
*
|
||||||
|
* @param {RoomObject[]} [objects] - The room objects that we want to add.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public add(...objects: RoomObject[]): void {
|
||||||
|
return objects.forEach((object) => {
|
||||||
|
object.room = this._room;
|
||||||
|
this._objects.push(object);
|
||||||
|
// @ts-expect-error
|
||||||
|
this._room.visualization.addChild(object);
|
||||||
|
if (object.onRoomAdded !== undefined) object.onRoomAdded(this._room);
|
||||||
|
if (object.visualization.loaded) object.visualization.render();
|
||||||
|
else object.visualization.renderPlaceholder();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the given room object into the object layer of the room.
|
||||||
|
*
|
||||||
|
* @param {RoomObject[]} [objects] - The room objects that we want to remove.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public remove(...objects: RoomObject[]): void {
|
||||||
|
return objects.forEach((object) => {
|
||||||
|
if (object.onRoomRemoved !== undefined) object.onRoomRemoved(this._room);
|
||||||
|
|
||||||
|
object.room = undefined;
|
||||||
|
// @ts-expect-error
|
||||||
|
this._room.visualization.removeChild(object);
|
||||||
|
this._objects = this._objects.filter((fObject) => fObject !== object);
|
||||||
|
|
||||||
|
object.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
104
src/objects/rooms/layers/RoomPartLayer.ts
Normal file
104
src/objects/rooms/layers/RoomPartLayer.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { Point } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
import type { RoomPart } from '../parts/RoomPart';
|
||||||
|
import { EventManager } from '../../interactions/EventManager';
|
||||||
|
import type { Tile } from '../parts/Tile';
|
||||||
|
import type { Stair } from '../parts/Stair';
|
||||||
|
import type { Dimension } from '../../../types/Dimension';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomPartLayer class that manage all the room parts.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class RoomPartLayer {
|
||||||
|
/**
|
||||||
|
* The room instance that will be managed by the camera.
|
||||||
|
*
|
||||||
|
* @member {Room}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _room: Room;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The part list.
|
||||||
|
*
|
||||||
|
* @member {RoomPart[]}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _parts: RoomPart[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room tiles interaction manager.
|
||||||
|
*
|
||||||
|
* @member {EventManager}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tileInteractionManager = new EventManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room walls interaction manager.
|
||||||
|
*
|
||||||
|
* @member {EventManager}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _wallInteractionManager = new EventManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [room] - The room instance that we want to visualize.
|
||||||
|
*/
|
||||||
|
constructor(room: Room) {
|
||||||
|
this._room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the given room part into the part layer of the room.
|
||||||
|
*
|
||||||
|
* @param {RoomPart} [part] - The room part that we want to add.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public add(part: RoomPart): void {
|
||||||
|
this._parts.push(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the part at the specified screen position.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The screen position.
|
||||||
|
* @return {Tile | Stair}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public getFromGlobal(position: Dimension.IPosition2D): Tile | Stair {
|
||||||
|
const container = this._room.visualization.children.find((container) => {
|
||||||
|
const point = new Point(position.x, position.y);
|
||||||
|
if (Boolean(container.hitArea?.contains(container.toLocal(point).x, container.toLocal(point).y)))
|
||||||
|
return container;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the tile event manager.
|
||||||
|
*
|
||||||
|
* @return {EventManager}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get tiles(): EventManager {
|
||||||
|
return this._tileInteractionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the wall event manager.
|
||||||
|
*
|
||||||
|
* @return {EventManager}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get walls(): EventManager {
|
||||||
|
return this._wallInteractionManager;
|
||||||
|
}
|
||||||
|
}
|
73
src/objects/rooms/materials/FloorMaterial.ts
Normal file
73
src/objects/rooms/materials/FloorMaterial.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type { Spritesheet } from 'pixi.js';
|
||||||
|
import { Assets, Sprite, Texture } from 'pixi.js';
|
||||||
|
|
||||||
|
import { Material } from './Material';
|
||||||
|
import type { Scuti } from '../../../Scuti';
|
||||||
|
import type { RoomMaterial } from '../../../types/RoomMaterial';
|
||||||
|
import { Logger } from '../../../utilities/Logger';
|
||||||
|
|
||||||
|
export class FloorMaterial extends Material {
|
||||||
|
/**
|
||||||
|
* The game engine instance that the room will be using to render texture.
|
||||||
|
*
|
||||||
|
* @member {Scuti}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _engine: Scuti;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The material id from materials.json.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _id: number | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Scuti} [engine] - The scuti engine instance to use.
|
||||||
|
* @param {number} [id] - The id of the material (it can be found into materials.json).
|
||||||
|
**/
|
||||||
|
constructor(engine: Scuti, id?: number) {
|
||||||
|
super(0xffffff, Texture.WHITE);
|
||||||
|
|
||||||
|
this._engine = engine;
|
||||||
|
this._id = id;
|
||||||
|
|
||||||
|
this._load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the material.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _load(): void {
|
||||||
|
const materials = Assets.get<RoomMaterial>('room/materials');
|
||||||
|
const defaultMaterial = materials.floorData.floors[0];
|
||||||
|
|
||||||
|
let material = materials.floorData.floors.find((material) => {
|
||||||
|
if (this._id != null) return material.id === this._id.toString();
|
||||||
|
else return defaultMaterial;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (material == null) {
|
||||||
|
const console = new Logger('FloorMaterial');
|
||||||
|
this._id != null && console.warn(`Unknown floor id: "${this._id}"`);
|
||||||
|
/** apply default (white) one rather than throwing an error */
|
||||||
|
material = defaultMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { color, materialId } = material.visualizations[0].layers[0];
|
||||||
|
const materialTexture = materials.floorData.textures.find((texture) => {
|
||||||
|
return texture.id === materialId.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const name = materialTexture?.bitmaps[0].assetName as string;
|
||||||
|
const texture = Assets.get<Spritesheet>('room/room').textures[`room_${name}.png`];
|
||||||
|
const sprite = new Sprite(texture);
|
||||||
|
|
||||||
|
this.color = color;
|
||||||
|
this.texture = new Texture(this._engine.application.renderer.generateTexture(sprite).baseTexture);
|
||||||
|
}
|
||||||
|
}
|
76
src/objects/rooms/materials/Material.ts
Normal file
76
src/objects/rooms/materials/Material.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import type { Texture } from 'pixi.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material class that regroup the color and texture to be applied on the wall or the floor.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Material {
|
||||||
|
/**
|
||||||
|
* The material color.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _color: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The material texture.
|
||||||
|
*
|
||||||
|
* @member {Texture}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _texture: Texture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} [color] - The material color.
|
||||||
|
* @param {Texture} [texture] - The material texture.
|
||||||
|
**/
|
||||||
|
constructor(color: number, texture: Texture) {
|
||||||
|
this._color = color;
|
||||||
|
this._texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the material color.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get color(): number {
|
||||||
|
return this._color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the material color.
|
||||||
|
*
|
||||||
|
* @param {number} [color] - The new material color.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set color(color: number) {
|
||||||
|
this._color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the material texture.
|
||||||
|
*
|
||||||
|
* @member {Texture}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get texture(): Texture {
|
||||||
|
return this._texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the material texture.
|
||||||
|
*
|
||||||
|
* @param {Texture} [texture] - The new material texture.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set texture(texture: Texture) {
|
||||||
|
this._texture = texture;
|
||||||
|
}
|
||||||
|
}
|
73
src/objects/rooms/materials/WallMaterial.ts
Normal file
73
src/objects/rooms/materials/WallMaterial.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type { Spritesheet } from 'pixi.js';
|
||||||
|
import { Assets, Sprite, Texture } from 'pixi.js';
|
||||||
|
|
||||||
|
import { Material } from './Material';
|
||||||
|
import type { Scuti } from '../../../Scuti';
|
||||||
|
import type { RoomMaterial } from '../../../types/RoomMaterial';
|
||||||
|
import { Logger } from '../../../utilities/Logger';
|
||||||
|
|
||||||
|
export class WallMaterial extends Material {
|
||||||
|
/**
|
||||||
|
* The game engine instance that the room will be using to render texture.
|
||||||
|
*
|
||||||
|
* @member {Scuti}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _engine: Scuti;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The material id from materials.json.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _id: number | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Scuti} [engine] - The scuti engine instance to use.
|
||||||
|
* @param {number} [id] - The id of the material (it can be found into materials.json).
|
||||||
|
**/
|
||||||
|
constructor(engine: Scuti, id?: number) {
|
||||||
|
super(0xffffff, Texture.WHITE);
|
||||||
|
|
||||||
|
this._engine = engine;
|
||||||
|
this._id = id;
|
||||||
|
|
||||||
|
this._load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the material.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _load(): void {
|
||||||
|
const materials = Assets.get<RoomMaterial>('room/materials');
|
||||||
|
const defaultMaterial = materials.wallData.walls[0];
|
||||||
|
|
||||||
|
let material = materials.wallData.walls.find((material) => {
|
||||||
|
if (this._id != null) return material.id === this._id.toString();
|
||||||
|
else return defaultMaterial;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (material == null) {
|
||||||
|
const console = new Logger('WallMaterial');
|
||||||
|
this._id != null && console.warn(`Unknown wall id: "${this._id}"`);
|
||||||
|
/** apply default (white) one rather than throwing an error */
|
||||||
|
material = defaultMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { color, materialId } = material.visualizations[0].layers[0];
|
||||||
|
const materialTexture = materials.wallData.textures.find((texture) => {
|
||||||
|
return texture.id === materialId.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const name = materialTexture?.bitmaps[0].assetName as string;
|
||||||
|
const texture = Assets.get<Spritesheet>('room/room').textures[`room_${name}.png`];
|
||||||
|
const sprite = new Sprite(texture);
|
||||||
|
|
||||||
|
this.color = color;
|
||||||
|
this.texture = new Texture(this._engine.application.renderer.generateTexture(sprite).baseTexture);
|
||||||
|
}
|
||||||
|
}
|
547
src/objects/rooms/objects/RoomObject.ts
Normal file
547
src/objects/rooms/objects/RoomObject.ts
Normal file
@ -0,0 +1,547 @@
|
|||||||
|
import type { Filter } from 'pixi.js';
|
||||||
|
import { Container } from 'pixi.js';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
|
||||||
|
import { EventManager } from '../../interactions/EventManager';
|
||||||
|
import { Logger } from '../../../utilities/Logger';
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
import type { Direction } from '../../../enums/Direction';
|
||||||
|
import type { RoomObjectVisualization } from './RoomObjectVisualization';
|
||||||
|
import type { IFloorPosition, IWallPosition } from '../../../types/Furniture';
|
||||||
|
import type { Dimension, IAvatarPosition, IRoomObjectConfig, IInteractionEvent } from '../../../types';
|
||||||
|
import type { FurnitureData } from '../../furnitures/visualizations/FurnitureData';
|
||||||
|
import { FloorFurniture } from '../../furnitures/FloorFurniture';
|
||||||
|
import { WallFurniture } from '../../furnitures/WallFurniture';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomObject class that is extended by the avatars or furnitures.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export abstract class RoomObject extends Container {
|
||||||
|
/**
|
||||||
|
* The object's position in the room.
|
||||||
|
*
|
||||||
|
* @member {FloorPosition | IWallPosition | IAvatarPosition}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _position: IFloorPosition | IWallPosition | IAvatarPosition;
|
||||||
|
|
||||||
|
private _isAnimating = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object's direction in the room.
|
||||||
|
*
|
||||||
|
* @member {Direction}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _direction: Direction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object's state that represent it's current playing animation.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
public _state!: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object's visualization.
|
||||||
|
*
|
||||||
|
* @member {FurnitureData}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
public _visualization!: RoomObjectVisualization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object's data.
|
||||||
|
*
|
||||||
|
* @member {FurnitureData}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
public _data!: FurnitureData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room object's logger instance.
|
||||||
|
*
|
||||||
|
* @member {Logger}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _logger = new Logger('RoomObject');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object interaction manager to handle all the clicks and taps.
|
||||||
|
*
|
||||||
|
* @member {EventManager}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _eventManager = new EventManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room instance that will be managed by the camera.
|
||||||
|
*
|
||||||
|
* @member {Room}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _room!: Room;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room object filters.
|
||||||
|
*
|
||||||
|
* @member {Filter[]}n
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _filters: Filter[] = [];
|
||||||
|
|
||||||
|
protected constructor(config: IRoomObjectConfig) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._position = config.position;
|
||||||
|
this._direction = config.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the object at te given position and in time.
|
||||||
|
*
|
||||||
|
* @param {IFloorPosition | IWallPosition | IAvatarPosition} [position] - The position where we want to move the furniture.
|
||||||
|
* @param {number} [duration] - The time to move the furniture to the given position.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
abstract move(position: IFloorPosition | IWallPosition | IAvatarPosition, duration: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate the furniture at the given direction and in time.
|
||||||
|
*
|
||||||
|
* @param {Direction} [direction] - The new direction of the furniture.
|
||||||
|
* @param {number} [duration] - The time to rotate the furniture at the given direction.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public rotate(direction?: Direction, duration: number = 0.15): void {
|
||||||
|
if (this instanceof FloorFurniture || this instanceof WallFurniture) {
|
||||||
|
if (this._visualization === undefined || this._isAnimating) return;
|
||||||
|
|
||||||
|
const z = (this.position as Dimension.IPosition3D).z;
|
||||||
|
|
||||||
|
gsap.to(this.position, {
|
||||||
|
z: z + 0.5,
|
||||||
|
duration,
|
||||||
|
onStart: () => {
|
||||||
|
this._isAnimating = true;
|
||||||
|
},
|
||||||
|
onUpdate: () => this._visualization.updatePosition(),
|
||||||
|
onComplete: () => {
|
||||||
|
if (direction == null) {
|
||||||
|
const direction = this.visualization.directions.indexOf(this.direction);
|
||||||
|
const nextDirection = (direction + 1) % this.visualization.directions.length;
|
||||||
|
|
||||||
|
this._direction = this.visualization.directions[nextDirection];
|
||||||
|
} else this._direction = direction;
|
||||||
|
|
||||||
|
this._visualization.render();
|
||||||
|
gsap.to(this.position, {
|
||||||
|
z,
|
||||||
|
duration,
|
||||||
|
onComplete: () => {
|
||||||
|
this._visualization.render();
|
||||||
|
this._isAnimating = false;
|
||||||
|
},
|
||||||
|
onUpdate: () => this._visualization.updatePosition()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// todo!(): rotate entities (avatar, pet or bot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room object room instance.
|
||||||
|
*
|
||||||
|
* @member {Room | undefined}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get room(): Room {
|
||||||
|
return this._room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current room instance.
|
||||||
|
*
|
||||||
|
* @param {Room} [room] - The new room instance.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set room(room: Room) {
|
||||||
|
this._room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the object event manager.
|
||||||
|
*
|
||||||
|
* @member {EventManager}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get eventManager(): EventManager {
|
||||||
|
return this._eventManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer down event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onPointerDown(): (event: IInteractionEvent) => void {
|
||||||
|
return this._eventManager.onPointerDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onPointerDown(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._eventManager.onPointerDown = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer up event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onPointerUp(): (event: IInteractionEvent) => void {
|
||||||
|
return this._eventManager.onPointerUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onPointerUp(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._eventManager.onPointerUp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer move event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onPointerMove(): (event: IInteractionEvent) => void {
|
||||||
|
return this._eventManager.onPointerMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onPointerMove(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._eventManager.onPointerMove = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer out event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onPointerOut(): (event: IInteractionEvent) => void {
|
||||||
|
return this._eventManager.onPointerOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onPointerOut(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._eventManager.onPointerOut = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer over event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onPointerOver(): (event: IInteractionEvent) => void {
|
||||||
|
return this._eventManager.onPointerOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onPointerOver(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._eventManager.onPointerOver = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer double click event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onDoubleClick(): (event: IInteractionEvent) => void {
|
||||||
|
return this._eventManager.onDoubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onDoubleClick(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._eventManager.onDoubleClick = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the assets starting load event.
|
||||||
|
*
|
||||||
|
* @member {() => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onLoad(): () => void {
|
||||||
|
return this._eventManager.onLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {() => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onLoad(value: () => void) {
|
||||||
|
this._eventManager.onLoad = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the assets ending load event.
|
||||||
|
*
|
||||||
|
* @member {() => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onLoadComplete(): () => void {
|
||||||
|
return this._eventManager.onLoadComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {() => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onLoadComplete(value: () => void) {
|
||||||
|
this._eventManager.onLoadComplete = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room add event.
|
||||||
|
*
|
||||||
|
* @member {(room: Room) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onRoomAdded(): (event: Room) => void {
|
||||||
|
return this._eventManager.onRoomAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(room: Room) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onRoomAdded(value: (room: Room) => void) {
|
||||||
|
this._eventManager.onRoomAdded = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room remove event.
|
||||||
|
*
|
||||||
|
* @member {(room: Room) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
get onRoomRemoved(): (event: Room) => void {
|
||||||
|
return this._eventManager.onRoomRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(room: Room) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
set onRoomRemoved(value: (room: Room) => void) {
|
||||||
|
this._eventManager.onRoomRemoved = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture state.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get state(): number {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the furniture state (so the animation).
|
||||||
|
*
|
||||||
|
* @param {number} [state] - The new furniture state.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set state(state: number) {
|
||||||
|
this._state = state;
|
||||||
|
this._visualization.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference the filters list.
|
||||||
|
*
|
||||||
|
* @member {Filter[]}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
// @ts-expect-error
|
||||||
|
public get filters(): Filter[] {
|
||||||
|
return this._filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the filters list.
|
||||||
|
*
|
||||||
|
* @member {Filter[]}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set filters(filters: Filter[]) {
|
||||||
|
this._filters = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a filter to the room object.
|
||||||
|
*
|
||||||
|
* @param {Filter} [filter] - The filter.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public addFilter(filter: Filter): void {
|
||||||
|
if (this._filters.includes(filter)) return;
|
||||||
|
this._filters.push(filter);
|
||||||
|
this._visualization.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove filter from the room object.
|
||||||
|
*
|
||||||
|
* @param {Filter} [filter] - The filter.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public removeFilter(filter: Filter): void {
|
||||||
|
this._filters = this._filters.filter((fFilter) => fFilter !== filter);
|
||||||
|
this._visualization.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the object's direction.
|
||||||
|
*
|
||||||
|
* @member {Direction}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get direction(): Direction {
|
||||||
|
return this._direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the object's direction
|
||||||
|
*
|
||||||
|
* @member {Direction}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set direction(direction: Direction) {
|
||||||
|
this._direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture position in the room.
|
||||||
|
*
|
||||||
|
* @member {IFloorPosition}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
// @ts-expect-error
|
||||||
|
public get position(): IFloorPosition | IWallPosition | IAvatarPosition {
|
||||||
|
return this._position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the room object from the room.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
destroy(): void {
|
||||||
|
if (this._visualization === undefined) return;
|
||||||
|
this._visualization.destroy();
|
||||||
|
if (this._room == null) return;
|
||||||
|
this._room.objects.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the room object logger instance.
|
||||||
|
*
|
||||||
|
* @member {Logger}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get logger(): Logger {
|
||||||
|
return this._logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the visualization instance.
|
||||||
|
*
|
||||||
|
* @member {RoomObjectVisualization}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get visualization(): RoomObjectVisualization {
|
||||||
|
return this._visualization;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the furniture data.
|
||||||
|
*
|
||||||
|
* @member {FurnitureData}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get data(): FurnitureData {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
}
|
76
src/objects/rooms/objects/RoomObjectVisualization.ts
Normal file
76
src/objects/rooms/objects/RoomObjectVisualization.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import type { Sprite } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Direction } from '../../../enums/Direction';
|
||||||
|
import { Logger } from '../../../utilities/Logger';
|
||||||
|
|
||||||
|
export abstract class RoomObjectVisualization {
|
||||||
|
/**
|
||||||
|
* The room object visualization's logger instance.
|
||||||
|
*
|
||||||
|
* @member {Logger}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _logger = new Logger('RoomObjectVisualization');
|
||||||
|
|
||||||
|
private _placeholder!: Sprite;
|
||||||
|
|
||||||
|
private _directions!: Direction[];
|
||||||
|
|
||||||
|
private _loaded = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the visual layers of the room object.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public abstract render(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the placeholder right before the layers of the room object when loading.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public abstract renderPlaceholder(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the position of each visual layer of the room object.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public abstract updatePosition(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all visual layers of the room object.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public abstract destroy(): void;
|
||||||
|
|
||||||
|
get loaded(): boolean {
|
||||||
|
return this._loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
set loaded(load: boolean) {
|
||||||
|
this._loaded = load;
|
||||||
|
}
|
||||||
|
|
||||||
|
set placeholder(placeholder: Sprite) {
|
||||||
|
this._placeholder = placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
get placeholder(): Sprite {
|
||||||
|
return this._placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
set directions(directions: Direction[]) {
|
||||||
|
this._directions = directions;
|
||||||
|
}
|
||||||
|
|
||||||
|
get directions(): Direction[] {
|
||||||
|
return this._directions;
|
||||||
|
}
|
||||||
|
|
||||||
|
get logger(): Logger {
|
||||||
|
return this._logger;
|
||||||
|
}
|
||||||
|
}
|
69
src/objects/rooms/parts/Cursor.ts
Normal file
69
src/objects/rooms/parts/Cursor.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import type { Spritesheet } from 'pixi.js';
|
||||||
|
import { Container, Assets, Sprite } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
import type { ICursorConfiguration, IPosition3D } from '../../../types/Room';
|
||||||
|
import { ZOrder } from '../../../utilities/ZOrder';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cursor class that show up when we move the cursor on a room tile.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Cursor extends Container {
|
||||||
|
/**
|
||||||
|
* The cursor position.
|
||||||
|
*
|
||||||
|
* @member {IPosition3D}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _position: IPosition3D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [_room] - The room instance where the cursor will be drawn.
|
||||||
|
* @param {ICursorConfiguration} [configuration] - The tile configuration.
|
||||||
|
**/
|
||||||
|
constructor(_room: Room, configuration: ICursorConfiguration) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._position = configuration.position;
|
||||||
|
|
||||||
|
/** Draw the cursor */
|
||||||
|
this._draw();
|
||||||
|
// todo!(): create the blue circle 'cursor_64_b' cursor when needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the cursor.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _draw(): void {
|
||||||
|
/** Creating the sprite */
|
||||||
|
const texture = Assets.get<Spritesheet>('room/cursors').textures['tile_cursor_64_a_0_0.png'];
|
||||||
|
const sprite = new Sprite(texture);
|
||||||
|
|
||||||
|
sprite.y = -20;
|
||||||
|
this.addChild(sprite);
|
||||||
|
|
||||||
|
/** Positionate the cursor and its zIndex */
|
||||||
|
this.moveTo(this._position);
|
||||||
|
this.zIndex = ZOrder.tileCursor(this._position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply position of the cursor on the x, y axis relative to the local coordinates of the parent.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The cursor position.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public moveTo(position: IPosition3D): void {
|
||||||
|
// this.zOrder = ZOrder.tileCursor(position);
|
||||||
|
this._position = position;
|
||||||
|
this.x = 32 * this._position.x - 32 * this._position.y;
|
||||||
|
this.y = 16 * this._position.x + 16 * this._position.y - 32 * this._position.z;
|
||||||
|
}
|
||||||
|
}
|
228
src/objects/rooms/parts/RoomPart.ts
Normal file
228
src/objects/rooms/parts/RoomPart.ts
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import { Container, Point } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Dimension } from '../../../types/Dimension';
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
import type { Stair, Tile } from '.';
|
||||||
|
import { EventManager } from '../../interactions/EventManager';
|
||||||
|
import type { IInteractionEvent } from '../../../types/Interaction';
|
||||||
|
|
||||||
|
export abstract class RoomPart extends Container {
|
||||||
|
/**
|
||||||
|
* The part's position in the room.
|
||||||
|
*
|
||||||
|
* @member {IWallPosition | IPosition3D}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
abstract _position: Dimension.IPosition3D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The part interaction manager to handle all the clicks and taps.
|
||||||
|
*
|
||||||
|
* @member {EventManager}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _interactionManager = new EventManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room instance where the ârt will be drawn.
|
||||||
|
*
|
||||||
|
* @member {Room}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _room: Room;
|
||||||
|
|
||||||
|
constructor(room: Room) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the part at the specified screen position.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The screen position.
|
||||||
|
* @return {Tile | Stair}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public getFromGlobal(position: Dimension.IPosition2D): Tile | Stair {
|
||||||
|
const container = this._room.visualization.children.find((container) => {
|
||||||
|
const point = new Point(position.x, position.y);
|
||||||
|
if (Boolean(container.hitArea?.contains(container.toLocal(point).x, container.toLocal(point).y)))
|
||||||
|
return container;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerInteractions(position: Dimension.IPosition3D): void {
|
||||||
|
this.on('pointerdown', (event) => {
|
||||||
|
return this.interactionManager.handlePointerDown({
|
||||||
|
event,
|
||||||
|
position: { x: position.x, y: position.y, z: position.z }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.on('pointerup', (event) => {
|
||||||
|
return this.interactionManager.handlePointerUp({
|
||||||
|
event,
|
||||||
|
position: { x: position.x, y: position.y, z: position.z }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.on('pointermove', (event) => {
|
||||||
|
return this.interactionManager.handlePointerMove({
|
||||||
|
event,
|
||||||
|
position: { x: position.x, y: position.y, z: position.z }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.on('pointerout', (event) => {
|
||||||
|
return this.interactionManager.handlePointerOut({
|
||||||
|
event,
|
||||||
|
position: { x: position.x, y: position.y, z: position.z }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.on('pointerover', (event) => {
|
||||||
|
return this.interactionManager.handlePointerOver({
|
||||||
|
event,
|
||||||
|
position: { x: position.x, y: position.y, z: position.z }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public set room(room: Room) {
|
||||||
|
this._room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get room(): Room {
|
||||||
|
return this._room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get interactionManager(): EventManager {
|
||||||
|
return this._interactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer down event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerDown(): (event: IInteractionEvent) => void {
|
||||||
|
return this._interactionManager.onPointerDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerDown(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._interactionManager.onPointerDown = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer up event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerUp(): (event: IInteractionEvent) => void {
|
||||||
|
return this._interactionManager.onPointerUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerUp(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._interactionManager.onPointerUp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer move event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerMove(): (event: IInteractionEvent) => void {
|
||||||
|
return this._interactionManager.onPointerMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerMove(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._interactionManager.onPointerMove = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer out event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerOut(): (event: IInteractionEvent) => void {
|
||||||
|
return this._interactionManager.onPointerOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerOut(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._interactionManager.onPointerOut = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer over event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onPointerOver(): (event: IInteractionEvent) => void {
|
||||||
|
return this._interactionManager.onPointerOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onPointerOver(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._interactionManager.onPointerOver = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the pointer double click event.
|
||||||
|
*
|
||||||
|
* @member {(event: IInteractionEvent) => void}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get onDoubleClick(): (event: IInteractionEvent) => void {
|
||||||
|
return this._interactionManager.onDoubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the event function that will be executed.
|
||||||
|
*
|
||||||
|
* @param {(event: IInteractionEvent) => void} [value] - The event function that will be executed.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set onDoubleClick(value: (event: IInteractionEvent) => void) {
|
||||||
|
this._interactionManager.onDoubleClick = value;
|
||||||
|
}
|
||||||
|
}
|
579
src/objects/rooms/parts/Stair.ts
Normal file
579
src/objects/rooms/parts/Stair.ts
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
import { Container, Graphics, Matrix, Point, Polygon } from 'pixi.js';
|
||||||
|
import { Color } from '@pixi/color';
|
||||||
|
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
import type { IPosition3D, IPosition2D, IStairConfiguration } from '../../../types/Room';
|
||||||
|
import type { Material } from '../materials/Material';
|
||||||
|
import { StairType } from '../../../enums/StairType';
|
||||||
|
import { Direction } from '../../../enums/Direction';
|
||||||
|
import { FloorMaterial } from '../materials/FloorMaterial';
|
||||||
|
import { ZOrder } from '../../../utilities/ZOrder';
|
||||||
|
import { RoomPart } from './RoomPart';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stair class that show up when two tiles side by side have a height difference of one.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Stair extends RoomPart {
|
||||||
|
/**
|
||||||
|
* The thickness of the stair part.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _thickness: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stair material that will be applied to this part, it contains the color and the texture of the stair.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _material: Material;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stair position.
|
||||||
|
*
|
||||||
|
* @member {IPosition3D}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
readonly _position: IPosition3D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stair type.
|
||||||
|
*
|
||||||
|
* @member {StairType}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _type: StairType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [room] - The room instance where the stair will be drawn.
|
||||||
|
* @param {IStairConfiguration} [configuration] - The stair configuration.
|
||||||
|
* @param {Material} [configuration.material] - The stair material that will be applied.
|
||||||
|
* @param {number} [configuration.thickness] - The stair thickness.
|
||||||
|
* @param {IPosition3D} [configuration.position] - The stair position.
|
||||||
|
* @param {StairType} [configuration.type] - The stair type.
|
||||||
|
**/
|
||||||
|
constructor(room: Room, configuration: IStairConfiguration) {
|
||||||
|
super(room);
|
||||||
|
|
||||||
|
/** Store the configuration */
|
||||||
|
this.room = room;
|
||||||
|
this._position = configuration.position;
|
||||||
|
this._thickness = configuration.thickness ?? 8;
|
||||||
|
this._material = configuration.material ?? new FloorMaterial(this.room.engine, 111);
|
||||||
|
|
||||||
|
this._type = configuration.type;
|
||||||
|
|
||||||
|
/** Register interactions */
|
||||||
|
this.registerInteractions(this._position);
|
||||||
|
|
||||||
|
/** Draw the stair */
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select which stair should be drawn by it's type.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _draw(): void {
|
||||||
|
if (this._type === StairType.STAIR) {
|
||||||
|
/** Straight stair */
|
||||||
|
|
||||||
|
switch (this._position.direction) {
|
||||||
|
/** Draw a north stair */
|
||||||
|
case Direction.NORTH:
|
||||||
|
return this._drawStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 40, y: 12 },
|
||||||
|
{ x: 32, y: 16 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 8, y: -12 },
|
||||||
|
{ x: 0, y: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Draw an east stair */
|
||||||
|
case Direction.EAST:
|
||||||
|
return this._drawStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 32, y: -16 },
|
||||||
|
{ x: 40, y: -12 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 0, y: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Draw a south stair */
|
||||||
|
case Direction.SOUTH:
|
||||||
|
return this._drawStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 40, y: 12 },
|
||||||
|
{ x: 32, y: 16 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: -8, y: -4 },
|
||||||
|
{ x: 24, y: -12 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Draw a west stair */
|
||||||
|
case Direction.WEST:
|
||||||
|
return this._drawStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 32, y: -16 },
|
||||||
|
{ x: 40, y: -12 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: -8, y: -12 },
|
||||||
|
{ x: 24, y: 12 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (this._type === StairType.OUTER_CORNER_STAIR) {
|
||||||
|
/** Corner stair */
|
||||||
|
switch (this._position.direction) {
|
||||||
|
/** Draw a north east stair */
|
||||||
|
case Direction.NORTH_EAST:
|
||||||
|
return this._drawCornerStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 16, y: 0 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: 4 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 16, y: -8 },
|
||||||
|
{ x: 24, y: -12 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Draw a south east stair */
|
||||||
|
case Direction.SOUTH_EAST:
|
||||||
|
return this._drawCornerStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 16, y: 0 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: 4 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: -8, y: 4 },
|
||||||
|
{ x: 24, y: -12 },
|
||||||
|
{ x: 0, y: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Draw a south west stair */
|
||||||
|
case Direction.SOUTH_WEST:
|
||||||
|
return this._drawCornerStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 16, y: 0 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: 4 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: -8, y: -4 },
|
||||||
|
{ x: 16, y: 16 },
|
||||||
|
{ x: 24, y: -12 },
|
||||||
|
{ x: -16, y: -8 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Draw a north west stair */
|
||||||
|
case Direction.NORTH_WEST:
|
||||||
|
return this._drawCornerStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 16, y: 0 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: 4 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 8, y: -12 },
|
||||||
|
{ x: 48, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: -8, y: -12 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (this._type === StairType.INNER_CORNER_STAIR) {
|
||||||
|
/** Inner corner stair */
|
||||||
|
switch (this._position.direction) {
|
||||||
|
/** Draw a north east inner stair */
|
||||||
|
case Direction.NORTH_EAST:
|
||||||
|
return this._drawCornerStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 16, y: 0 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: 4 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: -8, y: 12 },
|
||||||
|
{ x: 16, y: 16 },
|
||||||
|
{ x: 24, y: -36 },
|
||||||
|
{ x: -16, y: 8 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
case Direction.SOUTH_EAST:
|
||||||
|
break;
|
||||||
|
|
||||||
|
/** Draw a south west inner stair */
|
||||||
|
case Direction.SOUTH_WEST:
|
||||||
|
return this._drawCornerStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 16, y: 0 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: 4 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 16, y: 8 },
|
||||||
|
{ x: 24, y: -12 },
|
||||||
|
{ x: 0, y: -24 },
|
||||||
|
{ x: 8, y: 12 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Draw a north west inner stair */
|
||||||
|
case Direction.NORTH_WEST:
|
||||||
|
return this._drawCornerStair(
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: -4 },
|
||||||
|
{ x: 16, y: 0 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 8, y: 4 },
|
||||||
|
{ x: 8, y: 4 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ x: 0, y: 16 },
|
||||||
|
{ x: -8, y: 4 },
|
||||||
|
{ x: 24, y: -36 },
|
||||||
|
{ x: 0, y: 16 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the stair using the given points and offsets.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D[]} [points] - The point list that will be used to draw the stair.
|
||||||
|
* @param {IPosition2D[]} [offsets] - The offset list that will be used to draw the stair.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _drawStair(points: IPosition2D[], offsets: IPosition2D[]): void {
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const step = new Container();
|
||||||
|
/** Top face */
|
||||||
|
|
||||||
|
const top = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(1).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 1, -0.5, this._position.y % 2 === 0 ? 32 : 64, this._position.y % 2 === 0 ? 16 : 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x, points[0].y)
|
||||||
|
.lineTo(points[1].x, points[1].y)
|
||||||
|
.lineTo(points[2].x, points[2].y)
|
||||||
|
.lineTo(points[3].x, points[3].y)
|
||||||
|
.lineTo(points[0].x, points[0].y)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Left face */
|
||||||
|
const left = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.8).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x, points[0].y)
|
||||||
|
.lineTo(points[0].x, points[0].y + this._thickness)
|
||||||
|
.lineTo(points[3].x, points[3].y + this._thickness)
|
||||||
|
.lineTo(points[3].x, points[3].y)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Right face */
|
||||||
|
const right = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.71).toNumber(),
|
||||||
|
matrix: new Matrix(1, -0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[3].x, points[3].y)
|
||||||
|
.lineTo(points[3].x, points[3].y + this._thickness)
|
||||||
|
.lineTo(points[2].x, points[2].y + this._thickness)
|
||||||
|
.lineTo(points[2].x, points[2].y)
|
||||||
|
.lineTo(points[3].x, points[3].y)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** And we combine everything */
|
||||||
|
step.addChild(top);
|
||||||
|
step.addChild(left);
|
||||||
|
step.addChild(right);
|
||||||
|
|
||||||
|
/** Add the offsets to the step */
|
||||||
|
step.x = offsets[0].x * i;
|
||||||
|
step.y = offsets[0].y * i;
|
||||||
|
|
||||||
|
/** Add the step to the stair */
|
||||||
|
this.addChild(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Positionate the stair */
|
||||||
|
this.x = 32 * this._position.x - 32 * this._position.y + offsets[1].x;
|
||||||
|
this.y = 16 * this._position.x + 16 * this._position.y - 32 * this._position.z + offsets[1].y;
|
||||||
|
|
||||||
|
/** Set the hit area */
|
||||||
|
this.hitArea = new Polygon(
|
||||||
|
new Point(0 - offsets[1].x, 0 - offsets[1].y),
|
||||||
|
new Point(32 - offsets[1].x, -16 - offsets[1].y),
|
||||||
|
new Point(64 - offsets[1].x, 0 - offsets[1].y),
|
||||||
|
new Point(32 - offsets[1].x, 16 - offsets[1].y),
|
||||||
|
new Point(0 - offsets[1].x, 0 - offsets[1].y)
|
||||||
|
);
|
||||||
|
/** Set the zIndex */
|
||||||
|
this.zIndex = ZOrder.floor(this._position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the corner stair using the given points, points offsets and offsets.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D[]} [points] - The point list that will be used to draw the stair.
|
||||||
|
* @param {IPosition2D[]} [pointsOffsets] - The offset point list that will be used to draw the stair.
|
||||||
|
* @param {IPosition2D[]} [offsets] - The offset list that will be used to draw the stair.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _drawCornerStair(points: IPosition2D[], pointsOffsets: IPosition2D[], offsets: IPosition2D[]): void {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const step = new Container();
|
||||||
|
/** Top face */
|
||||||
|
|
||||||
|
const top = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(1).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 1, -0.5, this._position.y % 2 === 0 ? 32 : 64, this._position.y % 2 === 0 ? 16 : 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x + -pointsOffsets[2].x * (2 - i), points[0].y + pointsOffsets[2].y * (2 - i))
|
||||||
|
.lineTo(points[1].x + pointsOffsets[1].x * (2 - i), points[1].y + pointsOffsets[1].y * (2 - i))
|
||||||
|
.lineTo(points[2].x + pointsOffsets[0].x * (2 - i), points[2].y + pointsOffsets[0].y * (2 - i))
|
||||||
|
.lineTo(points[3].x + -pointsOffsets[3].x * (2 - i), points[3].y + pointsOffsets[3].y * (2 - i))
|
||||||
|
.lineTo(points[0].x + -pointsOffsets[2].x * (2 - i), points[0].y + pointsOffsets[2].y * (2 - i))
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Left face */
|
||||||
|
const left = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.8).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x + -pointsOffsets[2].x * (2 - i), points[0].y + pointsOffsets[2].y * (2 - i))
|
||||||
|
.lineTo(
|
||||||
|
points[0].x + -pointsOffsets[2].x * (2 - i),
|
||||||
|
points[0].y + pointsOffsets[2].y * (2 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(
|
||||||
|
points[3].x + -pointsOffsets[3].x * (2 - i),
|
||||||
|
points[3].y + pointsOffsets[3].y * (2 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(points[3].x + -pointsOffsets[3].x * (2 - i), points[3].y + pointsOffsets[3].y * (2 - i))
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Right face */
|
||||||
|
const right = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.71).toNumber(),
|
||||||
|
matrix: new Matrix(1, -0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[3].x + -pointsOffsets[3].x * (2 - i), points[3].y + pointsOffsets[3].y * (2 - i))
|
||||||
|
.lineTo(
|
||||||
|
points[3].x + -pointsOffsets[3].x * (2 - i),
|
||||||
|
points[3].y + pointsOffsets[3].y * (2 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(
|
||||||
|
points[2].x + -pointsOffsets[0].x * (2 - i),
|
||||||
|
points[2].y + pointsOffsets[0].y * (2 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(points[2].x + -pointsOffsets[0].x * (2 - i), points[2].y + pointsOffsets[0].y * (2 - i))
|
||||||
|
.lineTo(points[3].x + -pointsOffsets[3].x * (2 - i), points[3].y + pointsOffsets[3].y * (2 - i))
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** And we combine everything */
|
||||||
|
step.addChild(top);
|
||||||
|
step.addChild(left);
|
||||||
|
step.addChild(right);
|
||||||
|
|
||||||
|
/** Add the offsets to the step */
|
||||||
|
step.x = offsets[3].x * i + offsets[1].x;
|
||||||
|
step.y = offsets[3].y * i + offsets[1].y;
|
||||||
|
|
||||||
|
/** zIndex */
|
||||||
|
if (this._type === StairType.OUTER_CORNER_STAIR) step.zIndex = -i;
|
||||||
|
if (
|
||||||
|
this._type === StairType.INNER_CORNER_STAIR ||
|
||||||
|
(this._type === StairType.OUTER_CORNER_STAIR && this._position.direction === Direction.SOUTH_WEST)
|
||||||
|
)
|
||||||
|
step.zIndex = 4 - i;
|
||||||
|
|
||||||
|
/** Add the step to the stair */
|
||||||
|
this.addChild(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const step = new Container();
|
||||||
|
|
||||||
|
/** Top face */
|
||||||
|
const top = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(1).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 1, -0.5, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x + pointsOffsets[0].x * (3 - i), points[0].y + pointsOffsets[0].y * (3 - i))
|
||||||
|
.lineTo(points[1].x + pointsOffsets[1].x * (3 - i), points[1].y + pointsOffsets[1].y * (3 - i))
|
||||||
|
.lineTo(points[2].x + pointsOffsets[2].x * (3 - i), points[2].y + pointsOffsets[2].y * (3 - i))
|
||||||
|
.lineTo(points[3].x + pointsOffsets[3].x * (3 - i), points[3].y + pointsOffsets[3].y * (3 - i))
|
||||||
|
.lineTo(points[0].x + pointsOffsets[0].x * (3 - i), points[0].y + pointsOffsets[0].y * (3 - i))
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Left face */
|
||||||
|
const left = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.8).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x + pointsOffsets[0].x * (3 - i), points[0].y + pointsOffsets[0].y * (3 - i))
|
||||||
|
.lineTo(
|
||||||
|
points[0].x + pointsOffsets[0].x * (3 - i),
|
||||||
|
points[0].y + pointsOffsets[0].y * (3 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(
|
||||||
|
points[3].x + pointsOffsets[3].x * (3 - i),
|
||||||
|
points[3].y + pointsOffsets[3].y * (3 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(points[3].x + pointsOffsets[3].x * (3 - i), points[3].y + pointsOffsets[3].y * (3 - i))
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Right face */
|
||||||
|
const right = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.71).toNumber(),
|
||||||
|
matrix: new Matrix(1, -0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(points[3].x + pointsOffsets[3].x * (3 - i), points[3].y + pointsOffsets[3].y * (3 - i))
|
||||||
|
.lineTo(
|
||||||
|
points[3].x + pointsOffsets[3].x * (3 - i),
|
||||||
|
points[3].y + pointsOffsets[3].y * (3 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(
|
||||||
|
points[2].x + pointsOffsets[2].x * (3 - i),
|
||||||
|
points[2].y + pointsOffsets[2].y * (3 - i) + this._thickness
|
||||||
|
)
|
||||||
|
.lineTo(points[2].x + pointsOffsets[2].x * (3 - i), points[2].y + pointsOffsets[2].y * (3 - i))
|
||||||
|
.lineTo(points[3].x + pointsOffsets[3].x * (3 - i), points[3].y + pointsOffsets[3].y * (3 - i))
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** And we combine everything */
|
||||||
|
step.addChild(top);
|
||||||
|
step.addChild(left);
|
||||||
|
step.addChild(right);
|
||||||
|
|
||||||
|
/** Add the offsets to the step */
|
||||||
|
step.x = offsets[0].x * i;
|
||||||
|
step.y = offsets[0].y * i;
|
||||||
|
|
||||||
|
/** zIndex */
|
||||||
|
if (this._type === StairType.INNER_CORNER_STAIR) step.zIndex = 3 - i;
|
||||||
|
|
||||||
|
/** Add the step to the stair */
|
||||||
|
this.addChild(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortableChildren = true;
|
||||||
|
|
||||||
|
/** Positionate the stair */
|
||||||
|
this.x = 32 * this._position.x - 32 * this._position.y + offsets[2].x;
|
||||||
|
this.y = 16 * this._position.x + 16 * this._position.y - 32 * this._position.z + offsets[2].y;
|
||||||
|
|
||||||
|
/** Set the hit area */
|
||||||
|
this.hitArea = new Polygon(
|
||||||
|
new Point(0 - offsets[2].x, 0 - offsets[2].y),
|
||||||
|
new Point(32 - offsets[2].x, -16 - offsets[2].y),
|
||||||
|
new Point(64 - offsets[2].x, 0 - offsets[2].y),
|
||||||
|
new Point(32 - offsets[2].x, 16 - offsets[2].y),
|
||||||
|
new Point(0 - offsets[2].x, 0 - offsets[2].y)
|
||||||
|
);
|
||||||
|
/** Set the zIndex */
|
||||||
|
this.zIndex = ZOrder.floor(this._position);
|
||||||
|
}
|
||||||
|
}
|
219
src/objects/rooms/parts/Tile.ts
Normal file
219
src/objects/rooms/parts/Tile.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import { Graphics, Matrix, Point, Polygon } from 'pixi.js';
|
||||||
|
import { Color } from '@pixi/color';
|
||||||
|
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
import type { ITileConfiguration, ITileInfo } from '../../../types/Room';
|
||||||
|
import type { Material } from '../materials/Material';
|
||||||
|
import { FloorMaterial } from '../materials/FloorMaterial';
|
||||||
|
import { ZOrder } from '../../../utilities/ZOrder';
|
||||||
|
import { RoomPart } from './RoomPart';
|
||||||
|
import type { Dimension } from '../../../types/Dimension';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tile class that show up during room rendering.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Tile extends RoomPart {
|
||||||
|
/**
|
||||||
|
* The ITileInfo instance
|
||||||
|
*
|
||||||
|
* @member {ITileInfo}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _tileInfo?: ITileInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The thickness of the tile part.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _thickness: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tile material that will be applied to this part, it contains the color and the texture of the tile.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _material: Material;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tile position.
|
||||||
|
*
|
||||||
|
* @member {IPosition3D}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
readonly _position: Dimension.IPosition3D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [room] - The room instance where the tile will be drawn.
|
||||||
|
* @param {ITileConfiguration} [configuration] - The tile configuration.
|
||||||
|
* @param {Material} [configuration.material] - The time material that will be applied.
|
||||||
|
* @param {number} [configuration.thickness] - The tile thickness.
|
||||||
|
* @param {IPosition3D} [configuration.position] - The tile position.
|
||||||
|
**/
|
||||||
|
constructor(room: Room, configuration: ITileConfiguration, tileInfo?: ITileInfo) {
|
||||||
|
super(room);
|
||||||
|
|
||||||
|
/** Store the configuration */
|
||||||
|
this.room = room;
|
||||||
|
this._tileInfo = tileInfo;
|
||||||
|
this._position = configuration.position;
|
||||||
|
this._thickness = configuration.thickness ?? 8;
|
||||||
|
this._material = configuration.material ?? new FloorMaterial(this.room.engine, 111);
|
||||||
|
|
||||||
|
/** Register interactions */
|
||||||
|
this.registerInteractions(this._position);
|
||||||
|
|
||||||
|
// TODO: Make the method public and use it when adding it to a room, not when instancing the class
|
||||||
|
/** Draw the tile */
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the tile.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _draw(): void {
|
||||||
|
/** Top face */
|
||||||
|
const top = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(1).toNumber(),
|
||||||
|
//matrix: new Matrix(1, 0.5, 1, -0.5, (this._position.x % 2 === 0 || this._position.y % 2 === 0) && !(this._position.x % 2 === 0 && this._position.y % 2 === 0) ? 32 : 0, (this._position.x % 2 === 0 || this._position.y % 2 === 0) && !(this._position.x % 2 === 0 && this._position.y % 2 === 0) ? 16 : 0)
|
||||||
|
matrix: new Matrix(1, 0.5, 1, -0.5, this._position.y % 2 === 0 ? 32 : 64, this._position.y % 2 === 0 ? 16 : 0)
|
||||||
|
})
|
||||||
|
.moveTo(0, 0)
|
||||||
|
.lineTo(32, -16)
|
||||||
|
.lineTo(64, 0)
|
||||||
|
.lineTo(32, 16)
|
||||||
|
.lineTo(0, 0)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
this.addChild(top);
|
||||||
|
|
||||||
|
let bottomTile;
|
||||||
|
let rightTile;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.room.tileMap.tileMap[this._position.y + 1] !== undefined &&
|
||||||
|
this.room.tileMap.tileMap[this._position.y + 1][this._position.x] !== undefined
|
||||||
|
) {
|
||||||
|
bottomTile = this.room.tileMap.getTileInfo({ x: this._position.x, y: this._position.y + 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.room.tileMap.tileMap[this._position.y][this._position.x + 1] !== undefined) {
|
||||||
|
rightTile = this.room.tileMap.getTileInfo({ x: this._position.x + 1, y: this._position.y });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Boolean(bottomTile) ||
|
||||||
|
Boolean(bottomTile?.stairType) ||
|
||||||
|
!Boolean(bottomTile?.tile) ||
|
||||||
|
(this._tileInfo != null && bottomTile?.height !== this._tileInfo.height)
|
||||||
|
) {
|
||||||
|
/** Left face */
|
||||||
|
const left = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.8).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(0, 0)
|
||||||
|
.lineTo(0, this._thickness)
|
||||||
|
.lineTo(32, 16 + this._thickness)
|
||||||
|
.lineTo(32, 16)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
this.addChild(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
rightTile == null ||
|
||||||
|
rightTile.stairType != null ||
|
||||||
|
!rightTile.tile ||
|
||||||
|
(this._tileInfo != null && rightTile.height !== this._tileInfo.height)
|
||||||
|
) {
|
||||||
|
/** Right face */
|
||||||
|
const right = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._material.texture,
|
||||||
|
color: new Color(this._material.color).premultiply(0.71).toNumber(),
|
||||||
|
matrix: new Matrix(1, -0.5, 0, 1, 0, 0)
|
||||||
|
})
|
||||||
|
.moveTo(32, 16)
|
||||||
|
.lineTo(32, 16 + this._thickness)
|
||||||
|
.lineTo(64, this._thickness)
|
||||||
|
.lineTo(64, 0)
|
||||||
|
.lineTo(32, 16)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
this.addChild(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Positionate the wall */
|
||||||
|
this.x = 32 * this._position.x - 32 * this._position.y;
|
||||||
|
this.y = 16 * this._position.x + 16 * this._position.y - 32 * this._position.z;
|
||||||
|
/** Set the hit area */
|
||||||
|
this.hitArea = new Polygon(
|
||||||
|
new Point(0, 0),
|
||||||
|
new Point(32, -16),
|
||||||
|
new Point(64, 0),
|
||||||
|
new Point(32, 16),
|
||||||
|
new Point(0, 0)
|
||||||
|
);
|
||||||
|
/** Set the zIndex */
|
||||||
|
this.zIndex = ZOrder.floor(this._position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the tile thickness.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get thickness(): number {
|
||||||
|
return this._thickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the tile thickness and redraw the tile.
|
||||||
|
*
|
||||||
|
* @param {number} [thickness] - The room tile thickness that will be applied.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set thickness(thickness: number) {
|
||||||
|
this._thickness = thickness;
|
||||||
|
/** Redraw the tile */
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the tile material instance.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get material(): Material {
|
||||||
|
return this._material;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the tile material and redraw the tile.
|
||||||
|
*
|
||||||
|
* @param {Material} [material] - The room tile material that will be applied.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public set material(material: Material) {
|
||||||
|
this._material = material;
|
||||||
|
/** Redraw the tile */
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
}
|
314
src/objects/rooms/parts/Wall.ts
Normal file
314
src/objects/rooms/parts/Wall.ts
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
import { Graphics, Matrix, Texture } from 'pixi.js';
|
||||||
|
import { Color } from '@pixi/color';
|
||||||
|
|
||||||
|
import type { Room } from '../Room';
|
||||||
|
import type { IPosition3D, IPosition2D, IWallConfiguration } from '../../../types/Room';
|
||||||
|
import type { Material } from '../materials/Material';
|
||||||
|
import { WallType } from '../../../enums/WallType';
|
||||||
|
import { WallMaterial } from '../materials/WallMaterial';
|
||||||
|
import { ZOrder } from '../../../utilities/ZOrder';
|
||||||
|
import { RoomPart } from './RoomPart';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wall class that show up on the sides of the tiles.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Wall extends RoomPart {
|
||||||
|
/**
|
||||||
|
* The thickness of the wall part.
|
||||||
|
*
|
||||||
|
* @member {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _thickness: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wall material that will be applied to this part, it contains the color and the texture of the wall.
|
||||||
|
*
|
||||||
|
* @member {Material}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _material: Material;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wall position.
|
||||||
|
*
|
||||||
|
* @member {IPosition3D}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
readonly _position: IPosition3D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wall type.
|
||||||
|
*
|
||||||
|
* @member {WallType}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _type: WallType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Room} [room] - The room instance where the wall will be drawn.
|
||||||
|
* @param {IWallConfiguration} [configuration] - The wall configuration.
|
||||||
|
* @param {Material} [configuration.material] - The wall material that will be applied.
|
||||||
|
* @param {number} [configuration.thickness] - The wall thickness.
|
||||||
|
* @param {number} [configuration.height] - The wall height.
|
||||||
|
* @param {IPosition3D} [configuration.position] - The wall position.
|
||||||
|
* @param {WallType} [configuration.type] - The wall type.
|
||||||
|
* @param {boolean} [configuration.door] - Is it a door wall?
|
||||||
|
**/
|
||||||
|
constructor(room: Room, configuration: IWallConfiguration) {
|
||||||
|
super(room);
|
||||||
|
|
||||||
|
/** Store the configuration */
|
||||||
|
this.room = room;
|
||||||
|
this._position = configuration.position;
|
||||||
|
this._thickness = configuration.thickness ?? 8;
|
||||||
|
this._material = configuration.material ?? new WallMaterial(this.room.engine);
|
||||||
|
this._height = configuration.height ?? 0;
|
||||||
|
this._type = configuration.type;
|
||||||
|
|
||||||
|
/** Draw the wall */
|
||||||
|
this._draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select which wall should be drawn by it's type.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _draw(): void {
|
||||||
|
if (this._type === WallType.LEFT_WALL) {
|
||||||
|
/** Draw a left wall */
|
||||||
|
this._drawWall([
|
||||||
|
{
|
||||||
|
x: -this._thickness,
|
||||||
|
y: -this._thickness / 2 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 115 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -this._thickness + 32,
|
||||||
|
y: -this._thickness / 2 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 131 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -this._thickness + 32 + this._thickness,
|
||||||
|
y:
|
||||||
|
-this._thickness / 2 +
|
||||||
|
this._position.z * 32 -
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
131 +
|
||||||
|
this._thickness / 2 -
|
||||||
|
this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -this._thickness + this._thickness,
|
||||||
|
y:
|
||||||
|
-this._thickness / 2 +
|
||||||
|
this._position.z * 32 -
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
115 +
|
||||||
|
this._thickness / 2 -
|
||||||
|
this._height * 64
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
} else if (this._type === WallType.RIGHT_WALL) {
|
||||||
|
/** Draw a right wall */
|
||||||
|
this._drawWall([
|
||||||
|
{
|
||||||
|
x: 32,
|
||||||
|
y: -16 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 115 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 32 + this._thickness,
|
||||||
|
y: -16 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 115 - this._thickness / 2 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 64 + this._thickness,
|
||||||
|
y: -16 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 99 - this._thickness / 2 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 64,
|
||||||
|
y: -16 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 99 - this._height * 64
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
} else if (this._type === WallType.CORNER_WALL) {
|
||||||
|
/** Draw a corner wall */
|
||||||
|
this._drawWall([
|
||||||
|
{
|
||||||
|
x: 32 - this._thickness,
|
||||||
|
y: -16 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 115 - this._thickness / 2 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 32,
|
||||||
|
y:
|
||||||
|
-16 +
|
||||||
|
this._position.z * 32 -
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
115 -
|
||||||
|
2 * (this._thickness / 2) -
|
||||||
|
this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 32 + this._thickness,
|
||||||
|
y: -16 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 115 - this._thickness / 2 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 32,
|
||||||
|
y: -16 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 115 - this._height * 64
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
} else if (this._type === WallType.DOOR_WALL) {
|
||||||
|
/** Draw a door wall */
|
||||||
|
this._drawWall([
|
||||||
|
{
|
||||||
|
x: -this._thickness + 32,
|
||||||
|
y: -this._thickness / 2 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 99 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -this._thickness + 64,
|
||||||
|
y: -this._thickness / 2 + this._position.z * 32 - this.room.tileMap.maxZ * 32 - 115 - this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -this._thickness + 64 + this._thickness,
|
||||||
|
y:
|
||||||
|
-this._thickness / 2 +
|
||||||
|
this._position.z * 32 -
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
115 +
|
||||||
|
this._thickness / 2 -
|
||||||
|
this._height * 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -this._thickness + 32 + this._thickness,
|
||||||
|
y:
|
||||||
|
-this._thickness / 2 +
|
||||||
|
this._position.z * 32 -
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
99 +
|
||||||
|
this._thickness / 2 -
|
||||||
|
this._height * 64
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the wall using the given points.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D[]} [points] - The point list that will be used to draw the wall.
|
||||||
|
* @return {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _drawWall(points: IPosition2D[]): void {
|
||||||
|
/** Top face */
|
||||||
|
const top = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: Texture.WHITE,
|
||||||
|
color: new Color(this._material.color).premultiply(0.61).toNumber()
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x, points[0].y)
|
||||||
|
.lineTo(points[1].x, points[1].y)
|
||||||
|
.lineTo(points[2].x, points[2].y)
|
||||||
|
.lineTo(points[3].x, points[3].y)
|
||||||
|
.lineTo(points[0].x, points[0].y)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Left face */
|
||||||
|
const left = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture: this._type === WallType.RIGHT_WALL ? this._material.texture : Texture.WHITE,
|
||||||
|
color: new Color(this._material.color).premultiply(1).toNumber(),
|
||||||
|
matrix: new Matrix(1, 0.5, 0, 1, points[0].x, points[0].y)
|
||||||
|
})
|
||||||
|
.moveTo(points[0].x, points[0].y)
|
||||||
|
.lineTo(
|
||||||
|
points[0].x,
|
||||||
|
points[0].y +
|
||||||
|
115 +
|
||||||
|
this.room.floorThickness +
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
this._position.z * 32 +
|
||||||
|
this._height * 64
|
||||||
|
)
|
||||||
|
.lineTo(
|
||||||
|
points[3].x,
|
||||||
|
points[3].y +
|
||||||
|
115 +
|
||||||
|
this.room.floorThickness +
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
this._position.z * 32 +
|
||||||
|
this._height * 64
|
||||||
|
)
|
||||||
|
.lineTo(points[3].x, points[3].y)
|
||||||
|
.endFill();
|
||||||
|
|
||||||
|
/** Right face */
|
||||||
|
const right = new Graphics()
|
||||||
|
.beginTextureFill({
|
||||||
|
texture:
|
||||||
|
this._type === WallType.LEFT_WALL || this._type === WallType.DOOR_WALL
|
||||||
|
? this._material.texture
|
||||||
|
: Texture.WHITE,
|
||||||
|
color: new Color(this._material.color).premultiply(0.8).toNumber(),
|
||||||
|
matrix: new Matrix(1, -0.5, 0, 1, points[0].x + this._thickness, points[0].y + 4)
|
||||||
|
})
|
||||||
|
.moveTo(points[3].x, points[3].y);
|
||||||
|
|
||||||
|
if (this._type === WallType.DOOR_WALL) {
|
||||||
|
right
|
||||||
|
.lineTo(
|
||||||
|
points[3].x,
|
||||||
|
points[3].y +
|
||||||
|
22 +
|
||||||
|
this.room.floorThickness +
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
this._position.z * 32 +
|
||||||
|
this._height * 64
|
||||||
|
)
|
||||||
|
.lineTo(
|
||||||
|
points[2].x,
|
||||||
|
points[2].y +
|
||||||
|
22 +
|
||||||
|
this.room.floorThickness +
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
this._position.z * 32 +
|
||||||
|
this._height * 64
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
right
|
||||||
|
.lineTo(
|
||||||
|
points[3].x,
|
||||||
|
points[3].y +
|
||||||
|
115 +
|
||||||
|
this.room.floorThickness +
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
this._position.z * 32 +
|
||||||
|
this._height * 64
|
||||||
|
)
|
||||||
|
.lineTo(
|
||||||
|
points[2].x,
|
||||||
|
points[2].y +
|
||||||
|
115 +
|
||||||
|
this.room.floorThickness +
|
||||||
|
this.room.tileMap.maxZ * 32 -
|
||||||
|
this._position.z * 32 +
|
||||||
|
this._height * 64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
right.lineTo(points[2].x, points[2].y).lineTo(points[3].x, points[3].y).endFill();
|
||||||
|
|
||||||
|
/** And we combine everything */
|
||||||
|
this.addChild(top);
|
||||||
|
this.addChild(left);
|
||||||
|
this.addChild(right);
|
||||||
|
|
||||||
|
/** Positionate the wall */
|
||||||
|
this.x = 32 * this._position.x - 32 * this._position.y;
|
||||||
|
this.y = 16 * this._position.x + 16 * this._position.y - 32 * this._position.z;
|
||||||
|
|
||||||
|
/** Set the zIndex */
|
||||||
|
this.zIndex = ZOrder.wall(this._position);
|
||||||
|
}
|
||||||
|
}
|
4
src/objects/rooms/parts/index.ts
Normal file
4
src/objects/rooms/parts/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './Cursor';
|
||||||
|
export * from './Stair';
|
||||||
|
export * from './Tile';
|
||||||
|
export * from './Wall';
|
93
src/types/Avatar.d.ts
vendored
Normal file
93
src/types/Avatar.d.ts
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import type { Direction } from '../enums/Direction';
|
||||||
|
import type { AvatarAction } from '../objects/avatars/actions/AvatarAction';
|
||||||
|
import type { Dimension } from './Dimension';
|
||||||
|
import type { IRoomObjectConfig } from './Room';
|
||||||
|
|
||||||
|
export type IAvatarPosition = Dimension.IPosition3D;
|
||||||
|
|
||||||
|
export type Figure = Map<string, { setId: number; colors: number[] }>;
|
||||||
|
|
||||||
|
export interface IAvatarConfig extends IRoomObjectConfig {
|
||||||
|
figure: string;
|
||||||
|
bodyDirection: Direction;
|
||||||
|
headDirection: Direction;
|
||||||
|
actions: AvatarAction[];
|
||||||
|
handItem?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAvatarPart {
|
||||||
|
colorable: number;
|
||||||
|
colorindex: number;
|
||||||
|
id: number;
|
||||||
|
index: number;
|
||||||
|
lib: { id: string; revision: string };
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IActionDefinition {
|
||||||
|
state: string;
|
||||||
|
precedence: string;
|
||||||
|
main: string;
|
||||||
|
geometrytype: string;
|
||||||
|
activepartset: string;
|
||||||
|
assetpartdefinition: string;
|
||||||
|
prevents: string;
|
||||||
|
params: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAnimationDefinition {
|
||||||
|
desc: string;
|
||||||
|
frames: Array<{
|
||||||
|
bodyparts: object;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAnimationFrameData {
|
||||||
|
assetpartdefinition: string;
|
||||||
|
repeats: number;
|
||||||
|
frame: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBodyPartConfiguration {
|
||||||
|
type: string;
|
||||||
|
setId: number;
|
||||||
|
colors: number[];
|
||||||
|
parts: IAvatarPart[];
|
||||||
|
actions: AvatarAction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAvatarPartSets {
|
||||||
|
partSets: object;
|
||||||
|
activePartSets: {
|
||||||
|
figure: string[];
|
||||||
|
head: string[];
|
||||||
|
speak: string[];
|
||||||
|
gesture: string[];
|
||||||
|
eye: string[];
|
||||||
|
handRight: string[];
|
||||||
|
handRightAndHead: string[];
|
||||||
|
handLeft: string[];
|
||||||
|
walk: string[];
|
||||||
|
sit: string[];
|
||||||
|
itemRight: string[];
|
||||||
|
swim: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAvatarLayerConfiguration {
|
||||||
|
type: string;
|
||||||
|
part: IAvatarPart;
|
||||||
|
gesture: string;
|
||||||
|
tint?: number;
|
||||||
|
z: number;
|
||||||
|
flip: boolean;
|
||||||
|
direction: Direction;
|
||||||
|
frame: number;
|
||||||
|
alpha?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LayerFrame {
|
||||||
|
action: AvatarAction;
|
||||||
|
frame: number;
|
||||||
|
repeat: number;
|
||||||
|
}
|
14
src/types/Configuration.d.ts
vendored
Normal file
14
src/types/Configuration.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { Spritesheet } from 'pixi.js';
|
||||||
|
|
||||||
|
export interface IRendererConfiguration {
|
||||||
|
canvas: HTMLElement;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
resources: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScutiSpritesheet extends Omit<Spritesheet, 'data'> {
|
||||||
|
data: {
|
||||||
|
partsType: { [key: string]: { gestures: string[] } };
|
||||||
|
};
|
||||||
|
}
|
15
src/types/Dimension.d.ts
vendored
Normal file
15
src/types/Dimension.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export namespace Dimension {
|
||||||
|
export type IPosition2D = Omit<IPosition3D, 'z'>;
|
||||||
|
export interface IPosition3D {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IOffsets2D = Omit<IOffsets3D, 'offsetZ'>;
|
||||||
|
export interface IOffsets3D {
|
||||||
|
offsetX: number;
|
||||||
|
offsetY: number;
|
||||||
|
offsetZ: number;
|
||||||
|
}
|
||||||
|
}
|
52
src/types/Figure.d.ts
vendored
Normal file
52
src/types/Figure.d.ts
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
export interface IFigureData {
|
||||||
|
palette: Record<string, Record<string, IFigureDataColor>>;
|
||||||
|
settype: Record<string, IFigureDataSetType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFigureDataPalette {
|
||||||
|
id: number;
|
||||||
|
color: IFigureDataColor[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFigureMap {
|
||||||
|
libs: Array<{ id: string; revision: string }>;
|
||||||
|
parts: Record<string, Record<string, number>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFigureDataSetType {
|
||||||
|
type: string;
|
||||||
|
paletteid: string;
|
||||||
|
mand_f_0: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
mand_f_1: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
mand_m_0: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
mand_m_1: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
set: Record<string, IFigureDataSet>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFigureDataColor {
|
||||||
|
id: number;
|
||||||
|
index: number;
|
||||||
|
club: number; // must be changed to something, either 0, 1, 2
|
||||||
|
selectable: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
hexCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFigureDataSet {
|
||||||
|
id: number;
|
||||||
|
gender: 'M' | 'F' | 'U'; // has been changed
|
||||||
|
club: number; // must be changed to something, either 0, 1, 2
|
||||||
|
colorable: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
selectable: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
preselectable: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
sellable?: boolean; // has been changed to boolean, can be either 1, 0, null
|
||||||
|
parts: IFigureDataPart[];
|
||||||
|
hiddenLayers: Array<{ partType: string }>; // !! can be empty
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFigureDataPart {
|
||||||
|
id: number;
|
||||||
|
type: string; // must be changed (i guess)
|
||||||
|
colorable: boolean; // has been changed to boolean, can be either 1, 0
|
||||||
|
index: number;
|
||||||
|
colorindex: number;
|
||||||
|
}
|
100
src/types/Furniture.d.ts
vendored
Normal file
100
src/types/Furniture.d.ts
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import type { BLEND_MODES } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { Direction } from '../enums/Direction';
|
||||||
|
import type { Dimension } from './Dimension';
|
||||||
|
import type { IRoomObjectConfig } from './Room';
|
||||||
|
|
||||||
|
export type IFloorPosition = Dimension.IPosition3D;
|
||||||
|
export type IWallPosition = Dimension.IPosition2D & Dimension.IOffsets2D;
|
||||||
|
|
||||||
|
export interface IFloorFurnitureConfiguration {
|
||||||
|
id: number;
|
||||||
|
position: IFloorPosition;
|
||||||
|
direction: Direction;
|
||||||
|
state?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWallFurniConfig extends IRoomObjectConfig {
|
||||||
|
id: number;
|
||||||
|
state?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFurnitureData {
|
||||||
|
floorItems: IFloorItem[];
|
||||||
|
wallItems: IWallItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISharedFurniData {
|
||||||
|
id: number;
|
||||||
|
className: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFloorItem extends ISharedFurniData {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
furniLine: string;
|
||||||
|
offerId: number;
|
||||||
|
adUrl: string;
|
||||||
|
excludeDynamic: false;
|
||||||
|
specialType: number;
|
||||||
|
customParams: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWallItem extends ISharedFurniData {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
furniLine: string;
|
||||||
|
offerId: number;
|
||||||
|
adUrl: string;
|
||||||
|
excludeDynamic: boolean;
|
||||||
|
specialType: number;
|
||||||
|
customParams: string;
|
||||||
|
dimensions: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
defaultDirection: number;
|
||||||
|
};
|
||||||
|
canStandOn: boolean;
|
||||||
|
canSitOn: boolean;
|
||||||
|
canLayOn: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFurnitureProperty {
|
||||||
|
dimensions: Dimension.IPosition2D; // not sure { x: .., y: .. }
|
||||||
|
infos: { logic: string; visualization: string };
|
||||||
|
visualization: IFurnitureVisualization;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFurnitureVisualization {
|
||||||
|
layerCount: number;
|
||||||
|
directions: Direction[];
|
||||||
|
colors: Record<string, Record<string, `#${string}`>>;
|
||||||
|
layers: Array<{ z: number; ignoreMouse: boolean; tag: string; alpha: number; ink: keyof typeof BLEND_MODES }>; // wrong types here
|
||||||
|
animation: Record<string, Record<string, { frameSequence: number[] }>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFurnitureLayerConfiguration {
|
||||||
|
layer: number | string;
|
||||||
|
alpha: number;
|
||||||
|
tint?: number | undefined;
|
||||||
|
z: number;
|
||||||
|
blendMode: BLEND_MODES;
|
||||||
|
flip: boolean;
|
||||||
|
frame: number;
|
||||||
|
ignoreMouse: boolean;
|
||||||
|
direction: Direction;
|
||||||
|
tag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFurnitureLayerData {
|
||||||
|
layer: number | string;
|
||||||
|
alpha: number;
|
||||||
|
tint?: number | undefined;
|
||||||
|
z: number;
|
||||||
|
blendMode: BLEND_MODES;
|
||||||
|
flip: boolean;
|
||||||
|
frame: number;
|
||||||
|
ignoreMouse: boolean;
|
||||||
|
direction: Direction;
|
||||||
|
tag?: string;
|
||||||
|
}
|
1
src/types/Global.d.ts
vendored
Normal file
1
src/types/Global.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type Nullable<T> = T | undefined | null;
|
10
src/types/Interaction.d.ts
vendored
Normal file
10
src/types/Interaction.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { FederatedPointerEvent } from 'pixi.js';
|
||||||
|
|
||||||
|
import type { IAvatarPosition } from './Avatar';
|
||||||
|
import type { IFloorPosition, IWallPosition } from './Furniture';
|
||||||
|
|
||||||
|
export interface IInteractionEvent {
|
||||||
|
tag?: string;
|
||||||
|
event: FederatedPointerEvent;
|
||||||
|
position?: IWallPosition | IFloorPosition | IAvatarPosition;
|
||||||
|
}
|
108
src/types/Room.d.ts
vendored
Normal file
108
src/types/Room.d.ts
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import type { StairType } from '../enums/StairType';
|
||||||
|
import type { Direction } from '../enums/Direction';
|
||||||
|
import type { WallType } from '../enums/WallType';
|
||||||
|
import type { Material } from '../objects/rooms/materials/Material';
|
||||||
|
import type { IFloorPosition, IWallPosition } from './Furniture';
|
||||||
|
import type { IAvatarPosition } from './Avatar';
|
||||||
|
|
||||||
|
export type TileMap = string[][];
|
||||||
|
|
||||||
|
export interface IRoomConfig {
|
||||||
|
tileMap: string;
|
||||||
|
floorMaterial?: Material;
|
||||||
|
floorThickness?: number;
|
||||||
|
wallMaterial?: Material;
|
||||||
|
wallHeight?: number;
|
||||||
|
wallThickness?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRoomObjectConfig {
|
||||||
|
position: IWallPosition | IFloorPosition | IAvatarPosition;
|
||||||
|
direction: Direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITileConfiguration {
|
||||||
|
material?: Material;
|
||||||
|
thickness?: number;
|
||||||
|
position: IPosition3D;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStairConfiguration {
|
||||||
|
material?: Material;
|
||||||
|
thickness?: number;
|
||||||
|
type: StairType;
|
||||||
|
position: IPosition3D;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWallConfiguration {
|
||||||
|
material?: Material;
|
||||||
|
thickness?: number;
|
||||||
|
height?: number;
|
||||||
|
position: IPosition3D;
|
||||||
|
type: WallType;
|
||||||
|
door?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICursorConfiguration {
|
||||||
|
position: IPosition3D;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPosition3D {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
direction?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPosition2D {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITileInfo {
|
||||||
|
tile: boolean;
|
||||||
|
door: boolean;
|
||||||
|
height: number;
|
||||||
|
stairType?: { type: StairType; direction: Direction };
|
||||||
|
wallType?: WallType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// missing types here
|
||||||
|
export interface RoomMaterial {
|
||||||
|
assets: { x: string; y: string; source?: string; flipH?: boolean };
|
||||||
|
floorData: {
|
||||||
|
floors: Array<{
|
||||||
|
id: string;
|
||||||
|
visualizations: Array<{ size: number; layers: Array<{ color: number; materialId: string }> }>;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
materials: Array<{
|
||||||
|
id: string;
|
||||||
|
matrices: Array<{ columns: Array<{ width: number; cells: Array<{ textureId: string }> }> }>;
|
||||||
|
}>;
|
||||||
|
textures: Array<{ id: string; bitmaps: Array<{ assetName: string }> }>;
|
||||||
|
wallData: Array<{
|
||||||
|
materials: Array<{
|
||||||
|
id: string;
|
||||||
|
matrices: Array<{ columns: Array<{ width: number; cells: Array<{ textureId: string }> }> }>;
|
||||||
|
}>;
|
||||||
|
textures: Array<{ id: string; bitmaps: Array<{ assetName: string }> }>;
|
||||||
|
walls: Array<{
|
||||||
|
id: string;
|
||||||
|
visualizations: Array<{ size: number; layers: Array<{ color: number; materialId: string }> }>;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
landscapeData: object;
|
||||||
|
// ...
|
||||||
|
visualizationType: string;
|
||||||
|
type: string;
|
||||||
|
logicType: string;
|
||||||
|
spritesheet: string;
|
||||||
|
name: string;
|
||||||
|
maskData: {
|
||||||
|
masks: Array<{
|
||||||
|
id: string;
|
||||||
|
visualizations: Array<{ size: number; layers: Array<{ color: number; materialId: string }> }>;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
}
|
54
src/types/RoomMaterial.d.ts
vendored
Normal file
54
src/types/RoomMaterial.d.ts
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
export interface RoomMaterial {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
visualizationType: string;
|
||||||
|
logicType: string;
|
||||||
|
spritesheet: string;
|
||||||
|
assets: Record<string, Assets>;
|
||||||
|
wallData: WallData;
|
||||||
|
floorData: FloorData;
|
||||||
|
landscapeData: LandscapeData;
|
||||||
|
maskData: MaskData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Assets {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
source?: string;
|
||||||
|
flipH?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FloorDataMaterial {
|
||||||
|
id: string;
|
||||||
|
matrices: PurpleMatrix[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurpleMatrix {
|
||||||
|
columns: PurpleColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FloorData {
|
||||||
|
floors: Floor[];
|
||||||
|
materials: FloorDataMaterial[];
|
||||||
|
textures: Texture[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WallData {
|
||||||
|
walls: Floor[];
|
||||||
|
materials: FloorDataMaterial[];
|
||||||
|
textures: Texture[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Floor {
|
||||||
|
id: string;
|
||||||
|
visualizations: FloorVisualization[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Texture {
|
||||||
|
id: string;
|
||||||
|
bitmaps: Bitmap[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Bitmap {
|
||||||
|
assetName: string;
|
||||||
|
}
|
8
src/types/index.d.ts
vendored
Normal file
8
src/types/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export * from './Avatar';
|
||||||
|
export * from './Configuration';
|
||||||
|
export * from './Dimension';
|
||||||
|
export * from './Figure';
|
||||||
|
export * from './Furniture';
|
||||||
|
export * from './Global';
|
||||||
|
export * from './Interaction';
|
||||||
|
export * from './Room';
|
26
src/utilities/AssetLoader.ts
Normal file
26
src/utilities/AssetLoader.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Assets, Cache } from 'pixi.js';
|
||||||
|
|
||||||
|
const domain = 'http://localhost:8081/';
|
||||||
|
|
||||||
|
interface LoadedKeys {
|
||||||
|
[key: string]: Promise<any>;
|
||||||
|
}
|
||||||
|
const loadedKeys: LoadedKeys = {};
|
||||||
|
|
||||||
|
const load = async (key: string, url: string, onUncached?: () => void): Promise<void> => {
|
||||||
|
if (loadedKeys[key] !== undefined) return await loadedKeys[key];
|
||||||
|
|
||||||
|
if (!Cache.has(key)) {
|
||||||
|
if (onUncached != null) onUncached();
|
||||||
|
Assets.add(key, AssetLoader.domain + url);
|
||||||
|
loadedKeys[key] = Assets.load(key);
|
||||||
|
await loadedKeys[key];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads assets from a certain domain
|
||||||
|
*
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export const AssetLoader = { domain, load };
|
107
src/utilities/Logger.ts
Normal file
107
src/utilities/Logger.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Material class that regroup the methods to make beautiful logs.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @memberof Scuti
|
||||||
|
*/
|
||||||
|
export class Logger {
|
||||||
|
/**
|
||||||
|
* The logger name (the name shown on the left of the log).
|
||||||
|
*
|
||||||
|
* @member {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} [name] - The logger name.
|
||||||
|
*/
|
||||||
|
constructor(name: string) {
|
||||||
|
this._name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a normal log message into the console.
|
||||||
|
*
|
||||||
|
* @param {string} [message] - The message that will be logged into the console.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public log(message: string): void {
|
||||||
|
this._log('#078000', '#FFFFFF', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an error log message in the console.
|
||||||
|
*
|
||||||
|
* @param {string} [message] - The message that will be logged into the console.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public error(message: string): void {
|
||||||
|
this._log('#E86C5D', '#FFFFFF', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a warning log message in the console.
|
||||||
|
*
|
||||||
|
* @param {string} [message] - The message that will be logged into the console.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public warn(message: string): void {
|
||||||
|
this._log('#FFD100', '#000000', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an info log message in the console.
|
||||||
|
*
|
||||||
|
* @param {string} [message] - The message that will be logged into the console.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public info(message: string): void {
|
||||||
|
this._log('#EC3262', '#FFFFFF', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a stylized message in the console.
|
||||||
|
*
|
||||||
|
* @param {string} [backgroundColor] - The color of the background of the message.
|
||||||
|
* @param {string} [textColor] - The color of the message.
|
||||||
|
* @param {string} [message] - The message that will be logged into the console.
|
||||||
|
* @return {void}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
private _log(backgroundColor: string, textColor: string, message: string): void {
|
||||||
|
console.log(
|
||||||
|
`%c ${this.time} %c ${this._name} %c ${message} `,
|
||||||
|
`background: #FFFFFF; color: #000000;`,
|
||||||
|
`background: #000000; color: #FFFFFF`,
|
||||||
|
`background: ${backgroundColor}; color: ${textColor};`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference of the current time of the day formatted in a string.
|
||||||
|
*
|
||||||
|
* @member {string}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get time(): string {
|
||||||
|
const date = new Date();
|
||||||
|
return String(date.getHours()) + ':' + String(date.getMinutes()) + ':' + String(date.getSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference of the logger name.
|
||||||
|
*
|
||||||
|
* @member {string}
|
||||||
|
* @readonly
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get name(): string {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
}
|
102
src/utilities/ZOrder.ts
Normal file
102
src/utilities/ZOrder.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import type { IPosition2D, IPosition3D } from '../types/Room';
|
||||||
|
import type { IFloorPosition, IWallPosition } from '../types/Furniture';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority values
|
||||||
|
*/
|
||||||
|
const PRIORITY_WALL = 6;
|
||||||
|
const PRIORITY_FLOOR = 7;
|
||||||
|
const PRIORITY_TILE_CURSOR = 11;
|
||||||
|
const PRIORITY_ROOM_AVATAR = 11;
|
||||||
|
const PRIORITY_ROOM_ITEM = 11;
|
||||||
|
const PRIORITY_WALL_ITEM = 9;
|
||||||
|
const PRIORITY_MULTIPLIER = 10000000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparable values
|
||||||
|
*/
|
||||||
|
const COMPARABLE_X_Y = 1000000;
|
||||||
|
const COMPARABLE_Z = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the zOrder of an avatar by it's position in the room.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The avatar position in the room.
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const avatar = function (position: IPosition3D, z: number): number {
|
||||||
|
return (
|
||||||
|
(Math.floor(position.x) + Math.floor(position.y)) * COMPARABLE_X_Y +
|
||||||
|
(position.z + 0.001 * COMPARABLE_Z) +
|
||||||
|
PRIORITY_MULTIPLIER * PRIORITY_ROOM_AVATAR +
|
||||||
|
z
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the zOrder of the floor by it's position in the room.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The floor position in the room.
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const floor = function (position: IPosition2D): number {
|
||||||
|
return (position.x + position.y) * COMPARABLE_X_Y + PRIORITY_MULTIPLIER * PRIORITY_WALL;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the zOrder of the wall by it's position in the room.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The wall position in the room.
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const wall = function (position: IPosition2D): number {
|
||||||
|
return (position.x + position.y) * COMPARABLE_X_Y + PRIORITY_MULTIPLIER * PRIORITY_WALL;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the zOrder of the tile cursor by it's position in the room.
|
||||||
|
*
|
||||||
|
* @param {IPosition2D} [position] - The tile cursor position in the room.
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const tileCursor = function (position: IPosition2D): number {
|
||||||
|
return (position.x + position.y) * COMPARABLE_X_Y + PRIORITY_MULTIPLIER * PRIORITY_TILE_CURSOR;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the zOrder of a floor item by it's position in the room.
|
||||||
|
*
|
||||||
|
* @param {IFloorPosition} [position] - The floor item position in the room.
|
||||||
|
* @param {number} [z] - The z value of the layer.
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const floorItem = function (position: IFloorPosition, z: number): number {
|
||||||
|
const compareY = Math.trunc(z / 100) / 10;
|
||||||
|
return (
|
||||||
|
(position.x + position.y + compareY) * COMPARABLE_X_Y +
|
||||||
|
position.z * COMPARABLE_Z +
|
||||||
|
PRIORITY_MULTIPLIER * PRIORITY_ROOM_ITEM
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the zOrder of a wall item by it's position in the room.
|
||||||
|
*
|
||||||
|
* @param {IPosition3D} [position] - The wall item position in the room.
|
||||||
|
* @param {number} [z] - The z value of the layer.
|
||||||
|
* @return {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const wallItem = function (position: IWallPosition, z: number): number {
|
||||||
|
return (position.x + position.y) * COMPARABLE_X_Y + z * COMPARABLE_Z + PRIORITY_MULTIPLIER * PRIORITY_WALL_ITEM;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZOrder variable that manage the z ordering of room objects.
|
||||||
|
*/
|
||||||
|
export const ZOrder = { avatar, tileCursor, floorItem, wallItem, floor, wall };
|
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "dist/types",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": false,
|
||||||
|
"removeComments": true
|
||||||
|
},
|
||||||
|
"include": ["src", "public"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user