icy3dw/src/client/game.ts

441 lines
12 KiB
TypeScript
Raw Normal View History

2022-04-09 11:29:54 +00:00
import { Socket } from 'socket.io-client';
2022-04-14 17:37:56 +00:00
import { Color, DoubleSide, MeshBasicMaterial, Vector3 } from 'three';
2022-04-17 13:00:13 +00:00
import { isMobileOrTablet } from '../common/helper';
2022-05-15 10:54:15 +00:00
import {
CharacterPacket,
CompositePacket,
PositionUpdatePacket,
} from '../common/types/packet';
2022-04-09 11:29:54 +00:00
import { IcyNetUser } from '../common/types/user';
import { Chat } from './object/chat';
import { Joystick } from './object/joystick';
2022-04-12 15:24:05 +00:00
import { PonyModelLoader } from './object/resource/pony-loader';
import { PonyEyes } from './object/model/eyes';
import { CubeMap } from './object/resource/cubemap';
2022-04-09 18:53:02 +00:00
import { VideoPlayer } from './object/other/video-player';
2022-04-09 11:29:54 +00:00
import { Player } from './object/player';
import { PlayerEntity } from './object/player-entity';
2022-04-09 18:03:49 +00:00
import { Renderer } from './renderer';
2022-04-14 17:37:56 +00:00
import { Grass } from './object/model/grass';
import { BaseTexture } from './object/resource/texture';
2022-04-16 06:50:46 +00:00
import { ClientWorld } from './object/world/client-world';
import { ClientWorldManifest } from './object/world/client-world-manifest';
import { ClientWorldLoader } from './object/world/client-world-loader';
import { LoadingManagerWrapper } from './object/resource/loading-manager';
2022-05-15 10:54:15 +00:00
import {
ClientToServerEvents,
ServerToClientEvents,
} from '../common/types/socket';
2022-04-09 11:29:54 +00:00
export class Game {
public players: (Player | PlayerEntity)[] = [];
public player!: Player;
public me!: IcyNetUser;
public joystick!: Joystick;
public chat!: Chat;
2022-04-16 06:59:03 +00:00
private _loading = LoadingManagerWrapper.getInstance();
2022-04-10 06:52:50 +00:00
private character: CharacterPacket = {};
2022-04-09 19:31:36 +00:00
private party: string[] = [];
2022-04-16 07:44:50 +00:00
private _locked = false;
2022-04-09 11:29:54 +00:00
2022-04-11 18:11:31 +00:00
public world!: ClientWorld;
2022-04-09 18:03:49 +00:00
public renderer = new Renderer();
2022-04-10 08:33:52 +00:00
private videoTest = new VideoPlayer(24, 12);
2022-04-09 18:53:02 +00:00
2022-05-15 10:54:15 +00:00
constructor(
public socket: Socket<ServerToClientEvents, ClientToServerEvents>,
) {}
2022-04-09 11:29:54 +00:00
async initialize(): Promise<void> {
2022-04-16 06:59:03 +00:00
this._loading.initialize();
2022-04-16 06:50:46 +00:00
2022-04-11 18:11:31 +00:00
const worldManifest = await ClientWorldManifest.loadManifest();
this.world = new ClientWorld(new ClientWorldLoader(), worldManifest);
const cube = await CubeMap.load('/assets/skybox/default');
2022-04-16 06:59:03 +00:00
const grasstex = await BaseTexture.load(
'/assets/terrain/decoration/grass01.png',
);
const flowertex = await BaseTexture.load(
'/assets/terrain/decoration/flowers02.png',
);
const flowertex2 = await BaseTexture.load(
'/assets/terrain/decoration/flowers01.png',
);
2022-04-11 18:11:31 +00:00
2022-04-16 10:21:07 +00:00
await PonyModelLoader.getInstance().initialize();
2022-04-11 19:43:45 +00:00
await PonyEyes.getInstance().initialize();
2022-04-11 17:00:10 +00:00
await this.world.initialize();
2022-04-09 18:03:49 +00:00
this.renderer.initialize();
2022-04-09 11:29:54 +00:00
this.bindSocket();
this.chat = new Chat();
this.chat.initialize();
this.socket.connect();
2022-04-09 18:03:49 +00:00
2022-04-09 18:53:02 +00:00
// experimental
this.videoTest.initialize();
2022-04-11 18:30:37 +00:00
this.videoTest.mesh.position.set(0, 22, 20);
2022-04-11 18:11:31 +00:00
this.videoTest.mesh.rotateY(Math.PI / 2);
2022-04-09 18:53:02 +00:00
this.renderer.scene.add(this.videoTest.mesh);
2022-04-09 19:44:00 +00:00
this.party = (localStorage.getItem('party')?.split('|') || []).filter(
(item) => item,
);
2022-04-09 18:53:02 +00:00
// end of
2022-04-09 11:29:54 +00:00
this.chat.registerSendFunction((message) => {
2022-05-15 10:54:15 +00:00
this.socket.emit('set.chat', message);
2022-04-09 11:29:54 +00:00
});
2022-04-09 18:03:49 +00:00
2022-04-10 08:33:52 +00:00
this.renderer.registerUpdateFunction((dt: number) => {
2022-04-09 18:03:49 +00:00
this.update(dt);
});
2022-04-11 17:00:10 +00:00
this.renderer.scene.add(this.world.world);
2022-04-11 18:11:31 +00:00
this.renderer.scene.background = cube.texture;
2022-04-14 17:37:56 +00:00
// test
const grassfield = Grass.getInstance().createGrassPatch(
new Vector3(10, 0, 10),
8,
0.5,
8,
2022-04-15 11:33:27 +00:00
this.world.getHeight.bind(this.world),
2022-04-14 17:37:56 +00:00
);
const flowerfield = Grass.getInstance().createGrassPatch(
new Vector3(10, 0, 10),
8,
4,
3,
2022-04-15 11:33:27 +00:00
this.world.getHeight.bind(this.world),
2022-04-14 17:37:56 +00:00
);
const flowerfield2 = Grass.getInstance().createGrassPatch(
new Vector3(8, 0, 8),
4,
4,
3,
2022-04-15 11:33:27 +00:00
this.world.getHeight.bind(this.world),
2022-04-14 17:37:56 +00:00
);
const grass = Grass.getInstance().createInstance(
grassfield,
new MeshBasicMaterial({
side: DoubleSide,
map: grasstex.texture,
alphaTest: 0.7,
}),
);
const flowers = Grass.getInstance().createInstance(
flowerfield,
new MeshBasicMaterial({
side: DoubleSide,
map: flowertex.texture,
alphaTest: 0.7,
}),
);
const flowers2 = Grass.getInstance().createInstance(
flowerfield2,
new MeshBasicMaterial({
side: DoubleSide,
map: flowertex2.texture,
alphaTest: 0.7,
}),
);
this.renderer.scene.add(grass);
this.renderer.scene.add(flowers);
this.renderer.scene.add(flowers2);
2022-04-16 06:59:03 +00:00
this._loading.isConnecting();
2022-04-16 07:44:50 +00:00
window.addEventListener('keyup', (ev) => {
if (ev.key === 'Shift') {
this.toggleCamLock();
}
});
}
public toggleCamLock(): boolean {
this._locked = !this._locked;
this.player?.setCameraLock(this._locked);
return this._locked;
2022-04-09 11:29:54 +00:00
}
public dispose() {
this.players.forEach((player) => {
2022-04-16 10:21:07 +00:00
player.dispose();
2022-04-09 18:03:49 +00:00
this.renderer.scene.remove(player.container);
2022-04-09 11:29:54 +00:00
});
this.joystick?.dispose();
this.players.length = 0;
}
public update(dt: number) {
this.players.forEach((player) => player.update(dt));
this.player?.createPacket(this.socket);
this.joystick?.update(dt);
2022-04-11 17:00:10 +00:00
2022-04-12 18:59:06 +00:00
this.player && this.world?.update(this.player.container.position);
2022-04-09 11:29:54 +00:00
}
bindSocket() {
this.socket.on('connect', () => {
this.dispose();
2022-04-16 06:59:03 +00:00
this._loading.connected();
2022-04-09 11:29:54 +00:00
console.log('connected');
});
2022-04-16 10:21:07 +00:00
this.socket.on('error.duplicate', () => {
this._loading.showError(
'Error: You are already connected on another device!',
);
});
2022-05-15 10:54:15 +00:00
this.socket.on('set.me', (user) => {
2022-04-09 11:29:54 +00:00
if (!user) {
2022-04-16 10:21:07 +00:00
this._loading.showError('Error: You need to log in!');
2022-04-09 11:29:54 +00:00
window.location.href = '/login';
return;
}
this.me = user;
2022-04-10 12:54:29 +00:00
this.chat.addMessage(
2022-04-11 17:00:10 +00:00
`Welcome to Icy3D World Experiment, ${user.display_name}!`,
2022-11-27 14:38:51 +00:00
undefined,
2022-04-10 12:54:29 +00:00
{
color: '#fbff4e',
},
);
2022-04-09 18:03:49 +00:00
const player = Player.fromUser(user, this.renderer.scene);
2022-04-16 10:21:07 +00:00
user.character && player.setCharacter(user.character);
user.position && player.container.position.fromArray(user.position);
user.rotation && player.container.rotation.fromArray(user.rotation);
2022-04-11 17:00:10 +00:00
player.setHeightSource(this.world);
2022-04-17 13:00:13 +00:00
player.setCamera(this.renderer);
2022-04-16 10:21:07 +00:00
2022-04-09 11:29:54 +00:00
this.players.push(player);
this.player = player;
this.joystick = new Joystick(player);
this.joystick.initialize();
2022-04-16 07:44:50 +00:00
this.joystick.addButton(-60, -20, 'LOCK', () => this.toggleCamLock());
this.joystick.addButton(135, -20, 'JUMP', () => this.player.jump());
2022-04-09 11:29:54 +00:00
if (isMobileOrTablet()) {
this.joystick.show();
}
2022-04-10 06:52:50 +00:00
2022-05-15 10:54:15 +00:00
this.socket.emit('set.character', this.character);
2022-04-09 11:29:54 +00:00
});
2022-05-15 10:54:15 +00:00
this.socket.on('player.join', (user) => {
2022-04-09 11:29:54 +00:00
if (user.id === this.me.id) {
return;
}
2022-04-09 18:03:49 +00:00
const newplayer = PlayerEntity.fromUser(user, this.renderer.scene);
2022-04-11 17:00:10 +00:00
newplayer.setHeightSource(this.world);
2022-11-27 14:38:51 +00:00
this.chat.addMessage(`${user.display_name} has joined the game.`, undefined, {
2022-04-10 12:54:29 +00:00
color: '#fbff4e',
});
2022-04-09 11:29:54 +00:00
this.players.push(newplayer);
});
2022-05-15 10:54:15 +00:00
this.socket.on('player.leave', (user) => {
2022-04-09 11:29:54 +00:00
const findPlayer = this.players.find((item) => item.user.id === user.id);
if (findPlayer) {
2022-11-27 14:38:51 +00:00
this.chat.addMessage(`${user.display_name} has left the game.`, undefined, {
2022-04-10 12:54:29 +00:00
color: '#fbff4e',
});
2022-04-09 18:03:49 +00:00
this.renderer.scene.remove(findPlayer.container);
2022-04-10 06:52:50 +00:00
findPlayer.dispose();
2022-04-09 11:29:54 +00:00
this.players.splice(this.players.indexOf(findPlayer), 1);
}
});
2022-11-27 14:38:51 +00:00
this.socket.on('player.list', (list: Partial<CompositePacket>[]) => {
list?.forEach((player) => {
2022-04-09 11:29:54 +00:00
if (player.id === this.me.id) {
return;
}
2022-11-27 14:38:51 +00:00
const newplayer = PlayerEntity.fromUser(player as CompositePacket, this.renderer.scene);
2022-04-11 17:00:10 +00:00
newplayer.setHeightSource(this.world);
2022-04-09 11:29:54 +00:00
newplayer.addUncommittedChanges(player);
this.players.push(newplayer);
});
2022-04-10 12:54:29 +00:00
this.chat.addMessage(
2022-11-27 14:38:51 +00:00
`List of players: ${(list || []).map((user) => user.display_name).join(', ')}`,
undefined,
2022-04-10 12:54:29 +00:00
{
color: '#fbff4e',
},
);
2022-04-09 11:29:54 +00:00
});
2022-05-15 10:54:15 +00:00
this.socket.on('player.update', (data) => {
2022-11-27 14:38:51 +00:00
data.forEach((item?: PositionUpdatePacket) => {
if (!item) return;
2022-04-09 14:49:30 +00:00
const player = this.players.find(
(player) => player.user.id === item.id,
);
if (
player &&
player instanceof PlayerEntity &&
player.user.id !== this.me.id
) {
player.addUncommittedChanges(item);
}
});
2022-04-09 11:29:54 +00:00
});
2022-05-15 10:54:15 +00:00
this.socket.on('player.character', (data) => {
2022-04-10 06:52:50 +00:00
const player = this.players.find((player) => player.user.id === data.id);
if (
player &&
player instanceof PlayerEntity &&
player.user.id !== this.me.id
) {
player.setCharacter(data);
}
});
2022-05-15 10:54:15 +00:00
this.socket.on('player.chat', (event) => {
2022-04-09 12:57:20 +00:00
const player = this.players.find(
(item) => item.user.id === event.sender.id,
);
if (player && player.user.id !== this.me.id) {
2022-04-09 15:17:07 +00:00
(player as PlayerEntity).addChat(event.message);
2022-04-09 12:57:20 +00:00
}
2022-04-09 19:53:14 +00:00
this.chat.addMessage(event.message, event.sender.display_name);
// experimental stuff
2022-04-10 06:03:31 +00:00
this.experimentalPlayerCmd(event.message, event.sender);
2022-04-09 11:29:54 +00:00
});
2022-04-10 12:54:29 +00:00
this.socket.on('disconnect', () => {
this.chat.addMessage(
`Disconnected from the server, reconnecting..`,
2022-11-27 14:38:51 +00:00
undefined,
2022-04-10 12:54:29 +00:00
{
color: '#ff0000',
},
);
});
2022-04-09 11:29:54 +00:00
}
2022-04-09 19:31:36 +00:00
2022-05-15 10:54:15 +00:00
private experimentalPlayerCmd(message: string, sender: Partial<IcyNetUser>) {
2022-04-10 06:52:50 +00:00
if (this.me.id === sender.id) {
if (message.startsWith('!color')) {
const [cmd, color] = message.split(' ');
try {
const colorr = new Color(color);
if (!colorr) {
throw 'invalid';
}
} catch (e: any) {
this.chat.addMessage('Invalid color.');
return;
}
2022-04-10 06:03:31 +00:00
2022-04-11 19:43:45 +00:00
this.character = { ...this.character, color };
2022-04-10 06:52:50 +00:00
this.player.setColor(color);
2022-05-15 10:54:15 +00:00
this.socket.emit('set.character', this.character);
2022-04-11 19:43:45 +00:00
}
if (message.startsWith('!eyecolor')) {
const [cmd, eyeColor] = message.split(' ');
try {
const colorr = new Color(eyeColor);
if (!colorr) {
throw 'invalid';
}
} catch (e: any) {
this.chat.addMessage('Invalid color.');
return;
}
this.character = { ...this.character, eyeColor };
this.player.setEyeColor(eyeColor);
2022-05-15 10:54:15 +00:00
this.socket.emit('set.character', this.character);
localStorage.setItem('set.character', JSON.stringify(this.character));
2022-04-10 06:03:31 +00:00
}
2022-04-10 06:52:50 +00:00
if (message.startsWith('!party')) {
const array = message.split(' ');
const name = array.slice(2).join(' ');
2022-04-10 06:03:31 +00:00
2022-04-10 06:52:50 +00:00
if (array[1] === 'join') {
this.party.push(name);
this.chat.addMessage(`Joined party of user "${name}".`);
}
2022-04-10 06:03:31 +00:00
2022-04-10 06:52:50 +00:00
if (array[1] === 'leave') {
this.party.splice(this.party.indexOf(name), 1);
this.chat.addMessage(`Left party of user "${name}".`);
}
2022-04-10 06:03:31 +00:00
2022-04-10 06:52:50 +00:00
if (array[1] === 'clear') {
this.party.length = 0;
this.chat.addMessage('Cleared party list.');
}
if (array[1] === 'list') {
this.chat.addMessage(
`You have joined the watch party of: ${this.party.join(', ')}`,
);
}
localStorage.setItem('party', this.party.join('|'));
}
2022-04-10 06:03:31 +00:00
}
if (
2022-11-27 14:38:51 +00:00
!(sender.display_name &&
(sender.display_name === this.me.display_name ||
this.party.includes(sender.display_name))
2022-04-10 06:03:31 +00:00
)
) {
2022-04-09 19:31:36 +00:00
return;
}
if (message.startsWith('!play')) {
const [cmd, src] = message.split(' ');
if (src) {
this.videoTest.setSource(src, true);
} else {
this.videoTest.play();
}
2022-04-09 19:53:14 +00:00
return;
2022-04-09 19:31:36 +00:00
}
if (message.startsWith('!stop') || message.startsWith('!pause')) {
this.videoTest.stop();
2022-04-09 19:53:14 +00:00
return;
2022-04-09 19:31:36 +00:00
}
if (message.startsWith('!volume')) {
const [cmd, vol] = message.split(' ');
2022-04-09 19:53:14 +00:00
if (!vol) {
this.chat.addMessage(
`Current volume: ${Math.floor(this.videoTest.video.volume * 100)}`,
);
return;
}
2022-04-09 19:31:36 +00:00
this.videoTest.setVolume(parseInt(vol.replace('%', ''), 10));
}
}
2022-04-09 11:29:54 +00:00
}