from nullptr.store import Store from nullptr.models.base import Base from nullptr.models.waypoint import Waypoint from nullptr.models.contract import Contract from nullptr.models.system import System from nullptr.models.survey import Survey from nullptr.models.ship import Ship from nullptr.analyzer import Analyzer from time import time import logging from nullptr.util import * class MissionError(Exception): pass class MissionParam: def __init__(self, cls, required=True, default=None): self.cls = cls self.required = required self.default = default def parse(self, val, store): if self.cls == str: return str(val) elif self.cls == int: return int(val) elif issubclass(self.cls, Base): data = store.get(self.cls, val) if data is None: raise ValueError('object not found') return data.symbol else: raise ValueError('unknown param typr') class Mission: @classmethod def params(cls): return { } def __init__(self, ship, store, api): self.ship = ship self.store = store self.api = api self.next_step = 0 self.analyzer = Analyzer(self.store) def sts(self, nm, v): if issubclass(type(v), Base): v = v.symbol self.ship.set_mission_state(nm, v) def rst(self, typ, nm): symbol = self.st(nm) return self.store.get(typ, symbol) def st(self, nm): if not nm in self.ship.mission_state: return None return self.ship.mission_state[nm] def status(self, nw=None): if nw is None: return self.ship.mission_status else: self.ship.mission_status = nw def start_state(self): return 'done' def error(self, msg): self.status('error') print(msg) def init_state(self): for name, param in self.params().items(): if param.required and param.default is None: if not name in self.ship.mission_state: return self.error(f'Param {name} not set') self.status(self.start_state()) def steps(self): return { } def step_done(self): logging.info(f'mission finished for {self.ship}') def is_waiting(self): return self.next_step > time() def is_finished(self): return self.status() in ['done','error'] def is_ready(self): return not self.is_waiting() and not self.is_finished() def step(self): steps = self.steps() if self.status() == 'init': self.init_state() status = self.status() if not status in steps: logging.warning(f"Invalid mission status {status}") self.status('error') return handler, next_step = steps[status] try: result = handler() except Exception as e: logging.error(e, exc_info=True) self.status('error') return if type(next_step) == str: self.status(next_step) elif type(next_step) == dict: if result not in next_step: logging.warning(f'Invalid step result {result}') self.status('error') return else: self.status(next_step[result]) print(f'{self.ship} {status} -> {self.status()}') class BaseMission(Mission): def step_go_dest(self): destination = self.rst(Waypoint, 'destination') if self.ship.location() == destination: return self.api.navigate(self.ship, destination) self.next_step = self.ship.arrival def step_go_site(self): site = self.rst(Waypoint,'site') if self.ship.location() == site: return self.api.navigate(self.ship, site) self.next_step = self.ship.arrival def step_unload(self): contract = self.rst(Contract, 'contract') delivery = self.st('delivery') if delivery == 'sell': return self.step_sell(False) typs = self.ship.deliverable_cargo(contract) if len(typs) == 0: return 'done' self.api.deliver(self.ship, typs[0], contract) if len(typs) == 1: return 'done' else: return 'more' def step_sell(self, except_resource=True): target = self.st('resource') market = self.store.get('Marketplace', self.ship.location_str) sellables = market.sellable_items(self.ship.cargo.keys()) if target in sellables and except_resource: sellables.remove(target) if len(sellables) == 0: return 'done' self.api.sell(self.ship, sellables[0]) if len(sellables) == 1: return 'done' else: return 'more' def step_load(self): cargo_space = self.ship.cargo_capacity - self.ship.cargo_units resource = self.st('resource') self.api.buy(self.ship, resource, cargo_space) def travel(self, nm): dest = self.rst(Waypoint, nm) traject = self.st('traject') loc = self.ship.location() if dest == loc: self.sts('traject', None) return 'done' elif traject is None: print(f'calculating path to {dest}') traject = self.calculate_traject(dest) hop = traject.pop(0) if len(hop.split('-')) == 3: self.api.navigate(self.ship, hop) self.next_step = self.ship.arrival else: self.api.jump(self.ship, hop) self.next_step = self.ship.cooldown self.sts('traject', traject) return 'more' def calculate_traject(self, dest): loc = self.ship.location() loc_sys = self.store.get(System, loc.system()) loc_jg = self.analyzer.get_jumpgate(loc_sys) dest_sys = self.store.get(System, dest.system()) dest_jg = self.analyzer.get_jumpgate(dest_sys) path = self.analyzer.find_path(loc_sys, dest_sys) result = [] print(loc.symbol, loc_jg.symbol) if loc.symbol != loc_jg.symbol: result.append(loc_jg.symbol) result += [s.symbol for s in path[1:]] if dest_jg.symbol != dest.symbol: result.append(dest.symbol) print(result) return result def step_travel_site(self): return self.travel('site') def step_travel_dest(self): return self.travel('destination') def step_dock(self): self.api.dock(self.ship) def step_refuel(self): if self.ship.fuel_current < 100: self.api.refuel(self.ship) def step_orbit(self): self.api.orbit(self.ship) class MiningMission(BaseMission): @classmethod def params(cls): return { 'site': MissionParam(Waypoint, True), 'resource': MissionParam(str, True), 'destination': MissionParam(Waypoint, True), 'delivery': MissionParam(str, True, 'deliver'), 'contract': MissionParam(Contract, False) } def start_state(self): return 'go_site' def steps(self): return { 'extract': (self.step_extract, { 'done': 'dock', 'more': 'extract' }), 'dock': (self.step_dock, 'sell'), 'sell': (self.step_sell, { 'more': 'sell', 'done': 'orbit', }), 'orbit': (self.step_orbit, 'jettison'), 'jettison': (self.step_dispose, { 'more': 'jettison', 'done': 'extract', 'full': 'go_dest' }), 'go_dest': (self.step_go_dest, 'dock_dest'), 'dock_dest': (self.step_dock, 'unload'), 'unload': (self.step_unload, { 'done': 'refuel', 'more': 'unload' }), 'refuel': (self.step_refuel, 'orbit_dest'), 'orbit_dest': (self.step_orbit, 'go_site'), 'go_site': (self.step_go_site, 'extract') } def get_survey(self): resource = self.st('resource') site = self.rst(Waypoint,'site') # todo optimize for s in self.store.all(Survey): if resource in s.deposits and site.symbol == s.waypoint(): return s return None def step_extract(self): survey = self.get_survey() print('using survey:', str(survey)) result = self.api.extract(self.ship, survey) symbol = sg(result,'extraction.yield.symbol') units = sg(result,'extraction.yield.units') print('extracted:', units, symbol) self.next_step = self.ship.cooldown if self.ship.cargo_units < self.ship.cargo_capacity: return 'more' else: return 'done' def step_dispose(self): contract = self.rst(Contract, 'contract') typs = self.ship.nondeliverable_cargo(contract) if len(typs) > 0: self.api.jettison(self.ship, typs[0]) if len(typs) > 1: return 'more' elif self.ship.cargo_units > self.ship.cargo_capacity - 3: return 'full' else: return 'done' class SurveyMission(BaseMission): def start_state(self): return 'survey' def steps(self): return { 'survey': (self.step_survey, 'survey') } def step_survey(self): result = self.api.survey(self.ship) #pprint(result, 2) self.next_step = self.ship.cooldown class HaulMission(BaseMission): def start_state(self): return 'orbit2' @classmethod def params(cls): return { 'site': MissionParam(Waypoint, True), 'resource': MissionParam(str, True), 'destination': MissionParam(Waypoint, True), 'delivery': MissionParam(str, True, 'deliver'), 'contract': MissionParam(Contract, False) } def steps(self): return { 'travel': (self.step_travel_site, { 'more': 'travel', 'done': 'dock' }), 'dock': (self.step_dock, 'load'), 'load': (self.step_load, 'orbit'), 'orbit': (self.step_orbit, 'travel_back'), 'travel_back': (self.step_travel_dest, { 'more': 'travel_back', 'done': 'dock2' }), 'dock2': (self.step_dock, 'unload'), 'unload': (self.step_unload, 'refuel'), 'refuel': (self.step_refuel, 'orbit2'), 'orbit2': (self.step_orbit, 'travel') } class TravelMission(BaseMission): def start_state(self): return 'travel' @classmethod def params(cls): return { 'destination': MissionParam(Waypoint, True) } def steps(self): return { 'travel': (self.step_travel_dest, { 'more': 'travel', 'done': 'done' }) } def create_mission(mtype, ship, store, api): types = { 'survey': SurveyMission, 'mine': MiningMission, 'haul': HaulMission, 'travel': TravelMission } if mtype not in types: logging.warning(f'invalid mission type {mtype}') return m = types[mtype](ship, store, api) return m