Skip to content

Commit

Permalink
Implement condition transfer tracking (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zerthox committed Dec 6, 2023
1 parent 74ab3d9 commit b8a9fb7
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 109 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "arcdps_buddy"
version = "0.5.2"
version = "0.6.0"
edition = "2021"
authors = ["Zerthox"]
repository = "https://github.com/zerthox/arcdps-buddy"
Expand Down
6 changes: 6 additions & 0 deletions src/combat/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ impl From<&Agent<'_>> for Target {
Self::new(kind, name)
}
}

impl PartialEq for Target {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
}
}
130 changes: 94 additions & 36 deletions src/combat/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ use log::debug;

pub use crate::data::Condition;

/// Error margin for times.
pub const TIME_EPSILON: u32 = 10;

/// Transfer tracking.
#[derive(Debug, Clone)]
pub struct TransferTracker {
/// Detected transfers.
pub transfers: Vec<Transfer>,
transfers: Vec<Transfer>,

/// Condition removes.
remove: Vec<Remove>,

/// Condition applies as transfer candidates.
apply: Vec<Transfer>,
apply: Vec<Apply>,
}

impl TransferTracker {
/// Time to retain candidates.
pub const RETAIN_TIME: i32 = 100;

/// Creates a new transfer tracker.
pub const fn new() -> Self {
Self {
transfers: Vec::new(),
Expand All @@ -28,38 +32,60 @@ impl TransferTracker {
}
}

/// Adds a condition remove.
/// Returns an iterator over found condition transfers.
pub fn found(&self) -> &[Transfer] {
&self.transfers
}

/// Adds a new condition remove.
pub fn add_remove(&mut self, remove: Remove) {
self.purge(remove.time);
if let Some(apply) = Self::find_remove(&mut self.apply, |apply| apply.matches(&remove)) {
debug!("transfer: {remove:?} matches {apply:?}");
self.transfers.push(apply)
debug!("transfer candidate {remove:?}");
if let Some(apply) = Self::find_take(&mut self.apply, |apply| apply.matches(&remove)) {
debug!("transfer match {remove:?} {apply:?}");
self.add_transfer(apply)
} else {
self.remove.push(remove)
}
}

/// Adds a condition apply as transfer candidate.
pub fn add_apply(&mut self, apply: Transfer) {
/// Adds a new condition apply.
pub fn add_apply(&mut self, apply: Apply) {
self.purge(apply.time);
if let Some(remove) = Self::find_remove(&mut self.remove, |remove| apply.matches(remove)) {
debug!("transfer: {apply:?} matches {remove:?}");
self.transfers.push(apply)
debug!("transfer candidate {apply:?}");
if let Some(remove) = Self::find_take(&mut self.remove, |remove| apply.matches(remove)) {
debug!("transfer match {apply:?} {remove:?}");
self.add_transfer(apply)
} else {
self.apply.push(apply)
}
}

fn find_remove<T>(vec: &mut Vec<T>, pred: impl FnMut(&T) -> bool) -> Option<T> {
if let Some(index) = vec.iter().position(pred) {
Some(vec.swap_remove(index))
/// Adds a new transfer.
fn add_transfer(&mut self, apply: Apply) {
let transfer = Transfer::from(apply);
if let Some(existing) = self
.transfers
.iter_mut()
.find(|other| transfer.is_group(other))
{
existing.stacks += 1;
debug!("transfer update {existing:?}");
} else {
None
debug!("transfer new {transfer:?}");
self.transfers.push(transfer)
}
}

/// Find an element matching the predicate and take it out of the [`Vec`].
fn find_take<T>(vec: &mut Vec<T>, pred: impl FnMut(&T) -> bool) -> Option<T> {
vec.iter()
.position(pred)
.map(|index| vec.swap_remove(index))
}

/// Purges old information.
fn purge(&mut self, now: i32) {
pub fn purge(&mut self, now: i32) {
self.remove.retain(|el| Self::check_time(el.time, now));
self.apply.retain(|el| Self::check_time(el.time, now));
}
Expand All @@ -76,6 +102,41 @@ impl Default for TransferTracker {
}
}

/// Information about a condition apply.
#[derive(Debug, Clone)]
pub struct Apply {
/// Time of the apply.
pub time: i32,

/// Condition applied.
pub condi: Condition,

/// Duration applied.
pub duration: i32,

/// Target the condition was applied to.
pub target: Target,
}

impl Apply {
/// Creates a new condition apply.
pub fn new(time: i32, condi: Condition, duration: i32, target: Target) -> Self {
Self {
time,
condi,
duration,
target,
}
}

/// Check whether the apply matches a remove.
pub fn matches(&self, remove: &Remove) -> bool {
self.condi == remove.condi
&& self.duration.abs_diff(remove.time) < TIME_EPSILON
&& self.time.abs_diff(remove.time) < TIME_EPSILON
}
}

/// Information about a condition remove.
#[derive(Debug, Clone)]
pub struct Remove {
Expand All @@ -85,17 +146,17 @@ pub struct Remove {
/// Condition removed.
pub condi: Condition,

/// Amount of stacks removed.
pub stacks: u32,
/// Duration removed.
pub duration: i32,
}

impl Remove {
/// Creates a new condition transfer.
pub fn new(time: i32, condi: Condition, stacks: u32) -> Self {
pub fn new(time: i32, condi: Condition, duration: i32) -> Self {
Self {
time,
condi,
stacks,
duration,
}
}
}
Expand All @@ -117,24 +178,21 @@ pub struct Transfer {
}

impl Transfer {
/// Error margin for transfer times.
pub const TIME_EPSILON: i32 = 10;
/// Check whether the transfers should be grouped.
pub fn is_group(&self, other: &Self) -> bool {
self.condi == other.condi
&& self.target == other.target
&& self.time.abs_diff(other.time) < TIME_EPSILON
}
}

/// Creates a new condition transfer.
pub fn new(time: i32, condi: Condition, stacks: u32, target: Target) -> Self {
impl From<Apply> for Transfer {
fn from(apply: Apply) -> Self {
Self {
time,
condi,
stacks,

target,
time: apply.time,
condi: apply.condi,
stacks: 1,
target: apply.target,
}
}

/// Check whether the transfer candidate matches a remove.
pub fn matches(&self, remove: &Remove) -> bool {
self.condi == remove.condi
&& self.stacks == remove.stacks
&& (self.time - remove.time) <= Self::TIME_EPSILON
}
}
8 changes: 8 additions & 0 deletions src/history/fight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,12 @@ impl<T> Fight<T> {
self.end = Some(time);
time - self.start
}

/// Calculates the timestamp as relative time to the fight start.
pub fn relative_time(&self, time: u64) -> Option<i32> {
match self.end {
Some(end) if time > end => None,
_ => Some((time - self.start) as i32),
}
}
}
35 changes: 26 additions & 9 deletions src/history/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,21 @@ impl<T> History<T> {
self.viewed = 0;
}
}
}

#[allow(unused)]
impl<T> History<T>
where
T: Default,
{
/// Calculates relative time to start for the latest fight.
pub fn relative_time(&self, time: u64) -> Option<i32> {
// TODO: handle timestamp in previous fight
self.latest_fight()
.and_then(|fight| fight.relative_time(time))
}

/// Returns the latest fight and the relative time to fight start.
pub fn fight_and_time(&mut self, time: u64) -> Option<(i32, &mut Fight<T>)> {
// TODO: handle timestamp in previous fight
self.latest_fight_mut()
.and_then(|fight| fight.relative_time(time).map(|time| (time, fight)))
}

/// Adds a fight to the history.
pub fn add_fight(&mut self, fight: Fight<T>) {
if let Some(prev) = self.fights.front() {
Expand All @@ -108,19 +116,28 @@ where
}

/// Adds a fight with default data to the history.
pub fn add_fight_default(&mut self, time: u64) {
pub fn add_fight_default(&mut self, time: u64)
where
T: Default,
{
self.add_fight(Fight::new(time, T::default()))
}

/// Adds a fight with default data and target information to the history.
pub fn add_fight_with_target(&mut self, time: u64, species: u32, target: Option<&Agent>) {
pub fn add_fight_with_target(&mut self, time: u64, species: u32, target: Option<&Agent>)
where
T: Default,
{
self.add_fight(Fight::with_target(time, species, target, T::default()));
}

/// Updates the target for the latest fight.
///
/// If there is no fight present or the latest fight already ended, a new fight with the target is added instead.
pub fn update_fight_target(&mut self, time: u64, species: u32, target: Option<&Agent>) {
pub fn update_fight_target(&mut self, time: u64, species: u32, target: Option<&Agent>)
where
T: Default,
{
match self.latest_fight_mut() {
Some(fight @ Fight { end: None, .. }) => fight.update_target(species, target),
_ => self.add_fight_with_target(time, species, target),
Expand Down
Loading

0 comments on commit b8a9fb7

Please sign in to comment.