mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:40:44 +00:00
go to player on spawn start (#3802)
## Description: Some new players were having trouble finding themselves on game start * Emits a GoToPlayerEvent (zoom=8) on the first turn after the spawn phase, using a hasGoneToPlayer flag to ensure it only fires once per session * Adds a zoom parameter to GoToPlayerEvent so callers can specify a target zoom level * Adds smooth zoom animation to TransformHandler — the camera now eases to the target scale alongside the existing position easing, with screen-center correction to avoid visual jumping on mobile (where canvas and map dimensions differ) * Moves GoToPlayerEvent, GoToPositionEvent, and GoToUnitEvent out of Leaderboard.ts into TransformHandler.ts, where they logically belong ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -53,7 +53,7 @@ import {
|
||||
} from "./Transport";
|
||||
import { createCanvas } from "./Utils";
|
||||
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
|
||||
import { GoToPlayerEvent } from "./graphics/layers/Leaderboard";
|
||||
import { GoToPlayerEvent } from "./graphics/TransformHandler";
|
||||
import { SoundManager } from "./sound/SoundManager";
|
||||
|
||||
export interface LobbyConfig {
|
||||
@@ -441,6 +441,8 @@ export class ClientGameRunner {
|
||||
console.log("Connected to game server!");
|
||||
this.transport.rejoinGame(this.turnsSeen);
|
||||
};
|
||||
|
||||
let hasGoneToPlayer = false;
|
||||
const onmessage = (message: ServerMessage) => {
|
||||
this.lastMessageTime = Date.now();
|
||||
if (message.type === "start") {
|
||||
@@ -472,7 +474,7 @@ export class ClientGameRunner {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventBus.emit(new GoToPlayerEvent(myPlayer));
|
||||
this.eventBus.emit(new GoToPlayerEvent(myPlayer, 10));
|
||||
};
|
||||
|
||||
goToPlayer();
|
||||
@@ -519,6 +521,15 @@ export class ClientGameRunner {
|
||||
);
|
||||
}
|
||||
if (message.type === "turn") {
|
||||
if (
|
||||
!this.gameView.inSpawnPhase() &&
|
||||
!hasGoneToPlayer &&
|
||||
this.gameView.myPlayer()
|
||||
) {
|
||||
hasGoneToPlayer = true;
|
||||
this.eventBus.emit(new GoToPlayerEvent(this.gameView.myPlayer()!, 8));
|
||||
}
|
||||
|
||||
// Track when we receive the turn to calculate delay
|
||||
const now = Date.now();
|
||||
if (this.lastTickReceiveTime > 0) {
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import { EventBus } from "../../core/EventBus";
|
||||
import { EventBus, GameEvent } from "../../core/EventBus";
|
||||
import { Cell } from "../../core/game/Game";
|
||||
import { GameView } from "../../core/game/GameView";
|
||||
import { GameView, PlayerView, UnitView } from "../../core/game/GameView";
|
||||
import { CenterCameraEvent, DragEvent, ZoomEvent } from "../InputHandler";
|
||||
import {
|
||||
GoToPlayerEvent,
|
||||
GoToPositionEvent,
|
||||
GoToUnitEvent,
|
||||
} from "./layers/Leaderboard";
|
||||
|
||||
export class GoToPlayerEvent implements GameEvent {
|
||||
constructor(
|
||||
public player: PlayerView,
|
||||
public zoom?: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class GoToPositionEvent implements GameEvent {
|
||||
constructor(
|
||||
public x: number,
|
||||
public y: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class GoToUnitEvent implements GameEvent {
|
||||
constructor(public unit: UnitView) {}
|
||||
}
|
||||
|
||||
export const GOTO_INTERVAL_MS = 16;
|
||||
export const CAMERA_MAX_SPEED = 15;
|
||||
@@ -20,6 +33,7 @@ export class TransformHandler {
|
||||
private lastGoToCallTime: number | null = null;
|
||||
|
||||
private target: Cell | null;
|
||||
private targetScale: number | null = null;
|
||||
private intervalID: NodeJS.Timeout | null = null;
|
||||
private changed = false;
|
||||
|
||||
@@ -183,6 +197,7 @@ export class TransformHandler {
|
||||
return;
|
||||
}
|
||||
this.target = new Cell(nameLocation.x, nameLocation.y);
|
||||
this.targetScale = event.zoom ?? null;
|
||||
this.intervalID = setInterval(() => this.goTo(), GOTO_INTERVAL_MS);
|
||||
}
|
||||
|
||||
@@ -214,10 +229,12 @@ export class TransformHandler {
|
||||
|
||||
if (this.target === null) throw new Error("null target");
|
||||
|
||||
if (
|
||||
Math.abs(this.target.x - screenX) + Math.abs(this.target.y - screenY) <
|
||||
2
|
||||
) {
|
||||
const positionClose =
|
||||
Math.abs(this.target.x - screenX) + Math.abs(this.target.y - screenY) < 2;
|
||||
const scaleClose =
|
||||
this.targetScale === null ||
|
||||
Math.abs(this.scale - this.targetScale) < 0.01;
|
||||
if (positionClose && scaleClose) {
|
||||
this.clearTarget();
|
||||
return;
|
||||
}
|
||||
@@ -242,6 +259,27 @@ export class TransformHandler {
|
||||
-CAMERA_MAX_SPEED,
|
||||
);
|
||||
|
||||
if (this.targetScale !== null) {
|
||||
const oldScale = this.scale;
|
||||
const zoomSmoothing = 0.7;
|
||||
const zoomR = 1 - Math.pow(zoomSmoothing, dt / 1000);
|
||||
const diff = this.targetScale - this.scale;
|
||||
const smoothStep = diff * zoomR;
|
||||
const minStep =
|
||||
Math.sign(diff) * Math.min(Math.abs(diff), (6.0 * dt) / 1000);
|
||||
this.scale +=
|
||||
Math.abs(smoothStep) >= Math.abs(minStep) ? smoothStep : minStep;
|
||||
// Keep screen center pinned as scale changes: (canvasSize - mapSize) / (2 * scale)
|
||||
// shifts the apparent center when canvas != map dimensions (always on mobile).
|
||||
const { width: canvasWidth, height: canvasHeight } = this.boundingRect();
|
||||
this.offsetX +=
|
||||
(canvasWidth - this.game.width()) *
|
||||
(1 / (2 * oldScale) - 1 / (2 * this.scale));
|
||||
this.offsetY +=
|
||||
(canvasHeight - this.game.height()) *
|
||||
(1 / (2 * oldScale) - 1 / (2 * this.scale));
|
||||
}
|
||||
|
||||
this.changed = true;
|
||||
}
|
||||
|
||||
@@ -321,6 +359,7 @@ export class TransformHandler {
|
||||
this.intervalID = null;
|
||||
}
|
||||
this.target = null;
|
||||
this.targetScale = null;
|
||||
}
|
||||
|
||||
override(x: number = 0, y: number = 0, s: number = 1) {
|
||||
|
||||
@@ -16,13 +16,13 @@ import {
|
||||
} from "../../Transport";
|
||||
import { renderTroops, translateText } from "../../Utils";
|
||||
import { getColoredSprite } from "../SpriteLoader";
|
||||
import { UIState } from "../UIState";
|
||||
import { Layer } from "./Layer";
|
||||
import {
|
||||
GoToPlayerEvent,
|
||||
GoToPositionEvent,
|
||||
GoToUnitEvent,
|
||||
} from "./Leaderboard";
|
||||
} from "../TransformHandler";
|
||||
import { UIState } from "../UIState";
|
||||
import { Layer } from "./Layer";
|
||||
const soldierIcon = assetUrl("images/SoldierIcon.svg");
|
||||
const swordIcon = assetUrl("images/SwordIcon.svg");
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import { Layer } from "./Layer";
|
||||
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
|
||||
import { onlyImages } from "../../../core/Util";
|
||||
import { renderNumber } from "../../Utils";
|
||||
import { GoToPlayerEvent, GoToUnitEvent } from "./Leaderboard";
|
||||
import { GoToPlayerEvent, GoToUnitEvent } from "../TransformHandler";
|
||||
|
||||
import { PlaySoundEffectEvent } from "../../sound/Sounds";
|
||||
import { getMessageTypeClasses, translateText } from "../../Utils";
|
||||
|
||||
@@ -2,9 +2,10 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { renderTroops, translateText } from "../../../client/Utils";
|
||||
import { EventBus, GameEvent } from "../../../core/EventBus";
|
||||
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { formatPercentage, renderNumber } from "../../Utils";
|
||||
import { GoToPlayerEvent } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
interface Entry {
|
||||
@@ -18,21 +19,6 @@ interface Entry {
|
||||
player: PlayerView;
|
||||
}
|
||||
|
||||
export class GoToPlayerEvent implements GameEvent {
|
||||
constructor(public player: PlayerView) {}
|
||||
}
|
||||
|
||||
export class GoToPositionEvent implements GameEvent {
|
||||
constructor(
|
||||
public x: number,
|
||||
public y: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class GoToUnitEvent implements GameEvent {
|
||||
constructor(public unit: UnitView) {}
|
||||
}
|
||||
|
||||
@customElement("leader-board")
|
||||
export class Leaderboard extends LitElement implements Layer {
|
||||
public game: GameView | null = null;
|
||||
|
||||
Reference in New Issue
Block a user