-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
234 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
//! Logic for determining which waypoints affect which levels of a world. | ||
//! | ||
//! The core waypoint mechanics: | ||
//! | ||
//! If you die, you respawn at the last waypoint you rested at, with all the | ||
//! regular enemies you damaged or killed restored to full health. Bosses are | ||
//! exceptions that stay dead no matter what after they've been defeated the | ||
//! first time. | ||
//! | ||
//! If you return to rest at the same waypoint you last rested at, enemies | ||
//! will respawn similarly as if you had died. This is to prevent the player | ||
//! from clearing areas by slow attrition where they go back to rest after | ||
//! killing each individual enemy and never need to face the area at full | ||
//! strength while minding their own limited resources. | ||
//! | ||
//! If you start at one waypoint and rest at a different one though, any | ||
//! changes made *in the area between the two waypoints* will be permanent. So | ||
//! it is possible to eventually clear up areas, as long as you're able to | ||
//! actually travel through them without resting. | ||
//! | ||
//! This module is about the logic to figure out just how the "area between | ||
//! two waypoints" is determined. | ||
use std::collections::BinaryHeap; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
use util::{HashMap, HashSet}; | ||
|
||
use crate::{Level, Location, World}; | ||
|
||
impl World { | ||
/// Return all segments between adjacent waypoints that contain given | ||
/// waypoint. | ||
fn waypoint_connections( | ||
&self, | ||
a: Location, | ||
) -> impl Iterator<Item = WaypointPair> + '_ { | ||
self.segment_cover | ||
.keys() | ||
.filter(move |p| p.contains(a)) | ||
.copied() | ||
} | ||
|
||
/// Return a list of sectors that will have changes permanently applied to | ||
/// them when the player started from waypoint `a` and stops at waypoint | ||
/// `b`. The waypoints must correspond to valid altars and be different, | ||
/// or the result will be empty. | ||
/// | ||
/// If the set of sectors where both waypoints are within the | ||
/// second-closest distance to that sector is non-empty, the waypoints are | ||
/// considered to be connected and this set is returned as the result. | ||
/// Otherwise, the result will be the union of the affected areas of all | ||
/// pairs of connected waypoints that form the shortest paths between `a` | ||
/// and `b`. | ||
fn area_between_waypoints( | ||
&self, | ||
a: Location, | ||
b: Location, | ||
) -> HashSet<Level> { | ||
self.shortest_paths_between_waypoints(a, b) | ||
.into_iter() | ||
.flat_map(|p| &self.segment_cover[&p]) | ||
.copied() | ||
.collect() | ||
} | ||
|
||
/// Return all the adjacent waypoint to waypoint connections that for | ||
/// every shortest path between waypoints a and b. | ||
fn shortest_paths_between_waypoints( | ||
&self, | ||
a: Location, | ||
b: Location, | ||
) -> Vec<WaypointPair> { | ||
// Start from set of waypoint_connections for a. | ||
// If b is found in this set, just return the pair. | ||
// | ||
// Return all pairs that are on a path from a to be such that no | ||
// shorter path exists connecting a and b. | ||
|
||
todo!() | ||
} | ||
|
||
fn compute_segment_covers(&self) -> HashMap<WaypointPair, Vec<Level>> { | ||
// Set of all levels: The keys of skeleton. | ||
// Set of altars: Keys in skeleton where the value is_altar. | ||
|
||
// Procedure: for each level start floodfilling skeleton-space in 3D | ||
// taxicab metric of unit level jumps in each direction along | ||
// connectivity (this needs an utility method cuz connectivity can get | ||
// hairy if we get to path segment analysis). Each level gets a vector | ||
// value that contains the taxicab-in-level-units distance to every | ||
// waypoint. | ||
// | ||
// The set of legs the level belongs to is all the ordered pairs of | ||
// the indices of the vector that are within two closest values to the | ||
// level. This is usually two waypoints, but it can be arbitrarily | ||
// many with weird geometries, so then you need to generate a bunch of | ||
// pairs for it. | ||
|
||
let waypoints: Vec<Level> = self | ||
.skeleton | ||
.iter() | ||
.filter_map(|(lev, seg)| seg.has_waypoint().then_some(lev)) | ||
.copied() | ||
.collect(); | ||
|
||
let mut distances: HashMap<Level, BinaryHeap<(usize, Level)>> = | ||
Default::default(); | ||
let mut iters = waypoints | ||
.iter() | ||
.map(|&lev| { | ||
util::bfs( | ||
|&(origin, lev)| { | ||
self.level_neighbors(lev).map(move |lev| (origin, lev)) | ||
}, | ||
vec![(lev, lev)], | ||
) | ||
}) | ||
.collect::<Vec<_>>(); | ||
let mut idx = iters.len() - 1; | ||
|
||
// Set up a custom multizip iterator that advances the floodfill out | ||
// from each waypoint in lockstep... | ||
|
||
// XXX Okay shit scratch this, this won't work, we can't actually stop | ||
// any single iteration from here, would need to wire the machinery | ||
// right down to the bfs neighbors for each, so what I'd need instead | ||
// is some kind of multistructure of the bfs stacks. | ||
|
||
for ((origin, lev), dist) in std::iter::from_fn(move || { | ||
idx += 1; | ||
if idx >= iters.len() { | ||
idx = 0; | ||
} | ||
iters[idx].next() | ||
}) { | ||
// TODO | ||
} | ||
|
||
todo!() | ||
} | ||
} | ||
|
||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] | ||
pub struct WaypointPair(Location, Location); | ||
|
||
impl From<(Location, Location)> for WaypointPair { | ||
fn from((a, b): (Location, Location)) -> Self { | ||
// Normalize the order of the points. | ||
if a.to_array() < b.to_array() { | ||
WaypointPair(a, b) | ||
} else { | ||
WaypointPair(b, a) | ||
} | ||
} | ||
} | ||
|
||
impl WaypointPair { | ||
pub fn contains(&self, loc: Location) -> bool { | ||
self.0 == loc || self.1 == loc | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::MapGenerator; | ||
|
||
use super::*; | ||
|
||
struct DummyGenerator(bool); | ||
|
||
impl MapGenerator for DummyGenerator { | ||
fn run( | ||
&self, | ||
_rng: &mut dyn rand::RngCore, | ||
_lot: &crate::Lot, | ||
) -> anyhow::Result<crate::Patch> { | ||
// No-op, these won't be actually run. | ||
Ok(Default::default()) | ||
} | ||
|
||
fn has_waypoint(&self) -> bool { | ||
// We only care about them for marking waypoints in the skeleton. | ||
self.0 | ||
} | ||
} | ||
|
||
#[test] | ||
fn waypoint_cover() { | ||
// Set up a world with a skeleton of segments with "yes | ||
// waypoint" and "no waypoint" dummy map generators. It should have | ||
// surface layer with spread-out waypoints and some dungeons with | ||
// waypoints at the bottom. Also some stuff that exercise multiple | ||
// possible paths between waypoints. | ||
|
||
// Test: Three altars at equal distances, making a level belong to | ||
// multiple arcs. | ||
|
||
todo!(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters