All current work

This commit is contained in:
2026-09-26 01:17:10 +03:00
commit 6f0139aae6
51 changed files with 9327 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
+163
View File
@@ -0,0 +1,163 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "elysium"
version = "0.1.0"
dependencies = [
"serde",
"toml",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "elysium"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
@@ -0,0 +1,21 @@
id = "apply_necro_warrior_stats"
name = "apply_necro_warrior_stats"
type = "Service"
tags = []
[targeting]
range = 0
range_type = "Self"
radius = 0
target = "Self"
required_tags = []
forbidden_tags = []
[cost]
mana = 0
stamina = 0
on_battle_start = 1
[[effects]]
id = "necro_warrior_stats"
multiplier = 1
+20
View File
@@ -0,0 +1,20 @@
id = "move_step"
name = "Шаг"
type = "Move"
tags = []
[targeting]
range = 1
range_type = "Line"
radius = 0
target = "Empty"
required_tags = []
forbidden_tags = []
[cost]
mana = 0
stamina = 1
[[effects]]
id = "move"
multiplier = 1
+24
View File
@@ -0,0 +1,24 @@
id = "necro_strike"
name = "Удар некроманта"
type = "Attack"
tags = ["ArmedAttack"]
[targeting]
range = 1
range_type = "Line"
radius = 0
target = "Enemy"
required_tags = ["Living"]
forbidden_tags = ["Undead"]
[cost]
mana = 1
stamina = 1
[[effects]]
id = "dark_damage"
multiplier = 1.2
[[effects]]
id = "slash_damage"
multiplier = 1
+20
View File
@@ -0,0 +1,20 @@
id = "spawn_corpse"
name = "Создать труп"
type = "Service"
tags = ["ArmedAttack"]
[targeting]
range = 0
range_type = "Self"
radius = 0
target = "Any"
required_tags = []
forbidden_tags = []
[cost]
mana = 0
stamina = 0
[[effects]]
id = "spawn_corpse"
multiplier = 1
+15
View File
@@ -0,0 +1,15 @@
id = "move"
name = "Движение"
service = true
category = "Move"
damage_type = "none"
base_amount = 1.0
stat_source = "none"
resist_stat = "none"
target_stat = "none"
target_tag = "none"
on_trigger_action = []
on_expire_action = []
on_death_action = []
spawn = []
effects = []
+15
View File
@@ -0,0 +1,15 @@
id = "dark_damage"
name = "Урон тьмы"
service = false
category = "Damage"
damage_type = "dark"
base_amount = 10.0
stat_source = "intellect"
resist_stat = "dark_armor"
target_stat = "none"
target_tag = "none"
on_trigger_action = []
on_expire_action = []
on_death_action = []
spawn = []
effects = []
@@ -0,0 +1,13 @@
id = "necro_warrior_stats"
name = "necro_warrior_stats урон"
category = "Self_Stats"
damage_type = "none"
base_amount = 0.0
stat_source = "none"
resist_stat = "none"
target_stat = "none"
target_tag = "none"
on_trigger_action = []
on_expire_action = []
on_death_action = []
spawn = []
@@ -0,0 +1,14 @@
id = "slash_damage"
name = "Режущий урон"
category = "Damage"
damage_type = "slash"
base_amount = 10.0
stat_source = "strength"
resist_stat = "slash_armor"
target_stat = "none"
target_tag = "none"
on_trigger_action = []
on_expire_action = []
on_death_action = []
spawn = []
effects = []
+14
View File
@@ -0,0 +1,14 @@
id = "slash_damage"
name = "Режущий урон"
category = "Damage"
damage_type = "slash"
base_amount = 10.0
stat_source = "strength"
resist_stat = "slash_armor"
target_stat = "none"
target_tag = "none"
on_trigger_action = []
on_expire_action = []
on_death_action = []
spawn = []
effects = []
+14
View File
@@ -0,0 +1,14 @@
id = "spawn_corpse"
name = "Создать труп"
category = "Spawn"
damage_type = "none"
base_amount = 1
stat_source = "none"
resist_stat = "none"
target_stat = "none"
target_tag = "none"
on_trigger_action = []
on_expire_action = []
on_death_action = []
spawn = ["corpse"]
effects = []
+63
View File
@@ -0,0 +1,63 @@
id = "corpse"
name = "Труп"
tags = ["Corpse"]
size = "none"
actions = [
]
[stats]
max_hp = 0
max_morale = 0
max_mana = 0
max_stamina = 0
[ammo]
max_ammo_spears = 0
max_ammo_arrows = 0
max_ammo_misc = 0
[special]
intellect = 0
strengh = 0
agility = 0
speed = 0
[resists]
pierce_armor = 0
slash_armor = 0
blunt_armor = 0
magic_armor = 0
fire_armor = 0
frost_armor = 0
lightning_armor = 0
light_armor = 0
dark_armor = 0
blight_armor = 0
[strategy]
position_priority = "Stationary"
[strategy.target_priority]
position = "Any"
criterion = "Any"
[strategy.thresholds]
flee_hp_pct = 0
flee_morale_pct = 0
min_stamina_reserve = 0
min_mana_reserve = 0
[strategy.counter]
actions = []
[strategy.overwatch]
radius = 0
actions = []
[strategy.on_death]
actions = []
[stategy.low_health]
actions = []
+70
View File
@@ -0,0 +1,70 @@
id = "necro_warrior"
name = "Некромант-воин"
tags = ["Undead", "Humanoid"]
size = "normal"
actions = [
"apply_necro_warrior_stats",
"necro_strike",
"move_step"
]
[stats]
max_hp = 30
max_morale = 100
max_mana = 10
max_stamina = 20
[ammo]
max_ammo_spears = 0
max_ammo_arrows = 0
max_ammo_misc = 0
[special]
intellect = 10
strengh = 10
agility = 10
speed = 5
[resists]
pierce_armor = 0
slash_armor = 0
blunt_armor = 0
magic_armor = 0
fire_armor = 0
frost_armor = 0
lightning_armor = 0
light_armor = 0
dark_armor = 0
blight_armor = 0
[strategy]
position_priority = "Frontline"
[strategy.target_priority]
position = "Frontline"
criterion = "MaxDanger"
[strategy.thresholds]
flee_hp_pct = 15
flee_morale_pct = 0
min_stamina_reserve = 0
min_mana_reserve = 0
[strategy.counter]
actions = ["necro_strike"]
[strategy.overwatch]
radius = 0
actions = []
[strategy.on_death]
actions = ["spawn_corpse"]
[strategy.aura]
radius = 0
effects = []
[stategy.low_health]
actions = []
+63
View File
@@ -0,0 +1,63 @@
mod models;
mod registry;
use registry::ContentRegistry;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Elysium Game Core Backend ===");
let mut registry = ContentRegistry::new();
if let Err(e) = registry.load_from_dir("data") {
eprintln!("Ошибка при загрузке реестра данных: {}", e);
return Err(e);
}
println!("\n--- Проверка загруженных данных ---");
for (id, unit) in &registry.units {
println!("\n[Юнит]: {} ({})", unit.name, id);
println!(" Размер: {}", unit.size);
println!(" Тэги: {:?}", unit.tags);
println!(
" Витальность: HP={}, Morale={}, Mana={}, Stamina={}",
unit.stats.max_hp, unit.stats.max_morale, unit.stats.max_mana, unit.stats.max_stamina
);
println!(
" Характеристики: Интеллект={}, Сила={}, Ловкость={}, Скорость={}",
unit.special.intellect, unit.special.strengh, unit.special.agility, unit.special.speed
);
println!(" Реакция на смерть (on_death): {:?}", unit.strategy.on_death.actions);
println!(" Контратака (counter): {:?}", unit.strategy.counter.actions);
println!(" Доступные экшены (в порядке приоритета):");
for action_id in &unit.actions {
if let Some(action) = registry.actions.get(action_id) {
let act_type = action.action_type.as_deref().unwrap_or("Normal");
println!(
" -> Экшен: '{}' [Тип: {}, Дальность: {}, На старт боя: {}]",
action.name, act_type, action.targeting.range, action.cost.on_battle_start
);
for effect_link in &action.effects {
if let Some(effect) = registry.effects.get(&effect_link.id) {
println!(
" * Эффект: '{}' [Служебный: {}, Категория: '{}', Базовый: {}, Множитель: {}]",
effect.name,
effect.service,
effect.category,
effect.base_amount,
effect_link.multiplier
);
} else {
eprintln!(" * [ОШИБКА] Эффект '{}' не найден в реестре!", effect_link.id);
}
}
} else {
eprintln!(" -> [ОШИБКА] Экшен '{}' не найден в реестре!", action_id);
}
}
}
Ok(())
}
+278
View File
@@ -0,0 +1,278 @@
use serde::{Deserialize, Serialize};
// ==================== EFFECT ====================
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EffectDefinition {
pub id: String,
pub name: String,
#[serde(default)]
pub service: bool,
pub category: String,
#[serde(default)]
pub damage_type: Option<String>,
pub base_amount: f32,
pub stat_source: Option<String>,
pub resist_stat: Option<String>,
pub target_stat: Option<String>,
pub target_tag: Option<String>,
#[serde(default)]
pub duration: Option<u32>,
// Триггерные экшены и спавн
#[serde(default)]
pub on_trigger_action: Vec<String>,
#[serde(default)]
pub on_expire_action: Vec<String>,
#[serde(default)]
pub on_death_action: Vec<String>,
#[serde(default)]
pub spawn: Vec<String>,
#[serde(default)]
pub effects: Vec<String>,
}
// ==================== ACTION ====================
// #[derive(Debug, Clone, Serialize, Deserialize)]
// pub enum RangeType {
// Instant,
// SelfTarget, // Подходит для RangeType = "Self"
// Line,
// Circle,
// }
// #[derive(Debug, Clone, Serialize, Deserialize)]
// pub enum TargetType {
// SelfTarget, // Подходит под "Self" в TOML
// Enemy,
// Ally,
// Empty,
// Object,
// Fort,
// }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TargetingRule {
pub range: u32,
pub range_type: String, // String для поддержки кастомных и гибких "Self", "Line" и т.д.
pub radius: u32,
pub target: String, // String для гибкости ("Self", "Enemy", "Ally", etc.)
#[serde(default)]
pub required_tags: Vec<String>,
#[serde(default)]
pub forbidden_tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ActionCost {
#[serde(default)]
pub health: i32,
#[serde(default)]
pub mana: i32,
#[serde(default)]
pub stamina: i32,
#[serde(default)]
pub on_battle_start: u32, // Боеприпас на старт боя
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EffectLink {
pub id: String,
#[serde(default = "default_multiplier")]
pub multiplier: f32,
}
fn default_multiplier() -> f32 {
1.0
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionDefinition {
pub id: String,
pub name: String,
#[serde(rename = "type", default)]
pub action_type: Option<String>, // Опциональный тип ("Service" и т.д.)
pub tags: Vec<String>,
pub targeting: TargetingRule,
pub cost: ActionCost,
pub effects: Vec<EffectLink>,
}
// ==================== UNIT & STATS ====================
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VitalStats {
pub max_hp: i32,
pub max_morale: i32,
pub max_mana: i32,
pub max_stamina: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AmmoStats {
#[serde(default)]
pub max_ammo_spears: u32,
#[serde(default)]
pub max_ammo_arrows: u32,
#[serde(default)]
pub max_ammo_misc: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpecialStats {
pub intellect: i32,
pub strengh: i32,
pub agility: i32,
pub speed: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResistsStats {
#[serde(default)]
pub pierce_armor: i32,
#[serde(default)]
pub slash_armor: i32,
#[serde(default)]
pub blunt_armor: i32,
#[serde(default)]
pub hack_armor: i32,
#[serde(default)]
pub magic_armor: i32,
#[serde(default)]
pub fire_armor: i32,
#[serde(default)]
pub frost_armor: i32,
#[serde(default)]
pub lightning_armor: i32,
#[serde(default)]
pub light_armor: i32,
#[serde(default)]
pub dark_armor: i32,
#[serde(default)]
pub blight_armor: i32
}
// ==================== STRATEGY & REACTIONS ====================
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PositionPriority {
Frontline,
Backline,
Rear,
Stationary
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TargetPositionPriority {
Frontline,
Backline,
Rear,
Any,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TargetCriterion {
Closest,
MaxDanger,
MinDanger,
LowestHP,
Any
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TargetPriorityConfig {
pub position: TargetPositionPriority,
pub criterion: TargetCriterion,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Thresholds {
#[serde(default)]
pub flee_hp_pct: u32,
#[serde(default)]
pub flee_morale_pct: u32,
#[serde(default)]
pub min_stamina_reserve: i32,
#[serde(default)]
pub min_mana_reserve: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReactionBlock {
#[serde(default)]
pub actions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LowHealthBlock {
#[serde(default)]
pub actions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OverwatchBlock {
#[serde(default)]
pub radius: u32,
#[serde(default)]
pub actions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AuraBlock {
#[serde(default)]
pub radius: u32,
#[serde(default)]
pub effects: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyConfig {
pub position_priority: PositionPriority,
pub target_priority: TargetPriorityConfig,
pub thresholds: Thresholds,
// Новые блоки реакций
#[serde(default)]
pub counter: ReactionBlock,
#[serde(default)]
pub overwatch: OverwatchBlock,
#[serde(default)]
pub on_death: ReactionBlock,
#[serde(default)]
pub aura: AuraBlock,
#[serde(default)]
pub low_health: LowHealthBlock
}
// ==================== UNIT TEMPLATE ====================
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnitTemplate {
pub id: String,
pub name: String,
#[serde(default = "default_size")]
pub size: String,
#[serde(default)]
pub tags: Vec<String>,
pub actions: Vec<String>,
pub stats: VitalStats,
#[serde(default)]
pub ammo: Option<AmmoStats>,
pub special: SpecialStats,
pub resists: ResistsStats,
pub strategy: StrategyConfig,
}
fn default_size() -> String {
"normal".to_string()
}
+67
View File
@@ -0,0 +1,67 @@
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use crate::models::{ActionDefinition, EffectDefinition, UnitTemplate};
#[derive(Debug, Default)]
pub struct ContentRegistry {
pub units: HashMap<String, UnitTemplate>,
pub actions: HashMap<String, ActionDefinition>,
pub effects: HashMap<String, EffectDefinition>,
}
impl ContentRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn load_from_dir<P: AsRef<Path>>(&mut self, base_dir: P) -> Result<(), Box<dyn std::error::Error>> {
let base = base_dir.as_ref();
// 1. Загружаем Эффекты
let effects_path = base.join("effects");
if effects_path.exists() {
for entry in fs::read_dir(effects_path)? {
let path = entry?.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("toml") {
let content = fs::read_to_string(&path)?;
let effect: EffectDefinition = toml::from_str(&content)?;
self.effects.insert(effect.id.clone(), effect);
}
}
}
// 2. Загружаем Экшены
let actions_path = base.join("actions");
if actions_path.exists() {
for entry in fs::read_dir(actions_path)? {
let path = entry?.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("toml") {
let content = fs::read_to_string(&path)?;
let action: ActionDefinition = toml::from_str(&content)?;
self.actions.insert(action.id.clone(), action);
}
}
}
// 3. Загружаем Юнитов
let units_path = base.join("units");
if units_path.exists() {
for entry in fs::read_dir(units_path)? {
let path = entry?.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("toml") {
let content = fs::read_to_string(&path)?;
let unit: UnitTemplate = toml::from_str(&content)?;
self.units.insert(unit.id.clone(), unit);
}
}
}
println!("=== Реестр успешно загружен ===");
println!(" - Эффектов: {}", self.effects.len());
println!(" - Экшенов: {}", self.actions.len());
println!(" - Юнитов: {}", self.units.len());
Ok(())
}
}