diff --git a/src-tauri/crates/olm_core/src/champions.rs b/src-tauri/crates/olm_core/src/champions.rs index 3bb2819b..6d0c5781 100644 --- a/src-tauri/crates/olm_core/src/champions.rs +++ b/src-tauri/crates/olm_core/src/champions.rs @@ -1604,6 +1604,8 @@ pub struct ChampionListEntry { pub name: String, pub tags: Vec, pub image: String, + /// Patch meta tier (S/A/B/C/D). Defaults to "B" when absent. + pub meta_tier: Option, } /// Load the champion catalog from `assets/draft/champion-list.json`. @@ -1636,6 +1638,11 @@ pub fn load_champion_catalog_from_path(path: &Path) -> Vec Vec Result Result, String> { synergies_json: row.get(5)?, image_tile_url: row.get(6)?, image_splash_url: row.get(7)?, + meta_tier: crate::domain::champion::MetaTier::B, }) }) .map_err(|e| format!("Failed to query champions: {}", e))?; @@ -203,6 +205,7 @@ pub fn get_champion_by_id(conn: &Connection, id: i64) -> Result synergies_json: row.get(5)?, image_tile_url: row.get(6)?, image_splash_url: row.get(7)?, + meta_tier: crate::domain::champion::MetaTier::B, }) }) .map_err(|e| format!("Failed to query champion: {}", e))?; @@ -234,6 +237,7 @@ pub fn get_champion_by_key(conn: &Connection, key: &str) -> Result i32 { + *self as i32 + } + + pub fn from_name(name: &str) -> Option { + match name.to_uppercase().as_str() { + "S" => Some(Self::S), + "A" => Some(Self::A), + "B" => Some(Self::B), + "C" => Some(Self::C), + "D" => Some(Self::D), + _ => None, + } + } +} + /// Represents a League of Legends champion stored in the database. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "typescript", derive(TS))] @@ -15,6 +45,9 @@ pub struct Champion { pub synergies_json: Option, pub image_tile_url: Option, pub image_splash_url: Option, + /// Current-patch meta tier (S = must-ban/pick, D = niche). + /// Populated from the patch champion list; defaults to B if unknown. + pub meta_tier: MetaTier, } /// Input for creating a new champion (without id, which is auto-generated). @@ -29,4 +62,5 @@ pub struct NewChampion { pub synergies_json: Option, pub image_tile_url: Option, pub image_splash_url: Option, + pub meta_tier: MetaTier, } diff --git a/src-tauri/src/commands/game.rs b/src-tauri/src/commands/game.rs index adaae179..0635102f 100644 --- a/src-tauri/src/commands/game.rs +++ b/src-tauri/src/commands/game.rs @@ -689,6 +689,7 @@ pub async fn get_champions() -> Result synergies_json: None, image_tile_url: Some(format!("/champion-tiles/{}.webp", champion_key)), image_splash_url: Some(format!("/champion-splash/{}.jpg", champion_key)), + meta_tier: olm_core::domain::champion::MetaTier::B, } }) .collect(); diff --git a/src/store/types.ts b/src/store/types.ts index e6fff49e..52122ba3 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -457,6 +457,7 @@ export interface ChampionData { synergies_json: string | null; image_tile_url: string | null; image_splash_url: string | null; + meta_tier: string; } export interface TransferOfferData { diff --git a/src/ui-v2/_legacy/components/match/ChampionDraft.tsx b/src/ui-v2/_legacy/components/match/ChampionDraft.tsx index 58029069..30b5157d 100644 --- a/src/ui-v2/_legacy/components/match/ChampionDraft.tsx +++ b/src/ui-v2/_legacy/components/match/ChampionDraft.tsx @@ -27,6 +27,8 @@ interface ChampionData { image: string; tags: string[]; roleHints: Role[]; + /// Patch meta tier for draft priority (S/A/B/C/D). Defaults to "B". + meta_tier: string; } interface DraftAction { @@ -780,7 +782,7 @@ export default function ChampionDraft({ useEffect(() => { try { - const data = championListSeed as { champions: Array<{ id: string; key: number; name: string; tags: string[]; image: string }> }; + const data = championListSeed as { champions: Array<{ id: string; key: number; name: string; tags: string[]; image: string; meta_tier?: string }> }; const list = (data.champions ?? []) .map((champion) => ({ id: champion.id, @@ -789,6 +791,7 @@ export default function ChampionDraft({ image: `/champion-tiles/${champion.id}.webp`, tags: champion.tags, roleHints: inferRoleHintsFromSeed(champion.id, champion.name, champion.tags), + meta_tier: champion.meta_tier ?? "B", })) .sort((a, b) => a.name.localeCompare(b.name)); @@ -2408,28 +2411,18 @@ export default function ChampionDraft({ const playerTips = tips.filter((tip) => tip.sourceType === "player"); if (draftAdviceStage === "pick") { + // Always show ALL player pick tips (one per role) so that when a player + // pivots their request the rest of the squad's preferences stay visible. const playerPickTips = playerTips.filter((tip) => tip.type === "pick").slice(0, 5); const coachWarnTips = coachTips.filter((tip) => tip.type !== "pick").slice(0, 1); - return [...playerPickTips, ...coachWarnTips].slice(0, 5); + return [...playerPickTips, ...coachWarnTips].slice(0, 6); } - const mixed: DraftAdviceTip[] = []; - let coachIdx = 0; - let playerIdx = 0; - - while (mixed.length < 4 && (coachIdx < coachTips.length || playerIdx < playerTips.length)) { - if (coachIdx < coachTips.length) { - mixed.push(coachTips[coachIdx]); - coachIdx += 1; - if (mixed.length >= 4) break; - } - if (playerIdx < playerTips.length) { - mixed.push(playerTips[playerIdx]); - playerIdx += 1; - } - } - - return mixed.slice(0, 4); + // Ban stage: show all player ban-request tips plus top coach tip. + // Player tips are rendered first and always visible. + const playerBanTips = playerTips.filter((tip) => tip.type === "ban").slice(0, 5); + const coachBanTips = coachTips.filter((tip) => tip.type === "ban").slice(0, 2); + return [...playerBanTips, ...coachBanTips].slice(0, 7); }, [ champions, gameState, @@ -2837,7 +2830,7 @@ export default function ChampionDraft({

{tip.champion ? ( -
+
{tip.champion.name} {tip.champion.name} + + {/* Meta tier badge */} + {tip.champion.meta_tier && ( + + {tip.champion.meta_tier} + + )} + + {/* Flex pick: show secondary roles if champion has multiple */} + {(() => { + const roleHints = tip.champion!.roleHints ?? []; + const secondaryRoles = roleHints.filter( + (r) => tip.sourceRole && r !== tip.sourceRole && ROLE_ORDER.includes(r as Role), + ); + if (secondaryRoles.length > 0) { + return secondaryRoles.map((role) => ( + + {role} + {role} + + )); + } + return null; + })()}
) : null}