racing
This commit is contained in:
parent
b5850bcb5f
commit
2e45acc513
@ -231,3 +231,24 @@ class Api:
|
|||||||
|
|
||||||
def shipyard(self, wp):
|
def shipyard(self, wp):
|
||||||
return self.request('get', f'systems/{wp.system()}/waypoints/{wp}/shipyard')
|
return self.request('get', f'systems/{wp.system()}/waypoints/{wp}/shipyard')
|
||||||
|
|
||||||
|
def extract(self, ship, survey=None):
|
||||||
|
data = {}
|
||||||
|
if survey is not None:
|
||||||
|
data['survey'] = survey.api_dict()
|
||||||
|
try:
|
||||||
|
data = self.request('post', f'my/ships/{ship}/extract', data=data)
|
||||||
|
except ApiError as e:
|
||||||
|
if e.code in [ 4221, 4224]:
|
||||||
|
survey.exhausted = True
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
ship.update(data)
|
||||||
|
return ship
|
||||||
|
|
||||||
|
def survey(self, ship):
|
||||||
|
data = self.request('post', f'my/ships/{ship}/survey')
|
||||||
|
ship.update(data)
|
||||||
|
result = self.store.update_list('Survey', mg(data, 'surveys'))
|
||||||
|
return result
|
||||||
|
|
73
nullptr/central_command.py
Normal file
73
nullptr/central_command.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from nullptr.store import store
|
||||||
|
from nullptr.models.ship import Ship
|
||||||
|
from nullptr.mission import *
|
||||||
|
from random import choice
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
class CentralCommand:
|
||||||
|
def __init__(self, api):
|
||||||
|
self.missions = {}
|
||||||
|
self.stopping = False
|
||||||
|
self.api = api
|
||||||
|
self.update_missions()
|
||||||
|
|
||||||
|
def get_ready_missions(self):
|
||||||
|
result = []
|
||||||
|
for ship, mission in self.missions.items():
|
||||||
|
if mission.is_ready():
|
||||||
|
result.append(ship)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def tick(self):
|
||||||
|
missions = self.get_ready_missions()
|
||||||
|
if len(missions) == 0: return
|
||||||
|
ship = choice(missions)
|
||||||
|
mission = self.missions[ship]
|
||||||
|
mission.step()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.update_missions()
|
||||||
|
while not self.stopping:
|
||||||
|
self.tick()
|
||||||
|
self.api.save()
|
||||||
|
sleep(0.5)
|
||||||
|
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)
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
print('ok')
|
||||||
|
ship.mission_state[nm] = parsed_val
|
||||||
|
|
||||||
|
def update_missions(self):
|
||||||
|
for s in store.all(Ship):
|
||||||
|
if s.mission is None:
|
||||||
|
if s in self.missions:
|
||||||
|
self.stop_mission(s)
|
||||||
|
elif s not in self.missions:
|
||||||
|
self.start_mission(s)
|
||||||
|
|
||||||
|
def start_mission(self, s):
|
||||||
|
mtype = s.mission
|
||||||
|
m = create_mission(mtype, s, self.api)
|
||||||
|
self.missions[s] = m
|
||||||
|
return m
|
||||||
|
|
||||||
|
def stop_mission(self, s):
|
||||||
|
if s in self.missions:
|
||||||
|
del self.missions[s]
|
@ -104,6 +104,8 @@ class Commander(CommandLine):
|
|||||||
traits.append('SHIPYARD')
|
traits.append('SHIPYARD')
|
||||||
if w.type == 'JUMP_GATE':
|
if w.type == 'JUMP_GATE':
|
||||||
traits.append('JUMP')
|
traits.append('JUMP')
|
||||||
|
if w.type == 'ASTEROID_FIELD':
|
||||||
|
traits.append('ASTROIDS')
|
||||||
print(w.symbol.split('-')[2], ', '.join(traits))
|
print(w.symbol.split('-')[2], ', '.join(traits))
|
||||||
|
|
||||||
def do_marketplace(self, waypoint_str):
|
def do_marketplace(self, waypoint_str):
|
||||||
@ -249,3 +251,8 @@ class Commander(CommandLine):
|
|||||||
ship_type = 'SHIP_' + ship_type
|
ship_type = 'SHIP_' + ship_type
|
||||||
s = self.api.purchase(ship_type, location)
|
s = self.api.purchase(ship_type, location)
|
||||||
pprint(s)
|
pprint(s)
|
||||||
|
|
||||||
|
def do_survey(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
r = self.api.survey(self.ship)
|
||||||
|
pprint(r)
|
268
nullptr/mission.py
Normal file
268
nullptr/mission.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
from nullptr.store import store
|
||||||
|
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 util import *
|
||||||
|
|
||||||
|
class MissionParam:
|
||||||
|
def __init__(self, cls, required=True, default=None):
|
||||||
|
self.cls = cls
|
||||||
|
self.required = required
|
||||||
|
self.default = default
|
||||||
|
|
||||||
|
def parse(self, val):
|
||||||
|
if self.cls == str:
|
||||||
|
return str(val)
|
||||||
|
elif self.cls == int:
|
||||||
|
return int(val)
|
||||||
|
elif issubclass(self.cls, StoreObject):
|
||||||
|
data = store.get(self.cls, val)
|
||||||
|
if data is None:
|
||||||
|
raise ValueError('object not found')
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
raise ValueError('unknown param typr')
|
||||||
|
|
||||||
|
class Mission:
|
||||||
|
ship: Ship
|
||||||
|
next_step: int = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def params(cls):
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, ship, api):
|
||||||
|
self.ship = ship
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
def sts(self, nm, v):
|
||||||
|
self.ship.mission_state[nm] = v
|
||||||
|
|
||||||
|
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)
|
||||||
|
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.st('site')
|
||||||
|
# todo optimize
|
||||||
|
for s in store.all(Survey):
|
||||||
|
if resource in s.deposits and site == 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.api.market(self.ship.location)
|
||||||
|
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.st('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.st('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.st('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.st('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, api):
|
||||||
|
types = {
|
||||||
|
'survey': SurveyMission,
|
||||||
|
'mine': MiningMission
|
||||||
|
}
|
||||||
|
if mtype not in types:
|
||||||
|
logging.warning(f'invalid mission type {mtype}')
|
||||||
|
return
|
||||||
|
m = types[mtype](ship, api)
|
||||||
|
return m
|
50
nullptr/models/survey.py
Normal file
50
nullptr/models/survey.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from time import time
|
||||||
|
from nullptr.util import *
|
||||||
|
from .system_member import SystemMember
|
||||||
|
|
||||||
|
size_names = ['SMALL','MODERATE','LARGE']
|
||||||
|
|
||||||
|
class Survey(SystemMember):
|
||||||
|
identifier = 'signature'
|
||||||
|
type: str = ''
|
||||||
|
deposits: list[str] = []
|
||||||
|
size: int = 0
|
||||||
|
expires: int = 0
|
||||||
|
expires_str: str = ''
|
||||||
|
exhausted: bool = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ext(cls):
|
||||||
|
return 'svy'
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
sector, system, waypoint, signature = self.symbol.split('-')
|
||||||
|
return f'atlas/{sector}/{system[0:1]}/{system}/{waypoint}-{signature}.{self.ext()}'
|
||||||
|
|
||||||
|
|
||||||
|
def is_expired(self):
|
||||||
|
return time() > self.expires or self.exhausted
|
||||||
|
|
||||||
|
def api_dict(self):
|
||||||
|
return {
|
||||||
|
'signature': self.symbol,
|
||||||
|
'symbol': str(self.waypoint),
|
||||||
|
'deposits': [{'symbol': d} for d in self.deposits],
|
||||||
|
'expiration': self.expires_str,
|
||||||
|
'size': size_names[self.size]
|
||||||
|
}
|
||||||
|
|
||||||
|
def update(self, d):
|
||||||
|
sz = must_get(d, 'size')
|
||||||
|
self.size = size_names.index(sz)
|
||||||
|
self.deposits = [d['symbol'] for d in must_get(d, 'deposits')]
|
||||||
|
self.seta('expires',d, 'expiration',parse_timestamp)
|
||||||
|
self.seta('expires_str',d, 'expiration')
|
||||||
|
|
||||||
|
def f(self, detail=1):
|
||||||
|
result = self.symbol
|
||||||
|
if detail > 1:
|
||||||
|
result += ' ' + ','.join(self.deposits)
|
||||||
|
minutes = max(self.expires - time(), 0) //60
|
||||||
|
result += ' ' + str(int(minutes)) + 'm'
|
||||||
|
return result
|
@ -8,6 +8,7 @@ from nullptr.models.system_member import SystemMember
|
|||||||
from nullptr.models.jumpgate import Jumpgate
|
from nullptr.models.jumpgate import Jumpgate
|
||||||
from nullptr.models.ship import Ship
|
from nullptr.models.ship import Ship
|
||||||
from nullptr.models.contract import Contract
|
from nullptr.models.contract import Contract
|
||||||
|
from nullptr.models.survey import Survey
|
||||||
from os.path import isfile, dirname, isdir
|
from os.path import isfile, dirname, isdir
|
||||||
import os
|
import os
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
|
Loading…
Reference in New Issue
Block a user