wip deepseek

This commit is contained in:
2026-07-07 16:42:42 +02:00
parent 00c67dd66a
commit 9555423f51
12 changed files with 2871 additions and 13 deletions
+220
View File
@@ -0,0 +1,220 @@
#!/usr/bin/env python3
"""
Extract game knowledge strings from AIAnalyze.cs, expand [MOD:] tags,
and write mod-specific knowledge files.
Usage: python expand_knowledge.py [--cs-path PATH]
Outputs:
knowledge_default.md — base game (vanilla) knowledge
knowledge_corona.md — Corona mod knowledge
"""
import re
import sys
import os
from pathlib import Path
# ── 1. Extract @"" verbatim strings from C# source ─────────────────────────
def extract_verbatim_string(text: str, start: int) -> tuple[str, int]:
"""
Extract a C# @"" verbatim string starting after '@"'.
Returns (content, end_position).
Handles "" as escaped double-quote.
"""
chars = []
i = start
while i < len(text):
c = text[i]
if c == '"':
# Escaped quote "" → one literal "
if i + 1 < len(text) and text[i + 1] == '"':
chars.append('"')
i += 2
continue
# End of verbatim string
i += 1
break
chars.append(c)
i += 1
return ''.join(chars), i
def find_strings(cs_path: str) -> dict[str, str]:
"""Find all known knowledge string variables in the .cs file and return
{variable_name: content}."""
with open(cs_path, 'r', encoding='utf-8') as f:
source = f.read()
known_vars = [
'generalDescriptions',
'alliedDescriptions',
'celestialDescriptions',
'infinityIsleVanilla',
'infinityIsleCorona',
]
results = {}
for var in known_vars:
# Look for: var {name} = @"
pattern = f'var {var} = @"'
idx = source.find(pattern)
if idx == -1:
print(f'[WARN] Could not find "{var}" in source')
continue
content, end = extract_verbatim_string(source, idx + len(pattern))
# content still has the leading newline from @"\n...
results[var] = content
print(f'[OK] {var}: {len(content)} chars')
return results
# ── 2. [MOD:] tag expansion ────────────────────────────────────────────────
def expand_mod_text(text: str, mod_name: str) -> str:
"""Expand [MOD:...] and [MOD:NO:...] tags for the given mod.
Mimics the logic in AIAnalyze.Process()."""
lines = text.replace('\r', '').split('\n')
result_lines = []
skip_next = False
for line in lines:
if skip_next:
skip_next = False
continue
stripped = line.strip()
# Line-level [MOD:xxx] (must be at start of line, possibly with leading spaces)
# Check for [MOD:NO:xxx] first
no_match = re.match(r'^(\s*)\[MOD:NO:([^\]]+)\]$', line)
if no_match:
indent, denied_mod = no_match.groups()
if denied_mod.lower() == mod_name.lower():
# [MOD:NO:corona] when mod is corona → skip content line
skip_next = True
# Either way, skip the tag line itself
continue
# Check for [MOD:xxx]
mod_match = re.match(r'^(\s*)\[MOD:([^\]]+)\]$', line)
if mod_match:
indent, entry_mod = mod_match.groups()
if entry_mod.lower() != mod_name.lower():
# This mod's content doesn't apply → skip content line
skip_next = True
# Skip the tag line itself
continue
# Inline tags: process [MOD:xxx]content[/MOD] and [MOD:NO:xxx]content[/MOD]
# Multiple inline tags can appear on one line (e.g. line 662)
processed = line
while True:
# Find next [MOD: or [MOD:NO:
tag_match = re.search(
r'\[MOD:(NO:)?([^\]]+)\](.*?)\[/MOD\]',
processed,
re.IGNORECASE
)
if not tag_match:
break
is_no = tag_match.group(1) is not None
entry_mod = tag_match.group(2)
inner_text = tag_match.group(3)
before = processed[:tag_match.start()]
after = processed[tag_match.end():]
include = False
if is_no:
# [MOD:NO:corona] → include if mod != corona
if entry_mod.lower() != mod_name.lower():
include = True
else:
# [MOD:corona] → include if mod == corona
if entry_mod.lower() == mod_name.lower():
include = True
if include:
processed = before + inner_text + after
else:
processed = before + after
result_lines.append(processed)
return '\n'.join(result_lines)
# ── 3. Assemble per-mod knowledge ──────────────────────────────────────────
def assemble_knowledge(strings: dict[str, str], mod_name: str) -> str:
"""Assemble the full knowledge text for a given mod."""
parts = []
# generalDescriptions — most is global rules, but has one inline [MOD:CORONA] tag
# on the faction list line; process it to expand that tag.
parts.append(expand_mod_text(strings.get('generalDescriptions', ''), mod_name).strip())
# alliedDescriptions
allied = strings.get('alliedDescriptions', '')
parts.append(expand_mod_text(allied, mod_name).strip())
# celestialDescriptions
celestial = strings.get('celestialDescriptions', '')
parts.append(expand_mod_text(celestial, mod_name).strip())
# Map description — mod-specific
map_key = f'infinityIsle{mod_name.capitalize()}'
if map_key in strings:
parts.append(strings[map_key].strip())
else:
# Fallback: default map
default_map = strings.get('infinityIsleVanilla', '')
parts.append(expand_mod_text(default_map, mod_name).strip())
return '\n\n\n'.join(p for p in parts if p)
# ── 4. Main ────────────────────────────────────────────────────────────────
def main():
# Determine paths
script_dir = Path(__file__).resolve().parent
repo_root = script_dir.parent # tools/ is directly under repo root
cs_path = repo_root / 'Utils' / 'AIAnalyze.cs'
output_dir = repo_root
# Override via CLI
if '--cs-path' in sys.argv:
idx = sys.argv.index('--cs-path')
if idx + 1 < len(sys.argv):
cs_path = Path(sys.argv[idx + 1])
print(f'Reading: {cs_path}')
if not cs_path.exists():
print(f'[ERROR] File not found: {cs_path}')
sys.exit(1)
# Extract all strings
strings = find_strings(str(cs_path))
if not strings:
print('[ERROR] No strings extracted, aborting.')
sys.exit(1)
# Generate per-mod files
for mod_name in ('default', 'corona'):
knowledge = assemble_knowledge(strings, mod_name)
out_path = output_dir / f'knowledge_{mod_name}.md'
with open(out_path, 'w', encoding='utf-8') as f:
f.write(knowledge)
lines = knowledge.count('\n') + 1
print(f'[OK] {out_path.name}: {lines} lines, {len(knowledge)} chars')
print('\nDone. Files written to:', output_dir)
if __name__ == '__main__':
main()