OSDN Git Service

出撃から帰投したときの資材の増減が正しく表示されないのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Logger.cs
1 // Copyright (C) 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.IO;\r
18 using System.Linq;\r
19 using System.Text;\r
20 \r
21 namespace KancolleSniffer\r
22 {\r
23     [Flags]\r
24     public enum LogType\r
25     {\r
26         None = 0,\r
27         Mission = 1,\r
28         Battle = 2,\r
29         Material = 4,\r
30         CreateItem = 8,\r
31         CreateShip = 16,\r
32         RemodelSlot = 32,\r
33         All = 63,\r
34     }\r
35 \r
36     public class Logger\r
37     {\r
38         private LogType _logType;\r
39         private readonly ShipInfo _shipInfo;\r
40         private readonly ItemInfo _itemInfo;\r
41         private readonly BattleInfo _battleInfo;\r
42         private Action<string, string, string> _writer;\r
43         private Func<DateTime> _nowFunc;\r
44         public const string DateTimeFormat = @"yyyy\-MM\-dd HH\:mm\:ss";\r
45         private dynamic _battle;\r
46         private dynamic _map;\r
47         private dynamic _basic;\r
48         private int _kdockId;\r
49         private DateTime _prevTime;\r
50         private int[] _currentMaterial = new int[Enum.GetValues(typeof(Material)).Length];\r
51         private int _materialLogInterval = 10;\r
52         private bool _start;\r
53 \r
54         public int MaterialLogInterval\r
55         {\r
56             set { _materialLogInterval = value; }\r
57         }\r
58 \r
59         public string OutputDir\r
60         {\r
61             set { _writer = new LogWriter(value).Write; }\r
62         }\r
63 \r
64         public Logger(ShipInfo ship, ItemInfo item, BattleInfo battle)\r
65         {\r
66             _shipInfo = ship;\r
67             _itemInfo = item;\r
68             _battleInfo = battle;\r
69             _writer = new LogWriter().Write;\r
70             _nowFunc = () => DateTime.Now;\r
71         }\r
72 \r
73         public void EnableLog(LogType type)\r
74         {\r
75             _logType = type;\r
76         }\r
77 \r
78         public void SetWriter(Action<string, string, string> writer, Func<DateTime> nowFunc)\r
79         {\r
80             _writer = writer;\r
81             _nowFunc = nowFunc;\r
82         }\r
83 \r
84         public void InspectMissionResult(dynamic json)\r
85         {\r
86             var r = (int)json.api_clear_result;\r
87             var rstr = r == 2 ? "大成功" : r == 1 ? "成功" : "失敗";\r
88             var material = new int[7];\r
89             if (r != 0)\r
90                 ((int[])json.api_get_material).CopyTo(material, 0);\r
91             foreach (var i in new[] {1, 2})\r
92             {\r
93                 var attr = "api_get_item" + i;\r
94                 if (!json.IsDefined(attr) || json[attr].api_useitem_id != -1)\r
95                     continue;\r
96                 var count = (int)json[attr].api_useitem_count;\r
97                 var flag = ((int[])json.api_useitem_flag)[i - 1];\r
98                 if (flag == 1)\r
99                     material[(int)Material.Bucket] = count;\r
100                 else if (flag == 2)\r
101                     material[(int)Material.Burner + 2] = count; // 高速建造材と開発資材が反対なのでいつか直す\r
102                 else if (flag == 3)\r
103                     material[(int)Material.Development - 2] = count;\r
104             }\r
105             if ((_logType & LogType.Mission) != 0)\r
106             {\r
107                 _writer("遠征報告書",\r
108                     string.Join(",", _nowFunc().ToString(DateTimeFormat),\r
109                         rstr, json.api_quest_name, string.Join(",", material)),\r
110                     "日付,結果,遠征,燃料,弾薬,鋼材,ボーキ,開発資材,高速修復材,高速建造材");\r
111             }\r
112         }\r
113 \r
114         public void InspectMapStart(dynamic json)\r
115         {\r
116             _start = true;\r
117             _map = json;\r
118             _battle = null;\r
119             if ((_logType & LogType.Material) != 0)\r
120                 WriteMaterialLog(_nowFunc());\r
121         }\r
122 \r
123         public void InspectMapNext(dynamic json)\r
124         {\r
125             _map = json;\r
126         }\r
127 \r
128         public void InspectBattle(dynamic json)\r
129         {\r
130             if (_battle != null) // 通常の夜戦は無視する\r
131                 return;\r
132             _battle = json;\r
133         }\r
134 \r
135         public void InspectBattleResult(dynamic result)\r
136         {\r
137             if ((_logType & LogType.Battle) == 0 || _map == null || _battle == null)\r
138             {\r
139                 _map = _battle = null;\r
140                 return;\r
141             }\r
142             var fships = new List<string>();\r
143             var deckId = _battle.api_dock_id() ? (int)_battle.api_dock_id - 1 : 0;\r
144             var deck = _shipInfo.GetDeck(deckId);\r
145             fships.AddRange(deck.Select(id =>\r
146             {\r
147                 if (id == -1)\r
148                     return ",";\r
149                 var s = _shipInfo.GetStatus(id);\r
150                 return $"{s.Name}(Lv{s.Level}),{s.NowHp}/{s.MaxHp}";\r
151             }));\r
152             var estatus = _battleInfo.EnemyResultStatus;\r
153             var edeck = ((int[])_battle.api_ship_ke).Skip(1).ToArray();\r
154             var eships = edeck.Select((id, i) =>\r
155             {\r
156                 if (id == -1)\r
157                     return ",";\r
158                 var s = estatus[i];\r
159                 return $"{s.Name},{s.NowHp}/{s.MaxHp}";\r
160             });\r
161             var cell = (int)_map.api_no;\r
162             var boss = "";\r
163             if (_start)\r
164                 boss = "出撃";\r
165             if (cell == (int)_map.api_bosscell_no || (int)_map.api_event_id == 5)\r
166                 boss = _start ? "出撃&ボス" : "ボス";\r
167             var dropType = result.api_get_ship() ? result.api_get_ship.api_ship_type : "";\r
168             if (result.api_get_useitem())\r
169             {\r
170                 if (dropType == "")\r
171                     dropType = "アイテム";\r
172                 else\r
173                     dropType += "+アイテム";\r
174             }\r
175             var dropName = result.api_get_ship() ? result.api_get_ship.api_ship_name : "";\r
176             if (result.api_get_useitem())\r
177             {\r
178                 var itemName = _itemInfo.GetUseItemName((int)result.api_get_useitem.api_useitem_id);\r
179                 if (dropName == "")\r
180                     dropName = itemName;\r
181                 else\r
182                     dropName += "+" + itemName;\r
183             }\r
184             var fp = _shipInfo.GetFighterPower(deckId);\r
185             var fpower = fp[0] == fp[1] ? fp[0].ToString() : fp[0] + "~" + fp[1];\r
186             _writer("海戦・ドロップ報告書", string.Join(",", _nowFunc().ToString(DateTimeFormat),\r
187                 result.api_quest_name,\r
188                 cell, boss,\r
189                 result.api_win_rank,\r
190                 BattleFormationName((int)_battle.api_formation[2]),\r
191                 FormationName(_battle.api_formation[0]),\r
192                 FormationName(_battle.api_formation[1]),\r
193                 result.api_enemy_info.api_deck_name,\r
194                 dropType, dropName,\r
195                 string.Join(",", fships),\r
196                 string.Join(",", eships),\r
197                 fpower, _battleInfo.EnemyFighterPower,\r
198                 AirControlLevelName(_battle)),\r
199                 "日付,海域,マス,ボス,ランク,艦隊行動,味方陣形,敵陣形,敵艦隊,ドロップ艦種,ドロップ艦娘," +\r
200                 "味方艦1,味方艦1HP,味方艦2,味方艦2HP,味方艦3,味方艦3HP,味方艦4,味方艦4HP,味方艦5,味方艦5HP,味方艦6,味方艦6HP," +\r
201                 "敵艦1,敵艦1HP,敵艦2,敵艦2HP,敵艦3,敵艦3HP,敵艦4,敵艦4HP,敵艦5,敵艦5HP,敵艦6,敵艦6HP," +\r
202                 "味方制空値,敵制空値,制空状態"\r
203                 );\r
204             _map = _battle = null;\r
205             _start = false;\r
206         }\r
207 \r
208         private string FormationName(dynamic f)\r
209         {\r
210             if (f is string) // 連合艦隊のときは文字列\r
211                 f = int.Parse(f);\r
212             switch ((int)f)\r
213             {\r
214                 case 1:\r
215                     return "単縦陣";\r
216                 case 2:\r
217                     return "複縦陣";\r
218                 case 3:\r
219                     return "輪形陣";\r
220                 case 4:\r
221                     return "梯形陣";\r
222                 case 5:\r
223                     return "単横陣";\r
224                 case 11:\r
225                     return "第一警戒航行序列";\r
226                 case 12:\r
227                     return "第二警戒航行序列";\r
228                 case 13:\r
229                     return "第三警戒航行序列";\r
230                 case 14:\r
231                     return "第四警戒航行序列";\r
232                 default:\r
233                     return "単縦陣";\r
234             }\r
235         }\r
236 \r
237         private static string BattleFormationName(int f)\r
238         {\r
239             switch (f)\r
240             {\r
241                 case 1:\r
242                     return "同航戦";\r
243                 case 2:\r
244                     return "反航戦";\r
245                 case 3:\r
246                     return "T字戦(有利)";\r
247                 case 4:\r
248                     return "T字戦(不利)";\r
249                 default:\r
250                     return "同航戦";\r
251             }\r
252         }\r
253 \r
254         private string AirControlLevelName(dynamic json)\r
255         {\r
256             // BattleInfo.AirControlLevelは夜戦で消されているかもしれないので、こちらで改めて調べる。\r
257             if (!json.api_kouku())\r
258                 return "";\r
259             var stage1 = json.api_kouku.api_stage1;\r
260             if (stage1 == null)\r
261                 return "";\r
262             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
263                 return "";\r
264             switch ((int)stage1.api_disp_seiku)\r
265             {\r
266                 case 0:\r
267                     return "航空均衡";\r
268                 case 1:\r
269                     return "制空権確保";\r
270                 case 2:\r
271                     return "航空優勢";\r
272                 case 3:\r
273                     return "航空劣勢";\r
274                 case 4:\r
275                     return "制空権喪失";\r
276                 default:\r
277                     return "";\r
278             }\r
279         }\r
280 \r
281         public void InspectBasic(dynamic json)\r
282         {\r
283             _basic = json;\r
284         }\r
285 \r
286         public void InspectCreateItem(string request, dynamic json)\r
287         {\r
288             if ((_logType & LogType.CreateItem) == 0)\r
289                 return;\r
290             var values = HttpUtility.ParseQueryString(request);\r
291             var name = "失敗";\r
292             var type = "";\r
293             if (json.api_slot_item())\r
294             {\r
295                 var spec = _itemInfo.GetSpecByItemId((int)json.api_slot_item.api_slotitem_id);\r
296                 name = spec.Name;\r
297                 type = spec.TypeName;\r
298             }\r
299             _writer("開発報告書",\r
300                 _nowFunc().ToString(DateTimeFormat) + "," +\r
301                 string.Join(",", name, type,\r
302                     values["api_item1"], values["api_item2"], values["api_item3"], values["api_item4"],\r
303                     Secretary(), _basic.api_level),\r
304                 "日付,開発装備,種別,燃料,弾薬,鋼材,ボーキ,秘書艦,司令部Lv");\r
305         }\r
306 \r
307         public void InspectCreateShip(string request)\r
308         {\r
309             var values = HttpUtility.ParseQueryString(request);\r
310             _kdockId = int.Parse(values["api_kdock_id"]);\r
311         }\r
312 \r
313         public void InspectKDock(dynamic json)\r
314         {\r
315             if ((_logType & LogType.CreateShip) == 0 || _basic == null || _kdockId == 0)\r
316                 return;\r
317             var kdock = ((dynamic[])json).First(e => e.api_id == _kdockId);\r
318             var material = Enumerable.Range(1, 5).Select(i => (int)kdock["api_item" + i]).ToArray();\r
319             var ship = _shipInfo.GetSpec((int)kdock.api_created_ship_id);\r
320             var avail = ((dynamic[])json).Count(e => (int)e.api_state == 0);\r
321             _writer("建造報告書",\r
322                 _nowFunc().ToString(DateTimeFormat) + "," +\r
323                 string.Join(",", material.First() >= 1500 ? "大型艦建造" : "通常艦建造",\r
324                     ship.Name, ship.ShipTypeName, string.Join(",", material), avail, Secretary(), _basic.api_level),\r
325                 "日付,種類,名前,艦種,燃料,弾薬,鋼材,ボーキ,開発資材,空きドック,秘書艦,司令部Lv");\r
326             _kdockId = 0;\r
327         }\r
328 \r
329         private string Secretary()\r
330         {\r
331             var ship = _shipInfo.GetShipStatuses(0)[0];\r
332             return ship.Name + "(" + ship.Level + ")";\r
333         }\r
334 \r
335         public void InspectMaterial(dynamic json)\r
336         {\r
337             if ((_logType & LogType.Material) == 0)\r
338                 return;\r
339             foreach (var e in json)\r
340                 _currentMaterial[(int)e.api_id - 1] = (int)e.api_value;\r
341             var now = _nowFunc();\r
342             if (now - _prevTime < TimeSpan.FromMinutes(_materialLogInterval))\r
343                 return;\r
344             WriteMaterialLog(now);\r
345         }\r
346 \r
347         public void WriteMaterialLog(DateTime now)\r
348         {\r
349             _prevTime = now;\r
350             _writer("資材ログ",\r
351                 now.ToString(DateTimeFormat) + "," +\r
352                 string.Join(",", _currentMaterial),\r
353                 "日付,燃料,弾薬,鋼材,ボーキ,高速建造材,高速修復材,開発資材,改修資材");\r
354         }\r
355 \r
356         public void SetCurrentMaterial(int[] material)\r
357         {\r
358             _currentMaterial = material;\r
359         }\r
360 \r
361         public void InspectRemodelSlot(string request, dynamic json)\r
362         {\r
363             if ((_logType & LogType.RemodelSlot) == 0)\r
364                 return;\r
365             var now = _nowFunc();\r
366             var values = HttpUtility.ParseQueryString(request);\r
367             var id = int.Parse(values["api_slot_id"]);\r
368             var name = _itemInfo.GetName(id);\r
369             var level = _itemInfo.GetStatus(id).Level;\r
370             var success = (int)json.api_remodel_flag == 1 ? "○" : "×";\r
371             var certain = int.Parse(values["api_certain_flag"]) == 1 ? "○" : "";\r
372             var useName = "";\r
373             var useNum = "";\r
374             if (json.api_use_slot_id())\r
375             {\r
376                 var use = (int[])json.api_use_slot_id;\r
377                 useName = _itemInfo.GetName(use[0]);\r
378                 useNum = use.Length.ToString();\r
379             }\r
380             var after = (int[])json.api_after_material;\r
381             var diff = new int[after.Length];\r
382             for (var i = 0; i < after.Length; i++)\r
383                 diff[i] = _currentMaterial[i] - after[i];\r
384             var ship1 = Secretary();\r
385             var ship2 = "";\r
386             var ships = _shipInfo.GetShipStatuses(0);\r
387             if (ships.Length >= 2)\r
388                 ship2 = ships[1].Name + "(" + ships[1].Level + ")";\r
389             _writer("改修報告書",\r
390                 now.ToString(DateTimeFormat) + "," +\r
391                 string.Join(",", name, level, success, certain, useName, useNum,\r
392                     diff[(int)Material.Fuel], diff[(int)Material.Bullet], diff[(int)Material.Steal],\r
393                     diff[(int)Material.Bouxite],\r
394                     diff[(int)Material.Development], diff[(int)Material.Screw],\r
395                     ship1, ship2),\r
396                 "日付,改修装備,レベル,成功,確実化,消費装備,消費数,燃料,弾薬,鋼材,ボーキ,開発資材,改修資材,秘書艦,二番艦");\r
397         }\r
398     }\r
399 \r
400     public class LogWriter\r
401     {\r
402         private readonly IFile _file;\r
403         private readonly string _outputDir;\r
404 \r
405         public interface IFile\r
406         {\r
407             string ReadAllText(string path);\r
408             void AppendAllText(string path, string text);\r
409             void Delete(string path);\r
410             bool Exists(string path);\r
411         }\r
412 \r
413         private class FileWrapper : IFile\r
414         {\r
415             // Shift_JISでないとExcelで文字化けする\r
416             private readonly Encoding _encoding = Encoding.GetEncoding("Shift_JIS");\r
417 \r
418             public string ReadAllText(string path) => File.ReadAllText(path, _encoding);\r
419 \r
420             public void AppendAllText(string path, string text)\r
421             {\r
422                 File.AppendAllText(path, text, _encoding);\r
423             }\r
424 \r
425             public void Delete(string path)\r
426             {\r
427                 File.Delete(path);\r
428             }\r
429 \r
430             public bool Exists(string path) => File.Exists(path);\r
431         }\r
432 \r
433         public LogWriter(string outputDir = null, IFile file = null)\r
434         {\r
435             _outputDir = outputDir ?? AppDomain.CurrentDomain.BaseDirectory;\r
436             _file = file ?? new FileWrapper();\r
437         }\r
438 \r
439         public void Write(string file, string s, string header)\r
440         {\r
441             var path = Path.Combine(_outputDir, file);\r
442             var csv = path + ".csv";\r
443             var tmp = path + ".tmp";\r
444             if (_file.Exists(tmp))\r
445             {\r
446                 try\r
447                 {\r
448                     _file.AppendAllText(csv, _file.ReadAllText(tmp));\r
449                     _file.Delete(tmp);\r
450                 }\r
451                 catch (IOException)\r
452                 {\r
453                 }\r
454             }\r
455             if (!_file.Exists(csv))\r
456                 s = header + "\r\n" + s;\r
457             foreach (var f in new[] {csv, tmp})\r
458             {\r
459                 try\r
460                 {\r
461                     _file.AppendAllText(f, s + "\r\n");\r
462                     break;\r
463                 }\r
464                 catch (IOException e)\r
465                 {\r
466                     if (f == tmp)\r
467                         throw new LogIOException("報告書の出力中にエラーが発生しました。", e);\r
468                 }\r
469             }\r
470         }\r
471     }\r
472 \r
473     public class LogIOException : Exception\r
474     {\r
475         public LogIOException()\r
476         {\r
477         }\r
478 \r
479         public LogIOException(string message) : base(message)\r
480         {\r
481         }\r
482 \r
483         public LogIOException(string message, Exception inner) : base(message, inner)\r
484         {\r
485         }\r
486     }\r
487 }