@@ -7,19 +7,23 @@ import yaml | |||||
__all__ = ["Universe"] | __all__ = ["Universe"] | ||||
class _SolarSystem: | |||||
"""Represents a solar system.""" | |||||
class _UniqueObject: | |||||
"""Base class for uniquely ID'd objects in the universe.""" | |||||
def __init__(self, universe, sid, data): | |||||
def __init__(self, universe, id_, data): | |||||
self._universe = universe | self._universe = universe | ||||
self._id = sid | |||||
self._id = id_ | |||||
self._data = data | self._data = data | ||||
@property | @property | ||||
def id(self): | def id(self): | ||||
"""The solar system's ID, as an integer.""" | |||||
"""The object's unique ID, as an integer.""" | |||||
return self._id | return self._id | ||||
class _SolarSystem(_UniqueObject): | |||||
"""Represents a solar system.""" | |||||
@property | @property | ||||
def name(self): | def name(self): | ||||
"""The solar system's name, as a string.""" | """The solar system's name, as a string.""" | ||||
@@ -40,19 +44,31 @@ class _SolarSystem: | |||||
"""The solar system's security status, as a float.""" | """The solar system's security status, as a float.""" | ||||
return self._data["security"] | return self._data["security"] | ||||
@property | |||||
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 | |||||
class _Constellation: | |||||
"""Represents a constellation.""" | |||||
@property | |||||
def is_nullsec(self): | |||||
"""Whether the solar system is in nullsec.""" | |||||
return self.security < 0.05 | |||||
def __init__(self, universe, cid, data): | |||||
self._universe = universe | |||||
self._id = cid | |||||
self._data = data | |||||
@property | |||||
def is_lowsec(self): | |||||
"""Whether the solar system is in nullsec.""" | |||||
return self.security >= 0.05 and self.security < 0.45 | |||||
@property | @property | ||||
def id(self): | |||||
"""The constellation's ID, as an integer.""" | |||||
return self._id | |||||
def is_highsec(self): | |||||
"""Whether the solar system is in nullsec.""" | |||||
return self.security >= 0.45 | |||||
class _Constellation(_UniqueObject): | |||||
"""Represents a constellation.""" | |||||
@property | @property | ||||
def name(self): | def name(self): | ||||
@@ -64,23 +80,36 @@ class _Constellation: | |||||
"""The constellation's region, as a _Region object.""" | """The constellation's region, as a _Region object.""" | ||||
return self._universe.region(self._data["region"]) | return self._universe.region(self._data["region"]) | ||||
@property | |||||
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 | |||||
class _Region: | |||||
class _Region(_UniqueObject): | |||||
"""Represents a region.""" | """Represents a region.""" | ||||
def __init__(self, universe, rid, data): | |||||
self._universe = universe | |||||
self._id = rid | |||||
self._data = data | |||||
@property | |||||
def name(self): | |||||
"""The region's name, as a string.""" | |||||
return self._data["name"] | |||||
@property | @property | ||||
def id(self): | |||||
"""The region's ID, as an integer.""" | |||||
return self._id | |||||
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 | |||||
class _Faction(_UniqueObject): | |||||
"""Represents a faction.""" | |||||
@property | @property | ||||
def name(self): | def name(self): | ||||
"""The region's name, as a string.""" | |||||
"""The faction's name, as a string.""" | |||||
return self._data["name"] | return self._data["name"] | ||||
@@ -115,16 +144,34 @@ class _DummyRegion(_Region): | |||||
}) | }) | ||||
class _DummyFaction(_Faction): | |||||
"""Represents an unknown or invalid faction.""" | |||||
def __init__(self, universe): | |||||
super().__init__(universe, -1, { | |||||
"name": "Unknown" | |||||
}) | |||||
class Universe: | class Universe: | ||||
"""EVE API module for static universe data.""" | """EVE API module for static universe data.""" | ||||
def __init__(self, datadir): | def __init__(self, datadir): | ||||
self._dir = datadir | self._dir = datadir | ||||
self._lock = Lock() | self._lock = Lock() | ||||
self._loaded = False | self._loaded = False | ||||
self._systems = {} | self._systems = {} | ||||
self._constellations = {} | self._constellations = {} | ||||
self._regions = {} | self._regions = {} | ||||
self._factions = {} | |||||
@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): | def _load(self): | ||||
"""Load in universe data. This can be called multiple times safely.""" | """Load in universe data. This can be called multiple times safely.""" | ||||
@@ -135,13 +182,16 @@ class Universe: | |||||
if self._loaded: | if self._loaded: | ||||
return | return | ||||
filename = str(self._dir / "galaxy.yml.gz") | |||||
with gzip.open(filename, "rb") as fp: | |||||
data = yaml.load(fp, Loader=yaml.CLoader) | |||||
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._systems = data["systems"] | |||||
self._constellations = data["constellations"] | |||||
self._regions = data["regions"] | |||||
self._loaded = True | self._loaded = True | ||||
def system(self, sid): | def system(self, sid): | ||||
@@ -173,3 +223,21 @@ class Universe: | |||||
if rid not in self._regions: | if rid not in self._regions: | ||||
return _DummyRegion(self) | return _DummyRegion(self) | ||||
return _Region(self, rid, self._regions[rid]) | return _Region(self, rid, self._regions[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 ship(self, sid): | |||||
"""Return a _Ship with the given ID. | |||||
If the ID is invalid, return a dummy unknown object with ID -1. | |||||
""" | |||||
... | |||||
raise NotImplementedError() |
@@ -90,6 +90,66 @@ def _build_galaxy_skeleton(ids, names): | |||||
print("done.") | print("done.") | ||||
return galaxy | return galaxy | ||||
def _load_assoc_for_system(galaxy, system, rid, cid): | |||||
data = _load_yaml(system / "solarsystem.staticdata") | |||||
sid = data["solarSystemID"] | |||||
sec = data["security"] | |||||
assert isinstance(sid, int) | |||||
assert isinstance(sec, float) | |||||
assert sid >= 0 | |||||
assert sec >= -1.0 and sec <= 1.0 | |||||
galaxy["systems"][sid]["constellation"] = cid | |||||
galaxy["systems"][sid]["region"] = rid | |||||
galaxy["systems"][sid]["security"] = sec | |||||
if "factionID" in data: | |||||
facid = data["factionID"] | |||||
assert isinstance(facid, int) | |||||
assert facid >= 0 | |||||
galaxy["systems"][sid]["faction"] = facid | |||||
def _load_assoc_for_constellation(galaxy, constellation, rid): | |||||
data = _load_yaml(constellation / "constellation.staticdata") | |||||
cid = data["constellationID"] | |||||
assert isinstance(cid, int) | |||||
assert cid >= 0 | |||||
galaxy["constellations"][cid]["region"] = rid | |||||
if "factionID" in data: | |||||
facid = data["factionID"] | |||||
assert isinstance(facid, int) | |||||
assert facid >= 0 | |||||
galaxy["constellations"][cid]["faction"] = facid | |||||
for system in constellation.iterdir(): | |||||
if not system.is_dir(): | |||||
continue | |||||
_load_assoc_for_system(galaxy, system, rid, cid) | |||||
def _load_assoc_for_region(galaxy, region): | |||||
data = _load_yaml(region / "region.staticdata") | |||||
rid = data["regionID"] | |||||
assert isinstance(rid, int) | |||||
assert rid >= 0 | |||||
if "factionID" in data: | |||||
facid = data["factionID"] | |||||
assert isinstance(facid, int) | |||||
assert facid >= 0 | |||||
galaxy["regions"][rid]["faction"] = facid | |||||
for constellation in region.iterdir(): | |||||
if not constellation.is_dir(): | |||||
continue | |||||
_load_assoc_for_constellation(galaxy, constellation, rid) | |||||
def _load_galaxy_associations(sde_dir, galaxy): | def _load_galaxy_associations(sde_dir, galaxy): | ||||
print("Loading galaxy staticdata... ", end="", flush=True) | print("Loading galaxy staticdata... ", end="", flush=True) | ||||
@@ -101,38 +161,8 @@ def _load_galaxy_associations(sde_dir, galaxy): | |||||
for region in base.iterdir(): | for region in base.iterdir(): | ||||
if not region.is_dir(): | if not region.is_dir(): | ||||
continue | continue | ||||
rdata = _load_yaml(region / "region.staticdata") | |||||
rid = rdata["regionID"] | |||||
assert isinstance(rid, int) | |||||
assert rid >= 0 | |||||
for constellation in region.iterdir(): | |||||
if not constellation.is_dir(): | |||||
continue | |||||
cdata = _load_yaml(constellation / "constellation.staticdata") | |||||
cid = cdata["constellationID"] | |||||
assert isinstance(cid, int) | |||||
assert cid >= 0 | |||||
galaxy["constellations"][cid]["region"] = rid | |||||
for system in constellation.iterdir(): | |||||
if not system.is_dir(): | |||||
continue | |||||
sdata = _load_yaml(system / "solarsystem.staticdata") | |||||
sid = sdata["solarSystemID"] | |||||
sec = sdata["security"] | |||||
assert isinstance(sid, int) | |||||
assert isinstance(sec, float) | |||||
assert sid >= 0 | |||||
assert sec >= -1.0 and sec <= 1.0 | |||||
galaxy["systems"][sid]["constellation"] = cid | |||||
galaxy["systems"][sid]["region"] = rid | |||||
galaxy["systems"][sid]["security"] = sec | |||||
_load_assoc_for_region(galaxy, region) | |||||
print("done.") | print("done.") | ||||
@@ -145,6 +175,25 @@ def _load_galaxy_associations(sde_dir, galaxy): | |||||
if system["region"] < 0 or system["constellation"] < 0: | if system["region"] < 0 or system["constellation"] < 0: | ||||
print("[WARNING] Orphaned system: %d=%s" % (sid, system["name"])) | print("[WARNING] Orphaned system: %d=%s" % (sid, system["name"])) | ||||
def _load_factions(sde_dir): | |||||
print("Loading factions... ", end="", flush=True) | |||||
data = _load_yaml(sde_dir / "bsd" / "chrFactions.yaml") | |||||
factions = {} | |||||
for entry in data: | |||||
fid = entry["factionID"] | |||||
name = entry["factionName"] | |||||
assert isinstance(fid, int) | |||||
assert isinstance(name, str) | |||||
assert fid >= 0 | |||||
factions[fid] = {"name": name} | |||||
print("done.") | |||||
return factions | |||||
def _dump_galaxy(out_dir, galaxy): | def _dump_galaxy(out_dir, galaxy): | ||||
print("Dumping galaxy... ", end="", flush=True) | print("Dumping galaxy... ", end="", flush=True) | ||||
@@ -154,22 +203,37 @@ def _dump_galaxy(out_dir, galaxy): | |||||
print("done.") | print("done.") | ||||
def _compress_galaxy(out_dir): | |||||
print("Compressing galaxy... ", end="", flush=True) | |||||
def _dump_entities(out_dir, factions): | |||||
print("Dumping entities... ", end="", flush=True) | |||||
fn_src = out_dir / "galaxy.yml" | |||||
fn_dst = out_dir / "galaxy.yml.gz" | |||||
entities = {"factions": factions} | |||||
with fn_src.open("rb") as f_in: | |||||
with gzip.open(str(fn_dst), "wb") as f_out: | |||||
shutil.copyfileobj(f_in, f_out) | |||||
filename = out_dir / "entities.yml" | |||||
with filename.open("w") as fp: | |||||
fp.write(yaml.dump(entities, Dumper=yaml.CDumper)) | |||||
print("done.") | print("done.") | ||||
def _compress(out_dir): | |||||
targets = ["galaxy", "entities"] | |||||
for basename in targets: | |||||
print("Compressing %s... " % basename, end="", flush=True) | |||||
fn_src = out_dir / (basename + ".yml") | |||||
fn_dst = out_dir / (basename + ".yml.gz") | |||||
with fn_src.open("rb") as f_in: | |||||
with gzip.open(str(fn_dst), "wb") as f_out: | |||||
shutil.copyfileobj(f_in, f_out) | |||||
print("done.") | |||||
def _cleanup(out_dir): | def _cleanup(out_dir): | ||||
print("Cleaning up... ", end="", flush=True) | print("Cleaning up... ", end="", flush=True) | ||||
(out_dir / "galaxy.yml").unlink() | |||||
targets = ["galaxy", "entities"] | |||||
for basename in targets: | |||||
(out_dir / (basename + ".yml")).unlink() | |||||
print("done.") | print("done.") | ||||
@@ -184,13 +248,19 @@ def import_sde(sde_dir, out_dir): | |||||
print("Counts: regions=%d, constellations=%d, systems=%d" % ( | print("Counts: regions=%d, constellations=%d, systems=%d" % ( | ||||
len(ids[_REGION]), len(ids[_CONSTELLATION]), len(ids[_SOLAR_SYSTEM]))) | len(ids[_REGION]), len(ids[_CONSTELLATION]), len(ids[_SOLAR_SYSTEM]))) | ||||
names = _load_names(sde_dir) | names = _load_names(sde_dir) | ||||
galaxy = _build_galaxy_skeleton(ids, names) | galaxy = _build_galaxy_skeleton(ids, names) | ||||
del ids | del ids | ||||
del names | del names | ||||
_load_galaxy_associations(sde_dir, galaxy) | _load_galaxy_associations(sde_dir, galaxy) | ||||
_dump_galaxy(out_dir, galaxy) | _dump_galaxy(out_dir, galaxy) | ||||
del galaxy | del galaxy | ||||
_compress_galaxy(out_dir) | |||||
factions = _load_factions(sde_dir) | |||||
_dump_entities(out_dir, factions) | |||||
del factions | |||||
_compress(out_dir) | |||||
_cleanup(out_dir) | _cleanup(out_dir) | ||||
def main(): | def main(): | ||||