Skip to content

Commit

Permalink
Part Browser Changes: Added 'Add Part' button for new parts, tech req…
Browse files Browse the repository at this point in the history
…uired is now autofilled.

Tech required autofilled based on a mapping file of of year and category fields.

Standardized the line endings on generated files. (to LF only)

Updated the year field on the NK-9 (2009) to match its tech year.
mattwrobel committed Dec 28, 2018
1 parent f80ac23 commit f28c610
Showing 12 changed files with 2,382 additions and 202 deletions.
2 changes: 1 addition & 1 deletion Source/Tech Tree/Parts Browser/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# RP-0-Parts-Browser
This is a browser/editor application for the RP-0 parts list (converted to json files per mod) that can also generate the needed configs from it.
This is a browser/editor application for the RP-0 parts list (converted to json files per mod) that can also generate the needed configs from it, created by [@mattwrobel](https://github.com/mattwrobel)

To get it working:
1. It uses Python 3, so that needs to be installed.
34 changes: 29 additions & 5 deletions Source/Tech Tree/Parts Browser/app.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

from flask import jsonify
from part_data import PartData
from tech_mapping import TechMapping
from flask import Flask, g
from flask import Blueprint, abort, g, render_template, redirect, request, url_for
from slugify import slugify
@@ -13,6 +14,9 @@
from identical_parts_cfg_generator import generate_identical_parts

part_data = PartData()
tech_mapping = TechMapping()

tech_mapping.validate_current(part_data.parts)

def create_app(test_config=None):

@@ -49,18 +53,26 @@ def unique_values_for_column(column_name):
sorted_values = list(part_data.unique_values_for_columns[column_name])
sorted_values.sort()
return jsonify({"data": sorted_values})
@app.route('/api/tech_mapping/<category>/<year>')
def get_tech_mapping(category, year):
return tech_mapping.get_tech_by_category_and_year(category, year)
@app.route('/api/combo_options/<column_name>')
def combo_options(column_name):
sorted_values = list(part_data.unique_values_for_columns[column_name])
sorted_values.sort()
return jsonify({"data": list(map(lambda x: {column_name: x}, sorted_values))})
if column_name != 'year':
sorted_values = list(part_data.unique_values_for_columns[column_name])
sorted_values.sort()
return jsonify({"data": list(map(lambda x: {column_name: x}, sorted_values))})
else:
sorted_values = list(tech_mapping.unique_years)
sorted_values.sort()
return jsonify({"data": list(map(lambda x: {column_name: x}, sorted_values))})

@app.route('/api/export_to_json')
def export_to_json():
for mod in part_data.unique_values_for_columns['mod']:
parts_for_mod = list(filter(lambda x: x['mod'] == mod, part_data.parts))
parts_for_mod.sort(key=lambda x: x['name'] if x['name'] is not None and len(x['name']) > 0 else x['title'] )
text_file = open("data/" + make_safe_filename(mod) + ".json", "w")
text_file = open("data/" + make_safe_filename(mod) + ".json", "w", newline='\n')
text_file.write(json.dumps(parts_for_mod, indent=4, separators=(',', ': ')))
text_file.close()
return "true"
@@ -102,9 +114,21 @@ def generate_all_configs():
def commit_changes():
queued_changes = request.get_json()
for row_id in queued_changes['queued_changes']:
new_part = False
part = part_data.get_part_by_name(queued_changes['queued_changes'][row_id]['name'])
# if the part can't be found, we assume it's a new part
if part is None:
part = {}
new_part = True
# if the mod isn't set, that is almost certainly because we're
# adding to a new mod, need to set it from the new_mod field
if 'mod' in queued_changes['queued_changes'][row_id]['changes'] and queued_changes['queued_changes'][row_id]['changes']['mod']['new'] == "":
queued_changes['queued_changes'][row_id]['changes']['mod']['new'] = queued_changes['queued_changes'][row_id]['changes']['new_mod']['new']
for field_name in queued_changes['queued_changes'][row_id]['changes']:
part[field_name] = queued_changes['queued_changes'][row_id]['changes'][field_name]['new']
if field_name not in ['mod_type', 'new_mod']:
part[field_name] = queued_changes['queued_changes'][row_id]['changes'][field_name]['new']
if new_part:
part_data.add_new_part(part)
export_to_json()
return "true"

4 changes: 2 additions & 2 deletions Source/Tech Tree/Parts Browser/data/Engine_Config.json
Original file line number Diff line number Diff line change
@@ -4315,7 +4315,7 @@
"entry_cost": "0",
"category": "STAGED",
"info": "",
"year": "1969",
"year": "2009",
"technology": "stagedCombustion2009",
"era": "04-ADV",
"ro": true,
@@ -8343,4 +8343,4 @@
"identical_part_name": "",
"module_tags": []
}
]
]
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ def generate_ecm_engines(parts):
# for purposes I don't full understand, we replace all '.' and '_' characters with '-'
# and '?' with ' '. That's what the downstream code expects for whatever reason.
ecm_configs += module_part_config_template.substitute(name=part['name'].replace('_','-').replace('.','-').replace('?',' '), ecm=part['entry_cost_mods'])
text_file = open("output/ECM-Engines.cfg", "w")
text_file = open("output/ECM-Engines.cfg", "w", newline='\n')
text_file.write(tree_ecm_engines_header)
text_file.write(ecm_configs)
text_file.write(tree_ecm_engines_footer)
2 changes: 1 addition & 1 deletion Source/Tech Tree/Parts Browser/ecm_parts_cfg_generator.py
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ def generate_ecm_parts(parts):
# for purposes I don't full understand, we replace all '.' and '_' characters with '-'
# and '?' with ' ' in the part names. That's what the downstream code expects for whatever reason.
ecm_configs += module_part_config_template.substitute(name=part['name'].replace('_','-').replace('.','-').replace('?',' '), ecm=part['entry_cost_mods'])
text_file = open("output/ECM-Parts.cfg", "w")
text_file = open("output/ECM-Parts.cfg", "w", newline='\n')
text_file.write(tree_ecm_parts_header)
text_file.write(ecm_configs)
text_file.write(tree_ecm_parts_footer)
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ def generate_identical_parts(parts):
sorted_parts.sort()
identical_part_configs += identical_part_template.substitute(name=name, identical_parts=",".join(sorted_parts))

text_file = open("output/identicalParts.cfg", "w")
text_file = open("output/identicalParts.cfg", "w", newline='\n')
text_file.write(identical_parts_header)
text_file.write(identical_part_configs)
text_file.close()
1,963 changes: 1,963 additions & 0 deletions Source/Tech Tree/Parts Browser/mappings/TechRequiredMappings.csv

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion Source/Tech Tree/Parts Browser/part_data.py
Original file line number Diff line number Diff line change
@@ -32,7 +32,14 @@ def get_part_by_name(self, name):
for part in self.parts:
if part["name"] == name:
return part
print('Did not find part, sadface.')
return None

def add_new_part(self, new_part):
print(f'Adding new part with name: {new_part["name"]}')
# add the part to the data
self.parts.append(new_part)
# reindex the columns
self.index_columns()
return None

def __init__(self):
75 changes: 75 additions & 0 deletions Source/Tech Tree/Parts Browser/tech_mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import csv
import json
import sys
from os import listdir
from os.path import isfile, join

class TechMapping:
tech_map = {}
unique_years = set()

def get_tech_by_category_and_year(self, category, year):
print(f'Getting tech for category: {category} and {year}')
if category in self.tech_map:
if year in self.tech_map[category]:
return self.tech_map[category][year]
return "not found"

def __init__(self):
"""Load the parts from the json files."""
self.load_tech_mapping()
print(f'Initialized the Tech mappings.')

column_index = {}

def load_tech_mapping(self):
with open('mappings/TechRequiredMappings.csv') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
if line_count == 0:
print(f'Column names are {", ".join(row)}')
# store a map of column name to index for pulling into the part object
for entry in enumerate(row):
self.column_index[entry[1]] = entry[0]
line_count += 1
else:
line_count += 1
self.create_mapping(row)
print(f'Loaded {line_count-1} tech mappings.')

# This method creates the mappings given a row from the TechMapping CSV file.
def create_mapping(self,row):
category = self.get_value(row, "Category").upper()
year = self.get_value(row, "Year")
tech_required = self.get_value(row, "Tech")
self.unique_years.add(year)

if category in self.tech_map:
if year in self.tech_map[category]:
print("Error, duplicate tech mapping row: " + category + " - " + year)
self.tech_map[category][year] = tech_required
else:
self.tech_map[category] = {year: tech_required}


# protects against missing keys causing exceptions in original import
def get_value(self,row,column_name):
if column_name in self.column_index:
return row[self.column_index[column_name]]
else:
return None

def validate_current(self, parts):
for part in parts:
name = part['name']
tech_required = part['technology']
category = part['category']
year = part['year']
expected_tech = self.tech_map[category][year]
if tech_required != expected_tech:
print(f"Tech mismatch for {name} - {category} - {year} expected: {expected_tech} actual: {tech_required}")




247 changes: 179 additions & 68 deletions Source/Tech Tree/Parts Browser/templates/browser/dashboard.html

Large diffs are not rendered by default.

152 changes: 76 additions & 76 deletions Source/Tech Tree/Parts Browser/tree_engine_cfg_generator.py
Original file line number Diff line number Diff line change
@@ -1,76 +1,76 @@
import part_data
from string import Template

tree_engine_header = """
//*********************************************************************************************
// ENGINE_CONFIG TECH TREE PLACEMENT
// This places the Engine_Config parts and creates the upgrade icons for the tree
//
// DO NOT EDIT THIS FILE DIRECTLY!!!
// This file is generated using the RP-0 Parts Browser
//
//*********************************************************************************************
@PART[*]:HAS[@MODULE[ModuleEngineConfigs]]:BEFORE[RealismOverhaulEnginesPost]
{
@MODULE[ModuleEngineConfigs],*
{
"""

tree_engine_mid = """
}
}
"""

module_engine_config_template = Template("""
@CONFIG[${name}]
{
%techRequired = ${technology}
%cost = ${cost}${optional_attributes}
}
""")

part_upgrade_config_template = Template("""
PARTUPGRADE
{
name = RFUpgrade_${name}
partIcon = RO-H1-RS27 // FIXME Once we get dedicated model
techRequired = ${technology}
entryCost = 0
cost = 0
title = ${engine_config} Engine Upgrade: ${name} Config
basicInfo = Engine Performance Upgrade
manufacturer = Engine Upgrade
deleteme = 1
description = The ${engine_config} Engine now supports the ${name} configuration for increased performance. Unlock it in the VAB/SPH through the engine configs interface.\\n\\n${description}
}
""")

def generate_engine_tree(parts):
engine_configs = ""
part_upgrades = ""
for part in parts:
if "Engine_Config" == part["mod"] and not part['orphan']:
engine_configs += generate_engine_config(part)
if 'upgrade' in part and part['upgrade'] is True:
part_upgrades += generate_part_upgrade_config(part)
text_file = open("output/TREE-Engines.cfg", "w")
text_file.write(tree_engine_header)
text_file.write(engine_configs)
text_file.write(tree_engine_mid)
text_file.write(part_upgrades)
text_file.close()

def generate_engine_config(part):
optional_attributes = ""
if 'description' in part and len(part['description']) > 0:
optional_attributes += """
%description = """ + part['description']
if 'upgrade' in part and part['upgrade'] is True:
optional_attributes += """
*@PARTUPGRADE[RFUpgrade_""" + part['name'] + """]/deleteme -= 1"""
return module_engine_config_template.substitute(name=part['name'], technology=part['technology'], cost=part['cost'], optional_attributes=optional_attributes)

def generate_part_upgrade_config(part):
return part_upgrade_config_template.substitute(name=part['name'], technology=part['technology'], engine_config=part['engine_config'], description=part['description'])

import part_data
from string import Template

tree_engine_header = """
//*********************************************************************************************
// ENGINE_CONFIG TECH TREE PLACEMENT
// This places the Engine_Config parts and creates the upgrade icons for the tree
//
// DO NOT EDIT THIS FILE DIRECTLY!!!
// This file is generated using the RP-0 Parts Browser
//
//*********************************************************************************************
@PART[*]:HAS[@MODULE[ModuleEngineConfigs]]:BEFORE[RealismOverhaulEnginesPost]
{
@MODULE[ModuleEngineConfigs],*
{
"""

tree_engine_mid = """
}
}
"""

module_engine_config_template = Template("""
@CONFIG[${name}]
{
%techRequired = ${technology}
%cost = ${cost}${optional_attributes}
}
""")

part_upgrade_config_template = Template("""
PARTUPGRADE
{
name = RFUpgrade_${name}
partIcon = RO-H1-RS27 // FIXME Once we get dedicated model
techRequired = ${technology}
entryCost = 0
cost = 0
title = ${engine_config} Engine Upgrade: ${name} Config
basicInfo = Engine Performance Upgrade
manufacturer = Engine Upgrade
deleteme = 1
description = The ${engine_config} Engine now supports the ${name} configuration for increased performance. Unlock it in the VAB/SPH through the engine configs interface.\\n\\n${description}
}
""")

def generate_engine_tree(parts):
engine_configs = ""
part_upgrades = ""
for part in parts:
if "Engine_Config" == part["mod"] and not part['orphan']:
engine_configs += generate_engine_config(part)
if 'upgrade' in part and part['upgrade'] is True:
part_upgrades += generate_part_upgrade_config(part)
text_file = open("output/TREE-Engines.cfg", "w", newline='\n')
text_file.write(tree_engine_header)
text_file.write(engine_configs)
text_file.write(tree_engine_mid)
text_file.write(part_upgrades)
text_file.close()

def generate_engine_config(part):
optional_attributes = ""
if 'description' in part and len(part['description']) > 0:
optional_attributes += """
%description = """ + part['description']
if 'upgrade' in part and part['upgrade'] is True:
optional_attributes += """
*@PARTUPGRADE[RFUpgrade_""" + part['name'] + """]/deleteme -= 1"""
return module_engine_config_template.substitute(name=part['name'], technology=part['technology'], cost=part['cost'], optional_attributes=optional_attributes)

def generate_part_upgrade_config(part):
return part_upgrade_config_template.substitute(name=part['name'], technology=part['technology'], engine_config=part['engine_config'], description=part['description'])

92 changes: 46 additions & 46 deletions Source/Tech Tree/Parts Browser/tree_parts_cfg_generator.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
import part_data
from string import Template

tree_parts_header = """
//*********************************************************************************************
// PARTS TECH TREE PLACEMENT
// This places all parts in the tech tree
//
// DO NOT EDIT THIS FILE DIRECTLY!!!
// This file is generated from the RP-0 Parts Browser
//
//*********************************************************************************************
"""

module_part_config_template = Template("""
@PART[${name}]:FOR[xxxRP0]
{
%TechRequired = ${technology}
%cost = ${cost}
%entryCost = ${entry_cost}
RP0conf = ${rp0_conf}
@description ^=:$$: <b><color=green>From ${mod} mod</color></b>${module_tags}
}""")
module_tag_template = Template("""
MODULE
{ name = ModuleTag${module_tag} }""")

def generate_parts_tree(parts):
part_configs = ""
for part in parts:
if part['name'] is not None and len(part['name']) > 0:
if part['mod'] != 'Engine_Config' and not part['orphan']:
part_configs += generate_part_config(part)
text_file = open("output/TREE-Parts.cfg", "w")
text_file.write(tree_parts_header)
text_file.write(part_configs)
text_file.close()

def generate_part_config(part):
module_tags = ''
for module_tag in part['module_tags']:
module_tags += module_tag_template.substitute(module_tag=module_tag)
if len(module_tags) > 0:
module_tags = "\n" + module_tags + "\n"
return module_part_config_template.substitute(name=part['name'], mod=part['mod'], technology=part['technology'], cost=part['cost'], entry_cost=part['entry_cost'], rp0_conf=str(part['rp0_conf']).lower(), module_tags=module_tags)

import part_data
from string import Template

tree_parts_header = """
//*********************************************************************************************
// PARTS TECH TREE PLACEMENT
// This places all parts in the tech tree
//
// DO NOT EDIT THIS FILE DIRECTLY!!!
// This file is generated from the RP-0 Parts Browser
//
//*********************************************************************************************
"""

module_part_config_template = Template("""
@PART[${name}]:FOR[xxxRP0]
{
%TechRequired = ${technology}
%cost = ${cost}
%entryCost = ${entry_cost}
RP0conf = ${rp0_conf}
@description ^=:$$: <b><color=green>From ${mod} mod</color></b>${module_tags}
}""")
module_tag_template = Template("""
MODULE
{ name = ModuleTag${module_tag} }""")

def generate_parts_tree(parts):
part_configs = ""
for part in parts:
if part['name'] is not None and len(part['name']) > 0:
if part['mod'] != 'Engine_Config' and not part['orphan']:
part_configs += generate_part_config(part)
text_file = open("output/TREE-Parts.cfg", "w", newline='\n')
text_file.write(tree_parts_header)
text_file.write(part_configs)
text_file.close()

def generate_part_config(part):
module_tags = ''
for module_tag in part['module_tags']:
module_tags += module_tag_template.substitute(module_tag=module_tag)
if len(module_tags) > 0:
module_tags = "\n" + module_tags + "\n"
return module_part_config_template.substitute(name=part['name'], mod=part['mod'], technology=part['technology'], cost=part['cost'], entry_cost=part['entry_cost'], rp0_conf=str(part['rp0_conf']).lower(), module_tags=module_tags)

0 comments on commit f28c610

Please sign in to comment.