def find_group_quotes(group, mid_quotes, conf): """Scan through a group, looking for applicable quote options.""" is_mid = group.name == 'midinst' group_id = group['name'] for quote in group.find_all('quote'): valid_quote = True for flag in quote: name = flag.name if name in ('priority', 'name', 'line', 'line_sp', 'line_coop'): # Not flags! continue if not conditions.check_flag(flag, fake_inst): valid_quote = False break quote_id = quote['id', quote['name', '']] utils.con_log(quote_id, valid_quote) if valid_quote: # Check if the ID is enabled! if conf.get_bool(group_id, quote_id, True): if ALLOW_MID_VOICES and is_mid: mid_quotes.extend(mode_quotes(quote)) else: inst_list = list(mode_quotes(quote)) if inst_list: yield ( quote['priority', '0'], inst_list, )
def test(self, inst, remove_vmf=True): """Try to satisfy this condition on the given instance.""" if not self.valid: return success = True for flag in self.flags: # utils.con_log('Flag: ' + repr(flag)) if not check_flag(flag, inst): success = False break # utils.con_log(success) if remove_vmf: # our suffixes won't touch the .vmf extension inst['file'] = inst['file', ''][:-4] results = self.results if success else self.else_results for res in results: try: func = RESULT_LOOKUP[res.name] except KeyError: utils.con_log( '"{}" is not a valid condition result!'.format( res.real_name, ) ) else: func(inst, res) if remove_vmf and not inst['file'].endswith('vmf'): inst['file'] += '.vmf'
def variant_weight(var): """Read variant commands from settings and create the weight list.""" count = var["number", ""] if count.isdecimal(): count = int(count) weight = var["weights", ""] if weight == "" or "," not in weight: utils.con_log("Invalid weight! (" + weight + ")") weight = [str(i) for i in range(1, count + 1)] else: # Parse the weight vals = weight.split(",") weight = [] if len(vals) == count: for i, val in enumerate(vals): val = val.strip() if val.isdecimal(): # repeat the index the correct number of times weight.extend(str(i + 1) for _ in range(1, int(val) + 1)) else: # Abandon parsing break if len(weight) == 0: utils.con_log("Failed parsing weight! ({!s})".format(weight)) weight = [str(i) for i in range(1, count + 1)] # random.choice(weight) will now give an index with the correct # probabilities. return weight else: return [""] # This won't append anything to the file
def res_fizzler_pair(begin_inst, res): """Modify the instance of a fizzler to link with its pair.""" orig_target = begin_inst['targetname'] if 'modelEnd' in orig_target: return # We only execute starting from the start side. orig_target = orig_target[:-11] # remove "_modelStart" end_name = orig_target + '_modelEnd' # What we search for # The name all these instances get pair_name = orig_target + '-model' + str(begin_inst.id) orig_file = begin_inst['file'] begin_file = res['StartInst', orig_file] end_file = res['EndInst', orig_file] mid_file = res['MidInst', ''] begin_inst['file'] = begin_file begin_inst['targetname'] = pair_name angles = Vec.from_str(begin_inst['angles']) # We round it to get rid of 0.00001 inprecision from the calculations. direction = round(Vec(0, 0, 1).rotate(angles.x, angles.y, angles.z)) ':type direction: utils.Vec' print(end_name, direction) begin_pos = Vec.from_str(begin_inst['origin']) axis_1, axis_2, main_axis = PAIR_AXES[direction.as_tuple()] for end_inst in VMF.by_class['func_instance']: if end_inst['targetname', ''] != end_name: # Only examine this barrier hazard's instances! continue end_pos = Vec.from_str(end_inst['origin']) if ( begin_pos[axis_1] == end_pos[axis_1] and begin_pos[axis_2] == end_pos[axis_2] ): length = int(end_pos[main_axis] - begin_pos[main_axis]) break else: utils.con_log('No matching pair for {}!!'.format(orig_target)) return end_inst['targetname'] = pair_name end_inst['file'] = end_file if mid_file != '': # Go 64 from each side, and always have at least 1 section # A 128 gap will have length = 0 for dis in range(0, abs(length) + 1, 128): new_pos = begin_pos + direction*dis VMF.create_ent( classname='func_instance', targetname=pair_name, angles=begin_inst['angles'], file=mid_file, origin=new_pos.join(' '), )
def load_config(): global CONF utils.con_log('Loading Settings...') try: with open("bee2/vrad_config.cfg") as config: CONF = Property.parse(config, 'bee2/vrad_config.cfg').find_key( 'Config', []) except FileNotFoundError: pass utils.con_log('Config Loaded!')
def debug_flag(inst, props): if props.has_children(): utils.con_log("Debug:") utils.con_log(str(props)) utils.con_log(str(inst)) elif props.value.endswith("="): utils.con_log("Debug: {props}{inst!s}".format(inst=inst, props=props.value)) else: utils.con_log("Debug: " + props.value) return True # The flag is always true
def res_fizzler_pair(begin_inst, res): """Modify the instance of a fizzler to link with its pair.""" orig_target = begin_inst["targetname"] if "modelEnd" in orig_target: return # We only execute starting from the start side. orig_target = orig_target[:-11] # remove "_modelStart" end_name = orig_target + "_modelEnd" # What we search for # The name all these instances get pair_name = orig_target + "-model" + str(begin_inst.id) orig_file = begin_inst["file"] begin_file = res["StartInst", orig_file] end_file = res["EndInst", orig_file] mid_file = res["MidInst", ""] begin_inst["file"] = begin_file begin_inst["targetname"] = pair_name angles = Vec.from_str(begin_inst["angles"]) # We round it to get rid of 0.00001 inprecision from the calculations. direction = Vec(0, 0, 1).rotate(angles.x, angles.y, angles.z) ":type direction: utils.Vec" begin_pos = Vec.from_str(begin_inst["origin"]) axis_1, axis_2, main_axis = PAIR_AXES[direction.as_tuple()] for end_inst in VMF.by_class["func_instance"]: if end_inst["targetname", ""] != end_name: # Only examine this barrier hazard's instances! continue end_pos = Vec.from_str(end_inst["origin"]) if begin_pos[axis_1] == end_pos[axis_1] and begin_pos[axis_2] == end_pos[axis_2]: length = int(end_pos[main_axis] - begin_pos[main_axis]) break else: utils.con_log("No matching pair for {}!!".format(orig_target)) return end_inst["targetname"] = pair_name end_inst["file"] = end_file if mid_file != "": # Go 64 from each side, and always have at least 1 section # A 128 gap will have length = 0 for dis in range(0, abs(length) + 1, 128): new_pos = begin_pos + direction * dis VMF.create_ent( classname="func_instance", targetname=pair_name, angles=begin_inst["angles"], file=mid_file, origin=new_pos.join(" "), )
def load_config(): global CONF utils.con_log('Loading Settings...') try: with open("bee2/vrad_config.cfg") as config: CONF = Property.parse(config, 'bee2/vrad_config.cfg').find_key( 'Config', [] ) except FileNotFoundError: pass utils.con_log('Config Loaded!')
def pack_file(zipfile, filename): """Check multiple locations for a resource file. """ for poss_path in RES_ROOT: full_path = os.path.normpath(os.path.join(poss_path, filename)) if os.path.isfile(full_path): zipfile.write( filename=full_path, arcname=filename, ) break else: utils.con_log('"bee2/' + filename + '" not found!')
def parse_package(zip_file, info, pak_id, disp_name): """Parse through the given package to find all the components.""" for pre in Property.find_key(info, 'Prerequisites', []).value: if pre.value not in packages: utils.con_log( 'Package "' + pre.value + '" required for "' + pak_id + '" - ignoring package!' ) return False objects = 0 # First read through all the components we have, so we can match # overrides to the originals for comp_type in OBJ_TYPES: allow_dupes = OBJ_TYPES[comp_type].allow_mult # Look for overrides for obj in info.find_all("Overrides", comp_type): obj_id = obj['id'] obj_override[comp_type][obj_id].append( ParseData(zip_file, obj_id, obj, pak_id) ) for obj in info.find_all(comp_type): obj_id = obj['id'] if obj_id in all_obj[comp_type]: if allow_dupes: # Pretend this is an override obj_override[comp_type][obj_id].append( ParseData(zip_file, obj_id, obj, pak_id) ) else: raise Exception('ERROR! "' + obj_id + '" defined twice!') objects += 1 all_obj[comp_type][obj_id] = ObjData( zip_file, obj, pak_id, disp_name, ) img_count = 0 img_loc = os.path.join('resources', 'bee2') for item in zip_names(zip_file): item = os.path.normcase(item).casefold() if item.startswith("resources"): extract_packages.res_count += 1 if item.startswith(img_loc): img_count += 1 return objects, img_count
def check_all(): """Check all conditions.""" utils.con_log('Checking Conditions...') for condition in conditions: if condition.valid: for inst in VMF.by_class['func_instance']: try: condition.test(inst) except SkipCondition: # This is raised to immediately stop running # this condition, and skip to the next instance. pass if len(condition.results) == 0: break remove_blank_inst() utils.con_log('Map has attributes: ', [ key for key, value in VOICE_ATTR.items() if value ]) utils.con_log('Style Vars:', dict(STYLE_VARS.items())) utils.con_log('Global instances: ', GLOBAL_INSTANCES)
def check_flag(flag, inst): # print('Checking {type} ({val!s} on {inst}'.format( # type=flag.real_name, # val=flag.value, # inst=inst['file'], # )) try: func = FLAG_LOOKUP[flag.name] except KeyError: utils.con_log('"' + flag.name + '" is not a valid condition flag!') return False else: res = func(inst, flag) return res
def run_vrad(args): "Execute the original VRAD." if utils.MAC: os_suff = '_osx' elif utils.LINUX: os_suff = '_linux' else: os_suff = '' joined_args = ( '"' + os.path.normpath( os.path.join(os.getcwd(), "vrad" + os_suff + "_original")) + '" ' + " ".join( # put quotes around args which contain spaces (quote(x) if " " in x else x) for x in args)) utils.con_log("Calling original VRAD...") utils.con_log(joined_args) code = subprocess.call( joined_args, stdout=None, stderr=subprocess.PIPE, shell=True, ) if code == 0: utils.con_log("Done!") else: utils.con_log("VRAD failed! (" + str(code) + ")") sys.exit(code)
def run_vrad(args): "Execute the original VRAD." if utils.MAC: os_suff = '_osx' elif utils.LINUX: os_suff = '_linux' else: os_suff = '' joined_args = ( '"' + os.path.normpath( os.path.join(os.getcwd(), "vrad" + os_suff + "_original") ) + '" ' + " ".join( # put quotes around args which contain spaces (quote(x) if " " in x else x) for x in args ) ) utils.con_log("Calling original VRAD...") utils.con_log(joined_args) code = subprocess.call( joined_args, stdout=None, stderr=subprocess.PIPE, shell=True, ) if code == 0: utils.con_log("Done!") else: utils.con_log("VRAD failed! (" + str(code) + ")") sys.exit(code)
def pack_file(zipfile, filename): """Check multiple locations for a resource file. """ for poss_path in RES_ROOT: full_path = os.path.normpath( os.path.join(poss_path, filename) ) if os.path.isfile(full_path): zipfile.write( filename=full_path, arcname=filename, ) break else: utils.con_log('"bee2/' + filename + '" not found!')
def place_catwalk_connections(instances, point_a, point_b): """Place catwalk sections to connect two straight points.""" diff = point_b - point_a # The horizontal unit vector in the direction we are placing catwalks direction = diff.copy() direction.z = 0 distance = direction.len() - 128 direction = direction.norm() if diff.z > 0: angle = INST_ANGLE[direction.as_tuple()] # We need to add stairs for stair_pos in range(0, int(diff.z), 128): # Move twice the vertical horizontally # plus 128 so we don't start in point A loc = point_a + (2 * stair_pos + 128) * direction # Do the vertical offset loc.z += stair_pos VMF.create_ent(classname="func_instance", origin=loc.join(" "), angles=angle, file=instances["stair"]) # This is the location we start flat sections at point_a = loc + 128 * direction point_a.z += 128 elif diff.z < 0: # We need to add downward stairs # They point opposite to normal ones utils.con_log("down from", point_a) angle = INST_ANGLE[(-direction).as_tuple()] for stair_pos in range(0, -int(diff.z), 128): utils.con_log(stair_pos) # Move twice the vertical horizontally loc = point_a + (2 * stair_pos + 256) * direction # Do the vertical offset plus additional 128 units # to account for the moved instance loc.z -= stair_pos + 128 VMF.create_ent(classname="func_instance", origin=loc.join(" "), angles=angle, file=instances["stair"]) # Adjust point A to be at the end of the catwalks point_a = loc # Remove the space the stairs take up from the horiz distance distance -= abs(diff.z) * 2 # Now do straight sections utils.con_log("Stretching ", distance, direction) angle = INST_ANGLE[direction.as_tuple()] loc = point_a + (direction * 128) # Figure out the most efficent number of sections for segment_len in utils.fit(distance, [512, 256, 128]): VMF.create_ent( classname="func_instance", origin=loc.join(" "), angles=angle, file=instances["straight_" + str(segment_len)], ) utils.con_log(loc) loc += segment_len * direction
def test(self, inst): """Try to satisfy this condition on the given instance.""" success = True for flag in self.flags: if not check_flag(flag, inst): success = False break results = self.results if success else self.else_results for res in results[:]: try: func = RESULT_LOOKUP[res.name] except KeyError: utils.con_log('"{}" is not a valid condition result!'.format(res.real_name)) else: should_del = func(inst, res) if should_del is True: results.remove(res)
def res_cust_output(inst, res): """Add an additional output to the instance with any values. Always points to the targeted item. """ over_name = '@' + inst['targetname'] + '_indicator' for toggle in VMF.by_class['func_instance']: if toggle.fixup['indicator_name', ''] == over_name: toggle_name = toggle['targetname'] break else: toggle_name = '' # we want to ignore the toggle instance, if it exists # Make this a set to ignore repeated targetnames targets = {o.target for o in inst.outputs if o.target != toggle_name} kill_signs = utils.conv_bool(res["remIndSign", '0'], False) dec_con_count = utils.conv_bool(res["decConCount", '0'], False) targ_conditions = list(res.find_all("targCondition")) pan_files = resolve_inst('[indPan]') if kill_signs or dec_con_count or targ_conditions: for con_inst in VMF.by_class['func_instance']: if con_inst['targetname'] in targets: if kill_signs and con_inst in pan_files: VMF.remove_ent(con_inst) if targ_conditions: for cond in targ_conditions: cond.value.test(con_inst) if dec_con_count and 'connectioncount' in con_inst.fixup: # decrease ConnectionCount on the ents, # so they can still process normal inputs try: val = int(con_inst.fixup['connectioncount']) con_inst.fixup['connectioncount'] = str(val-1) except ValueError: # skip if it's invalid utils.con_log( con_inst['targetname'] + ' has invalid ConnectionCount!' ) for targ in targets: for out in res.find_all('addOut'): add_output(inst, out, targ)
def parse_package(zip_file, info, pak_id, disp_name): """Parse through the given package to find all the components.""" for pre in Property.find_key(info, 'Prerequisites', []).value: if pre.value not in packages: utils.con_log('Package "' + pre.value + '" required for "' + pak_id + '" - ignoring package!') return False objects = 0 # First read through all the components we have, so we can match # overrides to the originals for comp_type in OBJ_TYPES: allow_dupes = OBJ_TYPES[comp_type].allow_mult # Look for overrides for obj in info.find_all("Overrides", comp_type): obj_id = obj['id'] obj_override[comp_type][obj_id].append( ParseData(zip_file, obj_id, obj, pak_id)) for obj in info.find_all(comp_type): obj_id = obj['id'] if obj_id in all_obj[comp_type]: if allow_dupes: # Pretend this is an override obj_override[comp_type][obj_id].append( ParseData(zip_file, obj_id, obj, pak_id)) else: raise Exception('ERROR! "' + obj_id + '" defined twice!') objects += 1 all_obj[comp_type][obj_id] = ObjData( zip_file, obj, pak_id, disp_name, ) img_count = 0 img_loc = os.path.join('resources', 'bee2') for item in zip_names(zip_file): item = os.path.normcase(item).casefold() if item.startswith("resources"): extract_packages.res_count += 1 if item.startswith(img_loc): img_count += 1 return objects, img_count
def parse_package(zip_file, info, pak_id, disp_name): """Parse through the given package to find all the components.""" global res_count for pre in Property.find_key(info, 'Prerequisites', []).value: if pre.value not in packages: utils.con_log( 'Package "' + pre.value + '" required for "' + pak_id + '" - ignoring package!' ) return False objects = 0 # First read through all the components we have, so we can match # overrides to the originals for comp_type in obj_types: # Look for overrides for obj in info.find_all("Overrides", comp_type): obj_id = obj['id'] obj_override[comp_type][obj_id].append( (zip_file, obj) ) for obj in info.find_all(comp_type): obj_id = obj['id'] if obj_id in all_obj[comp_type]: raise Exception('ERROR! "' + obj_id + '" defined twice!') objects += 1 all_obj[comp_type][obj_id] = ObjData( zip_file, obj, pak_id, disp_name, ) if res_count != -1: for item in zip_names(zip_file): if item.startswith("resources"): res_count += 1 loader.set_length("RES", res_count) return objects
def find_group_quotes(group, mid_quotes, conf): is_mid = group.name == 'midinst' group_id = group['name'] for quote in group.find_all('quote'): valid_quote = True for flag in quote: name = flag.name if name == 'instance': continue # break out if a flag is unsatisfied if name == 'has' and map_attr[flag.value.casefold()] is False: valid_quote = False break elif name == 'nothas' and map_attr[flag.value.casefold()] is True: valid_quote = False break elif (name == 'stylevartrue' and style_vars[flag.value.casefold()] is False): valid_quote = False break elif (name == 'stylevarfalse' and style_vars[flag.value.casefold()] is True): valid_quote = False break quote_id = quote['id', quote['name', '']] utils.con_log(quote_id, valid_quote) if valid_quote: # Check if the ID is enabled! if conf.get_bool(group_id, quote_id, True): if ALLOW_MID_VOICES and is_mid: mid_quotes.extend(quote.find_all('instance')) else: inst_list = list(quote.find_all('instance')) if inst_list: yield ( quote['priority', '0'], inst_list, )
def check_all(): """Check all conditions.""" utils.con_log("Checking Conditions...") for condition in conditions: for inst in VMF.by_class["func_instance"]: try: condition.test(inst) except NextInstance: # This is raised to immediately stop running # this condition, and skip to the next instance. pass except EndCondition: # This is raised to immediately stop running # this condition, and skip to the next condtion. break if not condition.results and not condition.else_results: utils.con_log("Exiting empty condition!") break # Condition has run out of results, quit early utils.con_log("Map has attributes: ", [key for key, value in VOICE_ATTR.items() if value]) utils.con_log("Style Vars:", dict(STYLE_VARS.items())) utils.con_log("Global instances: ", GLOBAL_INSTANCES)
def find_packages(pak_dir, zips, zip_name_lst): """Search a folder for packages, recursing if necessary.""" found_pak = False for name in os.listdir(pak_dir): # Both files and dirs name = os.path.join(pak_dir, name) is_dir = os.path.isdir(name) if name.endswith('.zip') and os.path.isfile(name): zip_file = ZipFile(name) elif is_dir: zip_file = FakeZip(name) else: utils.con_log('Extra file: ', name) continue if 'info.txt' in zip_file.namelist(): # Is it valid? zips.append(zip_file) zip_name_lst.append(os.path.abspath(name)) print('Reading package "' + name + '"') with zip_file.open('info.txt') as info_file: info = Property.parse(info_file, name + ':info.txt') pak_id = info['ID'] disp_name = info['Name', pak_id] packages[pak_id] = PackageData( zip_file, info, name, disp_name, ) found_pak = True else: if is_dir: # This isn't a package, so check the subfolders too... print('Checking subdir "{}" for packages...'.format(name)) find_packages(name, zips, zip_name_lst) else: zip_file.close() print('ERROR: Bad package "{}"!'.format(name)) if not found_pak: print('No packages in folder!')
def resolve(path) -> list: """Replace an instance path with the values it refers to. Valid paths: - "<ITEM_ID:1,2,5>": matches the given indexes for that item. - "<ITEM_ID:cube_black, cube_white>": the same, with strings for indexes - "[spExitCorridor]": Hardcoded shortcuts for specific items This returns a list of instances which match the selector. """ if path.startswith('<') and path.endswith('>'): path = path[1:-1] if ':' in path: # We have a set of subitems to parse item, subitem = path.split(':') try: item_values = INSTANCE_FILES[item] except KeyError: utils.con_log( '"{}" not a valid item!'.format(item) ) return [] out = set() for val in subitem.split(','): ind = SUBITEMS.get( val.strip().casefold(), int(val.strip()), ) # Only add if it's actually in range if 0 <= ind < len(item_values): out.add(item_values[ind]) return list(out) else: try: return INSTANCE_FILES[path] except KeyError: utils.con_log( '"{}" not a valid item!'.format(path) ) return [] elif path.startswith('[') and path.endswith(']'): path = path[1:-1].casefold() try: return INST_SPECIAL[path] except KeyError: utils.con_log('"{}" not a valid instance category!'.format(path)) return [] else: # Just a normal path return [path.casefold()]
def res_cust_fizzler(base_inst, res): """Modify a fizzler item to allow for custom brush ents.""" from vbsp import TEX_FIZZLER model_name = res["modelname", None] make_unique = utils.conv_bool(res["UniqueModel", "0"]) fizz_name = base_inst["targetname", ""] # search for the model instances model_targetnames = (fizz_name + "_modelStart", fizz_name + "_modelEnd") is_laser = False for inst in VMF.by_class["func_instance"]: if inst["targetname", ""] in model_targetnames: if inst.fixup["skin", "0"] == "2": is_laser = True if model_name is not None: if model_name == "": inst["targetname"] = base_inst["targetname"] else: inst["targetname"] = base_inst["targetname"] + "-" + model_name if make_unique: inst.make_unique() for key, value in base_inst.fixup.items(): inst.fixup[key] = value new_brush_config = list(res.find_all("brush")) if len(new_brush_config) == 0: return # No brush modifications if is_laser: # This is a laserfield! We can't edit those brushes! utils.con_log("CustFizzler excecuted on LaserField!") return for orig_brush in VMF.by_class["trigger_portal_cleanser"] & VMF.by_target[fizz_name + "_brush"]: print(orig_brush) VMF.remove_ent(orig_brush) for config in new_brush_config: new_brush = orig_brush.copy() VMF.add_ent(new_brush) new_brush.clear_keys() # Wipe the original keyvalues new_brush["origin"] = orig_brush["origin"] new_brush["targetname"] = fizz_name + "-" + config["name", "brush"] # All ents must have a classname! new_brush["classname"] = "trigger_portal_cleanser" for prop in config["keys", []]: new_brush[prop.name] = prop.value laserfield_conf = config.find_key("MakeLaserField", None) if laserfield_conf.value is not None: # Resize the brush into a laserfield format, without # the 128*64 parts. If the brush is 128x128, we can # skip the resizing since it's already correct. laser_tex = laserfield_conf["texture", "effects/laserplane"] nodraw_tex = laserfield_conf["nodraw", "tools/toolsnodraw"] tex_width = utils.conv_int(laserfield_conf["texwidth", "512"], 512) is_short = False for side in new_brush.sides(): if side.mat.casefold() == "effects/fizzler": is_short = True break if is_short: for side in new_brush.sides(): if side.mat.casefold() == "effects/fizzler": side.mat = laser_tex uaxis = side.uaxis.split(" ") vaxis = side.vaxis.split(" ") # the format is like "[1 0 0 -393.4] 0.25" side.uaxis = " ".join(uaxis[:3]) + " 0] 0.25" side.vaxis = " ".join(vaxis[:4]) + " 0.25" else: side.mat = nodraw_tex else: # The hard part - stretching the brush. convert_to_laserfield(new_brush, laser_tex, nodraw_tex, tex_width) else: # Just change the textures for side in new_brush.sides(): try: side.mat = config[TEX_FIZZLER[side.mat.casefold()]] except (KeyError, IndexError): # If we fail, just use the original textures pass
def add_voice( voice_data, has_items, style_vars_, vmf_file, map_seed, mode='SP', ): """Add a voice line to the map.""" global ALLOW_MID_VOICES, VMF, map_attr, style_vars, GAME_MODE utils.con_log('Adding Voice Lines!') if len(voice_data.value) == 0: utils.con_log('Error - No Voice Line Data!') return VMF = vmf_file map_attr = has_items style_vars = style_vars_ GAME_MODE = mode norm_config = ConfigFile('voice.cfg', root='bee2') mid_config = ConfigFile('mid_voice.cfg', root='bee2') quote_base = voice_data['base', False] quote_loc = voice_data['quote_loc', '-10000 0 0'] if quote_base: print('Adding Base instance!') VMF.create_ent( classname='func_instance', targetname='voice', file=INST_PREFIX + quote_base, angles='0 0 0', origin=quote_loc, fixup_style='0', ) ALLOW_MID_VOICES = not style_vars.get('NoMidVoices', False) mid_quotes = [] for group in itertools.chain( voice_data.find_all('group'), voice_data.find_all('midinst'), ): quote_targetname = group['Choreo_Name', '@choreo'] possible_quotes = sorted( find_group_quotes( group, mid_quotes, conf=mid_config if group.name == 'midinst' else norm_config, ), key=sort_func, reverse=True, ) if possible_quotes: choreo_loc = group['choreo_loc', quote_loc] chosen = possible_quotes[0][1] utils.con_log('Chosen:', '\n'.join(map(repr, chosen))) # Join the IDs for the voice lines to the map seed, # so each quote block will chose different lines. random.seed(map_seed + '-VOICE_' + '|'.join(prop['id', 'ID'] for prop in chosen)) # Add one of the associated quotes add_quote(random.choice(chosen), quote_targetname, choreo_loc) print('Mid quotes: ', mid_quotes) for mid_item in mid_quotes: # Add all the mid quotes target = mid_item['target', ''] for prop in mid_item: add_quote(prop, target, quote_loc) utils.con_log('Done!')
def resolve(path) -> list: """Replace an instance path with the values it refers to. Valid paths: - "<ITEM_ID:1,2,5>": matches the given indexes for that item. - "<ITEM_ID:cube_black, cube_white>": the same, with strings for indexes - "[spExitCorridor]": Hardcoded shortcuts for specific items This returns a list of instances which match the selector. When using <> values, the '' path will never be returned. """ if path.startswith('<') and path.endswith('>'): path = path[1:-1] if ':' in path: # We have a set of subitems to parse item, subitem = path.split(':') try: item_values = INSTANCE_FILES[item] except KeyError: utils.con_log( '"{}" not a valid item!'.format(item) ) return [] out = [] for val in subitem.split(','): ind = SUBITEMS.get(val.strip().casefold(), None) if ind is None: try: ind = int(val.strip()) except ValueError as e: utils.con_log('--------\nValid subitems:') utils.con_log('\n'.join( ('> ' + k + ' = ' + str(v)) for k, v in SUBITEMS.items() )) utils.con_log('--------') raise Exception( '"' + val + '" is not a valid instance' ' subtype or index!' ) # Only add if it's actually in range, and skip "" values if 0 <= ind < len(item_values) and item_values[ind] != '': out.append(item_values[ind]) return out else: try: # Skip "" instances return [ inst for inst in INSTANCE_FILES[path] if inst != '' ] except KeyError: utils.con_log( '"{}" not a valid item!'.format(path) ) return [] elif path.startswith('[') and path.endswith(']'): path = path[1:-1].casefold() try: return INST_SPECIAL[path] except KeyError: utils.con_log('"{}" not a valid instance category!'.format(path)) return [] else: # Just a normal path return [path.casefold()]
"-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 VBSP 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 utils.con_log("Map path is " + path) if path == "": raise Exception("No map passed!") if not path.endswith(".bsp"): path += ".bsp" if '-force_peti' in args or '-force_hammer' in args: # we have override command! if '-force_peti' in args: utils.con_log('OVERRIDE: Applying cheap lighting!') is_peti = True else: utils.con_log('OVERRIDE: Preserving args!') is_peti = False else:
def res_cust_fizzler(base_inst, res): """Modify a fizzler item to allow for custom brush ents.""" model_name = res['modelname', None] make_unique = utils.conv_bool(res['UniqueModel', '0']) fizz_name = base_inst['targetname', ''] # search for the model instances model_targetnames = ( fizz_name + '_modelStart', fizz_name + '_modelEnd', ) for inst in VMF.by_class['func_instance']: if inst['targetname', ''] in model_targetnames: if inst.fixup['skin', '0'] == '2': # This is a laserfield! We can't edit that! utils.con_log('CustFizzler excecuted on LaserField!') return if model_name is not None: if model_name == '': inst['targetname'] = base_inst['targetname'] else: inst['targetname'] = ( base_inst['targetname'] + '-' + model_name ) if make_unique: inst.make_unique() for key, value in base_inst.fixup.items(): inst.fixup[key] = value new_brush_config = list(res.find_all('brush')) if len(new_brush_config) == 0: return # No brush modifications for orig_brush in ( VMF.by_class['trigger_portal_cleanser'] & VMF.by_target[fizz_name + '_brush']): print(orig_brush) VMF.remove_ent(orig_brush) for config in new_brush_config: new_brush = orig_brush.copy() VMF.add_ent(new_brush) new_brush.clear_keys() # Wipe the original keyvalues new_brush['origin'] = orig_brush['origin'] new_brush['targetname'] = ( fizz_name + '-' + config['name', 'brush'] ) # All ents must have a classname! new_brush['classname'] = 'trigger_portal_cleanser' for prop in config['keys', []]: new_brush[prop.name] = prop.value laserfield_conf = config.find_key('MakeLaserField', None) if laserfield_conf.value is not None: # Resize the brush into a laserfield format, without # the 128*64 parts. If the brush is 128x128, we can # skip the resizing since it's already correct. laser_tex = laserfield_conf['texture', 'effects/laserplane'] nodraw_tex = laserfield_conf['nodraw', 'tools/toolsnodraw'] tex_width = utils.conv_int( laserfield_conf['texwidth', '512'], 512 ) is_short = False for side in new_brush.sides(): if side.mat.casefold() == 'effects/fizzler': is_short = True break if is_short: for side in new_brush.sides(): if side.mat.casefold() == 'effects/fizzler': side.mat = laser_tex uaxis = side.uaxis.split(" ") vaxis = side.vaxis.split(" ") # the format is like "[1 0 0 -393.4] 0.25" side.uaxis = ' '.join(uaxis[:3]) + ' 0] 0.25' side.vaxis = ' '.join(vaxis[:4]) + ' 0.25' else: side.mat = nodraw_tex else: # The hard part - stretching the brush. convert_to_laserfield( new_brush, laser_tex, nodraw_tex, tex_width, ) else: # Just change the textures for side in new_brush.sides(): try: side.mat = config[ vbsp.TEX_FIZZLER[side.mat.casefold()] ] except (KeyError, IndexError): # If we fail, just use the original textures pass
def add_voice( voice_data, has_items, style_vars_, vmf_file, timer_config=utils.EmptyMapping, mode='SP', ): """Add a voice line to the map.""" global ALLOW_MID_VOICES, VMF, map_attr, style_vars print('Adding Voice!') if len(voice_data.value) == 0: print('No Data!') return VMF = vmf_file map_attr = has_items style_vars = style_vars_ if mode == 'SP': norm_config = ConfigFile('SP.cfg', root='bee2') mid_config = ConfigFile('MID_SP.cfg', root='bee2') else: norm_config = ConfigFile('COOP.cfg', root='bee2') mid_config = ConfigFile('MID_COOP.cfg', root='bee2') quote_base = voice_data['base', False] quote_loc = voice_data['quote_loc', '-10000 0 0'] if quote_base: print('Adding Base instance!') VMF.create_ent( classname='func_instance', targetname='voice', file=INST_PREFIX + quote_base, angles='0 0 0', origin=quote_loc, fixup_style='0', ) ALLOW_MID_VOICES = not style_vars.get('NoMidVoices', False) mid_quotes = [] for group in itertools.chain( voice_data.find_all('group'), voice_data.find_all('midinst'), ): quote_targetname = group['Choreo_Name', '@choreo'] possible_quotes = sorted( find_group_quotes( group, mid_quotes, conf=mid_config if group.name == 'midinst' else norm_config, ), key=sort_func, reverse=True, ) if possible_quotes: # If we have a timer value, we go that many back from the # highest priority item. If it fails, default to the first. timer_val = timer_config.get( group['config', ''].casefold(), '3') try: timer_val = int(timer_val) - 3 except ValueError: timer_val = 0 choreo_loc = group['choreo_loc', quote_loc] utils.con_log('Timer value: ', timer_val) try: chosen = possible_quotes[timer_val][1] except IndexError: chosen = possible_quotes[0][1] utils.con_log('Chosen: {!r}'.format(chosen)) # Add all the associated quotes for prop in chosen: add_quote(prop, quote_targetname, choreo_loc) print('mid quotes: ', mid_quotes) for mid_item in mid_quotes: # Add all the mid quotes target = mid_item['target', ''] for prop in mid_item: add_quote(prop, target, quote_loc) utils.con_log('Done!')
def res_track_plat(_, res): """Logic specific to Track Platforms. This allows switching the instances used depending on if the track is horizontal or vertical and sets the track targetnames to a useful value. """ # Get the instances from editoritems (inst_bot_grate, inst_bottom, inst_middle, inst_top, inst_plat, inst_plat_oscil, inst_single) = resolve_inst( res["orig_item"] ) single_plat_inst = res["single_plat", ""] track_targets = res["track_name", ""] track_files = [inst_bottom, inst_middle, inst_top, inst_single] platforms = [inst_plat, inst_plat_oscil] # All the track_set in the map, indexed by origin track_instances = { Vec.from_str(inst["origin"]).as_tuple(): inst for inst in VMF.by_class["func_instance"] if inst["file"].casefold() in track_files } utils.con_log("Track instances:") utils.con_log("\n".join("{!s}: {}".format(k, v["file"]) for k, v in track_instances.items())) # Now we loop through all platforms in the map, and then locate their # track_set for plat_inst in VMF.by_class["func_instance"]: if plat_inst["file"].casefold() not in platforms: continue # Not a platform! utils.con_log('Modifying "' + plat_inst["targetname"] + '"!') plat_loc = Vec.from_str(plat_inst["origin"]) angles = Vec.from_str(plat_inst["angles"]) # The direction away from the wall/floor/ceil normal = Vec(0, 0, 1).rotate(angles.x, angles.y, angles.z) for tr_origin, first_track in track_instances.items(): if plat_loc == tr_origin: # Check direction if normal == Vec(0, 0, 1).rotate(*Vec.from_str(first_track["angles"])): break else: raise Exception('Platform "{}" has no track!'.format(plat_inst["targetname"])) track_type = first_track["file"].casefold() if track_type == inst_single: # Track is one block long, use a single-only instance and # remove track! plat_inst["file"] = single_plat_inst first_track.remove() continue # Next platform track_set = set() if track_type == inst_top or track_type == inst_middle: # search left track_scan(track_set, track_instances, first_track, middle_file=inst_middle, x_dir=-1) if track_type == inst_bottom or track_type == inst_middle: # search right track_scan(track_set, track_instances, first_track, middle_file=inst_middle, x_dir=+1) # Give every track a targetname matching the platform for ind, track in enumerate(track_set, start=1): if track_targets == "": track["targetname"] = plat_inst["targetname"] else: track["targetname"] = plat_inst["targetname"] + "-" + track_targets + str(ind) # Now figure out which way the track faces: # The direction horizontal track is offset side_dir = Vec(0, 1, 0).rotate(*Vec.from_str(first_track["angles"])) # The direction of the platform surface facing = Vec(-1, 0, 0).rotate(angles.x, angles.y, angles.z) if side_dir == facing: track_facing = "HORIZ" elif side_dir == -facing: track_facing = "HORIZ_MIRR" else: track_facing = "VERT" # Now add the suffixes if track_facing == "VERT": if utils.conv_bool(res["vert_suffix", ""]): for inst in track_set: add_suffix(inst, "_vert") if utils.conv_bool(res["plat_suffix", ""]): add_suffix(plat_inst, "_vert") elif track_facing == "HORIZ_MIRR": if utils.conv_bool(res["horiz_suffix", ""]): for inst in track_set: add_suffix(inst, "_horiz_mirrored") if utils.conv_bool(res["plat_suffix", ""]): add_suffix(plat_inst, "_horiz") else: # == 'HORIZ' if utils.conv_bool(res["horiz_suffix", ""]): for inst in track_set: add_suffix(inst, "_horiz") if utils.conv_bool(res["plat_suffix", ""]): add_suffix(plat_inst, "_horiz") return True # Only run once!
def mod_screenshots(): """Modify the map's screenshot.""" mod_type = CONF['screenshot_type', 'PETI'].lower() if mod_type == 'cust': utils.con_log('Using custom screenshot!') scr_loc = CONF['screenshot', ''] elif mod_type == 'auto': utils.con_log('Using automatic screenshot!') scr_loc = None # The automatic screenshots are found at this location: auto_path = os.path.join( '..', 'portal2', '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. screens = [ os.path.join(auto_path, path) for path in os.listdir(auto_path) ] screens.sort( key=os.path.getmtime, reverse=True, # Go from most recent to least ) playtested = False for scr_shot in screens: utils.con_log(scr_shot) 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: utils.con_log('Screenshot "{scr}" too old ({diff!s})'.format( scr=scr_shot, diff=diff )) continue # If we got here, it's a good screenshot! utils.con_log('Chosen "{}"'.format(scr_shot)) utils.con_log('Map Playtested:', playtested) scr_loc = scr_shot break else: # If we get to the end, we failed to find an automatic # screenshot! utils.con_log('No Auto Screenshot found!') mod_type = 'peti' # Suppress the "None not found" error if utils.conv_bool(CONF['clean_screenshots', '0']): utils.con_log('Cleaning up screenshots...') # Clean up this folder - otherwise users will get thousands of # pics in there! for screen in screens: if screen != scr_loc: os.remove(screen) utils.con_log('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_screenshots(): utils.con_log('Replacing "{}"...'.format(screen)) # Allow us to edit the file... unset_readonly(screen) shutil.copy(scr_loc, screen) # Make the screenshot readonly, so P2 can't replace it. # Then it'll use our own set_readonly(screen) else: if mod_type != 'peti': # Error if the screenshot doesn't exist utils.con_log('"{}" not found!'.format(scr_loc)) utils.con_log('Using PeTI screenshot!') for screen in find_screenshots(): # Make the screenshot writeable, so P2 will replace it utils.con_log('Making "{}" replaceable...'.format(screen)) unset_readonly(screen)
def res_make_catwalk(_, res): """Speciallised result to generate catwalks from markers. Only runs once, and then quits the condition list. """ utils.con_log("Starting catwalk generator...") marker = resolve_inst(res["markerInst"]) output_target = res["output_name", "MARKER"] instances = { name: resolve_inst(res[name, ""])[0] for name in ( "straight_128", "straight_256", "straight_512", "corner", "tjunction", "crossjunction", "end", "stair", "end_wall", "support_wall", "support_ceil", "support_floor", "single_wall", "markerInst", ) } # If there are no attachments remove a catwalk piece instances["NONE"] = "" if instances["end_wall"] == "": instances["end_wall"] = instances["end"] connections = {} # The directions this instance is connected by (NSEW) markers = {} for inst in VMF.by_class["func_instance"]: if inst["file"].casefold() not in marker: continue # [North, South, East, West ] connections[inst] = [False, False, False, False] markers[inst["targetname"]] = inst if not markers: return True # No catwalks! utils.con_log("Conn:", connections) utils.con_log("Markers:", markers) # First loop through all the markers, adding connecting sections for inst in markers.values(): for conn in inst.outputs: if conn.output != output_target or conn.input != output_target: # Indicator toggles or similar, delete these print("Removing ", conn.target) for del_inst in VMF.by_target[conn.target]: del_inst.remove() continue inst2 = markers[conn.target] print(inst["targetname"], "<->", inst2["targetname"]) origin1 = Vec.from_str(inst["origin"]) origin2 = Vec.from_str(inst2["origin"]) if origin1.x != origin2.x and origin1.y != origin2.y: utils.con_log("Instances not aligned!") continue y_dir = origin1.x == origin2.x # Which way the connection is if y_dir: dist = abs(origin1.y - origin2.y) else: dist = abs(origin1.x - origin2.x) vert_dist = origin1.z - origin2.z utils.con_log("Dist =", dist, ", Vert =", vert_dist) if dist // 2 < vert_dist: # The stairs are 2 long, 1 high. utils.con_log("Not enough room for stairs!") continue if dist > 128: # add straight sections in between place_catwalk_connections(instances, origin1, origin2) # Update the lists based on the directions that were set conn_lst1 = connections[inst] conn_lst2 = connections[inst2] if origin1.x < origin2.x: conn_lst1[2] = True # E conn_lst2[3] = True # W elif origin2.x < origin1.x: conn_lst1[3] = True # W conn_lst2[2] = True # E if origin1.y < origin2.y: conn_lst1[0] = True # N conn_lst2[1] = True # S elif origin2.y < origin1.y: conn_lst1[1] = True # S conn_lst2[0] = True # N inst.outputs.clear() # Remove the outputs now, they're useless for inst, dir_mask in connections.items(): # Set the marker instances based on the attached walkways. print(inst["targetname"], dir_mask) angle = Vec.from_str(inst["angles"], 0, 0, 0) new_type, inst["angles"] = utils.CONN_LOOKUP[tuple(dir_mask)] inst["file"] = instances[CATWALK_TYPES[new_type]] normal = Vec(0, 0, 1).rotate(angle.x, angle.y, angle.z) ":type normal: Vec" if new_type is utils.CONN_TYPES.side: # If the end piece is pointing at a wall, switch the instance. if normal.z == 0: # Treat booleans as ints to get the direction the connection is # in - True == 1, False == 0 conn_dir = Vec(x=dir_mask[2] - dir_mask[3], y=dir_mask[0] - dir_mask[1], z=0) # +E, -W # +N, -S, if normal == conn_dir: inst["file"] = instances["end_wall"] continue # We never have normal supports on end pieces elif new_type is utils.CONN_TYPES.none: # Unconnected catwalks on the wall switch to a special instance. # This lets players stand next to a portal surface on the wall. if normal.z == 0: inst["file"] = instances["single_wall"] inst["angles"] = INST_ANGLE[normal.as_tuple()] else: inst.remove() continue # These don't get supports otherwise # Add regular supports if normal == (0, 0, 1): supp = instances["support_floor"] elif normal == (0, 0, -1): supp = instances["support_ceil"] else: supp = instances["support_wall"] if supp: VMF.create_ent( classname="func_instance", origin=inst["origin"], angles=INST_ANGLE[normal.as_tuple()], file=supp ) utils.con_log("Finished catwalk generation!") return True # Don't run this again
def main(argv): utils.con_log('BEE2 VRAD hook started!') args = " ".join(argv) fast_args = argv[1:] full_args = argv[1:] path = argv[-1] # The path is the last argument to vrad fast_args[-1] = os.path.normpath(path) utils.con_log("Map path is " + path) if path == "": raise Exception("No map passed!") 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 VBSP 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 '-force_peti' in args or '-force_hammer' in args: # we have override command! if '-force_peti' in args: utils.con_log('OVERRIDE: Applying cheap lighting!') is_peti = True else: utils.con_log('OVERRIDE: Preserving args!') is_peti = False else: # If we don't get the special -force args, check for the name # equalling preview to determine if we should convert # If that is false, check the config file to see what was # specified there. is_peti = ( os.path.basename(path) == "preview.bsp" or utils.conv_bool(CONF['force_full'], False) ) mod_screenshots() if is_peti: utils.con_log("Forcing Cheap Lighting!") run_vrad(fast_args) else: utils.con_log("Hammer map detected! Not forcing cheap lighting..") run_vrad(full_args) if '-no_pack' not in args: pack_content(path) else: utils.con_log("No items to pack!") utils.con_log("BEE2 VRAD hook finished!")
def mod_screenshots(): """Modify the map's screenshot.""" mod_type = CONF['screenshot_type', 'PETI'].lower() if mod_type == 'cust': utils.con_log('Using custom screenshot!') scr_loc = CONF['screenshot', ''] elif mod_type == 'auto': utils.con_log('Using automatic screenshot!') scr_loc = None # The automatic screenshots are found at this location: auto_path = os.path.join('..', 'portal2', '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. screens = [ os.path.join(auto_path, path) for path in os.listdir(auto_path) ] screens.sort( key=os.path.getmtime, reverse=True, # Go from most recent to least ) playtested = False for scr_shot in screens: utils.con_log(scr_shot) 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: utils.con_log('Screenshot "{scr}" too old ({diff!s})'.format( scr=scr_shot, diff=diff)) continue # If we got here, it's a good screenshot! utils.con_log('Chosen "{}"'.format(scr_shot)) utils.con_log('Map Playtested:', playtested) scr_loc = scr_shot break else: # If we get to the end, we failed to find an automatic # screenshot! utils.con_log('No Auto Screenshot found!') mod_type = 'peti' # Suppress the "None not found" error if utils.conv_bool(CONF['clean_screenshots', '0']): utils.con_log('Cleaning up screenshots...') # Clean up this folder - otherwise users will get thousands of # pics in there! for screen in screens: if screen != scr_loc: os.remove(screen) utils.con_log('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_screenshots(): utils.con_log('Replacing "{}"...'.format(screen)) # Allow us to edit the file... unset_readonly(screen) shutil.copy(scr_loc, screen) # Make the screenshot readonly, so P2 can't replace it. # Then it'll use our own set_readonly(screen) else: if mod_type != 'peti': # Error if the screenshot doesn't exist utils.con_log('"{}" not found!'.format(scr_loc)) utils.con_log('Using PeTI screenshot!') for screen in find_screenshots(): # Make the screenshot writeable, so P2 will replace it utils.con_log('Making "{}" replaceable...'.format(screen)) unset_readonly(screen)
def pack_content(path): """Pack any custom content into the map.""" files = set() try: pack_list = open(path[:-4] + '.filelist.txt') except (IOError, FileNotFoundError): pass else: with pack_list: for line in pack_list: files.add(line.strip().lower()) if '' in files: # Allow blank lines in original files files.remove('') if not files: utils.con_log('No files to pack!') return utils.con_log('Files to pack:') for file in sorted(files): utils.con_log(' # "' + file + '"') utils.con_log("Packing Files!") bsp_file = BSP(path) utils.con_log(' - Header read') bsp_file.read_header() # Manipulate the zip entirely in memory zip_data = BytesIO() zip_data.write(bsp_file.get_lump(BSP_LUMPS.PAKFILE)) zipfile = ZipFile(zip_data, mode='a') utils.con_log(' - Existing zip read') for file in files: pack_file(zipfile, file) utils.con_log(' - Added files') zipfile.close() # Finalise the zip modification # Copy the zipfile into the BSP file, and adjust the headers bsp_file.replace_lump( path, BSP_LUMPS.PAKFILE, zip_data.getvalue(), # Get the binary data we need ) utils.con_log(' - BSP written!') utils.con_log("Packing complete!")
def main(argv): utils.con_log('BEE2 VRAD hook started!') args = " ".join(argv) fast_args = argv[1:] full_args = argv[1:] path = argv[-1] # The path is the last argument to vrad fast_args[-1] = os.path.normpath(path) utils.con_log("Map path is " + path) if path == "": raise Exception("No map passed!") 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 VBSP 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 '-force_peti' in args or '-force_hammer' in args: # we have override command! if '-force_peti' in args: utils.con_log('OVERRIDE: Applying cheap lighting!') is_peti = True else: utils.con_log('OVERRIDE: Preserving args!') is_peti = False else: # If we don't get the special -force args, check for the name # equalling preview to determine if we should convert # If that is false, check the config file to see what was # specified there. is_peti = (os.path.basename(path) == "preview.bsp" or utils.conv_bool(CONF['force_full'], False)) mod_screenshots() if is_peti: utils.con_log("Forcing Cheap Lighting!") run_vrad(fast_args) else: utils.con_log("Hammer map detected! Not forcing cheap lighting..") run_vrad(full_args) if '-no_pack' not in args: pack_content(path) else: utils.con_log("No items to pack!") utils.con_log("BEE2 VRAD hook finished!")
def add_quote(quote, targetname, quote_loc): """Add a quote to the map.""" utils.con_log('Adding quote: ', quote) for prop in quote: name = prop.name.casefold() if name == 'file': VMF.create_ent( classname='func_instance', targetname='', file=INST_PREFIX + prop.value, origin=quote_loc, fixup_style='2', # No fixup ) elif name == 'choreo': c_line = prop.value # Add this to the beginning, since all scenes need it... if not c_line.startswith('scenes/'): c_line = 'scenes/' + c_line VMF.create_ent( classname='logic_choreographed_scene', targetname=targetname, origin=quote_loc, scenefile=c_line, busyactor="1", # Wait for actor to stop talking onplayerdeath='0', ) elif name == 'snd': VMF.create_ent( classname='ambient_generic', spawnflags='49', # Infinite Range, Starts Silent targetname=targetname, origin=quote_loc, message=prop.value, health='10', # Volume ) elif name == 'ambientchoreo': # For some lines, they don't play globally. Workaround this # by placing an ambient_generic and choreo ent, and play the # sound when the choreo starts. VMF.create_ent( classname='ambient_generic', spawnflags='49', # Infinite Range, Starts Silent targetname=targetname + '_snd', origin=quote_loc, message=prop['File'], health='10', # Volume ) c_line = prop['choreo'] # Add this to the beginning, since all scenes need it... if not c_line.startswith('scenes/'): c_line = 'scenes/' + c_line choreo = VMF.create_ent( classname='logic_choreographed_scene', targetname=targetname, origin=quote_loc, scenefile=c_line, busyactor="1", # Wait for actor to stop talking onplayerdeath='0', ) choreo.outputs.append( vmfLib.Output('OnStart', targetname + '_snd', 'PlaySound')) elif name == 'bullseye': # Cave's voice lines require a special named bullseye to # work correctly. # Don't add the same one more than once. if prop.value not in ADDED_BULLSEYES: VMF.create_ent( classname='npc_bullseye', # Not solid, Take No Damage, Think outside PVS spawnflags='222224', targetname=prop.value, origin=quote_loc, angles='0 0 0', ) ADDED_BULLSEYES.add(prop.value) elif name == 'setstylevar': # Set this stylevar to True # This is useful so some styles can react to which line was # chosen. style_vars[prop.value.casefold()] = True