예제 #1
0
    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)
예제 #2
0
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)
예제 #3
0
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,
                ))