def join_markers(inst_a, inst_b, is_start=False): """Join two marker ents together with corners. This returns a list of solids used for the vphysics_motion trigger. """ origin_a = Vec.from_str(inst_a['ent']['origin']) origin_b = Vec.from_str(inst_b['ent']['origin']) norm_a = Vec(-1, 0, 0).rotate_by_str(inst_a['ent']['angles']) norm_b = Vec(-1, 0, 0).rotate_by_str(inst_b['ent']['angles']) config = inst_a['conf'] if norm_a == norm_b: # Either straight-line, or s-bend. dist = (origin_a - origin_b).mag() if origin_a + (norm_a * dist) == origin_b: make_straight( origin_a, norm_a, dist, config, is_start, ) # else: S-bend, we don't do the geometry for this.. return if norm_a == -norm_b: # U-shape bend.. make_ubend( origin_a, origin_b, norm_a, config, max_size=inst_a['size'], ) return try: corner_ang, flat_angle = CORNER_ANG[norm_a.as_tuple(), norm_b.as_tuple()] if origin_a[flat_angle] != origin_b[flat_angle]: # It needs to be flat in this angle! raise ValueError except ValueError: # The tubes need two corners to join together - abort for that. return else: make_bend( origin_a, origin_b, norm_a, norm_b, corner_ang, config, max_size=inst_a['size'], )
def make_straight( origin: Vec, normal: Vec, dist: int, config: dict, is_start=False, ): """Make a straight line of instances from one point to another.""" # 32 added to the other directions, plus extended dist in the direction # of the normal - 1 p1 = origin + (normal * ((dist // 128 * 128) - 96)) # The starting brush needs to # stick out a bit further, to cover the # point_push entity. p2 = origin - (normal * (96 if is_start else 32)) # bbox before +- 32 to ensure the above doesn't wipe it out p1, p2 = Vec.bbox(p1, p2) solid = vbsp.VMF.make_prism( # Expand to 64x64 in the other two directions p1 - 32, p2 + 32, mat='tools/toolstrigger', ).solid motion_trigger(solid.copy()) push_trigger(origin, normal, [solid]) angles = normal.to_angle() support_file = config['support'] straight_file = config['straight'] support_positions = ( SUPPORT_POS[normal.as_tuple()] if support_file else [] ) for off in range(0, int(dist), 128): position = origin + off * normal vbsp.VMF.create_ent( classname='func_instance', origin=position, angles=angles, file=straight_file, ) for supp_ang, supp_off in support_positions: if (position + supp_off).as_tuple() in SOLIDS: vbsp.VMF.create_ent( classname='func_instance', origin=position, angles=supp_ang, file=support_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 = 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 color_square(color: utils.Vec, size=16): """Create a square image of the given size, with the given color.""" key = color.x, color.y, color.z, size try: return cached_squares[key] except KeyError: img = Image.new( mode='RGB', size=(size, size), color=(int(color.x), int(color.y), int(color.z)), ) tk_img = ImageTk.PhotoImage(image=img) cached_squares[color.as_tuple(), size] = tk_img return tk_img
def res_hollow_brush(inst, res): """Hollow out the attached brush, as if EmbeddedVoxel was set. This just removes the surface if it's already an embeddedVoxel. This allows multiple items to embed thinly in the same block without affecting each other. """ loc = Vec(0, 0, -64).rotate_by_str(inst['angles']) loc += Vec.from_str(inst['origin']) try: group = SOLIDS[loc.as_tuple()] except KeyError: LOGGER.warning('No brush for hollowing at ({})', loc) return # No brush here? conditions.hollow_block( group, remove_orig_face=utils.conv_bool(res['RemoveFace', False]) )
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 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_fizzler_pair(begin_inst, res): """Modify the instance of a fizzler to link with its pair. Each pair will be given a name along the lines of "fizz_name-model1334". Values: - StartInst, EndInst: The instances used for each end - MidInst: An instance placed every 128 units between emitters. """ 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 direction = Vec(0, 0, 1).rotate_by_str(begin_inst['angles']) begin_pos = Vec.from_str(begin_inst['origin']) axis_1, axis_2, main_axis = PAIR_AXES[direction.as_tuple()] for end_inst in vbsp.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: LOGGER.warning('No matching pair for {}!!', 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 vbsp.VMF.create_ent( classname='func_instance', targetname=pair_name, angles=begin_inst['angles'], file=mid_file, origin=new_pos.join(' '), )
def make_ubend( origin_a: Vec, origin_b: Vec, normal: Vec, config, max_size: int, is_start=False, ): """Create u-shaped bends.""" offset = origin_b - origin_a out_axis = normal.axis() out_off = offset[out_axis] offset[out_axis] = 0 if len(offset) == 2: # Len counts the non-zero values.. # If 2, the ubend is diagonal so it's ambigous where to put the bends. return [] side_norm = offset.norm() for side_axis, side_dist in zip('xyz', offset): if side_dist: side_dist = abs(side_dist) + 128 break else: # The two tube items are on top of another, that's # impossible to generate. return [] # Calculate the size of the various parts. # first/second _size = size of the corners. # first/second _straight = length of straight sections # off_straight = length of straight in between corners if out_off == 0: # Both tubes are parallel to each other - use half the distance # for the bends. first_size = second_size = min( 3, max_size, side_dist // (128 * 2), ) first_straight = second_straight = 0 side_straight = side_dist - 2 * 128 * first_size elif out_off > 0: # The second tube is further away than the first - the first bend # should be largest. # We need 1 spot for the second bend. first_size = min( 3, max_size, side_dist // 128 - 1, out_off, ) second_size = min(3, side_dist // 128 - first_size, max_size) first_straight = (out_off + 128) - 128 * second_size second_straight = (first_size - second_size) * 128 side_straight = (side_dist / 128 - first_size - second_size) * 128 elif out_off < 0: # The first tube is further away than the second - the second bend # should be largest. second_size = min( 3, max_size, side_dist // 128 - 1, -out_off # -out = abs() ) first_size = min(3, side_dist // 128 - second_size, max_size) first_straight = (second_size - first_size) * 128 second_straight = (-out_off + 128) - 128 * second_size side_straight = (side_dist / 128 - first_size - second_size) * 128 else: return [] # Not possible.. # We always have a straight segment at the first marker point - move # everything up slightly. first_straight += 128 LOGGER.info( 'Ubend {}: {}, c={}, {}, c={}, {}', out_off, first_straight, first_size, side_straight, second_size, second_straight, ) make_straight( origin_a, normal, first_straight, config, is_start, ) first_corner_loc = origin_a + (normal * first_straight) make_corner( first_corner_loc, CORNER_ANG[normal.as_tuple(), side_norm.as_tuple()].ang, first_size, config, ) off_straight_loc = first_corner_loc + normal * (128 * (first_size - 1)) off_straight_loc += side_norm * (128 * first_size) if side_straight > 0: make_straight( off_straight_loc, side_norm, side_straight, config, ) sec_corner_loc = off_straight_loc + side_norm * side_straight make_corner( sec_corner_loc, CORNER_ANG[side_norm.as_tuple(), (-normal).as_tuple()].ang, second_size, config, ) if second_straight > 0: make_straight( sec_corner_loc - normal * (128 * second_size), -normal, second_straight, config, )
def res_insert_overlay(inst: VLib.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 = conditions.import_template( temp_id, origin, angles, targetname=inst['targetname', ''], force_type=TEMP_TYPES.detail, ) for over in temp.overlay: # type: VLib.Entity random.seed('TEMP_OVERLAY_' + over['basisorigin']) mat = random.choice(replace.get( over['material'], (over['material'], ), )) if mat[:1] == '$': mat = inst.fixup[mat] 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 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 res_make_catwalk(_, res): """Speciallised result to generate catwalks from markers. Only runs once, and then quits the condition list. Instances: MarkerInst: The instance set in editoritems. Straight_128/256/512: Straight sections. Extends East Corner: A corner piece. Connects on N and W sides. TJunction; A T-piece. Connects on all but the East side. CrossJunction: A X-piece. Connects on all sides. End: An end piece. Connects on the East side. Stair: A stair. Starts East and goes Up and West. End_wall: Connects a West wall to a East catwalk. Support_Wall: A support extending from the East wall. Support_Ceil: A support extending from the ceiling. Support_Floor: A support extending from the floor. Support_Goo: A floor support, designed for goo pits. Single_Wall: A section connecting to an East wall. """ LOGGER.info("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', 'support_goo', '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 = {} # Find all our markers, so we can look them up by targetname. for inst in vbsp.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 # Snap the markers to the grid. If on glass it can become offset... origin = Vec.from_str(inst['origin']) origin = origin // 128 * 128 # type: Vec origin += 64 while origin.as_tuple() in conditions.GOO_LOCS: # The instance is in goo! Switch to floor orientation, and move # up until it's in air. inst['angles'] = '0 0 0' origin.z += 128 inst['origin'] = str(origin) if not markers: return RES_EXHAUSTED LOGGER.info('Connections: {}', connections) LOGGER.info('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 entities. # Find the associated overlays too. for del_inst in vbsp.VMF.by_target[conn.target]: conditions.remove_ant_toggle(del_inst) continue inst2 = markers[conn.target] LOGGER.debug('{} <-> {}', 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: LOGGER.warning('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 LOGGER.debug('Dist = {}, Vert = {}', dist, vert_dist) if (dist - 128) // 2 < abs(vert_dist): # The stairs are 2 long, 1 high. Check there's enough room # Subtract the last block though, since that's a corner. LOGGER.warning('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. normal = Vec(0, 0, 1).rotate_by_str(inst['angles']) new_type, inst['angles'] = utils.CONN_LOOKUP[tuple(dir_mask)] inst['file'] = instances[CATWALK_TYPES[new_type]] 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], # +E, -W y=dir_mask[0] - dir_mask[1], # +N, -S, z=0, ) 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): # If in goo, use different supports! origin = Vec.from_str(inst['origin']) origin.z -= 128 if origin.as_tuple() in conditions.GOO_LOCS: supp = instances['support_goo'] else: supp = instances['support_floor'] elif normal == (0, 0, -1): supp = instances['support_ceil'] else: supp = instances['support_wall'] if supp: vbsp.VMF.create_ent( classname='func_instance', origin=inst['origin'], angles=INST_ANGLE[normal.as_tuple()], file=supp, ) LOGGER.info('Finished catwalk generation!') return RES_EXHAUSTED
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