|
"""
|
|
Redesigned Library System with Instant Discovery and Cooldown for Tag Collector Game
|
|
"""
|
|
|
|
import streamlit as st
|
|
import random
|
|
import time
|
|
import math
|
|
import pandas as pd
|
|
import datetime
|
|
from game_constants import (
|
|
TAG_CURRENCY_NAME,
|
|
RARITY_LEVELS,
|
|
ENKEPHALIN_CURRENCY_NAME,
|
|
ENKEPHALIN_ICON,
|
|
TAG_POWER_BONUSES
|
|
)
|
|
from essence_generator import display_essence_generator
|
|
from tag_categories import (
|
|
TAG_CATEGORIES,
|
|
get_collection_power_level
|
|
)
|
|
import tag_storage
|
|
|
|
|
|
LIBRARY_INFO = {
|
|
"name": "The Library",
|
|
"description": "A vast repository of knowledge where tags are discovered through patient exploration and research.",
|
|
"color": "#4A148C",
|
|
"rarities_available": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City", "Impuritas Civitas"],
|
|
"odds_multiplier": 2.0
|
|
}
|
|
|
|
|
|
LIBRARY_FLOORS = [
|
|
{
|
|
"name": "Floor of General Works",
|
|
"description": "The foundation of knowledge. Contains basic tags with limited rarity.",
|
|
"required_tags": 0,
|
|
"rarity_boost": 0.0,
|
|
"color": "#8D99AE",
|
|
"unlocked": True,
|
|
"rarities": ["Canard", "Urban Myth", "Urban Legend"],
|
|
"odds_multiplier": 1.0
|
|
},
|
|
{
|
|
"name": "Floor of History",
|
|
"description": "Archives of past knowledge. Offers more access to uncommon tags.",
|
|
"required_tags": 25,
|
|
"rarity_boost": 0.2,
|
|
"color": "#457B9D",
|
|
"rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague"],
|
|
"odds_multiplier": 1.2
|
|
},
|
|
{
|
|
"name": "Floor of Technological Sciences",
|
|
"description": "Repository of technical knowledge. Access to rare tags begins here.",
|
|
"required_tags": 75,
|
|
"rarity_boost": 0.4,
|
|
"color": "#2B9348",
|
|
"rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague"],
|
|
"odds_multiplier": 1.5
|
|
},
|
|
{
|
|
"name": "Floor of Literature",
|
|
"description": "A vast collection of narrative concepts. Higher chance of rare discoveries.",
|
|
"required_tags": 150,
|
|
"rarity_boost": 0.6,
|
|
"color": "#6A0572",
|
|
"rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare"],
|
|
"odds_multiplier": 1.8
|
|
},
|
|
{
|
|
"name": "Floor of Art",
|
|
"description": "The realm of aesthetic concepts. First access to Urban Nightmare tags.",
|
|
"required_tags": 250,
|
|
"rarity_boost": 0.8,
|
|
"color": "#D90429",
|
|
"rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare"],
|
|
"odds_multiplier": 2.2
|
|
},
|
|
{
|
|
"name": "Floor of Natural Sciences",
|
|
"description": "Where empirical knowledge is cataloged. Significant chance of very rare tags.",
|
|
"required_tags": 500,
|
|
"rarity_boost": 1.0,
|
|
"color": "#1A759F",
|
|
"rarities": ["Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City"],
|
|
"odds_multiplier": 2.5
|
|
},
|
|
{
|
|
"name": "Floor of Language",
|
|
"description": "The domain of linguistic concepts. First glimpse of Star of the City tags.",
|
|
"required_tags": 1000,
|
|
"rarity_boost": 1.2,
|
|
"color": "#FF8C00",
|
|
"rarities": ["Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City"],
|
|
"odds_multiplier": 3.0
|
|
},
|
|
{
|
|
"name": "Floor of Social Sciences",
|
|
"description": "Complex social patterns and abstractions. Notable chance of exceptional rarities.",
|
|
"required_tags": 2000,
|
|
"rarity_boost": 1.4,
|
|
"color": "#76B041",
|
|
"rarities": ["Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City"],
|
|
"odds_multiplier": 3.5
|
|
},
|
|
{
|
|
"name": "Floor of Philosophy",
|
|
"description": "The realm of profound thought. First access to the rarest 'Impuritas Civitas' tags.",
|
|
"required_tags": 5000,
|
|
"rarity_boost": 1.6,
|
|
"color": "#7209B7",
|
|
"rarities": ["Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City", "Impuritas Civitas"],
|
|
"odds_multiplier": 5.0
|
|
},
|
|
{
|
|
"name": "Floor of Religion",
|
|
"description": "The ultimate repository of the most profound conceptual territories.",
|
|
"required_tags": 10000,
|
|
"rarity_boost": 2.0,
|
|
"color": "#FFBD00",
|
|
"rarities": ["Urban Plague", "Urban Nightmare", "Star of the City", "Impuritas Civitas"],
|
|
"odds_multiplier": 10.0
|
|
}
|
|
]
|
|
|
|
def start_instant_expedition():
|
|
"""
|
|
Start an instant expedition with a cooldown before next expedition.
|
|
|
|
Returns:
|
|
List of discoveries or None if on cooldown
|
|
"""
|
|
|
|
current_time = time.time()
|
|
|
|
if hasattr(st.session_state, 'last_expedition_time'):
|
|
elapsed_time = current_time - st.session_state.last_expedition_time
|
|
cooldown_duration = calculate_expedition_duration()
|
|
|
|
if elapsed_time < cooldown_duration:
|
|
|
|
time_remaining = cooldown_duration - elapsed_time
|
|
minutes, seconds = divmod(int(time_remaining), 60)
|
|
st.error(f"Expedition on cooldown. {minutes:02d}:{seconds:02d} remaining.")
|
|
return None
|
|
|
|
|
|
discoveries = generate_expedition_discoveries()
|
|
|
|
|
|
st.session_state.last_expedition_time = current_time
|
|
|
|
|
|
tag_storage.save_game(st.session_state)
|
|
|
|
|
|
if 'library_tab_index' not in st.session_state:
|
|
st.session_state.library_tab_index = 0
|
|
|
|
return discoveries
|
|
|
|
def generate_expedition_discoveries():
|
|
"""
|
|
Generate expedition discoveries instantly.
|
|
|
|
Returns:
|
|
List of discovered tags and their info
|
|
"""
|
|
|
|
current_floor = None
|
|
collection_size = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0
|
|
|
|
if hasattr(st.session_state, 'library_floors'):
|
|
|
|
for floor in reversed(st.session_state.library_floors):
|
|
if collection_size >= floor["required_tags"]:
|
|
current_floor = floor
|
|
break
|
|
|
|
|
|
if not current_floor:
|
|
current_floor = st.session_state.library_floors[0] if hasattr(st.session_state, 'library_floors') else {
|
|
"name": "Archival Records",
|
|
"rarities": ["Canard", "Urban Myth"],
|
|
"rarity_boost": 0.0
|
|
}
|
|
|
|
|
|
rarity_odds = calculate_rarity_odds()
|
|
|
|
|
|
tags_capacity = calculate_expedition_capacity()
|
|
|
|
|
|
discoveries = []
|
|
for _ in range(tags_capacity):
|
|
|
|
rarities = list(rarity_odds.keys())
|
|
weights = list(rarity_odds.values())
|
|
selected_rarity = random.choices(rarities, weights=weights, k=1)[0]
|
|
|
|
|
|
possible_tags = []
|
|
|
|
|
|
if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata:
|
|
|
|
for tag, tag_info in st.session_state.tag_rarity_metadata.items():
|
|
|
|
if tag in st.session_state.discovered_tags:
|
|
continue
|
|
|
|
|
|
if isinstance(tag_info, dict) and "rarity" in tag_info:
|
|
if tag_info["rarity"] == selected_rarity:
|
|
possible_tags.append(tag)
|
|
elif tag_info == selected_rarity:
|
|
possible_tags.append(tag)
|
|
|
|
|
|
if not possible_tags:
|
|
if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata:
|
|
for tag, tag_info in st.session_state.tag_rarity_metadata.items():
|
|
|
|
tag_rarity = tag_info.get("rarity", tag_info) if isinstance(tag_info, dict) else tag_info
|
|
if tag_rarity != selected_rarity:
|
|
continue
|
|
|
|
possible_tags.append(tag)
|
|
|
|
|
|
if not possible_tags:
|
|
|
|
if hasattr(st.session_state, 'metadata') and 'idx_to_tag' in st.session_state.metadata:
|
|
all_tags = list(st.session_state.metadata['idx_to_tag'].values())
|
|
|
|
possible_tags = random.sample(all_tags, min(20, len(all_tags)))
|
|
else:
|
|
|
|
possible_tags = ["portrait", "landscape", "digital_art", "anime", "realistic",
|
|
"fantasy", "sci-fi", "city", "nature", "character"]
|
|
|
|
|
|
if possible_tags:
|
|
selected_tag = random.choice(possible_tags)
|
|
|
|
|
|
category = "unknown"
|
|
if hasattr(st.session_state, 'metadata') and 'tag_to_category' in st.session_state.metadata:
|
|
if selected_tag in st.session_state.metadata['tag_to_category']:
|
|
category = st.session_state.metadata['tag_to_category'][selected_tag]
|
|
|
|
|
|
is_new = tag_storage.add_discovered_tag(
|
|
tag=selected_tag,
|
|
rarity=selected_rarity,
|
|
session_state=st.session_state,
|
|
library_floor=current_floor["name"],
|
|
category=category
|
|
)
|
|
|
|
|
|
st.session_state.library_growth["total_discoveries"] += 1
|
|
st.session_state.library_growth["last_discovery_time"] = time.time()
|
|
|
|
|
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
discoveries.append({
|
|
"tag": selected_tag,
|
|
"rarity": selected_rarity,
|
|
"is_new": is_new,
|
|
"timestamp": timestamp,
|
|
"library": current_floor["name"]
|
|
})
|
|
|
|
|
|
tag_storage.save_library_state(session_state=st.session_state)
|
|
tag_storage.save_game(st.session_state)
|
|
|
|
return discoveries
|
|
|
|
def update_discovered_tag_categories():
|
|
"""Update categories of discovered tags from metadata if they're unknown"""
|
|
if not hasattr(st.session_state, 'discovered_tags') or not st.session_state.discovered_tags:
|
|
return 0
|
|
|
|
updated_count = 0
|
|
|
|
|
|
if hasattr(st.session_state, 'metadata') and 'tag_to_category' in st.session_state.metadata:
|
|
tag_to_category = st.session_state.metadata['tag_to_category']
|
|
|
|
for tag, info in st.session_state.discovered_tags.items():
|
|
if info.get('category', 'unknown') == 'unknown' and tag in tag_to_category:
|
|
info['category'] = tag_to_category[tag]
|
|
updated_count += 1
|
|
|
|
|
|
if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata:
|
|
for tag, info in st.session_state.discovered_tags.items():
|
|
if info.get('category', 'unknown') == 'unknown' and tag in st.session_state.tag_rarity_metadata:
|
|
tag_metadata = st.session_state.tag_rarity_metadata[tag]
|
|
if isinstance(tag_metadata, dict) and "category" in tag_metadata:
|
|
info['category'] = tag_metadata["category"]
|
|
updated_count += 1
|
|
|
|
if updated_count > 0:
|
|
print(f"Updated categories for {updated_count} discovered tags")
|
|
tag_storage.save_library_state(session_state=st.session_state)
|
|
|
|
return updated_count
|
|
|
|
def calculate_expedition_duration():
|
|
"""
|
|
Calculate the duration of cooldown after an expedition based on upgrades.
|
|
|
|
Returns:
|
|
Duration in seconds for the cooldown
|
|
"""
|
|
base_duration = 10
|
|
|
|
|
|
speed_level = 1
|
|
if hasattr(st.session_state, 'library_upgrades'):
|
|
speed_level = st.session_state.library_upgrades.get("speed", 1)
|
|
|
|
|
|
duration_multiplier = 0.9 ** (speed_level - 1)
|
|
|
|
|
|
duration = max(1, base_duration * duration_multiplier)
|
|
|
|
return duration
|
|
|
|
def calculate_expedition_capacity():
|
|
"""
|
|
Calculate how many tags can be discovered in one expedition.
|
|
|
|
Returns:
|
|
Number of tags that can be discovered
|
|
"""
|
|
base_capacity = 1
|
|
|
|
|
|
capacity_level = 1
|
|
if hasattr(st.session_state, 'library_upgrades'):
|
|
capacity_level = st.session_state.library_upgrades.get("capacity", 1)
|
|
|
|
|
|
capacity = base_capacity + (capacity_level - 1)
|
|
|
|
return capacity
|
|
|
|
def calculate_rarity_odds():
|
|
"""
|
|
Calculate rarity odds based on library floor level and upgrades.
|
|
|
|
Returns:
|
|
Dictionary of {rarity: probability} for available rarities
|
|
"""
|
|
|
|
current_floor = None
|
|
if hasattr(st.session_state, 'library_floors'):
|
|
collection_size = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0
|
|
|
|
|
|
for floor in reversed(st.session_state.library_floors):
|
|
if collection_size >= floor["required_tags"]:
|
|
current_floor = floor
|
|
break
|
|
|
|
|
|
if not current_floor:
|
|
current_floor = st.session_state.library_floors[0] if hasattr(st.session_state, 'library_floors') else {
|
|
"rarities": ["Canard", "Urban Myth"],
|
|
"rarity_boost": 0.0,
|
|
"odds_multiplier": 1.0
|
|
}
|
|
|
|
|
|
available_rarities = current_floor.get("rarities", ["Canard", "Urban Myth"])
|
|
odds_multiplier = current_floor.get("odds_multiplier", 1.0)
|
|
|
|
|
|
base_weights = {
|
|
"Canard": 70,
|
|
"Urban Myth": 20,
|
|
"Urban Legend": 7,
|
|
"Urban Plague": 2,
|
|
"Urban Nightmare": 1,
|
|
"Star of the City": 0.1,
|
|
"Impuritas Civitas": 0.01
|
|
}
|
|
|
|
|
|
floor_rarity_boost = current_floor.get("rarity_boost", 0.0)
|
|
|
|
|
|
rarity_level = 1
|
|
if hasattr(st.session_state, 'library_upgrades'):
|
|
rarity_level = st.session_state.library_upgrades.get("rarity", 1)
|
|
|
|
|
|
upgrade_rarity_boost = (rarity_level - 1) * 0.2
|
|
|
|
|
|
total_boost = floor_rarity_boost + upgrade_rarity_boost
|
|
|
|
|
|
adjusted_weights = {}
|
|
for rarity in available_rarities:
|
|
if rarity == "Canard":
|
|
|
|
adjusted_weights[rarity] = base_weights[rarity] * (1.0 - total_boost * 0.7)
|
|
elif rarity == "Urban Myth":
|
|
|
|
adjusted_weights[rarity] = base_weights[rarity] * (1.0 - total_boost * 0.3)
|
|
else:
|
|
|
|
rarity_index = list(RARITY_LEVELS.keys()).index(rarity)
|
|
|
|
boost_factor = 1.0 + (total_boost * odds_multiplier * (rarity_index + 1))
|
|
adjusted_weights[rarity] = base_weights[rarity] * boost_factor
|
|
|
|
|
|
total = sum(adjusted_weights.values())
|
|
normalized_weights = {r: w/total for r, w in adjusted_weights.items()}
|
|
|
|
return normalized_weights
|
|
|
|
def format_time_remaining(seconds):
|
|
"""
|
|
Format seconds into a human-readable time remaining format.
|
|
|
|
Args:
|
|
seconds: Seconds remaining
|
|
|
|
Returns:
|
|
String with formatted time
|
|
"""
|
|
if seconds < 60:
|
|
return f"{int(seconds)} seconds"
|
|
elif seconds < 3600:
|
|
minutes = seconds / 60
|
|
return f"{int(minutes)} minutes"
|
|
else:
|
|
hours = seconds / 3600
|
|
minutes = (seconds % 3600) / 60
|
|
if minutes > 0:
|
|
return f"{int(hours)} hours, {int(minutes)} minutes"
|
|
else:
|
|
return f"{int(hours)} hours"
|
|
|
|
def display_cooldown_timer():
|
|
"""Display a countdown timer until the next expedition is available"""
|
|
|
|
current_time = time.time()
|
|
cooldown_remaining = 0
|
|
cooldown_duration = calculate_expedition_duration()
|
|
|
|
if hasattr(st.session_state, 'last_expedition_time'):
|
|
elapsed_time = current_time - st.session_state.last_expedition_time
|
|
if elapsed_time < cooldown_duration:
|
|
cooldown_remaining = cooldown_duration - elapsed_time
|
|
|
|
|
|
if cooldown_remaining > 0:
|
|
minutes, seconds = divmod(int(cooldown_remaining), 60)
|
|
|
|
|
|
st.markdown("""
|
|
<div style="background-color: rgba(255, 152, 0, 0.15);
|
|
border: 1px solid #FF9800;
|
|
border-radius: 5px;
|
|
padding: 10px;
|
|
text-align: center;
|
|
margin-bottom: 15px;
|
|
color: #ffffff;">
|
|
<p style="margin: 0; font-weight: bold;">⏱️ Next expedition available in:</p>
|
|
<p style="font-size: 1.2em; margin: 5px 0;">{:02d}:{:02d}</p>
|
|
</div>
|
|
""".format(minutes, seconds), unsafe_allow_html=True)
|
|
|
|
|
|
if st.button("🔄 Refresh Timer", key="refresh_timer"):
|
|
st.rerun()
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def display_library_exploration_interface():
|
|
"""Display the unified interface for library exploration using Streamlit elements."""
|
|
|
|
tag_count = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0
|
|
|
|
|
|
if not hasattr(st.session_state, 'collected_tags') or not st.session_state.collected_tags:
|
|
st.warning("Start scanning images to collect tags first. The library will grow as you collect more tags!")
|
|
return
|
|
|
|
|
|
current_floor = None
|
|
if hasattr(st.session_state, 'library_floors'):
|
|
|
|
for floor in reversed(st.session_state.library_floors):
|
|
if tag_count >= floor["required_tags"]:
|
|
current_floor = floor
|
|
break
|
|
|
|
|
|
if not current_floor:
|
|
current_floor = st.session_state.library_floors[0] if hasattr(st.session_state, 'library_floors') else {
|
|
"name": "Floor of General Works",
|
|
"description": "The foundational level of knowledge.",
|
|
"color": "#607D8B",
|
|
"rarities": ["Canard", "Urban Myth"]
|
|
}
|
|
|
|
|
|
total_discoveries = st.session_state.library_growth["total_discoveries"]
|
|
|
|
|
|
floor_container = st.container()
|
|
with floor_container:
|
|
|
|
st.markdown(f"""
|
|
<div style="border-left: 5px solid {current_floor['color']};
|
|
border-radius: 5px;
|
|
background-color: rgba({int(current_floor['color'][1:3], 16)},
|
|
{int(current_floor['color'][3:5], 16)},
|
|
{int(current_floor['color'][5:7], 16)}, 0.15);
|
|
padding: 15px 10px 10px 15px;
|
|
margin-bottom: 15px;
|
|
color: #ffffff;">
|
|
<h3 style="margin-top: 0; color: {current_floor['color']};">{current_floor['name']}</h3>
|
|
<p>{current_floor['description']}</p>
|
|
<p>Total Discoveries: <strong>{total_discoveries}</strong></p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown("<hr style='margin: 20px 0; border: 0; height: 1px; background-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));'>", unsafe_allow_html=True)
|
|
|
|
|
|
st.subheader("Expedition Details")
|
|
|
|
|
|
capacity = calculate_expedition_capacity()
|
|
|
|
|
|
col1, col2 = st.columns(2)
|
|
|
|
with col1:
|
|
|
|
cooldown_duration = calculate_expedition_duration()
|
|
st.write(f"📊 Cooldown: {format_time_remaining(cooldown_duration)}")
|
|
st.write(f"🔍 Tag Discoveries: {capacity} per expedition")
|
|
|
|
with col2:
|
|
|
|
rarity_odds = calculate_rarity_odds()
|
|
available_rarities = current_floor.get("rarities", ["Canard", "Urban Myth"])
|
|
|
|
|
|
for rarity in available_rarities:
|
|
if rarity in rarity_odds:
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
percentage = rarity_odds[rarity]*100
|
|
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
st.markdown(f"""
|
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
|
<span style="animation: rainbow-text 4s linear infinite; font-weight: bold; width: 140px;">{rarity}:</span>
|
|
<div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;">
|
|
<div style="width: {min(percentage*5, 100)}%; background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); height: 10px; border-radius: 5px;"></div>
|
|
</div>
|
|
<span style="margin-left: 10px;">{percentage:.1f}%</span>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
elif rarity == "Star of the City":
|
|
st.markdown(f"""
|
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
|
<span style="color:{color}; text-shadow: 0 0 3px gold; font-weight: bold; width: 140px;">{rarity}:</span>
|
|
<div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;">
|
|
<div style="width: {min(percentage*5, 100)}%; background-color: {color}; box-shadow: 0 0 5px gold; height: 10px; border-radius: 5px;"></div>
|
|
</div>
|
|
<span style="margin-left: 10px;">{percentage:.1f}%</span>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
elif rarity == "Urban Nightmare":
|
|
st.markdown(f"""
|
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
|
<span style="color:{color}; text-shadow: 0 0 1px #FF5722; font-weight: bold; width: 140px;">{rarity}:</span>
|
|
<div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;">
|
|
<div style="width: {min(percentage*5, 100)}%; background-color: {color}; animation: pulse-bar 3s infinite; height: 10px; border-radius: 5px;"></div>
|
|
</div>
|
|
<span style="margin-left: 10px;">{percentage:.1f}%</span>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
else:
|
|
st.markdown(f"""
|
|
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
|
<span style="color:{color}; font-weight: bold; width: 140px;">{rarity}:</span>
|
|
<div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;">
|
|
<div style="width: {min(percentage*5, 100)}%; background-color: {color}; height: 10px; border-radius: 5px;"></div>
|
|
</div>
|
|
<span style="margin-left: 10px;">{percentage:.1f}%</span>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
on_cooldown = display_cooldown_timer()
|
|
|
|
|
|
|
|
if st.button("🚀 Start Expedition", key="start_expedition", use_container_width=True, disabled=on_cooldown):
|
|
if not on_cooldown:
|
|
discoveries = start_instant_expedition()
|
|
if discoveries:
|
|
|
|
st.session_state.expedition_results = discoveries
|
|
|
|
st.success(f"Expedition completed! Discovered {len(discoveries)} new tags!")
|
|
|
|
st.balloons()
|
|
|
|
display_expedition_results(discoveries)
|
|
|
|
tag_storage.save_game(st.session_state)
|
|
else:
|
|
|
|
st.error("Expedition on cooldown. Please wait until the timer expires.")
|
|
|
|
|
|
display_library_upgrades()
|
|
|
|
def display_expedition_results(results):
|
|
"""Display results from completed expeditions using Streamlit elements with enhanced dark-mode visuals."""
|
|
st.subheader("Expedition Discoveries")
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes rainbow-text {
|
|
0% { color: red; }
|
|
14% { color: orange; }
|
|
28% { color: yellow; }
|
|
42% { color: green; }
|
|
57% { color: blue; }
|
|
71% { color: indigo; }
|
|
85% { color: violet; }
|
|
100% { color: red; }
|
|
}
|
|
|
|
@keyframes rainbow-border {
|
|
0% { border-color: red; }
|
|
14% { border-color: orange; }
|
|
28% { border-color: yellow; }
|
|
42% { border-color: green; }
|
|
57% { border-color: blue; }
|
|
71% { border-color: indigo; }
|
|
85% { border-color: violet; }
|
|
100% { border-color: red; }
|
|
}
|
|
|
|
@keyframes star-glow {
|
|
0% { box-shadow: 0 0 5px #FFD700; }
|
|
50% { box-shadow: 0 0 15px #FFD700; }
|
|
100% { box-shadow: 0 0 5px #FFD700; }
|
|
}
|
|
|
|
@keyframes nightmare-pulse {
|
|
0% { border-color: #FF9800; }
|
|
50% { border-color: #FF5722; }
|
|
100% { border-color: #FF9800; }
|
|
}
|
|
|
|
@keyframes pulse-bar {
|
|
0% { opacity: 0.8; }
|
|
50% { opacity: 1; }
|
|
100% { opacity: 0.8; }
|
|
}
|
|
|
|
.expedition-tag-impuritas {
|
|
animation: rainbow-text 4s linear infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.expedition-card-impuritas {
|
|
background-color: rgba(255, 0, 0, 0.15);
|
|
border-radius: 8px;
|
|
border: 3px solid red;
|
|
padding: 12px;
|
|
animation: rainbow-border 4s linear infinite;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.expedition-card-star {
|
|
background-color: rgba(255, 215, 0, 0.15);
|
|
border-radius: 8px;
|
|
border: 2px solid gold;
|
|
padding: 12px;
|
|
animation: star-glow 2s infinite;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.expedition-card-nightmare {
|
|
background-color: rgba(255, 152, 0, 0.15);
|
|
border-radius: 8px;
|
|
border: 2px solid #FF9800;
|
|
padding: 12px;
|
|
animation: nightmare-pulse 3s infinite;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.expedition-card-plague {
|
|
background-color: rgba(156, 39, 176, 0.12);
|
|
border-radius: 8px;
|
|
border: 1px solid #9C27B0;
|
|
padding: 12px;
|
|
box-shadow: 0 0 3px #9C27B0;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.expedition-card-legend {
|
|
background-color: rgba(33, 150, 243, 0.15);
|
|
border-radius: 8px;
|
|
border: 1px solid #2196F3;
|
|
padding: 12px;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.expedition-card-myth {
|
|
background-color: rgba(76, 175, 80, 0.15);
|
|
border-radius: 8px;
|
|
border: 1px solid #4CAF50;
|
|
padding: 12px;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.expedition-card-canard {
|
|
background-color: rgba(170, 170, 170, 0.15);
|
|
border-radius: 8px;
|
|
border: 1px solid #AAAAAA;
|
|
padding: 12px;
|
|
color: #ffffff;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
results_by_rarity = {}
|
|
for result in results:
|
|
rarity = result["rarity"]
|
|
if rarity not in results_by_rarity:
|
|
results_by_rarity[rarity] = []
|
|
results_by_rarity[rarity].append(result)
|
|
|
|
|
|
ordered_rarities = list(RARITY_LEVELS.keys())
|
|
ordered_rarities.reverse()
|
|
|
|
|
|
for rarity in ordered_rarities:
|
|
if rarity not in results_by_rarity:
|
|
continue
|
|
|
|
rarity_results = results_by_rarity[rarity]
|
|
if not rarity_results:
|
|
continue
|
|
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
st.markdown(f"""
|
|
<div style="background-color: rgba(255, 0, 0, 0.15); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 2px solid red; animation: rainbow-border 4s linear infinite; color: #ffffff;">
|
|
<h3 style="margin-top: 0; animation: rainbow-text 4s linear infinite;">✨ EXTRAORDINARY DISCOVERY!</h3>
|
|
<p>You found {len(rarity_results)} Impuritas Civitas tag(s)!</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
st.balloons()
|
|
elif rarity == "Star of the City":
|
|
st.markdown(f"""
|
|
<div style="background-color: rgba(255, 215, 0, 0.15); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 2px solid gold; animation: star-glow 2s infinite; color: #ffffff;">
|
|
<h3 style="margin-top: 0; color: {color}; text-shadow: 0 0 3px gold;">🌟 EXCEPTIONAL DISCOVERY!</h3>
|
|
<p>You found {len(rarity_results)} Star of the City tag(s)!</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
elif rarity == "Urban Nightmare":
|
|
st.markdown(f"""
|
|
<div style="background-color: rgba(255, 152, 0, 0.15); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 2px solid #FF9800; animation: nightmare-pulse 3s infinite; color: #ffffff;">
|
|
<h3 style="margin-top: 0; color: {color}; text-shadow: 0 0 1px #FF5722;">👑 RARE DISCOVERY!</h3>
|
|
<p>You found {len(rarity_results)} Urban Nightmare tag(s)!</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
elif rarity == "Urban Plague":
|
|
st.markdown(f"""
|
|
<div style="background-color: rgba(156, 39, 176, 0.12); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 1px solid #9C27B0; box-shadow: 0 0 3px #9C27B0; color: #ffffff;">
|
|
<h3 style="margin-top: 0; color: {color}; text-shadow: 0 0 1px #9C27B0;">⚔️ UNCOMMON DISCOVERY!</h3>
|
|
<p>You found {len(rarity_results)} Urban Plague tag(s)!</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
else:
|
|
st.markdown(f"### {rarity} ({len(rarity_results)} discoveries)")
|
|
|
|
|
|
cols = st.columns(3)
|
|
for i, result in enumerate(rarity_results):
|
|
col_idx = i % 3
|
|
with cols[col_idx]:
|
|
tag = result["tag"]
|
|
floor_name = result.get("library", "Library")
|
|
|
|
|
|
rarity_class = rarity.lower().replace(' ', '-')
|
|
card_class = f"expedition-card-{rarity_class}"
|
|
|
|
|
|
tag_html = f"""<div class="{card_class}">"""
|
|
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span class="expedition-tag-impuritas">✨ {tag}</span></p>"""
|
|
elif rarity == "Star of the City":
|
|
tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; text-shadow: 0 0 3px gold; font-weight: bold;">🌟 {tag}</span></p>"""
|
|
elif rarity == "Urban Nightmare":
|
|
tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; text-shadow: 0 0 1px #FF5722; font-weight: bold;">👑 {tag}</span></p>"""
|
|
elif rarity == "Urban Plague":
|
|
tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; text-shadow: 0 0 1px #9C27B0; font-weight: bold;">⚔️ {tag}</span></p>"""
|
|
else:
|
|
tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; font-weight: bold;">{tag}</span></p>"""
|
|
|
|
|
|
is_new = result.get("is_new", False)
|
|
new_badge = """<span style="background-color: #4CAF50; color: white; padding: 2px 6px; border-radius: 10px; font-size: 0.7em; margin-left: 5px;">NEW</span>""" if is_new else ""
|
|
|
|
|
|
tag_html += f"""
|
|
<p style="margin: 0; font-size: 0.9em;">Found in: {floor_name} {new_badge}</p>
|
|
<p style="margin: 5px 0 0 0; font-size: 0.9em;">Rarity: <span style="color: {color};">{rarity}</span></p>
|
|
</div>
|
|
"""
|
|
|
|
st.markdown(tag_html, unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown("<hr style='margin: 20px 0; border: 0; height: 1px; background-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));'>", unsafe_allow_html=True)
|
|
|
|
def display_library_building():
|
|
"""Display a visual representation of the library building with all floors."""
|
|
st.subheader("The Great Library Building")
|
|
|
|
|
|
collection_size = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0
|
|
|
|
|
|
current_floor_index = 0
|
|
for i, floor in enumerate(st.session_state.library_floors):
|
|
if collection_size >= floor["required_tags"]:
|
|
current_floor_index = i
|
|
|
|
|
|
total_floors = len(st.session_state.library_floors)
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes floor-glow {
|
|
0% { box-shadow: 0 0 5px rgba(13, 110, 253, 0.5); }
|
|
50% { box-shadow: 0 0 15px rgba(13, 110, 253, 0.8); }
|
|
100% { box-shadow: 0 0 5px rgba(13, 110, 253, 0.5); }
|
|
}
|
|
|
|
.library-roof {
|
|
background: linear-gradient(90deg, #8D6E63, #A1887F);
|
|
height: 35px;
|
|
width: 90%;
|
|
margin: 0 auto;
|
|
border-radius: 8px 8px 0 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: white;
|
|
font-weight: bold;
|
|
box-shadow: 0 -2px 10px rgba(0,0,0,0.4);
|
|
}
|
|
|
|
.library-floor {
|
|
height: 65px;
|
|
width: 80%;
|
|
margin: 0 auto;
|
|
border: 1px solid #444;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 20px;
|
|
position: relative;
|
|
transition: all 0.3s ease;
|
|
color: #ffffff;
|
|
background-color: #2c2c2c;
|
|
}
|
|
|
|
.library-floor:hover {
|
|
transform: translateX(10px);
|
|
}
|
|
|
|
.library-floor.current {
|
|
box-shadow: 0 0 10px rgba(13, 110, 253, 0.5);
|
|
z-index: 2;
|
|
animation: floor-glow 2s infinite;
|
|
border-left: 5px solid #0d6efd;
|
|
}
|
|
|
|
.library-floor.locked {
|
|
background-color: #1e1e1e;
|
|
color: #777;
|
|
filter: grayscale(50%);
|
|
}
|
|
|
|
.library-floor-number {
|
|
position: absolute;
|
|
left: -30px;
|
|
width: 25px;
|
|
height: 25px;
|
|
background-color: #0d6efd;
|
|
color: white;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.library-floor.locked .library-floor-number {
|
|
background-color: #555;
|
|
}
|
|
|
|
.library-entrance {
|
|
background: linear-gradient(90deg, #5D4037, #795548);
|
|
height: 45px;
|
|
width: 35%;
|
|
margin: 0 auto;
|
|
border-radius: 10px 10px 0 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: white;
|
|
font-weight: bold;
|
|
box-shadow: 0 -2px 10px rgba(0,0,0,0.4);
|
|
}
|
|
|
|
.library-floor-details {
|
|
flex: 1;
|
|
}
|
|
|
|
.library-floor-name {
|
|
font-weight: bold;
|
|
margin: 0;
|
|
}
|
|
|
|
.library-floor-description {
|
|
font-size: 0.85em;
|
|
margin: 3px 0 0 0;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.library-floor-status {
|
|
display: flex;
|
|
align-items: center;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.library-floor-rarities {
|
|
font-size: 0.8em;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.rarity-dot {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
margin-right: 3px;
|
|
}
|
|
|
|
/* Special animations for rarer floors */
|
|
.library-floor.star {
|
|
background-color: rgba(255, 215, 0, 0.15);
|
|
}
|
|
|
|
.library-floor.impuritas {
|
|
background-color: rgba(255, 0, 0, 0.15);
|
|
}
|
|
|
|
@keyframes rainbow-border {
|
|
0% { border-color: red; }
|
|
14% { border-color: orange; }
|
|
28% { border-color: yellow; }
|
|
42% { border-color: green; }
|
|
57% { border-color: blue; }
|
|
71% { border-color: indigo; }
|
|
85% { border-color: violet; }
|
|
100% { border-color: red; }
|
|
}
|
|
|
|
.rainbow-border {
|
|
animation: rainbow-border 4s linear infinite;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown('<div class="library-roof">🏛️ The Great Library</div>', unsafe_allow_html=True)
|
|
|
|
|
|
for i in reversed(range(total_floors)):
|
|
floor = st.session_state.library_floors[i]
|
|
is_current = i == current_floor_index
|
|
is_unlocked = collection_size >= floor["required_tags"]
|
|
|
|
|
|
floor_class = "library-floor"
|
|
if is_current:
|
|
floor_class += " current"
|
|
if not is_unlocked:
|
|
floor_class += " locked"
|
|
|
|
|
|
if i >= 8 and is_unlocked:
|
|
floor_class += " impuritas"
|
|
elif i >= 6 and is_unlocked:
|
|
floor_class += " star"
|
|
|
|
|
|
rarity_dots = ""
|
|
for rarity in floor.get("rarities", []):
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
rarity_dots += f'<span class="rarity-dot" style="background-color:{color};"></span>'
|
|
|
|
|
|
if is_unlocked:
|
|
floor_style = f"background-color: rgba({int(floor['color'][1:3], 16)}, {int(floor['color'][3:5], 16)}, {int(floor['color'][5:7], 16)}, 0.25);"
|
|
else:
|
|
floor_style = ""
|
|
|
|
|
|
border_class = ""
|
|
if i == 9 and is_unlocked:
|
|
border_class = "rainbow-border"
|
|
|
|
|
|
floor_content = f"""
|
|
<div class="{floor_class} {border_class}" style="{floor_style}">
|
|
<span class="library-floor-number">{i+1}</span>
|
|
<div class="library-floor-details">
|
|
<p class="library-floor-name">{floor['name']}</p>
|
|
<p class="library-floor-description">{floor['description'] if is_unlocked else 'Locked'}</p>
|
|
<div class="library-floor-rarities">{rarity_dots}</div>
|
|
</div>
|
|
<div class="library-floor-status">
|
|
{"🔓" if is_unlocked else "🔒"} {floor['required_tags']} tags
|
|
</div>
|
|
</div>
|
|
"""
|
|
st.markdown(floor_content, unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown('<div class="library-entrance">📚 Entrance</div>', unsafe_allow_html=True)
|
|
|
|
|
|
with st.expander("Floor Details", expanded=False):
|
|
|
|
st.markdown("""
|
|
<style>
|
|
.floor-details-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.floor-details-table th {
|
|
background-color: #333;
|
|
padding: 8px;
|
|
text-align: left;
|
|
border: 1px solid #444;
|
|
}
|
|
|
|
.floor-details-table td {
|
|
padding: 8px;
|
|
border: 1px solid #444;
|
|
}
|
|
|
|
.floor-details-table tr:nth-child(even) {
|
|
background-color: rgba(255,255,255,0.03);
|
|
}
|
|
|
|
.floor-details-table tr:nth-child(odd) {
|
|
background-color: rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.floor-details-table tr:hover {
|
|
background-color: rgba(13, 110, 253, 0.1);
|
|
}
|
|
|
|
.current-floor {
|
|
background-color: rgba(13, 110, 253, 0.15) !important;
|
|
}
|
|
</style>
|
|
|
|
<table class="floor-details-table">
|
|
<tr>
|
|
<th>Floor</th>
|
|
<th>Name</th>
|
|
<th>Status</th>
|
|
<th>Req. Tags</th>
|
|
<th>Rarities</th>
|
|
<th>Rarity Boost</th>
|
|
</tr>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
for i, floor in enumerate(st.session_state.library_floors):
|
|
is_unlocked = collection_size >= floor["required_tags"]
|
|
is_current = i == current_floor_index
|
|
|
|
|
|
rarity_text = ""
|
|
for rarity in floor.get("rarities", []):
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
rarity_text += f"<span style='animation: rainbow-text 4s linear infinite;'>{rarity}</span>, "
|
|
elif rarity == "Star of the City":
|
|
rarity_text += f"<span style='color:{color}; text-shadow: 0 0 3px gold;'>{rarity}</span>, "
|
|
elif rarity == "Urban Nightmare":
|
|
rarity_text += f"<span style='color:{color}; text-shadow: 0 0 1px #FF5722;'>{rarity}</span>, "
|
|
elif rarity == "Urban Plague":
|
|
rarity_text += f"<span style='color:{color}; text-shadow: 0 0 1px #9C27B0;'>{rarity}</span>, "
|
|
else:
|
|
rarity_text += f"<span style='color:{color};'>{rarity}</span>, "
|
|
|
|
|
|
row_class = "current-floor" if is_current else ""
|
|
|
|
|
|
st.markdown(f"""
|
|
<tr class="{row_class}">
|
|
<td>{i+1}</td>
|
|
<td>{floor["name"]}</td>
|
|
<td>{"🔓 Unlocked" if is_unlocked else "🔒 Locked"}</td>
|
|
<td>{floor["required_tags"]}</td>
|
|
<td>{rarity_text[:-2] if rarity_text else ""}</td>
|
|
<td>+{int(floor.get('rarity_boost', 0) * 100)}%</td>
|
|
</tr>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown("</table>", unsafe_allow_html=True)
|
|
|
|
def add_discovered_tag(tag, rarity, library_floor=None):
|
|
"""
|
|
Add a tag to the discovered tags with enriched metadata
|
|
|
|
Args:
|
|
tag: The tag name
|
|
rarity: The tag rarity level
|
|
library_floor: The library floor where it was discovered (optional)
|
|
|
|
Returns:
|
|
bool: True if it's a new discovery, False if already discovered
|
|
"""
|
|
is_new = tag not in st.session_state.discovered_tags
|
|
|
|
|
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
category = "unknown"
|
|
if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata:
|
|
if tag in st.session_state.tag_rarity_metadata:
|
|
tag_info = st.session_state.tag_rarity_metadata[tag]
|
|
if isinstance(tag_info, dict) and "category" in tag_info:
|
|
category = tag_info["category"]
|
|
|
|
|
|
if is_new:
|
|
tag_info = {
|
|
"rarity": rarity,
|
|
"discovery_time": timestamp,
|
|
"category": category,
|
|
"discovery_count": 1,
|
|
"last_seen": timestamp
|
|
}
|
|
|
|
if library_floor:
|
|
tag_info["library_floor"] = library_floor
|
|
|
|
st.session_state.discovered_tags[tag] = tag_info
|
|
|
|
|
|
if 'explored_library_tiers' not in st.session_state:
|
|
st.session_state.explored_library_tiers = set()
|
|
|
|
if library_floor:
|
|
st.session_state.explored_library_tiers.add(library_floor)
|
|
else:
|
|
|
|
tag_info = st.session_state.discovered_tags[tag]
|
|
tag_info["discovery_count"] = tag_info.get("discovery_count", 1) + 1
|
|
tag_info["last_seen"] = timestamp
|
|
|
|
|
|
if library_floor:
|
|
tag_info["library_floor"] = library_floor
|
|
|
|
|
|
if 'explored_library_tiers' not in st.session_state:
|
|
st.session_state.explored_library_tiers = set()
|
|
|
|
st.session_state.explored_library_tiers.add(library_floor)
|
|
|
|
|
|
tag_storage.save_library_state(session_state=st.session_state)
|
|
|
|
return is_new
|
|
|
|
def calculate_upgrade_cost(upgrade_type, current_level):
|
|
base_cost = {
|
|
"speed": 50,
|
|
"capacity": 100,
|
|
"rarity": 150
|
|
}
|
|
|
|
return int(base_cost[upgrade_type] * (current_level * 1.5))
|
|
|
|
def purchase_library_upgrade(upgrade_type):
|
|
"""
|
|
Purchase a library upgrade
|
|
|
|
Args:
|
|
upgrade_type: The type of upgrade ("speed", "capacity", or "rarity")
|
|
|
|
Returns:
|
|
bool: True if purchased successfully, False otherwise
|
|
"""
|
|
|
|
current_level = st.session_state.library_upgrades.get(upgrade_type, 1)
|
|
cost = calculate_upgrade_cost(upgrade_type, current_level)
|
|
|
|
|
|
if st.session_state.tag_currency < cost:
|
|
return False
|
|
|
|
|
|
st.session_state.tag_currency -= cost
|
|
st.session_state.library_upgrades[upgrade_type] = current_level + 1
|
|
|
|
|
|
if hasattr(st.session_state, 'game_stats'):
|
|
if "currency_spent" not in st.session_state.game_stats:
|
|
st.session_state.game_stats["currency_spent"] = 0
|
|
st.session_state.game_stats["currency_spent"] += cost
|
|
|
|
|
|
tag_storage.save_library_state(session_state=st.session_state)
|
|
tag_storage.save_game(st.session_state)
|
|
|
|
return True
|
|
|
|
def display_library_upgrades():
|
|
"""Display and manage upgrades for the library using Streamlit elements with enhanced visuals."""
|
|
st.subheader("Library Upgrades")
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
.upgrade-card {
|
|
border: 1px solid #444;
|
|
border-radius: 10px;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
background-color: #222;
|
|
color: #ffffff;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.upgrade-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.upgrade-title {
|
|
font-size: 1.2em;
|
|
font-weight: bold;
|
|
margin-bottom: 10px;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.upgrade-level {
|
|
display: inline-block;
|
|
background-color: #0d6efd;
|
|
color: white;
|
|
padding: 3px 8px;
|
|
border-radius: 10px;
|
|
font-size: 0.8em;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.upgrade-stat {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.upgrade-stat-label {
|
|
width: 100px;
|
|
font-size: 0.9em;
|
|
color: #adb5bd;
|
|
}
|
|
|
|
.upgrade-stat-value {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.upgrade-cost {
|
|
margin-top: 10px;
|
|
font-weight: bold;
|
|
color: #6610f2;
|
|
}
|
|
|
|
.level-bar {
|
|
height: 6px;
|
|
background-color: #333;
|
|
border-radius: 3px;
|
|
margin-bottom: 10px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.level-progress {
|
|
height: 100%;
|
|
background-color: #0d6efd;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
@keyframes pulse-button {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.05); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
|
|
.pulse-button {
|
|
animation: pulse-button 2s infinite;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
st.write("Improve your expeditions with these upgrades:")
|
|
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
|
|
upgrades = st.session_state.library_upgrades
|
|
|
|
|
|
with col1:
|
|
speed_level = upgrades.get("speed", 1)
|
|
speed_cost = calculate_upgrade_cost("speed", speed_level)
|
|
|
|
|
|
max_level = 10
|
|
progress_percentage = min(100, (speed_level / max_level) * 100)
|
|
|
|
|
|
st.markdown(f"""
|
|
<div class="upgrade-card">
|
|
<div class="upgrade-title">⏱️ Speed Upgrade</div>
|
|
<div class="upgrade-level">Level {speed_level}</div>
|
|
<div class="level-bar">
|
|
<div class="level-progress" style="width: {progress_percentage}%;"></div>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Effect:</span>
|
|
<span class="upgrade-stat-value">Reduces cooldown time</span>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Current:</span>
|
|
<span class="upgrade-stat-value">{format_time_remaining(calculate_expedition_duration())}</span>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Next Level:</span>
|
|
<span class="upgrade-stat-value">{format_time_remaining(calculate_expedition_duration() * 0.9)}</span>
|
|
</div>
|
|
<div class="upgrade-cost" style="color: #9D4EDD;">Cost: {speed_cost} {TAG_CURRENCY_NAME}</div>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
can_afford = st.session_state.tag_currency >= speed_cost
|
|
button_class = "pulse-button" if can_afford else ""
|
|
|
|
if st.button(f"Upgrade Speed", key=f"upgrade_speed", disabled=not can_afford, use_container_width=True):
|
|
if purchase_library_upgrade("speed"):
|
|
st.success(f"Speed upgraded to level {speed_level + 1}!")
|
|
st.rerun()
|
|
else:
|
|
st.error(f"Not enough {TAG_CURRENCY_NAME}. Need {speed_cost}.")
|
|
|
|
|
|
with col2:
|
|
capacity_level = upgrades.get("capacity", 1)
|
|
capacity_cost = calculate_upgrade_cost("capacity", capacity_level)
|
|
|
|
|
|
max_level = 10
|
|
progress_percentage = min(100, (capacity_level / max_level) * 100)
|
|
|
|
|
|
st.markdown(f"""
|
|
<div class="upgrade-card">
|
|
<div class="upgrade-title">🔍 Capacity Upgrade</div>
|
|
<div class="upgrade-level">Level {capacity_level}</div>
|
|
<div class="level-bar">
|
|
<div class="level-progress" style="width: {progress_percentage}%;"></div>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Effect:</span>
|
|
<span class="upgrade-stat-value">Increases tags discovered</span>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Current:</span>
|
|
<span class="upgrade-stat-value">{calculate_expedition_capacity()} tags</span>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Next Level:</span>
|
|
<span class="upgrade-stat-value">{calculate_expedition_capacity() + 1} tags</span>
|
|
</div>
|
|
<div class="upgrade-cost" style="color: #9D4EDD;">Cost: {capacity_cost} {TAG_CURRENCY_NAME}</div>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
can_afford = st.session_state.tag_currency >= capacity_cost
|
|
button_class = "pulse-button" if can_afford else ""
|
|
|
|
if st.button(f"Upgrade Capacity", key=f"upgrade_capacity", disabled=not can_afford, use_container_width=True):
|
|
if purchase_library_upgrade("capacity"):
|
|
st.success(f"Capacity upgraded to level {capacity_level + 1}!")
|
|
st.rerun()
|
|
else:
|
|
st.error(f"Not enough {TAG_CURRENCY_NAME}. Need {capacity_cost}.")
|
|
|
|
|
|
with col3:
|
|
rarity_level = upgrades.get("rarity", 1)
|
|
rarity_cost = calculate_upgrade_cost("rarity", rarity_level)
|
|
|
|
|
|
max_level = 10
|
|
progress_percentage = min(100, (rarity_level / max_level) * 100)
|
|
|
|
|
|
st.markdown(f"""
|
|
<div class="upgrade-card">
|
|
<div class="upgrade-title">💎 Rarity Upgrade</div>
|
|
<div class="upgrade-level">Level {rarity_level}</div>
|
|
<div class="level-bar">
|
|
<div class="level-progress" style="width: {progress_percentage}%;"></div>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Effect:</span>
|
|
<span class="upgrade-stat-value">Improves rare tag chance</span>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Current:</span>
|
|
<span class="upgrade-stat-value">+{(rarity_level - 1) * 20}% boost</span>
|
|
</div>
|
|
<div class="upgrade-stat">
|
|
<span class="upgrade-stat-label">Next Level:</span>
|
|
<span class="upgrade-stat-value">+{rarity_level * 20}% boost</span>
|
|
</div>
|
|
<div class="upgrade-cost" style="color: #9D4EDD;">Cost: {rarity_cost} {TAG_CURRENCY_NAME}</div>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
can_afford = st.session_state.tag_currency >= rarity_cost
|
|
button_class = "pulse-button" if can_afford else ""
|
|
|
|
if st.button(f"Upgrade Rarity", key=f"upgrade_rarity", disabled=not can_afford, use_container_width=True):
|
|
if purchase_library_upgrade("rarity"):
|
|
st.success(f"Rapacity upgraded to level {rarity_level + 1}!")
|
|
st.rerun()
|
|
else:
|
|
st.error(f"Not enough {TAG_CURRENCY_NAME}. Need {rarity_cost}.")
|
|
|
|
|
|
st.markdown("""
|
|
<div style="background-color: rgba(13, 110, 253, 0.15);
|
|
border-left: 4px solid #0d6efd;
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
margin-top: 20px;
|
|
color: #ffffff;">
|
|
<h4 style="margin-top: 0; color: #6495ED;">📚 Library Growth</h4>
|
|
<p style="margin-bottom: 0;">
|
|
Your library will grow as you collect more tags. Each floor of the library unlocks new rarities and
|
|
improves your chances of finding rare tags. Continue collecting tags to unlock deeper levels of the library!
|
|
</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
def initialize_library_system():
|
|
"""Initialize the library system state in session state if not already present."""
|
|
if 'library_system_initialized' not in st.session_state:
|
|
st.session_state.library_system_initialized = True
|
|
|
|
|
|
library_state = tag_storage.load_library_state(st.session_state)
|
|
|
|
if library_state:
|
|
|
|
print("Library system loaded from storage.")
|
|
else:
|
|
|
|
st.session_state.discovered_tags = {}
|
|
st.session_state.library_exploration_history = []
|
|
|
|
|
|
if 'enkephalin' not in st.session_state:
|
|
st.session_state.enkephalin = 0
|
|
|
|
|
|
st.session_state.expedition_results = []
|
|
|
|
|
|
st.session_state.library_growth = {
|
|
"total_discoveries": 0,
|
|
"last_discovery_time": time.time()
|
|
}
|
|
|
|
|
|
st.session_state.library_upgrades = {
|
|
"speed": 1,
|
|
"capacity": 1,
|
|
"rarity": 1
|
|
}
|
|
|
|
|
|
st.session_state.explored_library_tiers = set()
|
|
|
|
print("Library system initialized with defaults.")
|
|
|
|
|
|
if 'library_floors' not in st.session_state:
|
|
st.session_state.library_floors = LIBRARY_FLOORS
|
|
|
|
|
|
update_discovered_tag_categories()
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
/* Star of the City animation */
|
|
@keyframes star-glow {
|
|
0% { text-shadow: 0 0 5px #FFD700; }
|
|
50% { text-shadow: 0 0 15px #FFD700; }
|
|
100% { text-shadow: 0 0 5px #FFD700; }
|
|
}
|
|
|
|
/* Impuritas Civitas animation */
|
|
@keyframes rainbow-text {
|
|
0% { color: red; }
|
|
14% { color: orange; }
|
|
28% { color: yellow; }
|
|
42% { color: green; }
|
|
57% { color: blue; }
|
|
71% { color: indigo; }
|
|
85% { color: violet; }
|
|
100% { color: red; }
|
|
}
|
|
|
|
@keyframes rainbow-border {
|
|
0% { border-color: red; }
|
|
14% { border-color: orange; }
|
|
28% { border-color: yellow; }
|
|
42% { border-color: green; }
|
|
57% { border-color: blue; }
|
|
71% { border-color: indigo; }
|
|
85% { border-color: violet; }
|
|
100% { border-color: red; }
|
|
}
|
|
|
|
/* Urban Nightmare animation */
|
|
@keyframes nightmare-pulse {
|
|
0% { border-color: #FF9800; }
|
|
50% { border-color: #FF5722; }
|
|
100% { border-color: #FF9800; }
|
|
}
|
|
|
|
/* Urban Plague subtle effect */
|
|
.glow-purple {
|
|
text-shadow: 0 0 3px #9C27B0;
|
|
}
|
|
|
|
/* Apply the animations to specific rarity classes */
|
|
.star-of-city {
|
|
animation: star-glow 2s infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.impuritas-civitas {
|
|
animation: rainbow-text 4s linear infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.urban-nightmare {
|
|
animation: nightmare-pulse 3s infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.urban-plague {
|
|
text-shadow: 0 0 3px #9C27B0;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
def display_library_extraction():
|
|
"""Display the library exploration interface."""
|
|
initialize_library_system()
|
|
|
|
st.title(f"Welcome to {LIBRARY_INFO['name']}")
|
|
st.markdown(f"""
|
|
<div style="background-color: rgba(74, 20, 140, 0.15);
|
|
border-radius: 10px;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border-left: 5px solid {LIBRARY_INFO['color']};
|
|
color: #ffffff;">
|
|
<p style="margin: 0;">{LIBRARY_INFO['description']}</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
/* Custom styling for tabs */
|
|
.stTabs [data-baseweb="tab-list"] {
|
|
gap: 2px;
|
|
}
|
|
|
|
.stTabs [data-baseweb="tab"] {
|
|
border-radius: 5px 5px 0 0;
|
|
padding: 10px 16px;
|
|
font-weight: 600;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
if 'library_tab_index' not in st.session_state:
|
|
st.session_state.library_tab_index = 0
|
|
|
|
|
|
explore_tab, discovered_tab, building_tab = st.tabs([
|
|
"📚 Library Exploration",
|
|
"🔍 Discovered Tags",
|
|
"🏛️ Library Building"
|
|
])
|
|
|
|
with explore_tab:
|
|
st.session_state.library_tab_index = 0
|
|
display_library_exploration_interface()
|
|
|
|
with discovered_tab:
|
|
st.session_state.library_tab_index = 1
|
|
st.subheader("Your Discovered Tags")
|
|
st.write("These are tags you've discovered through the library system. They differ from your collected tags, which are obtained from scanning images.")
|
|
|
|
|
|
display_discovered_tags()
|
|
|
|
with building_tab:
|
|
st.session_state.library_tab_index = 2
|
|
display_library_building()
|
|
|
|
def display_discovered_tags():
|
|
"""Display the user's discovered tags with the same visual style as the tag collection"""
|
|
|
|
if not hasattr(st.session_state, 'discovered_tags') or not st.session_state.discovered_tags:
|
|
st.info("Explore the library to discover new tags!")
|
|
return
|
|
|
|
unique_tags = len(st.session_state.discovered_tags)
|
|
st.write(f"You have discovered {unique_tags} unique tags.")
|
|
|
|
|
|
rarity_counts = {}
|
|
for tag_info in st.session_state.discovered_tags.values():
|
|
rarity = tag_info.get("rarity", "Unknown")
|
|
if rarity not in rarity_counts:
|
|
rarity_counts[rarity] = 0
|
|
rarity_counts[rarity] += 1
|
|
|
|
|
|
active_rarities = {r: c for r, c in rarity_counts.items() if c > 0}
|
|
|
|
|
|
if active_rarities:
|
|
display_discovered_rarity_distribution(active_rarities)
|
|
|
|
|
|
sort_options = ["Category (rarest first)", "Rarity", "Discovery Time"]
|
|
selected_sort = st.selectbox("Sort tags by:", sort_options, key="discovered_tags_sort")
|
|
|
|
|
|
if selected_sort == "Category (rarest first)":
|
|
|
|
categories = {}
|
|
for tag, info in st.session_state.discovered_tags.items():
|
|
category = info.get("category", "unknown")
|
|
if category not in categories:
|
|
categories[category] = []
|
|
categories[category].append((tag, info))
|
|
|
|
|
|
for category, tags in sorted(categories.items()):
|
|
|
|
rarity_order = list(RARITY_LEVELS.keys())
|
|
|
|
|
|
def get_rarity_index(tag_tuple):
|
|
tag, info = tag_tuple
|
|
rarity = info.get("rarity", "Unknown")
|
|
if rarity in rarity_order:
|
|
return len(rarity_order) - rarity_order.index(rarity)
|
|
return 0
|
|
|
|
sorted_tags = sorted(tags, key=get_rarity_index, reverse=True)
|
|
|
|
|
|
has_rare_tags = any(info.get("rarity") in ["Impuritas Civitas", "Star of the City"]
|
|
for _, info in sorted_tags)
|
|
|
|
|
|
category_display = category.capitalize()
|
|
if category in TAG_CATEGORIES:
|
|
category_info = TAG_CATEGORIES[category]
|
|
category_icon = category_info.get("icon", "")
|
|
category_color = category_info.get("color", "#888888")
|
|
category_display = f"<span style='color:{category_color};'>{category_icon} {category.capitalize()}</span>"
|
|
|
|
|
|
header = f"{category_display} ({len(tags)} tags)"
|
|
if has_rare_tags:
|
|
header += " ✨ Contains rare tags!"
|
|
|
|
|
|
st.markdown(header, unsafe_allow_html=True)
|
|
with st.expander("Show/Hide", expanded=has_rare_tags):
|
|
|
|
rarity_groups = {}
|
|
for tag, info in sorted_tags:
|
|
rarity = info.get("rarity", "Unknown")
|
|
if rarity not in rarity_groups:
|
|
rarity_groups[rarity] = []
|
|
rarity_groups[rarity].append((tag, info))
|
|
|
|
|
|
for rarity in reversed(rarity_order):
|
|
if rarity in rarity_groups:
|
|
tags_in_rarity = rarity_groups[rarity]
|
|
if tags_in_rarity:
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
rarity_style = f"animation:rainbow-text 4s linear infinite;font-weight:bold;"
|
|
elif rarity == "Star of the City":
|
|
rarity_style = f"color:{color};text-shadow:0 0 3px gold;font-weight:bold;"
|
|
elif rarity == "Urban Nightmare":
|
|
rarity_style = f"color:{color};text-shadow:0 0 1px #FF5722;font-weight:bold;"
|
|
else:
|
|
rarity_style = f"color:{color};font-weight:bold;"
|
|
|
|
st.markdown(f"<span style='{rarity_style}'>{rarity.capitalize()}</span> ({len(tags_in_rarity)} tags)", unsafe_allow_html=True)
|
|
display_discovered_tag_grid(tags_in_rarity)
|
|
st.markdown("---")
|
|
|
|
elif selected_sort == "Rarity":
|
|
|
|
rarity_groups = {}
|
|
for tag, info in st.session_state.discovered_tags.items():
|
|
rarity = info.get("rarity", "Unknown")
|
|
if rarity not in rarity_groups:
|
|
rarity_groups[rarity] = []
|
|
rarity_groups[rarity].append((tag, info))
|
|
|
|
|
|
ordered_rarities = list(RARITY_LEVELS.keys())
|
|
ordered_rarities.reverse()
|
|
|
|
|
|
for rarity in ordered_rarities:
|
|
if rarity in rarity_groups:
|
|
tags = rarity_groups[rarity]
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
rarity_html = f"<span style='color:{color};font-weight:bold;'>{rarity.capitalize()}</span>"
|
|
if rarity == "Impuritas Civitas":
|
|
rarity_html = f"<span style='animation:rainbow-text 4s linear infinite;font-weight:bold;'>{rarity.capitalize()}</span>"
|
|
elif rarity == "Star of the City":
|
|
rarity_html = f"<span style='color:{color};text-shadow:0 0 3px gold;font-weight:bold;'>{rarity.capitalize()}</span>"
|
|
elif rarity == "Urban Nightmare":
|
|
rarity_html = f"<span style='color:{color};text-shadow:0 0 1px #FF5722;font-weight:bold;'>{rarity.capitalize()}</span>"
|
|
|
|
|
|
st.markdown(f"### {rarity_html} ({len(tags)} tags)", unsafe_allow_html=True)
|
|
with st.expander("Show/Hide", expanded=rarity in ["Impuritas Civitas", "Star of the City"]):
|
|
|
|
category_groups = {}
|
|
for tag, info in tags:
|
|
category = info.get("category", "unknown")
|
|
if category not in category_groups:
|
|
category_groups[category] = []
|
|
category_groups[category].append((tag, info))
|
|
|
|
|
|
for category, category_tags in sorted(category_groups.items()):
|
|
|
|
category_display = category.capitalize()
|
|
if category in TAG_CATEGORIES:
|
|
category_info = TAG_CATEGORIES[category]
|
|
category_icon = category_info.get("icon", "")
|
|
category_color = category_info.get("color", "#888888")
|
|
category_display = f"<span style='color:{category_color};'>{category_icon} {category.capitalize()}</span>"
|
|
|
|
st.markdown(f"#### {category_display} ({len(category_tags)} tags)", unsafe_allow_html=True)
|
|
display_discovered_tag_grid(category_tags)
|
|
st.markdown("---")
|
|
|
|
elif selected_sort == "Discovery Time":
|
|
|
|
sorted_tags = []
|
|
for tag, info in st.session_state.discovered_tags.items():
|
|
discovery_time = info.get("discovery_time", "")
|
|
sorted_tags.append((tag, info, discovery_time))
|
|
|
|
sorted_tags.sort(key=lambda x: x[2], reverse=True)
|
|
|
|
|
|
date_groups = {}
|
|
for tag, info, time_str in sorted_tags:
|
|
|
|
date = time_str.split()[0] if " " in time_str else time_str
|
|
|
|
if date not in date_groups:
|
|
date_groups[date] = []
|
|
date_groups[date].append((tag, info))
|
|
|
|
|
|
for date, tags in date_groups.items():
|
|
date_display = date if date else "Unknown date"
|
|
st.markdown(f"### Discovered on {date_display} ({len(tags)} tags)")
|
|
|
|
with st.expander("Show/Hide", expanded=date == list(date_groups.keys())[0]):
|
|
display_discovered_tag_grid(tags)
|
|
st.markdown("---")
|
|
|
|
def display_discovered_rarity_distribution(active_rarities):
|
|
"""Display distribution of discovered tags by rarity with themed animations"""
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes grid-glow {
|
|
0% { text-shadow: 0 0 2px gold; }
|
|
50% { text-shadow: 0 0 6px gold; }
|
|
100% { text-shadow: 0 0 2px gold; }
|
|
}
|
|
|
|
@keyframes grid-rainbow {
|
|
0% { color: red; }
|
|
14% { color: orange; }
|
|
28% { color: yellow; }
|
|
42% { color: green; }
|
|
57% { color: blue; }
|
|
71% { color: indigo; }
|
|
85% { color: violet; }
|
|
100% { color: red; }
|
|
}
|
|
|
|
@keyframes grid-pulse {
|
|
0% { opacity: 0.8; }
|
|
50% { opacity: 1; }
|
|
100% { opacity: 0.8; }
|
|
}
|
|
|
|
.grid-star {
|
|
text-shadow: 0 0 3px gold;
|
|
animation: grid-glow 2s infinite;
|
|
}
|
|
|
|
.grid-impuritas {
|
|
animation: grid-rainbow 4s linear infinite;
|
|
}
|
|
|
|
.grid-nightmare {
|
|
text-shadow: 0 0 1px #FF5722;
|
|
animation: grid-pulse 3s infinite;
|
|
}
|
|
|
|
.grid-plague {
|
|
text-shadow: 0 0 1px #9C27B0;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
rarity_cols = st.columns(len(active_rarities))
|
|
for i, (rarity, count) in enumerate(active_rarities.items()):
|
|
with rarity_cols[i]:
|
|
|
|
color = RARITY_LEVELS.get(rarity, {}).get("color", "#888888")
|
|
|
|
|
|
style = f"color:{color};font-weight:bold;"
|
|
class_name = ""
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
class_name = "grid-impuritas"
|
|
elif rarity == "Star of the City":
|
|
class_name = "grid-star"
|
|
elif rarity == "Urban Nightmare":
|
|
class_name = "grid-nightmare"
|
|
elif rarity == "Urban Plague":
|
|
class_name = "grid-plague"
|
|
|
|
if class_name:
|
|
st.markdown(
|
|
f"<div style='text-align:center;'><span class='{class_name}' style='font-weight:bold;'>{rarity.capitalize()}</span><br>{count}</div>",
|
|
unsafe_allow_html=True
|
|
)
|
|
else:
|
|
st.markdown(
|
|
f"<div style='text-align:center;'><span style='{style}'>{rarity.capitalize()}</span><br>{count}</div>",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
def display_discovered_tag_grid(tags):
|
|
"""Display discovered tags in a grid layout with discovery information"""
|
|
|
|
cols = st.columns(3)
|
|
for i, (tag, info) in enumerate(sorted(tags)):
|
|
col_idx = i % 3
|
|
with cols[col_idx]:
|
|
rarity = info.get("rarity", "Unknown")
|
|
discovery_time = info.get("discovery_time", "")
|
|
library_floor = info.get("library_floor", "")
|
|
discovery_count = info.get("discovery_count", 1)
|
|
|
|
color = RARITY_LEVELS.get(rarity, {}).get("color", "#888888")
|
|
|
|
|
|
sample_count = None
|
|
if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata:
|
|
if tag in st.session_state.tag_rarity_metadata:
|
|
tag_info = st.session_state.tag_rarity_metadata[tag]
|
|
if isinstance(tag_info, dict) and "sample_count" in tag_info:
|
|
sample_count = tag_info["sample_count"]
|
|
|
|
|
|
sample_display = ""
|
|
if sample_count is not None:
|
|
if sample_count >= 1000000:
|
|
sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count/1000000:.1f}M)</span>"
|
|
elif sample_count >= 1000:
|
|
sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count/1000:.1f}K)</span>"
|
|
else:
|
|
sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count})</span>"
|
|
|
|
|
|
tag_html = tag
|
|
if rarity == "Impuritas Civitas":
|
|
tag_html = f"<span style='animation: rainbow-text 4s linear infinite;'>{tag}</span>"
|
|
elif rarity == "Star of the City":
|
|
tag_html = f"<span style='text-shadow: 0 0 3px gold;'>{tag}</span>"
|
|
elif rarity == "Urban Nightmare":
|
|
tag_html = f"<span style='text-shadow: 0 0 1px #FF9800;'>{tag}</span>"
|
|
|
|
|
|
st.markdown(
|
|
f"{tag_html} <span style='background-color:{color};color:white;padding:2px 6px;border-radius:10px;font-size:0.8em;'>{rarity.capitalize()}</span> {sample_display}",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
|
|
if library_floor:
|
|
st.markdown(f"<span style='font-size:0.85em;'>Found in: {library_floor}</span>", unsafe_allow_html=True)
|
|
|
|
if discovery_count > 1:
|
|
st.markdown(f"<span style='font-size:0.85em;'>Seen {discovery_count} times</span>", unsafe_allow_html=True) |