progress
Commander cleanup First impl of ship logs Ship display improved Store debugged
This commit is contained in:
parent
2181583843
commit
237dcc8c14
13
main.py
13
main.py
@ -2,18 +2,21 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from nullptr.commander import Commander
|
from nullptr.commander import Commander
|
||||||
import os
|
import os
|
||||||
|
from nullptr.store_analyzer import StoreAnalyzer
|
||||||
from nullptr.models.base import Base
|
from nullptr.models.base import Base
|
||||||
def main(args):
|
def main(args):
|
||||||
if not os.path.isdir(args.data_dir):
|
if not os.path.isdir(args.data_dir):
|
||||||
os.makedirs(args.data_dir )
|
os.makedirs(args.data_dir )
|
||||||
c = Commander(args.data_dir)
|
if args.analyze:
|
||||||
c.run()
|
a = StoreAnalyzer(verbose=True)
|
||||||
|
a.run(args.analyze)
|
||||||
# X1-AG74-41076A
|
else:
|
||||||
# X1-KS52-51429E
|
c = Commander(args.data_dir)
|
||||||
|
c.run()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('-d', '--data-dir', default='data')
|
parser.add_argument('-d', '--data-dir', default='data')
|
||||||
|
parser.add_argument('-a', '--analyze', type=argparse.FileType('rb'))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
main(args)
|
main(args)
|
||||||
|
@ -8,6 +8,14 @@ from copy import copy
|
|||||||
class AnalyzerException(Exception):
|
class AnalyzerException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def path_dist(m):
|
||||||
|
t = 0
|
||||||
|
o = Point(0,0)
|
||||||
|
for w in m:
|
||||||
|
t +=w.distance(o)
|
||||||
|
o = w
|
||||||
|
return t
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Point:
|
class Point:
|
||||||
x: int
|
x: int
|
||||||
@ -70,7 +78,6 @@ class Analyzer:
|
|||||||
possibles = sorted(candidates, key=lambda m: m[2])
|
possibles = sorted(candidates, key=lambda m: m[2])
|
||||||
possibles = possibles[:10]
|
possibles = possibles[:10]
|
||||||
results = []
|
results = []
|
||||||
print(len(possibles))
|
|
||||||
for typ,m,d in possibles:
|
for typ,m,d in possibles:
|
||||||
system = m.waypoint.system
|
system = m.waypoint.system
|
||||||
p = self.find_path(origin, system)
|
p = self.find_path(origin, system)
|
||||||
@ -101,7 +108,8 @@ class Analyzer:
|
|||||||
path = []
|
path = []
|
||||||
mkts = [m.waypoint for m in self.store.all_members(orig.system, Marketplace)]
|
mkts = [m.waypoint for m in self.store.all_members(orig.system, Marketplace)]
|
||||||
cur = orig
|
cur = orig
|
||||||
|
if orig == to:
|
||||||
|
return []
|
||||||
while cur != to:
|
while cur != to:
|
||||||
best = cur
|
best = cur
|
||||||
bestdist = cur.distance(to)
|
bestdist = cur.distance(to)
|
||||||
@ -156,30 +164,25 @@ class Analyzer:
|
|||||||
|
|
||||||
def find_trade(self, system):
|
def find_trade(self, system):
|
||||||
prices = self.prices(system)
|
prices = self.prices(system)
|
||||||
|
occupied_resources = set()
|
||||||
|
for s in self.store.all('Ship'):
|
||||||
|
if s.mission != 'haul':
|
||||||
|
continue
|
||||||
|
occupied_resources.add(s.mission_state['resource'])
|
||||||
best = None
|
best = None
|
||||||
for resource, markets in prices.items():
|
for resource, markets in prices.items():
|
||||||
|
if resource in occupied_resources:
|
||||||
|
continue
|
||||||
source = sorted(markets, key=lambda x: x['buy'])[0]
|
source = sorted(markets, key=lambda x: x['buy'])[0]
|
||||||
dest = sorted(markets, key=lambda x: x['sell'])[-1]
|
dest = sorted(markets, key=lambda x: x['sell'])[-1]
|
||||||
margin = dest['sell'] -source['buy']
|
margin = dest['sell'] -source['buy']
|
||||||
if margin < 0:
|
|
||||||
continue
|
|
||||||
dist = source['wp'].distance(dest['wp'])
|
dist = source['wp'].distance(dest['wp'])
|
||||||
dist = max(dist, 0.0001)
|
dist = max(dist, 0.0001)
|
||||||
score = margin / dist
|
score = margin / dist
|
||||||
|
if margin < 0:
|
||||||
|
continue
|
||||||
o = TradeOption(resource, source['wp'], dest['wp'], margin, dist, score)
|
o = TradeOption(resource, source['wp'], dest['wp'], margin, dist, score)
|
||||||
if best is None or best.score < o.score:
|
if best is None or best.score < o.score:
|
||||||
best = o
|
best = o
|
||||||
return best
|
return best
|
||||||
|
|
||||||
def market_scan(self, system):
|
|
||||||
m = [w.waypoint for w in self.store.all_members(system, Marketplace) if not w.is_fuel()]
|
|
||||||
ms = len(m)
|
|
||||||
path = self.solve_tsp(m)
|
|
||||||
cur = Point(0,0)
|
|
||||||
for w in path:
|
|
||||||
print(w, w.distance(cur))
|
|
||||||
cur = w
|
|
||||||
far = self.store.get(Waypoint, 'X1-NN7-B7')
|
|
||||||
for w in path:
|
|
||||||
print(w, w.distance(far))
|
|
||||||
|
|
@ -178,6 +178,7 @@ class Api:
|
|||||||
def navigate(self, ship, wp):
|
def navigate(self, ship, wp):
|
||||||
data = {'waypointSymbol': str(wp)}
|
data = {'waypointSymbol': str(wp)}
|
||||||
response = self.request('post', f'my/ships/{ship}/navigate', data)
|
response = self.request('post', f'my/ships/{ship}/navigate', data)
|
||||||
|
ship.log(f'nav to {wp}')
|
||||||
ship.update(response)
|
ship.update(response)
|
||||||
|
|
||||||
def dock(self, ship):
|
def dock(self, ship):
|
||||||
@ -193,6 +194,7 @@ class Api:
|
|||||||
def flight_mode(self, ship, mode):
|
def flight_mode(self, ship, mode):
|
||||||
data = {'flightMode': mode}
|
data = {'flightMode': mode}
|
||||||
data = self.request('patch', f'my/ships/{ship}/nav', data)
|
data = self.request('patch', f'my/ships/{ship}/nav', data)
|
||||||
|
ship.update(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def jump(self, ship, waypoint):
|
def jump(self, ship, waypoint):
|
||||||
@ -243,6 +245,7 @@ class Api:
|
|||||||
'units': units
|
'units': units
|
||||||
}
|
}
|
||||||
data = self.request('post', f'my/ships/{ship}/sell', data)
|
data = self.request('post', f'my/ships/{ship}/sell', data)
|
||||||
|
ship.log(f'sell {units} of {typ}')
|
||||||
if 'cargo' in data:
|
if 'cargo' in data:
|
||||||
ship.update(data)
|
ship.update(data)
|
||||||
if 'agent' in data:
|
if 'agent' in data:
|
||||||
@ -255,6 +258,7 @@ class Api:
|
|||||||
'units': amt
|
'units': amt
|
||||||
}
|
}
|
||||||
data = self.request('post', f'my/ships/{ship}/purchase', data)
|
data = self.request('post', f'my/ships/{ship}/purchase', data)
|
||||||
|
ship.log(f'buy {amt} of {typ} at {ship.location}')
|
||||||
if 'cargo' in data:
|
if 'cargo' in data:
|
||||||
ship.update(data)
|
ship.update(data)
|
||||||
if 'agent' in data:
|
if 'agent' in data:
|
||||||
@ -271,6 +275,7 @@ class Api:
|
|||||||
'units': units
|
'units': units
|
||||||
}
|
}
|
||||||
data = self.request('post', f'my/ships/{ship.symbol}/jettison', data)
|
data = self.request('post', f'my/ships/{ship.symbol}/jettison', data)
|
||||||
|
ship.log(f'drop {units} of {typ}')
|
||||||
if 'cargo' in data:
|
if 'cargo' in data:
|
||||||
ship.update(data)
|
ship.update(data)
|
||||||
if 'agent' in data:
|
if 'agent' in data:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from nullptr.command_line import CommandLine
|
from nullptr.command_line import CommandLine
|
||||||
from nullptr.store import Store
|
from nullptr.store import Store
|
||||||
from nullptr.analyzer import Analyzer, Point
|
from nullptr.analyzer import Analyzer, Point, path_dist
|
||||||
import argparse
|
import argparse
|
||||||
from nullptr.models import *
|
from nullptr.models import *
|
||||||
from nullptr.api import Api
|
from nullptr.api import Api
|
||||||
@ -33,23 +33,27 @@ class Commander(CommandLine):
|
|||||||
self.stop_auto= False
|
self.stop_auto= False
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
######## INFRA #########
|
||||||
def handle_eof(self):
|
def handle_eof(self):
|
||||||
self.store.close()
|
self.store.close()
|
||||||
readline.write_history_file(self.hist_file)
|
readline.write_history_file(self.hist_file)
|
||||||
print("Goodbye!")
|
print("Goodbye!")
|
||||||
|
|
||||||
|
def do_pp(self):
|
||||||
|
pprint(self.api.last_result)
|
||||||
|
|
||||||
def prompt(self):
|
def prompt(self):
|
||||||
if self.ship:
|
if self.ship:
|
||||||
return f'{self.ship.symbol}> '
|
return f'{self.ship.symbol}> '
|
||||||
else:
|
else:
|
||||||
return '> '
|
return '> '
|
||||||
|
|
||||||
def has_ship(self):
|
def after_cmd(self):
|
||||||
if self.ship is not None:
|
self.store.flush()
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print('set a ship')
|
|
||||||
|
|
||||||
|
def do_auto(self):
|
||||||
|
self.centcom.run_interactive()
|
||||||
|
######## Resolvers #########
|
||||||
def ask_obj(self, typ, prompt):
|
def ask_obj(self, typ, prompt):
|
||||||
obj = None
|
obj = None
|
||||||
while obj is None:
|
while obj is None:
|
||||||
@ -59,6 +63,12 @@ class Commander(CommandLine):
|
|||||||
print('not found')
|
print('not found')
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def has_ship(self):
|
||||||
|
if self.ship is not None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print('set a ship')
|
||||||
|
|
||||||
def select_agent(self):
|
def select_agent(self):
|
||||||
agents = self.store.all(Agent)
|
agents = self.store.all(Agent)
|
||||||
agent = next(agents, None)
|
agent = next(agents, None)
|
||||||
@ -66,6 +76,27 @@ class Commander(CommandLine):
|
|||||||
agent = self.agent_setup()
|
agent = self.agent_setup()
|
||||||
return agent
|
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:
|
||||||
|
raise CommandError('not found')
|
||||||
|
|
||||||
|
def resolve_system(self, system_str):
|
||||||
|
if type(system_str) == System:
|
||||||
|
return system_str
|
||||||
|
if system_str == '':
|
||||||
|
if not self.has_ship(): return
|
||||||
|
system = self.ship.location.system
|
||||||
|
else:
|
||||||
|
system = self.store.get(System, system_str)
|
||||||
|
return system
|
||||||
|
|
||||||
|
######## First run #########
|
||||||
def agent_setup(self):
|
def agent_setup(self):
|
||||||
symbol = input('agent name: ')
|
symbol = input('agent name: ')
|
||||||
agent = self.store.get(Agent, symbol, create=True)
|
agent = self.store.get(Agent, symbol, create=True)
|
||||||
@ -90,38 +121,186 @@ class Commander(CommandLine):
|
|||||||
self.store.flush()
|
self.store.flush()
|
||||||
return agent
|
return agent
|
||||||
|
|
||||||
def resolve(self, typ, arg):
|
def do_token(self):
|
||||||
arg = arg.upper()
|
print(self.agent.token)
|
||||||
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:
|
|
||||||
raise CommandError('not found')
|
|
||||||
|
|
||||||
def resolve_system(self, system_str):
|
def do_register(self, faction):
|
||||||
if type(system_str) == System:
|
self.api.register(faction.upper())
|
||||||
return system_str
|
pprint(self.api.agent)
|
||||||
if system_str == '':
|
|
||||||
if not self.has_ship(): return
|
|
||||||
system = self.ship.location.system
|
|
||||||
else:
|
|
||||||
system = self.store.get(System, system_str)
|
|
||||||
return system
|
|
||||||
|
|
||||||
def after_cmd(self):
|
|
||||||
self.store.flush()
|
|
||||||
|
|
||||||
|
######## Fleet #########
|
||||||
def do_info(self, arg=''):
|
def do_info(self, arg=''):
|
||||||
if arg.startswith('r'):
|
if arg.startswith('r'):
|
||||||
self.api.info()
|
self.api.info()
|
||||||
|
|
||||||
pprint(self.agent, 100)
|
pprint(self.agent, 100)
|
||||||
|
|
||||||
def do_auto(self):
|
def do_ships(self, arg=''):
|
||||||
self.centcom.run_interactive()
|
if arg.startswith('r'):
|
||||||
|
r = self.api.list_ships()
|
||||||
|
else:
|
||||||
|
r = sorted(list(self.store.all('Ship')))
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
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, 5)
|
||||||
|
|
||||||
|
######## Atlas #########
|
||||||
|
def do_systems(self, page=1):
|
||||||
|
r = self.api.list_systems(int(page))
|
||||||
|
pprint(self.api.last_meta)
|
||||||
|
|
||||||
|
def do_catalog(self, system_str=''):
|
||||||
|
system = self.resolve_system(system_str)
|
||||||
|
r = self.api.list_waypoints(system)
|
||||||
|
for w in r:
|
||||||
|
if 'MARKETPLACE' in w.traits:
|
||||||
|
self.api.marketplace(w)
|
||||||
|
if w.type == 'JUMP_GATE':
|
||||||
|
self.api.jumps(w)
|
||||||
|
|
||||||
|
def do_system(self, system_str):
|
||||||
|
system = self.store.get(System, system_str)
|
||||||
|
r = self.api.list_waypoints(system)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_waypoints(self, system_str=''):
|
||||||
|
loc = None
|
||||||
|
if system_str == '':
|
||||||
|
if not self.has_ship(): return
|
||||||
|
loc = self.ship.location
|
||||||
|
system = loc.system
|
||||||
|
else:
|
||||||
|
system = self.store.get(System, system_str)
|
||||||
|
print(f'=== waypoints in {system}')
|
||||||
|
r = self.store.all_members(system, 'Waypoint')
|
||||||
|
for w in r:
|
||||||
|
|
||||||
|
wname = w.symbol.split('-')[2]
|
||||||
|
traits = ", ".join(w.traits())
|
||||||
|
typ = w.type[0]
|
||||||
|
if typ not in ['F','J'] and len(traits) == 0:
|
||||||
|
continue
|
||||||
|
if loc:
|
||||||
|
dist = loc.distance(w)
|
||||||
|
print(f'{wname:4} {typ} {dist:6} {traits}')
|
||||||
|
else:
|
||||||
|
print(f'{wname:4} {typ} {traits}')
|
||||||
|
|
||||||
|
def do_members(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
system = self.ship.location.system
|
||||||
|
pprint(list(self.store.all_members(system)))
|
||||||
|
|
||||||
|
def do_wp(self, s=''):
|
||||||
|
self.do_waypoints(s)
|
||||||
|
|
||||||
|
######## Specials #########
|
||||||
|
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, 3)
|
||||||
|
|
||||||
|
def do_atlas(self, state=None):
|
||||||
|
atlas = self.store.get(Atlas, 'ATLAS')
|
||||||
|
if state is not None:
|
||||||
|
atlas.enabled = True if state == 'on' else 'off'
|
||||||
|
pprint(atlas, 5)
|
||||||
|
|
||||||
|
def do_jumps(self, waypoint_str=None):
|
||||||
|
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)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_shipyard(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
location = self.ship.location
|
||||||
|
data = self.api.shipyard(location)
|
||||||
|
for s in must_get(data, 'ships'):
|
||||||
|
print(s['type'], s['purchasePrice'])
|
||||||
|
|
||||||
|
######## Commerce #########
|
||||||
|
def do_refuel(self, source='market'):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
from_cargo = source != 'market'
|
||||||
|
r = self.api.refuel(self.ship, from_cargo=from_cargo)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_cargo(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
print(f'== Cargo {self.ship.cargo_units}/{self.ship.cargo_capacity} ==')
|
||||||
|
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, amt=None):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
self.api.sell(self.ship, resource.upper(), amt)
|
||||||
|
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_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)
|
||||||
|
|
||||||
|
######## Mining #########
|
||||||
|
def do_siphon(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
data = self.api.siphon(self.ship)
|
||||||
|
pprint(data)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
######## Missions #########
|
||||||
def print_mission(self):
|
def print_mission(self):
|
||||||
print(f'mission: {self.ship.mission} ({self.ship.mission_status})')
|
print(f'mission: {self.ship.mission} ({self.ship.mission_status})')
|
||||||
pprint(self.ship.mission_state)
|
pprint(self.ship.mission_state)
|
||||||
@ -155,11 +334,45 @@ class Commander(CommandLine):
|
|||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
self.centcom.set_mission_param(self.ship, nm, val)
|
self.centcom.set_mission_param(self.ship, nm, val)
|
||||||
|
|
||||||
|
######## Contracts #########
|
||||||
def active_contract(self):
|
def active_contract(self):
|
||||||
for c in self.store.all('Contract'):
|
for c in self.store.all('Contract'):
|
||||||
if c.accepted and not c.fulfilled: return c
|
if c.accepted and not c.fulfilled: return c
|
||||||
raise CommandError('no active contract')
|
raise CommandError('no active contract')
|
||||||
|
|
||||||
|
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_negotiate(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
r = self.api.negotiate(self.ship)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_accept(self, c):
|
||||||
|
contract = self.resolve('Contract', c)
|
||||||
|
r = self.api.accept_contract(contract)
|
||||||
|
pprint(r)
|
||||||
|
|
||||||
|
def do_deliver(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
site = self.ship.location
|
||||||
|
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)
|
||||||
|
|
||||||
|
######## Automissions #########
|
||||||
def do_cmine(self):
|
def do_cmine(self):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
site = self.ship.location
|
site = self.ship.location
|
||||||
@ -219,204 +432,25 @@ class Commander(CommandLine):
|
|||||||
self.centcom.set_mission_param(self.ship, 'hops', markets)
|
self.centcom.set_mission_param(self.ship, 'hops', markets)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
def totaldist(self, m):
|
|
||||||
t = 0
|
|
||||||
o = Point(0,0)
|
|
||||||
for w in m:
|
|
||||||
t +=w.distance(o)
|
|
||||||
o = w
|
|
||||||
return t
|
|
||||||
|
|
||||||
def do_sprobe(self):
|
def do_sprobe(self):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
system = self.ship.location.system
|
system = self.ship.location.system
|
||||||
m = [m.waypoint for m in self.store.all_members(system, 'Marketplace')]
|
m = [m.waypoint for m in self.store.all_members(system, 'Marketplace')]
|
||||||
print("pre", self.totaldist(m))
|
print("pre", path_dist(m))
|
||||||
m = self.analyzer.solve_tsp(m)
|
m = self.analyzer.solve_tsp(m)
|
||||||
print("post", self.totaldist(m))
|
print("post", path_dist(m))
|
||||||
hops = [w.symbol for w in m]
|
hops = [w.symbol for w in m]
|
||||||
self.centcom.init_mission(self.ship, 'probe')
|
self.centcom.init_mission(self.ship, 'probe')
|
||||||
self.centcom.set_mission_param(self.ship, 'hops', hops)
|
self.centcom.set_mission_param(self.ship, 'hops', hops)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
|
######## Travel #########
|
||||||
def do_travel(self, dest):
|
def do_travel(self, dest):
|
||||||
dest = self.resolve('Waypoint', dest)
|
dest = self.resolve('Waypoint', dest)
|
||||||
self.centcom.init_mission(self.ship, 'travel')
|
self.centcom.init_mission(self.ship, 'travel')
|
||||||
self.centcom.set_mission_param(self.ship, 'dest', dest)
|
self.centcom.set_mission_param(self.ship, 'dest', dest)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
def do_register(self, faction):
|
|
||||||
self.api.register(faction.upper())
|
|
||||||
pprint(self.api.agent)
|
|
||||||
|
|
||||||
def do_systems(self, page=1):
|
|
||||||
r = self.api.list_systems(int(page))
|
|
||||||
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_defrag(self):
|
|
||||||
self.store.defrag()
|
|
||||||
|
|
||||||
def do_catalog(self, system_str=''):
|
|
||||||
system = self.resolve_system(system_str)
|
|
||||||
r = self.api.list_waypoints(system)
|
|
||||||
for w in r:
|
|
||||||
if 'MARKETPLACE' in w.traits:
|
|
||||||
self.api.marketplace(w)
|
|
||||||
if w.type == 'JUMP_GATE':
|
|
||||||
self.api.jumps(w)
|
|
||||||
|
|
||||||
def do_market_scan(self):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
loc = self.ship.location.system
|
|
||||||
pprint(self.analyzer.market_scan(loc))
|
|
||||||
|
|
||||||
def do_system(self, system_str):
|
|
||||||
system = self.store.get(System, system_str)
|
|
||||||
r = self.api.list_waypoints(system)
|
|
||||||
pprint(r)
|
|
||||||
|
|
||||||
def do_waypoints(self, system_str=''):
|
|
||||||
loc = None
|
|
||||||
if system_str == '':
|
|
||||||
if not self.has_ship(): return
|
|
||||||
loc = self.ship.location
|
|
||||||
system = loc.system
|
|
||||||
else:
|
|
||||||
system = self.store.get(System, system_str)
|
|
||||||
print(f'=== waypoints in {system}')
|
|
||||||
r = self.store.all_members(system, 'Waypoint')
|
|
||||||
for w in r:
|
|
||||||
traits = []
|
|
||||||
if w.type == 'JUMP_GATE':
|
|
||||||
traits.append('JUMP')
|
|
||||||
if w.type == 'GAS_GIANT':
|
|
||||||
traits.append('GAS')
|
|
||||||
if 'SHIPYARD' in w.traits:
|
|
||||||
traits.append('SHIPYARD')
|
|
||||||
if 'MARKETPLACE' in w.traits:
|
|
||||||
traits.append('MARKET')
|
|
||||||
|
|
||||||
|
|
||||||
if w.type == 'ASTEROID':
|
|
||||||
if 'COMMON_METAL_DEPOSITS' in w.traits:
|
|
||||||
traits.append('METAL')
|
|
||||||
if 'PRECIOUS_METAL_DEPOSITS' in w.traits:
|
|
||||||
traits.append('GOLD')
|
|
||||||
if 'MINERAL_DEPOSITS' in w.traits:
|
|
||||||
traits.append('MINS')
|
|
||||||
if 'STRIPPED' in w.traits:
|
|
||||||
traits.append('STRIPPED')
|
|
||||||
|
|
||||||
wname = w.symbol.split('-')[2]
|
|
||||||
traits = ', '.join(traits)
|
|
||||||
typ = w.type[0]
|
|
||||||
if typ not in ['F','J'] and len(traits) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if loc:
|
|
||||||
dist = loc.distance(w)
|
|
||||||
print(f'{wname:4} {typ} {dist:6} {traits}')
|
|
||||||
else:
|
|
||||||
print(f'{wname:4} {typ} {traits}')
|
|
||||||
|
|
||||||
def do_members(self):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
system = self.ship.location.system
|
|
||||||
pprint(list(self.store.all_members(system)))
|
|
||||||
|
|
||||||
def do_wp(self, s=''):
|
|
||||||
self.do_waypoints(s)
|
|
||||||
|
|
||||||
def do_marketplace(self, waypoint_str):
|
|
||||||
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
|
||||||
r = self.api.marketplace(waypoint)
|
|
||||||
|
|
||||||
def do_atlas(self, state=None):
|
|
||||||
atlas = self.store.get(Atlas, 'ATLAS')
|
|
||||||
if state is not None:
|
|
||||||
atlas.enabled = True if state == 'on' else 'off'
|
|
||||||
pprint(atlas, 5)
|
|
||||||
|
|
||||||
def do_jumps(self, waypoint_str=None):
|
|
||||||
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)
|
|
||||||
pprint(r)
|
|
||||||
|
|
||||||
def do_query(self, resource):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
location = self.ship.location
|
|
||||||
resource = resource.upper()
|
|
||||||
print('Found markets:')
|
|
||||||
for typ, m, d, plen in self.analyzer.find_closest_markets(resource, 'buy,exchange',location):
|
|
||||||
price = '?'
|
|
||||||
if resource in m.prices:
|
|
||||||
price = m.prices[resource]['buy']
|
|
||||||
print(m, typ[0], f'{plen-1:3} hops {price}')
|
|
||||||
|
|
||||||
def do_path(self):
|
|
||||||
orig = self.ask_obj(System, 'from: ')
|
|
||||||
dest = self.ask_obj(System, 'to: ')
|
|
||||||
# orig = self.store.get(System, 'X1-KS52')
|
|
||||||
# dest = self.store.get(System, 'X1-DA90')
|
|
||||||
path = self.analyzer.find_path(orig, dest)
|
|
||||||
pprint(path)
|
|
||||||
|
|
||||||
def do_ships(self, arg=''):
|
|
||||||
if arg.startswith('r'):
|
|
||||||
r = self.api.list_ships()
|
|
||||||
else:
|
|
||||||
r = sorted(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
|
|
||||||
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=''):
|
|
||||||
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):
|
def do_go(self, arg):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
system = self.ship.location.system
|
system = self.ship.location.system
|
||||||
@ -435,19 +469,6 @@ class Commander(CommandLine):
|
|||||||
self.api.orbit(self.ship)
|
self.api.orbit(self.ship)
|
||||||
pprint(self.ship)
|
pprint(self.ship)
|
||||||
|
|
||||||
def do_siphon(self):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
data = self.api.siphon(self.ship)
|
|
||||||
pprint(data)
|
|
||||||
|
|
||||||
def do_negotiate(self):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
r = self.api.negotiate(self.ship)
|
|
||||||
pprint(r)
|
|
||||||
|
|
||||||
def do_token(self):
|
|
||||||
print(self.agent.token)
|
|
||||||
|
|
||||||
def do_speed(self, speed):
|
def do_speed(self, speed):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
speed = speed.upper()
|
speed = speed.upper()
|
||||||
@ -456,25 +477,42 @@ class Commander(CommandLine):
|
|||||||
print('please choose from:', speeds)
|
print('please choose from:', speeds)
|
||||||
self.api.flight_mode(self.ship, speed)
|
self.api.flight_mode(self.ship, speed)
|
||||||
|
|
||||||
def do_refuel(self, source='market'):
|
def do_jump(self, waypoint_str):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
from_cargo = source != 'market'
|
w = self.resolve('Waypoint', waypoint_str)
|
||||||
r = self.api.refuel(self.ship, from_cargo=from_cargo)
|
self.api.jump(self.ship, w)
|
||||||
pprint(r)
|
pprint(self.ship)
|
||||||
|
|
||||||
def do_accept(self, c):
|
######## Analysis #########
|
||||||
contract = self.resolve('Contract', c)
|
def do_stats(self):
|
||||||
r = self.api.accept_contract(contract)
|
total = 0
|
||||||
pprint(r)
|
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_market(self, arg=''):
|
def do_defrag(self):
|
||||||
if arg == '':
|
self.store.defrag()
|
||||||
if not self.has_ship(): return
|
|
||||||
waypoint = self.ship.location
|
|
||||||
else:
|
def do_query(self, resource):
|
||||||
waypoint = self.resolve('Waypoint', arg)
|
if not self.has_ship(): return
|
||||||
r = self.api.marketplace(waypoint)
|
location = self.ship.location
|
||||||
pprint(r, 3)
|
resource = resource.upper()
|
||||||
|
print('Found markets:')
|
||||||
|
for typ, m, d, plen in self.analyzer.find_closest_markets(resource, 'buy,exchange',location):
|
||||||
|
price = '?'
|
||||||
|
if resource in m.prices:
|
||||||
|
price = m.prices[resource]['buy']
|
||||||
|
print(m, typ[0], f'{plen-1:3} hops {price}')
|
||||||
|
|
||||||
|
def do_findtrade(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
system = self.ship.location.system
|
||||||
|
t = self.analyzer.find_trade(system)
|
||||||
|
pprint(t)
|
||||||
|
|
||||||
def do_prices(self, resource=None):
|
def do_prices(self, resource=None):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
@ -484,68 +522,3 @@ class Commander(CommandLine):
|
|||||||
pprint(prices[resource.upper()])
|
pprint(prices[resource.upper()])
|
||||||
else:
|
else:
|
||||||
pprint(prices)
|
pprint(prices)
|
||||||
|
|
||||||
def do_cargo(self):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
print(f'== Cargo {self.ship.cargo_units}/{self.ship.cargo_capacity} ==')
|
|
||||||
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, amt=None):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
self.api.sell(self.ship, resource.upper(), amt)
|
|
||||||
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
|
|
||||||
data = self.api.shipyard(location)
|
|
||||||
for s in must_get(data, 'ships'):
|
|
||||||
print(s['type'], s['purchasePrice'])
|
|
||||||
|
|
||||||
def do_jump(self, waypoint_str):
|
|
||||||
if not self.has_ship(): return
|
|
||||||
|
|
||||||
w = self.resolve('Waypoint', waypoint_str)
|
|
||||||
self.api.jump(self.ship, w)
|
|
||||||
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)
|
|
||||||
|
@ -22,12 +22,19 @@ class Base:
|
|||||||
identifier = 'symbol'
|
identifier = 'symbol'
|
||||||
|
|
||||||
def __init__(self, symbol, store):
|
def __init__(self, symbol, store):
|
||||||
self.disable_dirty = True
|
self._disable_dirty = True
|
||||||
self.file_offset = None
|
self._file_offset = None
|
||||||
self.store = store
|
self.store = store
|
||||||
self.symbol = symbol
|
self.symbol = symbol
|
||||||
self.define()
|
self.define()
|
||||||
self.disable_dirty = False
|
self._disable_dirty = False
|
||||||
|
|
||||||
|
def __setstate__(self, d):
|
||||||
|
self.__init__(d['symbol'], d['store'])
|
||||||
|
self.__dict__.update(d)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return {k:v for k,v in self.__dict__.items() if not k.startswith('_')}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ext(cls):
|
def ext(cls):
|
||||||
@ -73,7 +80,7 @@ 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','disable_dirty', 'file_offset'] and not self.disable_dirty:
|
if not name.startswith('_') and not self._disable_dirty:
|
||||||
self.store.dirty(self)
|
self.store.dirty(self)
|
||||||
if issubclass(type(value), Base):
|
if issubclass(type(value), Base):
|
||||||
value = Reference.create(value)
|
value = Reference.create(value)
|
||||||
@ -91,11 +98,6 @@ class Base:
|
|||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def load(self, d):
|
|
||||||
self.disable_dirty = True
|
|
||||||
self.__dict__.update(d)
|
|
||||||
self.disable_dirty = False
|
|
||||||
|
|
||||||
def type(self):
|
def type(self):
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ from .base import Base
|
|||||||
from time import time
|
from time import time
|
||||||
from nullptr.util import *
|
from nullptr.util import *
|
||||||
from nullptr.models import Waypoint
|
from nullptr.models import Waypoint
|
||||||
|
import os
|
||||||
|
|
||||||
class Ship(Base):
|
class Ship(Base):
|
||||||
def define(self):
|
def define(self):
|
||||||
@ -18,6 +19,18 @@ class Ship(Base):
|
|||||||
self.mission:str = None
|
self.mission:str = None
|
||||||
self.mission_status:str = 'init'
|
self.mission_status:str = 'init'
|
||||||
self.role = None
|
self.role = None
|
||||||
|
self.frame = ''
|
||||||
|
self.speed = "CRUISE"
|
||||||
|
self._log_file = None
|
||||||
|
|
||||||
|
def log(self, m):
|
||||||
|
if self._log_file is None:
|
||||||
|
fn = os.path.join(self.store.data_dir, f'{self.symbol}.{self.ext()}.log')
|
||||||
|
self._log_file = open(fn, 'a')
|
||||||
|
ts = int(time())
|
||||||
|
m = m.strip()
|
||||||
|
self._log_file.write(f'{ts} {m}\n')
|
||||||
|
self._log_file.flush()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ext(self):
|
def ext(self):
|
||||||
@ -30,6 +43,8 @@ class Ship(Base):
|
|||||||
|
|
||||||
def update(self, d):
|
def update(self, d):
|
||||||
self.seta('status', d, 'nav.status')
|
self.seta('status', d, 'nav.status')
|
||||||
|
self.seta('speed', d, "nav.flightMode")
|
||||||
|
self.seta('frame', d, 'frame.name')
|
||||||
getter = self.store.getter(Waypoint, create=True)
|
getter = self.store.getter(Waypoint, create=True)
|
||||||
self.seta('location', d, 'nav.waypointSymbol', interp=getter)
|
self.seta('location', d, 'nav.waypointSymbol', interp=getter)
|
||||||
self.seta('cargo_capacity', d, 'cargo.capacity')
|
self.seta('cargo_capacity', d, 'cargo.capacity')
|
||||||
@ -104,16 +119,45 @@ class Ship(Base):
|
|||||||
self.update_timers()
|
self.update_timers()
|
||||||
arrival = int(self.arrival - time())
|
arrival = int(self.arrival - time())
|
||||||
cooldown = int(self.cooldown - time())
|
cooldown = int(self.cooldown - time())
|
||||||
r = self.symbol
|
|
||||||
if detail > 1:
|
role = self.role
|
||||||
if self.role is not None:
|
if role is None:
|
||||||
r += f' {self.role}'
|
role = 'none'
|
||||||
r += ' ' + self.status
|
mstatus = self.mission_status
|
||||||
r += f' [{self.fuel_current}/{self.fuel_capacity}]'
|
if mstatus == 'error':
|
||||||
r += ' ' + str(self.location)
|
mstatus = mstatus.upper()
|
||||||
|
status = self.status.lower()
|
||||||
|
if status.startswith('in_'):
|
||||||
|
status = status[3:]
|
||||||
|
|
||||||
|
if detail < 2:
|
||||||
|
r = self.symbol
|
||||||
|
elif detail == 2:
|
||||||
|
symbol = self.symbol.split('-')[1]
|
||||||
|
|
||||||
|
r = f'{symbol:<2} {role:7} {mstatus:8} {str(self.location):11}'
|
||||||
if self.is_travelling():
|
if self.is_travelling():
|
||||||
r += f' [A: {arrival}]'
|
r += f' [A: {arrival}]'
|
||||||
if self.is_cooldown():
|
if self.is_cooldown():
|
||||||
r += f' [C: {cooldown}]'
|
r += f' [C: {cooldown}]'
|
||||||
|
else:
|
||||||
|
r = f'== {self.symbol} {self.frame} ==\n'
|
||||||
|
r += f'Role: {role}\n'
|
||||||
|
r += f'Mission: {self.mission} ({mstatus})\n'
|
||||||
|
for k, v in self.mission_state.items():
|
||||||
|
r += f' {k}: {v}\n'
|
||||||
|
adj = 'to' if self.status == 'IN_TRANSIT' else 'at'
|
||||||
|
r += f'Status {self.status} {adj} {self.location}\n'
|
||||||
|
|
||||||
|
r += f'Fuel: {self.fuel_current}/{self.fuel_capacity}\n'
|
||||||
|
r += f'Speed: {self.speed}\n'
|
||||||
|
r += f'Cargo: {self.cargo_units}/{self.cargo_capacity}\n'
|
||||||
|
for res, u in self.cargo.items():
|
||||||
|
r += f' {res}: {u}\n'
|
||||||
|
if self.is_travelling():
|
||||||
|
r += f'Arrival: {arrival} seconds\n'
|
||||||
|
if self.is_cooldown():
|
||||||
|
r += f'Cooldown: {cooldown} seconds \n'
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
@ -32,3 +32,26 @@ class Waypoint(Base):
|
|||||||
def ext(self):
|
def ext(self):
|
||||||
return 'way'
|
return 'way'
|
||||||
|
|
||||||
|
def traits(self):
|
||||||
|
traits = []
|
||||||
|
if self.type == 'JUMP_GATE':
|
||||||
|
traits.append('JUMP')
|
||||||
|
if self.type == 'GAS_GIANT':
|
||||||
|
traits.append('GAS')
|
||||||
|
if 'SHIPYARD' in self.traits:
|
||||||
|
traits.append('SHIPYARD')
|
||||||
|
if 'MARKETPLACE' in self.traits:
|
||||||
|
traits.append('MARKET')
|
||||||
|
|
||||||
|
|
||||||
|
if self.type == 'ASTEROID':
|
||||||
|
if 'COMMON_METAL_DEPOSITS' in self.traits:
|
||||||
|
traits.append('METAL')
|
||||||
|
if 'PRECIOUS_METAL_DEPOSITS' in self.traits:
|
||||||
|
traits.append('GOLD')
|
||||||
|
if 'MINERAL_DEPOSITS' in self.traits:
|
||||||
|
traits.append('MINS')
|
||||||
|
if 'STRIPPED' in self.traits:
|
||||||
|
traits.append('STRIPPED')
|
||||||
|
return traits
|
||||||
|
|
@ -63,6 +63,7 @@ class ChunkHeader:
|
|||||||
class Store:
|
class Store:
|
||||||
def __init__(self, data_file, verbose=False):
|
def __init__(self, data_file, verbose=False):
|
||||||
self.init_models()
|
self.init_models()
|
||||||
|
self.data_dir = os.path.dirname(data_file)
|
||||||
self.fil = open_file(data_file)
|
self.fil = open_file(data_file)
|
||||||
self.data = {m: {} for m in self.models}
|
self.data = {m: {} for m in self.models}
|
||||||
self.system_members = {}
|
self.system_members = {}
|
||||||
@ -102,8 +103,7 @@ class Store:
|
|||||||
buf = BytesIO(data)
|
buf = BytesIO(data)
|
||||||
p = StoreUnpickler(buf, self)
|
p = StoreUnpickler(buf, self)
|
||||||
obj = p.load()
|
obj = p.load()
|
||||||
obj.file_offset = offset
|
obj._file_offset = offset
|
||||||
obj.disable_dirty = False
|
|
||||||
self.hold(obj)
|
self.hold(obj)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@ -143,27 +143,31 @@ class Store:
|
|||||||
h = ChunkHeader()
|
h = ChunkHeader()
|
||||||
h.size = sz
|
h.size = sz
|
||||||
h.used = used
|
h.used = used
|
||||||
h.offset = self.fil.tell()
|
h.offset = offset
|
||||||
h.write(self.fil)
|
h.write(self.fil)
|
||||||
return offset, h
|
return offset, h
|
||||||
|
|
||||||
def purge(self, obj):
|
def purge(self, obj):
|
||||||
if obj.file_offset is None:
|
if obj._file_offset is None:
|
||||||
return
|
return
|
||||||
self.fil.seek(obj.file_offset)
|
self.fil.seek(obj._file_offset)
|
||||||
hdr = ChunkHeader.parse(self.fil)
|
hdr = ChunkHeader.parse(self.fil)
|
||||||
hdr.in_use = False
|
hdr.in_use = False
|
||||||
self.fil.seek(obj.file_offset)
|
self.fil.seek(obj._file_offset)
|
||||||
hdr.write(self.fil)
|
hdr.write(self.fil)
|
||||||
obj.file_offset = None
|
if type(obj) in self.data and obj.symbol in self.data[type(obj)]:
|
||||||
|
del self.data[type(obj)][obj.symbol]
|
||||||
|
if obj in self.dirty_objects:
|
||||||
|
self.dirty_objects.remove(obj)
|
||||||
|
obj._file_offset = None
|
||||||
|
|
||||||
def store(self, obj):
|
def store(self, obj):
|
||||||
data = self.dump_object(obj)
|
data = self.dump_object(obj)
|
||||||
osize = len(data)
|
osize = len(data)
|
||||||
# is there an existing chunk for this obj?
|
# is there an existing chunk for this obj?
|
||||||
if obj.file_offset is not None:
|
if obj._file_offset is not None:
|
||||||
# read chunk hdr
|
# read chunk hdr
|
||||||
self.fil.seek(obj.file_offset)
|
self.fil.seek(obj._file_offset)
|
||||||
hdr = ChunkHeader.parse(self.fil)
|
hdr = ChunkHeader.parse(self.fil)
|
||||||
csize = hdr.size
|
csize = hdr.size
|
||||||
# if the chunk is too small
|
# if the chunk is too small
|
||||||
@ -171,15 +175,15 @@ class Store:
|
|||||||
# free the chunk
|
# free the chunk
|
||||||
hdr.in_use = False
|
hdr.in_use = False
|
||||||
# force a new chunk
|
# force a new chunk
|
||||||
obj.file_offset = None
|
obj._file_offset = None
|
||||||
else:
|
else:
|
||||||
# if it is big enough, update the used field
|
# if it is big enough, update the used field
|
||||||
hdr.used = osize
|
hdr.used = osize
|
||||||
self.fil.seek(hdr.offset)
|
self.fil.seek(hdr.offset)
|
||||||
hdr.write(self.fil)
|
hdr.write(self.fil)
|
||||||
|
|
||||||
if obj.file_offset is None:
|
if obj._file_offset is None:
|
||||||
obj.file_offset, hdr = self.allocate_chunk(osize)
|
obj._file_offset, hdr = self.allocate_chunk(osize)
|
||||||
# print(type(obj).__name__, hdr)
|
# print(type(obj).__name__, hdr)
|
||||||
self.fil.write(data)
|
self.fil.write(data)
|
||||||
slack = b'\x00' * (hdr.size - hdr.used)
|
slack = b'\x00' * (hdr.size - hdr.used)
|
||||||
@ -294,14 +298,17 @@ class Store:
|
|||||||
self.fil.flush()
|
self.fil.flush()
|
||||||
self.dirty_objects = set()
|
self.dirty_objects = set()
|
||||||
dur = time() - start_time
|
dur = time() - start_time
|
||||||
# print(f'flush done {it} items {dur:.2f}')
|
self.p(f'flush done {it} items {dur:.2f}')
|
||||||
|
|
||||||
def defrag(self):
|
def defrag(self):
|
||||||
nm = self.fil.name
|
nm = self.fil.name
|
||||||
self.fil.close()
|
self.fil.close()
|
||||||
|
bakfile = nm+'.bak'
|
||||||
|
if os.path.isfile(bakfile):
|
||||||
|
os.remove(bakfile)
|
||||||
os.rename(nm, nm + '.bak')
|
os.rename(nm, nm + '.bak')
|
||||||
self.fil = open(nm, 'ab+')
|
self.fil = open_file(nm)
|
||||||
for t in self.data:
|
for t in self.data:
|
||||||
for o in self.all(t):
|
for o in self.all(t):
|
||||||
o.file_offset = None
|
o._file_offset = None
|
||||||
self.store(o)
|
self.store(o)
|
||||||
|
58
nullptr/store_analyzer.py
Normal file
58
nullptr/store_analyzer.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from nullptr.store import CHUNK_MAGIC, ChunkHeader, StoreUnpickler
|
||||||
|
from hexdump import hexdump
|
||||||
|
from io import BytesIO
|
||||||
|
class FakeStore:
|
||||||
|
def get(self, typ, sym, create=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
class StoreAnalyzer:
|
||||||
|
def __init__(self, verbose=False):
|
||||||
|
self.verbose = verbose
|
||||||
|
|
||||||
|
def load_obj(self, f, sz):
|
||||||
|
buf = BytesIO(f.read(sz))
|
||||||
|
p = StoreUnpickler(buf, FakeStore())
|
||||||
|
obj = p.load()
|
||||||
|
return obj
|
||||||
|
print(obj.symbol, type(obj).__name__)
|
||||||
|
|
||||||
|
def run(self, f):
|
||||||
|
lastpos = 0
|
||||||
|
pos = 0
|
||||||
|
objs = {}
|
||||||
|
result = True
|
||||||
|
f.seek(0)
|
||||||
|
while True:
|
||||||
|
lastpos = pos
|
||||||
|
pos = f.tell()
|
||||||
|
m = f.read(8)
|
||||||
|
if len(m) < 8:
|
||||||
|
break
|
||||||
|
if m != CHUNK_MAGIC:
|
||||||
|
print(f'missing magic at {pos}')
|
||||||
|
result = False
|
||||||
|
self.investigate(f, lastpos)
|
||||||
|
break
|
||||||
|
f.seek(-8, 1)
|
||||||
|
h = ChunkHeader.parse(f)
|
||||||
|
if self.verbose:
|
||||||
|
print(h, pos)
|
||||||
|
if h.in_use:
|
||||||
|
obj = self.load_obj(f, h.used)
|
||||||
|
kobj = obj.symbol, type(obj).__name__
|
||||||
|
if kobj in objs:
|
||||||
|
print(f'Double object {kobj} prev {objs[kobj]} latest {h}')
|
||||||
|
result = False
|
||||||
|
objs[kobj] = h
|
||||||
|
else:
|
||||||
|
f.seek(h.used, 1)
|
||||||
|
f.seek(h.size - h.used, 1)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def investigate(self, f, lastpos):
|
||||||
|
print(f'dumping 1024 bytes from {lastpos}')
|
||||||
|
f.seek(lastpos, 0)
|
||||||
|
d = f.read(1024)
|
||||||
|
|
||||||
|
hexdump(d)
|
||||||
|
print(d.index(CHUNK_MAGIC))
|
@ -1,7 +1,10 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
import tempfile
|
||||||
from nullptr.store import Store
|
from nullptr.store import Store, ChunkHeader
|
||||||
from nullptr.models import Base
|
from nullptr.models import Base
|
||||||
|
from io import BytesIO
|
||||||
|
import os
|
||||||
|
from nullptr.store_analyzer import StoreAnalyzer
|
||||||
|
|
||||||
class Dummy(Base):
|
class Dummy(Base):
|
||||||
def define(self):
|
def define(self):
|
||||||
@ -16,7 +19,7 @@ class Dummy(Base):
|
|||||||
return 'dum'
|
return 'dum'
|
||||||
|
|
||||||
def f(self, detail=1):
|
def f(self, detail=1):
|
||||||
r = super().f(detail)
|
r = super().f(detail) + '.' + self.ext()
|
||||||
if detail >2:
|
if detail >2:
|
||||||
r += f' c:{self.count}'
|
r += f' c:{self.count}'
|
||||||
return r
|
return r
|
||||||
@ -50,10 +53,19 @@ class TestStore(unittest.TestCase):
|
|||||||
dum2 = self.s.get(Dummy, "7",create=True)
|
dum2 = self.s.get(Dummy, "7",create=True)
|
||||||
self.reopen()
|
self.reopen()
|
||||||
dum = self.s.get(Dummy, "5")
|
dum = self.s.get(Dummy, "5")
|
||||||
|
old_off = dum._file_offset
|
||||||
|
self.assertTrue(old_off is not None)
|
||||||
dum.data = "A" * 1000
|
dum.data = "A" * 1000
|
||||||
dum.count = 1337
|
dum.count = 1337
|
||||||
|
self.s.flush()
|
||||||
|
new_off = dum._file_offset
|
||||||
|
self.assertTrue(new_off is not None)
|
||||||
|
self.assertNotEqual(old_off, new_off)
|
||||||
self.reopen()
|
self.reopen()
|
||||||
dum = self.s.get(Dummy, "5")
|
dum = self.s.get(Dummy, "5")
|
||||||
|
newer_off = dum._file_offset
|
||||||
|
self.assertTrue(newer_off is not None)
|
||||||
|
self.assertEqual(new_off, newer_off)
|
||||||
self.assertEqual(1337, dum.count)
|
self.assertEqual(1337, dum.count)
|
||||||
|
|
||||||
def test_purge(self):
|
def test_purge(self):
|
||||||
@ -64,6 +76,8 @@ class TestStore(unittest.TestCase):
|
|||||||
self.s.flush()
|
self.s.flush()
|
||||||
self.s.purge(dum)
|
self.s.purge(dum)
|
||||||
self.reopen()
|
self.reopen()
|
||||||
|
dum = self.s.get(Dummy, "5")
|
||||||
|
self.assertIsNone(dum)
|
||||||
dum2 = self.s.get(Dummy, "7")
|
dum2 = self.s.get(Dummy, "7")
|
||||||
self.assertEqual(1337, dum2.count)
|
self.assertEqual(1337, dum2.count)
|
||||||
|
|
||||||
@ -98,4 +112,59 @@ class TestStore(unittest.TestCase):
|
|||||||
dum3 = self.s.get(Dummy, "9")
|
dum3 = self.s.get(Dummy, "9")
|
||||||
self.assertEqual(1338, dum3.count)
|
self.assertEqual(1338, dum3.count)
|
||||||
|
|
||||||
|
def test_dont_relocate(self):
|
||||||
|
dum = self.s.get(Dummy, "5", create=True)
|
||||||
|
dum.data = "A"
|
||||||
|
self.s.flush()
|
||||||
|
old_off = dum._file_offset
|
||||||
|
self.reopen()
|
||||||
|
dum2 = self.s.get(Dummy, "5")
|
||||||
|
dum2.data = "BCDE"
|
||||||
|
self.s.flush()
|
||||||
|
new_off = dum._file_offset
|
||||||
|
self.assertEqual(old_off, new_off)
|
||||||
|
|
||||||
|
def test_chunk_header(self):
|
||||||
|
a = ChunkHeader()
|
||||||
|
a.size = 123
|
||||||
|
a.used = 122
|
||||||
|
a.in_use = True
|
||||||
|
b = BytesIO()
|
||||||
|
a.write(b)
|
||||||
|
b.seek(0)
|
||||||
|
c = ChunkHeader.parse(b)
|
||||||
|
self.assertEqual(c.size, a.size)
|
||||||
|
self.assertEqual(c.used, a.used)
|
||||||
|
self.assertEqual(c.in_use, True)
|
||||||
|
c.in_use = False
|
||||||
|
b.seek(0)
|
||||||
|
c.write(b)
|
||||||
|
b.seek(0)
|
||||||
|
d = ChunkHeader.parse(b)
|
||||||
|
self.assertEqual(d.size, a.size)
|
||||||
|
self.assertEqual(d.used, a.used)
|
||||||
|
self.assertEqual(d.in_use, False)
|
||||||
|
|
||||||
|
def test_mass(self):
|
||||||
|
num = 50
|
||||||
|
for i in range(num):
|
||||||
|
dum = self.s.get(Dummy, str(i), create=True)
|
||||||
|
dum.data = str(i)
|
||||||
|
dum.count = 0
|
||||||
|
self.reopen()
|
||||||
|
sz = os.stat(self.store_file.name).st_size
|
||||||
|
for j in range(50):
|
||||||
|
for i in range(num):
|
||||||
|
dum = self.s.get(Dummy, str(i))
|
||||||
|
# this works because j is max 49, and the slack is 64
|
||||||
|
# so no growing is needed
|
||||||
|
self.assertEqual(dum.data, "B" * j + str(i))
|
||||||
|
self.assertEqual(dum.count, j)
|
||||||
|
dum.data = "B" * (j+1) + str(i)
|
||||||
|
dum.count += 1
|
||||||
|
self.reopen()
|
||||||
|
sz2 = os.stat(self.store_file.name).st_size
|
||||||
|
self.assertEqual(sz, sz2)
|
||||||
|
an = StoreAnalyzer().run(self.store_file)
|
||||||
|
self.assertTrue(an)
|
||||||
|
|
@ -1,2 +1,3 @@
|
|||||||
requests
|
requests
|
||||||
readline
|
readline
|
||||||
|
hexdump
|
||||||
|
Loading…
Reference in New Issue
Block a user