Android build v1.0.34
This commit is contained in:
Binary file not shown.
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.warren.iwanttoheal"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 52
|
||||
versionName "1.0.33"
|
||||
versionCode 53
|
||||
versionName "1.0.34"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// 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
|
||||
CASE items.item_level
|
||||
WHEN 1 THEN 'Raw '
|
||||
WHEN 5 THEN 'Honed '
|
||||
WHEN 10 THEN 'Green '
|
||||
WHEN 15 THEN 'Blue '
|
||||
WHEN 20 THEN 'Purple '
|
||||
WHEN 25 THEN 'Orange '
|
||||
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)
|
||||
VALUES
|
||||
(10, 3, 2, 2, 101, 1100, 2),
|
||||
(15, 4, 5, 3, 103, 1200, 3),
|
||||
(20, 6, 7, 4, 104, 1300, 4),
|
||||
(25, 8, 9, 5, 105, 1400, 5);
|
||||
|
||||
@@ -1347,18 +1350,20 @@ WHERE recipe_id IN (
|
||||
SELECT crafting_recipes.id
|
||||
FROM crafting_recipes
|
||||
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
|
||||
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
|
||||
SET rarity = CASE item_level
|
||||
WHEN 1 THEN 'common'
|
||||
WHEN 5 THEN 'uncommon'
|
||||
WHEN 10 THEN 'uncommon'
|
||||
WHEN 15 THEN 'rare'
|
||||
WHEN 20 THEN 'epic'
|
||||
WHEN 25 THEN 'legendary'
|
||||
ELSE rarity
|
||||
@@ -1370,7 +1375,9 @@ SET name = (
|
||||
SELECT
|
||||
CASE items.item_level
|
||||
WHEN 1 THEN 'Raw '
|
||||
WHEN 5 THEN 'Honed '
|
||||
WHEN 10 THEN 'Green '
|
||||
WHEN 15 THEN 'Blue '
|
||||
WHEN 20 THEN 'Purple '
|
||||
WHEN 25 THEN 'Orange '
|
||||
ELSE ''
|
||||
|
||||
@@ -43,7 +43,7 @@ const TICK_MS = 700
|
||||
type RoguelikeMode = 'dungeon' | 'raid'
|
||||
type RoguelikeUpgradeTiming = 'boss' | 'encounter'
|
||||
type RoguelikeAbilityLabelMode = 'ability' | 'slot'
|
||||
type SlotKey = '1' | '2' | '3' | '4' | '5'
|
||||
type SlotKey = '1' | '2' | '3' | '4' | '5' | '6'
|
||||
type RoguelikeMechanic =
|
||||
| 'party-pulse'
|
||||
| '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) {
|
||||
return [
|
||||
...memberHotEffects(member),
|
||||
{
|
||||
id: `${spell.id}-${crypto.randomUUID()}`,
|
||||
id: effectId(spell.id),
|
||||
label: spell.name,
|
||||
ticks,
|
||||
power: Math.max(1, Math.round(spell.power / 2)),
|
||||
@@ -136,7 +140,7 @@ function addBounceHeal(member: PartyMember, spell: Spell) {
|
||||
return [
|
||||
...(member.bounceHeals ?? []),
|
||||
{
|
||||
id: `${spell.id}-${crypto.randomUUID()}`,
|
||||
id: effectId(spell.id),
|
||||
label: spell.name,
|
||||
charges: 4,
|
||||
power: spell.power,
|
||||
@@ -164,7 +168,7 @@ function buildRoguelikeUpgrades(
|
||||
spells: Spell[],
|
||||
labelMode: RoguelikeAbilityLabelMode,
|
||||
): 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)
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ export function CustomizeScreen({ profile, onBack, onSaved }: Props) {
|
||||
function chooseClass(nextClass: GameClass) {
|
||||
const starterAbilities = nextClass.spells
|
||||
.filter((ability) => ability.unlockLevel <= profile.character.level)
|
||||
.slice(0, 5)
|
||||
.slice(0, 6)
|
||||
.map((ability) => ability.id)
|
||||
setClassId(nextClass.id)
|
||||
setSlots([...starterAbilities, ...Array(6 - starterAbilities.length).fill(null)])
|
||||
|
||||
@@ -44,7 +44,7 @@ type PvpEncounter = DungeonEncounter & {
|
||||
sourceEncounterId?: number
|
||||
}
|
||||
|
||||
type SlotKey = '1' | '2' | '3' | '4' | '5'
|
||||
type SlotKey = '1' | '2' | '3' | '4' | '5' | '6'
|
||||
type AbilityLabelMode = 'ability' | 'slot'
|
||||
|
||||
type SelfBuffId =
|
||||
@@ -164,7 +164,7 @@ function slotLabel(slot: SlotKey, spells: Spell[], labelMode: AbilityLabelMode)
|
||||
}
|
||||
|
||||
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)
|
||||
return [
|
||||
{
|
||||
@@ -193,7 +193,7 @@ function buildSelfBuffChoices(spells: Spell[], labelMode: AbilityLabelMode): Arr
|
||||
}
|
||||
|
||||
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)
|
||||
return [
|
||||
{
|
||||
@@ -334,7 +334,7 @@ function scoreSelfBuff(buff: Choice<SelfBuffId>, spells: Spell[]) {
|
||||
if (buff.id === 'fifth-cast-free') return 8
|
||||
if (buff.id === 'group-heal-boost') return 8
|
||||
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)
|
||||
if (!spell) return 5
|
||||
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 starterSpells = useMemo(() => gameClass.spells
|
||||
.filter((spell) => spell.unlockLevel === 1)
|
||||
.slice(0, 5)
|
||||
.slice(0, 6)
|
||||
.map((spell, index) => toCombatSpell(spell, String(index + 1))), [gameClass.spells])
|
||||
const [abilityLabelMode] = useState<AbilityLabelMode>('ability')
|
||||
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 }) {
|
||||
const [enabled, setEnabledState] = useState(
|
||||
() => localStorage.getItem(STORAGE_KEY) === 'true',
|
||||
@@ -599,7 +606,9 @@ export function DualScreenTopCombat({
|
||||
</div>
|
||||
)}
|
||||
<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>}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
+30
-9
@@ -103,6 +103,7 @@ const offlineSaveKey = 'chronicle.offlineSave.v1'
|
||||
const onlineCacheKey = 'chronicle.onlineCache.v1'
|
||||
const authTokenKey = 'chronicle.authToken.v1'
|
||||
const offlineAccount = { id: -1, username: 'Offline' }
|
||||
const ABILITY_SLOT_COUNT = 6
|
||||
|
||||
function clone<T>(value: T): T {
|
||||
return structuredClone(value)
|
||||
@@ -147,7 +148,7 @@ function upgradeV1Save(v1: { profile: CharacterProfile; lootRolls: Record<string
|
||||
level: cid === p.character.classId ? p.character.level : 1,
|
||||
experience: cid === p.character.classId ? p.character.experience : 0,
|
||||
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,
|
||||
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 {
|
||||
return {
|
||||
return normalizeSaveAbilitySlots({
|
||||
...v2,
|
||||
version: 3,
|
||||
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 {
|
||||
@@ -178,12 +200,12 @@ function normalizeOfflineSave(raw: unknown): OfflineSave | null {
|
||||
profile?: CharacterProfile
|
||||
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) {
|
||||
return upgradeV2Save(candidate as Omit<OfflineSave, 'version' | 'completedRaidPhases'> & { version: 2 })
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -448,7 +470,7 @@ function mergeProfileIntoSave(profile: CharacterProfile, existingSave?: OfflineS
|
||||
level: profile.character.level,
|
||||
experience: profile.character.experience,
|
||||
talentPoints: profile.character.talentPoints,
|
||||
abilitySlots: [...profile.abilitySlots],
|
||||
abilitySlots: normalizeAbilitySlots(profile.abilitySlots),
|
||||
talentRanks,
|
||||
inventory: clone(profile.inventory),
|
||||
}
|
||||
@@ -787,9 +809,9 @@ function emptyCharacterData(classId: number): CharacterData {
|
||||
const inventory: Item[] = []
|
||||
const startingAbilitySlots: Array<number | null> = gc.spells
|
||||
.filter((s) => s.unlockLevel === 1)
|
||||
.slice(0, 5)
|
||||
.slice(0, ABILITY_SLOT_COUNT)
|
||||
.map((s) => s.id)
|
||||
while (startingAbilitySlots.length < 6) startingAbilitySlots.push(null)
|
||||
while (startingAbilitySlots.length < ABILITY_SLOT_COUNT) startingAbilitySlots.push(null)
|
||||
return {
|
||||
level: 1,
|
||||
experience: 0,
|
||||
@@ -833,8 +855,7 @@ function createLocalRepository(store: LocalSaveStore): GameRepository {
|
||||
const gameClass = static_.classes.find((candidate) => candidate.id === classId)
|
||||
if (!gameClass) throw new Error('Selected class does not exist.')
|
||||
|
||||
const slots = abilitySlots.slice(0, 6)
|
||||
while (slots.length < 6) slots.push(null)
|
||||
const slots = normalizeAbilitySlots(abilitySlots)
|
||||
const selectedIds = slots.filter((id): id is number => id !== null)
|
||||
if (new Set(selectedIds).size !== selectedIds.length) {
|
||||
throw new Error('The same ability cannot be equipped twice.')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user