Compare commits

...

3 Commits

Author SHA1 Message Date
dependabot[bot] dae6aa6a87 Bump react-router-dom from 6.25.0 to 7.14.0 in /frontend
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.25.0 to 7.14.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.14.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-version: 7.14.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 21:06:11 +00:00
morbalint a6ecbfe927 fix in increase gives kepzettseg (#87)
* fix in increase gives kepzettseg

* Update frontend/src/second-edition/domain-models/kepzettsegek2E.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-05 13:22:00 +02:00
morbalint 588de98262 add some level up tests and maybe fixes (#86)
* add some level up tests and maybe fixes

* address review comments

* fix tolvaj multiclass

* tolvaj level 9 kepzettseg
2026-04-05 12:50:15 +02:00
13 changed files with 664 additions and 47 deletions
+1 -1
View File
@@ -24,7 +24,7 @@
"react-bootstrap": "^2.10.2",
"react-dom": "^18.2.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.17.0",
"react-router-dom": "^7.14.0",
"typescript": "^5.2.2",
"workbox-background-sync": "^7.1.0",
"workbox-broadcast-update": "^7.1.0",
@@ -8,21 +8,23 @@ import {AppDispatch, RootState} from "../../store";
import {characterSelector, setHarcosSpecialization} from "../domain-models/characterSlice";
function HarcosFegyverSpecializacio(props: {
szint: number
harcosSzint: number,
karakterSzint: number,
}) {
const {
szint
harcosSzint,
karakterSzint,
} = props
const dispatch = useDispatch.withTypes<AppDispatch>()()
const character = useSelector.withTypes<RootState>()(characterSelector)
const specialization = character.szintlepesek[szint-1].harcosFegyver || 'kard_hosszu'
const specialization = character.szintlepesek[karakterSzint-1]?.harcosFegyver || 'kard_hosszu'
const existingSpecializations = character.szintlepesek.filter(x => x.harcosFegyver != null).map(x => x.harcosFegyver! as string)
const changeSpecialization = (specialization: string) => dispatch(setHarcosSpecialization({szint, fegyver: specialization}))
const changeSpecialization = (specialization: string) => dispatch(setHarcosSpecialization({szint: karakterSzint, fegyver: specialization}))
const fegyver = GetFegyver(specialization)
const availableFegyverek = AllowedFegyver(Osztaly2E.Harcos)
const pickableFegyverek = availableFegyverek.filter(f => f.Id === specialization || existingSpecializations.filter(x => x === f.Id).length < (szint < 9 ? 1 : 2))
const pickableFegyverek = availableFegyverek.filter(f => f.Id === specialization || existingSpecializations.filter(x => x === f.Id).length < (harcosSzint < 9 ? 1 : 2))
return <>
@@ -32,7 +34,7 @@ function HarcosFegyverSpecializacio(props: {
fegyverek={pickableFegyverek}
selectedId={specialization}
onChange={changeSpecialization}
dataTestId={"harcos-specialization-" + szint}
dataTestId={"harcos-specialization-" + karakterSzint}
/>
{fegyver == null && <span className='form-field-error'>Ismeretlen fegyver: {specialization}</span>}
</div>
@@ -0,0 +1,126 @@
import React from "react";
import {render} from "@testing-library/react";
import {configureStore} from "@reduxjs/toolkit";
import {Provider} from "react-redux";
import KarakterKepzettsegek from "./Kepzettsegek2E";
import userReducer from "../../shared/domain-models/userSlice";
import characterReducer, {setCharacter} from "../domain-models/characterSlice";
import {DefaultKarakter, Karakter2E} from "../domain-models/karakter2E";
import {Osztaly2E} from "../domain-models/osztaly2E";
import {Tulajdonsag2E} from "../domain-models/tulajdonsag2E";
function createStore() {
return configureStore({
reducer: {
user: userReducer,
character2E: characterReducer,
},
});
}
describe("KarakterKepzettsegek", () => {
it("shows one more kepzettseg selector when INT modifier increases from level-up", () => {
const karakter: Karakter2E = {
...DefaultKarakter,
szint: 4,
tulajdonsagok: {
...DefaultKarakter.tulajdonsagok,
t_int: 17,
},
szintlepesek: [
{ osztaly: Osztaly2E.Harcos, HProll: 10 },
{ osztaly: Osztaly2E.Harcos, HProll: 7 },
{ osztaly: Osztaly2E.Harcos, HProll: 6 },
{ osztaly: Osztaly2E.Harcos, HProll: 8, tulajdonsagNoveles: Tulajdonsag2E.Intelligencia },
],
tolvajKepzettsegek: [],
varazslatok: [],
};
const store = createStore();
store.dispatch(setCharacter(karakter));
const sut = render(
<Provider store={store}>
<KarakterKepzettsegek />
</Provider>
);
const selectors = sut.container.querySelectorAll('[data-testid^="kepzettseg-"]');
expect(selectors).toHaveLength(7);
});
it("shows one more kepzettseg selector when INT modifier increases at level 8", () => {
const karakter: Karakter2E = {
...DefaultKarakter,
szint: 8,
tulajdonsagok: {
...DefaultKarakter.tulajdonsagok,
t_int: 14,
},
szintlepesek: [
{ osztaly: Osztaly2E.Harcos, HProll: 10 },
{ osztaly: Osztaly2E.Harcos, HProll: 7 },
{ osztaly: Osztaly2E.Harcos, HProll: 6 },
{ osztaly: Osztaly2E.Harcos, HProll: 8, tulajdonsagNoveles: Tulajdonsag2E.Ero },
{ osztaly: Osztaly2E.Harcos, HProll: 4 },
{ osztaly: Osztaly2E.Harcos, HProll: 9 },
{ osztaly: Osztaly2E.Harcos, HProll: 5 },
{ osztaly: Osztaly2E.Harcos, HProll: 6, tulajdonsagNoveles: Tulajdonsag2E.Intelligencia },
],
tolvajKepzettsegek: [],
varazslatok: [],
};
const store = createStore();
store.dispatch(setCharacter(karakter));
const sut = render(
<Provider store={store}>
<KarakterKepzettsegek />
</Provider>
);
const selectors = sut.container.querySelectorAll('[data-testid^="kepzettseg-"]');
expect(selectors).toHaveLength(6);
});
it("shows one more kepzettseg selector when INT modifier increases at level 12", () => {
const karakter: Karakter2E = {
...DefaultKarakter,
szint: 12,
tulajdonsagok: {
...DefaultKarakter.tulajdonsagok,
t_int: 17,
},
szintlepesek: [
{ osztaly: Osztaly2E.Harcos, HProll: 10 },
{ osztaly: Osztaly2E.Harcos, HProll: 7 },
{ osztaly: Osztaly2E.Harcos, HProll: 6 },
{ osztaly: Osztaly2E.Harcos, HProll: 8, tulajdonsagNoveles: Tulajdonsag2E.Ero },
{ osztaly: Osztaly2E.Harcos, HProll: 4 },
{ osztaly: Osztaly2E.Harcos, HProll: 9 },
{ osztaly: Osztaly2E.Harcos, HProll: 5 },
{ osztaly: Osztaly2E.Harcos, HProll: 6, tulajdonsagNoveles: Tulajdonsag2E.Egeszseg },
{ osztaly: Osztaly2E.Harcos, HProll: 3 },
{ osztaly: Osztaly2E.Harcos, HProll: 7 },
{ osztaly: Osztaly2E.Harcos, HProll: 5 },
{ osztaly: Osztaly2E.Harcos, HProll: 4, tulajdonsagNoveles: Tulajdonsag2E.Intelligencia },
],
tolvajKepzettsegek: [],
varazslatok: [],
};
const store = createStore();
store.dispatch(setCharacter(karakter));
const sut = render(
<Provider store={store}>
<KarakterKepzettsegek />
</Provider>
);
const selectors = sut.container.querySelectorAll('[data-testid^="kepzettseg-"]');
expect(selectors).toHaveLength(7);
});
});
@@ -13,7 +13,7 @@ import {arraySetN} from "../../util";
import {useDispatch, useSelector} from "react-redux";
import {AppDispatch, RootState} from "../../store";
import {characterSelector, setKepzettsegek, setTolvajKepzettsegek} from "../domain-models/characterSlice";
import {TulajdonsagokFajjal} from "../domain-models/faj2E";
import {TulajdonsagokTotal} from "../domain-models/tulajdonsag2E";
function InternalKepzettsegekSelector(props: {
title: string,
@@ -70,8 +70,8 @@ function KarakterKepzettsegek () {
const karakter = useSelector.withTypes<RootState>()(characterSelector);
const dispatch = useDispatch.withTypes<AppDispatch>()();
const tulajdonsagokFajjal = TulajdonsagokFajjal(karakter.tulajdonsagok, karakter.faj)
const numberOfKepzettsegek = GetNumberOfKepzettsegek(tulajdonsagokFajjal.t_int, karakter.faj)
const tulajdonsagokTotal = TulajdonsagokTotal(karakter)
const numberOfKepzettsegek = GetNumberOfKepzettsegek(tulajdonsagokTotal.t_int, karakter.faj)
const kepzettsegek = karakter.kepzettsegek
const fajError = karakter.kepzettsegFajError
const osztalyError = karakter.kepzettsegOsztalyError
@@ -0,0 +1,248 @@
import React from "react";
import {fireEvent, render} from "@testing-library/react";
import {configureStore} from "@reduxjs/toolkit";
import {Provider} from "react-redux";
import LevelUps from "./LevelUps";
import userReducer from "../../shared/domain-models/userSlice";
import characterReducer, {setCharacter} from "../domain-models/characterSlice";
import {AllowedFegyver} from "../domain-models/allowed-fegyver";
import {Osztaly2E} from "../domain-models/osztaly2E";
import {ChangeOsztalyAtSzint, DefaultKarakter, Karakter2E} from "../domain-models/karakter2E";
import {LevelUp} from "../domain-models/szintlepes";
import {GetAvailableKepzettsegek} from "../domain-models/kepzettsegek2E";
function applyLevelUp(karakter: Karakter2E): Karakter2E {
let next: Karakter2E | undefined;
LevelUp(karakter, updated => {
next = updated;
});
expect(next).toBeDefined();
return next as Karakter2E;
}
describe("LevelUps Harcos specialization indexing", () => {
it("updates specialization on the selected character level for multiclass", async () => {
const level1Tolvaj: Karakter2E = {
...DefaultKarakter,
szint: 1,
szintlepesek: [{
osztaly: Osztaly2E.Tolvaj,
HProll: 6,
}],
tolvajKepzettsegek: [],
varazslatok: [],
};
const level2Auto = applyLevelUp(level1Tolvaj);
const karakter = ChangeOsztalyAtSzint(level2Auto, Osztaly2E.Harcos, 2);
const store = configureStore({
reducer: {
user: userReducer,
character2E: characterReducer,
},
});
store.dispatch(setCharacter(karakter));
const sut = render(
<Provider store={store}>
<LevelUps
karakter={store.getState().character2E}
changeKarakter={(updated) => store.dispatch(setCharacter(updated))}
/>
</Provider>
);
const selector = await sut.findByTestId("harcos-specialization-2");
const currentSpec = karakter.szintlepesek[1]?.harcosFegyver;
const nextSpec = AllowedFegyver(Osztaly2E.Harcos)
.map(x => x.Id)
.find(id => id !== currentSpec);
expect(nextSpec).toBeDefined();
fireEvent.change(selector, {target: {value: nextSpec as string}});
const stateAfter = store.getState().character2E;
expect(stateAfter.szintlepesek[0]?.harcosFegyver).toBeUndefined();
expect(stateAfter.szintlepesek[1]?.harcosFegyver).toBe(nextSpec);
});
it("does not render tulajdonsag selector at level 4 when all abilities are maxed", () => {
const maxedCharacter: Karakter2E = {
...DefaultKarakter,
szint: 1,
tulajdonsagok: {
t_ero: 18,
t_ugy: 18,
t_egs: 18,
t_int: 18,
t_bol: 18,
t_kar: 18,
},
szintlepesek: [{
osztaly: Osztaly2E.Pap,
HProll: 8,
}],
tolvajKepzettsegek: [],
varazslatok: [],
};
const level2 = applyLevelUp(maxedCharacter);
const level3 = applyLevelUp(level2);
const level4 = applyLevelUp(level3);
expect(level4.szintlepesek[3]?.tulajdonsagNoveles).toBeUndefined();
const store = configureStore({
reducer: {
user: userReducer,
character2E: characterReducer,
},
});
store.dispatch(setCharacter(level4));
const sut = render(
<Provider store={store}>
<LevelUps
karakter={store.getState().character2E}
changeKarakter={(updated) => store.dispatch(setCharacter(updated))}
/>
</Provider>
);
expect(sut.queryByTestId("tulajdonsag-4")).toBeNull();
});
it("does not render kaloz krit selector when tengeresz milestone exists but krit is missing", () => {
const inconsistentTengeresz: Karakter2E = {
...DefaultKarakter,
szint: 3,
szintlepesek: [
{
osztaly: Osztaly2E.Tengeresz,
HProll: 10,
},
{
osztaly: Osztaly2E.Tengeresz,
HProll: 5,
},
{
osztaly: Osztaly2E.Tengeresz,
HProll: 7,
}
],
tolvajKepzettsegek: [],
varazslatok: [],
};
const store = configureStore({
reducer: {
user: userReducer,
character2E: characterReducer,
},
});
store.dispatch(setCharacter(inconsistentTengeresz));
const sut = render(
<Provider store={store}>
<LevelUps
karakter={store.getState().character2E}
changeKarakter={(updated) => store.dispatch(setCharacter(updated))}
/>
</Provider>
);
expect(sut.queryByTestId("kaloz-krit-3")).toBeNull();
});
it("shows and updates Tolvaj extra skill on multiclass Tolvaj level 5", async () => {
const multiclassTolvaj: Karakter2E = {
...DefaultKarakter,
szint: 6,
szintlepesek: [
{ osztaly: Osztaly2E.Tolvaj, HProll: 6 },
{ osztaly: Osztaly2E.Harcos, HProll: 10 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3, tolvajExtraKepzettseg: "k_meregkeveres" },
],
tolvajKepzettsegek: ["k_alcazas", "k_csapdak", "k_egyensulyozas", "k_hamisitas"],
varazslatok: [],
};
const store = configureStore({
reducer: {
user: userReducer,
character2E: characterReducer,
},
});
store.dispatch(setCharacter(multiclassTolvaj));
const sut = render(
<Provider store={store}>
<LevelUps
karakter={store.getState().character2E}
changeKarakter={(updated) => store.dispatch(setCharacter(updated))}
/>
</Provider>
);
const selector = await sut.findByTestId("tolvaj-extra-kepzettseg-6");
const nextKepzettseg = GetAvailableKepzettsegek(multiclassTolvaj)
.map(x => x.Id)
.find(id => id !== multiclassTolvaj.szintlepesek[5]?.tolvajExtraKepzettseg);
expect(nextKepzettseg).toBeDefined();
fireEvent.change(selector, {target: {value: nextKepzettseg as string}});
const stateAfter = store.getState().character2E;
expect(stateAfter.szintlepesek[5]?.tolvajExtraKepzettseg).toBe(nextKepzettseg);
expect(stateAfter.szintlepesek[4]?.tolvajExtraKepzettseg).toBeUndefined();
});
it("at Tolvaj level 9 selector includes class-restricted non-Tolvaj skills", async () => {
const tolvajLevel9: Karakter2E = {
...DefaultKarakter,
szint: 9,
szintlepesek: [
{ osztaly: Osztaly2E.Tolvaj, HProll: 6 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 2, tolvajExtraKepzettseg: "k_meregkeveres" },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, tolvajExtraKepzettseg: "k_alkimia" },
],
tolvajKepzettsegek: ["k_alcazas", "k_csapdak", "k_egyensulyozas", "k_hamisitas"],
varazslatok: [],
};
const store = configureStore({
reducer: {
user: userReducer,
character2E: characterReducer,
},
});
store.dispatch(setCharacter(tolvajLevel9));
const sut = render(
<Provider store={store}>
<LevelUps
karakter={store.getState().character2E}
changeKarakter={(updated) => store.dispatch(setCharacter(updated))}
/>
</Provider>
);
const selector = await sut.findByTestId("tolvaj-extra-kepzettseg-9");
const arcaneOption = selector.querySelector('option[value="k_csillagjoslas"]');
expect(arcaneOption).not.toBeNull();
fireEvent.change(selector, {target: {value: "k_csillagjoslas"}});
expect(store.getState().character2E.szintlepesek[8]?.tolvajExtraKepzettseg).toBe("k_csillagjoslas");
});
});
@@ -41,6 +41,8 @@ function LevelUp(props: {
const kalozSzint = classLevels[Osztaly2E.Tengeresz]
const tolvajSzint = classLevels[Osztaly2E.Tolvaj]
const szintlepes = karakter.szintlepesek[szint-1];
const tulajdonsagNoveles = szintlepes?.tulajdonsagNoveles;
const kalozKritikus = szintlepes?.kalozKritikus;
return <div>
<div className='row mt-3'>
@@ -54,10 +56,10 @@ function LevelUp(props: {
osztalySzint={classLevels[osztaly]}
dataTestId={`osztaly-${szint}`}
/>}
{szint % 4 === 0 &&
{szint % 4 === 0 && tulajdonsagNoveles != null &&
<TulajdonsagNoveles
tulajdonsagok={karakter.tulajdonsagok}
tulajdonsagNoveles={karakter.szintlepesek[szint-1].tulajdonsagNoveles!}
tulajdonsagNoveles={tulajdonsagNoveles}
changeTulajdonsagNoveles={changeTulajdonsagNoveles}
dataTestId={`tulajdonsag-${szint}`}
/>}
@@ -69,12 +71,13 @@ function LevelUp(props: {
/>
{osztaly === Osztaly2E.Harcos && harcosSzint % 2 === 1 &&
<HarcosFegyverSpecializacio
szint={harcosSzint}
harcosSzint={harcosSzint}
karakterSzint={szint}
/>
}
{osztaly === Osztaly2E.Tengeresz && kalozSzint % 3 === 0 &&
{osztaly === Osztaly2E.Tengeresz && kalozSzint % 3 === 0 && kalozKritikus != null &&
<KalozKritikus
kritFegyverId={szintlepes.kalozKritikus!}
kritFegyverId={kalozKritikus}
existingKrits={karakter.szintlepesek.map(x => x.kalozKritikus)}
changeKrit={changeKalozKritikus}
szint={kalozSzint}
@@ -82,12 +85,17 @@ function LevelUp(props: {
}
{osztaly === Osztaly2E.Tolvaj
&& (tolvajSzint === 5 || tolvajSzint === 9)
&& szintlepes.tolvajExtraKepzettseg != null &&
&& szintlepes?.tolvajExtraKepzettseg != null &&
<div className='row m-2'>
<label className='col-md-2 col-sm-3 col-form-label'>Extra képzettség</label>
<KepzettsegSelector
selected={Kepzettsegek[szintlepes.tolvajExtraKepzettseg]}
kepzettsegek={[Kepzettsegek[szintlepes.tolvajExtraKepzettseg], ...GetAvailableKepzettsegek(karakter)]}
selected={Kepzettsegek[szintlepes!.tolvajExtraKepzettseg]}
kepzettsegek={[
Kepzettsegek[szintlepes!.tolvajExtraKepzettseg],
...GetAvailableKepzettsegek(karakter, {
ignoreClassRestrictions: tolvajSzint === 9,
})
]}
dataTestId={`tolvaj-extra-kepzettseg-${szint}`}
changeKepzettseg={changeExtraTolvajKepzettseg}
/>
@@ -100,16 +108,19 @@ export function LevelUps(props: {karakter: Karakter2E, changeKarakter: (inputs:
const {karakter, changeKarakter} = props;
const tulajdonsagokAtSzint = (szint: number) => TulajdonsagokTotal({...karakter, szintlepesek: karakter.szintlepesek.slice(0, szint)})
const szintlepesAt = (szint: number) => karakter.szintlepesek[szint-1]!
const changeRolledHPAtSzint = (szint: number) => (newHProll: number) => {
changeKarakter({...karakter, szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {...karakter.szintlepesek[szint-1], HProll: newHProll})})
const szintlepes = szintlepesAt(szint)
changeKarakter({...karakter, szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {...szintlepes, HProll: newHProll})})
console.log(karakter)
}
const changeTulajdonsagNovelesAtSzint = (szint: number) => {
return (t: Tulajdonsag2E) => {
const szintlepes = szintlepesAt(szint)
changeKarakter({...karakter,
szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {
...karakter.szintlepesek[szint-1],
...szintlepes,
tulajdonsagNoveles: t
})
})
@@ -118,10 +129,11 @@ export function LevelUps(props: {karakter: Karakter2E, changeKarakter: (inputs:
const changeKalozKritikusAtSzint = (szint: number) => {
return (krit: string) => {
const szintlepes = szintlepesAt(szint)
changeKarakter({
...karakter,
szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {
...karakter.szintlepesek[szint-1],
...szintlepes,
kalozKritikus: krit
})
})
@@ -130,10 +142,11 @@ export function LevelUps(props: {karakter: Karakter2E, changeKarakter: (inputs:
const changeExtraTolvajKepzettseg = (szint: number) => {
return (k: KepzettsegId) => {
const szintlepes = szintlepesAt(szint)
changeKarakter({
...karakter,
szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {
...karakter.szintlepesek[szint-1],
...szintlepes,
tolvajExtraKepzettseg: k,
})
})
@@ -144,10 +157,13 @@ export function LevelUps(props: {karakter: Karakter2E, changeKarakter: (inputs:
return <>
{levels.slice(1).map(szint =>
{
const szintlepes = szintlepesAt(szint)
return (
<LevelUp
key={`level-up-${szint}`}
szint={szint}
osztaly={karakter.szintlepesek[szint-1].osztaly}
osztaly={szintlepes.osztaly}
karakter={{...karakter, szintlepesek: karakter.szintlepesek.slice(0, szint) ,tulajdonsagok: tulajdonsagokAtSzint(szint)}}
changeTulajdonsagNoveles={changeTulajdonsagNovelesAtSzint(szint)}
changeRolledHP={changeRolledHPAtSzint(szint)}
@@ -155,6 +171,8 @@ export function LevelUps(props: {karakter: Karakter2E, changeKarakter: (inputs:
changeOsztaly={(o) => changeKarakter(ChangeOsztalyAtSzint(karakter, o, szint))}
changeExtraTolvajKepzettseg={changeExtraTolvajKepzettseg(szint)}
/>
)
}
)}
</>
}
@@ -9,6 +9,7 @@ import {AllowedFegyver} from "./allowed-fegyver";
import {BaseHP} from "./masodlagos_ertekek";
import {dAny} from "../../shared/domain-models/kockak";
import {KarakterVarazslat} from "./varazslat";
import {GetAvailableKepzettsegek} from "./kepzettsegek2E";
export interface Karakter2E {
id?: string,
@@ -51,13 +52,15 @@ export const DefaultKarakter: Karakter2E = {
varazslatok: [],
}
function setOsztalyForSzintLepes(szintlepesek: Szintlepes[], osztaly: Osztaly2E, szint: number): Szintlepes[] {
function setOsztalyForSzintLepes(karakter: Karakter2E, osztaly: Osztaly2E, szint: number): Szintlepes[] {
const szintlepesek = karakter.szintlepesek
const prev = szintlepesek.slice(0, szint-1)
const current : Szintlepes = {
...szintlepesek[szint-1],
osztaly,
harcosFegyver: undefined,
kalozKritikus: undefined
kalozKritikus: undefined,
tolvajExtraKepzettseg: undefined,
}
const tail = szintlepesek.slice(szint).map(x => {return {...x}})
@@ -117,6 +120,39 @@ function setOsztalyForSzintLepes(szintlepesek: Szintlepes[], osztaly: Osztaly2E,
tail[i].kalozKritikus = kalozKritAtSzint(kalozSzint)
}
}
let tolvajSzint = prev.filter(x => x.osztaly === Osztaly2E.Tolvaj).length
const existingTolvajExtraKepzettsegek = szintlepesek
.map(x => x.tolvajExtraKepzettseg)
.filter(x => x != null) as KepzettsegId[]
const tolvajExtraKepzettsegAtSzint = (tSzint: number, atLevelSzintlepesek: Szintlepes[]): (KepzettsegId | undefined) => {
if (tSzint !== 5 && tSzint !== 9) {
return undefined
}
const extraIndex = tSzint === 5 ? 0 : 1
if (existingTolvajExtraKepzettsegek.length > extraIndex) {
return existingTolvajExtraKepzettsegek[extraIndex]
}
return GetAvailableKepzettsegek({
szintlepesek: atLevelSzintlepesek,
faj: karakter.faj,
kepzettsegek: karakter.kepzettsegek,
tolvajKepzettsegek: karakter.tolvajKepzettsegek,
}, {
ignoreClassRestrictions: tSzint === 9,
})[0]?.Id
}
if (osztaly === Osztaly2E.Tolvaj) {
tolvajSzint++
current.tolvajExtraKepzettseg = tolvajExtraKepzettsegAtSzint(tolvajSzint, [...prev, current])
}
for (let i = 0; i < tail.length; i++) {
if (tail[i].osztaly === Osztaly2E.Tolvaj) {
tolvajSzint++
tail[i].tolvajExtraKepzettseg = tolvajExtraKepzettsegAtSzint(tolvajSzint, [...prev, current, ...tail.slice(0, i+1)])
}
}
const HD = BaseHP(osztaly)
if (szint === 1) {
@@ -138,6 +174,6 @@ export function ChangeLvl1Osztaly(karakter: Karakter2E, osztaly: Osztaly2E): Kar
export function ChangeOsztalyAtSzint(karakter: Karakter2E, osztaly: Osztaly2E, szint: number): Karakter2E {
return {
...karakter,
szintlepesek: setOsztalyForSzintLepes(karakter.szintlepesek, osztaly, szint)
szintlepesek: setOsztalyForSzintLepes(karakter, osztaly, szint)
}
}
@@ -106,6 +106,28 @@ describe("Kepzettsegek 2E", () => {
const actual = GetAvailableKepzettsegek(karakter)
expect(actual).not.toContain(Kepzettsegek.k_meregkeveres)
})
test("at Tolvaj level 9 extra skill can include class-restricted non-Tolvaj skills", () => {
const karakter = {
...DefaultKarakter,
szintlepesek: [
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, tulajdonsagNoveles: Tulajdonsag2E.Ugyesseg },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, tolvajExtraKepzettseg: Kepzettsegek.k_meregkeveres.Id },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, },
{ osztaly: Osztaly2E.Tolvaj, HProll: 6, },
]
}
const restricted = GetAvailableKepzettsegek(karakter)
expect(restricted).not.toContain(Kepzettsegek.k_csillagjoslas)
const unrestricted = GetAvailableKepzettsegek(karakter, { ignoreClassRestrictions: true })
expect(unrestricted).toContain(Kepzettsegek.k_csillagjoslas)
})
})
test("multiclass characters get access to kepzettsegek from all classes", () => {
const karakter = {
@@ -13,7 +13,8 @@ function availableKezpettsegListFajjal(faj: Faj2E, ...osztalyok: Osztaly2E[]): K
}
export function GetNumberOfKepzettsegek(t_int: number, faj: Faj2E, max: number = 11) {
//max = 3 + 1 + 3 + 5 = 12 // 3 base, 1 human, 3 from max ability modifier, 4 from Thief +1 at 9th level
// Default cap is 11. Base calculation is 3 + 1 (human) + 3 (max ability modifier) = 7;
// thief-specific extra selections are handled elsewhere and are not part of this default max.
let numberOfKepzettseg = 3 + Modifier(t_int) + (faj === Faj2E.Ember ? 1 : 0)
if (numberOfKepzettseg < 1) {
@@ -27,9 +28,15 @@ export function GetNumberOfKepzettsegek(t_int: number, faj: Faj2E, max: number =
return numberOfKepzettseg;
}
export function GetAvailableKepzettsegek(karakter: Pick<Karakter2E, 'szintlepesek' | 'faj' | 'kepzettsegek' | 'tolvajKepzettsegek'>): Kepzettseg[] {
export function GetAvailableKepzettsegek(
karakter: Pick<Karakter2E, 'szintlepesek' | 'faj' | 'kepzettsegek' | 'tolvajKepzettsegek'>,
options?: { ignoreClassRestrictions?: boolean }
): Kepzettseg[] {
const osztalyok = new Set(karakter.szintlepesek.map(x => x.osztaly))
const availableKepzettsegek = availableKezpettsegListFajjal(karakter.faj, ...osztalyok)
const baseKepzettsegList = options?.ignoreClassRestrictions
? KepzettsegLista
: availableKezpettsegListFajjal(karakter.faj, ...osztalyok)
const availableKepzettsegek = baseKepzettsegList
.filter(k => !karakter.kepzettsegek.includes(k.Id))
if (!osztalyok.has(Osztaly2E.Tolvaj)) {
@@ -0,0 +1,151 @@
import {ChangeOsztalyAtSzint, DefaultKarakter, Karakter2E} from "./karakter2E";
import {Osztaly2E} from "./osztaly2E";
import {LevelUp} from "./szintlepes";
function applyLevelUp(karakter: Karakter2E): Karakter2E {
let next: Karakter2E | undefined;
LevelUp(karakter, updated => {
next = updated;
});
expect(next).toBeDefined();
return next as Karakter2E;
}
function level1Tolvaj(): Karakter2E {
return {
...DefaultKarakter,
szint: 1,
szintlepesek: [{
osztaly: Osztaly2E.Tolvaj,
HProll: 6,
}],
tolvajKepzettsegek: [],
varazslatok: [],
};
}
describe("Harcos specialization in multiclass level-ups", () => {
it("adds a specialization when first Harcos level is gained at character level 2", () => {
const afterLevel2 = applyLevelUp(level1Tolvaj());
const multiclassed = ChangeOsztalyAtSzint(afterLevel2, Osztaly2E.Harcos, 2);
const secondLevel = multiclassed.szintlepesek[1];
expect(secondLevel?.osztaly).toBe(Osztaly2E.Harcos);
expect(secondLevel?.harcosFegyver).toBeDefined();
});
it("does not add a specialization on even Harcos class level", () => {
const afterLevel2 = applyLevelUp(level1Tolvaj());
const multiclassed = ChangeOsztalyAtSzint(afterLevel2, Osztaly2E.Harcos, 2);
const afterLevel3 = applyLevelUp(multiclassed);
const thirdLevel = afterLevel3.szintlepesek[2];
expect(thirdLevel?.osztaly).toBe(Osztaly2E.Harcos);
expect(thirdLevel?.harcosFegyver).toBeUndefined();
});
it("adds the next specialization on odd Harcos class level after multiclass", () => {
const afterLevel2 = applyLevelUp(level1Tolvaj());
const multiclassed = ChangeOsztalyAtSzint(afterLevel2, Osztaly2E.Harcos, 2);
const afterLevel3 = applyLevelUp(multiclassed);
const afterLevel4 = applyLevelUp(afterLevel3);
const firstHarcosSpec = multiclassed.szintlepesek[1]?.harcosFegyver;
const thirdHarcosLevelSpec = afterLevel4.szintlepesek[3]?.harcosFegyver;
expect(firstHarcosSpec).toBeDefined();
expect(thirdHarcosLevelSpec).toBeDefined();
expect(thirdHarcosLevelSpec).not.toBe(firstHarcosSpec);
});
});
describe("Tolvaj extra skill in multiclass level-ups", () => {
it("adds extra skill when Tolvaj class reaches level 5 at higher character level", () => {
const karakter: Karakter2E = {
...DefaultKarakter,
szint: 5,
szintlepesek: [
{ osztaly: Osztaly2E.Tolvaj, HProll: 6 },
{ osztaly: Osztaly2E.Harcos, HProll: 10 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
],
tolvajKepzettsegek: ["k_alcazas", "k_csapdak", "k_egyensulyozas", "k_hamisitas"],
varazslatok: [],
};
const afterLevel6 = applyLevelUp(karakter);
const sixthLevel = afterLevel6.szintlepesek[5];
expect(sixthLevel?.osztaly).toBe(Osztaly2E.Tolvaj);
expect(sixthLevel?.tolvajExtraKepzettseg).toBeDefined();
});
it("recomputes missing extra skill when class change makes current level Tolvaj level 5", () => {
const karakter: Karakter2E = {
...DefaultKarakter,
szint: 5,
szintlepesek: [
{ osztaly: Osztaly2E.Tolvaj, HProll: 6 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
{ osztaly: Osztaly2E.Harcos, HProll: 8 },
],
tolvajKepzettsegek: ["k_alcazas", "k_csapdak", "k_egyensulyozas", "k_hamisitas"],
varazslatok: [],
};
const changed = ChangeOsztalyAtSzint(karakter, Osztaly2E.Tolvaj, 5);
const fifthLevel = changed.szintlepesek[4];
expect(fifthLevel?.osztaly).toBe(Osztaly2E.Tolvaj);
expect(fifthLevel?.tolvajExtraKepzettseg).toBeDefined();
});
it("at Tolvaj class level 9 LevelUp can auto-pick a class-restricted non-Tolvaj skill", () => {
const karakter: Karakter2E = {
...DefaultKarakter,
szint: 8,
szintlepesek: [
{ osztaly: Osztaly2E.Tolvaj, HProll: 6 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 2, tolvajExtraKepzettseg: "k_meregkeveres" },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
],
tolvajKepzettsegek: ["k_alcazas", "k_csapdak", "k_egyensulyozas", "k_hamisitas"],
varazslatok: [],
};
const afterLevel9 = applyLevelUp(karakter);
expect(afterLevel9.szintlepesek[8]?.tolvajExtraKepzettseg).toBe("k_alkimia");
});
it("at Tolvaj class level 9 class-change recomputation can set non-Tolvaj restricted skill", () => {
const karakter: Karakter2E = {
...DefaultKarakter,
szint: 9,
szintlepesek: [
{ osztaly: Osztaly2E.Tolvaj, HProll: 6 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 2, tolvajExtraKepzettseg: "k_meregkeveres" },
{ osztaly: Osztaly2E.Tolvaj, HProll: 4 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 5 },
{ osztaly: Osztaly2E.Tolvaj, HProll: 3 },
{ osztaly: Osztaly2E.Harcos, HProll: 8 },
],
tolvajKepzettsegek: ["k_alcazas", "k_csapdak", "k_egyensulyozas", "k_hamisitas"],
varazslatok: [],
};
const changed = ChangeOsztalyAtSzint(karakter, Osztaly2E.Tolvaj, 9);
expect(changed.szintlepesek[8]?.tolvajExtraKepzettseg).toBe("k_alkimia");
});
});
@@ -67,7 +67,9 @@ export function LevelUp(karakter: Karakter2E, changeKarakter: (input: Karakter2E
if (autoPickedOsztaly === Osztaly2E.Tolvaj){
const tolvajSzint = 1 + karakter.szintlepesek.filter(x => x.osztaly === Osztaly2E.Tolvaj).length
if (tolvajSzint === 5 || tolvajSzint === 9) {
const availableKepzettsegek = GetAvailableKepzettsegek(karakter)
const availableKepzettsegek = GetAvailableKepzettsegek(karakter, {
ignoreClassRestrictions: tolvajSzint === 9,
})
szintlepes.tolvajExtraKepzettseg = availableKepzettsegek[0].Id;
}
}
@@ -109,7 +109,7 @@ function CreateCharacter2E(props: {
<h4 className='col align-self-center'>Tanult</h4>
</div>
<OsztalySelector2E />
{karakter.szintlepesek[0].osztaly === Osztaly2E.Harcos && <HarcosFegyverSpecializacio szint={1}/>}
{karakter.szintlepesek[0].osztaly === Osztaly2E.Harcos && <HarcosFegyverSpecializacio harcosSzint={1} karakterSzint={1}/>}
<KarakterKepzettsegek />
<hr/>
{/* 1. szinten */}
+21 -16
View File
@@ -703,11 +703,6 @@
redux-thunk "^3.1.0"
reselect "^5.1.0"
"@remix-run/router@1.18.0":
version "1.18.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.18.0.tgz#20b033d1f542a100c1d57cfd18ecf442d1784732"
integrity sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==
"@restart/hooks@^0.4.9":
version "0.4.16"
resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb"
@@ -1381,6 +1376,11 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
cookie@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c"
integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==
copy-anything@^2.0.1:
version "2.0.6"
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480"
@@ -2429,20 +2429,20 @@ react-refresh@^0.17.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53"
integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==
react-router-dom@^6.17.0:
version "6.25.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.25.0.tgz#f46f98553e1b4b3bcd2e4bb1021e9144b02671bf"
integrity sha512-BhcczgDWWgvGZxjDDGuGHrA8HrsSudilqTaRSBYLWDayvo1ClchNIDVt5rldqp6e7Dro5dEFx9Mzc+r292lN0w==
react-router-dom@^7.14.0:
version "7.14.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.14.0.tgz#9d2df92ec9ce47e696808dc2a0e0a0c794ab278a"
integrity sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==
dependencies:
"@remix-run/router" "1.18.0"
react-router "6.25.0"
react-router "7.14.0"
react-router@6.25.0:
version "6.25.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.25.0.tgz#24f72cdf3bcc9be0f60b95af6cd88950c3d246e4"
integrity sha512-bziKjCcDbcxgWS9WlWFcQIVZ2vJHnCP6DGpQDT0l+0PFDasfJKgzf9CM22eTyhFsZkjk8ApCdKjJwKtzqH80jQ==
react-router@7.14.0:
version "7.14.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.14.0.tgz#33169c9ac03b298bb51aad13e038ba548c79a862"
integrity sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==
dependencies:
"@remix-run/router" "1.18.0"
cookie "^1.0.1"
set-cookie-parser "^2.6.0"
react-transition-group@^4.4.5:
version "4.4.5"
@@ -2572,6 +2572,11 @@ semver@^7.7.3:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
set-cookie-parser@^2.6.0:
version "2.7.2"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68"
integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"