Phasmophobia Helper

An online application tailored for players of the popular ghost hunting game, Phasmophobia. Built using TypeScript, this tool aids players in identifying different ghost types, thereby improving strategic decision-making during gameplay.
Tech stack
NextJS
TypeScript
Zustand
Details
Sole developer
A screenshot of the front page of Phasmophobia Helper

The idea

The project is an online application designed for players of the popular ghost hunting game, Phasmophobia. Its purpose is to assist players in identifying different ghost types, thus enhancing strategic decision-making during gameplay. It has more features and information than the in-game journal.

Tech stack

The application is built using Next.js and TypeScript. Instead of traditional Redux, Zustand, a lightweight state management library, is utilized for managing the application's store. Other notable features of the tech stack include local storage preferences, custom Google Analytics events, and a "limited evidence mode" useful for the game's harder game modes.

export default function usePossibleGhosts() {
  const ghosts = ghostData as Ghost[]
  const checkedEvidences = useCheckedEvidences()
  const disabledEvidences = useDisabledEvidences()

  return useMemo(() => {
    return ghosts.filter((ghost) => {
      let passed = true
      const ghostEvidences = [...ghost.evidences, ...(ghost.falseEvidences ?? [])]

      checkedEvidences.forEach((evidence) => {
        if (!arrayContains(evidence, ghostEvidences)) passed = false
      })

      disabledEvidences.forEach((evidence) => {
        if (arrayContains(evidence, ghostEvidences)) passed = false
      })

      return passed
    })
  }, [ghosts, checkedEvidences, disabledEvidences])
}
import { create } from 'zustand'
import { createEvidencesSlice, EvidencesSlice } from './evidences'
import { createGhostsSlice, GhostsSlice } from './ghosts'

export const useStore = create<GhostsSlice & EvidencesSlice>()((...a) => ({
  ...createGhostsSlice(...a),
  ...createEvidencesSlice(...a),
}))

export const useEliminatedGhosts = () => useStore((state) => state.eliminatedGhosts)
export const useSetEliminatedGhosts = () => useStore((state) => state.setEliminatedGhosts)
export const useCheckedEvidences = () => useStore((state) => state.checkedEvidences)
export const useSetCheckedEvidences = () => useStore((state) => state.setCheckedEvidences)
export const useDisabledEvidences = () => useStore((state) => state.disabledEvidences)
export const useSetDisabledEvidences = () => useStore((state) => state.setDisabledEvidences)

Obstacles

The main technical hurdle encountered was setting up and effectively utilizing the application's store. While Redux was considered, Zustand was ultimately chosen for its size, simplicity, and efficiency. The store primarily holds essential data such as checked evidence boxes, disabled evidence boxes, and manually disabled ghost types. The challenge was to ensure that the store only stored necessary information and that the UI reacted appropriately to changes in the store.

Lessons learned

The project provided valuable insights into two key areas. Firstly, working with TypeScript in a production environment proved to be beneficial, enhancing code readability, maintainability, and type safety. Secondly, the importance of storing only essential data in the application's store became evident. By minimizing the stored data to critical pieces of information and adjusting the UI accordingly, the application remained efficient and responsive.