Skip to content

Commit 500cd19

Browse files
committed
Update OpenRA agents and add MCP dataflow example (secure version)
1 parent a6c3a7b commit 500cd19

File tree

24 files changed

+1368
-923
lines changed

24 files changed

+1368
-923
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ Red Alert: AI Uprising,红警之AI崛起!我们邀你来共同探索!加
9090
- 参赛者将日志上传至[官网][https://hackathon.mofa.ai/leaderboard/]
9191
- 组委会根据评估规则评分并更新[排名](https://hackathon.mofa.ai/leaderboard/)
9292

93+
#### 日志位置
94+
MacOS位于 : ~/Library/Application Support/OpenRA/Logs
95+
Windows位于:%APPDATA%\OpenRA\Logs
96+
9397
## 决赛(嘉年华)
9498

9599
决赛将于 9月13日-14日 在 [GOSIM China 大会](https://hangzhou2025.gosim.org)现场举办。这不仅仅是是进一步的技术比拼,我们更希望将它打造为红警玩家、AI开发者和开源社区的盛事(嘉年华)。
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# OpenRA Copilot Agent
2+
3+
OpenRA 游戏 AI 助手代理,基于 MoFA 框架实现的单节点游戏控制系统。
4+
5+
## 功能特性
6+
7+
- 使用 OpenAI Function Calling 解析自然语言指令
8+
- 直接调用 OpenRA 游戏 API 执行游戏操作
9+
- 支持单位生产、移动、攻击、建造等完整游戏控制
10+
- 集成 40+ 个游戏控制工具函数
11+
12+
## 安装使用
13+
14+
```bash
15+
pip install -e .
16+
```
17+
18+
## 环境变量
19+
20+
- `OPENAI_API_KEY`: OpenAI API 密钥
21+
- `OPENAI_BASE_URL`: OpenAI API 基础URL
22+
- `OPENAI_MODEL`: 使用的模型名称
23+
- `OPENRA_PATH`: OpenRA AI 库路径
24+
- `OPENRA_LIBRARY_PATH`: OpenRA Copilot 库路径

examples/mofa/agent-hub/openra-copilot-agent/openra_copilot_agent/main.py

Lines changed: 574 additions & 89 deletions
Large diffs are not rendered by default.

examples/mofa/agent-hub/openra-copilot-agent/openra_copilot_agent/openra_tools.py

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@
55

66
import sys
77
import os
8-
sys.path.append(os.getenv('OPENRA_PATH', '/Users/liyao/Code/mofa/OpenCodeAlert/Copilot/openra_ai'))
8+
9+
# 添加 OpenRA 库路径
10+
openra_path = os.getenv('OPENRA_PATH', '/Users/liyao/Code/mofa/OpenCodeAlert/Copilot/openra_ai')
11+
if openra_path not in sys.path:
12+
sys.path.append(openra_path)
13+
14+
# 添加 MCP 项目中的 OpenRA_Copilot_Library 路径
15+
library_path = os.getenv('OPENRA_LIBRARY_PATH', '/Users/liyao/Code/mofa/Hackathon2025/examples/mcp')
16+
if library_path not in sys.path:
17+
sys.path.append(library_path)
918

1019
from OpenRA_Copilot_Library import GameAPI
1120
from OpenRA_Copilot_Library.models import Location, TargetsQueryParam, Actor, MapQueryResult
@@ -86,10 +95,27 @@ def visible_units(self, type: List[str], faction: str, range: str, restrain: Lis
8695
for u in units
8796
]
8897

89-
def produce(self, unit_type: str, quantity: int) -> int:
90-
"""生产指定类型和数量的单位,返回生产任务 ID"""
91-
wait_id = self.api.produce(unit_type, quantity, auto_place_building=True)
92-
return wait_id or -1
98+
def produce(self, unit_type: str, quantity: int) -> Dict[str, Any]:
99+
"""生产指定类型和数量的单位,返回详细结果"""
100+
try:
101+
wait_id = self.api.produce(unit_type, quantity, auto_place_building=True)
102+
if wait_id is None:
103+
return {
104+
"success": False,
105+
"error": "生产失败 - 可能缺少必要的建筑、资源不足、或需要先展开基地车",
106+
"wait_id": None
107+
}
108+
return {
109+
"success": True,
110+
"message": f"成功开始生产{unit_type},数量{quantity}",
111+
"wait_id": wait_id
112+
}
113+
except Exception as e:
114+
return {
115+
"success": False,
116+
"error": f"生产异常: {str(e)}",
117+
"wait_id": None
118+
}
93119

94120
def move_units(self, actor_ids: List[int], x: int, y: int, attack_move: bool = False) -> str:
95121
"""移动一批单位到指定坐标"""
@@ -205,19 +231,22 @@ def update_actor(self, actor_id: int) -> Optional[Dict[str, Any]]:
205231
"hpPercent": getattr(actor, "hp_percent", None)
206232
}
207233

208-
def deploy_units(self, actor_ids: List[int] = None) -> str:
234+
def deploy_units(self, actor_ids: List[int] = None) -> Dict[str, Any]:
209235
"""展开或部署指定单位列表,如果没有指定ID则尝试寻找和部署基地车"""
210236
if actor_ids:
211237
# 指定了ID,正常部署
212-
actors = [Actor(i) for i in actor_ids]
213-
self.api.deploy_units(actors)
214-
return "ok"
238+
try:
239+
actors = [Actor(i) for i in actor_ids]
240+
self.api.deploy_units(actors)
241+
return {"success": True, "message": f"成功部署单位ID: {actor_ids}"}
242+
except Exception as e:
243+
return {"success": False, "error": f"部署指定单位失败: {str(e)}"}
215244
else:
216245
# 没有指定ID,尝试直接使用 API 调用部署基地车
217246
# 1. 先尝试使用内置的 deploy_mcv_and_wait 方法
218247
try:
219248
self.api.deploy_mcv_and_wait(1.0)
220-
return "成功部署基地车 (使用内置方法)"
249+
return {"success": True, "message": "基地车成功展开 (使用内置方法)"}
221250
except Exception as e:
222251
print(f"内置方法失败: {e}")
223252

@@ -233,7 +262,7 @@ def deploy_units(self, actor_ids: List[int] = None) -> str:
233262
for i, params in enumerate(deploy_attempts):
234263
try:
235264
response = self.api._send_request('deploy', params)
236-
return f"成功部署 (尝试{i+1}): {response.get('response', '')}"
265+
return {"success": True, "message": f"基地车部署成功 (方法{i+1}): {response.get('response', '')}"}
237266
except Exception as e:
238267
continue
239268

@@ -243,11 +272,11 @@ def deploy_units(self, actor_ids: List[int] = None) -> str:
243272
actor = self.api.get_actor_by_id(actor_id)
244273
if actor and ('mcv' in actor.type.lower() or 'construction' in actor.type.lower() or '基地车' in actor.type):
245274
self.api.deploy_units([actor])
246-
return f"找到并部署基地车 ID: {actor_id}"
275+
return {"success": True, "message": f"找到并成功部署基地车 ID: {actor_id}"}
247276
except:
248277
continue
249278

250-
return "未找到基地车可以部署,所有尝试都失败了"
279+
return {"success": False, "error": "未找到基地车或所有部署方法都失败了,可能需要在OpenRA游戏中手动操作或检查游戏状态"}
251280

252281
def move_camera_to_actor(self, actor_id: int) -> str:
253282
"""将镜头移动到指定 Actor 的位置"""
@@ -385,4 +414,28 @@ def place_building(self, queue_type: str, x: int = None, y: int = None) -> str:
385414
response = self.api._send_request('place_building', params)
386415
return response.get('response', '建筑放置完成')
387416
except Exception as e:
388-
return f"放置建筑失败: {e}"
417+
return f"放置建筑失败: {e}"
418+
419+
def get_unexplored_nearby_positions(self, map_result: Dict[str, Any], current_x: int, current_y: int, max_distance: int) -> List[Dict[str, int]]:
420+
"""获取当前位置附近尚未探索的坐标列表"""
421+
# 将 dict 转回 MapQueryResult
422+
mq = MapQueryResult(
423+
MapWidth=map_result["width"],
424+
MapHeight=map_result["height"],
425+
Height=map_result["heightMap"],
426+
IsVisible=map_result["visible"],
427+
IsExplored=map_result["explored"],
428+
Terrain=map_result["terrain"],
429+
ResourcesType=map_result["resourcesType"],
430+
Resources=map_result["resources"]
431+
)
432+
433+
# 调用底层方法
434+
locs = self.api.get_unexplored_nearby_positions(
435+
mq,
436+
Location(current_x, current_y),
437+
max_distance
438+
)
439+
440+
# 序列化为 JSON-friendly 格式
441+
return [{"x": loc.x, "y": loc.y} for loc in locs]

0 commit comments

Comments
 (0)