feat: PartyRoomGameLogTrackerProvider

This commit is contained in:
xkeyC 2025-11-20 23:58:31 +08:00
parent f551ccfbde
commit 3e9f82ecdf
22 changed files with 320 additions and 140 deletions

View File

@ -15,8 +15,8 @@ class LogAnalyzeLineData {
// //
final String? victimId; // ID (actor_death) final String? victimId; // ID (actor_death)
final String? killerId; // ID (actor_death)
final String? location; // (request_location_inventory) final String? location; // (request_location_inventory)
final String? area; //
final String? playerName; // (player_login) final String? playerName; // (player_login)
const LogAnalyzeLineData({ const LogAnalyzeLineData({
@ -26,8 +26,8 @@ class LogAnalyzeLineData {
this.dateTime, this.dateTime,
this.tag, this.tag,
this.victimId, this.victimId,
this.killerId,
this.location, this.location,
this.area,
this.playerName, this.playerName,
}); });
@ -76,10 +76,10 @@ class GameLogAnalyzer {
// //
static final _fatalCollisionPatterns = { static final _fatalCollisionPatterns = {
'zone': RegExp(r'\[Part:[^\]]*?Zone:\s*([^,\]]+)'), 'vehicle': RegExp(r'Fatal Collision occured for vehicle\s+(\S+)'),
'zone': RegExp(r'Zone:\s*([^,\]]+)'),
'player_pilot': RegExp(r'PlayerPilot:\s*(\d)'), 'player_pilot': RegExp(r'PlayerPilot:\s*(\d)'),
'hit_entity': RegExp(r'hitting entity:\s*(\w+)'), 'hit_entity': RegExp(r'hitting entity:\s*(\w+)'),
'hit_entity_vehicle': RegExp(r'hitting entity:[^\[]*\[Zone:\s*([^\s-]+)'),
'distance': RegExp(r'Distance:\s*([\d.]+)'), 'distance': RegExp(r'Distance:\s*([\d.]+)'),
}; };
@ -93,10 +93,9 @@ class GameLogAnalyzer {
// //
static final _actorDeathPattern = RegExp( static final _actorDeathPattern = RegExp(
r"CActor::Kill: '([^']+)'.*?" // ID r"Actor '([^']+)'.*?" // ID
r"in zone '([^']+)'.*?" // r"ejected from zone '([^']+)'.*?" // /
r"killed by '([^']+)'.*?" // ID r"to zone '([^']+)'", //
r"with damage type '([^']+)'", //
); );
// //
@ -217,7 +216,7 @@ class GameLogAnalyzer {
} }
}); });
break; break;
case "Actor Death": case "[ActorState] Dead":
data = _parseActorDeath(line, playerName, shouldCount, (isKill, isDeath, isSelfKill) { data = _parseActorDeath(line, playerName, shouldCount, (isKill, isDeath, isSelfKill) {
if (isSelfKill) { if (isSelfKill) {
selfKillCount++; selfKillCount++;
@ -372,10 +371,10 @@ class GameLogAnalyzer {
static String? _safeExtract(RegExp pattern, String line) => pattern.firstMatch(line)?.group(1)?.trim(); static String? _safeExtract(RegExp pattern, String line) => pattern.firstMatch(line)?.group(1)?.trim();
static LogAnalyzeLineData? _parseFatalCollision(String line) { static LogAnalyzeLineData? _parseFatalCollision(String line) {
final vehicle = _safeExtract(_fatalCollisionPatterns['vehicle']!, line) ?? unknownValue;
final zone = _safeExtract(_fatalCollisionPatterns['zone']!, line) ?? unknownValue; final zone = _safeExtract(_fatalCollisionPatterns['zone']!, line) ?? unknownValue;
final playerPilot = (_safeExtract(_fatalCollisionPatterns['player_pilot']!, line) ?? '0') == '1'; final playerPilot = (_safeExtract(_fatalCollisionPatterns['player_pilot']!, line) ?? '0') == '1';
final hitEntity = _safeExtract(_fatalCollisionPatterns['hit_entity']!, line) ?? unknownValue; final hitEntity = _safeExtract(_fatalCollisionPatterns['hit_entity']!, line) ?? unknownValue;
final hitEntityVehicle = _safeExtract(_fatalCollisionPatterns['hit_entity_vehicle']!, line) ?? unknownValue;
final distance = double.tryParse(_safeExtract(_fatalCollisionPatterns['distance']!, line) ?? '') ?? 0.0; final distance = double.tryParse(_safeExtract(_fatalCollisionPatterns['distance']!, line) ?? '') ?? 0.0;
return LogAnalyzeLineData( return LogAnalyzeLineData(
@ -385,7 +384,7 @@ class GameLogAnalyzer {
zone, zone,
playerPilot ? '' : '', playerPilot ? '' : '',
hitEntity, hitEntity,
hitEntityVehicle, vehicle,
distance.toStringAsFixed(2), distance.toStringAsFixed(2),
), ),
dateTime: _getLogLineDateTimeString(line), dateTime: _getLogLineDateTimeString(line),
@ -436,27 +435,25 @@ class GameLogAnalyzer {
final match = _actorDeathPattern.firstMatch(line); final match = _actorDeathPattern.firstMatch(line);
if (match != null) { if (match != null) {
final victimId = match.group(1) ?? unknownValue; final victimId = match.group(1) ?? unknownValue;
final zone = match.group(2) ?? unknownValue; final fromZone = match.group(2) ?? unknownValue;
final killerId = match.group(3) ?? unknownValue; final toZone = match.group(3) ?? unknownValue;
final damageType = match.group(4) ?? unknownValue;
if (shouldCount) { if (shouldCount) {
if (victimId.trim() == killerId.trim()) { final isDeath = victimId.trim() == playerName;
onDeath(false, false, true); // if (isDeath) {
} else { onDeath(false, true, false);
final isDeath = victimId.trim() == playerName;
final isKill = killerId.trim() == playerName;
onDeath(isKill, isDeath, false);
} }
} }
return LogAnalyzeLineData( return LogAnalyzeLineData(
type: "actor_death", type: "actor_death",
title: S.current.log_analyzer_filter_character_death, title: S.current.log_analyzer_filter_character_death,
data: S.current.log_analyzer_death_details(victimId, damageType, killerId, zone), data: S.current.log_analyzer_death_details(victimId, fromZone, toZone),
dateTime: _getLogLineDateTimeString(line), dateTime: _getLogLineDateTimeString(line),
victimId: victimId, // location: fromZone,
killerId: killerId, // area: toZone,
victimId: victimId,
playerName: playerName,
); );
} }
return null; return null;

View File

@ -130,8 +130,8 @@ class MessageLookup extends MessageLookupByLibrary {
static String m46(v0, v1, v2, v3, v4) => static String m46(v0, v1, v2, v3, v4) =>
"Area: ${v0} Player driving: ${v1} Collision entity: ${v2} \nCollision vehicle: ${v3} Collision distance: ${v4} "; "Area: ${v0} Player driving: ${v1} Collision entity: ${v2} \nCollision vehicle: ${v3} Collision distance: ${v4} ";
static String m47(v0, v1, v2, v3) => static String m47(v0, v2, v3) =>
"Victim ID: ${v0} Cause of death: ${v1} \nKiller ID: ${v2} \nArea: ${v3}"; "Victim ID: ${v0} \nLocation: ${v2} \nArea: ${v3}";
static String m48(v0) => "Detailed information: ${v0}"; static String m48(v0) => "Detailed information: ${v0}";

View File

@ -119,8 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m46(v0, v1, v2, v3, v4) => static String m46(v0, v1, v2, v3, v4) =>
"エリア:${v0} プレイヤー操縦:${v1} 衝突エンティティ:${v2} \n衝突ビークル:${v3} 衝突距離:${v4} "; "エリア:${v0} プレイヤー操縦:${v1} 衝突エンティティ:${v2} \n衝突ビークル:${v3} 衝突距離:${v4} ";
static String m47(v0, v1, v2, v3) => static String m47(v0, v2, v3) => "被害者ID${v0} \n位置:${v2} \nエリア:${v3}";
"被害者ID${v0} 死因:${v1} \n殺害者ID${v2} \nエリア:${v3}";
static String m48(v0) => "詳細情報:${v0}"; static String m48(v0) => "詳細情報:${v0}";

View File

@ -124,8 +124,8 @@ class MessageLookup extends MessageLookupByLibrary {
static String m46(v0, v1, v2, v3, v4) => static String m46(v0, v1, v2, v3, v4) =>
"Зона: ${v0} Управление игроком: ${v1} Объект столкновения: ${v2} \nТехника столкновения: ${v3} Дистанция столкновения: ${v4} "; "Зона: ${v0} Управление игроком: ${v1} Объект столкновения: ${v2} \nТехника столкновения: ${v3} Дистанция столкновения: ${v4} ";
static String m47(v0, v1, v2, v3) => static String m47(v0, v2, v3) =>
"ID жертвы: ${v0} Причина смерти: ${v1} \nID убийцы: ${v2} \nЗона: ${v3}"; "ID жертвы: ${v0} \nID убийцы: ${v2} \nЗона: ${v3}";
static String m48(v0) => "Подробная информация: ${v0}"; static String m48(v0) => "Подробная информация: ${v0}";

View File

@ -119,8 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m46(v0, v1, v2, v3, v4) => static String m46(v0, v1, v2, v3, v4) =>
"区域:${v0} 玩家驾驶:${v1} 碰撞实体:${v2} \n碰撞载具: ${v3} 碰撞距离:${v4} "; "区域:${v0} 玩家驾驶:${v1} 碰撞实体:${v2} \n碰撞载具: ${v3} 碰撞距离:${v4} ";
static String m47(v0, v1, v2, v3) => static String m47(v0, v2, v3) => "受害者ID${v0} \n位置:${v2} \n区域:${v3}";
"受害者ID${v0} 死因:${v1} \n击杀者ID${v2} \n区域:${v3}";
static String m48(v0) => "详细信息:${v0}"; static String m48(v0) => "详细信息:${v0}";
@ -1406,7 +1405,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_rsi_launcher_enhance_init_msg2": "tools_rsi_launcher_enhance_init_msg2":
MessageLookupByLibrary.simpleMessage("正在从网络获取增强数据..."), MessageLookupByLibrary.simpleMessage("正在从网络获取增强数据..."),
"tools_rsi_launcher_enhance_msg_error": "tools_rsi_launcher_enhance_msg_error":
MessageLookupByLibrary.simpleMessage("获取增强数据失败,可能是网络问题或当前版本不支持"), MessageLookupByLibrary.simpleMessage("当前版本不支持,請等待適配..."),
"tools_rsi_launcher_enhance_msg_error_get_launcher_info_error": "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error":
MessageLookupByLibrary.simpleMessage("读取启动器信息失败!"), MessageLookupByLibrary.simpleMessage("读取启动器信息失败!"),
"tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args": "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args":

View File

@ -115,8 +115,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m46(v0, v1, v2, v3, v4) => static String m46(v0, v1, v2, v3, v4) =>
"區域:${v0} 玩家駕駛:${v1} 碰撞實體:${v2} \n碰撞載具: ${v3} 碰撞距離:${v4} "; "區域:${v0} 玩家駕駛:${v1} 碰撞實體:${v2} \n碰撞載具: ${v3} 碰撞距離:${v4} ";
static String m47(v0, v1, v2, v3) => static String m47(v0, v2, v3) => "受害者ID${v0} \n位置:${v2} \n區域:${v3}";
"受害者ID${v0} 死因:${v1} \n擊殺者ID${v2} \n區域:${v3}";
static String m48(v0) => "詳細資訊:${v0}"; static String m48(v0) => "詳細資訊:${v0}";
@ -1397,7 +1396,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_rsi_launcher_enhance_init_msg2": "tools_rsi_launcher_enhance_init_msg2":
MessageLookupByLibrary.simpleMessage("正在從網路取得增強資料..."), MessageLookupByLibrary.simpleMessage("正在從網路取得增強資料..."),
"tools_rsi_launcher_enhance_msg_error": "tools_rsi_launcher_enhance_msg_error":
MessageLookupByLibrary.simpleMessage("增強資料取得失敗,可能是網路問題或目前版本不支援"), MessageLookupByLibrary.simpleMessage("目前版本不支援"),
"tools_rsi_launcher_enhance_msg_error_get_launcher_info_error": "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error":
MessageLookupByLibrary.simpleMessage("讀取啟動器資訊失敗!"), MessageLookupByLibrary.simpleMessage("讀取啟動器資訊失敗!"),
"tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args": "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args":

View File

@ -5808,18 +5808,13 @@ class S {
); );
} }
/// `Victim ID: {v0} Cause of death: {v1} \nKiller ID: {v2} \nArea: {v3}` /// `Victim ID: {v0} \nLocation: {v2} \nArea: {v3}`
String log_analyzer_death_details( String log_analyzer_death_details(Object v0, Object v2, Object v3) {
Object v0,
Object v1,
Object v2,
Object v3,
) {
return Intl.message( return Intl.message(
'Victim ID: $v0 Cause of death: $v1 \nKiller ID: $v2 \nArea: $v3', 'Victim ID: $v0 \nLocation: $v2 \nArea: $v3',
name: 'log_analyzer_death_details', name: 'log_analyzer_death_details',
desc: '', desc: '',
args: [v0, v1, v2, v3], args: [v0, v2, v3],
); );
} }

View File

@ -1141,7 +1141,7 @@
"@log_analyzer_disintegration": {}, "@log_analyzer_disintegration": {},
"log_analyzer_vehicle_damage_details": "Vehicle model: {v0} \nArea: {v1} \nDamage level: {v2} ({v3}) Responsible party: {v4}", "log_analyzer_vehicle_damage_details": "Vehicle model: {v0} \nArea: {v1} \nDamage level: {v2} ({v3}) Responsible party: {v4}",
"@log_analyzer_vehicle_damage_details": {}, "@log_analyzer_vehicle_damage_details": {},
"log_analyzer_death_details": "Victim ID: {v0} Cause of death: {v1} \nKiller ID: {v2} \nArea: {v3}", "log_analyzer_death_details": "Victim ID: {v0} \nLocation: {v2} \nArea: {v3}",
"@log_analyzer_death_details": {}, "@log_analyzer_death_details": {},
"log_analyzer_player_login": "Player {v0} logged in...", "log_analyzer_player_login": "Player {v0} logged in...",
"@log_analyzer_player_login": {}, "@log_analyzer_player_login": {},

View File

@ -1139,7 +1139,7 @@
"@log_analyzer_disintegration": {}, "@log_analyzer_disintegration": {},
"log_analyzer_vehicle_damage_details": "ビークルモデル:{v0} \nエリア{v1} \n損傷レベル{v2} {v3} 責任者:{v4}", "log_analyzer_vehicle_damage_details": "ビークルモデル:{v0} \nエリア{v1} \n損傷レベル{v2} {v3} 責任者:{v4}",
"@log_analyzer_vehicle_damage_details": {}, "@log_analyzer_vehicle_damage_details": {},
"log_analyzer_death_details": "被害者ID{v0} 死因:{v1} \n殺害者ID{v2} \nエリア{v3}", "log_analyzer_death_details": "被害者ID{v0} \n位置{v2} \nエリア{v3}",
"@log_analyzer_death_details": {}, "@log_analyzer_death_details": {},
"log_analyzer_player_login": "プレイヤー {v0} ログイン中...", "log_analyzer_player_login": "プレイヤー {v0} ログイン中...",
"@log_analyzer_player_login": {}, "@log_analyzer_player_login": {},

View File

@ -1139,7 +1139,7 @@
"@log_analyzer_disintegration": {}, "@log_analyzer_disintegration": {},
"log_analyzer_vehicle_damage_details": "Модель техники: {v0} \nЗона: {v1} \nУровень повреждения: {v2} ({v3}) Виновник: {v4}", "log_analyzer_vehicle_damage_details": "Модель техники: {v0} \nЗона: {v1} \nУровень повреждения: {v2} ({v3}) Виновник: {v4}",
"@log_analyzer_vehicle_damage_details": {}, "@log_analyzer_vehicle_damage_details": {},
"log_analyzer_death_details": "ID жертвы: {v0} Причина смерти: {v1} \nID убийцы: {v2} \nЗона: {v3}", "log_analyzer_death_details": "ID жертвы: {v0} \nID убийцы: {v2} \nЗона: {v3}",
"@log_analyzer_death_details": {}, "@log_analyzer_death_details": {},
"log_analyzer_player_login": "Игрок {v0} входит в игру...", "log_analyzer_player_login": "Игрок {v0} входит в игру...",
"@log_analyzer_player_login": {}, "@log_analyzer_player_login": {},

View File

@ -772,7 +772,7 @@
"tools_rsi_launcher_enhance_title": "RSI 启动器增强", "tools_rsi_launcher_enhance_title": "RSI 启动器增强",
"tools_rsi_launcher_enhance_msg_version": "启动器内部版本信息:{v0}", "tools_rsi_launcher_enhance_msg_version": "启动器内部版本信息:{v0}",
"tools_rsi_launcher_enhance_msg_patch_status": "补丁状态:{v0}", "tools_rsi_launcher_enhance_msg_patch_status": "补丁状态:{v0}",
"tools_rsi_launcher_enhance_msg_error": "获取增强数据失败,可能是网络问题或当前版本不支持", "tools_rsi_launcher_enhance_msg_error": "当前版本不支持,請等待適配...",
"tools_rsi_launcher_enhance_title_localization": "RSI 启动器本地化", "tools_rsi_launcher_enhance_title_localization": "RSI 启动器本地化",
"tools_rsi_launcher_enhance_subtitle_localization": "为 RSI 启动器增加多语言支持。", "tools_rsi_launcher_enhance_subtitle_localization": "为 RSI 启动器增加多语言支持。",
"tools_rsi_launcher_enhance_title_download_booster": "RSI 启动器下载增强", "tools_rsi_launcher_enhance_title_download_booster": "RSI 启动器下载增强",
@ -890,7 +890,7 @@
"log_analyzer_soft_death": "软死亡", "log_analyzer_soft_death": "软死亡",
"log_analyzer_disintegration": "解体", "log_analyzer_disintegration": "解体",
"log_analyzer_vehicle_damage_details": "载具型号:{v0} \n区域{v1} \n损毁等级{v2} {v3} 责任方:{v4}", "log_analyzer_vehicle_damage_details": "载具型号:{v0} \n区域{v1} \n损毁等级{v2} {v3} 责任方:{v4}",
"log_analyzer_death_details": "受害者ID{v0} 死因:{v1} \n击杀者ID{v2} \n区域{v3}", "log_analyzer_death_details": "受害者ID{v0} \n位置{v2} \n区域{v3}",
"log_analyzer_player_login": "玩家 {v0} 登录 ...", "log_analyzer_player_login": "玩家 {v0} 登录 ...",
"log_analyzer_view_local_inventory": "查看本地库存", "log_analyzer_view_local_inventory": "查看本地库存",
"log_analyzer_player_location": "玩家ID{v0} 位置:{v1}", "log_analyzer_player_location": "玩家ID{v0} 位置:{v1}",

View File

@ -907,7 +907,7 @@
"@tools_rsi_launcher_enhance_msg_version": {}, "@tools_rsi_launcher_enhance_msg_version": {},
"tools_rsi_launcher_enhance_msg_patch_status": "補丁狀態:{v0}", "tools_rsi_launcher_enhance_msg_patch_status": "補丁狀態:{v0}",
"@tools_rsi_launcher_enhance_msg_patch_status": {}, "@tools_rsi_launcher_enhance_msg_patch_status": {},
"tools_rsi_launcher_enhance_msg_error": "增強資料取得失敗,可能是網路問題或目前版本不支援", "tools_rsi_launcher_enhance_msg_error": "目前版本不支援",
"@tools_rsi_launcher_enhance_msg_error": {}, "@tools_rsi_launcher_enhance_msg_error": {},
"tools_rsi_launcher_enhance_title_localization": "RSI 啟動器本地化", "tools_rsi_launcher_enhance_title_localization": "RSI 啟動器本地化",
"@tools_rsi_launcher_enhance_title_localization": {}, "@tools_rsi_launcher_enhance_title_localization": {},
@ -1141,7 +1141,7 @@
"@log_analyzer_disintegration": {}, "@log_analyzer_disintegration": {},
"log_analyzer_vehicle_damage_details": "載具型號:{v0} \n區域{v1} \n損毀等級{v2} {v3} 責任方:{v4}", "log_analyzer_vehicle_damage_details": "載具型號:{v0} \n區域{v1} \n損毀等級{v2} {v3} 責任方:{v4}",
"@log_analyzer_vehicle_damage_details": {}, "@log_analyzer_vehicle_damage_details": {},
"log_analyzer_death_details": "受害者ID{v0} 死因:{v1} \n擊殺者ID{v2} \n區域{v3}", "log_analyzer_death_details": "受害者ID{v0} \n位置{v2} \n區域{v3}",
"@log_analyzer_death_details": {}, "@log_analyzer_death_details": {},
"log_analyzer_player_login": "玩家 {v0} 登入 ...", "log_analyzer_player_login": "玩家 {v0} 登入 ...",
"@log_analyzer_player_login": {}, "@log_analyzer_player_login": {},

View File

@ -75,7 +75,11 @@ class PartyRoom extends _$PartyRoom {
Box? _confBox; Box? _confBox;
StreamSubscription<partroom.RoomEvent>? _eventStreamSubscription; StreamSubscription<partroom.RoomEvent>? _eventStreamSubscription;
Timer? _heartbeatTimer; Timer? _heartbeatTimer;
Timer? _reconnectTimer;
bool _disposed = false; bool _disposed = false;
int _reconnectAttempts = 0;
static const int _maxReconnectAttempts = 5;
static const Duration _reconnectDelay = Duration(seconds: 3);
@override @override
PartyRoomFullState build() { PartyRoomFullState build() {
@ -398,6 +402,7 @@ class PartyRoom extends _$PartyRoom {
await _stopHeartbeat(); await _stopHeartbeat();
await _stopEventStream(); await _stopEventStream();
_reconnectAttempts = 0;
_dismissRoom(); _dismissRoom();
dPrint('[PartyRoom] Left room: $roomUuid'); dPrint('[PartyRoom] Left room: $roomUuid');
@ -421,6 +426,7 @@ class PartyRoom extends _$PartyRoom {
await _stopHeartbeat(); await _stopHeartbeat();
await _stopEventStream(); await _stopEventStream();
_reconnectAttempts = 0;
_dismissRoom(); _dismissRoom();
dPrint('[PartyRoom] Dismissed room: $roomUuid'); dPrint('[PartyRoom] Dismissed room: $roomUuid');
@ -754,24 +760,106 @@ class PartyRoom extends _$PartyRoom {
_eventStreamSubscription = stream.listen( _eventStreamSubscription = stream.listen(
(event) { (event) {
//
_reconnectAttempts = 0;
_handleRoomEvent(event); _handleRoomEvent(event);
}, },
onError: (error) { onError: (error) {
dPrint('[PartyRoom] Event stream error: $error'); dPrint('[PartyRoom] Event stream error: $error');
//
_scheduleReconnect(roomUuid);
}, },
onDone: () { onDone: () {
dPrint('[PartyRoom] Event stream closed'); dPrint('[PartyRoom] Event stream closed');
//
_scheduleReconnect(roomUuid);
}, },
); );
dPrint('[PartyRoom] Event stream started'); dPrint('[PartyRoom] Event stream started');
//
_reconnectAttempts = 0;
} catch (e) { } catch (e) {
dPrint('[PartyRoom] StartEventStream error: $e'); dPrint('[PartyRoom] StartEventStream error: $e');
//
_scheduleReconnect(roomUuid);
}
}
///
void _scheduleReconnect(String roomUuid) {
//
if (_disposed || state.room.roomUuid == null || state.room.roomUuid != roomUuid) {
dPrint('[PartyRoom] Skip reconnect: disposed=$_disposed, roomUuid=${state.room.roomUuid}');
return;
}
//
if (_reconnectTimer?.isActive ?? false) {
return;
}
//
if (_reconnectAttempts >= _maxReconnectAttempts) {
dPrint('[PartyRoom] Max reconnect attempts reached ($_maxReconnectAttempts)');
return;
}
_reconnectAttempts++;
dPrint(
'[PartyRoom] Scheduling reconnect attempt $_reconnectAttempts/$_maxReconnectAttempts in ${_reconnectDelay.inSeconds}s',
);
_reconnectTimer = Timer(_reconnectDelay, () async {
await _attemptReconnect(roomUuid);
});
}
///
Future<void> _attemptReconnect(String roomUuid) async {
if (_disposed || state.room.roomUuid == null || state.room.roomUuid != roomUuid) {
dPrint('[PartyRoom] Abort reconnect: no longer in room');
return;
}
try {
dPrint('[PartyRoom] Attempting to reconnect event stream...');
//
final client = state.client.roomClient;
if (client == null) {
dPrint('[PartyRoom] Reconnect failed: client not available');
_scheduleReconnect(roomUuid);
return;
}
// getMyRoom
final response = await client.getMyRoom(partroom.GetMyRoomRequest(), options: _getAuthCallOptions());
if (!response.hasRoom() || response.room.roomUuid != roomUuid) {
dPrint('[PartyRoom] Reconnect failed: no longer in room');
//
await _stopHeartbeat();
await _stopEventStream();
_reconnectAttempts = 0;
_dismissRoom();
return;
}
//
dPrint('[PartyRoom] Still in room, restarting event stream');
await _startEventStream(roomUuid);
} catch (e) {
dPrint('[PartyRoom] Reconnect attempt failed: $e');
//
_scheduleReconnect(roomUuid);
} }
} }
/// ///
Future<void> _stopEventStream() async { Future<void> _stopEventStream() async {
_reconnectTimer?.cancel();
_reconnectTimer = null;
await _eventStreamSubscription?.cancel(); await _eventStreamSubscription?.cancel();
_eventStreamSubscription = null; _eventStreamSubscription = null;
dPrint('[PartyRoom] Event stream stopped'); dPrint('[PartyRoom] Event stream stopped');
@ -794,6 +882,21 @@ class PartyRoom extends _$PartyRoom {
case partroom.RoomEventType.MEMBER_JOINED: case partroom.RoomEventType.MEMBER_JOINED:
case partroom.RoomEventType.MEMBER_LEFT: case partroom.RoomEventType.MEMBER_LEFT:
case partroom.RoomEventType.MEMBER_KICKED: case partroom.RoomEventType.MEMBER_KICKED:
if (event.type == partroom.RoomEventType.MEMBER_KICKED) {
//
if (event.member.gameUserId == state.auth.userInfo?.gameUserId) {
//
_dismissRoom();
return;
}
}
//
if (event.type == partroom.RoomEventType.MEMBER_LEFT) {
if (event.member.gameUserId == state.auth.userInfo?.gameUserId) {
//
return;
}
}
// //
if (state.room.roomUuid != null) { if (state.room.roomUuid != null) {
getRoomMembers(state.room.roomUuid!); getRoomMembers(state.room.roomUuid!);

View File

@ -44,7 +44,7 @@ final class PartyRoomProvider
} }
} }
String _$partyRoomHash() => r'02cdd156995799411eb47107d5c197f43e78629e'; String _$partyRoomHash() => r'd7182854c8caf5bb362c45a4e6e2ab40c2ef1b09';
/// PartyRoom Provider /// PartyRoom Provider

View File

@ -105,6 +105,12 @@ class PartyRoomUIModel extends _$PartyRoomUIModel {
playTime: currentGameStartTime != gameStartTime ? gameStartTime : null, playTime: currentGameStartTime != gameStartTime ? gameStartTime : null,
); );
} }
if (next.deathEvents?.isNotEmpty ?? false) {
for (final event in next.deathEvents!) {
ref.read(partyRoomProvider.notifier).sendSignal("special_death", params: {"location": event.$1, "area": event.$2});
}
}
} }
/// ///

View File

@ -41,7 +41,7 @@ final class PartyRoomUIModelProvider
} }
} }
String _$partyRoomUIModelHash() => r'a0b6c3632ff33f2d58882f9bc1ab58c69c2487f4'; String _$partyRoomUIModelHash() => r'add4703c9129465718a7850ea09025aa1ff35358';
abstract class _$PartyRoomUIModel extends $Notifier<PartyRoomUIState> { abstract class _$PartyRoomUIModel extends $Notifier<PartyRoomUIState> {
PartyRoomUIState build(); PartyRoomUIState build();

View File

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -18,8 +19,7 @@ sealed class PartyRoomGameLogTrackerProviderState with _$PartyRoomGameLogTracker
@Default(0) int kills, @Default(0) int kills,
@Default(0) int deaths, @Default(0) int deaths,
DateTime? gameStartTime, DateTime? gameStartTime,
@Default([]) List<String> killedIds, // ID List<(String, String)>? deathEvents,
@Default([]) List<String> deathIds, // ID
}) = _PartyRoomGameLogTrackerProviderState; }) = _PartyRoomGameLogTrackerProviderState;
} }
@ -32,6 +32,7 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider
@override @override
PartyRoomGameLogTrackerProviderState build({required DateTime startTime}) { PartyRoomGameLogTrackerProviderState build({required DateTime startTime}) {
startTime = DateTime.now();
dPrint("[PartyRoomGameLogTrackerProvider] init $startTime"); dPrint("[PartyRoomGameLogTrackerProvider] init $startTime");
ref.onDispose(() { ref.onDispose(() {
_disposed = true; _disposed = true;
@ -65,16 +66,9 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider
} }
} catch (e) { } catch (e) {
// //
state = state.copyWith( state = state.copyWith(location: '<游戏未启动>', gameStartTime: null, kills: 0, deaths: 0);
location: '<游戏未启动>',
gameStartTime: null,
kills: 0,
deaths: 0,
killedIds: [],
deathIds: [],
);
} }
await Future.delayed(const Duration(seconds: 5)); await Future.delayed(const Duration(seconds: 10));
} }
} }
@ -90,9 +84,7 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider
// //
final location = statistics.latestLocation == null ? '<主菜单>' : '[${statistics.latestLocation}]'; final location = statistics.latestLocation == null ? '<主菜单>' : '[${statistics.latestLocation}]';
// _lastQueryTime ID List<(String, String)> deathEvents = [];
final newKilledIds = <String>[];
final newDeathIds = <String>[];
if (_lastQueryTime != null) { if (_lastQueryTime != null) {
// actor_death // actor_death
@ -125,24 +117,15 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider
} }
} }
// 使 if (data.playerName == data.victimId) {
final victimId = data.victimId; //
final killerId = data.killerId; deathEvents.add((data.location ?? "-", data.area ?? "-"));
if (victimId != null && killerId != null && victimId != killerId) {
// ID
if (killerId == statistics.playerName) {
newKilledIds.add(victimId);
}
// ID
if (victimId == statistics.playerName) {
newDeathIds.add(killerId);
}
} }
} }
} }
// debugPrint(
// "[PartyRoomGameLogTrackerProvider] location $location killCount ${statistics.killCount} deathCount ${statistics.deathCount}",
// );
// //
state = state.copyWith( state = state.copyWith(
location: location, location: location,
@ -151,10 +134,7 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider
deaths: statistics.deathCount, deaths: statistics.deathCount,
// startTime // startTime
gameStartTime: statistics.gameStartTime, gameStartTime: statistics.gameStartTime,
// deathEvents: deathEvents,
killedIds: newKilledIds,
//
deathIds: newDeathIds, //
); );
// //

View File

@ -12,10 +12,9 @@ part of 'game_log_tracker_provider.dart';
// dart format off // dart format off
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$PartyRoomGameLogTrackerProviderState { mixin _$PartyRoomGameLogTrackerProviderState implements DiagnosticableTreeMixin {
String get location; int get kills; int get deaths; DateTime? get gameStartTime; List<String> get killedIds;// ID String get location; int get kills; int get deaths; DateTime? get gameStartTime; List<(String, String)>? get deathEvents;
List<String> get deathIds;
/// Create a copy of PartyRoomGameLogTrackerProviderState /// Create a copy of PartyRoomGameLogTrackerProviderState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -23,19 +22,25 @@ mixin _$PartyRoomGameLogTrackerProviderState {
$PartyRoomGameLogTrackerProviderStateCopyWith<PartyRoomGameLogTrackerProviderState> get copyWith => _$PartyRoomGameLogTrackerProviderStateCopyWithImpl<PartyRoomGameLogTrackerProviderState>(this as PartyRoomGameLogTrackerProviderState, _$identity); $PartyRoomGameLogTrackerProviderStateCopyWith<PartyRoomGameLogTrackerProviderState> get copyWith => _$PartyRoomGameLogTrackerProviderStateCopyWithImpl<PartyRoomGameLogTrackerProviderState>(this as PartyRoomGameLogTrackerProviderState, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'PartyRoomGameLogTrackerProviderState'))
..add(DiagnosticsProperty('location', location))..add(DiagnosticsProperty('kills', kills))..add(DiagnosticsProperty('deaths', deaths))..add(DiagnosticsProperty('gameStartTime', gameStartTime))..add(DiagnosticsProperty('deathEvents', deathEvents));
}
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other.killedIds, killedIds)&&const DeepCollectionEquality().equals(other.deathIds, deathIds)); return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other.deathEvents, deathEvents));
} }
@override @override
int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(killedIds),const DeepCollectionEquality().hash(deathIds)); int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(deathEvents));
@override @override
String toString() { String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, killedIds: $killedIds, deathIds: $deathIds)'; return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, deathEvents: $deathEvents)';
} }
@ -46,7 +51,7 @@ abstract mixin class $PartyRoomGameLogTrackerProviderStateCopyWith<$Res> {
factory $PartyRoomGameLogTrackerProviderStateCopyWith(PartyRoomGameLogTrackerProviderState value, $Res Function(PartyRoomGameLogTrackerProviderState) _then) = _$PartyRoomGameLogTrackerProviderStateCopyWithImpl; factory $PartyRoomGameLogTrackerProviderStateCopyWith(PartyRoomGameLogTrackerProviderState value, $Res Function(PartyRoomGameLogTrackerProviderState) _then) = _$PartyRoomGameLogTrackerProviderStateCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String location, int kills, int deaths, DateTime? gameStartTime, List<String> killedIds, List<String> deathIds String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents
}); });
@ -63,15 +68,14 @@ class _$PartyRoomGameLogTrackerProviderStateCopyWithImpl<$Res>
/// Create a copy of PartyRoomGameLogTrackerProviderState /// Create a copy of PartyRoomGameLogTrackerProviderState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? killedIds = null,Object? deathIds = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? deathEvents = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as String,kills: null == kills ? _self.kills : kills // ignore: cast_nullable_to_non_nullable as String,kills: null == kills ? _self.kills : kills // ignore: cast_nullable_to_non_nullable
as int,deaths: null == deaths ? _self.deaths : deaths // ignore: cast_nullable_to_non_nullable as int,deaths: null == deaths ? _self.deaths : deaths // ignore: cast_nullable_to_non_nullable
as int,gameStartTime: freezed == gameStartTime ? _self.gameStartTime : gameStartTime // ignore: cast_nullable_to_non_nullable as int,gameStartTime: freezed == gameStartTime ? _self.gameStartTime : gameStartTime // ignore: cast_nullable_to_non_nullable
as DateTime?,killedIds: null == killedIds ? _self.killedIds : killedIds // ignore: cast_nullable_to_non_nullable as DateTime?,deathEvents: freezed == deathEvents ? _self.deathEvents : deathEvents // ignore: cast_nullable_to_non_nullable
as List<String>,deathIds: null == deathIds ? _self.deathIds : deathIds // ignore: cast_nullable_to_non_nullable as List<(String, String)>?,
as List<String>,
)); ));
} }
@ -153,10 +157,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List<String> killedIds, List<String> deathIds)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _PartyRoomGameLogTrackerProviderState() when $default != null: case _PartyRoomGameLogTrackerProviderState() when $default != null:
return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.killedIds,_that.deathIds);case _: return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.deathEvents);case _:
return orElse(); return orElse();
} }
@ -174,10 +178,10 @@ return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_tha
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List<String> killedIds, List<String> deathIds) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _PartyRoomGameLogTrackerProviderState(): case _PartyRoomGameLogTrackerProviderState():
return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.killedIds,_that.deathIds);} return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.deathEvents);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///
@ -191,10 +195,10 @@ return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_tha
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String location, int kills, int deaths, DateTime? gameStartTime, List<String> killedIds, List<String> deathIds)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _PartyRoomGameLogTrackerProviderState() when $default != null: case _PartyRoomGameLogTrackerProviderState() when $default != null:
return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.killedIds,_that.deathIds);case _: return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.deathEvents);case _:
return null; return null;
} }
@ -205,28 +209,21 @@ return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_tha
/// @nodoc /// @nodoc
class _PartyRoomGameLogTrackerProviderState implements PartyRoomGameLogTrackerProviderState { class _PartyRoomGameLogTrackerProviderState with DiagnosticableTreeMixin implements PartyRoomGameLogTrackerProviderState {
const _PartyRoomGameLogTrackerProviderState({this.location = '', this.kills = 0, this.deaths = 0, this.gameStartTime, final List<String> killedIds = const [], final List<String> deathIds = const []}): _killedIds = killedIds,_deathIds = deathIds; const _PartyRoomGameLogTrackerProviderState({this.location = '', this.kills = 0, this.deaths = 0, this.gameStartTime, final List<(String, String)>? deathEvents}): _deathEvents = deathEvents;
@override@JsonKey() final String location; @override@JsonKey() final String location;
@override@JsonKey() final int kills; @override@JsonKey() final int kills;
@override@JsonKey() final int deaths; @override@JsonKey() final int deaths;
@override final DateTime? gameStartTime; @override final DateTime? gameStartTime;
final List<String> _killedIds; final List<(String, String)>? _deathEvents;
@override@JsonKey() List<String> get killedIds { @override List<(String, String)>? get deathEvents {
if (_killedIds is EqualUnmodifiableListView) return _killedIds; final value = _deathEvents;
if (value == null) return null;
if (_deathEvents is EqualUnmodifiableListView) return _deathEvents;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_killedIds); return EqualUnmodifiableListView(value);
}
// ID
final List<String> _deathIds;
// ID
@override@JsonKey() List<String> get deathIds {
if (_deathIds is EqualUnmodifiableListView) return _deathIds;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_deathIds);
} }
@ -237,19 +234,25 @@ class _PartyRoomGameLogTrackerProviderState implements PartyRoomGameLogTrackerPr
_$PartyRoomGameLogTrackerProviderStateCopyWith<_PartyRoomGameLogTrackerProviderState> get copyWith => __$PartyRoomGameLogTrackerProviderStateCopyWithImpl<_PartyRoomGameLogTrackerProviderState>(this, _$identity); _$PartyRoomGameLogTrackerProviderStateCopyWith<_PartyRoomGameLogTrackerProviderState> get copyWith => __$PartyRoomGameLogTrackerProviderStateCopyWithImpl<_PartyRoomGameLogTrackerProviderState>(this, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'PartyRoomGameLogTrackerProviderState'))
..add(DiagnosticsProperty('location', location))..add(DiagnosticsProperty('kills', kills))..add(DiagnosticsProperty('deaths', deaths))..add(DiagnosticsProperty('gameStartTime', gameStartTime))..add(DiagnosticsProperty('deathEvents', deathEvents));
}
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other._killedIds, _killedIds)&&const DeepCollectionEquality().equals(other._deathIds, _deathIds)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other._deathEvents, _deathEvents));
} }
@override @override
int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(_killedIds),const DeepCollectionEquality().hash(_deathIds)); int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(_deathEvents));
@override @override
String toString() { String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, killedIds: $killedIds, deathIds: $deathIds)'; return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, deathEvents: $deathEvents)';
} }
@ -260,7 +263,7 @@ abstract mixin class _$PartyRoomGameLogTrackerProviderStateCopyWith<$Res> implem
factory _$PartyRoomGameLogTrackerProviderStateCopyWith(_PartyRoomGameLogTrackerProviderState value, $Res Function(_PartyRoomGameLogTrackerProviderState) _then) = __$PartyRoomGameLogTrackerProviderStateCopyWithImpl; factory _$PartyRoomGameLogTrackerProviderStateCopyWith(_PartyRoomGameLogTrackerProviderState value, $Res Function(_PartyRoomGameLogTrackerProviderState) _then) = __$PartyRoomGameLogTrackerProviderStateCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String location, int kills, int deaths, DateTime? gameStartTime, List<String> killedIds, List<String> deathIds String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents
}); });
@ -277,15 +280,14 @@ class __$PartyRoomGameLogTrackerProviderStateCopyWithImpl<$Res>
/// Create a copy of PartyRoomGameLogTrackerProviderState /// Create a copy of PartyRoomGameLogTrackerProviderState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? killedIds = null,Object? deathIds = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? deathEvents = freezed,}) {
return _then(_PartyRoomGameLogTrackerProviderState( return _then(_PartyRoomGameLogTrackerProviderState(
location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as String,kills: null == kills ? _self.kills : kills // ignore: cast_nullable_to_non_nullable as String,kills: null == kills ? _self.kills : kills // ignore: cast_nullable_to_non_nullable
as int,deaths: null == deaths ? _self.deaths : deaths // ignore: cast_nullable_to_non_nullable as int,deaths: null == deaths ? _self.deaths : deaths // ignore: cast_nullable_to_non_nullable
as int,gameStartTime: freezed == gameStartTime ? _self.gameStartTime : gameStartTime // ignore: cast_nullable_to_non_nullable as int,gameStartTime: freezed == gameStartTime ? _self.gameStartTime : gameStartTime // ignore: cast_nullable_to_non_nullable
as DateTime?,killedIds: null == killedIds ? _self._killedIds : killedIds // ignore: cast_nullable_to_non_nullable as DateTime?,deathEvents: freezed == deathEvents ? _self._deathEvents : deathEvents // ignore: cast_nullable_to_non_nullable
as List<String>,deathIds: null == deathIds ? _self._deathIds : deathIds // ignore: cast_nullable_to_non_nullable as List<(String, String)>?,
as List<String>,
)); ));
} }

View File

@ -66,7 +66,7 @@ final class PartyRoomGameLogTrackerProviderProvider
} }
String _$partyRoomGameLogTrackerProviderHash() => String _$partyRoomGameLogTrackerProviderHash() =>
r'ecb015eb46d25bfe11bbb153242fd5c4f20ef367'; r'3e1560b2fffc5461a41bece57b43e27f4112ad0c';
final class PartyRoomGameLogTrackerProviderFamily extends $Family final class PartyRoomGameLogTrackerProviderFamily extends $Family
with with

View File

@ -97,15 +97,12 @@ class PartyRoomMemberItem extends ConsumerWidget {
), ),
Row( Row(
children: [ children: [
Text( Expanded(
member.status.currentLocation.isNotEmpty ? member.status.currentLocation : '...', child: Text(
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .9)), member.status.currentLocation.isNotEmpty ? member.status.currentLocation : '...',
overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .9)),
), overflow: TextOverflow.ellipsis,
SizedBox(width: 4), ),
Text(
"K: ${member.status.kills} D: ${member.status.deaths}",
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)),
), ),
], ],
), ),

View File

@ -210,6 +210,11 @@ class _MessageItem extends ConsumerWidget {
final userName = _getEventUserName(roomEvent); final userName = _getEventUserName(roomEvent);
final avatarUrl = _getEventAvatarUrl(roomEvent); final avatarUrl = _getEventAvatarUrl(roomEvent);
//
if (isSignal && roomEvent.signalId == 'special_death') {
return _buildDeathMessageCard(context, roomEvent, userName, avatarUrl);
}
final text = _getEventText(roomEvent, ref); final text = _getEventText(roomEvent, ref);
if (text == null) return const SizedBox.shrink(); if (text == null) return const SizedBox.shrink();
@ -259,6 +264,104 @@ class _MessageItem extends ConsumerWidget {
); );
} }
Widget _buildDeathMessageCard(
BuildContext context,
partroom.RoomEvent roomEvent,
String userName,
String? avatarUrl,
) {
final location = roomEvent.signalParams['location'] ?? '未知位置';
final area = roomEvent.signalParams['area'] ?? '未知区域';
return Container(
margin: const EdgeInsets.only(bottom: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildUserAvatar(userName, avatarUrl: avatarUrl, size: 28),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Row(
children: [
Text(
userName,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white),
),
const SizedBox(width: 8),
Text(
_formatTime(roomEvent.timestamp),
style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)),
),
],
),
const SizedBox(height: 8),
//
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF2B2D31),
border: Border.all(color: const Color(0xFFED4245).withValues(alpha: 0.3)),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFFED4245).withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(FluentIcons.status_error_full, size: 14, color: Color(0xFFED4245)),
),
const SizedBox(width: 8),
const Text(
'玩家死亡',
style: TextStyle(fontSize: 14, color: Color(0xFFED4245), fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 12),
//
_buildInfoRow(icon: FluentIcons.location, label: '位置', value: location),
const SizedBox(height: 8),
//
_buildInfoRow(icon: FluentIcons.map_pin, label: '区域', value: area),
],
),
),
],
),
),
],
),
);
}
Widget _buildInfoRow({required IconData icon, required String label, required String value}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 14, color: Colors.white.withValues(alpha: .4)),
const SizedBox(width: 8),
Text(
'$label: ',
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .4), fontWeight: FontWeight.w500),
),
Expanded(
child: Text(value, style: const TextStyle(fontSize: 13, color: Color(0xFFDBDEE1))),
),
],
);
}
String _getEventUserName(partroom.RoomEvent event) { String _getEventUserName(partroom.RoomEvent event) {
switch (event.type) { switch (event.type) {
case partroom.RoomEventType.SIGNAL_BROADCAST: case partroom.RoomEventType.SIGNAL_BROADCAST:

View File

@ -123,7 +123,7 @@ class PartyRoomRegisterPage extends HookConsumerWidget {
? const Color(0xFF4CAF50) ? const Color(0xFF4CAF50)
: isActive : isActive
? const Color(0xFF4A9EFF) ? const Color(0xFF4A9EFF)
: Colors.grey.withValues(alpha: 0.3), : Colors.white.withValues(alpha: 0.4),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Center( child: Center(
@ -143,7 +143,7 @@ class PartyRoomRegisterPage extends HookConsumerWidget {
title, title,
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: isActive ? const Color(0xFF4A9EFF) : Colors.grey.withValues(alpha: 0.7), color: isActive ? const Color(0xFF4A9EFF) : Colors.white.withValues(alpha: 0.4),
fontWeight: isActive ? FontWeight.bold : FontWeight.normal, fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
), ),
), ),