2023-06-18 17:15:51 +00:00
|
|
|
from nullptr.store import Store
|
|
|
|
from nullptr.models.base import Base
|
2023-06-17 18:18:14 +00:00
|
|
|
from nullptr.models.waypoint import Waypoint
|
|
|
|
from nullptr.models.contract import Contract
|
2023-06-20 19:46:05 +00:00
|
|
|
from nullptr.models.system import System
|
2023-06-17 18:18:14 +00:00
|
|
|
from nullptr.models.survey import Survey
|
|
|
|
from nullptr.models.ship import Ship
|
2023-06-20 19:46:05 +00:00
|
|
|
from nullptr.analyzer import Analyzer
|
2023-06-17 18:18:14 +00:00
|
|
|
from time import time
|
|
|
|
import logging
|
2023-06-18 17:15:51 +00:00
|
|
|
from nullptr.util import *
|
2023-06-17 18:18:14 +00:00
|
|
|
|
2023-06-18 17:15:51 +00:00
|
|
|
class MissionError(Exception):
|
|
|
|
pass
|
|
|
|
|
2023-06-17 18:18:14 +00:00
|
|
|
class MissionParam:
|
|
|
|
def __init__(self, cls, required=True, default=None):
|
|
|
|
self.cls = cls
|
|
|
|
self.required = required
|
|
|
|
self.default = default
|
|
|
|
|
2023-06-18 17:15:51 +00:00
|
|
|
def parse(self, val, store):
|
2023-06-17 18:18:14 +00:00
|
|
|
if self.cls == str:
|
|
|
|
return str(val)
|
|
|
|
elif self.cls == int:
|
|
|
|
return int(val)
|
2023-06-18 17:15:51 +00:00
|
|
|
elif issubclass(self.cls, Base):
|
2023-06-17 18:18:14 +00:00
|
|
|
data = store.get(self.cls, val)
|
|
|
|
if data is None:
|
|
|
|
raise ValueError('object not found')
|
2023-06-18 17:15:51 +00:00
|
|
|
return data.symbol
|
2023-06-17 18:18:14 +00:00
|
|
|
else:
|
|
|
|
raise ValueError('unknown param typr')
|
|
|
|
|
|
|
|
class Mission:
|
|
|
|
@classmethod
|
|
|
|
def params(cls):
|
|
|
|
return {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-06-18 17:15:51 +00:00
|
|
|
def __init__(self, ship, store, api):
|
2023-06-17 18:18:14 +00:00
|
|
|
self.ship = ship
|
2023-06-18 17:15:51 +00:00
|
|
|
self.store = store
|
2023-06-17 18:18:14 +00:00
|
|
|
self.api = api
|
2023-06-18 17:15:51 +00:00
|
|
|
self.next_step = 0
|
2023-06-20 19:46:05 +00:00
|
|
|
self.analyzer = Analyzer(self.store)
|
2023-06-17 18:18:14 +00:00
|
|
|
|
|
|
|
def sts(self, nm, v):
|
2023-06-18 17:15:51 +00:00
|
|
|
if issubclass(type(v), Base):
|
|
|
|
v = v.symbol
|
|
|
|
self.ship.set_mission_state(nm, v)
|
2023-06-17 18:18:14 +00:00
|
|
|
|
2023-06-18 17:15:51 +00:00
|
|
|
def rst(self, typ, nm):
|
|
|
|
symbol = self.st(nm)
|
|
|
|
return self.store.get(typ, symbol)
|
|
|
|
|
2023-06-17 18:18:14 +00:00
|
|
|
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:
|
2023-06-18 17:15:51 +00:00
|
|
|
logging.error(e, exc_info=True)
|
2023-06-17 18:18:14 +00:00
|
|
|
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()}')
|
|
|
|
|
2023-06-20 19:46:05 +00:00
|
|
|
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'
|
2023-06-17 18:18:14 +00:00
|
|
|
|
2023-06-20 19:46:05 +00:00
|
|
|
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
|
2023-06-20 21:12:57 +00:00
|
|
|
if traject == []:
|
|
|
|
traject= None
|
2023-06-20 19:46:05 +00:00
|
|
|
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):
|
2023-06-17 18:18:14 +00:00
|
|
|
@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')
|
2023-06-18 17:15:51 +00:00
|
|
|
site = self.rst(Waypoint,'site')
|
2023-06-17 18:18:14 +00:00
|
|
|
# todo optimize
|
2023-06-18 17:15:51 +00:00
|
|
|
for s in self.store.all(Survey):
|
|
|
|
if resource in s.deposits and site.symbol == s.waypoint():
|
2023-06-17 18:18:14 +00:00
|
|
|
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):
|
2023-06-18 17:15:51 +00:00
|
|
|
contract = self.rst(Contract, 'contract')
|
2023-06-17 18:18:14 +00:00
|
|
|
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'
|
|
|
|
|
2023-06-20 19:46:05 +00:00
|
|
|
class SurveyMission(BaseMission):
|
2023-06-17 18:18:14 +00:00
|
|
|
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
|
|
|
|
|
2023-06-20 19:46:05 +00:00
|
|
|
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'
|
|
|
|
}),
|
2023-06-21 07:54:07 +00:00
|
|
|
'dock': (self.step_dock, 'refuel'),
|
|
|
|
'refuel': (self.step_unload, 'load'),
|
2023-06-20 19:46:05 +00:00
|
|
|
'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'),
|
2023-06-21 07:54:07 +00:00
|
|
|
'unload': (self.step_unload, 'refuel2'),
|
|
|
|
'refuel2': (self.step_refuel, 'orbit2'),
|
2023-06-20 19:46:05 +00:00
|
|
|
'orbit2': (self.step_orbit, 'travel')
|
|
|
|
}
|
|
|
|
|
2023-06-20 20:30:21 +00:00
|
|
|
class TravelMission(BaseMission):
|
|
|
|
def start_state(self):
|
2023-06-20 21:12:57 +00:00
|
|
|
return 'orbit'
|
2023-06-20 20:30:21 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def params(cls):
|
|
|
|
return {
|
|
|
|
'destination': MissionParam(Waypoint, True)
|
|
|
|
}
|
2023-06-20 19:46:05 +00:00
|
|
|
|
2023-06-20 20:30:21 +00:00
|
|
|
def steps(self):
|
|
|
|
return {
|
2023-06-20 21:12:57 +00:00
|
|
|
'orbit': (self.step_orbit, 'travel'),
|
2023-06-20 20:38:29 +00:00
|
|
|
'travel': (self.step_travel_dest, {
|
2023-06-20 20:30:21 +00:00
|
|
|
'more': 'travel',
|
|
|
|
'done': 'done'
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-06-18 17:15:51 +00:00
|
|
|
def create_mission(mtype, ship, store, api):
|
2023-06-17 18:18:14 +00:00
|
|
|
types = {
|
|
|
|
'survey': SurveyMission,
|
2023-06-20 19:46:05 +00:00
|
|
|
'mine': MiningMission,
|
2023-06-20 20:30:21 +00:00
|
|
|
'haul': HaulMission,
|
|
|
|
'travel': TravelMission
|
2023-06-17 18:18:14 +00:00
|
|
|
}
|
|
|
|
if mtype not in types:
|
|
|
|
logging.warning(f'invalid mission type {mtype}')
|
|
|
|
return
|
2023-06-18 17:15:51 +00:00
|
|
|
m = types[mtype](ship, store, api)
|
2023-06-17 18:18:14 +00:00
|
|
|
return m
|