def parse_map(vmf: VMF, has_attr: Dict[str, bool]) -> None: """Find all glass/grating in the map. This removes the per-tile instances, and all original brushwork. The frames are updated with a fixup var, as appropriate. """ frame_inst = resolve('[glass_frames]', silent=True) glass_inst = resolve_one('[glass_128]') pos = None for brush_ent in vmf.by_class['func_detail']: is_glass = False for face in brush_ent.sides(): if face.mat == consts.Special.GLASS: has_attr['glass'] = True pos = face.get_origin() is_glass = True break if is_glass: brush_ent.remove() BARRIERS[get_pos_norm(pos)] = BarrierType.GLASS for brush_ent in vmf.by_class['func_brush']: is_grating = False for face in brush_ent.sides(): if face.mat == consts.Special.GRATING: has_attr['grating'] = True pos = face.get_origin() is_grating = True break if is_grating: brush_ent.remove() BARRIERS[get_pos_norm(pos)] = BarrierType.GRATING for inst in vmf.by_class['func_instance']: filename = inst['file'].casefold() if filename == glass_inst: inst.remove() elif filename in frame_inst: # Add a fixup to allow distinguishing the type. pos = Vec.from_str(inst['origin']) // 128 * 128 + (64, 64, 64) norm = Vec(z=-1) @ Angle.from_str(inst['angles']) try: inst.fixup[consts.FixupVars.BEE_GLS_TYPE] = BARRIERS[ pos.as_tuple(), norm.as_tuple()].value except KeyError: LOGGER.warning('No glass/grating for frame at {}, {}?', pos, norm) if options.get(str, 'glass_pack') and has_attr['glass']: packing.pack_list(vmf, options.get(str, 'glass_pack'))
def parse_map(vmf: VMF, has_attr: Dict[str, bool]) -> None: """Remove instances from the map, and store off the positions.""" glass_inst = resolve_one('[glass_128]') pos = None for brush_ent in vmf.by_class['func_detail']: is_glass = False for face in brush_ent.sides(): if face.mat == consts.Special.GLASS: has_attr['glass'] = True pos = face.get_origin() is_glass = True break if is_glass: brush_ent.remove() BARRIERS[get_pos_norm(pos)] = BarrierType.GLASS for brush_ent in vmf.by_class['func_brush']: is_grating = False for face in brush_ent.sides(): if face.mat == consts.Special.GRATING: has_attr['grating'] = True pos = face.get_origin() is_grating = True break if is_grating: brush_ent.remove() BARRIERS[get_pos_norm(pos)] = BarrierType.GRATING for inst in vmf.by_class['func_instance']: filename = inst['file'].casefold() if filename == glass_inst: inst.remove() if options.get(str, 'glass_pack') and has_attr['glass']: packing.pack_list(vmf, options.get(str, 'glass_pack'))
def add_quote( vmf: VMF, quote: Property, targetname: str, quote_loc: Vec, style_vars: dict, use_dings: bool, ) -> None: """Add a quote to the map.""" LOGGER.info('Adding quote: {}', quote) only_once = atomic = False cc_emit_name = None start_ents = [] # type: List[Entity] end_commands = [] start_names = [] # The OnUser1 outputs always play the quote (PlaySound/Start), so you can # mix ent types in the same pack. for prop in quote: name = prop.name.casefold() if name == 'file': vmf.create_ent( classname='func_instance', targetname='', file=INST_PREFIX + prop.value, origin=quote_loc, fixup_style='2', # No fixup ) elif name == 'choreo': # If the property has children, the children are a set of sequential # voice lines. # If the name is set to '@glados_line', the ents will be named # ('@glados_line', 'glados_line_2', 'glados_line_3', ...) start_names.append(targetname) if prop.has_children(): secondary_name = targetname.lstrip('@') + '_' # Evenly distribute the choreo ents across the width of the # voice-line room. off = Vec(y=120 / (len(prop) + 1)) start = quote_loc - (0, 60, 0) + off for ind, choreo_line in enumerate( prop, start=1): # type: int, Property is_first = (ind == 1) is_last = (ind == len(prop)) name = (targetname if is_first else secondary_name + str(ind)) choreo = add_choreo( vmf, choreo_line.value, targetname=name, loc=start + off * (ind - 1), use_dings=use_dings, is_first=is_first, is_last=is_last, only_once=only_once, ) # Add a IO command to start the next one. if not is_last: choreo.add_out( Output( 'OnCompletion', secondary_name + str(ind + 1), 'Start', delay=0.1, )) if is_first: # Ensure this works with cc_emit start_ents.append(choreo) if is_last: for out in end_commands: choreo.add_out(out.copy()) end_commands.clear() else: # Add a single choreo command. choreo = add_choreo( vmf, prop.value, targetname, quote_loc, use_dings=use_dings, only_once=only_once, ) start_ents.append(choreo) for out in end_commands: choreo.add_out(out.copy()) end_commands.clear() elif name == 'snd': start_names.append(targetname) snd = vmf.create_ent( classname='ambient_generic', spawnflags='49', # Infinite Range, Starts Silent targetname=targetname, origin=quote_loc, message=prop.value, health='10', # Volume ) snd.add_out( Output( 'OnUser1', targetname, 'PlaySound', only_once=only_once, )) start_ents.append(snd) elif name == 'bullseye': add_bullseye(vmf, quote_loc, prop.value) elif name == 'cc_emit': # In Aperture Tag, this additional console command is used # to add the closed captions. # Store in a variable, so we can be sure to add the output # regardless of the property order. cc_emit_name = prop.value elif name == 'setstylevar': # Set this stylevar to True # This is useful so some styles can react to which line was # chosen. style_vars[prop.value.casefold()] = True elif name == 'packlist': packing.pack_list(vmf, prop.value) elif name == 'pack': if prop.has_children(): packing.pack_files(vmf, *[subprop.value for subprop in prop]) else: packing.pack_files(vmf, prop.value) elif name == 'choreo_name': # Change the targetname used for subsequent entities targetname = prop.value elif name == 'onlyonce': only_once = srctools.conv_bool(prop.value) elif name == 'atomic': atomic = srctools.conv_bool(prop.value) elif name == 'endcommand': end_commands.append( Output( 'OnCompletion', prop['target'], prop['input'], prop['parm', ''], prop.float('delay'), only_once=prop.bool('only_once'), times=prop.int('times', -1), )) if cc_emit_name: for ent in start_ents: ent.add_out( Output( 'OnUser1', '@command', 'Command', param='cc_emit ' + cc_emit_name, )) # If Atomic is true, after a line is started all variants # are blocked from playing. if atomic: for ent in start_ents: for name in start_names: if ent['targetname'] == name: # Don't block yourself. continue ent.add_out(Output( 'OnUser1', name, 'Kill', only_once=True, ))