Compare commits
19 Commits
market-inf
...
mining-aga
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a85c6c367 | ||
|
|
21f93f078d | ||
|
|
3a3e3b9da2 | ||
|
|
f849f871d2 | ||
|
|
9369c6982f | ||
|
|
8b29ca8f58 | ||
|
|
a229b9e300 | ||
|
|
46f9597e2e | ||
|
|
c2a1f787a2 | ||
|
|
9987481848 | ||
|
|
293b6d1c3e | ||
|
|
e31dce9861 | ||
|
|
2e45acc513 | ||
|
|
b5850bcb5f | ||
|
|
bb93950fa3 | ||
|
|
3f93d863a0 | ||
|
|
2c96cbb533 | ||
|
|
92a5a02180 | ||
|
|
ffd094df87 |
3
main.py
3
main.py
@@ -2,7 +2,7 @@ import argparse
|
|||||||
from nullptr.commander import Commander
|
from nullptr.commander import Commander
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
c = Commander(args.store_dir, args.agent)
|
c = Commander(args.store_dir)
|
||||||
c.run()
|
c.run()
|
||||||
|
|
||||||
# X1-AG74-41076A
|
# X1-AG74-41076A
|
||||||
@@ -11,6 +11,5 @@ def main(args):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('-s', '--store-dir', default='data')
|
parser.add_argument('-s', '--store-dir', default='data')
|
||||||
parser.add_argument('-a', '--agent', default=None)
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
main(args)
|
main(args)
|
||||||
|
|||||||
162
nullptr/api.py
162
nullptr/api.py
@@ -3,6 +3,7 @@ from nullptr.models.system import System
|
|||||||
from nullptr.models.waypoint import Waypoint
|
from nullptr.models.waypoint import Waypoint
|
||||||
from nullptr.models.marketplace import Marketplace
|
from nullptr.models.marketplace import Marketplace
|
||||||
from nullptr.models.jumpgate import Jumpgate
|
from nullptr.models.jumpgate import Jumpgate
|
||||||
|
from nullptr.models.ship import Ship
|
||||||
from .util import *
|
from .util import *
|
||||||
from time import sleep
|
from time import sleep
|
||||||
class ApiError(Exception):
|
class ApiError(Exception):
|
||||||
@@ -17,7 +18,9 @@ class Api:
|
|||||||
def __init__(self, store, agent):
|
def __init__(self, store, agent):
|
||||||
self.agent = agent
|
self.agent = agent
|
||||||
self.store = store
|
self.store = store
|
||||||
|
self.requests_sent = 0
|
||||||
self.meta = None
|
self.meta = None
|
||||||
|
self.last_result = None
|
||||||
self.root = 'https://api.spacetraders.io/v2/'
|
self.root = 'https://api.spacetraders.io/v2/'
|
||||||
|
|
||||||
def token(self):
|
def token(self):
|
||||||
@@ -37,6 +40,7 @@ class Api:
|
|||||||
headers = {}
|
headers = {}
|
||||||
if need_token:
|
if need_token:
|
||||||
headers['Authorization'] = 'Bearer ' + self.token()
|
headers['Authorization'] = 'Bearer ' + self.token()
|
||||||
|
self.requests_sent += 1
|
||||||
if method == 'get':
|
if method == 'get':
|
||||||
params['limit'] = 20
|
params['limit'] = 20
|
||||||
r = requests.request(method, self.root+path, json=data, headers=headers, params=params)
|
r = requests.request(method, self.root+path, json=data, headers=headers, params=params)
|
||||||
@@ -86,11 +90,165 @@ class Api:
|
|||||||
system = waypoint.system()
|
system = waypoint.system()
|
||||||
symbol = str(waypoint)
|
symbol = str(waypoint)
|
||||||
data = self.request('get', f'systems/{system}/waypoints/{waypoint}/market')
|
data = self.request('get', f'systems/{system}/waypoints/{waypoint}/market')
|
||||||
return self.store.update(Marketplace, symbol, data)
|
return self.store.update(Marketplace, data)
|
||||||
|
|
||||||
def jumps(self, waypoint):
|
def jumps(self, waypoint):
|
||||||
data = self.request('get', f'systems/{waypoint.system()}/waypoints/{waypoint}/jump-gate')
|
data = self.request('get', f'systems/{waypoint.system()}/waypoints/{waypoint}/jump-gate')
|
||||||
symbol = str(waypoint)
|
symbol = str(waypoint)
|
||||||
return self.store.update(Jumpgate, symbol, data)
|
return self.store.update(Jumpgate, data, symbol)
|
||||||
|
|
||||||
|
def list_ships(self):
|
||||||
|
data = self.request('get', 'my/ships')
|
||||||
|
return self.store.update_list(Ship, data)
|
||||||
|
|
||||||
|
def list_contracts(self):
|
||||||
|
data = self.request('get', 'my/contracts')
|
||||||
|
return self.store.update_list('Contract', data)
|
||||||
|
|
||||||
|
def negotiate(self, ship):
|
||||||
|
data = self.request('post', f'my/ships/{ship}/negotiate/contract')
|
||||||
|
if data is not None and 'contract' in data:
|
||||||
|
contract = self.store.update('Contract', data['contract'])
|
||||||
|
return contract
|
||||||
|
|
||||||
|
def deliver(self, ship, typ, contract):
|
||||||
|
units = ship.get_cargo(typ)
|
||||||
|
if units == 0:
|
||||||
|
print("Resource not in cargo")
|
||||||
|
return {}
|
||||||
|
data = {
|
||||||
|
'shipSymbol': str(ship),
|
||||||
|
'tradeSymbol': typ.upper(),
|
||||||
|
'units': units
|
||||||
|
}
|
||||||
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/deliver', data)
|
||||||
|
if 'cargo' in data:
|
||||||
|
ship.update(data)
|
||||||
|
if 'contract' in data:
|
||||||
|
contract.update(data['contract'])
|
||||||
|
return contract
|
||||||
|
|
||||||
|
def fulfill(self, contract):
|
||||||
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/fulfill')
|
||||||
|
if 'contract' in data:
|
||||||
|
contract.update(data['contract'])
|
||||||
|
if 'agent' in data:
|
||||||
|
self.agent.update(data['agent'])
|
||||||
|
return contract
|
||||||
|
|
||||||
|
def navigate(self, ship, wp):
|
||||||
|
data = {'waypointSymbol': str(wp)}
|
||||||
|
response = self.request('post', f'my/ships/{ship}/navigate', data)
|
||||||
|
ship.update(response)
|
||||||
|
|
||||||
|
def dock(self, ship):
|
||||||
|
data = self.request('post', f'my/ships/{ship}/dock')
|
||||||
|
ship.update(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def orbit(self, ship):
|
||||||
|
data = self.request('post', f'my/ships/{ship}/orbit')
|
||||||
|
ship.update(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def refuel(self, ship):
|
||||||
|
data = self.request('post', f'my/ships/{ship}/refuel')
|
||||||
|
if 'fuel' in data:
|
||||||
|
ship.update(data)
|
||||||
|
if 'agent' in data:
|
||||||
|
self.agent.update(data['agent'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def accept_contract(self, contract):
|
||||||
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/accept')
|
||||||
|
if 'contract' in data:
|
||||||
|
contract.update(data['contract'])
|
||||||
|
if 'agent' in data:
|
||||||
|
self.agent.update(data['agent'])
|
||||||
|
return contract
|
||||||
|
|
||||||
|
def sell(self, ship, typ):
|
||||||
|
units = ship.get_cargo(typ)
|
||||||
|
data = {
|
||||||
|
'symbol': typ,
|
||||||
|
'units': units
|
||||||
|
}
|
||||||
|
data = self.request('post', f'my/ships/{ship}/sell', data)
|
||||||
|
if 'cargo' in data:
|
||||||
|
ship.update(data)
|
||||||
|
if 'agent' in data:
|
||||||
|
self.agent.update(data['agent'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def buy(self, ship, typ, amt):
|
||||||
|
data = {
|
||||||
|
'symbol': typ,
|
||||||
|
'units': amt
|
||||||
|
}
|
||||||
|
data = self.request('post', f'my/ships/{ship}/purchase', data)
|
||||||
|
if 'cargo' in data:
|
||||||
|
ship.update(data)
|
||||||
|
if 'agent' in data:
|
||||||
|
self.agent.update(data['agent'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def jettison(self, ship, typ):
|
||||||
|
units = ship.get_cargo(typ)
|
||||||
|
if units == 0:
|
||||||
|
print('cargo not found')
|
||||||
|
return
|
||||||
|
data = {
|
||||||
|
'symbol': typ,
|
||||||
|
'units': units
|
||||||
|
}
|
||||||
|
data = self.request('post', f'my/ships/{ship.symbol}/jettison', data)
|
||||||
|
if 'cargo' in data:
|
||||||
|
ship.update(data)
|
||||||
|
if 'agent' in data:
|
||||||
|
self.agent.update(data['agent'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def purchase(self, typ, wp):
|
||||||
|
data = {
|
||||||
|
'shipType': typ,
|
||||||
|
'waypointSymbol': str(wp)
|
||||||
|
}
|
||||||
|
data = self.request('post', 'my/ships', data)
|
||||||
|
if 'agent' in data:
|
||||||
|
self.agent.update(data['agent'])
|
||||||
|
if 'ship' in data:
|
||||||
|
ship = self.store.update('Ship', data['ship'])
|
||||||
|
return ship
|
||||||
|
|
||||||
|
def jump(self, ship, system):
|
||||||
|
data = {
|
||||||
|
"systemSymbol": system.symbol
|
||||||
|
}
|
||||||
|
data = self.request('post', f'my/ships/{ship}/jump', data)
|
||||||
|
if 'nav' in data:
|
||||||
|
ship.update(data)
|
||||||
|
return ship
|
||||||
|
|
||||||
|
def shipyard(self, wp):
|
||||||
|
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 data
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
97
nullptr/central_command.py
Normal file
97
nullptr/central_command.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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, store, api):
|
||||||
|
self.missions = {}
|
||||||
|
self.stopping = False
|
||||||
|
self.store = store
|
||||||
|
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 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:
|
||||||
|
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
|
||||||
|
|
||||||
|
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, self.store)
|
||||||
|
except ValueError as e:
|
||||||
|
raise MissionError(e)
|
||||||
|
return
|
||||||
|
ship.set_mission_state(nm, parsed_val)
|
||||||
|
|
||||||
|
def update_missions(self):
|
||||||
|
for s in self.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)
|
||||||
|
if s in self.missions:
|
||||||
|
m = self.missions[s]
|
||||||
|
m.next_step = max(s.cooldown, s.arrival)
|
||||||
|
|
||||||
|
def start_mission(self, s):
|
||||||
|
mtype = s.mission
|
||||||
|
m = create_mission(mtype, s, self.store, self.api)
|
||||||
|
self.missions[s] = m
|
||||||
|
return m
|
||||||
|
|
||||||
|
def stop_mission(self, s):
|
||||||
|
if s in self.missions:
|
||||||
|
del self.missions[s]
|
||||||
@@ -41,7 +41,7 @@ class CommandLine:
|
|||||||
print(f'command not found; {c}')
|
print(f'command not found; {c}')
|
||||||
|
|
||||||
def handle_error(self, cmd, args, e):
|
def handle_error(self, cmd, args, e):
|
||||||
logging.error(e, exc_info=str(type(e))!='ApiErrorp')
|
logging.error(e, exc_info=type(e).__name__ not in ['ApiError','CommandError'])
|
||||||
|
|
||||||
def handle_empty(self):
|
def handle_empty(self):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -12,67 +12,175 @@ 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):
|
||||||
|
pass
|
||||||
|
|
||||||
class Commander(CommandLine):
|
class Commander(CommandLine):
|
||||||
def __init__(self, store_dir='data', agent=None):
|
def __init__(self, store_dir='data'):
|
||||||
self.store_dir = store_dir
|
self.store_dir = store_dir
|
||||||
self.store = Store(store_dir)
|
self.store = Store(store_dir)
|
||||||
self.store.load()
|
self.store.load()
|
||||||
self.agent = self.select_agent(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.stop_auto= False
|
self.stop_auto= False
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def prompt(self):
|
||||||
|
if self.ship:
|
||||||
|
return f'{self.ship.symbol}> '
|
||||||
|
else:
|
||||||
|
return '> '
|
||||||
|
|
||||||
|
def has_ship(self):
|
||||||
|
if self.ship is not None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print('set a ship')
|
||||||
|
|
||||||
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')
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def select_agent(self, agent_str):
|
def select_agent(self):
|
||||||
if agent_str is not None:
|
agents = self.store.all(Agent)
|
||||||
return self.store.get(Agent, agent_str)
|
agent = next(agents, None)
|
||||||
|
if agent is None:
|
||||||
|
symbol = input('agent name: ')
|
||||||
|
agent = self.store.get(Agent, symbol, create=True)
|
||||||
|
return agent
|
||||||
|
|
||||||
|
def resolve(self, typ, arg):
|
||||||
|
arg = arg.upper()
|
||||||
|
matches = [c for c in self.store.all(typ) if c.symbol.startswith(arg)]
|
||||||
|
if len(matches) == 1:
|
||||||
|
return matches[0]
|
||||||
|
elif len(matches) > 1:
|
||||||
|
raise CommandError('multiple matches')
|
||||||
else:
|
else:
|
||||||
agents = self.store.all(Agent)
|
raise CommandError('not found')
|
||||||
agent = next(agents, None)
|
|
||||||
if agent is None:
|
|
||||||
symbol = input('agent name: ')
|
|
||||||
agent = self.store.get(Agent, symbol)
|
|
||||||
return agent
|
|
||||||
|
|
||||||
def after_cmd(self):
|
def after_cmd(self):
|
||||||
self.store.flush()
|
self.store.flush()
|
||||||
|
|
||||||
def do_info(self):
|
def do_info(self, arg=''):
|
||||||
pprint(self.api.info(), 100)
|
if arg.startswith('r'):
|
||||||
|
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):
|
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)
|
||||||
|
|
||||||
def do_systems(self, page=1):
|
def do_systems(self, page=1):
|
||||||
r = self.api.list_systems(int(page))
|
r = self.api.list_systems(int(page))
|
||||||
pprint(self.api.last_meta)
|
pprint(self.api.last_meta)
|
||||||
|
|
||||||
|
def do_stats(self):
|
||||||
|
total = 0
|
||||||
|
for t in self.store.data:
|
||||||
|
num = len(self.store.data[t])
|
||||||
|
nam = t.__name__
|
||||||
|
total += num
|
||||||
|
print(f'{num:5d} {nam}')
|
||||||
|
print(f'{total:5d} total')
|
||||||
|
|
||||||
|
def do_waypoints(self, system_str=''):
|
||||||
|
if system_str == '':
|
||||||
|
if not self.has_ship(): return
|
||||||
|
system = self.ship.location().system()
|
||||||
|
else:
|
||||||
|
system = self.store.get(System, system_str)
|
||||||
|
r = self.store.all_members(system, 'Waypoint')
|
||||||
|
for w in r:
|
||||||
|
traits = []
|
||||||
|
if 'MARKETPLACE' in w.traits:
|
||||||
|
traits.append('MARKET')
|
||||||
|
if 'SHIPYARD' in w.traits:
|
||||||
|
traits.append('SHIPYARD')
|
||||||
|
if w.type == 'JUMP_GATE':
|
||||||
|
traits.append('JUMP')
|
||||||
|
if w.type == 'ASTEROID_FIELD':
|
||||||
|
traits.append('ASTROIDS')
|
||||||
|
print(w.symbol.split('-')[2], ', '.join(traits))
|
||||||
|
|
||||||
|
def do_wp(self, s=''):
|
||||||
|
self.do_waypoints(s)
|
||||||
|
|
||||||
def do_waypoints(self, system_str):
|
|
||||||
system = self.store.get(System, system_str.upper())
|
|
||||||
r = self.api.list_waypoints(system)
|
|
||||||
pprint(r)
|
|
||||||
|
|
||||||
def do_marketplace(self, waypoint_str):
|
def do_marketplace(self, waypoint_str):
|
||||||
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
||||||
r = self.api.marketplace(waypoint)
|
r = self.api.marketplace(waypoint)
|
||||||
|
|
||||||
def do_jumps(self, waypoint_str):
|
def do_jumps(self, waypoint_str=None):
|
||||||
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
if waypoint_str is None:
|
||||||
|
if not self.has_ship(): return
|
||||||
|
waypoint = self.ship.location()
|
||||||
|
else:
|
||||||
|
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
||||||
r = self.api.jumps(waypoint)
|
r = self.api.jumps(waypoint)
|
||||||
pprint(r)
|
pprint(r)
|
||||||
|
|
||||||
@@ -94,3 +202,148 @@ class Commander(CommandLine):
|
|||||||
# dest = self.store.get(System, 'X1-DA90')
|
# dest = self.store.get(System, 'X1-DA90')
|
||||||
path = self.analyzer.find_path(orig, dest)
|
path = self.analyzer.find_path(orig, dest)
|
||||||
pprint(path)
|
pprint(path)
|
||||||
|
|
||||||
|
def do_ships(self, arg=''):
|
||||||
|
if arg.startswith('r'):
|
||||||
|
r = self.api.list_ships()
|
||||||
|
else:
|
||||||
|
r = list(self.store.all('Ship'))
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_contracts(self, arg=''):
|
||||||
|
if arg.startswith('r'):
|
||||||
|
r = self.api.list_contracts()
|
||||||
|
else:
|
||||||
|
r = list(self.store.all('Contract'))
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_deliver(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']
|
||||||
|
self.api.deliver(self.ship, resource, contract)
|
||||||
|
pprint(contract)
|
||||||
|
|
||||||
|
def do_ship(self, arg=''):
|
||||||
|
if arg != '':
|
||||||
|
symbol = f'{self.agent.symbol}-{arg}'
|
||||||
|
ship = self.store.get('Ship', symbol)
|
||||||
|
if ship is None:
|
||||||
|
print('not found')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.ship = ship
|
||||||
|
pprint(self.ship)
|
||||||
|
|
||||||
|
def do_pp(self):
|
||||||
|
pprint(self.api.last_result)
|
||||||
|
|
||||||
|
def do_go(self, arg):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
system = self.ship.location().system()
|
||||||
|
symbol = f'{system}-{arg}'
|
||||||
|
dest = self.resolve('Waypoint', symbol)
|
||||||
|
self.api.navigate(self.ship, dest)
|
||||||
|
pprint(self.ship)
|
||||||
|
|
||||||
|
def do_dock(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
self.api.dock(self.ship)
|
||||||
|
pprint(self.ship)
|
||||||
|
|
||||||
|
def do_orbit(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
self.api.orbit(self.ship)
|
||||||
|
pprint(self.ship)
|
||||||
|
|
||||||
|
def do_negotiate(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
r = self.api.negotiate(self.ship)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_refuel(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
r = self.api.refuel(self.ship)
|
||||||
|
pprint(self.ship)
|
||||||
|
|
||||||
|
def do_accept(self, c):
|
||||||
|
contract = self.resolve('Contract', c)
|
||||||
|
r = self.api.accept_contract(contract)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_market(self, arg=''):
|
||||||
|
if arg == '':
|
||||||
|
if not self.has_ship(): return
|
||||||
|
waypoint = self.ship.location()
|
||||||
|
else:
|
||||||
|
waypoint = self.resolve('Waypoint', arg)
|
||||||
|
r = self.api.marketplace(waypoint)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_cargo(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
for c, units in self.ship.cargo.items():
|
||||||
|
print(f'{units:4d} {c}')
|
||||||
|
|
||||||
|
def do_buy(self, resource, amt=None):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
if amt is None:
|
||||||
|
amt = self.ship.cargo_capacity - self.ship.cargo_units
|
||||||
|
self.api.buy(self.ship, resource.upper(), amt)
|
||||||
|
self.do_cargo()
|
||||||
|
|
||||||
|
def do_sell(self, resource):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
self.api.sell(self.ship, resource.upper())
|
||||||
|
self.do_cargo()
|
||||||
|
|
||||||
|
def do_dump(self, resource):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
self.api.jettison(self.ship, resource.upper())
|
||||||
|
self.do_cargo()
|
||||||
|
|
||||||
|
def do_shipyard(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
location = self.ship.location()
|
||||||
|
pprint(self.api.shipyard(location))
|
||||||
|
|
||||||
|
def do_jump(self, system_str):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
if '-' not in system_str:
|
||||||
|
sector = self.ship.location_str.split('-')[0]
|
||||||
|
system_str = f'{sector}-{system_str}'
|
||||||
|
system = self.resolve('System', system_str)
|
||||||
|
self.api.jump(self.ship, system)
|
||||||
|
pprint(self.ship)
|
||||||
|
|
||||||
|
def do_purchase(self, ship_type):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
location = self.ship.location()
|
||||||
|
ship_type = ship_type.upper()
|
||||||
|
if not ship_type.startswith('SHIP'):
|
||||||
|
ship_type = 'SHIP_' + ship_type
|
||||||
|
s = self.api.purchase(ship_type, location)
|
||||||
|
pprint(s)
|
||||||
|
|
||||||
|
def do_survey(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
r = self.api.survey(self.ship)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_surveys(self):
|
||||||
|
pprint(list(self.store.all('Survey')))
|
||||||
|
|
||||||
|
def do_extract(self, survey_str=''):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
survey = None
|
||||||
|
if survey_str != '':
|
||||||
|
survey = self.resolve('Survey', survey_str)
|
||||||
|
result = self.api.extract(self.ship, survey)
|
||||||
|
|
||||||
|
symbol = mg(result,'extraction.yield.symbol')
|
||||||
|
units = mg(result,'extraction.yield.units')
|
||||||
|
print(units, symbol)
|
||||||
|
|||||||
277
nullptr/mission.py
Normal file
277
nullptr/mission.py
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from dataclasses import dataclass
|
|
||||||
from nullptr.util import sg
|
from nullptr.util import sg
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Base:
|
class Base:
|
||||||
|
identifier = 'symbol'
|
||||||
symbol: str
|
symbol: str
|
||||||
store: object
|
store: object
|
||||||
|
|
||||||
def __init__(self, symbol, store):
|
def __init__(self, symbol, store):
|
||||||
|
self.disable_dirty = False
|
||||||
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))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.symbol == other.symbol and type(self) == type(other)
|
return self.symbol == other.symbol and type(self) == type(other)
|
||||||
|
|
||||||
def seta(self, attr, d, name=None):
|
def seta(self, attr, d, name=None, interp=None):
|
||||||
if name is None:
|
if name is None:
|
||||||
name = attr
|
name = attr
|
||||||
val = sg(d, name)
|
val = sg(d, name)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
|
if interp is not None:
|
||||||
|
val = interp(val)
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
|
|
||||||
def setlst(self, attr, d, name, member):
|
def setlst(self, attr, d, name, member):
|
||||||
@@ -31,12 +37,20 @@ class Base:
|
|||||||
setattr(self, attr, lst)
|
setattr(self, attr, lst)
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if name not in ['symbol','store','__dict__']:
|
if name not in ['symbol','store','disable_dirty'] and not self.disable_dirty:
|
||||||
self.store.dirty(self)
|
self.store.dirty(self)
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
def update(self, d):
|
def update(self, d):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def is_expired(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def load(self, d):
|
||||||
|
self.disable_dirty = True
|
||||||
|
self.__dict__.update(d)
|
||||||
|
self.disable_dirty = False
|
||||||
|
|
||||||
def dict(self):
|
def dict(self):
|
||||||
r = {}
|
r = {}
|
||||||
|
|||||||
72
nullptr/models/contract.py
Normal file
72
nullptr/models/contract.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
from time import time
|
||||||
|
from nullptr.util import *
|
||||||
|
from .base import Base
|
||||||
|
|
||||||
|
class Contract(Base):
|
||||||
|
identifier = 'id'
|
||||||
|
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):
|
||||||
|
return 'cnt'
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
return f'contracts/{self.symbol}.{self.ext()}'
|
||||||
|
|
||||||
|
def is_expired(self):
|
||||||
|
return time() > self.expires
|
||||||
|
|
||||||
|
def api_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.symbol.lower(),
|
||||||
|
'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')
|
||||||
|
self.seta('accepted', d, 'accepted')
|
||||||
|
self.seta('fulfilled', d, 'fulfilled')
|
||||||
|
self.seta('type', d, 'type')
|
||||||
|
self.pay = mg(d, 'terms.payment.onAccepted') + mg(d, 'terms.payment.onFulfilled')
|
||||||
|
deliveries = must_get(d, 'terms.deliver')
|
||||||
|
self.deliveries = []
|
||||||
|
for e in deliveries:
|
||||||
|
delivery = {}
|
||||||
|
delivery['trade_symbol'] = must_get(e, 'tradeSymbol')
|
||||||
|
delivery['units_fulfilled'] = must_get(e, 'unitsFulfilled')
|
||||||
|
delivery['units_required'] = must_get(e, 'unitsRequired')
|
||||||
|
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 '-'
|
||||||
|
fulfilled = 'F' if self.fulfilled else '-'
|
||||||
|
result = f'{self.symbol} {hours}h {accepted}{fulfilled}'
|
||||||
|
if detail > 1:
|
||||||
|
result += '\n'
|
||||||
|
for d in self.deliveries:
|
||||||
|
result += f"({d['units_fulfilled']} / {d['units_required']}) {d['trade_symbol']} to {d['destination']}"
|
||||||
|
return result
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
from .system_member import SystemMember
|
from .system_member import SystemMember
|
||||||
from typing import List
|
from dataclasses import field
|
||||||
|
|
||||||
class Jumpgate(SystemMember):
|
class Jumpgate(SystemMember):
|
||||||
range: int
|
def define(self):
|
||||||
faction: str
|
self.range: int = 0
|
||||||
systems:List[str] = []
|
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')
|
||||||
@@ -18,3 +19,10 @@ class Jumpgate(SystemMember):
|
|||||||
def path(self):
|
def path(self):
|
||||||
sector, system, _ = self.symbol.split('-')
|
sector, system, _ = self.symbol.split('-')
|
||||||
return f'atlas/{sector}/{system[0:1]}/{system}/{self.symbol}.{self.ext()}'
|
return f'atlas/{sector}/{system[0:1]}/{system}/{self.symbol}.{self.ext()}'
|
||||||
|
|
||||||
|
def f(self, detail=1):
|
||||||
|
r = self.symbol
|
||||||
|
if detail > 1:
|
||||||
|
r += '\n'
|
||||||
|
r += '\n'.join(self.systems)
|
||||||
|
return r
|
||||||
|
|||||||
@@ -1,21 +1,58 @@
|
|||||||
|
|
||||||
from .system_member import SystemMember
|
from .system_member import SystemMember
|
||||||
from typing import List
|
from time import time
|
||||||
|
from nullptr.util import *
|
||||||
|
from dataclasses import field
|
||||||
|
|
||||||
class Marketplace(SystemMember):
|
class Marketplace(SystemMember):
|
||||||
imports:List[str] = []
|
def define(self):
|
||||||
exports:List[str] = []
|
self.imports:list = []
|
||||||
exchange:List[str] = []
|
self.exports:list = []
|
||||||
|
self.exchange:list = []
|
||||||
|
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')
|
||||||
self.setlst('exports', d, 'exports', 'symbol')
|
self.setlst('exports', d, 'exports', 'symbol')
|
||||||
self.setlst('exchange', d, 'exchange', 'symbol')
|
self.setlst('exchange', d, 'exchange', 'symbol')
|
||||||
|
if 'tradeGoods' in d:
|
||||||
|
self.last_prices = time()
|
||||||
|
prices = {}
|
||||||
|
for g in mg(d, 'tradeGoods'):
|
||||||
|
price = {}
|
||||||
|
symbol= mg(g, 'symbol')
|
||||||
|
price['symbol'] = symbol
|
||||||
|
price['buy'] = mg(g, 'purchasePrice')
|
||||||
|
price['sell'] = mg(g, 'sellPrice')
|
||||||
|
prices[symbol] = price
|
||||||
|
self.prices = prices
|
||||||
|
|
||||||
|
def sellable_items(self, resources):
|
||||||
|
return [r for r in resources if r in self.prices]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ext(self):
|
def ext(self):
|
||||||
return 'mkt'
|
return 'mkt'
|
||||||
|
|
||||||
|
def rtype(self, r):
|
||||||
|
if r in self.imports:
|
||||||
|
return 'I'
|
||||||
|
if r in self.exports:
|
||||||
|
return 'E'
|
||||||
|
if r in self.exchange:
|
||||||
|
return 'X'
|
||||||
|
return '?'
|
||||||
|
|
||||||
def path(self):
|
def path(self):
|
||||||
sector, system, _ = self.symbol.split('-')
|
sector, system, _ = self.symbol.split('-')
|
||||||
return f'atlas/{sector}/{system[0:1]}/{system}/{self.symbol}.{self.ext()}'
|
return f'atlas/{sector}/{system[0:1]}/{system}/{self.symbol}.{self.ext()}'
|
||||||
|
|
||||||
|
def f(self, detail=1):
|
||||||
|
r = self.symbol
|
||||||
|
if detail > 1:
|
||||||
|
r += '\n'
|
||||||
|
for p in self.prices.values():
|
||||||
|
t = self.rtype(p['symbol'])
|
||||||
|
r += f'{t} {p["symbol"]:25s} {p["sell"]:5d} {p["buy"]:5d}\n'
|
||||||
|
return r
|
||||||
|
|||||||
104
nullptr/models/ship.py
Normal file
104
nullptr/models/ship.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from .base import Base
|
||||||
|
from time import time
|
||||||
|
from nullptr.util import *
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
class Ship(Base):
|
||||||
|
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):
|
||||||
|
return 'shp'
|
||||||
|
|
||||||
|
def location(self):
|
||||||
|
return self.store.get('Waypoint', self.location_str)
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
agent = self.symbol.split('-')[0]
|
||||||
|
return f'{agent}/{self.symbol}.{self.ext()}'
|
||||||
|
|
||||||
|
def update(self, d):
|
||||||
|
self.seta('status', d, 'nav.status')
|
||||||
|
self.seta('location_str', d, 'nav.waypointSymbol')
|
||||||
|
self.seta('cargo_capacity', d, 'cargo.capacity')
|
||||||
|
self.seta('cargo_units', d, 'cargo.units')
|
||||||
|
self.seta('fuel_capacity', d, 'fuel.capacity')
|
||||||
|
self.seta('fuel_current', d,'fuel.current')
|
||||||
|
cargo = sg(d, 'cargo.inventory')
|
||||||
|
if cargo is not None:
|
||||||
|
self.load_cargo(cargo)
|
||||||
|
self.seta('cooldown', d, 'cooldown.expiration', parse_timestamp)
|
||||||
|
self.seta('arrival', d, 'nav.route.arrival', parse_timestamp)
|
||||||
|
|
||||||
|
def tick(self):
|
||||||
|
if self.status == 'IN_TRANSIT' and self.arrival < time():
|
||||||
|
self.status = 'IN_ORBIT'
|
||||||
|
|
||||||
|
def is_cooldown(self):
|
||||||
|
return self.cooldown > time()
|
||||||
|
|
||||||
|
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:
|
||||||
|
return 0
|
||||||
|
return self.cargo[typ]
|
||||||
|
|
||||||
|
def load_cargo(self, cargo):
|
||||||
|
result = {}
|
||||||
|
for i in cargo:
|
||||||
|
symbol = must_get(i, 'symbol')
|
||||||
|
units = must_get(i, 'units')
|
||||||
|
result[symbol] = units
|
||||||
|
self.cargo = result
|
||||||
|
|
||||||
|
def deliverable_cargo(self, contract):
|
||||||
|
result = []
|
||||||
|
for d in contract.deliveries:
|
||||||
|
if self.get_cargo(d['trade_symbol']) > 0:
|
||||||
|
result.append(d['trade_symbol'])
|
||||||
|
return result
|
||||||
|
|
||||||
|
def nondeliverable_cargo(self, contract):
|
||||||
|
cargo = self.cargo.keys()
|
||||||
|
deliveries = [d['trade_symbol'] for d in contract.deliveries]
|
||||||
|
garbage = [c for c in cargo if c not in deliveries]
|
||||||
|
return garbage
|
||||||
|
|
||||||
|
def update_timers(self):
|
||||||
|
if self.status == 'IN_TRANSIT' and self.arrival < time():
|
||||||
|
self.status = 'IN_ORBIT'
|
||||||
|
|
||||||
|
|
||||||
|
def f(self, detail=1):
|
||||||
|
self.update_timers()
|
||||||
|
arrival = int(self.arrival - time())
|
||||||
|
cooldown = int(self.cooldown - time())
|
||||||
|
r = self.symbol
|
||||||
|
if detail > 1:
|
||||||
|
r += ' ' + self.status
|
||||||
|
r += f' [{self.fuel_current}/{self.fuel_capacity}]'
|
||||||
|
r += ' ' + str(self.location())
|
||||||
|
if self.is_travelling():
|
||||||
|
r += f' [A: {arrival}]'
|
||||||
|
if self.is_cooldown():
|
||||||
|
r += f' [C: {cooldown}]'
|
||||||
|
return r
|
||||||
|
|
||||||
55
nullptr/models/survey.py
Normal file
55
nullptr/models/survey.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from time import time
|
||||||
|
from nullptr.util import *
|
||||||
|
from .system_member import SystemMember
|
||||||
|
|
||||||
|
size_names = ['SMALL','MODERATE','LARGE']
|
||||||
|
|
||||||
|
class Survey(SystemMember):
|
||||||
|
identifier = 'signature'
|
||||||
|
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):
|
||||||
|
return 'svy'
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
sector, system, waypoint, signature = self.symbol.split('-')
|
||||||
|
return f'atlas/{sector}/{system[0:1]}/{system}/{self.symbol}.{self.ext()}'
|
||||||
|
|
||||||
|
|
||||||
|
def is_expired(self):
|
||||||
|
return time() > self.expires or self.exhausted
|
||||||
|
|
||||||
|
def waypoint(self):
|
||||||
|
p = self.symbol.split('-')
|
||||||
|
return '-'.join(p[:3])
|
||||||
|
|
||||||
|
def api_dict(self):
|
||||||
|
return {
|
||||||
|
'signature': self.symbol,
|
||||||
|
'symbol': 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
|
||||||
@@ -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)
|
||||||
@@ -22,3 +23,6 @@ class System(Base):
|
|||||||
|
|
||||||
def distance(self, other):
|
def distance(self, other):
|
||||||
return int(sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2))
|
return int(sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2))
|
||||||
|
|
||||||
|
def sector(self):
|
||||||
|
return self.symbol.split('-')[0]
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
from .system_member import SystemMember
|
from .system_member import SystemMember
|
||||||
from nullptr.util import *
|
from nullptr.util import *
|
||||||
from typing import List
|
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[str]=[]
|
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)
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ from nullptr.models.agent import Agent
|
|||||||
from nullptr.models.marketplace import Marketplace
|
from nullptr.models.marketplace import Marketplace
|
||||||
from nullptr.models.system_member import SystemMember
|
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.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
|
||||||
@@ -20,10 +23,13 @@ class Store:
|
|||||||
self.data = {m: {} for m in self.models}
|
self.data = {m: {} for m in self.models}
|
||||||
self.system_members = {}
|
self.system_members = {}
|
||||||
self.dirty_objects = set()
|
self.dirty_objects = set()
|
||||||
|
self.cleanup_interval = 600
|
||||||
|
self.last_cleanup = 0
|
||||||
|
|
||||||
def init_models(self):
|
def init_models(self):
|
||||||
self.models = all_subclasses(Base)
|
self.models = all_subclasses(Base)
|
||||||
self.extensions = {c.ext(): c for c in self.models}
|
self.extensions = {c.ext(): c for c in self.models}
|
||||||
|
self.model_names = {c.__name__: c for c in self.models}
|
||||||
|
|
||||||
def dirty(self, obj):
|
def dirty(self, obj):
|
||||||
self.dirty_objects.add(obj)
|
self.dirty_objects.add(obj)
|
||||||
@@ -44,8 +50,8 @@ class Store:
|
|||||||
|
|
||||||
typ = self.extensions[ext]
|
typ = self.extensions[ext]
|
||||||
obj = self.create(typ, symbol)
|
obj = self.create(typ, symbol)
|
||||||
data['store'] = self
|
obj.load(data)
|
||||||
obj.__dict__ = data
|
obj.store = self
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@@ -78,6 +84,9 @@ class Store:
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get(self, typ, symbol, create=False):
|
def get(self, typ, symbol, create=False):
|
||||||
|
if type(typ) == str and typ in self.model_names:
|
||||||
|
typ = self.model_names[typ]
|
||||||
|
symbol = symbol.upper()
|
||||||
if typ not in self.data:
|
if typ not in self.data:
|
||||||
return None
|
return None
|
||||||
if symbol not in self.data[typ]:
|
if symbol not in self.data[typ]:
|
||||||
@@ -87,28 +96,57 @@ class Store:
|
|||||||
return None
|
return None
|
||||||
return self.data[typ][symbol]
|
return self.data[typ][symbol]
|
||||||
|
|
||||||
def update(self, typ, symbol, data):
|
def update(self, typ, data, symbol=None):
|
||||||
|
if type(typ) == str and typ in self.model_names:
|
||||||
|
typ = self.model_names[typ]
|
||||||
|
if symbol is None:
|
||||||
|
symbol = mg(data, typ.identifier)
|
||||||
obj = self.get(typ, symbol, True)
|
obj = self.get(typ, symbol, True)
|
||||||
obj.update(data)
|
obj.update(data)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def update_list(self, typ, lst):
|
def update_list(self, typ, lst):
|
||||||
return [self.update(typ, mg(d, 'symbol'), d) for d in lst]
|
return [self.update(typ, d) for d in lst]
|
||||||
|
|
||||||
def all(self, typ):
|
def all(self, typ):
|
||||||
|
if type(typ) == str and typ in self.model_names:
|
||||||
|
typ = self.model_names[typ]
|
||||||
|
|
||||||
for m in self.data[typ].values():
|
for m in self.data[typ].values():
|
||||||
yield m
|
yield m
|
||||||
|
|
||||||
def all_members(self, system, typ=None):
|
def all_members(self, system, typ=None):
|
||||||
|
if type(typ) == str and typ in self.model_names:
|
||||||
|
typ = self.model_names[typ]
|
||||||
|
|
||||||
if type(system) == System:
|
if type(system) == System:
|
||||||
system = system.symbol
|
system = system.symbol
|
||||||
|
|
||||||
if system not in self.system_members:
|
if system not in self.system_members:
|
||||||
return
|
return
|
||||||
for m in self.system_members[system]:
|
for m in self.system_members[system]:
|
||||||
if typ is None or type(m) == typ:
|
if typ is None or type(m) == typ:
|
||||||
yield m
|
yield m
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
if time() < self.last_cleanup + self.cleanup_interval:
|
||||||
|
return
|
||||||
|
start_time = time()
|
||||||
|
expired = list()
|
||||||
|
for t in self.data:
|
||||||
|
for o in self.all(t):
|
||||||
|
if o.is_expired():
|
||||||
|
expired.append(o)
|
||||||
|
for o in expired:
|
||||||
|
path = o.path()
|
||||||
|
if isfile(path):
|
||||||
|
os.remove(path)
|
||||||
|
del self.data[type(o)][o.symbol]
|
||||||
|
dur = time() - start_time
|
||||||
|
# print(f'cleaned {len(expired)} in {dur:.03f} seconds')
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
self.cleanup()
|
||||||
it = 0
|
it = 0
|
||||||
start_time = time()
|
start_time = time()
|
||||||
for obj in self.dirty_objects:
|
for obj in self.dirty_objects:
|
||||||
|
|||||||
Reference in New Issue
Block a user