GameTileView.tsx 12.1 Ko
Télécharger le fichier
import { GameConfigurationData, JoinQueueMessageComposer, PurchaseSnowWarGameTokensOfferComposer, SnowWarGameTokenOffer } from '@octane/renderer';
import { CSSProperties, FC, useEffect, useState } from 'react';
import {
applySnowWarPlural,
ColorUtils,
CreateLinkEvent,
formatSnowWarBlockCountdown,
GetConfigurationValue,
getGamesLeftStatus,
getSnowWarTokenPrice,
LocalizeText,
SendMessageComposer,
snowWarBlockSecondsLeft,
SNOWWAR_TOKEN_BUTTONS
} from '../../../api';
import { SNOWWAR_AIR_ASSETS } from '../../../assets/images/snowstorm/air';
import snowStormLogo from '../../../assets/images/snowstorm/snowstorm.png';
import { useGameCenter, useNotification, useSnowWar } from '../../../hooks';
import { localizeWithFallback, snowWarErrorText } from './snowwar/SnowWarErrorTexts';
import { SnowWarStrokeText } from './snowwar/SnowWarStrokeText';
import { BaseButton } from '../../../common/BaseButton';
import { NativeTextTree } from '../../../common/native-text/NativeTextTree';
/**
* One 275x250 game card in the game center hub. Play joins the game's queue
* on the server; for SnowWar the queue/countdown status is shown inside the
* tile until the match starts and the arena takes over the screen.
*/
export const GameTileView: FC<{ game: GameConfigurationData }> = ({ game }) =>
{
const { accountStatus, setSelectedGame, blockedUntil, setInstructionsOpen, tokenOffers } = useGameCenter();
const { showConfirm = null } = useNotification();
const [now, setNow] = useState(() => Date.now());
// Tick once a second while the leave block runs.
useEffect(() =>
{
const tick = () => setNow(Date.now());
const first = setTimeout(tick, 0);
const interval = (blockedUntil > Date.now()) ? setInterval(tick, 1000) : null;
return () =>
{
clearTimeout(first);
if (interval) clearInterval(interval);
};
}, [ blockedUntil ]);
const blockSeconds = snowWarBlockSecondsLeft(blockedUntil, now);
const { phase, queuePosition, queueSize, lobbySeconds, queueInfo, errorCode, arenaQueuePosition = -1, leaveQueue, requestLeaderboard, startEditing } = useSnowWar();
const isSnowWar = (game.gameNameId === 'snowwar');
const inQueue = isSnowWar && ((phase === 'queued') || (phase === 'lobby'));
// Not enough players yet to start the countdown - show "awaiting players"
// (X/min) instead of a bare "1/1" queue position.
const minPlayers = queueInfo?.minPlayers ?? 0;
const awaitingPlayers = (phase === 'queued') && (minPlayers > 1) && (queueSize < minPlayers);
// AIR games_main footer: "Total Games Left: N" (red at 0, hidden when
// unlimited), the token buttons, the HC upsell, and a play button that
// counts the leave-game block down as m:ss.
const freeGamesLeft = accountStatus?.freeGamesLeft ?? -1;
const gamesLeft = getGamesLeftStatus(freeGamesLeft, accountStatus?.hasUnlimitedGames ?? true);
const tokenButtons = SNOWWAR_TOKEN_BUTTONS
.map(button => ({ button, offer: (tokenOffers ?? []).find(offer => offer.localizationId === button.localizationId) }))
.filter(entry => !!entry.offer);
const openClubCenter = () =>
{
const links = GetConfigurationValue<Record<string, string>>('catalog.links', {});
CreateLinkEvent(`catalog/open/${links?.['hc.buy_hc'] ?? 'habbo_club'}`);
};
// AIR buySnowWarTokensOffer: confirm with the price, then buy the offer.
const buyTokens = (offer: SnowWarGameTokenOffer, games: number) =>
{
const price = getSnowWarTokenPrice(offer.priceInCredits, offer.priceInActivityPoints, offer.activityPointType);
const priceText = LocalizeText(price.key, price.parameters, price.replacements);
const message = LocalizeText('catalog.purchase.confirmation.dialog.costs', [ 'offer_name', 'price' ], [ `${games}x SnowStorm`, priceText ]);
const buy = () => SendMessageComposer(new PurchaseSnowWarGameTokensOfferComposer(offer.offerId));
if (!showConfirm) return buy();
showConfirm(message, buy, null, null, null, localizeWithFallback('catalog.purchase.confirmation.dialog.title', 'Order Info'));
};
const showLeaderboard = GetConfigurationValue<boolean>('games.highscores.enabled', true);
const onPlay = () =>
{
setSelectedGame(game);
SendMessageComposer(new JoinQueueMessageComposer(game.gameId));
};
const title = localizeWithFallback(`gamecenter.${game.gameNameId}.description_title`, game.gameNameId);
const description = localizeWithFallback(`gamecenter.${game.gameNameId}.description_content`, '');
const errorText = (isSnowWar && (phase === 'idle') && errorCode) ? snowWarErrorText(errorCode) : null;
return (
<NativeTextTree>
<div className={`game-tile${isSnowWar ? ' game-tile--snowwar' : ''}`}>
<div
className="game-tile__banner"
style={{ backgroundColor: ColorUtils.uintHexColor(game.bgColor), backgroundImage: `url(${game.assetUrl}${game.gameNameId}_theme.png)` }}>
<img alt={game.gameNameId} className="game-tile__logo" src={isSnowWar ? snowStormLogo : `${game.assetUrl}${game.gameNameId}_logo.png`} />
</div>
<div className="game-tile__body">
<div className="game-tile__title">{title}</div>
{!inQueue && description && <div className="game-tile__desc">{description}</div>}
{inQueue && (
<div className="game-tile__queue">
<div>
{((phase === 'queued') && (arenaQueuePosition > 0))
? localizeWithFallback('snowwar.lobby_arena_queue_position', 'Position in queue: %position%')
.replace('%position%', arenaQueuePosition.toString())
: (phase !== 'queued')
? applySnowWarPlural(localizeWithFallback('snowwar.lobby_game_start_countdown', '%seconds% seconds until the Storm!'), 'seconds', lobbySeconds)
: awaitingPlayers
? `${localizeWithFallback('snowwar.lobby_waiting_for_more_players', 'Waiting for more players...')} (${queueSize}/${minPlayers})`
: localizeWithFallback('snowwar.queue.position', 'In queue: %position% / %size%')
.replace('%position%', queuePosition.toString())
.replace('%size%', queueSize.toString())}
</div>
<BaseButton className="snowwar-button snowwar-button--danger" type="button" onClick={() => leaveQueue()}>
{localizeWithFallback('snowwar.queue.leave', 'Leave queue')}
</BaseButton>
</div>
)}
{!isSnowWar && (
<BaseButton disabled className="snowwar-button game-tile__play" type="button">
{localizeWithFallback('gamecenter.coming_soon', 'Coming soon!')}
</BaseButton>
)}
{isSnowWar && !inQueue && (
<div className="game-tile__links">
<BaseButton className="game-tile__link" type="button" onClick={() => setInstructionsOpen(true)}>
{localizeWithFallback('snowwar.instructions.link', 'How To Play')}
</BaseButton>
{showLeaderboard && (
<BaseButton className="game-tile__link" type="button" onClick={() => requestLeaderboard(false, true, 0)}>
{localizeWithFallback('snowwar.leaderboards.link', 'Leaderboard')}
</BaseButton>
)}
</div>
)}
{isSnowWar && !inQueue && (
<div className="game-tile__footer">
{gamesLeft.showCounter && (
<div className="game-tile__games-left">
<span>{localizeWithFallback('snowwar.games_left', 'Total Games Left:')}</span>
{/* AIR games_left over games_left_stroke: red stroke at 0, blue otherwise. */}
<span className={`game-tile__games-left-count game-tile__games-left-count--${gamesLeft.counterTone}`}>
<SnowWarStrokeText stroke={(gamesLeft.counterTone === 'red') ? '#ff0000' : '#1077ac'} text={freeGamesLeft} />
</span>
</div>
)}
{gamesLeft.showCounter && (tokenButtons.length > 0) && (
<div className="game-tile__tokens">
<span>{localizeWithFallback('snowwar.buy_more_games', 'Buy more games:')}</span>
<div className="game-tile__token-buttons">
{tokenButtons.map(({ button, offer }) => (
<BaseButton
key={offer.offerId}
aria-label={`${button.games}`}
className="game-tile__token"
style={{ '--token-image': `url(${SNOWWAR_AIR_ASSETS[button.asset]})`, '--token-image-hi': `url(${SNOWWAR_AIR_ASSETS[`${button.asset}_hi`]})` } as CSSProperties}
type="button"
onClick={() => buyTokens(offer, button.games)} />
))}
</div>
</div>
)}
{gamesLeft.showCounter && (
<BaseButton className="game-tile__vip" type="button" onClick={openClubCenter}>
<img alt="" className="game-tile__hc-icon" src={SNOWWAR_AIR_ASSETS.hc_icon} />
<span>{localizeWithFallback('snowwar.get_more_games', 'Get additional free daily games with HC!')}</span>
</BaseButton>
)}
<div className="game-tile__actions">
{/* AIR updateGettingMoreGamesOption: no play button with 0 games left. */}
{gamesLeft.canStart && (
<BaseButton
className={`snowwar-button game-tile__play${(blockSeconds > 0) ? ' game-tile__play--blocked' : ''}`}
disabled={blockSeconds > 0}
type="button"
onClick={onPlay}>
{(blockSeconds > 0)
? formatSnowWarBlockCountdown(blockSeconds)
: localizeWithFallback('snowwar.play', 'Play now!')}
</BaseButton>
)}
{queueInfo?.canEdit && (
<BaseButton className="snowwar-button snowwar-button--edit" type="button" onClick={() => startEditing()}>
{localizeWithFallback('snowwar.editor.build_custom', 'Build Custom Arena')}
</BaseButton>
)}
</div>
</div>
)}
{errorText && <div className="game-tile__error">{errorText}</div>}
</div>
</div>
</NativeTextTree>
);
};
