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:
61
src/tick.rs
61
src/tick.rs
@@ -223,33 +223,60 @@ pub async fn run_tick_engine(state: SharedState) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tick cooldowns for all online players ---
|
||||
let cd_pids: Vec<usize> = st.players.keys().copied().collect();
|
||||
for pid in cd_pids {
|
||||
if let Some(conn) = st.players.get_mut(&pid) {
|
||||
conn.player.cooldowns.retain(|_, cd| {
|
||||
*cd -= 1;
|
||||
*cd > 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Passive regen for online players not in combat ---
|
||||
if tick % REGEN_EVERY_N_TICKS == 0 {
|
||||
let regen_pids: Vec<usize> = st
|
||||
.players
|
||||
.iter()
|
||||
.filter(|(_, c)| {
|
||||
c.combat.is_none() && c.player.stats.hp < c.player.stats.max_hp
|
||||
})
|
||||
.filter(|(_, c)| c.combat.is_none())
|
||||
.map(|(&id, _)| id)
|
||||
.collect();
|
||||
|
||||
for pid in regen_pids {
|
||||
if let Some(conn) = st.players.get_mut(&pid) {
|
||||
let heal =
|
||||
(conn.player.stats.max_hp * REGEN_PERCENT / 100).max(1);
|
||||
let old = conn.player.stats.hp;
|
||||
conn.player.stats.hp =
|
||||
(conn.player.stats.hp + heal).min(conn.player.stats.max_hp);
|
||||
let healed = conn.player.stats.hp - old;
|
||||
if healed > 0 {
|
||||
messages.entry(pid).or_default().push_str(&format!(
|
||||
"\r\n {} You recover {} HP. ({}/{})\r\n",
|
||||
ansi::color(ansi::DIM, "~~"),
|
||||
healed,
|
||||
conn.player.stats.hp,
|
||||
conn.player.stats.max_hp,
|
||||
));
|
||||
let s = &mut conn.player.stats;
|
||||
let mut regen_msg = String::new();
|
||||
|
||||
// HP regen
|
||||
if s.hp < s.max_hp {
|
||||
let heal = (s.max_hp * REGEN_PERCENT / 100).max(1);
|
||||
let old = s.hp;
|
||||
s.hp = (s.hp + heal).min(s.max_hp);
|
||||
let healed = s.hp - old;
|
||||
if healed > 0 {
|
||||
regen_msg.push_str(&format!(
|
||||
"\r\n {} You recover {} HP. ({}/{})",
|
||||
ansi::color(ansi::DIM, "~~"), healed, s.hp, s.max_hp,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Mana regen
|
||||
if s.mana < s.max_mana {
|
||||
let regen = (s.max_mana * REGEN_PERCENT / 100).max(1);
|
||||
s.mana = (s.mana + regen).min(s.max_mana);
|
||||
}
|
||||
|
||||
// Endurance regen
|
||||
if s.endurance < s.max_endurance {
|
||||
let regen = (s.max_endurance * REGEN_PERCENT / 100).max(1);
|
||||
s.endurance = (s.endurance + regen).min(s.max_endurance);
|
||||
}
|
||||
|
||||
if !regen_msg.is_empty() {
|
||||
regen_msg.push_str("\r\n");
|
||||
messages.entry(pid).or_default().push_str(®en_msg);
|
||||
}
|
||||
}
|
||||
st.save_player_to_db(pid);
|
||||
|
||||
Reference in New Issue
Block a user