mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:50:43 +00:00
can donate to allies
This commit is contained in:
@@ -161,6 +161,7 @@
|
||||
* disable select on mobile DONE 10/6/2024
|
||||
* disable double tap on mobile DONE 10/6/2024
|
||||
* donate troops button
|
||||
* rewrite mobile input handling
|
||||
* Make fake humans spawn by their country
|
||||
* fake humans target enemies
|
||||
* create private lobby menu
|
||||
|
||||
+23
-1
@@ -51,7 +51,18 @@ export class SendTargetPlayerIntentEvent implements GameEvent {
|
||||
}
|
||||
|
||||
export class SendEmojiIntentEvent implements GameEvent {
|
||||
constructor(public readonly recipient: Player | typeof AllPlayers, public readonly emoji: string) { }
|
||||
constructor(
|
||||
public readonly recipient: Player | typeof AllPlayers,
|
||||
public readonly emoji: string
|
||||
) { }
|
||||
}
|
||||
|
||||
export class SendDonateIntentEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly sender: Player,
|
||||
public readonly recipient: Player,
|
||||
public readonly troops: number | null,
|
||||
) { }
|
||||
}
|
||||
|
||||
export class Transport {
|
||||
@@ -74,6 +85,7 @@ export class Transport {
|
||||
this.eventBus.on(SendBoatAttackIntentEvent, (e) => this.onSendBoatAttackIntent(e))
|
||||
this.eventBus.on(SendTargetPlayerIntentEvent, (e) => this.onSendTargetPlayerIntent(e))
|
||||
this.eventBus.on(SendEmojiIntentEvent, (e) => this.onSendEmojiIntent(e))
|
||||
this.eventBus.on(SendDonateIntentEvent, (e) => this.onSendDonateIntent(e))
|
||||
}
|
||||
|
||||
connect(onconnect: () => void, onmessage: (message: ServerMessage) => void, isActive: () => boolean) {
|
||||
@@ -216,6 +228,16 @@ export class Transport {
|
||||
})
|
||||
}
|
||||
|
||||
private onSendDonateIntent(event: SendDonateIntentEvent) {
|
||||
this.sendIntent({
|
||||
type: "donate",
|
||||
clientID: this.clientID,
|
||||
sender: event.sender.id(),
|
||||
recipient: event.recipient.id(),
|
||||
troops: event.troops,
|
||||
})
|
||||
}
|
||||
|
||||
private sendIntent(intent: Intent) {
|
||||
if (this.socket.readyState === WebSocket.OPEN) {
|
||||
const msg = ClientIntentMessageSchema.parse({
|
||||
|
||||
@@ -3,7 +3,7 @@ import {AllPlayers, Cell, Game, Player} 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, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../Transport";
|
||||
import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../Transport";
|
||||
import {TransformHandler} from "../TransformHandler";
|
||||
import {Layer} from "./Layer";
|
||||
import * as d3 from 'd3';
|
||||
@@ -14,6 +14,8 @@ import swordIcon from '../../../../resources/images/SwordIconWhite.png';
|
||||
import targetIcon from '../../../../resources/images/TargetIconWhite.png';
|
||||
import emojiIcon from '../../../../resources/images/EmojiIconWhite.png';
|
||||
import disabledIcon from '../../../../resources/images/DisabledIcon.png';
|
||||
import donateIcon from '../../../../resources/images/DonateIconWhite.png';
|
||||
import {MessageType} from "./EventsDisplay";
|
||||
|
||||
|
||||
enum Slot {
|
||||
@@ -298,6 +300,14 @@ export class RadialMenu implements Layer {
|
||||
return
|
||||
}
|
||||
|
||||
if (myPlayer.canDonate(other)) {
|
||||
this.activateMenuElement(Slot.Target, "#53ac75", donateIcon, () => {
|
||||
this.eventBus.emit(
|
||||
new SendDonateIntentEvent(myPlayer, other, null)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (myPlayer.isAlliedWith(other)) {
|
||||
this.activateMenuElement(Slot.Alliance, "#c74848", traitorIcon, () => {
|
||||
this.eventBus.emit(
|
||||
|
||||
@@ -13,6 +13,7 @@ export type Intent = SpawnIntent
|
||||
| BreakAllianceIntent
|
||||
| TargetPlayerIntent
|
||||
| EmojiIntent
|
||||
| DonateIntent
|
||||
|
||||
export type AttackIntent = z.infer<typeof AttackIntentSchema>
|
||||
export type SpawnIntent = z.infer<typeof SpawnIntentSchema>
|
||||
@@ -23,6 +24,7 @@ export type AllianceRequestReplyIntent = z.infer<typeof AllianceRequestReplyInte
|
||||
export type BreakAllianceIntent = z.infer<typeof BreakAllianceIntentSchema>
|
||||
export type TargetPlayerIntent = z.infer<typeof TargetPlayerIntentSchema>
|
||||
export type EmojiIntent = z.infer<typeof EmojiIntentSchema>
|
||||
export type DonateIntent = z.infer<typeof DonateIntentSchema>
|
||||
|
||||
export type Turn = z.infer<typeof TurnSchema>
|
||||
|
||||
@@ -128,6 +130,13 @@ export const EmojiIntentSchema = BaseIntentSchema.extend({
|
||||
emoji: EmojiSchema,
|
||||
})
|
||||
|
||||
export const DonateIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal('donate'),
|
||||
sender: z.string(),
|
||||
recipient: z.string(),
|
||||
troops: z.number().nullable(),
|
||||
})
|
||||
|
||||
const IntentSchema = z.union([
|
||||
AttackIntentSchema,
|
||||
SpawnIntentSchema,
|
||||
@@ -138,6 +147,7 @@ const IntentSchema = z.union([
|
||||
BreakAllianceIntentSchema,
|
||||
TargetPlayerIntentSchema,
|
||||
EmojiIntentSchema,
|
||||
DonateIntentSchema,
|
||||
]);
|
||||
|
||||
const TurnSchema = z.object({
|
||||
|
||||
@@ -53,6 +53,8 @@ export interface Config {
|
||||
targetCooldown(): Tick
|
||||
emojiMessageCooldown(): Tick
|
||||
emojiMessageDuration(): Tick
|
||||
donateCooldown(): Tick
|
||||
defaultDonationAmount(sender: Player): number
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
|
||||
@@ -7,6 +7,12 @@ import {pastelTheme} from "./PastelTheme";
|
||||
|
||||
|
||||
export class DefaultConfig implements Config {
|
||||
defaultDonationAmount(sender: Player): number {
|
||||
return Math.floor(sender.troops() / 3)
|
||||
}
|
||||
donateCooldown(): Tick {
|
||||
return 10 * 10
|
||||
}
|
||||
emojiMessageDuration(): Tick {
|
||||
return 5 * 10
|
||||
}
|
||||
@@ -114,7 +120,7 @@ export class DefaultConfig implements Config {
|
||||
}
|
||||
|
||||
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number {
|
||||
return attacker.troops() / 5
|
||||
return Math.floor(attacker.troops() / 5)
|
||||
}
|
||||
|
||||
attackAmount(attacker: Player, defender: Player | TerraNullius) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import {AllPlayers, Execution, MutableGame, MutablePlayer, PlayerID} from "../game/Game";
|
||||
|
||||
export class DonateExecution implements Execution {
|
||||
|
||||
private sender: MutablePlayer
|
||||
private recipient: MutablePlayer
|
||||
|
||||
private active = true
|
||||
|
||||
constructor(
|
||||
private senderID: PlayerID,
|
||||
private recipientID: PlayerID,
|
||||
private troops: number | null
|
||||
) { }
|
||||
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.sender = mg.player(this.senderID)
|
||||
this.recipient = mg.player(this.recipientID)
|
||||
if (this.troops == null) {
|
||||
this.troops = mg.config().defaultDonationAmount(this.sender)
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.sender.canDonate(this.recipient)) {
|
||||
this.sender.donate(this.recipient, this.troops)
|
||||
} else {
|
||||
console.warn(`cannot send tropps from ${this.sender} to ${this.recipient}`)
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
|
||||
owner(): MutablePlayer {
|
||||
return null
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {AllianceRequestReplyExecution} from "./alliance/AllianceRequestReplyExec
|
||||
import {BreakAllianceExecution} from "./alliance/BreakAllianceExecution";
|
||||
import {TargetPlayerExecution} from "./TargetPlayerExecution";
|
||||
import {EmojiExecution} from "./EmojiExecution";
|
||||
import {DonateExecution} from "./DonateExecution";
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +71,8 @@ export class Executor {
|
||||
return new TargetPlayerExecution(intent.requestor, intent.target)
|
||||
} else if (intent.type == "emoji") {
|
||||
return new EmojiExecution(intent.sender, intent.recipient, intent.emoji)
|
||||
} else if (intent.type == "donate") {
|
||||
return new DonateExecution(intent.sender, intent.recipient, intent.troops)
|
||||
}
|
||||
else {
|
||||
throw new Error(`intent type ${intent} not found`)
|
||||
|
||||
@@ -4,6 +4,7 @@ import {GameEvent} from "../EventBus"
|
||||
import {ClientID, GameID} from "../Schemas"
|
||||
import {DisplayMessageEvent, MessageType} from "../../client/graphics/layers/EventsDisplay"
|
||||
import {BreakAllianceExecution} from "../execution/alliance/BreakAllianceExecution"
|
||||
import {DonateExecution} from "../execution/DonateExecution"
|
||||
|
||||
export type PlayerID = string
|
||||
export type Tick = number
|
||||
@@ -159,6 +160,7 @@ export interface Player {
|
||||
isAlliedWith(other: Player): boolean
|
||||
allianceWith(other: Player): Alliance | null
|
||||
// Includes recent requests that are in cooldown
|
||||
// TODO: why can't I have "canSendAllyRequest" function instead?
|
||||
recentOrPendingAllianceRequestWith(other: Player): boolean
|
||||
isTraitor(): boolean
|
||||
canTarget(other: Player): boolean
|
||||
@@ -169,13 +171,14 @@ export interface Player {
|
||||
toString(): string
|
||||
canSendEmoji(recipient: Player | typeof AllPlayers): boolean
|
||||
outgoingEmojis(): EmojiMessage[]
|
||||
canDonate(recipient: Player): boolean
|
||||
}
|
||||
|
||||
export interface MutablePlayer extends Player {
|
||||
setName(name: string): void
|
||||
setTroops(troops: number): void
|
||||
addTroops(troops: number): void
|
||||
removeTroops(troops: number): void
|
||||
removeTroops(troops: number): number
|
||||
conquer(tile: Tile): void
|
||||
relinquish(tile: Tile): void
|
||||
executions(): Execution[]
|
||||
@@ -191,8 +194,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: string): void
|
||||
donate(recipient: MutablePlayer, troops: number): void
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
|
||||
@@ -5,13 +5,18 @@ import {CellString, GameImpl} from "./GameImpl";
|
||||
import {BoatImpl} from "./BoatImpl";
|
||||
import {TileImpl} from "./TileImpl";
|
||||
import {TerraNulliusImpl} from "./TerraNulliusImpl";
|
||||
import {threadId} from "worker_threads";
|
||||
import {MessageType} from "../../client/graphics/layers/EventsDisplay";
|
||||
import {renderTroops} from "../../client/graphics/Utils";
|
||||
|
||||
interface Target {
|
||||
tick: Tick
|
||||
target: Player
|
||||
}
|
||||
|
||||
class Donation {
|
||||
constructor(public readonly recipient: Player, public readonly tick: Tick) { }
|
||||
}
|
||||
|
||||
export class PlayerImpl implements MutablePlayer {
|
||||
isTraitor_ = false
|
||||
|
||||
@@ -28,6 +33,8 @@ export class PlayerImpl implements MutablePlayer {
|
||||
|
||||
private outgoingEmojis_: EmojiMessage[] = []
|
||||
|
||||
private sentDonations: Donation[] = []
|
||||
|
||||
constructor(private gs: GameImpl, private readonly playerInfo: PlayerInfo, private _troops) {
|
||||
this._name = playerInfo.name;
|
||||
}
|
||||
@@ -99,9 +106,10 @@ export class PlayerImpl implements MutablePlayer {
|
||||
addTroops(troops: number): void {
|
||||
this._troops += Math.floor(troops);
|
||||
}
|
||||
removeTroops(troops: number): void {
|
||||
this._troops -= Math.floor(troops);
|
||||
this._troops = Math.max(this._troops, 0);
|
||||
removeTroops(troops: number): number {
|
||||
const toRemove = Math.floor(Math.min(this._troops, troops))
|
||||
this._troops -= toRemove;
|
||||
return toRemove
|
||||
}
|
||||
|
||||
isPlayer(): this is MutablePlayer {return true as const;}
|
||||
@@ -234,6 +242,27 @@ export class PlayerImpl implements MutablePlayer {
|
||||
return true
|
||||
}
|
||||
|
||||
canDonate(recipient: Player): boolean {
|
||||
if (!this.isAlliedWith(recipient)) {
|
||||
return false
|
||||
}
|
||||
for (const donation of this.sentDonations) {
|
||||
if (donation.recipient == recipient) {
|
||||
if (this.gs.ticks() - donation.tick < this.gs.config().donateCooldown()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
donate(recipient: MutablePlayer, troops: number): void {
|
||||
this.sentDonations.push(new Donation(recipient, this.gs.ticks()))
|
||||
recipient.addTroops(this.removeTroops(troops))
|
||||
this.gs.displayMessage(`Sent ${renderTroops(troops)} troops to ${recipient.name()}`, MessageType.INFO, this.id())
|
||||
this.gs.displayMessage(`Recieved ${renderTroops(troops)} troops from ${this.name()}`, MessageType.SUCCESS, recipient.id())
|
||||
}
|
||||
|
||||
hash(): number {
|
||||
return simpleHash(this.id()) * (this.troops() + this.numTilesOwned());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user