1use enum_dispatch::enum_dispatch;
2use strum_macros::{EnumIs, EnumString, EnumTryAs};
3
4use crate::{
5 error::Error,
6 from_sql_for_enum,
7 node::{
8 air::AirAirport, bus::BusStop, rail::RailStation, sea::SeaStop, spawn_warp::SpawnWarp,
9 town::Town, AnyNode, Node,
10 },
11 util::{ConnectionExt, ID},
12 Result, GD,
13};
14
15#[enum_dispatch]
16pub trait LocatedNode: Node + Copy {
17 fn world(self, gd: &GD) -> Result<Option<World>> {
18 gd.0.query_one(
19 "SELECT world FROM NodeLocation WHERE i = ?",
20 (self.i(),),
21 |a| a.get(0),
22 )
23 .map_err(Into::into)
24 }
25
26 fn coordinates(self, gd: &GD) -> Result<Option<(f64, f64)>> {
27 gd.0.query_one(
28 "SELECT x, y FROM NodeLocation WHERE i = ?",
29 (self.i(),),
30 |a| {
31 let Some(x) = a.get::<_, Option<f64>>(0)? else {
32 return Ok(None);
33 };
34 let Some(y) = a.get::<_, Option<f64>>(1)? else {
35 return Ok(None);
36 };
37 Ok(Some((x, y)))
38 },
39 )
40 .map_err(Into::into)
41 }
42
43 fn nodes_in_proximity(self, gd: &GD) -> Result<Vec<(AnyLocatedNode, Proximity)>> {
44 gd.0.query_and_then_get_vec(
45 "SELECT node1, node2 FROM Proximity WHERE node1 = ?1 OR node2 = ?1",
46 (self.i(),),
47 |row| {
48 let node1 = row.get(0)?;
49 let node2 = row.get(1)?;
50 Ok((
51 AnyLocatedNode::from_id(gd, if node1 == self.i() { node2 } else { node1 })?
52 .unwrap(),
53 Proximity(node1, node2),
54 ))
55 },
56 )
57 }
58
59 fn shared_facilities(self, gd: &GD) -> Result<Vec<AnyLocatedNode>> {
60 gd.0.query_and_then_get_vec(
61 "SELECT node1, node2 FROM SharedFacility WHERE node1 = ?1 OR node2 = ?1",
62 (self.i(),),
63 |row| {
64 let node1 = row.get(0)?;
65 let node2 = row.get(1)?;
66 AnyLocatedNode::from_id(gd, if node1 == self.i() { node2 } else { node1 })
67 .map(|a| a.unwrap())
68 },
69 )
70 }
71}
72
73#[enum_dispatch(Node, LocatedNode)]
74#[derive(Clone, Copy, PartialEq, Eq, Debug, EnumIs, EnumTryAs)]
75pub enum AnyLocatedNode {
76 AirAirport,
77 BusStop,
78 RailStation,
79 SeaStop,
80 SpawnWarp,
81 Town,
82}
83macro_rules! impl_any_located_node {
84 ($($Variant:ident),+) => {
85 impl TryFrom<AnyNode> for AnyLocatedNode {
86 type Error = Error;
87
88 fn try_from(value: AnyNode) -> std::result::Result<Self, Self::Error> {
89 match value {
90 $(AnyNode::$Variant(a) => Ok(Self::$Variant(a)),)+
91 _ => Err(Error::NodeNotLocated(value.i())),
92 }
93 }
94 }
95
96 impl From<AnyLocatedNode> for AnyNode {
97 fn from(value: AnyLocatedNode) -> Self {
98 match value {
99 $(AnyLocatedNode::$Variant(a) => Self::$Variant(a),)+
100 }
101 }
102 }
103 };
104}
105impl_any_located_node!(AirAirport, BusStop, RailStation, SeaStop, SpawnWarp, Town);
106
107impl AnyLocatedNode {
108 pub fn from_id(gd: &GD, id: ID) -> Result<Option<Self>> {
109 AnyNode::from_id(gd, id)?.map_or_else(|| Ok(None), |a| a.try_into().map(Some))
110 }
111}
112
113#[derive(Clone, Copy, PartialEq, Eq, Debug, EnumString)]
114pub enum World {
115 Old,
116 New,
117 Space,
118}
119from_sql_for_enum!(World);
120
121pub struct Proximity(pub(crate) ID, pub(crate) ID);
122
123impl Proximity {
124 pub fn distance(self, gd: &GD) -> Result<f64> {
125 gd.0.query_one(
126 "SELECT distance FROM Proximity WHERE node1 = ? AND node2 = ?",
127 (self.0, self.1),
128 |a| a.get(0),
129 )
130 .map_err(Into::into)
131 }
132 pub fn explicit(self, gd: &GD) -> Result<bool> {
133 gd.0.query_one(
134 "SELECT explicit FROM Proximity WHERE node1 = ? AND node2 = ?",
135 (self.0, self.1),
136 |a| a.get(0),
137 )
138 .map_err(Into::into)
139 }
140}