1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 |
- import { useEffect, useState } from "react";
- import {
- dispatch,
- useGameId,
- useIsMuted,
- usePlayerName,
- } from "../../../domain/gameStore";
- import { usePlayers } from "../../../hooks/useGameInfo";
- import styles from "./KillFeed.module.css";
- import hitmarker from "../../../assets/hitmarker.svg";
- import hitsound from "../../../assets/hitsound.wav";
- // okay, in an ideal world this would be part of the game store or something
- // and it would get properly managed by reactive state
- // but also, this totally works as is, and the only downside is it might potentially grow too big
- // but that only happens if someone plays that many gun games without ever leaving the window
- const shownItems = new Set();
- const KillFeed = () => {
- const muted = useIsMuted();
- const playerName = usePlayerName();
- const gameId = useGameId();
- const players = usePlayers();
- useEffect(() => {
- if (players?.find(({ currentRound }) => currentRound === null)) {
- dispatch.goToSummary();
- }
- }, [players]);
- const [shownItemsState, setShownItemsState] = useState(shownItems);
- const [display, setDisplay] = useState([]);
- useEffect(() => {
- const toDisplay =
- players
- ?.filter(({ name }) => name !== playerName)
- ?.flatMap(({ name, guesses }) =>
- Object.entries(guesses).map(([round, { score }]) => ({
- name,
- round,
- score,
- }))
- )
- ?.filter(
- ({ name, round }) =>
- !shownItemsState.has(`${gameId}-${name}-${round}`)
- ) ?? [];
- setDisplay(toDisplay);
- const timeout = setTimeout(() => {
- toDisplay.forEach(({ name, round }) => {
- shownItems.add(`${gameId}-${name}-${round}`);
- });
- setShownItemsState(new Set(shownItems));
- }, 5000);
- return () => {
- clearTimeout(timeout);
- };
- }, [shownItemsState, gameId, players, playerName]);
- useEffect(() => {
- if (!muted) {
- display.forEach(() => {
- const audio = new Audio(hitsound);
- audio.volume = 0.5;
- // delay up to half a second so overlapping sounds better
- const delayedPlay = () =>
- setTimeout(() => audio.play(), Math.random() * 500);
- audio.addEventListener("canplaythrough", delayedPlay);
- // clean up after ourselves in the hopes that the browser actually deletes this audio element
- audio.addEventListener("ended", () =>
- audio.removeEventListener("canplaythrough", delayedPlay)
- );
- });
- }
- }, [display, muted]);
- return (
- <>
- {display.map(() => (
- <img
- alt="hitmarker"
- className={styles.hitmarker}
- style={{
- top: `${10 + Math.random() * 80}vh`,
- left: `${10 + Math.random() * 80}vw`,
- }}
- src={hitmarker}
- />
- ))}
- <div className={styles.feed}>
- {display.map(({ name, round, score }) => (
- <span className={styles.item}>
- <span className={styles.name}>{name}</span>{" "}
- {score >= 4950 ? "🎯" : "🖱️"} 🗺️ {round}
- </span>
- ))}
- </div>
- </>
- );
- };
- export default KillFeed;
|