Compare commits
31 Commits
mining-aga
...
3010a8186d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3010a8186d | ||
|
|
d6fe1cf183 | ||
|
|
bb64880822 | ||
|
|
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 |
83
Readme.md
Normal file
83
Readme.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# 0ptr script for spacetraders.io
|
||||||
|
This is my script for running spacetraders.io. It has been completely written and operated from an iphone.
|
||||||
|
|
||||||
|
Because i'm using a less-than-optimal development platform, some 'best-practice' code standards have been thrown out of the window. For instance, some variables have obscure names just to keep them short.
|
||||||
|
|
||||||
|
The script offers a command-line interface allowing you to do most ST operations by hand. More importantly, it offers the functionality to assign missions to ships and execute them automatically.
|
||||||
|
|
||||||
|
# Getting started
|
||||||
|
I'm using the [pythonista app](http://omz-software.com/pythonista/) to develop and test the script. Just open main.py and hit run.
|
||||||
|
|
||||||
|
To keep the script running for longer periods of time, I'm using a VPS with docker. Start the script like so:
|
||||||
|
|
||||||
|
`docker run -v 0ptr:/data -ti 0ptr`
|
||||||
|
|
||||||
|
The script will ask for an agent name. Make sure you choose a unique one. After hitting enter, you will be presented with the prompt: `>`
|
||||||
|
|
||||||
|
Now you'll want to register. You need to provide your faction:
|
||||||
|
|
||||||
|
`> register cosmic`
|
||||||
|
|
||||||
|
If all goes well, you should have an account now.
|
||||||
|
|
||||||
|
Next is building up our view of the universe: the atlas.
|
||||||
|
|
||||||
|
`> universe`
|
||||||
|
|
||||||
|
This command will iterate over all systems and obtain all waypoint information. **this will take several hours**. However, if you are impatient, you can stop the process after a couple of minutes by hitting enter. It should at least have indexed your local system to continue.
|
||||||
|
|
||||||
|
Now, let's see what ships we own.
|
||||||
|
|
||||||
|
`> ships refresh`
|
||||||
|
|
||||||
|
This will populate our internal cache of present ships and their state. After this, the state is updated continually by each command that alters it. You should be able to omit the 'refresh' after this.
|
||||||
|
|
||||||
|
We should also populate the cache of contracts:
|
||||||
|
|
||||||
|
`> contracts refresh`
|
||||||
|
|
||||||
|
Same here: you can omit the 'refresh' from now on and view your contracts with the bare `contracts` command without sending a http request.
|
||||||
|
|
||||||
|
You are now ready to begin operations.
|
||||||
|
|
||||||
|
# Operations
|
||||||
|
To get a ship to do something, you first have to select it:
|
||||||
|
|
||||||
|
```
|
||||||
|
> ship 1
|
||||||
|
KILO-1 DOCKED [1035/1200] X1-YU85-03282C
|
||||||
|
KILO-1>
|
||||||
|
```
|
||||||
|
|
||||||
|
This prints the current state of the ship. Notice that the prompt now includes the name of the ship. Any command you now give will be executed by that ship.
|
||||||
|
|
||||||
|
A quick, non-complete list of commands:
|
||||||
|
|
||||||
|
* go: intra-system travel to another waypoint
|
||||||
|
* orbit
|
||||||
|
* dock
|
||||||
|
* market: show local market data
|
||||||
|
* shipyard: show local shipyard data
|
||||||
|
* jumps: show local jumpgate data
|
||||||
|
* mission: set ships mission type
|
||||||
|
* mine: mine and deliver a resource
|
||||||
|
* probe: find resource prices in nearby markets
|
||||||
|
* survey: generate surveys in a loop
|
||||||
|
* travel: travel to any waypoint in the universe using jumpgates (assuming a route is found)
|
||||||
|
* haul: buy goods at a site and bring them to a destination
|
||||||
|
* mset: set a mission parameter
|
||||||
|
* purchase: purchase a ship at a shipyard
|
||||||
|
* buy: buy a resource at the local market
|
||||||
|
* sell: sell a resource at the local market
|
||||||
|
* jump: jump to a remote system using the local jumpgate
|
||||||
|
* cmine: configures a mining mission for the current contract
|
||||||
|
* chaul configures a hauling mission for the current contract
|
||||||
|
* cprobe: configures a probing mission for the current contract
|
||||||
|
* query: lists nearest markets and buy prices (if known) of a resource
|
||||||
|
|
||||||
|
Look in commander.py for more available commands.
|
||||||
|
|
||||||
|
# Data storage
|
||||||
|
The script stores each object in a separate json file in the data directory. The default location for this directory is in your workingdir. The docker image places the data dir in '/data'
|
||||||
|
|
||||||
|
If you want to re-register an agent, or there is a sever reset, just delete all data in that directory.
|
||||||
@@ -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,14 +30,42 @@ 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
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
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
|
from threading import Thread
|
||||||
|
|
||||||
|
class CentralCommandError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class CentralCommand:
|
class CentralCommand:
|
||||||
def __init__(self, store, api):
|
def __init__(self, store, api):
|
||||||
self.missions = {}
|
self.missions = {}
|
||||||
@@ -27,14 +30,6 @@ class CentralCommand:
|
|||||||
mission = self.missions[ship]
|
mission = self.missions[ship]
|
||||||
mission.step()
|
mission.step()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def wait_for_stop(self):
|
|
||||||
try:
|
|
||||||
input()
|
|
||||||
except EOFError:
|
|
||||||
pass
|
|
||||||
self.stopping = True
|
|
||||||
print('stopping...')
|
|
||||||
|
|
||||||
def run_interactive(self):
|
def run_interactive(self):
|
||||||
print('auto mode. hit enter to stop')
|
print('auto mode. hit enter to stop')
|
||||||
@@ -43,7 +38,17 @@ class CentralCommand:
|
|||||||
t.start()
|
t.start()
|
||||||
self.run()
|
self.run()
|
||||||
print('manual mode')
|
print('manual mode')
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_stop(self):
|
||||||
|
try:
|
||||||
|
input()
|
||||||
|
except EOFError:
|
||||||
|
pass
|
||||||
|
self.stopping = True
|
||||||
|
print('stopping...')
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.update_missions()
|
self.update_missions()
|
||||||
while not self.stopping:
|
while not self.stopping:
|
||||||
@@ -86,6 +91,21 @@ class CentralCommand:
|
|||||||
m = self.missions[s]
|
m = self.missions[s]
|
||||||
m.next_step = max(s.cooldown, s.arrival)
|
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.store, self.api)
|
m = create_mission(mtype, s, self.store, self.api)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -82,13 +82,6 @@ class Commander(CommandLine):
|
|||||||
def do_auto(self):
|
def do_auto(self):
|
||||||
self.centcom.run_interactive()
|
self.centcom.run_interactive()
|
||||||
|
|
||||||
def set_mission(self, arg=''):
|
|
||||||
if arg == 'none':
|
|
||||||
arg = None
|
|
||||||
self.ship.mission = arg
|
|
||||||
self.ship.mission_status = 'init'
|
|
||||||
self.centcom.start_mission(self.ship)
|
|
||||||
|
|
||||||
def print_mission(self):
|
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)
|
||||||
@@ -96,12 +89,15 @@ class Commander(CommandLine):
|
|||||||
def do_mission(self, arg=''):
|
def do_mission(self, arg=''):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
if arg:
|
if arg:
|
||||||
self.set_mission(arg)
|
self.centcom.init_mission(self.ship, arg)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
def do_mset(self, args):
|
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
|
if not self.has_ship(): return
|
||||||
nm, val = args.split(' ')
|
|
||||||
self.centcom.set_mission_param(self.ship, nm, val)
|
self.centcom.set_mission_param(self.ship, nm, val)
|
||||||
|
|
||||||
def active_contract(self):
|
def active_contract(self):
|
||||||
@@ -118,20 +114,66 @@ class Commander(CommandLine):
|
|||||||
raise CommandError('no delivery')
|
raise CommandError('no delivery')
|
||||||
resource = delivery['trade_symbol']
|
resource = delivery['trade_symbol']
|
||||||
destination = delivery['destination']
|
destination = delivery['destination']
|
||||||
self.set_mission('mine')
|
self.centcom.init_mission(self.ship, 'mine')
|
||||||
self.centcom.set_mission_param(self.ship, 'site', site)
|
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, 'resource', resource)
|
||||||
self.centcom.set_mission_param(self.ship, 'destination', destination)
|
self.centcom.set_mission_param(self.ship, 'dest', destination)
|
||||||
self.centcom.set_mission_param(self.ship, 'contract', contract.symbol)
|
self.centcom.set_mission_param(self.ship, 'contract', contract.symbol)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
|
def do_chaul(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
if len(ship.cargo) > 0:
|
||||||
|
raise CommandError('please dump cargo first')
|
||||||
|
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())
|
||||||
site = self.ship.location_str
|
pprint(self.api.agent)
|
||||||
contract = self.active_contract()
|
|
||||||
self.do_mission('mine')
|
|
||||||
self.centcom.set_mission_param(self.ship, 'site', site)
|
|
||||||
self.centcom.set_mission_param(self.ship, 'contract', contract)
|
|
||||||
|
|
||||||
def do_universe(self, page=1):
|
def do_universe(self, page=1):
|
||||||
self.atlas_builder.run(page)
|
self.atlas_builder.run(page)
|
||||||
@@ -184,16 +226,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: ')
|
||||||
@@ -227,6 +269,10 @@ class Commander(CommandLine):
|
|||||||
resource = delivery['trade_symbol']
|
resource = delivery['trade_symbol']
|
||||||
self.api.deliver(self.ship, resource, contract)
|
self.api.deliver(self.ship, resource, contract)
|
||||||
pprint(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 != '':
|
||||||
@@ -309,7 +355,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
|
||||||
|
|||||||
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
|
||||||
|
|
||||||
@@ -2,9 +2,12 @@ from nullptr.store import Store
|
|||||||
from nullptr.models.base import Base
|
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 nullptr.util import *
|
from nullptr.util import *
|
||||||
|
|
||||||
@@ -22,6 +25,8 @@ class MissionParam:
|
|||||||
return str(val)
|
return str(val)
|
||||||
elif self.cls == int:
|
elif self.cls == int:
|
||||||
return int(val)
|
return int(val)
|
||||||
|
elif self.cls == list:
|
||||||
|
return [i.strip() for i in val.split(',')]
|
||||||
elif issubclass(self.cls, Base):
|
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:
|
||||||
@@ -42,6 +47,7 @@ class Mission:
|
|||||||
self.store = store
|
self.store = store
|
||||||
self.api = api
|
self.api = api
|
||||||
self.next_step = 0
|
self.next_step = 0
|
||||||
|
self.analyzer = Analyzer(self.store)
|
||||||
|
|
||||||
def sts(self, nm, v):
|
def sts(self, nm, v):
|
||||||
if issubclass(type(v), Base):
|
if issubclass(type(v), Base):
|
||||||
@@ -121,95 +127,21 @@ 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.rst(Waypoint,'site')
|
|
||||||
# todo optimize
|
|
||||||
for s in self.store.all(Survey):
|
|
||||||
if resource in s.deposits and site.symbol == s.waypoint():
|
|
||||||
return s
|
|
||||||
return None
|
|
||||||
|
|
||||||
def step_extract(self):
|
|
||||||
survey = self.get_survey()
|
|
||||||
print('using survey:', str(survey))
|
|
||||||
result = self.api.extract(self.ship, survey)
|
|
||||||
symbol = sg(result,'extraction.yield.symbol')
|
|
||||||
units = sg(result,'extraction.yield.units')
|
|
||||||
print('extracted:', units, symbol)
|
|
||||||
self.next_step = self.ship.cooldown
|
|
||||||
if self.ship.cargo_units < self.ship.cargo_capacity:
|
|
||||||
return 'more'
|
|
||||||
else:
|
|
||||||
return 'done'
|
|
||||||
|
|
||||||
def step_sell(self, except_resource=True):
|
|
||||||
target = self.st('resource')
|
|
||||||
market = self.store.get('Marketplace', self.ship.location_str)
|
|
||||||
sellables = market.sellable_items(self.ship.cargo.keys())
|
|
||||||
if target in sellables and except_resource:
|
|
||||||
sellables.remove(target)
|
|
||||||
if len(sellables) == 0:
|
|
||||||
return 'done'
|
|
||||||
self.api.sell(self.ship, sellables[0])
|
|
||||||
if len(sellables) == 1:
|
|
||||||
return 'done'
|
|
||||||
else:
|
|
||||||
return 'more'
|
|
||||||
|
|
||||||
def step_go_dest(self):
|
def step_go_dest(self):
|
||||||
destination = self.rst(Waypoint, '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.rst(Contract, 'contract')
|
contract = self.rst(Contract, 'contract')
|
||||||
delivery = self.st('delivery')
|
delivery = self.st('delivery')
|
||||||
@@ -223,55 +155,96 @@ class MiningMission(Mission):
|
|||||||
return 'done'
|
return 'done'
|
||||||
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.rst(Contract, '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):
|
|
||||||
site = self.rst(Waypoint,'site')
|
|
||||||
if self.ship.location() == site:
|
|
||||||
return
|
|
||||||
self.api.navigate(self.ship, site)
|
|
||||||
self.next_step = self.ship.arrival
|
|
||||||
|
|
||||||
class SurveyMission(Mission):
|
|
||||||
def start_state(self):
|
|
||||||
return 'survey'
|
|
||||||
|
|
||||||
|
def travel_steps(self, nm, destination, next_step):
|
||||||
def steps(self):
|
destination = self.st(destination)
|
||||||
|
calc = partial(self.step_calculate_traject, destination)
|
||||||
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, store, api):
|
|
||||||
types = {
|
|
||||||
'survey': SurveyMission,
|
|
||||||
'mine': MiningMission
|
|
||||||
}
|
|
||||||
if mtype not in types:
|
|
||||||
logging.warning(f'invalid mission type {mtype}')
|
|
||||||
return
|
|
||||||
m = types[mtype](ship, store, api)
|
|
||||||
return m
|
|
||||||
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')
|
||||||
|
|
||||||
@@ -7,11 +7,12 @@ class Base:
|
|||||||
store: object
|
store: object
|
||||||
|
|
||||||
def __init__(self, symbol, store):
|
def __init__(self, symbol, store):
|
||||||
self.disable_dirty = False
|
self.disable_dirty = True
|
||||||
self.store = store
|
self.store = store
|
||||||
self.symbol = symbol
|
self.symbol = symbol
|
||||||
self.define()
|
self.define()
|
||||||
|
self.disable_dirty = False
|
||||||
|
|
||||||
def define(self):
|
def define(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class Marketplace(SystemMember):
|
|||||||
price['symbol'] = symbol
|
price['symbol'] = symbol
|
||||||
price['buy'] = mg(g, 'purchasePrice')
|
price['buy'] = mg(g, 'purchasePrice')
|
||||||
price['sell'] = mg(g, 'sellPrice')
|
price['sell'] = mg(g, 'sellPrice')
|
||||||
|
price['volume'] = mg(g, 'tradeVolume')
|
||||||
prices[symbol] = price
|
prices[symbol] = price
|
||||||
self.prices = prices
|
self.prices = prices
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ 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
|
||||||
|
|||||||
41
store.md
Normal file
41
store.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# The store format
|
||||||
|
This project uses a custom database format just because we can.
|
||||||
|
|
||||||
|
The script reads the entire store on startup. Each object can br altered in memory. A 'dirty' status is set when an object is changed. periodically the script will flush the store, writing all changes back to disk.
|
||||||
|
|
||||||
|
## Objects
|
||||||
|
First lets discuss what we are storing.
|
||||||
|
|
||||||
|
Objects are identified by type and symbol. A symbol is an uppercase alphanumeric string that optionally contains '-' chars. the type is a lowecase alphanumeric string of length 3.
|
||||||
|
|
||||||
|
For each type, the store has a class defined that is used to load objects of that type.
|
||||||
|
|
||||||
|
An object identifier is its symbol and type joined with a '.' character.
|
||||||
|
|
||||||
|
Some examples of object identifiers:
|
||||||
|
|
||||||
|
* X1-J84.sys
|
||||||
|
* X1-J84-0828772.way
|
||||||
|
* CAPT-J-1.shp
|
||||||
|
|
||||||
|
A waypoint is always part of a system. This is also visible because the waypoint symbol is prefixed by the system symbol. However, this relation is not enforced or used by the store. The symbol is an opaque string.
|
||||||
|
|
||||||
|
An object has attributes. Values of attributes can be strings, ints, floats, bools, lists, dicts and references. lists and dicts can also only contain the values listed. References are pointers to other objects in the store.
|
||||||
|
|
||||||
|
## Indices
|
||||||
|
An index is a dict with a string as key and a list of objects as value. The dict is built when loading the store. when the index is iterated, each object is re-checked and removed if necessary.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
* store.load(fil) loads all objects
|
||||||
|
* store.get(type, symbol, create=False) fetches the object. If create==False: None if it wasnt present
|
||||||
|
* store.all(type) generator for all objects of a goven type
|
||||||
|
* store.cleanup() removes all expired objects
|
||||||
|
* store.flush() writes all dirty objects to disk
|
||||||
|
*
|
||||||
|
|
||||||
|
type may be a class or a string containing the name of a class. The type should be a subclass of models.base.Base
|
||||||
|
|
||||||
|
# file format
|
||||||
|
the file format is a header followed by a number of blocks. the size and number of blocks are dictated by the header.
|
||||||
|
|
||||||
Reference in New Issue
Block a user