A corporation manager and dashboard for EVE Online
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

368 lines
9.9 KiB

  1. # -*- coding: utf-8 -*-
  2. import gzip
  3. from threading import Lock
  4. import yaml
  5. __all__ = ["Universe"]
  6. class _UniqueObject:
  7. """Base class for uniquely ID'd objects in the universe."""
  8. def __init__(self, universe, id_, data):
  9. self._universe = universe
  10. self._id = id_
  11. self._data = data
  12. @property
  13. def id(self):
  14. """The object's unique ID, as an integer."""
  15. return self._id
  16. class _SolarSystem(_UniqueObject):
  17. """Represents a solar system."""
  18. @property
  19. def name(self):
  20. """The solar system's name, as a string."""
  21. return self._data["name"]
  22. @property
  23. def constellation(self):
  24. """The solar system's constellation, as a _Constellation object."""
  25. return self._universe.constellation(self._data["constellation"])
  26. @property
  27. def region(self):
  28. """The solar system's region, as a _Region object."""
  29. return self._universe.region(self._data["region"])
  30. @property
  31. def security(self):
  32. """The solar system's security status, as a float."""
  33. return self._data["security"]
  34. @property
  35. def coords(self):
  36. """The solar system's coordinates, as a 3-tuple of floats (x, y, z)."""
  37. return tuple(self._data["coords"])
  38. @property
  39. def faction(self):
  40. """The solar system's faction, as a _Faction object, or None."""
  41. if "faction" in self._data:
  42. return self._universe.faction(self._data["faction"])
  43. return self.constellation.faction
  44. @property
  45. def is_nullsec(self):
  46. """Whether the solar system is in nullsec."""
  47. return self.security < 0.05
  48. @property
  49. def is_lowsec(self):
  50. """Whether the solar system is in nullsec."""
  51. return self.security >= 0.05 and self.security < 0.45
  52. @property
  53. def is_highsec(self):
  54. """Whether the solar system is in nullsec."""
  55. return self.security >= 0.45
  56. @property
  57. def is_whspace(self):
  58. """Whether the solar system is in wormhole space."""
  59. return self.region.is_whspace
  60. class _Constellation(_UniqueObject):
  61. """Represents a constellation."""
  62. @property
  63. def name(self):
  64. """The constellation's name, as a string."""
  65. return self._data["name"]
  66. @property
  67. def region(self):
  68. """The constellation's region, as a _Region object."""
  69. return self._universe.region(self._data["region"])
  70. @property
  71. def faction(self):
  72. """The constellation's faction, as a _Faction object, or None."""
  73. if "faction" in self._data:
  74. return self._universe.faction(self._data["faction"])
  75. return self.region.faction
  76. @property
  77. def is_whspace(self):
  78. """Whether the constellation is in wormhole space."""
  79. return self.region.is_whspace
  80. class _Region(_UniqueObject):
  81. """Represents a region."""
  82. @property
  83. def name(self):
  84. """The region's name, as a string."""
  85. return self._data["name"]
  86. @property
  87. def faction(self):
  88. """The region's faction, as a _Faction object, or None."""
  89. if "faction" in self._data:
  90. return self._universe.faction(self._data["faction"])
  91. return None
  92. @property
  93. def is_whspace(self):
  94. """Whether the region is in wormhole space."""
  95. return self._id >= 11000000
  96. class _Faction(_UniqueObject):
  97. """Represents a faction."""
  98. @property
  99. def name(self):
  100. """The faction's name, as a string."""
  101. return self._data["name"]
  102. class _Type(_UniqueObject):
  103. """Represents any type, including ships and materials."""
  104. @property
  105. def name(self):
  106. """The item's name, as a string."""
  107. return self._data["name"]
  108. @property
  109. def group_id(self):
  110. """The item's group ID, as an integer."""
  111. return self._data["group_id"]
  112. @property
  113. def market_group_id(self):
  114. """The item's market group ID, as an integer, or None."""
  115. return self._data.get("market_group_id")
  116. class _Killable(_UniqueObject):
  117. """Represents a killable object, like a ship, structure, or fighter."""
  118. def __init__(self, universe, kid, cat, data):
  119. super().__init__(universe, kid, data)
  120. self._cat = cat
  121. @property
  122. def name(self):
  123. """The killable object's name, as a string."""
  124. return self._data["name"]
  125. @property
  126. def group(self):
  127. """The killable object's group, as a string."""
  128. return self._data["group"]
  129. @property
  130. def is_ship(self):
  131. """Whether the killable object is a ship."""
  132. return self._cat == "ships"
  133. @property
  134. def is_structure(self):
  135. """Whether the killable object is a structure."""
  136. return self._cat == "structures"
  137. @property
  138. def is_fighter(self):
  139. """Whether the killable object is a fighter."""
  140. return self._cat == "fighters"
  141. class _DummySolarSystem(_SolarSystem):
  142. """Represents an unknown or invalid solar system."""
  143. def __init__(self, universe):
  144. super().__init__(universe, -1, {
  145. "name": "Unknown",
  146. "constellation": -1,
  147. "region": -1,
  148. "security": 0.0,
  149. "coords": (0, 0, 0)
  150. })
  151. class _DummyConstellation(_Constellation):
  152. """Represents an unknown or invalid constellation."""
  153. def __init__(self, universe):
  154. super().__init__(universe, -1, {
  155. "name": "Unknown",
  156. "region": -1
  157. })
  158. class _DummyRegion(_Region):
  159. """Represents an unknown or invalid region."""
  160. def __init__(self, universe):
  161. super().__init__(universe, -1, {
  162. "name": "Unknown"
  163. })
  164. class _DummyFaction(_Faction):
  165. """Represents an unknown or invalid faction."""
  166. def __init__(self, universe):
  167. super().__init__(universe, -1, {
  168. "name": "Unknown"
  169. })
  170. class _DummyType(_Type):
  171. """Represents an unknown or invalid type."""
  172. def __init__(self, universe):
  173. super().__init__(universe, -1, {
  174. "name": "Unknown",
  175. "group_id": -1,
  176. "market_group_id": -1
  177. })
  178. class _DummyKillable(_Killable):
  179. """Represents an unknown or invalid killable object."""
  180. def __init__(self, universe):
  181. super().__init__(universe, -1, None, {
  182. "name": "Unknown",
  183. "group": "Unknown"
  184. })
  185. class Universe:
  186. """EVE API module for static universe data."""
  187. def __init__(self, datadir):
  188. self._dir = datadir
  189. self._lock = Lock()
  190. self._loaded = False
  191. self._systems = {}
  192. self._constellations = {}
  193. self._regions = {}
  194. self._factions = {}
  195. self._types = {}
  196. self._killable_idx = {}
  197. self._killable_tab = {}
  198. @staticmethod
  199. def _load_yaml(path):
  200. """Load in and return a YAML file with the given path."""
  201. with gzip.open(str(path), "rb") as fp:
  202. return yaml.load(fp, Loader=yaml.CLoader)
  203. def _load(self):
  204. """Load in universe data. This can be called multiple times safely."""
  205. if self._loaded:
  206. return
  207. with self._lock:
  208. if self._loaded:
  209. return
  210. galaxy = self._load_yaml(self._dir / "galaxy.yml.gz")
  211. self._systems = galaxy["systems"]
  212. self._constellations = galaxy["constellations"]
  213. self._regions = galaxy["regions"]
  214. del galaxy
  215. entities = self._load_yaml(self._dir / "entities.yml.gz")
  216. self._factions = entities["factions"]
  217. del entities
  218. self._types = self._load_yaml(self._dir / "types.yml.gz")
  219. killables = self._load_yaml(self._dir / "killables.yml.gz")
  220. self._killable_idx = {kid: cat for cat, kids in killables.items()
  221. for kid in kids}
  222. self._killable_tab = killables
  223. del killables
  224. self._loaded = True
  225. def system(self, sid):
  226. """Return a _SolarSystem with the given ID.
  227. If the ID is invalid, return a dummy unknown object with ID -1.
  228. """
  229. self._load()
  230. if sid not in self._systems:
  231. return _DummySolarSystem(self)
  232. return _SolarSystem(self, sid, self._systems[sid])
  233. def systems(self):
  234. """Return an iterator over all _SolarSystems."""
  235. self._load()
  236. for sid in self._systems:
  237. yield self.system(sid)
  238. def constellation(self, cid):
  239. """Return a _Constellation with the given ID.
  240. If the ID is invalid, return a dummy unknown object with ID -1.
  241. """
  242. self._load()
  243. if cid not in self._constellations:
  244. return _DummyConstellation(self)
  245. return _Constellation(self, cid, self._constellations[cid])
  246. def region(self, rid):
  247. """Return a _Region with the given ID.
  248. If the ID is invalid, return a dummy unknown object with ID -1.
  249. """
  250. self._load()
  251. if rid not in self._regions:
  252. return _DummyRegion(self)
  253. return _Region(self, rid, self._regions[rid])
  254. def faction(self, fid):
  255. """Return a _Faction with the given ID.
  256. If the ID is invalid, return a dummy unknown object with ID -1.
  257. """
  258. self._load()
  259. if fid not in self._factions:
  260. return _DummyFaction(self)
  261. return _Faction(self, fid, self._factions[fid])
  262. def type(self, tid):
  263. """Return a _Type with the given ID.
  264. If the ID is invalid, return a dummy unknown object with ID -1.
  265. """
  266. self._load()
  267. if tid not in self._types:
  268. return _DummyKillable(self)
  269. return _Type(self, tid, self._types[tid])
  270. def killable(self, kid):
  271. """Return a _Killable with the given ID.
  272. If the ID is invalid, return a dummy unknown object with ID -1.
  273. """
  274. self._load()
  275. if kid not in self._killable_idx:
  276. return _DummyKillable(self)
  277. cat = self._killable_idx[kid]
  278. return _Killable(self, kid, cat, self._killable_tab[cat][kid])