From a0b7ab175e6ef5739f0c7248a284a6b4fd4902f5 Mon Sep 17 00:00:00 2001 From: Kashargul <144968721+Kashargul@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:29:55 +0100 Subject: [PATCH] fix spacebattle (#19240) * fix spacebattle * clean that up * . * eh * fine * . --- .../casino/board_games/space_battle.dm | 2 +- code/modules/tgui/states/board_game.dm | 11 ++-- .../interfaces/ChessCheckers/PlayerMenu.tsx | 15 +++++ .../tgui/interfaces/NineMen/PlayerMenu.tsx | 4 ++ .../tgui/interfaces/SpaceBattle/GameArea.tsx | 45 ++++++++++----- .../interfaces/SpaceBattle/PlayerMenu.tsx | 56 +++++++++++++++---- .../tgui/interfaces/SpaceBattle/functions.ts | 26 +++++++++ .../tgui/interfaces/SpaceBattle/types.ts | 2 +- .../tgui/interfaces/VorePanel/functions.ts | 37 ++++++------ .../tgui/interfaces/VorePanel/types.ts | 6 +- .../tgui/interfaces/common/AnimatedArrows.tsx | 35 ++++++++++++ .../tgui/styles/interfaces/SpaceBattle.scss | 4 ++ tgui/packages/tgui/styles/main.scss | 1 + 13 files changed, 188 insertions(+), 56 deletions(-) create mode 100644 tgui/packages/tgui/styles/interfaces/SpaceBattle.scss diff --git a/code/modules/casino/board_games/space_battle.dm b/code/modules/casino/board_games/space_battle.dm index 8e8158e3bf6..d446badde3b 100644 --- a/code/modules/casino/board_games/space_battle.dm +++ b/code/modules/casino/board_games/space_battle.dm @@ -71,7 +71,7 @@ "current_player" = ui.user, "player_one" = player_one_mob, "player_two" = player_two_mob, - "all_placed" = ships_have_been_placed == (PLAYER_ONE_PLACED_SHIPS | PLAYER_TWO_PLACED_SHIPS), + "all_placed" = ships_have_been_placed, "shots_fired_pone" = shots_fired_pone, "shots_fired_ptwo" = shots_fired_ptwo, "destroyed_ships_pone" = destroyed_ships_pone, diff --git a/code/modules/tgui/states/board_game.dm b/code/modules/tgui/states/board_game.dm index 7ced0b7ef03..2f97455aff4 100644 --- a/code/modules/tgui/states/board_game.dm +++ b/code/modules/tgui/states/board_game.dm @@ -1,7 +1,7 @@ /** * tgui state: board_game_state * - * Checks that the default living handling or if the user or the object are inside a belly + * Checks the distance only based on the turf, to allow interaction with devices held in hand or people inside a belly **/ GLOBAL_DATUM_INIT(tgui_board_game_state, /datum/tgui_state/board_game_state, new) @@ -9,10 +9,7 @@ GLOBAL_DATUM_INIT(tgui_board_game_state, /datum/tgui_state/board_game_state, new /datum/tgui_state/board_game_state/can_use_topic(atom/src_object, mob/user) if(!isliving(user)) return STATUS_UPDATE - var/mob/living/living_user = user - if(isbelly(living_user.loc) || isbelly(src_object.loc)) - return living_user.board_game_can_use_tgui_topic(src_object) - return living_user.default_can_use_tgui_topic(src_object) + return user.board_game_can_use_tgui_topic(src_object) /mob/proc/board_game_can_use_tgui_topic(atom/src_object) return STATUS_CLOSE @@ -23,4 +20,8 @@ GLOBAL_DATUM_INIT(tgui_board_game_state, /datum/tgui_state/board_game_state, new var/dist = get_dist(get_turf(src_object), get_turf(src)) if(dist <= 1) return STATUS_INTERACTIVE + else if(dist <= 2) // View only if 2-3 tiles away. + return STATUS_UPDATE + else if(dist <= 5) // Disable if 5 tiles away. + return STATUS_DISABLED return STATUS_CLOSE diff --git a/tgui/packages/tgui/interfaces/ChessCheckers/PlayerMenu.tsx b/tgui/packages/tgui/interfaces/ChessCheckers/PlayerMenu.tsx index c7535e9ef41..105e9285976 100644 --- a/tgui/packages/tgui/interfaces/ChessCheckers/PlayerMenu.tsx +++ b/tgui/packages/tgui/interfaces/ChessCheckers/PlayerMenu.tsx @@ -1,5 +1,6 @@ import { useBackend } from 'tgui/backend'; import { Box, Button, Section, Stack } from 'tgui-core/components'; +import { AnimatedArrows, AnimatedArrowsLeft } from '../common/AnimatedArrows'; import { VorePanelTooltip } from '../VorePanel/VorePanelElements/VorePanelTooltip'; import { gameTooltip } from './constants'; import { gameStateToText, stateToColor } from './functions'; @@ -111,6 +112,9 @@ export const PlayerMenu = (props) => { {gameStateToText(game_state, game_flags, winner)} + + + { ); }; + +export const PlayerIndicator = (props: { gameState: number }) => { + const { gameState } = props; + if (gameState === 1) { + return ; + } + if (gameState === 2) { + return ; + } + return null; +}; diff --git a/tgui/packages/tgui/interfaces/NineMen/PlayerMenu.tsx b/tgui/packages/tgui/interfaces/NineMen/PlayerMenu.tsx index 93738fbdeb9..c6d07f5f27b 100644 --- a/tgui/packages/tgui/interfaces/NineMen/PlayerMenu.tsx +++ b/tgui/packages/tgui/interfaces/NineMen/PlayerMenu.tsx @@ -1,5 +1,6 @@ import { useBackend } from 'tgui/backend'; import { Box, Button, Section, Stack } from 'tgui-core/components'; +import { PlayerIndicator } from '../ChessCheckers/PlayerMenu'; import { VorePanelTooltip } from '../VorePanel/VorePanelElements/VorePanelTooltip'; import { gameTooltip, phastToText } from './constants'; import { gameStateToText, stateToColor } from './functions'; @@ -110,6 +111,9 @@ export const PlayerMenu = (props) => { {phastToText[phase]} + + + ; destroyedShips: Ship[]; @@ -86,7 +86,7 @@ const Playfield = (props: { const { game_state, ship_sizes, visible_ships } = data; const { player, - isOponent, + isOpponent, isSelf, shotsFired, destroyedShips, @@ -167,7 +167,12 @@ const Playfield = (props: { function getCellStyle(x: number, y: number) { const key = `${x},${y}`; - let cellStyle: React.CSSProperties = {}; + let cellStyle: { + backgroundColor?: string; + icon?: string; + disabled?: boolean; + classes: string[]; + } = { classes: [] }; if (visible_ships) { for (const ship of visible_ships) { @@ -184,16 +189,21 @@ const Playfield = (props: { const shot = shotsFired[key]; if (shot === 1) { cellStyle.backgroundColor = 'red'; + cellStyle.icon = 'explosion'; + cellStyle.disabled = true; } else if (shot === 0) { cellStyle.backgroundColor = 'white'; + cellStyle.icon = 'water'; + cellStyle.disabled = true; } } for (const ship of destroyedShips) { if (ship.coords?.some((coord) => coord[0] === x && coord[1] === y)) { cellStyle = { - border: '2px solid gold', - backgroundColor: 'maroon', + icon: 'burst', + classes: ['SpaceBattle__ShipSunk'], + disabled: true, }; } } @@ -227,11 +237,10 @@ const Playfield = (props: { ) : ( handleButtonHover(x, y)}> { if (game_state === 1) { @@ -253,14 +262,20 @@ const Playfield = (props: { }); } }} + icon={getCellStyle(x, y).icon} + color={ + !isHighlighted && getCellStyle(x, y).backgroundColor + } style={{ backgroundColor: isHighlighted ? invalidCells ? 'rgba(255, 0, 0, 0.5)' : 'rgba(76, 175, 80, 0.5)' - : getCellStyle(x, y).backgroundColor, - border: getCellStyle(x, y).border, + : undefined, }} + lineHeight="75px" + iconColor="black" + iconSize={2.5} width={5} height={5} /> diff --git a/tgui/packages/tgui/interfaces/SpaceBattle/PlayerMenu.tsx b/tgui/packages/tgui/interfaces/SpaceBattle/PlayerMenu.tsx index 82d02cf1d55..0554edd104e 100644 --- a/tgui/packages/tgui/interfaces/SpaceBattle/PlayerMenu.tsx +++ b/tgui/packages/tgui/interfaces/SpaceBattle/PlayerMenu.tsx @@ -1,5 +1,6 @@ import { useBackend } from 'tgui/backend'; import { Box, Button, Section, Stack } from 'tgui-core/components'; +import { AnimatedArrows, AnimatedArrowsLeft } from '../common/AnimatedArrows'; import { VorePanelTooltip } from '../VorePanel/VorePanelElements/VorePanelTooltip'; import { gameStateToText, gameTooltip } from './constants'; import { stateToColor } from './functions'; @@ -83,17 +84,20 @@ export const PlayerMenu = (props: { > ))} - {game_state === 1 && !!player_one && !!player_two && !!all_placed && ( - - { - act('start_game'); - }} - > - Start Game - - - )} + {game_state === 1 && + !!player_one && + !!player_two && + all_placed === 3 && ( + + { + act('start_game'); + }} + > + Start Game + + + )} {!!player_one && !!player_two && game_state === 4 && ( <> @@ -147,6 +151,9 @@ export const PlayerMenu = (props: { {gameStateToText[game_state] ?? `${winner} Won`} + + + ); }; + +const PlayerIndicator = (props: { gameState: number; allPlaced: number }) => { + const { gameState, allPlaced } = props; + if (gameState === 1) { + return ( + + {(allPlaced & 0x1) === 0 && ( + + + + )} + {(allPlaced & 0x2) === 0 && ( + + + + )} + + ); + } + if (gameState === 2) { + return ; + } + if (gameState === 3) { + return ; + } + return null; +}; diff --git a/tgui/packages/tgui/interfaces/SpaceBattle/functions.ts b/tgui/packages/tgui/interfaces/SpaceBattle/functions.ts index 1a25ee8eaf2..68bee1dd0bd 100644 --- a/tgui/packages/tgui/interfaces/SpaceBattle/functions.ts +++ b/tgui/packages/tgui/interfaces/SpaceBattle/functions.ts @@ -38,3 +38,29 @@ export function getNextAvailableShip( } return null; } + +export function mapDisabled( + state: number, + self: boolean, + opponent: boolean, +): boolean { + if (!self && !opponent) { + return true; + } + + switch (state) { + case 0: + case 4: + return true; + + case 1: + return opponent && self ? false : opponent; + + case 2: + case 3: + return opponent && self ? false : self; + + default: + return false; + } +} diff --git a/tgui/packages/tgui/interfaces/SpaceBattle/types.ts b/tgui/packages/tgui/interfaces/SpaceBattle/types.ts index 8a48e4718a4..578a14c95d2 100644 --- a/tgui/packages/tgui/interfaces/SpaceBattle/types.ts +++ b/tgui/packages/tgui/interfaces/SpaceBattle/types.ts @@ -13,7 +13,7 @@ export type Data = { total_ships: Record; player_one: string; player_two: string; - ships_have_been_placed: BooleanLike; + all_placed: number; shots_fired_pone: Record; shots_fired_ptwo: Record; destroyed_ships_pone: Ship[]; diff --git a/tgui/packages/tgui/interfaces/VorePanel/functions.ts b/tgui/packages/tgui/interfaces/VorePanel/functions.ts index 472fbac2c2e..1915c8a3ee6 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/functions.ts +++ b/tgui/packages/tgui/interfaces/VorePanel/functions.ts @@ -27,31 +27,32 @@ export function calcLineHeight(lim: number, height: number): string { } export function fixCorruptedData( - toSanitize: - | string - | string[] - | null - | Record, + toSanitize: string | string[] | null | Record, ): { corrupted?: boolean; data: string | string[] } { - if (toSanitize === null) { + if (toSanitize == null) { return { data: '' }; } - if (typeof toSanitize === 'string') { + + if (typeof toSanitize === 'string' || Array.isArray(toSanitize)) { return { data: toSanitize }; } - if (Array.isArray(toSanitize)) { - return { data: toSanitize }; - } - const clearedData = Object.entries(toSanitize).map((entry) => { - if (typeof entry[0] === 'string') { - return entry[0]; - } else if (typeof entry[1] === 'string') { - return entry[1]; - } else { - return ''; + + const isNumeric = (v: string) => /^\d+$/.test(v); + + const clearedData = Object.entries(toSanitize).map(([key, value]) => { + if (!isNumeric(value.toString())) { + return value.toString(); } + if (!isNumeric(key)) { + return key; + } + return ''; }); - return { corrupted: true, data: clearedData || [] }; + + return { + corrupted: true, + data: clearedData, + }; } export function bellyTemperatureToColor(temp: number): string | undefined { diff --git a/tgui/packages/tgui/interfaces/VorePanel/types.ts b/tgui/packages/tgui/interfaces/VorePanel/types.ts index 71d6f3904be..ab960d16375 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/types.ts +++ b/tgui/packages/tgui/interfaces/VorePanel/types.ts @@ -419,11 +419,7 @@ export type AestMessageData = { possible_messages: string[]; aest_subtab: string; max_length: number; - active_message: - | string - | string[] - | null - | Record; // The record is an ancient data corruption, it's not valid! + active_message: string | string[] | null | Record; // The record is an ancient data corruption, it's not valid! set_action: string; tooltip: string; sub_action?: string; diff --git a/tgui/packages/tgui/interfaces/common/AnimatedArrows.tsx b/tgui/packages/tgui/interfaces/common/AnimatedArrows.tsx index cb56833872d..e99c72dc5dd 100644 --- a/tgui/packages/tgui/interfaces/common/AnimatedArrows.tsx +++ b/tgui/packages/tgui/interfaces/common/AnimatedArrows.tsx @@ -35,3 +35,38 @@ export const AnimatedArrows = ( ); }; + +export const AnimatedArrowsLeft = ( + props: { on: boolean } & ComponentProps, +) => { + const { on, ...rest } = props; + + const [activeArrow, setActiveArrow] = useState(0); + + // Lower to make it animate faster + const SPEED = 200; + + useEffect(() => { + const id = setInterval(() => { + setActiveArrow((arrow) => (arrow - 1 + 3) % 3); + }, SPEED); + return () => clearInterval(id); + }, []); + + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/styles/interfaces/SpaceBattle.scss b/tgui/packages/tgui/styles/interfaces/SpaceBattle.scss new file mode 100644 index 00000000000..e83fb1cb50f --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/SpaceBattle.scss @@ -0,0 +1,4 @@ +.SpaceBattle__ShipSunk { + background-color: #800000 !important; + border: 2px solid gold; +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index d9f22f04b54..f60a3df5f2a 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -48,6 +48,7 @@ @include meta.load-css('./interfaces/RIGSuit.scss'); @include meta.load-css('./interfaces/Roulette.scss'); @include meta.load-css('./interfaces/Safe.scss'); +@include meta.load-css('./interfaces/SpaceBattle.scss'); @include meta.load-css('./interfaces/TachyonArray.scss'); @include meta.load-css('./interfaces/Techweb.scss'); @include meta.load-css('./interfaces/IDCard.scss');