OSDN Git Service

基地航空隊の仕様変更に対応する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Sniffer.cs
1 // Copyright (C) 2013, 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Linq;\r
18 \r
19 namespace KancolleSniffer\r
20 {\r
21     public class Sniffer\r
22     {\r
23         private bool _start;\r
24         private readonly ItemInfo _itemInfo = new ItemInfo();\r
25         private readonly MaterialInfo _materialInfo = new MaterialInfo();\r
26         private readonly QuestInfo _questInfo = new QuestInfo();\r
27         private readonly MissionInfo _missionInfo = new MissionInfo();\r
28         private readonly ShipInfo _shipInfo;\r
29         private readonly ConditionTimer _conditionTimer;\r
30         private readonly DockInfo _dockInfo;\r
31         private readonly AkashiTimer _akashiTimer;\r
32         private readonly Achievement _achievement = new Achievement();\r
33         private readonly BattleInfo _battleInfo;\r
34         private readonly Logger _logger;\r
35         private readonly ExMapInfo _exMapInfo = new ExMapInfo();\r
36         private readonly MiscTextInfo _miscTextInfo = new MiscTextInfo();\r
37         private readonly BaseAirCoprs _baseAirCoprs;\r
38         private readonly Status _status = new Status();\r
39         private bool _saveState;\r
40         private readonly List<IHaveState> _haveState;\r
41 \r
42         [Flags]\r
43         public enum Update\r
44         {\r
45             None = 0,\r
46             Error = 1 << 0,\r
47             Start = 1 << 1,\r
48             Item = 1 << 2,\r
49             Ship = 1 << 3,\r
50             Timer = 1 << 4,\r
51             NDock = 1 << 5,\r
52             Mission = 1 << 6,\r
53             QuestList = 1 << 7,\r
54             Battle = 1 << 8,\r
55             All = (1 << 9) - 1\r
56         }\r
57 \r
58         public Sniffer()\r
59         {\r
60             _shipInfo = new ShipInfo(_itemInfo);\r
61             _conditionTimer = new ConditionTimer(_shipInfo);\r
62             _dockInfo = new DockInfo(_shipInfo, _materialInfo);\r
63             _akashiTimer = new AkashiTimer(_shipInfo, _dockInfo);\r
64             _battleInfo = new BattleInfo(_shipInfo, _itemInfo);\r
65             _logger = new Logger(_shipInfo, _itemInfo, _battleInfo);\r
66             _baseAirCoprs = new BaseAirCoprs(_itemInfo);\r
67             _haveState = new List<IHaveState> {_achievement, _materialInfo, _conditionTimer, _exMapInfo};\r
68         }\r
69 \r
70         private void SaveState()\r
71         {\r
72             if (!_saveState)\r
73                 return;\r
74             if (!_haveState.Any(x => x.NeedSave))\r
75                 return;\r
76             foreach (var x in _haveState)\r
77                 x.SaveState(_status);\r
78             _status.Save();\r
79         }\r
80 \r
81         public void LoadState()\r
82         {\r
83             _status.Load();\r
84             foreach (var x in _haveState)\r
85                 x.LoadState(_status);\r
86             _saveState = true;\r
87         }\r
88 \r
89         public Update Sniff(string url, string request, dynamic json)\r
90         {\r
91             if (!json.api_result())\r
92                 return Update.Error;\r
93             if ((int)json.api_result != 1)\r
94                 return Update.None;\r
95             var data = json.api_data() ? json.api_data : new object();\r
96 \r
97             if (url.EndsWith("api_start2"))\r
98             {\r
99                 return ApiStart(data);\r
100             }\r
101             if (!_start)\r
102                 return Update.None;\r
103             if (url.EndsWith("api_port/port"))\r
104                 return ApiPort(data);\r
105             if (url.Contains("member"))\r
106                 return ApiMember(url, json);\r
107             if (url.Contains("kousyou"))\r
108                 return ApiKousyou(url, request, data);\r
109             if (url.Contains("battle"))\r
110                 return ApiBattle(url, request, data);\r
111             return ApiOthers(url, request, data);\r
112         }\r
113 \r
114         private Update ApiStart(dynamic data)\r
115         {\r
116             _shipInfo.InspectMaster(data);\r
117             _missionInfo.InspectMaster(data.api_mst_mission);\r
118             _itemInfo.InspectMaster(data);\r
119             _exMapInfo.ResetIfNeeded();\r
120             _start = true;\r
121             return Update.Start;\r
122         }\r
123 \r
124         private Update ApiPort(dynamic data)\r
125         {\r
126             _itemInfo.InspectBasic(data.api_basic);\r
127             _materialInfo.InspectMaterial(data.api_material, true);\r
128             _logger.InspectBasic(data.api_basic);\r
129             _logger.InspectMaterial(data.api_material);\r
130             _shipInfo.InspectShip(data);\r
131             _shipInfo.ClearBadlyDamagedShips();\r
132             _conditionTimer.CalcRegenTime();\r
133             _missionInfo.InspectDeck(data.api_deck_port);\r
134             _dockInfo.InspectNDock(data.api_ndock);\r
135             _akashiTimer.Port();\r
136             _achievement.InspectBasic(data.api_basic);\r
137             if (data.api_parallel_quest_count()) // 昔のログにはないので\r
138                 _questInfo.QuestCount = (int)data.api_parallel_quest_count;\r
139             if (data.api_event_object())\r
140                 _baseAirCoprs.InspectEventObject(data.api_event_object);\r
141             if (data.api_plane_info())\r
142                 _baseAirCoprs.InspectPlaneInfo(data.api_plane_info);\r
143             _battleInfo.CleanupResult();\r
144             _battleInfo.InBattle = false;\r
145             _shipInfo.ClearEscapedShips();\r
146             _miscTextInfo.ClearIfNeeded();\r
147             SaveState();\r
148             return Update.All;\r
149         }\r
150 \r
151         private Update ApiMember(string url, dynamic json)\r
152         {\r
153             var data = json.api_data() ? json.api_data : new object();\r
154 \r
155             if (url.EndsWith("api_get_member/require_info"))\r
156             {\r
157                 _itemInfo.InspectSlotItem(data.api_slot_item, true);\r
158                 _dockInfo.InspectKDock(data.api_kdock);\r
159                 return Update.Timer;\r
160             }\r
161             if (url.EndsWith("api_get_member/basic"))\r
162             {\r
163                 _itemInfo.InspectBasic(data);\r
164                 _logger.InspectBasic(data);\r
165                 return Update.None;\r
166             }\r
167             if (url.EndsWith("api_get_member/slot_item"))\r
168             {\r
169                 _itemInfo.InspectSlotItem(data, true);\r
170                 return Update.Item;\r
171             }\r
172             if (url.EndsWith("api_get_member/kdock"))\r
173             {\r
174                 _dockInfo.InspectKDock(data);\r
175                 _logger.InspectKDock(data);\r
176                 return Update.Timer;\r
177             }\r
178             if (url.EndsWith("api_get_member/ndock"))\r
179             {\r
180                 _dockInfo.InspectNDock(data);\r
181                 _conditionTimer.CheckCond();\r
182                 _akashiTimer.CheckFleet();\r
183                 return Update.NDock | Update.Timer | Update.Ship;\r
184             }\r
185             if (url.EndsWith("api_get_member/questlist"))\r
186             {\r
187                 _questInfo.InspectQuestList(data);\r
188                 return Update.QuestList;\r
189             }\r
190             if (url.EndsWith("api_get_member/deck"))\r
191             {\r
192                 _shipInfo.InspectDeck(data);\r
193                 _missionInfo.InspectDeck(data);\r
194                 _akashiTimer.CheckFleet();\r
195                 return Update.Mission | Update.Timer;\r
196             }\r
197             if (url.EndsWith("api_get_member/ship2"))\r
198             {\r
199                 // ここだけjsonなので注意\r
200                 _shipInfo.InspectShip(json);\r
201                 _akashiTimer.CheckFleet();\r
202                 _battleInfo.InBattle = false;\r
203                 return Update.Item | Update.Ship | Update.Battle;\r
204             }\r
205             if (url.EndsWith("api_get_member/ship_deck"))\r
206             {\r
207                 _shipInfo.InspectShip(data);\r
208                 _akashiTimer.CheckFleet();\r
209                 _battleInfo.InBattle = false;\r
210                 return Update.Ship | Update.Battle;\r
211             }\r
212             if (url.EndsWith("api_get_member/ship3"))\r
213             {\r
214                 _shipInfo.InspectShip(data);\r
215                 _akashiTimer.CheckFleet();\r
216                 _conditionTimer.CheckCond();\r
217                 return Update.Ship;\r
218             }\r
219             if (url.EndsWith("api_get_member/material"))\r
220             {\r
221                 _materialInfo.InspectMaterial(data);\r
222                 return Update.Item;\r
223             }\r
224             if (url.EndsWith("api_get_member/mapinfo"))\r
225             {\r
226                 _exMapInfo.InspectMapInfo(data);\r
227                 _miscTextInfo.InspectMapInfo(data);\r
228                 if (data.api_air_base())\r
229                     _baseAirCoprs.Inspect(data.api_air_base);\r
230                 return Update.Item;\r
231             }\r
232             if (url.EndsWith("api_req_member/get_practice_enemyinfo"))\r
233             {\r
234                 _miscTextInfo.InspectPracticeEnemyInfo(data);\r
235                 return Update.Item;\r
236             }\r
237             if (url.EndsWith("api_get_member/preset_deck"))\r
238             {\r
239                 _shipInfo.InspectPresetDeck(data);\r
240                 return Update.None;\r
241             }\r
242             if (url.EndsWith("api_get_member/base_air_corps"))\r
243             {\r
244                 _baseAirCoprs.Inspect(data);\r
245                 return Update.Ship;\r
246             }\r
247             return Update.None;\r
248         }\r
249 \r
250         private Update ApiKousyou(string url, string request, dynamic data)\r
251         {\r
252             if (url.EndsWith("api_req_kousyou/createitem"))\r
253             {\r
254                 _itemInfo.InspectCreateItem(data);\r
255                 _materialInfo.InspectCreateIem(data);\r
256                 _logger.InspectCreateItem(request, data);\r
257                 return Update.Item;\r
258             }\r
259             if (url.EndsWith("api_req_kousyou/getship"))\r
260             {\r
261                 _itemInfo.InspectGetShip(data);\r
262                 _shipInfo.InspectShip(data);\r
263                 _dockInfo.InspectKDock(data.api_kdock);\r
264                 _conditionTimer.CheckCond();\r
265                 return Update.Item | Update.Timer;\r
266             }\r
267             if (url.EndsWith("api_req_kousyou/destroyship"))\r
268             {\r
269                 _shipInfo.InspectDestroyShip(request, data);\r
270                 _materialInfo.InspectDestroyShip(data);\r
271                 _conditionTimer.CheckCond();\r
272                 _akashiTimer.CheckFleet();\r
273                 return Update.Item | Update.Ship;\r
274             }\r
275             if (url.EndsWith("api_req_kousyou/destroyitem2"))\r
276             {\r
277                 _itemInfo.InspectDestroyItem(request, data);\r
278                 _materialInfo.InspectDestroyItem(data);\r
279                 return Update.Item;\r
280             }\r
281             if (url.EndsWith("api_req_kousyou/remodel_slot"))\r
282             {\r
283                 _logger.SetCurrentMaterial(_materialInfo.Current);\r
284                 _logger.InspectRemodelSlot(request, data); // 資材の差が必要なので_materialInfoより前\r
285                 _itemInfo.InspectRemodelSlot(data);\r
286                 _materialInfo.InspectRemodelSlot(data);\r
287                 return Update.Item;\r
288             }\r
289             if (url.EndsWith("api_req_kousyou/createship"))\r
290             {\r
291                 _logger.InspectCreateShip(request);\r
292                 return Update.None;\r
293             }\r
294             if (url.EndsWith("api_req_kousyou/createship_speedchange"))\r
295             {\r
296                 _dockInfo.InspectCreateShipSpeedChange(request);\r
297                 return Update.Timer;\r
298             }\r
299             return Update.None;\r
300         }\r
301 \r
302         private Update ApiBattle(string url, string request, dynamic data)\r
303         {\r
304             if (IsNormalBattleAPI(url))\r
305             {\r
306                 _battleInfo.InspectBattle(data, url);\r
307                 _logger.InspectBattle(data);\r
308                 return Update.Ship | Update.Battle;\r
309             }\r
310             if (url.EndsWith("api_req_practice/battle") || url.EndsWith("api_req_practice/midnight_battle"))\r
311             {\r
312                 if (url.EndsWith("/battle"))\r
313                 {\r
314                     _shipInfo.InspectMapStart(request); // 演習を出撃中とみなす\r
315                     _conditionTimer.InvalidateCond();\r
316                     _miscTextInfo.ClearFlag = true;\r
317                 }\r
318                 _battleInfo.InspectBattle(data, url);\r
319                 return Update.Ship | Update.Battle | Update.Timer;\r
320             }\r
321             if (url.EndsWith("api_req_sortie/battleresult"))\r
322             {\r
323                 _battleInfo.InspectBattleResult(data);\r
324                 _exMapInfo.InspectBattleResult(data);\r
325                 _logger.InspectBattleResult(data);\r
326                 return Update.Ship;\r
327             }\r
328             if (url.EndsWith("api_req_practice/battle_result"))\r
329             {\r
330                 _battleInfo.InspectPracticeResult(data);\r
331                 return Update.Ship;\r
332             }\r
333             if (IsCombinedBattleAPI(url))\r
334             {\r
335                 _battleInfo.InspectCombinedBattle(data, url);\r
336                 _logger.InspectBattle(data);\r
337                 return Update.Ship | Update.Battle;\r
338             }\r
339             if (url.EndsWith("api_req_combined_battle/battleresult"))\r
340             {\r
341                 _battleInfo.InspectCombinedBattleResult(data);\r
342                 _logger.InspectBattleResult(data);\r
343                 return Update.Ship;\r
344             }\r
345             if (url.EndsWith("api_req_combined_battle/goback_port"))\r
346             {\r
347                 _battleInfo.CauseCombinedBattleEscape();\r
348                 return Update.Ship;\r
349             }\r
350             return Update.None;\r
351         }\r
352 \r
353         private bool IsNormalBattleAPI(string url)\r
354         {\r
355             return url.EndsWith("api_req_sortie/battle") ||\r
356                    url.EndsWith("api_req_sortie/airbattle") ||\r
357                    url.EndsWith("api_req_sortie/ld_airbattle") ||\r
358                    url.EndsWith("api_req_battle_midnight/battle") ||\r
359                    url.EndsWith("api_req_battle_midnight/sp_midnight");\r
360         }\r
361 \r
362         private bool IsCombinedBattleAPI(string url)\r
363         {\r
364             return url.EndsWith("api_req_combined_battle/battle") ||\r
365                    url.EndsWith("api_req_combined_battle/airbattle") ||\r
366                    url.EndsWith("api_req_combined_battle/ld_airbattle") ||\r
367                    url.EndsWith("api_req_combined_battle/battle_water") ||\r
368                    url.EndsWith("api_req_combined_battle/midnight_battle") ||\r
369                    url.EndsWith("api_req_combined_battle/sp_midnight");\r
370         }\r
371 \r
372         private Update ApiOthers(string url, string request, dynamic data)\r
373         {\r
374             if (url.EndsWith("api_req_hensei/change"))\r
375             {\r
376                 _shipInfo.InspectChange(request);\r
377                 _akashiTimer.InspectChange(request);\r
378                 return Update.Ship;\r
379             }\r
380             if (url.EndsWith("api_req_hensei/preset_select"))\r
381             {\r
382                 _shipInfo.InspectDeck(new[] {data});\r
383                 _akashiTimer.CheckFleet();\r
384                 return Update.Ship;\r
385             }\r
386             if (url.EndsWith("api_req_hensei/preset_register"))\r
387             {\r
388                 _shipInfo.InspectPresetRegister(data);\r
389                 return Update.None;\r
390             }\r
391             if (url.EndsWith("api_req_hensei/preset_delete"))\r
392             {\r
393                 _shipInfo.InspectPresetDelete(request);\r
394                 return Update.Timer;\r
395             }\r
396             if (url.EndsWith("api_req_hensei/combined"))\r
397             {\r
398                 _shipInfo.InspectCombined(request);\r
399                 return Update.Ship;\r
400             }\r
401             if (url.EndsWith("api_req_hokyu/charge"))\r
402             {\r
403                 _shipInfo.InspectCharge(data);\r
404                 _materialInfo.InspectCharge(data);\r
405                 return Update.Item | Update.Ship;\r
406             }\r
407             if (url.EndsWith("api_req_kaisou/powerup"))\r
408             {\r
409                 _shipInfo.InspectPowerup(request, data);\r
410                 _conditionTimer.CheckCond();\r
411                 _akashiTimer.CheckFleet();\r
412                 return Update.Item | Update.Ship;\r
413             }\r
414             if (url.EndsWith("api_req_kaisou/slot_exchange_index"))\r
415             {\r
416                 _shipInfo.InspectSlotExchange(request, data);\r
417                 return Update.Ship;\r
418             }\r
419             if (url.EndsWith("api_req_kaisou/slot_deprive"))\r
420             {\r
421                 _shipInfo.InspectSlotDeprive(data);\r
422                 return Update.Ship;\r
423             }\r
424             if (url.EndsWith("api_req_nyukyo/start"))\r
425             {\r
426                 _dockInfo.InspectNyukyo(request);\r
427                 _conditionTimer.CheckCond();\r
428                 _akashiTimer.CheckFleet();\r
429                 return Update.Item | Update.Ship;\r
430             }\r
431             if (url.EndsWith("api_req_nyukyo/speedchange"))\r
432             {\r
433                 _dockInfo.InspectSpeedChange(request);\r
434                 _conditionTimer.CheckCond();\r
435                 return Update.NDock | Update.Timer | Update.Ship;\r
436             }\r
437             if (url.EndsWith("api_req_map/start"))\r
438             {\r
439                 _shipInfo.InspectMapStart(request); // 出撃中判定が必要なので_conditionTimerより前\r
440                 _conditionTimer.InvalidateCond();\r
441                 _exMapInfo.InspectMapStart(data);\r
442                 _logger.InspectMapStart(data);\r
443                 _miscTextInfo.ClearFlag = true;\r
444                 return Update.Timer | Update.Ship;\r
445             }\r
446             if (url.EndsWith("api_req_map/next"))\r
447             {\r
448                 _battleInfo.InspectMapNext(request);\r
449                 _exMapInfo.InspectMapNext(data);\r
450                 _logger.InspectMapNext(data);\r
451                 return Update.None;\r
452             }\r
453             if (url.EndsWith("api_req_mission/result"))\r
454             {\r
455                 _materialInfo.InspectMissionResult(data);\r
456                 _logger.InspectMissionResult(data);\r
457                 return Update.Item;\r
458             }\r
459             if (url.EndsWith("api_req_quest/stop"))\r
460             {\r
461                 _questInfo.InspectStop(request);\r
462                 return Update.QuestList;\r
463             }\r
464             if (url.EndsWith("api_req_quest/clearitemget"))\r
465             {\r
466                 _questInfo.InspectClearItemGet(request);\r
467                 return Update.QuestList;\r
468             }\r
469             if (url.EndsWith("api_req_air_corps/supply"))\r
470             {\r
471                 _materialInfo.InspectAirCorpsSupply(data);\r
472                 _baseAirCoprs.InspectSupply(request, data);\r
473                 return Update.Item;\r
474             }\r
475             if (url.EndsWith("api_req_air_corps/set_plane"))\r
476             {\r
477                 _materialInfo.InspectAirCorpsSetPlane(data);\r
478                 _baseAirCoprs.InspectSetPlane(request, data);\r
479                 return Update.Item | Update.Ship;\r
480             }\r
481             if (url.EndsWith("api_req_air_corps/set_action"))\r
482             {\r
483                 _baseAirCoprs.InspectSetAction(request);\r
484                 return Update.Ship;\r
485             }\r
486             return Update.None;\r
487         }\r
488 \r
489         public NameAndTimer[] NDock => _dockInfo.NDock;\r
490 \r
491         public RingTimer[] KDock => _dockInfo.KDock;\r
492 \r
493         public ItemInfo Item => _itemInfo;\r
494 \r
495         public MaterialInfo Material => _materialInfo;\r
496 \r
497         public QuestStatus[] Quests => _questInfo.Quests;\r
498 \r
499         public NameAndTimer[] Missions => _missionInfo.Missions;\r
500 \r
501         public DateTime GetConditionTimer(int fleet) => _conditionTimer.GetTimer(fleet);\r
502 \r
503         public int[] GetConditionNotice() => _conditionTimer.GetNotice();\r
504 \r
505         public ShipStatus[] GetShipStatuses(int fleet) => _shipInfo.GetShipStatuses(fleet);\r
506 \r
507         public int[] GetDeck(int fleet) => _shipInfo.GetDeck(fleet);\r
508 \r
509         public int CombinedFleetType => _shipInfo.CombinedFleetType;\r
510 \r
511         public ChargeStatus[] ChargeStatuses => _shipInfo.ChargeStatuses;\r
512 \r
513         public int[] GetFighterPower(int fleet) => _shipInfo.GetFighterPower(fleet);\r
514 \r
515         public double GetContactTriggerRate(int fleet) => _shipInfo.GetContactTriggerRate(fleet);\r
516 \r
517         public double GetFleetLineOfSights(int fleet) => _shipInfo.GetLineOfSights(fleet);\r
518 \r
519         public ShipStatus[] RepairList => _shipInfo.GetRepairList(_dockInfo);\r
520 \r
521         public ShipStatus[] ShipList => _shipInfo.ShipList;\r
522 \r
523         public string[] BadlyDamagedShips => _shipInfo.BadlyDamagedShips;\r
524 \r
525         public ItemStatus[] ItemList\r
526         {\r
527             get\r
528             {\r
529                 _itemInfo.ClearHolder();\r
530                 _shipInfo.SetItemHolder();\r
531                 _baseAirCoprs.SetItemHolder();\r
532                 return _itemInfo.ItemList;\r
533             }\r
534         }\r
535 \r
536         public AkashiTimer AkashiTimer => _akashiTimer;\r
537 \r
538         public Achievement Achievement => _achievement;\r
539 \r
540         public BattleInfo Battle => _battleInfo;\r
541 \r
542         public ExMapInfo ExMap => _exMapInfo;\r
543 \r
544         public string MiscText => _miscTextInfo.Text;\r
545 \r
546         public BaseAirCoprs.BaseInfo[] BaseAirCorps => _baseAirCoprs.AllAirCorps;\r
547 \r
548         public void SetLogWriter(Action<string, string, string> writer, Func<DateTime> nowFunc)\r
549         {\r
550             _logger.SetWriter(writer, nowFunc);\r
551         }\r
552 \r
553         public void SkipMaster()\r
554         {\r
555             _start = true;\r
556         }\r
557 \r
558         public void EnableLog(LogType type)\r
559         {\r
560             _logger.EnableLog(type);\r
561         }\r
562 \r
563         public int MaterialLogInterval\r
564         {\r
565             set { _logger.MaterialLogInterval = value; }\r
566         }\r
567 \r
568         public string LogOutputDir\r
569         {\r
570             set { _logger.OutputDir = value; }\r
571         }\r
572     }\r
573 \r
574     public class NameAndTimer\r
575     {\r
576         public string Name { get; set; }\r
577         public RingTimer Timer { get; set; }\r
578 \r
579         public NameAndTimer()\r
580         {\r
581             Timer = new RingTimer();\r
582         }\r
583     }\r
584 \r
585     public class RingTimer\r
586     {\r
587         private readonly TimeSpan _spare;\r
588 \r
589         public TimeSpan Rest { get; private set; }\r
590 \r
591         public bool IsFinished => EndTime != DateTime.MinValue && Rest <= _spare;\r
592 \r
593         public DateTime EndTime { get; private set; }\r
594 \r
595         public bool NeedRing { get; set; }\r
596 \r
597         public RingTimer(int spare = 60)\r
598         {\r
599             _spare = TimeSpan.FromSeconds(spare);\r
600         }\r
601 \r
602         public void SetEndTime(double time)\r
603         {\r
604             SetEndTime((int)time == 0\r
605                 ? DateTime.MinValue\r
606                 : new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(time / 1000));\r
607         }\r
608 \r
609         public void SetEndTime(DateTime time)\r
610         {\r
611             EndTime = time;\r
612         }\r
613 \r
614         public void Update()\r
615         {\r
616             if (EndTime == DateTime.MinValue)\r
617             {\r
618                 Rest = TimeSpan.Zero;\r
619                 return;\r
620             }\r
621             var prev = Rest;\r
622             Rest = EndTime - DateTime.Now;\r
623             if (Rest < TimeSpan.Zero)\r
624                 Rest = TimeSpan.Zero;\r
625             if (prev > _spare && _spare >= Rest)\r
626                 NeedRing = true;\r
627         }\r
628 \r
629         public string ToString(bool finish = false)\r
630             => EndTime == DateTime.MinValue\r
631                 ? ""\r
632                 : finish ? EndTime.ToString(@"dd\ HH\:mm") : $@"{(int)Rest.TotalHours:d2}:{Rest:mm\:ss}";\r
633     }\r
634 }