Пример #1
0
def test_instances() -> None:
    """Test instance definitions."""
    [[item], renderables] = Item.parse(START_EXPORTING + '''
    "Instances"
        {
        "0" // Full PeTI style definition
            {
            "Name"				"instances/p2editor/something.vmf"
            "EntityCount"		"30" 
            "BrushCount"		"28"
            "BrushSideCount"	"4892"
            }
        "another_name" "instances/more_custom.vmf"
        "bee2_second_CUst" "instances/even_more.vmf"
        "1" 
            {
            "Name" "instances/somewhere_else/item.vmf"
            }
        "5" "instances/skipping_indexes.vmf"
        "2" "instances/direct_path.vmf"
        "cust_name"
            {
            "Name" "instances/a_custom_item.vmf"
            "EntityCount"		"327" 
            "BrushCount"		"1"
            "BrushSideCount"	"32"
            }
        }
    }} // End exporting + item
    ''')
    assert len(item.instances) == 6
    assert item.instances[0] == InstCount(
        FSPath("instances/p2editor/something.vmf"), 30, 28, 4892)
    assert item.instances[1] == InstCount(
        FSPath("instances/somewhere_else/item.vmf"), 0, 0, 0)
    assert item.instances[2] == InstCount(FSPath("instances/direct_path.vmf"),
                                          0, 0, 0)
    assert item.instances[3] == InstCount(FSPath(), 0, 0, 0)
    assert item.instances[4] == InstCount(FSPath(), 0, 0, 0)
    assert item.instances[5] == InstCount(
        FSPath("instances/skipping_indexes.vmf"), 0, 0, 0)
    # Counts discarded for custom items, and casefolded.
    assert item.cust_instances == {
        "another_name": FSPath("instances/more_custom.vmf"),
        "second_cust": FSPath("instances/even_more.vmf"),
        "cust_name": FSPath("instances/a_custom_item.vmf"),
    }
Пример #2
0
def parse_item_folder(
    folders_to_parse: set[str],
    filesystem: FileSystem,
    pak_id: str,
) -> dict[str, ItemVariant]:
    """Parse through the data in item/ folders.

    folders is a dict, with the keys set to the folder names we want.
    The values will be filled in with itemVariant values
    """
    folders: dict[str, ItemVariant] = {}
    for fold in folders_to_parse:
        prop_path = 'items/' + fold + '/properties.txt'
        editor_path = 'items/' + fold + '/editoritems.txt'
        config_path = 'items/' + fold + '/vbsp_config.cfg'

        first_item: EditorItem | None = None
        extra_items: list[EditorItem] = []
        try:
            props = filesystem.read_prop(prop_path).find_key('Properties')
            f = filesystem[editor_path].open_str()
        except FileNotFoundError as err:
            raise IOError('"' + pak_id + ':items/' + fold + '" not valid! '
                          'Folder likely missing! ') from err
        with f:
            tok = Tokenizer(f, editor_path)
            for tok_type, tok_value in tok:
                if tok_type is Token.STRING:
                    if tok_value.casefold() != 'item':
                        raise tok.error('Unknown item option "{}"!', tok_value)
                    if first_item is None:
                        first_item = EditorItem.parse_one(tok)
                    else:
                        extra_items.append(EditorItem.parse_one(tok))
                elif tok_type is not Token.NEWLINE:
                    raise tok.error(tok_type)

        if first_item is None:
            raise ValueError(f'"{pak_id}:items/{fold}/editoritems.txt has no '
                             '"Item" block!')

        try:
            editor_vmf = VMF.parse(
                filesystem.read_prop(editor_path[:-3] + 'vmf'))
        except FileNotFoundError:
            pass
        else:
            editoritems_vmf.load(first_item, editor_vmf)
        first_item.generate_collisions()

        # extra_items is any extra blocks (offset catchers, extent items).
        # These must not have a palette section - it'll override any the user
        # chooses.
        for extra_item in extra_items:
            extra_item.generate_collisions()
            for subtype in extra_item.subtypes:
                if subtype.pal_pos is not None:
                    LOGGER.warning(
                        f'"{pak_id}:items/{fold}/editoritems.txt has '
                        f'palette set for extra item blocks. Deleting.')
                    subtype.pal_icon = subtype.pal_pos = subtype.pal_name = None

        # In files this is specified as PNG, but it's always really VTF.
        try:
            all_icon = FSPath(props['all_icon']).with_suffix('.vtf')
        except LookupError:
            all_icon = None

        folders[fold] = ItemVariant(
            editoritems=first_item,
            editor_extra=extra_items,

            # Add the folder the item definition comes from,
            # so we can trace it later for debug messages.
            source=f'<{pak_id}>/items/{fold}',
            pak_id=pak_id,
            vbsp_config=lazy_conf.BLANK,
            authors=sep_values(props['authors', '']),
            tags=sep_values(props['tags', '']),
            desc=desc_parse(props, f'{pak_id}:{prop_path}', pak_id),
            ent_count=props['ent_count', ''],
            url=props['infoURL', None],
            icons={
                prop.name: img.Handle.parse(
                    prop,
                    pak_id,
                    64,
                    64,
                    subfolder='items',
                )
                for prop in props.find_children('icon')
            },
            all_name=props['all_name', None],
            all_icon=all_icon,
        )

        if Item.log_ent_count and not folders[fold].ent_count:
            LOGGER.warning(
                '"{id}:{path}" has missing entity count!',
                id=pak_id,
                path=prop_path,
            )

        # If we have one of the grouping icon definitions but not both required
        # ones then notify the author.
        has_name = folders[fold].all_name is not None
        has_icon = folders[fold].all_icon is not None
        if (has_name or has_icon or 'all'
                in folders[fold].icons) and (not has_name or not has_icon):
            LOGGER.warning(
                'Warning: "{id}:{path}" has incomplete grouping icon '
                'definition!',
                id=pak_id,
                path=prop_path,
            )
        folders[fold].vbsp_config = lazy_conf.from_file(
            utils.PackagePath(pak_id, config_path),
            missing_ok=True,
            source=folders[fold].source,
        )
    return folders
Пример #3
0
def parse_item_folder(
    folders_to_parse: Set[str],
    filesystem: FileSystem,
    pak_id: str,
) -> Dict[str, ItemVariant]:
    """Parse through the data in item/ folders.

    folders is a dict, with the keys set to the folder names we want.
    The values will be filled in with itemVariant values
    """
    folders: Dict[str, ItemVariant] = {}
    for fold in folders_to_parse:
        prop_path = 'items/' + fold + '/properties.txt'
        editor_path = 'items/' + fold + '/editoritems.txt'
        config_path = 'items/' + fold + '/vbsp_config.cfg'

        first_item: Optional[Item] = None
        extra_items: List[EditorItem] = []
        with filesystem:
            try:
                props = filesystem.read_prop(prop_path).find_key('Properties')
                f = filesystem[editor_path].open_str()
            except FileNotFoundError as err:
                raise IOError('"' + pak_id + ':items/' + fold + '" not valid!'
                              'Folder likely missing! ') from err
            with f:
                tok = Tokenizer(f, editor_path)
                for tok_type, tok_value in tok:
                    if tok_type is Token.STRING:
                        if tok_value.casefold() != 'item':
                            raise tok.error('Unknown item option "{}"!',
                                            tok_value)
                        if first_item is None:
                            first_item = EditorItem.parse_one(tok)
                        else:
                            extra_items.append(EditorItem.parse_one(tok))
                    elif tok_type is not Token.NEWLINE:
                        raise tok.error(tok_type)

        if first_item is None:
            raise ValueError('"{}:items/{}/editoritems.txt has no '
                             '"Item" block!'.format(pak_id, fold))

        # extra_items is any extra blocks (offset catchers, extent items).
        # These must not have a palette section - it'll override any the user
        # chooses.
        for extra_item in extra_items:
            for subtype in extra_item.subtypes:
                if subtype.pal_pos is not None:
                    LOGGER.warning(
                        '"{}:items/{}/editoritems.txt has palette set for extra'
                        ' item blocks. Deleting.'.format(pak_id, fold))
                    subtype.pal_icon = subtype.pal_pos = subtype.pal_name = None

        try:
            all_icon = FSPath(props['all_icon'])
        except LookupError:
            all_icon = None

        folders[fold] = ItemVariant(
            editoritems=first_item,
            editor_extra=extra_items,

            # Add the folder the item definition comes from,
            # so we can trace it later for debug messages.
            source='<{}>/items/{}'.format(pak_id, fold),
            vbsp_config=Property(None, []),
            authors=sep_values(props['authors', '']),
            tags=sep_values(props['tags', '']),
            desc=desc_parse(props, pak_id + ':' + prop_path),
            ent_count=props['ent_count', ''],
            url=props['infoURL', None],
            icons={p.name: p.value
                   for p in props['icon', []]},
            all_name=props['all_name', None],
            all_icon=all_icon,
        )

        if Item.log_ent_count and not folders[fold].ent_count:
            LOGGER.warning(
                '"{id}:{path}" has missing entity count!',
                id=pak_id,
                path=prop_path,
            )

        # If we have at least 1, but not all of the grouping icon
        # definitions then notify the author.
        num_group_parts = ((folders[fold].all_name is not None) +
                           (folders[fold].all_icon is not None) +
                           ('all' in folders[fold].icons))
        if 0 < num_group_parts < 3:
            LOGGER.warning(
                'Warning: "{id}:{path}" has incomplete grouping icon '
                'definition!',
                id=pak_id,
                path=prop_path,
            )
        try:
            with filesystem:
                folders[fold].vbsp_config = conf = filesystem.read_prop(
                    config_path, )
        except FileNotFoundError:
            folders[fold].vbsp_config = conf = Property(None, [])

        set_cond_source(conf, folders[fold].source)
    return folders
Пример #4
0
def test_parse_goo() -> None:
    """Verify all the values in a goo item definition are correct."""
    [[item], _] = Item.parse('''
    Item
    {
        "Type"		"ITEM_GOO"
        "ItemClass"	"ItemGoo"
        "Editor"
        {
            "SubType"
            {
                "Name"		"PORTAL2_PuzzleEditor_Item_goo"
                "Model"
                {
                    "ModelName"		"goo_man.3ds"
                }
                "Model"
                {
                    "ModelName"		"goo_man_water.mdl"
                }
                "Palette"
                {
                    "Tooltip"	"PORTAL2_PuzzleEditor_Palette_goo"
                    "Image"		"palette/goo.png"
                    "Position"	"2 6 0"
                }
                "Sounds"
                {
                    "SOUND_CREATED"					"P2Editor.PlaceOther"
                    "SOUND_EDITING_ACTIVATE"		"P2Editor.ExpandOther"
                    "SOUND_EDITING_DEACTIVATE"		"P2Editor.CollapseOther"
                    "SOUND_DELETED"					"P2Editor.RemoveOther"
                }
            }
            "MovementHandle"	"HANDLE_NONE"
            "DesiredFacing"		"DESIRES_UP"
        }
        "Exporting"
        {
            "TargetName"		"goo"
            "Offset"		"64 64 64"
            "OccupiedVoxels"
            {
                "Voxel"
                {
                    "Pos"				"0 0 0"
                    "CollideType"		"COLLIDE_NOTHING"
                    "CollideAgainst"	"COLLIDE_NOTHING"
    
                    "Surface"
                    {
                        "Normal"	"0 0 1"
                    }
                }
            }
        }
    }
    ''')
    assert item.id == "ITEM_GOO"
    assert item.cls is ItemClass.GOO
    assert len(item.subtypes) == 1
    [subtype] = item.subtypes
    assert subtype.name == "PORTAL2_PuzzleEditor_Item_goo"
    assert subtype.models == [
        # Regardless of original extension, both become .mdl since that's more
        # correct.
        FSPath("goo_man.mdl"),
        FSPath("goo_man_water.mdl"),
    ]
    assert subtype.pal_name == "PORTAL2_PuzzleEditor_Palette_goo"
    assert subtype.pal_icon == FSPath("palette/goo.vtf")
    assert subtype.pal_pos == (2, 6)

    assert subtype.sounds == {
        Sound.CREATE: "P2Editor.PlaceOther",
        Sound.PROPS_OPEN: "P2Editor.ExpandOther",
        Sound.PROPS_CLOSE: "P2Editor.CollapseOther",
        Sound.DELETE: "P2Editor.RemoveOther",
        # Default values.
        Sound.SELECT: '',
        Sound.DESELECT: '',
    }
    assert item.handle is Handle.NONE
    assert item.facing is DesiredFacing.UP
    assert item.targetname == "goo"
    assert item.offset == Vec(64, 64, 64)

    assert len(item.occupy_voxels) == 1
    occupation: OccupiedVoxel
    [occupation] = item.occupy_voxels
    assert occupation.type is CollType.NOTHING
    assert occupation.against is CollType.NOTHING
    assert occupation.pos == Vec(0, 0, 0)
    assert occupation.normal == Vec(0, 0, 1)
    assert occupation.subpos is None

    # Check these are default.
    assert item.occupies_voxel is False
    assert item.copiable is True
    assert item.deletable is True
    assert item.anchor_goo is False
    assert item.anchor_barriers is False
    assert item.pseudo_handle is False
    assert item.force_input is False
    assert item.force_output is False
    assert item.antline_points == {
        ConnSide.UP: [],
        ConnSide.DOWN: [],
        ConnSide.LEFT: [],
        ConnSide.RIGHT: [],
    }
    assert item.animations == {}
    assert item.properties == {}
    assert item.invalid_surf == set()
    assert item.cust_instances == {}
    assert item.instances == []
    assert item.embed_voxels == set()
    assert item.embed_faces == []
    assert item.overlays == []
    assert item.conn_inputs == {}
    assert item.conn_outputs == {}
    assert item.conn_config is None
Пример #5
0
    def parse(cls, data: ParseData):
        """Parse a style definition."""
        info = data.info  # type: Property
        filesystem = data.fsys  # type: FileSystem
        selitem_data = SelitemData.parse(info)
        base = info['base', '']
        has_video = srctools.conv_bool(
            info['has_video', ''],
            not data.is_override,  # Assume no video for override
        )
        vpk_name = info['vpk_name', ''].casefold()

        sugg = info.find_key('suggested', [])
        if data.is_override:
            # For overrides, we default to no suggestion..
            sugg = (
                sugg['quote', ''],
                sugg['music', ''],
                sugg['skybox', ''],
                sugg['elev', ''],
            )
        else:
            sugg = (
                sugg['quote', '<NONE>'],
                sugg['music', '<NONE>'],
                sugg['skybox', 'SKY_BLACK'],
                sugg['elev', '<NONE>'],
            )

        corr_conf = info.find_key('corridors', [])
        corridors = {}

        icon_folder = corr_conf['icon_folder', '']

        for group, length in CORRIDOR_COUNTS.items():
            group_prop = corr_conf.find_key(group, [])
            for i in range(1, length + 1):
                prop = group_prop.find_key(str(i), '')  # type: Property

                if icon_folder:
                    icon = '{}/{}/{}.jpg'.format(icon_folder, group, i)
                    # If this doesn't actually exist, don't use this.
                    if 'resources/bee2/corr/' + icon not in data.fsys:
                        LOGGER.debug('No "resources/bee2/{}"!', icon)
                        icon = ''
                else:
                    icon = ''

                if prop.has_children():
                    corridors[group, i] = CorrDesc(
                        name=prop['name', ''],
                        icon=prop['icon', icon],
                        desc=prop['Desc', ''],
                    )
                else:
                    corridors[group, i] = CorrDesc(
                        name=prop.value,
                        icon=icon,
                        desc='',
                    )

        if base == '':
            base = None
        try:
            folder = 'styles/' + info['folder']
        except IndexError:
            # It's OK for override styles to be missing their 'folder'
            # value.
            if data.is_override:
                items = []
                renderables = {}
                vbsp = None
            else:
                raise ValueError(
                    f'Style "{data.id}" missing configuration folder!')
        else:
            with filesystem:
                with filesystem[folder + '/items.txt'].open_str() as f:
                    items, renderables = EditorItem.parse(f)
                try:
                    vbsp = filesystem.read_prop(folder + '/vbsp_config.cfg')
                except FileNotFoundError:
                    vbsp = None

        return cls(
            style_id=data.id,
            selitem_data=selitem_data,
            items=items,
            renderables=renderables,
            config=vbsp,
            base_style=base,
            suggested=sugg,
            has_video=has_video,
            corridors=corridors,
            vpk_name=vpk_name,
        )