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

file reorg and cleanup

besaid.zone 66bea96c 0bd99d32

verified
-34
apps/skillulator/src/contstants.ts
··· 1 - export const JOBS = [ 2 - { 3 - name: "blade", 4 - image: "blade.png", 5 - }, 6 - { 7 - name: "knight", 8 - image: "knight.png", 9 - }, 10 - { 11 - name: "elementor", 12 - image: "elementor.png", 13 - }, 14 - { 15 - name: "psykeeper", 16 - image: "psychikeeper.png", 17 - }, 18 - { 19 - name: "billposter", 20 - image: "billposter.png", 21 - }, 22 - { 23 - name: "ringmaster", 24 - image: "ringmaster.png", 25 - }, 26 - { 27 - name: "ranger", 28 - image: "ranger.png", 29 - }, 30 - { 31 - name: "jester", 32 - image: "jester.png", 33 - }, 34 - ];
+17 -17
apps/skillulator/src/i18n.ts
··· 3 3 4 4 import Backend from "i18next-http-backend"; 5 5 import LanguageDetector from "i18next-browser-languagedetector"; 6 - import { languages } from "./utils"; 6 + import { languages } from "./utils/constants"; 7 7 8 8 i18n 9 - .use(Backend) 10 - .use(LanguageDetector) 11 - .use(initReactI18next) 12 - .init({ 13 - fallbackLng: "en", 14 - lng: "en", 15 - debug: false, 16 - interpolation: { 17 - escapeValue: false, 18 - }, 19 - }); 9 + .use(Backend) 10 + .use(LanguageDetector) 11 + .use(initReactI18next) 12 + .init({ 13 + fallbackLng: "en", 14 + lng: "en", 15 + debug: false, 16 + interpolation: { 17 + escapeValue: false, 18 + }, 19 + }); 20 20 21 21 i18n.on("languageChanged", (lng) => { 22 - const htmlLang = languages.find((lang) => lang.label === lng); 23 - document.documentElement.setAttribute( 24 - "lang", 25 - htmlLang?.locale ? htmlLang.locale : htmlLang!.label 26 - ); 22 + const htmlLang = languages.find((lang) => lang.label === lng); 23 + document.documentElement.setAttribute( 24 + "lang", 25 + htmlLang?.locale ? htmlLang.locale : "en", 26 + ); 27 27 }); 28 28 29 29 export default i18n;
+8 -8
apps/skillulator/src/routes/__root.tsx
··· 1 - import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; 1 + import { createRootRoute, Outlet } from "@tanstack/react-router"; 2 2 import { Navbar } from "../components/Navbar"; 3 3 import { Footer } from "../components/Footer"; 4 4 5 5 export const Route = createRootRoute({ 6 - component: () => ( 7 - <> 8 - <Navbar /> 9 - <Outlet /> 10 - <Footer /> 11 - </> 12 - ), 6 + component: () => ( 7 + <> 8 + <Navbar /> 9 + <Outlet /> 10 + <Footer /> 11 + </> 12 + ), 13 13 });
+5 -1
apps/skillulator/src/routes/c.$class.tsx
··· 11 11 import { useTranslation } from "react-i18next"; 12 12 import { Link, useNavigate, useParams } from "@tanstack/react-router"; 13 13 import Skill from "../components/Skill"; 14 - import { decodeTree, encodeTree, getJobByName } from "../utils/index"; 14 + import { 15 + decodeTree, 16 + encodeTree, 17 + getJobByName, 18 + } from "../utils/skill-tree-helpers"; 15 19 import { useTreeStore } from "../zustand/treeStore"; 16 20 import { t } from "i18next"; 17 21
+36 -35
apps/skillulator/src/routes/index.lazy.tsx
··· 1 1 import { Suspense } from "react"; 2 2 import { useTranslation } from "react-i18next"; 3 - import { JOBS } from "../contstants"; 3 + import { JOBS } from "../utils/constants"; 4 4 import { createLazyFileRoute, Link } from "@tanstack/react-router"; 5 5 6 6 export const Route = createLazyFileRoute("/")({ 7 - component: Index, 7 + component: Index, 8 8 }); 9 9 10 10 function Index() { 11 - const { t } = useTranslation(); 11 + const { t } = useTranslation(); 12 12 13 - return ( 14 - <> 15 - <Suspense fallback="loading..."> 16 - <main className="flex flex-col items-center justify-center px-3 pt-32 pb-3"> 17 - <h1 className="mb-4 text-3xl font-bold">Skillulator</h1> 18 - <div className="grid w-full grid-cols-2 gap-2 mb-4 lg:w-max lg:grid-cols-4"> 19 - {JOBS.map((job) => ( 20 - <Link 21 - aria-label={`Go to the ${job.name} skill tree`} 22 - to={`/c/${job.name}`} 23 - key={job.name} 24 - 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 - <img 27 - src={`https://skillulator.lol/icons/classes/${job.image}`} 28 - className="w-10 h-10 md:h-12 md:w-12" 29 - /> 30 - <span className="capitalize">{job.name}</span> 31 - </Link> 32 - ))} 33 - </div> 34 - <h2 className="text-lg font-bold">{t("secondaryTitle")}</h2> 35 - <ul className="text-sm list-disc list-inside"> 36 - <li>{t("appInstructions.inst1")}</li> 37 - <li>{t("appInstructions.inst2")}</li> 38 - <li>{t("appInstructions.inst3")}</li> 39 - <li>{t("appInstructions.inst4")}</li> 40 - </ul> 41 - </main> 42 - </Suspense> 43 - </> 44 - ); 13 + return ( 14 + <> 15 + <Suspense fallback="loading..."> 16 + <main className="flex flex-col items-center justify-center px-3 pt-32 pb-3"> 17 + <h1 className="mb-4 text-3xl font-bold">Skillulator</h1> 18 + <div className="grid w-full grid-cols-2 gap-2 mb-4 lg:w-max lg:grid-cols-4"> 19 + {JOBS.map((job) => ( 20 + <Link 21 + aria-label={`Go to the ${job.name} skill tree`} 22 + to={`/c/${job.name}`} 23 + key={job.name} 24 + 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 + <img 27 + alt="" 28 + src={`https://skillulator.lol/icons/classes/${job.image}`} 29 + className="w-10 h-10 md:h-12 md:w-12" 30 + /> 31 + <span className="capitalize">{job.name}</span> 32 + </Link> 33 + ))} 34 + </div> 35 + <h2 className="text-lg font-bold">{t("secondaryTitle")}</h2> 36 + <ul className="text-sm list-disc list-inside"> 37 + <li>{t("appInstructions.inst1")}</li> 38 + <li>{t("appInstructions.inst2")}</li> 39 + <li>{t("appInstructions.inst3")}</li> 40 + <li>{t("appInstructions.inst4")}</li> 41 + </ul> 42 + </main> 43 + </Suspense> 44 + </> 45 + ); 45 46 }
+147
apps/skillulator/src/utils/constants.ts
··· 1 + export const JOB_SKILLPOINTS = { 2 + // elementor 3 + 9150: { 4 + firstJobSP: 90, 5 + secondJobSP: 300, 6 + }, 7 + //psykeeper 8 + 5709: { 9 + firstJobSP: 90, 10 + secondJobSP: 90, 11 + }, 12 + // blade 13 + 2246: { 14 + firstJobSP: 60, 15 + secondJobSP: 80, 16 + }, 17 + // knight 18 + 5330: { 19 + firstJobSP: 60, 20 + secondJobSP: 80, 21 + }, 22 + // billposter 23 + 7424: { 24 + firstJobSP: 60, 25 + secondJobSP: 120, 26 + }, 27 + // ringmaster 28 + 9389: { 29 + firstJobSP: 60, 30 + secondJobSP: 100, 31 + }, 32 + // ranger 33 + 9295: { 34 + firstJobSP: 50, 35 + secondJobSP: 100, 36 + }, 37 + // jester 38 + 3545: { 39 + firstJobSP: 50, 40 + secondJobSP: 100, 41 + }, 42 + }; 43 + 44 + export const languages = [ 45 + { 46 + label: "en", 47 + value: "en", 48 + language: "English", 49 + }, 50 + { 51 + label: "pt-BR", 52 + value: "br", 53 + locale: "pt-BR", 54 + language: "Português", 55 + }, 56 + { 57 + label: "zh", 58 + value: "cns", 59 + locale: "zh-CN", 60 + language: "Chinese", 61 + }, 62 + { 63 + label: "ja", 64 + value: "jp", 65 + language: "Japanese", 66 + }, 67 + { 68 + label: "ko", 69 + value: "kr", 70 + language: "Korean", 71 + }, 72 + { 73 + label: "es", 74 + value: "sp", 75 + language: "Spanish", 76 + }, 77 + { 78 + label: "ru", 79 + value: "ru", 80 + language: "Russian", 81 + }, 82 + { 83 + label: "de", 84 + value: "de", 85 + language: "German", 86 + }, 87 + { 88 + label: "fi", 89 + value: "fi", 90 + language: "Finnish", 91 + }, 92 + { 93 + label: "id", 94 + value: "id", 95 + language: "Indonesian", 96 + }, 97 + { 98 + label: "it", 99 + value: "it", 100 + language: "Italian", 101 + }, 102 + { 103 + label: "nl", 104 + value: "nl", 105 + language: "Dutch", 106 + }, 107 + { 108 + label: "pl", 109 + value: "pl", 110 + language: "Polish", 111 + }, 112 + ]; 113 + 114 + export const JOBS = [ 115 + { 116 + name: "blade", 117 + image: "blade.png", 118 + }, 119 + { 120 + name: "knight", 121 + image: "knight.png", 122 + }, 123 + { 124 + name: "elementor", 125 + image: "elementor.png", 126 + }, 127 + { 128 + name: "psykeeper", 129 + image: "psychikeeper.png", 130 + }, 131 + { 132 + name: "billposter", 133 + image: "billposter.png", 134 + }, 135 + { 136 + name: "ringmaster", 137 + image: "ringmaster.png", 138 + }, 139 + { 140 + name: "ranger", 141 + image: "ranger.png", 142 + }, 143 + { 144 + name: "jester", 145 + image: "jester.png", 146 + }, 147 + ];
-244
apps/skillulator/src/utils/index.ts
··· 1 - import type { State } from "../zustand/treeStore"; 2 - 3 - export function getJobById(jobId: number, jobs: State["jobTree"]) { 4 - return jobs.filter((job) => job.id === jobId).at(0); 5 - } 6 - 7 - export function getSkillById( 8 - skillId: number, 9 - skills: State["jobTree"][number]["skills"], 10 - ) { 11 - return skills.find((skill) => skill.id === skillId); 12 - } 13 - 14 - export function getJobByName(jobName: string, jobs: State["jobTree"]) { 15 - return jobs.find( 16 - (job) => job.name.en.toLowerCase() === jobName.toLowerCase(), 17 - ); 18 - } 19 - 20 - export function encodeTree( 21 - skills: State["jobTree"][number]["skills"], 22 - characterLevel: number, 23 - ) { 24 - return ( 25 - skills?.map((skill) => `${skill.id}:${skill.skillLevel}`).join(",") + 26 - `#${characterLevel}` 27 - ); 28 - } 29 - 30 - export function decodeTree(encodedSkills: string) { 31 - const characterLevel = encodedSkills.split("#").at(1); 32 - const decodedTree = encodedSkills.split("#").at(0); 33 - return { 34 - untangledSkillMap: decodedTree! 35 - .split(",") 36 - .map((skill) => skill.split(":")) 37 - .map((s) => ({ skill: +s[0], level: +s[1] })), 38 - characterLevel, 39 - }; 40 - } 41 - 42 - export function getSkillPointsForLevel(characterLevel: number) { 43 - switch (true) { 44 - case characterLevel >= 15 && characterLevel <= 20: 45 - return characterLevel * 2; 46 - case characterLevel > 20 && characterLevel <= 40: 47 - return (characterLevel - 20) * 3 + 20 * 2; 48 - case characterLevel > 40 && characterLevel <= 60: 49 - return (characterLevel - 40) * 4 + 20 * 3 + 20 * 2; 50 - case characterLevel > 60 && characterLevel <= 80: 51 - return (characterLevel - 60) * 5 + 20 * 4 + 20 * 3 + 20 * 2; 52 - case characterLevel > 80 && characterLevel <= 100: 53 - return (characterLevel - 80) * 6 + 20 * 5 + 20 * 4 + 20 * 3 + 20 * 2; 54 - case characterLevel > 100 && characterLevel <= 120: 55 - return ( 56 - (characterLevel - 100) * 7 + 20 * 6 + 20 * 5 + 20 * 4 + 20 * 3 + 20 * 2 57 - ); 58 - case characterLevel > 120 && characterLevel <= 140: 59 - return ( 60 - (characterLevel - 120) * 8 + 61 - 20 * 7 + 62 - 20 * 6 + 63 - 20 * 5 + 64 - 20 * 4 + 65 - 20 * 3 + 66 - 20 * 2 67 - ); 68 - case characterLevel > 140 && characterLevel <= 150: 69 - return ( 70 - (characterLevel - 140) * 1 + 71 - 20 * 8 + 72 - 20 * 7 + 73 - 20 * 6 + 74 - 20 * 5 + 75 - 20 * 4 + 76 - 20 * 3 + 77 - 20 * 2 78 - ); 79 - case characterLevel > 150 && characterLevel <= 160: 80 - return ( 81 - (characterLevel - 150) * 2 + 82 - 20 * 1 + 83 - 20 * 8 + 84 - 20 * 7 + 85 - 20 * 6 + 86 - 20 * 5 + 87 - 20 * 4 + 88 - 20 * 3 + 89 - 20 * 2 90 - ); 91 - case characterLevel > 160 && characterLevel <= 165: 92 - return ( 93 - (characterLevel - 160) * 2 + 94 - 20 * 1 + 95 - 20 * 8 + 96 - 20 * 7 + 97 - 20 * 6 + 98 - 20 * 5 + 99 - 20 * 4 + 100 - 20 * 3 + 101 - 20 * 2 + 102 - 20 * 2 103 - ); 104 - default: 105 - return 0; 106 - } 107 - } 108 - 109 - // eh this could be named better lol 110 - export const classSkillPoints = { 111 - // elementor 112 - 9150: { 113 - firstJobSP: 90, 114 - secondJobSP: 300, 115 - }, 116 - //psykeeper 117 - 5709: { 118 - firstJobSP: 90, 119 - secondJobSP: 90, 120 - }, 121 - // blade 122 - 2246: { 123 - firstJobSP: 60, 124 - secondJobSP: 80, 125 - }, 126 - // knight 127 - 5330: { 128 - firstJobSP: 60, 129 - secondJobSP: 80, 130 - }, 131 - // billposter 132 - 7424: { 133 - firstJobSP: 60, 134 - secondJobSP: 120, 135 - }, 136 - // ringmaster 137 - 9389: { 138 - firstJobSP: 60, 139 - secondJobSP: 100, 140 - }, 141 - // ranger 142 - 9295: { 143 - firstJobSP: 50, 144 - secondJobSP: 100, 145 - }, 146 - // jester 147 - 3545: { 148 - firstJobSP: 50, 149 - secondJobSP: 100, 150 - }, 151 - }; 152 - 153 - export function getJobTotalSkillPoints( 154 - jobMap: typeof classSkillPoints, 155 - jobId: number, 156 - characterLevel: number, 157 - ) { 158 - if (characterLevel >= 60) { 159 - return ( 160 - getSkillPointsForLevel(characterLevel) + 161 - jobMap[jobId].firstJobSP + 162 - jobMap[jobId].secondJobSP 163 - ); 164 - } 165 - 166 - return getSkillPointsForLevel(characterLevel) + jobMap[jobId].firstJobSP; 167 - } 168 - 169 - export const languages = [ 170 - { 171 - label: "en", 172 - value: "en", 173 - language: "English", 174 - }, 175 - { 176 - label: "pt-BR", 177 - value: "br", 178 - locale: "pt-BR", 179 - language: "Português", 180 - }, 181 - { 182 - label: "zh", 183 - value: "cns", 184 - locale: "zh-CN", 185 - language: "Chinese", 186 - }, 187 - { 188 - label: "ja", 189 - value: "jp", 190 - language: "Japanese", 191 - }, 192 - { 193 - label: "ko", 194 - value: "kr", 195 - language: "Korean", 196 - }, 197 - { 198 - label: "es", 199 - value: "sp", 200 - language: "Spanish", 201 - }, 202 - { 203 - label: "ru", 204 - value: "ru", 205 - language: "Russian", 206 - }, 207 - { 208 - label: "de", 209 - value: "de", 210 - language: "German", 211 - }, 212 - { 213 - label: "fi", 214 - value: "fi", 215 - language: "Finnish", 216 - }, 217 - { 218 - label: "id", 219 - value: "id", 220 - language: "Indonesian", 221 - }, 222 - { 223 - label: "it", 224 - value: "it", 225 - language: "Italian", 226 - }, 227 - { 228 - label: "nl", 229 - value: "nl", 230 - language: "Dutch", 231 - }, 232 - { 233 - label: "pl", 234 - value: "pl", 235 - language: "Polish", 236 - }, 237 - ]; 238 - 239 - export function getLanguageForSkill( 240 - langs: typeof languages, 241 - appLanguage: string, 242 - ) { 243 - return langs.find((lang) => lang.label === appLanguage)?.value; 244 - }
+8
apps/skillulator/src/utils/language.ts
··· 1 + import type { languages } from "./constants"; 2 + 3 + export function getLanguageForSkill( 4 + langs: typeof languages, 5 + appLanguage: string, 6 + ) { 7 + return langs.find((lang) => lang.label === appLanguage)?.value ?? "en"; 8 + }
+94
apps/skillulator/src/utils/skill-tree-helpers.ts
··· 1 + import type { State } from "../zustand/treeStore"; 2 + import type { JOB_SKILLPOINTS } from "./constants"; 3 + 4 + type Skills = State["jobTree"][number]["skills"]; 5 + type Jobs = State["jobTree"]; 6 + type SkillBracket = { 7 + maxLevel: number; 8 + pointsPerLevel: number; 9 + }; 10 + 11 + export function getJobById(jobId: number, jobs: Jobs) { 12 + return jobs.filter((job) => job.id === jobId).at(0); 13 + } 14 + 15 + export function getSkillById(skillId: number, skills: Skills) { 16 + return skills.filter((skill) => skill.id === skillId).at(0); 17 + } 18 + 19 + export function getJobByName(jobName: string, jobs: Jobs) { 20 + return jobs.find( 21 + (job) => job.name.en.toLowerCase() === jobName.toLowerCase(), 22 + ); 23 + } 24 + 25 + export function encodeTree(skills: Skills, characterLevel: number) { 26 + return `${skills.map((skill) => `${skill.id}:${skill.skillLevel}`).join(",")}#${characterLevel}`; 27 + } 28 + 29 + export function decodeTree(encodedSkills: string) { 30 + const characterLevel = encodedSkills.split("#").at(1); 31 + const decodedTree = encodedSkills.split("#").at(0); 32 + if (characterLevel && decodedTree) { 33 + return { 34 + normalizedSkillMap: decodedTree 35 + .split(",") 36 + .map((skill) => skill.split(":")) 37 + .map((s) => ({ skill: Number(s[0]), level: Number(s[1]) })), 38 + characterLevel, 39 + }; 40 + } 41 + throw new Error("Could not decode skill tree"); 42 + } 43 + 44 + const SKILL_BRACKETS: SkillBracket[] = [ 45 + { maxLevel: 20, pointsPerLevel: 2 }, 46 + { maxLevel: 40, pointsPerLevel: 3 }, 47 + { maxLevel: 60, pointsPerLevel: 4 }, 48 + { maxLevel: 80, pointsPerLevel: 5 }, 49 + { maxLevel: 100, pointsPerLevel: 6 }, 50 + { maxLevel: 120, pointsPerLevel: 7 }, 51 + { maxLevel: 140, pointsPerLevel: 8 }, 52 + { maxLevel: 150, pointsPerLevel: 1 }, 53 + { maxLevel: 160, pointsPerLevel: 2 }, 54 + { maxLevel: 165, pointsPerLevel: 2 }, 55 + ]; 56 + 57 + export function getSkillPointsForLevel(characterLevel: number): number { 58 + if (characterLevel < 15) { 59 + return 0; 60 + } 61 + 62 + let totalPoints = 0; 63 + let previousMaxLevel = 0; 64 + 65 + for (const bracket of SKILL_BRACKETS) { 66 + const effectiveLevel = Math.min(characterLevel, bracket.maxLevel); 67 + const levelsInBracket = Math.max(0, effectiveLevel - previousMaxLevel); 68 + 69 + totalPoints += levelsInBracket * bracket.pointsPerLevel; 70 + previousMaxLevel = bracket.maxLevel; 71 + 72 + if (characterLevel <= bracket.maxLevel) { 73 + break; 74 + } 75 + } 76 + 77 + return totalPoints; 78 + } 79 + 80 + export function getJobTotalSkillPoints( 81 + jobMap: typeof JOB_SKILLPOINTS, 82 + jobId: number, 83 + characterLevel: number, 84 + ) { 85 + if (characterLevel >= 60) { 86 + return ( 87 + getSkillPointsForLevel(characterLevel) + 88 + jobMap[jobId].firstJobSP + 89 + jobMap[jobId].secondJobSP 90 + ); 91 + } 92 + 93 + return getSkillPointsForLevel(characterLevel) + jobMap[jobId].firstJobSP; 94 + }