diff --git a/CHANGES.txt b/CHANGES.txt index bdfd8c42..7b7f3796 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,13 +2,20 @@ TradeDangerous, Copyright (C) Oliver "kfsone" Smith, July 2014 ============================================================================== -v6.2.4 [wip] +v6.2.4 Dec 21 2014 +. (kfsone) Experimental "add-station" command in misc, +. (kfsone) Added "--near" to olddata command, +. (kfsone) Route calculation performance, +. (kfsone) Added "Black Market" flag to station data, +. (kfsone) Added Black Market indicators to "local" command, +. (kfsone) Reorganized Ship and ShipVendor data (prices are ship based now), +. (kfsone) Draft version of "jsonprices", . (gazelle) Auto-completion for bash users (see scripts/README.txt) . (gazelle) Nice overhaul of the csv export command . (kfsone) Fix for UTF-8 decoding error, . (kfsone) Rebuild cache before .prices file after downloading .csvs . (maddavo) Combat Stabilisers do exist -+ Systems, Stations, Data: Maddavo, Gazelle ++ Systems, Stations, Data: Maddavo, Gazelle, Kfsone, many others v6.2.3 Dec 17 2014 . (kfsone) "maddavo" import plugin: diff --git a/commands/buy_cmd.py b/commands/buy_cmd.py index 66d8d354..90866830 100644 --- a/commands/buy_cmd.py +++ b/commands/buy_cmd.py @@ -18,35 +18,35 @@ help='Require at least this quantity.', default=0, type=int, - ), + ), ParseArgument('--near', help='Find sellers within jump range of this system.', type=str - ), - ParseArgument('--ly-per', - help='Maximum light years per jump.', + ), + ParseArgument('--ly', + help='[Requires --near] Systems within this range of --near.', default=None, dest='maxLyPer', metavar='N.NN', type=float, - ), + ), ParseArgument('--limit', help='Maximum number of results to list.', default=None, type=int, - ), + ), ParseArgument('--ages', help='Show age of data.', default=False, action='store_true', - ), + ), MutuallyExclusiveGroup( ParseArgument('--price-sort', '-P', help='(When using --near) Sort by price not distance', action='store_true', default=False, dest='sortByPrice', - ), + ), ParseArgument('--stock-sort', '-S', help='Sort by stock followed by price', action='store_true', diff --git a/commands/local_cmd.py b/commands/local_cmd.py index 845ee881..fc011c93 100644 --- a/commands/local_cmd.py +++ b/commands/local_cmd.py @@ -95,7 +95,8 @@ def run(results, cmdenv, tdb): rr = ResultRow( station=station, dist=station.lsFromStar, - age=age + age=age, + blackMarket=station.blackMarket, ) row.stations.append(rr) results.rows.append(row) @@ -127,17 +128,22 @@ def render(results, cmdenv, tdb): key=lambda row: row.dist) ) + marketStates = { 'Y': 'Yes', 'N': 'No', '?': 'Unk' } showStations = cmdenv.detail or cmdenv.ages if showStations: stnRowFmt = RowFormat(prefix=' + ').append( ColumnFormat("Station", '<', 32, - key=lambda row: row.station.str()) + key=lambda row: row.station.str()) ) if cmdenv.detail: stnRowFmt.append( ColumnFormat("Dist", '>', '10', - key=lambda row: '{}ls'.format(row.dist) if row.dist else '') + key=lambda row: '{}ls'.format(row.dist) if row.dist else '') ) + stnRowFmt.append( + ColumnFormat("BMkt", '>', '4', + key=lambda row: marketStates[row.blackMarket] + )) if cmdenv.ages: stnRowFmt.append( ColumnFormat("Age/days", '>', 7, diff --git a/commands/olddata_cmd.py b/commands/olddata_cmd.py index 626b4541..f4b49873 100644 --- a/commands/olddata_cmd.py +++ b/commands/olddata_cmd.py @@ -18,7 +18,18 @@ help='Maximum number of results to show', default=20, type=int, - ), + ), + ParseArgument('--near', + help='Find sellers within jump range of this system.', + type=str + ), + ParseArgument('--ly', + help='[Requires --near] Systems within this range of --near.', + default=None, + dest='maxLyPer', + metavar='N.NN', + type=float, + ), ] ###################################################################### @@ -39,22 +50,84 @@ def run(results, cmdenv, tdb): else: limitClause = "" + fields = [ + "si.station_id", + "JULIANDAY('NOW') - JULIANDAY(MAX(si.modified))", + "stn.ls_from_star", + ] + + joins = [] + wheres = [] + havings = [] + + nearSys = cmdenv.nearSystem + if nearSys: + maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy + maxLy2 = maxLy ** 2 + fields.append( + "dist2(" + "sys.pos_x, sys.pos_y, sys.pos_z," + "{}, {}, {}" + ") AS d2".format( + nearSys.posX, + nearSys.posY, + nearSys.posZ, + )) + joins.append("INNER JOIN System sys USING (system_id)") + wheres.append("""( + sys.pos_x BETWEEN {} and {} + AND sys.pos_y BETWEEN {} and {} + AND sys.pos_z BETWEEN {} and {} + )""".format( + nearSys.posX - maxLy, + nearSys.posX + maxLy, + nearSys.posY - maxLy, + nearSys.posY + maxLy, + nearSys.posZ - maxLy, + nearSys.posZ + maxLy, + )) + havings.append("d2 <= {}".format(maxLy2)) + else: + fields.append("0") + + fieldStr = ','.join(fields) + + if joins: + joinStr = ' '.join(joins) + else: + joinStr = '' + + if wheres: + whereStr = 'WHERE ' + ' AND '.join(wheres) + else: + whereStr = '' + + if havings: + haveStr = 'HAVING ' + ' AND '.join(havings) + else: + haveStr = '' + stmt = """ - SELECT si.station_id, - JULIANDAY('NOW') - JULIANDAY(MAX(si.modified)), - stn.ls_from_star + SELECT {fields} FROM StationItem as si INNER JOIN Station stn USING (station_id) + {joins} + {wheres} GROUP BY 1 + {having} ORDER BY 2 DESC {limit} """.format( - limit=limitClause + fields=fieldStr, + joins=joinStr, + wheres=whereStr, + having=haveStr, + limit=limitClause, ) cmdenv.DEBUG1(stmt) - for (stnID, age, ls) in tdb.query(stmt): + for (stnID, age, ls, dist2) in tdb.query(stmt): cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls) row = ResultRow() row.station = tdb.stationByID[stnID] @@ -63,6 +136,7 @@ def run(results, cmdenv, tdb): row.ls = "{:n}".format(ls) else: row.ls = "?" + row.dist2 = dist2 results.rows.append(row) return results @@ -92,6 +166,10 @@ def render(results, cmdenv, tdb): key=lambda row: row.ls) ) + if cmdenv.nearSystem: + rowFmt.addColumn('Dist', '>', 6, '.2f', + key=lambda row: math.sqrt(row.dist2)) + if not cmdenv.quiet: heading, underline = rowFmt.heading() print(heading, underline, sep='\n') diff --git a/jsonprices.py b/jsonprices.py index e294434a..70f99476 100644 --- a/jsonprices.py +++ b/jsonprices.py @@ -74,7 +74,7 @@ def lookup_system(tdb, tdenv, name, x, y, z): return None -def lookup_station(tdb, tdenv, system, name, lsFromStar): +def lookup_station(tdb, tdenv, system, name, lsFromStar, blackMarket): normalizedName = tradedb.TradeDB.normalizedStr(name) for stn in system.stations: stnNormalizedName = tradedb.TradeDB.normalizedStr(stn.dbname) @@ -97,7 +97,7 @@ def lookup_station(tdb, tdenv, system, name, lsFromStar): return stn if tdenv.addUnknown: - return tdb.addLocalStation(system, name, lsFromStar) + return tdb.addLocalStation(system, name, lsFromStar, blackMarket) return None @@ -120,6 +120,13 @@ def load_prices_json( stnName = stnData['name'] lsFromStar = stnData['ls'] + try: + blackMarket = stnData['bm'].upper() + if not blackMarket in [ 'Y', 'N' ]: + blackMarket = '?' + except KeyError: + blackMarket = '?' + system = lookup_system( tdb, tdenv, sysName, @@ -145,6 +152,7 @@ def load_prices_json( system, stnName, lsFromStar, + blackMarket, ) if not station: if tdenv.ignoreUnknown: diff --git a/misc/add-station.py b/misc/add-station.py index 6a7b8a83..17a86d9e 100644 --- a/misc/add-station.py +++ b/misc/add-station.py @@ -136,9 +136,9 @@ def addStation(conn, sysName, stnName, distLs, blackMarket): cur = conn.cursor() cur.execute(""" INSERT INTO Station - (system_id, name, ls_from_star) - VALUES (?, ?, ?) - """, [systemID, stnName, distLs]) + (system_id, name, ls_from_star, blackMarket) + VALUES (?, ?, ?, ?) + """, [systemID, stnName, distLs, blackMarket]) stationID = cur.lastrowid conn.commit() diff --git a/tradedb.py b/tradedb.py index 9a9d8045..1c7f8bb5 100644 --- a/tradedb.py +++ b/tradedb.py @@ -138,10 +138,13 @@ class Station(object): Describes a station within a given system along with what trade opportunities it presents. """ - __slots__ = ('ID', 'system', 'dbname', 'lsFromStar', 'tradingWith', 'itemCount') + __slots__ = ('ID', 'system', 'dbname', 'lsFromStar', 'blackMarket', 'tradingWith', 'itemCount') - def __init__(self, ID, system, dbname, lsFromStar, itemCount): - self.ID, self.system, self.dbname, self.lsFromStar, self.itemCount = ID, system, dbname, lsFromStar, itemCount + def __init__(self, ID, system, dbname, lsFromStar, blackMarket, itemCount): + self.ID, self.system, self.dbname = ID, system, dbname + self.lsFromStar = lsFromStar + self.blackMarket = blackMarket + self.itemCount = itemCount self.tradingWith = None # dict[tradingPartnerStation] -> [ available trades ] system.stations.append(self) @@ -155,7 +158,12 @@ def str(self): def __repr__(self): - return "Station(ID={}, system='{}', dbname='{}', lsFromStar={})".format(self.ID, re.escape(self.system.dbname), re.escape(self.dbname), self.lsFromStar) + return "Station(ID={}, system='{}', dbname='{}', lsFromStar={})".format( + self.ID, + re.escape(self.system.dbname), + re.escape(self.dbname), + self.lsFromStar + ) ###################################################################### @@ -337,6 +345,16 @@ def __init__(self, maxSystemLinkLy=tdenv.maxSystemLinkLy, ) + @staticmethod + def calculateDistance2(lx, ly, lz, rx, ry, rz): + """ + Returns the square of the distance between two points + """ + dX = (lx - rx) + dY = (ly - ry) + dZ = (lz - rz) + return (dX ** 2) + (dY ** 2) + (dZ ** 2) + ############################################################ # Access to the underlying database. @@ -348,6 +366,7 @@ def getDB(self): import sqlite3 conn = sqlite3.connect(self.dbFilename) conn.execute("PRAGMA foreign_keys=ON") + conn.create_function('dist2', 6, TradeDB.calculateDistance2) return conn except ImportError as e: print("ERROR: You don't appear to have the Python sqlite3 module installed. Impressive. No, wait, the other one: crazy.") @@ -584,7 +603,7 @@ def _loadStations(self): If you have previously loaded Stations, this will orphan the old objects. """ stmt = """ - SELECT station_id, system_id, name, ls_from_star, + SELECT station_id, system_id, name, ls_from_star, blackMarket, (SELECT COUNT(*) FROM StationItem WHERE station_id = Station.station_id) AS itemCount @@ -593,15 +612,21 @@ def _loadStations(self): self.cur.execute(stmt) stationByID = {} systemByID = self.systemByID - for (ID, systemID, name, lsFromStar, itemCount) in self.cur: - station = Station(ID, systemByID[systemID], name, lsFromStar, itemCount) + for ( + ID, systemID, name, lsFromStar, blackMarket, itemCount + ) in self.cur: + station = Station( + ID, systemByID[systemID], name, + lsFromStar, blackMarket, + itemCount + ) stationByID[ID] = station self.stationByID = stationByID self.tdenv.DEBUG1("Loaded {:n} Stations", len(stationByID)) - def addLocalStation(self, system, name, lsFromStar): + def addLocalStation(self, system, name, lsFromStar, blackMarket): """ Add a station to the local cache and memory copy. """ @@ -610,20 +635,20 @@ def addLocalStation(self, system, name, lsFromStar): cur = db.cursor() cur.execute(""" INSERT INTO Station ( - name, system_id, ls_from_star + name, system_id, ls_from_star, blackMarket ) VALUES ( - ?, ?, ? + ?, ?, ?, ? ) """, [ name, system.ID, lsFromStar ]) ID = cur.lastrowid - station = Station(ID, system, name, lsFromStar, 0) + station = Station(ID, system, name, lsFromStar, blackMarket, 0) self.stationByID[ID] = station db.commit() if not self.tdenv.quiet: - print("- Added new station #{}: {}/{} [{}ls]".format( - ID, system.name(), name, lsFromStar + print("- Added new station #{}: {}/{} [ls:{}, bm:{}]".format( + ID, system.name(), name, lsFromStar, blackMarket )) return station