From 801feb040a2a814001d4063a26e488abd5294331 Mon Sep 17 00:00:00 2001 From: hassayag Date: Thu, 4 Jun 2026 23:56:49 +0100 Subject: [PATCH 1/6] ci: do not increment version on _version.py as it will create a blackhole --- .github/workflows/increment-version-dev.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/increment-version-dev.yaml b/.github/workflows/increment-version-dev.yaml index e8a6ed2a..16e342fb 100644 --- a/.github/workflows/increment-version-dev.yaml +++ b/.github/workflows/increment-version-dev.yaml @@ -1,7 +1,10 @@ name: Increment Version (develop) on: push: - paths: 'src/**' + paths: | + - 'src/**' + - '!src/_version.py' + branches: - 'develop' From 9896bea6b8359541bac15042002bb84cba8bd5c7 Mon Sep 17 00:00:00 2001 From: hassayag Date: Thu, 4 Jun 2026 23:58:40 +0100 Subject: [PATCH 2/6] ci: ignore _version.py changes on deploy.yaml --- .github/workflows/deploy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 6e3b5b89..7fdee43a 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -6,6 +6,7 @@ on: - master paths: - 'src/**' + - '!src/_version.py' - 'Dockerfile' - '.github/workflows/deploy.yaml' - 'poetry.lock' From 912a5c6113ab2362f8dd009f430b9ee067dd5710 Mon Sep 17 00:00:00 2001 From: LVL1024 <70866179+LVL1024@users.noreply.github.com> Date: Wed, 17 Jun 2026 08:00:25 -0400 Subject: [PATCH 3/6] ci: fix race condition causing deploy to skip on new PRs (#391) Co-authored-by: hassayag <56391012+hassayag@users.noreply.github.com> --- .github/workflows/deploy.yaml | 10 ---------- .github/workflows/pr-open.yaml | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 7fdee43a..0885eee3 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -11,14 +11,6 @@ on: - '.github/workflows/deploy.yaml' - 'poetry.lock' - pull_request: - types: [opened, reopened] - paths: - - 'src/**' - - 'Dockerfile' - - '.github/workflows/deploy.yaml' - - 'poetry.lock' - workflow_dispatch: inputs: english-only: @@ -62,8 +54,6 @@ jobs: echo "branch=${{ inputs.branch-override }}" >> $GITHUB_OUTPUT elif [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then echo "branch=${{ github.ref_name }}" >> $GITHUB_OUTPUT - elif [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then - echo "branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then echo "branch=${{ github.ref_name }}" >> $GITHUB_OUTPUT else diff --git a/.github/workflows/pr-open.yaml b/.github/workflows/pr-open.yaml index 4985401e..5a2e4f71 100644 --- a/.github/workflows/pr-open.yaml +++ b/.github/workflows/pr-open.yaml @@ -74,3 +74,35 @@ jobs: _Parsed data in [deadlock-data PR](${{ steps.create_data_pr.outputs.pr_url }}) - Reopen deadbot PR or run deploy workflow for this branch [here](https://github.com/deadlock-wiki/deadbot/actions/workflows/deploy.yaml) to reparse the data_" gh pr edit ${{ github.event.number }} --body "$new_body" + + check-deploy-paths: + name: Check deploy paths + runs-on: ubuntu-latest + needs: open-data-pr + outputs: + should_deploy: ${{ steps.filter.outputs.should_deploy }} + steps: + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + should_deploy: + - 'src/!(_version.py)' + - 'src/!(_version.py)/**' + - 'Dockerfile' + - '.github/workflows/deploy.yaml' + - 'poetry.lock' + + trigger-deploy: + name: Trigger Deploy + needs: [open-data-pr, check-deploy-paths] + if: ${{ github.head_ref != 'develop' && github.head_ref != 'master' && needs.check-deploy-paths.outputs.should_deploy == 'true' }} + uses: ./.github/workflows/deploy.yaml + with: + branch-override: ${{ github.head_ref }} + english-only: false + secrets: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} + STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} + BOT_WIKI_PASS: ${{ secrets.BOT_WIKI_PASS }} From cdc5b26ea8062308f52a458e0b6ebcb88f80a1f6 Mon Sep 17 00:00:00 2001 From: Daniil Popov Date: Thu, 18 Jun 2026 14:24:04 +0200 Subject: [PATCH 4/6] feat: parse game convars into convars.json (#393) Co-authored-by: hassayag <56391012+hassayag@users.noreply.github.com> --- src/parser/parser.py | 9 +++ src/parser/parsers/convars.py | 101 ++++++++++++++++++++++++++++++++++ src/wiki/pages.py | 1 + 3 files changed, 111 insertions(+) create mode 100644 src/parser/parsers/convars.py diff --git a/src/parser/parser.py b/src/parser/parser.py index 926b4a20..1083310a 100644 --- a/src/parser/parser.py +++ b/src/parser/parser.py @@ -15,6 +15,7 @@ npc_units, game_map, misc, + convars, ) from utils import json_utils from loguru import logger @@ -148,6 +149,7 @@ def run(self): self._parse_soul_unlocks() self._parse_generics() self._parse_misc() + self._parse_convars() self._parse_map() logger.trace('Done parsing') @@ -313,3 +315,10 @@ def _parse_misc(self): parsed_misc = misc.MiscParser(self.data['scripts']['misc']).run() json_utils.write(self.OUTPUT_DIR + '/json/misc-data.json', json_utils.sort_dict(parsed_misc)) + + def _parse_convars(self): + logger.trace('Parsing Convars...') + convars_file = os.path.join(self.game_dir, 'DumpSource2', 'convars.txt') + parsed_convars = convars.ConvarsParser(convars_file).run() + + json_utils.write(self.OUTPUT_DIR + '/json/convars.json', json_utils.sort_dict(parsed_convars)) diff --git a/src/parser/parsers/convars.py b/src/parser/parsers/convars.py new file mode 100644 index 00000000..1547f125 --- /dev/null +++ b/src/parser/parsers/convars.py @@ -0,0 +1,101 @@ +class ConvarsParser: + """ + Parses the game-wide convars dump (DumpSource2/convars.txt) into a dict + keyed by convar name. Each entry is either the bare type-coerced value, or + a {"value", "description"} object when the convar has a description. + + Each record in the source file spans: + () + # tab-indented, may span multiple lines + + + The literal "" is normalised to an empty string, and the + trailing "()" group is dropped. Values that are quoted in the source + are kept as strings; unquoted values are coerced to bool/int/float when + possible, otherwise left as strings. + """ + + NO_DESCRIPTION = '' + + def __init__(self, convars_file): + self.convars_file = convars_file + + def run(self): + with open(self.convars_file, 'r', encoding='utf-8') as f: + lines = f.read().splitlines() + + convars = {} + i = 0 + total = len(lines) + while i < total: + line = lines[i] + + if line == '' or line[0] in (' ', '\t'): + i += 1 + continue + + name, value = self._parse_definition(line) + + i += 1 + description_lines = [] + while i < total and lines[i].startswith('\t'): + description_lines.append(lines[i].strip()) + i += 1 + + # Skip debug convars - not useful for the wiki + if 'debug' in name.lower(): + continue + + # Skip convars with an empty string value (but keep falsy + # values like 0 and false, which are meaningful) + if isinstance(value, str) and value == '': + continue + + description = ' '.join(description_lines).strip() + if description == self.NO_DESCRIPTION: + description = '' + + # Only wrap in an object when there's a description to carry; + # otherwise store the bare value directly under the convar name + if description: + convars[name] = {'value': value, 'description': description} + else: + convars[name] = value + + return convars + + def _parse_definition(self, line): + """Split a definition line into (name, coerced_value), dropping flags.""" + name, _, rest = line.partition(' ') + + # Flags are the trailing "(...)" group; drop everything from the last + # opening paren onward. Descriptions can contain parens, but they live + # on separate indented lines, so this only ever sees the flags group. + paren_index = rest.rfind('(') + if paren_index != -1: + rest = rest[:paren_index] + + return name, self._coerce(rest.strip()) + + def _coerce(self, raw): + """Coerce an unquoted value to bool/int/float; keep quoted as string.""" + # Explicitly quoted in the dump -> treat as a string (e.g. "value") + if len(raw) >= 2 and raw[0] == '"' and raw[-1] == '"': + return raw[1:-1] + + if raw == 'true': + return True + if raw == 'false': + return False + + try: + return int(raw) + except ValueError: + pass + + try: + return float(raw) + except ValueError: + pass + + return raw diff --git a/src/wiki/pages.py b/src/wiki/pages.py index 867477e7..a4fef650 100644 --- a/src/wiki/pages.py +++ b/src/wiki/pages.py @@ -44,6 +44,7 @@ 'ResourceLookup.json': 'json/resource-lookup.json', 'MidtownMetadata.json': 'json/midtown-metadata.json', 'MiscData.json': 'json/misc-data.json', + 'Convars.json': 'json/convars.json', } # Ignore these pages as they are not automated From 4af96ccf44ef4609e41055a6ac294cf287604a79 Mon Sep 17 00:00:00 2001 From: Daniil Popov Date: Mon, 22 Jun 2026 13:20:33 +0200 Subject: [PATCH 5/6] feat: parse ability behaviour bits into BehaviourBits field (#394) --- src/parser/maps.py | 21 +++++++++++++++++++++ src/parser/parsers/abilities/__main__.py | 1 + 2 files changed, 22 insertions(+) diff --git a/src/parser/maps.py b/src/parser/maps.py index 2a19fd81..31d43eef 100644 --- a/src/parser/maps.py +++ b/src/parser/maps.py @@ -74,6 +74,27 @@ def get_ability_activation(value): return ABILITY_ACTIVATION_MAP.get(value) +def get_behaviour_bits(value): + """Split a '|'-delimited behaviour bits string into a PascalCased list. + + Each flag has its CITADEL_ABILITY_ prefix stripped, e.g. + "CITADEL_ABILITY_BEHAVIOR_MOVEMENT | CITADEL_ABILITY_BEHAVIOR_DEACTIVATE_CROUCH_TOGGLE_ON_CAST" + -> ['BehaviorMovement', 'BehaviorDeactivateCrouchToggleOnCast'] + """ + if not value: + return None + + bits = [] + for token in value.split('|'): + token = token.strip() + if not token: + continue + if token.startswith('CITADEL_ABILITY_'): + token = token[len('CITADEL_ABILITY_') :] + bits.append(''.join(part.capitalize() for part in token.split('_') if part)) + return bits + + # i.e. ECitadelStat_Vitality -> Vitality def get_attr_group(value): return value.split('ECitadelStat_')[1] diff --git a/src/parser/parsers/abilities/__main__.py b/src/parser/parsers/abilities/__main__.py index cc88cc58..1447c484 100644 --- a/src/parser/parsers/abilities/__main__.py +++ b/src/parser/parsers/abilities/__main__.py @@ -43,6 +43,7 @@ def _parse_ability(self, ability_key): 'Key': ability_key, 'Name': self.localizations.get(ability_key, None), 'IsDisabled': ability.get('m_bDisabled', False), + 'BehaviourBits': maps.get_behaviour_bits(ability.get('m_AbilityBehaviorsBits')), } stats = ability.get('m_mapAbilityProperties', {}) From 7cf26b1c15c6d9887bc97d8aa68b3def19f1691d Mon Sep 17 00:00:00 2001 From: Deadbot0 Date: Mon, 22 Jun 2026 11:23:00 +0000 Subject: [PATCH 6/6] [skip ci] chore: bumped version to 1.14.0 --- pyproject.toml | 2 +- src/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 200b03e4..88a60d6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "Deadbot" -version = "1.13.2" +version = "1.14.0" description = "Bot that lives to serve deadlock.wiki" readme = "README.md" authors=[] diff --git a/src/_version.py b/src/_version.py index f43a2786..e4f2ad49 100644 --- a/src/_version.py +++ b/src/_version.py @@ -1 +1 @@ -__version__ = '1.13.2' +__version__ = '1.14.0'