Compare commits
38 Commits
293b6d1c3e
...
hauling
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d124179bf | ||
|
|
9b9a149e3f | ||
|
|
9e6583ac24 | ||
|
|
6c98eec738 | ||
|
|
11031599cf | ||
|
|
7eea63ac82 | ||
|
|
dc862088cd | ||
|
|
35bc586b72 | ||
|
|
2a5680c16d | ||
|
|
4d51ad53c0 | ||
|
|
5fbce54285 | ||
|
|
27bd054e8b | ||
|
|
38a2ee7870 | ||
|
|
7c3eaa825f | ||
|
|
ddd693a66e | ||
|
|
b43568f476 | ||
|
|
ff4643d7ac | ||
|
|
0e3f939b9a | ||
|
|
2d792dffae | ||
|
|
4043c5585e | ||
|
|
b19e3ed2b2 | ||
|
|
b7d3347fac | ||
|
|
42e370fde5 | ||
|
|
b202b80541 | ||
|
|
b023718450 | ||
|
|
fbda97df61 | ||
|
|
707f142e7a | ||
|
|
35ea9e2e04 | ||
|
|
3a85c6c367 | ||
|
|
21f93f078d | ||
|
|
3a3e3b9da2 | ||
|
|
f849f871d2 | ||
|
|
9369c6982f | ||
|
|
8b29ca8f58 | ||
|
|
a229b9e300 | ||
|
|
46f9597e2e | ||
|
|
c2a1f787a2 | ||
|
|
9987481848 |
@@ -1,6 +1,7 @@
|
|||||||
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.system import System
|
from nullptr.models.system import System
|
||||||
|
from nullptr.models.waypoint import Waypoint
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -29,15 +30,43 @@ class Analyzer:
|
|||||||
|
|
||||||
def find_markets(self, resource, sellbuy):
|
def find_markets(self, resource, sellbuy):
|
||||||
for m in self.store.all(Marketplace):
|
for m in self.store.all(Marketplace):
|
||||||
resources = m.imports if sellbuy == 'sell' else m.exports
|
if 'sell' in sellbuy and resource in m.imports:
|
||||||
if resource in resources:
|
yield ('sell', m)
|
||||||
yield m
|
|
||||||
|
elif 'buy' in sellbuy and resource in m.exports:
|
||||||
|
yield ('buy', m)
|
||||||
|
|
||||||
|
elif 'exchange' in sellbuy and resource in m.exchange:
|
||||||
|
yield ('exchange', m)
|
||||||
|
|
||||||
|
def find_closest_markets(self, resource, sellbuy, location):
|
||||||
|
if type(location) == str:
|
||||||
|
location = self.store.get(Waypoint, location)
|
||||||
|
mkts = self.find_markets(resource, sellbuy)
|
||||||
|
candidates = []
|
||||||
|
origin = self.store.get(System, location.system())
|
||||||
|
for typ, m in mkts:
|
||||||
|
system = self.store.get(System, m.system())
|
||||||
|
d = origin.distance(system)
|
||||||
|
candidates.append((typ, m, d))
|
||||||
|
possibles = sorted(candidates, key=lambda m: m[2])
|
||||||
|
possibles = possibles[:10]
|
||||||
|
results = []
|
||||||
|
for typ,m,d in possibles:
|
||||||
|
system = self.store.get(System, m.system())
|
||||||
|
p = self.find_path(origin, system)
|
||||||
|
if p is None: continue
|
||||||
|
results.append((typ,m,d,len(p)))
|
||||||
|
return results
|
||||||
|
|
||||||
|
def solve_tsp(self, waypoints):
|
||||||
|
# todo actually try to solve it
|
||||||
|
return waypoints
|
||||||
|
|
||||||
def get_jumpgate(self, system):
|
def get_jumpgate(self, system):
|
||||||
gates = self.store.all_members(system, Jumpgate)
|
gates = self.store.all_members(system, Jumpgate)
|
||||||
return next(gates, None)
|
return next(gates, None)
|
||||||
|
|
||||||
|
|
||||||
def find_path(self, orig, to, depth=100, seen=None):
|
def find_path(self, orig, to, depth=100, seen=None):
|
||||||
if depth < 1: return None
|
if depth < 1: return None
|
||||||
if seen is None:
|
if seen is None:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class Api:
|
|||||||
def request(self, method, path, data=None, need_token=True, params={}):
|
def request(self, method, path, data=None, need_token=True, params={}):
|
||||||
try:
|
try:
|
||||||
return self.request_once(method, path, data, need_token, params)
|
return self.request_once(method, path, data, need_token, params)
|
||||||
except ApiLimitError:
|
except (ApiLimitError, requests.exceptions.Timeout):
|
||||||
print('oops, hit the limit. take a break')
|
print('oops, hit the limit. take a break')
|
||||||
sleep(10)
|
sleep(10)
|
||||||
return self.request_once(method, path, data, need_token, params)
|
return self.request_once(method, path, data, need_token, params)
|
||||||
@@ -121,7 +121,7 @@ class Api:
|
|||||||
'tradeSymbol': typ.upper(),
|
'tradeSymbol': typ.upper(),
|
||||||
'units': units
|
'units': units
|
||||||
}
|
}
|
||||||
data = self.request('post', f'my/contracts/{contract}/deliver', data)
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/deliver', data)
|
||||||
if 'cargo' in data:
|
if 'cargo' in data:
|
||||||
ship.update(data)
|
ship.update(data)
|
||||||
if 'contract' in data:
|
if 'contract' in data:
|
||||||
@@ -129,7 +129,7 @@ class Api:
|
|||||||
return contract
|
return contract
|
||||||
|
|
||||||
def fulfill(self, contract):
|
def fulfill(self, contract):
|
||||||
data = self.request('post', f'my/contracts/{contract}/fulfill')
|
data = self.request('post', f'my/contracts/{contract.symbol.lower()}/fulfill')
|
||||||
if 'contract' in data:
|
if 'contract' in data:
|
||||||
contract.update(data['contract'])
|
contract.update(data['contract'])
|
||||||
if 'agent' in data:
|
if 'agent' in data:
|
||||||
@@ -221,8 +221,10 @@ class Api:
|
|||||||
return ship
|
return ship
|
||||||
|
|
||||||
def jump(self, ship, system):
|
def jump(self, ship, system):
|
||||||
|
if type(system) == System:
|
||||||
|
system = system.symbol
|
||||||
data = {
|
data = {
|
||||||
"systemSymbol": system.symbol
|
"systemSymbol": system
|
||||||
}
|
}
|
||||||
data = self.request('post', f'my/ships/{ship}/jump', data)
|
data = self.request('post', f'my/ships/{ship}/jump', data)
|
||||||
if 'nav' in data:
|
if 'nav' in data:
|
||||||
@@ -244,7 +246,7 @@ class Api:
|
|||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
ship.update(data)
|
ship.update(data)
|
||||||
return ship
|
return data
|
||||||
|
|
||||||
def survey(self, ship):
|
def survey(self, ship):
|
||||||
data = self.request('post', f'my/ships/{ship}/survey')
|
data = self.request('post', f'my/ships/{ship}/survey')
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
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.missions import create_mission, get_mission_class
|
||||||
from random import choice
|
from random import choice
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
class CentralCommandError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
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 +25,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,24 +72,41 @@ 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)
|
||||||
elif s not in self.missions:
|
elif s not in self.missions:
|
||||||
self.start_mission(s)
|
self.start_mission(s)
|
||||||
|
if s in self.missions:
|
||||||
|
m = self.missions[s]
|
||||||
|
m.next_step = max(s.cooldown, s.arrival)
|
||||||
|
|
||||||
|
def init_mission(self, s, mtyp):
|
||||||
|
if mtyp == 'none':
|
||||||
|
s.mission_state = {}
|
||||||
|
s.mission_status = None
|
||||||
|
s.mission = None
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
mclass = get_mission_class(mtyp)
|
||||||
|
except ValueError:
|
||||||
|
raise CentralCommandError('no such mission')
|
||||||
|
s.mission = mtyp
|
||||||
|
s.mission_status = 'init'
|
||||||
|
s.mission_state = {k: v.default for k,v in mclass.params().items()}
|
||||||
|
self.start_mission(s)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -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=type(e).__name__ not in ['ApiError','CommandError'])
|
logging.error(e, exc_info=type(e).__name__ not in ['ApiError','CommandError', 'CentralCommandError'])
|
||||||
|
|
||||||
def handle_empty(self):
|
def handle_empty(self):
|
||||||
pass
|
pass
|
||||||
@@ -90,5 +90,8 @@ class CommandLine:
|
|||||||
except EOFError:
|
except EOFError:
|
||||||
self.handle_eof()
|
self.handle_eof()
|
||||||
break
|
break
|
||||||
self.handle_cmd(c)
|
try:
|
||||||
|
self.handle_cmd(c)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e, exc_info=True)
|
||||||
|
|
||||||
|
|||||||
@@ -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,99 @@ class Commander(CommandLine):
|
|||||||
|
|
||||||
pprint(self.agent, 100)
|
pprint(self.agent, 100)
|
||||||
|
|
||||||
|
def do_auto(self):
|
||||||
|
self.centcom.run_interactive()
|
||||||
|
|
||||||
|
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.centcom.init_mission(self.ship, arg)
|
||||||
|
self.print_mission()
|
||||||
|
|
||||||
|
def do_mreset(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
self.ship.mission_state = {}
|
||||||
|
|
||||||
|
def do_mset(self, nm, val):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
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.centcom.init_mission(self.ship, '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, 'dest', destination)
|
||||||
|
self.centcom.set_mission_param(self.ship, 'contract', contract.symbol)
|
||||||
|
self.print_mission()
|
||||||
|
|
||||||
|
def do_chaul(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
contract = self.active_contract()
|
||||||
|
delivery = contract.unfinished_delivery()
|
||||||
|
if delivery is None:
|
||||||
|
raise CommandError('no delivery')
|
||||||
|
resource = delivery['trade_symbol']
|
||||||
|
destination = delivery['destination']
|
||||||
|
m = self.analyzer.find_closest_markets(resource, 'buy', destination)
|
||||||
|
if len(m) == 0:
|
||||||
|
m = self.analyzer.find_closest_markets(resource, 'exchange', destination)
|
||||||
|
if len(m) == 0:
|
||||||
|
print('no market found')
|
||||||
|
return
|
||||||
|
_, m, _, _ = m[0]
|
||||||
|
site = self.store.get(Waypoint, m.symbol)
|
||||||
|
self.centcom.init_mission(self.ship, 'haul')
|
||||||
|
self.centcom.set_mission_param(self.ship, 'site', site.symbol)
|
||||||
|
self.centcom.set_mission_param(self.ship, 'resource', resource)
|
||||||
|
self.centcom.set_mission_param(self.ship, 'dest', destination)
|
||||||
|
self.centcom.set_mission_param(self.ship, 'contract', contract.symbol)
|
||||||
|
self.print_mission()
|
||||||
|
|
||||||
|
def do_cprobe(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
contract = self.active_contract()
|
||||||
|
delivery = contract.unfinished_delivery()
|
||||||
|
if delivery is None:
|
||||||
|
raise CommandError('no delivery')
|
||||||
|
resource = delivery['trade_symbol']
|
||||||
|
destination = delivery['destination']
|
||||||
|
m = self.analyzer.find_closest_markets(resource, 'buy,exchange', destination)
|
||||||
|
if len(m) is None:
|
||||||
|
print('no market found')
|
||||||
|
return
|
||||||
|
markets = [ mkt[1] for mkt in m]
|
||||||
|
markets = self.analyzer.solve_tsp(markets)
|
||||||
|
hops = ','.join([m.symbol for m in markets])
|
||||||
|
self.centcom.init_mission(self.ship, 'probe')
|
||||||
|
self.centcom.set_mission_param(self.ship, 'hops', hops)
|
||||||
|
self.print_mission()
|
||||||
|
|
||||||
|
def do_travel(self, dest):
|
||||||
|
dest = self.resolve('Waypoint', dest)
|
||||||
|
self.centcom.init_mission(self.ship, 'travel')
|
||||||
|
self.centcom.set_mission_param(self.ship, 'dest', dest.symbol)
|
||||||
|
self.print_mission()
|
||||||
|
|
||||||
def do_register(self, faction):
|
def do_register(self, faction):
|
||||||
self.api.register(faction.upper())
|
self.api.register(faction.upper())
|
||||||
|
pprint(self.api.agent)
|
||||||
|
|
||||||
def do_universe(self, page=1):
|
def do_universe(self, page=1):
|
||||||
self.atlas_builder.run(page)
|
self.atlas_builder.run(page)
|
||||||
@@ -88,6 +180,14 @@ class Commander(CommandLine):
|
|||||||
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=''):
|
def do_waypoints(self, system_str=''):
|
||||||
if system_str == '':
|
if system_str == '':
|
||||||
@@ -124,16 +224,16 @@ class Commander(CommandLine):
|
|||||||
r = self.api.jumps(waypoint)
|
r = self.api.jumps(waypoint)
|
||||||
pprint(r)
|
pprint(r)
|
||||||
|
|
||||||
def do_query(self):
|
def do_query(self, resource):
|
||||||
location = self.ask_obj(System, 'Where are you? ')
|
if not self.has_ship(): return
|
||||||
resource = input('what resource?').upper()
|
location = self.ship.location()
|
||||||
sellbuy = self.ask_multichoice(['sell','buy'], 'do you want to sell or buy?')
|
resource = resource.upper()
|
||||||
print('Found markets:')
|
print('Found markets:')
|
||||||
for m in self.analyzer.find_markets(resource, sellbuy):
|
for typ, m, d, plen in self.analyzer.find_closest_markets(resource, 'buy,exchange',location):
|
||||||
system = self.store.get(System, m.system())
|
price = '?'
|
||||||
p = self.analyzer.find_path(location, system)
|
if resource in m.prices:
|
||||||
if p is None: continue
|
price = m.prices[resource]['buy']
|
||||||
print(m, f'{len(p)-1} hops')
|
print(m, typ[0], f'{plen-1:3} hops {price}')
|
||||||
|
|
||||||
def do_path(self):
|
def do_path(self):
|
||||||
orig = self.ask_obj(System, 'from: ')
|
orig = self.ask_obj(System, 'from: ')
|
||||||
@@ -157,6 +257,21 @@ class Commander(CommandLine):
|
|||||||
r = list(self.store.all('Contract'))
|
r = list(self.store.all('Contract'))
|
||||||
pprint(r)
|
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_fulfill(self):
|
||||||
|
contract = self.active_contract()
|
||||||
|
self.api.fulfill(contract)
|
||||||
|
|
||||||
def do_ship(self, arg=''):
|
def do_ship(self, arg=''):
|
||||||
if arg != '':
|
if arg != '':
|
||||||
symbol = f'{self.agent.symbol}-{arg}'
|
symbol = f'{self.agent.symbol}-{arg}'
|
||||||
@@ -238,7 +353,9 @@ class Commander(CommandLine):
|
|||||||
def do_shipyard(self):
|
def do_shipyard(self):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
location = self.ship.location()
|
location = self.ship.location()
|
||||||
pprint(self.api.shipyard(location))
|
data = self.api.shipyard(location)
|
||||||
|
for s in must_get(data, 'ships'):
|
||||||
|
print(s['type'], s['purchasePrice'])
|
||||||
|
|
||||||
def do_jump(self, system_str):
|
def do_jump(self, system_str):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
@@ -262,3 +379,17 @@ class Commander(CommandLine):
|
|||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
r = self.api.survey(self.ship)
|
r = self.api.survey(self.ship)
|
||||||
pprint(r)
|
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)
|
||||||
|
|||||||
23
nullptr/missions/__init__.py
Normal file
23
nullptr/missions/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from nullptr.missions.survey import SurveyMission
|
||||||
|
from nullptr.missions.mine import MiningMission
|
||||||
|
from nullptr.missions.haul import HaulMission
|
||||||
|
from nullptr.missions.travel import TravelMission
|
||||||
|
from nullptr.missions.probe import ProbeMission
|
||||||
|
|
||||||
|
def get_mission_class( mtype):
|
||||||
|
types = {
|
||||||
|
'survey': SurveyMission,
|
||||||
|
'mine': MiningMission,
|
||||||
|
'haul': HaulMission,
|
||||||
|
'travel': TravelMission,
|
||||||
|
'probe': ProbeMission
|
||||||
|
}
|
||||||
|
if mtype not in types:
|
||||||
|
raise ValueError(f'invalid mission type {mtype}')
|
||||||
|
return types[mtype]
|
||||||
|
|
||||||
|
def create_mission(mtype, ship, store, api):
|
||||||
|
typ = get_mission_class(mtype)
|
||||||
|
m = typ(ship, store, api)
|
||||||
|
return m
|
||||||
|
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
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.system import System
|
||||||
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 nullptr.analyzer import Analyzer
|
||||||
from time import time
|
from time import time
|
||||||
|
from functools import partial
|
||||||
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 +20,43 @@ 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 self.cls == list:
|
||||||
|
return [i.strip() for i in val.split(',')]
|
||||||
|
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
|
||||||
|
self.analyzer = Analyzer(self.store)
|
||||||
|
|
||||||
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 +113,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:
|
||||||
@@ -112,97 +127,23 @@ class Mission:
|
|||||||
self.status(next_step[result])
|
self.status(next_step[result])
|
||||||
print(f'{self.ship} {status} -> {self.status()}')
|
print(f'{self.ship} {status} -> {self.status()}')
|
||||||
|
|
||||||
|
class BaseMission(Mission):
|
||||||
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):
|
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
|
||||||
|
|
||||||
def step_dock(self):
|
def step_go_site(self):
|
||||||
self.api.dock(self.ship)
|
site = self.rst(Waypoint,'site')
|
||||||
|
if self.ship.location() == site:
|
||||||
|
return
|
||||||
|
self.api.navigate(self.ship, site)
|
||||||
|
self.next_step = self.ship.arrival
|
||||||
|
|
||||||
def step_unload(self):
|
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)
|
||||||
@@ -215,54 +156,95 @@ class MiningMission(Mission):
|
|||||||
else:
|
else:
|
||||||
return 'more'
|
return 'more'
|
||||||
|
|
||||||
def step_refuel(self):
|
def step_sell(self, except_resource=True):
|
||||||
self.api.refuel(self.ship)
|
target = self.st('resource')
|
||||||
|
market = self.store.get('Marketplace', self.ship.location_str)
|
||||||
def step_dispose(self):
|
sellables = market.sellable_items(self.ship.cargo.keys())
|
||||||
contract = self.st('contract')
|
if target in sellables and except_resource:
|
||||||
typs = self.ship.nondeliverable_cargo(contract)
|
sellables.remove(target)
|
||||||
if len(typs) > 0:
|
if len(sellables) == 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'
|
return 'done'
|
||||||
|
self.api.sell(self.ship, sellables[0])
|
||||||
|
if len(sellables) == 1:
|
||||||
|
return 'done'
|
||||||
|
else:
|
||||||
|
return 'more'
|
||||||
|
|
||||||
|
def step_load(self):
|
||||||
|
cargo_space = self.ship.cargo_capacity - self.ship.cargo_units
|
||||||
|
resource = self.st('resource')
|
||||||
|
self.api.buy(self.ship, resource, cargo_space)
|
||||||
|
|
||||||
|
def step_travel(self):
|
||||||
|
traject = self.st('traject')
|
||||||
|
if traject is None or traject == []:
|
||||||
|
return 'done'
|
||||||
|
dest = self.store.get(Waypoint, traject[-1])
|
||||||
|
loc = self.ship.location()
|
||||||
|
print(dest, loc)
|
||||||
|
if dest == loc:
|
||||||
|
self.sts('traject', None)
|
||||||
|
return 'done'
|
||||||
|
hop = traject.pop(0)
|
||||||
|
if len(hop.split('-')) == 3:
|
||||||
|
self.api.navigate(self.ship, hop)
|
||||||
|
self.next_step = self.ship.arrival
|
||||||
|
else:
|
||||||
|
self.api.jump(self.ship, hop)
|
||||||
|
self.next_step = self.ship.cooldown
|
||||||
|
if traject == []:
|
||||||
|
traject= None
|
||||||
|
self.sts('traject', traject)
|
||||||
|
return 'more'
|
||||||
|
|
||||||
|
def step_calculate_traject(self, dest):
|
||||||
|
if type(dest) == str:
|
||||||
|
dest = self.store.get(Waypoint, dest)
|
||||||
|
loc = self.ship.location()
|
||||||
|
loc_sys = self.store.get(System, loc.system())
|
||||||
|
loc_jg = self.analyzer.get_jumpgate(loc_sys)
|
||||||
|
dest_sys = self.store.get(System, dest.system())
|
||||||
|
dest_jg = self.analyzer.get_jumpgate(dest_sys)
|
||||||
|
if dest_sys == loc_sys:
|
||||||
|
result = [dest.symbol]
|
||||||
|
self.sts('traject', result)
|
||||||
|
return
|
||||||
|
path = self.analyzer.find_path(loc_sys, dest_sys)
|
||||||
|
result = []
|
||||||
|
if loc.symbol != loc_jg.symbol:
|
||||||
|
result.append(loc_jg.symbol)
|
||||||
|
result += [s.symbol for s in path[1:]]
|
||||||
|
if dest_jg.symbol != dest.symbol:
|
||||||
|
result.append(dest.symbol)
|
||||||
|
self.sts('traject', result)
|
||||||
|
print(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def step_dock(self):
|
||||||
|
self.api.dock(self.ship)
|
||||||
|
|
||||||
|
def step_refuel(self):
|
||||||
|
if self.ship.fuel_capacity == 0:
|
||||||
|
return
|
||||||
|
if self.ship.fuel_current / self.ship.fuel_capacity < 0.5:
|
||||||
|
try:
|
||||||
|
self.api.refuel(self.ship)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
def step_orbit(self):
|
def step_orbit(self):
|
||||||
self.api.orbit(self.ship)
|
self.api.orbit(self.ship)
|
||||||
|
|
||||||
def step_go_site(self):
|
def travel_steps(self, nm, destination, next_step):
|
||||||
site = self.st('site')
|
destination = self.st(destination)
|
||||||
if self.ship.location == site:
|
calc = partial(self.step_calculate_traject, destination)
|
||||||
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 {
|
return {
|
||||||
'survey': (self.step_survey, 'survey')
|
f'travel-{nm}': (self.step_orbit, f'calc-trav-{nm}'),
|
||||||
|
f'calc-trav-{nm}': (calc, f'go-{nm}'),
|
||||||
|
f'go-{nm}': (self.step_travel, {
|
||||||
|
'done': f'dock-{nm}',
|
||||||
|
'more': f'go-{nm}'
|
||||||
|
}),
|
||||||
|
f'dock-{nm}': (self.step_dock, f'refuel-{nm}'),
|
||||||
|
f'refuel-{nm}': (self.step_refuel, next_step)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
25
nullptr/missions/haul.py
Normal file
25
nullptr/missions/haul.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from nullptr.missions.base import BaseMission, MissionParam
|
||||||
|
from nullptr.models.waypoint import Waypoint
|
||||||
|
from nullptr.models.survey import Survey
|
||||||
|
from nullptr.models.contract import Contract
|
||||||
|
class HaulMission(BaseMission):
|
||||||
|
def start_state(self):
|
||||||
|
return 'travel-to'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def params(cls):
|
||||||
|
return {
|
||||||
|
'site': MissionParam(Waypoint, True),
|
||||||
|
'resource': MissionParam(str, True),
|
||||||
|
'dest': MissionParam(Waypoint, True),
|
||||||
|
'delivery': MissionParam(str, True, 'deliver'),
|
||||||
|
'contract': MissionParam(Contract, False)
|
||||||
|
}
|
||||||
|
|
||||||
|
def steps(self):
|
||||||
|
return {
|
||||||
|
**self.travel_steps('to', 'site', 'load'),
|
||||||
|
'load': (self.step_load, 'travel-back'),
|
||||||
|
**self.travel_steps('back', 'dest', 'unload'),
|
||||||
|
'unload': (self.step_unload, 'travel-to'),
|
||||||
|
}
|
||||||
80
nullptr/missions/mine.py
Normal file
80
nullptr/missions/mine.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from nullptr.missions.base import BaseMission, MissionParam
|
||||||
|
from nullptr.models.waypoint import Waypoint
|
||||||
|
from nullptr.models.survey import Survey
|
||||||
|
from nullptr.models.contract import Contract
|
||||||
|
from nullptr.util import *
|
||||||
|
|
||||||
|
class MiningMission(BaseMission):
|
||||||
|
@classmethod
|
||||||
|
def params(cls):
|
||||||
|
return {
|
||||||
|
'site': MissionParam(Waypoint, True),
|
||||||
|
'resource': MissionParam(str, True),
|
||||||
|
'dest': MissionParam(Waypoint, True),
|
||||||
|
'delivery': MissionParam(str, True, 'deliver'),
|
||||||
|
'contract': MissionParam(Contract, False)
|
||||||
|
}
|
||||||
|
|
||||||
|
def start_state(self):
|
||||||
|
return 'travel-to'
|
||||||
|
|
||||||
|
def steps(self):
|
||||||
|
return {
|
||||||
|
**self.travel_steps('to', 'site', 'orbit1'),
|
||||||
|
'orbit1': (self.step_orbit, 'extract'),
|
||||||
|
'extract': (self.step_extract, {
|
||||||
|
'done': 'dock',
|
||||||
|
'more': 'extract'
|
||||||
|
}),
|
||||||
|
'dock': (self.step_dock, 'sell'),
|
||||||
|
'sell': (self.step_sell, {
|
||||||
|
'more': 'sell',
|
||||||
|
'done': 'orbit2',
|
||||||
|
}),
|
||||||
|
'orbit2': (self.step_orbit, 'jettison'),
|
||||||
|
'jettison': (self.step_dispose, {
|
||||||
|
'more': 'jettison',
|
||||||
|
'done': 'extract',
|
||||||
|
'full': 'travel-back'
|
||||||
|
}),
|
||||||
|
**self.travel_steps('back', 'dest', 'unload'),
|
||||||
|
'unload': (self.step_unload, {
|
||||||
|
'done': 'travel-to',
|
||||||
|
'more': 'unload'
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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'
|
||||||
|
|
||||||
33
nullptr/missions/probe.py
Normal file
33
nullptr/missions/probe.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from nullptr.missions.base import BaseMission, MissionParam
|
||||||
|
from nullptr.models.waypoint import Waypoint
|
||||||
|
|
||||||
|
class ProbeMission(BaseMission):
|
||||||
|
def start_state(self):
|
||||||
|
return 'next-hop'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def params(cls):
|
||||||
|
return {
|
||||||
|
'hops': MissionParam(list, True),
|
||||||
|
'next-hop': MissionParam(int, True, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
def steps(self):
|
||||||
|
return {
|
||||||
|
'next-hop': (self.step_next_hop, 'travel-to'),
|
||||||
|
**self.travel_steps('to', 'site', 'market'),
|
||||||
|
'market': (self.step_market, 'next-hop'),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def step_market(self):
|
||||||
|
loc = self.ship.location()
|
||||||
|
self.api.marketplace(loc)
|
||||||
|
|
||||||
|
def step_next_hop(self):
|
||||||
|
hops = self.st('hops')
|
||||||
|
next_hop = self.st('next-hop')
|
||||||
|
hop = hops[next_hop]
|
||||||
|
self.sts('site', hop)
|
||||||
|
self.sts('next-hop', (next_hop+1) % len(hops))
|
||||||
|
|
||||||
15
nullptr/missions/survey.py
Normal file
15
nullptr/missions/survey.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from nullptr.missions.base import BaseMission, MissionParam
|
||||||
|
|
||||||
|
class SurveyMission(BaseMission):
|
||||||
|
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
|
||||||
16
nullptr/missions/travel.py
Normal file
16
nullptr/missions/travel.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from nullptr.missions.base import BaseMission, MissionParam
|
||||||
|
from nullptr.models.waypoint import Waypoint
|
||||||
|
|
||||||
|
class TravelMission(BaseMission):
|
||||||
|
def start_state(self):
|
||||||
|
return 'travel-to'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def params(cls):
|
||||||
|
return {
|
||||||
|
'dest': MissionParam(Waypoint, True)
|
||||||
|
}
|
||||||
|
|
||||||
|
def steps(self):
|
||||||
|
return self.travel_steps('to', 'dest', 'done')
|
||||||
|
|
||||||
@@ -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,16 +1,20 @@
|
|||||||
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'
|
identifier = 'symbol'
|
||||||
symbol: str
|
symbol: str
|
||||||
store: object
|
store: object
|
||||||
|
|
||||||
def __init__(self, symbol, store):
|
def __init__(self, symbol, store):
|
||||||
|
self.disable_dirty = True
|
||||||
self.store = store
|
self.store = store
|
||||||
self.symbol = symbol
|
self.symbol = symbol
|
||||||
|
self.define()
|
||||||
|
self.disable_dirty = False
|
||||||
|
|
||||||
|
def define(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((str(type(self)), self.symbol))
|
return hash((str(type(self)), self.symbol))
|
||||||
@@ -34,15 +38,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):
|
def load(self, d):
|
||||||
self.__dict__ = d
|
self.disable_dirty = True
|
||||||
|
self.__dict__.update(d)
|
||||||
|
self.disable_dirty = False
|
||||||
|
|
||||||
def dict(self):
|
def dict(self):
|
||||||
r = {}
|
r = {}
|
||||||
|
|||||||
@@ -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 '-'
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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')
|
||||||
@@ -27,6 +28,9 @@ class Marketplace(SystemMember):
|
|||||||
prices[symbol] = price
|
prices[symbol] = price
|
||||||
self.prices = prices
|
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'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
@@ -19,16 +20,20 @@ class Survey(SystemMember):
|
|||||||
|
|
||||||
def path(self):
|
def path(self):
|
||||||
sector, system, waypoint, signature = self.symbol.split('-')
|
sector, system, waypoint, signature = self.symbol.split('-')
|
||||||
return f'atlas/{sector}/{system[0:1]}/{system}/{waypoint}-{signature}.{self.ext()}'
|
return f'atlas/{sector}/{system[0:1]}/{system}/{self.symbol}.{self.ext()}'
|
||||||
|
|
||||||
|
|
||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
return time() > self.expires or self.exhausted
|
return time() > self.expires or self.exhausted
|
||||||
|
|
||||||
|
def waypoint(self):
|
||||||
|
p = self.symbol.split('-')
|
||||||
|
return '-'.join(p[:3])
|
||||||
|
|
||||||
def api_dict(self):
|
def api_dict(self):
|
||||||
return {
|
return {
|
||||||
'signature': self.symbol,
|
'signature': self.symbol,
|
||||||
'symbol': str(self.waypoint),
|
'symbol': self.waypoint(),
|
||||||
'deposits': [{'symbol': d} for d in self.deposits],
|
'deposits': [{'symbol': d} for d in self.deposits],
|
||||||
'expiration': self.expires_str,
|
'expiration': self.expires_str,
|
||||||
'size': size_names[self.size]
|
'size': size_names[self.size]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ 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)
|
||||||
@@ -122,11 +124,30 @@ class Store:
|
|||||||
|
|
||||||
if system not in self.system_members:
|
if system not in self.system_members:
|
||||||
return
|
return
|
||||||
|
print('typ', typ)
|
||||||
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