def add_floor_sides(locs): """We need to replace nodraw textures around the outside of the holes. This requires looping through all faces, since these will have been nodrawed. """ added_locations = { barrier.wall.as_tuple(): False for barrier in locs } for face in conditions.VMF.iter_wfaces(world=True, detail=False): if face.mat != 'tools/toolsnodraw': continue loc = face.get_origin().as_tuple() if loc in added_locations: random.seed('floor_side_{}_{}_{}'.format(*loc)) face.mat = random.choice(MATS['squarebeams']) added_locations[loc] = True # Swap these to flip the texture diagonally, so the beam is at top face.uaxis, face.vaxis = face.vaxis, face.uaxis face.uaxis.offset = 48 vbsp.IGNORED_FACES.add(face) # Look for the ones without a texture - these are open to the void and # need to be sealed. The template chamfers the edges # to prevent showing void at outside corners. for wall_loc, ceil_loc, rot in locs: if added_locations[wall_loc.as_tuple()]: continue diag_loc = (wall_loc.x, wall_loc.y, wall_loc.z + 128) temp_data = conditions.import_template( # If there's a wall surface directly above this point # or a ceiling brush in the next block over # we want to use a world brush to seal the leak. # Otherwise we use the detail version for inside the map. temp_name=( FLOOR_TEMP_SIDE_DETAIL if ceil_loc not in conditions.SOLIDS and diag_loc not in conditions.SOLIDS else FLOOR_TEMP_SIDE_WORLD ), origin=wall_loc, angles=Vec(0, rot, 0), ) conditions.retexture_template( temp_data, wall_loc, # Switch to use the configured squarebeams texture replace_tex={ 'anim_wp/framework/squarebeams': random.choice( MATS['squarebeams'] ), } )
def add_floor_sides(locs): """We need to replace nodraw textures around the outside of the holes. This requires looping through all faces, since these will have been nodrawed. """ added_locations = {barrier.wall.as_tuple(): False for barrier in locs} for face in conditions.VMF.iter_wfaces(world=True, detail=False): if face.mat != 'tools/toolsnodraw': continue loc = face.get_origin().as_tuple() if loc in added_locations: random.seed('floor_side_{}_{}_{}'.format(*loc)) face.mat = random.choice(MATS['squarebeams']) added_locations[loc] = True # Swap these to flip the texture diagonally, so the beam is at top face.uaxis, face.vaxis = face.vaxis, face.uaxis face.uaxis.offset = 48 vbsp.IGNORED_FACES.add(face) # Look for the ones without a texture - these are open to the void and # need to be sealed. The template chamfers the edges # to prevent showing void at outside corners. for wall_loc, ceil_loc, rot in locs: if added_locations[wall_loc.as_tuple()]: continue diag_loc = (wall_loc.x, wall_loc.y, wall_loc.z + 128) temp_data = conditions.import_template( # If there's a wall surface directly above this point # or a ceiling brush in the next block over # we want to use a world brush to seal the leak. # Otherwise we use the detail version for inside the map. temp_name=(FLOOR_TEMP_SIDE_DETAIL if ceil_loc not in conditions.SOLIDS and diag_loc not in conditions.SOLIDS else FLOOR_TEMP_SIDE_WORLD), origin=wall_loc, angles=Vec(0, rot, 0), ) conditions.retexture_template( temp_data, wall_loc, # Switch to use the configured squarebeams texture replace_tex={ consts.Special.SQUAREBEAMS: random.choice(MATS['squarebeams']), })
def res_import_template(inst, res): """Import a template VMF file, retexturing it to match orientatation. It will be placed overlapping the given instance. Options: - ID: The ID of the template to be inserted. - force: a space-seperated list of overrides. If 'white' or 'black' is present, the colour of tiles will be overriden. If a tile size ('2x2', '4x4', 'wall', 'special') is included, all tiles will be switched to that size (if not a floor/ceiling). If 'world' or 'detail' is present, the brush will be forced to that type. - replace: A block of template material -> replacement textures. This is case insensitive - any texture here will not be altered otherwise. - replaceBrush: The position of a brush to replace (0 0 0=the surface). This brush will be removed, and overlays will be fixed to use all faces with the same normal. """ ( temp_id, replace_tex, force_colour, force_grid, force_type, replace_brush_pos, ) = res.value if temp_id not in TEMPLATES: # The template map is read in after setup is performed, so # it must be checked here! # We don't want an error, just quit LOGGER.warning('"{}" not a valid template!', temp_id) return origin = Vec.from_str(inst['origin']) angles = Vec.from_str(inst['angles', '0 0 0']) temp_data = conditions.import_template( temp_id, origin, angles, targetname=inst['targetname', ''], force_type=force_type, ) conditions.retexture_template( temp_data, origin, replace_tex, force_colour, force_grid, ) # This is the original brush the template is replacing. We fix overlay # face IDs, so this brush is replaced by the faces in the template pointing # the same way. if replace_brush_pos is None: return pos = Vec(replace_brush_pos).rotate(angles.x, angles.y, angles.z) pos += origin try: brush_group = SOLIDS[pos.as_tuple()] except KeyError: return vbsp.VMF.remove_brush(brush_group.solid) new_ids = [] all_brushes = temp_data.world if temp_data.detail is not None: for ent in temp_data.detail: all_brushes.extend(ent.solids) for brush in all_brushes: # type: VLib.Solid for face in brush.sides: # Only faces pointing the same way! if face.normal() == brush_group.normal: # Skip tool brushes (nodraw, player clips..) if face.mat.casefold().startswith('tools/'): continue new_ids.append(str(face.id)) if new_ids: conditions.reallocate_overlays({ str(brush_group.face.id): new_ids, })
def res_import_template(inst: Entity, res: Property): """Import a template VMF file, retexturing it to match orientatation. It will be placed overlapping the given instance. Options: - ID: The ID of the template to be inserted. Add visgroups to additionally add after a colon, comma-seperated (temp_id:vis1,vis2) - force: a space-seperated list of overrides. If 'white' or 'black' is present, the colour of tiles will be overriden. If 'invert' is added, white/black tiles will be swapped. If a tile size ('2x2', '4x4', 'wall', 'special') is included, all tiles will be switched to that size (if not a floor/ceiling). If 'world' or 'detail' is present, the brush will be forced to that type. - replace: A block of template material -> replacement textures. This is case insensitive - any texture here will not be altered otherwise. - replaceBrush: The position of a brush to replace (0 0 0=the surface). This brush will be removed, and overlays will be fixed to use all faces with the same normal. Can alternately be a block: - Pos: The position to replace. - additionalIDs: Space-separated list of face IDs in the template to also fix for overlays. The surface should have close to a vertical normal, to prevent rescaling the overlay. - removeBrush: If true, the original brush will not be removed. - transferOverlay: Allow disabling transferring overlays to this template. The IDs will be removed instead. (This can be an instvar). - keys/localkeys: If set, a brush entity will instead be generated with these values. This overrides force world/detail. Specially-handled keys: - "origin", offset automatically. - "movedir" on func_movelinear - set a normal surrounded by <>, this gets replaced with angles. - invertVar or colorVar: If this fixup value is true, tile colour will be swapped to the opposite of the current force option. If it is set to 'white' or 'black', that colour will be forced instead. - visgroup: Sets how vigsrouped parts are handled. If 'none' (default), they are ignored. If 'choose', one is chosen. If a number, that is the percentage chance for each visgroup to be added. """ ( orig_temp_id, replace_tex, force_colour, force_grid, force_type, replace_brush_pos, rem_replace_brush, transfer_overlays, additional_replace_ids, invert_var, visgroup_func, key_block, ) = res.value temp_id = conditions.resolve_value(inst, orig_temp_id) temp_name, vis = conditions.parse_temp_name(temp_id) if temp_name not in TEMPLATES: # The template map is read in after setup is performed, so # it must be checked here! # We don't want an error, just quit if temp_id != orig_temp_id: LOGGER.warning('{} -> "{}" is not a valid template!', orig_temp_id, temp_name) else: LOGGER.warning('"{}" is not a valid template!', temp_name) return if invert_var != '': invert_val = inst.fixup[invert_var].casefold() if invert_val == 'white': force_colour = conditions.MAT_TYPES.white elif invert_val == 'black': force_colour = conditions.MAT_TYPES.black elif srctools.conv_bool(invert_val): force_colour = conditions.TEMP_COLOUR_INVERT[force_colour] origin = Vec.from_str(inst['origin']) angles = Vec.from_str(inst['angles', '0 0 0']) temp_data = conditions.import_template( temp_id, origin, angles, targetname=inst['targetname', ''], force_type=force_type, visgroup_choose=visgroup_func, ) if key_block is not None: conditions.set_ent_keys(temp_data.detail, inst, key_block) br_origin = Vec.from_str(key_block.find_key('keys')['origin']) br_origin.localise(origin, angles) temp_data.detail['origin'] = br_origin move_dir = temp_data.detail['movedir', ''] if move_dir.startswith('<') and move_dir.endswith('>'): move_dir = Vec.from_str(move_dir).rotate(*angles) temp_data.detail['movedir'] = move_dir.to_angle() # Add it to the list of ignored brushes, so vbsp.change_brush() doesn't # modify it. vbsp.IGNORED_BRUSH_ENTS.add(temp_data.detail) try: # This is the original brush the template is replacing. We fix overlay # face IDs, so this brush is replaced by the faces in the template # pointing # the same way. if replace_brush_pos is None: raise KeyError # Not set, raise to jump out of the try block pos = Vec(replace_brush_pos).rotate(angles.x, angles.y, angles.z) pos += origin brush_group = SOLIDS[pos.as_tuple()] except KeyError: # Not set or solid group doesn't exist, skip.. pass else: conditions.steal_from_brush( temp_data, brush_group, rem_replace_brush, additional_replace_ids, conv_bool(conditions.resolve_value(inst, transfer_overlays), True), ) conditions.retexture_template( temp_data, origin, inst.fixup, replace_tex, force_colour, force_grid, )
def convert_floor( loc: Vec, overlay_ids, mats, settings, signage_loc, detail, noise_weight, noise_func: SimplexNoise, ): """Cut out tiles at the specified location.""" try: brush = conditions.SOLIDS[loc.as_tuple()] except KeyError: return False # No tile here! if brush.normal == (0, 0, 1): # This is a pillar block - there isn't actually tiles here! # We need to generate a squarebeams brush to fill this gap. brush.face.mat = 'tools/toolsnodraw' # It won't be visible temp_data = conditions.import_template( temp_name=FLOOR_TEMP_PILLAR, origin=loc, ) conditions.retexture_template( temp_data, loc, # Switch to use the configured squarebeams texture replace_tex={ consts.Special.SQUAREBEAMS: random.choice(MATS['squarebeams']), }) return False # The new brush IDs overlays need to use # NOTE: strings, not ints! ant_locs = overlay_ids[str(brush.face.id)] = [] # Move the floor brush down and switch to the floorbase texture. for plane in brush.face.planes: plane.z -= FLOOR_DEPTH brush.face.mat = random.choice(mats['floorbase']) loc.x -= 64 loc.y -= 64 for x, y in utils.iter_grid(max_x=4, max_y=4): tile_loc = loc + (x * 32 + 16, y * 32 + 16, 0) if tile_loc.as_tuple() in signage_loc: # Force the tile to be present under signage.. should_make_tile = True rand = 100 # We don't need to check this again in future! signage_loc.remove(tile_loc.as_tuple()) else: # Create a number between 0-100 rand = 100 * get_noise(tile_loc // 32, noise_func) + 10 # Adjust based on the noise_weight value, so boundries have more tiles rand *= 0.1 + 0.9 * (1 - noise_weight) should_make_tile = rand < settings['floor_chance'] if random.randint(0, 7) == 0: # Sometimes there'll be random holes/extra tiles should_make_tile = not should_make_tile if should_make_tile: # Full tile tile = make_tile( p1=tile_loc - (16, 16, 0), p2=tile_loc + (16, 16, -2), top_mat=vbsp.get_tex(str(brush.color) + '.floor'), bottom_mat='tools/toolsnodraw', beam_mat=random.choice(mats['squarebeams']), ) detail.solids.append(tile.solid) ant_locs.append(str(tile.top.id)) elif rand < settings['floor_glue_chance']: # 'Glue' tile - this chance should be higher, making these appear # bordering the full tiles. tile = make_tile( p1=tile_loc - (16, 16, 1), p2=tile_loc + (16, 16, -2), top_mat=random.choice(mats['tile_glue']), bottom_mat='tools/toolsnodraw', beam_mat=random.choice(mats['squarebeams']), ) detail.solids.append(tile.solid) else: # No tile at this loc! pass return True
def res_import_template(inst, res): """Import a template VMF file, retexturing it to match orientatation. It will be placed overlapping the given instance. Options: - ID: The ID of the template to be inserted. - force: a space-seperated list of overrides. If 'white' or 'black' is present, the colour of tiles will be overriden. If 'invert' is added, white/black tiles will be swapped. If a tile size ('2x2', '4x4', 'wall', 'special') is included, all tiles will be switched to that size (if not a floor/ceiling). If 'world' or 'detail' is present, the brush will be forced to that type. - replace: A block of template material -> replacement textures. This is case insensitive - any texture here will not be altered otherwise. - replaceBrush: The position of a brush to replace (0 0 0=the surface). This brush will be removed, and overlays will be fixed to use all faces with the same normal. - keys/localkeys: If set, a brush entity will instead be generated with these values. This overrides force world/detail. The origin is set automatically. - invertVar: If this fixup value is true, tile colour will be swapped to the opposite of the current force option. If it is set to 'white' or 'black', that colour will be forced instead. """ ( temp_id, replace_tex, force_colour, force_grid, force_type, replace_brush_pos, invert_var, key_block, ) = res.value if temp_id not in TEMPLATES: # The template map is read in after setup is performed, so # it must be checked here! # We don't want an error, just quit LOGGER.warning('"{}" not a valid template!', temp_id) return if invert_var != '': invert_val = inst.fixup[invert_var].casefold() if invert_val == 'white': force_colour = conditions.MAT_TYPES.white elif invert_val == 'black': force_colour = conditions.MAT_TYPES.black elif utils.conv_bool(invert_val): force_colour = conditions.TEMP_COLOUR_INVERT[force_colour] origin = Vec.from_str(inst['origin']) angles = Vec.from_str(inst['angles', '0 0 0']) temp_data = conditions.import_template( temp_id, origin, angles, targetname=inst['targetname', ''], force_type=force_type, ) conditions.retexture_template( temp_data, origin, replace_tex, force_colour, force_grid, ) if key_block is not None: conditions.set_ent_keys(temp_data.detail, inst, key_block) br_origin = Vec.from_str(key_block.find_key('keys')['origin']) br_origin.localise(origin, angles) temp_data.detail['origin'] = br_origin # Add it to the list of ignored brushes, so vbsp.change_brush() doesn't # modify it. vbsp.IGNORED_BRUSH_ENTS.add(temp_data.detail) # This is the original brush the template is replacing. We fix overlay # face IDs, so this brush is replaced by the faces in the template pointing # the same way. if replace_brush_pos is None: return pos = Vec(replace_brush_pos).rotate(angles.x, angles.y, angles.z) pos += origin try: brush_group = SOLIDS[pos.as_tuple()] except KeyError: return vbsp.VMF.remove_brush(brush_group.solid) new_ids = [] all_brushes = temp_data.world # Overlays can't be applied to entities (other than func_detail). if temp_data.detail is not None and key_block is None: all_brushes.extend(temp_data.detail.solids) for brush in all_brushes: # type: VLib.Solid for face in brush.sides: # Only faces pointing the same way! if face.normal() == brush_group.normal: # Skip tool brushes (nodraw, player clips..) if face.mat.casefold().startswith('tools/'): continue new_ids.append(str(face.id)) if new_ids: conditions.reallocate_overlays({ str(brush_group.face.id): new_ids, })
def convert_floor( loc, overlay_ids, mats, settings, signage_loc, detail, ): """Cut out tiles at the specified location.""" try: brush = conditions.SOLIDS[loc.as_tuple()] except KeyError: return False # No tile here! if brush.normal == (0, 0, 1): # This is a pillar block - there isn't actually tiles here! # We need to generate a squarebeams brush to fill this gap. brush.face.mat = 'tools/toolsnodraw' # It won't be visible temp_data = conditions.import_template( temp_name=FLOOR_TEMP_PILLAR, origin=loc, ) conditions.retexture_template( temp_data, loc, # Switch to use the configured squarebeams texture replace_tex={ 'anim_wp/framework/squarebeams': random.choice( MATS['squarebeams'] ), } ) return False # The new brush IDs overlays need to use # NOTE: strings, not ints! ant_locs = overlay_ids[str(brush.face.id)] = [] # Move the floor brush down and switch to the floorbase texture. for plane in brush.face.planes: plane.z -= FLOOR_DEPTH brush.face.mat = random.choice(mats['floorbase']) loc.x -= 64 loc.y -= 64 random.seed('cutout_tile' + loc.join(' ')) tile_map = [ (random.randint(0, 100) < settings['floor_chance']) for _ in range(16) ] for x, y in utils.iter_grid(max_x=4, max_y=4): tile_loc = loc + (x*32 + 16, y*32 + 16, 0) if tile_loc.as_tuple() in signage_loc: should_make_tile = True # We don't need to check this again in future! signage_loc.remove(tile_loc.as_tuple()) else: should_make_tile = tile_map[x*4 + y] if should_make_tile: # Full tile tile = make_tile( p1=tile_loc - (16, 16, 0), p2=tile_loc + (16, 16, -2), top_mat=vbsp.get_tex(str(brush.color) + '.floor'), bottom_mat='tools/toolsnodraw', beam_mat=random.choice(mats['squarebeams']), ) detail.solids.append(tile.solid) ant_locs.append(str(tile.top.id)) elif random.randint(0, 100) < settings['floor_glue_chance']: # 'Glue' tile tile = make_tile( p1=tile_loc - (16, 16, 1), p2=tile_loc + (16, 16, -2), top_mat=random.choice(mats['tile_glue']), bottom_mat='tools/toolsnodraw', beam_mat=random.choice(mats['squarebeams']), ) detail.solids.append(tile.solid) else: # No tile at this loc! pass return True
def convert_floor( loc: Vec, overlay_ids, mats, settings, signage_loc, detail, noise_weight, noise_func: SimplexNoise, ): """Cut out tiles at the specified location.""" try: brush = conditions.SOLIDS[loc.as_tuple()] except KeyError: return False # No tile here! if brush.normal == (0, 0, 1): # This is a pillar block - there isn't actually tiles here! # We need to generate a squarebeams brush to fill this gap. brush.face.mat = 'tools/toolsnodraw' # It won't be visible temp_data = conditions.import_template( temp_name=FLOOR_TEMP_PILLAR, origin=loc, ) conditions.retexture_template( temp_data, loc, # Switch to use the configured squarebeams texture replace_tex={ 'anim_wp/framework/squarebeams': random.choice( MATS['squarebeams'] ), } ) return False # The new brush IDs overlays need to use # NOTE: strings, not ints! ant_locs = overlay_ids[str(brush.face.id)] = [] # Move the floor brush down and switch to the floorbase texture. for plane in brush.face.planes: plane.z -= FLOOR_DEPTH brush.face.mat = random.choice(mats['floorbase']) loc.x -= 64 loc.y -= 64 for x, y in utils.iter_grid(max_x=4, max_y=4): tile_loc = loc + (x * 32 + 16, y * 32 + 16, 0) if tile_loc.as_tuple() in signage_loc: # Force the tile to be present under signage.. should_make_tile = True rand = 100 # We don't need to check this again in future! signage_loc.remove(tile_loc.as_tuple()) else: # Create a number between 0-100 rand = 100 * get_noise(tile_loc // 32, noise_func) + 10 # Adjust based on the noise_weight value, so boundries have more tiles rand *= 0.1 + 0.9 * (1 - noise_weight) should_make_tile = rand < settings['floor_chance'] if random.randint(0, 7) == 0: # Sometimes there'll be random holes/extra tiles should_make_tile = not should_make_tile if should_make_tile: # Full tile tile = make_tile( p1=tile_loc - (16, 16, 0), p2=tile_loc + (16, 16, -2), top_mat=vbsp.get_tex(str(brush.color) + '.floor'), bottom_mat='tools/toolsnodraw', beam_mat=random.choice(mats['squarebeams']), ) detail.solids.append(tile.solid) ant_locs.append(str(tile.top.id)) elif rand < settings['floor_glue_chance']: # 'Glue' tile - this chance should be higher, making these appear # bordering the full tiles. tile = make_tile( p1=tile_loc - (16, 16, 1), p2=tile_loc + (16, 16, -2), top_mat=random.choice(mats['tile_glue']), bottom_mat='tools/toolsnodraw', beam_mat=random.choice(mats['squarebeams']), ) detail.solids.append(tile.solid) else: # No tile at this loc! pass return True