def parse(cls, gm_id, config: ConfigFile): steam_id = config.get_val(gm_id, "SteamID", "<none>") if not steam_id.isdigit(): raise ValueError("Game {} has invalid Steam ID: {}".format(gm_id, steam_id)) folder = config.get_val(gm_id, "Dir", "") if not folder: raise ValueError("Game {} has no folder!".format(gm_id)) mod_time = config.get_int(gm_id, "ModTime", 0) return cls(gm_id, steam_id, folder, mod_time)
def parse(cls, gm_id, config: ConfigFile): steam_id = config.get_val(gm_id, 'SteamID', '<none>') if not steam_id.isdigit(): raise ValueError('Game {} has invalid Steam ID: {}'.format( gm_id, steam_id)) folder = config.get_val(gm_id, 'Dir', '') if not folder: raise ValueError('Game {} has no folder!'.format(gm_id)) mod_time = config.get_int(gm_id, 'ModTime', 0) return cls(gm_id, steam_id, folder, mod_time)
def parse(cls, gm_id, config: ConfigFile): steam_id = config.get_val(gm_id, 'SteamID', '<none>') if not steam_id.isdigit(): raise ValueError('Game {} has invalid Steam ID: {}'.format( gm_id, steam_id)) folder = config.get_val(gm_id, 'Dir', '') if not folder: raise ValueError('Game {} has no folder!'.format(gm_id)) mod_times = {} for name, value in config.items(gm_id): if name.startswith('pack_mod_'): mod_times[name[9:].casefold()] = srctools.conv_int(value) return cls(gm_id, steam_id, folder, mod_times)
def parse(cls, gm_id, config: ConfigFile): steam_id = config.get_val(gm_id, 'SteamID', '<none>') if not steam_id.isdigit(): raise ValueError( 'Game {} has invalid Steam ID: {}'.format(gm_id, steam_id) ) folder = config.get_val(gm_id, 'Dir', '') if not folder: raise ValueError( 'Game {} has no folder!'.format(gm_id) ) mod_time = config.get_int(gm_id, 'ModTime', 0) return cls(gm_id, steam_id, folder, mod_time)
def parse(cls, gm_id: str, config: ConfigFile) -> 'Game': """Parse out the given game ID from the config file.""" steam_id = config.get_val(gm_id, 'SteamID', '<none>') if not steam_id.isdigit(): raise ValueError(f'Game {gm_id} has invalid Steam ID: {steam_id}') folder = config.get_val(gm_id, 'Dir', '') if not folder: raise ValueError(f'Game {gm_id} has no folder!') if not os.path.exists(folder): raise ValueError( f'Folder {folder} does not exist for game {gm_id}!') mod_times = {} for name, value in config.items(gm_id): if name.startswith('pack_mod_'): mod_times[name[9:].casefold()] = srctools.conv_int(value) return cls(gm_id, steam_id, folder, mod_times)
PLAYER_MODELS = { 'ATLAS': 'ATLAS', 'PBODY': 'P-Body', 'SP': 'Chell', 'PETI': 'Bendy', } PLAYER_MODEL_ORDER = ['Bendy', 'Chell', 'ATLAS', 'P-Body'] PLAYER_MODELS_REV = {value: key for key, value in PLAYER_MODELS.items()} COMPILE_CFG = ConfigFile('compile.cfg') COMPILE_CFG.set_defaults(COMPILE_DEFAULTS) window = None UI = {} chosen_thumb = StringVar( value=COMPILE_CFG.get_val('Screenshot', 'Type', 'AUTO') ) player_model_var = StringVar( value=PLAYER_MODELS.get( COMPILE_CFG.get_val('General', 'player_model', 'PETI'), PLAYER_MODELS['PETI'], ) ) start_in_elev = IntVar(value=0) cust_file_loc = COMPILE_CFG.get_val('Screenshot', 'Loc', '') cust_file_loc_var = StringVar(value='') count_brush = IntVar(value=0) count_ents = IntVar(value=0) count_overlay = IntVar(value=0)
def modify(conf: ConfigFile, game_folder: Path) -> None: """Modify the map's screenshot.""" mod_type = conf.get_val('Screenshot', 'type', 'PETI').lower() if mod_type == 'cust': LOGGER.info('Using custom screenshot!') scr_loc = str(utils.conf_location('screenshot.jpg')) elif mod_type == 'auto': LOGGER.info('Using automatic screenshot!') scr_loc = None # The automatic screenshots are found at this location: auto_path = os.path.join(game_folder, 'screenshots') # We need to find the most recent one. If it's named # "previewcomplete", we want to ignore it - it's a flag # to indicate the map was playtested correctly. try: screens = [ os.path.join(auto_path, path) for path in os.listdir(auto_path) ] except FileNotFoundError: # The screenshot folder doesn't exist! screens = [] screens.sort( key=os.path.getmtime, reverse=True, # Go from most recent to least ) playtested = False for scr_shot in screens: filename = os.path.basename(scr_shot) if filename.startswith('bee2_playtest_flag'): # Previewcomplete is a flag to indicate the map's # been playtested. It must be newer than the screenshot playtested = True continue elif filename.startswith('bee2_screenshot'): continue # Ignore other screenshots # We have a screenshot. Check to see if it's # not too old. (Old is > 2 hours) date = datetime.fromtimestamp(os.path.getmtime(scr_shot)) diff = datetime.now() - date if diff.total_seconds() > 2 * 3600: LOGGER.info( 'Screenshot "{scr}" too old ({diff!s})', scr=scr_shot, diff=diff, ) continue # If we got here, it's a good screenshot! LOGGER.info('Chosen "{}"', scr_shot) LOGGER.info('Map Playtested: {}', playtested) scr_loc = scr_shot break else: # If we get to the end, we failed to find an automatic # screenshot! LOGGER.info('No Auto Screenshot found!') mod_type = 'peti' # Suppress the "None not found" error if conf.get_bool('Screenshot', 'del_old'): LOGGER.info('Cleaning up screenshots...') # Clean up this folder - otherwise users will get thousands of # pics in there! for screen in screens: if screen != scr_loc and os.path.isfile(screen): os.remove(screen) LOGGER.info('Done!') else: # PeTI type, or something else scr_loc = None if scr_loc is not None and os.path.isfile(scr_loc): # We should use a screenshot! for screen in find(): LOGGER.info('Replacing "{}"...', screen) # Allow us to edit the file... utils.unset_readonly(screen) shutil.copy(scr_loc, screen) # Make the screenshot readonly, so P2 can't replace it. # Then it'll use our own utils.set_readonly(screen) else: if mod_type != 'peti': # Error if we were looking for a screenshot LOGGER.warning('"{}" not found!', scr_loc) LOGGER.info('Using PeTI screenshot!') for screen in find(): # Make the screenshot writeable, so P2 will replace it LOGGER.info('Making "{}" replaceable...', screen) utils.unset_readonly(screen)
PLAYER_MODELS = { 'ATLAS': _('ATLAS'), 'PBODY': _('P-Body'), 'SP': _('Chell'), 'PETI': _('Bendy'), } PLAYER_MODEL_ORDER = ['PETI', 'SP', 'ATLAS', 'PBODY'] PLAYER_MODELS_REV = {value: key for key, value in PLAYER_MODELS.items()} COMPILE_CFG = ConfigFile('compile.cfg') COMPILE_CFG.set_defaults(COMPILE_DEFAULTS) window = None UI = {} chosen_thumb = StringVar( value=COMPILE_CFG.get_val('Screenshot', 'Type', 'AUTO') ) tk_screenshot = None # The preview image shown # Location we copy custom screenshots to SCREENSHOT_LOC = os.path.abspath(os.path.join( os.getcwd(), '..', 'config', 'screenshot.jpg' )) VOICE_PRIORITY_VAR = IntVar( value=COMPILE_CFG.get_bool('General', 'use_voice_priority', True) )
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!")