diff --git a/SourceManager.py b/SourceManager.py index ae8302f..f73a897 100755 --- a/SourceManager.py +++ b/SourceManager.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/python3.6 VERSION = 1.0 #SYS IMPORTS @@ -27,14 +27,14 @@ DEBUG = 0 ALL_YES = 0 class colors: - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' class new_o: def __init__(self, server_name): @@ -89,6 +89,228 @@ def create_symbolic_file(source_file, symbolic_file): print(f"Execute system cmd: {cmd}") SCall(cmd.split()) +class tf2idb: + def __init__(self, TF_FOLDER, ITEMS_GAME, DB_FILE): + import vdf + import sqlite3 + import collections + import copy + + def dict_merge(dct, merge_dct): + """ Recursive dict merge. Inspired by :meth:``dict.update()``, instead of + updating only top-level keys, dict_merge recurses down into dicts nested + to an arbitrary depth, updating keys. The ``merge_dct`` is merged into + ``dct``. + :param dct: dict onto which the merge is executed + :param merge_dct: dct merged into dct + :return: None + """ + for k, v in merge_dct.items(): + if (k == 'used_by_classes' or k == 'model_player_per_class'): #handles Demoman vs demoman... Valve pls + v = dict((k2.lower(), v2) for k2, v2 in v.items()) + if (k in dct and isinstance(dct[k], dict) and isinstance(v, collections.Mapping)): + dict_merge(dct[k], v) + else: + dct[k] = copy.deepcopy(v) + + def resolve_prefabs(item, prefabs): + # generate list of prefabs + prefab_list = item.get('prefab', '').split() + for prefab in prefab_list: + subprefabs = prefabs[prefab].get('prefab', '').split() + prefab_list.extend(p for p in subprefabs if p not in prefab_list) + + # iterate over prefab list and merge, nested prefabs first + result = {} + for prefab in ( prefabs[p] for p in reversed(prefab_list) ): + dict_merge(result, prefab) + + dict_merge(result, item) + return result, prefab_list + + data = None + + ITEMS_GAME = TF_FOLDER + "/" + ITEMS_GAME + DB_FILE = TF_FOLDER + "/" + DB_FILE + + db = sqlite3.connect(DB_FILE) + dbc = db.cursor() + + with open(ITEMS_GAME) as f: + data = vdf.parse(f) + data = data['items_game'] + + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_class') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_item_attributes') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_item') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_particles') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_equip_conflicts') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_equip_regions') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_capabilities') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_attributes') + dbc.execute('DROP TABLE IF EXISTS new_tf2idb_qualities') + + dbc.execute('CREATE TABLE "new_tf2idb_class" ("id" INTEGER NOT NULL , "class" TEXT NOT NULL , "slot" TEXT , PRIMARY KEY ("id", "class"))') + dbc.execute('CREATE TABLE "new_tf2idb_item_attributes" (' + '"id" INTEGER NOT NULL,' + '"attribute" INTEGER NOT NULL,' + '"value" TEXT NOT NULL,' + '"static" INTEGER,' + 'PRIMARY KEY ("id", "attribute")' + ')' + ) + dbc.execute('CREATE TABLE "new_tf2idb_item" (' + '"id" INTEGER PRIMARY KEY NOT NULL,' + '"name" TEXT NOT NULL,' + '"item_name" TEXT,' + '"class" TEXT NOT NULL,' + '"slot" TEXT,' + '"quality" TEXT NOT NULL,' + '"tool_type" TEXT,' + '"min_ilevel" INTEGER,' + '"max_ilevel" INTEGER,' + '"baseitem" INTEGER,' + '"holiday_restriction" TEXT,' + '"has_string_attribute" INTEGER,' + '"propername" INTEGER' + ')' + ) + dbc.execute('CREATE TABLE "new_tf2idb_particles" ("id" INTEGER PRIMARY KEY NOT NULL , "name" TEXT NOT NULL )') + dbc.execute('CREATE TABLE "new_tf2idb_equip_conflicts" ("name" TEXT NOT NULL , "region" TEXT NOT NULL , PRIMARY KEY ("name", "region"))') + dbc.execute('CREATE TABLE "new_tf2idb_equip_regions" ("id" INTEGER NOT NULL , "region" TEXT NOT NULL , PRIMARY KEY ("id", "region"))') + dbc.execute('CREATE TABLE "new_tf2idb_capabilities" ("id" INTEGER NOT NULL , "capability" TEXT NOT NULL )') + dbc.execute('CREATE TABLE "new_tf2idb_attributes" (' + '"id" INTEGER PRIMARY KEY NOT NULL,' + '"name" TEXT NOT NULL,' + '"attribute_class" TEXT,' + '"attribute_type" TEXT,' + '"description_string" TEXT,' + '"description_format" TEXT,' + '"effect_type" TEXT,' + '"hidden" INTEGER,' + '"stored_as_integer" INTEGER,' + '"armory_desc" TEXT,' + '"is_set_bonus" INTEGER,' + '"is_user_generated" INTEGER,' + '"can_affect_recipe_component_name" INTEGER,' + '"apply_tag_to_item_definition" TEXT' + ')' + ) + dbc.execute('CREATE TABLE "new_tf2idb_qualities" ("name" TEXT PRIMARY KEY NOT NULL , "value" INTEGER NOT NULL )') + + nonce = int(time()) + dbc.execute('CREATE INDEX "tf2idb_item_attributes_%i" ON "new_tf2idb_item_attributes" ("attribute" ASC)' % nonce) + dbc.execute('CREATE INDEX "tf2idb_class_%i" ON "new_tf2idb_class" ("class" ASC)' % nonce) + dbc.execute('CREATE INDEX "tf2idb_item_%i" ON "new_tf2idb_item" ("slot" ASC)' % nonce) + + + # qualities + for qname,qdata in data['qualities'].items(): + dbc.execute('INSERT INTO new_tf2idb_qualities (name, value) VALUES (?,?)', (qname, qdata['value'])) + + # particles + for particle_type,particle_list in data['attribute_controlled_attached_particles'].items(): + for k,v in particle_list.items(): + dbc.execute('INSERT INTO new_tf2idb_particles (id,name) VALUES (?,?)', (k, v['system']) ) #TODO add the other fields too + + # attributes + attribute_type = {} + for k,v in data['attributes'].items(): + at = v.get('attribute_type') + if at: + atype = at + else: + if v.get('stored_as_integer'): + atype = 'integer' + else: + atype = 'float' + attribute_type[v['name'].lower()] = (k, atype) + dbc.execute('INSERT INTO new_tf2idb_attributes ' + '(id,name,attribute_class,attribute_type,description_string,description_format,effect_type,hidden,stored_as_integer,armory_desc,is_set_bonus,' + 'is_user_generated,can_affect_recipe_component_name,apply_tag_to_item_definition) ' + 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)', + (k,v.get('name'),v.get('attribute_class'),v.get('attribute_type'),v.get('description_string'),v.get('description_format'), + v.get('effect_type'),v.get('hidden'),v.get('stored_as_integer'),v.get('armory_desc'),v.get('is_set_bonus'), + v.get('is_user_generated'),v.get('can_affect_recipe_component_name'),v.get('apply_tag_to_item_definition') + ) + ) + + # conflicts + for k,v in data['equip_conflicts'].items(): + for region in v.keys(): + dbc.execute('INSERT INTO new_tf2idb_equip_conflicts (name,region) VALUES (?,?)', (k, region)) + + # items + for id,v in data['items'].items(): + if id == 'default': + continue + i, prefabs_used = resolve_prefabs(v, data['prefabs']) + baseitem = 'baseitem' in i + + try: + tool = None + if 'tool' in i: + tool = i['tool'].get('type') + + has_string_attribute = False + if 'static_attrs' in i: + for name,value in i['static_attrs'].items(): + aid,atype = attribute_type[name.lower()] + if atype == 'string': + has_string_attribute = True + dbc.execute('INSERT INTO new_tf2idb_item_attributes (id,attribute,value,static) VALUES (?,?,?,?)', (id,aid,value,1)) + + if 'attributes' in i: + for name,info in i['attributes'].items(): + aid,atype = attribute_type[name.lower()] + if atype == 'string': + has_string_attribute = True + dbc.execute('INSERT INTO new_tf2idb_item_attributes (id,attribute,value,static) VALUES (?,?,?,?)', (id,aid,info['value'],0)) + + dbc.execute('INSERT INTO new_tf2idb_item ' + '(id,name,item_name,class,slot,quality,tool_type,min_ilevel,max_ilevel,baseitem,holiday_restriction,has_string_attribute,propername) ' + 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)', + (id,i['name'],i.get('item_name'),i['item_class'],i.get('item_slot'),i.get('item_quality', ''), tool, i.get('min_ilevel'), i.get('max_ilevel'),baseitem, + i.get('holiday_restriction'), has_string_attribute, i.get('propername')) + ) + + if 'used_by_classes' in i: + for prof, val in i['used_by_classes'].items(): + dbc.execute('INSERT INTO new_tf2idb_class (id,class,slot) VALUES (?,?,?)', (id, prof.lower(), val if val != '1' else None)) + + region_field = i.get('equip_region') or i.get('equip_regions') + if region_field: + if type(region_field) is str: + region_field = {region_field: 1} + for region in region_field.keys(): + dbc.execute('INSERT INTO new_tf2idb_equip_regions (id,region) VALUES (?,?)', (id, region)) + + # capabilties + for capability,val in i.get('capabilities', {}).items(): + dbc.execute('INSERT INTO new_tf2idb_capabilities (id,capability) VALUES (?,?)', (id, (capability if val != '0' else '!'+capability))) + + except: + traceback.print_exc() + print(id) + raise + + def replace_table(name): + dbc.execute('DROP TABLE IF EXISTS %s' % name) + dbc.execute('ALTER TABLE new_%s RENAME TO %s' % (name,name)) + + replace_table('tf2idb_class') + replace_table('tf2idb_item_attributes') + replace_table('tf2idb_item') + replace_table('tf2idb_particles') + replace_table('tf2idb_equip_conflicts') + replace_table('tf2idb_equip_regions') + replace_table('tf2idb_capabilities') + replace_table('tf2idb_attributes') + replace_table('tf2idb_qualities') + + db.commit() + dbc.execute('VACUUM') + class Manager: servers = [] max_lines = 4 @@ -235,7 +457,36 @@ class Server: self.o.info("Send sm plugins refresh command...", end = "\t") self.rcon("sm plugins refresh", hide_server_name=True) return True - + + def clear_maps(self, exclude = "itemtest.bsp"): + map_list = glob(f"{self.root}/maps/*.bsp") + self.o.info(f"Delete {len(map_list) - 1} maps?") + if self.wait_input(): + for map_path in map_list: + if exclude in map_path: + continue + + remove_file(map_path) + return True + + def clear_download_cache(self): + cache_files = glob(f"{self.root}/download/user_custom/*/*.dat") + self.o.info(f"Delete {len(cache_files)} cache files?") + if not self.wait_input(): + return True + + count = 0 + for cache_file in cache_files: + try: + remove_file(cache_file) + count += 1 + except Exception as delete_error: + print(cache_file, delete_error) + sys.exit(1) + + self.o.info(f"Deleted {count} cache files") + return True + def upgrade_plugin(self, fresh_path, need_reloads_plugins = True): new_hash = hashfile(fresh_path) fresh_plugin_name = fresh_path.split("/")[-1:][0] @@ -256,7 +507,15 @@ class Server: return True self.o.info("Upgraded plugin not found in server...") return False - + + def update_tf2idb(self): + from sqlite3 import OperationalError + self.o.info("Updating TF2IDB") + try: + tf2idb(self.root, "scripts/items/items_game.txt", "addons/sourcemod/data/sqlite/tf2idb.sq3") + except OperationalError: + self.o.error("Cannot find database file") + def wait_input(self, yes = "y", no = "n"): if ALL_YES: @@ -612,6 +871,11 @@ if __name__ == "__main__": parser.add_argument("--UpgradePlugin", "-upg", help = "Path of file to uprade. Names must match.", type = str, default = "") parser.add_argument("--NoReloadPlugins", "-nrp", help = "Upgrade plugins without send sm plugins refresh command", default = False, action = "store_true") ################################################################################################################################################ + parser.add_argument("--DeleteDownloadCache", "-ddc", help = "Clear download cache", default = False, action = "store_true") + parser.add_argument("--DeleteUnusedMaps", "-dum", help = "Delete maps from maps folder", default = False, action = "store_true") + ################################################################################################################################################ + parser.add_argument("--UpdateTF2IDB", help = "Update tf2idb database", default = False, action = "store_true") + ################################################################################################################################################ args = parser.parse_args() ALL_YES = 1 if args.yes else 0 ################################## @@ -701,3 +965,15 @@ if __name__ == "__main__": else: o.error("Invalid name of remove plugin!") sys.exit(1) + ################################### + if args.DeleteDownloadCache: + manager.execute("clear_download_cache") + sys.exit(0) + ################################### + if args.DeleteUnusedMaps: + manager.execute("clear_maps") + sys.exit(0) + ################################### + if args.UpdateTF2IDB: + manager.execute("update_tf2idb") + sys.exit(0) \ No newline at end of file