From fc0f80cd47408e48e411e66727a7a60276e15f0a Mon Sep 17 00:00:00 2001 From: Scott Benton Date: Fri, 24 May 2024 20:28:21 -0400 Subject: [PATCH] feat(NPCs): Allow ironsworn npcs to have custom species --- CHANGELOG.MD | 1 + .../features/worlds/NPCSection/NPCSection.tsx | 4 +- .../features/worlds/NPCSection/OpenNPC.tsx | 106 +++++++++++++----- src/types/NPCs.type.ts | 4 +- 4 files changed, 84 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 194a8a39..f68ba672 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -12,6 +12,7 @@ - Re-added missing roll buttons for assets in the move dialog - Made tensions clocks available in Iron Fellowship +- You can now enter other species into the NPC species box. They will use the default Ironlander name oracles (Ironsworn) ### Bug Fixes diff --git a/src/components/features/worlds/NPCSection/NPCSection.tsx b/src/components/features/worlds/NPCSection/NPCSection.tsx index fed7acea..e9f3a325 100644 --- a/src/components/features/worlds/NPCSection/NPCSection.tsx +++ b/src/components/features/worlds/NPCSection/NPCSection.tsx @@ -17,7 +17,7 @@ import { useStore } from "stores/store"; import { useState } from "react"; import { useGameSystemValue } from "hooks/useGameSystemValue"; import { GAME_SYSTEMS } from "types/GameSystems.type"; -import { NPC, NPCSpecies } from "types/NPCs.type"; +import { DefaultNPCSpecies, NPC } from "types/NPCs.type"; export interface NPCSectionProps { isSinglePlayer?: boolean; @@ -28,7 +28,7 @@ export function NPCSection(props: NPCSectionProps) { const { isSinglePlayer, showHiddenTag } = props; const defaultNPC = useGameSystemValue>({ - [GAME_SYSTEMS.IRONSWORN]: { species: NPCSpecies.Ironlander }, + [GAME_SYSTEMS.IRONSWORN]: { species: DefaultNPCSpecies.Ironlander }, [GAME_SYSTEMS.STARFORGED]: {}, }); const searchPlaceholder = useGameSystemValue({ diff --git a/src/components/features/worlds/NPCSection/OpenNPC.tsx b/src/components/features/worlds/NPCSection/OpenNPC.tsx index 62897400..14d93afd 100644 --- a/src/components/features/worlds/NPCSection/OpenNPC.tsx +++ b/src/components/features/worlds/NPCSection/OpenNPC.tsx @@ -13,7 +13,7 @@ import { useMediaQuery, useTheme, } from "@mui/material"; -import { NPC, NPCSpecies } from "types/NPCs.type"; +import { NPC, DefaultNPCSpecies } from "types/NPCs.type"; import { DebouncedOracleInput } from "components/shared/DebouncedOracleInput"; import { useRef } from "react"; import DeleteIcon from "@mui/icons-material/Delete"; @@ -34,6 +34,60 @@ import { GAME_SYSTEMS } from "types/GameSystems.type"; import { Sector } from "types/Sector.type"; import { Difficulty } from "types/Track.type"; +const defaultNPCSpeciesOptions: { + enum: DefaultNPCSpecies; + label: string; +}[] = [ + { + enum: DefaultNPCSpecies.Ironlander, + label: "Ironlander", + }, + { + enum: DefaultNPCSpecies.Elf, + label: "Elf", + }, + { + enum: DefaultNPCSpecies.Giant, + label: "Giant", + }, + { + enum: DefaultNPCSpecies.Varou, + label: "Varou", + }, + { + enum: DefaultNPCSpecies.Troll, + label: "Troll", + }, + { + enum: DefaultNPCSpecies.Other, + label: "Other", + }, +]; + +const getSpeciesEnumFromLabel = ( + label: string | undefined | null +): DefaultNPCSpecies | string | null => { + if (!label) { + return null; + } + const matchingOption = defaultNPCSpeciesOptions.find( + (option) => option.label === label + ); + return matchingOption?.enum ?? label; +}; + +const getSpeciesLabelFromEnum = ( + enumValue: DefaultNPCSpecies | string | null | undefined +): string | null => { + if (!enumValue) { + return null; + } + const matchingOption = defaultNPCSpeciesOptions.find( + (option) => option.enum === enumValue + ); + return matchingOption?.label ?? enumValue; +}; + export interface OpenNPCProps { worldId: string; npcId: string; @@ -43,16 +97,16 @@ export interface OpenNPCProps { closeNPC: () => void; } -const nameOracles: { [key in NPCSpecies]: string | string[] } = { - [NPCSpecies.Ironlander]: [ +const nameOracles: { [key in DefaultNPCSpecies]: string | string[] } = { + [DefaultNPCSpecies.Ironlander]: [ "classic/oracles/name/ironlander/a", "classic/oracles/name/ironlander/b", ], - [NPCSpecies.Elf]: "classic/oracles/name/elf", - [NPCSpecies.Giant]: "classic/oracles/name/other/giants", - [NPCSpecies.Varou]: "classic/oracles/name/other/varou", - [NPCSpecies.Troll]: "classic/oracles/name/other/trolls", - [NPCSpecies.Other]: [ + [DefaultNPCSpecies.Elf]: "classic/oracles/name/elf", + [DefaultNPCSpecies.Giant]: "classic/oracles/name/other/giants", + [DefaultNPCSpecies.Varou]: "classic/oracles/name/other/varou", + [DefaultNPCSpecies.Troll]: "classic/oracles/name/other/trolls", + [DefaultNPCSpecies.Other]: [ "classic/oracles/name/ironlander/a", "classic/oracles/name/ironlander/b", ], @@ -164,7 +218,10 @@ export function OpenNPC(props: OpenNPCProps) { }); const npcNameOracles = useGameSystemValue({ - [GAME_SYSTEMS.IRONSWORN]: nameOracles[npc.species ?? NPCSpecies.Ironlander], + [GAME_SYSTEMS.IRONSWORN]: + npc.species && npc.species in nameOracles + ? nameOracles[npc.species as DefaultNPCSpecies] + : nameOracles[DefaultNPCSpecies.Ironlander], [GAME_SYSTEMS.STARFORGED]: [ "starforged/oracles/characters/name/given", "starforged/oracles/characters/name/family_name", @@ -347,25 +404,20 @@ export function OpenNPC(props: OpenNPCProps) { )} {!isStarforged && ( - - handleUpdateNPC({ - species: (evt.target.value ?? - NPCSpecies.Ironlander) as NPCSpecies, - }) + option.label + )} + value={getSpeciesLabelFromEnum(npc.species)} + onChange={(evt, value) => + handleUpdateNPC({ species: getSpeciesEnumFromLabel(value) }) } - fullWidth - > - Ironlander - Elf - Giant - Varou - Troll - Other - + renderInput={(params) => ( + + )} + /> )} {!isStarforged && ( diff --git a/src/types/NPCs.type.ts b/src/types/NPCs.type.ts index e85f9a65..4f5f3e11 100644 --- a/src/types/NPCs.type.ts +++ b/src/types/NPCs.type.ts @@ -1,6 +1,6 @@ import { Difficulty } from "./Track.type"; -export enum NPCSpecies { +export enum DefaultNPCSpecies { Ironlander = "ironlander", Elf = "elf", Giant = "giant", @@ -12,7 +12,7 @@ export enum NPCSpecies { export interface NPC { name: string; pronouns?: string; - species?: NPCSpecies; // Ironsworn only + species?: string | null; // Ironsworn only lastLocationId?: string; // Ironsworn only lastSectorId?: string; // Starforged only imageFilenames?: string[];