Implement guild system with multi-guild, spells, and combat casting
- Guild data model: world/guilds/*.toml defines guilds with stat growth, resource type (mana/endurance), spell lists, level caps, and race restrictions. Spells are separate files in world/spells/*.toml. - Player resources: mana and endurance pools added to PlayerStats with DB migration. Passive regen for mana/endurance when out of combat. - Guild commands: guild list/info/join/leave with multi-guild support. spells/skills command shows available spells per guild level. - Spell casting: cast command works in and out of combat. Offensive spells queue as CombatAction::Cast and resolve on tick. Heal/utility spells resolve immediately out of combat. Mana/endurance costs, cooldowns, and damage types all enforced. - Chargen integration: classes reference a guild via TOML field. On character creation, player auto-joins the seeded guild at level 1. Class selection shows "→ joins <Guild>" hint. - Look command: now accepts optional target argument to inspect NPCs (stats/attitude), objects, exits, players, or inventory items. - 4 guilds (warriors/rogues/mages/clerics) and 16 spells created for the existing class archetypes. Made-with: Cursor
This commit is contained in:
@@ -132,6 +132,95 @@ pub fn resolve_combat_tick(
|
||||
));
|
||||
}
|
||||
}
|
||||
CombatAction::Cast(ref spell_id) => {
|
||||
let spell = state.world.get_spell(spell_id).cloned();
|
||||
if let Some(spell) = spell {
|
||||
if let Some(conn) = state.players.get_mut(&player_id) {
|
||||
if spell.cost_mana > conn.player.stats.mana {
|
||||
out.push_str(&format!(
|
||||
" {} Not enough mana for {}!\r\n",
|
||||
ansi::color(ansi::RED, "!!"), spell.name,
|
||||
));
|
||||
} else if spell.cost_endurance > conn.player.stats.endurance {
|
||||
out.push_str(&format!(
|
||||
" {} Not enough endurance for {}!\r\n",
|
||||
ansi::color(ansi::RED, "!!"), spell.name,
|
||||
));
|
||||
} else {
|
||||
conn.player.stats.mana -= spell.cost_mana;
|
||||
conn.player.stats.endurance -= spell.cost_endurance;
|
||||
if spell.cooldown_ticks > 0 {
|
||||
conn.player.cooldowns.insert(spell_id.clone(), spell.cooldown_ticks);
|
||||
}
|
||||
|
||||
if spell.spell_type == "heal" {
|
||||
let old_hp = conn.player.stats.hp;
|
||||
conn.player.stats.hp = (conn.player.stats.hp + spell.heal).min(conn.player.stats.max_hp);
|
||||
let healed = conn.player.stats.hp - old_hp;
|
||||
out.push_str(&format!(
|
||||
" {} You cast {}! Restored {} HP.\r\n",
|
||||
ansi::color(ansi::GREEN, "++"),
|
||||
ansi::color(ansi::CYAN, &spell.name),
|
||||
ansi::bold(&healed.to_string()),
|
||||
));
|
||||
} else {
|
||||
// Offensive spell
|
||||
let spell_dmg = spell.damage + state.rng.next_range(1, 4);
|
||||
let new_npc_hp = (npc_hp_before - spell_dmg).max(0);
|
||||
out.push_str(&format!(
|
||||
" {} You cast {} on {} for {} {} damage!\r\n",
|
||||
ansi::color(ansi::MAGENTA, "**"),
|
||||
ansi::color(ansi::CYAN, &spell.name),
|
||||
ansi::color(ansi::RED, &npc_template.name),
|
||||
ansi::bold(&spell_dmg.to_string()),
|
||||
spell.damage_type,
|
||||
));
|
||||
if new_npc_hp <= 0 {
|
||||
if let Some(inst) = state.npc_instances.get_mut(&npc_id) {
|
||||
inst.alive = false;
|
||||
inst.hp = 0;
|
||||
inst.death_time = Some(Instant::now());
|
||||
}
|
||||
npc_died = true;
|
||||
xp_gained = npc_combat.xp_reward;
|
||||
out.push_str(&format!(
|
||||
" {} {} collapses! You gain {} XP.\r\n",
|
||||
ansi::color(ansi::GREEN, "**"),
|
||||
ansi::color(ansi::RED, &npc_template.name),
|
||||
ansi::bold(&xp_gained.to_string()),
|
||||
));
|
||||
conn.combat = None;
|
||||
conn.player.stats.xp += xp_gained;
|
||||
} else {
|
||||
if let Some(inst) = state.npc_instances.get_mut(&npc_id) {
|
||||
inst.hp = new_npc_hp;
|
||||
}
|
||||
out.push_str(&format!(
|
||||
" {} {} HP: {}/{}\r\n",
|
||||
ansi::color(ansi::DIM, " "),
|
||||
npc_template.name, new_npc_hp, npc_combat.max_hp,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply status effect from spell
|
||||
if let Some(ref eff) = spell.effect {
|
||||
if spell.spell_type == "offensive" {
|
||||
// Could apply effect to NPC in future, for now just note it
|
||||
} else {
|
||||
let pname = conn.player.name.clone();
|
||||
state.db.save_effect(&pname, eff, spell.effect_duration, spell.effect_magnitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.push_str(&format!(
|
||||
" {} Spell not found.\r\n",
|
||||
ansi::color(ansi::RED, "!!"),
|
||||
));
|
||||
}
|
||||
}
|
||||
CombatAction::UseItem(idx) => {
|
||||
if let Some(conn) = state.players.get_mut(&player_id) {
|
||||
if idx < conn.player.inventory.len() {
|
||||
|
||||
Reference in New Issue
Block a user