From cd9ce8a43025e365cc473b32eb80c97742fac2be Mon Sep 17 00:00:00 2001 From: Aotumuri Date: Sun, 6 Apr 2025 11:01:34 +0900 Subject: [PATCH] add chat display --- src/client/graphics/GameRenderer.ts | 10 ++ src/client/graphics/layers/ChatDisplay.ts | 190 ++++++++++++++++++++++ src/client/index.html | 1 + 3 files changed, 201 insertions(+) create mode 100644 src/client/graphics/layers/ChatDisplay.ts diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index a566c041b..6726dcf4e 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -7,6 +7,7 @@ import { GameStartingModal } from "../gameStartingModal"; import { TransformHandler } from "./TransformHandler"; import { UIState } from "./UIState"; import { BuildMenu } from "./layers/BuildMenu"; +import { ChatDisplay } from "./layers/ChatDisplay"; import { ChatModal } from "./layers/ChatModal"; import { ControlPanel } from "./layers/ControlPanel"; import { EmojiTable } from "./layers/EmojiTable"; @@ -82,6 +83,14 @@ export function createRenderer( eventsDisplay.game = game; eventsDisplay.clientID = clientID; + const chatDisplay = document.querySelector("chat-display") as ChatDisplay; + if (!(eventsDisplay instanceof ChatDisplay)) { + consolex.error("chat display not found"); + } + chatDisplay.eventBus = eventBus; + chatDisplay.game = game; + chatDisplay.clientID = clientID; + const playerInfo = document.querySelector( "player-info-overlay", ) as PlayerInfoOverlay; @@ -136,6 +145,7 @@ export function createRenderer( new UILayer(game, eventBus, clientID, transformHandler), new NameLayer(game, transformHandler, clientID), eventsDisplay, + chatDisplay, buildMenu, new RadialMenu( eventBus, diff --git a/src/client/graphics/layers/ChatDisplay.ts b/src/client/graphics/layers/ChatDisplay.ts new file mode 100644 index 000000000..c8a2c1c56 --- /dev/null +++ b/src/client/graphics/layers/ChatDisplay.ts @@ -0,0 +1,190 @@ +import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import { DirectiveResult } from "lit/directive.js"; +import { unsafeHTML, UnsafeHTMLDirective } from "lit/directives/unsafe-html.js"; +import { EventBus } from "../../../core/EventBus"; +import { MessageType } from "../../../core/game/Game"; +import { + DisplayMessageUpdate, + GameUpdateType, +} from "../../../core/game/GameUpdates"; +import { GameView } from "../../../core/game/GameView"; +import { ClientID } from "../../../core/Schemas"; +import { onlyImages } from "../../../core/Util"; +import { Layer } from "./Layer"; + +interface ChatEvent { + description: string; + unsafeDescription?: boolean; + createdAt: number; + highlight?: boolean; +} + +@customElement("chat-display") +export class ChatDisplay extends LitElement implements Layer { + public eventBus: EventBus; + public game: GameView; + public clientID: ClientID; + + private updateMap = new Map([ + [GameUpdateType.DisplayEvent, (u) => this.onDisplayMessageEvent(u)], + ]); + + @state() private _hidden: boolean = false; + @state() private newEvents: number = 0; + @state() private chatEvents: ChatEvent[] = []; + + private toggleHidden() { + this._hidden = !this._hidden; + if (this._hidden) { + this.newEvents = 0; + } + this.requestUpdate(); + } + + private addEvent(event: ChatEvent) { + this.chatEvents = [...this.chatEvents, event]; + if (this._hidden) { + this.newEvents++; + } + this.requestUpdate(); + } + + private removeEvent(index: number) { + this.chatEvents = [ + ...this.chatEvents.slice(0, index), + ...this.chatEvents.slice(index + 1), + ]; + } + + onDisplayMessageEvent(event: DisplayMessageUpdate) { + if (event.messageType !== MessageType.CHAT) return; + const myPlayer = this.game.playerByClientID(this.clientID); + if ( + event.playerID != null && + (!myPlayer || myPlayer.smallID() !== event.playerID) + ) { + return; + } + + this.addEvent({ + description: event.message, + createdAt: this.game.ticks(), + highlight: true, + unsafeDescription: true, + }); + } + + init() {} + + tick() { + const updates = this.game.updatesSinceLastTick(); + const messages = updates[GameUpdateType.DisplayEvent] as + | DisplayMessageUpdate[] + | undefined; + + console.log("message:", messages); + + if (messages) { + for (const msg of messages) { + if (msg.messageType === MessageType.CHAT) { + const myPlayer = this.game.playerByClientID(this.clientID); + if ( + msg.playerID != null && + (!myPlayer || myPlayer.smallID() !== msg.playerID) + ) { + continue; + } + + this.chatEvents = [ + ...this.chatEvents, + { + description: msg.message, + unsafeDescription: true, + createdAt: this.game.ticks(), + }, + ]; + } + } + } + + if (this.chatEvents.length > 100) { + this.chatEvents = this.chatEvents.slice(-100); + } + } + + private getChatContent( + chat: ChatEvent, + ): string | DirectiveResult { + return chat.unsafeDescription + ? unsafeHTML(onlyImages(chat.description)) + : chat.description; + } + + render() { + return html` +
+
+ +
+ +
+ + + + + + + + ${this.chatEvents.map( + (chat) => html` + + + + `, + )} + +
+ ${this.getChatContent(chat)} +
+
+
+ `; + } + + createRenderRoot() { + return this; + } +} diff --git a/src/client/index.html b/src/client/index.html index 52552a820..7b9299682 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -292,6 +292,7 @@ class="w-full sm:w-2/3 sm:fixed sm:right-0 sm:bottom-0 sm:flex justify-end" style="pointer-events: none" > +