updated EventsDisplay to be lit component

This commit is contained in:
Evan
2024-11-01 20:12:26 -07:00
parent a31a35c038
commit 915359feca
5 changed files with 321 additions and 357 deletions
+10 -1
View File
@@ -40,12 +40,21 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus:
controlPanel.eventBus = eventBus controlPanel.eventBus = eventBus
controlPanel.uiState = uiState controlPanel.uiState = uiState
const eventsDisplay = document.querySelector('events-display') as EventsDisplay;
if (!(eventsDisplay instanceof EventsDisplay)) {
console.error('events display not found')
}
eventsDisplay.eventBus = eventBus
eventsDisplay.game = game
eventsDisplay.clientID = clientID
const layers: Layer[] = [ const layers: Layer[] = [
new TerrainLayer(game), new TerrainLayer(game),
new TerritoryLayer(game, eventBus), new TerritoryLayer(game, eventBus),
new NameLayer(game, game.config().theme(), transformHandler, clientID), new NameLayer(game, game.config().theme(), transformHandler, clientID),
new UILayer(eventBus, game, clientID, transformHandler), new UILayer(eventBus, game, clientID, transformHandler),
new EventsDisplay(eventBus, game, clientID), eventsDisplay,
new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, uiState), new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, uiState),
leaderboard, leaderboard,
controlPanel, controlPanel,
+307 -273
View File
@@ -1,325 +1,359 @@
import { nullable } from "zod"; import { LitElement, html, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { EventBus, GameEvent } from "../../../core/EventBus"; import { EventBus, GameEvent } from "../../../core/EventBus";
import { AllianceExpiredEvent, AllianceRequestEvent, AllianceRequestReplyEvent, AllPlayers, BrokeAllianceEvent, EmojiMessageEvent, Game, Player, PlayerID, TargetPlayerEvent } from "../../../core/game/Game"; import {
AllianceExpiredEvent,
AllianceRequestEvent,
AllianceRequestReplyEvent,
AllPlayers,
BrokeAllianceEvent,
EmojiMessageEvent,
Game,
Player,
PlayerID,
TargetPlayerEvent
} from "../../../core/game/Game";
import { ClientID } from "../../../core/Schemas"; import { ClientID } from "../../../core/Schemas";
import { Layer } from "./Layer"; import { Layer } from "./Layer";
import { SendAllianceReplyIntentEvent } from "../../Transport"; import { SendAllianceReplyIntentEvent } from "../../Transport";
export enum MessageType { export enum MessageType {
SUCCESS, SUCCESS,
INFO, INFO,
WARN, WARN,
ERROR, ERROR,
} }
export class DisplayMessageEvent implements GameEvent { export class DisplayMessageEvent implements GameEvent {
constructor( constructor(
public readonly message: string, public readonly message: string,
public readonly type: MessageType, public readonly type: MessageType,
public readonly playerID: PlayerID | null = null public readonly playerID: PlayerID | null = null
) { } ) { }
} }
interface Event { interface Event {
description: string; description: string;
buttons?: { buttons?: {
text: string text: string;
className: string className: string;
action: () => void action: () => void;
}[]; }[];
type: MessageType; type: MessageType;
highlight?: boolean; highlight?: boolean;
createdAt: number createdAt: number;
onDelete?: () => void onDelete?: () => void;
} }
export class EventsDisplay implements Layer { @customElement('events-display')
private events: Event[]; export class EventsDisplay extends LitElement implements Layer {
private tableContainer: HTMLDivElement; public eventBus: EventBus;
public game: Game;
public clientID: ClientID;
private events: Event[] = [];
constructor(private eventBus: EventBus, private game: Game, private clientID: ClientID) { static styles = css`
const element = document.getElementById("app"); :host {
element.style.zIndex = "1000" display: block;
if (!element) throw new Error(`Container element with id app not found`); position: fixed;
this.events = []; bottom: 10px;
this.createTableContainer() right: 10px;
z-index: 1000;
max-width: 400px;
} }
init() { .events-table {
this.eventBus.on(AllianceRequestEvent, a => this.onAllianceRequestEvent(a)) width: 100%;
this.eventBus.on(AllianceRequestReplyEvent, a => this.onAllianceRequestReplyEvent(a)) border-collapse: collapse;
this.eventBus.on(DisplayMessageEvent, e => this.onDisplayMessageEvent(e)) background-color: rgba(0, 0, 0, 0.7);
this.eventBus.on(BrokeAllianceEvent, e => this.onBrokeAllianceEvent(e)) color: white;
this.eventBus.on(AllianceExpiredEvent, e => this.onAllianceExpiredEvent(e)) box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
this.eventBus.on(TargetPlayerEvent, e => this.onTargetPlayerEvent(e)) font-size: 1.2em;
this.eventBus.on(EmojiMessageEvent, e => this.onEmojiMessageEvent(e))
this.renderTable()
} }
tick() { .events-table th,
let remainingEvents: Event[] = [] .events-table td {
for (const event of this.events) { padding: 15px;
if (this.game.ticks() - event.createdAt < 50) { text-align: left;
remainingEvents.push(event) border-bottom: 1px solid rgba(255, 255, 255, 0.0);
} else if (event.onDelete != null) { z-index: 1000;
event.onDelete()
}
}
if (remainingEvents.length > 5) {
remainingEvents = remainingEvents.slice(-5)
}
let shouldRender = false
if (this.events.length != remainingEvents.length) {
shouldRender = true
}
this.events = remainingEvents
if (shouldRender) {
this.renderTable()
}
} }
private createTableContainer() { .events-table th {
this.tableContainer = document.createElement('div'); background-color: rgba(0, 0, 0, 0.0);
this.tableContainer.id = 'table-container'; font-size: 1.2em;
this.tableContainer.className = 'events-display'; text-transform: uppercase;
this.tableContainer.style.display = "none";
document.body.appendChild(this.tableContainer);
} }
shouldTransform(): boolean { .events-table tr:hover {
return false background-color: rgba(255, 255, 255, 0.0);
} }
onDisplayMessageEvent(event: DisplayMessageEvent) { .btn {
if (event.playerID != null) { display: inline-block;
const myPlayer = this.game.playerByClientID(this.clientID) padding: 8px 16px;
if (myPlayer == null) { margin: 5px 10px 5px 0;
return background-color: #4CAF50;
} color: white;
if (myPlayer == null) { text-decoration: none;
return border-radius: 4px;
} transition: background-color 0.3s;
if (myPlayer.id() != event.playerID) { border: none;
return cursor: pointer;
}
}
this.addEvent({
description: event.message,
createdAt: this.game.ticks(),
highlight: true,
type: event.type,
})
this.renderTable()
} }
onAllianceRequestEvent(event: AllianceRequestEvent): void { .btn:hover {
const myPlayer = this.game.playerByClientID(this.clientID) background-color: #45a049;
if (myPlayer == null) {
return
}
if (event.allianceRequest.recipient() != myPlayer) {
return
}
this.addEvent({
description: `${event.allianceRequest.requestor().name()} requests an alliance!`,
buttons: [
{
text: "Accept",
className: "btn",
action: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, true)),
},
{
text: "Reject",
className: "btn btn-info",
action: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, false)),
}
],
highlight: true,
type: MessageType.INFO,
createdAt: this.game.ticks(),
onDelete: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, false))
});
} }
// TODO: move this to DisplayMessageEvent .btn-info {
onAllianceRequestReplyEvent(event: AllianceRequestReplyEvent) { background-color: #2196F3;
const myPlayer = this.game.playerByClientID(this.clientID)
if (myPlayer == null) {
return
}
if (event.allianceRequest.requestor() != myPlayer) {
return
}
this.addEvent({
description: `${event.allianceRequest.recipient().name()} ${event.accepted ? "accepted" : "rejected"} your alliance request`,
type: event.accepted ? MessageType.SUCCESS : MessageType.ERROR,
highlight: true,
createdAt: this.game.ticks(),
});
} }
onBrokeAllianceEvent(event: BrokeAllianceEvent) { .btn-info:hover {
const myPlayer = this.game.playerByClientID(this.clientID) background-color: #0b7dda;
if (myPlayer == null) {
return
}
if (event.traitor == myPlayer) {
this.addEvent({
description: `You broke your alliance with ${event.betrayed.name()}, making you a TRAITOR`,
type: MessageType.ERROR,
highlight: true,
createdAt: this.game.ticks(),
})
}
if (event.betrayed == myPlayer) {
this.addEvent({
description: `${event.traitor.name()}, broke their alliance with you`,
type: MessageType.ERROR,
highlight: true,
createdAt: this.game.ticks(),
})
}
} }
onAllianceExpiredEvent(event: AllianceExpiredEvent) { .success td { color: rgb(120, 255, 140); }
const myPlayer = this.game.playerByClientID(this.clientID) .info td { color: rgb(230, 230, 230); }
if (myPlayer == null) { .warn td { color: rgb(255, 220, 80) }
return .error td { color: rgb(255, 100, 100); }
}
let other: Player = null @media (max-width: 600px) {
if (event.player1 == myPlayer) { .events-table th,
other = event.player2 .events-table td {
} padding: 10px;
if (event.player2 == myPlayer) { }
other = event.player1
} .btn {
if (other == null) { display: block;
return margin: 5px 0;
} }
if (!myPlayer.isAlive() || !other.isAlive()) {
return
}
this.addEvent({
description: `Your alliance with ${other.name()} expired`,
type: MessageType.WARN,
highlight: true,
createdAt: this.game.ticks(),
})
} }
onTargetPlayerEvent(event: TargetPlayerEvent) { .button-container {
const myPlayer = this.game.playerByClientID(this.clientID) display: flex;
if (myPlayer == null) { gap: 8px;
return flex-wrap: wrap;
}
if (myPlayer.isAlliedWith(event.player)) {
this.addEvent({
description: `${event.player.name()} requests you attack ${event.target.name()}`,
type: MessageType.INFO,
highlight: true,
createdAt: this.game.ticks(),
})
}
} }
onEmojiMessageEvent(event: EmojiMessageEvent) { @media (max-width: 600px) {
const myPlayer = this.game.playerByClientID(this.clientID) .button-container {
if (myPlayer == null) { flex-direction: column;
return }
} }
if (event.message.recipient == myPlayer) { `;
this.addEvent({
description: `${event.message.sender.displayName()}:${event.message.emoji}`, constructor() {
type: MessageType.INFO, super();
highlight: true, this.events = [];
createdAt: this.game.ticks(), }
})
} init() {
if (event.message.sender == myPlayer && event.message.recipient != AllPlayers) { this.eventBus.on(AllianceRequestEvent, a => this.onAllianceRequestEvent(a));
this.addEvent({ this.eventBus.on(AllianceRequestReplyEvent, a => this.onAllianceRequestReplyEvent(a));
description: `Sent ${event.message.recipient.displayName()} ${event.message.emoji}`, this.eventBus.on(DisplayMessageEvent, e => this.onDisplayMessageEvent(e));
type: MessageType.INFO, this.eventBus.on(BrokeAllianceEvent, e => this.onBrokeAllianceEvent(e));
highlight: true, this.eventBus.on(AllianceExpiredEvent, e => this.onAllianceExpiredEvent(e));
createdAt: this.game.ticks(), this.eventBus.on(TargetPlayerEvent, e => this.onTargetPlayerEvent(e));
}) this.eventBus.on(EmojiMessageEvent, e => this.onEmojiMessageEvent(e));
} }
tick() {
let remainingEvents = this.events.filter(event => {
const shouldKeep = this.game.ticks() - event.createdAt < 50;
if (!shouldKeep && event.onDelete) {
event.onDelete();
}
return shouldKeep;
});
if (remainingEvents.length > 5) {
remainingEvents = remainingEvents.slice(-5);
} }
addEvent(event: Event): void { if (this.events.length !== remainingEvents.length) {
this.events.push(event); this.events = remainingEvents;
this.renderTable() this.requestUpdate()
}
}
private addEvent(event: Event) {
this.events = [...this.events, event];
this.requestUpdate()
}
private removeEvent(index: number) {
this.events = [
...this.events.slice(0, index),
...this.events.slice(index + 1)
];
}
shouldTransform(): boolean {
return false;
}
renderLayer(): void { }
onDisplayMessageEvent(event: DisplayMessageEvent) {
const myPlayer = this.game.playerByClientID(this.clientID);
if (event.playerID != null && (!myPlayer || myPlayer.id() !== event.playerID)) {
return;
} }
removeEvent(index: number): void { this.addEvent({
this.events.splice(index, 1); description: event.message,
createdAt: this.game.ticks(),
highlight: true,
type: event.type,
});
}
onAllianceRequestEvent(event: AllianceRequestEvent) {
const myPlayer = this.game.playerByClientID(this.clientID);
if (!myPlayer || event.allianceRequest.recipient() !== myPlayer) {
return;
} }
updateEvent(index: number, event: Event): void { this.addEvent({
this.events[index] = event; description: `${event.allianceRequest.requestor().name()} requests an alliance!`,
} buttons: [
{
renderLayer(): void { } text: "Accept",
className: "btn",
renderTable(): void { action: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, true)),
if (this.events.length === 0) { },
this.tableContainer.innerHTML = ""; {
this.tableContainer.style.display = "none"; text: "Reject",
return; className: "btn btn-info",
action: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, false)),
} }
],
highlight: true,
type: MessageType.INFO,
createdAt: this.game.ticks(),
onDelete: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, false))
});
}
this.tableContainer.style.display = "block"; onAllianceRequestReplyEvent(event: AllianceRequestReplyEvent) {
const myPlayer = this.game.playerByClientID(this.clientID);
if (!myPlayer || event.allianceRequest.requestor() !== myPlayer) {
return;
}
this.addEvent({
description: `${event.allianceRequest.recipient().name()} ${event.accepted ? "accepted" : "rejected"} your alliance request`,
type: event.accepted ? MessageType.SUCCESS : MessageType.ERROR,
highlight: true,
createdAt: this.game.ticks(),
});
}
let tableHtml = ` onBrokeAllianceEvent(event: BrokeAllianceEvent) {
<table class="events-table"> const myPlayer = this.game.playerByClientID(this.clientID);
<tbody> if (!myPlayer) return;
`;
this.events.forEach((event, eventIndex) => { if (event.traitor === myPlayer) {
const typeClass = MessageType[event.type].toLowerCase(); this.addEvent({
tableHtml += ` description: `You broke your alliance with ${event.betrayed.name()}, making you a TRAITOR`,
<tr class="${event.highlight ? 'highlight' : ''} ${typeClass}"> type: MessageType.ERROR,
<td> highlight: true,
${event.description} createdAt: this.game.ticks(),
${event.buttons ? '<div class="button-container">' + event.buttons.map((btn, btnIndex) => });
`<button class="${btn.className}" data-event-index="${eventIndex}" data-button-index="${btnIndex}">${btn.text}</button>` } else if (event.betrayed === myPlayer) {
).join('') + '</div>' : ''} this.addEvent({
</td> description: `${event.traitor.name()}, broke their alliance with you`,
type: MessageType.ERROR,
highlight: true,
createdAt: this.game.ticks(),
});
}
}
onAllianceExpiredEvent(event: AllianceExpiredEvent) {
const myPlayer = this.game.playerByClientID(this.clientID);
if (!myPlayer) return;
const other = event.player1 === myPlayer ? event.player2 : event.player2 === myPlayer ? event.player1 : null;
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
this.addEvent({
description: `Your alliance with ${other.name()} expired`,
type: MessageType.WARN,
highlight: true,
createdAt: this.game.ticks(),
});
}
onTargetPlayerEvent(event: TargetPlayerEvent) {
const myPlayer = this.game.playerByClientID(this.clientID);
if (!myPlayer || !myPlayer.isAlliedWith(event.player)) return;
this.addEvent({
description: `${event.player.name()} requests you attack ${event.target.name()}`,
type: MessageType.INFO,
highlight: true,
createdAt: this.game.ticks(),
});
}
onEmojiMessageEvent(event: EmojiMessageEvent) {
const myPlayer = this.game.playerByClientID(this.clientID);
if (!myPlayer) return;
if (event.message.recipient === myPlayer) {
this.addEvent({
description: `${event.message.sender.displayName()}:${event.message.emoji}`,
type: MessageType.INFO,
highlight: true,
createdAt: this.game.ticks(),
});
} else if (event.message.sender === myPlayer && event.message.recipient !== AllPlayers) {
this.addEvent({
description: `Sent ${event.message.recipient.displayName()} ${event.message.emoji}`,
type: MessageType.INFO,
highlight: true,
createdAt: this.game.ticks(),
});
}
}
render() {
if (this.events.length === 0) {
return html``;
}
return html`
<table class="events-table">
<tbody>
${this.events.map((event, index) => html`
<tr class="${event.highlight ? 'highlight' : ''} ${MessageType[event.type].toLowerCase()}">
<td>
${event.description}
${event.buttons ? html`
<div class="button-container">
${event.buttons.map(btn => html`
<button
class="${btn.className}"
@click=${() => {
btn.action();
this.removeEvent(index);
this.requestUpdate()
}}
>
${btn.text}
</button>
`)}
</div>
` : ''}
</td>
</tr> </tr>
`; `)}
}); </tbody>
</table>
tableHtml += `
</tbody>
</table>
`; `;
this.tableContainer.innerHTML = tableHtml; }
// Add event listeners to buttons
this.tableContainer.querySelectorAll('button').forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const target = e.target as HTMLElement;
const eventIndex = parseInt(target.getAttribute('data-event-index') || '');
const buttonIndex = parseInt(target.getAttribute('data-button-index') || '');
if (!isNaN(eventIndex) && !isNaN(buttonIndex)) {
const event = this.events[eventIndex];
const buttonAction = event.buttons?.[buttonIndex]?.action;
if (buttonAction) {
buttonAction();
this.removeEvent(eventIndex);
this.renderTable();
}
}
});
});
}
} }
+1
View File
@@ -73,6 +73,7 @@
<emoji-table></emoji-table> <emoji-table></emoji-table>
<leader-board></leader-board> <leader-board></leader-board>
<control-panel></control-panel> <control-panel></control-panel>
<events-display></events-display>
<script> <script>
window.addEventListener('DOMContentLoaded', (event) => { window.addEventListener('DOMContentLoaded', (event) => {
+1 -63
View File
@@ -282,66 +282,4 @@ h3 {
align-items: center; align-items: center;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
} }
/* Events display */
.events-display {
position: fixed;
bottom: 0;
right: 0;
z-index: 1000;
background-color: rgba(0, 0, 0, 0.7);
padding: 10px;
box-sizing: border-box;
max-height: 50vh;
overflow-y: auto;
width: 500px;
}
@media (max-width: 768px) {
.events-display {
left: 0;
width: 100%;
}
}
.events-table {
width: 100%;
border-collapse: collapse;
font-size: 16px;
}
.events-table td {
padding: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
font-size: 18px;
}
.events-table button {
margin-right: 12px;
margin-bottom: 6px;
padding: 8px 16px;
font-size: 16px;
}
.events-table .highlight {
background-color: rgba(255, 255, 0, 0.1);
}
.events-table .success {
color: #66FF66;
}
.events-table .info {
color: white;
}
.events-table .warn {
color: orange;
}
.events-table .error {
color: red;
}
+2 -20
View File
@@ -1,5 +1,5 @@
import {PlayerInfo} from "../game/Game"; import { PlayerInfo } from "../game/Game";
import {DefaultConfig} from "./DefaultConfig"; import { DefaultConfig } from "./DefaultConfig";
export const devConfig = new class extends DefaultConfig { export const devConfig = new class extends DefaultConfig {
percentageTilesOwnedToWin(): number { percentageTilesOwnedToWin(): number {
@@ -22,22 +22,4 @@ export const devConfig = new class extends DefaultConfig {
return 400 return 400
} }
// allianceDuration(): Tick {
// return 10 * 10
// }
// startTroops(playerInfo: PlayerInfo): number {
// // if (playerInfo.isBot) {
// // return 5000
// // }
// return 50000
// }
// troopAdditionRate(player: Player): number {
// if (player.isBot()) {
// return 1000
// } else {
// return 1000000
// }
// }
} }