0ptr/nullptr/central_command.py

252 lines
7.1 KiB
Python
Raw Normal View History

from nullptr.store import Store
2023-06-17 18:18:14 +00:00
from nullptr.models.ship import Ship
from nullptr.missions import create_mission, get_mission_class
2024-01-20 19:33:50 +00:00
from nullptr.models.waypoint import Waypoint
2024-01-13 20:42:49 +00:00
from random import choice, randrange
2024-01-25 18:57:49 +00:00
from time import sleep, time
from threading import Thread
from nullptr.atlas_builder import AtlasBuilder
2024-01-24 18:03:57 +00:00
from nullptr.analyzer import Analyzer, Point
2023-06-17 18:18:14 +00:00
class CentralCommandError(Exception):
pass
2023-06-17 18:18:14 +00:00
class CentralCommand:
def __init__(self, store, api):
2023-06-17 18:18:14 +00:00
self.missions = {}
self.stopping = False
self.store = store
2024-01-04 20:34:31 +00:00
self.analyzer = Analyzer(store)
2023-06-17 18:18:14 +00:00
self.api = api
self.atlas_builder = AtlasBuilder(store, api)
2024-01-16 18:13:10 +00:00
self.update_missions()
2023-06-17 18:18:14 +00:00
def get_ready_missions(self):
result = []
for ship, mission in self.missions.items():
if mission.is_ready():
result.append(ship)
return result
2024-01-16 18:13:10 +00:00
def single_step(self, ship):
if ship not in self.missions:
print('ship has no mission')
mission = self.missions[ship]
mission.step()
2023-06-17 18:18:14 +00:00
def tick(self):
2024-01-04 20:34:31 +00:00
self.update_missions()
2023-06-17 18:18:14 +00:00
missions = self.get_ready_missions()
if len(missions) == 0: return False
2023-06-17 18:18:14 +00:00
ship = choice(missions)
mission = self.missions[ship]
mission.step()
return True
def run_interactive(self):
print('auto mode. hit enter to stop')
t = Thread(target=self.wait_for_stop)
t.daemon = True
t.start()
self.run()
print('manual mode')
def wait_for_stop(self):
try:
input()
except EOFError:
pass
self.stopping = True
print('stopping...')
2023-06-17 18:18:14 +00:00
def run(self):
while not self.stopping:
did_step = True
request_counter = self.api.requests_sent
2024-01-25 18:57:49 +00:00
start = time()
while request_counter == self.api.requests_sent and did_step:
did_step = self.tick()
if request_counter == self.api.requests_sent:
self.atlas_builder.do_work()
else:
2023-07-18 10:44:53 +00:00
pass # print('nowork')
self.store.flush()
2024-01-25 18:57:49 +00:00
dur = time() - start
# print(f'step {dur:.03}')
zs = 0.5 - dur
if zs > 0:
sleep(zs)
2023-06-17 18:18:14 +00:00
self.stopping = False
def stop(self):
self.stopping = True
def set_mission_param(self, ship, nm, val):
if ship not in self.missions:
print('set a mission for this ship first')
return
mission = self.missions[ship]
params = mission.params()
if not nm in params:
print(f'{nm} is not a valid param')
return
param = params[nm]
try:
parsed_val = param.parse(val, self.store)
2023-06-17 18:18:14 +00:00
except ValueError as e:
raise MissionError(e)
2023-06-17 18:18:14 +00:00
return
ship.set_mission_state(nm, parsed_val)
2024-01-04 20:34:31 +00:00
def smipa(self,s,n,v):
self.set_mission_param(s,n,v)
2023-06-17 18:18:14 +00:00
def update_missions(self):
for s in self.store.all(Ship):
2024-01-04 20:34:31 +00:00
if s.mission_status == 'done':
s.mission = None
2023-06-17 18:18:14 +00:00
if s.mission is None:
if s in self.missions:
self.stop_mission(s)
2024-01-04 20:34:31 +00:00
if s.mission is None:
self.assign_mission(s)
if s.mission is not None and s not in self.missions:
2023-06-17 18:18:14 +00:00
self.start_mission(s)
if s in self.missions:
m = self.missions[s]
2024-01-04 20:34:31 +00:00
2024-01-24 18:03:57 +00:00
def find_gas(self, system):
2024-01-20 19:33:50 +00:00
m = [w for w in self.store.all_members(system, 'Waypoint') if w.type == 'GAS_GIANT']
if len(m)==0:
raise CentralCommandError('no gas giant found')
return m[0]
2024-01-24 18:03:57 +00:00
def find_metal(self, system):
m = [w for w in self.store.all_members(system, Waypoint) if 'COMMON_METAL_DEPOSITS' in w.traits]
if len(m) == 0:
return None
origin = Point(0,0)
m = sorted(m, key=lambda w: w.distance(origin))
return m[0]
2024-01-04 20:34:31 +00:00
def assign_mission(self, s):
2024-01-13 20:42:49 +00:00
if s.role == 'trader':
self.assign_trade(s)
elif s.role == 'probe':
self.assign_probe(s)
2024-01-20 19:33:50 +00:00
elif s.role == 'siphon':
self.assign_siphon(s)
elif s.role == 'hauler':
self.assign_hauler(s)
2024-01-24 18:03:57 +00:00
elif s.role == 'surveyor':
self.assign_survey(s)
2024-01-25 18:57:49 +00:00
elif s.role == 'miner':
self.assign_mine(s)
def assign_mine(self, s):
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
resources = s.crew.resources
self.init_mission(s, 'mine')
self.smipa(s, 'site', w)
self.smipa(s, 'resources', resources)
2024-01-24 18:03:57 +00:00
def assign_survey(self, s):
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
self.init_mission(s, 'survey')
self.smipa(s, 'site', w)
2024-01-20 19:33:50 +00:00
def assign_hauler(self, s):
2024-01-24 18:03:57 +00:00
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
resources = s.crew.resources
resource = choice(resources)
m = self.analyzer.best_sell_market(s.location.system, resource)
s.log(f'assigning haul mission from {w} to {m}')
2024-01-20 19:33:50 +00:00
self.init_mission(s, 'haul')
self.smipa(s, 'site', w)
self.smipa(s, 'dest', m)
2024-01-21 19:21:38 +00:00
self.smipa(s, 'resources', resources)
2024-01-20 19:33:50 +00:00
def assign_siphon(self, s):
2024-01-24 18:03:57 +00:00
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
2024-01-20 19:33:50 +00:00
self.init_mission(s, 'siphon')
self.smipa(s, 'site', w)
2024-01-13 20:42:49 +00:00
def assign_probe(self, s):
system = s.location.system
m = [m.waypoint for m in self.store.all_members(system, 'Marketplace')]
m = self.analyzer.solve_tsp(m)
hops = [w.symbol for w in m]
start_hop = randrange(0, len(hops))
2024-01-24 18:03:57 +00:00
s.log(f'Assigning {s} to probe {len(hops)} starting at {hops[start_hop]}')
2024-01-13 20:42:49 +00:00
self.init_mission(s, 'probe')
self.smipa(s, 'hops', hops)
self.smipa(s, 'next-hop', start_hop)
def assign_trade(self, s):
2024-01-04 20:34:31 +00:00
t = self.analyzer.find_trade(s.location.system)
if t is None:
print(f"No trade for {s} found. Idling")
self.init_mission(s,'idle')
self.smipa(s, 'seconds', 600)
return
2024-01-24 18:03:57 +00:00
s.log(f'assigning {s} to deliver {t.resource} from {t.source} to {t.dest} at a margin of {t.margin}')
2024-01-13 20:42:49 +00:00
self.init_mission(s, 'trade')
2024-01-04 20:34:31 +00:00
self.smipa(s, 'site', t.source)
self.smipa(s, 'dest', t.dest)
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)
2024-01-04 20:34:31 +00:00
2024-01-06 06:17:53 +00:00
def restart_mission(self, s, status='init'):
2024-01-04 20:34:31 +00:00
if s not in self.missions:
raise CentralCommandError("no mission assigned")
2024-01-06 06:17:53 +00:00
s.mission_status = status
2023-06-17 18:18:14 +00:00
def start_mission(self, s):
mtype = s.mission
m = create_mission(mtype, s, self.store, self.api)
2023-06-17 18:18:14 +00:00
self.missions[s] = m
2024-01-20 19:33:50 +00:00
m.status(s.mission_status)
2023-06-17 18:18:14 +00:00
return m
def stop_mission(self, s):
if s in self.missions:
del self.missions[s]
2024-01-24 18:03:57 +00:00
def create_default_crews(self):
system = self.api.agent.headquarters.system
gas_w = self.find_gas(system)
metal_w = self.find_metal(system)
metal = self.store.get('Crew', 'METAL', create=True)
metal.site = metal_w
2024-01-25 18:57:49 +00:00
metal.resources = ['COPPER_ORE','IRON_ORE','ALUMINUM_ORE']
2024-01-24 18:03:57 +00:00
gas = self.store.get('Crew', 'GAS', create=True)
gas.site = gas_w
gas.resources = ['HYDROCARBON','LIQUID_HYDROGEN','LIQUID_NITROGEN']
return [gas, metal]