A corporation manager and dashboard for EVE Online
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

425 lignes
11 KiB

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