mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:20:45 +00:00
Bomb Direction (#2435)
Resolves #2434 ## Description: Allows bomb direction to be inverted by pressing a hotkey - currently "U". **Check the issue for screenshots / videos.** ## 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: w.o.n --------- Co-authored-by: Evan <evanpelle@gmail.com> Co-authored-by: iamlewis <lewismmmm@gmail.com>
This commit is contained in:
@@ -130,7 +130,8 @@
|
||||
"icon_embargo": "Dollar stop sign - Embargo. This player has stopped trading with you automatically or manually.",
|
||||
"icon_request": "Envelope - Alliance request. This player has sent you an alliance request.",
|
||||
"info_enemy_panel": "Enemy info panel",
|
||||
"exit_confirmation": "Are you sure you want to exit the game?"
|
||||
"exit_confirmation": "Are you sure you want to exit the game?",
|
||||
"bomb_direction": "Atom / Hydrogen bomb arc direction"
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Single Player",
|
||||
|
||||
@@ -138,6 +138,10 @@ export class HelpModal extends LitElement {
|
||||
<td>${this.renderKey(keybinds.toggleView)}</td>
|
||||
<td>${translateText("help_modal.action_alt_view")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="key">U</span></td>
|
||||
<td>${translateText("help_modal.bomb_direction")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="scroll-combo-horizontal">
|
||||
|
||||
@@ -89,6 +89,8 @@ export class GhostStructureChangedEvent implements GameEvent {
|
||||
constructor(public readonly ghostStructure: UnitType | null) {}
|
||||
}
|
||||
|
||||
export class SwapRocketDirectionEvent implements GameEvent {}
|
||||
|
||||
export class ShowBuildMenuEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
@@ -200,6 +202,7 @@ export class InputHandler {
|
||||
attackRatioUp: "KeyY",
|
||||
boatAttack: "KeyB",
|
||||
groundAttack: "KeyG",
|
||||
swapDirection: "KeyU",
|
||||
modifierKey: isMac ? "MetaLeft" : "ControlLeft",
|
||||
altKey: "AltLeft",
|
||||
buildCity: "Digit1",
|
||||
@@ -427,6 +430,11 @@ export class InputHandler {
|
||||
this.setGhostStructure(UnitType.MIRV);
|
||||
}
|
||||
|
||||
if (e.code === this.keybinds.swapDirection) {
|
||||
e.preventDefault();
|
||||
this.eventBus.emit(new SwapRocketDirectionEvent());
|
||||
}
|
||||
|
||||
// Shift-D to toggle performance overlay
|
||||
console.log(e.code, e.shiftKey, e.ctrlKey, e.altKey, e.metaKey);
|
||||
if (e.code === "KeyD" && e.shiftKey) {
|
||||
|
||||
@@ -91,6 +91,7 @@ export class BuildUnitIntentEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly unit: UnitType,
|
||||
public readonly tile: TileRef,
|
||||
public readonly rocketDirectionUp?: boolean,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -573,6 +574,7 @@ export class Transport {
|
||||
clientID: this.lobbyConfig.clientID,
|
||||
unit: event.unit,
|
||||
tile: event.tile,
|
||||
rocketDirectionUp: event.rocketDirectionUp,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,11 @@ export function createRenderer(
|
||||
const transformHandler = new TransformHandler(game, eventBus, canvas);
|
||||
const userSettings = new UserSettings();
|
||||
|
||||
const uiState = { attackRatio: 20, ghostStructure: null } as UIState;
|
||||
const uiState = {
|
||||
attackRatio: 20,
|
||||
ghostStructure: null,
|
||||
rocketDirectionUp: true,
|
||||
} as UIState;
|
||||
|
||||
//hide when the game renders
|
||||
const startingModal = document.querySelector(
|
||||
@@ -73,6 +77,7 @@ export function createRenderer(
|
||||
}
|
||||
buildMenu.game = game;
|
||||
buildMenu.eventBus = eventBus;
|
||||
buildMenu.uiState = uiState;
|
||||
buildMenu.transformHandler = transformHandler;
|
||||
|
||||
const leaderboard = document.querySelector("leader-board") as Leaderboard;
|
||||
@@ -241,7 +246,7 @@ export function createRenderer(
|
||||
new UnitLayer(game, eventBus, transformHandler),
|
||||
new FxLayer(game),
|
||||
new UILayer(game, eventBus, transformHandler),
|
||||
new NukeTrajectoryPreviewLayer(game, eventBus, transformHandler),
|
||||
new NukeTrajectoryPreviewLayer(game, eventBus, transformHandler, uiState),
|
||||
new StructureIconsLayer(game, eventBus, uiState, transformHandler),
|
||||
new NameLayer(game, transformHandler, eventBus),
|
||||
eventsDisplay,
|
||||
|
||||
@@ -3,4 +3,5 @@ import { UnitType } from "../../core/game/Game";
|
||||
export interface UIState {
|
||||
attackRatio: number;
|
||||
ghostStructure: UnitType | null;
|
||||
rocketDirectionUp: boolean;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "../../Transport";
|
||||
import { renderNumber } from "../../Utils";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { UIState } from "../UIState";
|
||||
import { Layer } from "./Layer";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
@@ -125,6 +126,7 @@ export const flattenedBuildTable = buildTable.flat();
|
||||
export class BuildMenu extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
public eventBus: EventBus;
|
||||
public uiState: UIState;
|
||||
private clickedTile: TileRef;
|
||||
public playerActions: PlayerActions | null;
|
||||
private filteredBuildTable: BuildItemDisplay[][] = buildTable;
|
||||
@@ -395,7 +397,14 @@ export class BuildMenu extends LitElement implements Layer {
|
||||
),
|
||||
);
|
||||
} else if (buildableUnit.canBuild) {
|
||||
this.eventBus.emit(new BuildUnitIntentEvent(buildableUnit.type, tile));
|
||||
const rocketDirectionUp =
|
||||
buildableUnit.type === UnitType.AtomBomb ||
|
||||
buildableUnit.type === UnitType.HydrogenBomb
|
||||
? this.uiState.rocketDirectionUp
|
||||
: undefined;
|
||||
this.eventBus.emit(
|
||||
new BuildUnitIntentEvent(buildableUnit.type, tile, rocketDirectionUp),
|
||||
);
|
||||
}
|
||||
this.hideMenu();
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@ import { UnitType } from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { ParabolaPathFinder } from "../../../core/pathfinding/PathFinding";
|
||||
import { GhostStructureChangedEvent, MouseMoveEvent } from "../../InputHandler";
|
||||
import {
|
||||
GhostStructureChangedEvent,
|
||||
MouseMoveEvent,
|
||||
SwapRocketDirectionEvent,
|
||||
} from "../../InputHandler";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { UIState } from "../UIState";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
/**
|
||||
@@ -27,6 +32,7 @@ export class NukeTrajectoryPreviewLayer implements Layer {
|
||||
private game: GameView,
|
||||
private eventBus: EventBus,
|
||||
private transformHandler: TransformHandler,
|
||||
private uiState: UIState,
|
||||
) {}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
@@ -50,6 +56,12 @@ export class NukeTrajectoryPreviewLayer implements Layer {
|
||||
this.cachedSpawnTile = null;
|
||||
}
|
||||
});
|
||||
this.eventBus.on(SwapRocketDirectionEvent, () => {
|
||||
// Toggle rocket direction
|
||||
this.uiState.rocketDirectionUp = !this.uiState.rocketDirectionUp;
|
||||
// Force trajectory recalculation
|
||||
this.lastTargetTile = null;
|
||||
});
|
||||
}
|
||||
|
||||
tick() {
|
||||
@@ -210,6 +222,7 @@ export class NukeTrajectoryPreviewLayer implements Layer {
|
||||
targetTile,
|
||||
speed,
|
||||
distanceBasedHeight,
|
||||
this.uiState.rocketDirectionUp,
|
||||
);
|
||||
|
||||
this.trajectoryPoints = pathFinder.allTiles();
|
||||
|
||||
@@ -373,10 +373,16 @@ export class StructureIconsLayer implements Layer {
|
||||
),
|
||||
);
|
||||
} else if (this.ghostUnit.buildableUnit.canBuild) {
|
||||
const unitType = this.ghostUnit.buildableUnit.type;
|
||||
const rocketDirectionUp =
|
||||
unitType === UnitType.AtomBomb || unitType === UnitType.HydrogenBomb
|
||||
? this.uiState.rocketDirectionUp
|
||||
: undefined;
|
||||
this.eventBus.emit(
|
||||
new BuildUnitIntentEvent(
|
||||
this.ghostUnit.buildableUnit.type,
|
||||
unitType,
|
||||
this.game.ref(tile.x, tile.y),
|
||||
rocketDirectionUp,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -311,6 +311,7 @@ export const BuildUnitIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal("build_unit"),
|
||||
unit: z.enum(UnitType),
|
||||
tile: z.number(),
|
||||
rocketDirectionUp: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const UpgradeStructureIntentSchema = BaseIntentSchema.extend({
|
||||
|
||||
@@ -21,6 +21,7 @@ export class ConstructionExecution implements Execution {
|
||||
private player: Player,
|
||||
private constructionType: UnitType,
|
||||
private tile: TileRef,
|
||||
private rocketDirectionUp?: boolean,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
@@ -104,7 +105,15 @@ export class ConstructionExecution implements Execution {
|
||||
case UnitType.AtomBomb:
|
||||
case UnitType.HydrogenBomb:
|
||||
this.mg.addExecution(
|
||||
new NukeExecution(this.constructionType, player, this.tile),
|
||||
new NukeExecution(
|
||||
this.constructionType,
|
||||
player,
|
||||
this.tile,
|
||||
null,
|
||||
-1,
|
||||
0,
|
||||
this.rocketDirectionUp,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case UnitType.MIRV:
|
||||
|
||||
@@ -106,7 +106,12 @@ export class Executor {
|
||||
case "embargo_all":
|
||||
return new EmbargoAllExecution(player, intent.action);
|
||||
case "build_unit":
|
||||
return new ConstructionExecution(player, intent.unit, intent.tile);
|
||||
return new ConstructionExecution(
|
||||
player,
|
||||
intent.unit,
|
||||
intent.tile,
|
||||
intent.rocketDirectionUp,
|
||||
);
|
||||
case "allianceExtension": {
|
||||
return new AllianceExtensionExecution(player, intent.recipient);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export class NukeExecution implements Execution {
|
||||
private src?: TileRef | null,
|
||||
private speed: number = -1,
|
||||
private waitTicks = 0,
|
||||
private rocketDirectionUp: boolean = true,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
@@ -137,6 +138,7 @@ export class NukeExecution implements Execution {
|
||||
this.dst,
|
||||
this.speed,
|
||||
this.nukeType !== UnitType.MIRVWarhead,
|
||||
this.rocketDirectionUp,
|
||||
);
|
||||
this.nuke = this.player.buildUnit(this.nukeType, spawn, {
|
||||
targetTile: this.dst,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Game } from "../game/Game";
|
||||
import { GameMap, TileRef } from "../game/GameMap";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { within } from "../Util";
|
||||
import { DistanceBasedBezierCurve } from "../utilities/Line";
|
||||
import { AStar, AStarResult, PathFindResultType } from "./AStar";
|
||||
import { MiniAStar } from "./MiniAStar";
|
||||
@@ -16,6 +17,7 @@ export class ParabolaPathFinder {
|
||||
dst: TileRef,
|
||||
increment: number = 3,
|
||||
distanceBasedHeight = true,
|
||||
directionUp = true,
|
||||
) {
|
||||
const p0 = { x: this.mg.x(orig), y: this.mg.y(orig) };
|
||||
const p3 = { x: this.mg.x(dst), y: this.mg.y(dst) };
|
||||
@@ -25,14 +27,24 @@ export class ParabolaPathFinder {
|
||||
const maxHeight = distanceBasedHeight
|
||||
? Math.max(distance / 3, parabolaMinHeight)
|
||||
: 0;
|
||||
// Use a bezier curve always pointing up
|
||||
// Use a bezier curve pointing up or down based on directionUp parameter
|
||||
const heightMultiplier = directionUp ? -1 : 1;
|
||||
const mapHeight = this.mg.height();
|
||||
const p1 = {
|
||||
x: p0.x + (p3.x - p0.x) / 4,
|
||||
y: Math.max(p0.y + (p3.y - p0.y) / 4 - maxHeight, 0),
|
||||
y: within(
|
||||
p0.y + (p3.y - p0.y) / 4 + heightMultiplier * maxHeight,
|
||||
0,
|
||||
mapHeight - 1,
|
||||
),
|
||||
};
|
||||
const p2 = {
|
||||
x: p0.x + ((p3.x - p0.x) * 3) / 4,
|
||||
y: Math.max(p0.y + ((p3.y - p0.y) * 3) / 4 - maxHeight, 0),
|
||||
y: within(
|
||||
p0.y + ((p3.y - p0.y) * 3) / 4 + heightMultiplier * maxHeight,
|
||||
0,
|
||||
mapHeight - 1,
|
||||
),
|
||||
};
|
||||
|
||||
this.curve = new DistanceBasedBezierCurve(p0, p1, p2, p3, increment);
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
eventBus = new EventBus();
|
||||
|
||||
inputHandler = new InputHandler(
|
||||
{ attackRatio: 20, ghostStructure: null },
|
||||
{ attackRatio: 20, ghostStructure: null, rocketDirectionUp: true },
|
||||
mockCanvas,
|
||||
eventBus,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user