haulin goods
This commit is contained in:
parent
b47fa44cb0
commit
1b7a528655
@ -10,4 +10,4 @@ RUN chmod +x /app/main.py
|
|||||||
VOLUME /data
|
VOLUME /data
|
||||||
#ENTRYPOINT bash
|
#ENTRYPOINT bash
|
||||||
ENTRYPOINT [ "python3", "/app/main.py"]
|
ENTRYPOINT [ "python3", "/app/main.py"]
|
||||||
CMD ["-s", "/data/store.npt", "-x", "/data/cmd.hst"]
|
CMD ["-d", "/data"]
|
||||||
|
8
main.py
8
main.py
@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
from nullptr.commander import Commander
|
from nullptr.commander import Commander
|
||||||
|
import os
|
||||||
from nullptr.models.base import Base
|
from nullptr.models.base import Base
|
||||||
def main(args):
|
def main(args):
|
||||||
c = Commander(args.store_file)
|
if not os.path.isdir(args.data_dir):
|
||||||
|
os.makedirs(args.data_dir )
|
||||||
|
c = Commander(args.data_dir)
|
||||||
c.run()
|
c.run()
|
||||||
|
|
||||||
# X1-AG74-41076A
|
# X1-AG74-41076A
|
||||||
@ -11,7 +14,6 @@ def main(args):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('-s', '--store-file', default='data/store.npt')
|
parser.add_argument('-d', '--data-dir', default='data')
|
||||||
parser.add_argument('-x', '--history-file', default='data/cmd.hst')
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
main(args)
|
main(args)
|
||||||
|
@ -4,6 +4,15 @@ from nullptr.models.system import System
|
|||||||
from nullptr.models.waypoint import Waypoint
|
from nullptr.models.waypoint import Waypoint
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TradeOption:
|
||||||
|
resource: str
|
||||||
|
source: Waypoint
|
||||||
|
dest: Waypoint
|
||||||
|
margin: int
|
||||||
|
dist: int
|
||||||
|
score: float
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SearchNode:
|
class SearchNode:
|
||||||
system: System
|
system: System
|
||||||
@ -88,3 +97,34 @@ class Analyzer:
|
|||||||
if len(dest) == 0:
|
if len(dest) == 0:
|
||||||
return None
|
return None
|
||||||
return self.find_path(dest, to, depth-1, seen)
|
return self.find_path(dest, to, depth-1, seen)
|
||||||
|
|
||||||
|
def prices(self, system):
|
||||||
|
prices = {}
|
||||||
|
for m in self.store.all_members(system, Marketplace):
|
||||||
|
for p in m.prices.values():
|
||||||
|
r = p['symbol']
|
||||||
|
if not r in prices:
|
||||||
|
prices[r] = []
|
||||||
|
prices[r].append({
|
||||||
|
'wp': m.waypoint,
|
||||||
|
'buy': p['buy'],
|
||||||
|
'sell': p['sell']
|
||||||
|
})
|
||||||
|
return prices
|
||||||
|
|
||||||
|
def find_trade(self, system):
|
||||||
|
prices = self.prices(system)
|
||||||
|
best = None
|
||||||
|
for resource, markets in prices.items():
|
||||||
|
source = sorted(markets, key=lambda x: x['buy'])[0]
|
||||||
|
dest = sorted(markets, key=lambda x: x['sell'])[-1]
|
||||||
|
margin = dest['sell'] -source['buy']
|
||||||
|
if margin < 0:
|
||||||
|
continue
|
||||||
|
dist = source['wp'].distance(dest['wp'])
|
||||||
|
dist = max(dist, 0.0001)
|
||||||
|
score = margin / dist
|
||||||
|
o = TradeOption(resource, source['wp'], dest['wp'], margin, dist, score)
|
||||||
|
if best is None or best.score < o.score:
|
||||||
|
best = o
|
||||||
|
return best
|
||||||
|
@ -5,6 +5,7 @@ from random import choice
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from nullptr.atlas_builder import AtlasBuilder
|
from nullptr.atlas_builder import AtlasBuilder
|
||||||
|
from nullptr.analyzer import Analyzer
|
||||||
|
|
||||||
class CentralCommandError(Exception):
|
class CentralCommandError(Exception):
|
||||||
pass
|
pass
|
||||||
@ -14,9 +15,9 @@ class CentralCommand:
|
|||||||
self.missions = {}
|
self.missions = {}
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.store = store
|
self.store = store
|
||||||
|
self.analyzer = Analyzer(store)
|
||||||
self.api = api
|
self.api = api
|
||||||
self.atlas_builder = AtlasBuilder(store, api)
|
self.atlas_builder = AtlasBuilder(store, api)
|
||||||
self.update_missions()
|
|
||||||
|
|
||||||
def get_ready_missions(self):
|
def get_ready_missions(self):
|
||||||
result = []
|
result = []
|
||||||
@ -26,6 +27,7 @@ class CentralCommand:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def tick(self):
|
def tick(self):
|
||||||
|
self.update_missions()
|
||||||
missions = self.get_ready_missions()
|
missions = self.get_ready_missions()
|
||||||
if len(missions) == 0: return False
|
if len(missions) == 0: return False
|
||||||
ship = choice(missions)
|
ship = choice(missions)
|
||||||
@ -41,7 +43,6 @@ class CentralCommand:
|
|||||||
self.run()
|
self.run()
|
||||||
print('manual mode')
|
print('manual mode')
|
||||||
|
|
||||||
|
|
||||||
def wait_for_stop(self):
|
def wait_for_stop(self):
|
||||||
try:
|
try:
|
||||||
input()
|
input()
|
||||||
@ -52,7 +53,6 @@ class CentralCommand:
|
|||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.update_missions()
|
|
||||||
while not self.stopping:
|
while not self.stopping:
|
||||||
did_step = True
|
did_step = True
|
||||||
request_counter = self.api.requests_sent
|
request_counter = self.api.requests_sent
|
||||||
@ -86,18 +86,43 @@ class CentralCommand:
|
|||||||
raise MissionError(e)
|
raise MissionError(e)
|
||||||
return
|
return
|
||||||
ship.set_mission_state(nm, parsed_val)
|
ship.set_mission_state(nm, parsed_val)
|
||||||
|
|
||||||
|
def smipa(self,s,n,v):
|
||||||
|
self.set_mission_param(s,n,v)
|
||||||
|
|
||||||
def update_missions(self):
|
def update_missions(self):
|
||||||
for s in self.store.all(Ship):
|
for s in self.store.all(Ship):
|
||||||
|
if s.mission_status == 'done':
|
||||||
|
s.mission = None
|
||||||
if s.mission is None:
|
if s.mission is None:
|
||||||
if s in self.missions:
|
if s in self.missions:
|
||||||
self.stop_mission(s)
|
self.stop_mission(s)
|
||||||
elif s not in self.missions:
|
if s.mission is None:
|
||||||
|
self.assign_mission(s)
|
||||||
|
if s.mission is not None and s not in self.missions:
|
||||||
self.start_mission(s)
|
self.start_mission(s)
|
||||||
if s in self.missions:
|
if s in self.missions:
|
||||||
m = self.missions[s]
|
m = self.missions[s]
|
||||||
m.next_step = max(s.cooldown, s.arrival)
|
|
||||||
|
|
||||||
|
def assign_mission(self, s):
|
||||||
|
if s.role == 'hauler':
|
||||||
|
self.assign_haul(s)
|
||||||
|
|
||||||
|
def assign_haul(self, s):
|
||||||
|
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
|
||||||
|
print(f'assigning {s} to deliver {t.resource} from {t.source} to {t.dest} at a margin of {t.margin}')
|
||||||
|
self.init_mission(s, 'haul')
|
||||||
|
self.smipa(s, 'resource', t.resource)
|
||||||
|
self.smipa(s, 'site', t.source)
|
||||||
|
self.smipa(s, 'dest', t.dest)
|
||||||
|
self.smipa(s, 'delivery', 'sell')
|
||||||
|
|
||||||
def init_mission(self, s, mtyp):
|
def init_mission(self, s, mtyp):
|
||||||
if mtyp == 'none':
|
if mtyp == 'none':
|
||||||
s.mission_state = {}
|
s.mission_state = {}
|
||||||
@ -112,6 +137,11 @@ class CentralCommand:
|
|||||||
s.mission_status = 'init'
|
s.mission_status = 'init'
|
||||||
s.mission_state = {k: v.default for k,v in mclass.params().items()}
|
s.mission_state = {k: v.default for k,v in mclass.params().items()}
|
||||||
self.start_mission(s)
|
self.start_mission(s)
|
||||||
|
|
||||||
|
def restart_mission(self, s):
|
||||||
|
if s not in self.missions:
|
||||||
|
raise CentralCommandError("no mission assigned")
|
||||||
|
s.mission_status = 'init'
|
||||||
|
|
||||||
def start_mission(self, s):
|
def start_mission(self, s):
|
||||||
mtype = s.mission
|
mtype = s.mission
|
||||||
|
@ -17,11 +17,13 @@ class CommandError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Commander(CommandLine):
|
class Commander(CommandLine):
|
||||||
def __init__(self, store_file='data/store.npt', hist_file = 'data/cmd.hst'):
|
def __init__(self, data_dir='data'):
|
||||||
|
store_file = os.path.join(data_dir, 'store.npt')
|
||||||
|
hist_file = os.path.join(data_dir, 'cmd.hst')
|
||||||
self.hist_file = hist_file
|
self.hist_file = hist_file
|
||||||
if os.path.isfile(hist_file):
|
if os.path.isfile(hist_file):
|
||||||
readline.read_history_file(hist_file)
|
readline.read_history_file(hist_file)
|
||||||
self.store = Store(store_file)
|
self.store = Store(store_file, True)
|
||||||
self.agent = self.select_agent()
|
self.agent = self.select_agent()
|
||||||
self.api = Api(self.store, self.agent)
|
self.api = Api(self.store, self.agent)
|
||||||
self.centcom = CentralCommand(self.store, self.api)
|
self.centcom = CentralCommand(self.store, self.api)
|
||||||
@ -123,6 +125,14 @@ class Commander(CommandLine):
|
|||||||
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)
|
||||||
|
|
||||||
|
def do_role(self, role):
|
||||||
|
roles = ['hauler']
|
||||||
|
if not self.has_ship(): return
|
||||||
|
if role not in roles:
|
||||||
|
print(f'role {role} not found. Choose from {roles}')
|
||||||
|
return
|
||||||
|
self.ship.role = role
|
||||||
|
|
||||||
def do_mission(self, arg=''):
|
def do_mission(self, arg=''):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
@ -130,6 +140,11 @@ class Commander(CommandLine):
|
|||||||
self.centcom.init_mission(self.ship, arg)
|
self.centcom.init_mission(self.ship, arg)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
|
def do_mrestart(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
self.centcom.restart_mission(self.ship)
|
||||||
|
self.print_mission()
|
||||||
|
|
||||||
def do_mreset(self):
|
def do_mreset(self):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
self.ship.mission_state = {}
|
self.ship.mission_state = {}
|
||||||
@ -254,9 +269,11 @@ class Commander(CommandLine):
|
|||||||
pprint(r)
|
pprint(r)
|
||||||
|
|
||||||
def do_waypoints(self, system_str=''):
|
def do_waypoints(self, system_str=''):
|
||||||
|
loc = None
|
||||||
if system_str == '':
|
if system_str == '':
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
system = self.ship.location.system
|
loc = self.ship.location
|
||||||
|
system = loc.system
|
||||||
else:
|
else:
|
||||||
system = self.store.get(System, system_str)
|
system = self.store.get(System, system_str)
|
||||||
print(f'=== waypoints in {system}')
|
print(f'=== waypoints in {system}')
|
||||||
@ -276,7 +293,7 @@ class Commander(CommandLine):
|
|||||||
traits.append('METAL')
|
traits.append('METAL')
|
||||||
if 'PRECIOUS_METAL_DEPOSITS' in w.traits:
|
if 'PRECIOUS_METAL_DEPOSITS' in w.traits:
|
||||||
traits.append('GOLD')
|
traits.append('GOLD')
|
||||||
if 'EXPLOSIVE_GASSES' in w.traits:
|
if 'EXPLOSIVE_GASES' in w.traits:
|
||||||
traits.append('GAS')
|
traits.append('GAS')
|
||||||
if 'MINERAL_DEPOSITS' in w.traits:
|
if 'MINERAL_DEPOSITS' in w.traits:
|
||||||
traits.append('MINS')
|
traits.append('MINS')
|
||||||
@ -288,7 +305,12 @@ class Commander(CommandLine):
|
|||||||
typ = w.type[0]
|
typ = w.type[0]
|
||||||
if typ not in ['F','J'] and len(traits) == 0:
|
if typ not in ['F','J'] and len(traits) == 0:
|
||||||
continue
|
continue
|
||||||
print(f'{wname:4} {typ} {traits}')
|
|
||||||
|
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):
|
def do_members(self):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
@ -441,17 +463,7 @@ class Commander(CommandLine):
|
|||||||
def do_prices(self, resource=None):
|
def do_prices(self, resource=None):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
system = self.ship.location.system
|
system = self.ship.location.system
|
||||||
prices = {}
|
prices = self.analyzer.prices(system)
|
||||||
for m in self.store.all_members(system, Marketplace):
|
|
||||||
for p in m.prices.values():
|
|
||||||
r = p['symbol']
|
|
||||||
if not r in prices:
|
|
||||||
prices[r] = []
|
|
||||||
prices[r].append({
|
|
||||||
'wp': m.symbol,
|
|
||||||
'buy': p['buy'],
|
|
||||||
'sell': p['sell']
|
|
||||||
})
|
|
||||||
if resource is not None:
|
if resource is not None:
|
||||||
pprint(prices[resource.upper()])
|
pprint(prices[resource.upper()])
|
||||||
else:
|
else:
|
||||||
|
@ -3,6 +3,8 @@ from nullptr.missions.mine import MiningMission
|
|||||||
from nullptr.missions.haul import HaulMission
|
from nullptr.missions.haul import HaulMission
|
||||||
from nullptr.missions.travel import TravelMission
|
from nullptr.missions.travel import TravelMission
|
||||||
from nullptr.missions.probe import ProbeMission
|
from nullptr.missions.probe import ProbeMission
|
||||||
|
from nullptr.missions.idle import IdleMission
|
||||||
|
|
||||||
|
|
||||||
def get_mission_class( mtype):
|
def get_mission_class( mtype):
|
||||||
types = {
|
types = {
|
||||||
@ -10,7 +12,8 @@ def get_mission_class( mtype):
|
|||||||
'mine': MiningMission,
|
'mine': MiningMission,
|
||||||
'haul': HaulMission,
|
'haul': HaulMission,
|
||||||
'travel': TravelMission,
|
'travel': TravelMission,
|
||||||
'probe': ProbeMission
|
'probe': ProbeMission,
|
||||||
|
'idle': IdleMission
|
||||||
}
|
}
|
||||||
if mtype not in types:
|
if mtype not in types:
|
||||||
raise ValueError(f'invalid mission type {mtype}')
|
raise ValueError(f'invalid mission type {mtype}')
|
||||||
|
@ -97,7 +97,7 @@ class Mission:
|
|||||||
logging.info(f'mission finished for {self.ship}')
|
logging.info(f'mission finished for {self.ship}')
|
||||||
|
|
||||||
def is_waiting(self):
|
def is_waiting(self):
|
||||||
return self.next_step > time()
|
return self.next_step > time() or self.ship.cooldown > time() or self.ship.arrival > time()
|
||||||
|
|
||||||
def is_finished(self):
|
def is_finished(self):
|
||||||
return self.status() in ['done','error']
|
return self.status() in ['done','error']
|
||||||
@ -147,6 +147,10 @@ class BaseMission(Mission):
|
|||||||
self.api.navigate(self.ship, site)
|
self.api.navigate(self.ship, site)
|
||||||
self.next_step = self.ship.arrival
|
self.next_step = self.ship.arrival
|
||||||
|
|
||||||
|
def step_market(self):
|
||||||
|
loc = self.ship.location
|
||||||
|
self.api.marketplace(loc)
|
||||||
|
|
||||||
def step_unload(self):
|
def step_unload(self):
|
||||||
delivery = self.st('delivery')
|
delivery = self.st('delivery')
|
||||||
if delivery == 'sell':
|
if delivery == 'sell':
|
||||||
@ -176,9 +180,15 @@ class BaseMission(Mission):
|
|||||||
return 'more'
|
return 'more'
|
||||||
|
|
||||||
def step_load(self):
|
def step_load(self):
|
||||||
|
credits = self.api.agent.credits
|
||||||
cargo_space = self.ship.cargo_capacity - self.ship.cargo_units
|
cargo_space = self.ship.cargo_capacity - self.ship.cargo_units
|
||||||
resource = self.st('resource')
|
resource = self.st('resource')
|
||||||
self.api.buy(self.ship, resource, cargo_space)
|
loc = self.ship.location
|
||||||
|
market = self.store.get('Marketplace', loc.symbol)
|
||||||
|
price = market.buy_price(resource)
|
||||||
|
affordable = credits // price
|
||||||
|
amount = min(cargo_space, affordable)
|
||||||
|
self.api.buy(self.ship, resource, amount)
|
||||||
|
|
||||||
def step_travel(self):
|
def step_travel(self):
|
||||||
traject = self.st('traject')
|
traject = self.st('traject')
|
||||||
|
@ -18,8 +18,10 @@ class HaulMission(BaseMission):
|
|||||||
|
|
||||||
def steps(self):
|
def steps(self):
|
||||||
return {
|
return {
|
||||||
**self.travel_steps('to', 'site', 'load'),
|
**self.travel_steps('to', 'site', 'market'),
|
||||||
|
'market': (self.step_market, 'load'),
|
||||||
'load': (self.step_load, 'travel-back'),
|
'load': (self.step_load, 'travel-back'),
|
||||||
**self.travel_steps('back', 'dest', 'unload'),
|
**self.travel_steps('back', 'dest', 'unload'),
|
||||||
'unload': (self.step_unload, 'travel-to'),
|
'unload': (self.step_unload, 'market-dest'),
|
||||||
|
'market-dest': (self.step_market, 'done'),
|
||||||
}
|
}
|
||||||
|
26
nullptr/missions/idle.py
Normal file
26
nullptr/missions/idle.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from nullptr.missions.base import BaseMission, MissionParam
|
||||||
|
import time
|
||||||
|
|
||||||
|
class IdleMission(BaseMission):
|
||||||
|
def start_state(self):
|
||||||
|
return 'start'
|
||||||
|
|
||||||
|
def step_wait(self):
|
||||||
|
self.next_step = int(time.time()) + self.st('seconds')
|
||||||
|
|
||||||
|
def step_idle(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def params(cls):
|
||||||
|
return {
|
||||||
|
'seconds': MissionParam(int, True)
|
||||||
|
}
|
||||||
|
|
||||||
|
def steps(self):
|
||||||
|
return {
|
||||||
|
'start': (self.step_wait, 'wait'),
|
||||||
|
'wait': (self.step_idle, 'done')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -20,10 +20,6 @@ class ProbeMission(BaseMission):
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def step_market(self):
|
|
||||||
loc = self.ship.location
|
|
||||||
self.api.marketplace(loc)
|
|
||||||
|
|
||||||
def step_next_hop(self):
|
def step_next_hop(self):
|
||||||
hops = self.st('hops')
|
hops = self.st('hops')
|
||||||
next_hop = self.st('next-hop')
|
next_hop = self.st('next-hop')
|
||||||
|
@ -35,7 +35,12 @@ class Marketplace(Base):
|
|||||||
price['volume'] = mg(g, 'tradeVolume')
|
price['volume'] = mg(g, 'tradeVolume')
|
||||||
prices[symbol] = price
|
prices[symbol] = price
|
||||||
self.prices = prices
|
self.prices = prices
|
||||||
|
|
||||||
|
def buy_price(self, resource):
|
||||||
|
if resource not in self.prices:
|
||||||
|
return None
|
||||||
|
return self.prices[resource]['buy']
|
||||||
|
|
||||||
def sellable_items(self, resources):
|
def sellable_items(self, resources):
|
||||||
return [r for r in resources if r in self.prices]
|
return [r for r in resources if r in self.prices]
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ class Ship(Base):
|
|||||||
self.fuel_capacity:int = 0
|
self.fuel_capacity:int = 0
|
||||||
self.mission:str = None
|
self.mission:str = None
|
||||||
self.mission_status:str = 'init'
|
self.mission_status:str = 'init'
|
||||||
|
self.role = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ext(self):
|
def ext(self):
|
||||||
@ -100,6 +101,8 @@ class Ship(Base):
|
|||||||
cooldown = int(self.cooldown - time())
|
cooldown = int(self.cooldown - time())
|
||||||
r = self.symbol
|
r = self.symbol
|
||||||
if detail > 1:
|
if detail > 1:
|
||||||
|
if self.role is not None:
|
||||||
|
r += f' {self.role}'
|
||||||
r += ' ' + self.status
|
r += ' ' + self.status
|
||||||
r += f' [{self.fuel_current}/{self.fuel_capacity}]'
|
r += f' [{self.fuel_current}/{self.fuel_capacity}]'
|
||||||
r += ' ' + str(self.location)
|
r += ' ' + str(self.location)
|
||||||
|
@ -2,6 +2,7 @@ from .base import Base, Reference
|
|||||||
from nullptr.models.system import System
|
from nullptr.models.system import System
|
||||||
from nullptr.util import *
|
from nullptr.util import *
|
||||||
from time import time
|
from time import time
|
||||||
|
from math import sqrt
|
||||||
|
|
||||||
class Waypoint(Base):
|
class Waypoint(Base):
|
||||||
def define(self):
|
def define(self):
|
||||||
@ -23,6 +24,9 @@ class Waypoint(Base):
|
|||||||
self.seta('is_under_construction', d, 'isUnderConstruction')
|
self.seta('is_under_construction', d, 'isUnderConstruction')
|
||||||
self.setlst('traits', d, 'traits', 'symbol')
|
self.setlst('traits', d, 'traits', 'symbol')
|
||||||
self.uncharted = 'UNCHARTED' in self.traits
|
self.uncharted = 'UNCHARTED' in self.traits
|
||||||
|
|
||||||
|
def distance(self, other):
|
||||||
|
return int(sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ext(self):
|
def ext(self):
|
||||||
|
@ -23,10 +23,12 @@ class StoreUnpickler(pickle.Unpickler):
|
|||||||
if pers_id == "STORE":
|
if pers_id == "STORE":
|
||||||
return self.store
|
return self.store
|
||||||
raise pickle.UnpicklingError("I don know the persid!")
|
raise pickle.UnpicklingError("I don know the persid!")
|
||||||
|
|
||||||
|
CHUNK_MAGIC = b'ChNk'
|
||||||
|
|
||||||
class ChunkHeader:
|
class ChunkHeader:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.magic = CHUNK_MAGIC
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
self.in_use = True
|
self.in_use = True
|
||||||
self.size = 0
|
self.size = 0
|
||||||
@ -35,14 +37,16 @@ class ChunkHeader:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, fil):
|
def parse(cls, fil):
|
||||||
offset = fil.tell()
|
offset = fil.tell()
|
||||||
d = fil.read(16)
|
d = fil.read(20)
|
||||||
if len(d) < 16:
|
if len(d) < 20:
|
||||||
return None
|
return None
|
||||||
o = cls()
|
o = cls()
|
||||||
o.offset = offset
|
o.offset = offset
|
||||||
d, o.used = unpack('<QQ', d)
|
o.magic, d, o.used = unpack('<4sQQ', d)
|
||||||
o.size = d & 0x7fffffffffffffff
|
o.size = d & 0x7fffffffffffffff
|
||||||
o.in_use = d & 0x8000000000000000 != 0
|
o.in_use = d & 0x8000000000000000 != 0
|
||||||
|
if o.magic != CHUNK_MAGIC:
|
||||||
|
raise ValueError(f"Invalid chunk magic: {o.magic}")
|
||||||
# print(o)
|
# print(o)
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@ -50,7 +54,7 @@ class ChunkHeader:
|
|||||||
d = self.size
|
d = self.size
|
||||||
if self.in_use:
|
if self.in_use:
|
||||||
d |= 1 << 63
|
d |= 1 << 63
|
||||||
d = pack('<QQ', d, self.used)
|
d = pack('<4sQQ', self.magic, d, self.used)
|
||||||
f.write(d)
|
f.write(d)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -113,11 +117,13 @@ class Store:
|
|||||||
self.p(hdr)
|
self.p(hdr)
|
||||||
total += hdr.size
|
total += hdr.size
|
||||||
if not hdr.in_use:
|
if not hdr.in_use:
|
||||||
|
print(f"skip {hdr.size} {self.fil.tell()}")
|
||||||
self.fil.seek(hdr.size, 1)
|
self.fil.seek(hdr.size, 1)
|
||||||
free += hdr.size
|
free += hdr.size
|
||||||
else:
|
else:
|
||||||
data = self.fil.read(hdr.used)
|
data = self.fil.read(hdr.used)
|
||||||
self.load_object(data, offset)
|
self.load_object(data, offset)
|
||||||
|
print(f"pad {hdr.size - hdr.used}")
|
||||||
self.fil.seek(hdr.size - hdr.used, 1)
|
self.fil.seek(hdr.size - hdr.used, 1)
|
||||||
cnt += 1
|
cnt += 1
|
||||||
offset = self.fil.tell()
|
offset = self.fil.tell()
|
||||||
|
@ -50,7 +50,7 @@ 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")
|
||||||
dum.data = "A" * 100
|
dum.data = "A" * 1000
|
||||||
dum.count = 1337
|
dum.count = 1337
|
||||||
self.reopen()
|
self.reopen()
|
||||||
dum = self.s.get(Dummy, "5")
|
dum = self.s.get(Dummy, "5")
|
||||||
@ -73,7 +73,7 @@ class TestStore(unittest.TestCase):
|
|||||||
dum2 = self.s.get(Dummy, "7",create=True)
|
dum2 = self.s.get(Dummy, "7",create=True)
|
||||||
self.reopen()
|
self.reopen()
|
||||||
dum2 = self.s.get(Dummy, "7")
|
dum2 = self.s.get(Dummy, "7")
|
||||||
dum2.data = "A" * 100
|
dum2.data = "A" * 1000
|
||||||
dum2.count = 1337
|
dum2.count = 1337
|
||||||
dum3 = self.s.get(Dummy, "9",create=True)
|
dum3 = self.s.get(Dummy, "9",create=True)
|
||||||
dum3.count = 1338
|
dum3.count = 1338
|
||||||
|
Loading…
Reference in New Issue
Block a user