1pub const LOT_WIDTH: i64 = 375;
10pub const MAX_FLOOR: i64 = 100;
12pub const MIN_FLOOR: i64 = -9;
15pub const LOBBY_INTERVAL: i64 = 15;
17#[doc(hidden)]
18pub const WEDDING_HALL_TOP_FLOOR: i64 = MAX_FLOOR + 4;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
24pub enum Kind {
30 Lobby,
32 Floor,
34 Office,
36 Condo,
38 HotelSingle,
40 HotelDouble,
42 HotelSuite,
44 FastFood,
46 Restaurant,
48 FoodHall,
50 Shop,
52 Cinema,
54 PartyHall,
56 Amusements,
58 BoutiqueBay,
60 FitnessClub,
62 Clinic,
64 Nightclub,
66 Spa,
68 SkyBar,
70 AquaticCenter,
72 Daycare,
74 Stairs,
76 Escalator,
78 ElevatorStandard,
80 ElevatorService,
82 ElevatorExpress,
84 ParkingRamp,
86 Parking,
88 Security,
90 Medical,
92 Housekeeping,
94 Recycling,
96 Metro,
98 WeddingHall,
100 RentalStudio,
102 RentalApartment,
104}
105
106#[doc(hidden)]
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub enum Category {
109 Structure,
110 Transport,
111 Office,
112 Residential,
113 Hotel,
114 Food,
115 Retail,
116 Entertainment,
117 Service,
118 Special,
119}
120
121#[doc(hidden)]
122#[derive(Clone, Copy, Debug)]
123pub struct Facility {
124 pub kind: Kind,
125 pub key: &'static str,
126 pub category: Category,
127 pub name: &'static str,
128 pub width: i64,
129 pub floors: Option<i64>,
130 pub cost: f64,
131 pub min_star: i64,
132 pub population: i64,
133 pub attendance: Option<i64>,
134 pub modern_only: bool,
135 pub transport: bool,
136 pub staff_only: bool,
137 pub basement: bool,
138}
139
140impl Facility {
141 #[doc(hidden)]
142 pub fn available_in(&self, mode: crate::clock::GameMode) -> bool {
147 !self.modern_only || mode.builds_modern_only_kinds()
148 }
149}
150
151mod predicates;
152mod table;
153#[doc(hidden)]
154pub use table::FACILITIES;
155
156impl Kind {
157 #[doc(hidden)]
158 pub fn facility(self) -> &'static Facility {
161 &FACILITIES[self as usize]
162 }
163
164 pub fn as_str(self) -> &'static str {
166 self.facility().key
167 }
168
169 pub fn parse(s: &str) -> Option<Kind> {
178 FACILITIES.iter().find(|f| f.key == s).map(|f| f.kind)
179 }
180
181 pub fn is_structural(self) -> bool {
183 matches!(self, Kind::Floor | Kind::Lobby)
184 }
185
186 pub fn is_transport(self) -> bool {
188 self.facility().transport
189 }
190
191 pub fn is_elevator(self) -> bool {
193 matches!(
194 self,
195 Kind::ElevatorStandard | Kind::ElevatorService | Kind::ElevatorExpress
196 )
197 }
198
199 #[doc(hidden)]
200 pub fn is_hotel(self) -> bool {
201 matches!(
202 self,
203 Kind::HotelSingle | Kind::HotelDouble | Kind::HotelSuite
204 )
205 }
206
207 #[doc(hidden)]
208 pub fn is_rental(self) -> bool {
209 matches!(self, Kind::RentalStudio | Kind::RentalApartment)
210 }
211
212 pub fn is_room(self) -> bool {
214 !self.is_structural() && !self.is_transport()
215 }
216
217 #[doc(hidden)]
218 pub fn floors(self) -> i64 {
220 self.facility().floors.unwrap_or(1)
221 }
222
223 #[doc(hidden)]
224 pub fn highest_story(self) -> i64 {
227 if self == Kind::WeddingHall {
228 MAX_FLOOR + self.floors() - 1
229 } else {
230 MAX_FLOOR
231 }
232 }
233
234 #[doc(hidden)]
235 pub fn highest_base_floor(self) -> i64 {
237 self.highest_story() - (self.floors() - 1)
238 }
239
240 #[doc(hidden)]
241 pub fn max_span(self) -> i64 {
243 match self {
244 Kind::Stairs | Kind::Escalator => 1,
245 Kind::ElevatorExpress => MAX_FLOOR - MIN_FLOOR,
246 _ => 30,
247 }
248 }
249
250 #[doc(hidden)]
251 pub fn is_fixed_span(self) -> bool {
252 self.is_transport() && self.max_span() == 1
253 }
254
255 #[doc(hidden)]
256 pub fn max_cars(self) -> i64 {
257 8
258 }
259
260 #[doc(hidden)]
261 pub fn build_minutes(self) -> f64 {
263 if self.is_structural() {
264 return 0.0;
265 }
266 let f = self.facility();
267 (8.0_f64 * 60.0).min(crate::jsmath::round(
268 60.0 + f.width as f64 * 8.0 + f.cost / 5000.0,
269 ))
270 }
271
272 #[doc(hidden)]
273 pub fn resale_refund(self) -> f64 {
275 (self.facility().cost * 0.5).floor()
276 }
277
278 #[doc(hidden)]
279 pub fn subtype_list(self) -> Option<&'static [&'static str]> {
281 Some(match self {
282 Kind::Restaurant => &[
283 "English Pub",
284 "French",
285 "Chinese",
286 "Sushi Bar",
287 "Steak House",
288 ],
289 Kind::FastFood => &[
290 "Japanese Soba",
291 "Chinese Cafe",
292 "Hamburger Stand",
293 "Ice Cream",
294 "Coffee Shop",
295 ],
296 Kind::Shop => &[
297 "Men's Clothing",
298 "Pet Store",
299 "Flower Shop",
300 "Book Store",
301 "Drug Store",
302 "Boutique",
303 "Electronics",
304 "Bank",
305 "Hair Salon",
306 "Post Office",
307 "Sports Gear",
308 ],
309 Kind::FoodHall => &[
310 "Ramen Bar",
311 "Taco Stand",
312 "Bubble Tea",
313 "Poke Bowl",
314 "Deli Counter",
315 "Coffee Cart",
316 ],
317 Kind::Amusements => &["Classic Arcade", "VR Lounge", "Claw Parlor", "Mini Golf"],
318 Kind::BoutiqueBay => &[
319 "Florist",
320 "Barber",
321 "Phone Repair",
322 "Vintage",
323 "Tattoo",
324 "Record Store",
325 "Gallery",
326 ],
327 Kind::FitnessClub => &[
328 "Weight Floor",
329 "Yoga Studio",
330 "Spin Studio",
331 "Boxing Gym",
332 "Climbing Wall",
333 ],
334 Kind::Clinic => &["Dental", "Urgent Care", "Optometry", "Pharmacy", "Physio"],
335 _ => return None,
336 })
337 }
338}
339
340impl Category {
341 #[doc(hidden)]
342 pub fn as_str(self) -> &'static str {
344 match self {
345 Category::Structure => "structure",
346 Category::Transport => "transport",
347 Category::Office => "office",
348 Category::Residential => "residential",
349 Category::Hotel => "hotel",
350 Category::Food => "food",
351 Category::Retail => "retail",
352 Category::Entertainment => "entertainment",
353 Category::Service => "service",
354 Category::Special => "special",
355 }
356 }
357}
358
359#[doc(hidden)]
360pub fn build_cap(kind: Kind) -> Option<i64> {
362 match kind {
363 Kind::Metro => Some(1),
364 Kind::WeddingHall => Some(1),
365 Kind::Security => Some(10),
366 Kind::Medical => Some(10),
367 Kind::Cinema => Some(16),
368 Kind::PartyHall => Some(16),
369 Kind::AquaticCenter => Some(8),
370 _ => None,
371 }
372}
373
374#[doc(hidden)]
375pub struct Pool {
376 pub kinds: &'static [Kind],
377 pub cap: i64,
378 pub label: &'static str,
379}
380
381#[doc(hidden)]
382pub static POOLED_CAPS: [Pool; 2] = [
384 Pool {
385 kinds: &[
386 Kind::ElevatorStandard,
387 Kind::ElevatorService,
388 Kind::ElevatorExpress,
389 ],
390 cap: 24,
391 label: "elevator shafts",
392 },
393 Pool {
394 kinds: &[Kind::Stairs, Kind::Escalator],
395 cap: 64,
396 label: "stairs/escalators",
397 },
398];
399
400#[doc(hidden)]
401pub fn is_lobby_floor(floor: i64) -> bool {
402 floor == 1 || (floor > 1 && floor % LOBBY_INTERVAL == 0)
403}
404
405#[doc(hidden)]
406pub fn is_sky_lobby_floor(floor: i64) -> bool {
407 floor >= 2 && floor % LOBBY_INTERVAL == 0
408}
409
410#[doc(hidden)]
411pub fn ground_floor_structure_kind(kind: Kind, floor: i64) -> Kind {
412 if kind == Kind::Floor && floor == 1 {
413 Kind::Lobby
414 } else {
415 kind
416 }
417}
418
419#[doc(hidden)]
420pub fn no_basement(kind: Kind) -> bool {
422 matches!(
423 kind,
424 Kind::Office
425 | Kind::Condo
426 | Kind::HotelSingle
427 | Kind::HotelDouble
428 | Kind::HotelSuite
429 | Kind::Housekeeping
430 )
431}
432
433#[doc(hidden)]
434pub fn covers_ground_floor(floor: i64, hgt: i64) -> bool {
435 floor <= 1 && floor + hgt > 1
436}
437
438#[doc(hidden)]
439pub const NEEDS_FLOORS: &str = "Transport must run through built floors. Lay floors first.";
440#[doc(hidden)]
441pub const SHAFT_OVERLAP: &str = "Transport shafts cannot overlap.";
442
443#[cfg(test)]
444mod tests;