def res_conveyor_belt(vmf: VMF, inst: Entity, res: Property) -> None: """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. The origin is treated specially - X is the distance from walls, y is the distance to the side, and z is the height. `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 = inst.fixup.int('$travel_distance') if move_dist <= 2: # There isn't room for a conveyor, so don't bother. inst.remove() return move_dir = Vec(1, 0, 0).rotate_by_str(inst.fixup['$travel_direction']) move_dir = move_dir.rotate_by_str(inst['angles']) start_offset = inst.fixup.float('$starting_position') teleport_to_start = res.bool('TrackTeleport', True) segment_inst_file = instanceLocs.resolve_one(res['SegmentInst', '']) rail_template = res['RailTemplate', None] 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 norm = Vec(z=1).rotate_by_str(inst['angles']) if res.bool('rotateSegments', True): inst['angles'] = angles = move_dir.to_angle_roll(norm) 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 tracks at the top, so they don't appear inside wall sections. track_start: Vec = start_pos + 48 * norm track_end: Vec = end_pos + 48 * norm for index, pos in enumerate(track_start.iter_line(track_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 != track_end: 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( vmf, 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=track_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. try: beam_keys = res.find_key('BeamKeys') except LookupError: pass else: beam = vmf.create_ent(classname='env_beam') 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=consts.Tools.TRIGGER, ).solid) if res.bool('NoPortalFloor'): # Block portals on the floor.. floor_noportal = vmf.create_ent( classname='func_noportal_volume', origin=track_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=consts.Tools.INVISIBLE, ).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=consts.Tools.INVISIBLE, ).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 = consts.Tools.TRIGGER
def make_frames(vmf: VMF, targ: str, conf: dict, bbox_min: Vec, bbox_max: Vec, norm: Vec): """Generate frames for a rectangular glass item.""" def make_frame(frame_type, loc, angles): """Make a frame instance.""" vmf.create_ent( classname='func_instance', targetname=targ, file=conf['frame_' + frame_type], # Position at the center of the block, instead of at the glass. origin=loc - norm * 64, angles=angles, ) if bbox_min == bbox_max: # 1x1 glass.. make_frame('single', bbox_min, norm.to_angle()) return norm_axis = norm.axis() u_axis, v_axis = Vec.INV_AXIS[norm_axis] u_norm = Vec() v_norm = Vec() u_norm[u_axis] = 1 v_norm[v_axis] = 1 single_u = bbox_min[u_axis] == bbox_max[u_axis] single_v = bbox_min[v_axis] == bbox_max[v_axis] # If single in either direction, it needs a u-bend. if single_u: ubend_axis = v_axis elif single_v: ubend_axis = u_axis else: ubend_axis = None if ubend_axis is not None: for bend_mag, bbox in [(1, bbox_min), (-1, bbox_max)]: make_frame( 'ubend', bbox, norm.to_angle_roll(Vec.with_axes(ubend_axis, bend_mag)), ) else: # Make 4 corners - one in each roll direction. for roll in range(0, 360, 90): angles = norm.to_angle(roll) # The two directions with a border in the corner instance. # We want to put it on those sides. corner_a = Vec(y=-1).rotate(*angles) corner_b = Vec(z=-1).rotate(*angles) # If the normal is positive, we want to be bbox_max in that axis, # otherwise bbox_min. pos = Vec.with_axes( norm_axis, bbox_min, corner_a.axis(), (bbox_max if corner_a >= (0, 0, 0) else bbox_min), corner_b.axis(), (bbox_max if corner_b >= (0, 0, 0) else bbox_min), ) make_frame( 'corner', pos, angles, ) # Make straight sections. straight_u_pos = norm.to_angle_roll(v_norm) straight_u_neg = norm.to_angle_roll(-v_norm) straight_v_pos = norm.to_angle_roll(u_norm) straight_v_neg = norm.to_angle_roll(-u_norm) for u_pos in range(int(bbox_min[u_axis] + 128), int(bbox_max[u_axis]), 128): make_frame( 'edge', Vec.with_axes(u_axis, u_pos, v_axis, bbox_min, norm_axis, bbox_min), straight_u_pos, ) make_frame( 'edge', Vec.with_axes(u_axis, u_pos, v_axis, bbox_max, norm_axis, bbox_min), straight_u_neg, ) for v_pos in range(int(bbox_min[v_axis] + 128), int(bbox_max[v_axis]), 128): make_frame( 'edge', Vec.with_axes(v_axis, v_pos, u_axis, bbox_min, norm_axis, bbox_min), straight_v_pos, ) make_frame( 'edge', Vec.with_axes(v_axis, v_pos, u_axis, bbox_max, norm_axis, bbox_min), straight_v_neg, )