Exemplo n.º 1
0
def res_random_setup(res):
    weight = ''
    results = []
    chance = 100
    seed = ''
    for prop in res:
        if prop.name == 'chance':
            # Allow ending with '%' sign
            chance = utils.conv_int(
                prop.value.rstrip('%'),
                chance,
            )
        elif prop.name == 'weights':
            weight = prop.value
        elif prop.name == 'seed':
            seed = prop.value
        else:
            results.append(prop)

    if not results:
        return None  # Invalid!

    weight = conditions.weighted_random(len(results), weight)

    # We also need to execute result setups on all child properties!
    for prop in results[:]:
        if prop.name == 'group':
            for sub_prop in prop.value[:]:
                Condition.setup_result(prop.value, sub_prop)
        else:
            Condition.setup_result(results, prop)

    return seed, chance, weight, results
Exemplo n.º 2
0
    def parse(cls, data):
        """Parse a voice line definition."""
        selitem_data = get_selitem_data(data.info)
        chars = {
            char.strip()
            for char in
            data.info['characters', ''].split(',')
            if char.strip()
        }

        # For Cave Johnson voicelines, this indicates what skin to use on the
        # portrait.
        port_skin = utils.conv_int(data.info['caveSkin', None], None)

        config = get_config(
            data.info,
            data.zip_file,
            'voice',
            pak_id=data.pak_id,
            prop_name='file',
        )

        return cls(
            data.id,
            selitem_data,
            config,
            chars=chars,
            skin=port_skin,
            )
Exemplo n.º 3
0
def res_faith_mods(inst, res):
    """Modify the trigger_catrapult that is created for ItemFaithPlate items.

    """
    # Get data about the trigger this instance uses for flinging
    fixup_var = res["instvar", ""]
    offset = utils.conv_int(res["raise_trig", "0"])
    if offset:
        angle = Vec.from_str(inst["angles", "0 0 0"])
        offset = Vec(0, 0, offset).rotate(angle.x, angle.y, angle.z)
        ":type offset Vec"
    for trig in VMF.by_class["trigger_catapult"]:
        if inst["targetname"] in trig["targetname"]:
            if offset:  # Edit both the normal and the helper trigger
                trig["origin"] = (Vec.from_str(trig["origin"]) + offset).join(" ")
                for solid in trig.solids:
                    solid.translate(offset)

            for out in trig.outputs:
                if out.inst_in == "animate_angled_relay":
                    out.inst_in = res["angled_targ", "animate_angled_relay"]
                    out.input = res["angled_in", "Trigger"]
                    if fixup_var:
                        inst.fixup[fixup_var] = "angled"
                    break
                elif out.inst_in == "animate_straightup_relay":
                    out.inst_in = res["straight_targ", "animate_straightup_relay"]
                    out.input = res["straight_in", "Trigger"]
                    if fixup_var:
                        inst.fixup[fixup_var] = "straight"
                    break
Exemplo n.º 4
0
def res_add_variant_setup(res):
    count = utils.conv_int(res['Number', ''], None)
    if count:
        return conditions.weighted_random(
            count,
            res['weights', ''],
        )
    else:
        return None
Exemplo n.º 5
0
 def test_conv_int_fails_on_float(self):
     # Check that float values fail
     marker = object()
     for string, result in floats:
         if isinstance(string, str):  # We don't want to check float-rounding
             self.assertIs(
                 utils.conv_int(string, marker),
                 marker,
                 msg=string,
             )
Exemplo n.º 6
0
def res_translate_inst(inst, res):
    """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
            utils.conv_bool(inst.fixup['$start_up'])
            else '<piston_bottom>'
        )

    if folded_val == '<piston_top>':
        val = Vec(z=128 * utils.conv_int(inst.fixup['$top_level', '1'], 1))
    elif folded_val == '<piston_bottom>':
        val = Vec(z=128 * utils.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(' ')
Exemplo n.º 7
0
def res_vactube_setup(res):
    group = res['group', 'DEFAULT_GROUP']

    if group not in VAC_CONFIGS:
        # Store our values in the CONFIGS dictionary
        config, inst_configs = VAC_CONFIGS[group] = {}, {}
    else:
        # Grab the already-filled values, and add to them
        config, inst_configs = VAC_CONFIGS[group]

    for block in res.find_all("Instance"):
        # Configuration info for each instance set..
        conf = {
            # The three sizes of corner instance
            ('corner', 1): block['corner_small_inst', ''],
            ('corner', 2): block['corner_medium_inst', ''],
            ('corner', 3): block['corner_large_inst', ''],

            ('corner_temp', 1): block['temp_corner_small', ''],
            ('corner_temp', 2): block['temp_corner_medium', ''],
            ('corner_temp', 3): block['temp_corner_large', ''],

            # Straight instances connected to the next part
            'straight': block['straight_inst', ''],

            # Supports attach to the 4 sides of the straight part,
            # if there's a brush there.
            'support': block['support_inst', ''],

            'is_tsection': utils.conv_bool(block['is_tsection', '0']),

            ('entry', 'wall'): block['entry_inst'],
            ('entry', 'floor'): block['entry_floor_inst'],
            ('entry', 'ceiling'): block['entry_ceil_inst'],

            'exit': block['exit_inst'],
        }

        for prop in block.find_all("File"):
            try:
                size, file = prop.value.split(":", 1)
            except ValueError:
                size = 1
                file = prop.value

            inst_configs[resolve_inst(file)[0]] = conf, utils.conv_int(size, 1)

    return group
Exemplo n.º 8
0
def res_faith_mods(inst, res):
    """Modify the trigger_catrapult that is created for ItemFaithPlate items.

    Values:
        - raise_trig: Raise or lower the trigger_catapults by this amount.
        - angled_targ, angled_in: Instance entity and input for angled plates
        - straight_targ, straight_in: Instance entity and input for
            straight plates
        - instvar: A $replace value to set to either 'angled' or '
            'straight'.
    """
    # Get data about the trigger this instance uses for flinging
    fixup_var = res['instvar', '']
    offset = utils.conv_int(res['raise_trig', '0'])
    if offset:
        offset = Vec(0, 0, offset).rotate_by_str(inst['angles', '0 0 0'])
        ':type offset Vec'
    for trig in vbsp.VMF.by_class['trigger_catapult']:
        if inst['targetname'] in trig['targetname']:
            if offset:  # Edit both the normal and the helper trigger
                trig['origin'] = (
                    Vec.from_str(trig['origin']) +
                    offset
                ).join(' ')
                for solid in trig.solids:
                    solid.translate(offset)

            # Inspect the outputs to determine the type.
            # We also change them if desired, since that's not possible
            # otherwise.

            for out in trig.outputs:
                if out.inst_in == 'animate_angled_relay':
                    out.inst_in = res['angled_targ', 'animate_angled_relay']
                    out.input = res['angled_in', 'Trigger']
                    if fixup_var:
                        inst.fixup[fixup_var] = 'angled'
                    break # There's only one output we want to look for...
                elif out.inst_in == 'animate_straightup_relay':
                    out.inst_in = res[
                        'straight_targ', 'animate_straightup_relay'
                    ]
                    out.input = res['straight_in', 'Trigger']
                    if fixup_var:
                        inst.fixup[fixup_var] = 'straight'
                    break
Exemplo n.º 9
0
def flag_random(inst, res: Property):
    """Randomly is either true or false."""
    if res.has_children():
        chance = res['chance', '100']
        seed = res['seed', '']
    else:
        chance = res.value
        seed = ''

    # Allow ending with '%' sign
    chance = utils.conv_int(chance.rstrip('%'), 100)

    random.seed('random_chance_{}:{}_{}_{}'.format(
        seed,
        inst['targetname', ''],
        inst['origin'],
        inst['angles'],
    ))
    return random.randrange(100) < chance
Exemplo n.º 10
0
def res_add_output_setup(res):
    output = res['output']
    input_name = res['input']
    inst_in = res['inst_out', '']
    inst_out = res['inst_out', '']
    targ = res['target']
    only_once = utils.conv_bool(res['only_once', None])
    times = 1 if only_once else utils.conv_int(res['times', None], -1)
    delay = utils.conv_float(res['delay', '0.0'])
    parm = res['parm', '']

    return (
        output,
        targ,
        input_name,
        parm,
        delay,
        times,
        inst_in,
        inst_out,
    )
Exemplo n.º 11
0
    def parse(cls, prop_block):
        """Create a condition from a Property block."""
        flags = []
        results = []
        else_results = []
        priority = 0
        for prop in prop_block:
            if prop.name == 'result':
                results.extend(prop.value)  # join multiple ones together
            elif prop.name == 'else':
                else_results.extend(prop.value)
            elif prop.name == 'priority':
                priority = utils.conv_int(prop.value, priority)
            else:
                flags.append(prop)

        return cls(
            flags=flags,
            results=results,
            else_results=else_results,
            priority=priority,
        )
Exemplo n.º 12
0
def res_unst_scaffold(_, res):
    """The condition to generate Unstationary Scaffolds.

    This is executed once to modify all instances.
    """
    # The instance types we're modifying
    if res.value not in SCAFFOLD_CONFIGS:
        # We've already executed this config group
        return RES_EXHAUSTED

    LOGGER.info("Running Scaffold Generator ({})...", res.value)
    TARG_INST, LINKS = SCAFFOLD_CONFIGS[res.value]
    del SCAFFOLD_CONFIGS[res.value]  # Don't let this run twice

    instances = {}
    # Find all the instances we're wanting to change, and map them to
    # targetnames
    for ent in vbsp.VMF.by_class["func_instance"]:
        file = ent["file"].casefold()
        targ = ent["targetname"]
        if file not in TARG_INST:
            continue
        config = TARG_INST[file]
        next_inst = set(out.target for out in ent.outputs)
        # Destroy these outputs, they're useless now!
        ent.outputs.clear()
        instances[targ] = {"ent": ent, "conf": config, "next": next_inst, "prev": None}

    # Now link each instance to its in and outputs
    for targ, inst in instances.items():
        scaff_targs = 0
        for ent_targ in inst["next"]:
            if ent_targ in instances:
                instances[ent_targ]["prev"] = targ
                inst["next"] = ent_targ
                scaff_targs += 1
            else:
                # If it's not a scaffold, it's probably an indicator_toggle.
                # We want to remove any them as well as the assoicated
                # antlines!
                for toggle in vbsp.VMF.by_target[ent_targ]:
                    conditions.remove_ant_toggle(toggle)
        if scaff_targs > 1:
            raise Exception("A scaffold item has multiple destinations!")
        elif scaff_targs == 0:
            inst["next"] = None  # End instance

    starting_inst = []
    # We need to find the start instances, so we can set everything up
    for inst in instances.values():
        if inst["prev"] is None and inst["next"] is None:
            # Static item!
            continue
        elif inst["prev"] is None:
            starting_inst.append(inst)

    # We need to make the link entities unique for each scaffold set,
    # otherwise the AllVar property won't work.
    group_counter = 0

    # Set all the instances and properties
    for start_inst in starting_inst:
        group_counter += 1
        ent = start_inst["ent"]
        for vals in LINKS.values():
            if vals["all"] is not None:
                ent.fixup[vals["all"]] = SCAFF_PATTERN.format(name=vals["name"], group=group_counter, index="*")

        should_reverse = utils.conv_bool(ent.fixup["$start_reversed"])

        # Now set each instance in the chain, including first and last
        for index, inst in enumerate(scaff_scan(instances, start_inst)):
            ent, conf = inst["ent"], inst["conf"]
            orient = "floor" if Vec(0, 0, 1).rotate_by_str(ent["angles"]) == (0, 0, 1) else "wall"

            # Find the offset used for the logic ents
            offset = (conf["off_" + orient]).copy()
            if conf["is_piston"]:
                # Adjust based on the piston position
                offset.z += 128 * utils.conv_int(
                    ent.fixup["$top_level" if ent.fixup["$start_up"] == "1" else "$bottom_level"]
                )
            offset.rotate_by_str(ent["angles"])
            offset += Vec.from_str(ent["origin"])

            if inst["prev"] is None:
                link_type = "start"
            elif inst["next"] is None:
                link_type = "end"
            else:
                link_type = "mid"

            if orient == "floor" and link_type != "mid" and conf["inst_end"] is not None:
                # Add an extra instance pointing in the direction
                # of the connected track. This would be the endcap
                # model.
                other_ent = instances[inst["next" if link_type == "start" else "prev"]]["ent"]

                other_pos = Vec.from_str(other_ent["origin"])
                our_pos = Vec.from_str(ent["origin"])
                link_dir = other_pos - our_pos
                link_ang = math.degrees(math.atan2(link_dir.y, link_dir.x))
                # Round to nearest 90 degrees
                # Add 45 so the switchover point is at the diagonals
                link_ang = (link_ang + 45) // 90 * 90
                vbsp.VMF.create_ent(
                    classname="func_instance",
                    targetname=ent["targetname"],
                    file=conf["inst_end"],
                    origin=offset.join(" "),
                    angles="0 {:.0f} 0".format(link_ang),
                )
                # Don't place the offset instance, this replaces that!
            elif conf["inst_offset"] is not None:
                # Add an additional rotated entity at the offset.
                # This is useful for the piston item.
                vbsp.VMF.create_ent(
                    classname="func_instance",
                    targetname=ent["targetname"],
                    file=conf["inst_offset"],
                    origin=offset.join(" "),
                    angles=ent["angles"],
                )

            logic_inst = vbsp.VMF.create_ent(
                classname="func_instance",
                targetname=ent["targetname"],
                file=conf.get("logic_" + link_type + ("_rev" if should_reverse else ""), ""),
                origin=offset.join(" "),
                angles=("0 0 0" if conf["rotate_logic"] else ent["angles"]),
            )
            for key, val in ent.fixup.items():
                # Copy over fixup values
                logic_inst.fixup[key] = val

            # Add the link-values
            for linkVar, link in LINKS.items():
                logic_inst.fixup[linkVar] = SCAFF_PATTERN.format(name=link["name"], group=group_counter, index=index)
                if inst["next"] is not None:
                    logic_inst.fixup[link["next"]] = SCAFF_PATTERN.format(
                        name=link["name"], group=group_counter, index=index + 1
                    )

            new_file = conf.get("inst_" + orient, "")
            if new_file != "":
                ent["file"] = new_file

    LOGGER.info("Finished Scaffold generation!")
    return RES_EXHAUSTED
Exemplo n.º 13
0
def res_add_overlay_inst(inst, res):
    """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
            of piston platform handles.
        angles: If set, overrides the base instance angles. This does
            not affect the offset property.
        fixup: 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 utils.conv_bool(res["copy_fixup", "1"]) and "fixup" not in res:
        # Copy the fixup values across from the original instance
        for fixup, value in inst.fixup.items():
            overlay_inst.fixup[fixup] = value

    # Copy additional fixup values over
    for prop in res.find_key("Fixup", []):  # type: Property
        if prop.value.startswith("$"):
            overlay_inst.fixup[prop.real_name] = inst.fixup[prop.value]
        else:
            overlay_inst.fixup[prop.real_name] = prop.value

    if utils.conv_bool(res["move_outputs", "0"]):
        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_bottom>":
            offset = Vec(z=utils.conv_int(inst.fixup["$bottom_level"]) * 128)
        elif folded_off == "<piston_top>":
            offset = Vec(z=utils.conv_int(inst.fixup["$top_level"], 1) * 128)
        else:
            # Regular vector
            offset = Vec.from_str(res["offset"])

        offset.rotate_by_str(inst["angles", "0 0 0"])
        overlay_inst["origin"] = (offset + Vec.from_str(inst["origin"])).join(" ")
    return overlay_inst
Exemplo n.º 14
0
def res_cust_fizzler(base_inst, res):
    """Customises the various components of a custom fizzler item.

    This should be executed on the base instance. Brush and MakeLaserField
    are ignored on laserfield barriers.
    Options:
        * ModelName: sets the targetname given to the model instances.
        * UniqueModel: If true, each model instance will get a suffix to
            allow unique targetnames.
        * Brush: A brush entity that will be generated (the original is
         deleted.)
            * Name is the instance name for the brush
            * Left/Right/Center/Short/Nodraw are the textures used
            * Keys are a block of keyvalues to be set. Targetname and
              Origin are auto-set.
            * Thickness will change the thickness of the fizzler if set.
              By default it is 2 units thick.
        * MakeLaserField generates a brush stretched across the whole
          area.
            * Name, keys and thickness are the same as the regular Brush.
            * Texture/Nodraw are the textures.
            * Width is the pixel width of the laser texture, used to
              scale it correctly.
    """
    model_name = res['modelname', None]
    make_unique = utils.conv_bool(res['UniqueModel', '0'])
    fizz_name = base_inst['targetname', '']

    # search for the model instances
    model_targetnames = (
        fizz_name + '_modelStart',
        fizz_name + '_modelEnd',
        )
    is_laser = False
    for inst in vbsp.VMF.by_class['func_instance']:
        if inst['targetname', ''] in model_targetnames:
            if inst.fixup['skin', '0'] == '2':
                is_laser = True
            if model_name is not None:
                if model_name == '':
                    inst['targetname'] = base_inst['targetname']
                else:
                    inst['targetname'] = (
                        base_inst['targetname'] +
                        '-' +
                        model_name
                    )
            if make_unique:
                inst.make_unique()

            for key, value in base_inst.fixup.items():
                inst.fixup[key] = value

    new_brush_config = list(res.find_all('brush'))
    if len(new_brush_config) == 0:
        return  # No brush modifications

    if is_laser:
        # This is a laserfield! We can't edit those brushes!
        LOGGER.warning('CustFizzler excecuted on LaserField!')
        return

    for orig_brush in (
            vbsp.VMF.by_class['trigger_portal_cleanser'] &
            vbsp.VMF.by_target[fizz_name + '_brush']):
        orig_brush.remove()
        for config in new_brush_config:
            new_brush = orig_brush.copy()
            vbsp.VMF.add_ent(new_brush)
            # Don't allow restyling it
            vbsp.IGNORED_BRUSH_ENTS.add(new_brush)

            new_brush.clear_keys()  # Wipe the original keyvalues
            new_brush['origin'] = orig_brush['origin']
            new_brush['targetname'] = (
                fizz_name +
                '-' +
                config['name', 'brush']
            )
            # All ents must have a classname!
            new_brush['classname'] = 'trigger_portal_cleanser'

            conditions.set_ent_keys(
                new_brush, base_inst,
                config,
            )

            laserfield_conf = config.find_key('MakeLaserField', None)
            if laserfield_conf.value is not None:
                # Resize the brush into a laserfield format, without
                # the 128*64 parts. If the brush is 128x128, we can
                # skip the resizing since it's already correct.
                laser_tex = laserfield_conf['texture', 'effects/laserplane']
                nodraw_tex = laserfield_conf['nodraw', 'tools/toolsnodraw']
                tex_width = utils.conv_int(
                    laserfield_conf['texwidth', '512'], 512
                )
                is_short = False
                for side in new_brush.sides():
                    if side.mat.casefold() == 'effects/fizzler':
                        is_short = True
                        break

                if is_short:
                    for side in new_brush.sides():
                        if side.mat.casefold() == 'effects/fizzler':
                            side.mat = laser_tex

                            side.uaxis.offset = 0
                            side.scale = 0.25
                        else:
                            side.mat = nodraw_tex
                else:
                    # The hard part - stretching the brush.
                    convert_to_laserfield(
                        new_brush,
                        laser_tex,
                        nodraw_tex,
                        tex_width,
                    )
            else:
                # Just change the textures
                for side in new_brush.sides():
                    try:
                        side.mat = config[
                            TEX_FIZZLER[side.mat.casefold()]
                        ]
                    except (KeyError, IndexError):
                        # If we fail, just use the original textures
                        pass

            widen_amount = utils.conv_float(config['thickness', '2'], 2.0)
            if widen_amount != 2:
                for brush in new_brush.solids:
                    conditions.widen_fizz_brush(
                        brush,
                        thickness=widen_amount,
                    )
Exemplo n.º 15
0
def res_cutout_tile(inst, res):
    """Generate random quarter tiles, like in Destroyed or Retro maps.

    - "MarkerItem" is the instance to look for.
    - "TileSize" can be "2x2" or "4x4".
    - rotateMax is the amount of degrees to rotate squarebeam models.

    Materials:
    - "squarebeams" is the squarebeams variant to use.
    - "ceilingwalls" are the sides of the ceiling section.
    - "floorbase" is the texture under floor sections.
    - "tile_glue" is used on top of a thinner tile segment.
    - "clip" is the player_clip texture used over floor segments.
        (This allows customising the surfaceprop.)
    - "Floor4x4Black", "Ceil2x2White" and other combinations can be used to
       override the textures used.
    """
    item = resolve_inst(res['markeritem'])

    INST_LOCS = {}  # Map targetnames -> surface loc
    CEIL_IO = []  # Pairs of ceil inst corners to cut out.
    FLOOR_IO = []  # Pairs of floor inst corners to cut out.

    overlay_ids = {}  # When we replace brushes, we need to fix any overlays
    # on that surface.

    MATS.clear()
    floor_edges = []  # Values to pass to add_floor_sides() at the end

    sign_loc = set(FORCE_LOCATIONS)
    # If any signage is present in the map, we need to force tiles to
    # appear at that location!
    for over in conditions.VMF.by_class['info_overlay']:
        if (
                over['material'].casefold() in FORCE_TILE_MATS and
                # Only check floor/ceiling overlays
                over['basisnormal'] in ('0 0 1', '0 0 -1')
                ):
            loc = Vec.from_str(over['origin'])
            # Sometimes (light bridges etc) a sign will be halfway between
            # tiles, so in that case we need to force 2 tiles.
            loc_min = (loc - (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            loc_max = (loc + (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            sign_loc.add(loc_min.as_tuple())
            sign_loc.add(loc_max.as_tuple())

    SETTINGS = {
        'floor_chance': utils.conv_int(
            res['floorChance', '100'], 100),
        'ceil_chance': utils.conv_int(
            res['ceilingChance', '100'], 100),
        'floor_glue_chance': utils.conv_int(
            res['floorGlueChance', '0']),
        'ceil_glue_chance': utils.conv_int(
            res['ceilingGlueChance', '0']),

        'rotate_beams': int(utils.conv_float(
            res['rotateMax', '0']) * BEAM_ROT_PRECISION),

        'beam_skin': res['squarebeamsSkin', '0'],

        'base_is_disp': utils.conv_bool(res['dispBase', '0']),

        'quad_floor': res['FloorSize', '4x4'].casefold() == '2x2',
        'quad_ceil': res['CeilingSize', '4x4'].casefold() == '2x2',
    }

    for mat_prop in res['Materials', []]:
        MATS[mat_prop.name].append(mat_prop.value)

    if SETTINGS['base_is_disp']:
        # We want the normal brushes to become nodraw.
        MATS['floorbase_disp'] = MATS['floorbase']
        MATS['floorbase'] = ['tools/toolsnodraw']

    for key, default in TEX_DEFAULT:
        if key not in MATS:
            MATS[key] = [default]

    # Find our marker ents
    for inst in conditions.VMF.by_class['func_instance']: # type: VLib.Entity
        if inst['file'].casefold() not in item:
            continue
        targ = inst['targetname']
        orient = Vec(0, 0, 1).rotate_by_str(inst['angles', '0 0 0'])
        # Check the orientation of the marker to figure out what to generate
        if orient == (0, 0, 1):
            io_list = FLOOR_IO
        else:
            io_list = CEIL_IO

        # Reuse orient to calculate where the solid face will be.
        loc = (orient * -64) + Vec.from_str(inst['origin'])
        INST_LOCS[targ] = loc

        for out in inst.output_targets():
            io_list.append((targ, out))

        if not inst.outputs and inst.fixup['$connectioncount'] == '0':
            # If the item doesn't have any connections, 'connect'
            # it to itself so we'll generate a 128x128 tile segment.
            io_list.append((targ, targ))
        inst.remove()  # Remove the instance itself from the map.
    for start_floor, end_floor in FLOOR_IO:
        if end_floor not in INST_LOCS:
            # Not a marker - remove this and the antline.
            for toggle in conditions.VMF.by_target[end_floor]:
                conditions.remove_ant_toggle(toggle)
            continue

        detail_ent = conditions.VMF.create_ent(
            classname='func_detail'
        )

        box_min = Vec(INST_LOCS[start_floor])
        box_min.min(INST_LOCS[end_floor])

        box_max = Vec(INST_LOCS[start_floor])
        box_max.max(INST_LOCS[end_floor])

        if box_min.z != box_max.z:
            continue  # They're not in the same level!
        z = box_min.z

        if SETTINGS['rotate_beams']:
            # We have to generate 1 model per 64x64 block to do rotation...
            gen_rotated_squarebeams(
                box_min - (64, 64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin'],
                max_rot=SETTINGS['rotate_beams'],
            )
        else:
            # Make the squarebeams props, using big models if possible
            gen_squarebeams(
                box_min + (-64, -64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin']
            )

        # Add a player_clip brush across the whole area
        conditions.VMF.add_brush(conditions.VMF.make_prism(
            p1=box_min - (64, 64, FLOOR_DEPTH),
            p2=box_max + (64, 64, 0),
            mat=MATS['clip'][0],
        ).solid)

        # Add a noportal_volume covering the surface, in case there's
        # room for a portal.
        noportal_solid = conditions.VMF.make_prism(
            # Don't go all the way to the sides, so it doesn't affect wall
            # brushes.
            p1=box_min - (63, 63, 9),
            p2=box_max + (63, 63, 0),
            mat='tools/toolsinvisible',
        ).solid
        noportal_ent = conditions.VMF.create_ent(
            classname='func_noportal_volume',
            origin=box_min.join(' '),
        )
        noportal_ent.solids.append(noportal_solid)

        if SETTINGS['base_is_disp']:
            # Use displacements for the base instead.
            make_alpha_base(
                box_min + (-64, -64, 0),
                box_max + (64, 64, 0),
            )

        for x, y in utils.iter_grid(
                min_x=int(box_min.x),
                max_x=int(box_max.x)+1,
                min_y=int(box_min.y),
                max_y=int(box_max.y)+1,
                stride=128,
                ):
            convert_floor(
                Vec(x, y, z),
                overlay_ids,
                MATS,
                SETTINGS,
                sign_loc,
                detail_ent,
            )

        # Mark borders we need to fill in, and the angle (for func_instance)
        # The wall is the face pointing inwards towards the bottom brush,
        # and the ceil is the ceiling of the block above the bordering grid
        # points.
        for x in range(int(box_min.x), int(box_max.x) + 1, 128):
            # North
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_max.y + 64, z - 64),
                ceil=Vec_tuple(x, box_max.y + 128, z),
                rot=270,
            ))
            # South
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_min.y - 64, z - 64),
                ceil=Vec_tuple(x, box_min.y - 128, z),
                rot=90,
            ))

        for y in range(int(box_min.y), int(box_max.y) + 1, 128):
            # East
            floor_edges.append(BorderPoints(
                wall=Vec(box_max.x + 64, y, z - 64),
                ceil=Vec_tuple(box_max.x + 128, y, z),
                rot=180,
            ))

            # West
            floor_edges.append(BorderPoints(
                wall=Vec(box_min.x - 64, y, z - 64),
                ceil=Vec_tuple(box_min.x - 128, y, z),
                rot=0,
            ))

    add_floor_sides(floor_edges)

    conditions.reallocate_overlays(overlay_ids)

    return True
Exemplo n.º 16
0
 def test_conv_int(self):
     for string, result in ints:
         self.assertEqual(utils.conv_int(string), result)
Exemplo n.º 17
0
 def test_conv_int_fails_on_str(self):
     for string in non_ints:
         self.assertEqual(utils.conv_int(string), 0)
         for default in def_vals:
             # Check all default values pass through unchanged
             self.assertIs(utils.conv_int(string, default), default)
Exemplo n.º 18
0
def res_faith_mods(inst: VLib.Entity, res: Property):
    """Modify the trigger_catrapult that is created for ItemFaithPlate items.

    Values:
        - raise_trig: Raise or lower the trigger_catapults by this amount.
        - angled_targ, angled_in: Instance entity and input for angled plates
        - straight_targ, straight_in: Instance entity and input for
            straight plates
        - instvar: A $replace value to set to either 'angled' or '
            'straight'.
        - enabledVar: A $replace value which will be copied to the main
            trigger's Start Disabled value (and inverted).
        - trig_temp: An ID for a template brush to add. This will be offset by
            the trigger's position (in the case of the 'helper' trigger).
    """
    # Get data about the trigger this instance uses for flinging
    fixup_var = res['instvar', '']
    trig_enabled = res['enabledVar', None]
    trig_temp = res['trig_temp', '']
    offset = utils.conv_int(res['raise_trig', '0'])
    if offset:
        offset = Vec(0, 0, offset).rotate_by_str(inst['angles', '0 0 0'])
    else:
        offset = Vec()

    if trig_enabled is not None:
        trig_enabled = utils.conv_bool(inst.fixup[trig_enabled])
    else:
        trig_enabled = None

    for trig in vbsp.VMF.by_class['trigger_catapult']:
        if inst['targetname'] not in trig['targetname']:
            continue

        # Edit both the normal and the helper trigger..
        trig_origin = trig['origin'] = Vec.from_str(trig['origin']) + offset

        if offset and not trig_temp:
            # No template, shift the current brushes.
            for solid in trig.solids:
                solid.translate(offset)
        elif trig_temp:
            trig.solids = conditions.import_template(
                temp_name=trig_temp,
                origin=trig_origin,
                angles=Vec.from_str(inst['angles']),
                force_type=conditions.TEMP_TYPES.world,
            ).world
            # Remove the trigger solids from worldspawn..
            for solid in trig.solids:
                vbsp.VMF.remove_brush(solid)

        if trig_enabled is not None and 'helper' not in trig['targetname']:
            trig['startdisabled'] = utils.bool_as_int(not trig_enabled)

        # Inspect the outputs to determine the type.
        # We also change them if desired, since that's not possible
        # otherwise.

        for out in trig.outputs:
            if out.inst_in == 'animate_angled_relay':
                # Instead of an instance: output, use local names.
                # This allows us to strip the proxy, as well as use
                # overlay instances.
                out.inst_in = None
                out.target = conditions.local_name(
                    inst,
                    res['angled_targ', 'animate_angled_relay']
                )
                out.input = res['angled_in', 'Trigger']
                if fixup_var:
                    inst.fixup[fixup_var] = 'angled'
                break  # There's only one output we want to look for...

            elif out.inst_in == 'animate_straightup_relay':
                out.inst_in = None
                out.target = conditions.local_name(
                    inst,
                    res[
                        'straight_targ',
                        'animate_straightup_relay'
                    ],
                )
                out.input = res['straight_in', 'Trigger']

                if fixup_var:
                    inst.fixup[fixup_var] = 'straight'
                break
Exemplo n.º 19
0
def res_cust_fizzler(base_inst, res):
    """Modify a fizzler item to allow for custom brush ents."""
    model_name = res['modelname', None]
    make_unique = utils.conv_bool(res['UniqueModel', '0'])
    fizz_name = base_inst['targetname', '']

    # search for the model instances
    model_targetnames = (
        fizz_name + '_modelStart',
        fizz_name + '_modelEnd',
        )
    for inst in VMF.by_class['func_instance']:
        if inst['targetname', ''] in model_targetnames:
            if inst.fixup['skin', '0'] == '2':
                # This is a laserfield! We can't edit that!
                utils.con_log('CustFizzler excecuted on LaserField!')
                return
            if model_name is not None:
                if model_name == '':
                    inst['targetname'] = base_inst['targetname']
                else:
                    inst['targetname'] = (
                        base_inst['targetname'] +
                        '-' +
                        model_name
                    )
            if make_unique:
                inst.make_unique()

            for key, value in base_inst.fixup.items():
                inst.fixup[key] = value
    new_brush_config = list(res.find_all('brush'))
    if len(new_brush_config) == 0:
        return  # No brush modifications
    for orig_brush in (
            VMF.by_class['trigger_portal_cleanser'] &
            VMF.by_target[fizz_name + '_brush']):
        print(orig_brush)
        VMF.remove_ent(orig_brush)
        for config in new_brush_config:
            new_brush = orig_brush.copy()
            VMF.add_ent(new_brush)
            new_brush.clear_keys()  # Wipe the original keyvalues
            new_brush['origin'] = orig_brush['origin']
            new_brush['targetname'] = (
                fizz_name +
                '-' +
                config['name', 'brush']
            )
            # All ents must have a classname!
            new_brush['classname'] = 'trigger_portal_cleanser'

            for prop in config['keys', []]:
                new_brush[prop.name] = prop.value

            laserfield_conf = config.find_key('MakeLaserField', None)
            if laserfield_conf.value is not None:
                # Resize the brush into a laserfield format, without
                # the 128*64 parts. If the brush is 128x128, we can
                # skip the resizing since it's already correct.
                laser_tex = laserfield_conf['texture', 'effects/laserplane']
                nodraw_tex = laserfield_conf['nodraw', 'tools/toolsnodraw']
                tex_width = utils.conv_int(
                    laserfield_conf['texwidth', '512'], 512
                )
                is_short = False
                for side in new_brush.sides():
                    if side.mat.casefold() == 'effects/fizzler':
                        is_short = True
                        break

                if is_short:
                    for side in new_brush.sides():
                        if side.mat.casefold() == 'effects/fizzler':
                            side.mat = laser_tex

                            uaxis = side.uaxis.split(" ")
                            vaxis = side.vaxis.split(" ")
                            # the format is like "[1 0 0 -393.4] 0.25"
                            side.uaxis = ' '.join(uaxis[:3]) + ' 0] 0.25'
                            side.vaxis = ' '.join(vaxis[:4]) + ' 0.25'
                        else:
                            side.mat = nodraw_tex
                else:
                    # The hard part - stretching the brush.
                    convert_to_laserfield(
                        new_brush,
                        laser_tex,
                        nodraw_tex,
                        tex_width,
                    )
            else:
                # Just change the textures
                for side in new_brush.sides():
                    try:
                        side.mat = config[
                            vbsp.TEX_FIZZLER[side.mat.casefold()]
                        ]
                    except (KeyError, IndexError):
                        # If we fail, just use the original textures
                        pass
Exemplo n.º 20
0
def res_cutout_tile(inst, res):
    """Generate random quarter tiles, like in Destroyed or Retro maps.

    - "MarkerItem" is the instance to look for.
    - "TileSize" can be "2x2" or "4x4".
    - rotateMax is the amount of degrees to rotate squarebeam models.

    Materials:
    - "squarebeams" is the squarebeams variant to use.
    - "ceilingwalls" are the sides of the ceiling section.
    - "floorbase" is the texture under floor sections.
    - "tile_glue" is used on top of a thinner tile segment.
    - "clip" is the player_clip texture used over floor segments.
        (This allows customising the surfaceprop.)
    - "Floor4x4Black", "Ceil2x2White" and other combinations can be used to
       override the textures used.
    """
    item = resolve_inst(res['markeritem'])

    INST_LOCS = {}  # Map targetnames -> surface loc
    CEIL_IO = []  # Pairs of ceil inst corners to cut out.
    FLOOR_IO = []  # Pairs of floor inst corners to cut out.

    overlay_ids = {}  # When we replace brushes, we need to fix any overlays
    # on that surface.

    MATS.clear()
    floor_edges = []  # Values to pass to add_floor_sides() at the end

    sign_loc = set(FORCE_LOCATIONS)
    # If any signage is present in the map, we need to force tiles to
    # appear at that location!
    for over in conditions.VMF.by_class['info_overlay']:
        if (
                over['material'].casefold() in FORCE_TILE_MATS and
                # Only check floor/ceiling overlays
                over['basisnormal'] in ('0 0 1', '0 0 -1')
                ):
            loc = Vec.from_str(over['origin'])
            # Sometimes (light bridges etc) a sign will be halfway between
            # tiles, so in that case we need to force 2 tiles.
            loc_min = (loc - (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            loc_max = (loc + (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            sign_loc.add(loc_min.as_tuple())
            sign_loc.add(loc_max.as_tuple())

    SETTINGS = {
        'floor_chance': utils.conv_int(
            res['floorChance', '100'], 100),
        'ceil_chance': utils.conv_int(
            res['ceilingChance', '100'], 100),
        'floor_glue_chance': utils.conv_int(
            res['floorGlueChance', '0']),
        'ceil_glue_chance': utils.conv_int(
            res['ceilingGlueChance', '0']),

        'rotate_beams': int(utils.conv_float(
            res['rotateMax', '0']) * BEAM_ROT_PRECISION),

        'beam_skin': res['squarebeamsSkin', '0'],

        'base_is_disp': utils.conv_bool(res['dispBase', '0']),

        'quad_floor': res['FloorSize', '4x4'].casefold() == '2x2',
        'quad_ceil': res['CeilingSize', '4x4'].casefold() == '2x2',
    }

    random.seed(vbsp.MAP_RAND_SEED + '_CUTOUT_TILE_NOISE')
    noise = SimplexNoise(period=4 * 40)  # 4 tiles/block, 50 blocks max

    # We want to know the number of neighbouring tile cutouts before
    # placing tiles - blocks away from the sides generate fewer tiles.
    floor_neighbours = defaultdict(dict)  # all_floors[z][x,y] = count

    for mat_prop in res['Materials', []]:
        MATS[mat_prop.name].append(mat_prop.value)

    if SETTINGS['base_is_disp']:
        # We want the normal brushes to become nodraw.
        MATS['floorbase_disp'] = MATS['floorbase']
        MATS['floorbase'] = ['tools/toolsnodraw']

        # Since this uses random data for initialisation, the alpha and
        # regular will use slightly different patterns.
        alpha_noise = SimplexNoise(period=4 * 50)
    else:
        alpha_noise = None

    for key, default in TEX_DEFAULT:
        if key not in MATS:
            MATS[key] = [default]

    # Find our marker ents
    for inst in conditions.VMF.by_class['func_instance']: # type: VLib.Entity
        if inst['file'].casefold() not in item:
            continue
        targ = inst['targetname']
        orient = Vec(0, 0, 1).rotate_by_str(inst['angles', '0 0 0'])
        # Check the orientation of the marker to figure out what to generate
        if orient == (0, 0, 1):
            io_list = FLOOR_IO
        else:
            io_list = CEIL_IO

        # Reuse orient to calculate where the solid face will be.
        loc = (orient * -64) + Vec.from_str(inst['origin'])
        INST_LOCS[targ] = loc

        for out in inst.output_targets():
            io_list.append((targ, out))

        if not inst.outputs and inst.fixup['$connectioncount'] == '0':
            # If the item doesn't have any connections, 'connect'
            # it to itself so we'll generate a 128x128 tile segment.
            io_list.append((targ, targ))
        inst.remove()  # Remove the instance itself from the map.

    for start_floor, end_floor in FLOOR_IO:
        if end_floor not in INST_LOCS:
            # Not a marker - remove this and the antline.
            for toggle in conditions.VMF.by_target[end_floor]:
                conditions.remove_ant_toggle(toggle)
            continue

        box_min = Vec(INST_LOCS[start_floor])
        box_min.min(INST_LOCS[end_floor])

        box_max = Vec(INST_LOCS[start_floor])
        box_max.max(INST_LOCS[end_floor])

        if box_min.z != box_max.z:
            continue  # They're not in the same level!
        z = box_min.z

        if SETTINGS['rotate_beams']:
            # We have to generate 1 model per 64x64 block to do rotation...
            gen_rotated_squarebeams(
                box_min - (64, 64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin'],
                max_rot=SETTINGS['rotate_beams'],
            )
        else:
            # Make the squarebeams props, using big models if possible
            gen_squarebeams(
                box_min + (-64, -64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin']
            )

        # Add a player_clip brush across the whole area
        conditions.VMF.add_brush(conditions.VMF.make_prism(
            p1=box_min - (64, 64, FLOOR_DEPTH),
            p2=box_max + (64, 64, 0),
            mat=MATS['clip'][0],
        ).solid)

        # Add a noportal_volume covering the surface, in case there's
        # room for a portal.
        noportal_solid = conditions.VMF.make_prism(
            # Don't go all the way to the sides, so it doesn't affect wall
            # brushes.
            p1=box_min - (63, 63, 9),
            p2=box_max + (63, 63, 0),
            mat='tools/toolsinvisible',
        ).solid
        noportal_ent = conditions.VMF.create_ent(
            classname='func_noportal_volume',
            origin=box_min.join(' '),
        )
        noportal_ent.solids.append(noportal_solid)

        if SETTINGS['base_is_disp']:
            # Use displacements for the base instead.
            make_alpha_base(
                box_min + (-64, -64, 0),
                box_max + (64, 64, 0),
                noise=alpha_noise,
            )

        for x, y in utils.iter_grid(
                min_x=int(box_min.x),
                max_x=int(box_max.x) + 1,
                min_y=int(box_min.y),
                max_y=int(box_max.y) + 1,
                stride=128,
                ):
            # Build the set of all positions..
            floor_neighbours[z][x, y] = -1

        # Mark borders we need to fill in, and the angle (for func_instance)
        # The wall is the face pointing inwards towards the bottom brush,
        # and the ceil is the ceiling of the block above the bordering grid
        # points.
        for x in range(int(box_min.x), int(box_max.x) + 1, 128):
            # North
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_max.y + 64, z - 64),
                ceil=Vec_tuple(x, box_max.y + 128, z),
                rot=270,
            ))
            # South
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_min.y - 64, z - 64),
                ceil=Vec_tuple(x, box_min.y - 128, z),
                rot=90,
            ))

        for y in range(int(box_min.y), int(box_max.y) + 1, 128):
            # East
            floor_edges.append(BorderPoints(
                wall=Vec(box_max.x + 64, y, z - 64),
                ceil=Vec_tuple(box_max.x + 128, y, z),
                rot=180,
            ))

            # West
            floor_edges.append(BorderPoints(
                wall=Vec(box_min.x - 64, y, z - 64),
                ceil=Vec_tuple(box_min.x - 128, y, z),
                rot=0,
            ))

    # Now count boundries near tiles, then generate them.

    # Do it seperately for each z-level:
    for z, xy_dict in floor_neighbours.items():  # type: float, dict
        for x, y in xy_dict:  # type: float, float
            # We want to count where there aren't any tiles
            xy_dict[x, y] = (
                ((x - 128, y - 128) not in xy_dict) +
                ((x - 128, y + 128) not in xy_dict) +
                ((x + 128, y - 128) not in xy_dict) +
                ((x + 128, y + 128) not in xy_dict) +

                ((x - 128, y) not in xy_dict) +
                ((x + 128, y) not in xy_dict) +
                ((x, y - 128) not in xy_dict) +
                ((x, y + 128) not in xy_dict)
            )

        max_x = max_y = 0

        weights = {}
        # Now the counts are all correct, compute the weight to apply
        # for tiles.
        # Adding the neighbouring counts will make a 5x5 area needed to set
        # the center to 0.

        for (x, y), cur_count in xy_dict.items():
            max_x = max(x, max_x)
            max_y = max(y, max_y)

            # Orthrogonal is worth 0.2, diagonal is worth 0.1.
            # Not-present tiles would be 8 - the maximum
            tile_count = (
                0.8 * cur_count +
                0.1 * xy_dict.get((x - 128, y - 128), 8) +
                0.1 * xy_dict.get((x - 128, y + 128), 8) +
                0.1 * xy_dict.get((x + 128, y - 128), 8) +
                0.1 * xy_dict.get((x + 128, y + 128), 8) +

                0.2 * xy_dict.get((x - 128, y), 8) +
                0.2 * xy_dict.get((x, y - 128), 8) +
                0.2 * xy_dict.get((x, y + 128), 8) +
                0.2 * xy_dict.get((x + 128, y), 8)
            )
            # The number ranges from 0 (all tiles) to 12.8 (no tiles).
            # All tiles should still have a small chance to generate tiles.
            weights[x, y] = min((tile_count + 0.5) / 8, 1)

        # Share the detail entity among same-height tiles..
        detail_ent = conditions.VMF.create_ent(
            classname='func_detail',
        )

        for x, y in xy_dict:
            convert_floor(
                Vec(x, y, z),
                overlay_ids,
                MATS,
                SETTINGS,
                sign_loc,
                detail_ent,
                noise_weight=weights[x, y],
                noise_func=noise,
            )

    add_floor_sides(floor_edges)

    conditions.reallocate_overlays(overlay_ids)

    return conditions.RES_EXHAUSTED
Exemplo n.º 21
0
def res_add_variant_setup(res):
    count = utils.conv_int(res["Number", ""], None)
    if count:
        return conditions.weighted_random(count, res["weights", ""])
    else:
        return None
Exemplo n.º 22
0
def res_cust_fizzler(base_inst, res):
    """Modify a fizzler item to allow for custom brush ents."""
    from vbsp import TEX_FIZZLER

    model_name = res["modelname", None]
    make_unique = utils.conv_bool(res["UniqueModel", "0"])
    fizz_name = base_inst["targetname", ""]

    # search for the model instances
    model_targetnames = (fizz_name + "_modelStart", fizz_name + "_modelEnd")
    is_laser = False
    for inst in VMF.by_class["func_instance"]:
        if inst["targetname", ""] in model_targetnames:
            if inst.fixup["skin", "0"] == "2":
                is_laser = True
            if model_name is not None:
                if model_name == "":
                    inst["targetname"] = base_inst["targetname"]
                else:
                    inst["targetname"] = base_inst["targetname"] + "-" + model_name
            if make_unique:
                inst.make_unique()

            for key, value in base_inst.fixup.items():
                inst.fixup[key] = value

    new_brush_config = list(res.find_all("brush"))
    if len(new_brush_config) == 0:
        return  # No brush modifications

    if is_laser:
        # This is a laserfield! We can't edit those brushes!
        utils.con_log("CustFizzler excecuted on LaserField!")
        return

    for orig_brush in VMF.by_class["trigger_portal_cleanser"] & VMF.by_target[fizz_name + "_brush"]:
        print(orig_brush)
        VMF.remove_ent(orig_brush)
        for config in new_brush_config:
            new_brush = orig_brush.copy()
            VMF.add_ent(new_brush)
            new_brush.clear_keys()  # Wipe the original keyvalues
            new_brush["origin"] = orig_brush["origin"]
            new_brush["targetname"] = fizz_name + "-" + config["name", "brush"]
            # All ents must have a classname!
            new_brush["classname"] = "trigger_portal_cleanser"

            for prop in config["keys", []]:
                new_brush[prop.name] = prop.value

            laserfield_conf = config.find_key("MakeLaserField", None)
            if laserfield_conf.value is not None:
                # Resize the brush into a laserfield format, without
                # the 128*64 parts. If the brush is 128x128, we can
                # skip the resizing since it's already correct.
                laser_tex = laserfield_conf["texture", "effects/laserplane"]
                nodraw_tex = laserfield_conf["nodraw", "tools/toolsnodraw"]
                tex_width = utils.conv_int(laserfield_conf["texwidth", "512"], 512)
                is_short = False
                for side in new_brush.sides():
                    if side.mat.casefold() == "effects/fizzler":
                        is_short = True
                        break

                if is_short:
                    for side in new_brush.sides():
                        if side.mat.casefold() == "effects/fizzler":
                            side.mat = laser_tex

                            uaxis = side.uaxis.split(" ")
                            vaxis = side.vaxis.split(" ")
                            # the format is like "[1 0 0 -393.4] 0.25"
                            side.uaxis = " ".join(uaxis[:3]) + " 0] 0.25"
                            side.vaxis = " ".join(vaxis[:4]) + " 0.25"
                        else:
                            side.mat = nodraw_tex
                else:
                    # The hard part - stretching the brush.
                    convert_to_laserfield(new_brush, laser_tex, nodraw_tex, tex_width)
            else:
                # Just change the textures
                for side in new_brush.sides():
                    try:
                        side.mat = config[TEX_FIZZLER[side.mat.casefold()]]
                    except (KeyError, IndexError):
                        # If we fail, just use the original textures
                        pass
Exemplo n.º 23
0
def add_quote(quote: Property, targetname, quote_loc, use_dings=False):
    """Add a quote to the map."""
    LOGGER.info('Adding quote: {}', quote)

    only_once = False
    cc_emit_name = None
    added_ents = []
    end_commands = []

    # 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':
            added_ents.append(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', ...)
            LOGGER.info('End-commands: {}', '\n'.join(map(str,end_commands)))
            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(
                        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(vmfLib.Output(
                            'OnCompletion',
                            secondary_name + str(ind + 1),
                            'Start',
                            delay=0.1,
                        ))
                    if is_first:  # Ensure this works with cc_emit
                        added_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(
                    prop.value,
                    targetname,
                    quote_loc,
                    use_dings=use_dings,
                    only_once=only_once,
                )
                added_ents.append(choreo)
                for out in end_commands:
                    choreo.add_out(out.copy())
                end_commands.clear()
        elif name == 'snd':
            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(
                vmfLib.Output(
                    'OnUser1',
                    targetname,
                    'PlaySound',
                    only_once=only_once,
                )
            )
            added_ents.append(snd)
        elif name == 'bullseye':
            # Cave's voice lines require a special named bullseye to
            # work correctly.

            # Don't add the same one more than once.
            if prop.value not in ADDED_BULLSEYES:
                VMF.create_ent(
                    classname='npc_bullseye',
                    # Not solid, Take No Damage, Think outside PVS
                    spawnflags='222224',
                    targetname=prop.value,
                    origin=quote_loc - (0, 0, 16),
                    angles='0 0 0',
                )
                ADDED_BULLSEYES.add(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':
            vbsp.TO_PACK.add(prop.value.casefold())
        elif name == 'pack':
            if prop.has_children():
                vbsp.PACK_FILES.update(
                    subprop.value
                    for subprop in
                    prop
                )
            else:
                vbsp.PACK_FILES.add(prop.value)
        elif name == 'choreo_name':
            # Change the targetname used for subsequent entities
            targetname = prop.value
        elif name == 'onlyonce':
            only_once = utils.conv_bool(prop.value)
        elif name == 'endcommand':
            end_commands.append(vmfLib.Output(
                'OnCompletion',
                prop['target'],
                prop['input'],
                prop['parm', ''],
                utils.conv_float(prop['delay']),
                only_once=utils.conv_bool(prop['only_once', None]),
                times=utils.conv_int(prop['times', None], -1),
            ))

    if cc_emit_name:
        for ent in added_ents:
            ent.add_out(vmfLib.Output(
                'OnUser1',
                '@command',
                'Command',
                param='cc_emit ' + cc_emit_name,
            ))