a collection of tools for fly for fun universe skillulator.lol

some more minor refactors and suggested changes

besaid.zone d968367e 66bea96c

verified
Changed files
+135 -149
apps
skillulator
+30 -30
apps/skillulator/src/components/Navbar.tsx
··· 1 - import { ChangeEvent, useState } from "react"; 1 + import type { ChangeEvent } from "react"; 2 2 import { useTranslation } from "react-i18next"; 3 - import { languages } from "../utils"; 3 + import { languages } from "../utils/constants"; 4 4 5 5 export function Navbar() { 6 - const { i18n } = useTranslation(); 6 + const { i18n } = useTranslation(); 7 7 8 - const preferredLanguage = i18n.language; 8 + const preferredLanguage = i18n.language; 9 9 10 - const handleLanguageChange = (event: ChangeEvent<HTMLSelectElement>) => { 11 - const lang = event.target.value; 12 - i18n.changeLanguage(lang); 13 - }; 10 + const handleLanguageChange = (event: ChangeEvent<HTMLSelectElement>) => { 11 + const lang = event.target.value; 12 + i18n.changeLanguage(lang); 13 + }; 14 14 15 - return ( 16 - <header className="py-4"> 17 - <nav className="flex px-5"> 18 - <label htmlFor="language" className="sr-only"> 19 - Select a language 20 - </label> 21 - <select 22 - name="language" 23 - defaultValue={preferredLanguage} 24 - onChange={handleLanguageChange} 25 - id="language" 26 - className="rounded-md border border-gray-300 px-2 py-1.5 shadow-sm ml-auto w-[100px] absolute right-20 a11y-focus" 27 - > 28 - {languages.map((lang, index) => ( 29 - <option value={lang.label} key={JSON.stringify({ lang, index })}> 30 - {lang.label.toUpperCase()} 31 - </option> 32 - ))} 33 - </select> 34 - </nav> 35 - </header> 36 - ); 15 + return ( 16 + <header className="py-4"> 17 + <nav className="flex px-5"> 18 + <label htmlFor="language" className="sr-only"> 19 + Select a language 20 + </label> 21 + <select 22 + name="language" 23 + defaultValue={preferredLanguage} 24 + onChange={handleLanguageChange} 25 + id="language" 26 + className="rounded-md border border-gray-300 px-2 py-1.5 shadow-sm ml-auto w-[100px] absolute right-20 a11y-focus" 27 + > 28 + {languages.map((lang, index) => ( 29 + <option value={lang.label} key={JSON.stringify({ lang, index })}> 30 + {lang.label.toUpperCase()} 31 + </option> 32 + ))} 33 + </select> 34 + </nav> 35 + </header> 36 + ); 37 37 }
+3 -3
apps/skillulator/src/components/Skill.tsx
··· 1 - import { getLanguageForSkill } from "../utils"; 2 - import { useTreeStore, State } from "../zustand/treeStore"; 1 + import { getLanguageForSkill } from "../utils/language"; 2 + import { useTreeStore, type State } from "../zustand/treeStore"; 3 3 import clsx from "clsx"; 4 - import { languages } from "../utils/index"; 4 + import { languages } from "../utils/constants"; 5 5 6 6 interface SkillProps { 7 7 skill: State["jobTree"][0]["skills"][0];
+2 -1
apps/skillulator/src/routes/index.lazy.tsx
··· 19 19 {JOBS.map((job) => ( 20 20 <Link 21 21 aria-label={`Go to the ${job.name} skill tree`} 22 - to={`/c/${job.name}`} 22 + params={{ class: job.name }} 23 + to="/c/$class" 23 24 key={job.name} 24 25 className="flex flex-col items-center justify-center px-1 py-2 duration-150 bg-white border border-gray-300 rounded-md hover:bg-gray-100 lg:px-5 a11y-focus" 25 26 >
+34
apps/skillulator/src/utils/skill-tree-helpers.ts
··· 16 16 return skills.filter((skill) => skill.id === skillId).at(0); 17 17 } 18 18 19 + export interface SkillRequirement { 20 + skill: number; 21 + level: number; 22 + hasMinLevel: boolean; 23 + } 24 + 25 + export function updateSkillRequirements( 26 + job: Jobs[0], 27 + skillId: number, 28 + newSkillLevel: number, 29 + ): void { 30 + if (!job) return; 31 + 32 + for (const skill of job.skills) { 33 + const requirement = skill.requirements.find((req) => req.skill === skillId); 34 + if (!requirement) continue; 35 + 36 + const requirementIndex = skill.requirements.findIndex( 37 + (req) => req.skill === skillId, 38 + ); 39 + 40 + const meetsRequirement = newSkillLevel >= requirement.level; 41 + skill.requirements[requirementIndex].hasMinLevel = meetsRequirement; 42 + } 43 + } 44 + 45 + export function checkSkillRequirements(skill: Skills[0]): boolean { 46 + return skill.requirements.every((req) => req.hasMinLevel); 47 + } 48 + 49 + export function isSkillMaxed(skill: Skills[0]): boolean { 50 + return skill.skillLevel === skill.levels; 51 + } 52 + 19 53 export function getJobByName(jobName: string, jobs: Jobs) { 20 54 return jobs.find( 21 55 (job) => job.name.en.toLowerCase() === jobName.toLowerCase(),
+66 -115
apps/skillulator/src/zustand/treeStore.ts
··· 2 2 import { create } from "zustand"; 3 3 import { tree as jobTree } from "../../data/tree"; 4 4 import { 5 - classSkillPoints, 6 5 getJobById, 7 6 getJobTotalSkillPoints, 8 7 getSkillById, 9 - } from "../utils"; 8 + updateSkillRequirements, 9 + } from "../utils/skill-tree-helpers"; 10 + import { JOB_SKILLPOINTS } from "../utils/constants"; 10 11 11 12 export type State = { 12 13 jobTree: typeof jobTree; 13 14 skillPoints: number; 14 - classSkillPoints: typeof classSkillPoints; 15 + JOB_SKILLPOINTS: typeof JOB_SKILLPOINTS; 15 16 }; 16 17 17 18 type Actions = { ··· 29 30 30 31 const initialState: State = { 31 32 jobTree, 32 - classSkillPoints, 33 + JOB_SKILLPOINTS, 33 34 skillPoints: 0, 34 35 }; 35 36 36 37 export const useTreeStore = create<State & Actions>()((set, _) => ({ 37 38 jobTree, 38 - classSkillPoints, 39 + JOB_SKILLPOINTS, 39 40 skillPoints: 0, 40 41 initSkillPoints: (jobId: number, characterLevel: number) => 41 42 set( 42 43 produce((state: State) => { 43 44 // need to figure out how many skill points are already spent and subtract it 44 - const skillPoints = getJobTotalSkillPoints( 45 - state.classSkillPoints, 45 + const totalSkillPoints = getJobTotalSkillPoints( 46 + state.JOB_SKILLPOINTS, 46 47 jobId, 47 48 characterLevel, 48 49 ); 49 50 50 - let skillPointsToSubtract = 0; 51 51 const job = getJobById(jobId, state.jobTree); 52 + if (!job) return state; 52 53 53 - if (job) { 54 - for (const skill of job.skills) { 55 - skillPointsToSubtract += skill.skillLevel * skill.points; 56 - } 57 - } 54 + const spentSkillPoints = job.skills.reduce( 55 + (total, skill) => total + skill.skillLevel * skill.points, 56 + 0, 57 + ); 58 58 59 - const remainingSkillPoints = skillPoints - skillPointsToSubtract; 59 + const remainingSkillPoints = totalSkillPoints - spentSkillPoints; 60 60 61 61 state.skillPoints = remainingSkillPoints; 62 62 return state; ··· 66 66 set( 67 67 produce((state: State) => { 68 68 const job = getJobById(jobId, state.jobTree); 69 - if (job) { 70 - const skill = getSkillById(skillId, job?.skills); 71 - if (skill) { 72 - if (skill.skillLevel === skill.levels) return state; 73 - if (skill.points > state.skillPoints) return state; 74 - skill.skillLevel += 1; 75 - state.skillPoints -= skill.points; 76 - } 69 + if (!job) return state; 70 + 71 + const skill = getSkillById(skillId, job.skills); 72 + if (!skill) return state; 73 + 74 + if (skill.skillLevel === skill.levels) return state; 75 + if (skill.points > state.skillPoints) return state; 77 76 78 - // find all required skills 79 - // if it's the min level, switch hasMinLevel to true 80 - for (const skill of job.skills) { 81 - const requiredSkill = skill.requirements.find( 82 - (required) => required.skill === skillId, 83 - ); 84 - const requiredSkillIndex = skill.requirements.findIndex( 85 - (required) => required.skill === skillId, 86 - ); 87 - if ( 88 - typeof requiredSkill !== "undefined" && 89 - requiredSkill.level === skill.skillLevel 90 - ) { 91 - skill.requirements[requiredSkillIndex].hasMinLevel = true; 92 - } 93 - } 94 - } 77 + skill.skillLevel += 1; 78 + state.skillPoints -= skill.points; 95 79 80 + updateSkillRequirements(job, skillId, skill.skillLevel); 96 81 return state; 97 82 }), 98 83 ), ··· 100 85 set( 101 86 produce((state: State) => { 102 87 const job = getJobById(jobId, state.jobTree); 103 - if (job) { 104 - const skill = getSkillById(skillId, job.skills); 105 - if (skill) { 106 - if (skill.skillLevel === 0) return state; 107 - skill.skillLevel -= 1; 108 - state.skillPoints += skill.points; 109 - } 88 + if (!job) return state; 89 + 90 + const skill = getSkillById(skillId, job.skills); 91 + if (!skill || skill.skillLevel === 0) return state; 92 + 93 + skill.skillLevel -= 1; 94 + state.skillPoints += skill.points; 110 95 111 - // find all required skills 112 - // if the skillLevel is less than the required skills required level switch to false 113 - for (const skill of job.skills) { 114 - const requiredSkill = skill.requirements.find( 115 - (required) => required.skill === skillId, 116 - ); 117 - const requiredSkillIndex = skill.requirements.findIndex( 118 - (required) => required.skill === skillId, 119 - ); 120 - if ( 121 - typeof requiredSkill !== "undefined" && 122 - skill.skillLevel < requiredSkill.level 123 - ) { 124 - skill.requirements[requiredSkillIndex].hasMinLevel = false; 125 - } 126 - } 127 - } 96 + updateSkillRequirements(job, skillId, skill.skillLevel); 97 + return state; 128 98 }), 129 99 ), 130 100 createPreloadedSkillTree: ( ··· 137 107 // we check the skill id and level and update the skillLevel accordingly 138 108 // we also need to do the check to see if the skill is the min level 139 109 const job = getJobById(jobId, state.jobTree); 140 - if (job) { 141 - for (const originalTreeSkill of job.skills) { 142 - for (const preloadedSkill of predefinedSkills) { 143 - if (originalTreeSkill.id === preloadedSkill.skill) { 144 - originalTreeSkill.skillLevel = preloadedSkill.level; 145 - } 110 + if (!job) return state; 111 + 112 + for (const originalTreeSkill of job.skills) { 113 + for (const predefinedTreeSkill of predefinedSkills) { 114 + if (originalTreeSkill.id === predefinedTreeSkill.skill) { 115 + originalTreeSkill.skillLevel = predefinedTreeSkill.level; 146 116 } 147 - const skill = getSkillById(originalTreeSkill.id, job.skills); 117 + } 148 118 149 - if (skill) { 150 - for (const s of job.skills) { 151 - const requiredSkill = s.requirements.find( 152 - (required) => required.skill === originalTreeSkill.id, 153 - ); 154 - const requiredSkillIndex = s.requirements.findIndex( 155 - (required) => required.skill === originalTreeSkill.id, 156 - ); 157 - if ( 158 - typeof requiredSkill !== "undefined" && 159 - requiredSkill.level <= skill.skillLevel 160 - ) { 161 - s.requirements[requiredSkillIndex].hasMinLevel = true; 162 - } 163 - } 164 - } 119 + const skill = getSkillById(originalTreeSkill.id, job.skills); 120 + if (skill) { 121 + updateSkillRequirements( 122 + job, 123 + originalTreeSkill.id, 124 + skill.skillLevel, 125 + ); 165 126 } 166 127 } 128 + return state; 167 129 }), 168 130 ), 169 131 increaseSkillToMax: (skillId: number, jobId: number) => 170 132 set( 171 133 produce((state: State) => { 172 134 const job = getJobById(jobId, state.jobTree); 173 - if (job) { 174 - const skill = getSkillById(skillId, job.skills); 135 + if (!job) return state; 175 136 176 - if (skill) { 177 - if (skill.levels === skill.skillLevel) return state; 178 - if (skill.levels * skill.skillLevel < state.skillPoints) { 179 - skill.skillLevel = 180 - state.skillPoints / skill.points > skill.levels 181 - ? skill.levels 182 - : Math.floor(+(state.skillPoints / skill.points)); 183 - state.skillPoints -= skill.skillLevel * skill.points; 184 - } 185 - } 137 + const skill = getSkillById(skillId, job.skills); 138 + if (!skill) return state; 139 + 140 + if (skill.levels === skill.skillLevel) return state; 141 + 142 + const maxPossibleLevel = Math.min( 143 + skill.levels, 144 + Math.floor(state.skillPoints / skill.points), 145 + ); 186 146 187 - // refactor this block of code 188 - for (const s of job.skills) { 189 - const requiredSkill = s.requirements.find( 190 - (required) => required.skill === skillId, 191 - ); 192 - const requiredSkillIndex = s.requirements.findIndex( 193 - (required) => required.skill === skillId, 194 - ); 195 - if ( 196 - typeof requiredSkill !== "undefined" && 197 - (requiredSkill.level < s.skillLevel || 198 - requiredSkill.level === s.skillLevel) 199 - ) { 200 - s.requirements[requiredSkillIndex].hasMinLevel = true; 201 - } 202 - } 147 + if (maxPossibleLevel > skill.skillLevel) { 148 + const levelsToAdd = maxPossibleLevel - skill.skillLevel; 149 + skill.skillLevel = maxPossibleLevel; 150 + state.skillPoints -= levelsToAdd * skill.points; 203 151 } 152 + 153 + updateSkillRequirements(job, skillId, skill.skillLevel); 154 + return state; 204 155 }), 205 156 ), 206 157 resetSkillTree: (jobId: number) => 207 158 set((state: State) => { 208 159 const skillPoints = getJobTotalSkillPoints( 209 - state.classSkillPoints, 160 + state.JOB_SKILLPOINTS, 210 161 jobId, 211 162 15, 212 163 );