Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5aac39c6c9 | |||
| 224249e372 | |||
| 66f5af4484 | |||
| 8f5a957963 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
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 58
|
versionCode 62
|
||||||
versionName "1.0.38"
|
versionName "1.0.42"
|
||||||
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.
|
||||||
|
|||||||
+461
-38
@@ -106,9 +106,8 @@ SET slug = 'rathian',
|
|||||||
image_url = COALESCE(NULLIF(image_url, ''), '/boss-placeholder.svg')
|
image_url = COALESCE(NULLIF(image_url, ''), '/boss-placeholder.svg')
|
||||||
WHERE id = 22;
|
WHERE id = 22;
|
||||||
|
|
||||||
INSERT OR IGNORE INTO mechanics
|
WITH mechanics_seed(id, encounter_id, name, mechanic_type, interval_seconds, power, description) AS (
|
||||||
(id, encounter_id, name, mechanic_type, interval_seconds, power, description)
|
VALUES
|
||||||
VALUES
|
|
||||||
(1, 3, 'Cinder Pulse', 'party_damage', 4.9, 12, 'Deals damage to the full party.'),
|
(1, 3, 'Cinder Pulse', 'party_damage', 4.9, 12, 'Deals damage to the full party.'),
|
||||||
(2, 3, 'Searing Mark', 'dispellable_dot', 7.7, 7, 'Marks one target with recurring damage until cleansed.'),
|
(2, 3, 'Searing Mark', 'dispellable_dot', 7.7, 7, 'Marks one target with recurring damage until cleansed.'),
|
||||||
(10, 12, 'Ember Wave', 'party_damage', 5.6, 14, 'A wave of ember energy hits the entire party.'),
|
(10, 12, 'Ember Wave', 'party_damage', 5.6, 14, 'A wave of ember energy hits the entire party.'),
|
||||||
@@ -120,7 +119,17 @@ VALUES
|
|||||||
(102, 105, 'Pillar of Judgment', 'party_damage', 5.2, 16, 'Columns of flame erupt beneath the raid.'),
|
(102, 105, 'Pillar of Judgment', 'party_damage', 5.2, 16, 'Columns of flame erupt beneath the raid.'),
|
||||||
(103, 105, 'Inquisitor''s Brand', 'dispellable_dot', 7.8, 10, 'A burning brand persists until cleansed.'),
|
(103, 105, 'Inquisitor''s Brand', 'dispellable_dot', 7.8, 10, 'A burning brand persists until cleansed.'),
|
||||||
(104, 108, 'Crownflare', 'party_damage', 4.9, 18, 'The Ember Crown releases a raid-wide flare.'),
|
(104, 108, 'Crownflare', 'party_damage', 4.9, 18, 'The Ember Crown releases a raid-wide flare.'),
|
||||||
(105, 108, 'Royal Decree', 'dispellable_dot', 7.1, 12, 'A lethal decree marks one raider for cleansing.');
|
(105, 108, 'Royal Decree', 'dispellable_dot', 7.1, 12, 'A lethal decree marks one raider for cleansing.')
|
||||||
|
)
|
||||||
|
INSERT OR IGNORE INTO mechanics
|
||||||
|
(id, encounter_id, name, mechanic_type, interval_seconds, power, description)
|
||||||
|
SELECT id, encounter_id, name, mechanic_type, interval_seconds, power, description
|
||||||
|
FROM mechanics_seed
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM encounters
|
||||||
|
WHERE encounters.id = mechanics_seed.encounter_id
|
||||||
|
);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO classes
|
INSERT OR IGNORE INTO classes
|
||||||
(id, slug, name, resource_name, max_resource, theme_color, description)
|
(id, slug, name, resource_name, max_resource, theme_color, description)
|
||||||
@@ -392,6 +401,16 @@ UPDATE items SET glyph = '/' WHERE slug = 'ashwood-crook';
|
|||||||
UPDATE items SET glyph = 'b' WHERE slug = 'cinderstep-boots';
|
UPDATE items SET glyph = 'b' WHERE slug = 'cinderstep-boots';
|
||||||
UPDATE items SET glyph = '^' WHERE slug = 'adepts-hood';
|
UPDATE items SET glyph = '^' WHERE slug = 'adepts-hood';
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO encounters
|
||||||
|
(id, dungeon_id, sequence, slug, name, encounter_type, max_health, base_damage, tank_damage, party_damage, description)
|
||||||
|
VALUES
|
||||||
|
(3, 1, 9003, 'legacy-loot-encounter-3', 'Legacy Loot Encounter 3', 'boss', 1, 1, 1, 1, 'Temporary legacy seed row.'),
|
||||||
|
(12, 1, 9012, 'legacy-loot-encounter-12', 'Legacy Loot Encounter 12', 'boss', 1, 1, 1, 1, 'Temporary legacy seed row.'),
|
||||||
|
(22, 1, 9022, 'legacy-loot-encounter-22', 'Legacy Loot Encounter 22', 'boss', 1, 1, 1, 1, 'Temporary legacy seed row.'),
|
||||||
|
(102, 2, 9102, 'legacy-loot-encounter-102', 'Legacy Loot Encounter 102', 'boss', 1, 1, 1, 1, 'Temporary legacy seed row.'),
|
||||||
|
(105, 2, 9105, 'legacy-loot-encounter-105', 'Legacy Loot Encounter 105', 'boss', 1, 1, 1, 1, 'Temporary legacy seed row.'),
|
||||||
|
(108, 2, 9108, 'legacy-loot-encounter-108', 'Legacy Loot Encounter 108', 'boss', 1, 1, 1, 1, 'Temporary legacy seed row.');
|
||||||
|
|
||||||
DELETE FROM encounter_loot;
|
DELETE FROM encounter_loot;
|
||||||
|
|
||||||
INSERT INTO encounter_loot
|
INSERT INTO encounter_loot
|
||||||
@@ -516,20 +535,20 @@ WHERE character_id IN (1, 2, 3)
|
|||||||
-- Coin gearing override: every boss/difficulty drops one boss coin, and each
|
-- Coin gearing override: every boss/difficulty drops one boss coin, and each
|
||||||
-- craft costs the target item level in that source boss coin.
|
-- craft costs the target item level in that source boss coin.
|
||||||
UPDATE crafting_recipes
|
UPDATE crafting_recipes
|
||||||
SET source_dungeon_id = 1,
|
SET source_dungeon_id = CASE WHEN EXISTS (SELECT 1 FROM encounters WHERE id = 3) THEN 1 ELSE NULL END,
|
||||||
source_encounter_id = 3
|
source_encounter_id = (SELECT id FROM encounters WHERE id = 3)
|
||||||
WHERE id BETWEEN 1001 AND 1409
|
WHERE id BETWEEN 1001 AND 1409
|
||||||
AND item_id IN (SELECT id FROM items WHERE slot IN ('helmet', 'chest', 'gloves'));
|
AND item_id IN (SELECT id FROM items WHERE slot IN ('helmet', 'chest', 'gloves'));
|
||||||
|
|
||||||
UPDATE crafting_recipes
|
UPDATE crafting_recipes
|
||||||
SET source_dungeon_id = 1,
|
SET source_dungeon_id = CASE WHEN EXISTS (SELECT 1 FROM encounters WHERE id = 12) THEN 1 ELSE NULL END,
|
||||||
source_encounter_id = 12
|
source_encounter_id = (SELECT id FROM encounters WHERE id = 12)
|
||||||
WHERE id BETWEEN 1001 AND 1409
|
WHERE id BETWEEN 1001 AND 1409
|
||||||
AND item_id IN (SELECT id FROM items WHERE slot IN ('boots', 'ring', 'trinket'));
|
AND item_id IN (SELECT id FROM items WHERE slot IN ('boots', 'ring', 'trinket'));
|
||||||
|
|
||||||
UPDATE crafting_recipes
|
UPDATE crafting_recipes
|
||||||
SET source_dungeon_id = 1,
|
SET source_dungeon_id = CASE WHEN EXISTS (SELECT 1 FROM encounters WHERE id = 22) THEN 1 ELSE NULL END,
|
||||||
source_encounter_id = 22
|
source_encounter_id = (SELECT id FROM encounters WHERE id = 22)
|
||||||
WHERE id BETWEEN 1001 AND 1409
|
WHERE id BETWEEN 1001 AND 1409
|
||||||
AND item_id IN (SELECT id FROM items WHERE slot IN ('weapon', 'pants', 'necklace'));
|
AND item_id IN (SELECT id FROM items WHERE slot IN ('weapon', 'pants', 'necklace'));
|
||||||
|
|
||||||
@@ -557,7 +576,7 @@ SET rarity = CASE item_level
|
|||||||
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
||||||
|
|
||||||
UPDATE items
|
UPDATE items
|
||||||
SET name = (
|
SET name = COALESCE((
|
||||||
SELECT
|
SELECT
|
||||||
CASE items.item_level
|
CASE items.item_level
|
||||||
WHEN 1 THEN 'Raw '
|
WHEN 1 THEN 'Raw '
|
||||||
@@ -585,14 +604,14 @@ SET name = (
|
|||||||
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
||||||
WHERE crafting_recipes.item_id = items.id
|
WHERE crafting_recipes.item_id = items.id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
),
|
), name),
|
||||||
description = (
|
description = COALESCE((
|
||||||
SELECT 'Crafted with ' || encounters.name || ' coins.'
|
SELECT 'Crafted with ' || encounters.name || ' coins.'
|
||||||
FROM crafting_recipes
|
FROM crafting_recipes
|
||||||
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
||||||
WHERE crafting_recipes.item_id = items.id
|
WHERE crafting_recipes.item_id = items.id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
), description)
|
||||||
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
||||||
|
|
||||||
CREATE TEMP TABLE IF NOT EXISTS coin_sources (
|
CREATE TEMP TABLE IF NOT EXISTS coin_sources (
|
||||||
@@ -611,11 +630,11 @@ DELETE FROM coin_sources;
|
|||||||
|
|
||||||
INSERT INTO coin_sources
|
INSERT INTO coin_sources
|
||||||
SELECT
|
SELECT
|
||||||
280000 + encounters.id * 100 + difficulties.dropped_item_level,
|
280000 + encounters.id * 1000 + difficulties.id,
|
||||||
encounters.id,
|
encounters.id,
|
||||||
difficulties.id,
|
difficulties.id,
|
||||||
difficulties.dropped_item_level,
|
difficulties.dropped_item_level,
|
||||||
encounters.slug || '-coin-ilvl-' || difficulties.dropped_item_level,
|
encounters.slug || '-coin-diff-' || difficulties.id || '-ilvl-' || difficulties.dropped_item_level,
|
||||||
CASE difficulties.dropped_item_level
|
CASE difficulties.dropped_item_level
|
||||||
WHEN 1 THEN 'Raw '
|
WHEN 1 THEN 'Raw '
|
||||||
WHEN 5 THEN 'Honed '
|
WHEN 5 THEN 'Honed '
|
||||||
@@ -647,6 +666,18 @@ INSERT OR IGNORE INTO items
|
|||||||
SELECT item_id, slug, name, 'component', rarity, item_level, 0, 0, glyph, description
|
SELECT item_id, slug, name, 'component', rarity, item_level, 0, 0, glyph, description
|
||||||
FROM coin_sources;
|
FROM coin_sources;
|
||||||
|
|
||||||
|
UPDATE coin_sources
|
||||||
|
SET item_id = (
|
||||||
|
SELECT items.id
|
||||||
|
FROM items
|
||||||
|
WHERE items.slug = coin_sources.slug
|
||||||
|
)
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM items
|
||||||
|
WHERE items.slug = coin_sources.slug
|
||||||
|
);
|
||||||
|
|
||||||
UPDATE items
|
UPDATE items
|
||||||
SET slug = (SELECT slug FROM coin_sources WHERE coin_sources.item_id = items.id),
|
SET slug = (SELECT slug FROM coin_sources WHERE coin_sources.item_id = items.id),
|
||||||
name = (SELECT name FROM coin_sources WHERE coin_sources.item_id = items.id),
|
name = (SELECT name FROM coin_sources WHERE coin_sources.item_id = items.id),
|
||||||
@@ -859,8 +890,8 @@ INSERT OR IGNORE INTO locations (id, slug, name, description) VALUES
|
|||||||
(3, 'monster-frontier', 'The Monster Frontier', 'Hunting grounds used for tiered monster-part progression.');
|
(3, 'monster-frontier', 'The Monster Frontier', 'Hunting grounds used for tiered monster-part progression.');
|
||||||
|
|
||||||
UPDATE dungeons
|
UPDATE dungeons
|
||||||
SET slug = 'tigrex-raid',
|
SET slug = 'legacy-generated-raid-2',
|
||||||
name = 'Tigrex Raid',
|
name = 'Legacy Generated Raid 2',
|
||||||
location_id = 3,
|
location_id = 3,
|
||||||
recommended_level = 5,
|
recommended_level = 5,
|
||||||
content_type = 'raid',
|
content_type = 'raid',
|
||||||
@@ -943,6 +974,68 @@ SELECT dungeon_id, dungeon_difficulty_id FROM generated_loot_tiers
|
|||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT raid_id, raid_difficulty_id FROM generated_loot_tiers;
|
SELECT raid_id, raid_difficulty_id FROM generated_loot_tiers;
|
||||||
|
|
||||||
|
UPDATE crafting_recipes
|
||||||
|
SET source_dungeon_id = NULL,
|
||||||
|
source_encounter_id = NULL
|
||||||
|
WHERE source_dungeon_id IN (
|
||||||
|
SELECT dungeon_id FROM generated_loot_tiers
|
||||||
|
UNION
|
||||||
|
SELECT raid_id FROM generated_loot_tiers
|
||||||
|
)
|
||||||
|
OR source_encounter_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM encounters
|
||||||
|
WHERE dungeon_id IN (
|
||||||
|
SELECT dungeon_id FROM generated_loot_tiers
|
||||||
|
UNION
|
||||||
|
SELECT raid_id FROM generated_loot_tiers
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM encounter_loot_roll_items
|
||||||
|
WHERE roll_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM encounter_loot_rolls
|
||||||
|
WHERE encounter_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM encounters
|
||||||
|
WHERE dungeon_id IN (
|
||||||
|
SELECT dungeon_id FROM generated_loot_tiers
|
||||||
|
UNION
|
||||||
|
SELECT raid_id FROM generated_loot_tiers
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM encounter_loot_rolls
|
||||||
|
WHERE encounter_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM encounters
|
||||||
|
WHERE dungeon_id IN (
|
||||||
|
SELECT dungeon_id FROM generated_loot_tiers
|
||||||
|
UNION
|
||||||
|
SELECT raid_id FROM generated_loot_tiers
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM encounter_loot
|
||||||
|
WHERE encounter_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM encounters
|
||||||
|
WHERE dungeon_id IN (
|
||||||
|
SELECT dungeon_id FROM generated_loot_tiers
|
||||||
|
UNION
|
||||||
|
SELECT raid_id FROM generated_loot_tiers
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM encounters
|
||||||
|
WHERE dungeon_id IN (
|
||||||
|
SELECT dungeon_id FROM generated_loot_tiers
|
||||||
|
UNION
|
||||||
|
SELECT raid_id FROM generated_loot_tiers
|
||||||
|
);
|
||||||
|
|
||||||
UPDATE encounters
|
UPDATE encounters
|
||||||
SET slug = CASE id
|
SET slug = CASE id
|
||||||
WHEN 100 THEN 'tigrex-raid-approach'
|
WHEN 100 THEN 'tigrex-raid-approach'
|
||||||
@@ -975,7 +1068,23 @@ SET slug = CASE id
|
|||||||
WHEN 108 THEN 'Gypceros drops boss coins for item level 10 crafting.'
|
WHEN 108 THEN 'Gypceros drops boss coins for item level 10 crafting.'
|
||||||
ELSE 'Hunters clear the raid path.'
|
ELSE 'Hunters clear the raid path.'
|
||||||
END
|
END
|
||||||
WHERE id BETWEEN 100 AND 108;
|
WHERE id BETWEEN 100 AND 108
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM encounters AS conflict
|
||||||
|
WHERE conflict.id NOT BETWEEN 100 AND 108
|
||||||
|
AND conflict.slug IN (
|
||||||
|
'tigrex-raid-approach',
|
||||||
|
'tigrex-raid-guardians',
|
||||||
|
'tigrex-raid',
|
||||||
|
'rathalos-raid-approach',
|
||||||
|
'rathalos-raid-guardians',
|
||||||
|
'rathalos-raid',
|
||||||
|
'gypceros-raid-approach',
|
||||||
|
'gypceros-raid-guardians',
|
||||||
|
'gypceros-raid'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO encounters
|
INSERT OR IGNORE INTO encounters
|
||||||
(id, dungeon_id, sequence, slug, name, encounter_type, max_health, base_damage, tank_damage, party_damage, description)
|
(id, dungeon_id, sequence, slug, name, encounter_type, max_health, base_damage, tank_damage, party_damage, description)
|
||||||
@@ -1063,7 +1172,12 @@ SELECT
|
|||||||
1.0
|
1.0
|
||||||
FROM generated_loot_tiers
|
FROM generated_loot_tiers
|
||||||
JOIN generated_bosses ON generated_bosses.item_level = generated_loot_tiers.item_level
|
JOIN generated_bosses ON generated_bosses.item_level = generated_loot_tiers.item_level
|
||||||
JOIN generated_drop_patterns ON generated_drop_patterns.boss_index = generated_bosses.boss_index;
|
JOIN generated_drop_patterns ON generated_drop_patterns.boss_index = generated_bosses.boss_index
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM encounters
|
||||||
|
WHERE encounters.id = generated_loot_tiers.dungeon_id * 100 + generated_bosses.boss_index * 3 + 3
|
||||||
|
);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO encounter_loot (encounter_id, item_id, difficulty_id, drop_weight, drop_chance)
|
INSERT OR IGNORE INTO encounter_loot (encounter_id, item_id, difficulty_id, drop_weight, drop_chance)
|
||||||
SELECT
|
SELECT
|
||||||
@@ -1077,7 +1191,15 @@ SELECT
|
|||||||
1.0
|
1.0
|
||||||
FROM generated_loot_tiers
|
FROM generated_loot_tiers
|
||||||
JOIN generated_bosses ON generated_bosses.item_level = generated_loot_tiers.item_level
|
JOIN generated_bosses ON generated_bosses.item_level = generated_loot_tiers.item_level
|
||||||
JOIN generated_drop_patterns ON generated_drop_patterns.boss_index = generated_bosses.boss_index;
|
JOIN generated_drop_patterns ON generated_drop_patterns.boss_index = generated_bosses.boss_index
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM encounters
|
||||||
|
WHERE encounters.id = CASE generated_loot_tiers.raid_id
|
||||||
|
WHEN 2 THEN 102 + generated_bosses.boss_index * 3
|
||||||
|
ELSE generated_loot_tiers.raid_id * 100 + generated_bosses.boss_index * 3 + 3
|
||||||
|
END
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TEMP TABLE IF NOT EXISTS generated_recipe_offsets (
|
CREATE TEMP TABLE IF NOT EXISTS generated_recipe_offsets (
|
||||||
recipe_offset INTEGER PRIMARY KEY,
|
recipe_offset INTEGER PRIMARY KEY,
|
||||||
@@ -1105,12 +1227,8 @@ SET source_dungeon_id = (
|
|||||||
WHERE id BETWEEN 1101 AND 1409;
|
WHERE id BETWEEN 1101 AND 1409;
|
||||||
|
|
||||||
UPDATE crafting_recipes
|
UPDATE crafting_recipes
|
||||||
SET source_dungeon_id = 2,
|
SET source_dungeon_id = NULL,
|
||||||
source_encounter_id = 102 + (
|
source_encounter_id = NULL
|
||||||
SELECT generated_recipe_offsets.boss_index * 3
|
|
||||||
FROM generated_recipe_offsets
|
|
||||||
WHERE crafting_recipes.id = 2000 + generated_recipe_offsets.recipe_offset
|
|
||||||
)
|
|
||||||
WHERE id BETWEEN 2001 AND 2009;
|
WHERE id BETWEEN 2001 AND 2009;
|
||||||
|
|
||||||
DELETE FROM crafting_recipe_components
|
DELETE FROM crafting_recipe_components
|
||||||
@@ -1319,20 +1437,20 @@ INSERT OR IGNORE INTO crafting_recipe_components (recipe_id, item_id, quantity)
|
|||||||
|
|
||||||
-- Final coin gearing override. Keep this after legacy loot edits.
|
-- Final coin gearing override. Keep this after legacy loot edits.
|
||||||
UPDATE crafting_recipes
|
UPDATE crafting_recipes
|
||||||
SET source_dungeon_id = 1,
|
SET source_dungeon_id = CASE WHEN EXISTS (SELECT 1 FROM encounters WHERE id = 3) THEN 1 ELSE NULL END,
|
||||||
source_encounter_id = 3
|
source_encounter_id = (SELECT id FROM encounters WHERE id = 3)
|
||||||
WHERE id BETWEEN 1001 AND 1409
|
WHERE id BETWEEN 1001 AND 1409
|
||||||
AND item_id IN (SELECT id FROM items WHERE slot IN ('helmet', 'chest', 'gloves'));
|
AND item_id IN (SELECT id FROM items WHERE slot IN ('helmet', 'chest', 'gloves'));
|
||||||
|
|
||||||
UPDATE crafting_recipes
|
UPDATE crafting_recipes
|
||||||
SET source_dungeon_id = 1,
|
SET source_dungeon_id = CASE WHEN EXISTS (SELECT 1 FROM encounters WHERE id = 12) THEN 1 ELSE NULL END,
|
||||||
source_encounter_id = 12
|
source_encounter_id = (SELECT id FROM encounters WHERE id = 12)
|
||||||
WHERE id BETWEEN 1001 AND 1409
|
WHERE id BETWEEN 1001 AND 1409
|
||||||
AND item_id IN (SELECT id FROM items WHERE slot IN ('boots', 'ring', 'trinket'));
|
AND item_id IN (SELECT id FROM items WHERE slot IN ('boots', 'ring', 'trinket'));
|
||||||
|
|
||||||
UPDATE crafting_recipes
|
UPDATE crafting_recipes
|
||||||
SET source_dungeon_id = 1,
|
SET source_dungeon_id = CASE WHEN EXISTS (SELECT 1 FROM encounters WHERE id = 22) THEN 1 ELSE NULL END,
|
||||||
source_encounter_id = 22
|
source_encounter_id = (SELECT id FROM encounters WHERE id = 22)
|
||||||
WHERE id BETWEEN 1001 AND 1409
|
WHERE id BETWEEN 1001 AND 1409
|
||||||
AND item_id IN (SELECT id FROM items WHERE slot IN ('weapon', 'pants', 'necklace'));
|
AND item_id IN (SELECT id FROM items WHERE slot IN ('weapon', 'pants', 'necklace'));
|
||||||
|
|
||||||
@@ -1375,7 +1493,7 @@ SET rarity = CASE item_level
|
|||||||
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
||||||
|
|
||||||
UPDATE items
|
UPDATE items
|
||||||
SET name = (
|
SET name = COALESCE((
|
||||||
SELECT
|
SELECT
|
||||||
CASE items.item_level
|
CASE items.item_level
|
||||||
WHEN 1 THEN 'Raw '
|
WHEN 1 THEN 'Raw '
|
||||||
@@ -1403,25 +1521,318 @@ SET name = (
|
|||||||
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
||||||
WHERE crafting_recipes.item_id = items.id
|
WHERE crafting_recipes.item_id = items.id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
),
|
), name),
|
||||||
description = (
|
description = COALESCE((
|
||||||
SELECT 'Crafted with ' || encounters.name || ' coins.'
|
SELECT 'Crafted with ' || encounters.name || ' coins.'
|
||||||
FROM crafting_recipes
|
FROM crafting_recipes
|
||||||
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
||||||
WHERE crafting_recipes.item_id = items.id
|
WHERE crafting_recipes.item_id = items.id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
|
), description)
|
||||||
|
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
||||||
|
|
||||||
|
-- Single-boss hunting grounds. Keep this late so legacy three-boss hunt
|
||||||
|
-- seed data and generated monster tiers are normalized into separate runs.
|
||||||
|
CREATE TEMP TABLE IF NOT EXISTS single_boss_dungeons (
|
||||||
|
dungeon_id INTEGER PRIMARY KEY,
|
||||||
|
boss_name TEXT NOT NULL,
|
||||||
|
boss_slug TEXT NOT NULL,
|
||||||
|
item_level INTEGER NOT NULL,
|
||||||
|
difficulty_floor INTEGER NOT NULL,
|
||||||
|
location_id INTEGER NOT NULL,
|
||||||
|
experience_reward INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM single_boss_dungeons;
|
||||||
|
INSERT INTO single_boss_dungeons
|
||||||
|
(dungeon_id, boss_name, boss_slug, item_level, difficulty_floor, location_id, experience_reward)
|
||||||
|
VALUES
|
||||||
|
(1, 'Bulldrome', 'bulldrome', 1, 1, 1, 125),
|
||||||
|
(2, 'Yian Kut-Ku', 'yian-kut-ku', 1, 1, 1, 125),
|
||||||
|
(3, 'Rathian', 'rathian', 1, 1, 1, 125),
|
||||||
|
(4, 'Tigrex', 'tigrex', 10, 2, 3, 205),
|
||||||
|
(5, 'Rathalos', 'rathalos', 10, 2, 3, 205),
|
||||||
|
(6, 'Gypceros', 'gypceros', 10, 2, 3, 205),
|
||||||
|
(7, 'Nargacuga', 'nargacuga', 15, 3, 3, 245),
|
||||||
|
(8, 'Azuros', 'azuros', 15, 3, 3, 245),
|
||||||
|
(9, 'Diablos', 'diablos', 15, 3, 3, 245),
|
||||||
|
(10, 'Barroth', 'barroth', 20, 4, 3, 285),
|
||||||
|
(11, 'Tobi Kadachi', 'tobi-kadachi', 20, 4, 3, 285),
|
||||||
|
(12, 'Monoblos', 'monoblos', 20, 4, 3, 285),
|
||||||
|
(13, 'Anjanath', 'anjanath', 25, 5, 3, 325),
|
||||||
|
(14, 'Bazelgeuse', 'bazelgeuse', 25, 5, 3, 325),
|
||||||
|
(15, 'Odogaron', 'odogaron', 25, 5, 3, 325);
|
||||||
|
|
||||||
|
CREATE TEMP TABLE IF NOT EXISTS single_boss_raids (
|
||||||
|
raid_id INTEGER PRIMARY KEY,
|
||||||
|
dungeon_id INTEGER NOT NULL,
|
||||||
|
difficulty_id INTEGER NOT NULL,
|
||||||
|
experience_reward INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM single_boss_raids;
|
||||||
|
INSERT INTO single_boss_raids (raid_id, dungeon_id, difficulty_id, experience_reward)
|
||||||
|
VALUES
|
||||||
|
(20, 4, 101, 275),
|
||||||
|
(21, 5, 101, 275),
|
||||||
|
(22, 6, 101, 275),
|
||||||
|
(23, 7, 103, 325),
|
||||||
|
(24, 8, 103, 325),
|
||||||
|
(25, 9, 103, 325),
|
||||||
|
(26, 10, 104, 375),
|
||||||
|
(27, 11, 104, 375),
|
||||||
|
(28, 12, 104, 375),
|
||||||
|
(29, 13, 105, 425),
|
||||||
|
(30, 14, 105, 425),
|
||||||
|
(31, 15, 105, 425);
|
||||||
|
|
||||||
|
UPDATE dungeons
|
||||||
|
SET slug = 'retired-dungeon-' || id
|
||||||
|
WHERE id BETWEEN 1 AND 31;
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO dungeons
|
||||||
|
(id, location_id, slug, name, recommended_level, content_type, party_size, completion_item_level, experience_reward, description)
|
||||||
|
SELECT
|
||||||
|
dungeon_id,
|
||||||
|
location_id,
|
||||||
|
boss_slug || '-hunting-ground',
|
||||||
|
boss_name || ' Hunting Ground',
|
||||||
|
CASE difficulty_floor WHEN 1 THEN 1 ELSE item_level END,
|
||||||
|
'dungeon',
|
||||||
|
6,
|
||||||
|
NULL,
|
||||||
|
experience_reward,
|
||||||
|
'A focused hunt through ' || boss_name || ' territory.'
|
||||||
|
FROM single_boss_dungeons;
|
||||||
|
|
||||||
|
UPDATE dungeons
|
||||||
|
SET location_id = (SELECT location_id FROM single_boss_dungeons WHERE dungeon_id = dungeons.id),
|
||||||
|
slug = (SELECT boss_slug || '-hunting-ground' FROM single_boss_dungeons WHERE dungeon_id = dungeons.id),
|
||||||
|
name = (SELECT boss_name || ' Hunting Ground' FROM single_boss_dungeons WHERE dungeon_id = dungeons.id),
|
||||||
|
recommended_level = (SELECT CASE difficulty_floor WHEN 1 THEN 1 ELSE item_level END FROM single_boss_dungeons WHERE dungeon_id = dungeons.id),
|
||||||
|
content_type = 'dungeon',
|
||||||
|
party_size = 6,
|
||||||
|
completion_item_level = NULL,
|
||||||
|
experience_reward = (SELECT experience_reward FROM single_boss_dungeons WHERE dungeon_id = dungeons.id),
|
||||||
|
description = (SELECT 'A focused hunt through ' || boss_name || ' territory.' FROM single_boss_dungeons WHERE dungeon_id = dungeons.id)
|
||||||
|
WHERE id IN (SELECT dungeon_id FROM single_boss_dungeons);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO dungeons
|
||||||
|
(id, location_id, slug, name, recommended_level, content_type, party_size, completion_item_level, experience_reward, description)
|
||||||
|
SELECT
|
||||||
|
single_boss_raids.raid_id,
|
||||||
|
single_boss_dungeons.location_id,
|
||||||
|
'apex-' || single_boss_dungeons.boss_slug || '-raid',
|
||||||
|
'Apex ' || single_boss_dungeons.boss_name || ' Raid',
|
||||||
|
single_boss_dungeons.item_level,
|
||||||
|
'raid',
|
||||||
|
18,
|
||||||
|
NULL,
|
||||||
|
single_boss_raids.experience_reward,
|
||||||
|
'A raid-scale hunt against Apex ' || single_boss_dungeons.boss_name || '.'
|
||||||
|
FROM single_boss_raids
|
||||||
|
JOIN single_boss_dungeons ON single_boss_dungeons.dungeon_id = single_boss_raids.dungeon_id;
|
||||||
|
|
||||||
|
UPDATE dungeons
|
||||||
|
SET slug = (SELECT 'apex-' || single_boss_dungeons.boss_slug || '-raid' FROM single_boss_raids JOIN single_boss_dungeons ON single_boss_dungeons.dungeon_id = single_boss_raids.dungeon_id WHERE single_boss_raids.raid_id = dungeons.id),
|
||||||
|
name = (SELECT 'Apex ' || single_boss_dungeons.boss_name || ' Raid' FROM single_boss_raids JOIN single_boss_dungeons ON single_boss_dungeons.dungeon_id = single_boss_raids.dungeon_id WHERE single_boss_raids.raid_id = dungeons.id),
|
||||||
|
location_id = (SELECT single_boss_dungeons.location_id FROM single_boss_raids JOIN single_boss_dungeons ON single_boss_dungeons.dungeon_id = single_boss_raids.dungeon_id WHERE single_boss_raids.raid_id = dungeons.id),
|
||||||
|
recommended_level = (SELECT single_boss_dungeons.item_level FROM single_boss_raids JOIN single_boss_dungeons ON single_boss_dungeons.dungeon_id = single_boss_raids.dungeon_id WHERE single_boss_raids.raid_id = dungeons.id),
|
||||||
|
content_type = 'raid',
|
||||||
|
party_size = 18,
|
||||||
|
completion_item_level = NULL,
|
||||||
|
experience_reward = (SELECT experience_reward FROM single_boss_raids WHERE single_boss_raids.raid_id = dungeons.id),
|
||||||
|
description = (SELECT 'A raid-scale hunt against Apex ' || single_boss_dungeons.boss_name || '.' FROM single_boss_raids JOIN single_boss_dungeons ON single_boss_dungeons.dungeon_id = single_boss_raids.dungeon_id WHERE single_boss_raids.raid_id = dungeons.id)
|
||||||
|
WHERE id IN (SELECT raid_id FROM single_boss_raids);
|
||||||
|
|
||||||
|
UPDATE crafting_recipes
|
||||||
|
SET source_dungeon_id = NULL,
|
||||||
|
source_encounter_id = NULL
|
||||||
|
WHERE id BETWEEN 1001 AND 1409;
|
||||||
|
|
||||||
|
DELETE FROM encounter_loot_roll_items
|
||||||
|
WHERE roll_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM encounter_loot_rolls
|
||||||
|
WHERE encounter_id IN (
|
||||||
|
SELECT id FROM encounters
|
||||||
|
WHERE dungeon_id IN (SELECT dungeon_id FROM single_boss_dungeons)
|
||||||
|
OR dungeon_id IN (SELECT raid_id FROM single_boss_raids)
|
||||||
)
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM encounter_loot_rolls
|
||||||
|
WHERE encounter_id IN (
|
||||||
|
SELECT id FROM encounters
|
||||||
|
WHERE dungeon_id IN (SELECT dungeon_id FROM single_boss_dungeons)
|
||||||
|
OR dungeon_id IN (SELECT raid_id FROM single_boss_raids)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM encounters WHERE dungeon_id IN (SELECT dungeon_id FROM single_boss_dungeons);
|
||||||
|
DELETE FROM encounters WHERE dungeon_id IN (SELECT raid_id FROM single_boss_raids);
|
||||||
|
|
||||||
|
INSERT INTO encounters
|
||||||
|
(id, dungeon_id, sequence, slug, name, encounter_type, max_health, base_damage, tank_damage, party_damage, description)
|
||||||
|
SELECT
|
||||||
|
single_boss_dungeons.dungeon_id * 100 + offset.value,
|
||||||
|
single_boss_dungeons.dungeon_id,
|
||||||
|
offset.value,
|
||||||
|
single_boss_dungeons.boss_slug || '-' || offset.slug,
|
||||||
|
CASE offset.encounter_type
|
||||||
|
WHEN 'boss' THEN single_boss_dungeons.boss_name
|
||||||
|
ELSE single_boss_dungeons.boss_name || ' ' || offset.name
|
||||||
|
END,
|
||||||
|
offset.encounter_type,
|
||||||
|
offset.health + single_boss_dungeons.item_level * 35,
|
||||||
|
offset.damage + single_boss_dungeons.difficulty_floor,
|
||||||
|
offset.tank_damage + single_boss_dungeons.difficulty_floor,
|
||||||
|
offset.party_damage + single_boss_dungeons.difficulty_floor * 2,
|
||||||
|
CASE offset.encounter_type
|
||||||
|
WHEN 'boss' THEN single_boss_dungeons.boss_name || ' drops boss coins for crafting.'
|
||||||
|
ELSE 'Hunters clear the path before ' || single_boss_dungeons.boss_name || '.'
|
||||||
|
END
|
||||||
|
FROM single_boss_dungeons
|
||||||
|
JOIN (
|
||||||
|
SELECT 1 AS value, 'approach' AS slug, 'Approach' AS name, 'trash' AS encounter_type, 430 AS health, 13 AS damage, 8 AS tank_damage, 24 AS party_damage
|
||||||
|
UNION ALL SELECT 2, 'guardians', 'Guardians', 'trash', 520, 15, 9, 26
|
||||||
|
UNION ALL SELECT 3, 'boss', '', 'boss', 860, 19, 14, 29
|
||||||
|
) AS offset;
|
||||||
|
|
||||||
|
INSERT INTO encounters
|
||||||
|
(id, dungeon_id, sequence, slug, name, encounter_type, max_health, base_damage, tank_damage, party_damage, description)
|
||||||
|
SELECT
|
||||||
|
single_boss_raids.raid_id * 100 + offset.value,
|
||||||
|
single_boss_raids.raid_id,
|
||||||
|
offset.value,
|
||||||
|
single_boss_dungeons.boss_slug || '-raid-' || offset.slug,
|
||||||
|
CASE offset.encounter_type
|
||||||
|
WHEN 'boss' THEN 'Apex ' || single_boss_dungeons.boss_name
|
||||||
|
ELSE 'Apex ' || single_boss_dungeons.boss_name || ' ' || offset.name
|
||||||
|
END,
|
||||||
|
offset.encounter_type,
|
||||||
|
offset.health + single_boss_dungeons.item_level * 35 + 900,
|
||||||
|
offset.damage + single_boss_dungeons.difficulty_floor,
|
||||||
|
offset.tank_damage + single_boss_dungeons.difficulty_floor,
|
||||||
|
offset.party_damage + single_boss_dungeons.difficulty_floor * 2 + 22,
|
||||||
|
CASE offset.encounter_type
|
||||||
|
WHEN 'boss' THEN 'Apex ' || single_boss_dungeons.boss_name || ' drops raid coins for crafting.'
|
||||||
|
ELSE 'Hunters clear the raid path before Apex ' || single_boss_dungeons.boss_name || '.'
|
||||||
|
END
|
||||||
|
FROM single_boss_raids
|
||||||
|
JOIN single_boss_dungeons
|
||||||
|
ON single_boss_dungeons.dungeon_id = single_boss_raids.dungeon_id
|
||||||
|
JOIN (
|
||||||
|
SELECT 1 AS value, 'approach' AS slug, 'Approach' AS name, 'trash' AS encounter_type, 650 AS health, 15 AS damage, 9 AS tank_damage, 28 AS party_damage
|
||||||
|
UNION ALL SELECT 2, 'guardians', 'Guardians', 'trash', 720, 16, 10, 30
|
||||||
|
UNION ALL SELECT 3, 'boss', '', 'boss', 980, 20, 14, 34
|
||||||
|
) AS offset;
|
||||||
|
|
||||||
|
DELETE FROM dungeon_difficulties;
|
||||||
|
INSERT OR IGNORE INTO dungeon_difficulties (dungeon_id, difficulty_id)
|
||||||
|
SELECT dungeon_id, difficulties.id
|
||||||
|
FROM single_boss_dungeons
|
||||||
|
JOIN difficulties ON difficulties.id BETWEEN single_boss_dungeons.difficulty_floor AND 5
|
||||||
|
WHERE difficulties.id BETWEEN 1 AND 5;
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO dungeon_difficulties (dungeon_id, difficulty_id)
|
||||||
|
SELECT raid_id, difficulty_id
|
||||||
|
FROM single_boss_raids;
|
||||||
|
|
||||||
|
CREATE TEMP TABLE IF NOT EXISTS recipe_source_override (
|
||||||
|
recipe_id INTEGER PRIMARY KEY,
|
||||||
|
dungeon_id INTEGER NOT NULL,
|
||||||
|
encounter_id INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM recipe_source_override;
|
||||||
|
INSERT INTO recipe_source_override (recipe_id, dungeon_id, encounter_id) VALUES
|
||||||
|
(1001, 1, 103), (1002, 1, 103), (1003, 1, 103),
|
||||||
|
(1004, 2, 203), (1005, 2, 203), (1006, 2, 203),
|
||||||
|
(1007, 3, 303), (1008, 3, 303), (1009, 3, 303),
|
||||||
|
(1101, 4, 403), (1102, 4, 403), (1103, 4, 403),
|
||||||
|
(1104, 5, 503), (1105, 5, 503), (1106, 5, 503),
|
||||||
|
(1107, 6, 603), (1108, 6, 603), (1109, 6, 603),
|
||||||
|
(1201, 7, 703), (1202, 7, 703), (1203, 7, 703),
|
||||||
|
(1204, 8, 803), (1205, 8, 803), (1206, 8, 803),
|
||||||
|
(1207, 9, 903), (1208, 9, 903), (1209, 9, 903),
|
||||||
|
(1301, 10, 1003), (1302, 10, 1003), (1303, 10, 1003),
|
||||||
|
(1304, 11, 1103), (1305, 11, 1103), (1306, 11, 1103),
|
||||||
|
(1307, 12, 1203), (1308, 12, 1203), (1309, 12, 1203),
|
||||||
|
(1401, 13, 1303), (1402, 13, 1303), (1403, 13, 1303),
|
||||||
|
(1404, 14, 1403), (1405, 14, 1403), (1406, 14, 1403),
|
||||||
|
(1407, 15, 1503), (1408, 15, 1503), (1409, 15, 1503);
|
||||||
|
|
||||||
|
UPDATE crafting_recipes
|
||||||
|
SET source_dungeon_id = (SELECT dungeon_id FROM recipe_source_override WHERE recipe_id = crafting_recipes.id),
|
||||||
|
source_encounter_id = (SELECT encounter_id FROM recipe_source_override WHERE recipe_id = crafting_recipes.id)
|
||||||
|
WHERE id IN (SELECT recipe_id FROM recipe_source_override);
|
||||||
|
|
||||||
|
UPDATE crafting_recipes
|
||||||
|
SET source_dungeon_id = (
|
||||||
|
SELECT single_boss_raids.raid_id
|
||||||
|
FROM generated_recipe_offsets
|
||||||
|
JOIN single_boss_raids
|
||||||
|
ON single_boss_raids.dungeon_id = 4 + generated_recipe_offsets.boss_index
|
||||||
|
WHERE crafting_recipes.id = 2000 + generated_recipe_offsets.recipe_offset
|
||||||
|
),
|
||||||
|
source_encounter_id = (
|
||||||
|
SELECT single_boss_raids.raid_id * 100 + 3
|
||||||
|
FROM generated_recipe_offsets
|
||||||
|
JOIN single_boss_raids
|
||||||
|
ON single_boss_raids.dungeon_id = 4 + generated_recipe_offsets.boss_index
|
||||||
|
WHERE crafting_recipes.id = 2000 + generated_recipe_offsets.recipe_offset
|
||||||
|
),
|
||||||
|
difficulty_id = 101
|
||||||
|
WHERE id BETWEEN 2001 AND 2009;
|
||||||
|
|
||||||
|
UPDATE items
|
||||||
|
SET name = COALESCE((
|
||||||
|
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 ''
|
||||||
|
END
|
||||||
|
|| encounters.name || ' '
|
||||||
|
|| CASE items.slot
|
||||||
|
WHEN 'weapon' THEN 'Weapon'
|
||||||
|
WHEN 'helmet' THEN 'Helmet'
|
||||||
|
WHEN 'chest' THEN 'Chest'
|
||||||
|
WHEN 'gloves' THEN 'Gloves'
|
||||||
|
WHEN 'boots' THEN 'Boots'
|
||||||
|
WHEN 'pants' THEN 'Pants'
|
||||||
|
WHEN 'ring' THEN 'Ring'
|
||||||
|
WHEN 'necklace' THEN 'Necklace'
|
||||||
|
WHEN 'trinket' THEN 'Trinket'
|
||||||
|
ELSE items.name
|
||||||
|
END
|
||||||
|
FROM crafting_recipes
|
||||||
|
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
||||||
|
WHERE crafting_recipes.item_id = items.id
|
||||||
|
LIMIT 1
|
||||||
|
), name),
|
||||||
|
description = COALESCE((
|
||||||
|
SELECT 'Crafted with ' || encounters.name || ' coins.'
|
||||||
|
FROM crafting_recipes
|
||||||
|
JOIN encounters ON encounters.id = crafting_recipes.source_encounter_id
|
||||||
|
WHERE crafting_recipes.item_id = items.id
|
||||||
|
LIMIT 1
|
||||||
|
), description)
|
||||||
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
WHERE id IN (SELECT item_id FROM crafting_recipes);
|
||||||
|
|
||||||
DELETE FROM coin_sources;
|
DELETE FROM coin_sources;
|
||||||
|
|
||||||
INSERT INTO coin_sources
|
INSERT INTO coin_sources
|
||||||
SELECT
|
SELECT
|
||||||
280000 + encounters.id * 100 + difficulties.dropped_item_level,
|
280000 + encounters.id * 1000 + difficulties.id,
|
||||||
encounters.id,
|
encounters.id,
|
||||||
difficulties.id,
|
difficulties.id,
|
||||||
difficulties.dropped_item_level,
|
difficulties.dropped_item_level,
|
||||||
encounters.slug || '-coin-ilvl-' || difficulties.dropped_item_level,
|
encounters.slug || '-coin-diff-' || difficulties.id || '-ilvl-' || difficulties.dropped_item_level,
|
||||||
CASE difficulties.dropped_item_level
|
CASE difficulties.dropped_item_level
|
||||||
WHEN 1 THEN 'Raw '
|
WHEN 1 THEN 'Raw '
|
||||||
WHEN 5 THEN 'Honed '
|
WHEN 5 THEN 'Honed '
|
||||||
@@ -1453,6 +1864,18 @@ INSERT OR IGNORE INTO items
|
|||||||
SELECT item_id, slug, name, 'component', rarity, item_level, 0, 0, glyph, description
|
SELECT item_id, slug, name, 'component', rarity, item_level, 0, 0, glyph, description
|
||||||
FROM coin_sources;
|
FROM coin_sources;
|
||||||
|
|
||||||
|
UPDATE coin_sources
|
||||||
|
SET item_id = (
|
||||||
|
SELECT items.id
|
||||||
|
FROM items
|
||||||
|
WHERE items.slug = coin_sources.slug
|
||||||
|
)
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM items
|
||||||
|
WHERE items.slug = coin_sources.slug
|
||||||
|
);
|
||||||
|
|
||||||
UPDATE items
|
UPDATE items
|
||||||
SET slug = (SELECT slug FROM coin_sources WHERE coin_sources.item_id = items.id),
|
SET slug = (SELECT slug FROM coin_sources WHERE coin_sources.item_id = items.id),
|
||||||
name = (SELECT name FROM coin_sources WHERE coin_sources.item_id = items.id),
|
name = (SELECT name FROM coin_sources WHERE coin_sources.item_id = items.id),
|
||||||
|
|||||||
@@ -790,6 +790,15 @@ export function getProfile(database, characterId, accountId) {
|
|||||||
WHERE rank <= 10
|
WHERE rank <= 10
|
||||||
ORDER BY dungeonId, difficultyId, startPart, completedParts, rank
|
ORDER BY dungeonId, difficultyId, startPart, completedParts, rank
|
||||||
`).all()
|
`).all()
|
||||||
|
const dungeonCompletionCounts = new Map(database.prepare(`
|
||||||
|
SELECT dungeon_id AS dungeonId, COUNT(*) AS count
|
||||||
|
FROM dungeon_runs
|
||||||
|
WHERE character_id = ?
|
||||||
|
AND result = 'victory'
|
||||||
|
AND start_part = 1
|
||||||
|
AND completed_parts >= 1
|
||||||
|
GROUP BY dungeon_id
|
||||||
|
`).all(characterId).map((row) => [row.dungeonId, row.count]))
|
||||||
|
|
||||||
const settings = Object.fromEntries(
|
const settings = Object.fromEntries(
|
||||||
database.prepare('SELECT key, value FROM game_settings').all()
|
database.prepare('SELECT key, value FROM game_settings').all()
|
||||||
@@ -869,6 +878,7 @@ export function getProfile(database, characterId, accountId) {
|
|||||||
}),
|
}),
|
||||||
dungeons: dungeons.map((dungeon) => ({
|
dungeons: dungeons.map((dungeon) => ({
|
||||||
...dungeon,
|
...dungeon,
|
||||||
|
completionCount: dungeonCompletionCounts.get(dungeon.id) ?? 0,
|
||||||
difficulties: dungeonDifficulties.filter(
|
difficulties: dungeonDifficulties.filter(
|
||||||
(difficulty) => difficulty.dungeonId === dungeon.id,
|
(difficulty) => difficulty.dungeonId === dungeon.id,
|
||||||
),
|
),
|
||||||
|
|||||||
+82
-33
@@ -1795,12 +1795,58 @@ h2 {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.run-title-row {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-title-row h2 {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-back-button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.run-setup-heading small {
|
.run-setup-heading small {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-pager {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-pager button {
|
||||||
|
background: #15161c;
|
||||||
|
border: 2px solid #090a0d;
|
||||||
|
color: var(--ink);
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 11px;
|
||||||
|
min-height: 34px;
|
||||||
|
outline: 2px solid #41404a;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-pager button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-pager span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.tier-grid {
|
.tier-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -1923,16 +1969,6 @@ h2 {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.part-start-row {
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(82px, 0.38fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hard-mode-button {
|
|
||||||
border-color: #c25b4b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.part-setup-panel .primary-button {
|
.part-setup-panel .primary-button {
|
||||||
min-height: 54px;
|
min-height: 54px;
|
||||||
}
|
}
|
||||||
@@ -2025,14 +2061,6 @@ h2 {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dungeon-run-screen .screen-heading {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dungeon-run-screen .screen-heading h1 {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dungeon-run-board,
|
.dungeon-run-board,
|
||||||
.dungeon-run-main {
|
.dungeon-run-main {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -2069,6 +2097,22 @@ h2 {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline-back-button {
|
||||||
|
font-size: 12px;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 4px 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-pager button {
|
||||||
|
font-size: 9px;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-pager span {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.dungeon-run-screen .eyebrow {
|
.dungeon-run-screen .eyebrow {
|
||||||
font-size: 7px;
|
font-size: 7px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
@@ -2207,21 +2251,6 @@ h2 {
|
|||||||
padding: 6px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dungeon-run-screen .screen-heading {
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dungeon-run-screen .screen-heading h1 {
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dungeon-run-screen .back-button {
|
|
||||||
font-size: 14px;
|
|
||||||
min-height: 34px;
|
|
||||||
padding: 5px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dungeon-run-board,
|
.dungeon-run-board,
|
||||||
.dungeon-run-main,
|
.dungeon-run-main,
|
||||||
.dungeon-setup-rail {
|
.dungeon-setup-rail {
|
||||||
@@ -2258,6 +2287,12 @@ h2 {
|
|||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline-back-button {
|
||||||
|
font-size: 10px;
|
||||||
|
min-height: 24px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.dungeon-run-screen .eyebrow,
|
.dungeon-run-screen .eyebrow,
|
||||||
.dungeon-run-screen .tier-grid strong,
|
.dungeon-run-screen .tier-grid strong,
|
||||||
.dungeon-choice-grid .activity-card strong,
|
.dungeon-choice-grid .activity-card strong,
|
||||||
@@ -2273,6 +2308,20 @@ h2 {
|
|||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-pager {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-pager button {
|
||||||
|
font-size: 8px;
|
||||||
|
min-height: 24px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-pager span {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
.dungeon-choice-grid {
|
.dungeon-choice-grid {
|
||||||
grid-auto-rows: minmax(52px, max-content);
|
grid-auto-rows: minmax(52px, max-content);
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-67
@@ -55,6 +55,7 @@ const MENU_ITEMS: Array<{
|
|||||||
|
|
||||||
const LAST_DIFFICULTY_KEY = 'i-want-to-heal:last-difficulty'
|
const LAST_DIFFICULTY_KEY = 'i-want-to-heal:last-difficulty'
|
||||||
const SHOW_LEADERBOARDS = false
|
const SHOW_LEADERBOARDS = false
|
||||||
|
const ACTIVITY_PAGE_SIZE = 4
|
||||||
|
|
||||||
function activityInitials(name: string) {
|
function activityInitials(name: string) {
|
||||||
return name
|
return name
|
||||||
@@ -81,14 +82,14 @@ function App() {
|
|||||||
return Number.isFinite(saved) && saved > 0 ? saved : 1
|
return Number.isFinite(saved) && saved > 0 ? saved : 1
|
||||||
})
|
})
|
||||||
const [selectedDungeonId, setSelectedDungeonId] = useState(1)
|
const [selectedDungeonId, setSelectedDungeonId] = useState(1)
|
||||||
const [selectedRaidId, setSelectedRaidId] = useState(2)
|
const [selectedRaidId, setSelectedRaidId] = useState(20)
|
||||||
const [roguelikeKind, setRoguelikeKind] = useState<'dungeon' | 'raid'>('dungeon')
|
const [roguelikeKind, setRoguelikeKind] = useState<'dungeon' | 'raid'>('dungeon')
|
||||||
const [roguelikeVariant, setRoguelikeVariant] = useState<RoguelikeVariant>('pve')
|
const [roguelikeVariant, setRoguelikeVariant] = useState<RoguelikeVariant>('pve')
|
||||||
const [roguelikeUpgradeTiming, setRoguelikeUpgradeTiming] = useState<RoguelikeUpgradeTiming>('encounter')
|
const [roguelikeUpgradeTiming, setRoguelikeUpgradeTiming] = useState<RoguelikeUpgradeTiming>('encounter')
|
||||||
const [roguelikeAbilityLabelMode, setRoguelikeAbilityLabelMode] = useState<RoguelikeAbilityLabelMode>('ability')
|
const [roguelikeAbilityLabelMode, setRoguelikeAbilityLabelMode] = useState<RoguelikeAbilityLabelMode>('ability')
|
||||||
const [pvpContentType, setPvpContentType] = useState<PvpContentType>('dungeon')
|
const [pvpContentType, setPvpContentType] = useState<PvpContentType>('dungeon')
|
||||||
const [selectedPart, setSelectedPart] = useState(1)
|
const [selectedMarathonMode, setSelectedMarathonMode] = useState(false)
|
||||||
const [selectedHardMode, setSelectedHardMode] = useState(false)
|
const [activityPage, setActivityPage] = useState(0)
|
||||||
const [combatContentId, setCombatContentId] = useState(1)
|
const [combatContentId, setCombatContentId] = useState(1)
|
||||||
const [leaderboardCategory, setLeaderboardCategory] = useState<'part_1' | 'part_2' | 'part_3' | 'full_run'>('part_1')
|
const [leaderboardCategory, setLeaderboardCategory] = useState<'part_1' | 'part_2' | 'part_3' | 'full_run'>('part_1')
|
||||||
const [showLoot, setShowLoot] = useState(false)
|
const [showLoot, setShowLoot] = useState(false)
|
||||||
@@ -231,18 +232,18 @@ function App() {
|
|||||||
const roguelikePool = profile.dungeons
|
const roguelikePool = profile.dungeons
|
||||||
.filter((candidate) => candidate.contentType === roguelikeKind)
|
.filter((candidate) => candidate.contentType === roguelikeKind)
|
||||||
.flatMap((candidate) => candidate.encounters)
|
.flatMap((candidate) => candidate.encounters)
|
||||||
const startPart = selectedPart
|
|
||||||
return (
|
return (
|
||||||
<CombatScreen
|
<CombatScreen
|
||||||
difficulty={difficulty}
|
difficulty={difficulty}
|
||||||
dungeon={dungeon}
|
dungeon={dungeon}
|
||||||
hardMode={selectedHardMode && combatContentId > 0}
|
hardMode={false}
|
||||||
|
marathonMode={selectedMarathonMode && combatContentId > 0}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
roguelikeMode={combatContentId < 0 ? roguelikeKind : undefined}
|
roguelikeMode={combatContentId < 0 ? roguelikeKind : undefined}
|
||||||
roguelikeUpgradeTiming={combatContentId < 0 ? roguelikeUpgradeTiming : undefined}
|
roguelikeUpgradeTiming={combatContentId < 0 ? roguelikeUpgradeTiming : undefined}
|
||||||
roguelikeAbilityLabelMode={combatContentId < 0 ? roguelikeAbilityLabelMode : undefined}
|
roguelikeAbilityLabelMode={combatContentId < 0 ? roguelikeAbilityLabelMode : undefined}
|
||||||
roguelikeEncounterPool={combatContentId < 0 ? roguelikePool : undefined}
|
roguelikeEncounterPool={combatContentId < 0 ? roguelikePool : undefined}
|
||||||
startPart={startPart}
|
startPart={1}
|
||||||
onExit={() => {
|
onExit={() => {
|
||||||
setScreen(combatContentId < 0 ? 'roguelike' : dungeon.contentType === 'raid' ? 'raids' : 'dungeons')
|
setScreen(combatContentId < 0 ? 'roguelike' : dungeon.contentType === 'raid' ? 'raids' : 'dungeons')
|
||||||
}}
|
}}
|
||||||
@@ -295,8 +296,7 @@ function App() {
|
|||||||
setCombatContentId(-1)
|
setCombatContentId(-1)
|
||||||
setSelectedDifficultyId(baseDungeon?.difficulties[0]?.id ?? 1)
|
setSelectedDifficultyId(baseDungeon?.difficulties[0]?.id ?? 1)
|
||||||
}
|
}
|
||||||
setSelectedPart(1)
|
setSelectedMarathonMode(false)
|
||||||
setSelectedHardMode(false)
|
|
||||||
setScreen('combat')
|
setScreen('combat')
|
||||||
}
|
}
|
||||||
const tierOptions = activityOptions
|
const tierOptions = activityOptions
|
||||||
@@ -315,26 +315,24 @@ function App() {
|
|||||||
?? tierOptions.slice().reverse().find((candidate) => profile.character.level >= candidate.unlockLevel)
|
?? tierOptions.slice().reverse().find((candidate) => profile.character.level >= candidate.unlockLevel)
|
||||||
?? tierOptions[0]
|
?? tierOptions[0]
|
||||||
const selectedTierItemLevel = selectedTier?.droppedItemLevel ?? 0
|
const selectedTierItemLevel = selectedTier?.droppedItemLevel ?? 0
|
||||||
const tierActivityOptions = activityOptions.filter((option) =>
|
const activityPageCount = Math.max(1, Math.ceil(activityOptions.length / ACTIVITY_PAGE_SIZE))
|
||||||
option.difficulties.some((difficulty) => difficulty.droppedItemLevel === selectedTierItemLevel),
|
const currentActivityPage = Math.min(activityPage, activityPageCount - 1)
|
||||||
|
const pagedActivityOptions = activityOptions.slice(
|
||||||
|
currentActivityPage * ACTIVITY_PAGE_SIZE,
|
||||||
|
currentActivityPage * ACTIVITY_PAGE_SIZE + ACTIVITY_PAGE_SIZE,
|
||||||
)
|
)
|
||||||
|
const activityPageStart = activityOptions.length === 0
|
||||||
|
? 0
|
||||||
|
: currentActivityPage * ACTIVITY_PAGE_SIZE + 1
|
||||||
|
const activityPageEnd = Math.min(activityOptions.length, (currentActivityPage + 1) * ACTIVITY_PAGE_SIZE)
|
||||||
const selectedActivityId = screen === 'raids' && raid ? raid.id : dungeon.id
|
const selectedActivityId = screen === 'raids' && raid ? raid.id : dungeon.id
|
||||||
const activity = tierActivityOptions.find((candidate) => candidate.id === selectedActivityId)
|
const activity = activityOptions.find((candidate) => candidate.id === selectedActivityId)
|
||||||
?? tierActivityOptions[0]
|
?? activityOptions[0]
|
||||||
?? (screen === 'raids' && raid ? raid : dungeon)
|
?? (screen === 'raids' && raid ? raid : dungeon)
|
||||||
const selectedDifficulty = activity.difficulties.find(
|
const selectedDifficulty = activity.difficulties.find(
|
||||||
(candidate) => candidate.droppedItemLevel === selectedTierItemLevel,
|
(candidate) => candidate.droppedItemLevel === selectedTierItemLevel,
|
||||||
) ?? activity.difficulties[0]
|
) ?? activity.difficulties[0]
|
||||||
const difficultyLocked = profile.character.level < selectedDifficulty.unlockLevel
|
const difficultyLocked = profile.character.level < selectedDifficulty.unlockLevel
|
||||||
const completedSections = activity.contentType === 'raid'
|
|
||||||
? profile.completedRaidPhases
|
|
||||||
: profile.completedDungeonParts
|
|
||||||
const sectionName = activity.contentType === 'raid' ? 'Phase' : 'Part'
|
|
||||||
const parts = [
|
|
||||||
{ part: 1, name: `${sectionName} 1`, encounterCount: 3, unlocked: true, hardUnlocked: completedSections >= 1 },
|
|
||||||
{ part: 2, name: `${sectionName} 2`, encounterCount: 3, unlocked: completedSections >= 1, hardUnlocked: completedSections >= 2 },
|
|
||||||
{ part: 3, name: `${sectionName} 3`, encounterCount: 3, unlocked: completedSections >= 2, hardUnlocked: completedSections >= 3 },
|
|
||||||
]
|
|
||||||
const cloudSync = getCloudSyncStatus()
|
const cloudSync = getCloudSyncStatus()
|
||||||
const canShowCloudSync = account.id !== -1 && cloudSync.available
|
const canShowCloudSync = account.id !== -1 && cloudSync.available
|
||||||
const lootPreviewEncounters = [...activity.encounters]
|
const lootPreviewEncounters = [...activity.encounters]
|
||||||
@@ -609,11 +607,6 @@ function App() {
|
|||||||
|
|
||||||
{(screen === 'dungeons' || screen === 'raids') && (
|
{(screen === 'dungeons' || screen === 'raids') && (
|
||||||
<section className="content-screen dungeon-run-screen">
|
<section className="content-screen dungeon-run-screen">
|
||||||
<ScreenHeading
|
|
||||||
eyebrow="Adventure"
|
|
||||||
title={activity.contentType === 'raid' ? 'Raids' : 'Dungeons'}
|
|
||||||
onBack={() => setScreen('menu')}
|
|
||||||
/>
|
|
||||||
<div className="dungeon-run-board">
|
<div className="dungeon-run-board">
|
||||||
<div className="dungeon-run-main">
|
<div className="dungeon-run-main">
|
||||||
<article className="run-summary-card dungeon-focus-card">
|
<article className="run-summary-card dungeon-focus-card">
|
||||||
@@ -622,7 +615,10 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="run-summary-copy">
|
<div className="run-summary-copy">
|
||||||
<p className="eyebrow">Selected Run</p>
|
<p className="eyebrow">Selected Run</p>
|
||||||
<h2>{activity.name}</h2>
|
<div className="run-title-row">
|
||||||
|
<h2>{activity.name}</h2>
|
||||||
|
<button className="back-button inline-back-button" onClick={() => setScreen('menu')} type="button">Back</button>
|
||||||
|
</div>
|
||||||
<p>{activity.description}</p>
|
<p>{activity.description}</p>
|
||||||
<div className="tag-row">
|
<div className="tag-row">
|
||||||
<span>Level {activity.recommendedLevel}</span>
|
<span>Level {activity.recommendedLevel}</span>
|
||||||
@@ -640,10 +636,30 @@ function App() {
|
|||||||
<p className="eyebrow">Pick Run</p>
|
<p className="eyebrow">Pick Run</p>
|
||||||
<h2>{screen === 'raids' ? 'Raid' : 'Dungeon'}</h2>
|
<h2>{screen === 'raids' ? 'Raid' : 'Dungeon'}</h2>
|
||||||
</div>
|
</div>
|
||||||
<small>{selectedDifficulty.name} rewards iLvl {selectedDifficulty.droppedItemLevel} components.</small>
|
{activityPageCount > 1 ? (
|
||||||
|
<div className="activity-pager" aria-label={`${screen === 'raids' ? 'Raid' : 'Dungeon'} pages`}>
|
||||||
|
<button
|
||||||
|
disabled={currentActivityPage === 0}
|
||||||
|
onClick={() => setActivityPage((page) => Math.max(0, page - 1))}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Prev
|
||||||
|
</button>
|
||||||
|
<span>{activityPageStart}-{activityPageEnd} of {activityOptions.length}</span>
|
||||||
|
<button
|
||||||
|
disabled={currentActivityPage >= activityPageCount - 1}
|
||||||
|
onClick={() => setActivityPage((page) => Math.min(activityPageCount - 1, page + 1))}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<small>{selectedDifficulty.name} rewards iLvl {selectedDifficulty.droppedItemLevel} components.</small>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="activity-card-grid dungeon-choice-grid">
|
<div className="activity-card-grid dungeon-choice-grid">
|
||||||
{tierActivityOptions.map((candidate) => {
|
{pagedActivityOptions.map((candidate) => {
|
||||||
const difficulty = candidate.difficulties.find(
|
const difficulty = candidate.difficulties.find(
|
||||||
(option) => option.droppedItemLevel === selectedDifficulty.droppedItemLevel,
|
(option) => option.droppedItemLevel === selectedDifficulty.droppedItemLevel,
|
||||||
) ?? candidate.difficulties[0]
|
) ?? candidate.difficulties[0]
|
||||||
@@ -695,6 +711,7 @@ function App() {
|
|||||||
disabled={locked}
|
disabled={locked}
|
||||||
key={difficulty.id}
|
key={difficulty.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setActivityPage(0)
|
||||||
const nextActivity = activity.difficulties.some(
|
const nextActivity = activity.difficulties.some(
|
||||||
(candidate) => candidate.droppedItemLevel === difficulty.droppedItemLevel,
|
(candidate) => candidate.droppedItemLevel === difficulty.droppedItemLevel,
|
||||||
)
|
)
|
||||||
@@ -725,43 +742,41 @@ function App() {
|
|||||||
<div className="run-setup-heading">
|
<div className="run-setup-heading">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Start</p>
|
<p className="eyebrow">Start</p>
|
||||||
<h2>{sectionName}</h2>
|
<h2>Run</h2>
|
||||||
</div>
|
</div>
|
||||||
<small>{difficultyLocked ? `Unlocks at level ${selectedDifficulty.unlockLevel}` : 'Choose a section to launch.'}</small>
|
<small>
|
||||||
|
{difficultyLocked
|
||||||
|
? `Unlocks at level ${selectedDifficulty.unlockLevel}`
|
||||||
|
: 'Marathon keeps health and mana between boss kills.'}
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div className="part-picker">
|
<div className="part-picker">
|
||||||
{parts.map((p) => (
|
<button
|
||||||
<div className="part-start-row" key={p.part}>
|
className="primary-button selected-part"
|
||||||
<button
|
disabled={difficultyLocked}
|
||||||
className={`primary-button ${selectedPart === p.part && !selectedHardMode ? 'selected-part' : ''} ${!p.unlocked ? 'locked' : ''}`}
|
onClick={() => {
|
||||||
disabled={difficultyLocked || !p.unlocked}
|
setSelectedMarathonMode(false)
|
||||||
onClick={() => {
|
setCombatContentId(activity.id)
|
||||||
setSelectedPart(p.part)
|
setSelectedDifficultyId(selectedDifficulty.id)
|
||||||
setSelectedHardMode(false)
|
setScreen('combat')
|
||||||
setCombatContentId(activity.id)
|
}}
|
||||||
setSelectedDifficultyId(selectedDifficulty.id)
|
type="button"
|
||||||
setScreen('combat')
|
>
|
||||||
}}
|
Start Hunt
|
||||||
type="button"
|
</button>
|
||||||
>
|
<button
|
||||||
{p.name}
|
className={`primary-button ${selectedMarathonMode ? 'selected-part' : ''}`}
|
||||||
</button>
|
disabled={difficultyLocked}
|
||||||
<button
|
onClick={() => {
|
||||||
className={`primary-button hard-mode-button ${selectedPart === p.part && selectedHardMode ? 'selected-part' : ''} ${!p.hardUnlocked ? 'locked' : ''}`}
|
setSelectedMarathonMode(true)
|
||||||
disabled={difficultyLocked || !p.hardUnlocked}
|
setCombatContentId(activity.id)
|
||||||
onClick={() => {
|
setSelectedDifficultyId(selectedDifficulty.id)
|
||||||
setSelectedPart(p.part)
|
setScreen('combat')
|
||||||
setSelectedHardMode(true)
|
}}
|
||||||
setCombatContentId(activity.id)
|
type="button"
|
||||||
setSelectedDifficultyId(selectedDifficulty.id)
|
>
|
||||||
setScreen('combat')
|
Marathon
|
||||||
}}
|
</button>
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Hard
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -874,10 +889,10 @@ function App() {
|
|||||||
</p>
|
</p>
|
||||||
<div className="leaderboard-tabs">
|
<div className="leaderboard-tabs">
|
||||||
{([
|
{([
|
||||||
{ key: 'part_1', label: `${sectionName} 1` },
|
{ key: 'part_1', label: 'Run' },
|
||||||
{ key: 'part_2', label: `${sectionName} 2` },
|
{ key: 'part_2', label: 'Legacy 2' },
|
||||||
{ key: 'part_3', label: `${sectionName} 3` },
|
{ key: 'part_3', label: 'Legacy 3' },
|
||||||
{ key: 'full_run', label: 'Full Run' },
|
{ key: 'full_run', label: 'Legacy Full' },
|
||||||
] as const).map((tab) => (
|
] as const).map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
|
|||||||
@@ -341,6 +341,7 @@ export function CombatScreen({
|
|||||||
difficulty,
|
difficulty,
|
||||||
dungeon,
|
dungeon,
|
||||||
hardMode = false,
|
hardMode = false,
|
||||||
|
marathonMode = false,
|
||||||
profile,
|
profile,
|
||||||
startPart = 1,
|
startPart = 1,
|
||||||
roguelikeMode,
|
roguelikeMode,
|
||||||
@@ -353,6 +354,7 @@ export function CombatScreen({
|
|||||||
difficulty: Difficulty
|
difficulty: Difficulty
|
||||||
dungeon: Dungeon
|
dungeon: Dungeon
|
||||||
hardMode?: boolean
|
hardMode?: boolean
|
||||||
|
marathonMode?: boolean
|
||||||
profile: CharacterProfile
|
profile: CharacterProfile
|
||||||
startPart?: number
|
startPart?: number
|
||||||
roguelikeMode?: RoguelikeMode
|
roguelikeMode?: RoguelikeMode
|
||||||
@@ -400,7 +402,7 @@ export function CombatScreen({
|
|||||||
})),
|
})),
|
||||||
[dungeon.partySize, profile.character.name],
|
[dungeon.partySize, profile.character.name],
|
||||||
)
|
)
|
||||||
const sectionName = isRoguelike ? 'Stage' : dungeon.contentType === 'raid' ? 'Phase' : 'Part'
|
const sectionName = isRoguelike ? 'Stage' : 'Run'
|
||||||
const contentName = isRoguelike ? 'Roguelike' : dungeon.contentType === 'raid' ? 'Raid' : 'Dungeon'
|
const contentName = isRoguelike ? 'Roguelike' : dungeon.contentType === 'raid' ? 'Raid' : 'Dungeon'
|
||||||
const initialEncounterIndex = (startPart - 1) * 3
|
const initialEncounterIndex = (startPart - 1) * 3
|
||||||
const enemyCount = hardMode ? 2 : 1
|
const enemyCount = hardMode ? 2 : 1
|
||||||
@@ -416,7 +418,7 @@ export function CombatScreen({
|
|||||||
const [combatState, setCombatState] = useState<SinglePlayerCombatState>(() => initialCombatState)
|
const [combatState, setCombatState] = useState<SinglePlayerCombatState>(() => initialCombatState)
|
||||||
const [selectedId, setSelectedId] = useState(partyTemplate[0].id)
|
const [selectedId, setSelectedId] = useState(partyTemplate[0].id)
|
||||||
const [encounterIndex, setEncounterIndex] = useState(initialEncounterIndex)
|
const [encounterIndex, setEncounterIndex] = useState(initialEncounterIndex)
|
||||||
const [status, setStatus] = useState<'playing' | 'won' | 'lost' | 'part-complete' | 'upgrade-choice'>('playing')
|
const [status, setStatus] = useState<'playing' | 'won' | 'lost' | 'part-complete' | 'marathon-choice' | 'upgrade-choice'>('playing')
|
||||||
const [paused, setPaused] = useState(false)
|
const [paused, setPaused] = useState(false)
|
||||||
const [speedMultiplier, setSpeedMultiplier] = useState<1 | 2>(1)
|
const [speedMultiplier, setSpeedMultiplier] = useState<1 | 2>(1)
|
||||||
const [targetGroup, setTargetGroup] = useState<0 | 1 | 2>(0)
|
const [targetGroup, setTargetGroup] = useState<0 | 1 | 2>(0)
|
||||||
@@ -430,6 +432,7 @@ export function CombatScreen({
|
|||||||
const [floatingTexts, setFloatingTexts] = useState<FloatingCombatText[]>([])
|
const [floatingTexts, setFloatingTexts] = useState<FloatingCombatText[]>([])
|
||||||
const [roguelikeUpgrades, setRoguelikeUpgrades] = useState<RoguelikeUpgrade[]>([])
|
const [roguelikeUpgrades, setRoguelikeUpgrades] = useState<RoguelikeUpgrade[]>([])
|
||||||
const [upgradeChoices, setUpgradeChoices] = useState<RoguelikeUpgrade[]>([])
|
const [upgradeChoices, setUpgradeChoices] = useState<RoguelikeUpgrade[]>([])
|
||||||
|
const [marathonBossesDefeated, setMarathonBossesDefeated] = useState(0)
|
||||||
const rewardClaimedRef = useRef(false)
|
const rewardClaimedRef = useRef(false)
|
||||||
const profileRefreshedRef = useRef(false)
|
const profileRefreshedRef = useRef(false)
|
||||||
const rolledEncounterIdsRef = useRef(new Set<string>())
|
const rolledEncounterIdsRef = useRef(new Set<string>())
|
||||||
@@ -439,6 +442,7 @@ export function CombatScreen({
|
|||||||
const partStartTimesRef = useRef<Record<number, number>>({})
|
const partStartTimesRef = useRef<Record<number, number>>({})
|
||||||
const nextLogId = useRef(2)
|
const nextLogId = useRef(2)
|
||||||
const nextFloatingTextId = useRef(1)
|
const nextFloatingTextId = useRef(1)
|
||||||
|
const marathonBossesDefeatedRef = useRef(0)
|
||||||
const combatRef = useRef(initialCombatState)
|
const combatRef = useRef(initialCombatState)
|
||||||
const selectedIdRef = useRef(partyTemplate[0].id)
|
const selectedIdRef = useRef(partyTemplate[0].id)
|
||||||
const runCombatTickRef = useRef<() => void>(() => {})
|
const runCombatTickRef = useRef<() => void>(() => {})
|
||||||
@@ -553,10 +557,10 @@ export function CombatScreen({
|
|||||||
|
|
||||||
const requestLootRoll = useCallback(
|
const requestLootRoll = useCallback(
|
||||||
(encounterId: number, rollIndex = 0) => {
|
(encounterId: number, rollIndex = 0) => {
|
||||||
const rollKey = `${encounterId}:${rollIndex}`
|
const rollKey = `${encounterId}:${rollIndex}:${marathonBossesDefeatedRef.current}`
|
||||||
if (rolledEncounterIdsRef.current.has(rollKey)) return
|
if (rolledEncounterIdsRef.current.has(rollKey)) return
|
||||||
rolledEncounterIdsRef.current.add(rollKey)
|
rolledEncounterIdsRef.current.add(rollKey)
|
||||||
const runToken = rollIndex === 0 ? runTokenRef.current : `${runTokenRef.current}-hard-${rollIndex}`
|
const runToken = `${runTokenRef.current}-${marathonBossesDefeatedRef.current}-${rollIndex}`
|
||||||
rollEncounterLoot(encounterId, difficulty.id, runToken)
|
rollEncounterLoot(encounterId, difficulty.id, runToken)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
setLootRolls((current) => [...current, result])
|
setLootRolls((current) => [...current, result])
|
||||||
@@ -609,10 +613,12 @@ export function CombatScreen({
|
|||||||
setFloatingTexts([])
|
setFloatingTexts([])
|
||||||
setRoguelikeUpgrades([])
|
setRoguelikeUpgrades([])
|
||||||
setUpgradeChoices([])
|
setUpgradeChoices([])
|
||||||
|
setMarathonBossesDefeated(0)
|
||||||
rewardClaimedRef.current = false
|
rewardClaimedRef.current = false
|
||||||
profileRefreshedRef.current = false
|
profileRefreshedRef.current = false
|
||||||
rolledEncounterIdsRef.current = new Set()
|
rolledEncounterIdsRef.current = new Set()
|
||||||
runTokenRef.current = crypto.randomUUID()
|
runTokenRef.current = crypto.randomUUID()
|
||||||
|
marathonBossesDefeatedRef.current = 0
|
||||||
resourceSpentRef.current = 0
|
resourceSpentRef.current = 0
|
||||||
runStartedAtRef.current = Date.now()
|
runStartedAtRef.current = Date.now()
|
||||||
partStartTimesRef.current = { [startPart]: runStartedAtRef.current }
|
partStartTimesRef.current = { [startPart]: runStartedAtRef.current }
|
||||||
@@ -1201,6 +1207,9 @@ export function CombatScreen({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isPartBoss && !isFinalBoss) {
|
if (isPartBoss && !isFinalBoss) {
|
||||||
|
const nextMarathonKills = marathonBossesDefeatedRef.current + 1
|
||||||
|
marathonBossesDefeatedRef.current = nextMarathonKills
|
||||||
|
setMarathonBossesDefeated(nextMarathonKills)
|
||||||
setCombat({
|
setCombat({
|
||||||
...current,
|
...current,
|
||||||
party: nextParty,
|
party: nextParty,
|
||||||
@@ -1209,12 +1218,28 @@ export function CombatScreen({
|
|||||||
elapsedTicks: nextElapsedTicks,
|
elapsedTicks: nextElapsedTicks,
|
||||||
enemyHealth: 0,
|
enemyHealth: 0,
|
||||||
})
|
})
|
||||||
setStatus('part-complete')
|
setStatus(marathonMode && encounter.isBoss ? 'marathon-choice' : 'part-complete')
|
||||||
addLog(`${encounter.enemyName} is defeated.`, 'loot')
|
addLog(`${encounter.enemyName} is defeated.`, 'loot')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encounterIndex === encounters.length - 1) {
|
if (encounterIndex === encounters.length - 1) {
|
||||||
|
if (marathonMode && encounter.isBoss) {
|
||||||
|
const nextMarathonKills = marathonBossesDefeatedRef.current + 1
|
||||||
|
marathonBossesDefeatedRef.current = nextMarathonKills
|
||||||
|
setMarathonBossesDefeated(nextMarathonKills)
|
||||||
|
setCombat({
|
||||||
|
...current,
|
||||||
|
party: nextParty,
|
||||||
|
resource: nextResource,
|
||||||
|
cooldowns: nextCooldowns,
|
||||||
|
elapsedTicks: nextElapsedTicks,
|
||||||
|
enemyHealth: 0,
|
||||||
|
})
|
||||||
|
setStatus('marathon-choice')
|
||||||
|
addLog(`${encounter.enemyName} is defeated. Continue marathon or end the hunt.`, 'loot')
|
||||||
|
return
|
||||||
|
}
|
||||||
setCombat({
|
setCombat({
|
||||||
...current,
|
...current,
|
||||||
party: nextParty,
|
party: nextParty,
|
||||||
@@ -1267,6 +1292,7 @@ export function CombatScreen({
|
|||||||
isPartBoss,
|
isPartBoss,
|
||||||
isFinalBoss,
|
isFinalBoss,
|
||||||
isRoguelike,
|
isRoguelike,
|
||||||
|
marathonMode,
|
||||||
upgradesEveryEncounter,
|
upgradesEveryEncounter,
|
||||||
roguelikeUpgradeCatalog,
|
roguelikeUpgradeCatalog,
|
||||||
roguelikeUpgrades,
|
roguelikeUpgrades,
|
||||||
@@ -1339,7 +1365,7 @@ export function CombatScreen({
|
|||||||
}).reverse()
|
}).reverse()
|
||||||
const dualScreenState = useMemo<DualScreenCombatState>(() => ({
|
const dualScreenState = useMemo<DualScreenCombatState>(() => ({
|
||||||
difficultyName: difficulty.name,
|
difficultyName: difficulty.name,
|
||||||
dungeonName: hardMode ? `${dungeon.name} Hard` : dungeon.name,
|
dungeonName: dungeon.name,
|
||||||
contentName,
|
contentName,
|
||||||
encounterName: encounter.enemyName,
|
encounterName: encounter.enemyName,
|
||||||
encounterDescription: encounter.description,
|
encounterDescription: encounter.description,
|
||||||
@@ -1418,7 +1444,7 @@ export function CombatScreen({
|
|||||||
>
|
>
|
||||||
{!dualScreenEnabled && <header className="topbar">
|
{!dualScreenEnabled && <header className="topbar">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">{difficulty.name}{hardMode ? ' Hard' : ''} - Item Level {difficulty.droppedItemLevel}</p>
|
<p className="eyebrow">{difficulty.name} - Item Level {difficulty.droppedItemLevel}</p>
|
||||||
<h1>{dungeon.name}</h1>
|
<h1>{dungeon.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="combat-header-actions">
|
<div className="combat-header-actions">
|
||||||
@@ -1441,7 +1467,7 @@ export function CombatScreen({
|
|||||||
</div>
|
</div>
|
||||||
<div className="enemy-info">
|
<div className="enemy-info">
|
||||||
<div className="bar-label">
|
<div className="bar-label">
|
||||||
<strong>{hardMode ? `${encounter.enemyName} x2` : encounter.enemyName}</strong>
|
<strong>{encounter.enemyName}</strong>
|
||||||
<span>{Math.ceil(enemyHealth)} / {encounterMaxHealth}</span>
|
<span>{Math.ceil(enemyHealth)} / {encounterMaxHealth}</span>
|
||||||
</div>
|
</div>
|
||||||
{hardMode ? (
|
{hardMode ? (
|
||||||
@@ -1620,7 +1646,7 @@ export function CombatScreen({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{status !== 'playing' && status !== 'part-complete' && status !== 'upgrade-choice' && (
|
{status !== 'playing' && status !== 'part-complete' && status !== 'marathon-choice' && status !== 'upgrade-choice' && (
|
||||||
<div className="result-screen">
|
<div className="result-screen">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">{status === 'won' ? `${contentName} Complete` : 'Party Defeated'}</p>
|
<p className="eyebrow">{status === 'won' ? `${contentName} Complete` : 'Party Defeated'}</p>
|
||||||
@@ -1735,12 +1761,42 @@ export function CombatScreen({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{status === 'marathon-choice' && (
|
||||||
|
<div className="result-screen">
|
||||||
|
<div>
|
||||||
|
<p className="eyebrow">Marathon</p>
|
||||||
|
<h2>{encounter.enemyName} Defeated</h2>
|
||||||
|
<p>
|
||||||
|
{marathonBossesDefeated} boss{marathonBossesDefeated === 1 ? '' : 'es'} defeated.
|
||||||
|
Continue with current health and {gameClass.resourceName}, or end the hunt.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const current = combatRef.current
|
||||||
|
setCombat({
|
||||||
|
...current,
|
||||||
|
enemyHealth: encounter.maxHealth * enemyCount,
|
||||||
|
elapsedTicks: 0,
|
||||||
|
})
|
||||||
|
setStatus('playing')
|
||||||
|
addLog(`Marathon continues. Another ${encounter.enemyName} appears.`, 'danger')
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Continue Marathon
|
||||||
|
</button>
|
||||||
|
<button className="secondary-result-button" onClick={() => finishRun(currentPart, startPart)} type="button">
|
||||||
|
End
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{status === 'part-complete' && (
|
{status === 'part-complete' && (
|
||||||
<div className="result-screen">
|
<div className="result-screen">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">{sectionName} Complete</p>
|
<p className="eyebrow">{sectionName} Complete</p>
|
||||||
<h2>{encounter.enemyName} Defeated</h2>
|
<h2>{encounter.enemyName} Defeated</h2>
|
||||||
<p>{canContinueAfterPart ? `Proceed to ${sectionName} ${currentPart + 1} or end the run?` : 'Hard mode for this section is complete.'}</p>
|
<p>{canContinueAfterPart ? `Proceed to ${sectionName} ${currentPart + 1} or end the run?` : 'Run checkpoint complete.'}</p>
|
||||||
{canContinueAfterPart && (
|
{canContinueAfterPart && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ export type DualScreenCombatState = {
|
|||||||
partySize: number
|
partySize: number
|
||||||
selectedId: string
|
selectedId: string
|
||||||
log: CombatLogEntry[]
|
log: CombatLogEntry[]
|
||||||
status: 'playing' | 'won' | 'lost' | 'part-complete' | 'upgrade-choice'
|
status: 'playing' | 'won' | 'lost' | 'part-complete' | 'marathon-choice' | 'upgrade-choice'
|
||||||
resource: number
|
resource: number
|
||||||
maxResource: number
|
maxResource: number
|
||||||
resourceName: string
|
resourceName: string
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ type OfflineSave = {
|
|||||||
activeClassId: number
|
activeClassId: number
|
||||||
completedDungeonParts: number
|
completedDungeonParts: number
|
||||||
completedRaidPhases: number
|
completedRaidPhases: number
|
||||||
|
dungeonCompletions?: Record<string, number>
|
||||||
characters: Record<number, CharacterData>
|
characters: Record<number, CharacterData>
|
||||||
lootRolls: Record<string, LootRoll>
|
lootRolls: Record<string, LootRoll>
|
||||||
}
|
}
|
||||||
@@ -159,6 +160,9 @@ function upgradeV1Save(v1: { profile: CharacterProfile; lootRolls: Record<string
|
|||||||
activeClassId: p.character.classId,
|
activeClassId: p.character.classId,
|
||||||
completedDungeonParts: p.completedDungeonParts,
|
completedDungeonParts: p.completedDungeonParts,
|
||||||
completedRaidPhases: p.completedRaidPhases ?? 0,
|
completedRaidPhases: p.completedRaidPhases ?? 0,
|
||||||
|
dungeonCompletions: Object.fromEntries(
|
||||||
|
p.dungeons.map((dungeon) => [String(dungeon.id), dungeon.completionCount ?? 0]),
|
||||||
|
),
|
||||||
characters,
|
characters,
|
||||||
lootRolls: v1.lootRolls ?? {},
|
lootRolls: v1.lootRolls ?? {},
|
||||||
}
|
}
|
||||||
@@ -314,6 +318,10 @@ function buildProfile(save: OfflineSave): CharacterProfile {
|
|||||||
updateCraftingRecipes(static_)
|
updateCraftingRecipes(static_)
|
||||||
static_.completedDungeonParts = save.completedDungeonParts
|
static_.completedDungeonParts = save.completedDungeonParts
|
||||||
static_.completedRaidPhases = save.completedRaidPhases
|
static_.completedRaidPhases = save.completedRaidPhases
|
||||||
|
static_.dungeons = static_.dungeons.map((dungeon) => ({
|
||||||
|
...dungeon,
|
||||||
|
completionCount: save.dungeonCompletions?.[String(dungeon.id)] ?? dungeon.completionCount ?? 0,
|
||||||
|
}))
|
||||||
|
|
||||||
return static_
|
return static_
|
||||||
}
|
}
|
||||||
@@ -491,6 +499,9 @@ function mergeProfileIntoSave(profile: CharacterProfile, existingSave?: OfflineS
|
|||||||
activeClassId: profile.character.classId,
|
activeClassId: profile.character.classId,
|
||||||
completedDungeonParts: profile.completedDungeonParts,
|
completedDungeonParts: profile.completedDungeonParts,
|
||||||
completedRaidPhases: profile.completedRaidPhases,
|
completedRaidPhases: profile.completedRaidPhases,
|
||||||
|
dungeonCompletions: Object.fromEntries(
|
||||||
|
profile.dungeons.map((dungeon) => [String(dungeon.id), dungeon.completionCount ?? 0]),
|
||||||
|
),
|
||||||
characters,
|
characters,
|
||||||
lootRolls: clone(existingSave?.lootRolls ?? {}),
|
lootRolls: clone(existingSave?.lootRolls ?? {}),
|
||||||
}
|
}
|
||||||
@@ -958,6 +969,10 @@ function createLocalRepository(store: LocalSaveStore): GameRepository {
|
|||||||
} else {
|
} else {
|
||||||
save.completedDungeonParts = Math.max(save.completedDungeonParts, partCount)
|
save.completedDungeonParts = Math.max(save.completedDungeonParts, partCount)
|
||||||
}
|
}
|
||||||
|
save.dungeonCompletions = {
|
||||||
|
...(save.dungeonCompletions ?? {}),
|
||||||
|
[String(dungeonId)]: (save.dungeonCompletions?.[String(dungeonId)] ?? 0) + 1,
|
||||||
|
}
|
||||||
|
|
||||||
let bonusItem: DungeonReward['bonusItem'] = null
|
let bonusItem: DungeonReward['bonusItem'] = null
|
||||||
if ((startPart ?? 1) === 1 && partCount >= 3 && dungeon.completionLoot.length > 0) {
|
if ((startPart ?? 1) === 1 && partCount >= 3 && dungeon.completionLoot.length > 0) {
|
||||||
|
|||||||
+3633
-2602
File diff suppressed because it is too large
Load Diff
@@ -168,6 +168,7 @@ export type Dungeon = {
|
|||||||
difficulties: Difficulty[]
|
difficulties: Difficulty[]
|
||||||
encounters: DungeonEncounter[]
|
encounters: DungeonEncounter[]
|
||||||
completionLoot: Array<Omit<Item, 'quantity' | 'equipped'>>
|
completionLoot: Array<Omit<Item, 'quantity' | 'equipped'>>
|
||||||
|
completionCount?: number
|
||||||
leaderboard: LeaderboardEntry[]
|
leaderboard: LeaderboardEntry[]
|
||||||
leaderboards: {
|
leaderboards: {
|
||||||
part_1: LeaderboardEntry[]
|
part_1: LeaderboardEntry[]
|
||||||
|
|||||||
Reference in New Issue
Block a user