This commit is contained in:
Richard 2024-01-24 19:03:57 +01:00
parent b0ef68a721
commit d8eb1c4954
12 changed files with 208 additions and 198 deletions

View File

@ -324,8 +324,8 @@ class Api:
'shipSymbol': dship.symbol
}
data = self.request('post', f'my/ships/{sship.symbol}/transfer', data)
sship.log(f'transferred {amt} {typ} to {dship}')
dship.log(f'received {amt} {typ} from {sship}')
sship.log(f'tra {amt} {typ} to {dship}')
dship.log(f'rec {amt} {typ} from {sship}', 10)
if 'cargo' in data:
sship.update(data)
dship.put_cargo(typ, amt)

View File

@ -6,7 +6,7 @@ from random import choice, randrange
from time import sleep
from threading import Thread
from nullptr.atlas_builder import AtlasBuilder
from nullptr.analyzer import Analyzer
from nullptr.analyzer import Analyzer, Point
class CentralCommandError(Exception):
pass
@ -112,13 +112,20 @@ class CentralCommand:
if s in self.missions:
m = self.missions[s]
def find_gas(self, s):
system = s.location.system
def find_gas(self, system):
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]
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]
def assign_mission(self, s):
if s.role == 'trader':
self.assign_trade(s)
@ -128,18 +135,34 @@ class CentralCommand:
self.assign_siphon(s)
elif s.role == 'hauler':
self.assign_hauler(s)
elif s.role == 'surveyor':
self.assign_survey(s)
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)
def assign_hauler(self, s):
w = self.find_gas(s)
m = self.analyzer.best_sell_market(s.location.system, 'HYDROCARBON')
resources = list(m.prices.keys())
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}')
self.init_mission(s, 'haul')
self.smipa(s, 'site', w)
self.smipa(s, 'dest', m)
self.smipa(s, 'resources', resources)
def assign_siphon(self, s):
w = self.find_gas(s)
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
self.init_mission(s, 'siphon')
self.smipa(s, 'site', w)
@ -149,7 +172,7 @@ class CentralCommand:
m = self.analyzer.solve_tsp(m)
hops = [w.symbol for w in m]
start_hop = randrange(0, len(hops))
print(f'Assigning {s} to probe {len(hops)} starting at {hops[start_hop]}')
s.log(f'Assigning {s} to probe {len(hops)} starting at {hops[start_hop]}')
self.init_mission(s, 'probe')
self.smipa(s, 'hops', hops)
self.smipa(s, 'next-hop', start_hop)
@ -161,7 +184,7 @@ class CentralCommand:
self.init_mission(s,'idle')
self.smipa(s, 'seconds', 600)
return
print(f'assigning {s} to deliver {t.resource} from {t.source} to {t.dest} at a margin of {t.margin}')
s.log(f'assigning {s} to deliver {t.resource} from {t.source} to {t.dest} at a margin of {t.margin}')
self.init_mission(s, 'trade')
self.smipa(s, 'site', t.source)
self.smipa(s, 'dest', t.dest)
@ -196,3 +219,18 @@ class CentralCommand:
def stop_mission(self, s):
if s in self.missions:
del self.missions[s]
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
metal.resources = ['COPPER_ORE','IRON_ORE']
gas = self.store.get('Crew', 'GAS', create=True)
gas.site = gas_w
gas.resources = ['HYDROCARBON','LIQUID_HYDROGEN','LIQUID_NITROGEN']
return [gas, metal]

View File

@ -56,8 +56,8 @@ class Commander(CommandLine):
self.centcom.run_interactive()
def do_log(self, level):
if not self.has_ship(): return
self.ship._log_level = int(level)
ship = self.has_ship()
ship._log_level = int(level)
######## Resolvers #########
def ask_obj(self, typ, prompt):
@ -71,9 +71,9 @@ class Commander(CommandLine):
def has_ship(self):
if self.ship is not None:
return True
return self.ship
else:
print('set a ship')
raise CommandError('set a ship')
def select_agent(self):
agents = self.store.all(Agent)
@ -96,8 +96,8 @@ class Commander(CommandLine):
if type(system_str) == System:
return system_str
if system_str == '':
if not self.has_ship(): return
system = self.ship.location.system
ship = self.has_ship()
system = ship.location.system
else:
system = self.store.get(System, system_str)
return system
@ -106,12 +106,12 @@ class Commander(CommandLine):
if type(w) == Waypoint:
return w
if w == '':
if not self.has_ship(): return
return self.ship.location
ship = self.has_ship()
return ship.location
p = w.split('-')
if len(p) == 1:
if not self.has_ship(): return
s = self.ship.location.system
ship = self.has_ship()
s = ship.location.system
w = f'{s}-{w}'
r = self.store.get(Waypoint, w)
if r is None:
@ -203,8 +203,8 @@ class Commander(CommandLine):
def do_waypoints(self, system_str=''):
loc = None
if system_str == '':
if not self.has_ship(): return
loc = self.ship.location
ship = self.has_ship()
loc = ship.location
system = loc.system
else:
system = self.store.get(System, system_str)
@ -224,8 +224,8 @@ class Commander(CommandLine):
print(f'{wname:4} {typ} {traits}')
def do_members(self):
if not self.has_ship(): return
system = self.ship.location.system
ship = self.has_ship()
system = ship.location.system
pprint(list(self.store.all_members(system)))
def do_wp(self, s=''):
@ -245,8 +245,8 @@ class Commander(CommandLine):
def do_jumps(self, waypoint_str=None):
if waypoint_str is None:
if not self.has_ship(): return
waypoint = self.ship.location
ship = self.has_ship()
waypoint = ship.location
else:
waypoint = self.store.get(Waypoint, waypoint_str.upper())
r = self.api.jumps(waypoint)
@ -266,49 +266,49 @@ class Commander(CommandLine):
######## Commerce #########
def do_refuel(self, source='market'):
if not self.has_ship(): return
ship = self.has_ship()
from_cargo = source != 'market'
r = self.api.refuel(self.ship, from_cargo=from_cargo)
r = self.api.refuel(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():
ship = self.has_ship()
print(f'== Cargo {ship.cargo_units}/{ship.cargo_capacity} ==')
for c, units in ship.cargo.items():
print(f'{units:4d} {c}')
def do_buy(self, resource, amt=None):
if not self.has_ship(): return
ship = self.has_ship()
if amt is None:
amt = self.ship.cargo_capacity - self.ship.cargo_units
self.api.buy(self.ship, resource.upper(), amt)
amt = ship.cargo_capacity - ship.cargo_units
self.api.buy(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)
ship = self.has_ship()
self.api.sell(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())
ship = self.has_ship()
self.api.jettison(ship, resource.upper())
self.do_cargo()
def do_transfer(self, resource, dship, amount=None):
if not self.has_ship(): return
ship = self.has_ship()
resource = resource.upper()
avail = self.ship.get_cargo(resource)
avail = ship.get_cargo(resource)
if amount is None: amount = avail
amount = int(amount)
if avail < amount:
raise CommandError('resource not in cargo')
dship = self.resolve_ship(dship)
self.api.transfer(self.ship, dship, resource, amount)
self.api.transfer(ship, dship, resource, amount)
def do_purchase(self, ship_type):
if not self.has_ship(): return
location = self.ship.location
ship = self.has_ship()
location = ship.location
ship_type = ship_type.upper()
if not ship_type.startswith('SHIP'):
ship_type = 'SHIP_' + ship_type
@ -317,23 +317,23 @@ class Commander(CommandLine):
######## Mining #########
def do_siphon(self):
if not self.has_ship(): return
data = self.api.siphon(self.ship)
ship = self.has_ship()
data = self.api.siphon(ship)
def do_survey(self):
if not self.has_ship(): return
r = self.api.survey(self.ship)
ship = self.has_ship()
r = self.api.survey(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
ship = self.has_ship()
survey = None
if survey_str != '':
survey = self.resolve('Survey', survey_str)
result = self.api.extract(self.ship, survey)
result = self.api.extract(ship, survey)
symbol = mg(result,'extraction.yield.symbol')
units = mg(result,'extraction.yield.units')
@ -346,39 +346,51 @@ class Commander(CommandLine):
pprint(self.ship.mission_state)
def do_role(self, role):
roles = [None, 'trader', 'probe', 'siphon', 'hauler']
if not self.has_ship(): return
roles = [None, 'trader', 'probe', 'siphon', 'hauler', 'surveyor', 'miner']
ship = self.has_ship()
if role == 'none':
role = None
if role not in roles:
print(f'role {role} not found. Choose from {roles}')
return
self.ship.role = role
ship.role = role
def do_mission(self, arg=''):
if not self.has_ship(): return
ship = self.has_ship()
if arg:
self.centcom.init_mission(self.ship, arg)
self.centcom.init_mission(ship, arg)
self.print_mission()
def do_mrestart(self, status='init'):
if not self.has_ship(): return
self.centcom.restart_mission(self.ship, status)
ship = self.has_ship()
self.centcom.restart_mission(ship, status)
self.print_mission()
def do_mstep(self):
if not self.has_ship(): return
self.centcom.single_step(self.ship)
ship = self.has_ship()
self.centcom.single_step(ship)
self.print_mission()
def do_mreset(self):
if not self.has_ship(): return
self.ship.mission_state = {}
ship = self.has_ship()
ship.mission_state = {}
def do_mset(self, nm, val):
if not self.has_ship(): return
self.centcom.set_mission_param(self.ship, nm, val)
ship = self.has_ship()
self.centcom.set_mission_param(ship, nm, val)
def do_crew(self, arg):
ship = self.has_ship()
crew = self.resolve('Crew', arg)
ship.crew = crew
pprint(ship)
######## Crews #########
def do_create_crews(self):
crews = self.centcom.create_default_crews()
for c in crews:
print(f'{c.symbol:15s} {c.site}')
######## Contracts #########
def active_contract(self):
for c in self.store.all('Contract'):
@ -393,8 +405,8 @@ class Commander(CommandLine):
pprint(r)
def do_negotiate(self):
if not self.has_ship(): return
r = self.api.negotiate(self.ship)
ship = self.has_ship()
r = self.api.negotiate(ship)
pprint(r)
def do_accept(self, c):
@ -403,118 +415,59 @@ class Commander(CommandLine):
pprint(r)
def do_deliver(self):
if not self.has_ship(): return
site = self.ship.location
ship = self.has_ship()
site = 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)
self.api.deliver(ship, resource, contract)
pprint(contract)
def do_fulfill(self):
contract = self.active_contract()
self.api.fulfill(contract)
######## Automissions #########
def do_cmine(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']
destination = delivery['destination']
self.centcom.init_mission(self.ship, 'mine')
self.centcom.set_mission_param(self.ship, 'site', site)
self.centcom.set_mission_param(self.ship, 'resource', resource)
self.centcom.set_mission_param(self.ship, 'dest', destination)
self.centcom.set_mission_param(self.ship, 'contract', contract)
self.print_mission()
def do_chaul(self):
if not self.has_ship(): return
if len(self.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, 'trade')
self.centcom.set_mission_param(self.ship, 'site', site)
self.centcom.set_mission_param(self.ship, 'resource', resource)
self.centcom.set_mission_param(self.ship, 'dest', destination)
self.centcom.set_mission_param(self.ship, 'contract', contract)
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) == 0:
print('no market found')
return
markets = [ mkt[1] for mkt in m]
markets = self.analyzer.solve_tsp(markets)
self.centcom.init_mission(self.ship, 'probe')
self.centcom.set_mission_param(self.ship, 'hops', markets)
self.print_mission()
######## Travel #########
def do_travel(self, dest):
ship = self.has_ship()
dest = self.resolve('Waypoint', dest)
self.centcom.init_mission(self.ship, 'travel')
self.centcom.set_mission_param(self.ship, 'dest', dest)
self.centcom.init_mission(ship, 'travel')
self.centcom.set_mission_param(ship, 'dest', dest)
self.print_mission()
def do_go(self, arg):
if not self.has_ship(): return
system = self.ship.location.system
ship = self.has_ship()
system = ship.location.system
symbol = f'{system}-{arg}'
dest = self.resolve('Waypoint', symbol)
self.api.navigate(self.ship, dest)
pprint(self.ship)
self.api.navigate(ship, dest)
pprint(ship)
def do_dock(self):
if not self.has_ship(): return
self.api.dock(self.ship)
pprint(self.ship)
ship = self.has_ship()
self.api.dock(ship)
pprint(ship)
def do_orbit(self):
if not self.has_ship(): return
self.api.orbit(self.ship)
pprint(self.ship)
ship = self.has_ship()
self.api.orbit(ship)
pprint(ship)
def do_speed(self, speed):
if not self.has_ship(): return
ship = self.has_ship()
speed = speed.upper()
speeds = ['DRIFT', 'STEALTH','CRUISE','BURN']
if speed not in speeds:
print('please choose from:', speeds)
self.api.flight_mode(self.ship, speed)
self.api.flight_mode(ship, speed)
def do_jump(self, waypoint_str):
if not self.has_ship(): return
ship = self.has_ship()
w = self.resolve('Waypoint', waypoint_str)
self.api.jump(self.ship, w)
pprint(self.ship)
self.api.jump(ship, w)
pprint(ship)
######## Analysis #########
def do_server(self):
@ -563,8 +516,8 @@ class Commander(CommandLine):
print('Dirty: ', obj in self.store.dirty_objects)
def do_query(self, resource):
if not self.has_ship(): return
location = self.ship.location
ship = self.has_ship()
location = ship.location
resource = resource.upper()
print('Found markets:')
for typ, m, d, plen in self.analyzer.find_closest_markets(resource, 'buy,exchange',location):
@ -574,14 +527,14 @@ class Commander(CommandLine):
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
ship = self.has_ship()
system = ship.location.system
t = self.analyzer.find_trade(system)
pprint(t)
def do_prices(self, resource=None):
if not self.has_ship(): return
system = self.ship.location.system
ship = self.has_ship()
system = ship.location.system
prices = self.analyzer.prices(system)
if resource is not None:
prices = {resource: prices[resource.upper()]}
@ -592,7 +545,7 @@ class Commander(CommandLine):
print(f"{m['wp'].symbol:12s} {m['category']} {m['volume']:5d} {m['buy']:5d} {m['sell']:5d}")
def do_path(self, waypoint_str):
if not self.has_ship(): return
ship = self.has_ship()
w = self.resolve('Waypoint', waypoint_str)
p = self.analyzer.find_nav_path(self.ship.location, w, self.ship.fuel_capacity)
p = self.analyzer.find_nav_path(ship.location, w, ship.fuel_capacity)
pprint(p)

View File

@ -111,7 +111,7 @@ class Mission:
}
def step_done(self):
self.ship.log(f'mission finished with balance {self.balance()}', 3)
self.ship.log(f'mission {type(self).__name__} finished with balance {self.balance()}', 3)
def is_waiting(self):
if self.next_step > time() or self.ship.cooldown > time() or self.ship.arrival > time():

View File

@ -9,39 +9,23 @@ class MiningMission(BaseMission):
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)
'resources': MissionParam(list, True)
}
p
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'
}),
**self.travel_steps('to', 'site', 'extract'),
'extract': (self.step_extract, {
'more': 'extract',
'full': 'unload'
}),
'unload': (self.step_unload, {
'more': 'unload',
'done': 'done'
})
}
def get_survey(self):

View File

@ -41,7 +41,8 @@ class SiphonMission(BaseMission):
else:
space = h.cargo_space()
amt = min(space, amt)
self.api.transfer(self.ship, h, r, amt)
if amt > 0:
self.api.transfer(self.ship, h, r, amt)
return 'more'
def steps(self):

View File

@ -1,11 +1,19 @@
from nullptr.missions.base import BaseMission, MissionParam
from nullptr.models.waypoint import Waypoint
class SurveyMission(BaseMission):
def start_state(self):
return 'survey'
return 'travel-to'
@classmethod
def params(cls):
return {
'site': MissionParam(Waypoint, True),
}
def steps(self):
return {
**self.travel_steps('to', 'site', 'survey'),
'survey': (self.step_survey, 'survey')
}

View File

@ -9,5 +9,6 @@ from nullptr.models.ship import Ship
from nullptr.models.contract import Contract
from nullptr.models.survey import Survey
from nullptr.models.atlas import Atlas
from nullptr.models.crew import Crew
__all__ = [ 'Waypoint', 'Sector', 'Ship', 'Survey', 'System', 'Agent', 'Marketplace', 'Jumpgate', 'Contract', 'Base', 'Atlas' ]
__all__ = [ 'Waypoint', 'Sector', 'Ship', 'Survey', 'System', 'Agent', 'Marketplace', 'Jumpgate', 'Contract', 'Base', 'Atlas', 'Crew' ]

View File

@ -1,12 +1,16 @@
from .base import Base
from nullptr.models.waypoint import Waypoint
class Agent(Base):
def define(self):
self.token: str = None
self.credits: int = 0
self.headquarters: Waypoint = None
def update(self, d):
self.seta('credits', d)
getter = self.store.getter(Waypoint, create=True)
self.seta('headquarters', d, interp=getter)
@classmethod
def ext(self):

16
nullptr/models/crew.py Normal file
View File

@ -0,0 +1,16 @@
from .base import Base
class Crew(Base):
@classmethod
def ext(self):
return 'crw'
def define(self):
self.site = None
self.resources = []
def f(self, detail=1):
r = super().f(detail)
if detail >2:
r += f'\nSite: {self.site}'
return r

View File

@ -19,6 +19,7 @@ class Ship(Base):
self.mission:str = None
self.mission_status:str = 'init'
self.role = None
self.crew = None
self.frame = ''
self.speed = "CRUISE"
self._log_file = None
@ -29,11 +30,12 @@ class Ship(Base):
fn = os.path.join(self.store.data_dir, f'{self.symbol}.{self.ext()}.log')
self._log_file = open(fn, 'a')
ts = strftime('%Y%m%d %H%M%S')
sts = strftime('%H%M%S')
m = m.strip()
self._log_file.write(f'{ts} {m}\n')
self._log_file.flush()
if l <= self._log_level:
print(f'{self} {ts} {m}')
print(f'{self} {sts} {m}')
@classmethod
def ext(self):
@ -139,6 +141,9 @@ class Ship(Base):
role = self.role
if role is None:
role = 'none'
crew = 'none'
if self.crew is not None:
crew = self.crew.symbol
mstatus = self.mission_status
if mstatus == 'error':
mstatus = mstatus.upper()
@ -160,7 +165,7 @@ class Ship(Base):
r += f' [C: {cooldown}]'
else:
r = f'== {self.symbol} {self.frame} ==\n'
r += f'Role: {role}\n'
r += f'Role: {crew} / {role}\n'
r += f'Mission: {self.mission} ({mstatus})\n'
for k, v in self.mission_state.items():
if type(v) == list:

View File

@ -47,15 +47,15 @@ class Waypoint(Base):
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')
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
def f(self, detail=1):