def res_get_item_config(inst: Entity, res: Property): """Check if an item config panel value matches another value. ID is the ID of the group. Name is the name of the widget. If UseTimer is true, it uses $timer_delay to choose the value to use. Value is the value to compare to. """ group_id = res['ID'] wid_name = res['Name'].casefold() desired_value = res['Value'] if res.bool('UseTimer'): timer_delay = inst.fixup.int('$timer_delay') else: timer_delay = None conf = vbsp_options.get_itemconf((group_id, wid_name), None, timer_delay) if conf is None: # Doesn't exist return False return conf == desired_value
def res_get_item_config(inst: Entity, res: Property): """Load a config from the item config panel onto a fixup. ID is the ID of the group. Name is the name of the widget, and resultVar is the location to store. If UseTimer is true, it uses $timer_delay to choose the value to use. Default is the default value, if the config isn't found. """ group_id = res['ID'] wid_name = res['Name'] default = res['default'] if res.bool('UseTimer'): timer_delay = inst.fixup.int('$timer_delay') else: timer_delay = None inst.fixup[res['ResultVar']] = vbsp_options.get_itemconf( (group_id, wid_name), default, timer_delay, )
def res_match_item_config(inst: Entity, res: Property) -> bool: """Check if an item config panel value matches another value. * `ID` is the ID of the group. * `Name` is the name of the widget. * If `UseTimer` is true, it uses `$timer_delay` to choose the value to use. * `Value` is the value to compare to. """ group_id = res['ID'] wid_name = res['Name'].casefold() desired_value = res['Value'] if res.bool('UseTimer'): timer_delay = inst.fixup.int('$timer_delay') else: timer_delay = None conf = vbsp_options.get_itemconf((group_id, wid_name), None, timer_delay) if conf is None: # Doesn't exist return False return conf == desired_value
def res_item_config_to_fixup(inst: Entity, res: Property): """Load a config from the item config panel onto a fixup. * `ID` is the ID of the group. * `Name` is the name of the widget. * `resultVar` is the location to store the value into. * If `UseTimer` is true, it uses `$timer_delay` to choose the value to use. * `Default` is the default value, if the config isn't found. """ group_id = res['ID'] wid_name = res['Name'] default = res['default'] if res.bool('UseTimer'): timer_delay = inst.fixup.int('$timer_delay') else: timer_delay = None inst.fixup[res['ResultVar']] = vbsp_options.get_itemconf( (group_id, wid_name), default, timer_delay, )
def make_barriers(vmf: VMF, get_tex: Callable[[str], str]): """Make barrier entities. get_tex is vbsp.get_tex.""" glass_temp = template_brush.get_scaling_template( vbsp_options.get(str, "glass_template") ) grate_temp = template_brush.get_scaling_template( vbsp_options.get(str, "grating_template") ) # Avoid error without this package. if HOLES: # Grab the template solids we need. hole_temp = template_brush.get_template( vbsp_options.get(str, 'glass_hole_temp') ) hole_world, hole_detail, _ = hole_temp.visgrouped({'small'}) hole_temp_small = hole_world + hole_detail hole_world, hole_detail, _ = hole_temp.visgrouped({'large'}) hole_temp_large = hole_world + hole_detail hole_world, hole_detail, _ = hole_temp.visgrouped({'large_corner'}) hole_temp_corner = hole_world + hole_detail else: hole_temp_small = hole_temp_large = hole_temp_corner = None floorbeam_temp = vbsp_options.get(str, 'glass_floorbeam_temp') if vbsp_options.get_itemconf('BEE_PELLET:PelletGrating', False): # Merge together these existing filters in global_pti_ents vmf.create_ent( origin=vbsp_options.get(Vec, 'global_pti_ents_loc'), targetname='@grating_filter', classname='filter_multi', filtertype=0, negated=0, filter01='@not_pellet', filter02='@not_paint_bomb', ) else: # Just skip paint bombs. vmf.create_ent( origin=vbsp_options.get(Vec, 'global_pti_ents_loc'), targetname='@grating_filter', classname='filter_activator_class', negated=1, filterclass='prop_paint_bomb', ) # Group the positions by planes in each orientation. # This makes them 2D grids which we can optimise. # (normal_dist, positive_axis, type) -> [(x, y)] slices = defaultdict(set) # type: Dict[Tuple[Tuple[float, float, float], bool, BarrierType], Set[Tuple[float, float]]] # We have this on the 32-grid so we can cut squares for holes. for (origin, normal), barr_type in BARRIERS.items(): origin = Vec(origin) normal = Vec(normal) norm_axis = normal.axis() u, v = origin.other_axes(norm_axis) norm_pos = Vec.with_axes(norm_axis, origin) slice_plane = slices[ norm_pos.as_tuple(), # distance from origin to this plane. normal[norm_axis] > 0, barr_type, ] for u_off in [-48, -16, 16, 48]: for v_off in [-48, -16, 16, 48]: slice_plane.add(( (u + u_off) // 32, (v + v_off) // 32, )) # Remove pane sections where the holes are. We then generate those with # templates for slanted parts. for (origin, normal), hole_type in HOLES.items(): barr_type = BARRIERS[origin, normal] origin = Vec(origin) normal = Vec(normal) norm_axis = normal.axis() u, v = origin.other_axes(norm_axis) norm_pos = Vec.with_axes(norm_axis, origin) slice_plane = slices[ norm_pos.as_tuple(), normal[norm_axis] > 0, barr_type, ] if hole_type is HoleType.LARGE: offsets = (-80, -48, -16, 16, 48, 80) hole_temp = hole_temp_large.copy() else: offsets = (-16, 16) hole_temp = hole_temp_small.copy() for u_off in offsets: for v_off in offsets: # Skip the corners on large holes. # Those aren't actually used, so skip them. That way # we can have them diagonally or without glass in the corner. if u_off in (-80, 80) and v_off in (-80, 80): continue slice_plane.discard(( (u + u_off) // 32, (v + v_off) // 32, )) # Now generate the curved brushwork. if barr_type is BarrierType.GLASS: front_temp = glass_temp front_mat = get_tex('special.glass') elif barr_type is BarrierType.GRATING: front_temp = grate_temp front_mat = get_tex('special.grating') else: raise NotImplementedError angles = normal.to_angle(0) # Angle corresponding to the brush, for the corners. angle_list = [angles] * len(hole_temp) # This is a tricky bit. Two large templates would collide # diagonally, # so chop off the corners, then put them back only if there's not # one diagonally. if hole_type is HoleType.LARGE: for roll in (0, 90, 180, 270): corn_angles = angles.copy() corn_angles.z = roll hole_off = origin + Vec(y=128, z=128).rotate(*corn_angles) diag_type = HOLES.get( (hole_off.as_tuple(), normal.as_tuple()), None, ) if diag_type is not HoleType.LARGE: hole_temp += hole_temp_corner angle_list += [corn_angles] * len(hole_temp_corner) def solid_pane_func(off1, off2, mat): """Given the two thicknesses, produce the curved hole from the template.""" off_min = min(off1, off2) off_max = max(off1, off2) new_brushes = [ brush.copy(vmf_file=vmf) for brush in hole_temp ] for brush, br_angles in zip(new_brushes, angle_list): for face in brush.sides: face.mat = mat f_norm = face.normal() if f_norm.x == 1: face.translate(Vec(x=4 - off_max)) # face.mat = 'min' elif f_norm.x == -1: face.translate(Vec(x=-4 - off_min)) # face.mat = 'max' face.localise(origin, br_angles) return new_brushes make_glass_grating( vmf, origin, normal, barr_type, front_temp, front_mat, solid_pane_func, ) for (plane_pos, is_pos, barr_type), pos_slice in slices.items(): plane_pos = Vec(plane_pos) norm_axis = plane_pos.axis() normal = Vec.with_axes(norm_axis, 1 if is_pos else -1) if barr_type is BarrierType.GLASS: front_temp = glass_temp front_mat = get_tex('special.glass') elif barr_type is BarrierType.GRATING: front_temp = grate_temp front_mat = get_tex('special.grating') else: raise NotImplementedError u_axis, v_axis = Vec.INV_AXIS[norm_axis] for min_u, min_v, max_u, max_v in grid_optimise(dict.fromkeys(pos_slice, True)): # These are two points in the origin plane, at the borders. pos_min = Vec.with_axes( norm_axis, plane_pos, u_axis, min_u * 32, v_axis, min_v * 32, ) pos_max = Vec.with_axes( norm_axis, plane_pos, u_axis, max_u * 32 + 32, v_axis, max_v * 32 + 32, ) def solid_pane_func(pos1, pos2, mat): """Make the solid brush.""" return [vmf.make_prism( pos_min + normal * (64.0 - pos1), pos_max + normal * (64.0 - pos2), mat=mat, ).solid] make_glass_grating( vmf, (pos_min + pos_max)/2, normal, barr_type, front_temp, front_mat, solid_pane_func, ) if floorbeam_temp: LOGGER.info('Adding Glass floor beams...') add_glass_floorbeams(vmf, floorbeam_temp) LOGGER.info('Done!')
def res_resizeable_trigger(vmf: VMF, res: Property): """Replace two markers with a trigger brush. This is run once to affect all of an item. Options: * `markerInst`: <ITEM_ID:1,2> value referencing the marker instances, or a filename. * `markerItem`: The item's ID * `previewConf`: A item config which enables/disables the preview overlay. * `previewInst`: An instance to place at the marker location in preview mode. This should contain checkmarks to display the value when testing. * `previewMat`: If set, the material to use for an overlay func_brush. The brush will be parented to the trigger, so it vanishes once killed. It is also non-solid. * `previewScale`: The scale for the func_brush materials. * `previewActivate`, `previewDeactivate`: The VMF output to turn the previewInst on and off. * `triggerActivate, triggerDeactivate`: The `instance:name;Output` outputs used when the trigger turns on or off. * `coopVar`: The instance variable which enables detecting both Coop players. The trigger will be a trigger_playerteam. * `coopActivate, coopDeactivate`: The `instance:name;Output` outputs used when coopVar is enabled. These should be suitable for a logic_coop_manager. * `coopOnce`: If true, kill the manager after it first activates. * `keys`: A block of keyvalues for the trigger brush. Origin and targetname will be set automatically. * `localkeys`: The same as above, except values will be changed to use instance-local names. """ marker = instanceLocs.resolve(res['markerInst']) marker_names = set() for inst in vmf.by_class['func_instance']: if inst['file'].casefold() in marker: marker_names.add(inst['targetname']) # Unconditionally delete from the map, so it doesn't # appear even if placed wrongly. inst.remove() if not marker_names: # No markers in the map - abort return RES_EXHAUSTED item_id = res['markerItem'] # Synthesise the item type used for the final trigger. item_type_sp = connections.ItemType( id=item_id + ':TRIGGER', output_act=Output.parse_name(res['triggerActivate', 'OnStartTouchAll']), output_deact=Output.parse_name(res['triggerDeactivate', 'OnEndTouchAll']), ) # For Coop, we add a logic_coop_manager in the mix so both players can # be handled. try: coop_var = res['coopVar'] except LookupError: coop_var = item_type_coop = None coop_only_once = False else: coop_only_once = res.bool('coopOnce') item_type_coop = connections.ItemType( id=item_id + ':TRIGGER_COOP', output_act=Output.parse_name(res['coopActivate', 'OnChangeToAllTrue']), output_deact=Output.parse_name(res['coopDeactivate', 'OnChangeToAnyFalse']), ) # Display preview overlays if it's preview mode, and the config is true pre_act = pre_deact = None if vbsp.IS_PREVIEW and vbsp_options.get_itemconf(res['previewConf', ''], False): preview_mat = res['previewMat', ''] preview_inst_file = res['previewInst', ''] preview_scale = res.float('previewScale', 0.25) # None if not found. with suppress(LookupError): pre_act = Output.parse(res.find_key('previewActivate')) with suppress(LookupError): pre_deact = Output.parse(res.find_key('previewDeactivate')) else: # Deactivate the preview_ options when publishing. preview_mat = preview_inst_file = '' preview_scale = 0.25 # Now go through each brush. # We do while + pop to allow removing both names each loop through. todo_names = set(marker_names) while todo_names: targ = todo_names.pop() mark1 = connections.ITEMS.pop(targ) for conn in mark1.outputs: if conn.to_item.name in marker_names: mark2 = conn.to_item conn.remove() # Delete this connection. todo_names.discard(mark2.name) del connections.ITEMS[mark2.name] break else: if not mark1.inputs: # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 1-block trigger. mark2 = mark1 else: # It's a marker with an input, the other in the pair # will handle everything. # But reinstate it in ITEMS. connections.ITEMS[targ] = mark1 continue inst1 = mark1.inst inst2 = mark2.inst is_coop = coop_var is not None and vbsp.GAME_MODE == 'COOP' and ( inst1.fixup.bool(coop_var) or inst2.fixup.bool(coop_var)) bbox_min, bbox_max = Vec.bbox(Vec.from_str(inst1['origin']), Vec.from_str(inst2['origin'])) origin = (bbox_max + bbox_min) / 2 # Extend to the edge of the blocks. bbox_min -= 64 bbox_max += 64 out_ent = trig_ent = vmf.create_ent( classname='trigger_multiple', # Default targetname=targ, origin=origin, angles='0 0 0', ) trig_ent.solids = [ vmf.make_prism( bbox_min, bbox_max, mat=const.Tools.TRIGGER, ).solid, ] # Use 'keys' and 'localkeys' blocks to set all the other keyvalues. conditions.set_ent_keys(trig_ent, inst, res) if is_coop: trig_ent['spawnflags'] = '1' # Clients trig_ent['classname'] = 'trigger_playerteam' out_ent = manager = vmf.create_ent( classname='logic_coop_manager', targetname=conditions.local_name(inst, 'man'), origin=origin, ) item = connections.Item( out_ent, item_type_coop, mark1.ant_floor_style, mark1.ant_wall_style, ) if coop_only_once: # Kill all the ents when both players are present. manager.add_out( Output('OnChangeToAllTrue', manager, 'Kill'), Output('OnChangeToAllTrue', targ, 'Kill'), ) trig_ent.add_out( Output('OnStartTouchBluePlayer', manager, 'SetStateATrue'), Output('OnStartTouchOrangePlayer', manager, 'SetStateBTrue'), Output('OnEndTouchBluePlayer', manager, 'SetStateAFalse'), Output('OnEndTouchOrangePlayer', manager, 'SetStateBFalse'), ) else: item = connections.Item( trig_ent, item_type_sp, mark1.ant_floor_style, mark1.ant_wall_style, ) # Register, and copy over all the antlines. connections.ITEMS[item.name] = item item.ind_panels = mark1.ind_panels | mark2.ind_panels item.antlines = mark1.antlines | mark2.antlines item.shape_signs = mark1.shape_signs + mark2.shape_signs if preview_mat: preview_brush = vmf.create_ent( classname='func_brush', parentname=targ, origin=origin, Solidity='1', # Not solid drawinfastreflection='1', # Draw in goo.. # Disable shadows and lighting.. disableflashlight='1', disablereceiveshadows='1', disableshadowdepth='1', disableshadows='1', ) preview_brush.solids = [ # Make it slightly smaller, so it doesn't z-fight with surfaces. vmf.make_prism( bbox_min + 0.5, bbox_max - 0.5, mat=preview_mat, ).solid, ] for face in preview_brush.sides(): face.scale = preview_scale if preview_inst_file: pre_inst = vmf.create_ent( classname='func_instance', targetname=targ + '_preview', file=preview_inst_file, # Put it at the second marker, since that's usually # closest to antlines if present. origin=inst2['origin'], ) if pre_act is not None: out = pre_act.copy() out.inst_out, out.output = item.output_act() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) if pre_deact is not None: out = pre_deact.copy() out.inst_out, out.output = item.output_deact() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) for conn in mark1.outputs | mark2.outputs: conn.from_item = item return RES_EXHAUSTED
def res_resizeable_trigger(vmf: VMF, res: Property): """Replace two markers with a trigger brush. This is run once to affect all of an item. Options: * `markerInst`: <ITEM_ID:1,2> value referencing the marker instances, or a filename. * `markerItem`: The item's ID * `previewConf`: A item config which enables/disables the preview overlay. * `previewInst`: An instance to place at the marker location in preview mode. This should contain checkmarks to display the value when testing. * `previewMat`: If set, the material to use for an overlay func_brush. The brush will be parented to the trigger, so it vanishes once killed. It is also non-solid. * `previewScale`: The scale for the func_brush materials. * `previewActivate`, `previewDeactivate`: The VMF output to turn the previewInst on and off. * `triggerActivate, triggerDeactivate`: The `instance:name;Output` outputs used when the trigger turns on or off. * `coopVar`: The instance variable which enables detecting both Coop players. The trigger will be a trigger_playerteam. * `coopActivate, coopDeactivate`: The `instance:name;Output` outputs used when coopVar is enabled. These should be suitable for a logic_coop_manager. * `coopOnce`: If true, kill the manager after it first activates. * `keys`: A block of keyvalues for the trigger brush. Origin and targetname will be set automatically. * `localkeys`: The same as above, except values will be changed to use instance-local names. """ marker = instanceLocs.resolve(res['markerInst']) marker_names = set() for inst in vmf.by_class['func_instance']: if inst['file'].casefold() in marker: marker_names.add(inst['targetname']) # Unconditionally delete from the map, so it doesn't # appear even if placed wrongly. inst.remove() if not marker_names: # No markers in the map - abort return RES_EXHAUSTED item_id = res['markerItem'] # Synthesise the item type used for the final trigger. item_type_sp = connections.ItemType( id=item_id + ':TRIGGER', output_act=Output.parse_name(res['triggerActivate', 'OnStartTouchAll']), output_deact=Output.parse_name(res['triggerDeactivate', 'OnEndTouchAll']), ) # For Coop, we add a logic_coop_manager in the mix so both players can # be handled. try: coop_var = res['coopVar'] except LookupError: coop_var = item_type_coop = None coop_only_once = False else: coop_only_once = res.bool('coopOnce') item_type_coop = connections.ItemType( id=item_id + ':TRIGGER_COOP', output_act=Output.parse_name( res['coopActivate', 'OnChangeToAllTrue'] ), output_deact=Output.parse_name( res['coopDeactivate', 'OnChangeToAnyFalse'] ), ) # Display preview overlays if it's preview mode, and the config is true pre_act = pre_deact = None if vbsp.IS_PREVIEW and vbsp_options.get_itemconf(res['previewConf', ''], False): preview_mat = res['previewMat', ''] preview_inst_file = res['previewInst', ''] preview_scale = res.float('previewScale', 0.25) # None if not found. with suppress(LookupError): pre_act = Output.parse(res.find_key('previewActivate')) with suppress(LookupError): pre_deact = Output.parse(res.find_key('previewDeactivate')) else: # Deactivate the preview_ options when publishing. preview_mat = preview_inst_file = '' preview_scale = 0.25 # Now go through each brush. # We do while + pop to allow removing both names each loop through. todo_names = set(marker_names) while todo_names: targ = todo_names.pop() mark1 = connections.ITEMS.pop(targ) for conn in mark1.outputs: if conn.to_item.name in marker_names: mark2 = conn.to_item conn.remove() # Delete this connection. todo_names.discard(mark2.name) del connections.ITEMS[mark2.name] break else: if not mark1.inputs: # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 1-block trigger. mark2 = mark1 else: # It's a marker with an input, the other in the pair # will handle everything. # But reinstate it in ITEMS. connections.ITEMS[targ] = mark1 continue inst1 = mark1.inst inst2 = mark2.inst is_coop = coop_var is not None and vbsp.GAME_MODE == 'COOP' and ( inst1.fixup.bool(coop_var) or inst2.fixup.bool(coop_var) ) bbox_min, bbox_max = Vec.bbox( Vec.from_str(inst1['origin']), Vec.from_str(inst2['origin']) ) origin = (bbox_max + bbox_min) / 2 # Extend to the edge of the blocks. bbox_min -= 64 bbox_max += 64 out_ent = trig_ent = vmf.create_ent( classname='trigger_multiple', # Default targetname=targ, origin=origin, angles='0 0 0', ) trig_ent.solids = [ vmf.make_prism( bbox_min, bbox_max, mat=const.Tools.TRIGGER, ).solid, ] # Use 'keys' and 'localkeys' blocks to set all the other keyvalues. conditions.set_ent_keys(trig_ent, inst, res) if is_coop: trig_ent['spawnflags'] = '1' # Clients trig_ent['classname'] = 'trigger_playerteam' out_ent = manager = vmf.create_ent( classname='logic_coop_manager', targetname=conditions.local_name(inst, 'man'), origin=origin, ) item = connections.Item( out_ent, item_type_coop, mark1.ant_floor_style, mark1.ant_wall_style, ) if coop_only_once: # Kill all the ents when both players are present. manager.add_out( Output('OnChangeToAllTrue', manager, 'Kill'), Output('OnChangeToAllTrue', targ, 'Kill'), ) trig_ent.add_out( Output('OnStartTouchBluePlayer', manager, 'SetStateATrue'), Output('OnStartTouchOrangePlayer', manager, 'SetStateBTrue'), Output('OnEndTouchBluePlayer', manager, 'SetStateAFalse'), Output('OnEndTouchOrangePlayer', manager, 'SetStateBFalse'), ) else: item = connections.Item( trig_ent, item_type_sp, mark1.ant_floor_style, mark1.ant_wall_style, ) # Register, and copy over all the antlines. connections.ITEMS[item.name] = item item.ind_panels = mark1.ind_panels | mark2.ind_panels item.antlines = mark1.antlines | mark2.antlines item.shape_signs = mark1.shape_signs + mark2.shape_signs if preview_mat: preview_brush = vmf.create_ent( classname='func_brush', parentname=targ, origin=origin, Solidity='1', # Not solid drawinfastreflection='1', # Draw in goo.. # Disable shadows and lighting.. disableflashlight='1', disablereceiveshadows='1', disableshadowdepth='1', disableshadows='1', ) preview_brush.solids = [ # Make it slightly smaller, so it doesn't z-fight with surfaces. vmf.make_prism( bbox_min + 0.5, bbox_max - 0.5, mat=preview_mat, ).solid, ] for face in preview_brush.sides(): face.scale = preview_scale if preview_inst_file: pre_inst = vmf.create_ent( classname='func_instance', targetname=targ + '_preview', file=preview_inst_file, # Put it at the second marker, since that's usually # closest to antlines if present. origin=inst2['origin'], ) if pre_act is not None: out = pre_act.copy() out.inst_out, out.output = item.output_act() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) if pre_deact is not None: out = pre_deact.copy() out.inst_out, out.output = item.output_deact() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) for conn in mark1.outputs | mark2.outputs: conn.from_item = item return RES_EXHAUSTED
def make_barriers(vmf: VMF): """Make barrier entities. get_tex is vbsp.get_tex.""" glass_temp = template_brush.get_scaling_template( vbsp_options.get(str, "glass_template")) grate_temp = template_brush.get_scaling_template( vbsp_options.get(str, "grating_template")) # Avoid error without this package. if HOLES: # Grab the template solids we need. hole_temp = template_brush.get_template( vbsp_options.get(str, 'glass_hole_temp')) hole_world, hole_detail, _ = hole_temp.visgrouped({'small'}) hole_temp_small = hole_world + hole_detail hole_world, hole_detail, _ = hole_temp.visgrouped({'large'}) hole_temp_large = hole_world + hole_detail hole_world, hole_detail, _ = hole_temp.visgrouped({'large_corner'}) hole_temp_corner = hole_world + hole_detail else: hole_temp_small = hole_temp_large = hole_temp_corner = None floorbeam_temp = vbsp_options.get(str, 'glass_floorbeam_temp') if vbsp_options.get_itemconf('BEE_PELLET:PelletGrating', False): # Merge together these existing filters in global_pti_ents vmf.create_ent( origin=vbsp_options.get(Vec, 'global_pti_ents_loc'), targetname='@grating_filter', classname='filter_multi', filtertype=0, negated=0, filter01='@not_pellet', filter02='@not_paint_bomb', ) else: # Just skip paint bombs. vmf.create_ent( origin=vbsp_options.get(Vec, 'global_pti_ents_loc'), targetname='@grating_filter', classname='filter_activator_class', negated=1, filterclass='prop_paint_bomb', ) # Group the positions by planes in each orientation. # This makes them 2D grids which we can optimise. # (normal_dist, positive_axis, type) -> [(x, y)] slices = defaultdict( set ) # type: Dict[Tuple[Tuple[float, float, float], bool, BarrierType], Set[Tuple[float, float]]] # We have this on the 32-grid so we can cut squares for holes. for (origin, normal), barr_type in BARRIERS.items(): origin = Vec(origin) normal = Vec(normal) norm_axis = normal.axis() u, v = origin.other_axes(norm_axis) norm_pos = Vec.with_axes(norm_axis, origin) slice_plane = slices[ norm_pos.as_tuple(), # distance from origin to this plane. normal[norm_axis] > 0, barr_type, ] for u_off in [-48, -16, 16, 48]: for v_off in [-48, -16, 16, 48]: slice_plane.add(( (u + u_off) // 32, (v + v_off) // 32, )) # Remove pane sections where the holes are. We then generate those with # templates for slanted parts. for (origin, normal), hole_type in HOLES.items(): barr_type = BARRIERS[origin, normal] origin = Vec(origin) normal = Vec(normal) norm_axis = normal.axis() u, v = origin.other_axes(norm_axis) norm_pos = Vec.with_axes(norm_axis, origin) slice_plane = slices[norm_pos.as_tuple(), normal[norm_axis] > 0, barr_type, ] if hole_type is HoleType.LARGE: offsets = (-80, -48, -16, 16, 48, 80) hole_temp = hole_temp_large.copy() else: offsets = (-16, 16) hole_temp = hole_temp_small.copy() for u_off in offsets: for v_off in offsets: # Skip the corners on large holes. # Those aren't actually used, so skip them. That way # we can have them diagonally or without glass in the corner. if u_off in (-80, 80) and v_off in (-80, 80): continue slice_plane.discard(( (u + u_off) // 32, (v + v_off) // 32, )) # Now generate the curved brushwork. if barr_type is BarrierType.GLASS: front_temp = glass_temp elif barr_type is BarrierType.GRATING: front_temp = grate_temp else: raise NotImplementedError angles = normal.to_angle(0) # Angle corresponding to the brush, for the corners. angle_list = [angles] * len(hole_temp) # This is a tricky bit. Two large templates would collide # diagonally, # so chop off the corners, then put them back only if there's not # one diagonally. if hole_type is HoleType.LARGE: for roll in (0, 90, 180, 270): corn_angles = angles.copy() corn_angles.z = roll hole_off = origin + Vec(y=128, z=128).rotate(*corn_angles) diag_type = HOLES.get( (hole_off.as_tuple(), normal.as_tuple()), None, ) if diag_type is not HoleType.LARGE: hole_temp += hole_temp_corner angle_list += [corn_angles] * len(hole_temp_corner) def solid_pane_func(off1, off2, mat): """Given the two thicknesses, produce the curved hole from the template.""" off_min = min(off1, off2) off_max = max(off1, off2) new_brushes = [brush.copy(vmf_file=vmf) for brush in hole_temp] for brush, br_angles in zip(new_brushes, angle_list): for face in brush.sides: face.mat = mat f_norm = face.normal() if f_norm.x == 1: face.translate(Vec(x=4 - off_max)) # face.mat = 'min' elif f_norm.x == -1: face.translate(Vec(x=-4 - off_min)) # face.mat = 'max' face.localise(origin, br_angles) return new_brushes make_glass_grating( vmf, origin, normal, barr_type, front_temp, solid_pane_func, ) for (plane_pos, is_pos, barr_type), pos_slice in slices.items(): plane_pos = Vec(plane_pos) norm_axis = plane_pos.axis() normal = Vec.with_axes(norm_axis, 1 if is_pos else -1) if barr_type is BarrierType.GLASS: front_temp = glass_temp elif barr_type is BarrierType.GRATING: front_temp = grate_temp else: raise NotImplementedError u_axis, v_axis = Vec.INV_AXIS[norm_axis] for min_u, min_v, max_u, max_v in grid_optimise( dict.fromkeys(pos_slice, True)): # These are two points in the origin plane, at the borders. pos_min = Vec.with_axes( norm_axis, plane_pos, u_axis, min_u * 32, v_axis, min_v * 32, ) pos_max = Vec.with_axes( norm_axis, plane_pos, u_axis, max_u * 32 + 32, v_axis, max_v * 32 + 32, ) def solid_pane_func(pos1, pos2, mat): """Make the solid brush.""" return [ vmf.make_prism( pos_min + normal * (64.0 - pos1), pos_max + normal * (64.0 - pos2), mat=mat, ).solid ] make_glass_grating( vmf, (pos_min + pos_max) / 2, normal, barr_type, front_temp, solid_pane_func, ) if floorbeam_temp: LOGGER.info('Adding Glass floor beams...') add_glass_floorbeams(vmf, floorbeam_temp) LOGGER.info('Done!')
def gen_flinch_trigs(self, vmf: VMF, name: str, start_disabled: str) -> None: """For deadly fizzlers optionally make them safer. This adds logic to force players back instead when walking into the field. Only applies to vertical triggers. """ normal = abs(self.normal()) # type: Vec # Horizontal fizzlers would just have you fall through. if normal.z: return # Disabled. if not vbsp_options.get_itemconf( ('VALVE_FIZZLER', 'FlinchBack'), False): return # Make global entities if not present. if '_fizz_flinch_hurt' not in vmf.by_target: glob_ent_loc = vbsp_options.get(Vec, 'global_ents_loc') vmf.create_ent( classname='point_hurt', targetname='_fizz_flinch_hurt', Damage=10, # Just for visuals and sounds. # BURN | ENERGYBEAM | PREVENT_PHYSICS_FORCE DamageType=8 | 1024 | 2048, DamageTarget='!activator', # Hurt the triggering player. DamageRadius=1, # Target makes this unused. origin=glob_ent_loc, ) # We need two catapults - one for each side. neg_brush = vmf.create_ent( targetname=name, classname='trigger_catapult', spawnflags=1, # Players only. origin=self.base_inst['origin'], physicsSpeed=0, playerSpeed=96, launchDirection=(-normal).to_angle(), startDisabled=start_disabled, ) neg_brush.add_out(Output('OnCatapulted', '_fizz_flinch_hurt', 'Hurt')) pos_brush = neg_brush.copy() pos_brush['launchDirection'] = normal.to_angle() vmf.add_ent(pos_brush) for seg_min, seg_max in self.emitters: neg_brush.solids.append( vmf.make_prism( p1=(seg_min - 4 * normal - 64 * self.up_axis), p2=seg_max + 64 * self.up_axis, mat=const.Tools.TRIGGER, ).solid) pos_brush.solids.append( vmf.make_prism( p1=seg_min - 64 * self.up_axis, p2=(seg_max + 4 * normal + 64 * self.up_axis), mat=const.Tools.TRIGGER, ).solid)
def gen_flinch_trigs(self, vmf: VMF, name: str, start_disabled: str) -> None: """For deadly fizzlers optionally make them safer. This adds logic to force players back instead when walking into the field. Only applies to vertical triggers. """ normal = abs(self.normal()) # type: Vec # Horizontal fizzlers would just have you fall through. if normal.z: return # Disabled. if not vbsp_options.get_itemconf(('VALVE_FIZZLER', 'FlinchBack'), False): return # Make global entities if not present. if '_fizz_flinch_hurt' not in vmf.by_target: glob_ent_loc = vbsp_options.get(Vec, 'global_ents_loc') vmf.create_ent( classname='point_hurt', targetname='_fizz_flinch_hurt', Damage=10, # Just for visuals and sounds. # BURN | ENERGYBEAM | PREVENT_PHYSICS_FORCE DamageType=8 | 1024 | 2048, DamageTarget='!activator', # Hurt the triggering player. DamageRadius=1, # Target makes this unused. origin=glob_ent_loc, ) # We need two catapults - one for each side. neg_brush = vmf.create_ent( targetname=name, classname='trigger_catapult', spawnflags=1, # Players only. origin=self.base_inst['origin'], physicsSpeed=0, playerSpeed=96, launchDirection=(-normal).to_angle(), startDisabled=start_disabled, ) neg_brush.add_out(Output('OnCatapulted', '_fizz_flinch_hurt', 'Hurt')) pos_brush = neg_brush.copy() pos_brush['launchDirection'] = normal.to_angle() vmf.add_ent(pos_brush) for seg_min, seg_max in self.emitters: neg_brush.solids.append(vmf.make_prism( p1=(seg_min - 4 * normal - 64 * self.up_axis ), p2=seg_max + 64 * self.up_axis, mat=const.Tools.TRIGGER, ).solid) pos_brush.solids.append(vmf.make_prism( p1=seg_min - 64 * self.up_axis, p2=(seg_max + 4 * normal + 64 * self.up_axis ), mat=const.Tools.TRIGGER, ).solid)
def res_resizeable_trigger(res: Property): """Replace two markers with a trigger brush. This is run once to affect all of an item. Options: * `markerInst`: <ITEM_ID:1,2> value referencing the marker instances, or a filename. * `markerItem`: The item's ID * `previewConf`: A item config which enables/disables the preview overlay. * `previewinst`: An instance to place at the marker location in preview mode. This should contain checkmarks to display the value when testing. * `previewMat`: If set, the material to use for an overlay func_brush. The brush will be parented to the trigger, so it vanishes once killed. It is also non-solid. * `previewScale`: The scale for the func_brush materials. * `previewActivate`, `previewDeactivate`: The `instance:name;Input` value to turn the previewInst on and off. * `triggerActivate, triggerDeactivate`: The outputs used when the trigger turns on or off. * `coopVar`: The instance variable which enables detecting both Coop players. The trigger will be a trigger_playerteam. * `coopActivate, coopDeactivate`: The outputs used when coopVar is enabled. These should be suitable for a logic_coop_manager. * `coopOnce`: If true, kill the manager after it first activates. * `keys`: A block of keyvalues for the trigger brush. Origin and targetname will be set automatically. * `localkeys`: The same as above, except values will be changed to use instance-local names. """ marker = instanceLocs.resolve(res['markerInst']) markers = {} for inst in vbsp.VMF.by_class['func_instance']: if inst['file'].casefold() in marker: markers[inst['targetname']] = inst if not markers: # No markers in the map - abort return RES_EXHAUSTED trig_act = res['triggerActivate', 'OnStartTouchAll'] trig_deact = res['triggerDeactivate', 'OnEndTouchAll'] coop_var = res['coopVar', None] coop_act = res['coopActivate', 'OnChangeToAllTrue'] coop_deact = res['coopDeactivate', 'OnChangeToAnyFalse'] coop_only_once = res.bool('coopOnce') marker_connection = conditions.CONNECTIONS[res['markerItem'].casefold()] mark_act_name, mark_act_out = marker_connection.out_act mark_deact_name, mark_deact_out = marker_connection.out_deact del marker_connection # Display preview overlays if it's preview mode, and the config is true if vbsp.IS_PREVIEW and vbsp_options.get_itemconf(res['previewConf', ''], False): preview_mat = res['previewMat', ''] preview_inst_file = res['previewInst', ''] pre_act_name, pre_act_inp = Output.parse_name(res['previewActivate', '']) pre_deact_name, pre_deact_inp = Output.parse_name( res['previewDeactivate', '']) preview_scale = srctools.conv_float(res['previewScale', '0.25'], 0.25) else: # Deactivate the preview_ options when publishing. preview_mat = preview_inst_file = '' pre_act_name = pre_deact_name = None pre_act_inp = pre_deact_inp = '' preview_scale = 0.25 # Now convert each brush # Use list() to freeze it, allowing us to delete from the dict for targ, inst in list(markers.items()): # type: str, VLib.Entity for out in inst.output_targets(): if out in markers: other = markers[out] # type: Entity del markers[out] # Don't let it get repeated break else: if inst.fixup['$connectioncount'] == '0': # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 1-block trigger. other = inst else: continue # It's a marker with an input, the other in the pair # will handle everything. for ent in {inst, other}: # Only do once if inst == other ent.remove() is_coop = coop_var is not None and vbsp.GAME_MODE == 'COOP' and ( inst.fixup.bool(coop_var) or other.fixup.bool(coop_var)) bbox_min, bbox_max = Vec.bbox(Vec.from_str(inst['origin']), Vec.from_str(other['origin'])) # Extend to the edge of the blocks. bbox_min -= 64 bbox_max += 64 out_ent = trig_ent = vbsp.VMF.create_ent( classname='trigger_multiple', # Default # Use the 1st instance's name - that way other inputs control the # trigger itself. targetname=targ, origin=inst['origin'], angles='0 0 0', ) trig_ent.solids = [ vbsp.VMF.make_prism( bbox_min, bbox_max, mat=const.Tools.TRIGGER, ).solid, ] # Use 'keys' and 'localkeys' blocks to set all the other keyvalues. conditions.set_ent_keys(trig_ent, inst, res) if is_coop: trig_ent['spawnflags'] = '1' # Clients trig_ent['classname'] = 'trigger_playerteam' out_ent_name = conditions.local_name(inst, 'man') out_ent = vbsp.VMF.create_ent(classname='logic_coop_manager', targetname=out_ent_name, origin=inst['origin']) if coop_only_once: # Kill all the ents when both players are present. out_ent.add_out( Output('OnChangeToAllTrue', out_ent_name, 'Kill'), Output('OnChangeToAllTrue', targ, 'Kill'), ) trig_ent.add_out( Output('OnStartTouchBluePlayer', out_ent_name, 'SetStateATrue'), Output('OnStartTouchOrangePlayer', out_ent_name, 'SetStateBTrue'), Output('OnEndTouchBluePlayer', out_ent_name, 'SetStateAFalse'), Output('OnEndTouchOrangePlayer', out_ent_name, 'SetStateBFalse'), ) act_out = coop_act deact_out = coop_deact else: act_out = trig_act deact_out = trig_deact if preview_mat: preview_brush = vbsp.VMF.create_ent( classname='func_brush', parentname=targ, origin=inst['origin'], Solidity='1', # Not solid drawinfastreflection='1', # Draw in goo.. # Disable shadows and lighting.. disableflashlight='1', disablereceiveshadows='1', disableshadowdepth='1', disableshadows='1', ) preview_brush.solids = [ # Make it slightly smaller, so it doesn't z-fight with surfaces. vbsp.VMF.make_prism( bbox_min + 0.5, bbox_max - 0.5, mat=preview_mat, ).solid, ] for face in preview_brush.sides(): face.scale = preview_scale if preview_inst_file: vbsp.VMF.create_ent( classname='func_instance', targetname=targ + '_preview', file=preview_inst_file, # Put it at the second marker, since that's usually # closest to antlines if present. origin=other['origin'], ) if pre_act_name and trig_act: out_ent.add_out( Output( trig_act, targ + '_preview', inst_in=pre_act_name, inp=pre_act_inp, )) if pre_deact_name and trig_deact: out_ent.add_out( Output( trig_deact, targ + '_preview', inst_in=pre_deact_name, inp=pre_deact_inp, )) # Now copy over the outputs from the markers, making it work. for out in inst.outputs + other.outputs: # Skip the output joining the two markers together. if out.target == other['targetname']: continue if out.inst_out == mark_act_name and out.output == mark_act_out: ent_out = act_out elif out.inst_out == mark_deact_name and out.output == mark_deact_out: ent_out = deact_out else: continue # Skip this output - it's somehow invalid for this item. if not ent_out: continue # Allow setting the output to "" to skip out_ent.add_out( Output( ent_out, out.target, inst_in=out.inst_in, inp=out.input, param=out.params, delay=out.delay, times=out.times, )) return RES_EXHAUSTED