Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1281be69d8 |
Binary file not shown.
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.warren.iwanttoheal"
|
applicationId "com.warren.iwanttoheal"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 52
|
versionCode 53
|
||||||
versionName "1.0.33"
|
versionName "1.0.34"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
+9
-2
@@ -561,7 +561,9 @@ SET name = (
|
|||||||
SELECT
|
SELECT
|
||||||
CASE items.item_level
|
CASE items.item_level
|
||||||
WHEN 1 THEN 'Raw '
|
WHEN 1 THEN 'Raw '
|
||||||
|
WHEN 5 THEN 'Honed '
|
||||||
WHEN 10 THEN 'Green '
|
WHEN 10 THEN 'Green '
|
||||||
|
WHEN 15 THEN 'Blue '
|
||||||
WHEN 20 THEN 'Purple '
|
WHEN 20 THEN 'Purple '
|
||||||
WHEN 25 THEN 'Orange '
|
WHEN 25 THEN 'Orange '
|
||||||
ELSE ''
|
ELSE ''
|
||||||
@@ -794,6 +796,7 @@ INSERT INTO generated_loot_tiers
|
|||||||
(item_level, dungeon_id, raid_id, dungeon_difficulty_id, raid_difficulty_id, recipe_base, craft_quantity)
|
(item_level, dungeon_id, raid_id, dungeon_difficulty_id, raid_difficulty_id, recipe_base, craft_quantity)
|
||||||
VALUES
|
VALUES
|
||||||
(10, 3, 2, 2, 101, 1100, 2),
|
(10, 3, 2, 2, 101, 1100, 2),
|
||||||
|
(15, 4, 5, 3, 103, 1200, 3),
|
||||||
(20, 6, 7, 4, 104, 1300, 4),
|
(20, 6, 7, 4, 104, 1300, 4),
|
||||||
(25, 8, 9, 5, 105, 1400, 5);
|
(25, 8, 9, 5, 105, 1400, 5);
|
||||||
|
|
||||||
@@ -1347,18 +1350,20 @@ WHERE recipe_id IN (
|
|||||||
SELECT crafting_recipes.id
|
SELECT crafting_recipes.id
|
||||||
FROM crafting_recipes
|
FROM crafting_recipes
|
||||||
JOIN items ON items.id = crafting_recipes.item_id
|
JOIN items ON items.id = crafting_recipes.item_id
|
||||||
WHERE items.item_level NOT IN (1, 10, 20, 25)
|
WHERE items.item_level NOT IN (1, 5, 10, 15, 20, 25)
|
||||||
);
|
);
|
||||||
|
|
||||||
DELETE FROM crafting_recipes
|
DELETE FROM crafting_recipes
|
||||||
WHERE item_id IN (
|
WHERE item_id IN (
|
||||||
SELECT id FROM items WHERE item_level NOT IN (1, 10, 20, 25)
|
SELECT id FROM items WHERE item_level NOT IN (1, 5, 10, 15, 20, 25)
|
||||||
);
|
);
|
||||||
|
|
||||||
UPDATE items
|
UPDATE items
|
||||||
SET rarity = CASE item_level
|
SET rarity = CASE item_level
|
||||||
WHEN 1 THEN 'common'
|
WHEN 1 THEN 'common'
|
||||||
|
WHEN 5 THEN 'uncommon'
|
||||||
WHEN 10 THEN 'uncommon'
|
WHEN 10 THEN 'uncommon'
|
||||||
|
WHEN 15 THEN 'rare'
|
||||||
WHEN 20 THEN 'epic'
|
WHEN 20 THEN 'epic'
|
||||||
WHEN 25 THEN 'legendary'
|
WHEN 25 THEN 'legendary'
|
||||||
ELSE rarity
|
ELSE rarity
|
||||||
@@ -1370,7 +1375,9 @@ SET name = (
|
|||||||
SELECT
|
SELECT
|
||||||
CASE items.item_level
|
CASE items.item_level
|
||||||
WHEN 1 THEN 'Raw '
|
WHEN 1 THEN 'Raw '
|
||||||
|
WHEN 5 THEN 'Honed '
|
||||||
WHEN 10 THEN 'Green '
|
WHEN 10 THEN 'Green '
|
||||||
|
WHEN 15 THEN 'Blue '
|
||||||
WHEN 20 THEN 'Purple '
|
WHEN 20 THEN 'Purple '
|
||||||
WHEN 25 THEN 'Orange '
|
WHEN 25 THEN 'Orange '
|
||||||
ELSE ''
|
ELSE ''
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const TICK_MS = 700
|
|||||||
type RoguelikeMode = 'dungeon' | 'raid'
|
type RoguelikeMode = 'dungeon' | 'raid'
|
||||||
type RoguelikeUpgradeTiming = 'boss' | 'encounter'
|
type RoguelikeUpgradeTiming = 'boss' | 'encounter'
|
||||||
type RoguelikeAbilityLabelMode = 'ability' | 'slot'
|
type RoguelikeAbilityLabelMode = 'ability' | 'slot'
|
||||||
type SlotKey = '1' | '2' | '3' | '4' | '5'
|
type SlotKey = '1' | '2' | '3' | '4' | '5' | '6'
|
||||||
type RoguelikeMechanic =
|
type RoguelikeMechanic =
|
||||||
| 'party-pulse'
|
| 'party-pulse'
|
||||||
| 'searing-mark'
|
| 'searing-mark'
|
||||||
@@ -120,11 +120,15 @@ function memberHotEffects(member: PartyMember) {
|
|||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function effectId(prefix: string) {
|
||||||
|
return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`
|
||||||
|
}
|
||||||
|
|
||||||
function addHotEffect(member: PartyMember, spell: Spell, ticks = 5) {
|
function addHotEffect(member: PartyMember, spell: Spell, ticks = 5) {
|
||||||
return [
|
return [
|
||||||
...memberHotEffects(member),
|
...memberHotEffects(member),
|
||||||
{
|
{
|
||||||
id: `${spell.id}-${crypto.randomUUID()}`,
|
id: effectId(spell.id),
|
||||||
label: spell.name,
|
label: spell.name,
|
||||||
ticks,
|
ticks,
|
||||||
power: Math.max(1, Math.round(spell.power / 2)),
|
power: Math.max(1, Math.round(spell.power / 2)),
|
||||||
@@ -136,7 +140,7 @@ function addBounceHeal(member: PartyMember, spell: Spell) {
|
|||||||
return [
|
return [
|
||||||
...(member.bounceHeals ?? []),
|
...(member.bounceHeals ?? []),
|
||||||
{
|
{
|
||||||
id: `${spell.id}-${crypto.randomUUID()}`,
|
id: effectId(spell.id),
|
||||||
label: spell.name,
|
label: spell.name,
|
||||||
charges: 4,
|
charges: 4,
|
||||||
power: spell.power,
|
power: spell.power,
|
||||||
@@ -164,7 +168,7 @@ function buildRoguelikeUpgrades(
|
|||||||
spells: Spell[],
|
spells: Spell[],
|
||||||
labelMode: RoguelikeAbilityLabelMode,
|
labelMode: RoguelikeAbilityLabelMode,
|
||||||
): RoguelikeUpgrade[] {
|
): RoguelikeUpgrade[] {
|
||||||
const slotUpgrades = (['1', '2', '3', '4', '5'] as SlotKey[]).flatMap((slot) => {
|
const slotUpgrades = (['1', '2', '3', '4', '5', '6'] as SlotKey[]).flatMap((slot) => {
|
||||||
const label = slotLabel(slot, spells, labelMode)
|
const label = slotLabel(slot, spells, labelMode)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function CustomizeScreen({ profile, onBack, onSaved }: Props) {
|
|||||||
function chooseClass(nextClass: GameClass) {
|
function chooseClass(nextClass: GameClass) {
|
||||||
const starterAbilities = nextClass.spells
|
const starterAbilities = nextClass.spells
|
||||||
.filter((ability) => ability.unlockLevel <= profile.character.level)
|
.filter((ability) => ability.unlockLevel <= profile.character.level)
|
||||||
.slice(0, 5)
|
.slice(0, 6)
|
||||||
.map((ability) => ability.id)
|
.map((ability) => ability.id)
|
||||||
setClassId(nextClass.id)
|
setClassId(nextClass.id)
|
||||||
setSlots([...starterAbilities, ...Array(6 - starterAbilities.length).fill(null)])
|
setSlots([...starterAbilities, ...Array(6 - starterAbilities.length).fill(null)])
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type PvpEncounter = DungeonEncounter & {
|
|||||||
sourceEncounterId?: number
|
sourceEncounterId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type SlotKey = '1' | '2' | '3' | '4' | '5'
|
type SlotKey = '1' | '2' | '3' | '4' | '5' | '6'
|
||||||
type AbilityLabelMode = 'ability' | 'slot'
|
type AbilityLabelMode = 'ability' | 'slot'
|
||||||
|
|
||||||
type SelfBuffId =
|
type SelfBuffId =
|
||||||
@@ -164,7 +164,7 @@ function slotLabel(slot: SlotKey, spells: Spell[], labelMode: AbilityLabelMode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildSelfBuffChoices(spells: Spell[], labelMode: AbilityLabelMode): Array<Choice<SelfBuffId>> {
|
function buildSelfBuffChoices(spells: Spell[], labelMode: AbilityLabelMode): Array<Choice<SelfBuffId>> {
|
||||||
const slotChoices = (['1', '2', '3', '4', '5'] as SlotKey[]).flatMap((slot) => {
|
const slotChoices = (['1', '2', '3', '4', '5', '6'] as SlotKey[]).flatMap((slot) => {
|
||||||
const label = slotLabel(slot, spells, labelMode)
|
const label = slotLabel(slot, spells, labelMode)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -193,7 +193,7 @@ function buildSelfBuffChoices(spells: Spell[], labelMode: AbilityLabelMode): Arr
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildOpponentDebuffChoices(spells: Spell[], labelMode: AbilityLabelMode): Array<Choice<OpponentDebuffId>> {
|
function buildOpponentDebuffChoices(spells: Spell[], labelMode: AbilityLabelMode): Array<Choice<OpponentDebuffId>> {
|
||||||
const slotChoices = (['1', '2', '3', '4', '5'] as SlotKey[]).flatMap((slot) => {
|
const slotChoices = (['1', '2', '3', '4', '5', '6'] as SlotKey[]).flatMap((slot) => {
|
||||||
const label = slotLabel(slot, spells, labelMode)
|
const label = slotLabel(slot, spells, labelMode)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -334,7 +334,7 @@ function scoreSelfBuff(buff: Choice<SelfBuffId>, spells: Spell[]) {
|
|||||||
if (buff.id === 'fifth-cast-free') return 8
|
if (buff.id === 'fifth-cast-free') return 8
|
||||||
if (buff.id === 'group-heal-boost') return 8
|
if (buff.id === 'group-heal-boost') return 8
|
||||||
if (buff.id === 'shield-boost') return 6
|
if (buff.id === 'shield-boost') return 6
|
||||||
const slot = buff.id.match(/slot([1-5])/i)?.[1] as SlotKey | undefined
|
const slot = buff.id.match(/slot([1-6])/i)?.[1] as SlotKey | undefined
|
||||||
const spell = spells.find((candidate) => candidate.key === slot)
|
const spell = spells.find((candidate) => candidate.key === slot)
|
||||||
if (!spell) return 5
|
if (!spell) return 5
|
||||||
if (buff.id.endsWith('extra-target')) {
|
if (buff.id.endsWith('extra-target')) {
|
||||||
@@ -408,7 +408,7 @@ export function PvPRoguelikeScreen({
|
|||||||
const gameClass = profile.classes.find((candidate) => candidate.id === profile.character.classId)!
|
const gameClass = profile.classes.find((candidate) => candidate.id === profile.character.classId)!
|
||||||
const starterSpells = useMemo(() => gameClass.spells
|
const starterSpells = useMemo(() => gameClass.spells
|
||||||
.filter((spell) => spell.unlockLevel === 1)
|
.filter((spell) => spell.unlockLevel === 1)
|
||||||
.slice(0, 5)
|
.slice(0, 6)
|
||||||
.map((spell, index) => toCombatSpell(spell, String(index + 1))), [gameClass.spells])
|
.map((spell, index) => toCombatSpell(spell, String(index + 1))), [gameClass.spells])
|
||||||
const [abilityLabelMode] = useState<AbilityLabelMode>('ability')
|
const [abilityLabelMode] = useState<AbilityLabelMode>('ability')
|
||||||
const selfBuffChoicesCatalog = useMemo(
|
const selfBuffChoicesCatalog = useMemo(
|
||||||
|
|||||||
+10
-1
@@ -118,6 +118,13 @@ function loadRecentSnapshot() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function memberHotEffects(member: PartyMember) {
|
||||||
|
if (member.hotEffects?.length) return member.hotEffects
|
||||||
|
return member.hotTicks > 0
|
||||||
|
? [{ id: 'legacy-renew', label: 'Renew', ticks: member.hotTicks }]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
export function DualScreenProvider({ children }: { children: ReactNode }) {
|
export function DualScreenProvider({ children }: { children: ReactNode }) {
|
||||||
const [enabled, setEnabledState] = useState(
|
const [enabled, setEnabledState] = useState(
|
||||||
() => localStorage.getItem(STORAGE_KEY) === 'true',
|
() => localStorage.getItem(STORAGE_KEY) === 'true',
|
||||||
@@ -599,7 +606,9 @@ export function DualScreenTopCombat({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="member-effects">
|
<div className="member-effects">
|
||||||
{member.hotTicks > 0 && <span className="buff">Renew</span>}
|
{memberHotEffects(member).map((effect) => (
|
||||||
|
<span className="buff" key={effect.id}>{effect.label}</span>
|
||||||
|
))}
|
||||||
{member.debuff && <span className="debuff">{member.debuff}</span>}
|
{member.debuff && <span className="debuff">{member.debuff}</span>}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+51
-30
@@ -103,6 +103,7 @@ const offlineSaveKey = 'chronicle.offlineSave.v1'
|
|||||||
const onlineCacheKey = 'chronicle.onlineCache.v1'
|
const onlineCacheKey = 'chronicle.onlineCache.v1'
|
||||||
const authTokenKey = 'chronicle.authToken.v1'
|
const authTokenKey = 'chronicle.authToken.v1'
|
||||||
const offlineAccount = { id: -1, username: 'Offline' }
|
const offlineAccount = { id: -1, username: 'Offline' }
|
||||||
|
const ABILITY_SLOT_COUNT = 6
|
||||||
|
|
||||||
function clone<T>(value: T): T {
|
function clone<T>(value: T): T {
|
||||||
return structuredClone(value)
|
return structuredClone(value)
|
||||||
@@ -147,7 +148,7 @@ function upgradeV1Save(v1: { profile: CharacterProfile; lootRolls: Record<string
|
|||||||
level: cid === p.character.classId ? p.character.level : 1,
|
level: cid === p.character.classId ? p.character.level : 1,
|
||||||
experience: cid === p.character.classId ? p.character.experience : 0,
|
experience: cid === p.character.classId ? p.character.experience : 0,
|
||||||
talentPoints: cid === p.character.classId ? p.character.talentPoints : 1,
|
talentPoints: cid === p.character.classId ? p.character.talentPoints : 1,
|
||||||
abilitySlots: cid === p.character.classId ? [...p.abilitySlots] : [],
|
abilitySlots: cid === p.character.classId ? normalizeAbilitySlots(p.abilitySlots) : [],
|
||||||
talentRanks,
|
talentRanks,
|
||||||
inventory: cid === p.character.classId ? clone(p.inventory) : [],
|
inventory: cid === p.character.classId ? clone(p.inventory) : [],
|
||||||
}
|
}
|
||||||
@@ -164,11 +165,32 @@ function upgradeV1Save(v1: { profile: CharacterProfile; lootRolls: Record<string
|
|||||||
}
|
}
|
||||||
|
|
||||||
function upgradeV2Save(v2: Omit<OfflineSave, 'version' | 'completedRaidPhases'> & { version: 2 }): OfflineSave {
|
function upgradeV2Save(v2: Omit<OfflineSave, 'version' | 'completedRaidPhases'> & { version: 2 }): OfflineSave {
|
||||||
return {
|
return normalizeSaveAbilitySlots({
|
||||||
...v2,
|
...v2,
|
||||||
version: 3,
|
version: 3,
|
||||||
completedRaidPhases: 0,
|
completedRaidPhases: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAbilitySlots(abilitySlots: unknown): Array<number | null> {
|
||||||
|
const slots = Array.isArray(abilitySlots)
|
||||||
|
? abilitySlots
|
||||||
|
.slice(0, ABILITY_SLOT_COUNT)
|
||||||
|
.map((value) => {
|
||||||
|
if (value === null || value === undefined) return null
|
||||||
|
const id = Number(value)
|
||||||
|
return Number.isInteger(id) ? id : null
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
while (slots.length < ABILITY_SLOT_COUNT) slots.push(null)
|
||||||
|
return slots
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSaveAbilitySlots(save: OfflineSave): OfflineSave {
|
||||||
|
for (const character of Object.values(save.characters)) {
|
||||||
|
character.abilitySlots = normalizeAbilitySlots(character.abilitySlots)
|
||||||
}
|
}
|
||||||
|
return save
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeOfflineSave(raw: unknown): OfflineSave | null {
|
function normalizeOfflineSave(raw: unknown): OfflineSave | null {
|
||||||
@@ -178,12 +200,12 @@ function normalizeOfflineSave(raw: unknown): OfflineSave | null {
|
|||||||
profile?: CharacterProfile
|
profile?: CharacterProfile
|
||||||
lootRolls?: Record<string, LootRoll>
|
lootRolls?: Record<string, LootRoll>
|
||||||
}
|
}
|
||||||
if (candidate.version === 3) return candidate as OfflineSave
|
if (candidate.version === 3) return normalizeSaveAbilitySlots(candidate as OfflineSave)
|
||||||
if (candidate.version === 2) {
|
if (candidate.version === 2) {
|
||||||
return upgradeV2Save(candidate as Omit<OfflineSave, 'version' | 'completedRaidPhases'> & { version: 2 })
|
return upgradeV2Save(candidate as Omit<OfflineSave, 'version' | 'completedRaidPhases'> & { version: 2 })
|
||||||
}
|
}
|
||||||
if (candidate.version === 1 && candidate.profile) {
|
if (candidate.version === 1 && candidate.profile) {
|
||||||
return upgradeV1Save(candidate as { profile: CharacterProfile; lootRolls: Record<string, LootRoll> })
|
return normalizeSaveAbilitySlots(upgradeV1Save(candidate as { profile: CharacterProfile; lootRolls: Record<string, LootRoll> }))
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -448,7 +470,7 @@ function mergeProfileIntoSave(profile: CharacterProfile, existingSave?: OfflineS
|
|||||||
level: profile.character.level,
|
level: profile.character.level,
|
||||||
experience: profile.character.experience,
|
experience: profile.character.experience,
|
||||||
talentPoints: profile.character.talentPoints,
|
talentPoints: profile.character.talentPoints,
|
||||||
abilitySlots: [...profile.abilitySlots],
|
abilitySlots: normalizeAbilitySlots(profile.abilitySlots),
|
||||||
talentRanks,
|
talentRanks,
|
||||||
inventory: clone(profile.inventory),
|
inventory: clone(profile.inventory),
|
||||||
}
|
}
|
||||||
@@ -787,9 +809,9 @@ function emptyCharacterData(classId: number): CharacterData {
|
|||||||
const inventory: Item[] = []
|
const inventory: Item[] = []
|
||||||
const startingAbilitySlots: Array<number | null> = gc.spells
|
const startingAbilitySlots: Array<number | null> = gc.spells
|
||||||
.filter((s) => s.unlockLevel === 1)
|
.filter((s) => s.unlockLevel === 1)
|
||||||
.slice(0, 5)
|
.slice(0, ABILITY_SLOT_COUNT)
|
||||||
.map((s) => s.id)
|
.map((s) => s.id)
|
||||||
while (startingAbilitySlots.length < 6) startingAbilitySlots.push(null)
|
while (startingAbilitySlots.length < ABILITY_SLOT_COUNT) startingAbilitySlots.push(null)
|
||||||
return {
|
return {
|
||||||
level: 1,
|
level: 1,
|
||||||
experience: 0,
|
experience: 0,
|
||||||
@@ -829,31 +851,30 @@ function createLocalRepository(store: LocalSaveStore): GameRepository {
|
|||||||
},
|
},
|
||||||
async saveProfile(classId, abilitySlots) {
|
async saveProfile(classId, abilitySlots) {
|
||||||
const save = requireStoredSave(store)
|
const save = requireStoredSave(store)
|
||||||
const static_ = clone(starterProfile) as CharacterProfile
|
const static_ = clone(starterProfile) as CharacterProfile
|
||||||
const gameClass = static_.classes.find((candidate) => candidate.id === classId)
|
const gameClass = static_.classes.find((candidate) => candidate.id === classId)
|
||||||
if (!gameClass) throw new Error('Selected class does not exist.')
|
if (!gameClass) throw new Error('Selected class does not exist.')
|
||||||
|
|
||||||
const slots = abilitySlots.slice(0, 6)
|
const slots = normalizeAbilitySlots(abilitySlots)
|
||||||
while (slots.length < 6) slots.push(null)
|
const selectedIds = slots.filter((id): id is number => id !== null)
|
||||||
const selectedIds = slots.filter((id): id is number => id !== null)
|
if (new Set(selectedIds).size !== selectedIds.length) {
|
||||||
if (new Set(selectedIds).size !== selectedIds.length) {
|
throw new Error('The same ability cannot be equipped twice.')
|
||||||
throw new Error('The same ability cannot be equipped twice.')
|
}
|
||||||
}
|
const activeChar = save.characters[save.activeClassId]
|
||||||
const activeChar = save.characters[save.activeClassId]
|
const validIds = new Set(
|
||||||
const validIds = new Set(
|
gameClass.spells
|
||||||
gameClass.spells
|
.filter((spell) => spell.unlockLevel <= activeChar.level)
|
||||||
.filter((spell) => spell.unlockLevel <= activeChar.level)
|
.map((spell) => spell.id),
|
||||||
.map((spell) => spell.id),
|
)
|
||||||
)
|
if (selectedIds.some((id) => !validIds.has(id))) {
|
||||||
if (selectedIds.some((id) => !validIds.has(id))) {
|
throw new Error('One or more abilities are locked or belong to another class.')
|
||||||
throw new Error('One or more abilities are locked or belong to another class.')
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!save.characters[classId]) {
|
if (!save.characters[classId]) {
|
||||||
save.characters[classId] = emptyCharacterData(classId)
|
save.characters[classId] = emptyCharacterData(classId)
|
||||||
}
|
}
|
||||||
save.characters[classId].abilitySlots = slots
|
save.characters[classId].abilitySlots = slots
|
||||||
save.activeClassId = classId
|
save.activeClassId = classId
|
||||||
store.writeSave(save)
|
store.writeSave(save)
|
||||||
return buildProfile(save)
|
return buildProfile(save)
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user