def main(argv: List[str]) -> None: LOGGER.info('BEE2 VRAD hook started!') args = " ".join(argv) fast_args = argv[1:] full_args = argv[1:] if not fast_args: # No arguments! LOGGER.info( 'No arguments!\n' "The BEE2 VRAD takes all the regular VRAD's " 'arguments, with some extra arguments:\n' '-force_peti: Force enabling map conversion. \n' "-force_hammer: Don't convert the map at all.\n" "If not specified, the map name must be \"preview.bsp\" to be " "treated as PeTI.") sys.exit() # The path is the last argument to vrad # P2 adds wrong slashes sometimes, so fix that. fast_args[-1] = path = os.path.normpath(argv[-1]) # type: str LOGGER.info("Map path is " + path) load_config() for a in fast_args[:]: if a.casefold() in ( "-both", "-final", "-staticproplighting", "-staticproppolys", "-textureshadows", ): # remove final parameters from the modified arguments fast_args.remove(a) elif a in ('-force_peti', '-force_hammer', '-no_pack'): # we need to strip these out, otherwise VRAD will get confused fast_args.remove(a) full_args.remove(a) fast_args = ['-bounce', '2', '-noextra'] + fast_args # Fast args: -bounce 2 -noextra -game $gamedir $path\$file # Final args: -both -final -staticproplighting -StaticPropPolys # -textureshadows -game $gamedir $path\$file if not path.endswith(".bsp"): path += ".bsp" if not os.path.exists(path): raise ValueError('"{}" does not exist!'.format(path)) if not os.path.isfile(path): raise ValueError('"{}" is not a file!'.format(path)) # If VBSP thinks it's hammer, trust it. if CONF.bool('is_hammer', False): is_peti = edit_args = False else: is_peti = True # Detect preview via knowing the bsp name. If we are in preview, # check the config file to see what was specified there. if os.path.basename(path) == "preview.bsp": edit_args = not CONF.bool('force_full', False) else: # publishing - always force full lighting. edit_args = False if '-force_peti' in args or '-force_hammer' in args: # we have override commands! if '-force_peti' in args: LOGGER.warning('OVERRIDE: Applying cheap lighting!') is_peti = edit_args = True else: LOGGER.warning('OVERRIDE: Preserving args!') is_peti = edit_args = False LOGGER.info('Final status: is_peti={}, edit_args={}', is_peti, edit_args) # Grab the currently mounted filesystems in P2. game = find_gameinfo(argv) root_folder = game.path.parent fsys = game.get_filesystem() fsys_tag = fsys_mel = None if is_peti and 'mel_vpk' in CONF: fsys_mel = VPKFileSystem(CONF['mel_vpk']) fsys.add_sys(fsys_mel) if is_peti and 'tag_dir' in CONF: fsys_tag = RawFileSystem(CONF['tag_dir']) fsys.add_sys(fsys_tag) LOGGER.info('Reading BSP') bsp_file = BSP(path) bsp_ents = bsp_file.read_ent_data() zip_data = BytesIO() zip_data.write(bsp_file.get_lump(BSP_LUMPS.PAKFILE)) zipfile = ZipFile(zip_data, mode='a') # Mount the existing packfile, so the cubemap files are recognised. fsys.systems.append((ZipFileSystem('', zipfile), '')) fsys.open_ref() LOGGER.info('Done!') LOGGER.info('Reading our FGD files...') fgd = load_fgd() packlist = PackList(fsys) packlist.load_soundscript_manifest( str(root_folder / 'bin/bee2/sndscript_cache.vdf')) # We nee to add all soundscripts in scripts/bee2_snd/ # This way we can pack those, if required. for soundscript in fsys.walk_folder('scripts/bee2_snd/'): if soundscript.path.endswith('.txt'): packlist.load_soundscript(soundscript, always_include=False) if is_peti: LOGGER.info('Adding special packed files:') music_data = CONF.find_key('MusicScript', []) if music_data: packlist.pack_file('scripts/BEE2_generated_music.txt', PackType.SOUNDSCRIPT, data=generate_music_script( music_data, packlist)) for filename, arcname in inject_files(): LOGGER.info('Injecting "{}" into packfile.', arcname) with open(filename, 'rb') as f: packlist.pack_file(arcname, data=f.read()) LOGGER.info('Run transformations...') run_transformations(bsp_ents, fsys, packlist) LOGGER.info('Scanning map for files to pack:') packlist.pack_from_bsp(bsp_file) packlist.pack_fgd(bsp_ents, fgd) packlist.eval_dependencies() LOGGER.info('Done!') if is_peti: packlist.write_manifest() else: # Write with the map name, so it loads directly. packlist.write_manifest(os.path.basename(path)[:-4]) # We need to disallow Valve folders. pack_whitelist = set() # type: Set[FileSystem] pack_blacklist = set() # type: Set[FileSystem] if is_peti: pack_blacklist |= { RawFileSystem(root_folder / 'portal2_dlc2'), RawFileSystem(root_folder / 'portal2_dlc1'), RawFileSystem(root_folder / 'portal2'), RawFileSystem(root_folder / 'platform'), RawFileSystem(root_folder / 'update'), } if fsys_mel is not None: pack_whitelist.add(fsys_mel) if fsys_tag is not None: pack_whitelist.add(fsys_tag) if '-no_pack' not in args: # Cubemap files packed into the map already. existing = set(zipfile.infolist()) LOGGER.info('Writing to BSP...') packlist.pack_into_zip( zipfile, ignore_vpk=True, whitelist=pack_whitelist, blacklist=pack_blacklist, ) LOGGER.info( 'Packed files:\n{}', '\n'.join([ zipinfo.filename for zipinfo in zipfile.infolist() if zipinfo.filename not in existing ])) dump_files(zipfile) zipfile.close() # Finalise the zip modification # Copy the zipfile into the BSP file, and adjust the headers. bsp_file.lumps[BSP_LUMPS.PAKFILE].data = zip_data.getvalue() # Copy new entity data. bsp_file.lumps[BSP_LUMPS.ENTITIES].data = BSP.write_ent_data(bsp_ents) bsp_file.save() LOGGER.info(' - BSP written!') if is_peti: mod_screenshots() if edit_args: LOGGER.info("Forcing Cheap Lighting!") run_vrad(fast_args) else: if is_peti: LOGGER.info( "Publishing - Full lighting enabled! (or forced to do so)") else: LOGGER.info("Hammer map detected! Not forcing cheap lighting..") run_vrad(full_args) LOGGER.info("BEE2 VRAD hook finished!")
def main(argv: List[str]) -> None: """Main VRAD script.""" LOGGER.info( "BEE{} VRAD hook initiallised, srctools v{}, Hammer Addons v{}", utils.BEE_VERSION, srctools.__version__, version_haddons, ) # Warn if srctools Cython code isn't installed. utils.check_cython(LOGGER.warning) args = " ".join(argv) fast_args = argv[1:] full_args = argv[1:] if not fast_args: # No arguments! LOGGER.info( 'No arguments!\n' "The BEE2 VRAD takes all the regular VRAD's " 'arguments, with some extra arguments:\n' '-force_peti: Force enabling map conversion. \n' "-force_hammer: Don't convert the map at all.\n" "If not specified, the map name must be \"preview.bsp\" to be " "treated as PeTI." ) sys.exit() # The path is the last argument to vrad # P2 adds wrong slashes sometimes, so fix that. fast_args[-1] = path = os.path.normpath(argv[-1]) LOGGER.info("Map path is " + path) LOGGER.info('Loading Settings...') config = ConfigFile('compile.cfg') for a in fast_args[:]: folded_a = a.casefold() if folded_a.casefold() in ( "-final", "-staticproplighting", "-staticproppolys", "-textureshadows", ): # remove final parameters from the modified arguments fast_args.remove(a) elif folded_a == '-both': # LDR Portal 2 isn't actually usable, so there's not much # point compiling for it. pos = fast_args.index(a) fast_args[pos] = full_args[pos] = '-hdr' elif a in ('-force_peti', '-force_hammer', '-no_pack'): # we need to strip these out, otherwise VRAD will get confused fast_args.remove(a) full_args.remove(a) fast_args = ['-bounce', '2', '-noextra'] + fast_args # Fast args: -bounce 2 -noextra -game $gamedir $path\$file # Final args: -both -final -staticproplighting -StaticPropPolys # -textureshadows -game $gamedir $path\$file if not path.endswith(".bsp"): path += ".bsp" if not os.path.exists(path): raise ValueError('"{}" does not exist!'.format(path)) if not os.path.isfile(path): raise ValueError('"{}" is not a file!'.format(path)) LOGGER.info('Reading BSP') bsp_file = BSP(path) # If VBSP marked it as Hammer, trust that. if srctools.conv_bool(bsp_file.ents.spawn['BEE2_is_peti']): is_peti = True # Detect preview via knowing the bsp name. If we are in preview, # check the config file to see what was specified there. if os.path.basename(path) == "preview.bsp": edit_args = not config.get_bool('General', 'vrad_force_full') # If shift is held, reverse. if utils.check_shift(): LOGGER.info('Shift held, inverting configured lighting option!') edit_args = not edit_args else: # publishing - always force full lighting. edit_args = False else: is_peti = edit_args = False if '-force_peti' in args or '-force_hammer' in args: # we have override commands! if '-force_peti' in args: LOGGER.warning('OVERRIDE: Applying cheap lighting!') is_peti = edit_args = True else: LOGGER.warning('OVERRIDE: Preserving args!') is_peti = edit_args = False LOGGER.info('Final status: is_peti={}, edit_args={}', is_peti, edit_args) if not is_peti: # Skip everything, if the user wants these features install the Hammer Addons postcompiler. LOGGER.info("Hammer map detected! Skipping all transforms.") run_vrad(full_args) return # Grab the currently mounted filesystems in P2. game = find_gameinfo(argv) root_folder = game.path.parent fsys = game.get_filesystem() # Special case - move the BEE2 filesystem FIRST, so we always pack files found there. for child_sys in fsys.systems[:]: if 'bee2' in child_sys[0].path.casefold(): fsys.systems.remove(child_sys) fsys.systems.insert(0, child_sys) zip_data = BytesIO() zip_data.write(bsp_file.get_lump(BSP_LUMPS.PAKFILE)) zipfile = ZipFile(zip_data) # Mount the existing packfile, so the cubemap files are recognised. fsys.add_sys(ZipFileSystem('<BSP pakfile>', zipfile)) LOGGER.info('Done!') LOGGER.debug('Filesystems:') for child_sys in fsys.systems[:]: LOGGER.debug('- {}: {!r}', child_sys[1], child_sys[0]) LOGGER.info('Reading our FGD files...') fgd = FGD.engine_dbase() packlist = PackList(fsys) LOGGER.info('Reading soundscripts...') packlist.load_soundscript_manifest( str(root_folder / 'bin/bee2/sndscript_cache.vdf') ) # We need to add all soundscripts in scripts/bee2_snd/ # This way we can pack those, if required. for soundscript in fsys.walk_folder('scripts/bee2_snd/'): if soundscript.path.endswith('.txt'): packlist.load_soundscript(soundscript, always_include=False) LOGGER.info('Reading particles....') packlist.load_particle_manifest() LOGGER.info('Loading transforms...') load_transforms() LOGGER.info('Checking for music:') music.generate(bsp_file.ents, packlist) LOGGER.info('Run transformations...') run_transformations(bsp_file.ents, fsys, packlist, bsp_file, game) LOGGER.info('Scanning map for files to pack:') packlist.pack_from_bsp(bsp_file) packlist.pack_fgd(bsp_file.ents, fgd) packlist.eval_dependencies() LOGGER.info('Done!') packlist.write_soundscript_manifest() packlist.write_particles_manifest(f'maps/{Path(path).stem}_particles.txt') # We need to disallow Valve folders. pack_whitelist: set[FileSystem] = set() pack_blacklist: set[FileSystem] = set() # Exclude absolutely everything except our folder. for child_sys, _ in fsys.systems: # Add 'bee2/' and 'bee2_dev/' only. if ( isinstance(child_sys, RawFileSystem) and 'bee2' in os.path.basename(child_sys.path).casefold() ): pack_whitelist.add(child_sys) else: pack_blacklist.add(child_sys) if config.get_bool('General', 'packfile_dump_enable'): dump_loc = Path(config.get_val( 'General', 'packfile_dump_dir', '../dump/' )).absolute() else: dump_loc = None if '-no_pack' not in args: # Cubemap files packed into the map already. existing = set(bsp_file.pakfile.namelist()) LOGGER.info('Writing to BSP...') packlist.pack_into_zip( bsp_file, ignore_vpk=True, whitelist=pack_whitelist, blacklist=pack_blacklist, dump_loc=dump_loc, ) LOGGER.info('Packed files:\n{}', '\n'.join( set(bsp_file.pakfile.namelist()) - existing )) LOGGER.info('Writing BSP...') bsp_file.save() LOGGER.info(' - BSP written!') screenshot.modify(config, game.path) if edit_args: LOGGER.info("Forcing Cheap Lighting!") run_vrad(fast_args) else: LOGGER.info("Publishing - Full lighting enabled! (or forced to do so)") run_vrad(full_args) LOGGER.info("BEE2 VRAD hook finished!")
def main(argv: List[str]) -> None: parser = argparse.ArgumentParser( description="Modifies the BSP file, allowing additional entities " "and bugfixes.", ) parser.add_argument("--nopack", dest="allow_pack", action="store_false", help="Prevent packing of files found in the map.") parser.add_argument( "--propcombine", action="store_true", help="Allow merging static props together.", ) parser.add_argument( "--showgroups", action="store_true", help="Show propcombined props, by setting their tint to random groups", ) parser.add_argument( "--dumpgroups", action="store_true", help="Write all props without propcombine groups to a new VMF.", ) parser.add_argument( "map", help="The path to the BSP file.", ) args = parser.parse_args(argv) # The path is the last argument to the compiler. # Hammer adds wrong slashes sometimes, so fix that. # Also if it's the VMF file, make it the BSP. path = Path(args.map).with_suffix('.bsp') # Open and start writing to the map's log file. handler = FileHandler(path.with_suffix('.log')) handler.setFormatter( Formatter( # One letter for level name '[{levelname}] {module}.{funcName}(): {message}', style='{', )) LOGGER.addHandler(handler) LOGGER.info('Srctools postcompiler hook started at {}!', datetime.datetime.now().isoformat()) LOGGER.info("Map path is {}", path) conf, game_info, fsys, pack_blacklist, plugin = config.parse(path) fsys.open_ref() packlist = PackList(fsys) LOGGER.info('Gameinfo: {}', game_info.path) LOGGER.info( 'Search paths: \n{}', '\n'.join([sys.path for sys, prefix in fsys.systems]), ) fgd = FGD.engine_dbase() LOGGER.info('Loading soundscripts...') packlist.load_soundscript_manifest( conf.path.with_name('srctools_sndscript_data.vdf')) LOGGER.info('Done! ({} sounds)', len(packlist.soundscripts)) LOGGER.info('Reading BSP...') bsp_file = BSP(path) LOGGER.info('Reading entities...') vmf = bsp_file.read_ent_data() LOGGER.info('Done!') # Mount the existing packfile, so the cubemap files are recognised. LOGGER.info('Mounting BSP packfile...') zipfile = ZipFile(BytesIO(bsp_file.get_lump(BSP_LUMPS.PAKFILE))) fsys.add_sys(ZipFileSystem('<BSP pakfile>', zipfile)) studiomdl_path = conf.get(str, 'studiomdl') if studiomdl_path: studiomdl_loc = (game_info.root / studiomdl_path).resolve() if not studiomdl_loc.exists(): LOGGER.warning('No studiomdl found at "{}"!', studiomdl_loc) studiomdl_loc = None else: LOGGER.warning('No studiomdl path provided.') studiomdl_loc = None LOGGER.info('Loading plugins...') plugin.load_all() use_comma_sep = conf.get(bool, 'use_comma_sep') if use_comma_sep is None: # Guess the format, by picking whatever the first output uses. for ent in vmf.entities: for out in ent.outputs: use_comma_sep = out.comma_sep break if use_comma_sep is None: LOGGER.warning( 'No outputs in map, could not determine BSP I/O format!') LOGGER.warning('Set "use_comma_sep" in srctools.vdf.') use_comma_sep = False LOGGER.info('Running transforms...') run_transformations(vmf, fsys, packlist, bsp_file, game_info, studiomdl_loc) if studiomdl_loc is not None and args.propcombine: decomp_cache_loc = conf.get(str, 'propcombine_cache') if decomp_cache_loc is not None: decomp_cache_loc = (game_info.root / decomp_cache_loc).resolve() decomp_cache_loc.mkdir(parents=True, exist_ok=True) if conf.get(bool, 'propcombine_crowbar'): # argv[0] is the location of our script/exe, which lets us locate # Crowbar from there. crowbar_loc = Path(sys.argv[0], '../Crowbar.exe').resolve() else: crowbar_loc = None LOGGER.info('Combining props...') propcombine.combine( bsp_file, vmf, packlist, game_info, studiomdl_loc=studiomdl_loc, qc_folders=[ game_info.root / folder for folder in conf.get( Property, 'propcombine_qc_folder').as_array(conv=Path) ], decomp_cache_loc=decomp_cache_loc, crowbar_loc=crowbar_loc, auto_range=conf.get(int, 'propcombine_auto_range'), min_cluster=conf.get(int, 'propcombine_min_cluster'), debug_tint=args.showgroups, debug_dump=args.dumpgroups, ) LOGGER.info('Done!') else: # Strip these if they're present. for ent in vmf.by_class['comp_propcombine_set']: ent.remove() bsp_file.lumps[BSP_LUMPS.ENTITIES].data = bsp_file.write_ent_data( vmf, use_comma_sep) if conf.get(bool, 'auto_pack') and args.allow_pack: LOGGER.info('Analysing packable resources...') packlist.pack_fgd(vmf, fgd) packlist.pack_from_bsp(bsp_file) packlist.eval_dependencies() if conf.get(bool, 'soundscript_manifest'): packlist.write_manifest() packlist.pack_into_zip(bsp_file, blacklist=pack_blacklist, ignore_vpk=False) with bsp_file.packfile() as pak_zip: # List out all the files, but group together files with the same extension. ext_for_name: Dict[str, List[str]] = defaultdict(list) for file in pak_zip.infolist(): filename = Path(file.filename) if '.' in filename.name: stem, ext = filename.name.split('.', 1) file_path = str(filename.parent / stem) else: file_path = file.filename ext = '' ext_for_name[file_path].append(ext) LOGGER.info('Packed files: \n{}'.format('\n'.join([ (f'{name}.{exts[0]}' if len(exts) == 1 else f'{name}.({"/".join(exts)})') for name, exts in sorted(ext_for_name.items()) ]))) LOGGER.info('Writing BSP...') bsp_file.save() LOGGER.info("srctools VRAD hook finished!")
def main(argv: List[str]) -> None: LOGGER.info('Srctools postcompiler hook started!') parser = argparse.ArgumentParser( description="Modifies the BSP file, allowing additional entities " "and bugfixes.", ) parser.add_argument("--nopack", dest="allow_pack", action="store_false", help="Prevent packing of files found in the map.") parser.add_argument( "--propcombine", action="store_true", help="Allow merging static props together.", ) parser.add_argument( "map", help="The path to the BSP file.", ) args = parser.parse_args(argv) # The path is the last argument to the compiler. # Hammer adds wrong slashes sometimes, so fix that. # Also if it's the VMF file, make it the BSP. path = Path(args.map).with_suffix('.bsp') LOGGER.info("Map path is {}", path) conf, game_info, fsys, pack_blacklist = config.parse(path) fsys.open_ref() packlist = PackList(fsys) LOGGER.info('Gameinfo: {}', game_info.path) LOGGER.info( 'Search paths: \n{}', '\n'.join([sys.path for sys, prefix in fsys.systems]), ) fgd = load_fgd() LOGGER.info('Loading soundscripts...') packlist.load_soundscript_manifest( conf.path.with_name('srctools_sndscript_data.vdf')) LOGGER.info('Done! ({} sounds)', len(packlist.soundscripts)) LOGGER.info('Reading BSP...') bsp_file = BSP(path) LOGGER.info('Reading entities...') vmf = bsp_file.read_ent_data() LOGGER.info('Done!') run_transformations(vmf, fsys, packlist) studiomdl_loc = conf.get(str, 'propcombine_studiomdl') if studiomdl_loc and args.propcombine: LOGGER.info('Combining props...') propcombine.combine( bsp_file, vmf, packlist, game_info, game_info.root / studiomdl_loc, [ game_info.root / folder for folder in conf.get( Property, 'propcombine_qc_folder').as_array(conv=Path) ], conf.get(int, 'propcombine_auto_range'), conf.get(int, 'propcombine_min_cluster'), ) LOGGER.info('Done!') bsp_file.lumps[BSP_LUMPS.ENTITIES].data = bsp_file.write_ent_data(vmf) if conf.get(bool, 'auto_pack') and args.allow_pack: LOGGER.info('Analysing packable resources...') packlist.pack_fgd(vmf, fgd) packlist.pack_from_bsp(bsp_file) packlist.eval_dependencies() with bsp_file.packfile() as pak_zip: packlist.pack_into_zip(pak_zip, blacklist=pack_blacklist) LOGGER.info('Packed files: \n{}'.format('\n'.join(pak_zip.namelist()))) LOGGER.info('Writing BSP...') bsp_file.save() LOGGER.info("srctools VRAD hook finished!")
def main(argv: List[str]) -> None: LOGGER.info('BEE2 VRAD hook started!') args = " ".join(argv) fast_args = argv[1:] full_args = argv[1:] if not fast_args: # No arguments! LOGGER.info( 'No arguments!\n' "The BEE2 VRAD takes all the regular VRAD's " 'arguments, with some extra arguments:\n' '-force_peti: Force enabling map conversion. \n' "-force_hammer: Don't convert the map at all.\n" "If not specified, the map name must be \"preview.bsp\" to be " "treated as PeTI." ) sys.exit() # The path is the last argument to vrad # P2 adds wrong slashes sometimes, so fix that. fast_args[-1] = path = os.path.normpath(argv[-1]) # type: str LOGGER.info("Map path is " + path) load_config() for a in fast_args[:]: if a.casefold() in ( "-both", "-final", "-staticproplighting", "-staticproppolys", "-textureshadows", ): # remove final parameters from the modified arguments fast_args.remove(a) elif a in ('-force_peti', '-force_hammer', '-no_pack'): # we need to strip these out, otherwise VRAD will get confused fast_args.remove(a) full_args.remove(a) fast_args = ['-bounce', '2', '-noextra'] + fast_args # Fast args: -bounce 2 -noextra -game $gamedir $path\$file # Final args: -both -final -staticproplighting -StaticPropPolys # -textureshadows -game $gamedir $path\$file if not path.endswith(".bsp"): path += ".bsp" if not os.path.exists(path): raise ValueError('"{}" does not exist!'.format(path)) if not os.path.isfile(path): raise ValueError('"{}" is not a file!'.format(path)) # If VBSP thinks it's hammer, trust it. if CONF.bool('is_hammer', False): is_peti = edit_args = False else: is_peti = True # Detect preview via knowing the bsp name. If we are in preview, # check the config file to see what was specified there. if os.path.basename(path) == "preview.bsp": edit_args = not CONF.bool('force_full', False) else: # publishing - always force full lighting. edit_args = False if '-force_peti' in args or '-force_hammer' in args: # we have override commands! if '-force_peti' in args: LOGGER.warning('OVERRIDE: Applying cheap lighting!') is_peti = edit_args = True else: LOGGER.warning('OVERRIDE: Preserving args!') is_peti = edit_args = False LOGGER.info('Final status: is_peti={}, edit_args={}', is_peti, edit_args) # Grab the currently mounted filesystems in P2. game = find_gameinfo(argv) root_folder = game.path.parent fsys = game.get_filesystem() fsys_tag = fsys_mel = None if is_peti and 'mel_vpk' in CONF: fsys_mel = VPKFileSystem(CONF['mel_vpk']) fsys.add_sys(fsys_mel) if is_peti and 'tag_dir' in CONF: fsys_tag = RawFileSystem(CONF['tag_dir']) fsys.add_sys(fsys_tag) LOGGER.info('Reading BSP') bsp_file = BSP(path) bsp_ents = bsp_file.read_ent_data() zip_data = BytesIO() zip_data.write(bsp_file.get_lump(BSP_LUMPS.PAKFILE)) zipfile = ZipFile(zip_data, mode='a') # Mount the existing packfile, so the cubemap files are recognised. fsys.systems.append((ZipFileSystem('', zipfile), '')) fsys.open_ref() LOGGER.info('Done!') LOGGER.info('Reading our FGD files...') fgd = load_fgd() packlist = PackList(fsys) packlist.load_soundscript_manifest( str(root_folder / 'bin/bee2/sndscript_cache.vdf') ) # We nee to add all soundscripts in scripts/bee2_snd/ # This way we can pack those, if required. for soundscript in fsys.walk_folder('scripts/bee2_snd/'): if soundscript.path.endswith('.txt'): packlist.load_soundscript(soundscript, always_include=False) if is_peti: LOGGER.info('Adding special packed files:') music_data = CONF.find_key('MusicScript', []) if music_data: packlist.pack_file( 'scripts/BEE2_generated_music.txt', PackType.SOUNDSCRIPT, data=generate_music_script(music_data, packlist) ) for filename, arcname in inject_files(): LOGGER.info('Injecting "{}" into packfile.', arcname) with open(filename, 'rb') as f: packlist.pack_file(arcname, data=f.read()) LOGGER.info('Run transformations...') run_transformations(bsp_ents, fsys, packlist) LOGGER.info('Scanning map for files to pack:') packlist.pack_from_bsp(bsp_file) packlist.pack_fgd(bsp_ents, fgd) packlist.eval_dependencies() LOGGER.info('Done!') if is_peti: packlist.write_manifest() else: # Write with the map name, so it loads directly. packlist.write_manifest(os.path.basename(path)[:-4]) # We need to disallow Valve folders. pack_whitelist = set() # type: Set[FileSystem] pack_blacklist = set() # type: Set[FileSystem] if is_peti: pack_blacklist |= { RawFileSystem(root_folder / 'portal2_dlc2'), RawFileSystem(root_folder / 'portal2_dlc1'), RawFileSystem(root_folder / 'portal2'), RawFileSystem(root_folder / 'platform'), RawFileSystem(root_folder / 'update'), } if fsys_mel is not None: pack_whitelist.add(fsys_mel) if fsys_tag is not None: pack_whitelist.add(fsys_tag) if '-no_pack' not in args: # Cubemap files packed into the map already. existing = set(zipfile.infolist()) LOGGER.info('Writing to BSP...') packlist.pack_into_zip( zipfile, ignore_vpk=True, whitelist=pack_whitelist, blacklist=pack_blacklist, ) LOGGER.info('Packed files:\n{}', '\n'.join([ zipinfo.filename for zipinfo in zipfile.infolist() if zipinfo.filename not in existing ])) dump_files(zipfile) zipfile.close() # Finalise the zip modification # Copy the zipfile into the BSP file, and adjust the headers. bsp_file.lumps[BSP_LUMPS.PAKFILE].data = zip_data.getvalue() # Copy new entity data. bsp_file.lumps[BSP_LUMPS.ENTITIES].data = BSP.write_ent_data(bsp_ents) bsp_file.save() LOGGER.info(' - BSP written!') if is_peti: mod_screenshots() if edit_args: LOGGER.info("Forcing Cheap Lighting!") run_vrad(fast_args) else: if is_peti: LOGGER.info("Publishing - Full lighting enabled! (or forced to do so)") else: LOGGER.info("Hammer map detected! Not forcing cheap lighting..") run_vrad(full_args) LOGGER.info("BEE2 VRAD hook finished!")
def main(argv: List[str]) -> None: parser = argparse.ArgumentParser( description="Modifies the BSP file, allowing additional entities " "and bugfixes.", ) parser.add_argument("--nopack", dest="allow_pack", action="store_false", help="Prevent packing of files found in the map.") parser.add_argument( "--propcombine", action="store_true", help="Allow merging static props together.", ) parser.add_argument( "--showgroups", action="store_true", help="Show propcombined props, by setting their tint to 0 255 0", ) parser.add_argument( "map", help="The path to the BSP file.", ) args = parser.parse_args(argv) # The path is the last argument to the compiler. # Hammer adds wrong slashes sometimes, so fix that. # Also if it's the VMF file, make it the BSP. path = Path(args.map).with_suffix('.bsp') # Open and start writing to the map's log file. handler = FileHandler(path.with_suffix('.log')) handler.setFormatter( Formatter( # One letter for level name '[{levelname}] {module}.{funcName}(): {message}', style='{', )) LOGGER.addHandler(handler) LOGGER.info('Srctools postcompiler hook started at {}!', datetime.datetime.now().isoformat()) LOGGER.info("Map path is {}", path) conf, game_info, fsys, pack_blacklist, plugins = config.parse(path) fsys.open_ref() packlist = PackList(fsys) LOGGER.info('Gameinfo: {}', game_info.path) LOGGER.info( 'Search paths: \n{}', '\n'.join([sys.path for sys, prefix in fsys.systems]), ) fgd = FGD.engine_dbase() LOGGER.info('Loading soundscripts...') packlist.load_soundscript_manifest( conf.path.with_name('srctools_sndscript_data.vdf')) LOGGER.info('Done! ({} sounds)', len(packlist.soundscripts)) LOGGER.info('Reading BSP...') bsp_file = BSP(path) LOGGER.info('Reading entities...') vmf = bsp_file.read_ent_data() LOGGER.info('Done!') studiomdl_path = conf.get(str, 'studiomdl') if studiomdl_path: studiomdl_loc = (game_info.root / studiomdl_path).resolve() if not studiomdl_loc.exists(): LOGGER.warning('No studiomdl found at "{}"!', studiomdl_loc) studiomdl_loc = None else: LOGGER.warning('No studiomdl path provided.') studiomdl_loc = None for plugin in plugins: plugin.load() use_comma_sep = conf.get(bool, 'use_comma_sep') if use_comma_sep is None: # Guess the format, by picking whatever the first output uses. for ent in vmf.entities: for out in ent.outputs: use_comma_sep = out.comma_sep break if use_comma_sep is None: LOGGER.warning( 'No outputs in map, could not determine BSP I/O format!') LOGGER.warning('Set "use_comma_sep" in srctools.vdf.') use_comma_sep = False run_transformations(vmf, fsys, packlist, bsp_file, game_info, studiomdl_loc) if studiomdl_loc is not None and args.propcombine: LOGGER.info('Combining props...') propcombine.combine( bsp_file, vmf, packlist, game_info, studiomdl_loc, [ game_info.root / folder for folder in conf.get( Property, 'propcombine_qc_folder').as_array(conv=Path) ], conf.get(int, 'propcombine_auto_range'), conf.get(int, 'propcombine_min_cluster'), debug_tint=args.showgroups, ) LOGGER.info('Done!') else: # Strip these if they're present. for ent in vmf.by_class['comp_propcombine_set']: ent.remove() bsp_file.lumps[BSP_LUMPS.ENTITIES].data = bsp_file.write_ent_data( vmf, use_comma_sep) if conf.get(bool, 'auto_pack') and args.allow_pack: LOGGER.info('Analysing packable resources...') packlist.pack_fgd(vmf, fgd) packlist.pack_from_bsp(bsp_file) packlist.eval_dependencies() packlist.pack_into_zip(bsp_file, blacklist=pack_blacklist, ignore_vpk=False) with bsp_file.packfile() as pak_zip: LOGGER.info('Packed files: \n{}'.format('\n'.join(pak_zip.namelist()))) LOGGER.info('Writing BSP...') bsp_file.save() LOGGER.info("srctools VRAD hook finished!")