def modify_platform(inst: Entity) -> None: """Modify each platform.""" min_pos = inst.fixup.int(FixupVars.PIST_BTM) max_pos = inst.fixup.int(FixupVars.PIST_TOP) start_up = inst.fixup.bool(FixupVars.PIST_IS_UP) # Allow doing variable lookups here. visgroup_names = [ conditions.resolve_value(inst, fname) for fname in conf_visgroup_names ] if len(ITEMS[inst['targetname']].inputs) == 0: # No inputs. Check for the 'auto' var if applicable. if automatic_var and inst.fixup.bool(automatic_var): pass # The item is automatically moving, so we generate the dynamics. else: # It's static, we just make that and exit. position = max_pos if start_up else min_pos inst.fixup[FixupVars.PIST_BTM] = position inst.fixup[FixupVars.PIST_TOP] = position static_inst = inst.copy() vmf.add_ent(static_inst) static_inst['file'] = fname = inst_filenames['fullstatic_' + str(position)] conditions.ALL_INST.add(fname) return init_script = 'SPAWN_UP <- {}'.format('true' if start_up else 'false') if snd_start and snd_stop: packing.pack_files(vmf, snd_start, snd_stop, file_type='sound') init_script += '; START_SND <- `{}`; STOP_SND <- `{}`'.format( snd_start, snd_stop) elif snd_start: packing.pack_files(vmf, snd_start, file_type='sound') init_script += '; START_SND <- `{}`'.format(snd_start) elif snd_stop: packing.pack_files(vmf, snd_stop, file_type='sound') init_script += '; STOP_SND <- `{}`'.format(snd_stop) script_ent = vmf.create_ent( classname='info_target', targetname=conditions.local_name(inst, 'script'), vscripts='BEE2/piston/common.nut', vscript_init_code=init_script, origin=inst['origin'], ) if has_dn_fizz: script_ent['thinkfunction'] = 'FizzThink' if start_up: st_pos, end_pos = max_pos, min_pos else: st_pos, end_pos = min_pos, max_pos script_ent.add_out( Output('OnUser1', '!self', 'RunScriptCode', f'moveto({st_pos})'), Output('OnUser2', '!self', 'RunScriptCode', f'moveto({end_pos})'), ) origin = Vec.from_str(inst['origin']) orient = Matrix.from_angle(Angle.from_str(inst['angles'])) off = orient.up(128) move_ang = off.to_angle() # Index -> func_movelinear. pistons: dict[int, Entity] = {} static_ent = vmf.create_ent('func_brush', origin=origin) for pist_ind in [1, 2, 3, 4]: pist_ent = inst.copy() vmf.add_ent(pist_ent) if pist_ind <= min_pos: # It's below the lowest position, so it can be static. pist_ent['file'] = fname = inst_filenames['static_' + str(pist_ind)] pist_ent['origin'] = brush_pos = origin + pist_ind * off temp_targ = static_ent else: # It's a moving component. pist_ent['file'] = fname = inst_filenames['dynamic_' + str(pist_ind)] if pist_ind > max_pos: # It's 'after' the highest position, so it never extends. # So simplify by merging those all. # The max pos was evaluated earlier, so this must be set. temp_targ = pistons[max_pos] if start_up: pist_ent['origin'] = brush_pos = origin + max_pos * off else: pist_ent['origin'] = brush_pos = origin + min_pos * off pist_ent.fixup['$parent'] = 'pist' + str(max_pos) else: # It's actually a moving piston. if start_up: brush_pos = origin + pist_ind * off else: brush_pos = origin + min_pos * off pist_ent['origin'] = brush_pos pist_ent.fixup['$parent'] = 'pist' + str(pist_ind) pistons[pist_ind] = temp_targ = vmf.create_ent( 'func_movelinear', targetname=conditions.local_name( pist_ent, f'pist{pist_ind}'), origin=brush_pos - off, movedir=move_ang, startposition=start_up, movedistance=128, speed=150, ) if pist_ind - 1 in pistons: pistons[pist_ind][ 'parentname'] = conditions.local_name( pist_ent, f'pist{pist_ind - 1}', ) if fname: conditions.ALL_INST.add(fname.casefold()) else: # No actual instance, remove. pist_ent.remove() temp_result = template_brush.import_template( vmf, template, brush_pos, orient, force_type=template_brush.TEMP_TYPES.world, add_to_map=False, additional_visgroups={visgroup_names[pist_ind - 1]}, ) temp_targ.solids.extend(temp_result.world) template_brush.retexture_template( temp_result, origin, pist_ent.fixup, generator=GenCat.PANEL, ) # Associate any set panel with the same entity, if it's present. tile_pos = origin - orient.up(128) panel: Optional[Panel] = None try: tiledef = TILES[tile_pos.as_tuple(), off.norm().as_tuple()] except KeyError: pass else: for panel in tiledef.panels: if panel.same_item(inst): break else: # Checked all of them. panel = None if panel is not None: if panel.brush_ent in vmf.entities and not panel.brush_ent.solids: panel.brush_ent.remove() panel.brush_ent = pistons[max(pistons.keys())] panel.offset = st_pos * off if not static_ent.solids and (panel is None or panel.brush_ent is not static_ent): static_ent.remove() if snd_loop: script_ent['classname'] = 'ambient_generic' script_ent['message'] = snd_loop script_ent['health'] = 10 # Volume script_ent['pitch'] = 100 script_ent['spawnflags'] = 16 # Start silent, looped. script_ent['radius'] = 1024 if source_ent: # Parent is irrelevant for actual entity locations, but it # survives for the script to read. script_ent['SourceEntityName'] = script_ent[ 'parentname'] = conditions.local_name(inst, source_ent)
def add_timer_relay(item: Item, has_sounds: bool) -> None: """Make a relay to play timer sounds, or fire once the outputs are done.""" assert item.timer is not None rl_name = item.name + '_timer_rl' relay = item.inst.map.create_ent( 'logic_relay', targetname=rl_name, startDisabled=0, spawnflags=0, ) if item.config.timer_sound_pos: relay_loc = item.config.timer_sound_pos.copy() relay_loc.localise( Vec.from_str(item.inst['origin']), Angle.from_str(item.inst['angles']), ) relay['origin'] = relay_loc else: relay['origin'] = item.inst['origin'] for cmd in item.config.timer_done_cmd: if cmd: relay.add_out( Output( 'OnTrigger', conditions.local_name(item.inst, cmd.target) or item.inst, conditions.resolve_value(item.inst, cmd.input), conditions.resolve_value(item.inst, cmd.params), inst_in=cmd.inst_in, delay=item.timer + cmd.delay, times=cmd.times, )) if item.config.timer_sound_pos is not None and has_sounds: timer_sound = options.get(str, 'timer_sound') timer_cc = options.get(str, 'timer_sound_cc') # The default sound has 'ticking' closed captions. # So reuse that if the style doesn't specify a different noise. # If explicitly set to '', we don't use this at all! if timer_cc is None and timer_sound != 'Portal.room1_TickTock': timer_cc = 'Portal.room1_TickTock' if timer_cc: timer_cc = 'cc_emit ' + timer_cc # Write out the VScript code to precache the sound, and play it on # demand. relay['vscript_init_code'] = ( 'function Precache() {' f'self.PrecacheSoundScript(`{timer_sound}`)' '}') relay['vscript_init_code2'] = ('function snd() {' f'self.EmitSound(`{timer_sound}`)' '}') packing.pack_files(item.inst.map, timer_sound, file_type='sound') for delay in range(item.timer): relay.add_out( Output( 'OnTrigger', '!self', 'CallScriptFunction', 'snd', delay=delay, )) if timer_cc: relay.add_out( Output( 'OnTrigger', '@command', 'Command', timer_cc, delay=delay, )) for outputs, cmd in [(item.timer_output_start(), 'Trigger'), (item.timer_output_stop(), 'CancelPending')]: for output in outputs: item.add_io_command(output, rl_name, cmd)
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, ))