Merge remote-tracking branch 'refs/remotes/origin/master'
This commit is contained in:
		
						commit
						293b6d1c3e
					
				| @ -231,3 +231,24 @@ class Api: | ||||
|      | ||||
|   def shipyard(self, wp): | ||||
|     return self.request('get', f'systems/{wp.system()}/waypoints/{wp}/shipyard') | ||||
| 
 | ||||
|   def extract(self, ship, survey=None): | ||||
|     data = {} | ||||
|     if survey is not None: | ||||
|       data['survey'] = survey.api_dict() | ||||
|     try: | ||||
|       data =  self.request('post', f'my/ships/{ship}/extract', data=data) | ||||
|     except ApiError as e: | ||||
|       if e.code in [ 4221, 4224]: | ||||
|         survey.exhausted = True | ||||
|       else: | ||||
|         raise e | ||||
|     ship.update(data) | ||||
|     return ship | ||||
|    | ||||
|   def survey(self, ship): | ||||
|     data =  self.request('post', f'my/ships/{ship}/survey') | ||||
|     ship.update(data) | ||||
|     result = self.store.update_list('Survey', mg(data, 'surveys')) | ||||
|     return result | ||||
|      | ||||
							
								
								
									
										73
									
								
								nullptr/central_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								nullptr/central_command.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| from nullptr.store import store | ||||
| from nullptr.models.ship import Ship | ||||
| from nullptr.mission import * | ||||
| from random import choice | ||||
| from time import sleep | ||||
| 
 | ||||
| class CentralCommand: | ||||
|   def __init__(self, api): | ||||
|     self.missions = {} | ||||
|     self.stopping = False | ||||
|     self.api = api | ||||
|     self.update_missions() | ||||
|    | ||||
|   def get_ready_missions(self): | ||||
|     result = [] | ||||
|     for ship, mission in self.missions.items(): | ||||
|       if mission.is_ready(): | ||||
|         result.append(ship) | ||||
|     return result | ||||
|      | ||||
|   def tick(self): | ||||
|     missions = self.get_ready_missions() | ||||
|     if len(missions) == 0: return | ||||
|     ship = choice(missions) | ||||
|     mission = self.missions[ship] | ||||
|     mission.step() | ||||
| 
 | ||||
|   def run(self): | ||||
|     self.update_missions() | ||||
|     while not self.stopping: | ||||
|       self.tick() | ||||
|       self.api.save() | ||||
|       sleep(0.5) | ||||
|     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) | ||||
|     except ValueError as e: | ||||
|       print(e) | ||||
|       return | ||||
|     print('ok') | ||||
|     ship.mission_state[nm] = parsed_val | ||||
|      | ||||
|   def update_missions(self): | ||||
|     for s in store.all(Ship): | ||||
|       if s.mission is None: | ||||
|         if s in self.missions: | ||||
|           self.stop_mission(s) | ||||
|       elif s not in self.missions: | ||||
|         self.start_mission(s) | ||||
|    | ||||
|   def start_mission(self, s): | ||||
|     mtype = s.mission | ||||
|     m = create_mission(mtype, s, self.api) | ||||
|     self.missions[s] = m | ||||
|     return m | ||||
|      | ||||
|   def stop_mission(self, s): | ||||
|     if s in self.missions: | ||||
|       del self.missions[s] | ||||
| @ -104,6 +104,8 @@ class Commander(CommandLine): | ||||
|         traits.append('SHIPYARD') | ||||
|       if w.type == 'JUMP_GATE': | ||||
|         traits.append('JUMP') | ||||
|       if w.type == 'ASTEROID_FIELD': | ||||
|         traits.append('ASTROIDS') | ||||
|       print(w.symbol.split('-')[2], ', '.join(traits)) | ||||
|    | ||||
|   def do_wp(self, s=''): | ||||
| @ -255,3 +257,8 @@ class Commander(CommandLine): | ||||
|       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) | ||||
							
								
								
									
										268
									
								
								nullptr/mission.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								nullptr/mission.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,268 @@ | ||||
| from nullptr.store import store | ||||
| from nullptr.models.waypoint import Waypoint | ||||
| from nullptr.models.contract import Contract | ||||
| from nullptr.models.survey import Survey | ||||
| from nullptr.models.ship import Ship | ||||
| from time import time | ||||
| import logging | ||||
| from util import * | ||||
| 
 | ||||
| class MissionParam: | ||||
|   def __init__(self, cls, required=True, default=None): | ||||
|     self.cls = cls | ||||
|     self.required = required | ||||
|     self.default = default | ||||
|    | ||||
|   def parse(self, val): | ||||
|     if self.cls == str: | ||||
|       return str(val) | ||||
|     elif self.cls == int: | ||||
|       return int(val) | ||||
|     elif issubclass(self.cls, StoreObject): | ||||
|       data = store.get(self.cls, val) | ||||
|       if data is None: | ||||
|         raise ValueError('object not found') | ||||
|       return data | ||||
|     else: | ||||
|       raise ValueError('unknown param typr') | ||||
|      | ||||
| class Mission: | ||||
|   ship: Ship | ||||
|   next_step: int = 0 | ||||
|    | ||||
|   @classmethod | ||||
|   def params(cls): | ||||
|     return { | ||||
|        | ||||
|     } | ||||
|    | ||||
|   def __init__(self, ship, api): | ||||
|     self.ship = ship | ||||
|     self.api = api | ||||
| 
 | ||||
|   def sts(self, nm, v): | ||||
|     self.ship.mission_state[nm] = v | ||||
|    | ||||
|   def st(self, nm): | ||||
|     if not nm in self.ship.mission_state: | ||||
|       return None | ||||
|     return self.ship.mission_state[nm] | ||||
|    | ||||
|   def status(self, nw=None): | ||||
|     if nw is None: | ||||
|       return self.ship.mission_status | ||||
|     else: | ||||
|       self.ship.mission_status = nw | ||||
|        | ||||
|   def start_state(self): | ||||
|     return 'done' | ||||
|    | ||||
|   def error(self, msg): | ||||
|     self.status('error') | ||||
|     print(msg) | ||||
| 
 | ||||
|   def init_state(self): | ||||
|     for name, param in self.params().items(): | ||||
|       if param.required and param.default is None: | ||||
|         if not name in self.ship.mission_state: | ||||
|           return self.error(f'Param {name} not set') | ||||
|     self.status(self.start_state()) | ||||
|    | ||||
|   def steps(self): | ||||
|     return { | ||||
|        | ||||
|     } | ||||
|    | ||||
|   def step_done(self): | ||||
|     logging.info(f'mission finished for {self.ship}') | ||||
|      | ||||
|   def is_waiting(self): | ||||
|     return self.next_step > time() | ||||
|    | ||||
|   def is_finished(self): | ||||
|     return self.status() in ['done','error'] | ||||
|    | ||||
|   def is_ready(self): | ||||
|     return not self.is_waiting() and not self.is_finished() | ||||
|    | ||||
|   def step(self): | ||||
|     steps = self.steps() | ||||
|     if self.status() == 'init': | ||||
|       self.init_state() | ||||
|     status = self.status() | ||||
|     if not status in steps: | ||||
|       logging.warning(f"Invalid mission status {status}") | ||||
|       self.status('error') | ||||
|       return | ||||
|     handler, next_step = steps[status] | ||||
|     try: | ||||
|       result = handler() | ||||
|     except Exception as e: | ||||
|       logging.error(e) | ||||
|       self.status('error') | ||||
|       return | ||||
|     if type(next_step) == str: | ||||
|       self.status(next_step) | ||||
|     elif type(next_step) == dict: | ||||
|       if result not in next_step: | ||||
|         logging.warning(f'Invalid step result {result}') | ||||
|         self.status('error') | ||||
|         return | ||||
|       else: | ||||
|         self.status(next_step[result]) | ||||
|     print(f'{self.ship} {status} -> {self.status()}') | ||||
| 
 | ||||
| 
 | ||||
| class MiningMission(Mission):  | ||||
|   @classmethod | ||||
|   def params(cls): | ||||
|     return { | ||||
|       'site': MissionParam(Waypoint, True), | ||||
|       'resource': MissionParam(str, True), | ||||
|       'destination': MissionParam(Waypoint, True), | ||||
|       'delivery': MissionParam(str, True, 'deliver'), | ||||
|       'contract': MissionParam(Contract, False) | ||||
|     } | ||||
|    | ||||
|   def start_state(self): | ||||
|     return 'go_site' | ||||
|      | ||||
|   def steps(self): | ||||
|     return { | ||||
|       'extract': (self.step_extract, { | ||||
|         'done': 'dock', | ||||
|         'more': 'extract' | ||||
|       }), | ||||
|       'dock': (self.step_dock, 'sell'), | ||||
|       'sell': (self.step_sell, { | ||||
|         'more': 'sell', | ||||
|         'done': 'orbit', | ||||
|       }), | ||||
|       'orbit': (self.step_orbit, 'jettison'), | ||||
|       'jettison': (self.step_dispose, { | ||||
|         'more': 'jettison', | ||||
|         'done': 'extract', | ||||
|         'full': 'go_dest' | ||||
|       }), | ||||
|       'go_dest': (self.step_go_dest, 'dock_dest'), | ||||
|       'dock_dest': (self.step_dock, 'unload'), | ||||
|       'unload': (self.step_unload, { | ||||
|         'done': 'refuel', | ||||
|         'more': 'unload' | ||||
|       }), | ||||
|       'refuel': (self.step_refuel, 'orbit_dest'), | ||||
|       'orbit_dest': (self.step_orbit, 'go_site'), | ||||
|       'go_site': (self.step_go_site, 'extract') | ||||
|     } | ||||
|    | ||||
|   def get_survey(self): | ||||
|     resource = self.st('resource') | ||||
|     site = self.st('site') | ||||
|     # todo optimize | ||||
|     for s in store.all(Survey): | ||||
|       if resource in s.deposits and site == s.waypoint: | ||||
|         return s | ||||
|     return None | ||||
|      | ||||
|   def step_extract(self): | ||||
|     survey = self.get_survey() | ||||
|     print('using survey:', str(survey))  | ||||
|     result = self.api.extract(self.ship, survey) | ||||
|     symbol = sg(result,'extraction.yield.symbol') | ||||
|     units  = sg(result,'extraction.yield.units') | ||||
|     print('extracted:', units, symbol) | ||||
|     self.next_step = self.ship.cooldown | ||||
|     if self.ship.cargo_units < self.ship.cargo_capacity: | ||||
|       return 'more' | ||||
|     else: | ||||
|       return 'done' | ||||
|      | ||||
|   def step_sell(self, except_resource=True): | ||||
|     target = self.st('resource') | ||||
|     market = self.api.market(self.ship.location) | ||||
|     sellables = market.sellable_items(self.ship.cargo.keys()) | ||||
|     if target in sellables and except_resource: | ||||
|       sellables.remove(target) | ||||
|     if len(sellables) == 0: | ||||
|       return 'done' | ||||
|     self.api.sell(self.ship, sellables[0]) | ||||
|     if len(sellables) == 1: | ||||
|       return 'done' | ||||
|     else: | ||||
|       return 'more' | ||||
|        | ||||
|   def step_go_dest(self): | ||||
|     destination = self.st('destination') | ||||
|     if self.ship.location == destination: | ||||
|       return | ||||
|     self.api.navigate(self.ship, destination) | ||||
|     self.next_step = self.ship.arrival | ||||
| 
 | ||||
|   def step_dock(self): | ||||
|     self.api.dock(self.ship) | ||||
| 
 | ||||
|   def step_unload(self): | ||||
|     contract = self.st('contract') | ||||
|     delivery = self.st('delivery') | ||||
|     if delivery == 'sell': | ||||
|       return self.step_sell(False) | ||||
|     typs = self.ship.deliverable_cargo(contract) | ||||
|     if len(typs) == 0: | ||||
|       return 'done' | ||||
|     self.api.deliver(self.ship, typs[0], contract) | ||||
|     if len(typs) == 1: | ||||
|       return 'done' | ||||
|     else: | ||||
|       return 'more' | ||||
|    | ||||
|   def step_refuel(self): | ||||
|     self.api.refuel(self.ship) | ||||
|      | ||||
|   def step_dispose(self): | ||||
|     contract = self.st('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' | ||||
|    | ||||
|    | ||||
|   def step_orbit(self): | ||||
|     self.api.orbit(self.ship) | ||||
| 
 | ||||
|   def step_go_site(self): | ||||
|     site = self.st('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 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 | ||||
| 
 | ||||
| def create_mission(mtype, ship, api): | ||||
|   types = { | ||||
|     'survey': SurveyMission, | ||||
|     'mine': MiningMission | ||||
|   } | ||||
|   if mtype not in types: | ||||
|     logging.warning(f'invalid mission type {mtype}') | ||||
|     return | ||||
|   m = types[mtype](ship, api) | ||||
|   return m | ||||
							
								
								
									
										50
									
								
								nullptr/models/survey.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								nullptr/models/survey.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| from time import time | ||||
| from nullptr.util import * | ||||
| from .system_member import SystemMember | ||||
| 
 | ||||
| size_names = ['SMALL','MODERATE','LARGE'] | ||||
| 
 | ||||
| class Survey(SystemMember): | ||||
|   identifier = 'signature' | ||||
|   type: str = '' | ||||
|   deposits: list[str] = [] | ||||
|   size: int = 0 | ||||
|   expires: int = 0 | ||||
|   expires_str: str = '' | ||||
|   exhausted: bool = False | ||||
| 
 | ||||
|   @classmethod | ||||
|   def ext(cls): | ||||
|     return 'svy' | ||||
|    | ||||
|   def path(self): | ||||
|     sector, system, waypoint, signature = self.symbol.split('-') | ||||
|     return f'atlas/{sector}/{system[0:1]}/{system}/{waypoint}-{signature}.{self.ext()}' | ||||
|      | ||||
|    | ||||
|   def is_expired(self): | ||||
|     return time() > self.expires or self.exhausted | ||||
|    | ||||
|   def api_dict(self): | ||||
|     return { | ||||
|       'signature': self.symbol, | ||||
|       'symbol': str(self.waypoint), | ||||
|       'deposits': [{'symbol': d} for d in self.deposits], | ||||
|       'expiration': self.expires_str, | ||||
|       'size': size_names[self.size] | ||||
|     } | ||||
|    | ||||
|   def update(self, d): | ||||
|     sz = must_get(d, 'size') | ||||
|     self.size = size_names.index(sz) | ||||
|     self.deposits = [d['symbol'] for d in must_get(d, 'deposits')] | ||||
|     self.seta('expires',d, 'expiration',parse_timestamp) | ||||
|     self.seta('expires_str',d, 'expiration') | ||||
|    | ||||
|   def f(self, detail=1): | ||||
|     result = self.symbol | ||||
|     if detail > 1: | ||||
|       result += ' ' + ','.join(self.deposits) | ||||
|       minutes = max(self.expires - time(), 0) //60 | ||||
|       result += ' ' + str(int(minutes)) + 'm' | ||||
|     return result | ||||
| @ -8,6 +8,7 @@ from nullptr.models.system_member import SystemMember | ||||
| from nullptr.models.jumpgate import Jumpgate | ||||
| from nullptr.models.ship import Ship | ||||
| from nullptr.models.contract import Contract | ||||
| from nullptr.models.survey import Survey | ||||
| from os.path import isfile, dirname, isdir | ||||
| import os | ||||
| from os.path import basename | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Richard Bronkhorst
						Richard Bronkhorst