|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- # -*- coding: utf-8 -*-
-
- import gzip
- from threading import Lock
-
- import yaml
-
- __all__ = ["Universe"]
-
- def _cache(func):
- """Wrap a no-argument method to cache its return value in the object."""
- def inner(self):
- key = func.__name__
- if key in self._cache:
- return self._cache[key]
- value = func(self)
- self._cache[key] = value
- return value
- return inner
-
-
- class _UniqueObject:
- """Base class for uniquely ID'd objects in the universe."""
-
- def __init__(self, universe, id_, data):
- self._universe = universe
- self._id = id_
- self._data = data
- self._cache = {}
-
- @property
- def id(self):
- """The object's unique ID, as an integer."""
- return self._id
-
-
- class _SolarSystem(_UniqueObject):
- """Represents a solar system."""
-
- @property
- def name(self):
- """The solar system's name, as a string."""
- return self._data["name"]
-
- @property
- @_cache
- def constellation(self):
- """The solar system's constellation, as a _Constellation object."""
- return self._universe.constellation(self._data["constellation"])
-
- @property
- @_cache
- def region(self):
- """The solar system's region, as a _Region object."""
- return self._universe.region(self._data["region"])
-
- @property
- def security(self):
- """The solar system's security status, as a float."""
- return self._data["security"]
-
- @property
- def coords(self):
- """The solar system's coordinates, as a 3-tuple of floats (x, y, z)."""
- return tuple(self._data["coords"])
-
- @property
- @_cache
- def gates(self):
- """The solar system's adjacent systems, via stargate.
-
- A list of _SolarSystem objects.
- """
- return [self._universe.system(sid) for sid in self._data["gates"]]
-
- @property
- @_cache
- def faction(self):
- """The solar system's faction, as a _Faction object, or None."""
- if "faction" in self._data:
- return self._universe.faction(self._data["faction"])
- return self.constellation.faction
-
- @property
- def is_nullsec(self):
- """Whether the solar system is in nullsec."""
- return self.security <= 0.0
-
- @property
- def is_lowsec(self):
- """Whether the solar system is in nullsec."""
- return self.security > 0.0 and self.security < 0.45
-
- @property
- def is_highsec(self):
- """Whether the solar system is in nullsec."""
- return self.security >= 0.45
-
- @property
- def is_whspace(self):
- """Whether the solar system is in wormhole space."""
- return self.region.is_whspace
-
-
- class _Constellation(_UniqueObject):
- """Represents a constellation."""
-
- @property
- def name(self):
- """The constellation's name, as a string."""
- return self._data["name"]
-
- @property
- @_cache
- def region(self):
- """The constellation's region, as a _Region object."""
- return self._universe.region(self._data["region"])
-
- @property
- @_cache
- def faction(self):
- """The constellation's faction, as a _Faction object, or None."""
- if "faction" in self._data:
- return self._universe.faction(self._data["faction"])
- return self.region.faction
-
- @property
- def is_whspace(self):
- """Whether the constellation is in wormhole space."""
- return self.region.is_whspace
-
-
- class _Region(_UniqueObject):
- """Represents a region."""
-
- @property
- def name(self):
- """The region's name, as a string."""
- return self._data["name"]
-
- @property
- @_cache
- def faction(self):
- """The region's faction, as a _Faction object, or None."""
- if "faction" in self._data:
- return self._universe.faction(self._data["faction"])
- return None
-
- @property
- def is_whspace(self):
- """Whether the region is in wormhole space."""
- return self._id >= 11000000
-
-
- class _Faction(_UniqueObject):
- """Represents a faction."""
-
- @property
- def name(self):
- """The faction's name, as a string."""
- return self._data["name"]
-
- @property
- @_cache
- def territory(self):
- """The faction's controlled territory.
-
- A list of _SolarSystems.
- """
- return [system for system in self._universe.systems()
- if system.faction and system.faction.id == self.id]
-
-
- class _Type(_UniqueObject):
- """Represents any type, including ships and materials."""
-
- @property
- def name(self):
- """The item's name, as a string."""
- return self._data["name"]
-
- @property
- def group_id(self):
- """The item's group ID, as an integer."""
- return self._data["group_id"]
-
- @property
- def market_group_id(self):
- """The item's market group ID, as an integer, or None."""
- return self._data.get("market_group_id")
-
-
- class _Killable(_UniqueObject):
- """Represents a killable object, like a ship, structure, or fighter."""
-
- def __init__(self, universe, kid, cat, data):
- super().__init__(universe, kid, data)
- self._cat = cat
-
- @property
- def name(self):
- """The killable object's name, as a string."""
- return self._data["name"]
-
- @property
- def group(self):
- """The killable object's group, as a string."""
- return self._data["group"]
-
- @property
- def is_ship(self):
- """Whether the killable object is a ship."""
- return self._cat == "ships"
-
- @property
- def is_structure(self):
- """Whether the killable object is a structure."""
- return self._cat == "structures"
-
- @property
- def is_fighter(self):
- """Whether the killable object is a fighter."""
- return self._cat == "fighters"
-
-
- class _DummySolarSystem(_SolarSystem):
- """Represents an unknown or invalid solar system."""
-
- def __init__(self, universe):
- super().__init__(universe, -1, {
- "name": "Unknown",
- "constellation": -1,
- "region": -1,
- "security": 0.0,
- "coords": (0.0, 0.0, 0.0),
- "gates": []
- })
-
-
- class _DummyConstellation(_Constellation):
- """Represents an unknown or invalid constellation."""
-
- def __init__(self, universe):
- super().__init__(universe, -1, {
- "name": "Unknown",
- "region": -1
- })
-
-
- class _DummyRegion(_Region):
- """Represents an unknown or invalid region."""
-
- def __init__(self, universe):
- super().__init__(universe, -1, {
- "name": "Unknown"
- })
-
-
- class _DummyFaction(_Faction):
- """Represents an unknown or invalid faction."""
-
- def __init__(self, universe):
- super().__init__(universe, -1, {
- "name": "Unknown"
- })
-
-
- class _DummyType(_Type):
- """Represents an unknown or invalid type."""
-
- def __init__(self, universe):
- super().__init__(universe, -1, {
- "name": "Unknown",
- "group_id": -1,
- "market_group_id": -1
- })
-
-
- class _DummyKillable(_Killable):
- """Represents an unknown or invalid killable object."""
-
- def __init__(self, universe):
- super().__init__(universe, -1, None, {
- "name": "Unknown",
- "group": "Unknown"
- })
-
-
- class Universe:
- """EVE API module for static universe data."""
-
- def __init__(self, datadir):
- self._dir = datadir
-
- self._lock = Lock()
- self._loaded = False
-
- self._systems = {}
- self._constellations = {}
- self._regions = {}
- self._factions = {}
- self._types = {}
- self._killable_idx = {}
- self._killable_tab = {}
-
- @staticmethod
- def _load_yaml(path):
- """Load in and return a YAML file with the given path."""
- with gzip.open(str(path), "rb") as fp:
- return yaml.load(fp, Loader=yaml.CLoader)
-
- def _load(self):
- """Load in universe data. This can be called multiple times safely."""
- if self._loaded:
- return
-
- with self._lock:
- if self._loaded:
- return
-
- galaxy = self._load_yaml(self._dir / "galaxy.yml.gz")
- self._systems = galaxy["systems"]
- self._constellations = galaxy["constellations"]
- self._regions = galaxy["regions"]
- del galaxy
-
- entities = self._load_yaml(self._dir / "entities.yml.gz")
- self._factions = entities["factions"]
- del entities
-
- self._types = self._load_yaml(self._dir / "types.yml.gz")
-
- killables = self._load_yaml(self._dir / "killables.yml.gz")
- self._killable_idx = {kid: cat for cat, kids in killables.items()
- for kid in kids}
- self._killable_tab = killables
- del killables
-
- self._loaded = True
-
- def system(self, sid):
- """Return a _SolarSystem with the given ID.
-
- If the ID is invalid, return a dummy unknown object with ID -1.
- """
- self._load()
- if sid not in self._systems:
- return _DummySolarSystem(self)
- return _SolarSystem(self, sid, self._systems[sid])
-
- def systems(self):
- """Return an iterator over all _SolarSystems."""
- self._load()
- for sid in self._systems:
- yield self.system(sid)
-
- def constellation(self, cid):
- """Return a _Constellation with the given ID.
-
- If the ID is invalid, return a dummy unknown object with ID -1.
- """
- self._load()
- if cid not in self._constellations:
- return _DummyConstellation(self)
- return _Constellation(self, cid, self._constellations[cid])
-
- def constellations(self):
- """Return an iterator over all _Constellations."""
- self._load()
- for cid in self._constellations:
- yield self.constellation(cid)
-
- def region(self, rid):
- """Return a _Region with the given ID.
-
- If the ID is invalid, return a dummy unknown object with ID -1.
- """
- self._load()
- if rid not in self._regions:
- return _DummyRegion(self)
- return _Region(self, rid, self._regions[rid])
-
- def regions(self):
- """Return an iterator over all _Regions."""
- self._load()
- for rid in self._regions:
- yield self.region(rid)
-
- def faction(self, fid):
- """Return a _Faction with the given ID.
-
- If the ID is invalid, return a dummy unknown object with ID -1.
- """
- self._load()
- if fid not in self._factions:
- return _DummyFaction(self)
- return _Faction(self, fid, self._factions[fid])
-
- def factions(self):
- """Return an iterator over all _Factions."""
- self._load()
- for fid in self._factions:
- yield self.faction(fid)
-
- def type(self, tid):
- """Return a _Type with the given ID.
-
- If the ID is invalid, return a dummy unknown object with ID -1.
- """
- self._load()
- if tid not in self._types:
- return _DummyKillable(self)
- return _Type(self, tid, self._types[tid])
-
- def killable(self, kid):
- """Return a _Killable with the given ID.
-
- If the ID is invalid, return a dummy unknown object with ID -1.
- """
- self._load()
- if kid not in self._killable_idx:
- return _DummyKillable(self)
- cat = self._killable_idx[kid]
- return _Killable(self, kid, cat, self._killable_tab[cat][kid])
|