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.survey import Survey from nullptr.models.ship import Ship 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 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 MiningMission(Mission): @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_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_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_dock(self): self.api.dock(self.ship) 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_refuel(self): self.api.refuel(self.ship) 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' def step_orbit(self): self.api.orbit(self.ship) 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 class SurveyMission(Mission): 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 def create_mission(mtype, ship, store, api): types = { 'survey': SurveyMission, 'mine': MiningMission } if mtype not in types: logging.warning(f'invalid mission type {mtype}') return m = types[mtype](ship, store, api) return m