import 'dart:async'; import 'dart:ui'; import 'package:fixnum/fixnum.dart'; import 'package:get/get.dart'; import 'package:grpc/grpc.dart'; import 'package:trackoffical_app/model/game_map.dart'; import 'package:trackoffical_app/service/api.dart'; import 'package:trackoffical_app/service/app_map.dart'; import 'package:trackoffical_app/service/game/game_instance_std/plug_sport_wear.dart'; import 'plug_location.dart'; import 'plug_orientation.dart'; import 'package:wakelock/wakelock.dart'; import '../../../model.dart'; import '../rule.dart'; import '../rule_in_order.dart'; import 'package:trackoffical_app/utils.dart'; import '../../../logger.dart'; import '../../../model/game_person_data.dart'; import '../../app.dart'; import '../../database.dart'; import '../game_instance.dart'; import 'package:trackoffical_app/pb.dart' as pb; export '../../../model.dart'; import 'package:sensor/sensor.dart' as sensor; class GameInstanceStd extends GameInstance { GameInstanceStd({ required GameState gameState, }) { model.gameSrcState.value = gameState; } void gameGiveUp() { _isGiveUp = true; stop(); } _setStartAt(DateTime? time) { if (time != null) { gameState.pbGameSave.startAt = time.toPb(); } else { gameState.pbGameSave.clearStartAt(); } model.gameSrcState.refresh(); } _setEndAt(DateTime? time) { if (time != null) { gameState.pbGameSave.stopAt = time.toPb(); } else { gameState.pbGameSave.clearStopAt(); } model.gameSrcState.refresh(); } void _gameStopSetValues({DateTime? now}) { if(endAt!=null){ return; } info('游戏结束'); if (now != null) { _setEndAt(now); } else { _setEndAt(this.now); } } void _updateCheckHistory() { gameState.pbGameSave.checkedSortedList.clear(); gameState.pbGameSave.checkedSortedList .addAll(checkedPointsHistory.map((e) => e.toPbSave()).toList()); } Future _saveToDatabase() async { await DatabaseService.to.saveGameState(gameState); } Future _saveToServer() async { info('进度上传开始'); await _api.gameSaveUpload( pb.GameSaveUploadRequest() ..gameSave= gameState.pbGameSave); info('进度上传完成'); } // 上报游戏结果 Future _settlement() async { info('结算开始'); for(final one in plugs){ await one.join(); info('[${one.runtimeType}]已卸载'); } while (true) { try { await _saveToServer(); break; } on GrpcError catch (e) { if (e.code == StatusCode.unavailable) { errorMsg.add('无法连接至网络,正在重试'); } else { break; } error(e); } catch (e) { error(e); break; } } while (true) { try { finishData = await _api.gameFinish(gameState.pbGameData.gameId, _isGiveUp); info('结算完成'); break; } on GrpcError catch (e) { if (e.code == StatusCode.unavailable) { errorMsg.add('无法连接至网络,正在重试'); } else { break; } error(e); Future.delayed(500.milliseconds); } catch (e) { error(e); break; } } } void _gameLoadCheckedCP() { final controlPointWantIdValueMap = {}; final controlPointAllIdValueMap = {}; for (var one in model.controlPointWantSequence) { controlPointWantIdValueMap[one.intId] = one; } for (var one in gameState.pbGameData.controlPointAll) { controlPointAllIdValueMap[one.id] = one; } for (var i = 0; i < gameState.pbGameSave.checkedSortedList.length; i++) { final one = gameState.pbGameSave.checkedSortedList[i]; final his = one.toModel(); final hisInfo1 = controlPointWantIdValueMap[his.intId]; final hisInfo2 = controlPointAllIdValueMap[his.intId]; if (hisInfo2 != null) { his.updateBySimple(hisInfo2); } if (hisInfo1 != null) { his.updateBy(hisInfo1); his.type = hisInfo1.type; } checkedPointsHistory.add(his); } for (var i = 0; i < checkedPointsHistory.length; i++) { final his = checkedPointsHistory[i]; if (i > 0) { final last = checkedPointsHistory[i - 1]; his.checkAfterPrev = his.checkAfterStart - last.checkAfterStart; his.checkDistanceAfterPrev = his.checkDistanceAfterStart - last.checkDistanceAfterStart; } else { his.checkAfterPrev = his.checkAfterStart; his.checkDistanceAfterPrev = his.checkDistanceAfterStart; } } } bool _isCheckTooFast(Int64 id) { final last = lastCheckedPoint; if(last==null){ return false; } return last.intId == id && now.difference((startAt??DateTime(2000)).add(last.checkAfterStart)) <= 10.seconds; } void _updateNewCPState(MControlPoint point, {DateTime? now}) { now = now ?? this.now; point.checkAfterStart = now.difference(startAt ?? now); point.checkAfterPrev = point.checkAfterStart; point.checkDistanceAfterStart = model.myPositionHistoryLen.value; point.checkDistanceAfterPrev = point.checkDistanceAfterStart; final last = lastCheckedPoint; if (last != null) { point.checkAfterPrev = point.checkAfterStart - last.checkAfterStart; point.checkDistanceAfterPrev = point.checkDistanceAfterStart - last.checkDistanceAfterStart; } } void save() { _updateCheckHistory(); _saveToDatabase().then((value) => info('本地存档完成')); _saveToServer().then((value) => info('上传存档完成')); } checkPointNFC( String identifier, void Function(MControlPoint cp) onChecked, Future Function () onConfirmFinish, void Function(pb.ControlPointSimple point) onProjectPoint, VoidCallback onNoPoint, ) { final checkedPoint = model.findCPWantByNfcId(identifier); if (checkedPoint != null) { checkPoint(checkedPoint, onChecked, onConfirmFinish); } else { final point = model.findCPInProjectByNfcId(identifier); if (point != null) { if (_isCheckTooFast(point.id)) { return; } final result = point.toModel(); _updateNewCPState(result); result.isSuccess = false; model.checkHistoryAdd(result); save(); onProjectPoint(point); } else { onNoPoint(); } } } checkPointGps( void Function(MControlPoint cp) onChecked, Future Function () onConfirmFinish, ) { if(model.isInPlanControlPointArea){ MControlPoint? checkedPoint; final point = model.nextPlanPoint; if (point != null) { checkedPoint = model.findCPInRoute(point); } checkPoint(checkedPoint!, onChecked, onConfirmFinish); }else if (model.isInWantControlPointArea) { MControlPoint? checkedPoint; final point = model.nextWantPoint; if (point != null) { checkedPoint = model.findCPInRoute(point); } checkPoint(checkedPoint!, onChecked, onConfirmFinish); } } Future checkPoint( MControlPoint cp, void Function (MControlPoint cp) onChecked, /// 打了结束点,询问是否结束 Future Function () onConfirmFinish, ) async{ if (rule.checkNeedReturn(cp)){ return; } final now = this.now; final result = rule.checkPoint(cp); result.isUnchecked=false; var isFinish = result.isSuccess && result.isFinish; if(!result.isSuccess && result.isFinish){ isFinish = await onConfirmFinish(); if(!isFinish){ return; } } _updateNewCPState(result, now: now); if(result.isStart && result.isSuccess){ _setStartAt(now); } if(isFinish){ _gameStopSetValues(now: now); } model.checkHistoryAdd(result); if(startAt!=null){ gameState.pbGameSave.duration = now.difference(startAt!).toPb(); if(endAt != null){ gameState.pbGameSave.duration = endAt!.difference(startAt!).toPb(); } } model.gameSrcState.refresh(); if(isFinish){ _isGiveUp=false; stop(); return; }else{ save(); } rule.recordLastPoint(cp); onChecked(result); model.controlPointWantSequence.refresh(); } void _jobDuration() { final startAt = model.startAt; if(startAt!= null){ final endAt = model.endAt?? now; model.duration.value = endAt.difference(startAt); } } // 达到强制结束时间 void _jobCheckMaxForcedEnd() { if (now.difference(createTime) > maxForcedEndDuration) { stop(); } } // 保存进度 void _workAutoSave() async { while (isActive) { await Future.delayed(500.milliseconds); if (model.gameSrcState.value.pbGameData.gameId > 0 && model.isStarted && !model.isFinish) { await _saveToDatabase(); } } } void _jobTrace() { final duration = _userProfile.gameSettingsTrackLengthSeconds.value.seconds; var out = []; for (var i = model.myPositionHistory.length - 1; i >= 0; i--) { var one = model.myPositionHistory[i]; if (now.difference(one.timestamp) > duration) { break; } if(i < model.myPositionOnMapHistory.length){ out.add(model.myPositionOnMapHistory[i]); } } myTrace.value= out.reversed.toList(); } void _jobPace() { final startDuration = model.duration.value; if (startDuration.inMilliseconds == 0) { return; } model.paceSecondKm.value = pacePerKm(model.myPositionHistoryLen.value, startDuration); if (checkedPointsHistory.isEmpty) { model.paceSecondKmFromLastCP.value = model.paceSecondKm.value; } else { final cp = checkedPointsHistory.last; model.paceSecondKmFromLastCP.value = pacePerKm( model.myPositionHistoryLenFromLastCP, startDuration - cp.checkAfterStart); } } @override Future onClose() async { _gameStopSetValues(); _updateCheckHistory(); await Future.wait([_saveToDatabase(), _settlement()]); if (await Wakelock.enabled) { Wakelock.disable(); } } @override Future onInit() async { info('载入项目[GameId]:$id'); await _saveToDatabase(); // ---- 载入服务器游戏设置 ---- _app.userProfile.cleanGameSettingsLock(); _app.userProfile.gameSettingsLoadLock(gameState.pbGameData.ruleList); // ---- 载入服务器游戏设置 ---- // 游戏规则 rule = RuleInOrder(model); loadProgress.value= _progressApi; var progress = _progressApi; gameMapData = gameState.pbGameData.mapZip.toGameMap(); await gameMapData.loadMemory(onReceiveProgress: (c, a) { if (a > 0) { var p = c.toDouble() / a; p = p * _progressMap + progress; loadProgress.value= (p); } }); progress +=_progressMap; await model.initControlPointWantSequence(gameMapData, loadPic: true, onProgress: (c, a) { if (a > 0) { var p = c / a; p = p * _progressCP + progress; loadProgress.value= (p); } }); _gameLoadCheckedCP(); Wakelock.enable(); addTimer(1.seconds, _jobCheckMaxForcedEnd); addTimer(1.seconds, _jobPace); addTimer(1.seconds, _jobTrace); addTimer(200.milliseconds, _jobDuration); _workAutoSave(); plugs.addAll([ PlugLocation(this), PlugOrientation(this), PlugSportWear(this) ]); } static const _progressMap = 0.4; static const _progressApi = 0.1; static const _progressCP = 0.3; bool _isGiveUp = false; final errorMsg = StreamController.broadcast(); final App _app = Get.find(); final _userProfile = App.to.userProfile; final ApiService _api = Get.find(); Rule rule = RuleMock(); var gameMapData = GameMap(); DateTime get now => _app.now; final model = GamePersonData(); GameState get gameState => model.gameSrcState.value; // 创建时间 DateTime get createTime => gameState.createTime; // 关门时间 Duration get maxPassDuration => gameState.pbGameData.maxDuration.toDuration(); // 强制结束时间 Duration get maxForcedEndDuration => gameState.pbGameData.maxForcedEndDuration.toDuration(); // 打开始点的时间 DateTime? get startAt => model.startAt; DateTime? get endAt => model.endAt; List get checkedPointsHistory => model.checkedPointsHistory; /// 指北针弧度,移动时通过GPS判断 final compassRadiansFused = 0.0.obs; final compassRadiansSrc = 0.0.obs; final orientation = sensor.Orientation().obs; var accelerometerEvent = sensor.Orientation(); bool get isPersonMoving { return gpsSpeedPerSecond > 0.5.meter && isDeviceMoving; } bool get isDeviceMoving { return sensorSpeedPerSecond> 0.1.meter; } Distance get gpsSpeedPerSecond{ final p = model.myPosition; if(p!= null){ return p.speed.meter; } return 0.meter; } var sensorSpeedPerSecond = 0.meter; @override int get id => gameState.pbGameData.gameId.toInt(); var finishData=pb.GameDetailReply(); final myTrace = [].obs; MControlPoint? get lastCheckedPoint { if (checkedPointsHistory.isNotEmpty) { return checkedPointsHistory.last; } else { return null; } } }