mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-09 15:54:47 -07:00
Implement game layer from protobuf to redux
This commit is contained in:
parent
d96d5e1589
commit
74803442d2
82 changed files with 2455 additions and 88 deletions
234
webclient/src/store/game/game.actions.ts
Normal file
234
webclient/src/store/game/game.actions.ts
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
import {
|
||||
AttachCardData,
|
||||
ChangeZonePropertiesData,
|
||||
CreateArrowData,
|
||||
CreateCounterData,
|
||||
CreateTokenData,
|
||||
DelCounterData,
|
||||
DeleteArrowData,
|
||||
DestroyCardData,
|
||||
DrawCardsData,
|
||||
DumpZoneData,
|
||||
FlipCardData,
|
||||
GameStateChangedData,
|
||||
MoveCardData,
|
||||
PlayerProperties,
|
||||
RevealCardsData,
|
||||
RollDieData,
|
||||
SetCardAttrData,
|
||||
SetCardCounterData,
|
||||
SetCounterData,
|
||||
ShuffleData,
|
||||
} from 'types';
|
||||
import { GameEntry } from './game.interfaces';
|
||||
import { Types } from './game.types';
|
||||
|
||||
export const Actions = {
|
||||
clearStore: () => ({
|
||||
type: Types.CLEAR_STORE,
|
||||
}),
|
||||
|
||||
gameJoined: (gameId: number, gameEntry: GameEntry) => ({
|
||||
type: Types.GAME_JOINED,
|
||||
gameId,
|
||||
gameEntry,
|
||||
}),
|
||||
|
||||
gameLeft: (gameId: number) => ({
|
||||
type: Types.GAME_LEFT,
|
||||
gameId,
|
||||
}),
|
||||
|
||||
gameClosed: (gameId: number) => ({
|
||||
type: Types.GAME_CLOSED,
|
||||
gameId,
|
||||
}),
|
||||
|
||||
gameHostChanged: (gameId: number, hostId: number) => ({
|
||||
type: Types.GAME_HOST_CHANGED,
|
||||
gameId,
|
||||
hostId,
|
||||
}),
|
||||
|
||||
gameStateChanged: (gameId: number, data: GameStateChangedData) => ({
|
||||
type: Types.GAME_STATE_CHANGED,
|
||||
gameId,
|
||||
data,
|
||||
}),
|
||||
|
||||
playerJoined: (gameId: number, playerProperties: PlayerProperties) => ({
|
||||
type: Types.PLAYER_JOINED,
|
||||
gameId,
|
||||
playerProperties,
|
||||
}),
|
||||
|
||||
playerLeft: (gameId: number, playerId: number, reason: number) => ({
|
||||
type: Types.PLAYER_LEFT,
|
||||
gameId,
|
||||
playerId,
|
||||
reason,
|
||||
}),
|
||||
|
||||
playerPropertiesChanged: (gameId: number, playerId: number, properties: PlayerProperties) => ({
|
||||
type: Types.PLAYER_PROPERTIES_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
properties,
|
||||
}),
|
||||
|
||||
kicked: (gameId: number) => ({
|
||||
type: Types.KICKED,
|
||||
gameId,
|
||||
}),
|
||||
|
||||
cardMoved: (gameId: number, playerId: number, data: MoveCardData) => ({
|
||||
type: Types.CARD_MOVED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardFlipped: (gameId: number, playerId: number, data: FlipCardData) => ({
|
||||
type: Types.CARD_FLIPPED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardDestroyed: (gameId: number, playerId: number, data: DestroyCardData) => ({
|
||||
type: Types.CARD_DESTROYED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardAttached: (gameId: number, playerId: number, data: AttachCardData) => ({
|
||||
type: Types.CARD_ATTACHED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
tokenCreated: (gameId: number, playerId: number, data: CreateTokenData) => ({
|
||||
type: Types.TOKEN_CREATED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardAttrChanged: (gameId: number, playerId: number, data: SetCardAttrData) => ({
|
||||
type: Types.CARD_ATTR_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardCounterChanged: (gameId: number, playerId: number, data: SetCardCounterData) => ({
|
||||
type: Types.CARD_COUNTER_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
arrowCreated: (gameId: number, playerId: number, data: CreateArrowData) => ({
|
||||
type: Types.ARROW_CREATED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
arrowDeleted: (gameId: number, playerId: number, data: DeleteArrowData) => ({
|
||||
type: Types.ARROW_DELETED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
counterCreated: (gameId: number, playerId: number, data: CreateCounterData) => ({
|
||||
type: Types.COUNTER_CREATED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
counterSet: (gameId: number, playerId: number, data: SetCounterData) => ({
|
||||
type: Types.COUNTER_SET,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
counterDeleted: (gameId: number, playerId: number, data: DelCounterData) => ({
|
||||
type: Types.COUNTER_DELETED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardsDrawn: (gameId: number, playerId: number, data: DrawCardsData) => ({
|
||||
type: Types.CARDS_DRAWN,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
cardsRevealed: (gameId: number, playerId: number, data: RevealCardsData) => ({
|
||||
type: Types.CARDS_REVEALED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
zoneShuffled: (gameId: number, playerId: number, data: ShuffleData) => ({
|
||||
type: Types.ZONE_SHUFFLED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
dieRolled: (gameId: number, playerId: number, data: RollDieData) => ({
|
||||
type: Types.DIE_ROLLED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
activePlayerSet: (gameId: number, activePlayerId: number) => ({
|
||||
type: Types.ACTIVE_PLAYER_SET,
|
||||
gameId,
|
||||
activePlayerId,
|
||||
}),
|
||||
|
||||
activePhaseSet: (gameId: number, phase: number) => ({
|
||||
type: Types.ACTIVE_PHASE_SET,
|
||||
gameId,
|
||||
phase,
|
||||
}),
|
||||
|
||||
turnReversed: (gameId: number, reversed: boolean) => ({
|
||||
type: Types.TURN_REVERSED,
|
||||
gameId,
|
||||
reversed,
|
||||
}),
|
||||
|
||||
zoneDumped: (gameId: number, playerId: number, data: DumpZoneData) => ({
|
||||
type: Types.ZONE_DUMPED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
zonePropertiesChanged: (gameId: number, playerId: number, data: ChangeZonePropertiesData) => ({
|
||||
type: Types.ZONE_PROPERTIES_CHANGED,
|
||||
gameId,
|
||||
playerId,
|
||||
data,
|
||||
}),
|
||||
|
||||
gameSay: (gameId: number, playerId: number, message: string) => ({
|
||||
type: Types.GAME_SAY,
|
||||
gameId,
|
||||
playerId,
|
||||
message,
|
||||
}),
|
||||
};
|
||||
155
webclient/src/store/game/game.dispatch.ts
Normal file
155
webclient/src/store/game/game.dispatch.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import {
|
||||
AttachCardData,
|
||||
ChangeZonePropertiesData,
|
||||
CreateArrowData,
|
||||
CreateCounterData,
|
||||
CreateTokenData,
|
||||
DelCounterData,
|
||||
DeleteArrowData,
|
||||
DestroyCardData,
|
||||
DrawCardsData,
|
||||
DumpZoneData,
|
||||
FlipCardData,
|
||||
GameStateChangedData,
|
||||
MoveCardData,
|
||||
PlayerProperties,
|
||||
RevealCardsData,
|
||||
RollDieData,
|
||||
SetCardAttrData,
|
||||
SetCardCounterData,
|
||||
SetCounterData,
|
||||
ShuffleData,
|
||||
} from 'types';
|
||||
import { store } from 'store/store';
|
||||
import { Actions } from './game.actions';
|
||||
import { GameEntry } from './game.interfaces';
|
||||
|
||||
export const Dispatch = {
|
||||
clearStore: () => {
|
||||
store.dispatch(Actions.clearStore());
|
||||
},
|
||||
|
||||
gameJoined: (gameId: number, gameEntry: GameEntry) => {
|
||||
store.dispatch(Actions.gameJoined(gameId, gameEntry));
|
||||
},
|
||||
|
||||
gameLeft: (gameId: number) => {
|
||||
store.dispatch(Actions.gameLeft(gameId));
|
||||
},
|
||||
|
||||
gameClosed: (gameId: number) => {
|
||||
store.dispatch(Actions.gameClosed(gameId));
|
||||
},
|
||||
|
||||
gameHostChanged: (gameId: number, hostId: number) => {
|
||||
store.dispatch(Actions.gameHostChanged(gameId, hostId));
|
||||
},
|
||||
|
||||
gameStateChanged: (gameId: number, data: GameStateChangedData) => {
|
||||
store.dispatch(Actions.gameStateChanged(gameId, data));
|
||||
},
|
||||
|
||||
playerJoined: (gameId: number, playerProperties: PlayerProperties) => {
|
||||
store.dispatch(Actions.playerJoined(gameId, playerProperties));
|
||||
},
|
||||
|
||||
playerLeft: (gameId: number, playerId: number, reason: number) => {
|
||||
store.dispatch(Actions.playerLeft(gameId, playerId, reason));
|
||||
},
|
||||
|
||||
playerPropertiesChanged: (gameId: number, playerId: number, properties: PlayerProperties) => {
|
||||
store.dispatch(Actions.playerPropertiesChanged(gameId, playerId, properties));
|
||||
},
|
||||
|
||||
kicked: (gameId: number) => {
|
||||
store.dispatch(Actions.kicked(gameId));
|
||||
},
|
||||
|
||||
cardMoved: (gameId: number, playerId: number, data: MoveCardData) => {
|
||||
store.dispatch(Actions.cardMoved(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardFlipped: (gameId: number, playerId: number, data: FlipCardData) => {
|
||||
store.dispatch(Actions.cardFlipped(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardDestroyed: (gameId: number, playerId: number, data: DestroyCardData) => {
|
||||
store.dispatch(Actions.cardDestroyed(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardAttached: (gameId: number, playerId: number, data: AttachCardData) => {
|
||||
store.dispatch(Actions.cardAttached(gameId, playerId, data));
|
||||
},
|
||||
|
||||
tokenCreated: (gameId: number, playerId: number, data: CreateTokenData) => {
|
||||
store.dispatch(Actions.tokenCreated(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardAttrChanged: (gameId: number, playerId: number, data: SetCardAttrData) => {
|
||||
store.dispatch(Actions.cardAttrChanged(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardCounterChanged: (gameId: number, playerId: number, data: SetCardCounterData) => {
|
||||
store.dispatch(Actions.cardCounterChanged(gameId, playerId, data));
|
||||
},
|
||||
|
||||
arrowCreated: (gameId: number, playerId: number, data: CreateArrowData) => {
|
||||
store.dispatch(Actions.arrowCreated(gameId, playerId, data));
|
||||
},
|
||||
|
||||
arrowDeleted: (gameId: number, playerId: number, data: DeleteArrowData) => {
|
||||
store.dispatch(Actions.arrowDeleted(gameId, playerId, data));
|
||||
},
|
||||
|
||||
counterCreated: (gameId: number, playerId: number, data: CreateCounterData) => {
|
||||
store.dispatch(Actions.counterCreated(gameId, playerId, data));
|
||||
},
|
||||
|
||||
counterSet: (gameId: number, playerId: number, data: SetCounterData) => {
|
||||
store.dispatch(Actions.counterSet(gameId, playerId, data));
|
||||
},
|
||||
|
||||
counterDeleted: (gameId: number, playerId: number, data: DelCounterData) => {
|
||||
store.dispatch(Actions.counterDeleted(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardsDrawn: (gameId: number, playerId: number, data: DrawCardsData) => {
|
||||
store.dispatch(Actions.cardsDrawn(gameId, playerId, data));
|
||||
},
|
||||
|
||||
cardsRevealed: (gameId: number, playerId: number, data: RevealCardsData) => {
|
||||
store.dispatch(Actions.cardsRevealed(gameId, playerId, data));
|
||||
},
|
||||
|
||||
zoneShuffled: (gameId: number, playerId: number, data: ShuffleData) => {
|
||||
store.dispatch(Actions.zoneShuffled(gameId, playerId, data));
|
||||
},
|
||||
|
||||
dieRolled: (gameId: number, playerId: number, data: RollDieData) => {
|
||||
store.dispatch(Actions.dieRolled(gameId, playerId, data));
|
||||
},
|
||||
|
||||
activePlayerSet: (gameId: number, activePlayerId: number) => {
|
||||
store.dispatch(Actions.activePlayerSet(gameId, activePlayerId));
|
||||
},
|
||||
|
||||
activePhaseSet: (gameId: number, phase: number) => {
|
||||
store.dispatch(Actions.activePhaseSet(gameId, phase));
|
||||
},
|
||||
|
||||
turnReversed: (gameId: number, reversed: boolean) => {
|
||||
store.dispatch(Actions.turnReversed(gameId, reversed));
|
||||
},
|
||||
|
||||
zoneDumped: (gameId: number, playerId: number, data: DumpZoneData) => {
|
||||
store.dispatch(Actions.zoneDumped(gameId, playerId, data));
|
||||
},
|
||||
|
||||
zonePropertiesChanged: (gameId: number, playerId: number, data: ChangeZonePropertiesData) => {
|
||||
store.dispatch(Actions.zonePropertiesChanged(gameId, playerId, data));
|
||||
},
|
||||
|
||||
gameSay: (gameId: number, playerId: number, message: string) => {
|
||||
store.dispatch(Actions.gameSay(gameId, playerId, message));
|
||||
},
|
||||
};
|
||||
59
webclient/src/store/game/game.interfaces.ts
Normal file
59
webclient/src/store/game/game.interfaces.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { ArrowInfo, CardInfo, CounterInfo, PlayerProperties } from 'types';
|
||||
|
||||
export interface GamesState {
|
||||
games: { [gameId: number]: GameEntry };
|
||||
}
|
||||
|
||||
/**
|
||||
* Full runtime state for a single active game (played or spectated).
|
||||
* Keyed by gameId in GamesState so multiple concurrent games are supported.
|
||||
*/
|
||||
export interface GameEntry {
|
||||
gameId: number;
|
||||
roomId: number;
|
||||
description: string;
|
||||
hostId: number;
|
||||
/** The playerId assigned to the local user in this game. */
|
||||
localPlayerId: number;
|
||||
spectator: boolean;
|
||||
judge: boolean;
|
||||
started: boolean;
|
||||
activePlayerId: number;
|
||||
activePhase: number;
|
||||
secondsElapsed: number;
|
||||
reversed: boolean;
|
||||
players: { [playerId: number]: PlayerEntry };
|
||||
messages: GameMessage[];
|
||||
}
|
||||
|
||||
/** Normalized from ServerInfo_Player — keyed collections for O(1) lookup. */
|
||||
export interface PlayerEntry {
|
||||
properties: PlayerProperties;
|
||||
deckList: string;
|
||||
/** Zones keyed by zone name (e.g. "hand", "deck", "table"). */
|
||||
zones: { [zoneName: string]: ZoneEntry };
|
||||
/** Player-level counters (e.g. life) keyed by counter id. */
|
||||
counters: { [counterId: number]: CounterInfo };
|
||||
/** Arrows keyed by arrow id. */
|
||||
arrows: { [arrowId: number]: ArrowInfo };
|
||||
}
|
||||
|
||||
/** Normalized from ServerInfo_Zone — card list is an ordered array matching proto. */
|
||||
export interface ZoneEntry {
|
||||
name: string;
|
||||
/** ZoneType enum value (0=Private, 1=Public, 2=Hidden). */
|
||||
type: number;
|
||||
withCoords: boolean;
|
||||
/** Authoritative card count (used for hidden zones where cardList may be empty). */
|
||||
cardCount: number;
|
||||
/** Ordered card list; may be empty for hidden zones with no dump active. */
|
||||
cards: CardInfo[];
|
||||
alwaysRevealTopCard: boolean;
|
||||
alwaysLookAtTopCard: boolean;
|
||||
}
|
||||
|
||||
export interface GameMessage {
|
||||
playerId: number;
|
||||
message: string;
|
||||
timeReceived: number;
|
||||
}
|
||||
729
webclient/src/store/game/game.reducer.ts
Normal file
729
webclient/src/store/game/game.reducer.ts
Normal file
|
|
@ -0,0 +1,729 @@
|
|||
import {
|
||||
ArrowInfo,
|
||||
CardAttribute,
|
||||
CardCounterInfo,
|
||||
CardInfo,
|
||||
CounterInfo,
|
||||
PlayerInfo,
|
||||
PlayerProperties,
|
||||
} from 'types';
|
||||
import { GameEntry, GameMessage, GamesState, PlayerEntry, ZoneEntry } from './game.interfaces';
|
||||
import { Types } from './game.types';
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function updateGame(state: GamesState, gameId: number, updates: Partial<GameEntry>): GamesState {
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
games: { ...state.games, [gameId]: { ...game, ...updates } },
|
||||
};
|
||||
}
|
||||
|
||||
function updatePlayer(
|
||||
state: GamesState,
|
||||
gameId: number,
|
||||
playerId: number,
|
||||
updates: Partial<PlayerEntry>
|
||||
): GamesState {
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
return updateGame(state, gameId, {
|
||||
players: { ...game.players, [playerId]: { ...player, ...updates } },
|
||||
});
|
||||
}
|
||||
|
||||
function updateZone(
|
||||
state: GamesState,
|
||||
gameId: number,
|
||||
playerId: number,
|
||||
zoneName: string,
|
||||
updates: Partial<ZoneEntry>
|
||||
): GamesState {
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[zoneName];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
return updatePlayer(state, gameId, playerId, {
|
||||
zones: { ...player.zones, [zoneName]: { ...zone, ...updates } },
|
||||
});
|
||||
}
|
||||
|
||||
function removeGame(state: GamesState, gameId: number): GamesState {
|
||||
const games = { ...state.games };
|
||||
delete games[gameId];
|
||||
return { ...state, games };
|
||||
}
|
||||
|
||||
/** Converts the proto PlayerInfo[] array into the keyed PlayerEntry map used in the store. */
|
||||
function normalizePlayers(playerList: PlayerInfo[]): { [playerId: number]: PlayerEntry } {
|
||||
const players: { [playerId: number]: PlayerEntry } = {};
|
||||
for (const player of playerList) {
|
||||
const playerId = player.properties.playerId;
|
||||
|
||||
const zones: { [zoneName: string]: ZoneEntry } = {};
|
||||
for (const zone of player.zoneList) {
|
||||
zones[zone.name] = {
|
||||
name: zone.name,
|
||||
type: zone.type,
|
||||
withCoords: zone.withCoords,
|
||||
cardCount: zone.cardCount,
|
||||
cards: [...zone.cardList],
|
||||
alwaysRevealTopCard: zone.alwaysRevealTopCard,
|
||||
alwaysLookAtTopCard: zone.alwaysLookAtTopCard,
|
||||
};
|
||||
}
|
||||
|
||||
const counters: { [counterId: number]: CounterInfo } = {};
|
||||
for (const counter of player.counterList) {
|
||||
counters[counter.id] = counter;
|
||||
}
|
||||
|
||||
const arrows: { [arrowId: number]: ArrowInfo } = {};
|
||||
for (const arrow of player.arrowList) {
|
||||
arrows[arrow.id] = arrow;
|
||||
}
|
||||
|
||||
players[playerId] = {
|
||||
properties: player.properties,
|
||||
deckList: player.deckList,
|
||||
zones,
|
||||
counters,
|
||||
arrows,
|
||||
};
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
function buildEmptyCard(
|
||||
id: number,
|
||||
name: string,
|
||||
x: number,
|
||||
y: number,
|
||||
faceDown: boolean,
|
||||
providerId: string
|
||||
): CardInfo {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
x,
|
||||
y,
|
||||
faceDown,
|
||||
tapped: false,
|
||||
attacking: false,
|
||||
color: '',
|
||||
pt: '',
|
||||
annotation: '',
|
||||
destroyOnZoneChange: false,
|
||||
doesntUntap: false,
|
||||
counterList: [],
|
||||
attachPlayerId: -1,
|
||||
attachZone: '',
|
||||
attachCardId: -1,
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Initial state ─────────────────────────────────────────────────────────────
|
||||
|
||||
const initialState: GamesState = {
|
||||
games: {},
|
||||
};
|
||||
|
||||
// ── Reducer ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export const gamesReducer = (state: GamesState = initialState, action: any): GamesState => {
|
||||
switch (action.type) {
|
||||
case Types.CLEAR_STORE: {
|
||||
return initialState;
|
||||
}
|
||||
|
||||
case Types.GAME_JOINED: {
|
||||
return {
|
||||
...state,
|
||||
games: { ...state.games, [action.gameId]: action.gameEntry },
|
||||
};
|
||||
}
|
||||
|
||||
case Types.GAME_LEFT:
|
||||
case Types.GAME_CLOSED:
|
||||
case Types.KICKED: {
|
||||
return removeGame(state, action.gameId);
|
||||
}
|
||||
|
||||
case Types.GAME_HOST_CHANGED: {
|
||||
return updateGame(state, action.gameId, { hostId: action.hostId });
|
||||
}
|
||||
|
||||
case Types.GAME_STATE_CHANGED: {
|
||||
const { gameId, data } = action;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const updates: Partial<GameEntry> = {};
|
||||
if (data.playerList?.length > 0) {
|
||||
updates.players = normalizePlayers(data.playerList);
|
||||
}
|
||||
if (data.gameStarted !== undefined && data.gameStarted !== null) {
|
||||
updates.started = data.gameStarted;
|
||||
}
|
||||
if (data.activePlayerId !== undefined && data.activePlayerId !== null) {
|
||||
updates.activePlayerId = data.activePlayerId;
|
||||
}
|
||||
if (data.activePhase !== undefined && data.activePhase !== null) {
|
||||
updates.activePhase = data.activePhase;
|
||||
}
|
||||
if (data.secondsElapsed !== undefined) {
|
||||
updates.secondsElapsed = data.secondsElapsed;
|
||||
}
|
||||
return updateGame(state, gameId, updates);
|
||||
}
|
||||
|
||||
case Types.PLAYER_JOINED: {
|
||||
const { gameId, playerProperties } = action;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const newPlayer: PlayerEntry = {
|
||||
properties: playerProperties as PlayerProperties,
|
||||
deckList: '',
|
||||
zones: {},
|
||||
counters: {},
|
||||
arrows: {},
|
||||
};
|
||||
return updateGame(state, gameId, {
|
||||
players: { ...game.players, [playerProperties.playerId]: newPlayer },
|
||||
});
|
||||
}
|
||||
|
||||
case Types.PLAYER_LEFT: {
|
||||
const { gameId, playerId } = action;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const players = { ...game.players };
|
||||
delete players[playerId];
|
||||
return updateGame(state, gameId, { players });
|
||||
}
|
||||
|
||||
case Types.PLAYER_PROPERTIES_CHANGED: {
|
||||
return updatePlayer(state, action.gameId, action.playerId, {
|
||||
properties: action.properties,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Card manipulation ────────────────────────────────────────────────────
|
||||
|
||||
case Types.CARD_MOVED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const {
|
||||
cardId,
|
||||
cardName,
|
||||
startPlayerId,
|
||||
startZone,
|
||||
position,
|
||||
targetPlayerId,
|
||||
targetZone,
|
||||
x,
|
||||
y,
|
||||
newCardId,
|
||||
faceDown,
|
||||
newCardProviderId,
|
||||
} = data;
|
||||
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const effectiveStartPlayerId = startPlayerId >= 0 ? startPlayerId : playerId;
|
||||
const sourcePlayer = game.players[effectiveStartPlayerId];
|
||||
const sourceZoneEntry = sourcePlayer?.zones[startZone];
|
||||
if (!sourcePlayer || !sourceZoneEntry) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Locate card in source zone (by id for visible zones, by position for hidden)
|
||||
let removedCard: CardInfo | undefined;
|
||||
let newSourceCards: CardInfo[];
|
||||
if (cardId >= 0) {
|
||||
removedCard = sourceZoneEntry.cards.find(c => c.id === cardId);
|
||||
newSourceCards = sourceZoneEntry.cards.filter(c => c.id !== cardId);
|
||||
} else if (position >= 0 && position < sourceZoneEntry.cards.length) {
|
||||
removedCard = sourceZoneEntry.cards[position];
|
||||
newSourceCards = sourceZoneEntry.cards.filter((_, i) => i !== position);
|
||||
} else {
|
||||
// Hidden zone with unknown position — just decrement count
|
||||
newSourceCards = sourceZoneEntry.cards;
|
||||
}
|
||||
|
||||
const effectiveNewId = newCardId >= 0 ? newCardId : (removedCard?.id ?? -1);
|
||||
const movedCard: CardInfo = removedCard
|
||||
? {
|
||||
...removedCard,
|
||||
id: effectiveNewId,
|
||||
name: cardName || removedCard.name,
|
||||
x,
|
||||
y,
|
||||
faceDown,
|
||||
providerId: newCardProviderId || removedCard.providerId,
|
||||
}
|
||||
: buildEmptyCard(effectiveNewId, cardName, x, y, faceDown, newCardProviderId ?? '');
|
||||
|
||||
let newState = updateZone(state, gameId, effectiveStartPlayerId, startZone, {
|
||||
cards: newSourceCards,
|
||||
cardCount: Math.max(0, sourceZoneEntry.cardCount - 1),
|
||||
});
|
||||
|
||||
const updatedGame = newState.games[gameId];
|
||||
const targetPlayer = updatedGame?.players[targetPlayerId];
|
||||
const targetZoneEntry = targetPlayer?.zones[targetZone];
|
||||
if (!targetPlayer || !targetZoneEntry) {
|
||||
return newState;
|
||||
}
|
||||
|
||||
newState = updateZone(newState, gameId, targetPlayerId, targetZone, {
|
||||
cards: [...targetZoneEntry.cards, movedCard],
|
||||
cardCount: targetZoneEntry.cardCount + 1,
|
||||
});
|
||||
return newState;
|
||||
}
|
||||
|
||||
case Types.CARD_FLIPPED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { zoneName, cardId, cardName, faceDown, cardProviderId } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[zoneName];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const cardIdx = zone.cards.findIndex(c => c.id === cardId);
|
||||
if (cardIdx < 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const updatedCards = [...zone.cards];
|
||||
updatedCards[cardIdx] = {
|
||||
...updatedCards[cardIdx],
|
||||
faceDown,
|
||||
name: cardName || updatedCards[cardIdx].name,
|
||||
providerId: cardProviderId || updatedCards[cardIdx].providerId,
|
||||
};
|
||||
return updateZone(state, gameId, playerId, zoneName, { cards: updatedCards });
|
||||
}
|
||||
|
||||
case Types.CARD_DESTROYED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { zoneName, cardId } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[zoneName];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return updateZone(state, gameId, playerId, zoneName, {
|
||||
cards: zone.cards.filter(c => c.id !== cardId),
|
||||
cardCount: Math.max(0, zone.cardCount - 1),
|
||||
});
|
||||
}
|
||||
|
||||
case Types.CARD_ATTACHED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { startZone, cardId, targetPlayerId, targetZone, targetCardId } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[startZone];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const cardIdx = zone.cards.findIndex(c => c.id === cardId);
|
||||
if (cardIdx < 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const updatedCards = [...zone.cards];
|
||||
updatedCards[cardIdx] = {
|
||||
...updatedCards[cardIdx],
|
||||
attachPlayerId: targetPlayerId,
|
||||
attachZone: targetZone,
|
||||
attachCardId: targetCardId,
|
||||
};
|
||||
return updateZone(state, gameId, playerId, startZone, { cards: updatedCards });
|
||||
}
|
||||
|
||||
case Types.TOKEN_CREATED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const {
|
||||
zoneName,
|
||||
cardId,
|
||||
cardName,
|
||||
color,
|
||||
pt,
|
||||
annotation,
|
||||
destroyOnZoneChange,
|
||||
x,
|
||||
y,
|
||||
cardProviderId,
|
||||
faceDown,
|
||||
} = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[zoneName];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const newCard: CardInfo = {
|
||||
id: cardId,
|
||||
name: cardName,
|
||||
x,
|
||||
y,
|
||||
faceDown,
|
||||
tapped: false,
|
||||
attacking: false,
|
||||
color,
|
||||
pt,
|
||||
annotation,
|
||||
destroyOnZoneChange,
|
||||
doesntUntap: false,
|
||||
counterList: [],
|
||||
attachPlayerId: -1,
|
||||
attachZone: '',
|
||||
attachCardId: -1,
|
||||
providerId: cardProviderId,
|
||||
};
|
||||
return updateZone(state, gameId, playerId, zoneName, {
|
||||
cards: [...zone.cards, newCard],
|
||||
cardCount: zone.cardCount + 1,
|
||||
});
|
||||
}
|
||||
|
||||
case Types.CARD_ATTR_CHANGED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { zoneName, cardId, attribute, attrValue } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[zoneName];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const cardIdx = zone.cards.findIndex(c => c.id === cardId);
|
||||
if (cardIdx < 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const attrPatch: Partial<CardInfo> = {};
|
||||
switch (attribute as CardAttribute) {
|
||||
case CardAttribute.AttrTapped: attrPatch.tapped = attrValue === '1'; break;
|
||||
case CardAttribute.AttrAttacking: attrPatch.attacking = attrValue === '1'; break;
|
||||
case CardAttribute.AttrFaceDown: attrPatch.faceDown = attrValue === '1'; break;
|
||||
case CardAttribute.AttrColor: attrPatch.color = attrValue; break;
|
||||
case CardAttribute.AttrPT: attrPatch.pt = attrValue; break;
|
||||
case CardAttribute.AttrAnnotation: attrPatch.annotation = attrValue; break;
|
||||
case CardAttribute.AttrDoesntUntap: attrPatch.doesntUntap = attrValue === '1'; break;
|
||||
}
|
||||
|
||||
const updatedCards = [...zone.cards];
|
||||
updatedCards[cardIdx] = { ...updatedCards[cardIdx], ...attrPatch };
|
||||
return updateZone(state, gameId, playerId, zoneName, { cards: updatedCards });
|
||||
}
|
||||
|
||||
case Types.CARD_COUNTER_CHANGED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { zoneName, cardId, counterId, counterValue } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[zoneName];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const cardIdx = zone.cards.findIndex(c => c.id === cardId);
|
||||
if (cardIdx < 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const card = zone.cards[cardIdx];
|
||||
let newCounterList: CardCounterInfo[];
|
||||
if (counterValue <= 0) {
|
||||
newCounterList = card.counterList.filter(c => c.id !== counterId);
|
||||
} else {
|
||||
const existing = card.counterList.findIndex(c => c.id === counterId);
|
||||
newCounterList =
|
||||
existing >= 0
|
||||
? card.counterList.map(c => (c.id === counterId ? { ...c, value: counterValue } : c))
|
||||
: [...card.counterList, { id: counterId, value: counterValue }];
|
||||
}
|
||||
|
||||
const updatedCards = [...zone.cards];
|
||||
updatedCards[cardIdx] = { ...card, counterList: newCounterList };
|
||||
return updateZone(state, gameId, playerId, zoneName, { cards: updatedCards });
|
||||
}
|
||||
|
||||
// ── Arrows ───────────────────────────────────────────────────────────────
|
||||
|
||||
case Types.ARROW_CREATED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { arrowInfo } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
return updatePlayer(state, gameId, playerId, {
|
||||
arrows: { ...player.arrows, [arrowInfo.id]: arrowInfo },
|
||||
});
|
||||
}
|
||||
|
||||
case Types.ARROW_DELETED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { arrowId } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const arrows = { ...player.arrows };
|
||||
delete arrows[arrowId];
|
||||
return updatePlayer(state, gameId, playerId, { arrows });
|
||||
}
|
||||
|
||||
// ── Player counters ───────────────────────────────────────────────────────
|
||||
|
||||
case Types.COUNTER_CREATED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { counterInfo } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
return updatePlayer(state, gameId, playerId, {
|
||||
counters: { ...player.counters, [counterInfo.id]: counterInfo },
|
||||
});
|
||||
}
|
||||
|
||||
case Types.COUNTER_SET: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { counterId, value } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const counter = player.counters[counterId];
|
||||
if (!counter) {
|
||||
return state;
|
||||
}
|
||||
return updatePlayer(state, gameId, playerId, {
|
||||
counters: { ...player.counters, [counterId]: { ...counter, count: value } },
|
||||
});
|
||||
}
|
||||
|
||||
case Types.COUNTER_DELETED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { counterId } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const counters = { ...player.counters };
|
||||
delete counters[counterId];
|
||||
return updatePlayer(state, gameId, playerId, { counters });
|
||||
}
|
||||
|
||||
// ── Zone operations ───────────────────────────────────────────────────────
|
||||
|
||||
case Types.CARDS_DRAWN: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { number: drawCount, cards } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const deckZone = player.zones['deck'];
|
||||
const handZone = player.zones['hand'];
|
||||
if (!handZone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Decrement deck count for the drawing player
|
||||
let newState = deckZone
|
||||
? updateZone(state, gameId, playerId, 'deck', {
|
||||
cardCount: Math.max(0, deckZone.cardCount - drawCount),
|
||||
})
|
||||
: state;
|
||||
|
||||
// Append revealed cards to hand (cards array is empty for non-drawing players;
|
||||
// use drawCount for count math so all observers track the correct hand/deck size)
|
||||
const updatedHand = newState.games[gameId]!.players[playerId]!.zones['hand']!;
|
||||
return updateZone(newState, gameId, playerId, 'hand', {
|
||||
cards: [...updatedHand.cards, ...cards],
|
||||
cardCount: updatedHand.cardCount + drawCount,
|
||||
});
|
||||
}
|
||||
|
||||
case Types.CARDS_REVEALED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { zoneName, cards } = data;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const player = game.players[playerId];
|
||||
if (!player) {
|
||||
return state;
|
||||
}
|
||||
const zone = player.zones[zoneName];
|
||||
if (!zone) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Merge revealed card data into existing zone cards (update existing, append new)
|
||||
const merged = [...zone.cards];
|
||||
for (const revealedCard of cards) {
|
||||
const idx = merged.findIndex(c => c.id === revealedCard.id);
|
||||
if (idx >= 0) {
|
||||
merged[idx] = { ...merged[idx], ...revealedCard };
|
||||
} else {
|
||||
merged.push(revealedCard);
|
||||
}
|
||||
}
|
||||
return updateZone(state, gameId, playerId, zoneName, { cards: merged });
|
||||
}
|
||||
|
||||
case Types.ZONE_PROPERTIES_CHANGED: {
|
||||
const { gameId, playerId, data } = action;
|
||||
const { zoneName, alwaysRevealTopCard, alwaysLookAtTopCard } = data;
|
||||
const patch: Partial<ZoneEntry> = {};
|
||||
if (alwaysRevealTopCard !== undefined && alwaysRevealTopCard !== null) {
|
||||
patch.alwaysRevealTopCard = alwaysRevealTopCard;
|
||||
}
|
||||
if (alwaysLookAtTopCard !== undefined && alwaysLookAtTopCard !== null) {
|
||||
patch.alwaysLookAtTopCard = alwaysLookAtTopCard;
|
||||
}
|
||||
return updateZone(state, gameId, playerId, zoneName, patch);
|
||||
}
|
||||
|
||||
// ── Turn / phase ──────────────────────────────────────────────────────────
|
||||
|
||||
case Types.ACTIVE_PLAYER_SET: {
|
||||
return updateGame(state, action.gameId, { activePlayerId: action.activePlayerId });
|
||||
}
|
||||
|
||||
case Types.ACTIVE_PHASE_SET: {
|
||||
return updateGame(state, action.gameId, { activePhase: action.phase });
|
||||
}
|
||||
|
||||
case Types.TURN_REVERSED: {
|
||||
return updateGame(state, action.gameId, { reversed: action.reversed });
|
||||
}
|
||||
|
||||
// ── Chat ──────────────────────────────────────────────────────────────────
|
||||
|
||||
case Types.GAME_SAY: {
|
||||
const { gameId, playerId, message } = action;
|
||||
const game = state.games[gameId];
|
||||
if (!game) {
|
||||
return state;
|
||||
}
|
||||
const newMessage: GameMessage = { playerId, message, timeReceived: Date.now() };
|
||||
return updateGame(state, gameId, {
|
||||
messages: [...game.messages, newMessage],
|
||||
});
|
||||
}
|
||||
|
||||
// ── Log-only events (state unchanged, future game log will use these) ─────
|
||||
case Types.ZONE_SHUFFLED:
|
||||
case Types.ZONE_DUMPED:
|
||||
case Types.DIE_ROLLED: {
|
||||
return state;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
72
webclient/src/store/game/game.selectors.ts
Normal file
72
webclient/src/store/game/game.selectors.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { GamesState, GameEntry, PlayerEntry, ZoneEntry } from './game.interfaces';
|
||||
|
||||
interface State {
|
||||
games: GamesState;
|
||||
}
|
||||
|
||||
export const Selectors = {
|
||||
getGames: ({ games }: State): { [gameId: number]: GameEntry } => games.games,
|
||||
|
||||
getGame: ({ games }: State, gameId: number): GameEntry | undefined => games.games[gameId],
|
||||
|
||||
getPlayers: ({ games }: State, gameId: number): { [playerId: number]: PlayerEntry } | undefined =>
|
||||
games.games[gameId]?.players,
|
||||
|
||||
getPlayer: ({ games }: State, gameId: number, playerId: number): PlayerEntry | undefined =>
|
||||
games.games[gameId]?.players[playerId],
|
||||
|
||||
getLocalPlayerId: ({ games }: State, gameId: number): number | undefined =>
|
||||
games.games[gameId]?.localPlayerId,
|
||||
|
||||
getLocalPlayer: (state: State, gameId: number): PlayerEntry | undefined => {
|
||||
const game = state.games.games[gameId];
|
||||
if (!game) {
|
||||
return undefined;
|
||||
}
|
||||
return game.players[game.localPlayerId];
|
||||
},
|
||||
|
||||
getZones: (
|
||||
{ games }: State,
|
||||
gameId: number,
|
||||
playerId: number
|
||||
): { [zoneName: string]: ZoneEntry } | undefined =>
|
||||
games.games[gameId]?.players[playerId]?.zones,
|
||||
|
||||
getZone: (
|
||||
{ games }: State,
|
||||
gameId: number,
|
||||
playerId: number,
|
||||
zoneName: string
|
||||
): ZoneEntry | undefined => games.games[gameId]?.players[playerId]?.zones[zoneName],
|
||||
|
||||
getCards: ({ games }: State, gameId: number, playerId: number, zoneName: string) =>
|
||||
games.games[gameId]?.players[playerId]?.zones[zoneName]?.cards ?? [],
|
||||
|
||||
getCounters: ({ games }: State, gameId: number, playerId: number) =>
|
||||
games.games[gameId]?.players[playerId]?.counters ?? {},
|
||||
|
||||
getArrows: ({ games }: State, gameId: number, playerId: number) =>
|
||||
games.games[gameId]?.players[playerId]?.arrows ?? {},
|
||||
|
||||
getActivePlayerId: ({ games }: State, gameId: number): number | undefined =>
|
||||
games.games[gameId]?.activePlayerId,
|
||||
|
||||
getActivePhase: ({ games }: State, gameId: number): number | undefined =>
|
||||
games.games[gameId]?.activePhase,
|
||||
|
||||
isStarted: ({ games }: State, gameId: number): boolean =>
|
||||
games.games[gameId]?.started ?? false,
|
||||
|
||||
isSpectator: ({ games }: State, gameId: number): boolean =>
|
||||
games.games[gameId]?.spectator ?? false,
|
||||
|
||||
isReversed: ({ games }: State, gameId: number): boolean =>
|
||||
games.games[gameId]?.reversed ?? false,
|
||||
|
||||
getMessages: ({ games }: State, gameId: number) =>
|
||||
games.games[gameId]?.messages ?? [],
|
||||
|
||||
getActiveGameIds: ({ games }: State): number[] =>
|
||||
Object.keys(games.games).map(Number),
|
||||
};
|
||||
34
webclient/src/store/game/game.types.ts
Normal file
34
webclient/src/store/game/game.types.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
export const Types = {
|
||||
CLEAR_STORE: '[Games] Clear Store',
|
||||
GAME_JOINED: '[Games] Game Joined',
|
||||
GAME_LEFT: '[Games] Game Left',
|
||||
GAME_CLOSED: '[Games] Game Closed',
|
||||
GAME_HOST_CHANGED: '[Games] Game Host Changed',
|
||||
GAME_STATE_CHANGED: '[Games] Game State Changed',
|
||||
PLAYER_JOINED: '[Games] Player Joined',
|
||||
PLAYER_LEFT: '[Games] Player Left',
|
||||
PLAYER_PROPERTIES_CHANGED: '[Games] Player Properties Changed',
|
||||
KICKED: '[Games] Kicked',
|
||||
CARD_MOVED: '[Games] Card Moved',
|
||||
CARD_FLIPPED: '[Games] Card Flipped',
|
||||
CARD_DESTROYED: '[Games] Card Destroyed',
|
||||
CARD_ATTACHED: '[Games] Card Attached',
|
||||
TOKEN_CREATED: '[Games] Token Created',
|
||||
CARD_ATTR_CHANGED: '[Games] Card Attribute Changed',
|
||||
CARD_COUNTER_CHANGED: '[Games] Card Counter Changed',
|
||||
ARROW_CREATED: '[Games] Arrow Created',
|
||||
ARROW_DELETED: '[Games] Arrow Deleted',
|
||||
COUNTER_CREATED: '[Games] Counter Created',
|
||||
COUNTER_SET: '[Games] Counter Set',
|
||||
COUNTER_DELETED: '[Games] Counter Deleted',
|
||||
CARDS_DRAWN: '[Games] Cards Drawn',
|
||||
CARDS_REVEALED: '[Games] Cards Revealed',
|
||||
ZONE_SHUFFLED: '[Games] Zone Shuffled',
|
||||
DIE_ROLLED: '[Games] Die Rolled',
|
||||
ACTIVE_PLAYER_SET: '[Games] Active Player Set',
|
||||
ACTIVE_PHASE_SET: '[Games] Active Phase Set',
|
||||
TURN_REVERSED: '[Games] Turn Reversed',
|
||||
ZONE_DUMPED: '[Games] Zone Dumped',
|
||||
ZONE_PROPERTIES_CHANGED: '[Games] Zone Properties Changed',
|
||||
GAME_SAY: '[Games] Game Say',
|
||||
};
|
||||
6
webclient/src/store/game/index.ts
Normal file
6
webclient/src/store/game/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { Types } from './game.types';
|
||||
export { gamesReducer } from './game.reducer';
|
||||
export { Actions } from './game.actions';
|
||||
export { Dispatch } from './game.dispatch';
|
||||
export { Selectors } from './game.selectors';
|
||||
export * from './game.interfaces';
|
||||
|
|
@ -3,8 +3,15 @@ export { store } from './store';
|
|||
// Common
|
||||
export { SortUtil } from './common';
|
||||
|
||||
// Server
|
||||
// Games
|
||||
export {
|
||||
Types as GameTypes,
|
||||
Selectors as GameSelectors,
|
||||
Dispatch as GameDispatch } from './game';
|
||||
|
||||
export * from 'store/game/game.interfaces';
|
||||
|
||||
// Server
|
||||
export {
|
||||
Types as ServerTypes,
|
||||
Selectors as ServerSelectors,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { combineReducers } from 'redux';
|
||||
|
||||
import { gamesReducer } from './game';
|
||||
import { roomsReducer } from './rooms';
|
||||
import { serverReducer } from './server';
|
||||
import { reducer as formReducer } from 'redux-form'
|
||||
import { actionReducer } from './actions'
|
||||
|
||||
export default combineReducers({
|
||||
games: gamesReducer,
|
||||
rooms: roomsReducer,
|
||||
server: serverReducer,
|
||||
|
||||
|
|
|
|||
|
|
@ -40,3 +40,461 @@ export enum LeaveGameReason {
|
|||
USER_LEFT = 3,
|
||||
USER_DISCONNECTED = 4
|
||||
}
|
||||
|
||||
// ── Enums ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export enum ZoneType {
|
||||
PrivateZone = 0,
|
||||
PublicZone = 1,
|
||||
HiddenZone = 2,
|
||||
}
|
||||
|
||||
/** Matches CardAttribute enum in card_attributes.proto */
|
||||
export enum CardAttribute {
|
||||
AttrTapped = 1,
|
||||
AttrAttacking = 2,
|
||||
AttrFaceDown = 3,
|
||||
AttrColor = 4,
|
||||
AttrPT = 5,
|
||||
AttrAnnotation = 6,
|
||||
AttrDoesntUntap = 7,
|
||||
}
|
||||
|
||||
// ── Primitive data structures (mirrors ServerInfo_* protos) ──────────────────
|
||||
|
||||
export interface Color {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a: number;
|
||||
}
|
||||
|
||||
/** Mirrors ServerInfo_CardCounter */
|
||||
export interface CardCounterInfo {
|
||||
id: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
/** Mirrors ServerInfo_Card */
|
||||
export interface CardInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
faceDown: boolean;
|
||||
tapped: boolean;
|
||||
attacking: boolean;
|
||||
color: string;
|
||||
pt: string;
|
||||
annotation: string;
|
||||
destroyOnZoneChange: boolean;
|
||||
doesntUntap: boolean;
|
||||
counterList: CardCounterInfo[];
|
||||
attachPlayerId: number;
|
||||
attachZone: string;
|
||||
attachCardId: number;
|
||||
providerId: string;
|
||||
}
|
||||
|
||||
/** Mirrors ServerInfo_Zone */
|
||||
export interface ZoneInfo {
|
||||
name: string;
|
||||
type: ZoneType;
|
||||
withCoords: boolean;
|
||||
cardCount: number;
|
||||
cardList: CardInfo[];
|
||||
alwaysRevealTopCard: boolean;
|
||||
alwaysLookAtTopCard: boolean;
|
||||
}
|
||||
|
||||
/** Mirrors ServerInfo_Counter */
|
||||
export interface CounterInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
counterColor: Color;
|
||||
radius: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** Mirrors ServerInfo_Arrow */
|
||||
export interface ArrowInfo {
|
||||
id: number;
|
||||
startPlayerId: number;
|
||||
startZone: string;
|
||||
startCardId: number;
|
||||
targetPlayerId: number;
|
||||
targetZone: string;
|
||||
targetCardId: number;
|
||||
arrowColor: Color;
|
||||
}
|
||||
|
||||
/** Mirrors ServerInfo_PlayerProperties */
|
||||
export interface PlayerProperties {
|
||||
playerId: number;
|
||||
userInfo: any;
|
||||
spectator: boolean;
|
||||
conceded: boolean;
|
||||
readyStart: boolean;
|
||||
deckHash: string;
|
||||
pingSeconds: number;
|
||||
sideboardLocked: boolean;
|
||||
judge: boolean;
|
||||
}
|
||||
|
||||
/** Mirrors ServerInfo_Player */
|
||||
export interface PlayerInfo {
|
||||
properties: PlayerProperties;
|
||||
deckList: string;
|
||||
zoneList: ZoneInfo[];
|
||||
counterList: CounterInfo[];
|
||||
arrowList: ArrowInfo[];
|
||||
}
|
||||
|
||||
// ── Game event payload interfaces (data arriving from server events) ──────────
|
||||
|
||||
export interface GameStateChangedData {
|
||||
playerList: PlayerInfo[];
|
||||
gameStarted: boolean;
|
||||
activePlayerId: number;
|
||||
activePhase: number;
|
||||
secondsElapsed: number;
|
||||
}
|
||||
|
||||
export interface GameSayData {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface MoveCardData {
|
||||
cardId: number;
|
||||
cardName: string;
|
||||
startPlayerId: number;
|
||||
startZone: string;
|
||||
position: number;
|
||||
targetPlayerId: number;
|
||||
targetZone: string;
|
||||
x: number;
|
||||
y: number;
|
||||
newCardId: number;
|
||||
faceDown: boolean;
|
||||
newCardProviderId: string;
|
||||
}
|
||||
|
||||
export interface FlipCardData {
|
||||
zoneName: string;
|
||||
cardId: number;
|
||||
cardName: string;
|
||||
faceDown: boolean;
|
||||
cardProviderId: string;
|
||||
}
|
||||
|
||||
export interface DestroyCardData {
|
||||
zoneName: string;
|
||||
cardId: number;
|
||||
}
|
||||
|
||||
export interface AttachCardData {
|
||||
startZone: string;
|
||||
cardId: number;
|
||||
targetPlayerId: number;
|
||||
targetZone: string;
|
||||
targetCardId: number;
|
||||
}
|
||||
|
||||
export interface CreateTokenData {
|
||||
zoneName: string;
|
||||
cardId: number;
|
||||
cardName: string;
|
||||
color: string;
|
||||
pt: string;
|
||||
annotation: string;
|
||||
destroyOnZoneChange: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
cardProviderId: string;
|
||||
faceDown: boolean;
|
||||
}
|
||||
|
||||
export interface SetCardAttrData {
|
||||
zoneName: string;
|
||||
cardId: number;
|
||||
attribute: CardAttribute;
|
||||
attrValue: string;
|
||||
}
|
||||
|
||||
export interface SetCardCounterData {
|
||||
zoneName: string;
|
||||
cardId: number;
|
||||
counterId: number;
|
||||
counterValue: number;
|
||||
}
|
||||
|
||||
export interface CreateArrowData {
|
||||
arrowInfo: ArrowInfo;
|
||||
}
|
||||
|
||||
export interface DeleteArrowData {
|
||||
arrowId: number;
|
||||
}
|
||||
|
||||
export interface CreateCounterData {
|
||||
counterInfo: CounterInfo;
|
||||
}
|
||||
|
||||
export interface SetCounterData {
|
||||
counterId: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface DelCounterData {
|
||||
counterId: number;
|
||||
}
|
||||
|
||||
export interface DrawCardsData {
|
||||
number: number;
|
||||
cards: CardInfo[];
|
||||
}
|
||||
|
||||
export interface RevealCardsData {
|
||||
zoneName: string;
|
||||
cardId: number[];
|
||||
otherPlayerId: number;
|
||||
cards: CardInfo[];
|
||||
grantWriteAccess: boolean;
|
||||
numberOfCards: number;
|
||||
}
|
||||
|
||||
export interface ShuffleData {
|
||||
zoneName: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export interface RollDieData {
|
||||
sides: number;
|
||||
value: number;
|
||||
values: number[];
|
||||
}
|
||||
|
||||
export interface DumpZoneData {
|
||||
zoneOwnerId: number;
|
||||
zoneName: string;
|
||||
numberCards: number;
|
||||
isReversed: boolean;
|
||||
}
|
||||
|
||||
export interface ChangeZonePropertiesData {
|
||||
zoneName: string;
|
||||
alwaysRevealTopCard: boolean;
|
||||
alwaysLookAtTopCard: boolean;
|
||||
}
|
||||
|
||||
export interface SetActivePlayerData {
|
||||
activePlayerId: number;
|
||||
}
|
||||
|
||||
export interface SetActivePhaseData {
|
||||
phase: number;
|
||||
}
|
||||
|
||||
export interface ReverseTurnData {
|
||||
reversed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed to every game event handler alongside the event payload.
|
||||
* Contains per-container metadata from GameEventContainer.
|
||||
* Not stored in Redux — transient routing metadata only.
|
||||
*/
|
||||
export interface GameEventMeta {
|
||||
gameId: number;
|
||||
playerId: number;
|
||||
/** Raw protobuf GameEventContext object. Not stored in Redux. */
|
||||
context: any;
|
||||
secondsElapsed: number;
|
||||
/** Proto type is uint32. Non-zero means the action was forced by a judge. */
|
||||
forcedByJudge: number;
|
||||
}
|
||||
|
||||
// ── Command parameter interfaces ─────────────────────────────────────────────
|
||||
|
||||
export interface CardToMove {
|
||||
cardId: number;
|
||||
faceDown?: boolean;
|
||||
pt?: string;
|
||||
tapped?: boolean;
|
||||
}
|
||||
|
||||
export interface MoveCardParams {
|
||||
startPlayerId: number;
|
||||
startZone: string;
|
||||
cardsToMove: { card: CardToMove[] };
|
||||
targetPlayerId: number;
|
||||
targetZone: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
isReversed?: boolean;
|
||||
}
|
||||
|
||||
export interface DrawCardsParams {
|
||||
number: number;
|
||||
}
|
||||
|
||||
export interface RollDieParams {
|
||||
sides: number;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface ShuffleParams {
|
||||
zoneName: string;
|
||||
start?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
export interface FlipCardParams {
|
||||
zone: string;
|
||||
cardId: number;
|
||||
faceDown: boolean;
|
||||
pt?: string;
|
||||
}
|
||||
|
||||
export interface AttachCardParams {
|
||||
startZone: string;
|
||||
cardId: number;
|
||||
targetPlayerId?: number;
|
||||
targetZone?: string;
|
||||
targetCardId?: number;
|
||||
}
|
||||
|
||||
export interface CreateTokenParams {
|
||||
zone: string;
|
||||
cardName: string;
|
||||
color?: string;
|
||||
pt?: string;
|
||||
annotation?: string;
|
||||
destroyOnZoneChange?: boolean;
|
||||
x?: number;
|
||||
y?: number;
|
||||
targetZone?: string;
|
||||
targetCardId?: number;
|
||||
targetMode?: number;
|
||||
cardProviderId?: string;
|
||||
faceDown?: boolean;
|
||||
}
|
||||
|
||||
export interface SetCardAttrParams {
|
||||
zone: string;
|
||||
cardId: number;
|
||||
attribute: CardAttribute;
|
||||
attrValue: string;
|
||||
}
|
||||
|
||||
export interface SetCardCounterParams {
|
||||
zone: string;
|
||||
cardId: number;
|
||||
counterId: number;
|
||||
counterValue: number;
|
||||
}
|
||||
|
||||
export interface IncCardCounterParams {
|
||||
zone: string;
|
||||
cardId: number;
|
||||
counterId: number;
|
||||
counterDelta: number;
|
||||
}
|
||||
|
||||
export interface RevealCardsParams {
|
||||
zoneName: string;
|
||||
cardId?: number[];
|
||||
playerId?: number;
|
||||
grantWriteAccess?: boolean;
|
||||
topCards?: number;
|
||||
}
|
||||
|
||||
export interface DumpZoneParams {
|
||||
playerId: number;
|
||||
zoneName: string;
|
||||
numberCards: number;
|
||||
isReversed?: boolean;
|
||||
}
|
||||
|
||||
export interface ChangeZonePropertiesParams {
|
||||
zoneName: string;
|
||||
alwaysRevealTopCard?: boolean;
|
||||
alwaysLookAtTopCard?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateArrowParams {
|
||||
startPlayerId: number;
|
||||
startZone: string;
|
||||
startCardId: number;
|
||||
targetPlayerId: number;
|
||||
targetZone?: string;
|
||||
targetCardId?: number;
|
||||
arrowColor: Color;
|
||||
deleteInPhase?: number;
|
||||
}
|
||||
|
||||
export interface DeleteArrowParams {
|
||||
arrowId: number;
|
||||
}
|
||||
|
||||
export interface CreateCounterParams {
|
||||
counterName: string;
|
||||
counterColor: Color;
|
||||
radius: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface SetCounterParams {
|
||||
counterId: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface IncCounterParams {
|
||||
counterId: number;
|
||||
delta: number;
|
||||
}
|
||||
|
||||
export interface DelCounterParams {
|
||||
counterId: number;
|
||||
}
|
||||
|
||||
export interface KickFromGameParams {
|
||||
playerId: number;
|
||||
}
|
||||
|
||||
export interface ReadyStartParams {
|
||||
ready: boolean;
|
||||
forceStart?: boolean;
|
||||
}
|
||||
|
||||
export interface MulliganParams {
|
||||
number: number;
|
||||
}
|
||||
|
||||
export interface DeckSelectParams {
|
||||
deck?: string;
|
||||
deckId?: number;
|
||||
}
|
||||
|
||||
export interface MoveCardToZone {
|
||||
cardName: string;
|
||||
startZone: string;
|
||||
targetZone: string;
|
||||
}
|
||||
|
||||
export interface SetSideboardPlanParams {
|
||||
moveList: MoveCardToZone[];
|
||||
}
|
||||
|
||||
export interface SetSideboardLockParams {
|
||||
locked: boolean;
|
||||
}
|
||||
|
||||
export interface SetActivePhaseParams {
|
||||
phase: number;
|
||||
}
|
||||
|
||||
export interface GameSayParams {
|
||||
message: string;
|
||||
}
|
||||
|
|
|
|||
6
webclient/src/websocket/commands/game/attachCard.ts
Normal file
6
webclient/src/websocket/commands/game/attachCard.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { AttachCardParams } from 'types';
|
||||
|
||||
export function attachCard(gameId: number, params: AttachCardParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_AttachCard', params);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { ChangeZonePropertiesParams } from 'types';
|
||||
|
||||
export function changeZoneProperties(gameId: number, params: ChangeZonePropertiesParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_ChangeZoneProperties', params);
|
||||
}
|
||||
5
webclient/src/websocket/commands/game/concede.ts
Normal file
5
webclient/src/websocket/commands/game/concede.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
|
||||
export function concede(gameId: number): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_Concede', {});
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/createArrow.ts
Normal file
6
webclient/src/websocket/commands/game/createArrow.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { CreateArrowParams } from 'types';
|
||||
|
||||
export function createArrow(gameId: number, params: CreateArrowParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_CreateArrow', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/createCounter.ts
Normal file
6
webclient/src/websocket/commands/game/createCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { CreateCounterParams } from 'types';
|
||||
|
||||
export function createCounter(gameId: number, params: CreateCounterParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_CreateCounter', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/createToken.ts
Normal file
6
webclient/src/websocket/commands/game/createToken.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { CreateTokenParams } from 'types';
|
||||
|
||||
export function createToken(gameId: number, params: CreateTokenParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_CreateToken', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/deckSelect.ts
Normal file
6
webclient/src/websocket/commands/game/deckSelect.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { DeckSelectParams } from 'types';
|
||||
|
||||
export function deckSelect(gameId: number, params: DeckSelectParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_DeckSelect', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/delCounter.ts
Normal file
6
webclient/src/websocket/commands/game/delCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { DelCounterParams } from 'types';
|
||||
|
||||
export function delCounter(gameId: number, params: DelCounterParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_DelCounter', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/deleteArrow.ts
Normal file
6
webclient/src/websocket/commands/game/deleteArrow.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { DeleteArrowParams } from 'types';
|
||||
|
||||
export function deleteArrow(gameId: number, params: DeleteArrowParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_DeleteArrow', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/drawCards.ts
Normal file
6
webclient/src/websocket/commands/game/drawCards.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { DrawCardsParams } from 'types';
|
||||
|
||||
export function drawCards(gameId: number, params: DrawCardsParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_DrawCards', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/dumpZone.ts
Normal file
6
webclient/src/websocket/commands/game/dumpZone.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { DumpZoneParams } from 'types';
|
||||
|
||||
export function dumpZone(gameId: number, params: DumpZoneParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_DumpZone', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/flipCard.ts
Normal file
6
webclient/src/websocket/commands/game/flipCard.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { FlipCardParams } from 'types';
|
||||
|
||||
export function flipCard(gameId: number, params: FlipCardParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_FlipCard', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/gameSay.ts
Normal file
6
webclient/src/websocket/commands/game/gameSay.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { GameSayParams } from 'types';
|
||||
|
||||
export function gameSay(gameId: number, params: GameSayParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_GameSay', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/incCardCounter.ts
Normal file
6
webclient/src/websocket/commands/game/incCardCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { IncCardCounterParams } from 'types';
|
||||
|
||||
export function incCardCounter(gameId: number, params: IncCardCounterParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_IncCardCounter', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/incCounter.ts
Normal file
6
webclient/src/websocket/commands/game/incCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { IncCounterParams } from 'types';
|
||||
|
||||
export function incCounter(gameId: number, params: IncCounterParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_IncCounter', params);
|
||||
}
|
||||
31
webclient/src/websocket/commands/game/index.ts
Normal file
31
webclient/src/websocket/commands/game/index.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
export { leaveGame } from './leaveGame';
|
||||
export { kickFromGame } from './kickFromGame';
|
||||
export { gameSay } from './gameSay';
|
||||
export { readyStart } from './readyStart';
|
||||
export { concede } from './concede';
|
||||
export { nextTurn } from './nextTurn';
|
||||
export { setActivePhase } from './setActivePhase';
|
||||
export { reverseTurn } from './reverseTurn';
|
||||
export { moveCard } from './moveCard';
|
||||
export { flipCard } from './flipCard';
|
||||
export { attachCard } from './attachCard';
|
||||
export { createToken } from './createToken';
|
||||
export { setCardAttr } from './setCardAttr';
|
||||
export { setCardCounter } from './setCardCounter';
|
||||
export { incCardCounter } from './incCardCounter';
|
||||
export { drawCards } from './drawCards';
|
||||
export { undoDraw } from './undoDraw';
|
||||
export { createArrow } from './createArrow';
|
||||
export { deleteArrow } from './deleteArrow';
|
||||
export { createCounter } from './createCounter';
|
||||
export { setCounter } from './setCounter';
|
||||
export { incCounter } from './incCounter';
|
||||
export { delCounter } from './delCounter';
|
||||
export { shuffle } from './shuffle';
|
||||
export { dumpZone } from './dumpZone';
|
||||
export { revealCards } from './revealCards';
|
||||
export { changeZoneProperties } from './changeZoneProperties';
|
||||
export { deckSelect } from './deckSelect';
|
||||
export { setSideboardPlan } from './setSideboardPlan';
|
||||
export { setSideboardLock } from './setSideboardLock';
|
||||
export { mulligan } from './mulligan';
|
||||
6
webclient/src/websocket/commands/game/kickFromGame.ts
Normal file
6
webclient/src/websocket/commands/game/kickFromGame.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { KickFromGameParams } from 'types';
|
||||
|
||||
export function kickFromGame(gameId: number, params: KickFromGameParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_KickFromGame', params);
|
||||
}
|
||||
5
webclient/src/websocket/commands/game/leaveGame.ts
Normal file
5
webclient/src/websocket/commands/game/leaveGame.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
|
||||
export function leaveGame(gameId: number): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_LeaveGame', {});
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/moveCard.ts
Normal file
6
webclient/src/websocket/commands/game/moveCard.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { MoveCardParams } from 'types';
|
||||
|
||||
export function moveCard(gameId: number, params: MoveCardParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_MoveCard', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/mulligan.ts
Normal file
6
webclient/src/websocket/commands/game/mulligan.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { MulliganParams } from 'types';
|
||||
|
||||
export function mulligan(gameId: number, params: MulliganParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_Mulligan', params);
|
||||
}
|
||||
5
webclient/src/websocket/commands/game/nextTurn.ts
Normal file
5
webclient/src/websocket/commands/game/nextTurn.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
|
||||
export function nextTurn(gameId: number): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_NextTurn', {});
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/readyStart.ts
Normal file
6
webclient/src/websocket/commands/game/readyStart.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { ReadyStartParams } from 'types';
|
||||
|
||||
export function readyStart(gameId: number, params: ReadyStartParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_ReadyStart', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/revealCards.ts
Normal file
6
webclient/src/websocket/commands/game/revealCards.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { RevealCardsParams } from 'types';
|
||||
|
||||
export function revealCards(gameId: number, params: RevealCardsParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_RevealCards', params);
|
||||
}
|
||||
5
webclient/src/websocket/commands/game/reverseTurn.ts
Normal file
5
webclient/src/websocket/commands/game/reverseTurn.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
|
||||
export function reverseTurn(gameId: number): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_ReverseTurn', {});
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/setActivePhase.ts
Normal file
6
webclient/src/websocket/commands/game/setActivePhase.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { SetActivePhaseParams } from 'types';
|
||||
|
||||
export function setActivePhase(gameId: number, params: SetActivePhaseParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_SetActivePhase', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/setCardAttr.ts
Normal file
6
webclient/src/websocket/commands/game/setCardAttr.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { SetCardAttrParams } from 'types';
|
||||
|
||||
export function setCardAttr(gameId: number, params: SetCardAttrParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_SetCardAttr', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/setCardCounter.ts
Normal file
6
webclient/src/websocket/commands/game/setCardCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { SetCardCounterParams } from 'types';
|
||||
|
||||
export function setCardCounter(gameId: number, params: SetCardCounterParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_SetCardCounter', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/setCounter.ts
Normal file
6
webclient/src/websocket/commands/game/setCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { SetCounterParams } from 'types';
|
||||
|
||||
export function setCounter(gameId: number, params: SetCounterParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_SetCounter', params);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { SetSideboardLockParams } from 'types';
|
||||
|
||||
export function setSideboardLock(gameId: number, params: SetSideboardLockParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_SetSideboardLock', params);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { SetSideboardPlanParams } from 'types';
|
||||
|
||||
export function setSideboardPlan(gameId: number, params: SetSideboardPlanParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_SetSideboardPlan', params);
|
||||
}
|
||||
6
webclient/src/websocket/commands/game/shuffle.ts
Normal file
6
webclient/src/websocket/commands/game/shuffle.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { ShuffleParams } from 'types';
|
||||
|
||||
export function shuffle(gameId: number, params: ShuffleParams): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_Shuffle', params);
|
||||
}
|
||||
5
webclient/src/websocket/commands/game/undoDraw.ts
Normal file
5
webclient/src/websocket/commands/game/undoDraw.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
|
||||
export function undoDraw(gameId: number): void {
|
||||
BackendService.sendGameCommand(gameId, 'Command_UndoDraw', {});
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export * as AdminCommands from './admin';
|
||||
export * as GameCommands from './game';
|
||||
export * as ModeratorCommands from './moderator';
|
||||
export * as RoomCommands from './room';
|
||||
export * as SessionCommands from './session';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import { ProtobufEvents } from '../../services/ProtobufService';
|
||||
import { playerPropertiesChanged } from './playerPropertiesChanged';
|
||||
|
||||
export const CommonEvents: ProtobufEvents = {
|
||||
'.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged,
|
||||
}
|
||||
export const CommonEvents: ProtobufEvents = {};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import { PlayerGamePropertiesData } from '../session/interfaces';
|
||||
import { SessionPersistence } from '../../persistence';
|
||||
|
||||
export function playerPropertiesChanged(payload: PlayerGamePropertiesData): void {
|
||||
SessionPersistence.playerPropertiesChanged(payload);
|
||||
}
|
||||
// Event_PlayerPropertiesChanged is handled as a game event in websocket/events/game/playerPropertiesChanged.ts
|
||||
// This file is retained for reference but is no longer registered in CommonEvents.
|
||||
export {};
|
||||
|
|
|
|||
6
webclient/src/websocket/events/game/attachCard.ts
Normal file
6
webclient/src/websocket/events/game/attachCard.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { AttachCardData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function attachCard(data: AttachCardData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardAttached(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { ChangeZonePropertiesData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function changeZoneProperties(data: ChangeZonePropertiesData, meta: GameEventMeta): void {
|
||||
GamePersistence.zonePropertiesChanged(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/createArrow.ts
Normal file
6
webclient/src/websocket/events/game/createArrow.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { CreateArrowData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function createArrow(data: CreateArrowData, meta: GameEventMeta): void {
|
||||
GamePersistence.arrowCreated(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/createCounter.ts
Normal file
6
webclient/src/websocket/events/game/createCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { CreateCounterData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function createCounter(data: CreateCounterData, meta: GameEventMeta): void {
|
||||
GamePersistence.counterCreated(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/createToken.ts
Normal file
6
webclient/src/websocket/events/game/createToken.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { CreateTokenData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function createToken(data: CreateTokenData, meta: GameEventMeta): void {
|
||||
GamePersistence.tokenCreated(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/delCounter.ts
Normal file
6
webclient/src/websocket/events/game/delCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { DelCounterData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function delCounter(data: DelCounterData, meta: GameEventMeta): void {
|
||||
GamePersistence.counterDeleted(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/deleteArrow.ts
Normal file
6
webclient/src/websocket/events/game/deleteArrow.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { DeleteArrowData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function deleteArrow(data: DeleteArrowData, meta: GameEventMeta): void {
|
||||
GamePersistence.arrowDeleted(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/destroyCard.ts
Normal file
6
webclient/src/websocket/events/game/destroyCard.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { DestroyCardData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function destroyCard(data: DestroyCardData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardDestroyed(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/drawCards.ts
Normal file
6
webclient/src/websocket/events/game/drawCards.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { DrawCardsData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function drawCards(data: DrawCardsData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardsDrawn(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/dumpZone.ts
Normal file
6
webclient/src/websocket/events/game/dumpZone.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { DumpZoneData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function dumpZone(data: DumpZoneData, meta: GameEventMeta): void {
|
||||
GamePersistence.zoneDumped(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/flipCard.ts
Normal file
6
webclient/src/websocket/events/game/flipCard.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { FlipCardData, GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function flipCard(data: FlipCardData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardFlipped(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/gameClosed.ts
Normal file
6
webclient/src/websocket/events/game/gameClosed.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function gameClosed(_data: {}, meta: GameEventMeta): void {
|
||||
GamePersistence.gameClosed(meta.gameId);
|
||||
}
|
||||
|
|
@ -1,29 +1,31 @@
|
|||
jest.mock('../../persistence', () => ({
|
||||
GamePersistence: {
|
||||
joinGame: jest.fn(),
|
||||
leaveGame: jest.fn(),
|
||||
playerJoined: jest.fn(),
|
||||
playerLeft: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { GamePersistence } from '../../persistence';
|
||||
import { joinGame } from './joinGame';
|
||||
import { leaveGame } from './leaveGame';
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
describe('joinGame event', () => {
|
||||
const { joinGame } = jest.requireActual('./joinGame');
|
||||
|
||||
it('delegates to GamePersistence.joinGame', () => {
|
||||
const data = { gameId: 5, player: { playerId: 1 } } as any;
|
||||
joinGame(data);
|
||||
expect(GamePersistence.joinGame).toHaveBeenCalledWith(data);
|
||||
it('delegates to GamePersistence.playerJoined with gameId from meta', () => {
|
||||
const playerProperties = { playerId: 1 };
|
||||
const data = { playerProperties } as any;
|
||||
const meta = { gameId: 5, playerId: 1, context: null, secondsElapsed: 0, forcedByJudge: 0 };
|
||||
joinGame(data, meta);
|
||||
expect(GamePersistence.playerJoined).toHaveBeenCalledWith(5, playerProperties);
|
||||
});
|
||||
});
|
||||
|
||||
describe('leaveGame event', () => {
|
||||
const { leaveGame } = jest.requireActual('./leaveGame');
|
||||
|
||||
it('delegates to GamePersistence.leaveGame', () => {
|
||||
leaveGame(42 as any);
|
||||
expect(GamePersistence.leaveGame).toHaveBeenCalledWith(42);
|
||||
it('delegates to GamePersistence.playerLeft with gameId/playerId from meta', () => {
|
||||
const data = { reason: 3 };
|
||||
const meta = { gameId: 5, playerId: 2, context: null, secondsElapsed: 0, forcedByJudge: 0 };
|
||||
leaveGame(data, meta);
|
||||
expect(GamePersistence.playerLeft).toHaveBeenCalledWith(5, 2, 3);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
10
webclient/src/websocket/events/game/gameHostChanged.ts
Normal file
10
webclient/src/websocket/events/game/gameHostChanged.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
/**
|
||||
* Event_GameHostChanged carries no payload fields.
|
||||
* The new host is identified by GameEvent.player_id (meta.playerId).
|
||||
*/
|
||||
export function gameHostChanged(_data: {}, meta: GameEventMeta): void {
|
||||
GamePersistence.gameHostChanged(meta.gameId, meta.playerId);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/gameSay.ts
Normal file
6
webclient/src/websocket/events/game/gameSay.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, GameSayData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function gameSay(data: GameSayData, meta: GameEventMeta): void {
|
||||
GamePersistence.gameSay(meta.gameId, meta.playerId, data.message);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/gameStateChanged.ts
Normal file
6
webclient/src/websocket/events/game/gameStateChanged.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, GameStateChangedData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function gameStateChanged(data: GameStateChangedData, meta: GameEventMeta): void {
|
||||
GamePersistence.gameStateChanged(meta.gameId, data);
|
||||
}
|
||||
|
|
@ -1,36 +1,62 @@
|
|||
import { ProtobufEvents } from '../../services/ProtobufService';
|
||||
import { attachCard } from './attachCard';
|
||||
import { changeZoneProperties } from './changeZoneProperties';
|
||||
import { createArrow } from './createArrow';
|
||||
import { createCounter } from './createCounter';
|
||||
import { createToken } from './createToken';
|
||||
import { delCounter } from './delCounter';
|
||||
import { deleteArrow } from './deleteArrow';
|
||||
import { destroyCard } from './destroyCard';
|
||||
import { drawCards } from './drawCards';
|
||||
import { dumpZone } from './dumpZone';
|
||||
import { flipCard } from './flipCard';
|
||||
import { gameClosed } from './gameClosed';
|
||||
import { gameHostChanged } from './gameHostChanged';
|
||||
import { gameSay } from './gameSay';
|
||||
import { gameStateChanged } from './gameStateChanged';
|
||||
import { joinGame } from './joinGame';
|
||||
import { kicked } from './kicked';
|
||||
import { leaveGame } from './leaveGame';
|
||||
|
||||
import { moveCard } from './moveCard';
|
||||
import { playerPropertiesChanged } from './playerPropertiesChanged';
|
||||
import { revealCards } from './revealCards';
|
||||
import { reverseTurn } from './reverseTurn';
|
||||
import { rollDie } from './rollDie';
|
||||
import { setActivePhase } from './setActivePhase';
|
||||
import { setActivePlayer } from './setActivePlayer';
|
||||
import { setCardAttr } from './setCardAttr';
|
||||
import { setCardCounter } from './setCardCounter';
|
||||
import { setCounter } from './setCounter';
|
||||
import { shuffle } from './shuffle';
|
||||
|
||||
export const GameEvents: ProtobufEvents = {
|
||||
'.Event_Join.ext': joinGame,
|
||||
'.Event_Leave.ext': leaveGame,
|
||||
'.Event_GameClosed.ext': () => console.log('Event_GameClosed.ext'),
|
||||
'.Event_GameHostChanged.ext': () => console.log('Event_GameHostChanged.ext'),
|
||||
'.Event_Kicked.ext': () => console.log('Event_Kicked.ext'),
|
||||
'.Event_GameStateChanged.ext': () => console.log('Event_GameStateChanged.ext'),
|
||||
// '.Event_PlayerPropertiesChanged.ext': () => console.log("Event_PlayerProperties.ext"),
|
||||
'.Event_GameSay.ext': () => console.log('Event_GameSay.ext'),
|
||||
'.Event_CreateArrow.ext': () => console.log('Event_CreateArrow.ext'),
|
||||
'.Event_DeleteArrow.ext': () => console.log('Event_DeleteArrow.ext'),
|
||||
'.Event_CreateCounter.ext': () => console.log('Event_CreateCounter.ext'),
|
||||
'.Event_SetCounter.ext': () => console.log('Event_SetCounter.ext'),
|
||||
'.Event_DelCounter.ext': () => console.log('Event_DelCounter.ext'),
|
||||
'.Event_DrawCards.ext': () => console.log('Event_DrawCards.ext'),
|
||||
'.Event_RevealCards.ext': () => console.log('Event_RevealCards.ext'),
|
||||
'.Event_Shuffle.ext': () => console.log('Event_Shuffle.ext'),
|
||||
'.Event_RollDie.ext': () => console.log('Event_Roll.ext'),
|
||||
'.Event_MoveCard.ext': () => console.log('Event_MoveCard.ext'),
|
||||
'.Event_FlipCard.ext': () => console.log('Event_FlipCard.ext'),
|
||||
'.Event_DestroyCard.ext': () => console.log('Event_DestroyCard.ext'),
|
||||
'.Event_AttachCard.ext': () => console.log('Event_AttachCard.ext'),
|
||||
'.Event_CreateToken.ext': () => console.log('Event_CreateToken.ext'),
|
||||
'.Event_SetCardAttribute.ext': () => console.log('Event_SetCardAttribute.ext'),
|
||||
'.Event_SetCardCounter.ext': () => console.log('Event_SetCardCounter.ext'),
|
||||
'.Event_SetActivePlayer.ext': () => console.log('Event_SetActivePlayer.ext'),
|
||||
'.Event_SetActivePhase.ext': () => console.log('Event_SetActivePhase.ext'),
|
||||
'.Event_DumpZone.ext': () => console.log('Event_DumpZone.ext'),
|
||||
'.Event_ChangeZoneProperties.ext': () => console.log('Event_ChangeZoneProperties.ext'),
|
||||
'.Event_ReverseTurn.ext': () => console.log('Event_ReverseTurn.ext'),
|
||||
'.Event_GameClosed.ext': gameClosed,
|
||||
'.Event_GameHostChanged.ext': gameHostChanged,
|
||||
'.Event_Kicked.ext': kicked,
|
||||
'.Event_GameStateChanged.ext': gameStateChanged,
|
||||
'.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged,
|
||||
'.Event_GameSay.ext': gameSay,
|
||||
'.Event_CreateArrow.ext': createArrow,
|
||||
'.Event_DeleteArrow.ext': deleteArrow,
|
||||
'.Event_CreateCounter.ext': createCounter,
|
||||
'.Event_SetCounter.ext': setCounter,
|
||||
'.Event_DelCounter.ext': delCounter,
|
||||
'.Event_DrawCards.ext': drawCards,
|
||||
'.Event_RevealCards.ext': revealCards,
|
||||
'.Event_Shuffle.ext': shuffle,
|
||||
'.Event_RollDie.ext': rollDie,
|
||||
'.Event_MoveCard.ext': moveCard,
|
||||
'.Event_FlipCard.ext': flipCard,
|
||||
'.Event_DestroyCard.ext': destroyCard,
|
||||
'.Event_AttachCard.ext': attachCard,
|
||||
'.Event_CreateToken.ext': createToken,
|
||||
'.Event_SetCardAttr.ext': setCardAttr,
|
||||
'.Event_SetCardCounter.ext': setCardCounter,
|
||||
'.Event_SetActivePlayer.ext': setActivePlayer,
|
||||
'.Event_SetActivePhase.ext': setActivePhase,
|
||||
'.Event_DumpZone.ext': dumpZone,
|
||||
'.Event_ChangeZoneProperties.ext': changeZoneProperties,
|
||||
'.Event_ReverseTurn.ext': reverseTurn,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { GamePersistence } from '../../persistence';
|
||||
import { PlayerGamePropertiesData } from '../session/interfaces';
|
||||
import { GameEventMeta, PlayerProperties } from 'types';
|
||||
|
||||
export function joinGame(playerGamePropertiesData: PlayerGamePropertiesData): void {
|
||||
GamePersistence.joinGame(playerGamePropertiesData);
|
||||
export function joinGame(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void {
|
||||
GamePersistence.playerJoined(meta.gameId, data.playerProperties);
|
||||
}
|
||||
|
|
|
|||
6
webclient/src/websocket/events/game/kicked.ts
Normal file
6
webclient/src/websocket/events/game/kicked.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function kicked(_data: {}, meta: GameEventMeta): void {
|
||||
GamePersistence.kicked(meta.gameId);
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { LeaveGameReason } from 'types';
|
||||
import { GameEventMeta } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
|
||||
export function leaveGame(reason: LeaveGameReason): void {
|
||||
GamePersistence.leaveGame(reason);
|
||||
export function leaveGame(data: { reason: number }, meta: GameEventMeta): void {
|
||||
GamePersistence.playerLeft(meta.gameId, meta.playerId, data.reason ?? 1);
|
||||
}
|
||||
|
|
|
|||
6
webclient/src/websocket/events/game/moveCard.ts
Normal file
6
webclient/src/websocket/events/game/moveCard.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, MoveCardData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function moveCard(data: MoveCardData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardMoved(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, PlayerProperties } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function playerPropertiesChanged(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void {
|
||||
GamePersistence.playerPropertiesChanged(meta.gameId, meta.playerId, data.playerProperties);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/revealCards.ts
Normal file
6
webclient/src/websocket/events/game/revealCards.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, RevealCardsData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function revealCards(data: RevealCardsData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardsRevealed(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/reverseTurn.ts
Normal file
6
webclient/src/websocket/events/game/reverseTurn.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, ReverseTurnData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function reverseTurn(data: ReverseTurnData, meta: GameEventMeta): void {
|
||||
GamePersistence.turnReversed(meta.gameId, data.reversed);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/rollDie.ts
Normal file
6
webclient/src/websocket/events/game/rollDie.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, RollDieData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function rollDie(data: RollDieData, meta: GameEventMeta): void {
|
||||
GamePersistence.dieRolled(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/setActivePhase.ts
Normal file
6
webclient/src/websocket/events/game/setActivePhase.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, SetActivePhaseData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function setActivePhase(data: SetActivePhaseData, meta: GameEventMeta): void {
|
||||
GamePersistence.activePhaseSet(meta.gameId, data.phase);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/setActivePlayer.ts
Normal file
6
webclient/src/websocket/events/game/setActivePlayer.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, SetActivePlayerData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function setActivePlayer(data: SetActivePlayerData, meta: GameEventMeta): void {
|
||||
GamePersistence.activePlayerSet(meta.gameId, data.activePlayerId);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/setCardAttr.ts
Normal file
6
webclient/src/websocket/events/game/setCardAttr.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, SetCardAttrData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function setCardAttr(data: SetCardAttrData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardAttrChanged(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/setCardCounter.ts
Normal file
6
webclient/src/websocket/events/game/setCardCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, SetCardCounterData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function setCardCounter(data: SetCardCounterData, meta: GameEventMeta): void {
|
||||
GamePersistence.cardCounterChanged(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/setCounter.ts
Normal file
6
webclient/src/websocket/events/game/setCounter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, SetCounterData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function setCounter(data: SetCounterData, meta: GameEventMeta): void {
|
||||
GamePersistence.counterSet(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
6
webclient/src/websocket/events/game/shuffle.ts
Normal file
6
webclient/src/websocket/events/game/shuffle.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { GameEventMeta, ShuffleData } from 'types';
|
||||
import { GamePersistence } from '../../persistence';
|
||||
|
||||
export function shuffle(data: ShuffleData, meta: GameEventMeta): void {
|
||||
GamePersistence.zoneShuffled(meta.gameId, meta.playerId, data);
|
||||
}
|
||||
|
|
@ -1,18 +1,25 @@
|
|||
import { GamePersistence } from './GamePersistence';
|
||||
|
||||
jest.mock('store', () => ({
|
||||
GameDispatch: {
|
||||
playerJoined: jest.fn(),
|
||||
playerLeft: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { GameDispatch } from 'store';
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
describe('GamePersistence', () => {
|
||||
it('joinGame logs to console', () => {
|
||||
const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
it('playerJoined dispatches via GameDispatch', () => {
|
||||
const data = { playerId: 1 } as any;
|
||||
GamePersistence.joinGame(data);
|
||||
expect(spy).toHaveBeenCalledWith('joinGame', data);
|
||||
spy.mockRestore();
|
||||
GamePersistence.playerJoined(5, data);
|
||||
expect(GameDispatch.playerJoined).toHaveBeenCalledWith(5, data);
|
||||
});
|
||||
|
||||
it('leaveGame logs to console', () => {
|
||||
const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
GamePersistence.leaveGame(0 as any);
|
||||
expect(spy).toHaveBeenCalledWith('leaveGame', 0);
|
||||
spy.mockRestore();
|
||||
it('playerLeft dispatches via GameDispatch', () => {
|
||||
GamePersistence.playerLeft(5, 1, 3);
|
||||
expect(GameDispatch.playerLeft).toHaveBeenCalledWith(5, 1, 3);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,142 @@
|
|||
import { PlayerGamePropertiesData } from '../events/session/interfaces';
|
||||
import { LeaveGameReason } from '../../types';
|
||||
import { GameDispatch } from 'store';
|
||||
import {
|
||||
AttachCardData,
|
||||
ChangeZonePropertiesData,
|
||||
CreateArrowData,
|
||||
CreateCounterData,
|
||||
CreateTokenData,
|
||||
DelCounterData,
|
||||
DeleteArrowData,
|
||||
DestroyCardData,
|
||||
DrawCardsData,
|
||||
DumpZoneData,
|
||||
FlipCardData,
|
||||
GameStateChangedData,
|
||||
MoveCardData,
|
||||
PlayerProperties,
|
||||
RevealCardsData,
|
||||
RollDieData,
|
||||
SetCardAttrData,
|
||||
SetCardCounterData,
|
||||
SetCounterData,
|
||||
ShuffleData,
|
||||
} from 'types';
|
||||
|
||||
export class GamePersistence {
|
||||
static joinGame(playerGamePropertiesData: PlayerGamePropertiesData) {
|
||||
console.log('joinGame', playerGamePropertiesData);
|
||||
static gameStateChanged(gameId: number, data: GameStateChangedData): void {
|
||||
GameDispatch.gameStateChanged(gameId, data);
|
||||
}
|
||||
|
||||
static leaveGame(reason: LeaveGameReason) {
|
||||
console.log('leaveGame', reason);
|
||||
static playerJoined(gameId: number, playerProperties: PlayerProperties): void {
|
||||
GameDispatch.playerJoined(gameId, playerProperties);
|
||||
}
|
||||
|
||||
static playerLeft(gameId: number, playerId: number, reason: number): void {
|
||||
GameDispatch.playerLeft(gameId, playerId, reason);
|
||||
}
|
||||
|
||||
static playerPropertiesChanged(gameId: number, playerId: number, properties: PlayerProperties): void {
|
||||
GameDispatch.playerPropertiesChanged(gameId, playerId, properties);
|
||||
}
|
||||
|
||||
static gameClosed(gameId: number): void {
|
||||
GameDispatch.gameClosed(gameId);
|
||||
}
|
||||
|
||||
static gameHostChanged(gameId: number, hostId: number): void {
|
||||
GameDispatch.gameHostChanged(gameId, hostId);
|
||||
}
|
||||
|
||||
static kicked(gameId: number): void {
|
||||
GameDispatch.kicked(gameId);
|
||||
}
|
||||
|
||||
static gameSay(gameId: number, playerId: number, message: string): void {
|
||||
GameDispatch.gameSay(gameId, playerId, message);
|
||||
}
|
||||
|
||||
static cardMoved(gameId: number, playerId: number, data: MoveCardData): void {
|
||||
GameDispatch.cardMoved(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static cardFlipped(gameId: number, playerId: number, data: FlipCardData): void {
|
||||
GameDispatch.cardFlipped(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static cardDestroyed(gameId: number, playerId: number, data: DestroyCardData): void {
|
||||
GameDispatch.cardDestroyed(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static cardAttached(gameId: number, playerId: number, data: AttachCardData): void {
|
||||
GameDispatch.cardAttached(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static tokenCreated(gameId: number, playerId: number, data: CreateTokenData): void {
|
||||
GameDispatch.tokenCreated(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static cardAttrChanged(gameId: number, playerId: number, data: SetCardAttrData): void {
|
||||
GameDispatch.cardAttrChanged(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static cardCounterChanged(gameId: number, playerId: number, data: SetCardCounterData): void {
|
||||
GameDispatch.cardCounterChanged(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static arrowCreated(gameId: number, playerId: number, data: CreateArrowData): void {
|
||||
GameDispatch.arrowCreated(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static arrowDeleted(gameId: number, playerId: number, data: DeleteArrowData): void {
|
||||
GameDispatch.arrowDeleted(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static counterCreated(gameId: number, playerId: number, data: CreateCounterData): void {
|
||||
GameDispatch.counterCreated(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static counterSet(gameId: number, playerId: number, data: SetCounterData): void {
|
||||
GameDispatch.counterSet(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static counterDeleted(gameId: number, playerId: number, data: DelCounterData): void {
|
||||
GameDispatch.counterDeleted(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static cardsDrawn(gameId: number, playerId: number, data: DrawCardsData): void {
|
||||
GameDispatch.cardsDrawn(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static cardsRevealed(gameId: number, playerId: number, data: RevealCardsData): void {
|
||||
GameDispatch.cardsRevealed(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static zoneShuffled(gameId: number, playerId: number, data: ShuffleData): void {
|
||||
GameDispatch.zoneShuffled(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static dieRolled(gameId: number, playerId: number, data: RollDieData): void {
|
||||
GameDispatch.dieRolled(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static activePlayerSet(gameId: number, activePlayerId: number): void {
|
||||
GameDispatch.activePlayerSet(gameId, activePlayerId);
|
||||
}
|
||||
|
||||
static activePhaseSet(gameId: number, phase: number): void {
|
||||
GameDispatch.activePhaseSet(gameId, phase);
|
||||
}
|
||||
|
||||
static turnReversed(gameId: number, reversed: boolean): void {
|
||||
GameDispatch.turnReversed(gameId, reversed);
|
||||
}
|
||||
|
||||
static zoneDumped(gameId: number, playerId: number, data: DumpZoneData): void {
|
||||
GameDispatch.zoneDumped(gameId, playerId, data);
|
||||
}
|
||||
|
||||
static zonePropertiesChanged(gameId: number, playerId: number, data: ChangeZonePropertiesData): void {
|
||||
GameDispatch.zonePropertiesChanged(gameId, playerId, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ jest.mock('store', () => ({
|
|||
replayModifyMatch: jest.fn(),
|
||||
replayDeleteMatch: jest.fn(),
|
||||
},
|
||||
GameDispatch: {
|
||||
gameJoined: jest.fn(),
|
||||
playerPropertiesChanged: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('websocket/utils', () => ({
|
||||
|
|
@ -68,7 +72,7 @@ jest.mock('../utils/NormalizeService', () => ({
|
|||
}));
|
||||
|
||||
import { SessionPersistence } from './SessionPersistence';
|
||||
import { ServerDispatch } from 'store';
|
||||
import { ServerDispatch, GameDispatch } from 'store';
|
||||
import { sanitizeHtml } from 'websocket/utils';
|
||||
import NormalizeService from '../utils/NormalizeService';
|
||||
import { StatusEnum } from 'types';
|
||||
|
|
@ -318,11 +322,9 @@ describe('SessionPersistence', () => {
|
|||
expect(ServerDispatch.notifyUser).toHaveBeenCalledWith(notif);
|
||||
});
|
||||
|
||||
it('playerPropertiesChanged logs to console', () => {
|
||||
const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
SessionPersistence.playerPropertiesChanged({} as any);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
it('playerPropertiesChanged dispatches via GameDispatch', () => {
|
||||
SessionPersistence.playerPropertiesChanged(5, 1, {} as any);
|
||||
expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, {});
|
||||
});
|
||||
|
||||
it('serverShutdown passes data', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { ServerDispatch } from 'store';
|
||||
import { GameDispatch, ServerDispatch } from 'store';
|
||||
import { DeckList, DeckStorageTreeItem, ReplayMatch, StatusEnum, User, WebSocketConnectOptions } from 'types';
|
||||
import { GameEntry } from 'store/game/game.interfaces';
|
||||
|
||||
import { sanitizeHtml } from 'websocket/utils';
|
||||
import {
|
||||
|
|
@ -180,15 +181,32 @@ export class SessionPersistence {
|
|||
}
|
||||
|
||||
static gameJoined(gameJoinedData: GameJoinedData): void {
|
||||
console.log('gameJoined', gameJoinedData);
|
||||
const { gameInfo, hostId, playerId, spectator, judge } = gameJoinedData;
|
||||
const gameEntry: GameEntry = {
|
||||
gameId: gameInfo.gameId,
|
||||
roomId: gameInfo.roomId,
|
||||
description: gameInfo.description,
|
||||
hostId,
|
||||
localPlayerId: playerId,
|
||||
spectator,
|
||||
judge,
|
||||
started: gameInfo.started,
|
||||
activePlayerId: -1,
|
||||
activePhase: -1,
|
||||
secondsElapsed: 0,
|
||||
reversed: false,
|
||||
players: {},
|
||||
messages: [],
|
||||
};
|
||||
GameDispatch.gameJoined(gameInfo.gameId, gameEntry);
|
||||
}
|
||||
|
||||
static notifyUser(notification: NotifyUserData): void {
|
||||
ServerDispatch.notifyUser(notification);
|
||||
}
|
||||
|
||||
static playerPropertiesChanged(payload: PlayerGamePropertiesData): void {
|
||||
console.log('playerPropertiesChanged', payload);
|
||||
static playerPropertiesChanged(gameId: number, playerId: number, payload: PlayerGamePropertiesData): void {
|
||||
GameDispatch.playerPropertiesChanged(gameId, playerId, payload);
|
||||
}
|
||||
|
||||
static serverShutdown(data: ServerShutdownData): void {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@ export interface CommandOptions {
|
|||
}
|
||||
|
||||
export class BackendService {
|
||||
static sendGameCommand(gameId: number, commandName: string, params: any, options: CommandOptions = {}): void {
|
||||
const command = ProtoController.root[commandName].create(params || {});
|
||||
const gc = ProtoController.root.GameCommand.create({
|
||||
[`.${commandName}.ext`]: command,
|
||||
});
|
||||
webClient.protobuf.sendGameCommand(gameId, gc, (raw: any) => {
|
||||
BackendService.handleResponse(commandName, raw, options);
|
||||
});
|
||||
}
|
||||
|
||||
static sendSessionCommand(commandName: string, params: any, options: CommandOptions): void {
|
||||
const command = ProtoController.root[commandName].create(params || {});
|
||||
const sc = ProtoController.root.SessionCommand.create({
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { CommonEvents, GameEvents, RoomEvents, SessionEvents } from '../events';
|
|||
import { WebClient } from '../WebClient';
|
||||
import { SessionCommands } from 'websocket';
|
||||
import { ProtoController } from './ProtoController';
|
||||
import { GameEventMeta } from 'types';
|
||||
|
||||
export interface ProtobufEvents {
|
||||
[event: string]: Function;
|
||||
|
|
@ -23,6 +24,15 @@ export class ProtobufService {
|
|||
this.pendingCommands = {};
|
||||
}
|
||||
|
||||
public sendGameCommand(gameId: number, gameCmd: any, callback?: Function) {
|
||||
const cmd = ProtoController.root.CommandContainer.create({
|
||||
gameId,
|
||||
gameCommand: [gameCmd],
|
||||
});
|
||||
|
||||
this.sendCommand(cmd, (raw: any) => callback && callback(raw));
|
||||
}
|
||||
|
||||
public sendRoomCommand(roomId: number, roomCmd: any, callback?: Function) {
|
||||
const cmd = ProtoController.root.CommandContainer.create({
|
||||
'roomId': roomId,
|
||||
|
|
@ -88,7 +98,7 @@ export class ProtobufService {
|
|||
this.processSessionEvent(msg.sessionEvent, msg);
|
||||
break;
|
||||
case ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER:
|
||||
this.processGameEvent(msg.gameEvent, msg);
|
||||
this.processGameEvent(msg.gameEventContainer, msg);
|
||||
break;
|
||||
default:
|
||||
console.log(msg);
|
||||
|
|
@ -121,8 +131,42 @@ export class ProtobufService {
|
|||
this.processEvent(response, SessionEvents, raw);
|
||||
}
|
||||
|
||||
private processGameEvent(response: any, raw: any): void {
|
||||
this.processEvent(response, GameEvents, raw);
|
||||
private processGameEvent(container: any, raw: any): void {
|
||||
if (!container?.eventList?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { gameId, context, secondsElapsed, forcedByJudge } = container;
|
||||
|
||||
for (const event of container.eventList) {
|
||||
const meta: GameEventMeta = {
|
||||
gameId: gameId ?? -1,
|
||||
playerId: event.playerId ?? -1,
|
||||
context,
|
||||
secondsElapsed: secondsElapsed ?? 0,
|
||||
forcedByJudge: forcedByJudge ?? 0,
|
||||
};
|
||||
|
||||
// Try registered game event handlers first, then common event handlers
|
||||
let handled = false;
|
||||
for (const key of Object.keys(GameEvents)) {
|
||||
const payload = event[key];
|
||||
if (payload !== undefined && payload !== null) {
|
||||
(GameEvents[key] as Function)(payload, meta);
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
for (const key of Object.keys(CommonEvents)) {
|
||||
const payload = event[key];
|
||||
if (payload !== undefined && payload !== null) {
|
||||
(CommonEvents[key] as Function)(payload, meta);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processEvent(response: any, events: ProtobufEvents, raw: any) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue