+3
-3
apps/skillulator/src/components/Skill.tsx
+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
+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
+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
+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
);