Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5aac39c6c9 | |||
| 224249e372 | |||
| 66f5af4484 | |||
| 8f5a957963 | |||
| f8a1fbc5e2 | |||
| bab2dce6c3 | |||
| cb38042eca |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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 55
|
versionCode 62
|
||||||
versionName "1.0.36"
|
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.
|
||||||
|
|||||||
+460
-37
@@ -106,8 +106,7 @@ 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.'),
|
||||||
@@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
+258
-44
@@ -1683,7 +1683,8 @@ h2 {
|
|||||||
|
|
||||||
.equipment-screen .equipment-layout,
|
.equipment-screen .equipment-layout,
|
||||||
.equipment-screen .crafting-panel,
|
.equipment-screen .crafting-panel,
|
||||||
.talent-screen .talent-tree {
|
.talent-screen .talent-tree,
|
||||||
|
.talent-screen .spell-effect-layout {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
@@ -1794,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;
|
||||||
@@ -1922,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;
|
||||||
}
|
}
|
||||||
@@ -2024,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;
|
||||||
@@ -2068,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;
|
||||||
@@ -2206,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 {
|
||||||
@@ -2257,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,
|
||||||
@@ -2272,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);
|
||||||
}
|
}
|
||||||
@@ -3474,6 +3524,8 @@ h2 {
|
|||||||
.effect-pool-panel {
|
.effect-pool-panel {
|
||||||
background: #191b25;
|
background: #191b25;
|
||||||
border: 2px solid #090a0d;
|
border: 2px solid #090a0d;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
outline: 2px solid #3a3944;
|
outline: 2px solid #3a3944;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
@@ -3581,24 +3633,30 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.effect-pool {
|
.effect-pool {
|
||||||
|
align-content: start;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
flex: 1;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
grid-template-rows: repeat(2, 62px);
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.effect-pool > button {
|
.effect-pool > button {
|
||||||
align-items: center;
|
align-content: center;
|
||||||
background: #20222d;
|
background: #20222d;
|
||||||
border: 2px solid #090a0d;
|
border: 2px solid #090a0d;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
grid-template-columns: 34px minmax(0, 1fr) auto;
|
grid-template-columns: 26px minmax(0, 1fr);
|
||||||
min-height: 72px;
|
min-height: 0;
|
||||||
outline: 2px solid #3a3944;
|
outline: 2px solid #3a3944;
|
||||||
padding: 9px;
|
overflow: hidden;
|
||||||
|
padding: 7px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3623,7 +3681,7 @@ h2 {
|
|||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
display: flex;
|
display: flex;
|
||||||
font-family: 'Press Start 2P', monospace;
|
font-family: 'Press Start 2P', monospace;
|
||||||
height: 34px;
|
height: 26px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3634,17 +3692,54 @@ h2 {
|
|||||||
|
|
||||||
.effect-pool strong {
|
.effect-pool strong {
|
||||||
font-family: 'Press Start 2P', monospace;
|
font-family: 'Press Start 2P', monospace;
|
||||||
font-size: 8px;
|
font-size: 7px;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.effect-pool small {
|
.effect-pool small {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 14px;
|
font-size: 11px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.effect-pool > button i {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect-pager {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect-pager button {
|
||||||
|
background: #15161c;
|
||||||
|
border: 2px solid #090a0d;
|
||||||
|
color: var(--ink);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: 'Press Start 2P', monospace;
|
||||||
|
font-size: 7px;
|
||||||
|
min-height: 28px;
|
||||||
|
outline: 2px solid #41404a;
|
||||||
|
padding: 4px 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect-pager button:disabled {
|
||||||
|
color: #676773;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect-pager span {
|
||||||
|
color: var(--gold);
|
||||||
|
font-family: 'Press Start 2P', monospace;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.spell-effect-layout {
|
.spell-effect-layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -3655,7 +3750,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.effect-pool {
|
.effect-pool {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-effect-strip {
|
.selected-effect-strip {
|
||||||
@@ -7576,6 +7671,125 @@ h2 {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workshop-shell .spell-effect-layout {
|
||||||
|
gap: 6px;
|
||||||
|
grid-template-columns: 172px minmax(0, 1fr);
|
||||||
|
margin-top: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-slots-panel,
|
||||||
|
.workshop-shell .effect-pool-panel {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-slots-panel {
|
||||||
|
gap: 5px;
|
||||||
|
grid-auto-rows: minmax(43px, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-slot {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-slot span,
|
||||||
|
.workshop-shell .effect-pool > button i,
|
||||||
|
.workshop-shell .effect-panel-heading > span,
|
||||||
|
.workshop-shell .effect-pager span {
|
||||||
|
font-size: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-slot strong {
|
||||||
|
font-size: 6px;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-slot small {
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: 2px;
|
||||||
|
max-height: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-panel-heading h2 {
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .selected-effect-strip {
|
||||||
|
gap: 6px;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .selected-effect-strip .eyebrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .selected-effect-strip strong {
|
||||||
|
font-size: 7px;
|
||||||
|
line-height: 1.1;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .selected-effect-strip small {
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: 3px;
|
||||||
|
max-height: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .selected-effect-strip .primary-button {
|
||||||
|
font-size: 7px;
|
||||||
|
min-height: 25px;
|
||||||
|
min-width: 72px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-pool {
|
||||||
|
gap: 5px;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
grid-template-rows: repeat(2, 52px);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-pool > button {
|
||||||
|
gap: 4px;
|
||||||
|
grid-template-columns: 22px minmax(0, 1fr);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-pool > button > span {
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-pool strong {
|
||||||
|
font-size: 6px;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-pool small {
|
||||||
|
font-size: 9px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-pager {
|
||||||
|
gap: 5px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workshop-shell .effect-pager button {
|
||||||
|
font-size: 6px;
|
||||||
|
min-height: 22px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.workshop-shell .talent-tier {
|
.workshop-shell .talent-tier {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
grid-template-columns: 66px minmax(0, 1fr);
|
grid-template-columns: 66px minmax(0, 1fr);
|
||||||
|
|||||||
+62
-47
@@ -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>
|
||||||
|
<div className="run-title-row">
|
||||||
<h2>{activity.name}</h2>
|
<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>
|
||||||
|
{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>
|
<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,44 +742,42 @@ 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) => (
|
|
||||||
<div className="part-start-row" key={p.part}>
|
|
||||||
<button
|
<button
|
||||||
className={`primary-button ${selectedPart === p.part && !selectedHardMode ? 'selected-part' : ''} ${!p.unlocked ? 'locked' : ''}`}
|
className="primary-button selected-part"
|
||||||
disabled={difficultyLocked || !p.unlocked}
|
disabled={difficultyLocked}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedPart(p.part)
|
setSelectedMarathonMode(false)
|
||||||
setSelectedHardMode(false)
|
|
||||||
setCombatContentId(activity.id)
|
setCombatContentId(activity.id)
|
||||||
setSelectedDifficultyId(selectedDifficulty.id)
|
setSelectedDifficultyId(selectedDifficulty.id)
|
||||||
setScreen('combat')
|
setScreen('combat')
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{p.name}
|
Start Hunt
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`primary-button hard-mode-button ${selectedPart === p.part && selectedHardMode ? 'selected-part' : ''} ${!p.hardUnlocked ? 'locked' : ''}`}
|
className={`primary-button ${selectedMarathonMode ? 'selected-part' : ''}`}
|
||||||
disabled={difficultyLocked || !p.hardUnlocked}
|
disabled={difficultyLocked}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedPart(p.part)
|
setSelectedMarathonMode(true)
|
||||||
setSelectedHardMode(true)
|
|
||||||
setCombatContentId(activity.id)
|
setCombatContentId(activity.id)
|
||||||
setSelectedDifficultyId(selectedDifficulty.id)
|
setSelectedDifficultyId(selectedDifficulty.id)
|
||||||
setScreen('combat')
|
setScreen('combat')
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Hard
|
Marathon
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div className="difficulty-section compact-difficulty-section">
|
<div className="difficulty-section compact-difficulty-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={() => {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type Props = {
|
|||||||
|
|
||||||
const EFFECT_SLOT_LEVELS = [5, 10, 15, 20] as const
|
const EFFECT_SLOT_LEVELS = [5, 10, 15, 20] as const
|
||||||
const EFFECT_CLASS_ID = 1
|
const EFFECT_CLASS_ID = 1
|
||||||
|
const EFFECTS_PER_PAGE = 8
|
||||||
const EFFECT_SOURCE_LABELS: Record<string, string> = {
|
const EFFECT_SOURCE_LABELS: Record<string, string> = {
|
||||||
mend: 'Mend',
|
mend: 'Mend',
|
||||||
radiance: 'Radiance',
|
radiance: 'Radiance',
|
||||||
@@ -42,6 +43,7 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P
|
|||||||
const [busyTalentId, setBusyTalentId] = useState<number | null>(null)
|
const [busyTalentId, setBusyTalentId] = useState<number | null>(null)
|
||||||
const [resetting, setResetting] = useState(false)
|
const [resetting, setResetting] = useState(false)
|
||||||
const [selectedTalentId, setSelectedTalentId] = useState<number | null>(null)
|
const [selectedTalentId, setSelectedTalentId] = useState<number | null>(null)
|
||||||
|
const [effectPage, setEffectPage] = useState(0)
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
const scrollRef = useRef<number>(0)
|
const scrollRef = useRef<number>(0)
|
||||||
const gameClass = profile.classes.find(
|
const gameClass = profile.classes.find(
|
||||||
@@ -54,6 +56,11 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P
|
|||||||
?? selectedEffects[0]
|
?? selectedEffects[0]
|
||||||
?? gameClass.talents[0]
|
?? gameClass.talents[0]
|
||||||
?? null
|
?? null
|
||||||
|
const effectPageCount = Math.max(1, Math.ceil(gameClass.talents.length / EFFECTS_PER_PAGE))
|
||||||
|
const visibleTalents = gameClass.talents.slice(
|
||||||
|
effectPage * EFFECTS_PER_PAGE,
|
||||||
|
effectPage * EFFECTS_PER_PAGE + EFFECTS_PER_PAGE,
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scrollTo(0, scrollRef.current)
|
window.scrollTo(0, scrollRef.current)
|
||||||
@@ -64,6 +71,10 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P
|
|||||||
setSelectedTalentId(selectedTalent?.id ?? null)
|
setSelectedTalentId(selectedTalent?.id ?? null)
|
||||||
}, [gameClass.talents, selectedTalent?.id, selectedTalentId])
|
}, [gameClass.talents, selectedTalent?.id, selectedTalentId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEffectPage((page) => Math.min(page, effectPageCount - 1))
|
||||||
|
}, [effectPageCount])
|
||||||
|
|
||||||
function saveScroll() {
|
function saveScroll() {
|
||||||
scrollRef.current = window.scrollY
|
scrollRef.current = window.scrollY
|
||||||
}
|
}
|
||||||
@@ -225,7 +236,7 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="effect-pool">
|
<div className="effect-pool">
|
||||||
{gameClass.talents.map((talent) => {
|
{visibleTalents.map((talent) => {
|
||||||
const reason = lockReason(talent)
|
const reason = lockReason(talent)
|
||||||
const active = talent.rank > 0
|
const active = talent.rank > 0
|
||||||
const selected = selectedTalent?.id === talent.id
|
const selected = selectedTalent?.id === talent.id
|
||||||
@@ -244,13 +255,32 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P
|
|||||||
<span>{talent.glyph}</span>
|
<span>{talent.glyph}</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>{talent.name}</strong>
|
<strong>{talent.name}</strong>
|
||||||
<small>{talent.description}</small>
|
<small>{EFFECT_SOURCE_LABELS[effectSource(talent.effectType)] ?? 'Spell'}</small>
|
||||||
</div>
|
</div>
|
||||||
<i>{isBusy ? 'Saving' : active ? 'Active' : reason || 'Available'}</i>
|
<i>{isBusy ? 'Saving' : active ? 'Active' : reason || 'Available'}</i>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{effectPageCount > 1 && (
|
||||||
|
<div className="effect-pager">
|
||||||
|
<button
|
||||||
|
disabled={effectPage === 0}
|
||||||
|
onClick={() => setEffectPage((page) => Math.max(0, page - 1))}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Prev
|
||||||
|
</button>
|
||||||
|
<span>{effectPage + 1}/{effectPageCount}</span>
|
||||||
|
<button
|
||||||
|
disabled={effectPage >= effectPageCount - 1}
|
||||||
|
onClick={() => setEffectPage((page) => Math.min(effectPageCount - 1, page + 1))}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+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) {
|
||||||
|
|||||||
+3638
-2607
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