|
5 | 5 | from __future__ import annotations |
6 | 6 |
|
7 | 7 | from typing import Any |
8 | | -from collections.abc import Callable, MutableMapping |
| 8 | +from collections.abc import Callable, MutableMapping, Iterator |
9 | 9 | from collections import Counter, defaultdict, ChainMap, deque |
10 | 10 | from pathlib import Path |
11 | 11 | import argparse |
|
49 | 49 | ('HLS', 'Half-Life: Source'), |
50 | 50 | ('DODS', 'Day of Defeat: Source'), |
51 | 51 | ('CSS', 'Counter-Strike: Source'), |
| 52 | + ('HL2DM', 'Half-Life 2: Deathmatch'), |
52 | 53 | ], |
53 | 54 | 'EP2': [ |
54 | 55 | ('MESA', 'Black Mesa'), |
@@ -714,6 +715,22 @@ def check_ent_sprites(ent: EntityDef, used: dict[str, list[str]]) -> None: |
714 | 715 | used[display].append(ent.classname) |
715 | 716 |
|
716 | 717 |
|
| 718 | +def iter_tags(fgd: FGD) -> Iterator[str]: |
| 719 | + """Iterate over all tags defined in the FGD.""" |
| 720 | + for ent in fgd: |
| 721 | + for helper in ent.get_helpers(HelperExtAppliesTo): |
| 722 | + yield from helper.applies |
| 723 | + for kv_map in ent.keyvalues.values(): |
| 724 | + for tags, kv in kv_map.items(): |
| 725 | + yield from tags |
| 726 | + if kv.val_list is not None: |
| 727 | + for tup in kv.val_list: |
| 728 | + yield from tup[-1] |
| 729 | + for io_map in itertools.chain(ent.inputs.values(), ent.outputs.values()): |
| 730 | + for tags in io_map: |
| 731 | + yield from tags |
| 732 | + |
| 733 | + |
717 | 734 | def action_count( |
718 | 735 | dbase: Path, |
719 | 736 | extra_db: Path | None, |
@@ -957,6 +974,26 @@ def report_missing_res(msg: str) -> None: |
957 | 974 | continue |
958 | 975 | print(f'{len(info):02}: {key[:64]!r} -> {info}') |
959 | 976 |
|
| 977 | + def check_parents(done: set[EntityDef], repeat: set[EntityDef], ent: EntityDef) -> None: |
| 978 | + if ent in done: |
| 979 | + repeat.add(ent) |
| 980 | + else: |
| 981 | + done.add(ent) |
| 982 | + for base in ent.bases: |
| 983 | + assert isinstance(base, EntityDef), (ent, ent.bases) |
| 984 | + check_parents(done, repeat, base) |
| 985 | + |
| 986 | + for ent in fgd: |
| 987 | + done: set[EntityDef] = set() |
| 988 | + repeat: set[EntityDef] = set() |
| 989 | + check_parents(done, repeat, ent) |
| 990 | + if repeat: |
| 991 | + print( |
| 992 | + f'Repeated bases: {ent.classname} = ' |
| 993 | + f'{[ent.classname for ent in repeat]}, ' |
| 994 | + f'all={[ent.classname for ent in done]}' |
| 995 | + ) |
| 996 | + |
960 | 997 |
|
961 | 998 | def action_import( |
962 | 999 | dbase: Path, |
@@ -1092,6 +1129,16 @@ def action_export( |
1092 | 1129 |
|
1093 | 1130 | print(f'Map size: ({fgd.map_size_min}, {fgd.map_size_max})') |
1094 | 1131 |
|
| 1132 | + # Gather all the tags used by entities, make sure there aren't unrecognised ones - typos etc. |
| 1133 | + used_tags = { |
| 1134 | + tag.lstrip('!-+').upper() |
| 1135 | + for tag in iter_tags(fgd) |
| 1136 | + } |
| 1137 | + print(f'{len(used_tags)}/{len(ALL_TAGS)} tags used in DB.') |
| 1138 | + extra_tags = used_tags - ALL_TAGS |
| 1139 | + if extra_tags: |
| 1140 | + raise ValueError(f'Unknown tags: {extra_tags}') |
| 1141 | + |
1095 | 1142 | aliases: dict[EntityDef, str | EntityDef] = {} |
1096 | 1143 | if engine_mode or collapse_bases: |
1097 | 1144 | # In engine mode, we don't care about specific games. |
@@ -1153,6 +1200,8 @@ def action_export( |
1153 | 1200 | break |
1154 | 1201 | elif '-ENGINE' not in tags and '!ENGINE' not in tags: |
1155 | 1202 | tag_map[tags] = value |
| 1203 | + elif isinstance(value, KVDef) and value.editor_only: |
| 1204 | + tag_map[tags - {'-ENGINE', '!ENGINE'}] = value |
1156 | 1205 |
|
1157 | 1206 | if not tag_map: |
1158 | 1207 | # All were set as non-engine, so it's not present. |
@@ -1418,6 +1467,7 @@ def action_export( |
1418 | 1467 | fgd.export( |
1419 | 1468 | txt_f, |
1420 | 1469 | custom_syntax=False, |
| 1470 | + escape_quotes='SINCE_STRATA' in tags, |
1421 | 1471 | # HL2/episodes require the old syntax. |
1422 | 1472 | old_report='UNTIL_L4D' in tags, |
1423 | 1473 | ) |
|
0 commit comments