
Doctor
Members-
Posts
99 -
Joined
-
Last visited
-
Donations
0.00 USD
Everything posted by Doctor
-
So, I'm semi-abandoning a project I started but am probably not going to finish anytime soon. But I dumped enough hours into it, and feel it has enough merit, that I am going to share the source code in case someone else thinks this is cool and wants to work on it. What is it? A replacement for enbmaps.de and all the other mapping sites we have. I wanted it to show where mobs spawned, and where roid fields were, along with the mining results from every zone (see the wiki on pages like Glenn, to see what I mean by 'mining results', the list of ores found in each sector). Right now, I have all the sectors and all the navs (that I could find) imported. Look in the 'initialization data' folder. No mobs/roids/gravity wells have been added yet. The annoyance of logging ore fields in a way that didn't drive me nuts (and the tedium of logging mobs) made me not want to bother...for about 2 weeks now. "What's the license" You may not use this software for commercial gain. No ads, no donations, no other revenue streams. If you break even with hosting and publicly (no request, publicly, on a normal page anyone can view) display that your operating it as non-profit, that's fine. It must remain open source in its entirety. Every image, every line of code, every byte of data in the database must all be accessible without authorization or signup. The code cannot be placed on github or any other site which intends to ingest the code into AI tools or otherwise monetize the code hosted there. Running your own git instance and hosting from there is fine. Any future license must require and honor these terms. Meaning, you cannot repackage this and change the license to MIT or something else. These terms of license must be in all downstream forks. For running a production site For an actual production site, you'll probably want to use mysql instead of sqlite, but sqlite is great for development and the code changeover from one to the other isn't bad. Mostly it's replacing ? with %s in a bunch of places. Well...the binding process is completely different, but meh. I didn't write it using mysql, again, if zen4's Promontory 21 hadn't been the shitshow it was, I'd have written it to use mysql. Also, you'll probably want to remove the Command Bar entirely from a production site. This tool wasn't really built to work like a wiki. It was meant for 1-2 people who are highly trusted to update the DB, then for it to mostly be a read-only experience. The idea behind adding mobs: Fly around with two chars. A JD and a PW are optimal. JD has 3 beams, 1 Energy, 1 EMP, 1 Plasma. PW has 3 guns, one loaded Explosive, 1 loaded Impact, 1 loaded Chemical. Telescopium on the JD DO NOT USE ANY RESIST DEBUFFS OF ANY KIND! Not even gravity link. Click on the 'Add Mob' button, shoot with energy laser, say it does 145 (-50) damage. You'd type in 145 for the Damage Component and -50 for the Resist Component. There's a blub on the page that explains all this. Fire 1 round from all the other weapon types and enter the damage/resist components. For positive numbers just enter the number without a + sign, so +500 is just 500. The page is smart enough to know that 145 (-50) meant you did 195 dmg, and 1000 (+500) meant you did 500. The page will do the math on its own to determine the mob resists and store them in the DB. A checkbox needs to be added for whether or not the mob is default hostile. "All this is in the wiki!" Yeah, but seeing it on a map is easier, and for things like mobs and ore, it helps to visually see where things are. Sure, there's Titanium in KV, but *where* in KV. Oh, only 2-3 of the 17 fields there. Cool. Now I know where to warp. "This javascript is written by a moron" Yeah. this is my first major javascript attempt. I was making this a web 2.0 page (so, one page load, then only DOM changes after) purely for practice/learning. You'll note I don't import jquery or any libraries. This is all hand-written baisc javascript. Maybe I'm a masochist. Maybe I just like learning. *shrug* "How did you get the navs?" Log into the game (ON ONE CHARACTER, DO NOT MULTIBOX FOR THIS unless using separate installs), enter a sector, Create a private channel (mostly to avoid spamming others) Target a 'normal' nav, press ctrl+t (I remapped it to b for easier pressing) Press n to go to the next visible nav Press ctrl+t Repeat until all visible navs are reported in chat. Don't worry about duplicates, the code below is smart enough to remove them. type 'hidden' in your private channel press 'x' to target the nearest hidden nav (move away if asteroids are in view) Press ctrl+t Press n to go to the next hidden nav Repeat until all hidden navs are dumped Close game Run script below on your chat.log file Copy-paste output into the "Add Navs" button, after you check the 'JSON Import' checkbox I imagine a script similar to this will be useful for import mobs/roids from chat as well. I did not do the work to make this a thing though. Also, I did not learn sectors could be asymmetric (like Grissom, where it's box is -450|575|-525|650) until I was almost done adding sectors, so roughly 40 of the 69 sectors in the game should be double-checked for asymmetry. sector_ids 1-53 are suspect, basically. Xipe Totec was the first asymmetric zone I remember finding. After Xipe, I made sure to check all 4 dimensions instead of assuming symmetry. --Parse_navs.py-- #!/usr/bin/python3 import argparse import json import re """THis list is also defined in functions.php and map_script.js, update all 3! $NAV_TYPE_MAP_BASE = [ "Normal Nav" => 0, "Hidden Nav" => 1, "Normal Gate" => 2, "Faction Gate" => 3, "Normal Station" => 4, "Faction Station" => 5, "Planet" => 6, "Landable Planet" => 7, "Weft" => 8, "Extended Weft" => 9, "Ore Field" => 10 ]; """ def parse_log(file_path, channel_name, player_name): results = {} sector_pattern = re.compile(r"We have entered (.+?) Sector \((.+?)\)") target_pattern = re.compile(r"\[(\d+)\] " + re.escape(player_name) + r": Target '(.+?)' at \(([-\d.]+), ([-\d.]+), ([-\d.]+)\)") hidden_nav_pattern = re.compile(r"\[(\d+)\] " + re.escape(player_name) + r": hidden") current_sector = "" hidden_nav_mode = False with open(file_path, 'r', encoding='latin-1') as file: for line in file: sector_match = sector_pattern.search(line) if sector_match: sector_name = sector_match.group(1) system_name = sector_match.group(2) current_sector = sector_name #print(f"We have entered {sector_name} ({system_name})") hidden_nav_mode = False if hidden_nav_pattern.search(line): hidden_nav_mode = True target_match = target_pattern.search(line) if target_match: target_channel = target_match.group(1) target_name = target_match.group(2) nav_type = 0; if target_name.startswith("Sector Gate ") or target_name.startswith("Accelerator") or target_name.startswith("System Gate"): nav_type = 2; if hidden_nav_mode: nav_type = 1 if target_channel == channel_name: x_coord = int(round(float(target_match.group(3)),0)) y_coord = int(round(float(target_match.group(4)),0)) z_coord = int(round(float(target_match.group(5)),0)) if current_sector not in results: results[current_sector] = {} #Assume normal navs until we have some way to mark other things automatically. coord_pack = [x_coord, y_coord, z_coord, nav_type] if target_name not in results[current_sector]: results[current_sector][target_name] = [coord_pack] elif coord_pack not in results[current_sector][target_name]: results[current_sector][target_name].append(coord_pack) #print(f"Target '{target_name}' at ({x_coord}, {y_coord}, {z_coord}, {nav_type})") #print(json.dumps(results, indent=4)) #print(results) print(format_output(results)) def format_output(data): formatted_output = "{" for key in data: formatted_output += f'"{key}": {{\n' for nav in data[key]: formatted_output += f'\t"{nav}": {data[key][nav]},\n' formatted_output = formatted_output[:-2] + "},\n" # Remove the trailing comma and newline character for the last line formatted_output = formatted_output[:-2] + "}\n" return formatted_output if __name__ == "__main__": parser = argparse.ArgumentParser(description="Parse log file") parser.add_argument("file", help="Path to the log file") parser.add_argument("--channel", default="100", help="Channel name (default: [100])") parser.add_argument("--player", default="Doctorje", help="Player name (default: Doctorje)") args = parser.parse_args() parse_log(args.file, args.channel, args.player) --parse_ores.py-- #!/usr/bin/python3 import argparse import json import re import sqlite3 import sys RESOURCE_SOURCE_MAP = { "Copper Ore": ["Rock", 1], "Crude Graphite": ["Hydrocarbon", 1], "Crude Nickel": ["Rock", 1], "Diridium Crystal": ["Crystal", 1], "Hydrogen": ["Gas", 1], "Iron Ore": ["Rock", 1], "Lead Ore": ["Rock", 1], "Lithium Ore": ["Glowing", 1], "Magnetite Ore": ["Rock", 1], "Mordanite": ["Rock", 1], "Nitrogen": ["Gas", 1], "Oxygen": ["Gas", 1], "Potash": ["Glowing", 1], "Raw Agate": ["Crystal", 1], "Raw Beryl": ["Crystal", 1], "Raw Black Tazeron": ["Hydrocarbon", 1], "Raw Bloodstone": ["Crystal", 1], "Raw Blue Tazeron": ["Hydrocarbon", 1], "Raw Crude Coal": ["Hydrocarbon", 1], "Raw Green Tazeron": ["Hydrocarbon", 1], "Raw Jasper": ["Crystal", 1], "Raw Onyx": ["Crystal", 1], "Raw Red Tazeron": ["Hydrocarbon", 1], "Raw Topaz": ["Crystal", 1], "Raw White Tazeron": ["Hydrocarbon", 1], "Raw Yellow Tazeron": ["Hydrocarbon", 1], "Sand": ["Hydrocarbon", 1], "Sponge Chloride": ["Crystal", 1], "Sulfates": ["Glowing", 1], "Tar": ["Hydrocarbon", 1], "Uranium Ore": ["Glowing", 1], "Zinc Ore": ["Rock", 1], "Aluminium Ore": ["Rock", 2], "Brominite": ["Glowing", 2], "Cadmium Ore": ["Glowing", 2], "Carbon Dioxide": ["Gas", 2], "Crude Oil": ["Hydrocarbon", 2], "Germanium Ore": ["Rock", 2], "Helium": ["Gas", 2], "Hermesite": ["Rock", 2], "Magnesium Ore": ["Rock", 2], "Methane": ["Gas", 2], "Molybdenum Ore": ["Rock", 2], "Phosphates": ["Glowing", 2], "Plutonium Ore": ["Glowing", 2], "Quartz Crystals": ["Hydrocarbon", 2], "Raw Amethyst": ["Crystal", 2], "Raw Coal": ["Hydrocarbon", 2], "Raw Garnet": ["Crystal", 2], "Raw Moonstone": ["Crystal", 2], "Raw Sunstone": ["Crystal", 2], "Raw Tourmaline": ["Crystal", 2], "Raw Turquoise": ["Crystal", 2], "Tin Ore": ["Rock", 2], "Zircon": ["Rock", 2], "Alanite": ["Glowing", 3], "Barite": ["Glowing", 3], "Boronite": ["Glowing", 3], "Caesium Ore": ["Rock", 3], "Calcite": ["Glowing", 3], "Cobalite": ["Rock", 3], "Fluorine": ["Gas", 3], "Gallium Ore": ["Rock", 3], "Indium Ore": ["Rock", 3], "Light Crude Oil": ["Hydrocarbon", 3], "Obsidian": ["Hydrocarbon", 3], "Polonium Ore": ["Glowing", 3], "Raw Alexandrite": ["Crystal", 3], "Raw Anthracite": ["Hydrocarbon", 3], "Raw Citrine": ["Crystal", 3], "Raw Lapis Lazuli": ["Crystal", 3], "Raw Malachite": ["Crystal", 3], "Star Iron Ore": ["Rock", 3], "Tungsten Ore": ["Rock", 3], "Anubium Ore": ["Rock", 4], "Argon": ["Gas", 4], "Californium Ore": ["Glowing", 4], "Ceresite": ["Glowing", 4], "Galactic Ore": ["Rock", 4], "Herculinium Ore": ["Rock", 4], "Manganese Ore": ["Rock", 4], "Meteoric Sand": ["Hydrocarbon", 4], "Neon": ["Gas", 4], "Raw Black Opal": ["Crystal", 4], "Raw Centauricite": ["Rock", 4], "Raw Fire Opal": ["Crystal", 4], "Raw Flawless Garnet": ["Crystal", 4], "Raw Ruby": ["Crystal", 4], "Saganite": ["Glowing", 4], "Silver Ore": ["Rock", 4], "Solar Sweet Oil": ["Hydrocarbon", 4], "Vanadium Ore": ["Rock", 4], "Andromesite": ["Hydrocarbon", 5], "Bastinium Ore": ["Glowing", 5], "Brood Oil": ["Hydrocarbon", 5], "Chromium Ore": ["Rock", 5], "Gold Ore": ["Rock", 5], "Hades Blood": ["Hydrocarbon", 5], "Halon": ["Gas", 5], "Hawkinsite": ["Glowing", 5], "Minervite": ["Glowing", 5], "Radium Ore": ["Glowing", 5], "Radon": ["Gas", 5], "Raw Black Pearl": ["Crystal", 5], "Raw Capellicite": ["Crystal", 5], "Raw Heartstone": ["Crystal", 5], "Raw Sapphire": ["Crystal", 5], "Raw Skystone": ["Crystal", 5], "Rhodite": ["Rock", 5], "Titanium Ore": ["Rock", 5], "Zalmoxium Ore": ["Rock", 5], "Adamantine Ore": ["Rock", 6], "Charon's Dust": ["Hydrocarbon", 6], "Curium Ore": ["Glowing", 6], "Discordite": ["Glowing", 6], "Hafnium Ore": ["Rock", 6], "Homerite": ["Rock", 6], "Krypton": ["Gas", 6], "Mirandium Ore": ["Rock", 6], "Osirium Ore": ["Rock", 6], "Oxium Ore": ["Rock", 6], "Platinum Ore": ["Rock", 6], "Raw Anthenicite": ["Hydrocarbon", 6], "Raw Charon Crystal": ["Glowing", 6], "Raw Emerald": ["Crystal", 6], "Raw Flawless Ruby": ["Crystal", 6], "Raw Icy Pearl": ["Crystal", 6], "Stygian Blackwater": ["Hydrocarbon", 6], "Xenon": ["Gas", 6], "Apollonite": ["Glowing", 7], "Brucite Ore": ["Glowing", 7], "Celestial Ore": ["Rock", 7], "Chalcophanite": ["Glowing", 7], "Conorite": ["Rock", 7], "Cupidite": ["Glowing", 7], "Demeter's Tears": ["Hydrocarbon", 7], "Horusium Ore": ["Glowing", 7], "Inderite": ["Rock", 7], "Iridium Ore": ["Rock", 7], "Leonite": ["Hydrocarbon", 7], "Neutronium Ore": ["Glowing", 7], "Niobite": ["Rock", 7], "Raw Barite": ["Glowing", 7], "Raw Diamond": ["Crystal", 7], "Raw Eye Stone": ["Crystal", 7], "Raw Firerock": ["Crystal", 7], "Raw Galactic Rimstone": ["Crystal", 7], "Raw Hadecite": ["Hydrocarbon", 7], "Stojsavline": ["Gas", 7], "Stygian Blacksand": ["Hydrocarbon", 7], "Tantalum Ore": ["Rock", 7], "Vaneon": ["Gas", 7], "Wexeon": ["Gas", 7], "Abyssian Dust": ["Hydrocarbon", 8], "Ambrosia Crude": ["Hydrocarbon", 8], "Boragon": ["Gas", 8], "Duplium Ore": ["Rock", 8], "Emperion": ["Gas", 8], "Helvatha": ["Hydrocarbon", 8], "Idunium Ore": ["Rock", 8], "Khnumium Ore": ["Glowing", 8], "Minosium Ore": ["Glowing", 8], "Morganium Ore": ["Rock", 8], "Persephonite": ["Glowing", 8], "Pyrrhotite Gneiss": ["Hydrocarbon", 8], "Raw Acheronite": ["Hydrocarbon", 8], "Raw Alunite": ["Rock", 8], "Raw Charybdis Voidstone": ["Crystal", 8], "Raw Meteoric Diamond": ["Crystal", 8], "Raw Mica": ["Hydrocarbon", 8], "Raw Scyllan Diamond": ["Crystal", 8], "Raw Tincal": ["Rock", 8], "Raw Voidgem": ["Crystal", 8], "Vanirum Ore": ["Rock", 8], "Yunieon Gas": ["Gas", 8], "Abaddon Ashes": ["Hydrocarbon", 9], "Aesirium Ore": ["Rock", 9], "Asmodeusium Ore": ["Rock", 9], "Astralite": ["Hydrocarbon", 9], "Balderium Ore": ["Rock", 9], "Crude Rutha": ["Hydrocarbon", 9], "Etherion": ["Gas", 9], "Grail Water": ["Hydrocarbon", 9], "Kronosite": ["Glowing", 9], "Modredium Ore": ["Rock", 9], "Nova Dust": ["Crystal", 9], "Noxion": ["Gas", 9], "Pagion": ["Gas", 9], "Raw Erebusite": ["Hydrocarbon", 9], "Raw Promethium": ["Rock", 9], "Raw Star Ore": ["Rock", 9], "Raw Tiberium Crystals": ["Crystal", 9], "Raw Wormstone": ["Crystal", 9], "Star Ash": ["Crystal", 9], "Thothium Ore": ["Hydrocarbon", 9], "Troseki": ["Rock", 9], "Ziosite": ["Hydrocarbon", 9] } """ Error List: Raw Erebusite comes from Hydrocarbon, not Crystal Raw Charon Crystal comes from Glowing, not Crystal? """ """THis list is also defined in functions.php and map_script.js, update all 3! $NAV_TYPE_MAP_BASE = [ "Normal Nav" => 0, "Hidden Nav" => 1, "Normal Gate" => 2, "Faction Gate" => 3, "Normal Station" => 4, "Faction Station" => 5, "Planet" => 6, "Landable Planet" => 7, "Weft" => 8, "Extended Weft" => 9, "Ore Field" => 10 ]; """ def createDB(db_cur): sql = """CREATE TABLE raw_roids( roid_id INTEGER PRIMARY KEY, sector_name TEXT NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, z INTEGER NOT NULL, source_type TEXT CHECK(source_type IN ('Rock','Glowing','Hydrocarbon','Crystal','Gas','Hulk')) NOT NULL, level INTEGER NOT NULL CHECK(level IN (1,2,3,4,5,6,7,8,9)))""" db_cur.execute(sql); sql = """CREATE TABLE raw_roid_contents( raw_roid_id INTEGER NOT NULL, roid_held TEXT NOT NULL, quantity INTEGER NOT NULL, FOREIGN KEY (raw_roid_id) REFERENCES raw_roids(roid_id) ON DELETE CASCADE)""" db_cur.execute(sql); def getNextID(db_cur, table_name, id_col_name): db_cur.execute(f"""SELECT IFNULL(MIN(t1.{id_col_name} + 1),1) AS next_available_id FROM {table_name} t1 LEFT JOIN {table_name} t2 ON t1.{id_col_name} + 1 = t2.{id_col_name} WHERE t2.{id_col_name} IS NULL;""") return db_cur.fetchone()[0] def addDataToDB(db_cur, roid_details, mining_details): #Check if the roid is already in the DB db_cur.execute("SELECT roid_id FROM raw_roids WHERE sector_name = ? AND x = ? AND y = ? AND z = ? AND source_type = ? AND level = ?", roid_details) row = db_cur.fetchone() roid_id = 0 if row: roid_id = row[0] else: roid_id = getNextID(db_cur, "raw_roids", "roid_id") final_roid_details = (roid_id,) + roid_details db_cur.execute('INSERT INTO raw_roids (roid_id, sector_name, x, y, z, source_type, level) VALUES (?,?, ?,?,?, ?,?)', final_roid_details) if mining_details: final_mining_details = (roid_id,) + mining_details db_cur.execute("INSERT INTO raw_roid_contents(raw_roid_id, roid_held, quantity) VALUES (?,?,?)", final_mining_details) #end addDataToDB def parse_log(file_path, channel_name, player_name): results = {} db_conn = sqlite3.connect(":memory:") #db_conn = sqlite3.connect("testing.db") db_conn.row_factory = sqlite3.Row db_cur = db_conn.cursor() createDB(db_cur) sector_pattern = re.compile(r"We have entered (.+?) Sector \((.+?)\)") target_pattern = re.compile(r"\[" + re.escape(channel_name) + "] " + re.escape(player_name) + r": Target '(.+?)' at \(([-\d.]+), ([-\d.]+), ([-\d.]+)\)") target_with_level_pattern = re.compile(r"\[" + re.escape(channel_name) + "] " + re.escape(player_name) + r": ([-\d.]+) Target '(.+?)' at \(([-\d.]+), ([-\d.]+), ([-\d.]+)\)") prospect_pattern = re.compile(r"COMPUTER: Prospected \(([-\d.]+)\) (.+?)$") roid_level_pattern = re.compile(r"\[(\d+)\] " + re.escape(player_name) + r": ([-\d.]+) Target '") current_sector = "" current_roid_level = 0 current_roid = None current_roid_ingame_type = None roid_contents = [] with open(file_path, 'r', encoding='latin-1') as file: for line in file: sector_match = sector_pattern.search(line) if sector_match: sector_name = sector_match.group(1) system_name = sector_match.group(2) current_sector = sector_name current_roid_level = 0 current_roid = None current_roid_ingame_type = None #print(f"We have entered {sector_name} ({system_name})") roid_level_match = roid_level_pattern.search(line) if roid_level_match: current_roid_level = roid_level_match.group(2) hulk_match = target_with_level_pattern.search(line) target_match = target_pattern.search(line) if (hulk_match or target_match) and current_roid is not None: if current_roid_ingame_type != 'Hulk': #Run through once to find max level ore in roid, but not for hulks. for contents in roid_contents: if current_roid[5] < RESOURCE_SOURCE_MAP[contents[0]][1]: current_roid[5] = RESOURCE_SOURCE_MAP[contents[0]][1] #and again to actually update db for contents in roid_contents: addDataToDB(db_cur, tuple(current_roid), contents) roid_contents = [] if hulk_match: target_level = hulk_match.group(1) target_name = hulk_match.group(2) x_coord = int(round(float(hulk_match.group(3)),0)) y_coord = int(round(float(hulk_match.group(4)),0)) z_coord = int(round(float(hulk_match.group(5)),0)) current_roid = [sector_name, x_coord,y_coord,z_coord, target_name, target_level] current_roid_ingame_type = target_name if target_match: target_name = target_match.group(1) x_coord = int(round(float(target_match.group(2)),0)) y_coord = int(round(float(target_match.group(3)),0)) z_coord = int(round(float(target_match.group(4)),0)) current_roid = [sector_name, x_coord,y_coord,z_coord, None, 0] current_roid_ingame_type = target_name prospect_match = prospect_pattern.search(line) if prospect_match and current_roid is not None: quantity = prospect_match.group(1) ore_name = prospect_match.group(2) if ore_name in RESOURCE_SOURCE_MAP: roid_type = RESOURCE_SOURCE_MAP[ore_name][0] roid_type_ingame = "" if roid_type == "Rock" or roid_type == "Glowing": roid_type_ingame = "Asteroid" elif roid_type == "Crystal": roid_type_ingame = "Crystalline Asteroid" elif roid_type == "Gas": roid_type_ingame = "Gas Cloud" elif roid_type == "Hydrocarbon": roid_type_ingame = "Hydrocarbon Deposit" else: roid_type_ingame = roid_type if current_roid_ingame_type != roid_type_ingame: print(f"INVALID SOURCE ERROR: {current_roid} has contents {ore_name} which should come from {roid_type} type roids.") sys.exit(1) current_roid[4] = roid_type roid_contents.append((ore_name, quantity)) db_conn.commit() db_cur.execute("Select * from raw_roids LEFT JOIN raw_roid_contents on roid_id = raw_roid_id") rows = db_cur.fetchall() the_json = dict() prev_roid_id = None for row in rows: # sys.stdout.write("DEBUG: ") # for data in row: # sys.stdout.write(f",{data} ") # sys.stdout.write("\n") if row['sector_name'] not in the_json: the_json[row['sector_name']] = dict() if row['source_type'] not in the_json[row['sector_name']]: the_json[row['sector_name']][row['source_type']] = [] roid_details = { "level": row['level'], "coords": [row['x'],row['y'],row['z']] } if row['roid_held'] is not None: if 'contents' not in roid_details: roid_details['contents'] = dict() roid_details['contents'][row['roid_held']] = row['quantity'] if row['roid_id'] != prev_roid_id: the_json[row['sector_name']][row['source_type']].append(roid_details) else: if row['roid_held'] in the_json[row['sector_name']][row['source_type']][-1]['contents']: the_json[row['sector_name']][row['source_type']][-1]['contents'][row['roid_held']] += row['quantity'] else: the_json[row['sector_name']][row['source_type']][-1]['contents'][row['roid_held']] = row['quantity'] prev_roid_id = row['roid_id'] #print(json.dumps(the_json, indent=4)) print(json.dumps(the_json)) #print(the_json) #print(format_output(the_json)) def format_output(data): formatted_output = "{" for sector in data: formatted_output += f'"{sector}": {{\n' for roid_type in data[sector]: formatted_output += f'\t"{roid_type}": [\n' for inner_data in data[sector][roid_type]: formatted_output += f'\t\t{{\n' for key in inner_data: formatted_output += f'\t\t\t"{key}": {inner_data[key]},\n' formatted_output = formatted_output[:-2] + "\n\t\t},\n" formatted_output = formatted_output[:-2] + "\n\t],\n" formatted_output = formatted_output[:-2] + "\n},\n" # Remove the trailing comma and newline character for the last line formatted_output = formatted_output[:-2] + "}\n" formatted_output = formatted_output.replace("'",'"') return formatted_output if __name__ == "__main__": parser = argparse.ArgumentParser(description="Parse log file") parser.add_argument("file", help="Path to the log file") parser.add_argument("--channel", default="100", help="Channel name (default: [100])") parser.add_argument("--player", default="Doctorje", help="Player name (default: Doctorje)") args = parser.parse_args() parse_log(args.file, args.channel, args.player) "How do I initialize this project?" I assume you are on linux and can get a basic PHP running on your own. sqlite3 better_map.db .read initialization_data/base_schema.sqlite_3 .read initialization_data/3_10_data_import.sql cat initialization_data/mining_data_1 Press "Add Ores", paste in the JSON, submit. cat initialization_data/mining_data_2 Press "Add Ores", paste in the JSON, submit. Edit get_missing_item.php and put in your net-7 username/password. Yes I know that 'isn't secure'. This is a one-person app that I'm likely to be the only user of. It's fine for that. If you do it 'for real', then make it better. Only needed if you want it to auto-fetch missing items (from Hulks) that are not currently in your DB. You should be good to go. The sector_data files are there as backups, in case the 3_10 import fails for some reason, or if you want to debug/test the JSON nav import feature. 4_28_backup_2.7z
-
That doesn't really work because Tarsis (the planet) is outside the bounds of the sector. Ragnarok as well. It's totally possible to have a nav outside the sector bounds when it is big enough to be seen from *within* the sector bounds. Well, technically, each nav has a 'detection radius' field, so you could just set every nav to be detectable from 300km and all the navs would be discovered the second you gated in. But that's obviously a bad idea. I'm saying that the client/server logic allows for this.
-
Just to illustrate visually how massive some of these sectors are compared to the navs in them (the white box is the in-game sector bounds, the navs are scaled correctly relative to the size of the white box): And, to show that not *every* sector is like this, here's a sector that's sized according to the navs within it: Side-note: I now appreciate how much work went into enbmaps.de because holy balls this is a lot of fiddling and tweaking. My maps are still very much a WIP. I plan to add overlays for mobs and ore, and filters to let you control name opacity (right now if you mouse over a nav the name becomes fully visible, but that's hard to screenshot) and scale. But I'm still at stage 3 or 4, where I'm going around collecting all the nav points in the game.
-
Why is Saturn so huge? It is 1176km tall and 782km wide. Why? The navs occupy an area that would fit within a (roughly) 250x250km square. Jupiter, also, is a bit wonky, being 800km wide when it needs to be roughly 1/2 that. Almost all sectors are symmetrical in their axis (meaning -588km in -X to 588km in +x, but the same sector can have a different Y, but Y is also symmetrical, so -188y and +188y, but X can be different). Except for (so far): X: (min, max) || Y: (min, max) Kitara's Veil: X: -157,157 || y: -75, 172 Kailaasa: X: -188, 529 || y: -149, 490 I found these while building a replacement for enbmaps. Mine is still very much in development. But these make no sense. So, I guess I have two fundamental questions: Why are some sectors 5x the size they need to be when there is nothing in the empty space? Or is there something there, and I'm going to have to do the most anal-retentive of box-searches using 4 characters and scanning lines 4km apart (since we can't detect hidden navs from > than about 3-4km) to find the one hidden nav somewhere in the middle of all that nothing? I was going to do the box searches anyway, it just makes it 100x more annoying. Why can't all the systems just have a symmetrical size? It makes the math to convert coordinate systems so much more annoying.
-
Looks like a bug with all MLs to me. I see the same behavior in Type Bs (lvl 4-6) and Type As (lvl 7-9). Projectiles seem fine. The search results are assuming a 37% reduction in reload time, while the item pages are assuming a 35% reduction in reload time. This is not the case with projectiles, where both assume a 37% reload time reduction at 200%. How or why this is the case? Only a web dev can answer that.
-
Win 10 is the reason my next PC is going to be Linux. Just holding out for zen5... (and I woulda built zen4 if I had known 5 was going to reuse that stupid-ass Promontory 21 chipset that only has 4x PCIe4.0 lanes instead of the PCIe5 lanes the CPU *actually* has for the chipset! grr!). Win 7 was the last 'ok' OS IMO.
-
For context: AFAIK: the in-game descriptions for skills cannot be changed because they are hard-coded in the client. Shield Nova, for example, states that it drains 21 energy per second per skill level, converting it at 130%, so it *should* do 21 * 10 * 1.3 = 273 DPS, but you can get it to hit for over 2000 DPS. So...clearly the devs decided to buff the ever-living-shizznit out of that particular skill for some reason, but only at lvl 7. This is also why mobs that have SI seem to melt you; because they are literally hitting you for around 2000 DPS from that skill.
-
Then let people help you. Give them access to the source, give them access to the servers, and let them actually, materially, help you. Many have offered over the years, and each seems to have been shot down for one reason or another. You are alone because you choose to be, not because you have to be. And I suspect that sentiment would be echoed by the broader playerbase, but maybe I'm wrong, so I'll let this post be my wind vain on that front. P.S. if you're still demanding an NDA be signed to help, drop it. On $5000/year of donations, there's no (practical) way to legally enforce it. It's an empty threat that puts a bad taste in the mouth of anyone with enough brain power to realize how pointless that threat and document are.
-
IDK which dev (one assumes Karu, but maybe there are others) who put the account name below the character name on the main portal. I often forget which account has which character, so that's super handy. thanks!
-
To be clear, I'm logged in and my JD can still fight/kill/trade to an alt. But apparently (from past experience) if I gate or dock at a station the game will break completely and I'll be unable to log in.
-
Woodman: I played EVE for about 8 years before winning at it. I know about Alpha/Omega/Skill injectors, but what's the thing that makes you not want to play? If I could find a WH corp that would let me do PvE and give me a fair split of income, I'd be happy with that. Stealth Bombing was about the only pvp I ever enjoyed.
-
Leveling mobs were balanced around the assumption that everyone would have 200% gear in all slots. This made mobs roughly 2x as hard as they 'should' be compared to Live. I'm pretty sure raids and other high-level content is balanced around max-resists and max-buffs. The devs have even gone to the effort of making some raids (Controller IIRC) impossible without more than one group specifically because they didn't like people being able to multi-box raids. So...you're asking to a nerf to something they've specifically balanced around.
-
There's a bunch of jokes I could make about women of the night given that it's up and down so much today. edit: and btw, it's down again.
-
Login server appears to be down again.
-
Help Requested: LUA code profiling/LUA experience
Doctor replied to Doctor's topic in General Discussion
Followup: It seems the problem is with Lua itself. It takes around 2 seconds to do anything at all. I've notified codemonkey and he's going to chase down options on either seeing if the wiki server can better handle lua (faster) or replacing lua if need be. -
The wiki is slow to load pages. I found one of the issues: https://www.net-7.org/wiki/index.php?title=Template_talk:Delink#And_the_proof_is_in_the_pudding With that change the "List of Ores" page goes from 20 seconds to load to just 1 second. My method was a bit of a hammer, I just removed the action it took entirely. But I suspect that is the right call given how it is used (effectively) everywhere via transclusion. The next slow template is actually a LUA module called by https://www.net-7.org/wiki/index.php?title=Template:Item/GenerateHeader The LUA module called is: https://www.net-7.org/wiki/index.php?title=Module:Item I gave a stab at fixing it, but my attempts did not yield useful results. If anyone here has any skill with profiling code, or writing LUA, and can see what is making that module take nearly 2 seconds to run, I'd love to know. And maybe I've picked the wrong target. Mediawiki's profiler only considers how long a *template* takes to run. If the template calls many sub-modules, then it is impossible to know (at least for me) which module is the culprit. I'd love to make the wiki faster, but I've hit the limit of what I know and can figure out for now.
-
I have reason to believe the Login server is down. My clients are stuck at "Connecting to server..." upon launch.
-
Update: Added a box to search by item name.
-
I wanted to share my technical achievement. I managed to automate (well, semi-automate, like a semi-auto vs full-auto gun) the process of FN->NV trade runs (and NV->FN) down to just pushing F3 (twice per sector). Here's what it CANNOT do: Walk around in stations Run in a loop where multiple clients are juggled (no way for it to know if you're warping or not, so i'd drop you out of warp) Fly around on NV planet. Be easily converted to something that is fully automated. So, it doesn't run afoul of the 'no unattended macros' rule. And, it has some (major) technical blockers that would prevent it from ever being useful while unattended for more than, maybe, 65 seconds at a time (AP warp from gate-to-gate is around 65 seconds ) I offer no support for this script. I share it because: It is a technical achievement and I want to brag a bit. Others can learn from my code and implement their own ideas Learning how to fix something is the best way to understand a new concept. I can't be bothered. It only works at 1280x700 resolution It is SUPER picky about graphics settings (maxed out, 100% on all sliders, don't touch Gamma), and any change in colors will break it. I also included some other useful scripts, like: how I launch my clients and run the unlocker on them automatically. Buying/selling trade items without (manually) clicking 40+ times. Inviting alts to a group. Moving items into the trade window (2 methods) Vault Collapse - Combine stacks in your vault! It's hard to see because they are mostly white and transparent, but just below this text I attached all the images the script needs to run. If there's a 'dark mode' for the forum, that would probably help see them. If not...uh... fn_nv_trade_automation_ahk.7z
-
An update has been released. It now includes Filters for Quality, Stack Size, and manufacturer.
-
Ohh...ohhhhh yes. This is SO NICE! Thank you! I can speed-click select stuff without the tooltip getting in the way. ❤️ To be clear, the tooltip is still entirely vital to the page being useful. So this feels like the best of all worlds. And removing the tooltip from the classlist would have been a huge pain since I use it as one of the targeting factors in my querySelectors. Thank you!
-
Yeah. I can totally add a checkbox to disable the tooltips. I almost have on several occasions. I'll work on that tomorrow. edit: I have a notification saying Karu quoted me...but I see no post from him?! Edit2: Ideally I'd just increase the timeout on the tooltip. Right now it displays instantly, but if I could add a, say, 500ms delay, it would probably fix all the problems with it being annoying. I haven't gotten jQuery (fundamentally the tooltip code lives in jquery from net7) and Tampermonkey to place nice with each other so far. I'm sure there's a way, I just haven't found it.
-
This tool makes the account-vault page click-to-select instead of drag-and-drop! (among other things) The Account Vault page is annoying to use. Drag-and-drop is slow. The tooltip likes to pop up under the mouse and left-clicking (which happens when your mouse switches are failing like mine; new ones on order) pops open a new tab instead of doing what you wanted. So, I decided to fix it. Asking for permissions has never gotten me very far here (or in life in general) so I just made what I wanted to exist. Now I share it, same as I do with all my wiki edits. https://imgur.com/FMFVUkq https://greasyfork.org/en/scripts/486075-net-7-account-vault-filters "What is it?" This is a tampermonkey/greasemonkey (and probably other similar software) script to add filters to the Account Vault page. How those applications work is basically just injecting javascript into the page and having certain functions run when it's useful. Tampermonkey and Greasemonkey are browser extensions, if you've never heard of them. If you install any-ole user script they CAN do very bad things, like steal passwords, fill your page with ads, etc. The link above shows the source code for what I'm doing, and you can clearly see none of that is going on. "What changed?" Drag-and-drop is replaced by click-to-select (click again, either on the item in the inventory or in the transfer slots) to remove. The old functionality of left-click (opening the Database page for that item) is moved to ctrl-click Visual filters to let you quickly see the items you are most likely to want to move and whether they are selected (green) or not. More filters are planned, but just what I have now is incredibly useful, not just for miners. "How do I use this?" Review/wait for comments to confirm I am not doing anything nefarious in the source code. Install Tampermonkey/Greasemonkey (both exist for FF and Chrome AFAIK, but it used to be one was the FF version and the other was the chrome version). Go to https://greasyfork.org/en/scripts/486075-net-7-account-vault-filters Click on "Install this script" It will pop up a box with an 'Install' button Go to the Account Vault page Happy filtering By default, tampermonkey checks for updates to scripts every day. It should inform you of updates, but I have not verified this procedure yet. What I have now is not the final form I see for this, but it is still very, very useful, so I wanted to share it as I continue to work on it. "What is this NOT?" It doesn't automate anything. It doesn't send any data anywhere. Not even to the net-7 servers (it lets the actions that are already on the page do that!) It doesn't log anything "What would I like to add/planned features?" Additional filters: PM-only Quality% via > # (so, only show items > 190% quality, for example) Stack Size > # (so, show me stacks only > 199 items) I'd love to have a 'full stacks only' checkbox, but the page doesn't contain the max stack size for items, so I'd need to hard code in the stack size for every item in the game (and there's literally 10,000+ items). Not really a good idea. Even if I just limited it to items with a stack size > 1, that's still 1000s of items. Item Name (it would be a 'contains' match, so CE3K would work, as would Asmo, etc) Allow filters to be combined (right now level filters can be combined, but combining PM-only with level filters takes a bit of code I haven't written yet. I know what to do, it's just a matter of writing it, and how much people want that) Filters beyond what is listed above would require dev support, as I can only work with the information already included in the page. "Notes for the Devs" Correct. I didn't ask you or tell you I was doing this. Now I have the thing I want and if I'm the only one who uses it, it still makes my life WAY better. So, I'm happy. You're welcome to look at what I've done and roll it into the page. On a development front, it is strange to me that the character list gets rebuilt every time the page changes. Like, if I load the vault for a character, the entire left-sidebar list of characters is rebuilt. Seems...excessive. Not sure if that's intentional or not. Also, can someone tell me (in a PM is fine) why you guys are sending CSS, Javascript, and HTML all in one response to the page? Why not just send a JSON object like most web pages and render that into the page content? It's not 'wrong' it's just strange. And WAY more data than needs to be sent. https://imgur.com/vqsXGXl For anyone curious, the above is what it looks like if you try to click on one of the 'red' items. It will let you select it and transfer it. I always assume you know what you're doing and don't try to get in your way. As I've said, the filters are visual only.
-
RTX Remix. Are you going to use it to enhance the games visuals?
Doctor replied to psionicinversion's topic in Suggestions
Long story short: No. Why? EA wants this EMU dead. They tolerate us so long as we don't modify the client via a formal agreement (I believe). So, if someone in the community wanted to get this working and make a forum post? People can modify the clients themselves to their heart's content. But the net-7 folks can't ship that or officially endorse it. -
Fundamental problem: Trade XP for trade goods is based on profit. Increasing the profit to a level where XP makes sense causes trade routes to create millions of credits per hour, eclipsing any other form of income generation. Trade XP from combat is superior to trade routes all the way to TL 50, assuming you have alts that are good at combat (PW/TW or multiple combat chars). Gating path 1: Make trade XP, for trade items >= lvl 6, scale with the item level. The core idea being that from TL 25 to TL 45 you would only increase the credits/hr from ~180k to ~360k (because trade runs can be multiboxed and combat doesn't scale the same way, so 6 * 360 = 2.1m/hr, which is 1/2 what combat can earn). but letting the trade level of the item make up for the slack in XP gain. This would be a new line of trade items starting at level 6 -> 9 (if needed). The other gate on this would be the sheer cost of the items. The level 6 items would cost around 1 million credits each, and only turn a 1100 or so credit profit, but they would offer nearly double the XP of the current IS->61 trade route (and that is why the item level would have to factor in at and above lvl 6). Level 7 trade items would be something that would kick in around TL 35, and cost 2 million each. (the actual cost would be a calculation of how long it would take to go from TL25-35 doing just trade runs, and the total profit expected during that time; the idea being you roll all your profits into affording the next best tier). Same patter for level 8 and 9 goods. It's also possible to just make the trade vendors refuse to speak to you unless you have TL35. Maybe make it part of a questline where you are 'trusted' to carry these expensive and rare goods. I'm ALL FOR making this something that has to be earned, rather than having brand newbies power leveling with it. Gating path 2: Give tradesmen a new ability, or line of devices, similar to the Polar Express, where they can carry 2, 3, 4, maybe up to 9 deep ONLY for trade goods. Or maybe make it a 7 level skill: TL0 / lvl 1: Allows you to carry 2 trade items per cargo slot in all slots. TL15 / lvl 2: Allows you to carry 3 trade items per cargo slot in all slots. TL25 / lvl 3: Allows you to carry 4 trade items per cargo slot in all slots. Also allows ores to stack to 600 instead of 300. TL35 / lvl 4: Allows you to carry 6 trade items per cargo slot in all slots. Allows ores to stack to 900. TL45 / lvl 5: Allows you to carry 9 trade items per cargo slot in all slots. Ores stack to 1200. Ammo can stack to 3x its normal stack size. At 9 stacks, the IS->61 route would print 1.6 million/hr, which is around 1/3 what a combat class can make using Shield Nova to farm low level mobs. So, from an income generation standpoint, it is not a big problem. Closing thoughts: Even if the skill only ever affected trade goods, that'd be fine. My overall goal is...frankly, until TL 25, it makes *way* more sense to level trade with a high level combat group (by looting their drops) than trade runs. Combat makes around 4-5 million credits/hr, trade makes 180,000. Combat can get you TL 21->25 in 1 hour, trade runs take almost 5 hours to do the same thing. I just want tradesmen to actually be masters of trading. And their cargo holds to be truly best-in-class. I want a tradesman to be able to level by trading in the same way a combat group can. And keep in mind, combat gets you trade and combat XP both, as well as more credits than trade runs. The only thing combat would be slower at is actual combat XP.