ui integration

Guide for ui integration

UI Integration

File: src/systems/ui/mod.rs

This document describes how the weapon interface system is integrated into the main UI layout.

System Parameters Update

Added GameData to UiParams

Before:
#[derive(bevy::ecs::system::SystemParam)]
pub struct UiParams<'w, 's> {
    pub ui_assets: Res<'w, UiAssets>,
    pub interactables: Query<'w, 's, &'static crate::systems::api::Interactable>,
    pub player_q: Query<'w, 's, (Entity, &'static crate::systems::player::skills::Skills, Option<&'static crate::systems::skills::magic::ActiveSpell>), With<crate::systems::player::Player>>,
    pub skills_q: Query<'w, 's, &'static crate::systems::player::skills::Skills>,
    pub inventory_q: Query<'w, 's, &'static crate::systems::player::inventory::Inventory>,
    pub equipment_q: Query<'w, 's, &'static crate::systems::player::inventory::Equipment>,
    pub skills_data: Res<'w, crate::systems::data::SkillsData>,
    pub spell_db: Res<'w, crate::systems::magic::SpellDatabase>,
}
After:
#[derive(bevy::ecs::system::SystemParam)]
pub struct UiParams<'w, 's> {
    pub ui_assets: Res<'w, UiAssets>,
    pub interactables: Query<'w, 's, &'static crate::systems::api::Interactable>,
    pub player_q: Query<'w, 's, (Entity, &'static crate::systems::player::skills::Skills, Option<&'static crate::systems::skills::magic::ActiveSpell>), With<crate::systems::player::Player>>,
    pub skills_q: Query<'w, 's, &'static crate::systems::player::skills::Skills>,
    pub inventory_q: Query<'w, 's, &'static crate::systems::player::inventory::Inventory>,
    pub equipment_q: Query<'w, 's, &'static crate::systems::player::inventory::Equipment>,
    pub skills_data: Res<'w, crate::systems::data::SkillsData>,
    pub game_data: Res<'w, crate::systems::data::GameData>,  // NEW
    pub spell_db: Res<'w, crate::systems::magic::SpellDatabase>,
}
Purpose: Access to GameData allows looking up ItemDefinition for equipped weapons.

Combat Tab Rendering

Old Implementation (Hardcoded)

UiTab::Combat => {
    ui.heading("Combat");
    ui.separator();
    
    let mut combat_level = 3;
    if let Ok((player_entity, _, _)) = player_q.get_single() {
        if let Ok(skills) = skills_q.get(player_entity) {
            combat_level = skills.get_combat_level();
        }
    }

    ui.label(format!("Level: {}", combat_level));
    ui.add_space(10.0);
    
    // HARDCODED STYLES
    let styles = [
        ("Accurate", AttackStyle::Accurate),
        ("Aggressive", AttackStyle::Aggressive),
        ("Defensive", AttackStyle::Defensive),
        ("Controlled", AttackStyle::Controlled),
    ];
    
    ui.columns(2, |columns| {
        for (i, (name, style)) in styles.iter().enumerate() {
            let is_selected = ui_state.selected_attack_style == *style;
            let btn = egui::Button::new(*name).selected(is_selected).min_size(egui::Vec2::new(80.0, 30.0));
            if columns[i % 2].add(btn).clicked() {
                ui_state.selected_attack_style = *style;
                combat_style_writer.send(CombatStyleEvent(*style));
            }
        }
    });
    ui.add_space(10.0);
    ui.label("Auto Retaliate: On");
},

New Implementation (Dynamic)

UiTab::Combat => {
    ui.heading("Combat");
    ui.separator();
    
    // Get Combat Level from Player
    let mut combat_level = 3;
    if let Ok((player_entity, _, _)) = player_q.get_single() {
        if let Ok(skills) = skills_q.get(player_entity) {
            combat_level = skills.get_combat_level();
        }
    }

    ui.label(format!("Level: {}", combat_level));
    ui.add_space(10.0);
    
    // STEP 1: Get Equipped Weapon
    let mut equipped_weapon_def = None;
    if let Ok(equipment) = equipment_q.get(player_entity) {
        if let Some(slot) = &equipment.weapon {
            equipped_weapon_def = params.game_data.items.get(&slot.id.0);
        }
    }

    // STEP 2: Get Weapon Interface
    let interface = crate::systems::combat::weapon_styles::get_weapon_interface(equipped_weapon_def);
    
    // STEP 3: Display Weapon Name
    ui.label(egui::RichText::new(&interface.name).strong().color(egui::Color32::WHITE));
    ui.add_space(10.0);
    
    let btn_size = egui::Vec2::new(90.0, 40.0);
    
    // STEP 4: Render Dynamic Buttons
    ui.columns(2, |columns| {
        for (i, option) in interface.styles.iter().enumerate() {
            // Check if this option matches current selected style
            let is_selected = ui_state.selected_attack_style == option.style;
            
            let btn = egui::Button::new(egui::RichText::new(&option.name).size(14.0))
                .selected(is_selected)
                .min_size(btn_size);
                
            if columns[i % 2].add(btn).on_hover_text(&option.description).clicked() {
                ui_state.selected_attack_style = option.style;
                combat_style_writer.send(CombatStyleEvent(option.style));
                // TODO: Update combat type in backend if needed
            }
        }
    });
    
    ui.add_space(15.0);
    
    // STEP 5: Auto-Engage Toggle Button
    let toggle_text = if true { "ON" } else { "OFF" }; // TODO: Read actual state
    let retaliate_color = if true { egui::Color32::GREEN } else { egui::Color32::RED };
    
    if ui.add(egui::Button::new(
        egui::RichText::new(format!("Auto-Engage: {}", toggle_text))
            .color(retaliate_color)
    ).min_size(egui::Vec2::new(180.0, 30.0))).clicked() {
        // TODO: Toggle auto retaliate component
    }
},

Key Changes

1. Weapon Lookup

let mut equipped_weapon_def = None;
if let Ok(equipment) = equipment_q.get(player_entity) {
    if let Some(slot) = &equipment.weapon {
        equipped_weapon_def = params.game_data.items.get(&slot.id.0);
    }
}
Purpose: Retrieves the ItemDefinition for the equipped weapon from GameData.
Fallback: If no weapon is equipped, equipped_weapon_def remains None, which triggers the unarmed interface.

2. Interface Retrieval

let interface = crate::systems::combat::weapon_styles::get_weapon_interface(equipped_weapon_def);
Purpose: Gets the appropriate WeaponInterface based on weapon type.
Returns: WeaponInterface struct with weapon name and available combat styles.

3. Weapon Name Display

ui.label(egui::RichText::new(&interface.name).strong().color(egui::Color32::WHITE));
Purpose: Shows "Unarmed", "Sword", "Bow", etc. to inform the player.
Styling: Bold white text for visibility.

4. Dynamic Button Rendering

ui.columns(2, |columns| {
    for (i, option) in interface.styles.iter().enumerate() {
        let is_selected = ui_state.selected_attack_style == option.style;
        
        let btn = egui::Button::new(egui::RichText::new(&option.name).size(14.0))
            .selected(is_selected)
            .min_size(btn_size);
            
        if columns[i % 2].add(btn).on_hover_text(&option.description).clicked() {
            ui_state.selected_attack_style = option.style;
            combat_style_writer.send(CombatStyleEvent(option.style));
        }
    }
});
Key Features:
  • Dynamic Count: Renders 3 or 4 buttons depending on weapon type
  • Hover Tooltips: Shows style descriptions on hover
  • Selection State: Highlights currently selected style
  • Event Emission: Sends CombatStyleEvent to update combat system

5. Auto-Engage Button

let toggle_text = if true { "ON" } else { "OFF" }; // TODO: Read actual state
let retaliate_color = if true { egui::Color32::GREEN } else { egui::Color32::RED };

if ui.add(egui::Button::new(
    egui::RichText::new(format!("Auto-Engage: {}", toggle_text))
        .color(retaliate_color)
).min_size(egui::Vec2::new(180.0, 30.0))).clicked() {
    // TODO: Toggle auto retaliate component
}
Features:
  • Renamed: "Auto Retaliate" → "Auto-Engage"
  • Visual Feedback: Green for ON, Red for OFF
  • Full-Width Button: 180px wide for prominence
  • TODO: Implement actual toggle logic

Data Flow

User Equips Weapon
       │
       ▼
Equipment Component Updated
       │
       ▼
UI System Queries Equipment
       │
       ▼
Lookup ItemDefinition in GameData
       │
       ▼
Call get_weapon_interface(item_def)
       │
       ▼
Weapon Type Detection (string matching)
       │
       ▼
Return WeaponInterface
       │
       ▼
Render Dynamic Buttons
       │
       ▼
User Clicks Style Button
       │
       ▼
Send CombatStyleEvent
       │
       ▼
Combat System Updates AttackStyle Component

Style Selection Persistence

The selected style is stored in UiState:
#[derive(Resource, Default)]
pub struct UiState {
    pub active_tab: UiTab,
    pub selected_attack_style: AttackStyle,  // Persisted selection
    pub active_menu: Option<ActiveMenu>,
}
Behavior:
  • Selection persists when switching weapons
  • If new weapon doesn't support current style, it remains selected but may not be optimal
  • Future Enhancement: Auto-select first available style when weapon changes

Event Handling

CombatStyleEvent

#[derive(Event)]
pub struct CombatStyleEvent(pub AttackStyle);
Sent When: User clicks a combat style button
Handled By: Combat system (updates AttackStyle component on player entity)
Purpose: Synchronizes UI selection with combat logic

TODO Items

  1. Auto-Engage State Management:
    • Add AutoRetaliate component to player entity
    • Read actual state instead of hardcoded true
    • Implement toggle logic in event handler
  2. Combat Type Propagation:
    • Currently only Style is sent via event
    • May need to also update CombatType when style changes
    • Consider adding CombatType to CombatStyleEvent
  3. Style Validation:
    • Prevent selecting styles not available for current weapon
    • Auto-select first available style on weapon change
  4. Visual Polish:
    • Add weapon icon next to weapon name
    • Animate button selection
    • Add sound effects for style changes

Testing Scenarios

  1. Weapon Switching:
    • Equip sword → verify 4 buttons (Chop, Slash, Lunge, Block)
    • Equip bow → verify 3 buttons (Precision, Burst, Snipe)
    • Unequip weapon → verify 3 buttons (Punch, Kick, Block)
  2. Style Selection:
    • Click each button → verify selection highlight
    • Hover over buttons → verify tooltips appear
    • Switch weapons → verify selection persists
  3. Auto-Engage:
    • Click button → verify color changes (when implemented)
    • Verify combat behavior matches toggle state
  4. Edge Cases:
    • Unknown weapon type → verify generic interface
    • Corrupted item data → verify graceful fallback
    • Rapid weapon switching → verify no UI glitches