Eon Builder is a character creation tool for the Eon 5 RPG system (Swedish tabletop RPG). It provides an interactive interface for building characters by managing attributes, skills, languages, mysteries, and other character properties according to Eon 5 rules.
- Framework: React with TypeScript
- Build Tool: Vite
- Package Manager: pnpm (use
pnpmfor all package operations) - Styling: Tailwind CSS with Sass
- State Management: React Context + useReducer with immer for immutable updates
- Testing: Jest with React Testing Library
- Linting: oxlint (from oxc project)
- Formatting: oxfmt (from oxc project)
-
src/eon5-types.ts: All TypeScript type definitionsEon5CharState: Main character state interfaceEon5Action: Discriminated union of all reducer actions- Skill, Attribute, Language, Mystery types
-
src/eon5-data.ts: Static game data and constantsATTRIBUTES: List of all character attributesSKILL_STATUS_INFO: Skill status types (T=Tränad, I=Inkompetent, B=Begåvad)MAX_SKILL_VALUE: Maximum skill value constant- Attribute names and other game constants
-
src/eon5-reducer.ts: State reducer using immer'sproduce- All state updates go through this reducer
- Uses immer for clean, mutable-style updates on draft state
- Handles attribute assignment, skill updates, language/mystery management, etc.
-
src/eon5-utils.ts: Utility functionsgetChunks(): Gets attribute distribution chunks based on model- Other helper functions for state calculations
src/components/Eon5Attributes.tsx: Attribute management UIsrc/components/Eon5Skills.tsx: Skill allocation UI- Other components: Various UI components for character creation steps
The app uses React Context with useReducer + immer:
// Dispatching actions
dispatch({ type: "SET_ATTRIBUTE_BASE", attribute: "STY", value: 10 })
// In reducer (using immer)
return produce(state, (draft) => {
draft.attributes[action.attribute].base = action.value
})Important: When modifying the reducer, use direct mutations on draft objects. Immer handles immutability automatically.
- Base attributes like STY (Styrka), TÅL (Tålighet), etc.
- Distribution Models: Different ways to allocate starting attribute values
- Chunks: Pre-defined value groups that can be assigned to attributes
- Assigned Chunks: Tracks which chunk is assigned to which attribute
- Status types:
null: Normal skill (max value from constant)T: Tränad (trained) - different max valueI: Inkompetent (incompetent) - cannot use skillB: Begåvad (gifted) - special status
- Base values: Some skills can have base value of 1
- Spent units: Points allocated to improve skills
- Dynamic skills: User-added custom skills
- Always update through the reducer - never mutate state directly in components
- Use immer patterns - write code that looks like mutation on the
draftobject - Early returns in reducer cases: use
return(no value) to keep original state unchanged - Complete replacements: return the new state directly (e.g.,
LOAD_STATE)
pnpm start- Start dev serverpnpm build- TypeScript compilation + Vite buildpnpm run lint:js- Run oxlint on src/pnpm run format- Format code with oxfmtpnpm test- Run Jest tests
- Add action type to
Eon5Actionunion ineon5-types.ts - Add case handler in
eon5Reducerineon5-reducer.ts - Use immer's draft pattern for state updates
- Update components to dispatch the new action
- Add property to
Eon5CharStateineon5-types.ts - Add initialization in the initial state
- Add reducer cases for updating the property
- Create UI components as needed
- This is a Swedish RPG - many terms are in Swedish
- The codebase uses pnpm, not npm or yarn
- State management uses immer - leverage it for cleaner reducer code
- Skills have fairly complex logic around status, base values, and unit allocation