def fix_base_brush(vmf: VMF, solid: Solid, face: Side): """Retexture the brush forming the bottom of a pit.""" if SETTINGS['skybox'] != '': face.mat = 'tools/toolsskybox' else: # We have a pit shell, we don't want a bottom. vmf.remove_brush(solid)
def fix_base_brush(vmf: VMF, solid: Solid, face: Side): """Retexture the brush forming the bottom of a pit.""" if SETTINGS['skybox'] != '': face.mat = 'tools/toolsskybox' vbsp.IGNORED_FACES.add(face) else: # We have a pit shell, we don't want a bottom. vmf.remove_brush(solid)
def flag_brush_at_loc(vmf: VMF, inst: Entity, flag: Property): """Checks to see if a wall is present at the given location. - `Pos` is the position of the brush, where `0 0 0` is the floor-position of the brush. - `Dir` is the normal the face is pointing. `(0 0 -1)` is up. - `Type` defines the type the brush must be: - `Any` requires either a black or white brush. - `None` means that no brush must be present. - `White` requires a portalable surface. - `Black` requires a non-portalable surface. - `SetVar` defines an instvar which will be given a value of `black`, `white` or `none` to allow the result to be reused. - If `gridPos` is true, the position will be snapped so it aligns with the 128 grid (Useful with fizzler/light strip items). - `RemoveBrush`: If set to `1`, the brush will be removed if found. Only do this to `EmbedFace` brushes, since it will remove the other sides as well. """ pos = Vec.from_str(flag['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 = flag['dir', None] if norm is not None: norm = Vec.from_str(norm).rotate_by_str(inst['angles', '0 0 0'], ) if srctools.conv_bool(flag['gridpos', '0']) and norm is not None: for axis in 'xyz': # Don't realign things in the normal's axis - # those are already fine. if norm[axis] == 0: pos[axis] = pos[axis] // 128 * 128 + 64 result_var = flag['setVar', ''] should_remove = srctools.conv_bool(flag['RemoveBrush', False], False) des_type = flag['type', 'any'].casefold() brush = SOLIDS.get(pos.as_tuple(), None) if brush is None or (norm is not None and abs(brush.normal) != abs(norm)): br_type = 'none' else: br_type = str(brush.color) if should_remove: vmf.remove_brush(brush.solid, ) if result_var: inst.fixup[result_var] = br_type if des_type == 'any' and br_type != 'none': return True return des_type == br_type
def read_from_map(self, vmf: VMF, has_attr: Dict[str, bool]) -> None: """Given the map file, set blocks.""" search_locs = [] for ent in vmf.entities: pos = ent['origin', None] if pos is None: continue pos = world_to_grid(Vec.from_str(pos)) # Exclude entities outside the main area - elevators mainly. # The border should never be set to air! if (0, 0, 0) <= pos <= (25, 25, 25): search_locs.append(pos) can_have_pit = bottomlessPit.pits_allowed() for brush in vmf.brushes[:]: tex = {face.mat.casefold() for face in brush.sides} bbox_min, bbox_max = brush.get_bbox() if ('nature/toxicslime_a2_bridge_intro' in tex or 'nature/toxicslime_puzzlemaker_cheap' in tex): # It's goo! x = bbox_min.x + 64 y = bbox_min.y + 64 g_x = x // 128 g_y = y // 128 is_pit = can_have_pit and bottomlessPit.is_pit(bbox_min, bbox_max) # If goo is multi-level, we want to record all pos! z_pos = range(int(bbox_min.z) + 64, int(bbox_max.z), 128) top_ind = len(z_pos) - 1 for ind, z in enumerate(z_pos): g_z = z // 128 self[g_x, g_y, g_z] = Block.from_pitgoo_attr( is_pit, is_top=(ind == top_ind), is_bottom=(ind == 0), ) # If goo has totally submerged tunnels, they are not filled. # Add each horizontal neighbour to the search list. # If not found they'll be ignored. if ind != top_ind: # Don't bother on the top level.. search_locs.extend([ (g_x - 1, g_y, g_z), (g_x + 1, g_y, g_z), (g_x, g_y + 1, g_z), (g_x, g_y - 1, g_z), ]) # Bottomless pits don't use goo, so remove the water.. if is_pit: vmf.remove_brush(brush) # Indicate that this map contains goo/pits if is_pit: has_attr[VOICE_ATTR_PIT] = True else: has_attr[VOICE_ATTR_GOO] = True continue pos = world_to_grid(brush.get_origin(bbox_min, bbox_max)) if bbox_max - bbox_min == (128, 128, 128): # Full block.. self[pos] = Block.SOLID else: # Must be an embbedvoxel block self[pos] = Block.EMBED LOGGER.info( 'Analysed map, filling air... ({} starting positions..)', len(search_locs) ) self.fill_air(search_locs) LOGGER.info('Air filled!')
def flag_brush_at_loc(vmf: VMF, inst: Entity, flag: Property): """Checks to see if a wall is present at the given location. - `Pos` is the position of the brush, where `0 0 0` is the floor-position of the brush. - `Dir` is the normal the face is pointing. `(0 0 -1)` is up. - `Type` defines the type the brush must be: - `Any` requires either a black or white brush. - `None` means that no brush must be present. - `White` requires a portalable surface. - `Black` requires a non-portalable surface. - `SetVar` defines an instvar which will be given a value of `black`, `white` or `none` to allow the result to be reused. - If `gridPos` is true, the position will be snapped so it aligns with the 128 grid (Useful with fizzler/light strip items). - `RemoveBrush`: If set to `1`, the brush will be removed if found. Only do this to `EmbedFace` brushes, since it will remove the other sides as well. """ pos = Vec.from_str(flag['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 = flag['dir', None] if norm is not None: norm = Vec.from_str(norm).rotate_by_str( inst['angles', '0 0 0'], ) if srctools.conv_bool(flag['gridpos', '0']) and norm is not None: for axis in 'xyz': # Don't realign things in the normal's axis - # those are already fine. if norm[axis] == 0: pos[axis] = pos[axis] // 128 * 128 + 64 result_var = flag['setVar', ''] should_remove = srctools.conv_bool(flag['RemoveBrush', False], False) des_type = flag['type', 'any'].casefold() brush = SOLIDS.get(pos.as_tuple(), None) if brush is None or (norm is not None and abs(brush.normal) != abs(norm)): br_type = 'none' else: br_type = str(brush.color) if should_remove: vmf.remove_brush( brush.solid, ) if result_var: inst.fixup[result_var] = br_type if des_type == 'any' and br_type != 'none': return True return des_type == br_type
def read_from_map(self, vmf: VMF, has_attr: Dict[str, bool]) -> None: """Given the map file, set blocks.""" from conditions import EMBED_OFFSETS from instance_traits import get_item_id # Starting points to fill air and goo. # We want to fill goo first... air_search_locs: List[Tuple[Vec, bool]] = [] goo_search_locs: List[Tuple[Vec, bool]] = [] for ent in vmf.entities: str_pos = ent['origin', None] if str_pos is None: continue pos = world_to_grid(Vec.from_str(str_pos)) # Exclude entities outside the main area - elevators mainly. # The border should never be set to air! if (0, 0, 0) <= pos <= (25, 25, 25): air_search_locs.append((Vec(pos.x, pos.y, pos.z), False)) # We need to manually set EmbeddedVoxel locations. # These might not be detected for items where there's a block # which is entirely empty - corridors and obs rooms for example. item_id = get_item_id(ent) if item_id: try: embed_locs = EMBED_OFFSETS[item_id] except KeyError: continue angles = Vec.from_str(ent['angles']) for local_pos in embed_locs: world_pos = local_pos - (0, 0, 1) world_pos.localise(pos, angles) self[world_pos] = Block.EMBED can_have_pit = bottomlessPit.pits_allowed() for brush in vmf.brushes[:]: tex = {face.mat.casefold() for face in brush.sides} bbox_min, bbox_max = brush.get_bbox() if ( 'nature/toxicslime_a2_bridge_intro' in tex or 'nature/toxicslime_puzzlemaker_cheap' in tex ): # It's goo! x = bbox_min.x + 64 y = bbox_min.y + 64 g_x = x // 128 g_y = y // 128 is_pit = can_have_pit and bottomlessPit.is_pit(bbox_min, bbox_max) # If goo is multi-level, we want to record all pos! z_pos = range(int(bbox_min.z) + 64, int(bbox_max.z), 128) top_ind = len(z_pos) - 1 for ind, z in enumerate(z_pos): g_z = z // 128 self[g_x, g_y, g_z] = Block.from_pitgoo_attr( is_pit, is_top=(ind == top_ind), is_bottom=(ind == 0), ) # If goo has totally submerged tunnels, they are not filled. # Add each horizontal neighbour to the search list. # If not found they'll be ignored. goo_search_locs += [ (Vec(g_x - 1, g_y, g_z), True), (Vec(g_x + 1, g_y, g_z), True), (Vec(g_x, g_y + 1, g_z), True), (Vec(g_x, g_y - 1, g_z), True), ] # Remove the brush, since we're not going to use it. vmf.remove_brush(brush) # Indicate that this map contains goo/pits if is_pit: has_attr[VOICE_ATTR_PIT] = True else: has_attr[VOICE_ATTR_GOO] = True continue pos = world_to_grid(brush.get_origin(bbox_min, bbox_max)) if bbox_max - bbox_min == (128, 128, 128): # Full block.. self[pos] = Block.SOLID else: # Must be an embbedvoxel block self[pos] = Block.EMBED LOGGER.info( 'Analysed map, filling air... ({} starting positions..)', len(air_search_locs) ) self.fill_air(goo_search_locs + air_search_locs) LOGGER.info('Air filled!')
def read_from_map(self, vmf: VMF, has_attr: dict[str, bool], items: dict[str, editoritems.Item]) -> None: """Given the map file, set blocks.""" from precomp.instance_traits import get_item_id from precomp import bottomlessPit # Starting points to fill air and goo. # We want to fill goo first... air_search_locs: list[tuple[Vec, bool]] = [] goo_search_locs: list[tuple[Vec, bool]] = [] for ent in vmf.entities: str_pos = ent['origin', None] if str_pos is None: continue pos = world_to_grid(Vec.from_str(str_pos)) # Exclude entities outside the main area - elevators mainly. # The border should never be set to air! if not ((0, 0, 0) <= pos <= (25, 25, 25)): continue # We need to manually set EmbeddedVoxel locations. # These might not be detected for items where there's a block # which is entirely empty - corridors and obs rooms for example. # We also need to check occupy locations, so that it can seed search # locs. item_id = get_item_id(ent) seeded = False if item_id: try: item = items[item_id.casefold()] except KeyError: pass else: orient = Matrix.from_angle(Angle.from_str(ent['angles'])) for local_pos in item.embed_voxels: # Offset down because 0 0 0 is the floor voxel. world_pos = (Vec(local_pos) - (0, 0, 1)) @ orient + pos self[round(world_pos, 0)] = Block.EMBED for occu in item.occupy_voxels: world_pos = Vec(occu.pos) @ orient + pos air_search_locs.append((round(world_pos, 0), False)) seeded = True if not seeded: # Assume origin is its location. air_search_locs.append((pos.copy(), False)) can_have_pit = bottomlessPit.pits_allowed() for brush in vmf.brushes[:]: tex = {face.mat.casefold() for face in brush.sides} bbox_min, bbox_max = brush.get_bbox() if ('nature/toxicslime_a2_bridge_intro' in tex or 'nature/toxicslime_puzzlemaker_cheap' in tex): # It's goo! x = bbox_min.x + 64 y = bbox_min.y + 64 g_x = x // 128 g_y = y // 128 is_pit = can_have_pit and bottomlessPit.is_pit( bbox_min, bbox_max) # If goo is multi-level, we want to record all pos! z_pos = range(int(bbox_min.z) + 64, int(bbox_max.z), 128) top_ind = len(z_pos) - 1 for ind, z in enumerate(z_pos): g_z = z // 128 self[g_x, g_y, g_z] = Block.from_pitgoo_attr( is_pit, is_top=(ind == top_ind), is_bottom=(ind == 0), ) # If goo has totally submerged tunnels, they are not filled. # Add each horizontal neighbour to the search list. # If not found they'll be ignored. goo_search_locs += [ (Vec(g_x - 1, g_y, g_z), True), (Vec(g_x + 1, g_y, g_z), True), (Vec(g_x, g_y + 1, g_z), True), (Vec(g_x, g_y - 1, g_z), True), ] # Remove the brush, since we're not going to use it. vmf.remove_brush(brush) # Indicate that this map contains goo/pits if is_pit: has_attr[VOICE_ATTR_PIT] = True else: has_attr[VOICE_ATTR_GOO] = True continue pos = world_to_grid(brush.get_origin(bbox_min, bbox_max)) if bbox_max - bbox_min == (128, 128, 128): # Full block.. self[pos] = Block.SOLID else: # Must be an embbedvoxel block self[pos] = Block.EMBED LOGGER.info('Analysed map, filling air... ({} starting positions..)', len(air_search_locs)) self.fill_air(goo_search_locs + air_search_locs) LOGGER.info('Air filled!')