1#![doc = include_str!("../README.md")]
2
3#[cfg(all(
4 any(
5 all(feature = "circles", any(feature = "squares", feature = "triangles")),
6 all(feature = "squares", feature = "triangles")
7 ),
8 not(doc)
9))]
10compile_error!(
11 r#"Only one of the features "circles", "squares", and "triangles" can be enabled at the same time"#
12);
13
14#[macro_use]
15mod macros {
16 macro_rules! try_can_from {
17 (impl TryFrom<$from:ident>, Error = $error:ident for $t:ty) => {
18 impl TryFrom<$from> for $t {
19 type Error = $error;
20
21 #[inline]
22 fn try_from(value: $from) -> Result<Self, Self::Error> {
23 if <$t as CanFrom<$from>>::can_from(&value) {
24 Ok(wasm_bindgen::JsCast::unchecked_into(value))
25 } else {
26 Err(wasm_bindgen::JsCast::unchecked_into(value))
27 }
28 }
29 }
30
31 impl<'a> TryFrom<&'a $from> for &'a $t {
32 type Error = &'a $error;
33
34 #[inline]
35 fn try_from(value: &'a $from) -> Result<Self, Self::Error> {
36 if <$t as CanFrom<$from>>::can_from(value) {
37 Ok(wasm_bindgen::JsCast::unchecked_ref(value))
38 } else {
39 Err(wasm_bindgen::JsCast::unchecked_ref(value))
40 }
41 }
42 }
43 };
44 }
45}
46
47pub mod console;
48pub mod graphics;
49pub mod memory;
50pub mod players;
51pub mod spirit;
52pub mod structure;
53
54#[cfg(feature = "RenderService")]
55pub mod render_service;
56
57use js_sys::{Array, JsString, Object, Reflect};
58use players::PlayerID;
59use spirit::{DeadSpirit, LivingEnemySpiritID, OperableSpiritID};
60use std::{convert::TryFrom, fmt::Debug, marker::PhantomData, ops::Deref};
61use structure::StructureID;
62use wasm_bindgen::{prelude::*, JsCast};
63pub mod prelude {
65 pub use crate::players::this_player_id;
66 pub use crate::spirit::{
67 my_spirits, spirits, DeadFriendlySpirit, DeadFriendlySpiritID, LivingEnemySpirit,
68 LivingEnemySpiritID, LivingFriendlySpirit, LivingFriendlySpiritID, OperableSpirit,
69 OperableSpiritID, Spirit, SpiritID,
70 };
71 pub use crate::structure::base::{base, bases, enemy_base, Base};
72 pub use crate::structure::outpost::{outpost_mdo, outposts, Outpost};
73 pub use crate::structure::star::{star_a1c, star_p89, star_zxq, stars, Star};
74 pub use crate::{
75 console, graphics, log, tick, Destructible, Entity, EntityID, EnumerateByID, GetByID,
76 OutpostSight, Position, Shape, Sight, TryGetByID,
77 };
78
79 #[cfg(feature = "RenderService")]
80 pub use crate::render_service;
81}
82
83pub(crate) trait CanFrom<S: JsCast>: JsCast {
84 fn can_from(value: &S) -> bool;
85}
86
87pub type Position = [f64];
89
90#[wasm_bindgen(typescript_type = "Shape")]
92pub enum Shape {
93 Circles = "circles",
94 Squares = "squares",
95 Triangles = "triangles",
96}
97
98#[wasm_bindgen]
100extern "C" {
101 #[wasm_bindgen(extends = Object, typescript_type = "OutpostSight")]
105 #[derive(Clone, Debug, PartialEq, Eq)]
106 pub type OutpostSight;
107
108 #[wasm_bindgen(method, getter)]
109 pub fn enemies(this: &OutpostSight) -> Vec<LivingEnemySpiritID>;
110}
111
112#[wasm_bindgen]
114extern "C" {
115 #[wasm_bindgen(extends = OutpostSight, typescript_type = "Sight")]
119 #[derive(Clone, Debug, PartialEq, Eq)]
120 pub type Sight;
121
122 #[wasm_bindgen(method, getter)]
123 pub fn friends(this: &Sight) -> Vec<OperableSpiritID>;
124
125 #[wasm_bindgen(method, getter)]
126 pub fn friends_beamable(this: &Sight) -> Vec<OperableSpiritID>;
127
128 #[wasm_bindgen(method, getter)]
129 pub fn enemies_beamable(this: &Sight) -> Vec<LivingEnemySpiritID>;
130
131 #[wasm_bindgen(method, getter)]
132 pub fn structures(this: &Sight) -> Vec<StructureID>;
133}
134
135#[wasm_bindgen]
137extern "C" {
138 #[wasm_bindgen(extends = JsString, typescript_type = "EntityID")]
140 #[derive(Clone, Debug, PartialEq, Eq)]
141 pub type EntityID;
142
143 #[wasm_bindgen(extends = Object, typescript_type = "Entity")]
147 #[derive(Clone, Debug, PartialEq, Eq)]
148 pub type Entity;
149
150 #[wasm_bindgen(method, getter)]
151 pub fn id(this: &Entity) -> EntityID;
152
153 #[wasm_bindgen(method, getter)]
154 pub fn position(this: &Entity) -> Box<Position>;
155
156 #[wasm_bindgen(method, getter)]
157 pub fn size(this: &Entity) -> u32;
158
159 #[wasm_bindgen(method, getter)]
160 pub fn energy(this: &Entity) -> i32;
161
162 #[wasm_bindgen(method, getter)]
163 fn _last_energized(this: &Entity) -> JsString;
164
165 #[wasm_bindgen(method, getter)]
166 pub fn energy_capacity(this: &Entity) -> i32;
167}
168
169impl Entity {
170 #[inline]
171 pub fn last_energized(&self) -> Option<EntityID> {
172 let jsval = self._last_energized();
173 if jsval.is_falsy() {
174 None
175 } else {
176 Some(jsval.unchecked_into())
177 }
178 }
179}
180
181#[wasm_bindgen]
183extern "C" {
184 #[wasm_bindgen(extends = EntityID, typescript_type = "EntityID")]
186 #[derive(Clone, Debug, PartialEq, Eq)]
187 pub type LivingEntityID;
188
189 #[wasm_bindgen(extends = Entity, typescript_type = "Entity")]
193 #[derive(Clone, Debug, PartialEq, Eq)]
194 pub type LivingEntity;
195
196 #[wasm_bindgen(method, getter)]
197 pub fn id(this: &LivingEntity) -> LivingEntityID;
198
199}
200
201impl CanFrom<Entity> for LivingEntity {
202 #[inline]
203 fn can_from(value: &Entity) -> bool {
204 !Reflect::has(value, &"hp".into()).unwrap()
205 || value.unchecked_ref::<Destructible>().hp() > 0
206 }
207}
208
209try_can_from!(impl TryFrom<Entity>, Error = DeadSpirit for LivingEntity);
210
211#[wasm_bindgen]
213extern "C" {
214 #[wasm_bindgen(extends = EntityID, typescript_type = "EntityID")]
216 #[derive(Clone, Debug, PartialEq, Eq)]
217 pub type DestructibleID;
218
219 #[wasm_bindgen(extends = Entity, typescript_type = "Destructible")]
223 #[derive(Clone, Debug, PartialEq, Eq)]
224 pub type Destructible;
225
226 #[wasm_bindgen(method, getter)]
227 pub fn id(this: &Destructible) -> DestructibleID;
228
229 #[wasm_bindgen(method, getter)]
230 pub fn hp(this: &Destructible) -> i32;
231
232 #[wasm_bindgen(method, getter)]
233 pub fn sight(this: &Destructible) -> Sight;
234
235 #[wasm_bindgen(method, getter)]
236 pub fn player_id(this: &Destructible) -> PlayerID;
237
238 #[wasm_bindgen(method, getter)]
239 pub fn shape(this: &Destructible) -> Shape;
240
241 #[wasm_bindgen(method, getter)]
242 pub fn color(this: &Destructible) -> String;
243}
244
245impl CanFrom<Entity> for Destructible {
246 #[inline]
247 fn can_from(value: &Entity) -> bool {
248 Reflect::has(value, &"hp".into()).unwrap()
249 }
250}
251
252try_can_from!(impl TryFrom<Entity>, Error = Entity for Destructible);
253
254#[wasm_bindgen]
256extern "C" {
257 #[wasm_bindgen(extends = DestructibleID, extends = LivingEntityID, typescript_type = "EntityID")]
259 #[derive(Clone, Debug, PartialEq, Eq)]
260 pub type LivingDestructibleID;
261
262 #[wasm_bindgen(extends = Destructible, extends = LivingEntity, typescript_type = "Destructible")]
266 #[derive(Clone, Debug, PartialEq, Eq)]
267 pub type LivingDestructible;
268
269 #[wasm_bindgen(method, getter)]
270 pub fn id(this: &LivingDestructible) -> LivingDestructibleID;
271}
272
273impl CanFrom<Destructible> for LivingDestructible {
274 #[inline]
275 fn can_from(value: &Destructible) -> bool {
276 value.hp() > 0
277 }
278}
279
280try_can_from!(impl TryFrom<Destructible>, Error = Destructible for LivingDestructible);
281
282impl CanFrom<LivingEntity> for LivingDestructible {
283 #[inline]
284 fn can_from(value: &LivingEntity) -> bool {
285 Reflect::has(value, &"hp".into()).unwrap()
286 }
287}
288
289try_can_from!(impl TryFrom<LivingEntity>, Error = LivingEntity for LivingDestructible);
290
291pub trait GetByID<ID: JsCast, V: JsCast>
298where
299 Self: AsRef<JsValue> + Deref<Target = Object>,
300{
301 fn get(&self, id: &ID) -> V {
303 Reflect::get(self.as_ref(), id.as_ref())
304 .ok()
305 .map(JsCast::unchecked_into)
306 .unwrap()
307 }
308}
309
310pub trait TryGetByID<ID: JsCast, V: JsCast>
315where
316 Self: AsRef<JsValue> + Deref<Target = Object>,
317{
318 fn get(&self, id: &ID) -> Option<V> {
320 Reflect::get(self.as_ref(), id.as_ref())
321 .ok()
322 .map(JsCast::unchecked_into)
323 }
324}
325
326impl<ID: JsCast, V: JsCast, T: GetByID<ID, V>> TryGetByID<ID, V> for T {}
327pub trait EnumerateByID<ID: JsCast, V: JsCast>: GetByID<ID, V> {
332 fn ids(&self) -> ArrayTypedIter<ID> {
334 let array = Object::keys(self);
335 ArrayTypedIter::<ID> {
336 range: 0..array.length(),
337 array,
338 phantom: PhantomData,
339 }
340 }
341
342 fn values(&self) -> ArrayTypedIter<V> {
344 let array = Object::values(self);
345 ArrayTypedIter::<V> {
346 range: 0..array.length(),
347 array,
348 phantom: PhantomData,
349 }
350 }
351}
352
353pub struct ArrayTypedIter<T: JsCast> {
355 range: std::ops::Range<u32>,
356 array: Array,
357 phantom: PhantomData<T>,
358}
359
360impl<T: JsCast> std::iter::Iterator for ArrayTypedIter<T> {
361 type Item = T;
362
363 fn next(&mut self) -> Option<Self::Item> {
364 let index = self.range.next()?;
365 Some(self.array.get(index).unchecked_into())
366 }
367
368 #[inline]
369 fn size_hint(&self) -> (usize, Option<usize>) {
370 self.range.size_hint()
371 }
372}
373
374impl<T: JsCast> std::iter::DoubleEndedIterator for ArrayTypedIter<T> {
375 fn next_back(&mut self) -> Option<Self::Item> {
376 let index = self.range.next_back()?;
377 Some(self.array.get(index).unchecked_into())
378 }
379}
380impl<T: JsCast> std::iter::FusedIterator for ArrayTypedIter<T> {}
381
382impl<T: JsCast> std::iter::ExactSizeIterator for ArrayTypedIter<T> {}
383
384#[wasm_bindgen]
386extern "C" {
387 #[wasm_bindgen(js_name = "tick")]
388 static _tick: u32;
389}
390
391#[inline(always)]
393pub fn tick() -> &'static u32 {
394 &_tick
395}