Skip to content

Commit

Permalink
attempt at improving Quest.json ARR logic and background change based…
Browse files Browse the repository at this point in the history
… on checkbox
  • Loading branch information
GitPaulo committed Aug 11, 2024
1 parent b796b15 commit 3d50bea
Show file tree
Hide file tree
Showing 5 changed files with 1,890 additions and 2,496 deletions.
138 changes: 122 additions & 16 deletions data/prepare_quest_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,17 @@
print(f"Failed to download Quest.csv. Status code: {response.status_code}")
exit()

# Load the CSV content into a DataFrame, skip the first and third rows, and use the second row as the header
usecols = ["#", "Name", "Id", "Expansion", "EventIconType", "PreviousQuest[0]", "PreviousQuest[1]", "PreviousQuest[2]", "PreviousQuest[3]"]
quest_data = pd.read_csv(StringIO(csv_content), skiprows=[0, 2], low_memory=False, usecols=usecols)
# Load the CSV content and include the first 1523 columns (including the penultimate column)
quest_data = pd.read_csv(StringIO(csv_content), skiprows=[0, 2], low_memory=False)

# Drop rows where 'Name' column has NaN values
quest_data = quest_data.dropna(subset=['Name'])

# Filter to keep rows where EventIconType is 3 which represents Main Scenario Quests
filtered_data = quest_data[quest_data['EventIconType'] == 3]

# Detect starting location based on the quest ID prefix
def detect_starting_location(quest_id):
if "Fst" in quest_id:
return "Gridania"
elif "Sea" in quest_id:
return "Limsa Lominsa"
elif "Wil" in quest_id:
return "Ul'dah"
else:
return None
# Access the penultimate column by its index (-2) and filter out obsolete quests
filtered_data = filtered_data[filtered_data.iloc[:, -2] == False]

# Convert expansion number to expansion name
def convert_expansion_number_to_name(expansion_number):
Expand Down Expand Up @@ -63,7 +54,7 @@ def convert_expansion_number_to_name(expansion_number):
with tqdm(total=len(filtered_data), desc="Processing Quests", ncols=100) as pbar:
for _, row in filtered_data.iterrows():
quest_name = row["Name"]
starting_location = detect_starting_location(row["Id"])
starting_location = None
expansion_name = convert_expansion_number_to_name(row["Expansion"])

# Optionally fetch the Image path
Expand Down Expand Up @@ -101,12 +92,47 @@ def convert_expansion_number_to_name(expansion_number):

pbar.update(1)

# Define the quests to start from and their corresponding locations
envoy_quests = {
"The Ul'dahn Envoy": "Ul'dah",
"The Lominsan Envoy": "Limsa Lominsa",
"The Gridanian Envoy": "Gridania"
}
envoy_quests_data = filtered_data[filtered_data['Name'].isin(envoy_quests.keys())]

# Traverse backwards from each envoy quest and assign starting locations
if not envoy_quests_data.empty:
for _, envoy_quest in envoy_quests_data.iterrows():
location = envoy_quests[envoy_quest['Name']]
print(f"\nAssigning starting location '{location}' by traversing backwards from '{envoy_quest['Name']}' with ID: {envoy_quest['#']}")

current_quest = envoy_quest
while current_quest is not None:
print(f"Assigning '{location}' to quest: {current_quest['Name']}, ID: {current_quest['#']}")
quests_by_number[current_quest['#']]['StartingLocation'] = location

# Find the previous quest(s)
previous_quest_ids = [str(current_quest[f'PreviousQuest[{i}]']).strip() for i in range(4) if not pd.isna(current_quest[f'PreviousQuest[{i}]'])]

# Move to the first available previous quest that is also an MSQ (EventIconType == 3)
current_quest = None
for prev_id in previous_quest_ids:
potential_quest = filtered_data.loc[(filtered_data['#'].astype(str).str.strip() == prev_id) & (filtered_data['EventIconType'] == 3)]

if not potential_quest.empty:
current_quest = potential_quest.iloc[0]
break
else:
print("No envoy quests were found in the data.")

# Calculate the next MSQ for each quest
for quest in quests_by_number.values():
for previous_quest_number in quest["PreviousQuests"]:
if previous_quest_number in quests_by_number:
quests_by_number[previous_quest_number]["NextMSQ"] = quest["#"]

print(f"NextMSQ calculated to {len(quests_by_number)} quests.")

# Remove quests that do not have a NextMSQ but are not final quests,
# but keep the quest with the highest # number.
max_quest_id = max(quests_by_number.keys())
Expand All @@ -115,7 +141,7 @@ def convert_expansion_number_to_name(expansion_number):
if quest["NextMSQ"] or quest_id == max_quest_id
}

print(f"After filtering, {len(quests_by_number)} quests remain.")
print(f"After filtering no NextMSQ, {len(quests_by_number)} quests remain.")

# Filter out duplicate quests with the same Name, NextMSQ, and StartingLocation
seen_quests = {}
Expand All @@ -131,7 +157,6 @@ def convert_expansion_number_to_name(expansion_number):

print(f"After removing duplicates, {len(quests_by_number)} quests remain.")


# Organize the data into the desired structure with correct MSQ order
quests_by_expansion = {}

Expand All @@ -147,6 +172,87 @@ def convert_expansion_number_to_name(expansion_number):

quests_by_expansion[expansion][starting_location].append(quest)

# Extract A Realm Reborn quests
arr_quests = [quest for quest in quests_by_number.values() if quest["Expansion"] == "A Realm Reborn"]

# Function to order quests based on the NextMSQ path, considering StartingLocation
def order_quests_by_next_msq(quests, location=None):
# Create a mapping from quest ID to quest
quest_map = {quest["#"]: quest for quest in quests}

# Find the starting quests (which do not appear in any NextMSQ)
start_quests = {quest["#"] for quest in quests} - {quest["NextMSQ"] for quest in quests if quest["NextMSQ"]}

if not start_quests:
print(f"No starting quest found in the A Realm Reborn category for {location if location else 'Main Quest Line'}. Skipping...")
return []

# Track processed quests to avoid duplicates
processed_quests = set()

# There might be multiple starting quests, so we handle each path separately
sorted_quests = []
for start_quest_id in start_quests:
current_quest_id = start_quest_id
while current_quest_id:
if current_quest_id not in quest_map:
print(f"Warning: Quest ID {current_quest_id} not found in quest_map. Skipping.")
break

if current_quest_id in processed_quests:
print(f"Skipping duplicate quest ID {current_quest_id}.")
break

current_quest = quest_map[current_quest_id]
sorted_quests.append(current_quest)
processed_quests.add(current_quest_id) # Mark this quest as processed

next_msq_id = current_quest["NextMSQ"]

if next_msq_id and next_msq_id in quest_map:
current_quest_id = next_msq_id
else:
current_quest_id = None # End of chain

return sorted_quests

# Sort the A Realm Reborn quests by location
sorted_quests_by_location = {}
locations = ["Gridania", "Ul'dah", "Limsa Lominsa"]

# Process each starting location
for location in locations:
location_quests = [quest for quest in quests_by_expansion["A Realm Reborn"].get(location, [])]
if location_quests: # Only process if there are quests for this location
sorted_quests_by_location[location] = order_quests_by_next_msq(location_quests, location)

len_gridania = len(sorted_quests_by_location.get("Gridania", []))
len_uldah = len(sorted_quests_by_location.get("Ul'dah", []))
len_limsa_lominsa = len(sorted_quests_by_location.get("Limsa Lominsa", []))
print(f"Sorted ARR location quests length: Gridania: {len_gridania}, Ul'dah: {len_uldah}, Limsa Lominsa: {len_limsa_lominsa}")

# Handle the Main Quest Line
main_quest_line_quests = [quest for quest in quests_by_expansion["A Realm Reborn"].get("Main Quest Line", [])]
if main_quest_line_quests:
sorted_quests_by_location["Main Quest Line"] = order_quests_by_next_msq(main_quest_line_quests, "Main Quest Line")

print(f"Sorted ARR Main Quest Line quests length: {len(sorted_quests_by_location['Main Quest Line'])}")

# Combine all sorted quests back into the quests_by_expansion structure
quests_by_expansion["A Realm Reborn"] = sorted_quests_by_location

# Validation function to check the integrity of the NextMSQ chain
def validate_next_msq_chain(sorted_quests):
for quest_list in sorted_quests.values():
for i in range(len(quest_list) - 1):
current_quest = quest_list[i]
next_quest = quest_list[i + 1]
if current_quest["NextMSQ"] != next_quest["#"]:
print(f"Validation error: Quest '{current_quest['Name']}' (ID: {current_quest['#']}) does not correctly link to '{next_quest['Name']}' (ID: {next_quest['#']}).")

# Validate the NextMSQ chain for each location and Main Quest Line
validate_next_msq_chain(sorted_quests_by_location)

# Convert the dictionary into the desired array format
quests_array = []
for expansion, locations in quests_by_expansion.items():
Expand Down
6 changes: 6 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ body {
background-color: rgba(244, 246, 244, 0.7);
border-radius: 8px;
}

@media (max-width: 640px) {
#footer-text {
display: none;
}
}
13 changes: 7 additions & 6 deletions src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body class="flex flex-col gap-y-8 mt-8" data-sveltekit-preload-data="hover">
<body class="flex flex-col mt-8" data-sveltekit-preload-data="hover">
<div class="background-container">
<div id="background" class="background-overlay"></div>
<div class="content-container">%sveltekit.body%</div>
</div>
</body>
<div id="footer" class="text-sm text-gray-400" style="display: none">
FINAL FANTASY is a registered trademark of Square Enix Holdings Co., Ltd.
<br />
FINAL FANTASY XIV © SQUARE ENIX CO., LTD.
<br />
<div id="footer" class="text-sm mt-4 text-gray-400" style="display: none">
<div id="foot-text" class="hidden md:block">
FINAL FANTASY is a registered trademark of Square Enix Holdings Co., Ltd.
<br />
FINAL FANTASY XIV © SQUARE ENIX CO., LTD.
</div>
<!-- Github -->
<a
href="https://GitHub.com/GitPaulo/FFXIV-Journey"
Expand Down
35 changes: 26 additions & 9 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(completedQuests));
}
function updateCurrentExpansion(): void {
let lastCompletedQuestId: number | null = null;
let lastCompletedExpansion: string | null = null;
for (const expansion of quests) {
for (const location in expansion.quests) {
for (const quest of expansion.quests[location]) {
if (completedQuests[quest["#"]]) {
lastCompletedQuestId = quest["#"];
lastCompletedExpansion = expansion.name;
}
}
}
}
if (lastCompletedExpansion) {
currentExpansion = lastCompletedExpansion;
updateBackground();
}
}
function resetOpenStates(): void {
openExpansions = {};
openLocations = {};
Expand Down Expand Up @@ -96,6 +117,9 @@
// Update progress bars for all expansions
calculateAllProgress();
// Update the current expansion
updateCurrentExpansion();
}
function calculateAllProgress(): void {
Expand Down Expand Up @@ -205,11 +229,12 @@
filteredQuests = loadedQuests;
resetOpenStates();
calculateAllProgress();
updateCurrentExpansion();
// Load always a bit
setTimeout(() => {
loading = false;
}, 250);
}, 350);
// Show footer after loading
const footer = document.getElementById("footer");
Expand Down Expand Up @@ -329,10 +354,6 @@
<details class="mb-8" open={openExpansions[expansion.name]}>
<summary
class="text-2xl font-semibold text-gray-800 cursor-pointer mb-4 bg-white rounded-lg p-4 shadow"
on:click={() => {
currentExpansion = expansion.name;
updateBackground();
}}
>
{expansion.name}
</summary>
Expand All @@ -344,10 +365,6 @@
{#if location !== "Main"}
<summary
class="text-xl font-semibold text-gray-600 cursor-pointer mb-3 bg-white rounded-lg p-4 shadow"
on:click={() => {
currentExpansion = expansion.name;
updateBackground();
}}
>
{location}
</summary>
Expand Down
Loading

0 comments on commit 3d50bea

Please sign in to comment.