Compare commits
13 Commits
master
...
feature/ba
Author | SHA1 | Date |
---|---|---|
brnrs | 4dcd6cc826 | 5 years ago |
brnrs | 13015bbc04 | 5 years ago |
brnrs | babd1084a6 | 5 years ago |
brnrs | e99e97637d | 5 years ago |
brnrs | 28d5069b02 | 5 years ago |
brnrs | 1a52695945 | 5 years ago |
brnrs | 28564b11bd | 5 years ago |
brnrs | 505016f160 | 5 years ago |
brnrs | d594185d1d | 5 years ago |
brnrs | 63bf5f8f66 | 5 years ago |
brnrs | 0a02f83c27 | 5 years ago |
brnrs | fe43ff2cd6 | 5 years ago |
brnrs | cb96c33135 | 5 years ago |
@ -0,0 +1,9 @@ |
||||
[package] |
||||
name = "adventure" |
||||
version = "0.1.0" |
||||
authors = ["brnrs <brnrs@brnrs.pl>"] |
||||
edition = "2018" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
@ -0,0 +1,11 @@ |
||||
pipeline { |
||||
agent any |
||||
stages { |
||||
stage('build') { |
||||
steps { |
||||
sh 'rustup update' |
||||
sh 'cargo test --verbose' |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,2 +1,7 @@ |
||||
# adventure |
||||
|
||||
### TODO: |
||||
- implement battles |
||||
- implement ActorDb |
||||
- implement Decider |
||||
- implement Effector |
@ -0,0 +1,124 @@ |
||||
use super::base::*; |
||||
|
||||
use std::collections::{BTreeMap, HashMap}; |
||||
|
||||
pub struct Actor { |
||||
pub id: ActorId, |
||||
pub team_id: TeamId, |
||||
pub name: String, |
||||
pub stats: ActorStats, |
||||
pub skills: Vec<SkillId>, |
||||
pub ai: Option<AiId>, |
||||
} |
||||
|
||||
pub struct ActorStats { |
||||
pub max_hp: Hp, |
||||
pub initiative: Increment, |
||||
} |
||||
|
||||
pub struct ActorDb { |
||||
pub container: BTreeMap<ActorId, Actor>, |
||||
} |
||||
|
||||
impl ActorDb { |
||||
#[allow(dead_code)] |
||||
pub fn from(actors: Vec<Actor>) -> ActorDb { |
||||
ActorDb { |
||||
container: actors.into_iter().map(|actor| (actor.id, actor)).collect(), |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub fn get(&self, id: ActorId) -> Option<&Actor> { |
||||
self.container.get(&id) |
||||
} |
||||
|
||||
pub fn iter(&self) -> std::collections::btree_map::Iter<ActorId, Actor> { |
||||
self.container.iter() |
||||
} |
||||
} |
||||
|
||||
pub struct Team { |
||||
pub id: TeamId, |
||||
pub actors: Vec<ActorId>, |
||||
pub ai: Option<AiId>, |
||||
} |
||||
|
||||
pub struct TeamDb { |
||||
container: BTreeMap<TeamId, Team>, |
||||
} |
||||
|
||||
pub struct TeamAiOverrides(pub Vec<(TeamId, AiId)>); |
||||
|
||||
#[allow(dead_code, unused_variables)] |
||||
impl TeamDb { |
||||
pub fn from(actor_db: &ActorDb, ai_overrides: TeamAiOverrides) -> TeamDb { |
||||
let mut map: BTreeMap<TeamId, Team> = BTreeMap::new(); |
||||
|
||||
actor_db.iter().for_each(|(actor_id, actor)| { |
||||
let team_id = actor.team_id; |
||||
if let Some(entry) = map.get_mut(&team_id) { |
||||
entry.actors.push(*actor_id); |
||||
} else { |
||||
let ai = ai_overrides |
||||
.0 |
||||
.iter() |
||||
.find(|(tid, aid)| *tid == team_id) |
||||
.map(|(_, aid)| *aid); |
||||
map.insert( |
||||
team_id, |
||||
Team { |
||||
id: team_id, |
||||
actors: vec![*actor_id], |
||||
ai, |
||||
}, |
||||
); |
||||
} |
||||
}); |
||||
|
||||
TeamDb { container: map } |
||||
} |
||||
|
||||
pub fn get(&self, id: TeamId) -> Option<&Team> { |
||||
self.container.get(&id) |
||||
} |
||||
|
||||
pub fn iter(&self) -> std::collections::btree_map::Iter<TeamId, Team> { |
||||
self.container.iter() |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub struct ActorStateDb { |
||||
container: HashMap<ActorId, ActorState>, |
||||
} |
||||
|
||||
#[allow(dead_code, unused_variables)] |
||||
impl ActorStateDb { |
||||
pub fn new() -> ActorStateDb { |
||||
ActorStateDb { |
||||
container: HashMap::new(), |
||||
} |
||||
} |
||||
|
||||
pub fn init(&mut self, actor: &Actor) { |
||||
self.container |
||||
.insert(actor.id, ActorState::Alive(actor.stats.max_hp)) |
||||
.unwrap_none() |
||||
} |
||||
|
||||
pub fn get(&self, actor_id: ActorId) -> Option<ActorState> { |
||||
self.container.get(&actor_id).cloned() |
||||
} |
||||
|
||||
pub fn set(&mut self, actor_id: ActorId, state: ActorState) { |
||||
self.container.insert(actor_id, state); |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Clone, Copy)] |
||||
#[allow(dead_code)] |
||||
pub enum ActorState { |
||||
Dead, |
||||
Alive(Hp), |
||||
} |
@ -0,0 +1,17 @@ |
||||
pub type ActorId = u32; |
||||
#[allow(dead_code)] |
||||
pub type TeamId = u32; |
||||
#[allow(dead_code)] |
||||
pub type SkillId = u32; |
||||
pub type Increment = u32; |
||||
pub type Hp = u32; |
||||
pub type AiId = u32; |
||||
|
||||
#[allow(dead_code)] |
||||
pub enum SkillTarget { |
||||
None, |
||||
Actor(ActorId), |
||||
Team(TeamId), |
||||
} |
||||
|
||||
pub struct EffectTarget(pub ActorId); |
@ -0,0 +1,203 @@ |
||||
pub mod actor; |
||||
pub mod base; |
||||
pub mod queue; |
||||
pub mod skill; |
||||
pub mod uic; |
||||
|
||||
use crate::context::*; |
||||
use crate::require; |
||||
|
||||
use actor::*; |
||||
use base::*; |
||||
use queue::*; |
||||
use skill::*; |
||||
use uic::*; |
||||
|
||||
#[allow(dead_code, unused_variables)] |
||||
pub fn run_battle(context: Context) -> Result<(), String> { |
||||
require!(context; ActorDb, ActorStateDb, TeamDb, DeciderDb, SkillDb, Uic)?; |
||||
let uic = context.get::<Uic>().unwrap(); |
||||
init_states(&context); |
||||
uic.start_battle(&context); |
||||
let mut queue = create_queue(&context.get::<ActorDb>().unwrap()); |
||||
uic.create_queue(&context, &queue); |
||||
|
||||
let battle_result = loop { |
||||
let actor_id = match queue.dequeue() { |
||||
None => break BattleResult::AllDead, |
||||
Some((_, actor_id)) => actor_id, |
||||
}; |
||||
uic.start_turn(&context, actor_id); |
||||
let (skill_id, target) = determine_decision(&context, actor_id)?; |
||||
let effects = determine_effects(&context, actor_id, skill_id, target)?; |
||||
let results = apply_effects(&context, effects)?; |
||||
if !results.0.is_empty() { |
||||
if let Some(battle_result) = check_result(&context) { |
||||
break battle_result; |
||||
} |
||||
} |
||||
let actor_db = context.get::<ActorDb>().unwrap(); |
||||
let actor_state_db = context.get::<ActorStateDb>().unwrap(); |
||||
if let ActorState::Alive(_) = actor_state_db.get(actor_id).unwrap() { |
||||
let actor = actor_db.get(actor_id).unwrap(); |
||||
queue.enqueue((actor.stats.initiative, actor_id)); |
||||
} |
||||
}; |
||||
|
||||
println!("Result: {:?}", battle_result); |
||||
Ok(()) |
||||
} |
||||
|
||||
fn create_queue(actor_db: &ActorDb) -> Queue { |
||||
let mut queue = Queue::new(); |
||||
actor_db |
||||
.iter() |
||||
.for_each(|(id, actor)| queue.enqueue((actor.stats.initiative, *id))); |
||||
|
||||
queue |
||||
} |
||||
|
||||
fn determine_ai( |
||||
context: &Context, |
||||
actor_id: base::ActorId, |
||||
default: base::AiId, |
||||
) -> Result<base::AiId, String> { |
||||
require!(context; actor::ActorDb, actor::TeamDb)?; |
||||
|
||||
let actor_db: &actor::ActorDb = &*context.get().unwrap(); |
||||
let actor = actor_db |
||||
.get(actor_id) |
||||
.ok_or_else(|| String::from("Actor not found"))?; |
||||
if let Some(ai_id) = actor.ai { |
||||
return Ok(ai_id); |
||||
} |
||||
|
||||
let team_db: &actor::TeamDb = &*context.get().unwrap(); |
||||
let team = team_db |
||||
.get(actor.team_id) |
||||
.ok_or_else(|| String::from("Team not found"))?; |
||||
if let Some(ai_id) = team.ai { |
||||
return Ok(ai_id); |
||||
} |
||||
|
||||
Ok(default) |
||||
} |
||||
|
||||
fn determine_decision( |
||||
context: &Context, |
||||
actor_id: ActorId, |
||||
) -> Result<(SkillId, SkillTarget), String> { |
||||
let decider_db: &DeciderDb = &*context.get().unwrap(); |
||||
let ai_id = determine_ai(&context, actor_id, 0)?; |
||||
let decider = decider_db |
||||
.get(ai_id) |
||||
.ok_or_else(|| String::from("Decider not found"))?; |
||||
|
||||
decider.decide(context, actor_id) |
||||
} |
||||
|
||||
fn determine_effects( |
||||
context: &Context, |
||||
actor_id: ActorId, |
||||
skill_id: SkillId, |
||||
target: SkillTarget, |
||||
) -> Result<Vec<SkillEffect>, String> { |
||||
let skill_db: &SkillDb = &*context.get().unwrap(); |
||||
let skill = skill_db |
||||
.get(skill_id) |
||||
.ok_or_else(|| String::from("Skill not found"))?; |
||||
|
||||
skill.func.apply(context, actor_id, target) |
||||
} |
||||
|
||||
struct DeadList(Vec<ActorId>); |
||||
|
||||
fn apply_effects(context: &Context, effects: Vec<SkillEffect>) -> Result<DeadList, String> { |
||||
let mut dead_vec = Vec::new(); |
||||
for effect in effects { |
||||
let actor_db = context.get::<ActorDb>().unwrap(); |
||||
let uic = context.get::<Uic>().unwrap(); |
||||
let mut actor_state_db = context.get_mut::<ActorStateDb>().unwrap(); |
||||
|
||||
match effect { |
||||
SkillEffect::Damage(dmg, EffectTarget(id)) => { |
||||
// TODO: error handling
|
||||
let state = actor_state_db.get(id).unwrap(); |
||||
|
||||
if let ActorState::Alive(hp_left) = state { |
||||
let new_hp = hp_left.saturating_sub(dmg); |
||||
let new_state = if new_hp > 0 { |
||||
ActorState::Alive(new_hp) |
||||
} else { |
||||
dead_vec.push(id); |
||||
ActorState::Dead |
||||
}; |
||||
actor_state_db.set(id, new_state); |
||||
uic.hp_effect(context, id, state, new_state); |
||||
} |
||||
} |
||||
SkillEffect::Heal(hp, EffectTarget(id)) => { |
||||
// TODO: error handling
|
||||
let max_hp = actor_db.get(id).unwrap().stats.max_hp; |
||||
let state = actor_state_db.get(id).unwrap(); |
||||
if let ActorState::Alive(hp_left) = state { |
||||
let after_heal = hp_left.saturating_add(hp); |
||||
let new_state = ActorState::Alive(if after_heal > max_hp { |
||||
max_hp |
||||
} else { |
||||
after_heal |
||||
}); |
||||
|
||||
actor_state_db.set(id, new_state); |
||||
uic.hp_effect(context, id, state, new_state); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Ok(DeadList(dead_vec)) |
||||
} |
||||
|
||||
fn check_result(context: &Context) -> Option<BattleResult> { |
||||
let team_db = context.get::<TeamDb>().unwrap(); |
||||
let actor_state_db = context.get::<ActorStateDb>().unwrap(); |
||||
|
||||
let is_alive = |actor_id: &u32| { |
||||
if let ActorState::Dead = actor_state_db.get(*actor_id).unwrap() { |
||||
false |
||||
} else { |
||||
true |
||||
} |
||||
}; |
||||
let alive: Vec<TeamId> = team_db |
||||
.iter() |
||||
.filter_map(|(team_id, team)| { |
||||
if team.actors.iter().any(is_alive) { |
||||
Some(*team_id) |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(); |
||||
|
||||
match alive.len() { |
||||
0 => Some(BattleResult::AllDead), |
||||
1 => Some(BattleResult::TeamWin(alive[0])), |
||||
_ => None, |
||||
} |
||||
} |
||||
|
||||
fn init_states(context: &Context) { |
||||
let actor_db = context.get::<ActorDb>().unwrap(); |
||||
let mut actor_state_db = context.get_mut::<ActorStateDb>().unwrap(); |
||||
|
||||
for (_, actor) in actor_db.iter() { |
||||
actor_state_db.init(actor) |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
#[derive(Debug)] |
||||
enum BattleResult { |
||||
TeamWin(base::TeamId), |
||||
AllDead, |
||||
} |
@ -0,0 +1,145 @@ |
||||
use super::base::*; |
||||
|
||||
pub struct Queue { |
||||
container: Vec<(Increment, ActorId)>, |
||||
} |
||||
|
||||
impl Queue { |
||||
#[allow(dead_code)] |
||||
pub fn new() -> Queue { |
||||
Queue { |
||||
container: Vec::new(), |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub fn enqueue(&mut self, (incr, id): (Increment, ActorId)) { |
||||
let mut incr = incr; |
||||
let mut insert = None; |
||||
for (i, (incr2, _)) in self.container.iter().enumerate() { |
||||
if incr < *incr2 { |
||||
insert = Some(i); |
||||
break; |
||||
} else if *incr2 != 0 { |
||||
incr -= incr2; |
||||
} |
||||
} |
||||
|
||||
if let Some(ix) = insert { |
||||
self.container[ix].0 -= incr; |
||||
self.container.insert(ix, (incr, id)); |
||||
} else { |
||||
self.container.push((incr, id)); |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub fn dequeue(&mut self) -> Option<(Increment, ActorId)> { |
||||
if self.container.is_empty() { |
||||
None |
||||
} else { |
||||
Some(self.container.remove(0)) |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub fn remove(&mut self, id: ActorId) { |
||||
let mut i = 0; |
||||
loop { |
||||
if i >= self.container.len() { |
||||
break; |
||||
} |
||||
if id == self.container[i].1 { |
||||
let (incr, _) = self.container.remove(i); |
||||
if i < self.container.len() { |
||||
self.container[i].0 += incr; |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
i += 1; |
||||
} |
||||
} |
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &(Increment, ActorId)> { |
||||
self.container.iter() |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
|
||||
#[test] |
||||
fn test_enqueue_between_elements() { |
||||
// Given
|
||||
let mut queue = Queue::new(); |
||||
queue.enqueue((2, 1)); |
||||
queue.enqueue((7, 2)); |
||||
queue.enqueue((3, 3)); |
||||
|
||||
// Expect
|
||||
// id-1 remains first
|
||||
assert_eq!(queue.dequeue(), Some((2, 1))); |
||||
// id-3 moves before id-2, with increment relative to id-1
|
||||
assert_eq!(queue.dequeue(), Some((1, 3))); |
||||
// id-2 has increment adjusted to be relative to id-3
|
||||
assert_eq!(queue.dequeue(), Some((4, 2))); |
||||
// no more remain
|
||||
assert_eq!(queue.dequeue(), None); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_remove_by_id_from_middle() { |
||||
// Given
|
||||
let mut queue = Queue::new(); |
||||
queue.enqueue((1, 1)); |
||||
queue.enqueue((2, 2)); |
||||
queue.enqueue((3, 3)); |
||||
queue.enqueue((4, 4)); |
||||
|
||||
// When
|
||||
queue.remove(3); |
||||
|
||||
// Then expect
|
||||
assert_eq!(queue.dequeue(), Some((1, 1))); |
||||
assert_eq!(queue.dequeue(), Some((1, 2))); |
||||
assert_eq!(queue.dequeue(), Some((2, 4))); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_remove_by_id_from_back() { |
||||
// Given
|
||||
let mut queue = Queue::new(); |
||||
queue.enqueue((1, 1)); |
||||
queue.enqueue((2, 2)); |
||||
queue.enqueue((3, 3)); |
||||
queue.enqueue((4, 4)); |
||||
|
||||
// When
|
||||
queue.remove(4); |
||||
|
||||
// Then expect
|
||||
assert_eq!(queue.dequeue(), Some((1, 1))); |
||||
assert_eq!(queue.dequeue(), Some((1, 2))); |
||||
assert_eq!(queue.dequeue(), Some((1, 3))); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_remove_by_id_from_front() { |
||||
// Given
|
||||
let mut queue = Queue::new(); |
||||
queue.enqueue((1, 1)); |
||||
queue.enqueue((2, 2)); |
||||
queue.enqueue((3, 3)); |
||||
queue.enqueue((4, 4)); |
||||
|
||||
// When
|
||||
queue.remove(1); |
||||
|
||||
// Then expect
|
||||
assert_eq!(queue.dequeue(), Some((2, 2))); |
||||
assert_eq!(queue.dequeue(), Some((1, 3))); |
||||
assert_eq!(queue.dequeue(), Some((1, 4))); |
||||
} |
||||
} |
@ -0,0 +1,126 @@ |
||||
use super::base::*; |
||||
use crate::context::Context; |
||||
|
||||
use std::collections::HashMap; |
||||
|
||||
#[allow(dead_code)] |
||||
pub struct DeciderDb { |
||||
container: HashMap<AiId, Box<dyn Decider>>, |
||||
} |
||||
#[allow(dead_code)] |
||||
impl DeciderDb { |
||||
pub fn from(deciders: Vec<(AiId, Box<dyn Decider>)>) -> DeciderDb { |
||||
DeciderDb { |
||||
container: deciders.into_iter().collect(), |
||||
} |
||||
} |
||||
|
||||
pub fn get(&self, ai_id: AiId) -> Option<&dyn Decider> { |
||||
self.container.get(&ai_id).map(|x| x.as_ref()) |
||||
} |
||||
} |
||||
|
||||
pub trait Decider { |
||||
fn decide( |
||||
&self, |
||||
context: &Context, |
||||
actor_id: ActorId, |
||||
) -> Result<(SkillId, SkillTarget), String>; |
||||
} |
||||
|
||||
pub struct HumanDecider; |
||||
|
||||
impl Decider for HumanDecider { |
||||
fn decide( |
||||
&self, |
||||
context: &Context, |
||||
actor_id: ActorId, |
||||
) -> Result<(SkillId, SkillTarget), String> { |
||||
let actor_db = context.get::<super::ActorDb>().unwrap(); |
||||
let skill_db = context.get::<super::SkillDb>().unwrap(); |
||||
let uic = context.get::<super::Uic>().unwrap(); |
||||
let actor = actor_db.get(actor_id).unwrap(); |
||||
let skill_id = uic.chose_skill(context, &actor.skills); |
||||
match skill_db.get(skill_id).unwrap().accepts { |
||||
AcceptableTargetTypes::Actor(alignments) => Ok(( |
||||
skill_id, |
||||
SkillTarget::Actor(uic.chose_target_actor(context, actor.team_id, alignments)), |
||||
)), |
||||
AcceptableTargetTypes::Team(aligments) => Ok(( |
||||
skill_id, |
||||
SkillTarget::Team(uic.chose_target_team(context, actor.team_id, aligments)), |
||||
)), |
||||
AcceptableTargetTypes::None => Ok((skill_id, SkillTarget::None)), |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub enum SkillEffect { |
||||
Damage(Hp, EffectTarget), |
||||
Heal(Hp, EffectTarget), |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
#[derive(Clone, Copy)] |
||||
pub enum AcceptableTargetAlignments { |
||||
Friend, |
||||
Enemy, |
||||
All, |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub enum AcceptableTargetTypes { |
||||
Actor(AcceptableTargetAlignments), |
||||
Team(AcceptableTargetAlignments), |
||||
None, |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub struct SkillDb { |
||||
container: HashMap<SkillId, Skill>, |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
impl SkillDb { |
||||
pub fn from(skills: Vec<Skill>) -> SkillDb { |
||||
SkillDb { |
||||
container: skills.into_iter().map(|skill| (skill.id, skill)).collect(), |
||||
} |
||||
} |
||||
|
||||
pub fn get(&self, skill_id: SkillId) -> Option<&Skill> { |
||||
self.container.get(&skill_id) |
||||
} |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub struct Skill { |
||||
pub id: SkillId, |
||||
pub name: String, |
||||
pub accepts: AcceptableTargetTypes, |
||||
pub func: Box<dyn SkillFunc>, |
||||
} |
||||
|
||||
pub trait SkillFunc { |
||||
fn apply( |
||||
&self, |
||||
context: &Context, |
||||
actor_id: ActorId, |
||||
target: SkillTarget, |
||||
) -> Result<Vec<SkillEffect>, String>; |
||||
} |
||||
|
||||
impl<T> SkillFunc for T |
||||
where |
||||
T: Fn(&Context, ActorId, SkillTarget) -> Result<Vec<SkillEffect>, String>, |
||||
{ |
||||
fn apply( |
||||
&self, |
||||
context: &Context, |
||||
actor_id: ActorId, |
||||
target: SkillTarget, |
||||
) -> Result<Vec<SkillEffect>, String> { |
||||
self(context, actor_id, target) |
||||
} |
||||
} |
@ -0,0 +1,157 @@ |
||||
use super::actor::{Actor, ActorDb, ActorState, ActorStateDb, Team, TeamDb}; |
||||
use super::base::{ActorId, SkillId, TeamId}; |
||||
use super::queue::Queue; |
||||
use super::skill::{AcceptableTargetAlignments, Skill, SkillDb}; |
||||
use crate::context::Context; |
||||
#[allow(dead_code, unused_variables)] |
||||
pub struct Uic; |
||||
#[allow(dead_code, unused_variables)] |
||||
impl Uic { |
||||
pub fn start_battle(&self, context: &Context) { |
||||
println!("Starting battle!"); |
||||
} |
||||
|
||||
pub fn create_queue(&self, context: &Context, queue: &Queue) { |
||||
print!("Turn Order: ["); |
||||
let actor_db: &ActorDb = &*context.get().unwrap(); |
||||
let mut iter = queue.iter(); |
||||
if let Some((_, id)) = iter.next() { |
||||
print!("{}", actor_db.get(*id).unwrap().name); |
||||
} |
||||
for (_, id) in iter { |
||||
print!(", {}", actor_db.get(*id).unwrap().name); |
||||
} |
||||
println!("]"); |
||||
} |
||||
|
||||
pub fn start_turn(&self, context: &Context, actor_id: ActorId) { |
||||
let actor_db = context.get::<ActorDb>().unwrap(); |
||||
let actor_state_db = context.get::<ActorStateDb>().unwrap(); |
||||
let actor = actor_db.get(actor_id).unwrap(); |
||||
let actor_state = actor_state_db.get(actor_id).unwrap(); |
||||
println!("Turn of {}", actor.name); |
||||
println!( |
||||
"Hp: {}/{}", |
||||
if let ActorState::Alive(hp) = actor_state { |
||||
hp |
||||
} else { |
||||
panic!() |
||||
}, |
||||
actor.stats.max_hp |
||||
); |
||||
} |
||||
|
||||
pub fn chose_skill(&self, context: &Context, skill_ids: &[SkillId]) -> SkillId { |
||||
let skill_db = context.get::<SkillDb>().unwrap(); |
||||
let skills: Vec<&Skill> = skill_ids |
||||
.iter() |
||||
.map(|id| skill_db.get(*id).unwrap()) |
||||
.collect(); |
||||
|
||||
println!("Choose skill:"); |
||||
for (i, skill) in skills.iter().enumerate() { |
||||
println!("{}: {}", i, skill.name); |
||||
} |
||||
|
||||
let stdin = std::io::stdin(); |
||||
let mut buf = String::new(); |
||||
stdin.read_line(&mut buf).unwrap(); |
||||
let i: usize = buf.trim().parse().unwrap(); |
||||
|
||||
skills[i].id |
||||
} |
||||
|
||||
pub fn chose_target_actor( |
||||
&self, |
||||
context: &Context, |
||||
team_id: TeamId, |
||||
alignments: AcceptableTargetAlignments, |
||||
) -> ActorId { |
||||
let team_db = context.get::<TeamDb>().unwrap(); |
||||
let actor_db = context.get::<ActorDb>().unwrap(); |
||||
let condition: Box<dyn Fn(&(&TeamId, &Team)) -> bool> = match alignments { |
||||
AcceptableTargetAlignments::Friend => { |
||||
Box::from(|(id, _): &(&TeamId, &Team)| **id == team_id) |
||||
} |
||||
AcceptableTargetAlignments::Enemy => { |
||||
Box::from(|(id, _): &(&TeamId, &Team)| **id != team_id) |
||||
} |
||||
AcceptableTargetAlignments::All => Box::from(|_: &(&TeamId, &Team)| true), |
||||
}; |
||||
let targets: Vec<&Actor> = team_db |
||||
.iter() |
||||
.filter(condition) |
||||
.flat_map(|(team_id, team)| team.actors.clone()) |
||||
.map(|id| actor_db.get(id).unwrap()) |
||||
.collect(); |
||||
println!("Choose target:"); |
||||
for (i, actor) in targets.iter().enumerate() { |
||||
println!("{}: {}", i, actor.name) |
||||
} |
||||
|
||||
let stdin = std::io::stdin(); |
||||
let mut buf = String::new(); |
||||
stdin.read_line(&mut buf).unwrap(); |
||||
let i: usize = buf.trim().parse().unwrap(); |
||||
|
||||
targets[i].id |
||||
} |
||||
|
||||
pub fn chose_target_team( |
||||
&self, |
||||
context: &Context, |
||||
team_id: TeamId, |
||||
alignments: AcceptableTargetAlignments, |
||||
) -> TeamId { |
||||
let team_db = context.get::<TeamDb>().unwrap(); |
||||
let actor_db = context.get::<ActorDb>().unwrap(); |
||||
let condition: Box<dyn Fn(&(&TeamId, &Team)) -> bool> = match alignments { |
||||
AcceptableTargetAlignments::Friend => { |
||||
Box::from(|(id, _): &(&TeamId, &Team)| **id == team_id) |
||||
} |
||||
AcceptableTargetAlignments::Enemy => { |
||||
Box::from(|(id, _): &(&TeamId, &Team)| **id != team_id) |
||||
} |
||||
AcceptableTargetAlignments::All => Box::from(|_: &(&TeamId, &Team)| true), |
||||
}; |
||||
let targets: Vec<TeamId> = team_db |
||||
.iter() |
||||
.filter(condition) |
||||
.map(|(team_id, team)| *team_id) |
||||
.collect(); |
||||
println!("Choose target:"); |
||||
for (i, team_id) in targets.iter().enumerate() { |
||||
println!("{}: {}", i, team_id) |
||||
} |
||||
|
||||
let stdin = std::io::stdin(); |
||||
let mut buf = String::new(); |
||||
stdin.read_line(&mut buf).unwrap(); |
||||
let i: usize = buf.trim().parse().unwrap(); |
||||
|
||||
targets[i] |
||||
} |
||||
|
||||
pub fn hp_effect( |
||||
&self, |
||||
context: &Context, |
||||
actor_id: ActorId, |
||||
old_state: ActorState, |
||||
new_state: ActorState, |
||||
) { |
||||
let actor_db = context.get::<ActorDb>().unwrap(); |
||||
let actor = actor_db.get(actor_id).unwrap(); |
||||
let max_hp = actor.stats.max_hp; |
||||
let name = &actor.name; |
||||
let old_str = match old_state { |
||||
ActorState::Alive(hp) => format!("{}/{}", hp, max_hp), |
||||
ActorState::Dead => String::from("Dead"), |
||||
}; |
||||
let new_str = match new_state { |
||||
ActorState::Alive(hp) => format!("{}/{}", hp, max_hp), |
||||
ActorState::Dead => String::from("Dead"), |
||||
}; |
||||
|
||||
println!("Effect on {}: {} => {}", name, old_str, new_str); |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
use std::any::{Any, TypeId}; |
||||
use std::cell::{Ref, RefCell, RefMut}; |
||||
use std::collections::HashMap; |
||||
|
||||
#[allow(dead_code)] |
||||
pub struct Context { |
||||
map: HashMap<TypeId, Box<RefCell<dyn Any>>>, |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
impl Context { |
||||
pub fn new() -> Context { |
||||
Context { |
||||
map: HashMap::new(), |
||||
} |
||||
} |
||||
pub fn set<T: Any + 'static>(&mut self, t: T) { |
||||
self.map |
||||
.insert(TypeId::of::<T>(), Box::new(RefCell::new(t))); |
||||
} |
||||
|
||||
pub fn get<T: Any + 'static>(&self) -> Option<Ref<T>> { |
||||
self.map |
||||
.get(&TypeId::of::<T>()) |
||||
.map(|cell| Ref::map(cell.borrow(), |t| t.downcast_ref::<T>().unwrap())) |
||||
} |
||||
|
||||
pub fn get_mut<T: Any + 'static>(&self) -> Option<RefMut<T>> { |
||||
self.map.get(&TypeId::of::<T>()).map(|cell| { |
||||
RefMut::map(cell.borrow_mut(), |t: &mut dyn Any| { |
||||
t.downcast_mut::<T>().unwrap() |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
pub fn contains<T: Any + 'static>(&self) -> bool { |
||||
self.map.contains_key(&TypeId::of::<T>()) |
||||
} |
||||
} |
||||
|
||||
#[macro_export] |
||||
macro_rules! require { |
||||
($context:expr; $( $T:ty ),* ) => { |
||||
Ok(()) |
||||
$( |
||||
.and( |
||||
if !$context.contains::<$T>() { |
||||
Err(format!("Context does not contain {}", std::any::type_name::<$T>())) |
||||
} else { |
||||
Ok(()) |
||||
} |
||||
) |
||||
)* |
||||
as std::result::Result<(), String> |
||||
|
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
|
||||
#[derive(Eq, PartialEq, Debug)] |
||||
struct TestStruct(&'static str); |
||||
|
||||
#[test] |
||||
fn test_context() { |
||||
// Given
|
||||
let test_data = TestStruct("some content"); |
||||
let mut context = Context::new(); |
||||
|
||||
// When
|
||||
context.set(test_data); |
||||
let retrieved_data: Ref<TestStruct> = context.get().unwrap(); |
||||
|
||||
// Then
|
||||
assert_eq!(*retrieved_data, TestStruct("some content")) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_mut() { |
||||
// Given
|
||||
let test_data = TestStruct("some content"); |
||||
let mut context = Context::new(); |
||||
|
||||
// When
|
||||
context.set(test_data); |
||||
{ |
||||
let mut retrieved_data: RefMut<TestStruct> = context.get_mut().unwrap(); |
||||
retrieved_data.0 = "some other content"; |
||||
} |
||||
|
||||
// Then
|
||||
assert_eq!( |
||||
*context.get::<TestStruct>().unwrap(), |
||||
TestStruct("some other content") |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
use super::context::Context; |
||||
use super::factory::Factory; |
||||
|
||||
use std::any::{Any, TypeId}; |
||||
use std::cell::RefCell; |
||||
use std::ops::Deref; |
||||
use std::rc::Rc; |
||||
|
||||
pub struct Component<T: Any + Sized + 'static>(Rc<RefCell<T>>); |
||||
#[allow(dead_code)] |
||||
impl<T: Any + Sized + 'static> Component<T> { |
||||
pub fn from(context: &dyn Context) -> Option<Result<Self, String>> { |
||||
if let Some(any) = context.get(TypeId::of::<Self>()) { |
||||
Some(Ok(Self::downcast_clone(any))) |
||||
} else { |
||||
match Factory::<T>::from(context) { |
||||
Some(factory) => Some( |
||||
factory |
||||
.build(context) |
||||
.map(|value| Self::register(context, value)), |
||||
), |
||||
None => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn register(context: &dyn Context, value: T) -> Component<T> { |
||||
let component = Component(Rc::new(RefCell::new(value))); |
||||
context.set(TypeId::of::<Self>(), Rc::new(component.clone())); |
||||
component |
||||
} |
||||
|
||||
fn downcast_clone(any: Rc<dyn Any + 'static>) -> Self { |
||||
any.downcast_ref::<Self>().unwrap().clone() |
||||
} |
||||
} |
||||
|
||||
impl<T: Any + Sized + 'static> Clone for Component<T> { |
||||
fn clone(&self) -> Self { |
||||
Component(self.0.clone()) |
||||
} |
||||
} |
||||
|
||||
impl<T: Any + Sized + 'static> Deref for Component<T> { |
||||
type Target = RefCell<T>; |
||||
fn deref(&self) -> &Self::Target { |
||||
self.0.deref() |
||||
} |
||||
} |
||||
|
||||
pub trait ComponentOverlay { |
||||
fn get_component<T: Any + Sized + 'static>(&self) -> Option<Result<Component<T>, String>>; |
||||
fn register_component<T: Any + Sized + 'static>(&self, value: T) -> Component<T>; |
||||
} |
||||
|
||||
impl ComponentOverlay for dyn Context + '_ { |
||||
fn get_component<T: Any + Sized + 'static>(&self) -> Option<Result<Component<T>, String>> { |
||||
Component::from(self) |
||||
} |
||||
fn register_component<T: Any + Sized + 'static>(&self, value: T) -> Component<T> { |
||||
Component::register(self, value) |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
use std::any::{Any, TypeId}; |
||||
use std::rc::Rc; |
||||
|
||||
pub trait Context { |
||||
fn get(&self, type_id: TypeId) -> Option<Rc<dyn Any + 'static>>; |
||||
fn set(&self, type_id: TypeId, component: Rc<dyn Any + 'static>); |
||||
} |
@ -0,0 +1,53 @@ |
||||
use super::context::Context; |
||||
use super::from_context::FromContext; |
||||
|
||||
use std::any::{Any, TypeId}; |
||||
use std::ops::Deref; |
||||
use std::rc::Rc; |
||||
pub struct Factory<T: Any + Sized + 'static>(Rc<dyn FromContext<Output = T>>); |
||||
#[allow(dead_code)] |
||||
impl<T: Any + Sized + 'static> Factory<T> { |
||||
pub fn from(context: &dyn Context) -> Option<Self> { |
||||
context |
||||
.get(TypeId::of::<Self>()) |
||||
.map(|boxed| boxed.downcast_ref::<Self>().unwrap().clone()) |
||||
} |
||||
|
||||
pub fn register(context: &dyn Context, value: Box<dyn FromContext<Output = T>>) -> Factory<T> { |
||||
let factory = Factory(Rc::from(value)); |
||||
context.set(TypeId::of::<Self>(), Rc::new(factory.clone())); |
||||
factory |
||||
} |
||||
} |
||||
|
||||
impl<T: Any + Sized + 'static> Clone for Factory<T> { |
||||
fn clone(&self) -> Self { |
||||
Factory(self.0.clone()) |
||||
} |
||||
} |
||||
|
||||
impl<T: Any + Sized + 'static> Deref for Factory<T> { |
||||
type Target = dyn FromContext<Output = T>; |
||||
fn deref(&self) -> &Self::Target { |
||||
self.0.deref() |
||||
} |
||||
} |
||||
|
||||
pub trait FactoryOverlay { |
||||
fn get_factory<T: Any + Sized + 'static>(&self) -> Option<Factory<T>>; |
||||
fn register_factory<T: Any + Sized + 'static>( |
||||
&self, |
||||
from_context: Box<dyn FromContext<Output = T>>, |
||||
) -> Factory<T>; |
||||
} |
||||
impl FactoryOverlay for dyn Context { |
||||
fn get_factory<T: Any + Sized + 'static>(&self) -> Option<Factory<T>> { |
||||
Factory::from(self) |
||||
} |
||||
fn register_factory<T: Any + Sized + 'static>( |
||||
&self, |
||||
from_context: Box<dyn FromContext<Output = T>>, |
||||
) -> Factory<T> { |
||||
Factory::register(self, from_context) |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
use super::context::Context; |
||||
|
||||
use std::any::Any; |
||||
|
||||
pub trait FromContext { |
||||
type Output: Any + Sized + 'static; |
||||
|
||||
fn build(&self, context: &dyn Context) -> Result<Self::Output, String>; |
||||
} |
||||
|
||||
impl<T: Any + Sized + 'static, FN: Fn(&dyn Context) -> Result<T, String>> FromContext for FN { |
||||
type Output = T; |
||||
|
||||
fn build(&self, context: &dyn Context) -> Result<Self::Output, String> { |
||||
self(context) |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
mod component; |
||||
mod context; |
||||
mod factory; |
||||
mod from_context; |
||||
mod simple; |
||||
mod subcontext; |
@ -0,0 +1,214 @@ |
||||
use super::context::Context; |
||||
use super::subcontext::SubContext; |
||||
|
||||
use std::any::{Any, TypeId}; |
||||
use std::cell::RefCell; |
||||
use std::collections::HashMap; |
||||
use std::ops::Deref; |
||||
use std::rc::Rc; |
||||
|
||||
pub struct SimpleContext(RefCell<HashMap<TypeId, Rc<dyn Any + 'static>>>); |
||||
|
||||
#[allow(dead_code)] |
||||
impl SimpleContext { |
||||
pub fn new() -> SimpleContext { |
||||
SimpleContext(RefCell::new(HashMap::new())) |
||||
} |
||||
} |
||||
|
||||
impl Context for SimpleContext { |
||||
fn get(&self, type_id: TypeId) -> Option<Rc<dyn Any + 'static>> { |
||||
self.0.borrow().get(&type_id).cloned() |
||||
} |
||||
|
||||
fn set(&self, type_id: TypeId, component: Rc<dyn Any + 'static>) { |
||||
self.0.borrow_mut().insert(type_id, component); |
||||
} |
||||
} |
||||
|
||||
impl Deref for SimpleContext { |
||||
type Target = dyn Context; |
||||
|
||||
fn deref(&self) -> &Self::Target { |
||||
self |
||||
} |
||||
} |
||||
|
||||
pub struct SimpleSubContext<'t>(SimpleContext, &'t dyn Context); |
||||
|
||||
impl Context for SimpleSubContext<'_> { |
||||
fn get(&self, type_id: TypeId) -> Option<Rc<dyn Any + 'static>> { |
||||
match self.0.get(type_id) { |
||||
any @ Some(_) => any, |
||||
None => self.1.get(type_id), |
||||
} |
||||
} |
||||
|
||||
fn set(&self, type_id: TypeId, component: Rc<dyn Any + 'static>) { |
||||
self.0.set(type_id, component) |
||||
} |
||||
} |
||||
|
||||
impl<'t> SubContext<'t> for SimpleSubContext<'t> { |
||||
fn subcontext(base_context: &'t dyn Context) -> Self { |
||||
SimpleSubContext(SimpleContext::new(), base_context) |
||||
} |
||||
} |
||||
|
||||
impl<'t> Deref for SimpleSubContext<'t> { |
||||
type Target = dyn Context + 't; |
||||
|
||||
fn deref(&self) -> &Self::Target { |
||||
self |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
use crate::contexts::component::*; |
||||
use crate::contexts::factory::*; |
||||
|
||||
#[test] |
||||
fn should_retrieve_components_from_context() { |
||||
// Given
|
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct A(&'static str); |
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct B(i32, i32); |
||||
|
||||
let context = SimpleContext::new(); |
||||
|
||||
context.register_component(5 as u32); |
||||
context.register_component("test123"); |
||||
context.register_component(A("some_value")); |
||||
context.register_component(B(8, 0)); |
||||
|
||||
// When
|
||||
let comp_u32 = context.get_component::<u32>().unwrap().unwrap(); |
||||
let comp_str = context.get_component::<&'static str>().unwrap().unwrap(); |
||||
let comp_a = context.get_component::<A>().unwrap().unwrap(); |
||||
let comp_b = context.get_component::<B>().unwrap().unwrap(); |
||||
|
||||
// Then
|
||||
assert_eq!(*comp_u32.borrow(), 5); |
||||
assert_eq!(*comp_str.borrow(), "test123"); |
||||
assert_eq!(*comp_a.borrow(), A("some_value")); |
||||
assert_eq!(*comp_b.borrow(), B(8, 0)); |
||||
} |
||||
|
||||
#[test] |
||||
fn should_utilize_factory_for_building_component() { |
||||
// Given
|
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct A(&'static str); |
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct B(i32, i32); |
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct C(&'static str, i32); |
||||
|
||||
let context = SimpleContext::new(); |
||||
|
||||
let c_from_context = |context: &dyn Context| { |
||||
let comp_a = context |
||||
.get_component::<A>() |
||||
.unwrap_or_else(|| Err(String::from("Component A not found")))?; |
||||
let comp_b = context |
||||
.get_component::<B>() |
||||
.unwrap_or_else(|| Err(String::from("Component B not found")))?; |
||||
|
||||
let &A(txt) = &*comp_a.borrow(); |
||||
let &B(n1, n2) = &*comp_b.borrow(); |
||||
|
||||
Ok(C(txt, n1 + n2)) |
||||
}; |
||||
|
||||
context.register_factory(Box::new(c_from_context)); |
||||
context.register_component(A("test")); |
||||
context.register_component(B(4, 8)); |
||||
|
||||
// When
|
||||
let comp_c = context |
||||
.get_component::<C>() |
||||
.unwrap_or_else(|| Err(String::from("Component C not found"))) |
||||
.unwrap(); |
||||
|
||||
// Then
|
||||
assert_eq!(*comp_c.borrow(), C("test", 12)); |
||||
} |
||||
|
||||
#[test] |
||||
fn should_utilize_factory_recursively() { |
||||
// Given
|
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct A(&'static str); |
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct B(i32, i32); |
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct C(&'static str, i32); |
||||
|
||||
let context = SimpleContext::new(); |
||||
|
||||
let b_from_context = |context: &dyn Context| { |
||||
let comp_a = context |
||||
.get_component::<A>() |
||||
.unwrap_or_else(|| Err(String::from("Component A not found")))?; |
||||
let &A(txt) = &*comp_a.borrow(); |
||||
|
||||
Ok(B( |
||||
txt.len() as i32, |
||||
txt.split_ascii_whitespace().count() as i32, |
||||
)) |
||||
}; |
||||
let c_from_context = |context: &dyn Context| { |
||||
let comp_a = context |
||||
.get_component::<A>() |
||||
.unwrap_or_else(|| Err(String::from("Component A not found")))?; |
||||
let comp_b = context |
||||
.get_component::<B>() |
||||
.unwrap_or_else(|| Err(String::from("Component B not found")))?; |
||||
let &A(txt) = &*comp_a.borrow(); |
||||
let &B(n1, n2) = &*comp_b.borrow(); |
||||
|
||||
Ok(C(txt, n1 + n2)) |
||||
}; |
||||
|
||||
context.register_factory(Box::new(b_from_context)); |
||||
context.register_factory(Box::new(c_from_context)); |
||||
context.register_component(A("test abc")); |
||||
|
||||
// When
|
||||
let comp_c = context |
||||
.get_component::<C>() |
||||
.unwrap_or_else(|| Err(String::from("Component C not found"))) |
||||
.unwrap(); |
||||
let comp_b = context |
||||
.get_component::<B>() |
||||
.unwrap_or_else(|| Err(String::from("Component B not found"))) |
||||
.unwrap(); |
||||
|
||||
// Then
|
||||
assert_eq!(*comp_b.borrow(), B(8, 2)); |
||||
assert_eq!(*comp_c.borrow(), C("test abc", 10)); |
||||
} |
||||
|
||||
#[test] |
||||
fn should_retrieve_component_from_base_context() { |
||||
// Given
|
||||
#[derive(Debug, Eq, PartialEq)] |
||||
struct A(&'static str); |
||||
let base_context = SimpleContext::new(); |
||||
let subcontext = SimpleSubContext::subcontext(&base_context); |
||||
|
||||
base_context.register_component(A("Bob's your uncle")); |
||||
|
||||
// When
|
||||
let comp_a = subcontext |
||||
.get_component::<A>() |
||||
.unwrap_or_else(|| Err(String::from("Component A not found"))) |
||||
.unwrap(); |
||||
|
||||
// Then
|
||||
assert_eq!(*comp_a.borrow(), A("Bob's your uncle")); |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
use super::context::Context; |
||||
|
||||
pub trait SubContext<'t>: Context { |
||||
fn subcontext(base_context: &'t dyn Context) -> Self; |
||||
} |
@ -0,0 +1,87 @@ |
||||
#![feature(trait_alias, option_unwrap_none)] |
||||
mod battle; |
||||
mod context; |
||||
mod contexts; |
||||
|
||||
use battle::actor::*; |
||||
use battle::base::*; |
||||
use battle::skill::*; |
||||
use battle::uic::Uic; |
||||
use context::Context; |
||||
|
||||
fn main() { |
||||
println!("Hello, world!"); |
||||
|
||||
let mut context = Context::new(); |
||||
context.set(ActorDb::from(actors_example())); |
||||
context.set(ActorStateDb::new()); |
||||
let team_db = TeamDb::from(&context.get().unwrap(), TeamAiOverrides(Vec::new())); |
||||
context.set(team_db); |
||||
context.set(DeciderDb::from(vec![(0, Box::from(HumanDecider))])); |
||||
let skill_db = SkillDb::from(skills_example()); |
||||
context.set(skill_db); |
||||
context.set(Uic); |
||||
battle::run_battle(context).unwrap(); |
||||
} |
||||
|
||||
fn actors_example() -> Vec<Actor> { |
||||
vec![ |
||||
Actor { |
||||
id: 0, |
||||
team_id: 0, |
||||
name: String::from("Geralt"), |
||||
stats: ActorStats { |
||||
initiative: 100, |
||||
max_hp: 100, |
||||
}, |
||||
ai: None, |
||||
skills: vec![0, 1], |
||||
}, |
||||
Actor { |
||||
id: 1, |
||||
team_id: 1, |
||||
name: String::from("Steff"), |
||||
stats: ActorStats { |
||||
initiative: 120, |
||||
max_hp: 60, |
||||
}, |
||||
ai: None, |
||||
skills: vec![0, 2], |
||||
}, |
||||
] |
||||
} |
||||
|
||||
fn skills_example() -> Vec<Skill> { |
||||
vec![ |
||||
Skill { |
||||
id: 0, |
||||
name: String::from("Wait"), |
||||
accepts: AcceptableTargetTypes::None, |
||||
func: Box::from(|_: &Context, _: ActorId, _: SkillTarget| Ok(Vec::new())), |
||||
}, |
||||
Skill { |
||||
id: 1, |
||||
name: String::from("Heavy Scratch"), |
||||
accepts: AcceptableTargetTypes::Actor(AcceptableTargetAlignments::Enemy), |
||||
func: Box::from(|_: &Context, _: ActorId, t: SkillTarget| { |
||||
if let SkillTarget::Actor(id) = t { |
||||
Ok(vec![SkillEffect::Damage(25, EffectTarget(id))]) |
||||
} else { |
||||
Err(String::from("Accepts only actor-targets")) |
||||
} |
||||
}), |
||||
}, |
||||
Skill { |
||||
id: 2, |
||||
name: String::from("Light Scratch"), |
||||
accepts: AcceptableTargetTypes::Actor(AcceptableTargetAlignments::Enemy), |
||||
func: Box::from(|_: &Context, _: ActorId, t: SkillTarget| { |
||||
if let SkillTarget::Actor(id) = t { |
||||
Ok(vec![SkillEffect::Damage(20, EffectTarget(id))]) |
||||
} else { |
||||
Err(String::from("Accepts only actor-targets")) |
||||
} |
||||
}), |
||||
}, |
||||
] |
||||
} |
Loading…
Reference in new issue