game_instance_std.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. import 'dart:async';
  2. import 'dart:ui';
  3. import 'package:fixnum/fixnum.dart';
  4. import 'package:get/get.dart';
  5. import 'package:grpc/grpc.dart';
  6. import 'package:trackoffical_app/model/game_map.dart';
  7. import 'package:trackoffical_app/service/api.dart';
  8. import 'package:trackoffical_app/service/app_map.dart';
  9. import 'package:trackoffical_app/service/game/game_instance_std/plug_sport_wear.dart';
  10. import 'plug_location.dart';
  11. import 'plug_orientation.dart';
  12. import 'package:wakelock/wakelock.dart';
  13. import '../../../model.dart';
  14. import '../rule.dart';
  15. import '../rule_in_order.dart';
  16. import 'package:trackoffical_app/utils.dart';
  17. import '../../../logger.dart';
  18. import '../../../model/game_person_data.dart';
  19. import '../../app.dart';
  20. import '../../database.dart';
  21. import '../game_instance.dart';
  22. import 'package:trackoffical_app/pb.dart' as pb;
  23. export '../../../model.dart';
  24. import 'package:sensor/sensor.dart' as sensor;
  25. class GameInstanceStd extends GameInstance {
  26. GameInstanceStd({
  27. required GameState gameState,
  28. }) {
  29. model.gameSrcState.value = gameState;
  30. }
  31. void gameGiveUp() {
  32. _isGiveUp = true;
  33. stop();
  34. }
  35. _setStartAt(DateTime? time) {
  36. if (time != null) {
  37. gameState.pbGameSave.startAt = time.toPb();
  38. } else {
  39. gameState.pbGameSave.clearStartAt();
  40. }
  41. model.gameSrcState.refresh();
  42. }
  43. _setEndAt(DateTime? time) {
  44. if (time != null) {
  45. gameState.pbGameSave.stopAt = time.toPb();
  46. } else {
  47. gameState.pbGameSave.clearStopAt();
  48. }
  49. model.gameSrcState.refresh();
  50. }
  51. void _gameStopSetValues({DateTime? now}) {
  52. if(endAt!=null){
  53. return;
  54. }
  55. info('游戏结束');
  56. if (now != null) {
  57. _setEndAt(now);
  58. } else {
  59. _setEndAt(this.now);
  60. }
  61. }
  62. void _updateCheckHistory() {
  63. gameState.pbGameSave.checkedSortedList.clear();
  64. gameState.pbGameSave.checkedSortedList
  65. .addAll(checkedPointsHistory.map((e) => e.toPbSave()).toList());
  66. }
  67. Future<void> _saveToDatabase() async {
  68. await DatabaseService.to.saveGameState(gameState);
  69. }
  70. Future<void> _saveToServer() async {
  71. info('进度上传开始');
  72. await _api.gameSaveUpload(
  73. pb.GameSaveUploadRequest()
  74. ..gameSave= gameState.pbGameSave);
  75. info('进度上传完成');
  76. }
  77. // 上报游戏结果
  78. Future<void> _settlement() async {
  79. info('结算开始');
  80. for(final one in plugs){
  81. await one.join();
  82. info('[${one.runtimeType}]已卸载');
  83. }
  84. while (true) {
  85. try {
  86. await _saveToServer();
  87. break;
  88. } on GrpcError catch (e) {
  89. if (e.code == StatusCode.unavailable) {
  90. errorMsg.add('无法连接至网络,正在重试');
  91. } else {
  92. break;
  93. }
  94. error(e);
  95. } catch (e) {
  96. error(e);
  97. break;
  98. }
  99. }
  100. while (true) {
  101. try {
  102. finishData = await _api.gameFinish(gameState.pbGameData.gameId, _isGiveUp);
  103. info('结算完成');
  104. break;
  105. } on GrpcError catch (e) {
  106. if (e.code == StatusCode.unavailable) {
  107. errorMsg.add('无法连接至网络,正在重试');
  108. } else {
  109. break;
  110. }
  111. error(e);
  112. Future.delayed(500.milliseconds);
  113. } catch (e) {
  114. error(e);
  115. break;
  116. }
  117. }
  118. }
  119. void _gameLoadCheckedCP() {
  120. final controlPointWantIdValueMap = <Int64, MControlPoint>{};
  121. final controlPointAllIdValueMap = <Int64, pb.ControlPointSimple>{};
  122. for (var one in model.controlPointWantSequence) {
  123. controlPointWantIdValueMap[one.intId] = one;
  124. }
  125. for (var one in gameState.pbGameData.controlPointAll) {
  126. controlPointAllIdValueMap[one.id] = one;
  127. }
  128. for (var i = 0; i < gameState.pbGameSave.checkedSortedList.length; i++) {
  129. final one = gameState.pbGameSave.checkedSortedList[i];
  130. final his = one.toModel();
  131. final hisInfo1 = controlPointWantIdValueMap[his.intId];
  132. final hisInfo2 = controlPointAllIdValueMap[his.intId];
  133. if (hisInfo2 != null) {
  134. his.updateBySimple(hisInfo2);
  135. }
  136. if (hisInfo1 != null) {
  137. his.updateBy(hisInfo1);
  138. his.type = hisInfo1.type;
  139. }
  140. checkedPointsHistory.add(his);
  141. }
  142. for (var i = 0; i < checkedPointsHistory.length; i++) {
  143. final his = checkedPointsHistory[i];
  144. if (i > 0) {
  145. final last = checkedPointsHistory[i - 1];
  146. his.checkAfterPrev = his.checkAfterStart - last.checkAfterStart;
  147. his.checkDistanceAfterPrev = his.checkDistanceAfterStart - last.checkDistanceAfterStart;
  148. } else {
  149. his.checkAfterPrev = his.checkAfterStart;
  150. his.checkDistanceAfterPrev = his.checkDistanceAfterStart;
  151. }
  152. }
  153. }
  154. bool _isCheckTooFast(Int64 id) {
  155. final last = lastCheckedPoint;
  156. if(last==null){
  157. return false;
  158. }
  159. return last.intId == id &&
  160. now.difference((startAt??DateTime(2000)).add(last.checkAfterStart)) <= 10.seconds;
  161. }
  162. void _updateNewCPState(MControlPoint point, {DateTime? now}) {
  163. now = now ?? this.now;
  164. point.checkAfterStart = now.difference(startAt ?? now);
  165. point.checkAfterPrev = point.checkAfterStart;
  166. point.checkDistanceAfterStart = model.myPositionHistoryLen.value;
  167. point.checkDistanceAfterPrev = point.checkDistanceAfterStart;
  168. final last = lastCheckedPoint;
  169. if (last != null) {
  170. point.checkAfterPrev = point.checkAfterStart - last.checkAfterStart;
  171. point.checkDistanceAfterPrev = point.checkDistanceAfterStart - last.checkDistanceAfterStart;
  172. }
  173. }
  174. void save() {
  175. _updateCheckHistory();
  176. _saveToDatabase().then((value) => info('本地存档完成'));
  177. _saveToServer().then((value) => info('上传存档完成'));
  178. }
  179. checkPointNFC(
  180. String identifier,
  181. void Function(MControlPoint cp) onChecked,
  182. Future<bool> Function () onConfirmFinish,
  183. void Function(pb.ControlPointSimple point) onProjectPoint,
  184. VoidCallback onNoPoint,
  185. ) {
  186. final checkedPoint = model.findCPWantByNfcId(identifier);
  187. if (checkedPoint != null) {
  188. checkPoint(checkedPoint, onChecked, onConfirmFinish);
  189. } else {
  190. final point = model.findCPInProjectByNfcId(identifier);
  191. if (point != null) {
  192. if (_isCheckTooFast(point.id)) {
  193. return;
  194. }
  195. final result = point.toModel();
  196. _updateNewCPState(result);
  197. result.isSuccess = false;
  198. model.checkHistoryAdd(result);
  199. save();
  200. onProjectPoint(point);
  201. } else {
  202. onNoPoint();
  203. }
  204. }
  205. }
  206. checkPointGps(
  207. void Function(MControlPoint cp) onChecked,
  208. Future<bool> Function () onConfirmFinish,
  209. ) {
  210. if(model.isInPlanControlPointArea){
  211. MControlPoint? checkedPoint;
  212. final point = model.nextPlanPoint;
  213. if (point != null) {
  214. checkedPoint = model.findCPInRoute(point);
  215. }
  216. checkPoint(checkedPoint!, onChecked, onConfirmFinish);
  217. }else if (model.isInWantControlPointArea) {
  218. MControlPoint? checkedPoint;
  219. final point = model.nextWantPoint;
  220. if (point != null) {
  221. checkedPoint = model.findCPInRoute(point);
  222. }
  223. checkPoint(checkedPoint!, onChecked, onConfirmFinish);
  224. }
  225. }
  226. Future<void> checkPoint(
  227. MControlPoint cp,
  228. void Function (MControlPoint cp) onChecked,
  229. /// 打了结束点,询问是否结束
  230. Future<bool> Function () onConfirmFinish,
  231. ) async{
  232. if (rule.checkNeedReturn(cp)){
  233. return;
  234. }
  235. final now = this.now;
  236. final result = rule.checkPoint(cp);
  237. result.isUnchecked=false;
  238. var isFinish = result.isSuccess && result.isFinish;
  239. if(!result.isSuccess && result.isFinish){
  240. isFinish = await onConfirmFinish();
  241. if(!isFinish){
  242. return;
  243. }
  244. }
  245. _updateNewCPState(result, now: now);
  246. if(result.isStart && result.isSuccess){
  247. _setStartAt(now);
  248. }
  249. if(isFinish){
  250. _gameStopSetValues(now: now);
  251. }
  252. model.checkHistoryAdd(result);
  253. if(startAt!=null){
  254. gameState.pbGameSave.duration = now.difference(startAt!).toPb();
  255. if(endAt != null){
  256. gameState.pbGameSave.duration = endAt!.difference(startAt!).toPb();
  257. }
  258. }
  259. model.gameSrcState.refresh();
  260. if(isFinish){
  261. _isGiveUp=false;
  262. stop();
  263. return;
  264. }else{
  265. save();
  266. }
  267. rule.recordLastPoint(cp);
  268. onChecked(result);
  269. model.controlPointWantSequence.refresh();
  270. }
  271. void _jobDuration() {
  272. final startAt = model.startAt;
  273. if(startAt!= null){
  274. final endAt = model.endAt?? now;
  275. model.duration.value = endAt.difference(startAt);
  276. }
  277. }
  278. // 达到强制结束时间
  279. void _jobCheckMaxForcedEnd() {
  280. if (now.difference(createTime) > maxForcedEndDuration) {
  281. stop();
  282. }
  283. }
  284. // 保存进度
  285. void _workAutoSave() async {
  286. while (isActive) {
  287. await Future.delayed(500.milliseconds);
  288. if (model.gameSrcState.value.pbGameData.gameId > 0 &&
  289. model.isStarted &&
  290. !model.isFinish) {
  291. await _saveToDatabase();
  292. }
  293. }
  294. }
  295. void _jobTrace() {
  296. final duration =
  297. _userProfile.gameSettingsTrackLengthSeconds.value.seconds;
  298. var out = <Offset>[];
  299. for (var i = model.myPositionHistory.length - 1; i >= 0; i--) {
  300. var one = model.myPositionHistory[i];
  301. if (now.difference(one.timestamp) > duration) {
  302. break;
  303. }
  304. if(i < model.myPositionOnMapHistory.length){
  305. out.add(model.myPositionOnMapHistory[i]);
  306. }
  307. }
  308. myTrace.value= out.reversed.toList();
  309. }
  310. void _jobPace() {
  311. final startDuration = model.duration.value;
  312. if (startDuration.inMilliseconds == 0) {
  313. return;
  314. }
  315. model.paceSecondKm.value =
  316. pacePerKm(model.myPositionHistoryLen.value, startDuration);
  317. if (checkedPointsHistory.isEmpty) {
  318. model.paceSecondKmFromLastCP.value = model.paceSecondKm.value;
  319. } else {
  320. final cp = checkedPointsHistory.last;
  321. model.paceSecondKmFromLastCP.value = pacePerKm(
  322. model.myPositionHistoryLenFromLastCP,
  323. startDuration - cp.checkAfterStart);
  324. }
  325. }
  326. @override
  327. Future<void> onClose() async {
  328. _gameStopSetValues();
  329. _updateCheckHistory();
  330. await Future.wait([_saveToDatabase(), _settlement()]);
  331. if (await Wakelock.enabled) {
  332. Wakelock.disable();
  333. }
  334. }
  335. @override
  336. Future<void> onInit() async {
  337. info('载入项目[GameId]:$id');
  338. await _saveToDatabase();
  339. // ---- 载入服务器游戏设置 ----
  340. _app.userProfile.cleanGameSettingsLock();
  341. _app.userProfile.gameSettingsLoadLock(gameState.pbGameData.ruleList);
  342. // ---- 载入服务器游戏设置 ----
  343. // 游戏规则
  344. rule = RuleInOrder(model);
  345. loadProgress.value= _progressApi;
  346. var progress = _progressApi;
  347. gameMapData = gameState.pbGameData.mapZip.toGameMap();
  348. await gameMapData.loadMemory(onReceiveProgress: (c, a) {
  349. if (a > 0) {
  350. var p = c.toDouble() / a;
  351. p = p * _progressMap + progress;
  352. loadProgress.value= (p);
  353. }
  354. });
  355. progress +=_progressMap;
  356. await model.initControlPointWantSequence(gameMapData, loadPic: true,
  357. onProgress: (c, a) {
  358. if (a > 0) {
  359. var p = c / a;
  360. p = p * _progressCP + progress;
  361. loadProgress.value= (p);
  362. }
  363. });
  364. _gameLoadCheckedCP();
  365. Wakelock.enable();
  366. addTimer(1.seconds, _jobCheckMaxForcedEnd);
  367. addTimer(1.seconds, _jobPace);
  368. addTimer(1.seconds, _jobTrace);
  369. addTimer(200.milliseconds, _jobDuration);
  370. _workAutoSave();
  371. plugs.addAll([
  372. PlugLocation(this),
  373. PlugOrientation(this),
  374. PlugSportWear(this)
  375. ]);
  376. }
  377. static const _progressMap = 0.4;
  378. static const _progressApi = 0.1;
  379. static const _progressCP = 0.3;
  380. bool _isGiveUp = false;
  381. final errorMsg = StreamController<String>.broadcast();
  382. final App _app = Get.find();
  383. final _userProfile = App.to.userProfile;
  384. final ApiService _api = Get.find();
  385. Rule rule = RuleMock();
  386. var gameMapData = GameMap();
  387. DateTime get now => _app.now;
  388. final model = GamePersonData();
  389. GameState get gameState => model.gameSrcState.value;
  390. // 创建时间
  391. DateTime get createTime => gameState.createTime;
  392. // 关门时间
  393. Duration get maxPassDuration => gameState.pbGameData.maxDuration.toDuration();
  394. // 强制结束时间
  395. Duration get maxForcedEndDuration =>
  396. gameState.pbGameData.maxForcedEndDuration.toDuration();
  397. // 打开始点的时间
  398. DateTime? get startAt => model.startAt;
  399. DateTime? get endAt => model.endAt;
  400. List<MControlPoint> get checkedPointsHistory => model.checkedPointsHistory;
  401. /// 指北针弧度,移动时通过GPS判断
  402. final compassRadiansFused = 0.0.obs;
  403. final compassRadiansSrc = 0.0.obs;
  404. final orientation = sensor.Orientation().obs;
  405. var accelerometerEvent = sensor.Orientation();
  406. bool get isPersonMoving {
  407. return gpsSpeedPerSecond > 0.5.meter && isDeviceMoving;
  408. }
  409. bool get isDeviceMoving {
  410. return sensorSpeedPerSecond> 0.1.meter;
  411. }
  412. Distance get gpsSpeedPerSecond{
  413. final p = model.myPosition;
  414. if(p!= null){
  415. return p.speed.meter;
  416. }
  417. return 0.meter;
  418. }
  419. var sensorSpeedPerSecond = 0.meter;
  420. @override
  421. int get id => gameState.pbGameData.gameId.toInt();
  422. var finishData=pb.GameDetailReply();
  423. final myTrace = <Offset>[].obs;
  424. MControlPoint? get lastCheckedPoint {
  425. if (checkedPointsHistory.isNotEmpty) {
  426. return checkedPointsHistory.last;
  427. } else {
  428. return null;
  429. }
  430. }
  431. }