core logic

Guide for core logic

Core Magic System Implementation

File: src/systems/skills/magic.rs

Key Components

1. Data Structures (Loaded from mantras.json)

#[derive(Deserialize, Clone)]
pub struct CombatSpell {
    pub spell_id: String,
    pub name: String,
    pub level_req: i32,
    pub bijas: Vec<BijaRequirement>,
    pub max_hit: i32,
    pub xp: f32,
    pub spell_tier: String,
}

#[derive(Deserialize, Clone)]
pub struct BijaRequirement {
    pub bija_id: String,
    pub quantity: i64,
}

#[derive(Deserialize, Clone)]
pub struct TeleportRecipe {
    pub teleport_id: String,
    pub name: String,
    pub level_req: i32,
    pub coconut_bijas: Vec<CoconutBijaSlot>,
    pub charged_coconut_id: String,
    pub destination: String,
    pub xp: f32,
}

#[derive(Deserialize, Clone)]
pub struct CoconutBijaSlot {
    pub eye: i32,
    pub bija_id: String,
}

2. Active Spell Component

#[derive(Component, Clone, Debug)]
pub struct ActiveSpell {
    pub spell_id: String,
}
Attached to player entity to track currently selected combat spell.

Event Handlers

SelectSpellEvent

Purpose: Player selects/deselects a spell from Spell Book UI
#[derive(Event)]
pub struct SelectSpellEvent {
    pub player: Entity,
    pub spell_id: Option<String>,
}
Logic:
  • If spell_id is Some: Insert/update ActiveSpell component
  • If spell_id is None: Remove ActiveSpell component

CastSpellEvent

Purpose: Player casts their active spell on a target
#[derive(Event)]
pub struct CastSpellEvent {
    pub player: Entity,
    pub target: Entity,
}
Logic:
  1. Check player has ActiveSpell component
  2. Look up spell data from SkillsData.combat_spells
  3. Verify player's Magic level >= spell's level_req
  4. Check inventory for required bijas
  5. Consume bijas from inventory
  6. Calculate damage (random 0 to max_hit)
  7. Apply damage to target's CombatStats
  8. Grant Magic XP to player
  9. Log combat message
Key Implementation Detail:
// Convert bija_id string to ItemId hash
let bija_item_id = ItemId(string_to_id(&bija.bija_id));

PrepareCoconutEvent

Purpose: Player charges a coconut with 3 bijas to create teleport item
#[derive(Event)]
pub struct PrepareCoconutEvent {
    pub player: Entity,
    pub recipe_id: String,
}
Logic:
  1. Look up recipe from SkillsData.teleport_recipes
  2. Verify player's Magic level >= recipe's level_req
  3. Check inventory for empty coconut (coconut item)
  4. Check inventory for all 3 required bijas
  5. Consume coconut + 3 bijas
  6. Add charged teleport item to inventory
  7. Grant Magic XP
Critical Fix:
// Must pass items_db to add_item
inventory.add_item(charged_id, 1, &game_data.items)?;

BreakCoconutEvent

Purpose: Player uses charged teleport to teleport
#[derive(Event)]
pub struct BreakCoconutEvent {
    pub player: Entity,
    pub charged_coconut_id: ItemId,
}
Logic:
  1. Find recipe by matching charged_coconut_id
  2. Verify player has the charged teleport in inventory
  3. Consume the teleport item
  4. Log destination (actual teleport TODO)
  5. Grant Magic XP

Integration with SkillsData

File: src/systems/data/mod.rs
#[derive(Resource)]
pub struct SkillsData {
    // ... existing fields
    pub combat_spells: HashMap<String, CombatSpell>,
    pub utility_spells: Vec<UtilitySpell>,
    pub teleport_recipes: Vec<TeleportRecipe>,
}
Loading Logic:
pub fn load_skills_data() -> Result<SkillsData, Box<dyn std::error::Error>> {
    // Load mantras.json
    let mantras_path = "data/mantras.json";
    let mantras_data = std::fs::read_to_string(mantras_path)?;
    let mantras: MantrasData = serde_json::from_str(&mantras_data)?;
    
    // Convert combat_spells Vec to HashMap
    let combat_spells = mantras.combat_spells.into_iter()
        .map(|spell| (spell.spell_id.clone(), spell))
        .collect();
    
    Ok(SkillsData {
        combat_spells,
        utility_spells: mantras.utility_spells,
        teleport_recipes: mantras.teleport_recipes,
        // ... other fields
    })
}

Common Patterns

Level Checking

let magic_level = skills.get_level(SkillType::Magic);
if magic_level < spell.level_req {
    warn!("Player level {} too low for spell (requires {})", magic_level, spell.level_req);
    return;
}

Bija Consumption

for bija in &spell.bijas {
    let bija_id = ItemId(string_to_id(&bija.bija_id));
    if inventory.count_item(bija_id) < bija.quantity {
        warn!("Not enough {} (need {})", bija.bija_id, bija.quantity);
        return;
    }
}

// Consume after validation
for bija in &spell.bijas {
    let bija_id = ItemId(string_to_id(&bija.bija_id));
    inventory.remove_item(bija_id, bija.quantity);
}

XP Granting

skills.add_xp(SkillType::Magic, spell.xp);

Compilation Fixes Applied

  1. Fixed add_item call: Added &game_data.items parameter
  2. Removed old magic integration: Commented out legacy rune-checking in combat/mod.rs
  3. Updated imports: Changed ItemId import path to match refactored inventory module
  4. Type casting: Added as i32 for level comparisons in UI