def place_sign( vmf: VMF, faces: Iterable[Side], sign: Sign, pos: Vec, normal: Vec, forward: Vec, rotate: bool = True, ) -> None: """Place the sign into the map.""" if rotate and normal.z == 0: # On the wall, point upward. forward = Vec(0, 0, 1) texture = sign.overlay if texture.startswith('<') and texture.endswith('>'): texture = vbsp.get_tex(texture[1:-1]) width, height = SIZES[sign.type] over = make_overlay( vmf, -normal, pos, uax=-width * Vec.cross(normal, forward).norm(), vax=-height * forward, material=texture, surfaces=faces, ) over['startu'] = '1' over['endu'] = '0' vbsp.IGNORED_OVERLAYS.add(over)
def res_add_brush(inst: Entity, res: Property): """Spawn in a brush at the indicated points. - point1 and point2 are locations local to the instance, with `0 0 0` as the floor-position. - type is either `black` or `white`. - detail should be set to True/False. If true the brush will be a func_detail instead of a world brush. The sides will be textured with 1x1, 2x2 or 4x4 wall, ceiling and floor textures as needed. """ import vbsp point1 = Vec.from_str(res['point1']) point2 = Vec.from_str(res['point2']) point1.z -= 64 # Offset to the location of the floor point2.z -= 64 # Rotate to match the instance point1.rotate_by_str(inst['angles']) point2.rotate_by_str(inst['angles']) origin = Vec.from_str(inst['origin']) point1 += origin # Then offset to the location of the instance point2 += origin tex_type = res['type', None] if tex_type not in ('white', 'black'): LOGGER.warning( 'AddBrush: "{}" is not a valid brush ' 'color! (white or black)', tex_type, ) tex_type = 'black' dim = point2 - point1 dim.max(-dim) # Figure out what grid size and scale is needed # Check the dimensions in two axes to figure out the largest # tile size that can fit in it. x_maxsize = min(dim.y, dim.z) y_maxsize = min(dim.x, dim.z) if x_maxsize <= 32: x_grid = '4x4' elif x_maxsize <= 64: x_grid = '2x2' else: x_grid = 'wall' if y_maxsize <= 32: y_grid = '4x4' elif y_maxsize <= 64: y_grid = '2x2' else: y_grid = 'wall' grid_offset = origin // 128 # type: Vec # All brushes in each grid have the same textures for each side. random.seed(grid_offset.join(' ') + '-partial_block') solids = vbsp.VMF.make_prism(point1, point2) ':type solids: VLib.PrismFace' # Ensure the faces aren't re-textured later vbsp.IGNORED_FACES.update(solids.solid.sides) solids.north.mat = vbsp.get_tex(tex_type + '.' + y_grid) solids.south.mat = vbsp.get_tex(tex_type + '.' + y_grid) solids.east.mat = vbsp.get_tex(tex_type + '.' + x_grid) solids.west.mat = vbsp.get_tex(tex_type + '.' + x_grid) solids.top.mat = vbsp.get_tex(tex_type + '.floor') solids.bottom.mat = vbsp.get_tex(tex_type + '.ceiling') if srctools.conv_bool(res['detail', False], False): # Add the brush to a func_detail entity vbsp.VMF.create_ent(classname='func_detail').solids = [solids.solid] else: # Add to the world vbsp.VMF.add_brush(solids.solid)
def res_set_texture(inst: Entity, res: Property): """Set the brush face at a location to a particular texture. pos is the position, relative to the instance (0 0 0 is the floor-surface). dir is the normal of the texture. If gridPos is true, the position will be snapped so it aligns with the 128 brushes (Useful with fizzler/light strip items). tex is the texture used. If tex begins and ends with '<>', certain textures will be used based on style: - '<delete>' will remove the brush entirely (it should be hollow). Caution should be used to ensure no leaks occur. - '<special>' the brush will be given a special texture like angled and flip panels. - '<white>' and '<black>' will use the regular textures for the given color. - '<white-2x2>', '<white-4x4>', '<black-2x2>', '<black-4x4'> will use the given wall-sizes. If on floors or ceilings these always use 4x4. - '<2x2>' or '<4x4>' will force to the given wall-size, keeping color. - '<special-white>' and '<special-black>' will use a special texture of the given color. If tex begins and ends with '[]', it is an option in the 'Textures' list. These are composed of a group and texture, separated by '.'. 'white.wall' are the white wall textures; 'special.goo' is the goo texture. If 'template' is set, the template should be an axis aligned cube. This will be rotated by the instance angles, and then the face with the same orientation will be applied to the face (with the rotation and texture). """ import vbsp pos = Vec.from_str(res['pos', '0 0 0']) pos.z -= 64 # Subtract so origin is the floor-position pos = pos.rotate_by_str(inst['angles', '0 0 0']) # Relative to the instance origin pos += Vec.from_str(inst['origin', '0 0 0']) norm = Vec.from_str(res['dir', '0 0 -1']).rotate_by_str(inst['angles', '0 0 0']) if srctools.conv_bool(res['gridpos', '0']): for axis in 'xyz': # Don't realign things in the normal's axis - # those are already fine. if not norm[axis]: pos[axis] //= 128 pos[axis] *= 128 pos[axis] += 64 brush = SOLIDS.get(pos.as_tuple(), None) ':type brush: solidGroup' if not brush or brush.normal != norm: return face_to_mod = brush.face # type: Side # Don't allow this to get overwritten later. vbsp.IGNORED_FACES.add(face_to_mod) temp = res['template', None] if temp: # Grab the scaling template and apply it to the brush. template_brush.get_scaling_template(temp).rotate( Vec.from_str(inst['angles']), Vec.from_str(inst['origin']), ).apply(face_to_mod) return tex = res['tex'] if tex.startswith('[') and tex.endswith(']'): face_to_mod.mat = vbsp.get_tex(tex[1:-1]) elif tex.startswith('<') and tex.endswith('>'): # Special texture names! tex = tex[1:-1].casefold() if tex == 'delete': vbsp.VMF.remove_brush(brush) return if tex == 'white': face_to_mod.mat = 'tile/white_wall_tile003a' elif tex == 'black': face_to_mod.mat = 'metal/black_wall_metal_002c' if tex == 'black' or tex == 'white': # For these two, run the regular logic to apply textures # correctly. vbsp.alter_mat( face_to_mod, vbsp.face_seed(face_to_mod), vbsp_options.get(bool, 'tile_texture_lock'), ) if tex == 'special': vbsp.set_special_mat(face_to_mod, str(brush.color)) elif tex == 'special-white': vbsp.set_special_mat(face_to_mod, 'white') return elif tex == 'special-black': vbsp.set_special_mat(brush.face, 'black') # Do <4x4>, <white-2x4>, etc color = str(brush.color) if tex.startswith('black') or tex.endswith('white'): # Override the color used for 2x2/4x4 brushes color = tex[:5] if tex.endswith('2x2') or tex.endswith('4x4'): # 4x4 and 2x2 instructions are ignored on floors and ceilings. orient = vbsp.get_face_orient(face_to_mod) if orient == vbsp.ORIENT.wall: face_to_mod.mat = vbsp.get_tex(color + '.' + tex[-3:]) else: face_to_mod.mat = vbsp.get_tex(color + '.' + str(orient)) else: face_to_mod.mat = tex
def convert_floor( vmf: VMF, loc: Vec, overlay_ids, mats, settings, signage_loc, detail, noise_weight, noise_func: SimplexNoise, ): """Cut out tiles at the specified location.""" # We pop it, so the face isn't detected by other logic - otherwise it'll # be retextured and whatnot, which we don't want. try: brush = conditions.SOLIDS.pop(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 = template_brush.import_template( vmf, temp_name=FLOOR_TEMP_PILLAR, origin=loc, ) template_brush.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( vmf, 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( vmf, 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_add_brush(inst, res): """Spawn in a brush at the indicated points. - point1 and point2 are locations local to the instance, with '0 0 0' as the floor-position. - type is either 'black' or 'white'. - detail should be set to True/False. If true the brush will be a func_detail instead of a world brush. The sides will be textured with 1x1, 2x2 or 4x4 wall, ceiling and floor textures as needed. """ import vbsp point1 = Vec.from_str(res['point1']) point2 = Vec.from_str(res['point2']) point1.z -= 64 # Offset to the location of the floor point2.z -= 64 # Rotate to match the instance point1.rotate_by_str(inst['angles']) point2.rotate_by_str(inst['angles']) origin = Vec.from_str(inst['origin']) point1 += origin # Then offset to the location of the instance point2 += origin tex_type = res['type', None] if tex_type not in ('white', 'black'): LOGGER.warning( 'AddBrush: "{}" is not a valid brush ' 'color! (white or black)', tex_type, ) tex_type = 'black' dim = point2 - point1 dim.max(-dim) # Figure out what grid size and scale is needed # Check the dimensions in two axes to figure out the largest # tile size that can fit in it. x_maxsize = min(dim.y, dim.z) y_maxsize = min(dim.x, dim.z) if x_maxsize <= 32: x_grid = '4x4' elif x_maxsize <= 64: x_grid = '2x2' else: x_grid = 'wall' if y_maxsize <= 32: y_grid = '4x4' elif y_maxsize <= 64: y_grid = '2x2' else: y_grid = 'wall' grid_offset = origin // 128 # type: Vec # All brushes in each grid have the same textures for each side. random.seed(grid_offset.join(' ') + '-partial_block') solids = vbsp.VMF.make_prism(point1, point2) ':type solids: VLib.PrismFace' # Ensure the faces aren't re-textured later vbsp.IGNORED_FACES.update(solids.solid.sides) solids.north.mat = vbsp.get_tex(tex_type + '.' + y_grid) solids.south.mat = vbsp.get_tex(tex_type + '.' + y_grid) solids.east.mat = vbsp.get_tex(tex_type + '.' + x_grid) solids.west.mat = vbsp.get_tex(tex_type + '.' + x_grid) solids.top.mat = vbsp.get_tex(tex_type + '.floor') solids.bottom.mat = vbsp.get_tex(tex_type + '.ceiling') if utils.conv_bool(res['detail', False], False): # Add the brush to a func_detail entity vbsp.VMF.create_ent( classname='func_detail' ).solids = [ solids.solid ] else: # Add to the world vbsp.VMF.add_brush(solids.solid)
def res_set_texture(inst, res): """Set the brush face at a location to a particular texture. pos is the position, relative to the instance (0 0 0 is the floor-surface). dir is the normal of the texture. If gridPos is true, the position will be snapped so it aligns with the 128 brushes (Useful with fizzler/light strip items). tex is the texture used. If tex begins and ends with '<>', certain textures will be used based on style: - If tex is '<special>', the brush will be given a special texture like angled and clear panels. - '<white>' and '<black>' will use the regular textures for the given color. - '<white-2x2>', '<white-4x4>', '<black-2x2>', '<black-4x4'> will use the given wall-sizes. If on floors or ceilings these always use 4x4. - '<2x2>' or '<4x4>' will force to the given wall-size, keeping color. - '<special-white>' and '<special-black>' will use a special texture of the given color. If tex begins and ends with '[]', it is an option in the 'Textures' list. These are composed of a group and texture, separated by '.'. 'white.wall' are the white wall textures; 'special.goo' is the goo texture. """ import vbsp pos = Vec.from_str(res['pos', '0 0 0']) pos.z -= 64 # Subtract so origin is the floor-position pos = pos.rotate_by_str(inst['angles', '0 0 0']) # Relative to the instance origin pos += Vec.from_str(inst['origin', '0 0 0']) norm = Vec.from_str(res['dir', '0 0 -1']).rotate_by_str( inst['angles', '0 0 0'] ) if utils.conv_bool(res['gridpos', '0']): for axis in 'xyz': # Don't realign things in the normal's axis - # those are already fine. if not norm[axis]: pos[axis] //= 128 pos[axis] *= 128 pos[axis] += 64 brush = SOLIDS.get(pos.as_tuple(), None) ':type brush: solidGroup' if not brush or brush.normal != norm: return tex = res['tex'] if tex.startswith('[') and tex.endswith(']'): brush.face.mat = vbsp.get_tex(tex[1:-1]) brush.face.mat = tex elif tex.startswith('<') and tex.endswith('>'): # Special texture names! tex = tex[1:-1].casefold() if tex == 'white': brush.face.mat = 'tile/white_wall_tile003a' elif tex == 'black': brush.face.mat = 'metal/black_wall_metal_002c' if tex == 'black' or tex == 'white': # For these two, run the regular logic to apply textures # correctly. vbsp.alter_mat( brush.face, vbsp.face_seed(brush.face), vbsp.get_bool_opt('tile_texture_lock', True), ) if tex == 'special': vbsp.set_special_mat(brush.face, str(brush.color)) elif tex == 'special-white': vbsp.set_special_mat(brush.face, 'white') return elif tex == 'special-black': vbsp.set_special_mat(brush.face, 'black') # Do <4x4>, <white-2x4>, etc color = str(brush.color) if tex.startswith('black') or tex.endswith('white'): # Override the color used for 2x2/4x4 brushes color = tex[:5] if tex.endswith('2x2') or tex.endswith('4x4'): # 4x4 and 2x2 instructions are ignored on floors and ceilings. orient = vbsp.get_face_orient(brush.face) if orient == vbsp.ORIENT.wall: brush.face.mat = vbsp.get_tex( color + '.' + tex[-3:] ) else: brush.face.mat = vbsp.get_tex( color + '.' + str(orient) ) else: brush.face.mat = tex # Don't allow this to get overwritten later. vbsp.IGNORED_FACES.add(brush.face)
def res_insert_overlay(inst: Entity, res: Property): """Use a template to insert one or more overlays on a surface. Options: - ID: The template ID. Brushes will be ignored. - Replace: old -> new material replacements. - Face_pos: The offset of the brush face. - Normal: The direction of the brush face. - Offset: An offset to move the overlays by. """ ( temp_id, replace, face, norm, offset, ) = res.value if temp_id[:1] == '$': temp_id = inst.fixup[temp_id] origin = Vec.from_str(inst['origin']) # type: Vec angles = Vec.from_str(inst['angles', '0 0 0']) face_pos = Vec(face).rotate(*angles) face_pos += origin normal = Vec(norm).rotate(*angles) # Don't make offset change the face_pos value.. origin += offset.copy().rotate_by_str(inst['angles', '0 0 0']) for axis, norm in enumerate(normal): # Align to the center of the block grid. The normal direction is # already correct. if norm == 0: face_pos[axis] = face_pos[axis] // 128 * 128 + 64 try: face_id = SOLIDS[face_pos.as_tuple()].face.id except KeyError: LOGGER.warning( 'Overlay brush position is not valid: {}', face_pos, ) return temp = template_brush.import_template( temp_id, origin, angles, targetname=inst['targetname', ''], force_type=TEMP_TYPES.detail, ) for over in temp.overlay: # type: Entity random.seed('TEMP_OVERLAY_' + over['basisorigin']) mat = random.choice( replace.get( over['material'], (over['material'], ), )) if mat[:1] == '$': mat = inst.fixup[mat] if mat.startswith('<') or mat.endswith('>'): # Lookup in the style data. import vbsp LOGGER.info('Tex: {}', vbsp.settings['textures'].keys()) mat = vbsp.get_tex(mat[1:-1]) over['material'] = mat over['sides'] = str(face_id) # Wipe the brushes from the map. if temp.detail is not None: temp.detail.remove() LOGGER.info( 'Overlay template "{}" could set keep_brushes=0.', temp_id, )
def retexture_template( template_data: ExportedTemplate, origin: Vec, fixup: srctools.vmf.EntityFixup=None, replace_tex: dict= srctools.EmptyMapping, force_colour: MAT_TYPES=None, force_grid: str=None, use_bullseye=False, no_clumping=False, ): """Retexture a template at the given location. - Only textures in the TEMPLATE_RETEXTURE dict will be replaced. - Others will be ignored (nodraw, plasticwall, etc) - Wall textures pointing up and down will switch to floor/ceiling textures. - Textures of the same type, normal and inst origin will randomise to the same type. - replace_tex is a replacement table. This overrides everything else. The values should either be a list (random), or a single value. - If force_colour is set, all tile textures will be switched accordingly. If set to 'INVERT', white and black textures will be swapped. - If force_grid is set, all tile textures will be that size: ('wall', '2x2', '4x4', 'special') - If use_bullseye is true, the bullseye textures will be used for all panel sides instead of the normal textures. (This overrides force_grid.) - Fixup is the inst.fixup value, used to allow $replace in replace_tex. - Set no_clump if the brush is used on a special entity, and therefore won't get retextured by the main code. That means we need to directly retexture here. """ import vbsp template = template_data.template # type: Template rev_id_mapping = { new_id: str(old_id) for old_id, new_id in template_data.orig_ids.items() } all_brushes = list(template_data.world) # type: List[Solid] if template_data.detail is not None: all_brushes.extend(template_data.detail.solids) # Template faces are randomised per block and side. This means # multiple templates in the same block get the same texture, so they # can clip into each other without looking bad. rand_prefix = 'TEMPLATE_{}_{}_{}:'.format(*(origin // 128)) # Even if not axis-aligned, make mostly-flat surfaces # floor/ceiling (+-40 degrees) # sin(40) = ~0.707 floor_tolerance = 0.8 can_clump = vbsp.can_clump() # Ensure all values are lists. replace_tex = { key.casefold(): ([value] if isinstance(value, str) else value) for key, value in replace_tex.items() } # For each face, if it needs to be forced to a colour, or None if not. force_colour_face = defaultdict(lambda: None) # Already sorted by priority. for color_picker in template.color_pickers: picker_pos = color_picker.offset.copy().rotate(*template_data.angles) picker_pos += template_data.origin picker_norm = color_picker.normal.copy().rotate(*template_data.angles) if color_picker.grid_snap: for axis in 'xyz': # Don't realign things in the normal's axis - # those are already fine. if not picker_norm[axis]: picker_pos[axis] = picker_pos[axis] // 128 * 128 + 64 brush = conditions.SOLIDS.get(picker_pos.as_tuple(), None) if brush is None or abs(brush.normal) != abs(picker_norm): # Doesn't exist. continue if color_picker.remove_brush and brush.solid in vbsp.VMF.brushes: brush.solid.remove() for side in color_picker.sides: # Only do the highest priority successful one. if force_colour_face[side] is None: force_colour_face[side] = brush.color for brush in all_brushes: for face in brush: orig_id = rev_id_mapping[face.id] if orig_id in template.skip_faces: continue folded_mat = face.mat.casefold() norm = face.normal() random.seed(rand_prefix + norm.join('_')) if orig_id in template.realign_faces: try: uaxis, vaxis = REALIGN_UVS[norm.as_tuple()] except KeyError: LOGGER.warning( 'Realign face in template "{}" ({} in final) is ' 'not on grid!', template.id, face.id, ) else: face.uaxis = uaxis.copy() face.vaxis = vaxis.copy() try: override_mat = replace_tex['#' + orig_id] except KeyError: try: override_mat = replace_tex[folded_mat] except KeyError: override_mat = None if override_mat is not None: # Replace_tex overrides everything. mat = random.choice(override_mat) if mat[:1] == '$' and fixup is not None: mat = fixup[mat] if mat.startswith('<') or mat.endswith('>'): # Lookup in the style data. mat = vbsp.get_tex(mat[1:-1]) face.mat = mat continue try: tex_type = TEMPLATE_RETEXTURE[folded_mat] except KeyError: continue # It's nodraw, or something we shouldn't change if isinstance(tex_type, str): # It's something like squarebeams or backpanels, just look # it up face.mat = vbsp.get_tex(tex_type) if tex_type == 'special.goo_cheap': if norm != (0, 0, 1): # Goo must be facing upright! # Retexture to nodraw, so a template can be made with # all faces goo to work in multiple orientations. face.mat = 'tools/toolsnodraw' else: # Goo always has the same orientation! face.uaxis = UVAxis( 1, 0, 0, offset=0, scale=vbsp_options.get(float, 'goo_scale'), ) face.vaxis = UVAxis( 0, -1, 0, offset=0, scale=vbsp_options.get(float, 'goo_scale'), ) continue # It's a regular wall type! tex_colour, grid_size = tex_type if force_colour_face[orig_id] is not None: tex_colour = force_colour_face[orig_id] elif force_colour == 'INVERT': # Invert the texture tex_colour = ( MAT_TYPES.white if tex_colour is MAT_TYPES.black else MAT_TYPES.black ) elif force_colour is not None: tex_colour = force_colour if force_grid is not None: grid_size = force_grid if 1 in norm or -1 in norm: # Facing NSEW or up/down # If axis-aligned, make the orientation aligned to world # That way multiple items merge well, and walls are upright. # We allow offsets < 1 grid tile, so items can be offset. face.uaxis.offset %= TEMP_TILE_PIX_SIZE[grid_size] face.vaxis.offset %= TEMP_TILE_PIX_SIZE[grid_size] if use_bullseye: # We want to use the bullseye textures, instead of normal # ones if norm.z < -floor_tolerance: face.mat = vbsp.get_tex( 'special.bullseye_{}_floor'.format(tex_colour) ) elif norm.z > floor_tolerance: face.mat = vbsp.get_tex( 'special.bullseye_{}_ceiling'.format(tex_colour) ) else: face.mat = '' # Ensure next if statement triggers # If those aren't defined, try the wall texture.. if face.mat == '': face.mat = vbsp.get_tex( 'special.bullseye_{}_wall'.format(tex_colour) ) if face.mat != '': continue # Set to a bullseye texture, # don't use the wall one if grid_size == 'special': # Don't use wall on faces similar to floor/ceiling: if -floor_tolerance < norm.z < floor_tolerance: face.mat = vbsp.get_tex( 'special.{!s}_wall'.format(tex_colour) ) else: face.mat = '' # Ensure next if statement triggers # Various fallbacks if not defined if face.mat == '': face.mat = vbsp.get_tex( 'special.{!s}'.format(tex_colour) ) if face.mat == '': # No special texture - use a wall one. grid_size = 'wall' else: # Set to a special texture, continue # don't use the wall one if norm.z > floor_tolerance: grid_size = 'ceiling' if norm.z < -floor_tolerance: grid_size = 'floor' if can_clump and not no_clumping: # For the clumping algorithm, set to Valve PeTI and let # clumping handle retexturing. vbsp.IGNORED_FACES.remove(face) if tex_colour is MAT_TYPES.white: if grid_size == '4x4': face.mat = 'tile/white_wall_tile003f' elif grid_size == '2x2': face.mat = 'tile/white_wall_tile003c' else: face.mat = 'tile/white_wall_tile003h' elif tex_colour is MAT_TYPES.black: if grid_size == '4x4': face.mat = 'metal/black_wall_metal_002b' elif grid_size == '2x2': face.mat = 'metal/black_wall_metal_002a' else: face.mat = 'metal/black_wall_metal_002e' else: face.mat = vbsp.get_tex( '{!s}.{!s}'.format(tex_colour, grid_size) ) for over in template_data.overlay[:]: random.seed('TEMP_OVERLAY_' + over['basisorigin']) mat = over['material'].casefold() if mat in replace_tex: mat = random.choice(replace_tex[mat]) if mat[:1] == '$': mat = fixup[mat] if mat.startswith('<') or mat.endswith('>'): # Lookup in the style data. mat = vbsp.get_tex(mat[1:-1]) elif mat in vbsp.TEX_VALVE: mat = vbsp.get_tex(vbsp.TEX_VALVE[mat]) else: continue if mat == '': # If blank, remove the overlay from the map and the list. # (Since it's inplace, this can affect the tuple.) template_data.overlay.remove(over) over.remove() else: over['material'] = mat
def convert_floor( vmf: VMF, loc: Vec, overlay_ids, mats, settings, signage_loc, detail, noise_weight, noise_func: SimplexNoise, ): """Cut out tiles at the specified location.""" # We pop it, so the face isn't detected by other logic - otherwise it'll # be retextured and whatnot, which we don't want. try: brush = conditions.SOLIDS.pop(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 = template_brush.import_template( temp_name=FLOOR_TEMP_PILLAR, origin=loc, ) template_brush.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( vmf, 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( vmf, 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_set_texture(inst: Entity, res: Property): """Set the brush face at a location to a particular texture. pos is the position, relative to the instance (0 0 0 is the floor-surface). dir is the normal of the texture. If gridPos is true, the position will be snapped so it aligns with the 128 brushes (Useful with fizzler/light strip items). tex is the texture used. If tex begins and ends with `<>`, certain textures will be used based on style: - `<delete>` will remove the brush entirely (it should be hollow). Caution should be used to ensure no leaks occur. - `<special>` the brush will be given a special texture like angled and flip panels. - `<white>` and `<black>` will use the regular textures for the given color. - `<white-2x2>`, `<white-4x4>`, `<black-2x2>`, `<black-4x4>` will use the given wall-sizes. If on floors or ceilings these always use 4x4. - `<2x2>` or `<4x4>` will force to the given wall-size, keeping color. - `<special-white>` and `<special-black>` will use a special texture of the given color. If tex begins and ends with `[]`, it is an option in the `Textures` list. These are composed of a group and texture, separated by `.`. `white.wall` are the white wall textures; `special.goo` is the goo texture. If `template` is set, the template should be an axis aligned cube. This will be rotated by the instance angles, and then the face with the same orientation will be applied to the face (with the rotation and texture). """ import vbsp pos = Vec.from_str(res['pos', '0 0 0']) pos.z -= 64 # Subtract so origin is the floor-position pos = pos.rotate_by_str(inst['angles', '0 0 0']) # Relative to the instance origin pos += Vec.from_str(inst['origin', '0 0 0']) norm = Vec.from_str(res['dir', '0 0 -1']).rotate_by_str( inst['angles', '0 0 0'] ) if srctools.conv_bool(res['gridpos', '0']): for axis in 'xyz': # Don't realign things in the normal's axis - # those are already fine. if not norm[axis]: pos[axis] //= 128 pos[axis] *= 128 pos[axis] += 64 brush = SOLIDS.get(pos.as_tuple(), None) if not brush or brush.normal != norm: return face_to_mod = brush.face # type: Side # Don't allow this to get overwritten later. vbsp.IGNORED_FACES.add(face_to_mod) temp = res['template', None] if temp: # Grab the scaling template and apply it to the brush. template_brush.get_scaling_template(temp).rotate( Vec.from_str(inst['angles']), Vec.from_str(inst['origin']), ).apply(face_to_mod) return tex = res['tex'] if tex.startswith('[') and tex.endswith(']'): face_to_mod.mat = vbsp.get_tex(tex[1:-1]) elif tex.startswith('<') and tex.endswith('>'): # Special texture names! tex = tex[1:-1].casefold() if tex == 'delete': vbsp.VMF.remove_brush(brush) return if tex == 'white': face_to_mod.mat = 'tile/white_wall_tile003a' elif tex == 'black': face_to_mod.mat = 'metal/black_wall_metal_002c' if tex == 'black' or tex == 'white': # For these two, run the regular logic to apply textures # correctly. vbsp.alter_mat( face_to_mod, vbsp.face_seed(face_to_mod), vbsp_options.get(bool, 'tile_texture_lock'), ) if tex == 'special': vbsp.set_special_mat(face_to_mod, str(brush.color)) elif tex == 'special-white': vbsp.set_special_mat(face_to_mod, 'white') return elif tex == 'special-black': vbsp.set_special_mat(brush.face, 'black') # Do <4x4>, <white-2x4>, etc color = str(brush.color) if tex.startswith('black') or tex.endswith('white'): # Override the color used for 2x2/4x4 brushes color = tex[:5] if tex.endswith('2x2') or tex.endswith('4x4'): # 4x4 and 2x2 instructions are ignored on floors and ceilings. orient = vbsp.get_face_orient(face_to_mod) if orient == vbsp.ORIENT.wall: face_to_mod.mat = vbsp.get_tex( color + '.' + tex[-3:] ) else: face_to_mod.mat = vbsp.get_tex( color + '.' + str(orient) ) else: face_to_mod.mat = tex
def res_insert_overlay(inst: Entity, res: Property): """Use a template to insert one or more overlays on a surface. Options: - ID: The template ID. Brushes will be ignored. - Replace: old -> new material replacements. - Face_pos: The offset of the brush face. - Normal: The direction of the brush face. - Offset: An offset to move the overlays by. """ ( temp_id, replace, face, norm, offset, ) = res.value if temp_id[:1] == '$': temp_id = inst.fixup[temp_id] origin = Vec.from_str(inst['origin']) # type: Vec angles = Vec.from_str(inst['angles', '0 0 0']) face_pos = Vec(face).rotate(*angles) face_pos += origin normal = Vec(norm).rotate(*angles) # Don't make offset change the face_pos value.. origin += offset.copy().rotate_by_str( inst['angles', '0 0 0'] ) for axis, norm in enumerate(normal): # Align to the center of the block grid. The normal direction is # already correct. if norm == 0: face_pos[axis] = face_pos[axis] // 128 * 128 + 64 try: face_id = SOLIDS[face_pos.as_tuple()].face.id except KeyError: LOGGER.warning( 'Overlay brush position is not valid: {}', face_pos, ) return temp = template_brush.import_template( temp_id, origin, angles, targetname=inst['targetname', ''], force_type=TEMP_TYPES.detail, ) for over in temp.overlay: # type: Entity random.seed('TEMP_OVERLAY_' + over['basisorigin']) mat = random.choice(replace.get( over['material'], (over['material'], ), )) if mat[:1] == '$': mat = inst.fixup[mat] if mat.startswith('<') or mat.endswith('>'): # Lookup in the style data. import vbsp LOGGER.info('Tex: {}', vbsp.settings['textures'].keys()) mat = vbsp.get_tex(mat[1:-1]) over['material'] = mat over['sides'] = str(face_id) # Wipe the brushes from the map. if temp.detail is not None: temp.detail.remove() LOGGER.info( 'Overlay template "{}" could set keep_brushes=0.', temp_id, )
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