1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
\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
7 // http://www.apache.org/licenses/LICENSE-2.0
\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
16 using System.Collections.Generic;
\r
17 using System.Globalization;
\r
21 using KancolleSniffer.Model;
\r
22 using KancolleSniffer.Util;
\r
24 namespace KancolleSniffer.Log
\r
33 CreateItem = 1 << 3,
\r
34 CreateShip = 1 << 4,
\r
35 RemodelSlot = 1 << 5,
\r
36 Achievement = 1 << 6,
\r
42 private LogType _logType;
\r
43 private readonly ShipInfo _shipInfo;
\r
44 private readonly ItemInfo _itemInfo;
\r
45 private Action<string, string, string> _writer;
\r
46 private Func<DateTime> _nowFunc;
\r
47 public const string DateTimeFormat = @"yyyy\-MM\-dd HH\:mm\:ss";
\r
48 private dynamic _basic;
\r
49 private int _kdockId;
\r
50 private DateTime _prevTime;
\r
51 private int[] _currentMaterial = new int[Enum.GetValues(typeof(Material)).Length];
\r
52 private int _materialLogInterval = 10;
\r
53 private int _lastExp = -1;
\r
54 private DateTime _lastDate;
\r
55 private DateTime _endOfMonth;
\r
56 private DateTime _nextDate;
\r
57 private readonly BattleLogger _battleLogger;
\r
59 public int MaterialLogInterval
\r
61 set => _materialLogInterval = value;
\r
64 public string OutputDir
\r
66 set => _writer = new LogWriter(value).Write;
\r
69 public static string FormatDateTime(DateTime date)
\r
71 return date.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
\r
74 public Logger(ShipInfo shipInfo, ItemInfo itemInfo, BattleInfo battleInfo)
\r
76 _shipInfo = shipInfo;
\r
77 _itemInfo = itemInfo;
\r
78 _battleLogger = new BattleLogger(itemInfo, battleInfo, WriteNow);
\r
79 _writer = new LogWriter().Write;
\r
80 _nowFunc = () => DateTime.Now;
\r
83 public void EnableLog(LogType type)
\r
88 public void SetWriter(Action<string, string, string> writer, Func<DateTime> nowFunc)
\r
94 private void WriteNow(string name, string log, string header)
\r
96 Write(name, _nowFunc(), log, header);
\r
99 private void Write(string name, DateTime time, string log, string header)
\r
101 _writer(name, FormatDateTime(time) + "," + log, header);
\r
104 public void FlashLog()
\r
106 FlashAchievementLog();
\r
109 public void InspectMapInfoMaster(dynamic json)
\r
111 _battleLogger.InspectMapInfoMaster(json);
\r
114 public void InspectMissionResult(dynamic json)
\r
116 var r = (int)json.api_clear_result;
\r
117 var resStr = r == 2 ? "大成功" : r == 1 ? "成功" : "失敗";
\r
118 var material = new int[8];
\r
120 ((int[])json.api_get_material).CopyTo(material, 0);
\r
121 foreach (var i in new[] {1, 2})
\r
123 var attr = "api_get_item" + i;
\r
124 if (!json.IsDefined(attr))
\r
126 var count = (int)json[attr].api_useitem_count;
\r
127 var flag = ((int[])json.api_useitem_flag)[i - 1];
\r
131 material[(int)Material.Bucket] = count;
\r
134 material[(int)Material.Burner + 2] = count; // 高速建造材と開発資材が反対なのでいつか直す
\r
137 material[(int)Material.Development - 2] = count;
\r
140 if ((int)json[attr].api_useitem_id == 4)
\r
141 material[(int)Material.Screw] = count;
\r
145 if ((_logType & LogType.Mission) != 0)
\r
149 resStr, json.api_quest_name, string.Join(",", material)),
\r
150 "日付,結果,遠征,燃料,弾薬,鋼材,ボーキ,開発資材,高速修復材,高速建造材,改修資材");
\r
154 public void InspectMapStart(dynamic json)
\r
156 if ((_logType & LogType.Battle) != 0)
\r
157 _battleLogger.InspectMapStart(json);
\r
158 if ((_logType & LogType.Material) != 0)
\r
159 WriteMaterialLog(_nowFunc());
\r
162 public void InspectMapNext(dynamic json)
\r
164 if ((_logType & LogType.Achievement) != 0 && json.api_get_eo_rate() && (int)json.api_get_eo_rate != 0)
\r
166 WriteNow("戦果", _lastExp + "," + (int)json.api_get_eo_rate, "日付,経験値,EO");
\r
168 if ((_logType & LogType.Battle) != 0)
\r
169 _battleLogger.InspectMapNext(json);
\r
172 public void InspectClearItemGet(dynamic json)
\r
174 if ((_logType & LogType.Achievement) == 0)
\r
176 if (!json.api_bounus())
\r
178 foreach (var entry in json.api_bounus)
\r
180 if (entry.api_type != 18)
\r
182 WriteNow("戦果", _lastExp + "," + (int)entry.api_count, "日付,経験値,EO");
\r
187 public void InspectBattleResult(dynamic result)
\r
189 if ((_logType & LogType.Achievement) != 0 && result.api_get_exmap_rate())
\r
191 var rate = result.api_get_exmap_rate is string
\r
192 ? int.Parse(result.api_get_exmap_rate)
\r
193 : (int)result.api_get_exmap_rate;
\r
196 WriteNow("戦果", _lastExp + "," + rate, "日付,経験値,EO");
\r
199 if ((_logType & LogType.Battle) != 0)
\r
200 _battleLogger.InspectBattleResult(result);
\r
203 public void InspectBasic(dynamic json)
\r
206 if ((_logType & LogType.Achievement) == 0)
\r
208 var now = _nowFunc();
\r
209 var exp = (int)json.api_experience;
\r
210 var isNewMonth = _endOfMonth == DateTime.MinValue || now.CompareTo(_endOfMonth) >= 0;
\r
211 var isNewDate = _nextDate == DateTime.MinValue || now.CompareTo(_nextDate) >= 0;
\r
212 if (isNewDate || isNewMonth)
\r
214 if (_lastDate != DateTime.MinValue)
\r
216 Write("戦果", _lastDate, _lastExp + ",0", "日付,経験値,EO");
\r
218 Write("戦果", now, exp + ",0", "日付,経験値,EO");
\r
221 _endOfMonth = new DateTime(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month),
\r
223 if (_endOfMonth.CompareTo(now) <= 0)
\r
225 var days = _endOfMonth.Month == 12
\r
226 ? DateTime.DaysInMonth(_endOfMonth.Year + 1, 1)
\r
227 : DateTime.DaysInMonth(_endOfMonth.Year, _endOfMonth.Month);
\r
228 _endOfMonth = _endOfMonth.AddDays(days);
\r
231 _nextDate = new DateTime(now.Year, now.Month, now.Day, 2, 0, 0);
\r
233 _nextDate = _nextDate.AddDays(1);
\r
234 if (_nextDate.Day == 1)
\r
235 _nextDate = _nextDate.AddDays(1);
\r
241 private void FlashAchievementLog()
\r
243 if ((_logType & LogType.Achievement) == 0)
\r
245 if (_lastDate != DateTime.MinValue)
\r
247 Write("戦果", _lastDate, _lastExp + ",0", "日付,経験値,EO");
\r
251 public void InspectCreateItem(string request, dynamic json)
\r
253 if ((_logType & LogType.CreateItem) == 0)
\r
255 var values = HttpUtility.ParseQueryString(request);
\r
256 foreach (var spec in CreateSpecList(json))
\r
259 string.Join(",", spec.Name, spec.TypeName,
\r
260 values["api_item1"], values["api_item2"], values["api_item3"], values["api_item4"],
\r
261 Secretary(), _basic.api_level),
\r
262 "日付,開発装備,種別,燃料,弾薬,鋼材,ボーキ,秘書艦,司令部Lv");
\r
266 private IEnumerable<ItemSpec> CreateSpecList(dynamic json)
\r
268 var fail = new ItemSpec
\r
273 if (json.api_get_items())
\r
275 return ((dynamic[])json.api_get_items).Select(entry =>
\r
276 (int)entry.api_slotitem_id != -1 ? _itemInfo.GetSpecByItemId((int)entry.api_slotitem_id) : fail);
\r
280 json.api_slot_item()
\r
281 ? _itemInfo.GetSpecByItemId((int)json.api_slot_item.api_slotitem_id)
\r
286 public void InspectCreateShip(string request)
\r
288 var values = HttpUtility.ParseQueryString(request);
\r
289 _kdockId = int.Parse(values["api_kdock_id"]);
\r
292 public void InspectKDock(dynamic json)
\r
294 if ((_logType & LogType.CreateShip) == 0 || _basic == null || _kdockId == 0)
\r
296 var kdock = ((dynamic[])json).First(e => e.api_id == _kdockId);
\r
297 var material = Enumerable.Range(1, 5).Select(i => (int)kdock["api_item" + i]).ToArray();
\r
298 var ship = _shipInfo.GetSpec((int)kdock.api_created_ship_id);
\r
299 var avail = ((dynamic[])json).Count(e => (int)e.api_state == 0);
\r
301 string.Join(",", material.First() >= 1500 ? "大型艦建造" : "通常艦建造",
\r
302 ship.Name, ship.ShipTypeName, string.Join(",", material), avail, Secretary(), _basic.api_level),
\r
303 "日付,種類,名前,艦種,燃料,弾薬,鋼材,ボーキ,開発資材,空きドック,秘書艦,司令部Lv");
\r
307 private string Secretary()
\r
309 var ship = _shipInfo.Fleets[0].Ships[0];
\r
310 return ship.Name + "(" + ship.Level + ")";
\r
313 public void InspectMaterial(dynamic json)
\r
315 if ((_logType & LogType.Material) == 0)
\r
317 foreach (var e in json)
\r
318 _currentMaterial[(int)e.api_id - 1] = (int)e.api_value;
\r
319 var now = _nowFunc();
\r
320 if (now - _prevTime < TimeSpan.FromMinutes(_materialLogInterval))
\r
322 WriteMaterialLog(now);
\r
325 private void WriteMaterialLog(DateTime now)
\r
329 string.Join(",", _currentMaterial),
\r
330 "日付,燃料,弾薬,鋼材,ボーキ,高速建造材,高速修復材,開発資材,改修資材");
\r
333 public void SetCurrentMaterial(int[] material)
\r
335 _currentMaterial = material;
\r
338 public void InspectRemodelSlot(string request, dynamic json)
\r
340 if ((_logType & LogType.RemodelSlot) == 0)
\r
342 var values = HttpUtility.ParseQueryString(request);
\r
343 var id = int.Parse(values["api_slot_id"]);
\r
344 var name = _itemInfo.GetName(id);
\r
345 var level = _itemInfo.GetStatus(id).Level;
\r
346 var success = (int)json.api_remodel_flag == 1 ? "○" : "×";
\r
347 var certain = int.Parse(values["api_certain_flag"]) == 1 ? "○" : "";
\r
350 if (json.api_use_slot_id())
\r
352 var use = (int[])json.api_use_slot_id;
\r
353 useName = _itemInfo.GetName(use[0]);
\r
354 useNum = use.Length.ToString();
\r
356 var after = (int[])json.api_after_material;
\r
357 var diff = new int[after.Length];
\r
358 for (var i = 0; i < after.Length; i++)
\r
359 diff[i] = _currentMaterial[i] - after[i];
\r
360 var ship1 = Secretary();
\r
362 var ships = _shipInfo.Fleets[0].Ships;
\r
363 if (!ships[1].Empty)
\r
364 ship2 = ships[1].Name + "(" + ships[1].Level + ")";
\r
366 string.Join(",", name, level, success, certain, useName, useNum,
\r
367 diff[(int)Material.Fuel], diff[(int)Material.Bullet], diff[(int)Material.Steal],
\r
368 diff[(int)Material.Bauxite],
\r
369 diff[(int)Material.Development], diff[(int)Material.Screw],
\r
371 "日付,改修装備,レベル,成功,確実化,消費装備,消費数,燃料,弾薬,鋼材,ボーキ,開発資材,改修資材,秘書艦,二番艦");
\r
375 public class LogWriter
\r
377 private readonly IFile _file;
\r
378 private readonly string _outputDir;
\r
380 public interface IFile
\r
382 string ReadAllText(string path);
\r
383 void AppendAllText(string path, string text);
\r
384 void Delete(string path);
\r
385 bool Exists(string path);
\r
388 private class FileWrapper : IFile
\r
390 // Shift_JISでないとExcelで文字化けする
\r
391 private readonly Encoding _encoding = Encoding.GetEncoding("Shift_JIS");
\r
393 public string ReadAllText(string path) => File.ReadAllText(path, _encoding);
\r
395 public void AppendAllText(string path, string text)
\r
397 File.AppendAllText(path, text, _encoding);
\r
400 public void Delete(string path)
\r
405 public bool Exists(string path) => File.Exists(path);
\r
408 public LogWriter(string outputDir = null, IFile file = null)
\r
410 _outputDir = outputDir ?? AppDomain.CurrentDomain.BaseDirectory;
\r
411 _file = file ?? new FileWrapper();
\r
414 public void Write(string file, string s, string header)
\r
416 var path = Path.Combine(_outputDir, file);
\r
417 var csv = path + ".csv";
\r
418 var tmp = path + ".tmp";
\r
419 if (_file.Exists(tmp))
\r
423 _file.AppendAllText(csv, _file.ReadAllText(tmp));
\r
426 catch (IOException)
\r
430 if (!_file.Exists(csv))
\r
431 s = header + "\r\n" + s;
\r
432 foreach (var f in new[] {csv, tmp})
\r
436 _file.AppendAllText(f, s + "\r\n");
\r
439 catch (IOException e)
\r
442 throw new LogIOException("報告書の出力中にエラーが発生しました。", e);
\r
448 public class LogIOException : Exception
\r
450 public LogIOException(string message, Exception inner) : base(message, inner)
\r