mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:40:43 +00:00
fix(protocol): support kick intents in binary gameplay
This commit is contained in:
+10
-1
@@ -9,6 +9,7 @@ import {
|
||||
INTENT_FLAG_OPTION_A,
|
||||
INTENT_FLAG_OPTION_B,
|
||||
createBinaryProtocolContext,
|
||||
hasBinaryIntentOpcode,
|
||||
intentTypeToOpcode,
|
||||
opcodeToIntentType,
|
||||
opcodeToUnitType,
|
||||
@@ -554,6 +555,8 @@ function encodeIntent(
|
||||
writer.writeBoolean(intent.paused);
|
||||
return;
|
||||
case "kick_player":
|
||||
writeRequiredPlayerRef(writer, intent.target, context);
|
||||
return;
|
||||
case "update_game_config":
|
||||
throw new Error(`Unsupported binary intent type: ${intent.type}`);
|
||||
}
|
||||
@@ -750,6 +753,12 @@ function decodeIntent(
|
||||
type: "toggle_pause",
|
||||
paused: reader.readBoolean(),
|
||||
};
|
||||
case "kick_player":
|
||||
assertIntentFlags(intentType, flags, 0);
|
||||
return {
|
||||
type: "kick_player",
|
||||
target: readRequiredPlayerRef(reader, context),
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unhandled binary intent type: ${intentType}`);
|
||||
@@ -824,7 +833,7 @@ export function isBinaryGameplayClientMessage(
|
||||
message: ClientMessage,
|
||||
): message is BinaryClientGameplayMessage {
|
||||
return (
|
||||
message.type === "intent" ||
|
||||
(message.type === "intent" && hasBinaryIntentOpcode(message.intent.type)) ||
|
||||
message.type === "hash" ||
|
||||
message.type === "ping"
|
||||
);
|
||||
|
||||
@@ -49,6 +49,7 @@ export enum BinaryIntentType {
|
||||
AllianceExtension = 20,
|
||||
DeleteUnit = 21,
|
||||
TogglePause = 22,
|
||||
KickPlayer = 23,
|
||||
}
|
||||
|
||||
export const INTENT_FLAG_OPTION_A = 1 << 0;
|
||||
@@ -88,7 +89,7 @@ const INTENT_TYPE_TO_OPCODE: Record<
|
||||
quick_chat: BinaryIntentType.QuickChat,
|
||||
allianceExtension: BinaryIntentType.AllianceExtension,
|
||||
delete_unit: BinaryIntentType.DeleteUnit,
|
||||
kick_player: undefined,
|
||||
kick_player: BinaryIntentType.KickPlayer,
|
||||
toggle_pause: BinaryIntentType.TogglePause,
|
||||
update_game_config: undefined,
|
||||
};
|
||||
@@ -116,6 +117,7 @@ const OPCODE_TO_INTENT_TYPE: Record<BinaryIntentType, Intent["type"]> = {
|
||||
[BinaryIntentType.AllianceExtension]: "allianceExtension",
|
||||
[BinaryIntentType.DeleteUnit]: "delete_unit",
|
||||
[BinaryIntentType.TogglePause]: "toggle_pause",
|
||||
[BinaryIntentType.KickPlayer]: "kick_player",
|
||||
};
|
||||
|
||||
const UNIT_TYPE_TO_OPCODE = new Map<UnitType, number>(
|
||||
@@ -155,6 +157,10 @@ export function intentTypeToOpcode(
|
||||
return opcode;
|
||||
}
|
||||
|
||||
export function hasBinaryIntentOpcode(intentType: Intent["type"]): boolean {
|
||||
return INTENT_TYPE_TO_OPCODE[intentType] !== undefined;
|
||||
}
|
||||
|
||||
export function opcodeToIntentType(opcode: number): Intent["type"] {
|
||||
const intentType = OPCODE_TO_INTENT_TYPE[opcode as BinaryIntentType];
|
||||
if (intentType === undefined) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
decodeBinaryServerGameplayMessage,
|
||||
encodeBinaryClientGameplayMessage,
|
||||
encodeBinaryServerGameplayMessage,
|
||||
isBinaryGameplayClientMessage,
|
||||
} from "../../src/core/BinaryCodec";
|
||||
import { BINARY_PROTOCOL_VERSION } from "../../src/core/BinaryProtocol";
|
||||
import {
|
||||
@@ -251,6 +252,13 @@ const clientIntentMessages: ClientIntentMessage[] = [
|
||||
paused: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "intent",
|
||||
intent: {
|
||||
type: "kick_player",
|
||||
target: "P0000002",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe("BinaryCodec", () => {
|
||||
@@ -283,6 +291,28 @@ describe("BinaryCodec", () => {
|
||||
expect(decoded).toEqual(message);
|
||||
});
|
||||
|
||||
it("only classifies supported intents as binary gameplay messages", () => {
|
||||
expect(
|
||||
isBinaryGameplayClientMessage({
|
||||
type: "intent",
|
||||
intent: {
|
||||
type: "kick_player",
|
||||
target: "P0000002",
|
||||
},
|
||||
} as ClientIntentMessage),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isBinaryGameplayClientMessage({
|
||||
type: "intent",
|
||||
intent: {
|
||||
type: "update_game_config",
|
||||
config: {},
|
||||
},
|
||||
} as ClientIntentMessage),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("round-trips server turn messages", () => {
|
||||
const message: ServerTurnMessage = {
|
||||
type: "turn",
|
||||
|
||||
@@ -237,6 +237,65 @@ describe("GameServer binary gameplay protocol", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("supports host kicks via binary gameplay intents after start", () => {
|
||||
const logger = createMockLogger();
|
||||
const hostPersistentId = "66666666-6666-4666-8666-666666666666";
|
||||
const game = new GameServer(
|
||||
"TEST0005",
|
||||
logger as any,
|
||||
Date.now(),
|
||||
{
|
||||
turnIntervalMs: () => 100,
|
||||
env: () => 0,
|
||||
} as any,
|
||||
createGameConfig(),
|
||||
hostPersistentId,
|
||||
);
|
||||
|
||||
const host = createClient("P0000001", hostPersistentId, "Host");
|
||||
const target = createClient(
|
||||
"P0000002",
|
||||
"77777777-7777-4777-8777-777777777777",
|
||||
"Target",
|
||||
);
|
||||
|
||||
expect(game.joinClient(host.client)).toBe("joined");
|
||||
expect(game.joinClient(target.client)).toBe("joined");
|
||||
game.start();
|
||||
|
||||
const startPayload = host.ws.sent.find(
|
||||
(message): message is string =>
|
||||
typeof message === "string" && message.includes('"type":"start"'),
|
||||
);
|
||||
expect(startPayload).toBeDefined();
|
||||
|
||||
const binaryContext = binaryContextFromGameStartInfo(
|
||||
JSON.parse(startPayload!).gameStartInfo,
|
||||
);
|
||||
const kickMessage = encodeBinaryClientGameplayMessage(
|
||||
{
|
||||
type: "intent",
|
||||
intent: {
|
||||
type: "kick_player",
|
||||
target: "P0000002",
|
||||
},
|
||||
},
|
||||
binaryContext,
|
||||
);
|
||||
|
||||
host.ws.emit("message", kickMessage, true);
|
||||
|
||||
expect(target.ws.sent).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('"error":"kick_reason.lobby_creator"'),
|
||||
]),
|
||||
);
|
||||
expect(target.ws.readyState).toBe(3);
|
||||
expect(game.activeClients.map((client) => client.clientID)).toEqual([
|
||||
"P0000001",
|
||||
]);
|
||||
});
|
||||
|
||||
it("kicks malformed binary gameplay messages after start", () => {
|
||||
const logger = createMockLogger();
|
||||
const game = new GameServer(
|
||||
|
||||
Reference in New Issue
Block a user