def res_rand_inst_shift(inst: Entity, res: Property) -> None: """Randomly shift a instance by the given amounts. The positions are local to the instance. """ ( min_x, max_x, min_y, max_y, min_z, max_z, seed, ) = res.value # type: float, float, float, float, float, float, str set_random_seed(inst, seed) offset = Vec( random.uniform(min_x, max_x), random.uniform(min_y, max_y), random.uniform(min_z, max_z), ) offset.rotate_by_str(inst['angles']) origin = Vec.from_str(inst['origin']) origin += offset inst['origin'] = origin
def res_rand_inst_shift(inst: Entity, res: Property): """Randomly shift a instance by the given amounts. The positions are local to the instance. """ import vbsp ( min_x, max_x, min_y, max_y, min_z, max_z, ) = res.value random.seed(vbsp.MAP_RAND_SEED + '_random_shift_' + inst['origin'] + inst['angles']) offset = Vec( random.uniform(min_x, max_x), random.uniform(min_y, max_y), random.uniform(min_z, max_z), ) offset.rotate_by_str(inst['angles']) origin = Vec.from_str(inst['origin']) origin += offset inst['origin'] = origin
def comp_entity_mover(ctx: Context): """Move an entity to another location.""" for mover in ctx.vmf.by_class['comp_entity_mover']: mover.remove() ref_name = mover['reference'] offset = Vec() if ref_name: for ent in ctx.vmf.search(ref_name): offset = Vec.from_str(mover['origin']) - Vec.from_str(ent['origin']) offset *= conv_float(mover['distance']) break else: LOGGER.warning( 'Can\'t find ref entity named "{}" ' 'for comp_ent_mover at <{}>!', ref_name, mover['origin'], ) else: # Use angles + movement. offset = Vec(x=conv_float(mover['distance'])) offset.rotate_by_str(mover['direction']) found_ent = None for found_ent in ctx.vmf.search(mover['target']): origin = Vec.from_str(found_ent['origin']) origin += offset found_ent['origin'] = str(origin) if found_ent is None: LOGGER.warning( 'No entities found named "{}" for comp_ent_mover at ({})!', mover['target'], mover['origin'], )
def flag_angles(inst: Entity, flag: Property): """Check that a instance is pointed in a direction. The value should be either just the angle to check, or a block of options: - `direction`: A unit vector (XYZ value) pointing in a direction, or some keywords: `+z`, `-y`, `N`/`S`/`E`/`W`, `up`/`down`, `floor`/`ceiling`, or `walls` for any wall side. - `From_dir`: The direction the unrotated instance is pointed in. This lets the flag check multiple directions. - `Allow_inverse`: If true, this also returns True if the instance is pointed the opposite direction . """ angle = inst['angles', '0 0 0'] if flag.has_children(): targ_angle = flag['direction', '0 0 0'] from_dir = flag['from_dir', '0 0 1'] if from_dir.casefold() in DIRECTIONS: from_dir = Vec(DIRECTIONS[from_dir.casefold()]) else: from_dir = Vec.from_str(from_dir, 0, 0, 1) allow_inverse = flag.bool('allow_inverse') else: targ_angle = flag.value from_dir = Vec(0, 0, 1) allow_inverse = False normal = DIRECTIONS.get(targ_angle.casefold(), None) if normal is None: return False # If it's not a special angle, # so it failed the exact match inst_normal = from_dir.rotate_by_str(angle) if normal == 'WALL': # Special case - it's not on the floor or ceiling return not (inst_normal == (0, 0, 1) or inst_normal == (0, 0, -1)) else: return inst_normal == normal or (allow_inverse and -inst_normal == normal)
def flag_angles(inst: Entity, flag: Property): """Check that a instance is pointed in a direction. The value should be either just the angle to check, or a block of options: - `Angle`: A unit vector (XYZ value) pointing in a direction, or some keywords: `+z`, `-y`, `N`/`S`/`E`/`W`, `up`/`down`, `floor`/`ceiling`, or `walls` for any wall side. - `From_dir`: The direction the unrotated instance is pointed in. This lets the flag check multiple directions - `Allow_inverse`: If true, this also returns True if the instance is pointed the opposite direction . """ angle = inst['angles', '0 0 0'] if flag.has_children(): targ_angle = flag['direction', '0 0 0'] from_dir = flag['from_dir', '0 0 1'] if from_dir.casefold() in DIRECTIONS: from_dir = Vec(DIRECTIONS[from_dir.casefold()]) else: from_dir = Vec.from_str(from_dir, 0, 0, 1) allow_inverse = srctools.conv_bool(flag['allow_inverse', '0']) else: targ_angle = flag.value from_dir = Vec(0, 0, 1) allow_inverse = False normal = DIRECTIONS.get(targ_angle.casefold(), None) if normal is None: return False # If it's not a special angle, # so it failed the exact match inst_normal = from_dir.rotate_by_str(angle) if normal == 'WALL': # Special case - it's not on the floor or ceiling return not (inst_normal == (0, 0, 1) or inst_normal == (0, 0, -1)) else: return inst_normal == normal or ( allow_inverse and -inst_normal == normal )
def flag_angles(inst: Entity, flag: Property): """Check that a instance is pointed in a direction. The value should be either just the angle to check, or a block of options: - Angle: A unit vector (XYZ value) pointing in a direction, or some keywords: +z, -y, N/S/E/W, up/down, floor/ceiling, or walls - From_dir: The direction the unrotated instance is pointed in. This lets the flag check multiple directions - Allow_inverse: If true, this also returns True if the instance is pointed the opposite direction . """ angle = inst['angles', '0 0 0'] if flag.has_children(): targ_angle = flag['direction', '0 0 0'] from_dir = flag['from_dir', '0 0 1'] if from_dir.casefold() in DIRECTIONS: from_dir = Vec(DIRECTIONS[from_dir.casefold()]) else: from_dir = Vec.from_str(from_dir, 0, 0, 1) allow_inverse = srctools.conv_bool(flag['allow_inverse', '0']) else: targ_angle = flag.value from_dir = Vec(0, 0, 1) allow_inverse = False normal = DIRECTIONS.get(targ_angle.casefold(), None) if normal is None: return False # If it's not a special angle, # so it failed the exact match inst_normal = from_dir.rotate_by_str(angle) if normal == 'WALL': # Special case - it's not on the floor or ceiling return not (inst_normal == (0, 0, 1) or inst_normal == (0, 0, -1)) else: return inst_normal == normal or (allow_inverse and -inst_normal == normal)
def res_translate_inst(inst: Entity, res: Property): """Translate the instance locally by the given amount. The special values <piston>, <piston_bottom> and <piston_top> can be used to offset it based on the starting position, bottom or top position of a piston platform. """ folded_val = res.value.casefold() if folded_val == '<piston>': folded_val = ('<piston_top>' if srctools.conv_bool( inst.fixup['$start_up']) else '<piston_bottom>') if folded_val == '<piston_top>': val = Vec(z=128 * srctools.conv_int(inst.fixup['$top_level', '1'], 1)) elif folded_val == '<piston_bottom>': val = Vec(z=128 * srctools.conv_int(inst.fixup['$bottom_level', '0'], 0)) else: val = Vec.from_str(res.value) offset = val.rotate_by_str(inst['angles']) inst['origin'] = (offset + Vec.from_str(inst['origin'])).join(' ')
def res_translate_inst(inst: Entity, res: Property): """Translate the instance locally by the given amount. The special values <piston>, <piston_bottom> and <piston_top> can be used to offset it based on the starting position, bottom or top position of a piston platform. """ folded_val = res.value.casefold() if folded_val == '<piston>': folded_val = ( '<piston_top>' if srctools.conv_bool(inst.fixup['$start_up']) else '<piston_bottom>' ) if folded_val == '<piston_top>': val = Vec(z=128 * srctools.conv_int(inst.fixup['$top_level', '1'], 1)) elif folded_val == '<piston_bottom>': val = Vec(z=128 * srctools.conv_int(inst.fixup['$bottom_level', '0'], 0)) else: val = Vec.from_str(res.value) offset = val.rotate_by_str(inst['angles']) inst['origin'] = (offset + Vec.from_str(inst['origin'])).join(' ')
def res_conveyor_belt(inst: Entity, res: Property): """Create a conveyor belt. * Options: * `SegmentInst`: Generated at each square. (`track` is the name of the path to attach to.) * `TrackTeleport`: Set the track points so they teleport trains to the start. * `Speed`: The fixup or number for the train speed. * `MotionTrig`: If set, a trigger_multiple will be spawned that `EnableMotion`s weighted cubes. The value is the name of the relevant filter. * `EndOutput`: Adds an output to the last track. The value is the same as outputs in VMFs. `RotateSegments`: If true (default), force segments to face in the direction of movement. * `BeamKeys`: If set, a list of keyvalues to use to generate an env_beam travelling from start to end. `RailTemplate`: A template for the track sections. This is made into a non-solid func_brush, combining all sections. * `NoPortalFloor`: If set, add a `func_noportal_volume` on the floor under the track. * `PaintFizzler`: If set, add a paint fizzler underneath the belt. """ move_dist = srctools.conv_int(inst.fixup['$travel_distance']) if move_dist <= 2: # There isn't room for a catwalk, so don't bother. inst.remove() return move_dir = Vec(1, 0, 0).rotate_by_str(inst.fixup['$travel_direction']) move_dir.rotate_by_str(inst['angles']) start_offset = srctools.conv_float(inst.fixup['$starting_position'], 0) teleport_to_start = res.bool('TrackTeleport', True) segment_inst_file = instanceLocs.resolve_one(res['SegmentInst', '']) rail_template = res['RailTemplate', None] vmf = inst.map track_speed = res['speed', None] start_pos = Vec.from_str(inst['origin']) end_pos = start_pos + move_dist * move_dir if start_offset > 0: # If an oscillating platform, move to the closest side.. offset = start_offset * move_dir # The instance is placed this far along, so move back to the end. start_pos -= offset end_pos -= offset if start_offset > 0.5: # Swap the direction of movement.. start_pos, end_pos = end_pos, start_pos inst['origin'] = start_pos # Find the angle which generates an instance pointing in the direction # of movement, with the same normal. norm = Vec(z=1).rotate_by_str(inst['angles']) for roll in range(0, 360, 90): angles = move_dir.to_angle(roll) if Vec(z=1).rotate(*angles) == norm: break else: raise ValueError("Can't find angles to give a" ' z={} and x={}!'.format(norm, move_dir)) if res.bool('rotateSegments', True): inst['angles'] = angles else: angles = Vec.from_str(inst['angles']) # Add the EnableMotion trigger_multiple seen in platform items. # This wakes up cubes when it starts moving. motion_filter = res['motionTrig', None] # Disable on walls, or if the conveyor can't be turned on. if norm != (0, 0, 1) or inst.fixup['$connectioncount'] == '0': motion_filter = None track_name = conditions.local_name(inst, 'segment_{}') rail_temp_solids = [] last_track = None # Place beams at the top, so they don't appear inside wall sections. beam_start = start_pos + 48 * norm # type: Vec beam_end = end_pos + 48 * norm # type: Vec for index, pos in enumerate(beam_start.iter_line(beam_end, stride=128), start=1): track = vmf.create_ent( classname='path_track', targetname=track_name.format(index) + '-track', origin=pos, spawnflags=0, orientationtype=0, # Don't rotate ) if track_speed is not None: track['speed'] = track_speed if last_track: last_track['target'] = track['targetname'] if index == 1 and teleport_to_start: track['spawnflags'] = 16 # Teleport here.. last_track = track # Don't place at the last point - it doesn't teleport correctly, # and would be one too many. if segment_inst_file and pos != end_pos: seg_inst = vmf.create_ent( classname='func_instance', targetname=track_name.format(index), file=segment_inst_file, origin=pos, angles=angles, ) seg_inst.fixup.update(inst.fixup) if rail_template: temp = template_brush.import_template( rail_template, pos, angles, force_type=template_brush.TEMP_TYPES.world, add_to_map=False, ) rail_temp_solids.extend(temp.world) if rail_temp_solids: vmf.create_ent( classname='func_brush', origin=beam_start, spawnflags=1, # Ignore +USE solidity=1, # Not solid vrad_brush_cast_shadows=1, drawinfastreflection=1, ).solids = rail_temp_solids if teleport_to_start: # Link back to the first track.. last_track['target'] = track_name.format(1) + '-track' # Generate an env_beam pointing from the start to the end of the track. beam_keys = res.find_key('BeamKeys', []) if beam_keys.value: beam = vmf.create_ent(classname='env_beam') # 3 offsets - x = distance from walls, y = side, z = height beam_off = beam_keys.vec('origin', 0, 63, 56) for prop in beam_keys: beam[prop.real_name] = prop.value # Localise the targetname so it can be triggered.. beam['LightningStart'] = beam['targetname'] = conditions.local_name( inst, beam['targetname', 'beam']) del beam['LightningEnd'] beam['origin'] = start_pos + Vec( -beam_off.x, beam_off.y, beam_off.z, ).rotate(*angles) beam['TargetPoint'] = end_pos + Vec( +beam_off.x, beam_off.y, beam_off.z, ).rotate(*angles) # Allow adding outputs to the last path_track. for prop in res.find_all('EndOutput'): output = Output.parse(prop) output.output = 'OnPass' output.inst_out = None output.comma_sep = False output.target = conditions.local_name(inst, output.target) last_track.add_out(output) if motion_filter is not None: motion_trig = vmf.create_ent( classname='trigger_multiple', targetname=conditions.local_name(inst, 'enable_motion_trig'), origin=start_pos, filtername=motion_filter, startDisabled=1, wait=0.1, ) motion_trig.add_out( Output('OnStartTouch', '!activator', 'ExitDisabledState')) # Match the size of the original... motion_trig.solids.append( vmf.make_prism( start_pos + Vec(72, -56, 58).rotate(*angles), end_pos + Vec(-72, 56, 144).rotate(*angles), mat='tools/toolstrigger', ).solid) if res.bool('NoPortalFloor'): # Block portals on the floor.. floor_noportal = vmf.create_ent( classname='func_noportal_volume', origin=beam_start, ) floor_noportal.solids.append( vmf.make_prism( start_pos + Vec(-60, -60, -66).rotate(*angles), end_pos + Vec(60, 60, -60).rotate(*angles), mat='tools/toolsinvisible', ).solid) # A brush covering under the platform. base_trig = vmf.make_prism( start_pos + Vec(-64, -64, 48).rotate(*angles), end_pos + Vec(64, 64, 56).rotate(*angles), mat='tools/toolsinvisible', ).solid vmf.add_brush(base_trig) # Make a paint_cleanser under the belt.. if res.bool('PaintFizzler'): pfizz = vmf.create_ent( classname='trigger_paint_cleanser', origin=start_pos, ) pfizz.solids.append(base_trig.copy()) for face in pfizz.sides(): face.mat = 'tools/toolstrigger'
def res_camera(vmf: VMF, inst: Entity, res: Property): """Result for the camera item. Options: - cam_off: The position that the camera yaws around. - yaw_off: The offset from cam_off that the camera rotates up/down. - pitch_off: The offset from yaw_off that is where the sensor is. - yaw_inst: The instance to place for the yaw rotation. - pitch_inst: The instance to place for the up/down rotation. - yaw_range: How many degrees can the camera rotate from a forward position? - pitch_range: How many degrees can the camera rotate up/down? """ conf = res.value normal = Vec(0, 0, 1).rotate_by_str(inst['angles']) if normal.z != 0: # Can't be on floor/ceiling! inst.remove() return base_yaw = math.degrees(math.atan2(normal.y, normal.x)) % 360 inst['angles'] = '0 {:g} 0'.format(base_yaw) base_loc = Vec.from_str(inst['origin']) try: plate = faithplate.PLATES.pop(inst['targetname']) except KeyError: LOGGER.warning( 'No faith plate info found for camera {}!', inst['targetname'], ) inst.remove() return # Remove the triggers. plate.trig.remove() if isinstance(plate, faithplate.StraightPlate): # Just point straight ahead. target_loc = base_loc + 512 * normal # And remove the helper. plate.helper_trig.remove() else: if isinstance(plate.target, Vec): target_loc = plate.target else: # We don't particularly care about aiming to the front of angled # panels. target_loc = plate.target.pos + 64 * plate.target.normal # Remove the helper and a bullseye. plate.target.remove_portal_helper() plate.target.bullseye_count -= 1 # Move three times to position the camera arms and lens. yaw_pos = Vec(conf['yaw_off']).rotate_by_str(inst['angles']) yaw_pos += base_loc pitch, yaw, _ = (target_loc - yaw_pos).to_angle() conditions.add_inst( vmf, targetname=inst['targetname'], file=conf['yaw_inst'], angles=Angle(yaw=yaw), origin=yaw_pos, ) pitch_pos = Vec(conf['pitch_off']) pitch_pos.rotate(yaw=yaw) pitch_pos.rotate_by_str(inst['angles']) pitch_pos += yaw_pos conditions.add_inst( vmf, targetname=inst['targetname'], file=conf['pitch_inst'], angles=Angle(pitch, yaw, 0.0), origin=pitch_pos, ) cam_pos = Vec(conf['cam_off']) cam_pos.rotate(pitch=pitch, yaw=yaw) cam_pos += pitch_pos # Recompute, since this can be slightly different if the camera is large. cam_angles = (target_loc - cam_pos).to_angle() ALL_CAMERAS.append(Camera(inst, cam_pos, cam_angles))
def res_add_overlay_inst(inst: Entity, res: Property): """Add another instance on top of this one. Values: File: The filename. Fixup Style: The Fixup style for the instance. '0' (default) is Prefix, '1' is Suffix, and '2' is None. Copy_Fixup: If true, all the $replace values from the original instance will be copied over. move_outputs: If true, outputs will be moved to this instance. offset: The offset (relative to the base) that the instance will be placed. Can be set to '<piston_top>' and '<piston_bottom>' to offset based on the configuration. '<piston_start>' will set it to the starting position, and '<piston_end>' will set it to the ending position. of piston platform handles. angles: If set, overrides the base instance angles. This does not affect the offset property. fixup/localfixup: Keyvalues in this block will be copied to the overlay entity. If the value starts with $, the variable will be copied over. If this is present, copy_fixup will be disabled. """ angle = res["angles", inst["angles", "0 0 0"]] overlay_inst = vbsp.VMF.create_ent( classname="func_instance", targetname=inst["targetname", ""], file=resolve_inst(res["file", ""])[0], angles=angle, origin=inst["origin"], fixup_style=res["fixup_style", "0"], ) # Don't run if the fixup block exists.. if srctools.conv_bool(res["copy_fixup", "1"]): if "fixup" not in res and "localfixup" not in res: # Copy the fixup values across from the original instance for fixup, value in inst.fixup.items(): overlay_inst.fixup[fixup] = value conditions.set_ent_keys(overlay_inst.fixup, inst, res, "fixup") if res.bool("move_outputs", False): overlay_inst.outputs = inst.outputs inst.outputs = [] if "offset" in res: folded_off = res["offset"].casefold() # Offset the overlay by the given distance # Some special placeholder values: if folded_off == "<piston_start>": if srctools.conv_bool(inst.fixup["$start_up", ""]): folded_off = "<piston_top>" else: folded_off = "<piston_bottom>" elif folded_off == "<piston_end>": if srctools.conv_bool(inst.fixup["$start_up", ""]): folded_off = "<piston_bottom>" else: folded_off = "<piston_top>" if folded_off == "<piston_bottom>": offset = Vec(z=srctools.conv_int(inst.fixup["$bottom_level"]) * 128) elif folded_off == "<piston_top>": offset = Vec(z=srctools.conv_int(inst.fixup["$top_level"], 1) * 128) else: # Regular vector offset = Vec.from_str(conditions.resolve_value(inst, res["offset"])) offset.rotate_by_str(inst["angles", "0 0 0"]) overlay_inst["origin"] = (offset + Vec.from_str(inst["origin"])).join(" ") return overlay_inst
def res_conveyor_belt(inst: Entity, res: Property): """Create a conveyor belt. Options: SegmentInst: Generated at each square. ('track' is the name of the path.) TrackTeleport: Set the track points so they teleport trains to the start. Speed: The fixup or number for the train speed. MotionTrig: If set, a trigger_multiple will be spawned that EnableMotions weighted cubes. The value is the name of the relevant filter. EndOutput: Adds an output to the last track. The value is the same as outputs in VMFs. RotateSegments: If true (default), force segments to face in the direction of movement RailTemplate: A template for the railings. This is made into a non-solid func_brush, combining all sections. """ move_dist = srctools.conv_int(inst.fixup['$travel_distance']) if move_dist <= 2: # There isn't room for a catwalk, so don't bother. inst.remove() return move_dir = Vec(1, 0, 0).rotate_by_str(inst.fixup['$travel_direction']) move_dir.rotate_by_str(inst['angles']) start_offset = srctools.conv_float(inst.fixup['$starting_position'], 0) teleport_to_start = res.bool('TrackTeleport', True) segment_inst_file = res['SegmentInst', ''] rail_template = res['RailTemplate', None] vmf = inst.map if segment_inst_file: segment_inst_file = conditions.resolve_inst(segment_inst_file)[0] track_speed = res['speed', None] start_pos = Vec.from_str(inst['origin']) end_pos = start_pos + move_dist * move_dir if start_offset > 0: # If an oscillating platform, move to the closest side.. offset = start_offset * move_dir # The instance is placed this far along, so move back to the end. start_pos -= offset end_pos -= offset if start_offset > 0.5: # Swap the direction of movement.. start_pos, end_pos = end_pos, start_pos inst['origin'] = start_pos # Find the angle which generates an instance pointing in the direction # of movement, with the same normal. norm = Vec(z=1).rotate_by_str(inst['angles']) for roll in range(0, 360, 90): angles = move_dir.to_angle(roll) if Vec(z=1).rotate(*angles) == norm: break else: raise ValueError( "Can't find angles to give a" ' z={} and x={}!'.format(norm, move_dir) ) if res.bool('rotateSegments', True): inst['angles'] = angles else: angles = Vec.from_str(inst['angles']) # Add the EnableMotion trigger_multiple seen in platform items. # This wakes up cubes when it starts moving. motion_filter = res['motionTrig', None] # Disable on walls, or if the conveyor can't be turned on. if norm != (0, 0, 1) or inst.fixup['$connectioncount'] == '0': motion_filter = None track_name = conditions.local_name(inst, 'segment_{}') rail_temp_solids = [] last_track = None # Place beams at the top, so they don't appear inside wall sections. beam_start = start_pos + 48 * norm # type: Vec beam_end = end_pos + 48 * norm # type: Vec for index, pos in enumerate(beam_start.iter_line(beam_end, stride=128), start=1): track = vmf.create_ent( classname='path_track', targetname=track_name.format(index) + '-track', origin=pos, spawnflags=0, orientationtype=0, # Don't rotate ) if track_speed is not None: track['speed'] = track_speed if last_track: last_track['target'] = track['targetname'] if index == 1 and teleport_to_start: track['spawnflags'] = 16 # Teleport here.. last_track = track # Don't place at the last point - it doesn't teleport correctly, # and would be one too many. if segment_inst_file and pos != end_pos: seg_inst = vmf.create_ent( classname='func_instance', targetname=track_name.format(index), file=segment_inst_file, origin=pos, angles=angles, ) seg_inst.fixup.update(inst.fixup) if rail_template: temp = conditions.import_template( rail_template, pos, angles, force_type=conditions.TEMP_TYPES.world, add_to_map=False, ) rail_temp_solids.extend(temp.world) if rail_temp_solids: vmf.create_ent( classname='func_brush', origin=beam_start, spawnflags=1, # Ignore +USE solidity=1, # Not solid vrad_brush_cast_shadows=1, drawinfastreflection=1, ).solids = rail_temp_solids if teleport_to_start: # Link back to the first track.. last_track['target'] = track_name.format(1) + '-track' # Generate an env_beam pointing from the start to the end of the track. beam_keys = res.find_key('BeamKeys', []) if beam_keys.value: beam = vmf.create_ent(classname='env_beam') # 3 offsets - x = distance from walls, y = side, z = height beam_off = beam_keys.vec('origin', 0, 63, 56) for prop in beam_keys: beam[prop.real_name] = prop.value # Localise the targetname so it can be triggered.. beam['LightningStart'] = beam['targetname'] = conditions.local_name( inst, beam['targetname', 'beam'] ) del beam['LightningEnd'] beam['origin'] = start_pos + Vec( -beam_off.x, beam_off.y, beam_off.z, ).rotate(*angles) beam['TargetPoint'] = end_pos + Vec( +beam_off.x, beam_off.y, beam_off.z, ).rotate(*angles) # Allow adding outputs to the last path_track. for prop in res.find_all('EndOutput'): output = Output.parse(prop) output.output = 'OnPass' output.inst_out = None output.comma_sep = False output.target = conditions.local_name(inst, output.target) last_track.add_out(output) if motion_filter is not None: motion_trig = vmf.create_ent( classname='trigger_multiple', targetname=conditions.local_name(inst, 'enable_motion_trig'), origin=start_pos, filtername=motion_filter, startDisabled=1, wait=0.1, ) motion_trig.add_out(Output('OnStartTouch', '!activator', 'ExitDisabledState')) # Match the size of the original... motion_trig.solids.append(vmf.make_prism( start_pos + Vec(72, -56, 58).rotate(*angles), end_pos + Vec(-72, 56, 144).rotate(*angles), mat='tools/toolstrigger', ).solid) if res.bool('NoPortalFloor'): # Block portals on the floor.. floor_noportal = vmf.create_ent( classname='func_noportal_volume', origin=beam_start, ) floor_noportal.solids.append(vmf.make_prism( start_pos + Vec(-60, -60, -66).rotate(*angles), end_pos + Vec(60, 60, -60).rotate(*angles), mat='tools/toolsinvisible', ).solid) # A brush covering under the platform. base_trig = vmf.make_prism( start_pos + Vec(-64, -64, 48).rotate(*angles), end_pos + Vec(64, 64, 56).rotate(*angles), mat='tools/toolsinvisible', ).solid vmf.add_brush(base_trig) # Make a paint_cleanser under the belt.. if res.bool('PaintFizzler'): pfizz = vmf.create_ent( classname='trigger_paint_cleanser', origin=start_pos, ) pfizz.solids.append(base_trig.copy()) for face in pfizz.sides(): face.mat = 'tools/toolstrigger'
def res_camera(inst: Entity, res: Property): """Result for the camera component. """ conf = res.value normal = Vec(0, 0, 1).rotate_by_str(inst['angles']) if normal.z != 0: # Can't be on floor/ceiling! inst.remove() return base_yaw = math.degrees(math.atan2(normal.y, normal.x)) % 360 inst['angles'] = '0 {:g} 0'.format(base_yaw) inst_name = inst['targetname'] try: target, = inst.map.by_target[inst_name + '-target'] # type: Entity except ValueError: # No targets with that name inst.remove() return for trig in inst.map.by_class['trigger_catapult']: # type: Entity if trig['targetname'].startswith(inst_name): trig.remove() target_loc = Vec.from_str(target['origin']) target.remove() # Not needed... BULLSYE_LOCS[target_loc.as_tuple()] += 1 base_loc = Vec.from_str(inst['origin']) # Move three times to position the camera arms and lens. yaw_pos = Vec(conf['yaw_off']).rotate_by_str(inst['angles']) yaw_pos += base_loc pitch, yaw, _ = (target_loc - yaw_pos).to_angle() inst.map.create_ent( classname='func_instance', targetname=inst['targetname'], file=conf['yaw_inst'], angles='0 {:g} 0'.format(yaw), origin=yaw_pos, ) pitch_pos = Vec(conf['pitch_off']) pitch_pos.rotate(yaw=yaw) pitch_pos.rotate_by_str(inst['angles']) pitch_pos += yaw_pos inst.map.create_ent( classname='func_instance', targetname=inst['targetname'], file=conf['pitch_inst'], angles='{:g} {:g} 0'.format(pitch, yaw), origin=pitch_pos, ) cam_pos = Vec(conf['cam_off']) cam_pos.rotate(pitch=pitch, yaw=yaw) cam_pos += pitch_pos # Recompute, since this can be slightly different if the camera is large. cam_angles = (target_loc - cam_pos).to_angle() ALL_CAMERAS.append(Camera(inst, res.value, cam_pos, cam_angles))
def res_camera(inst: Entity, res: Property): """Result for the camera component. """ conf = res.value normal = Vec(0, 0, 1).rotate_by_str(inst['angles']) if normal.z != 0: # Can't be on floor/ceiling! inst.remove() return base_yaw = math.degrees(math.atan2(normal.y, normal.x)) % 360 inst['angles'] = '0 {:g} 0'.format(base_yaw) inst_name = inst['targetname'] try: [target] = inst.map.by_target[inst_name + '-target'] # type: Entity except ValueError: # No targets with that name inst.remove() return for trig in inst.map.by_class['trigger_catapult']: # type: Entity if trig['targetname'].startswith(inst_name): trig.remove() target_loc = Vec.from_str(target['origin']) target.remove() # Not needed... BULLSYE_LOCS[target_loc.as_tuple()] += 1 base_loc = Vec.from_str(inst['origin']) # Move three times to position the camera arms and lens. yaw_pos = Vec(conf['yaw_off']).rotate_by_str(inst['angles']) yaw_pos += base_loc pitch, yaw, _ = (target_loc - yaw_pos).to_angle() inst.map.create_ent( classname='func_instance', targetname=inst['targetname'], file=conf['yaw_inst'], angles='0 {:g} 0'.format(yaw), origin=yaw_pos, ) pitch_pos = Vec(conf['pitch_off']) pitch_pos.rotate(yaw=yaw) pitch_pos.rotate_by_str(inst['angles']) pitch_pos += yaw_pos inst.map.create_ent( classname='func_instance', targetname=inst['targetname'], file=conf['pitch_inst'], angles='{:g} {:g} 0'.format(pitch, yaw), origin=pitch_pos, ) cam_pos = Vec(conf['cam_off']) cam_pos.rotate(pitch=pitch, yaw=yaw) cam_pos += pitch_pos # Recompute, since this can be slightly different if the camera is large. cam_angles = (target_loc - cam_pos).to_angle() ALL_CAMERAS.append(Camera(inst, res.value, cam_pos, cam_angles))
def res_add_overlay_inst(inst: Entity, res: Property): """Add another instance on top of this one. Values: File: The filename. Fixup Style: The Fixup style for the instance. '0' (default) is Prefix, '1' is Suffix, and '2' is None. Copy_Fixup: If true, all the $replace values from the original instance will be copied over. move_outputs: If true, outputs will be moved to this instance. offset: The offset (relative to the base) that the instance will be placed. Can be set to '<piston_top>' and '<piston_bottom>' to offset based on the configuration. '<piston_start>' will set it to the starting position, and '<piston_end>' will set it to the ending position. of piston platform handles. angles: If set, overrides the base instance angles. This does not affect the offset property. fixup/localfixup: Keyvalues in this block will be copied to the overlay entity. If the value starts with $, the variable will be copied over. If this is present, copy_fixup will be disabled. """ angle = res['angles', inst['angles', '0 0 0']] overlay_inst = vbsp.VMF.create_ent( classname='func_instance', targetname=inst['targetname', ''], file=resolve_inst(res['file', ''])[0], angles=angle, origin=inst['origin'], fixup_style=res['fixup_style', '0'], ) # Don't run if the fixup block exists.. if srctools.conv_bool(res['copy_fixup', '1']): if 'fixup' not in res and 'localfixup' not in res: # Copy the fixup values across from the original instance for fixup, value in inst.fixup.items(): overlay_inst.fixup[fixup] = value conditions.set_ent_keys(overlay_inst.fixup, inst, res, 'fixup') if res.bool('move_outputs', False): overlay_inst.outputs = inst.outputs inst.outputs = [] if 'offset' in res: folded_off = res['offset'].casefold() # Offset the overlay by the given distance # Some special placeholder values: if folded_off == '<piston_start>': if srctools.conv_bool(inst.fixup['$start_up', '']): folded_off = '<piston_top>' else: folded_off = '<piston_bottom>' elif folded_off == '<piston_end>': if srctools.conv_bool(inst.fixup['$start_up', '']): folded_off = '<piston_bottom>' else: folded_off = '<piston_top>' if folded_off == '<piston_bottom>': offset = Vec( z=srctools.conv_int(inst.fixup['$bottom_level']) * 128, ) elif folded_off == '<piston_top>': offset = Vec( z=srctools.conv_int(inst.fixup['$top_level'], 1) * 128, ) else: # Regular vector offset = Vec.from_str(conditions.resolve_value(inst, res['offset'])) offset.rotate_by_str( inst['angles', '0 0 0'] ) overlay_inst['origin'] = ( offset + Vec.from_str(inst['origin']) ).join(' ') return overlay_inst