318 lines
8.4 KiB
TypeScript
318 lines
8.4 KiB
TypeScript
import { Socket } from 'socket.io-client';
|
|
import { Color } from 'three';
|
|
import { isMobileOrTablet } from '../common/helper';
|
|
import { CharacterPacket, CompositePacket } from '../common/types/packet';
|
|
import { IcyNetUser } from '../common/types/user';
|
|
import { ThirdPersonCamera } from './object/camera';
|
|
import { Chat } from './object/chat';
|
|
import { Joystick } from './object/joystick';
|
|
import { VideoPlayer } from './object/other/video-player';
|
|
import { Player } from './object/player';
|
|
import { PlayerEntity } from './object/player-entity';
|
|
import modelLoaderInstance from './object/pony-loader';
|
|
import { ClientWorld } from './object/world/ClientWorld';
|
|
import { ClientWorldLoader } from './object/world/ClientWorldLoader';
|
|
import { Renderer } from './renderer';
|
|
|
|
export class Game {
|
|
public players: (Player | PlayerEntity)[] = [];
|
|
public player!: Player;
|
|
public me!: IcyNetUser;
|
|
public thirdPersonCamera!: ThirdPersonCamera;
|
|
public joystick!: Joystick;
|
|
public chat!: Chat;
|
|
private character: CharacterPacket = {};
|
|
private party: string[] = [];
|
|
|
|
public world = new ClientWorld(new ClientWorldLoader());
|
|
public renderer = new Renderer();
|
|
|
|
private videoTest = new VideoPlayer(24, 12);
|
|
|
|
constructor(public socket: Socket) {}
|
|
|
|
async initialize(): Promise<void> {
|
|
await modelLoaderInstance.loadPonyModel();
|
|
await this.world.initialize();
|
|
|
|
this.renderer.initialize();
|
|
this.bindSocket();
|
|
this.chat = new Chat();
|
|
this.chat.initialize();
|
|
this.socket.connect();
|
|
|
|
// experimental
|
|
this.videoTest.initialize();
|
|
this.videoTest.mesh.position.set(0, 6, -20);
|
|
this.renderer.scene.add(this.videoTest.mesh);
|
|
this.party = (localStorage.getItem('party')?.split('|') || []).filter(
|
|
(item) => item,
|
|
);
|
|
|
|
const colorGet = localStorage.getItem('color');
|
|
if (colorGet) {
|
|
this.character.color = colorGet;
|
|
}
|
|
// end of
|
|
|
|
this.chat.registerSendFunction((message) => {
|
|
this.socket.emit('chat-send', message);
|
|
});
|
|
|
|
this.renderer.registerUpdateFunction((dt: number) => {
|
|
this.update(dt);
|
|
});
|
|
|
|
this.renderer.scene.add(this.world.world);
|
|
}
|
|
|
|
public dispose() {
|
|
this.players.forEach((player) => {
|
|
this.renderer.scene.remove(player.container);
|
|
});
|
|
|
|
this.thirdPersonCamera?.dispose();
|
|
this.joystick?.dispose();
|
|
|
|
this.players.length = 0;
|
|
}
|
|
|
|
public update(dt: number) {
|
|
this.players.forEach((player) => player.update(dt));
|
|
this.player?.createPacket(this.socket);
|
|
this.thirdPersonCamera?.update(dt);
|
|
this.joystick?.update(dt);
|
|
|
|
this.world?.update(dt);
|
|
}
|
|
|
|
bindSocket() {
|
|
this.socket.on('connect', () => {
|
|
this.dispose();
|
|
console.log('connected');
|
|
});
|
|
|
|
this.socket.on('me', (user) => {
|
|
console.log(user);
|
|
|
|
if (!user) {
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
|
|
this.me = user;
|
|
|
|
this.chat.addMessage(
|
|
`Welcome to Icy3D World Experiment, ${user.display_name}!`,
|
|
null,
|
|
{
|
|
color: '#fbff4e',
|
|
},
|
|
);
|
|
|
|
const player = Player.fromUser(user, this.renderer.scene);
|
|
player.setCharacter(this.character);
|
|
player.setHeightSource(this.world);
|
|
this.players.push(player);
|
|
this.player = player;
|
|
this.thirdPersonCamera = new ThirdPersonCamera(
|
|
this.renderer.camera,
|
|
this.player.container,
|
|
this.renderer.canvas,
|
|
);
|
|
this.thirdPersonCamera.initialize();
|
|
|
|
this.joystick = new Joystick(player);
|
|
this.joystick.initialize();
|
|
|
|
if (isMobileOrTablet()) {
|
|
this.joystick.show();
|
|
}
|
|
|
|
this.socket.emit('character', this.character);
|
|
});
|
|
|
|
this.socket.on('playerjoin', (user) => {
|
|
if (user.id === this.me.id) {
|
|
return;
|
|
}
|
|
|
|
const newplayer = PlayerEntity.fromUser(user, this.renderer.scene);
|
|
newplayer.setHeightSource(this.world);
|
|
this.chat.addMessage(`${user.display_name} has joined the game.`, null, {
|
|
color: '#fbff4e',
|
|
});
|
|
this.players.push(newplayer);
|
|
});
|
|
|
|
this.socket.on('playerleave', (user) => {
|
|
const findPlayer = this.players.find((item) => item.user.id === user.id);
|
|
if (findPlayer) {
|
|
this.chat.addMessage(`${user.display_name} has left the game.`, null, {
|
|
color: '#fbff4e',
|
|
});
|
|
|
|
this.renderer.scene.remove(findPlayer.container);
|
|
findPlayer.dispose();
|
|
this.players.splice(this.players.indexOf(findPlayer), 1);
|
|
}
|
|
});
|
|
|
|
this.socket.on('players', (list: CompositePacket[]) => {
|
|
list.forEach((player) => {
|
|
if (player.id === this.me.id) {
|
|
return;
|
|
}
|
|
|
|
const newplayer = PlayerEntity.fromUser(player, this.renderer.scene);
|
|
newplayer.setHeightSource(this.world);
|
|
newplayer.addUncommittedChanges(player);
|
|
this.players.push(newplayer);
|
|
});
|
|
|
|
this.chat.addMessage(
|
|
`List of players: ${list.map((user) => user.display_name).join(', ')}`,
|
|
null,
|
|
{
|
|
color: '#fbff4e',
|
|
},
|
|
);
|
|
});
|
|
|
|
this.socket.on('playerupdate', (data) => {
|
|
data.forEach((item) => {
|
|
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);
|
|
}
|
|
});
|
|
});
|
|
|
|
this.socket.on('playercharacter', (data) => {
|
|
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);
|
|
}
|
|
});
|
|
|
|
this.socket.on('chat', (event) => {
|
|
const player = this.players.find(
|
|
(item) => item.user.id === event.sender.id,
|
|
);
|
|
|
|
if (player && player.user.id !== this.me.id) {
|
|
(player as PlayerEntity).addChat(event.message);
|
|
}
|
|
|
|
this.chat.addMessage(event.message, event.sender.display_name);
|
|
|
|
// experimental stuff
|
|
this.experimentalPlayerCmd(event.message, event.sender);
|
|
});
|
|
|
|
this.socket.on('disconnect', () => {
|
|
this.chat.addMessage(
|
|
`Disconnected from the server, reconnecting..`,
|
|
null,
|
|
{
|
|
color: '#ff0000',
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
private experimentalPlayerCmd(message: string, sender: IcyNetUser) {
|
|
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;
|
|
}
|
|
|
|
this.player.setColor(color);
|
|
this.socket.emit('character', { color });
|
|
localStorage.setItem('color', color);
|
|
}
|
|
|
|
if (message.startsWith('!party')) {
|
|
const array = message.split(' ');
|
|
const name = array.slice(2).join(' ');
|
|
|
|
if (array[1] === 'join') {
|
|
this.party.push(name);
|
|
this.chat.addMessage(`Joined party of user "${name}".`);
|
|
}
|
|
|
|
if (array[1] === 'leave') {
|
|
this.party.splice(this.party.indexOf(name), 1);
|
|
this.chat.addMessage(`Left party of user "${name}".`);
|
|
}
|
|
|
|
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('|'));
|
|
}
|
|
}
|
|
|
|
if (
|
|
!(
|
|
sender.display_name === this.me.display_name ||
|
|
this.party.includes(sender.display_name)
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (message.startsWith('!play')) {
|
|
const [cmd, src] = message.split(' ');
|
|
if (src) {
|
|
this.videoTest.setSource(src, true);
|
|
} else {
|
|
this.videoTest.play();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (message.startsWith('!stop') || message.startsWith('!pause')) {
|
|
this.videoTest.stop();
|
|
return;
|
|
}
|
|
|
|
if (message.startsWith('!volume')) {
|
|
const [cmd, vol] = message.split(' ');
|
|
if (!vol) {
|
|
this.chat.addMessage(
|
|
`Current volume: ${Math.floor(this.videoTest.video.volume * 100)}`,
|
|
);
|
|
return;
|
|
}
|
|
this.videoTest.setVolume(parseInt(vol.replace('%', ''), 10));
|
|
}
|
|
}
|
|
}
|