Update central_command.py, commander.py and eleven other files

This commit is contained in:
Richard Bronkhorst 2023-06-18 19:15:51 +02:00
parent a229b9e300
commit 8b29ca8f58
13 changed files with 192 additions and 81 deletions

View File

@ -1,13 +1,15 @@
from nullptr.store import store from nullptr.store import Store
from nullptr.models.ship import Ship from nullptr.models.ship import Ship
from nullptr.mission import * from nullptr.mission import *
from random import choice from random import choice
from time import sleep from time import sleep
from threading import Thread
class CentralCommand: class CentralCommand:
def __init__(self, api): def __init__(self, store, api):
self.missions = {} self.missions = {}
self.stopping = False self.stopping = False
self.store = store
self.api = api self.api = api
self.update_missions() self.update_missions()
@ -20,16 +22,36 @@ class CentralCommand:
def tick(self): def tick(self):
missions = self.get_ready_missions() missions = self.get_ready_missions()
if len(missions) == 0: return if len(missions) == 0: return False
ship = choice(missions) ship = choice(missions)
mission = self.missions[ship] mission = self.missions[ship]
mission.step() mission.step()
return True
def wait_for_stop(self):
try:
input()
except EOFError:
pass
self.stopping = True
print('stopping...')
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 run(self): def run(self):
self.update_missions() self.update_missions()
while not self.stopping: while not self.stopping:
self.tick() did_step = True
self.api.save() request_counter = self.api.requests_sent
while request_counter == self.api.requests_sent and did_step:
did_step = self.tick()
self.store.flush()
sleep(0.5) sleep(0.5)
self.stopping = False self.stopping = False
@ -47,15 +69,14 @@ class CentralCommand:
return return
param = params[nm] param = params[nm]
try: try:
parsed_val = param.parse(val) parsed_val = param.parse(val, self.store)
except ValueError as e: except ValueError as e:
print(e) raise MissionError(e)
return return
print('ok') ship.set_mission_state(nm, parsed_val)
ship.mission_state[nm] = parsed_val
def update_missions(self): def update_missions(self):
for s in store.all(Ship): for s in self.store.all(Ship):
if s.mission is None: if s.mission is None:
if s in self.missions: if s in self.missions:
self.stop_mission(s) self.stop_mission(s)
@ -64,7 +85,7 @@ class CentralCommand:
def start_mission(self, s): def start_mission(self, s):
mtype = s.mission mtype = s.mission
m = create_mission(mtype, s, self.api) m = create_mission(mtype, s, self.store, self.api)
self.missions[s] = m self.missions[s] = m
return m return m

View File

@ -12,7 +12,7 @@ from .util import *
from time import sleep, time from time import sleep, time
from threading import Thread from threading import Thread
from nullptr.atlas_builder import AtlasBuilder from nullptr.atlas_builder import AtlasBuilder
from nullptr.central_command import CentralCommand
class CommandError(Exception): class CommandError(Exception):
pass pass
@ -24,6 +24,7 @@ class Commander(CommandLine):
self.agent = self.select_agent() self.agent = self.select_agent()
self.api = Api(self.store, self.agent) self.api = Api(self.store, self.agent)
self.atlas_builder = AtlasBuilder(self.store, self.api) self.atlas_builder = AtlasBuilder(self.store, self.api)
self.centcom = CentralCommand(self.store, self.api)
self.analyzer = Analyzer(self.store) self.analyzer = Analyzer(self.store)
self.ship = None self.ship = None
@ -45,7 +46,7 @@ class Commander(CommandLine):
def ask_obj(self, typ, prompt): def ask_obj(self, typ, prompt):
obj = None obj = None
while obj is None: while obj is None:
symbol = input(prompt) symbol = input(prompt).strip()
obj = self.store.get(typ, symbol.upper()) obj = self.store.get(typ, symbol.upper())
if obj is None: if obj is None:
print('not found') print('not found')
@ -78,8 +79,59 @@ class Commander(CommandLine):
pprint(self.agent, 100) pprint(self.agent, 100)
def do_auto(self):
self.centcom.run_interactive()
def set_mission(self, arg=''):
if arg == 'none':
arg = None
self.ship.mission = arg
self.ship.mission_status = 'init'
self.centcom.start_mission(self.ship)
def print_mission(self):
print(f'mission: {self.ship.mission} ({self.ship.mission_status})')
pprint(self.ship.mission_state)
def do_mission(self, arg=''):
if not self.has_ship(): return
if arg:
self.set_mission(arg)
self.print_mission()
def do_mset(self, args):
if not self.has_ship(): return
nm, val = args.split(' ')
self.centcom.set_mission_param(self.ship, nm, val)
def active_contract(self):
for c in self.store.all('Contract'):
if c.accepted and not c.fulfilled: return c
raise CommandError('no active contract')
def do_cmine(self):
if not self.has_ship(): return
site = self.ship.location_str
contract = self.active_contract()
delivery = contract.unfinished_delivery()
if delivery is None:
raise CommandError('no delivery')
resource = delivery['trade_symbol']
destination = delivery['destination']
self.set_mission('mine')
self.centcom.set_mission_param(self.ship, 'site', site)
self.centcom.set_mission_param(self.ship, 'resource', resource)
self.centcom.set_mission_param(self.ship, 'destination', destination)
self.centcom.set_mission_param(self.ship, 'contract', contract.symbol)
self.print_mission()
def do_register(self, faction): def do_register(self, faction):
self.api.register(faction.upper()) self.api.register(faction.upper())
site = self.ship.location_str
contract = self.active_contract()
self.do_mission('mine')
self.centcom.set_mission_param(self.ship, 'site', site)
self.centcom.set_mission_param(self.ship, 'contract', contract)
def do_universe(self, page=1): def do_universe(self, page=1):
self.atlas_builder.run(page) self.atlas_builder.run(page)

View File

@ -1,11 +1,15 @@
from nullptr.store import store from nullptr.store import Store
from nullptr.models.base import Base
from nullptr.models.waypoint import Waypoint from nullptr.models.waypoint import Waypoint
from nullptr.models.contract import Contract from nullptr.models.contract import Contract
from nullptr.models.survey import Survey from nullptr.models.survey import Survey
from nullptr.models.ship import Ship from nullptr.models.ship import Ship
from time import time from time import time
import logging import logging
from util import * from nullptr.util import *
class MissionError(Exception):
pass
class MissionParam: class MissionParam:
def __init__(self, cls, required=True, default=None): def __init__(self, cls, required=True, default=None):
@ -13,35 +17,40 @@ class MissionParam:
self.required = required self.required = required
self.default = default self.default = default
def parse(self, val): def parse(self, val, store):
if self.cls == str: if self.cls == str:
return str(val) return str(val)
elif self.cls == int: elif self.cls == int:
return int(val) return int(val)
elif issubclass(self.cls, StoreObject): elif issubclass(self.cls, Base):
data = store.get(self.cls, val) data = store.get(self.cls, val)
if data is None: if data is None:
raise ValueError('object not found') raise ValueError('object not found')
return data return data.symbol
else: else:
raise ValueError('unknown param typr') raise ValueError('unknown param typr')
class Mission: class Mission:
ship: Ship
next_step: int = 0
@classmethod @classmethod
def params(cls): def params(cls):
return { return {
} }
def __init__(self, ship, api): def __init__(self, ship, store, api):
self.ship = ship self.ship = ship
self.store = store
self.api = api self.api = api
self.next_step = 0
def sts(self, nm, v): def sts(self, nm, v):
self.ship.mission_state[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): def st(self, nm):
if not nm in self.ship.mission_state: if not nm in self.ship.mission_state:
@ -98,7 +107,7 @@ class Mission:
try: try:
result = handler() result = handler()
except Exception as e: except Exception as e:
logging.error(e) logging.error(e, exc_info=True)
self.status('error') self.status('error')
return return
if type(next_step) == str: if type(next_step) == str:
@ -157,10 +166,10 @@ class MiningMission(Mission):
def get_survey(self): def get_survey(self):
resource = self.st('resource') resource = self.st('resource')
site = self.st('site') site = self.rst(Waypoint,'site')
# todo optimize # todo optimize
for s in store.all(Survey): for s in self.store.all(Survey):
if resource in s.deposits and site == s.waypoint: if resource in s.deposits and site.symbol == s.waypoint():
return s return s
return None return None
@ -192,8 +201,8 @@ class MiningMission(Mission):
return 'more' return 'more'
def step_go_dest(self): def step_go_dest(self):
destination = self.st('destination') destination = self.rst(Waypoint, 'destination')
if self.ship.location == destination: if self.ship.location() == destination:
return return
self.api.navigate(self.ship, destination) self.api.navigate(self.ship, destination)
self.next_step = self.ship.arrival self.next_step = self.ship.arrival
@ -202,7 +211,7 @@ class MiningMission(Mission):
self.api.dock(self.ship) self.api.dock(self.ship)
def step_unload(self): def step_unload(self):
contract = self.st('contract') contract = self.rst(Contract, 'contract')
delivery = self.st('delivery') delivery = self.st('delivery')
if delivery == 'sell': if delivery == 'sell':
return self.step_sell(False) return self.step_sell(False)
@ -219,7 +228,7 @@ class MiningMission(Mission):
self.api.refuel(self.ship) self.api.refuel(self.ship)
def step_dispose(self): def step_dispose(self):
contract = self.st('contract') contract = self.rst(Contract, 'contract')
typs = self.ship.nondeliverable_cargo(contract) typs = self.ship.nondeliverable_cargo(contract)
if len(typs) > 0: if len(typs) > 0:
self.api.jettison(self.ship, typs[0]) self.api.jettison(self.ship, typs[0])
@ -235,8 +244,8 @@ class MiningMission(Mission):
self.api.orbit(self.ship) self.api.orbit(self.ship)
def step_go_site(self): def step_go_site(self):
site = self.st('site') site = self.rst(Waypoint,'site')
if self.ship.location == site: if self.ship.location() == site:
return return
self.api.navigate(self.ship, site) self.api.navigate(self.ship, site)
self.next_step = self.ship.arrival self.next_step = self.ship.arrival
@ -256,7 +265,7 @@ class SurveyMission(Mission):
#pprint(result, 2) #pprint(result, 2)
self.next_step = self.ship.cooldown self.next_step = self.ship.cooldown
def create_mission(mtype, ship, api): def create_mission(mtype, ship, store, api):
types = { types = {
'survey': SurveyMission, 'survey': SurveyMission,
'mine': MiningMission 'mine': MiningMission
@ -264,5 +273,5 @@ def create_mission(mtype, ship, api):
if mtype not in types: if mtype not in types:
logging.warning(f'invalid mission type {mtype}') logging.warning(f'invalid mission type {mtype}')
return return
m = types[mtype](ship, api) m = types[mtype](ship, store, api)
return m return m

View File

@ -1,8 +1,9 @@
from .base import Base from .base import Base
class Agent(Base): class Agent(Base):
token: str = None def define(self):
credits: int = 0 self.token: str = None
self.credits: int = 0
def update(self, d): def update(self, d):
self.seta('credits', d) self.seta('credits', d)

View File

@ -9,6 +9,10 @@ class Base:
def __init__(self, symbol, store): def __init__(self, symbol, store):
self.store = store self.store = store
self.symbol = symbol self.symbol = symbol
self.define()
def define(self):
pass
def __hash__(self): def __hash__(self):
return hash((str(type(self)), self.symbol)) return hash((str(type(self)), self.symbol))

View File

@ -5,13 +5,14 @@ from .base import Base
class Contract(Base): class Contract(Base):
identifier = 'id' identifier = 'id'
type: str def define(self):
deliveries: list self.type: str = ''
accepted: bool self.deliveries: list = []
fulfilled: bool self.accepted: bool = False
expires: int self.fulfilled: bool = False
expires_str: str self.expires: int = 0
pay: int self.expires_str: str = ''
self.pay: int = 0
@classmethod @classmethod
def ext(cls): def ext(cls):
@ -29,6 +30,18 @@ class Contract(Base):
'expiration': self.expires_str, 'expiration': self.expires_str,
} }
def is_done(self):
for d in self.deliveries:
if d['units_fulfilled'] > d['units_requires']:
return False
return False
def unfinished_delivery(self):
for d in self.deliveries:
if d['units_required'] > d['units_fulfilled']:
return d
return None
def update(self, d): def update(self, d):
self.seta('expires',d, 'terms.deadline',parse_timestamp) self.seta('expires',d, 'terms.deadline',parse_timestamp)
self.seta('expires_str', d,'terms.deadline') self.seta('expires_str', d,'terms.deadline')
@ -46,6 +59,7 @@ class Contract(Base):
delivery['destination'] = must_get(e, 'destinationSymbol') delivery['destination'] = must_get(e, 'destinationSymbol')
self.deliveries.append(delivery) self.deliveries.append(delivery)
def f(self, detail=1): def f(self, detail=1):
hours = int(max(0, self.expires - time()) / 3600) hours = int(max(0, self.expires - time()) / 3600)
accepted = 'A' if self.accepted else '-' accepted = 'A' if self.accepted else '-'

View File

@ -2,9 +2,10 @@ from .system_member import SystemMember
from dataclasses import field from dataclasses import field
class Jumpgate(SystemMember): class Jumpgate(SystemMember):
range: int def define(self):
faction: str self.range: int = 0
systems: list = [] self.faction: str = ''
self.systems: list = []
def update(self, d): def update(self, d):
self.setlst('systems', d, 'connectedSystems', 'symbol') self.setlst('systems', d, 'connectedSystems', 'symbol')

View File

@ -5,11 +5,12 @@ from nullptr.util import *
from dataclasses import field from dataclasses import field
class Marketplace(SystemMember): class Marketplace(SystemMember):
imports:list = [] def define(self):
exports:list = [] self.imports:list = []
exchange:list = [] self.exports:list = []
prices:dict = {} self.exchange:list = []
last_prices:int = 0 self.prices:dict = {}
self.last_prices:int = 0
def update(self, d): def update(self, d):
self.setlst('imports', d, 'imports', 'symbol') self.setlst('imports', d, 'imports', 'symbol')

View File

@ -4,18 +4,19 @@ from nullptr.util import *
from dataclasses import dataclass, field from dataclasses import dataclass, field
class Ship(Base): class Ship(Base):
cargo:dict = {} def define(self):
mission_state:dict = {} self.cargo:dict = {}
status:str = '' self.mission_state:dict = {}
cargo_capacity:int = 0 self.status:str = ''
cargo_units:int = 0 self.cargo_capacity:int = 0
location_str = '' self.cargo_units:int = 0
cooldown:int = 0 self.location_str = ''
arrival:int = 0 self.cooldown:int = 0
fuel_current:int = 0 self.arrival:int = 0
fuel_capacity:int = 0 self.fuel_current:int = 0
mission:str = None self.fuel_capacity:int = 0
mission_status:str = 'init' self.mission:str = None
self.mission_status:str = 'init'
@classmethod @classmethod
def ext(self): def ext(self):
@ -51,6 +52,10 @@ class Ship(Base):
def is_travelling(self): def is_travelling(self):
return self.status == 'IN_TRANSIT' return self.status == 'IN_TRANSIT'
def set_mission_state(self, nm, val):
self.mission_state[nm] = val
self.store.dirty(self)
def get_cargo(self, typ): def get_cargo(self, typ):
if typ not in self.cargo: if typ not in self.cargo:
return 0 return 0

View File

@ -6,12 +6,13 @@ size_names = ['SMALL','MODERATE','LARGE']
class Survey(SystemMember): class Survey(SystemMember):
identifier = 'signature' identifier = 'signature'
type: str = '' def define(self):
deposits: list[str] = [] self.type: str = ''
size: int = 0 self.deposits: list[str] = []
expires: int = 0 self.size: int = 0
expires_str: str = '' self.expires: int = 0
exhausted: bool = False self.expires_str: str = ''
self.exhausted: bool = False
@classmethod @classmethod
def ext(cls): def ext(cls):

View File

@ -3,9 +3,10 @@ from .base import Base
from math import sqrt from math import sqrt
class System(Base): class System(Base):
x:int = 0 def define(self):
y:int = 0 self.x:int = 0
type:str = 'unknown' self.y:int = 0
self.type:str = 'unknown'
def update(self, d): def update(self, d):
self.seta('x', d) self.seta('x', d)

View File

@ -3,11 +3,12 @@ from nullptr.util import *
from dataclasses import field from dataclasses import field
class Waypoint(SystemMember): class Waypoint(SystemMember):
x:int = 0 def define(self):
y:int = 0 self.x:int = 0
type:str = 'unknown' self.y:int = 0
traits:list = [] self.type:str = 'unknown'
faction:str = '' self.traits:list = []
self.faction:str = ''
def update(self, d): def update(self, d):
self.seta('x', d) self.seta('x', d)

View File

@ -129,7 +129,7 @@ class Store:
yield m yield m
def cleanup(self): def cleanup(self):
if time() > self.last_cleanup + self.cleanup_interval: if time() < self.last_cleanup + self.cleanup_interval:
return return
start_time = time() start_time = time()
expired = list() expired = list()