from nullptr.store import Store from nullptr.models.ship import Ship from nullptr.missions import create_mission, get_mission_class from nullptr.models.waypoint import Waypoint from random import choice, randrange from time import sleep, time from threading import Thread from nullptr.atlas_builder import AtlasBuilder from nullptr.analyzer import Analyzer, Point class CentralCommandError(Exception): pass class CentralCommand: def __init__(self, store, api): self.missions = {} self.stopping = False self.store = store self.analyzer = Analyzer(store) self.api = api self.atlas_builder = AtlasBuilder(store, api) self.update_missions() def get_ready_missions(self): result = [] prio = 1 for ship, mission in self.missions.items(): p = mission.is_ready() if p == prio: result.append(ship) elif p > prio: prio = p result = [ship] return result def single_step(self, ship): if ship not in self.missions: print('ship has no mission') mission = self.missions[ship] mission.step() def tick(self): self.update_missions() missions = self.get_ready_missions() if len(missions) == 0: return False ship = choice(missions) mission = self.missions[ship] mission.step() return True def run_interactive(self): print('auto mode. hit enter to stop') t = Thread(target=self.wait_for_stop) t.daemon = True t.start() self.run() print('manual mode') def wait_for_stop(self): try: input() except EOFError: pass self.stopping = True print('stopping...') def run(self): while not self.stopping: did_step = True request_counter = self.api.requests_sent start = time() while request_counter == self.api.requests_sent and did_step: did_step = self.tick() if request_counter == self.api.requests_sent: self.atlas_builder.do_work() else: pass # print('nowork') self.store.flush() dur = time() - start # print(f'step {dur:.03}') zs = 0.5 - dur if zs > 0: sleep(zs) self.stopping = False def stop(self): self.stopping = True def set_mission_param(self, ship, nm, val): if ship not in self.missions: print('set a mission for this ship first') return mission = self.missions[ship] params = mission.params() if not nm in params: print(f'{nm} is not a valid param') return param = params[nm] try: parsed_val = param.parse(val, self.store) except ValueError as e: raise MissionError(e) return ship.set_mission_state(nm, parsed_val) def smipa(self,s,n,v): self.set_mission_param(s,n,v) def update_missions(self): for s in self.store.all(Ship): if s.mission_status == 'done': s.mission = None if s.mission is None: if s in self.missions: self.stop_mission(s) if s.mission is None: self.assign_mission(s) if s.mission is not None and s not in self.missions: self.start_mission(s) if s in self.missions: m = self.missions[s] def find_gas(self, system): m = [w for w in self.store.all_members(system, 'Waypoint') if w.type == 'GAS_GIANT'] if len(m)==0: raise CentralCommandError('no gas giant found') return m[0] def find_metal(self, system): m = [w for w in self.store.all_members(system, Waypoint) if 'COMMON_METAL_DEPOSITS' in w.traits] if len(m) == 0: return None origin = Point(0,0) m = sorted(m, key=lambda w: w.distance(origin)) return m[0] def assign_mission(self, s): if s.role == 'trader': self.assign_trade(s) elif s.role == 'probe': self.assign_probe(s) elif s.role == 'siphon': self.assign_siphon(s) elif s.role == 'hauler': self.assign_hauler(s) elif s.role == 'surveyor': self.assign_survey(s) elif s.role == 'miner': self.assign_mine(s) def assign_mine(self, s): if s.crew is None: raise CentralCommandError('ship has no crew') w = s.crew.site resources = s.crew.resources self.init_mission(s, 'mine') self.smipa(s, 'site', w) self.smipa(s, 'resources', resources) def assign_survey(self, s): if s.crew is None: raise CentralCommandError('ship has no crew') w = s.crew.site self.init_mission(s, 'survey') self.smipa(s, 'site', w) def assign_hauler(self, s): if s.crew is None: raise CentralCommandError('ship has no crew') w = s.crew.site resources = s.crew.resources resource = choice(resources) m = self.analyzer.best_sell_market(s.location.system, resource) s.log(f'assigning haul mission from {w} to {m}') self.init_mission(s, 'haul') self.smipa(s, 'site', w) self.smipa(s, 'dest', m) self.smipa(s, 'resources', resources) def assign_siphon(self, s): if s.crew is None: raise CentralCommandError('ship has no crew') w = s.crew.site self.init_mission(s, 'siphon') self.smipa(s, 'site', w) def assign_probe(self, s): system = s.location.system m = [m.waypoint for m in self.store.all_members(system, 'Marketplace')] m = self.analyzer.solve_tsp(m) hops = [w.symbol for w in m] start_hop = randrange(0, len(hops)) s.log(f'Assigning {s} to probe {len(hops)} starting at {hops[start_hop]}') self.init_mission(s, 'probe') self.smipa(s, 'hops', hops) self.smipa(s, 'next-hop', start_hop) def assign_trade(self, s): t = self.analyzer.find_trade(s.location.system) if t is None: print(f"No trade for {s} found. Idling") self.init_mission(s,'idle') self.smipa(s, 'seconds', 600) return s.log(f'assigning {s} to deliver {t.resource} from {t.source} to {t.dest} at a margin of {t.margin}') self.init_mission(s, 'trade') self.smipa(s, 'site', t.source) self.smipa(s, 'dest', t.dest) def init_mission(self, s, mtyp): if mtyp == 'none': s.mission_state = {} s.mission_status = None s.mission = None return try: mclass = get_mission_class(mtyp) except ValueError: raise CentralCommandError('no such mission') s.mission = mtyp s.mission_status = 'init' s.mission_state = {k: v.default for k,v in mclass.params().items()} self.start_mission(s) def restart_mission(self, s, status='init'): if s not in self.missions: raise CentralCommandError("no mission assigned") s.mission_status = status def start_mission(self, s): mtype = s.mission m = create_mission(mtype, s, self.store, self.api) self.missions[s] = m m.status(s.mission_status) return m def stop_mission(self, s): if s in self.missions: del self.missions[s] def create_default_crews(self): system = self.api.agent.headquarters.system gas_w = self.find_gas(system) metal_w = self.find_metal(system) metal = self.store.get('Crew', 'METAL', create=True) metal.site = metal_w metal.resources = ['COPPER_ORE','IRON_ORE','ALUMINUM_ORE'] gas = self.store.get('Crew', 'GAS', create=True) gas.site = gas_w gas.resources = ['HYDROCARBON','LIQUID_HYDROGEN','LIQUID_NITROGEN'] return [gas, metal]