aboutsummaryrefslogtreecommitdiff
path: root/libs/basic_games/games/game_borderlands1.py
blob: 4b90b51368da600b2ed6a795499063393a51103b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import re

import mobase
from mobase import FileTreeEntry, IFileTree, ModDataChecker

from ..basic_features import BasicModDataChecker
from ..basic_game import BasicGame

_extention_pattern = re.compile("\\.(upk|umap|u|int|dll|exe)$", re.I)
_mapslot_pattern = re.compile("^Mapslot\\d\\d?\\.umap$", re.I)

_mod_dirs = {
    "Binaries".casefold(): "/",
    "WillowGame".casefold(): "/",
    "CookedPC".casefold(): "WillowGame/",
    "Localization".casefold(): "WillowGame/",
    "Maps".casefold(): "WillowGame/CookedPC/",
}

_slots_path = "WillowGame/CookedPC/Maps/MapSlots"


def _check_filetree(
    filetree: IFileTree | FileTreeEntry, recurse: bool
) -> ModDataChecker.CheckReturn:
    if filetree.isFile():
        if _extention_pattern.search(filetree.name()):
            if _mapslot_pattern.search(filetree.name()):
                return ModDataChecker.FIXABLE
            else:
                return ModDataChecker.INVALID
    elif recurse and isinstance(filetree, IFileTree):
        status = ModDataChecker.VALID
        for child in filetree:
            child_status = _check_filetree(child, True)
            if child_status is ModDataChecker.INVALID:
                return ModDataChecker.INVALID
            elif child_status is ModDataChecker.FIXABLE:
                status = ModDataChecker.FIXABLE
        return status

    return ModDataChecker.VALID


def _get_nest(filetree: IFileTree) -> IFileTree | None:
    children = tuple(filetree)
    if (
        len(children) == 1
        and children[0].isDir()
        and isinstance(children[0], IFileTree)
        and _mod_dirs.get(children[0].name().casefold()) is None
    ):
        return children[0]
    return None


def _get_slotstree(roottree: IFileTree) -> IFileTree:
    slotstree: IFileTree | None = roottree.find(_slots_path)  # type: ignore
    if slotstree is None:
        slotstree = roottree.addDirectory(_slots_path)
    return slotstree


def _fix_mapslots(filetree: IFileTree, roottree: IFileTree) -> None:
    for child in filetree:
        if child.isDir() and isinstance(child, IFileTree):
            _fix_mapslots(child, roottree)
        elif _mapslot_pattern.search(child.name()):
            child.moveTo(_get_slotstree(roottree))


class Borderlands1ModDataChecker(BasicModDataChecker):
    def dataLooksValid(self, filetree: IFileTree) -> ModDataChecker.CheckReturn:
        parent = filetree.parent()
        if parent is not None:
            return self.dataLooksValid(parent)

        status = ModDataChecker.VALID

        nest = _get_nest(filetree)
        if nest is not None:
            status = ModDataChecker.FIXABLE
            filetree = nest

        if _check_filetree(filetree, False) is ModDataChecker.INVALID:
            return ModDataChecker.INVALID

        slotstree = filetree.find(_slots_path)
        if slotstree is not None and not slotstree.isDir():
            return ModDataChecker.INVALID

        for child in filetree:
            if child.isDir():
                destination = _mod_dirs.get(child.name().casefold())
                if destination is None:
                    child_status = _check_filetree(child, True)
                    if child_status is ModDataChecker.INVALID:
                        return ModDataChecker.INVALID
                    elif child_status is ModDataChecker.FIXABLE:
                        status = ModDataChecker.FIXABLE
                elif destination != "/":
                    status = ModDataChecker.FIXABLE
            elif _mapslot_pattern.search(child.name()):
                status = ModDataChecker.FIXABLE
            elif _extention_pattern.search(child.name()):
                return ModDataChecker.INVALID
        return status

    def fix(self, filetree: IFileTree) -> IFileTree:
        nest = _get_nest(filetree)
        if nest is not None:
            conflict: FileTreeEntry | None = None
            for child in tuple(nest):
                if not child.moveTo(filetree):
                    conflict = child
                    conflict.detach()
            filetree.remove(nest)
            if conflict is not None:
                conflict.moveTo(filetree)

        for child in tuple(filetree):
            if child.isDir() and isinstance(child, IFileTree):
                destination = _mod_dirs.get(child.name().casefold())
                if destination is None:
                    _fix_mapslots(child, filetree)
                elif destination != "/":
                    filetree.move(child, destination)
            elif _mapslot_pattern.search(child.name()):
                child.moveTo(_get_slotstree(filetree))

        return filetree


class Borderlands1Game(BasicGame):
    Name = "Borderlands 1 Support Plugin"
    Author = "Miner Of Worlds, RedxYeti, mopioid"
    Version = "1.0.0"

    def init(self, organizer: mobase.IOrganizer) -> bool:
        super().init(organizer)
        self._register_feature(Borderlands1ModDataChecker())
        return True

    GameName = "Borderlands"
    GameShortName = "Borderlands"
    GameNexusName = "Borderlands GOTY"
    GameSteamId = 8980
    GameBinary = "Binaries/Borderlands.exe"
    GameDataPath = "."
    GameSaveExtension = "sav"
    GameDocumentsDirectory = "%DOCUMENTS%/My Games/Borderlands/"
    GameSavesDirectory = "%GAME_DOCUMENTS%/savedata"
    GameIniFiles = "%GAME_DOCUMENTS%/WillowGame/Config"