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

add disable logic when master skill is selected to master variation skills

besaid.zone dc98a4ba 93ed9c9d

verified
Changed files
+237 -223
apps
skillulator
src
routes
c
$class
zustand
+45 -32
apps/skillulator/src/routes/c/$class/components/Skill.tsx
··· 99 99 </button> 100 100 </div> 101 101 <div className="flex gap-4"> 102 - {props?.masterVariations?.map((variation) => ( 103 - <div 104 - key={variation.id} 105 - data-skill={props.skill.name} 106 - className="relative flex flex-col items-center flex-1 py-2 bg-white border border-gray-300 rounded-md basis-1/2 min-content" 107 - > 108 - <SkillIconButton 109 - locale={props.lang} 110 - skill={{ ...variation, id: props.skill.id }} 111 - variations={props?.masterVariations} 112 - masterVariationSkillId={variation?.id} 113 - jobId={props.jobId} 114 - hasMinLevelRequirements={variation?.hasMinLevelRequirements} 115 - isMaxed={variation?.isMaxed} 116 - /> 117 - <div> 118 - {variation.requirements.map((variation, index: number) => ( 119 - <Requirement 120 - key={JSON.stringify({ variation, index })} 121 - hasMinLevelRequirements={variation.hasMinLevel} 122 - skill={{ level: variation.level, name: variation.name }} 123 - /> 124 - ))} 102 + {props?.masterVariations?.map((variation, index) => { 103 + const selectedMasterVariation = props?.masterVariations?.some( 104 + (s) => s.isSelected === true, 105 + ) 106 + ? props?.masterVariations?.filter((s) => s.isSelected === true) 107 + : props.masterVariations; 108 + 109 + const isSelectedMasterVariation = 110 + !selectedMasterVariation?.every((v) => v.id === variation.id) && 111 + selectedMasterVariation?.length === 1; 112 + 113 + return ( 114 + <div 115 + key={JSON.stringify({ id: variation.id, index })} 116 + data-skill={props.skill.name} 117 + className="relative flex flex-col items-center flex-1 py-2 bg-white border border-gray-300 rounded-md basis-1/2 min-content" 118 + > 119 + <SkillIconButton 120 + locale={props.lang} 121 + skill={{ ...variation, id: props.skill.id }} 122 + masterVariationSkillId={variation?.id} 123 + isSelectedMasterVariation={isSelectedMasterVariation} 124 + jobId={props.jobId} 125 + hasMinLevelRequirements={variation?.hasMinLevelRequirements} 126 + isMaxed={variation?.isMaxed} 127 + /> 128 + <div> 129 + {variation.requirements.map((variation, index: number) => ( 130 + <Requirement 131 + key={JSON.stringify({ variation, index })} 132 + hasMinLevelRequirements={variation.hasMinLevel} 133 + skill={{ level: variation.level, name: variation.name }} 134 + /> 135 + ))} 136 + </div> 137 + {/* <DecreaseSkillPointButton {...props} /> */} 138 + <IncreaseSkillToMaxButton 139 + skillId={variation.id} 140 + jobId={props.jobId} 141 + isSelectedMasterVariation={isSelectedMasterVariation} 142 + hasMinLevelRequirements={variation?.hasMinLevelRequirements} 143 + isMaxed={variation?.isMaxed} 144 + /> 125 145 </div> 126 - {/* <DecreaseSkillPointButton {...props} /> */} 127 - <IncreaseSkillToMaxButton 128 - skillId={variation.id} 129 - jobId={props.jobId} 130 - hasMinLevelRequirements={variation?.hasMinLevelRequirements} 131 - isMaxed={variation?.isMaxed} 132 - /> 133 - </div> 134 - ))} 146 + ); 147 + })} 135 148 </div> 136 149 </dialog> 137 150 );
+14 -25
apps/skillulator/src/routes/c/$class/components/action-buttons.tsx
··· 2 2 import { useTreeStore } from "@/zustand/treeStore"; 3 3 import type { Skill } from "@/types"; 4 4 import { t } from "i18next"; 5 - import { useState } from "react"; 6 5 7 6 export function DecreaseSkillPointButton(props: { 8 7 skill: Skill; ··· 40 39 isMaxed: boolean; 41 40 jobId: number | undefined; 42 41 skillId: number; 42 + isSelectedMasterVariation?: boolean; 43 43 }) { 44 44 const { increaseSkillToMax } = useTreeStore(); 45 45 return ( 46 46 <button 47 47 type="button" 48 - disabled={!props.hasMinLevelRequirements || props.isMaxed} 48 + disabled={ 49 + !props.hasMinLevelRequirements || 50 + props.isMaxed || 51 + props.isSelectedMasterVariation 52 + } 49 53 className={clsx( 50 54 "absolute px-4 py-1 text-xs font-bold text-indigo-900 uppercase bg-indigo-100 border border-indigo-200 rounded-sm right-2 top-2 disabled:cursor-not-allowed a11y-focus", 51 - props.hasMinLevelRequirements && !props.isMaxed 55 + props.hasMinLevelRequirements && 56 + !props.isMaxed && 57 + !props.isSelectedMasterVariation 52 58 ? "grayscale-0" 53 59 : "grayscale", 54 60 )} ··· 69 75 isMaxed: boolean; 70 76 locale: string; 71 77 masterVariationSkillId?: number; 72 - variations?: Skill["masterVariations"]; 78 + isSelectedMasterVariation?: boolean; 73 79 }) { 74 80 const { decreaseSkillPoint, increaseSkillPoint } = useTreeStore(); 75 81 76 - const selectedMasterVariation = props?.variations?.some( 77 - (s) => s.isSelected === true, 78 - ) 79 - ? props?.variations?.filter((s) => s.isSelected === true) 80 - : props.variations; 81 - 82 - const isSelectedMasterVariation = 83 - !selectedMasterVariation?.every( 84 - (variation) => variation.id === props.masterVariationSkillId, 85 - ) && selectedMasterVariation?.length === 1; 86 - 87 82 return ( 88 83 <button 89 84 type="button" ··· 127 122 }); 128 123 }} 129 124 // disable also if any other master variation skill has been picked (isSelected === true) 130 - disabled={!props.hasMinLevelRequirements || isSelectedMasterVariation} 125 + disabled={ 126 + !props.hasMinLevelRequirements || props.isSelectedMasterVariation 127 + } 131 128 className="flex flex-col items-center disabled:cursor-not-allowed" 132 129 > 133 - {/* <span 134 - className={clsx( 135 - "font-bold block uppercase", 136 - props.hasMinLevelRequirements ? "text-gray-900" : "text-gray-300", 137 - )} 138 - > 139 - {props.isMaxed ? "max" : `${props.skill.skillLevel}`} 140 - </span> */} 141 130 <div className="relative"> 142 131 <img 143 132 alt="" 144 133 className={clsx( 145 134 "h-12 w-12", 146 - props.hasMinLevelRequirements && !isSelectedMasterVariation 135 + props.hasMinLevelRequirements && !props.isSelectedMasterVariation 147 136 ? "grayscale-0" 148 137 : "grayscale", 149 138 )}
+178 -166
apps/skillulator/src/zustand/treeStore.ts
··· 1 1 import { produce } from "immer"; 2 2 import { create } from "zustand"; 3 + import { immer } from "zustand/middleware/immer"; 3 4 import { tree as jobTree } from "@/data/tree"; 4 5 import { 5 6 getJobById, ··· 50 51 skillPoints: 0, 51 52 }; 52 53 53 - export const useTreeStore = create<State & Actions>()((set, get) => ({ 54 - jobTree, 55 - JOB_SKILLPOINTS, 56 - skillPoints: 0, 57 - initSkillPoints: (jobId: number, characterLevel: number) => 58 - set( 59 - produce((state: State) => { 60 - // need to figure out how many skill points are already spent and subtract it 61 - const totalSkillPoints = getJobTotalSkillPoints( 62 - state.JOB_SKILLPOINTS, 63 - jobId, 64 - characterLevel, 65 - ); 66 - 67 - const job = getJobById(jobId, state.jobTree); 68 - if (!job) return state; 69 - 70 - const spentSkillPoints = job.skills.reduce( 71 - (total, skill) => total + skill.skillLevel * skill.points, 72 - 0, 73 - ); 74 - 75 - const remainingSkillPoints = totalSkillPoints - spentSkillPoints; 76 - 77 - state.skillPoints = remainingSkillPoints; 78 - return state; 79 - }), 80 - ), 81 - increaseSkillPoint: ({ 82 - jobId, 83 - skillId, 84 - masterVariationSkillId, 85 - }: { jobId: number; skillId: number; masterVariationSkillId?: number }) => 86 - set( 87 - produce((state: State) => { 88 - const job = getJobById(jobId, state.jobTree); 89 - if (!job) return state; 90 - 91 - /** 92 - * Master variation skills don't live in the outer skill array, only within the skill they are variations of... 93 - */ 94 - const skill = getSkillById(skillId, job.skills); 95 - if ( 96 - skill?.masterVariations?.length && 97 - typeof masterVariationSkillId !== "undefined" 98 - ) { 99 - if (!masterVariationSkillId) return state; 100 - const masterVariationSkill = getSkillById( 101 - masterVariationSkillId, 102 - skill.masterVariations, 54 + export const useTreeStore = create<State & Actions>()( 55 + immer((set) => ({ 56 + jobTree, 57 + JOB_SKILLPOINTS, 58 + skillPoints: 0, 59 + initSkillPoints: (jobId: number, characterLevel: number) => 60 + set( 61 + produce((state: State) => { 62 + // need to figure out how many skill points are already spent and subtract it 63 + const totalSkillPoints = getJobTotalSkillPoints( 64 + state.JOB_SKILLPOINTS, 65 + jobId, 66 + characterLevel, 103 67 ); 104 68 105 - if (!masterVariationSkill) return state; 69 + const job = getJobById(jobId, state.jobTree); 70 + if (!job) return state; 106 71 107 - if (masterVariationSkill.skillLevel === masterVariationSkill.levels) 108 - return state; 109 - if (masterVariationSkill.points > state.skillPoints) return state; 72 + const spentSkillPoints = job.skills.reduce( 73 + (total, skill) => total + skill.skillLevel * skill.points, 74 + 0, 75 + ); 110 76 111 - masterVariationSkill.skillLevel += 1; 112 - state.skillPoints -= masterVariationSkill.points; 77 + const remainingSkillPoints = totalSkillPoints - spentSkillPoints; 113 78 114 - updateSkillRequirements( 115 - job, 116 - masterVariationSkillId, 117 - masterVariationSkill.skillLevel, 118 - ); 79 + state.skillPoints = remainingSkillPoints; 119 80 return state; 120 - } 81 + }), 82 + ), 83 + increaseSkillPoint: ({ 84 + jobId, 85 + skillId, 86 + masterVariationSkillId, 87 + }: { 88 + jobId: number; 89 + skillId: number; 90 + masterVariationSkillId?: number; 91 + }) => 92 + set( 93 + produce((state: State) => { 94 + const job = getJobById(jobId, state.jobTree); 95 + if (!job) return state; 121 96 122 - if (!skill) return state; 97 + /** 98 + * Master variation skills don't live in the outer skill array, only within the skill they are variations of... 99 + */ 100 + const skill = getSkillById(skillId, job.skills); 123 101 124 - if (skill.skillLevel === skill.levels) return state; 125 - if (skill.points > state.skillPoints) return state; 102 + if ( 103 + skill?.masterVariations?.length && 104 + typeof masterVariationSkillId !== "undefined" 105 + ) { 106 + if (!masterVariationSkillId) return state; 107 + const masterVariationSkill = getSkillById( 108 + masterVariationSkillId, 109 + skill.masterVariations, 110 + ); 126 111 127 - skill.skillLevel += 1; 128 - state.skillPoints -= skill.points; 112 + if (!masterVariationSkill) return state; 129 113 130 - updateSkillRequirements(job, skillId, skill.skillLevel); 131 - return state; 132 - }), 133 - ), 134 - decreaseSkillPoint: ({ 135 - jobId, 136 - skillId, 137 - masterVariationSkillId, 138 - }: { jobId: number; skillId: number; masterVariationSkillId?: number }) => 139 - set( 140 - produce((state: State) => { 141 - const job = getJobById(jobId, state.jobTree); 142 - if (!job) return state; 114 + if (masterVariationSkill.skillLevel === masterVariationSkill.levels) 115 + return state; 116 + if (masterVariationSkill.points > state.skillPoints) return state; 143 117 144 - const skill = getSkillById(skillId, job.skills); 145 - if ( 146 - skill?.masterVariations?.length && 147 - typeof masterVariationSkillId !== "undefined" 148 - ) { 149 - if (!masterVariationSkillId) return state; 150 - const masterVariationSkill = getSkillById( 151 - masterVariationSkillId, 152 - skill.masterVariations, 153 - ); 118 + masterVariationSkill.skillLevel += 1; 119 + state.skillPoints -= masterVariationSkill.points; 154 120 155 - if (!masterVariationSkill) return state; 156 - if (masterVariationSkill.skillLevel === 0) { 121 + updateSkillRequirements( 122 + job, 123 + masterVariationSkillId, 124 + masterVariationSkill.skillLevel, 125 + ); 157 126 return state; 158 127 } 159 128 160 - masterVariationSkill.skillLevel -= 1; 161 - state.skillPoints += masterVariationSkill.points; 129 + if (!skill) return state; 162 130 163 - updateSkillRequirements( 164 - job, 165 - masterVariationSkillId, 166 - masterVariationSkill.skillLevel, 167 - ); 168 - return state; 169 - } 131 + if (skill.skillLevel === skill.levels) return state; 132 + if (skill.points > state.skillPoints) return state; 170 133 171 - if (!skill || skill.skillLevel === 0) return state; 134 + skill.skillLevel += 1; 135 + state.skillPoints -= skill.points; 172 136 173 - skill.skillLevel -= 1; 174 - state.skillPoints += skill.points; 137 + updateSkillRequirements(job, skillId, skill.skillLevel); 138 + return state; 139 + }), 140 + ), 141 + decreaseSkillPoint: ({ 142 + jobId, 143 + skillId, 144 + masterVariationSkillId, 145 + }: { 146 + jobId: number; 147 + skillId: number; 148 + masterVariationSkillId?: number; 149 + }) => 150 + set( 151 + produce((state: State) => { 152 + const job = getJobById(jobId, state.jobTree); 153 + if (!job) return state; 175 154 176 - updateSkillRequirements(job, skillId, skill.skillLevel); 177 - return state; 178 - }), 179 - ), 180 - createPreloadedSkillTree: ( 181 - jobId: number, 182 - predefinedSkills: Array<Record<string, number>>, 183 - ) => 184 - set( 185 - produce((state: State) => { 186 - // we map over the pre-defined skills 187 - // we check the skill id and level and update the skillLevel accordingly 188 - // we also need to do the check to see if the skill is the min level 189 - const job = getJobById(jobId, state.jobTree); 190 - if (!job) return state; 155 + const skill = getSkillById(skillId, job.skills); 156 + if ( 157 + skill?.masterVariations?.length && 158 + typeof masterVariationSkillId !== "undefined" 159 + ) { 160 + if (!masterVariationSkillId) return state; 161 + const masterVariationSkill = getSkillById( 162 + masterVariationSkillId, 163 + skill.masterVariations, 164 + ); 191 165 192 - for (const originalTreeSkill of job.skills) { 193 - for (const predefinedTreeSkill of predefinedSkills) { 194 - if (originalTreeSkill.id === predefinedTreeSkill.skill) { 195 - originalTreeSkill.skillLevel = predefinedTreeSkill.level; 166 + if (!masterVariationSkill) return state; 167 + if (masterVariationSkill.skillLevel === 0) { 168 + return state; 196 169 } 197 - } 170 + 171 + masterVariationSkill.skillLevel -= 1; 172 + state.skillPoints += masterVariationSkill.points; 198 173 199 - const skill = getSkillById(originalTreeSkill.id, job.skills); 200 - if (skill) { 201 174 updateSkillRequirements( 202 175 job, 203 - originalTreeSkill.id, 204 - skill.skillLevel, 176 + masterVariationSkillId, 177 + masterVariationSkill.skillLevel, 205 178 ); 179 + return state; 206 180 } 207 - } 208 - return state; 209 - }), 210 - ), 211 - increaseSkillToMax: (skillId: number, jobId: number) => 212 - set( 213 - produce((state: State) => { 214 - const job = getJobById(jobId, state.jobTree); 215 - if (!job) return state; 216 181 217 - const skill = getSkillById(skillId, job.skills); 218 - if (!skill) return state; 182 + if (!skill || skill.skillLevel === 0) return state; 219 183 220 - if (skill.levels === skill.skillLevel) return state; 184 + skill.skillLevel -= 1; 185 + state.skillPoints += skill.points; 221 186 222 - const maxPossibleLevel = Math.min( 223 - skill.levels, 224 - Math.floor(state.skillPoints / skill.points), 225 - ); 187 + updateSkillRequirements(job, skillId, skill.skillLevel); 188 + return state; 189 + }), 190 + ), 191 + createPreloadedSkillTree: ( 192 + jobId: number, 193 + predefinedSkills: Array<Record<string, number>>, 194 + ) => 195 + set( 196 + produce((state: State) => { 197 + // we map over the pre-defined skills 198 + // we check the skill id and level and update the skillLevel accordingly 199 + // we also need to do the check to see if the skill is the min level 200 + const job = getJobById(jobId, state.jobTree); 201 + if (!job) return state; 226 202 227 - if (maxPossibleLevel > skill.skillLevel) { 228 - const levelsToAdd = maxPossibleLevel - skill.skillLevel; 229 - skill.skillLevel = maxPossibleLevel; 230 - state.skillPoints -= levelsToAdd * skill.points; 231 - } 203 + for (const originalTreeSkill of job.skills) { 204 + for (const predefinedTreeSkill of predefinedSkills) { 205 + if (originalTreeSkill.id === predefinedTreeSkill.skill) { 206 + originalTreeSkill.skillLevel = predefinedTreeSkill.level; 207 + } 208 + } 232 209 233 - updateSkillRequirements(job, skillId, skill.skillLevel); 234 - return state; 235 - }), 236 - ), 237 - resetSkillTree: (jobId: number) => 238 - set((state: State) => { 239 - const skillPoints = getJobTotalSkillPoints( 240 - state.JOB_SKILLPOINTS, 241 - jobId, 242 - 15, 243 - ); 210 + const skill = getSkillById(originalTreeSkill.id, job.skills); 211 + if (skill) { 212 + updateSkillRequirements( 213 + job, 214 + originalTreeSkill.id, 215 + skill.skillLevel, 216 + ); 217 + } 218 + } 219 + return state; 220 + }), 221 + ), 222 + increaseSkillToMax: (skillId: number, jobId: number) => 223 + set( 224 + produce((state: State) => { 225 + const job = getJobById(jobId, state.jobTree); 226 + if (!job) return state; 244 227 245 - return { 246 - ...initialState, 247 - skillPoints, 248 - }; 249 - }), 250 - })); 228 + const skill = getSkillById(skillId, job.skills); 229 + if (!skill) return state; 230 + 231 + if (skill.levels === skill.skillLevel) return state; 232 + 233 + const maxPossibleLevel = Math.min( 234 + skill.levels, 235 + Math.floor(state.skillPoints / skill.points), 236 + ); 237 + 238 + if (maxPossibleLevel > skill.skillLevel) { 239 + const levelsToAdd = maxPossibleLevel - skill.skillLevel; 240 + skill.skillLevel = maxPossibleLevel; 241 + state.skillPoints -= levelsToAdd * skill.points; 242 + } 243 + 244 + updateSkillRequirements(job, skillId, skill.skillLevel); 245 + return state; 246 + }), 247 + ), 248 + resetSkillTree: (jobId: number) => 249 + set((state: State) => { 250 + const skillPoints = getJobTotalSkillPoints( 251 + state.JOB_SKILLPOINTS, 252 + jobId, 253 + 15, 254 + ); 255 + 256 + return { 257 + ...initialState, 258 + skillPoints, 259 + }; 260 + }), 261 + })), 262 + );