1use enum_dispatch::enum_dispatch;
2use strum_macros::{EnumIs, EnumString, EnumTryAs};
3
4use crate::{
5 _from_sql_for_enum,
6 error::Error,
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 include_str!("../sql/located/nodes_in_proximity.sql"),
46 (self.i(),),
47 |row| {
48 let other_i = row.get(0)?;
49 Ok((
50 AnyLocatedNode::from_id(gd, other_i)?.unwrap(),
51 Proximity(self.i().min(other_i), self.i().max(other_i)),
52 ))
53 },
54 )
55 }
56
57 fn shared_facilities(self, gd: &GD) -> Result<Vec<AnyLocatedNode>> {
58 gd.0.query_and_then_get_vec(
59 include_str!("../sql/located/shared_facilities.sql"),
60 (self.i(),),
61 |row| {
62 let other_i = row.get(0)?;
63 AnyLocatedNode::from_id(gd, other_i).map(|a| a.unwrap())
64 },
65 )
66 }
67}
68
69#[enum_dispatch(Node, LocatedNode)]
70#[derive(Clone, Copy, PartialEq, Eq, Debug, EnumIs, EnumTryAs)]
71pub enum AnyLocatedNode {
72 AirAirport,
73 BusStop,
74 RailStation,
75 SeaStop,
76 SpawnWarp,
77 Town,
78}
79macro_rules! impl_any_located_node {
80 ($($Variant:ident),+) => {
81 impl TryFrom<AnyNode> for AnyLocatedNode {
82 type Error = Error;
83
84 fn try_from(value: AnyNode) -> std::result::Result<Self, Self::Error> {
85 match value {
86 $(AnyNode::$Variant(a) => Ok(Self::$Variant(a)),)+
87 _ => Err(Error::NodeNotLocated(value.i())),
88 }
89 }
90 }
91
92 impl From<AnyLocatedNode> for AnyNode {
93 fn from(value: AnyLocatedNode) -> Self {
94 match value {
95 $(AnyLocatedNode::$Variant(a) => Self::$Variant(a),)+
96 }
97 }
98 }
99 };
100}
101impl_any_located_node!(AirAirport, BusStop, RailStation, SeaStop, SpawnWarp, Town);
102
103impl AnyLocatedNode {
104 pub fn from_id(gd: &GD, id: ID) -> Result<Option<Self>> {
105 AnyNode::from_id(gd, id)?.map_or_else(|| Ok(None), |a| a.try_into().map(Some))
106 }
107}
108
109#[derive(Clone, Copy, PartialEq, Eq, Debug, EnumString)]
110pub enum World {
111 Old,
112 New,
113 Space,
114}
115_from_sql_for_enum!(World);
116
117pub struct Proximity(pub(crate) ID, pub(crate) ID);
118
119impl Proximity {
120 pub fn distance(self, gd: &GD) -> Result<f64> {
121 gd.0.query_one(
122 "SELECT distance FROM Proximity WHERE node1 = ? AND node2 = ?",
123 (self.0, self.1),
124 |a| a.get(0),
125 )
126 .map_err(Into::into)
127 }
128 pub fn explicit(self, gd: &GD) -> Result<bool> {
129 gd.0.query_one(
130 "SELECT explicit FROM Proximity WHERE node1 = ? AND node2 = ?",
131 (self.0, self.1),
132 |a| a.get(0),
133 )
134 .map_err(Into::into)
135 }
136}