refactor: merge FH4+FH5 into 'forza_horizon', add separate FH6 game plugin
This commit is contained in:
@@ -102,8 +102,8 @@ TurboSu/
|
||||
│ └── parsers/ # 内置解析器 (base/forza/ac/acc/f1/iracing)
|
||||
├── games/
|
||||
│ ├── builtin/ # 7 款内置游戏插件
|
||||
│ │ ├── forza_horizon_5/ # manifest.json + parser.py
|
||||
│ │ ├── forza_horizon_4/
|
||||
│ │ ├── forza_horizon/ # Forza Horizon 4/5 (合并)
|
||||
│ │ ├── forza_horizon_6/ # Forza Horizon 6
|
||||
│ │ ├── forza_motorsport/
|
||||
│ │ ├── assetto_corsa/
|
||||
│ │ ├── assetto_corsa_competizione/
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"description": "Forza 系列专属复合仪表:转速弧+档位大字+时速+油门刹车行程+油量水温",
|
||||
"author": "Yei.J. (AskaEth)",
|
||||
"version": "1.0.0",
|
||||
"supported_games": "forza_horizon_5,forza_horizon_4,forza_motorsport",
|
||||
"supported_games": "forza_horizon,forza_horizon_6,forza_motorsport",
|
||||
"config": {
|
||||
"icon": "🎮",
|
||||
"aspect_ratio": "16:9",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"description": "当前圈速、最佳圈和上圈时间对比显示。仅支持 Forza 系列和 F1",
|
||||
"author": "Yei.J. (AskaEth)",
|
||||
"version": "1.0.0",
|
||||
"supported_games": "forza_horizon_5,forza_horizon_4,forza_motorsport,f1_24",
|
||||
"supported_games": "forza_horizon,forza_horizon_6,forza_motorsport,f1_24",
|
||||
"config": {
|
||||
"icon": "⏱️",
|
||||
"aspect_ratio": "4:3",
|
||||
|
||||
@@ -38,7 +38,7 @@ dashboards/my_dashboard/
|
||||
| `description` | string | 否 | 描述文字 |
|
||||
| `author` | string | 否 | 作者 |
|
||||
| `version` | string | 否 | 版本号 |
|
||||
| `supported_games` | string | 否 | 支持的游戏: `"all"` 或 ID 逗号分隔 (如 `"forza_horizon_5,f1_24"`) |
|
||||
| `supported_games` | string | 否 | 支持的游戏: `"all"` 或 ID 逗号分隔 (如 `"forza_horizon,f1_24"`) |
|
||||
| `config.icon` | string | 否 | 预览图标 emoji |
|
||||
| `config.aspect_ratio` | string | 否 | 渲染比例: `auto` / `16:9` / `4:3` / `1:1` / `21:9` |
|
||||
| `config.render_mode` | string | 否 | 渲染模式: `contain` / `cover` / `fill` / `center` |
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"id": "forza_horizon_5",
|
||||
"name": "Forza Horizon 5",
|
||||
"id": "forza_horizon",
|
||||
"name": "Forza Horizon 4/5",
|
||||
"parser_type": "forza",
|
||||
"telemetry_format": "fh5",
|
||||
"description": "极限竞速:地平线5 遥测数据输出",
|
||||
"description": "极限竞速:地平线4 & 5 UDP遥测数据 (FH6兼容格式)",
|
||||
"author": "Yei.J. (AskaEth)",
|
||||
"version": "1.0.0",
|
||||
"default_port": 20777
|
||||
@@ -4,15 +4,15 @@ from server.telemetry.data import TelemetryData
|
||||
|
||||
|
||||
def get_parser():
|
||||
return ForzaHorizon4Parser()
|
||||
return ForzaHorizonParser()
|
||||
|
||||
|
||||
class ForzaHorizon4Parser:
|
||||
class ForzaHorizonParser:
|
||||
def game_id(self) -> str:
|
||||
return "forza_horizon_4"
|
||||
return "forza_horizon"
|
||||
|
||||
def parse(self, data: bytes, addr: tuple) -> TelemetryData:
|
||||
td = TelemetryData(game_id="forza_horizon_4", timestamp=time.time())
|
||||
td = TelemetryData(game_id="forza_horizon", timestamp=time.time())
|
||||
td.raw = {"raw_hex": data.hex(), "length": len(data), "addr": f"{addr[0]}:{addr[1]}"}
|
||||
|
||||
if len(data) < 312:
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "forza_horizon_4",
|
||||
"name": "Forza Horizon 4",
|
||||
"parser_type": "forza",
|
||||
"telemetry_format": "fh4",
|
||||
"description": "极限竞速:地平线4 遥测数据输出",
|
||||
"author": "Yei.J. (AskaEth)",
|
||||
"version": "1.0.0",
|
||||
"default_port": 20777
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "forza_horizon_6",
|
||||
"name": "Forza Horizon 6",
|
||||
"parser_type": "forza",
|
||||
"telemetry_format": "fh6",
|
||||
"description": "极限竞速:地平线6 UDP遥测数据 (新增CarGroup/Smashable字段)",
|
||||
"author": "Yei.J. (AskaEth)",
|
||||
"version": "1.0.0",
|
||||
"default_port": 20777
|
||||
}
|
||||
@@ -4,75 +4,52 @@ from server.telemetry.data import TelemetryData
|
||||
|
||||
|
||||
def get_parser():
|
||||
return ForzaHorizon5Parser()
|
||||
return ForzaHorizon6Parser()
|
||||
|
||||
|
||||
class ForzaHorizon5Parser:
|
||||
class ForzaHorizon6Parser:
|
||||
def game_id(self) -> str:
|
||||
return "forza_horizon_5"
|
||||
return "forza_horizon_6"
|
||||
|
||||
def parse(self, data: bytes, addr: tuple) -> TelemetryData:
|
||||
td = TelemetryData(game_id="forza_horizon_5", timestamp=time.time())
|
||||
td.raw = {
|
||||
"raw_hex": data.hex(),
|
||||
"length": len(data),
|
||||
"addr": f"{addr[0]}:{addr[1]}",
|
||||
}
|
||||
td = TelemetryData(game_id="forza_horizon_6", timestamp=time.time())
|
||||
td.raw = {"raw_hex": data.hex(), "length": len(data), "addr": f"{addr[0]}:{addr[1]}"}
|
||||
|
||||
if len(data) < 312:
|
||||
return td
|
||||
|
||||
try:
|
||||
td.raw["is_race_on"] = struct.unpack_from("<i", data, 0)[0]
|
||||
|
||||
# 引擎
|
||||
td.max_rpm = struct.unpack_from("<f", data, 8)[0]
|
||||
td.rpm = struct.unpack_from("<f", data, 16)[0]
|
||||
|
||||
# 加速度 (m/s²)
|
||||
td.acceleration_x = struct.unpack_from("<f", data, 20)[0]
|
||||
td.acceleration_y = struct.unpack_from("<f", data, 24)[0]
|
||||
td.acceleration_z = struct.unpack_from("<f", data, 28)[0]
|
||||
|
||||
# 位置
|
||||
td.raw["car_group"] = struct.unpack_from("<I", data, 232)[0]
|
||||
td.raw["smashable_vel_diff"] = struct.unpack_from("<f", data, 236)[0]
|
||||
td.raw["smashable_mass"] = struct.unpack_from("<f", data, 240)[0]
|
||||
|
||||
td.position_x = struct.unpack_from("<f", data, 244)[0]
|
||||
td.position_y = struct.unpack_from("<f", data, 248)[0]
|
||||
td.position_z = struct.unpack_from("<f", data, 252)[0]
|
||||
|
||||
# 速度 (m/s)
|
||||
td.speed_kmh = struct.unpack_from("<f", data, 256)[0] * 3.6
|
||||
td.speed_mph = td.speed_kmh * 0.621371
|
||||
|
||||
# 马力 & 扭矩
|
||||
td.horsepower = struct.unpack_from("<f", data, 260)[0] / 745.7
|
||||
td.torque = struct.unpack_from("<f", data, 264)[0]
|
||||
|
||||
# 增压 & 燃油
|
||||
td.boost = struct.unpack_from("<f", data, 284)[0]
|
||||
td.fuel = struct.unpack_from("<f", data, 288)[0]
|
||||
|
||||
# 圈速
|
||||
td.best_lap = struct.unpack_from("<f", data, 296)[0]
|
||||
td.last_lap = struct.unpack_from("<f", data, 300)[0]
|
||||
td.lap_time = struct.unpack_from("<f", data, 304)[0]
|
||||
td.lap_number = struct.unpack_from("<H", data, 312)[0]
|
||||
|
||||
# 排名
|
||||
td.raw["race_position"] = struct.unpack_from("<B", data, 314)[0]
|
||||
|
||||
# 玩家输入 (U8, 0-255)
|
||||
td.throttle = struct.unpack_from("<B", data, 315)[0] / 255.0
|
||||
td.brake = struct.unpack_from("<B", data, 316)[0] / 255.0
|
||||
td.clutch = struct.unpack_from("<B", data, 317)[0] / 255.0
|
||||
td.handbrake = struct.unpack_from("<B", data, 318)[0] / 255.0
|
||||
|
||||
# 档位
|
||||
td.gear = struct.unpack_from("<B", data, 319)[0]
|
||||
|
||||
# 转向 (-127 ~ 127)
|
||||
td.steering = max(-1.0, min(1.0,
|
||||
struct.unpack_from("<b", data, 320)[0] / 127.0))
|
||||
|
||||
td.steering = max(-1.0, min(1.0, struct.unpack_from("<b", data, 320)[0] / 127.0))
|
||||
except struct.error:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user