mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 18:06:39 +00:00
Merge main into biome
This commit is contained in:
+1
-4
@@ -99,10 +99,7 @@ export default [
|
||||
"function-call-argument-newline": ["error", "consistent"],
|
||||
"max-depth": ["error", { max: 5 }],
|
||||
"max-len": ["error", { code: 120 }],
|
||||
"max-lines": [
|
||||
"error",
|
||||
{ max: 676, skipBlankLines: true, skipComments: true },
|
||||
],
|
||||
"max-lines": ["error", { max: 677, skipBlankLines: true, skipComments: true }],
|
||||
"max-lines-per-function": ["error", { max: 561 }],
|
||||
"no-loss-of-precision": "error",
|
||||
"no-multi-spaces": "error",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 107 B After Width: | Height: | Size: 213 B |
Binary file not shown.
|
Before Width: | Height: | Size: 114 B After Width: | Height: | Size: 213 B |
Binary file not shown.
|
Before Width: | Height: | Size: 98 B After Width: | Height: | Size: 199 B |
@@ -66,6 +66,10 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
}
|
||||
|
||||
async onUserMe(userMeResponse: UserMeResponse | null) {
|
||||
if (userMeResponse === null) {
|
||||
this.userSettings.setSelectedPattern(undefined);
|
||||
this.selectedPattern = undefined;
|
||||
}
|
||||
this.patterns = await patterns(userMeResponse);
|
||||
this.me = userMeResponse;
|
||||
this.requestUpdate();
|
||||
@@ -143,7 +147,7 @@ export class TerritoryPatternsModal extends LitElement {
|
||||
@mouseleave=${() => this.handleMouseLeave()}
|
||||
>
|
||||
<div class="text-sm font-bold mb-1">
|
||||
${translateText(`territory_patterns.pattern.${pattern.name}`)}
|
||||
${translatePatternName("territory_patterns.pattern", pattern.name)}
|
||||
</div>
|
||||
<div
|
||||
class="preview-container"
|
||||
@@ -419,3 +423,12 @@ export function generatePreviewDataUrl(
|
||||
patternCache.set(pattern, dataUrl);
|
||||
return dataUrl;
|
||||
}
|
||||
|
||||
function translatePatternName(prefix: string, patternName: string): string {
|
||||
const translation = translateText(`${prefix}.${patternName}`);
|
||||
if (translation.startsWith(prefix)) {
|
||||
// Translation was not found, fallback to pattern name
|
||||
return patternName[0].toUpperCase() + patternName.substring(1);
|
||||
}
|
||||
return translation;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ type GameEvent = {
|
||||
duration?: Tick;
|
||||
focusID?: number;
|
||||
unitView?: UnitView;
|
||||
shouldDelete?: (game: GameView) => boolean;
|
||||
};
|
||||
|
||||
@customElement("events-display")
|
||||
@@ -205,7 +206,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
let remainingEvents = this.events.filter((event) => {
|
||||
if (this.game === undefined) return;
|
||||
const shouldKeep =
|
||||
this.game.ticks() - event.createdAt < (event.duration ?? 600);
|
||||
this.game.ticks() - event.createdAt < (event.duration ?? 600) &&
|
||||
!event.shouldDelete?.(this.game);
|
||||
if (!shouldKeep && event.onDelete) {
|
||||
event.onDelete();
|
||||
}
|
||||
@@ -468,18 +470,13 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
},
|
||||
],
|
||||
createdAt: this.game.ticks(),
|
||||
description: translateText("events_display.request_alliance", {
|
||||
name: requestor.name(),
|
||||
}),
|
||||
duration: 150,
|
||||
focusID: update.requestorID,
|
||||
highlight: true,
|
||||
onDelete: () =>
|
||||
this.eventBus?.emit(
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false),
|
||||
),
|
||||
priority: 0,
|
||||
type: MessageType.ALLIANCE_REQUEST,
|
||||
duration: this.game.config().allianceRequestDuration() - 20, // 2 second buffer
|
||||
shouldDelete: (game) => {
|
||||
// Recipient sent a separate request, so they became allied without the recipient responding.
|
||||
return requestor.isAlliedWith(recipient);
|
||||
},
|
||||
focusID: update.requestorID,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ export class ColorAllocator {
|
||||
case ColoredTeams.Bot:
|
||||
return botTeamColors;
|
||||
default:
|
||||
throw new Error(`Unknown team color: ${team}`);
|
||||
return [this.assignColor(team)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ export type Config = {
|
||||
shellLifetime(): number;
|
||||
boatMaxNumber(): number;
|
||||
allianceDuration(): Tick;
|
||||
allianceRequestDuration(): Tick;
|
||||
allianceRequestCooldown(): Tick;
|
||||
temporaryEmbargoDuration(): Tick;
|
||||
targetDuration(): Tick;
|
||||
|
||||
@@ -532,6 +532,9 @@ export class DefaultConfig implements Config {
|
||||
targetCooldown(): Tick {
|
||||
return 15 * 10;
|
||||
}
|
||||
allianceRequestDuration(): Tick {
|
||||
return 20 * 10;
|
||||
}
|
||||
allianceRequestCooldown(): Tick {
|
||||
return 30 * 10;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ export class CityExecution implements Execution {
|
||||
this.city.tile(),
|
||||
this.mg.config().trainStationMaxRange(),
|
||||
UnitType.Factory,
|
||||
this.player.id(),
|
||||
);
|
||||
if (nearbyFactory) {
|
||||
this.mg.addExecution(new TrainStationExecution(this.city));
|
||||
|
||||
@@ -97,7 +97,6 @@ export class PortExecution implements Execution {
|
||||
this.port.tile(),
|
||||
this.mg.config().trainStationMaxRange(),
|
||||
UnitType.Factory,
|
||||
this.player.id(),
|
||||
);
|
||||
if (nearbyFactory) {
|
||||
this.mg.addExecution(new TrainStationExecution(this.port));
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { Execution, Game, Player, PlayerID } from "../../game/Game";
|
||||
import {
|
||||
AllianceRequest,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
} from "../../game/Game";
|
||||
|
||||
export class AllianceRequestExecution implements Execution {
|
||||
private req: AllianceRequest | null = null;
|
||||
private active = true;
|
||||
private recipient: Player | null = null;
|
||||
private mg: Game | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly requestor: Player,
|
||||
@@ -10,29 +17,50 @@ export class AllianceRequestExecution implements Execution {
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
if (!mg.hasPlayer(this.recipientID)) {
|
||||
console.warn(
|
||||
`AllianceRequestExecution recipient ${this.recipientID} not found`,
|
||||
);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.recipient = mg.player(this.recipientID);
|
||||
const recipient = mg.player(this.recipientID);
|
||||
|
||||
if (!this.requestor.canSendAllianceRequest(recipient)) {
|
||||
console.warn("cannot send alliance request");
|
||||
this.active = false;
|
||||
} else {
|
||||
const incoming = recipient
|
||||
.outgoingAllianceRequests()
|
||||
.find((r) => r.recipient() === this.requestor);
|
||||
if (incoming) {
|
||||
// If the recipient already has pending alliance request,
|
||||
// then accept it instead of creating a new one.
|
||||
this.active = false;
|
||||
incoming.accept();
|
||||
} else {
|
||||
this.req = this.requestor.createAllianceRequest(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.recipient === null) {
|
||||
throw new Error("Not initialized");
|
||||
if (
|
||||
this.req?.status() === "accepted" ||
|
||||
this.req?.status() === "rejected"
|
||||
) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (this.requestor.isFriendly(this.recipient)) {
|
||||
console.warn("already allied");
|
||||
} else if (!this.requestor.canSendAllianceRequest(this.recipient)) {
|
||||
console.warn("recent or pending alliance request");
|
||||
} else {
|
||||
this.requestor.createAllianceRequest(this.recipient);
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (
|
||||
this.mg.ticks() - (this.req?.createdAt() ?? 0) >
|
||||
this.mg.config().allianceRequestDuration()
|
||||
) {
|
||||
this.req?.reject();
|
||||
this.active = false;
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { GameImpl } from "./GameImpl";
|
||||
import { AllianceRequestUpdate, GameUpdateType } from "./GameUpdates";
|
||||
|
||||
export class AllianceRequestImpl implements AllianceRequest {
|
||||
private status_: "pending" | "accepted" | "rejected" = "pending";
|
||||
|
||||
constructor(
|
||||
private readonly requestor_: Player,
|
||||
private readonly recipient_: Player,
|
||||
@@ -10,6 +12,10 @@ export class AllianceRequestImpl implements AllianceRequest {
|
||||
private readonly game: GameImpl,
|
||||
) {}
|
||||
|
||||
status(): "pending" | "accepted" | "rejected" {
|
||||
return this.status_;
|
||||
}
|
||||
|
||||
requestor(): Player {
|
||||
return this.requestor_;
|
||||
}
|
||||
@@ -23,9 +29,11 @@ export class AllianceRequestImpl implements AllianceRequest {
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
this.status_ = "accepted";
|
||||
this.game.acceptAllianceRequest(this);
|
||||
}
|
||||
reject(): void {
|
||||
this.status_ = "rejected";
|
||||
this.game.rejectAllianceRequest(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -352,6 +352,7 @@ export type AllianceRequest = {
|
||||
requestor(): Player;
|
||||
recipient(): Player;
|
||||
createdAt(): Tick;
|
||||
status(): "pending" | "accepted" | "rejected";
|
||||
};
|
||||
|
||||
export type Alliance = {
|
||||
@@ -670,7 +671,7 @@ export type Game = {
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
type: UnitType,
|
||||
playerId: PlayerID,
|
||||
playerId?: PlayerID,
|
||||
): boolean;
|
||||
nearbyUnits(
|
||||
tile: TileRef,
|
||||
|
||||
@@ -753,7 +753,7 @@ export class GameImpl implements Game {
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
type: UnitType,
|
||||
playerId: PlayerID,
|
||||
playerId?: PlayerID,
|
||||
) {
|
||||
return this.unitGrid.hasUnitNearby(tile, searchRange, type, playerId);
|
||||
}
|
||||
|
||||
@@ -396,9 +396,9 @@ export class PlayerImpl implements Player {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasPending =
|
||||
this.incomingAllianceRequests().some((ar) => ar.requestor() === other) ||
|
||||
this.outgoingAllianceRequests().some((ar) => ar.recipient() === other);
|
||||
const hasPending = this.outgoingAllianceRequests().some(
|
||||
(ar) => ar.recipient() === other,
|
||||
);
|
||||
|
||||
if (hasPending) {
|
||||
return false;
|
||||
@@ -555,6 +555,9 @@ export class PlayerImpl implements Player {
|
||||
}
|
||||
|
||||
canSendEmoji(recipient: Player | typeof AllPlayers): boolean {
|
||||
if (recipient === this) {
|
||||
return false;
|
||||
}
|
||||
const recipientID =
|
||||
recipient === AllPlayers ? AllPlayers : recipient.smallID();
|
||||
const prevMsgs = this.outgoingEmojis_.filter(
|
||||
|
||||
@@ -170,12 +170,28 @@ export class UnitGrid {
|
||||
return nearby;
|
||||
}
|
||||
|
||||
private unitIsInRange(
|
||||
unit: Unit | UnitView,
|
||||
tile: TileRef,
|
||||
rangeSquared: number,
|
||||
playerId?: PlayerID,
|
||||
): boolean {
|
||||
if (!unit.isActive()) {
|
||||
return false;
|
||||
}
|
||||
if (playerId !== undefined && unit.owner().id() !== playerId) {
|
||||
return false;
|
||||
}
|
||||
const distSquared = this.squaredDistanceFromTile(unit, tile);
|
||||
return distSquared <= rangeSquared;
|
||||
}
|
||||
|
||||
// Return true if it finds an owned specific unit in range
|
||||
hasUnitNearby(
|
||||
tile: TileRef,
|
||||
searchRange: number,
|
||||
type: UnitType,
|
||||
playerId: PlayerID,
|
||||
playerId?: PlayerID,
|
||||
): boolean {
|
||||
const { startGridX, endGridX, startGridY, endGridY } = this.getCellsInRange(
|
||||
tile,
|
||||
@@ -187,11 +203,8 @@ export class UnitGrid {
|
||||
const unitSet = this.grid[cy][cx].get(type);
|
||||
if (unitSet === undefined) continue;
|
||||
for (const unit of unitSet) {
|
||||
if (unit.owner().id() === playerId && unit.isActive()) {
|
||||
const distSquared = this.squaredDistanceFromTile(unit, tile);
|
||||
if (distSquared <= rangeSquared) {
|
||||
return true;
|
||||
}
|
||||
if (this.unitIsInRange(unit, tile, rangeSquared, playerId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ const frequency: Partial<Record<GameMapName, number>> = {
|
||||
SouthAmerica: 5,
|
||||
StraitOfGibraltar: 5,
|
||||
World: 8,
|
||||
Yenisei: 6,
|
||||
};
|
||||
|
||||
type MapWithMode = {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Game, Player, PlayerType } from "../src/core/game/Game";
|
||||
import { playerInfo, setup } from "./util/Setup";
|
||||
import { AllianceRequestExecution } from "../src/core/execution/alliance/AllianceRequestExecution";
|
||||
import { AllianceRequestReplyExecution } from "../src/core/execution/alliance/AllianceRequestReplyExecution";
|
||||
|
||||
let game: Game;
|
||||
let player1: Player;
|
||||
let player2: Player;
|
||||
|
||||
describe("AllianceRequestExecution", () => {
|
||||
beforeEach(async () => {
|
||||
game = await setup(
|
||||
"plains",
|
||||
{
|
||||
infiniteGold: true,
|
||||
instantBuild: true,
|
||||
infiniteTroops: true,
|
||||
},
|
||||
[
|
||||
playerInfo("player1", PlayerType.Human),
|
||||
playerInfo("player2", PlayerType.Human),
|
||||
playerInfo("player3", PlayerType.FakeHuman),
|
||||
],
|
||||
);
|
||||
|
||||
player1 = game.player("player1");
|
||||
player1.conquer(game.ref(0, 0));
|
||||
|
||||
player2 = game.player("player2");
|
||||
player2.conquer(game.ref(0, 1));
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
});
|
||||
|
||||
test("Can create alliance by replying", () => {
|
||||
game.addExecution(new AllianceRequestExecution(player1, player2.id()));
|
||||
game.executeNextTick();
|
||||
|
||||
game.addExecution(
|
||||
new AllianceRequestReplyExecution(player1.id(), player2, true),
|
||||
);
|
||||
game.executeNextTick();
|
||||
game.executeNextTick();
|
||||
|
||||
expect(player1.isAlliedWith(player2)).toBeTruthy();
|
||||
expect(player2.isAlliedWith(player1)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Can create alliance by sending alliance request back", () => {
|
||||
game.addExecution(new AllianceRequestExecution(player1, player2.id()));
|
||||
game.executeNextTick();
|
||||
|
||||
game.addExecution(new AllianceRequestExecution(player2, player1.id()));
|
||||
game.executeNextTick();
|
||||
|
||||
expect(player1.isAlliedWith(player2)).toBeTruthy();
|
||||
expect(player2.isAlliedWith(player1)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Alliance request expires", () => {
|
||||
game.config().allianceRequestDuration = () => 5;
|
||||
game.addExecution(new AllianceRequestExecution(player1, player2.id()));
|
||||
game.executeNextTick();
|
||||
|
||||
expect(player1.outgoingAllianceRequests()).toHaveLength(1);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
|
||||
expect(player1.outgoingAllianceRequests()).toHaveLength(0);
|
||||
expect(player1.isAlliedWith(player2)).toBeFalsy();
|
||||
expect(player2.isAlliedWith(player1)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user