mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
send fire emoji to other players
This commit is contained in:
@@ -157,17 +157,17 @@
|
||||
* BUG: boat icon appears when click inland DONE 10/2/2024
|
||||
* Auto deploy to dev on commit DONE 10/2/2024
|
||||
* add emoji button
|
||||
* make disabled icon crossed out icon
|
||||
* donate troops button
|
||||
* Make fake humans spawn by their country
|
||||
* BUG: double tap zooms on mobile
|
||||
* fake humans target enemies
|
||||
* make year clock
|
||||
* create private lobby menu
|
||||
* block user inputs if too far behind server
|
||||
* BUG: FakeHuman don't be enemy if don't share border (or send boat)
|
||||
* store cookies
|
||||
* BUG: names dissapear on bottom of screen
|
||||
* UI: leader board
|
||||
* UI: boats
|
||||
* UI: current attacks
|
||||
* Load terrain dataImage in background
|
||||
* BUG: half encircle Antartica causes capture
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+17
-1
@@ -1,5 +1,5 @@
|
||||
import {EventBus, GameEvent} from "../core/EventBus"
|
||||
import {AllianceRequest, Cell, Game, Player, PlayerID, PlayerType} from "../core/game/Game"
|
||||
import {AllianceRequest, AllPlayers, Cell, Emoji, Player, PlayerID, PlayerType} from "../core/game/Game"
|
||||
import {ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema} from "../core/Schemas"
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export class SendAllianceRequestIntentEvent implements GameEvent {
|
||||
public readonly recipient: Player
|
||||
) { }
|
||||
}
|
||||
|
||||
export class SendBreakAllianceIntentEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly requestor: Player,
|
||||
@@ -49,6 +50,10 @@ export class SendTargetPlayerIntentEvent implements GameEvent {
|
||||
) { }
|
||||
}
|
||||
|
||||
export class SendEmojiIntentEvent implements GameEvent {
|
||||
constructor(public readonly recipient: Player | typeof AllPlayers, public readonly emoji: Emoji) { }
|
||||
}
|
||||
|
||||
export class Transport {
|
||||
|
||||
public onconnect: () => {}
|
||||
@@ -68,6 +73,7 @@ export class Transport {
|
||||
this.eventBus.on(SendAttackIntentEvent, (e) => this.onSendAttackIntent(e))
|
||||
this.eventBus.on(SendBoatAttackIntentEvent, (e) => this.onSendBoatAttackIntent(e))
|
||||
this.eventBus.on(SendTargetPlayerIntentEvent, (e) => this.onSendTargetPlayerIntent(e))
|
||||
this.eventBus.on(SendEmojiIntentEvent, (e) => this.onSendEmojiIntent(e))
|
||||
}
|
||||
|
||||
connect(onconnect: () => void, onmessage: (message: ServerMessage) => void, isActive: () => boolean) {
|
||||
@@ -200,6 +206,16 @@ export class Transport {
|
||||
})
|
||||
}
|
||||
|
||||
private onSendEmojiIntent(event: SendEmojiIntentEvent) {
|
||||
this.sendIntent({
|
||||
type: "emoji",
|
||||
clientID: this.clientID,
|
||||
sender: this.playerID,
|
||||
recipient: event.recipient == AllPlayers ? AllPlayers : event.recipient.id(),
|
||||
emoji: event.emoji
|
||||
})
|
||||
}
|
||||
|
||||
private sendIntent(intent: Intent) {
|
||||
if (this.socket.readyState === WebSocket.OPEN) {
|
||||
const msg = ClientIntentMessageSchema.parse({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {nullable} from "zod";
|
||||
import {EventBus, GameEvent} from "../../../core/EventBus";
|
||||
import {AllianceExpiredEvent, AllianceRequestEvent, AllianceRequestReplyEvent, BrokeAllianceEvent, Game, Player, PlayerID, TargetPlayerEvent} from "../../../core/game/Game";
|
||||
import {AllianceExpiredEvent, AllianceRequestEvent, AllianceRequestReplyEvent, BrokeAllianceEvent, EmojiMessageEvent, Game, Player, PlayerID, TargetPlayerEvent} from "../../../core/game/Game";
|
||||
import {ClientID} from "../../../core/Schemas";
|
||||
import {Layer} from "./Layer";
|
||||
import {SendAllianceReplyIntentEvent} from "../../Transport";
|
||||
@@ -53,6 +53,7 @@ export class EventsDisplay implements Layer {
|
||||
this.eventBus.on(BrokeAllianceEvent, e => this.onBrokeAllianceEvent(e))
|
||||
this.eventBus.on(AllianceExpiredEvent, e => this.onAllianceExpiredEvent(e))
|
||||
this.eventBus.on(TargetPlayerEvent, e => this.onTargetPlayerEvent(e))
|
||||
this.eventBus.on(EmojiMessageEvent, e => this.onEmojiMessageEvent(e))
|
||||
this.renderTable()
|
||||
}
|
||||
|
||||
@@ -226,6 +227,21 @@ export class EventsDisplay implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
onEmojiMessageEvent(event: EmojiMessageEvent) {
|
||||
const myPlayer = this.game.playerByClientID(this.clientID)
|
||||
if (myPlayer == null) {
|
||||
return
|
||||
}
|
||||
if (event.message.recipient == myPlayer) {
|
||||
this.addEvent({
|
||||
description: `${event.message.sender.name()}:${event.message.emoji}`,
|
||||
type: MessageType.INFO,
|
||||
highlight: true,
|
||||
createdAt: this.game.ticks(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addEvent(event: Event): void {
|
||||
this.events.push(event);
|
||||
this.renderTable()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {EventBus} from "../../../core/EventBus";
|
||||
import {Cell, Game, Player, PlayerID} from "../../../core/game/Game";
|
||||
import {AllPlayers, Cell, Emoji, Game, Player, PlayerID} from "../../../core/game/Game";
|
||||
import {ClientID} from "../../../core/Schemas";
|
||||
import {and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore} from "../../../core/Util";
|
||||
import {ContextMenuEvent, MouseUpEvent} from "../../InputHandler";
|
||||
import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../Transport";
|
||||
import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../Transport";
|
||||
import {TransformHandler} from "../TransformHandler";
|
||||
import {MessageType} from "./EventsDisplay";
|
||||
import {Layer} from "./Layer";
|
||||
@@ -13,20 +13,14 @@ import allianceIcon from '../../../../resources/images/AllianceIconWhite.png';
|
||||
import boatIcon from '../../../../resources/images/BoatIconWhite.png';
|
||||
import swordIcon from '../../../../resources/images/SwordIconWhite.png';
|
||||
import targetIcon from '../../../../resources/images/TargetIconWhite.png';
|
||||
import emojiIcon from '../../../../resources/images/EmojiIconWhite.png';
|
||||
|
||||
|
||||
enum RadialElement {
|
||||
RequestAlliance,
|
||||
BreakAlliance,
|
||||
BoatAttack,
|
||||
Target,
|
||||
}
|
||||
|
||||
enum Slot {
|
||||
Alliance,
|
||||
Boat,
|
||||
Target,
|
||||
FOURTH
|
||||
Emoji
|
||||
}
|
||||
|
||||
export class RadialMenu implements Layer {
|
||||
@@ -38,7 +32,7 @@ export class RadialMenu implements Layer {
|
||||
[Slot.Alliance, {name: "alliance", disabled: true, action: () => { }, color: null, icon: null, defaultIcon: allianceIcon}],
|
||||
[Slot.Boat, {name: "boat", disabled: true, action: () => { }, color: null, icon: null, defaultIcon: boatIcon}],
|
||||
[Slot.Target, {name: "target", disabled: true, action: () => { }, defaultIcon: targetIcon}],
|
||||
|
||||
[Slot.Emoji, {name: "emoji", disabled: true, action: () => { }, defaultIcon: emojiIcon}],
|
||||
]);
|
||||
|
||||
private readonly menuSize = 190;
|
||||
@@ -230,6 +224,15 @@ export class RadialMenu implements Layer {
|
||||
return
|
||||
}
|
||||
|
||||
if (tile.hasOwner()) {
|
||||
const target = tile.owner() == myPlayer ? AllPlayers : (tile.owner() as Player)
|
||||
this.activateMenuElement(Slot.Emoji, "#ebe250", emojiIcon, () => {
|
||||
this.eventBus.emit(
|
||||
new SendEmojiIntentEvent(target, Emoji.Fire)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (tile.owner() != myPlayer && tile.isLand() && myPlayer.sharesBorderWith(other)) {
|
||||
if (other.isPlayer()) {
|
||||
if (!myPlayer.isAlliedWith(other)) {
|
||||
|
||||
+16
-3
@@ -1,5 +1,5 @@
|
||||
import {z} from 'zod';
|
||||
import {PlayerType} from './game/Game';
|
||||
import {Emoji, PlayerType} from './game/Game';
|
||||
|
||||
export type GameID = string
|
||||
export type ClientID = string
|
||||
@@ -12,6 +12,7 @@ export type Intent = SpawnIntent
|
||||
| AllianceRequestReplyIntent
|
||||
| BreakAllianceIntent
|
||||
| TargetPlayerIntent
|
||||
| EmojiIntent
|
||||
|
||||
export type AttackIntent = z.infer<typeof AttackIntentSchema>
|
||||
export type SpawnIntent = z.infer<typeof SpawnIntentSchema>
|
||||
@@ -21,7 +22,7 @@ export type AllianceRequestIntent = z.infer<typeof AllianceRequestIntentSchema>
|
||||
export type AllianceRequestReplyIntent = z.infer<typeof AllianceRequestReplyIntentSchema>
|
||||
export type BreakAllianceIntent = z.infer<typeof BreakAllianceIntentSchema>
|
||||
export type TargetPlayerIntent = z.infer<typeof TargetPlayerIntentSchema>
|
||||
|
||||
export type EmojiIntent = z.infer<typeof EmojiIntentSchema>
|
||||
|
||||
export type Turn = z.infer<typeof TurnSchema>
|
||||
|
||||
@@ -37,6 +38,8 @@ export type ClientJoinMessage = z.infer<typeof ClientJoinMessageSchema>
|
||||
export type ClientLeaveMessage = z.infer<typeof ClientLeaveMessageSchema>
|
||||
|
||||
const PlayerTypeSchema = z.nativeEnum(PlayerType);
|
||||
const EmojiSchema = z.nativeEnum(Emoji);
|
||||
|
||||
|
||||
// TODO: create Cell schema
|
||||
|
||||
@@ -46,9 +49,11 @@ export interface Lobby {
|
||||
numClients: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Zod schemas
|
||||
const BaseIntentSchema = z.object({
|
||||
type: z.enum(['attack', 'spawn', 'boat', 'name']),
|
||||
type: z.enum(['attack', 'spawn', 'boat', 'name', 'targetPlayer', 'emoji']),
|
||||
clientID: z.string(),
|
||||
});
|
||||
|
||||
@@ -112,6 +117,13 @@ export const TargetPlayerIntentSchema = BaseIntentSchema.extend({
|
||||
target: z.string(),
|
||||
})
|
||||
|
||||
export const EmojiIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal('emoji'),
|
||||
sender: z.string(),
|
||||
recipient: z.string(),
|
||||
emoji: EmojiSchema,
|
||||
})
|
||||
|
||||
const IntentSchema = z.union([
|
||||
AttackIntentSchema,
|
||||
SpawnIntentSchema,
|
||||
@@ -121,6 +133,7 @@ const IntentSchema = z.union([
|
||||
AllianceRequestReplyIntentSchema,
|
||||
BreakAllianceIntentSchema,
|
||||
TargetPlayerIntentSchema,
|
||||
EmojiIntentSchema,
|
||||
]);
|
||||
|
||||
const TurnSchema = z.object({
|
||||
|
||||
@@ -51,6 +51,8 @@ export interface Config {
|
||||
allianceRequestCooldown(): Tick
|
||||
targetDuration(): Tick
|
||||
targetCooldown(): Tick
|
||||
emojiMessageCooldown(): Tick
|
||||
emojiMessageDuration(): Tick
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
|
||||
@@ -7,6 +7,12 @@ import {pastelTheme} from "./PastelTheme";
|
||||
|
||||
|
||||
export class DefaultConfig implements Config {
|
||||
emojiMessageDuration(): Tick {
|
||||
return 5 * 10
|
||||
}
|
||||
emojiMessageCooldown(): Tick {
|
||||
return 15 * 10
|
||||
}
|
||||
targetDuration(): Tick {
|
||||
return 10 * 10
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ export const devConfig = new class extends DefaultConfig {
|
||||
return 95
|
||||
}
|
||||
numSpawnPhaseTurns(): number {
|
||||
return 40
|
||||
}
|
||||
gameCreationRate(): number {
|
||||
return 2 * 1000
|
||||
}
|
||||
lobbyLifetime(): number {
|
||||
return 2 * 1000
|
||||
return 80
|
||||
}
|
||||
// gameCreationRate(): number {
|
||||
// return 2 * 1000
|
||||
// }
|
||||
// lobbyLifetime(): number {
|
||||
// return 2 * 1000
|
||||
// }
|
||||
turnIntervalMs(): number {
|
||||
return 100
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {AllPlayers, Emoji, Execution, MutableGame, MutablePlayer, PlayerID} from "../game/Game";
|
||||
|
||||
export class EmojiExecution implements Execution {
|
||||
|
||||
private requestor: MutablePlayer
|
||||
private recipient: MutablePlayer | typeof AllPlayers
|
||||
|
||||
private active = true
|
||||
|
||||
constructor(
|
||||
private senderID: PlayerID,
|
||||
private recipientID: PlayerID | typeof AllPlayers,
|
||||
private emoji: Emoji
|
||||
) { }
|
||||
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.requestor = mg.player(this.senderID)
|
||||
this.recipient = this.recipientID == AllPlayers ? AllPlayers : mg.player(this.recipientID)
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.canSendEmoji(this.recipient)) {
|
||||
this.requestor.sendEmoji(this.recipient, this.emoji)
|
||||
} else {
|
||||
console.warn(`cannot send emoji from ${this.requestor} to ${this.recipient}`)
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
|
||||
owner(): MutablePlayer {
|
||||
return null
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {AllianceRequestExecution} from "./alliance/AllianceRequestExecution";
|
||||
import {AllianceRequestReplyExecution} from "./alliance/AllianceRequestReplyExecution";
|
||||
import {BreakAllianceExecution} from "./alliance/BreakAllianceExecution";
|
||||
import {TargetPlayerExecution} from "./TargetPlayerExecution";
|
||||
import {EmojiExecution} from "./EmojiExecution";
|
||||
|
||||
|
||||
|
||||
@@ -67,6 +68,8 @@ export class Executor {
|
||||
return new BreakAllianceExecution(intent.requestor, intent.recipient)
|
||||
} else if (intent.type == "targetPlayer") {
|
||||
return new TargetPlayerExecution(intent.requestor, intent.target)
|
||||
} else if (intent.type == "emoji") {
|
||||
return new EmojiExecution(intent.sender, intent.recipient, intent.emoji)
|
||||
}
|
||||
else {
|
||||
throw new Error(`intent type ${intent} not found`)
|
||||
|
||||
@@ -8,6 +8,26 @@ import {BreakAllianceExecution} from "../execution/alliance/BreakAllianceExecuti
|
||||
export type PlayerID = string
|
||||
export type Tick = number
|
||||
|
||||
export enum Emoji {
|
||||
ThumbsUp = "👍",
|
||||
ThumbsDown = "👎",
|
||||
Smile = "😊",
|
||||
Sad = "😢",
|
||||
Heart = "❤️",
|
||||
Fire = "🔥",
|
||||
}
|
||||
|
||||
export const AllPlayers = "AllPlayers" as const;
|
||||
|
||||
export class EmojiMessage {
|
||||
constructor(
|
||||
public readonly sender: Player,
|
||||
public readonly recipient: Player | typeof AllPlayers,
|
||||
public readonly emoji: Emoji,
|
||||
public readonly createdAt: Tick
|
||||
) { }
|
||||
}
|
||||
|
||||
export class Cell {
|
||||
|
||||
private strRepr: string
|
||||
@@ -156,6 +176,8 @@ export interface Player {
|
||||
// Targets of player and all allies.
|
||||
transitiveTargets(): Player[]
|
||||
toString(): string
|
||||
canSendEmoji(recipient: Player | typeof AllPlayers): boolean
|
||||
outgoingEmojis(): EmojiMessage[]
|
||||
}
|
||||
|
||||
export interface MutablePlayer extends Player {
|
||||
@@ -178,6 +200,8 @@ export interface MutablePlayer extends Player {
|
||||
target(other: Player): void
|
||||
targets(): MutablePlayer[]
|
||||
transitiveTargets(): MutablePlayer[]
|
||||
// Null means send to all Players
|
||||
sendEmoji(recipient: Player | typeof AllPlayers, emoji: Emoji): void
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
@@ -241,4 +265,8 @@ export class AllianceExpiredEvent implements GameEvent {
|
||||
|
||||
export class TargetPlayerEvent implements GameEvent {
|
||||
constructor(public readonly player: Player, public readonly target: Player) { }
|
||||
}
|
||||
|
||||
export class EmojiMessageEvent implements GameEvent {
|
||||
constructor(public readonly message: EmojiMessage) { }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, MutableGame, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent} from "./Game";
|
||||
import {MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, MutableGame, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent, Emoji, EmojiMessage, EmojiMessageEvent, AllPlayers} from "./Game";
|
||||
import {ClientID} from "../Schemas";
|
||||
import {simpleHash} from "../Util";
|
||||
import {CellString, GameImpl} from "./GameImpl";
|
||||
@@ -26,6 +26,8 @@ export class PlayerImpl implements MutablePlayer {
|
||||
|
||||
private targets_: Target[] = []
|
||||
|
||||
private outgoingEmojis_: EmojiMessage[] = []
|
||||
|
||||
constructor(private gs: GameImpl, private readonly playerInfo: PlayerInfo, private _troops) {
|
||||
this._name = playerInfo.name;
|
||||
}
|
||||
@@ -207,6 +209,29 @@ export class PlayerImpl implements MutablePlayer {
|
||||
return [...new Set(ts)]
|
||||
}
|
||||
|
||||
sendEmoji(recipient: Player | typeof AllPlayers, emoji: Emoji): void {
|
||||
if (recipient == this) {
|
||||
throw Error(`Cannot send emoji to oneself: ${this}`)
|
||||
}
|
||||
const msg = new EmojiMessage(this, recipient, emoji, this.gs.ticks())
|
||||
this.outgoingEmojis_.push(msg)
|
||||
this.gs.eventBus.emit(new EmojiMessageEvent(msg))
|
||||
}
|
||||
|
||||
outgoingEmojis(): EmojiMessage[] {
|
||||
return null
|
||||
}
|
||||
|
||||
canSendEmoji(recipient: Player | null): boolean {
|
||||
const prevMsgs = this.outgoingEmojis_.filter(msg => msg.recipient == recipient)
|
||||
for (const msg of prevMsgs) {
|
||||
if (this.gs.ticks() - msg.createdAt < this.gs.config().emojiMessageCooldown()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
hash(): number {
|
||||
return simpleHash(this.id()) * (this.troops() + this.numTilesOwned());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user