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.
 
 
 
 
 

341 lines
9.2 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 faction(self):
  36. """The solar system's faction, as a _Faction object, or None."""
  37. if "faction" in self._data:
  38. return self._universe.faction(self._data["faction"])
  39. return self.constellation.faction
  40. @property
  41. def is_nullsec(self):
  42. """Whether the solar system is in nullsec."""
  43. return self.security < 0.05
  44. @property
  45. def is_lowsec(self):
  46. """Whether the solar system is in nullsec."""
  47. return self.security >= 0.05 and self.security < 0.45
  48. @property
  49. def is_highsec(self):
  50. """Whether the solar system is in nullsec."""
  51. return self.security >= 0.45
  52. class _Constellation(_UniqueObject):
  53. """Represents a constellation."""
  54. @property
  55. def name(self):
  56. """The constellation's name, as a string."""
  57. return self._data["name"]
  58. @property
  59. def region(self):
  60. """The constellation's region, as a _Region object."""
  61. return self._universe.region(self._data["region"])
  62. @property
  63. def faction(self):
  64. """The constellation's faction, as a _Faction object, or None."""
  65. if "faction" in self._data:
  66. return self._universe.faction(self._data["faction"])
  67. return self.region.faction
  68. class _Region(_UniqueObject):
  69. """Represents a region."""
  70. @property
  71. def name(self):
  72. """The region's name, as a string."""
  73. return self._data["name"]
  74. @property
  75. def faction(self):
  76. """The region's faction, as a _Faction object, or None."""
  77. if "faction" in self._data:
  78. return self._universe.faction(self._data["faction"])
  79. return None
  80. class _Faction(_UniqueObject):
  81. """Represents a faction."""
  82. @property
  83. def name(self):
  84. """The faction's name, as a string."""
  85. return self._data["name"]
  86. class _Type(_UniqueObject):
  87. """Represents any type, including ships and materials."""
  88. @property
  89. def name(self):
  90. """The item's name, as a string."""
  91. return self._data["name"]
  92. @property
  93. def group_id(self):
  94. """The item's group ID, as an integer."""
  95. return self._data["group_id"]
  96. @property
  97. def market_group_id(self):
  98. """The item's market group ID, as an integer, or None."""
  99. return self._data.get("market_group_id")
  100. class _Killable(_UniqueObject):
  101. """Represents a killable object, like a ship, structure, or fighter."""
  102. def __init__(self, universe, kid, cat, data):
  103. super().__init__(universe, kid, data)
  104. self._cat = cat
  105. @property
  106. def name(self):
  107. """The killable object's name, as a string."""
  108. return self._data["name"]
  109. @property
  110. def group(self):
  111. """The killable object's group, as a string."""
  112. return self._data["group"]
  113. @property
  114. def is_ship(self):
  115. """Whether the killable object is a ship."""
  116. return self._cat == "ships"
  117. @property
  118. def is_structure(self):
  119. """Whether the killable object is a structure."""
  120. return self._cat == "structures"
  121. @property
  122. def is_fighter(self):
  123. """Whether the killable object is a fighter."""
  124. return self._cat == "fighters"
  125. class _DummySolarSystem(_SolarSystem):
  126. """Represents an unknown or invalid solar system."""
  127. def __init__(self, universe):
  128. super().__init__(universe, -1, {
  129. "name": "Unknown",
  130. "constellation": -1,
  131. "region": -1,
  132. "security": 0.0
  133. })
  134. class _DummyConstellation(_Constellation):
  135. """Represents an unknown or invalid constellation."""
  136. def __init__(self, universe):
  137. super().__init__(universe, -1, {
  138. "name": "Unknown",
  139. "region": -1
  140. })
  141. class _DummyRegion(_Region):
  142. """Represents an unknown or invalid region."""
  143. def __init__(self, universe):
  144. super().__init__(universe, -1, {
  145. "name": "Unknown"
  146. })
  147. class _DummyFaction(_Faction):
  148. """Represents an unknown or invalid faction."""
  149. def __init__(self, universe):
  150. super().__init__(universe, -1, {
  151. "name": "Unknown"
  152. })
  153. class _DummyType(_Type):
  154. """Represents an unknown or invalid type."""
  155. def __init__(self, universe):
  156. super().__init__(universe, -1, {
  157. "name": "Unknown",
  158. "group_id": -1,
  159. "market_group_id": -1
  160. })
  161. class _DummyKillable(_Killable):
  162. """Represents an unknown or invalid killable object."""
  163. def __init__(self, universe):
  164. super().__init__(universe, -1, None, {
  165. "name": "Unknown",
  166. "group": "Unknown"
  167. })
  168. class Universe:
  169. """EVE API module for static universe data."""
  170. def __init__(self, datadir):
  171. self._dir = datadir
  172. self._lock = Lock()
  173. self._loaded = False
  174. self._systems = {}
  175. self._constellations = {}
  176. self._regions = {}
  177. self._factions = {}
  178. self._types = {}
  179. self._killable_idx = {}
  180. self._killable_tab = {}
  181. @staticmethod
  182. def _load_yaml(path):
  183. """Load in and return a YAML file with the given path."""
  184. with gzip.open(str(path), "rb") as fp:
  185. return yaml.load(fp, Loader=yaml.CLoader)
  186. def _load(self):
  187. """Load in universe data. This can be called multiple times safely."""
  188. if self._loaded:
  189. return
  190. with self._lock:
  191. if self._loaded:
  192. return
  193. galaxy = self._load_yaml(self._dir / "galaxy.yml.gz")
  194. self._systems = galaxy["systems"]
  195. self._constellations = galaxy["constellations"]
  196. self._regions = galaxy["regions"]
  197. del galaxy
  198. entities = self._load_yaml(self._dir / "entities.yml.gz")
  199. self._factions = entities["factions"]
  200. del entities
  201. self._types = self._load_yaml(self._dir / "types.yml.gz")
  202. killables = self._load_yaml(self._dir / "killables.yml.gz")
  203. self._killable_idx = {kid: cat for cat, kids in killables.items()
  204. for kid in kids}
  205. self._killable_tab = killables
  206. del killables
  207. self._loaded = True
  208. def system(self, sid):
  209. """Return a _SolarSystem with the given ID.
  210. If the ID is invalid, return a dummy unknown object with ID -1.
  211. """
  212. self._load()
  213. if sid not in self._systems:
  214. return _DummySolarSystem(self)
  215. return _SolarSystem(self, sid, self._systems[sid])
  216. def constellation(self, cid):
  217. """Return a _Constellation with the given ID.
  218. If the ID is invalid, return a dummy unknown object with ID -1.
  219. """
  220. self._load()
  221. if cid not in self._constellations:
  222. return _DummyConstellation(self)
  223. return _Constellation(self, cid, self._constellations[cid])
  224. def region(self, rid):
  225. """Return a _Region with the given ID.
  226. If the ID is invalid, return a dummy unknown object with ID -1.
  227. """
  228. self._load()
  229. if rid not in self._regions:
  230. return _DummyRegion(self)
  231. return _Region(self, rid, self._regions[rid])
  232. def faction(self, fid):
  233. """Return a _Faction with the given ID.
  234. If the ID is invalid, return a dummy unknown object with ID -1.
  235. """
  236. self._load()
  237. if fid not in self._factions:
  238. return _DummyFaction(self)
  239. return _Faction(self, fid, self._factions[fid])
  240. def type(self, tid):
  241. """Return a _Type with the given ID.
  242. If the ID is invalid, return a dummy unknown object with ID -1.
  243. """
  244. self._load()
  245. if tid not in self._types:
  246. return _DummyKillable(self)
  247. return _Type(self, tid, self._types[tid])
  248. def killable(self, kid):
  249. """Return a _Killable with the given ID.
  250. If the ID is invalid, return a dummy unknown object with ID -1.
  251. """
  252. self._load()
  253. if kid not in self._killable_idx:
  254. return _DummyKillable(self)
  255. cat = self._killable_idx[kid]
  256. return _Killable(self, kid, cat, self._killable_tab[cat][kid])