背景

群星更新了4.0,然后新版本的科技树不知道长什么样,科技树mod也坏了,就打算自己修一下,顺便学习一下Antlr4

方案

1. 解析游戏的配置文件

科技树的配置文件在common/technology/目录下
本地化文件在localization/目录下,这次只考虑中文
变量在common/scripted_variables目录下
还有个scripted_trigger在common/scripted_triggers目录下,这次没有用到
每个配置文件的配置项都可以看做是一个对象,对象中有赋值语句和条件计算语句,计划是在grammar中定义整个结构,这样可以在Visitor中读取到每个Property的数据

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
technology_body
: technology_body_start (
area
| tier
| category
| icon
| modifier
| cost
| cost_by_script
| cost_per_level
| is_rare
| is_dangerous
| weight
| levels
| prerequisites
| technology_swap
| potential
| gateway
| repeatable
| weight_groups
| mod_weight_if_group_picked
| start_tech
| is_reverse_engineerable
| ai_update_type
| is_insight
| feature_flags
| prereqfor_desc
| weight_modifier
| ai_weight
| starting_potential)+ technology_body_end
;

area: 'area' ASSIGN area_val;
area_val: val;
tier: 'tier' ASSIGN tier_val;
tier_val: val;
category: 'category' ASSIGN category_val ;
category_val: LBRACE val RBRACE;
icon: 'icon' ASSIGN icon_val;
icon_val: val;
cost: 'cost' ASSIGN cost_val;
cost_val: val;
...
以下忽略

每个Property的val使用单独的rule来定义,这样可以方便在visitor中获取结构简单的值,减少visitor的复杂度

具体的grammar我放在下面的项目中的dev分支:
https://github.com/codexvn/antlr4-stellaris

2. 输出科技树

计划是通过mermaid来输出科技树,原因是语法比较简单
使用类图来进行展示
一方面有依赖属性
另一方面类的属性和方法可以和科技树的属性和解锁条件对应上

具体的流程放在以下的项目中的dev分支
https://github.com/codexvn/stellaris-parser

按照以下模版进行渲染,由于我不懂前端,所以这个模版使用的是chatgpt生成的,支持滚动和缩放

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
                <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Mermaid Graph - Left to Right with Zoom</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#container {
width: 100vw;
height: 100vh;
overflow: hidden;
background: #f9f9f9;
position: relative;
}
#zoom-area {
width: 100%;
height: 100%;
transform-origin: 0 0;
cursor: grab;
}
.mermaid {
font-family: 'Segoe UI', sans-serif;
}
</style>
</head>
<body>
<div id="container">
<div id="zoom-area" class="mermaid">
classDiagram
direction LR
<mermaid_content>
</div>
</div>

<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
mermaid.initialize({
startOnLoad: true,
maxEdges: 5000, // 支持大图
maxTextSize: 1000000
});
</script>

<script>
// 缩放和平移功能
const zoomArea = document.getElementById("zoom-area");
let scale = 1;
let originX = 0, originY = 0;
let isDragging = false;
let startX, startY;

document.addEventListener("wheel", function (e) {
e.preventDefault();
const zoomFactor = 0.1;
if (e.deltaY < 0) {
scale *= (1 + zoomFactor);
} else {
scale *= (1 - zoomFactor);
}
zoomArea.style.transform = `translate(${originX}px, ${originY}px) scale(${scale})`;
}, { passive: false });

zoomArea.addEventListener("mousedown", (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
zoomArea.style.cursor = "grabbing";
});

document.addEventListener("mouseup", () => {
isDragging = false;
zoomArea.style.cursor = "grab";
});

document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
originX += e.clientX - startX;
originY += e.clientY - startY;
startX = e.clientX;
startY = e.clientY;
zoomArea.style.transform = `translate(${originX}px, ${originY}px) scale(${scale})`;
});
</script>
</body>
</html>

最终效果

点击以下链接
构建于20250518