From 8b29ca8f58062ac09d199035fbc1805cb71840a0 Mon Sep 17 00:00:00 2001 From: Richard Bronkhorst Date: Sun, 18 Jun 2023 19:15:51 +0200 Subject: [PATCH] Update central_command.py, commander.py and eleven other files --- nullptr/central_command.py | 45 +++++++++++++++++++-------- nullptr/commander.py | 58 +++++++++++++++++++++++++++++++++-- nullptr/mission.py | 53 +++++++++++++++++++------------- nullptr/models/agent.py | 5 +-- nullptr/models/base.py | 4 +++ nullptr/models/contract.py | 28 ++++++++++++----- nullptr/models/jumpgate.py | 7 +++-- nullptr/models/marketplace.py | 11 ++++--- nullptr/models/ship.py | 29 ++++++++++-------- nullptr/models/survey.py | 13 ++++---- nullptr/models/system.py | 7 +++-- nullptr/models/waypoint.py | 11 ++++--- nullptr/store.py | 2 +- 13 files changed, 192 insertions(+), 81 deletions(-) diff --git a/nullptr/central_command.py b/nullptr/central_command.py index 9e7ae99..bddbba7 100644 --- a/nullptr/central_command.py +++ b/nullptr/central_command.py @@ -1,13 +1,15 @@ -from nullptr.store import store +from nullptr.store import Store from nullptr.models.ship import Ship from nullptr.mission import * from random import choice from time import sleep +from threading import Thread class CentralCommand: - def __init__(self, api): + def __init__(self, store, api): self.missions = {} self.stopping = False + self.store = store self.api = api self.update_missions() @@ -20,16 +22,36 @@ class CentralCommand: def tick(self): missions = self.get_ready_missions() - if len(missions) == 0: return + if len(missions) == 0: return False ship = choice(missions) mission = self.missions[ship] 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): self.update_missions() while not self.stopping: - self.tick() - self.api.save() + did_step = True + 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) self.stopping = False @@ -47,15 +69,14 @@ class CentralCommand: return param = params[nm] try: - parsed_val = param.parse(val) + parsed_val = param.parse(val, self.store) except ValueError as e: - print(e) + raise MissionError(e) return - print('ok') - ship.mission_state[nm] = parsed_val + ship.set_mission_state(nm, parsed_val) def update_missions(self): - for s in store.all(Ship): + for s in self.store.all(Ship): if s.mission is None: if s in self.missions: self.stop_mission(s) @@ -64,7 +85,7 @@ class CentralCommand: def start_mission(self, s): mtype = s.mission - m = create_mission(mtype, s, self.api) + m = create_mission(mtype, s, self.store, self.api) self.missions[s] = m return m diff --git a/nullptr/commander.py b/nullptr/commander.py index f9141cb..9263d2b 100644 --- a/nullptr/commander.py +++ b/nullptr/commander.py @@ -12,7 +12,7 @@ from .util import * from time import sleep, time from threading import Thread from nullptr.atlas_builder import AtlasBuilder - +from nullptr.central_command import CentralCommand class CommandError(Exception): pass @@ -24,6 +24,7 @@ class Commander(CommandLine): self.agent = self.select_agent() self.api = Api(self.store, self.agent) self.atlas_builder = AtlasBuilder(self.store, self.api) + self.centcom = CentralCommand(self.store, self.api) self.analyzer = Analyzer(self.store) self.ship = None @@ -45,7 +46,7 @@ class Commander(CommandLine): def ask_obj(self, typ, prompt): obj = None while obj is None: - symbol = input(prompt) + symbol = input(prompt).strip() obj = self.store.get(typ, symbol.upper()) if obj is None: print('not found') @@ -77,10 +78,61 @@ class Commander(CommandLine): self.api.info() 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): 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): self.atlas_builder.run(page) diff --git a/nullptr/mission.py b/nullptr/mission.py index 493cfff..23b3f32 100644 --- a/nullptr/mission.py +++ b/nullptr/mission.py @@ -1,48 +1,57 @@ -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.contract import Contract from nullptr.models.survey import Survey from nullptr.models.ship import Ship from time import time import logging -from util import * +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): + def parse(self, val, store): if self.cls == str: return str(val) elif self.cls == int: return int(val) - elif issubclass(self.cls, StoreObject): + elif issubclass(self.cls, Base): data = store.get(self.cls, val) if data is None: raise ValueError('object not found') - return data + return data.symbol else: raise ValueError('unknown param typr') class Mission: - ship: Ship - next_step: int = 0 - @classmethod def params(cls): return { } - def __init__(self, ship, api): + def __init__(self, ship, store, api): self.ship = ship + self.store = store self.api = api + self.next_step = 0 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): if not nm in self.ship.mission_state: return None @@ -98,7 +107,7 @@ class Mission: try: result = handler() except Exception as e: - logging.error(e) + logging.error(e, exc_info=True) self.status('error') return if type(next_step) == str: @@ -157,10 +166,10 @@ class MiningMission(Mission): def get_survey(self): resource = self.st('resource') - site = self.st('site') + site = self.rst(Waypoint,'site') # todo optimize - for s in store.all(Survey): - if resource in s.deposits and site == s.waypoint: + for s in self.store.all(Survey): + if resource in s.deposits and site.symbol == s.waypoint(): return s return None @@ -192,8 +201,8 @@ class MiningMission(Mission): return 'more' def step_go_dest(self): - destination = self.st('destination') - if self.ship.location == destination: + destination = self.rst(Waypoint, 'destination') + if self.ship.location() == destination: return self.api.navigate(self.ship, destination) self.next_step = self.ship.arrival @@ -202,7 +211,7 @@ class MiningMission(Mission): self.api.dock(self.ship) def step_unload(self): - contract = self.st('contract') + contract = self.rst(Contract, 'contract') delivery = self.st('delivery') if delivery == 'sell': return self.step_sell(False) @@ -219,7 +228,7 @@ class MiningMission(Mission): self.api.refuel(self.ship) def step_dispose(self): - contract = self.st('contract') + contract = self.rst(Contract, 'contract') typs = self.ship.nondeliverable_cargo(contract) if len(typs) > 0: self.api.jettison(self.ship, typs[0]) @@ -235,8 +244,8 @@ class MiningMission(Mission): self.api.orbit(self.ship) def step_go_site(self): - site = self.st('site') - if self.ship.location == site: + site = self.rst(Waypoint,'site') + if self.ship.location() == site: return self.api.navigate(self.ship, site) self.next_step = self.ship.arrival @@ -256,7 +265,7 @@ class SurveyMission(Mission): #pprint(result, 2) self.next_step = self.ship.cooldown -def create_mission(mtype, ship, api): +def create_mission(mtype, ship, store, api): types = { 'survey': SurveyMission, 'mine': MiningMission @@ -264,5 +273,5 @@ def create_mission(mtype, ship, api): if mtype not in types: logging.warning(f'invalid mission type {mtype}') return - m = types[mtype](ship, api) + m = types[mtype](ship, store, api) return m diff --git a/nullptr/models/agent.py b/nullptr/models/agent.py index ba8beac..5ebb0b4 100644 --- a/nullptr/models/agent.py +++ b/nullptr/models/agent.py @@ -1,8 +1,9 @@ from .base import Base class Agent(Base): - token: str = None - credits: int = 0 + def define(self): + self.token: str = None + self.credits: int = 0 def update(self, d): self.seta('credits', d) diff --git a/nullptr/models/base.py b/nullptr/models/base.py index 92327d7..c7c8a26 100644 --- a/nullptr/models/base.py +++ b/nullptr/models/base.py @@ -9,7 +9,11 @@ class Base: def __init__(self, symbol, store): self.store = store self.symbol = symbol + self.define() + def define(self): + pass + def __hash__(self): return hash((str(type(self)), self.symbol)) diff --git a/nullptr/models/contract.py b/nullptr/models/contract.py index 1713340..0f5356d 100644 --- a/nullptr/models/contract.py +++ b/nullptr/models/contract.py @@ -5,13 +5,14 @@ from .base import Base class Contract(Base): identifier = 'id' - type: str - deliveries: list - accepted: bool - fulfilled: bool - expires: int - expires_str: str - pay: int + def define(self): + self.type: str = '' + self.deliveries: list = [] + self.accepted: bool = False + self.fulfilled: bool = False + self.expires: int = 0 + self.expires_str: str = '' + self.pay: int = 0 @classmethod def ext(cls): @@ -29,6 +30,18 @@ class Contract(Base): '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): self.seta('expires',d, 'terms.deadline',parse_timestamp) self.seta('expires_str', d,'terms.deadline') @@ -46,6 +59,7 @@ class Contract(Base): delivery['destination'] = must_get(e, 'destinationSymbol') self.deliveries.append(delivery) + def f(self, detail=1): hours = int(max(0, self.expires - time()) / 3600) accepted = 'A' if self.accepted else '-' diff --git a/nullptr/models/jumpgate.py b/nullptr/models/jumpgate.py index fcd073a..068b7f6 100644 --- a/nullptr/models/jumpgate.py +++ b/nullptr/models/jumpgate.py @@ -2,9 +2,10 @@ from .system_member import SystemMember from dataclasses import field class Jumpgate(SystemMember): - range: int - faction: str - systems: list = [] + def define(self): + self.range: int = 0 + self.faction: str = '' + self.systems: list = [] def update(self, d): self.setlst('systems', d, 'connectedSystems', 'symbol') diff --git a/nullptr/models/marketplace.py b/nullptr/models/marketplace.py index 3a68c96..fc59251 100644 --- a/nullptr/models/marketplace.py +++ b/nullptr/models/marketplace.py @@ -5,11 +5,12 @@ from nullptr.util import * from dataclasses import field class Marketplace(SystemMember): - imports:list = [] - exports:list = [] - exchange:list = [] - prices:dict = {} - last_prices:int = 0 + def define(self): + self.imports:list = [] + self.exports:list = [] + self.exchange:list = [] + self.prices:dict = {} + self.last_prices:int = 0 def update(self, d): self.setlst('imports', d, 'imports', 'symbol') diff --git a/nullptr/models/ship.py b/nullptr/models/ship.py index 70d648c..5172f04 100644 --- a/nullptr/models/ship.py +++ b/nullptr/models/ship.py @@ -4,18 +4,19 @@ from nullptr.util import * from dataclasses import dataclass, field class Ship(Base): - cargo:dict = {} - mission_state:dict = {} - status:str = '' - cargo_capacity:int = 0 - cargo_units:int = 0 - location_str = '' - cooldown:int = 0 - arrival:int = 0 - fuel_current:int = 0 - fuel_capacity:int = 0 - mission:str = None - mission_status:str = 'init' + def define(self): + self.cargo:dict = {} + self.mission_state:dict = {} + self.status:str = '' + self.cargo_capacity:int = 0 + self.cargo_units:int = 0 + self.location_str = '' + self.cooldown:int = 0 + self.arrival:int = 0 + self.fuel_current:int = 0 + self.fuel_capacity:int = 0 + self.mission:str = None + self.mission_status:str = 'init' @classmethod def ext(self): @@ -50,6 +51,10 @@ class Ship(Base): def is_travelling(self): 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): if typ not in self.cargo: diff --git a/nullptr/models/survey.py b/nullptr/models/survey.py index fed5101..c673017 100644 --- a/nullptr/models/survey.py +++ b/nullptr/models/survey.py @@ -6,12 +6,13 @@ 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 + def define(self): + self.type: str = '' + self.deposits: list[str] = [] + self.size: int = 0 + self.expires: int = 0 + self.expires_str: str = '' + self.exhausted: bool = False @classmethod def ext(cls): diff --git a/nullptr/models/system.py b/nullptr/models/system.py index 9d62d99..fb3370a 100644 --- a/nullptr/models/system.py +++ b/nullptr/models/system.py @@ -3,9 +3,10 @@ from .base import Base from math import sqrt class System(Base): - x:int = 0 - y:int = 0 - type:str = 'unknown' + def define(self): + self.x:int = 0 + self.y:int = 0 + self.type:str = 'unknown' def update(self, d): self.seta('x', d) diff --git a/nullptr/models/waypoint.py b/nullptr/models/waypoint.py index 68b85f4..4d5dbb0 100644 --- a/nullptr/models/waypoint.py +++ b/nullptr/models/waypoint.py @@ -3,11 +3,12 @@ from nullptr.util import * from dataclasses import field class Waypoint(SystemMember): - x:int = 0 - y:int = 0 - type:str = 'unknown' - traits:list = [] - faction:str = '' + def define(self): + self.x:int = 0 + self.y:int = 0 + self.type:str = 'unknown' + self.traits:list = [] + self.faction:str = '' def update(self, d): self.seta('x', d) diff --git a/nullptr/store.py b/nullptr/store.py index e1eb50e..4e411fe 100644 --- a/nullptr/store.py +++ b/nullptr/store.py @@ -129,7 +129,7 @@ class Store: yield m def cleanup(self): - if time() > self.last_cleanup + self.cleanup_interval: + if time() < self.last_cleanup + self.cleanup_interval: return start_time = time() expired = list()