use crate::context::ResourceContext;
use crate::event::EventBus;
use super::config::ChainOfCommandConfig;
use super::events::*;
use super::hook::ChainOfCommandHook;
use super::rank_definitions::RankDefinitions;
use super::service::HierarchyService;
use super::state::HierarchyState;
use super::types::{OrderOutcome, PromotionError};
#[allow(dead_code)]
pub struct HierarchySystem<H: ChainOfCommandHook> {
hook: H,
service: HierarchyService,
}
impl<H: ChainOfCommandHook> HierarchySystem<H> {
pub fn new(hook: H) -> Self {
Self {
hook,
service: HierarchyService,
}
}
pub async fn process_events(&mut self, resources: &mut ResourceContext) {
let promote_requests = self
.collect_events::<MemberPromoteRequested>(resources)
.await;
let order_requests = self.collect_events::<OrderIssueRequested>(resources).await;
let loyalty_requests = self
.collect_events::<LoyaltyDecayRequested>(resources)
.await;
let add_requests = self.collect_events::<MemberAddRequested>(resources).await;
let remove_requests = self
.collect_events::<MemberRemoveRequested>(resources)
.await;
for request in promote_requests {
self.process_promote_request(request, resources).await;
}
for request in order_requests {
self.process_order_request(request, resources).await;
}
for request in loyalty_requests {
self.process_loyalty_decay_request(request, resources).await;
}
for request in add_requests {
self.process_add_member_request(request, resources).await;
}
for request in remove_requests {
self.process_remove_member_request(request, resources).await;
}
}
async fn collect_events<T: Clone + 'static + crate::event::Event>(
&self,
resources: &mut ResourceContext,
) -> Vec<T> {
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
let reader = bus.reader::<T>();
reader.iter().cloned().collect()
} else {
Vec::new()
}
}
async fn process_promote_request(
&mut self,
request: MemberPromoteRequested,
resources: &mut ResourceContext,
) {
let faction_id = request.faction_id.clone();
let member_id = request.member_id.clone();
let new_rank = request.new_rank.clone();
let config = match resources.get::<ChainOfCommandConfig>().await {
Some(c) => c.clone(),
None => return,
};
let rank_defs = match resources.get::<RankDefinitions>().await {
Some(r) => r.clone(),
None => return,
};
let (old_rank, result) = {
let mut state = match resources.get_mut::<HierarchyState>().await {
Some(s) => s,
None => return,
};
let hierarchy = match state.get_hierarchy_mut(&faction_id) {
Some(h) => h,
None => {
self.publish_event(
PromotionFailedEvent {
faction_id,
member_id,
reason: PromotionError::FactionNotFound,
},
resources,
)
.await;
return;
}
};
let member = match hierarchy.get_member_mut(&member_id) {
Some(m) => m,
None => {
self.publish_event(
PromotionFailedEvent {
faction_id,
member_id,
reason: PromotionError::MemberNotFound,
},
resources,
)
.await;
return;
}
};
let old_rank = member.rank.clone();
let current_rank_def = match rank_defs.get(&member.rank) {
Some(r) => r,
None => {
self.publish_event(
PromotionFailedEvent {
faction_id,
member_id,
reason: PromotionError::RankNotFound,
},
resources,
)
.await;
return;
}
};
let new_rank_def = match rank_defs.get(&new_rank) {
Some(r) => r,
None => {
self.publish_event(
PromotionFailedEvent {
faction_id,
member_id,
reason: PromotionError::RankNotFound,
},
resources,
)
.await;
return;
}
};
if !HierarchyService::can_promote(member, current_rank_def, new_rank_def, &config) {
self.publish_event(
PromotionFailedEvent {
faction_id,
member_id,
reason: PromotionError::NotEligible,
},
resources,
)
.await;
return;
}
if !self
.hook
.can_promote_custom(member, new_rank_def, resources)
.await
{
self.publish_event(
PromotionFailedEvent {
faction_id,
member_id,
reason: PromotionError::CustomConditionFailed,
},
resources,
)
.await;
return;
}
member.rank = new_rank.clone();
member.turns_since_promotion = 0;
member.morale = (member.morale + 0.2).min(1.0);
member.loyalty = (member.loyalty + 0.1).min(1.0);
(old_rank, Ok::<(), ()>(()))
};
if result.is_ok() {
self.hook
.on_member_promoted(&faction_id, &member_id, &new_rank, resources)
.await;
self.publish_event(
MemberPromotedEvent {
faction_id,
member_id,
old_rank,
new_rank,
},
resources,
)
.await;
}
}
async fn process_order_request(
&mut self,
request: OrderIssueRequested,
resources: &mut ResourceContext,
) {
let faction_id = request.faction_id.clone();
let superior_id = request.superior_id.clone();
let subordinate_id = request.subordinate_id.clone();
let order = request.order.clone();
let config = match resources.get::<ChainOfCommandConfig>().await {
Some(c) => c.clone(),
None => return,
};
let outcome = {
let state = match resources.get::<HierarchyState>().await {
Some(s) => s,
None => return,
};
let hierarchy = match state.get_hierarchy(&faction_id) {
Some(h) => h,
None => return,
};
let superior = match hierarchy.get_member(&superior_id) {
Some(m) => m.clone(),
None => return,
};
let subordinate = match hierarchy.get_member(&subordinate_id) {
Some(m) => m.clone(),
None => return,
};
if !hierarchy.is_direct_subordinate(&subordinate_id, &superior_id) {
return; }
let compliance_rate = HierarchyService::calculate_order_compliance(
&subordinate,
&superior,
config.base_order_compliance_rate,
);
let executed = rand::random::<f32>() < compliance_rate;
if executed {
OrderOutcome::Executed
} else {
OrderOutcome::Refused {
reason: format!(
"Low loyalty ({:.0}%) or morale ({:.0}%)",
subordinate.loyalty * 100.0,
subordinate.morale * 100.0
),
}
}
};
match outcome {
OrderOutcome::Executed => {
self.hook
.execute_order(&faction_id, &subordinate_id, &order, resources)
.await;
self.publish_event(
OrderExecutedEvent {
faction_id,
superior_id,
subordinate_id,
order,
},
resources,
)
.await;
}
OrderOutcome::Refused { reason } => {
self.hook
.on_order_refused(&faction_id, &subordinate_id, &order, resources)
.await;
self.publish_event(
OrderRefusedEvent {
faction_id,
superior_id,
subordinate_id,
order,
reason,
},
resources,
)
.await;
}
}
}
async fn process_loyalty_decay_request(
&mut self,
request: LoyaltyDecayRequested,
resources: &mut ResourceContext,
) {
let delta_turns = request.delta_turns;
let config = match resources.get::<ChainOfCommandConfig>().await {
Some(c) => c.clone(),
None => return,
};
let members_affected = {
let mut state = match resources.get_mut::<HierarchyState>().await {
Some(s) => s,
None => return,
};
let mut count = 0;
for (_faction_id, hierarchy) in state.all_hierarchies_mut() {
let member_ids: Vec<_> =
hierarchy.all_members().map(|(id, _)| id.clone()).collect();
for member_id in member_ids {
let superior_modifier = hierarchy
.get_member(&member_id)
.and_then(|m| m.superior.clone())
.and_then(|sup_id| hierarchy.get_member(&sup_id))
.map(|sup| {
let member = hierarchy.get_member(&member_id).unwrap();
HierarchyService::calculate_loyalty_modifier(member, sup)
})
.unwrap_or(0.0);
if let Some(member) = hierarchy.get_member_mut(&member_id) {
member.loyalty = HierarchyService::decay_loyalty(
member.loyalty,
config.loyalty_decay_rate,
delta_turns,
);
member.loyalty = (member.loyalty + superior_modifier).min(1.0);
member.tenure += delta_turns;
member.turns_since_promotion += delta_turns;
count += 1;
}
}
}
count
};
self.publish_event(
LoyaltyDecayProcessedEvent {
delta_turns,
members_affected,
},
resources,
)
.await;
}
async fn process_add_member_request(
&mut self,
request: MemberAddRequested,
resources: &mut ResourceContext,
) {
let faction_id = request.faction_id.clone();
let member_id = request.member.id.clone();
{
let mut state = match resources.get_mut::<HierarchyState>().await {
Some(s) => s,
None => return,
};
if let Some(hierarchy) = state.get_hierarchy_mut(&faction_id) {
hierarchy.add_member(request.member);
}
}
self.publish_event(
MemberAddedEvent {
faction_id,
member_id,
},
resources,
)
.await;
}
async fn process_remove_member_request(
&mut self,
request: MemberRemoveRequested,
resources: &mut ResourceContext,
) {
let faction_id = request.faction_id.clone();
let member_id = request.member_id.clone();
{
let mut state = match resources.get_mut::<HierarchyState>().await {
Some(s) => s,
None => return,
};
if let Some(hierarchy) = state.get_hierarchy_mut(&faction_id) {
hierarchy.remove_member(&member_id);
}
}
self.publish_event(
MemberRemovedEvent {
faction_id,
member_id,
},
resources,
)
.await;
}
async fn publish_event<T: Clone + 'static + crate::event::Event + serde::Serialize>(
&self,
event: T,
resources: &mut ResourceContext,
) {
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
bus.publish(event);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::chain_of_command::{DefaultChainOfCommandHook, Member, RankDefinition};
use crate::plugin::AuthorityLevel;
fn create_test_resources() -> ResourceContext {
let mut ctx = ResourceContext::new();
let bus = EventBus::new();
ctx.insert(bus);
ctx.insert(ChainOfCommandConfig::default());
let mut ranks = RankDefinitions::new();
ranks.add(RankDefinition::new(
"private",
"Private",
0,
AuthorityLevel::Private,
));
ranks.add(RankDefinition::new(
"sergeant",
"Sergeant",
1,
AuthorityLevel::SquadLeader,
));
ctx.insert(ranks);
let mut state = HierarchyState::new();
state.register_faction("test_faction");
ctx.insert(state);
ctx
}
#[tokio::test]
async fn test_system_creation() {
let hook = DefaultChainOfCommandHook;
let _system = HierarchySystem::new(hook);
}
#[tokio::test]
async fn test_process_add_member_request() {
let hook = DefaultChainOfCommandHook;
let mut system = HierarchySystem::new(hook);
let mut resources = create_test_resources();
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(MemberAddRequested {
faction_id: "test_faction".to_string(),
member: Member::new("m1", "Member 1", "private"),
});
bus.dispatch();
}
system.process_events(&mut resources).await;
let state = resources.get::<HierarchyState>().await.unwrap();
let hierarchy = state.get_hierarchy(&"test_faction".to_string()).unwrap();
assert!(hierarchy.has_member(&"m1".to_string()));
}
#[tokio::test]
async fn test_process_remove_member_request() {
let hook = DefaultChainOfCommandHook;
let mut system = HierarchySystem::new(hook);
let mut resources = create_test_resources();
{
let mut state = resources.get_mut::<HierarchyState>().await.unwrap();
let hierarchy = state
.get_hierarchy_mut(&"test_faction".to_string())
.unwrap();
hierarchy.add_member(Member::new("m1", "Member 1", "private"));
}
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(MemberRemoveRequested {
faction_id: "test_faction".to_string(),
member_id: "m1".to_string(),
});
bus.dispatch();
}
system.process_events(&mut resources).await;
let state = resources.get::<HierarchyState>().await.unwrap();
let hierarchy = state.get_hierarchy(&"test_faction".to_string()).unwrap();
assert!(!hierarchy.has_member(&"m1".to_string()));
}
#[tokio::test]
async fn test_process_promote_request_success() {
let hook = DefaultChainOfCommandHook;
let mut system = HierarchySystem::new(hook);
let mut resources = create_test_resources();
{
let mut state = resources.get_mut::<HierarchyState>().await.unwrap();
let hierarchy = state
.get_hierarchy_mut(&"test_faction".to_string())
.unwrap();
hierarchy.add_member(
Member::new("m1", "Member 1", "private")
.with_tenure(10)
.with_loyalty(0.8),
);
}
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(MemberPromoteRequested {
faction_id: "test_faction".to_string(),
member_id: "m1".to_string(),
new_rank: "sergeant".to_string(),
});
bus.dispatch();
}
system.process_events(&mut resources).await;
let state = resources.get::<HierarchyState>().await.unwrap();
let hierarchy = state.get_hierarchy(&"test_faction".to_string()).unwrap();
let member = hierarchy.get_member(&"m1".to_string()).unwrap();
assert_eq!(member.rank, "sergeant");
assert_eq!(member.turns_since_promotion, 0);
}
#[tokio::test]
async fn test_process_promote_request_insufficient_tenure() {
let hook = DefaultChainOfCommandHook;
let mut system = HierarchySystem::new(hook);
let mut resources = create_test_resources();
{
let mut state = resources.get_mut::<HierarchyState>().await.unwrap();
let hierarchy = state
.get_hierarchy_mut(&"test_faction".to_string())
.unwrap();
hierarchy.add_member(
Member::new("m1", "Member 1", "private")
.with_tenure(2) .with_loyalty(0.8),
);
}
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(MemberPromoteRequested {
faction_id: "test_faction".to_string(),
member_id: "m1".to_string(),
new_rank: "sergeant".to_string(),
});
bus.dispatch();
}
system.process_events(&mut resources).await;
let state = resources.get::<HierarchyState>().await.unwrap();
let hierarchy = state.get_hierarchy(&"test_faction".to_string()).unwrap();
let member = hierarchy.get_member(&"m1".to_string()).unwrap();
assert_eq!(member.rank, "private"); }
#[tokio::test]
async fn test_process_loyalty_decay() {
let hook = DefaultChainOfCommandHook;
let mut system = HierarchySystem::new(hook);
let mut resources = create_test_resources();
{
let mut state = resources.get_mut::<HierarchyState>().await.unwrap();
let hierarchy = state
.get_hierarchy_mut(&"test_faction".to_string())
.unwrap();
hierarchy.add_member(
Member::new("m1", "Member 1", "private")
.with_loyalty(1.0)
.with_tenure(0),
);
}
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(LoyaltyDecayRequested { delta_turns: 5 });
bus.dispatch();
}
system.process_events(&mut resources).await;
let state = resources.get::<HierarchyState>().await.unwrap();
let hierarchy = state.get_hierarchy(&"test_faction".to_string()).unwrap();
let member = hierarchy.get_member(&"m1".to_string()).unwrap();
assert!((member.loyalty - 0.9).abs() < 0.001);
assert_eq!(member.tenure, 5);
}
}