OSDN Git Service

ItemInfoの抽象度を上げる
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Logger.cs
1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 //\r
3 // This program is part of KancolleSniffer.\r
4 //\r
5 // KancolleSniffer is free software: you can redistribute it and/or modify\r
6 // it under the terms of the GNU General Public License as published by\r
7 // the Free Software Foundation, either version 3 of the License, or\r
8 // (at your option) any later version.\r
9 //\r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 //\r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, see <http://www.gnu.org/licenses/>.\r
17 \r
18 using System;\r
19 using System.Collections.Generic;\r
20 using System.IO;\r
21 using System.Linq;\r
22 using System.Text;\r
23 using System.Web;\r
24 \r
25 namespace KancolleSniffer\r
26 {\r
27     [Flags]\r
28     public enum LogType\r
29     {\r
30         None = 0,\r
31         Mission = 1,\r
32         Battle = 2,\r
33         Material = 4,\r
34         CreateItem = 8,\r
35         CreateShip = 16,\r
36         RemodelSlot = 32,\r
37         All = 63,\r
38     }\r
39 \r
40     public class Logger\r
41     {\r
42         private LogType _logType;\r
43         private readonly ShipInfo _shipInfo;\r
44         private readonly ItemInfo _itemInfo;\r
45         private readonly BattleInfo _battleInfo;\r
46         private Action<string, string, string> _writer;\r
47         private Func<DateTime> _nowFunc;\r
48         public const string DateTimeFormat = @"yyyy\-MM\-dd HH\:mm\:ss";\r
49         private dynamic _battle;\r
50         private dynamic _map;\r
51         private dynamic _basic;\r
52         private int _kdockId;\r
53         private DateTime _prevTime;\r
54         private int[] _currentMaterial;\r
55         private int _materialLogInterval = 10;\r
56         private bool _start;\r
57 \r
58         public int MaterialLogInterval\r
59         {\r
60             set { _materialLogInterval = value; }\r
61         }\r
62 \r
63         public string OutputDir\r
64         {\r
65             set { _writer = new LogWriter(value).Write; }\r
66         }\r
67 \r
68         public Logger(ShipInfo ship, ItemInfo item, BattleInfo battle)\r
69         {\r
70             _shipInfo = ship;\r
71             _itemInfo = item;\r
72             _battleInfo = battle;\r
73             _writer = new LogWriter().Write;\r
74             _nowFunc = () => DateTime.Now;\r
75         }\r
76 \r
77         public void EnableLog(LogType type)\r
78         {\r
79             _logType = type;\r
80         }\r
81 \r
82         public void SetWriter(Action<string, string, string> writer, Func<DateTime> nowFunc)\r
83         {\r
84             _writer = writer;\r
85             _nowFunc = nowFunc;\r
86         }\r
87 \r
88         public void InspectMissionResult(dynamic json)\r
89         {\r
90             var r = (int)json.api_clear_result;\r
91             var rstr = r == 2 ? "大成功" : r == 1 ? "成功" : "失敗";\r
92             var material = new int[7];\r
93             if (r != 0)\r
94                 ((int[])json.api_get_material).CopyTo(material, 0);\r
95             foreach (var i in new[] {1, 2})\r
96             {\r
97                 var attr = "api_get_item" + i;\r
98                 if (!json.IsDefined(attr) || json[attr].api_useitem_id != -1)\r
99                     continue;\r
100                 var count = (int)json[attr].api_useitem_count;\r
101                 var flag = ((int[])json.api_useitem_flag)[i - 1];\r
102                 if (flag == 1)\r
103                     material[(int)Material.Bucket] = count;\r
104                 else if (flag == 2)\r
105                     material[(int)Material.Burner + 2] = count; // 高速建造材と開発資材が反対なのでいつか直す\r
106                 else if (flag == 3)\r
107                     material[(int)Material.Development - 2] = count;\r
108             }\r
109             if ((_logType & LogType.Mission) != 0)\r
110             {\r
111                 _writer("遠征報告書",\r
112                     string.Join(",", _nowFunc().ToString(DateTimeFormat),\r
113                         rstr, json.api_quest_name, string.Join(",", material)),\r
114                     "日付,結果,遠征,燃料,弾薬,鋼材,ボーキ,開発資材,高速修復材,高速建造材");\r
115             }\r
116         }\r
117 \r
118         public void InspectMapStart(dynamic json)\r
119         {\r
120             _start = true;\r
121             _map = json;\r
122         }\r
123 \r
124         public void InspectMapNext(dynamic json)\r
125         {\r
126             _map = json;\r
127         }\r
128 \r
129         public void InspectBattle(dynamic json)\r
130         {\r
131             if (_battle != null) // 通常の夜戦は無視する\r
132                 return;\r
133             _battle = json;\r
134         }\r
135 \r
136         public void InspectBattleResult(dynamic result)\r
137         {\r
138             if ((_logType & LogType.Battle) == 0 || _map == null || _battle == null)\r
139             {\r
140                 _map = _battle = null;\r
141                 return;\r
142             }\r
143             var fships = new List<string>();\r
144             var deck = _shipInfo.GetDeck(_battle.api_dock_id() ? (int)_battle.api_dock_id - 1 : 0);\r
145             fships.AddRange(deck.Select(id =>\r
146             {\r
147                 if (id == -1)\r
148                     return ",";\r
149                 var s = _shipInfo[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 = new List<string>();\r
155             for (var i = 0; i < edeck.Count(); i++)\r
156             {\r
157                 eships.Add(edeck[i] == -1\r
158                     ? ","\r
159                     : $"{estatus[i].Name},{estatus[i].NowHp}/{estatus[i].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             _writer("海戦・ドロップ報告書", string.Join(",", _nowFunc().ToString(DateTimeFormat),\r
185                 result.api_quest_name,\r
186                 cell, boss,\r
187                 result.api_win_rank,\r
188                 BattleFormationName((int)_battle.api_formation[2]),\r
189                 FormationName(_battle.api_formation[0]),\r
190                 FormationName(_battle.api_formation[1]),\r
191                 result.api_enemy_info.api_deck_name,\r
192                 dropType, dropName,\r
193                 string.Join(",", fships),\r
194                 string.Join(",", eships)),\r
195                 "日付,海域,マス,ボス,ランク,艦隊行動,味方陣形,敵陣形,敵艦隊,ドロップ艦種,ドロップ艦娘," +\r
196                 "味方艦1,味方艦1HP,味方艦2,味方艦2HP,味方艦3,味方艦3HP,味方艦4,味方艦4HP,味方艦5,味方艦5HP,味方艦6,味方艦6HP," +\r
197                 "敵艦1,敵艦1HP,敵艦2,敵艦2HP,敵艦3,敵艦3HP,敵艦4,敵艦4HP,敵艦5,敵艦5HP,敵艦6,敵艦6HP"\r
198                 );\r
199             _map = _battle = null;\r
200             _start = false;\r
201         }\r
202 \r
203         private string FormationName(dynamic f)\r
204         {\r
205             if (f is string) // 連合艦隊のときは文字列\r
206                 f = int.Parse(f);\r
207             switch ((int)f)\r
208             {\r
209                 case 1:\r
210                     return "単縦陣";\r
211                 case 2:\r
212                     return "複縦陣";\r
213                 case 3:\r
214                     return "輪形陣";\r
215                 case 4:\r
216                     return "梯形陣";\r
217                 case 5:\r
218                     return "単横陣";\r
219                 case 11:\r
220                     return "第一警戒航行序列";\r
221                 case 12:\r
222                     return "第二警戒航行序列";\r
223                 case 13:\r
224                     return "第三警戒航行序列";\r
225                 case 14:\r
226                     return "第四警戒航行序列";\r
227                 default:\r
228                     return "単縦陣";\r
229             }\r
230         }\r
231 \r
232         private static String BattleFormationName(int f)\r
233         {\r
234             switch (f)\r
235             {\r
236                 case 1:\r
237                     return "同航戦";\r
238                 case 2:\r
239                     return "反航戦";\r
240                 case 3:\r
241                     return "T字戦(有利)";\r
242                 case 4:\r
243                     return "T字戦(不利)";\r
244                 default:\r
245                     return "同航戦";\r
246             }\r
247         }\r
248 \r
249         public void InspectBasic(dynamic json)\r
250         {\r
251             _basic = json;\r
252         }\r
253 \r
254         public void InspectCreateItem(string request, dynamic json)\r
255         {\r
256             if ((_logType & LogType.CreateItem) == 0)\r
257                 return;\r
258             var values = HttpUtility.ParseQueryString(request);\r
259             var name = "失敗";\r
260             var type = "";\r
261             if (json.api_slot_item())\r
262             {\r
263                 var spec = _itemInfo.GetSpecByItemId((int)json.api_slot_item.api_slotitem_id);\r
264                 name = spec.Name;\r
265                 type = spec.TypeName;\r
266             }\r
267             _writer("開発報告書",\r
268                 _nowFunc().ToString(DateTimeFormat) + "," +\r
269                 string.Join(",", name, type,\r
270                     values["api_item1"], values["api_item2"], values["api_item3"], values["api_item4"],\r
271                     Secretary(), _basic.api_level),\r
272                 "日付,開発装備,種別,燃料,弾薬,鋼材,ボーキ,秘書艦,司令部Lv");\r
273         }\r
274 \r
275         public void InspectCreateShip(string request)\r
276         {\r
277             var values = HttpUtility.ParseQueryString(request);\r
278             _kdockId = int.Parse(values["api_kdock_id"]);\r
279         }\r
280 \r
281         public void InspectKDock(dynamic json)\r
282         {\r
283             if ((_logType & LogType.CreateShip) == 0 || _basic == null || _kdockId == 0)\r
284                 return;\r
285             var kdock = ((dynamic[])json).First(e => e.api_id == _kdockId);\r
286             var material = Enumerable.Range(1, 5).Select(i => (int)kdock["api_item" + i]).ToArray();\r
287             var ship = _shipInfo.GetSpec((int)kdock.api_created_ship_id);\r
288             var avail = ((dynamic[])json).Count(e => (int)e.api_state == 0);\r
289             _writer("建造報告書",\r
290                 _nowFunc().ToString(DateTimeFormat) + "," +\r
291                 string.Join(",", material.First() >= 1500 ? "大型艦建造" : "通常艦建造",\r
292                     ship.Name, ship.ShipTypeName, string.Join(",", material), avail, Secretary(), _basic.api_level),\r
293                 "日付,種類,名前,艦種,燃料,弾薬,鋼材,ボーキ,開発資材,空きドック,秘書艦,司令部Lv");\r
294             _kdockId = 0;\r
295         }\r
296 \r
297         private string Secretary()\r
298         {\r
299             var ship = _shipInfo.GetShipStatuses(0)[0];\r
300             return ship.Name + "(" + ship.Level + ")";\r
301         }\r
302 \r
303         public void InspectMaterial(dynamic json)\r
304         {\r
305             if ((_logType & LogType.Material) == 0)\r
306                 return;\r
307             var now = _nowFunc();\r
308             if (now - _prevTime < TimeSpan.FromMinutes(_materialLogInterval))\r
309                 return;\r
310             _prevTime = now;\r
311             var material = new int[8];\r
312             foreach (var e in json)\r
313                 material[(int)e.api_id - 1] = (int)e.api_value;\r
314             _writer("資材ログ",\r
315                 now.ToString(DateTimeFormat) + "," +\r
316                 string.Join(",", material),\r
317                 "日付,燃料,弾薬,鋼材,ボーキ,高速建造材,高速修復材,開発資材,改修資材");\r
318         }\r
319 \r
320         public void SetCurrentMaterial(int[] material)\r
321         {\r
322             _currentMaterial = material;\r
323         }\r
324 \r
325         public void InspectRemodelSlot(string request, dynamic json)\r
326         {\r
327             if ((_logType & LogType.RemodelSlot) == 0)\r
328                 return;\r
329             var now = _nowFunc();\r
330             var values = HttpUtility.ParseQueryString(request);\r
331             var id = int.Parse(values["api_slot_id"]);\r
332             var name = _itemInfo.GetName(id);\r
333             var level = _itemInfo.GetStatus(id).Level;\r
334             var success = (int)json.api_remodel_flag == 1 ? "○" : "×";\r
335             var certain = int.Parse(values["api_certain_flag"]) == 1 ? "○" : "";\r
336             var useName = "";\r
337             var useNum = "";\r
338             if (json.api_use_slot_id())\r
339             {\r
340                 var use = (int[])json.api_use_slot_id;\r
341                 useName = _itemInfo.GetName(use[0]);\r
342                 useNum = use.Length.ToString();\r
343             }\r
344             var after = (int[])json.api_after_material;\r
345             var diff = new int[after.Length];\r
346             for (var i = 0; i < after.Length; i++)\r
347                 diff[i] = _currentMaterial[i] - after[i];\r
348             var ship1 = Secretary();\r
349             var ship2 = "";\r
350             var ships = _shipInfo.GetShipStatuses(0);\r
351             if (ships.Length >= 2)\r
352                 ship2 = ships[1].Name + "(" + ships[1].Level + ")";\r
353             _writer("改修報告書",\r
354                 now.ToString(DateTimeFormat) + "," +\r
355                 string.Join(",", name, level, success, certain, useName, useNum,\r
356                     diff[(int)Material.Fuel], diff[(int)Material.Bullet], diff[(int)Material.Steal],\r
357                     diff[(int)Material.Bouxite],\r
358                     diff[(int)Material.Development], diff[(int)Material.Screw],\r
359                     ship1, ship2),\r
360                 "日付,改修装備,レベル,成功,確実化,消費装備,消費数,燃料,弾薬,鋼材,ボーキ,開発資材,改修資材,秘書艦,二番艦");\r
361         }\r
362     }\r
363 \r
364     public class LogWriter\r
365     {\r
366         private readonly IFile _file;\r
367         private readonly string _outputDir;\r
368 \r
369         public interface IFile\r
370         {\r
371             string ReadAllText(string path);\r
372             void AppendAllText(string path, string text);\r
373             void Delete(string path);\r
374             bool Exists(string path);\r
375         }\r
376 \r
377         private class FileWrapper : IFile\r
378         {\r
379             // Shift_JISでないとExcelで文字化けする\r
380             private readonly Encoding _encoding = Encoding.GetEncoding("Shift_JIS");\r
381 \r
382             public string ReadAllText(string path) => File.ReadAllText(path, _encoding);\r
383 \r
384             public void AppendAllText(string path, string text)\r
385             {\r
386                 File.AppendAllText(path, text, _encoding);\r
387             }\r
388 \r
389             public void Delete(string path)\r
390             {\r
391                 File.Delete(path);\r
392             }\r
393 \r
394             public bool Exists(string path) => File.Exists(path);\r
395         }\r
396 \r
397         public LogWriter(string outputDir = null, IFile file = null)\r
398         {\r
399             _outputDir = outputDir ?? AppDomain.CurrentDomain.BaseDirectory;\r
400             _file = file ?? new FileWrapper();\r
401         }\r
402 \r
403         public void Write(string file, string s, string header)\r
404         {\r
405             var path = Path.Combine(_outputDir, file);\r
406             var csv = path + ".csv";\r
407             var tmp = path + ".tmp";\r
408             if (_file.Exists(tmp))\r
409             {\r
410                 try\r
411                 {\r
412                     _file.AppendAllText(csv, _file.ReadAllText(tmp));\r
413                     _file.Delete(tmp);\r
414                 }\r
415                 catch (IOException)\r
416                 {\r
417                 }\r
418             }\r
419             if (!_file.Exists(csv))\r
420                 s = header + "\r\n" + s;\r
421             foreach (var f in new[] {csv, tmp})\r
422             {\r
423                 try\r
424                 {\r
425                     _file.AppendAllText(f, s + "\r\n");\r
426                     break;\r
427                 }\r
428                 catch (IOException)\r
429                 {\r
430                 }\r
431             }\r
432         }\r
433     }\r
434 }