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
22 namespace KancolleSniffer
\r
31 CreateItem = 1 << 3,
\r
32 CreateShip = 1 << 4,
\r
33 RemodelSlot = 1 << 5,
\r
34 Achivement = 1 << 6,
\r
40 private LogType _logType;
\r
41 private readonly ShipInfo _shipInfo;
\r
42 private readonly ItemInfo _itemInfo;
\r
43 private readonly BattleInfo _battleInfo;
\r
44 private Action<string, string, string> _writer;
\r
45 private Func<DateTime> _nowFunc;
\r
46 public const string DateTimeFormat = @"yyyy\-MM\-dd HH\:mm\:ss";
\r
47 private dynamic _battle;
\r
48 private dynamic _map;
\r
49 private dynamic _basic;
\r
50 private int _kdockId;
\r
51 private DateTime _prevTime;
\r
52 private int[] _currentMaterial = new int[Enum.GetValues(typeof(Material)).Length];
\r
53 private int _materialLogInterval = 10;
\r
54 private bool _start;
\r
55 private int _lastExp = -1;
\r
56 private DateTime _lastDate;
\r
57 private DateTime _endOfMonth;
\r
58 private DateTime _nextDate;
\r
60 public int MaterialLogInterval
\r
62 set => _materialLogInterval = value;
\r
65 public string OutputDir
\r
67 set => _writer = new LogWriter(value).Write;
\r
70 public static string FormatDateTime(DateTime date)
\r
72 return date.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
\r
75 public Logger(ShipInfo ship, ItemInfo item, BattleInfo battle)
\r
79 _battleInfo = battle;
\r
80 _writer = new LogWriter().Write;
\r
81 _nowFunc = () => DateTime.Now;
\r
84 public void EnableLog(LogType type)
\r
89 public void SetWriter(Action<string, string, string> writer, Func<DateTime> nowFunc)
\r
95 public void FlashLog()
\r
97 FlashAchivementLog();
\r
100 public void InspectMissionResult(dynamic json)
\r
102 var r = (int)json.api_clear_result;
\r
103 var rstr = r == 2 ? "大成功" : r == 1 ? "成功" : "失敗";
\r
104 var material = new int[7];
\r
106 ((int[])json.api_get_material).CopyTo(material, 0);
\r
107 foreach (var i in new[] {1, 2})
\r
109 var attr = "api_get_item" + i;
\r
110 if (!json.IsDefined(attr) || json[attr].api_useitem_id != -1)
\r
112 var count = (int)json[attr].api_useitem_count;
\r
113 var flag = ((int[])json.api_useitem_flag)[i - 1];
\r
115 material[(int)Material.Bucket] = count;
\r
116 else if (flag == 2)
\r
117 material[(int)Material.Burner + 2] = count; // 高速建造材と開発資材が反対なのでいつか直す
\r
118 else if (flag == 3)
\r
119 material[(int)Material.Development - 2] = count;
\r
121 if ((_logType & LogType.Mission) != 0)
\r
124 string.Join(",", FormatDateTime(_nowFunc()),
\r
125 rstr, json.api_quest_name, string.Join(",", material)),
\r
126 "日付,結果,遠征,燃料,弾薬,鋼材,ボーキ,開発資材,高速修復材,高速建造材");
\r
130 public void InspectMapStart(dynamic json)
\r
135 if ((_logType & LogType.Material) != 0)
\r
136 WriteMaterialLog(_nowFunc());
\r
139 public void InspectMapNext(dynamic json)
\r
141 if ((_logType & LogType.Achivement) != 0 && json.api_get_eo_rate() && (int)json.api_get_eo_rate != 0)
\r
144 FormatDateTime(_nowFunc()) + "," + _lastExp + "," + (int)json.api_get_eo_rate,
\r
150 public void InspectClearItemGet(dynamic json)
\r
152 if ((_logType & LogType.Achivement) == 0)
\r
154 if (!json.api_bounus())
\r
156 foreach (var entry in json.api_bounus)
\r
158 if (entry.api_type != 18)
\r
161 FormatDateTime(_nowFunc()) + "," + _lastExp + "," + (int)entry.api_count,
\r
167 public void InspectBattle(dynamic json)
\r
169 if (_battle != null) // 通常の夜戦は無視する
\r
174 public void InspectBattleResult(dynamic result)
\r
176 if ((_logType & LogType.Achivement) != 0 && result.api_get_exmap_rate())
\r
178 var rate = result.api_get_exmap_rate is string
\r
179 ? int.Parse(result.api_get_exmap_rate)
\r
180 : (int)result.api_get_exmap_rate;
\r
183 _writer("戦果", FormatDateTime(_nowFunc()) + "," + _lastExp + "," + rate,
\r
187 if ((_logType & LogType.Battle) == 0 || _map == null || _battle == null)
\r
189 _map = _battle = null;
\r
192 var fships = GenerateFirendShipList();
\r
193 var eships = GenerateEnemyShipList();
\r
194 var cell = (int)_map.api_no;
\r
198 if (cell == (int)_map.api_bosscell_no || (int)_map.api_event_id == 5)
\r
199 boss = _start ? "出撃&ボス" : "ボス";
\r
200 var dropType = result.api_get_ship() ? result.api_get_ship.api_ship_type : "";
\r
201 if (result.api_get_useitem())
\r
203 if (dropType == "")
\r
206 dropType += "+アイテム";
\r
208 var dropName = result.api_get_ship() ? result.api_get_ship.api_ship_name : "";
\r
209 if (result.api_get_useitem())
\r
211 var itemName = _itemInfo.GetUseItemName((int)result.api_get_useitem.api_useitem_id);
\r
212 if (dropName == "")
\r
213 dropName = itemName;
\r
215 dropName += "+" + itemName;
\r
217 var fp = _shipInfo.GetFighterPower(BattleInfo.DeckId(_battle));
\r
218 var fpower = fp[0] == fp[1] ? fp[0].ToString() : fp[0] + "~" + fp[1];
\r
219 _writer("海戦・ドロップ報告書", string.Join(",", FormatDateTime(_nowFunc()),
\r
220 result.api_quest_name,
\r
222 result.api_win_rank,
\r
223 BattleFormationName((int)_battle.api_formation[2]),
\r
224 FormationName(_battle.api_formation[0]),
\r
225 FormationName(_battle.api_formation[1]),
\r
226 result.api_enemy_info.api_deck_name,
\r
227 dropType, dropName,
\r
228 string.Join(",", fships),
\r
229 string.Join(",", eships),
\r
230 fpower, _battleInfo.EnemyFighterPower.AirCombat + _battleInfo.EnemyFighterPower.UnknownMark,
\r
231 AirControlLevelName(_battle)),
\r
232 "日付,海域,マス,ボス,ランク,艦隊行動,味方陣形,敵陣形,敵艦隊,ドロップ艦種,ドロップ艦娘," +
\r
233 "味方艦1,味方艦1HP,味方艦2,味方艦2HP,味方艦3,味方艦3HP,味方艦4,味方艦4HP,味方艦5,味方艦5HP,味方艦6,味方艦6HP," +
\r
234 "敵艦1,敵艦1HP,敵艦2,敵艦2HP,敵艦3,敵艦3HP,敵艦4,敵艦4HP,敵艦5,敵艦5HP,敵艦6,敵艦6HP," +
\r
237 _map = _battle = null;
\r
241 private IEnumerable<string> GenerateFirendShipList()
\r
243 int deckId = BattleInfo.DeckId(_battle);
\r
244 if (_battle.api_f_nowhps_combined())
\r
246 var main = _shipInfo.GetDeck(0);
\r
247 var guard = _shipInfo.GetDeck(1);
\r
248 return main.Zip(guard, (m, g) =>
\r
250 if (m == -1 && g == -1)
\r
256 var sm = _shipInfo.GetStatus(m);
\r
257 name = $"{sm.Name}(Lv{sm.Level})";
\r
258 hp = $"{sm.NowHp}/{sm.MaxHp}";
\r
264 var sg = _shipInfo.GetStatus(g);
\r
265 name += $"{sg.Name}(Lv{sg.Level})";
\r
266 hp += $"{sg.NowHp}/{sg.MaxHp}";
\r
268 return name + "," + hp;
\r
271 var deck = _shipInfo.GetDeck(deckId);
\r
272 if (deck.Length > 6)
\r
274 var result = new List<string>();
\r
275 for (var i = 0; i < 12 - deck.Length; i++)
\r
277 var s = _shipInfo.GetStatus(deck[i]);
\r
278 result.Add($"{s.Name}(Lv{s.Level}),{s.NowHp}/{s.MaxHp}");
\r
280 for (var i = 0; i < deck.Length - 6; i++)
\r
282 var s1 = _shipInfo.GetStatus(deck[12 - deck.Length + i]);
\r
283 var s2 = _shipInfo.GetStatus(deck[6 + i]);
\r
285 $"{s1.Name}(Lv{s1.Level})・{s2.Name}(Lv{s2.Level})," +
\r
286 $"{s1.NowHp}/{s1.MaxHp}・{s2.NowHp}/{s2.MaxHp}");
\r
290 return deck.Select(id =>
\r
294 var s = _shipInfo.GetStatus(id);
\r
295 return $"{s.Name}(Lv{s.Level}),{s.NowHp}/{s.MaxHp}";
\r
299 private IEnumerable<string> GenerateEnemyShipList()
\r
301 var result = _battleInfo.Result.Enemy.Main.Concat(Enumerable.Repeat(new ShipStatus(), 6)).Take(6);
\r
302 if (_battleInfo.Result.Enemy.Guard.Length == 0)
\r
304 return result.Select(s => s.Id == -1 ? "," : $"{s.Name},{s.NowHp}/{s.MaxHp}").ToList();
\r
307 var guard = _battleInfo.Result.Enemy.Guard.Concat(Enumerable.Repeat(new ShipStatus(), 6)).Take(6);
\r
308 return main.Zip(guard, (m, g) =>
\r
310 if (m.Id == -1 && g.Id == -1)
\r
316 name = $"{m.Name}";
\r
317 hp = $"{m.NowHp}/{m.MaxHp}";
\r
323 name += $"{g.Name}";
\r
324 hp += $"{g.NowHp}/{g.MaxHp}";
\r
326 return name + "," + hp;
\r
330 private string FormationName(dynamic f)
\r
332 if (f is string) // 連合艦隊のときは文字列
\r
361 private static string BattleFormationName(int f)
\r
378 private string AirControlLevelName(dynamic json)
\r
380 // BattleInfo.AirControlLevelは夜戦で消されているかもしれないので、こちらで改めて調べる。
\r
381 if (!json.api_kouku())
\r
383 var stage1 = json.api_kouku.api_stage1;
\r
384 if (stage1 == null)
\r
386 if (stage1.api_f_count == 0 && stage1.api_e_count == 0)
\r
388 switch ((int)stage1.api_disp_seiku)
\r
405 public void InspectBasic(dynamic json)
\r
408 if ((_logType & LogType.Achivement) == 0)
\r
410 var now = _nowFunc();
\r
411 var exp = (int)json.api_experience;
\r
412 var isNewMonth = _endOfMonth == DateTime.MinValue || now.CompareTo(_endOfMonth) >= 0;
\r
413 var isNewDate = _nextDate == DateTime.MinValue || now.CompareTo(_nextDate) >= 0;
\r
414 if (isNewDate || isNewMonth)
\r
416 if (_lastDate != DateTime.MinValue)
\r
418 _writer("戦果", FormatDateTime(_lastDate) + "," + _lastExp + ",0", "日付,経験値,EO");
\r
420 _writer("戦果", FormatDateTime(now) + "," + exp + ",0", "日付,経験値,EO");
\r
423 _endOfMonth = new DateTime(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month),
\r
425 if (_endOfMonth.CompareTo(now) <= 0)
\r
427 var days = _endOfMonth.Month == 12
\r
428 ? DateTime.DaysInMonth(_endOfMonth.Year + 1, 1)
\r
429 : DateTime.DaysInMonth(_endOfMonth.Year, _endOfMonth.Month);
\r
430 _endOfMonth = _endOfMonth.AddDays(days);
\r
433 _nextDate = new DateTime(now.Year, now.Month, now.Day, 2, 0, 0);
\r
435 _nextDate = _nextDate.AddDays(1);
\r
436 if (_nextDate.Day == 1)
\r
437 _nextDate = _nextDate.AddDays(1);
\r
443 private void FlashAchivementLog()
\r
445 if ((_logType & LogType.Achivement) == 0)
\r
447 if (_lastDate != DateTime.MinValue)
\r
449 _writer("戦果", FormatDateTime(_lastDate) + "," + _lastExp + ",0", "日付,経験値,EO");
\r
453 public void InspectCreateItem(string request, dynamic json)
\r
455 if ((_logType & LogType.CreateItem) == 0)
\r
457 var values = HttpUtility.ParseQueryString(request);
\r
460 if (json.api_slot_item())
\r
462 var spec = _itemInfo.GetSpecByItemId((int)json.api_slot_item.api_slotitem_id);
\r
464 type = spec.TypeName;
\r
467 FormatDateTime(_nowFunc()) + "," +
\r
468 string.Join(",", name, type,
\r
469 values["api_item1"], values["api_item2"], values["api_item3"], values["api_item4"],
\r
470 Secretary(), _basic.api_level),
\r
471 "日付,開発装備,種別,燃料,弾薬,鋼材,ボーキ,秘書艦,司令部Lv");
\r
474 public void InspectCreateShip(string request)
\r
476 var values = HttpUtility.ParseQueryString(request);
\r
477 _kdockId = int.Parse(values["api_kdock_id"]);
\r
480 public void InspectKDock(dynamic json)
\r
482 if ((_logType & LogType.CreateShip) == 0 || _basic == null || _kdockId == 0)
\r
484 var kdock = ((dynamic[])json).First(e => e.api_id == _kdockId);
\r
485 var material = Enumerable.Range(1, 5).Select(i => (int)kdock["api_item" + i]).ToArray();
\r
486 var ship = _shipInfo.GetSpec((int)kdock.api_created_ship_id);
\r
487 var avail = ((dynamic[])json).Count(e => (int)e.api_state == 0);
\r
489 FormatDateTime(_nowFunc()) + "," +
\r
490 string.Join(",", material.First() >= 1500 ? "大型艦建造" : "通常艦建造",
\r
491 ship.Name, ship.ShipTypeName, string.Join(",", material), avail, Secretary(), _basic.api_level),
\r
492 "日付,種類,名前,艦種,燃料,弾薬,鋼材,ボーキ,開発資材,空きドック,秘書艦,司令部Lv");
\r
496 private string Secretary()
\r
498 var ship = _shipInfo.GetShipStatuses(0)[0];
\r
499 return ship.Name + "(" + ship.Level + ")";
\r
502 public void InspectMaterial(dynamic json)
\r
504 if ((_logType & LogType.Material) == 0)
\r
506 foreach (var e in json)
\r
507 _currentMaterial[(int)e.api_id - 1] = (int)e.api_value;
\r
508 var now = _nowFunc();
\r
509 if (now - _prevTime < TimeSpan.FromMinutes(_materialLogInterval))
\r
511 WriteMaterialLog(now);
\r
514 public void WriteMaterialLog(DateTime now)
\r
518 FormatDateTime(now) + "," +
\r
519 string.Join(",", _currentMaterial),
\r
520 "日付,燃料,弾薬,鋼材,ボーキ,高速建造材,高速修復材,開発資材,改修資材");
\r
523 public void SetCurrentMaterial(int[] material)
\r
525 _currentMaterial = material;
\r
528 public void InspectRemodelSlot(string request, dynamic json)
\r
530 if ((_logType & LogType.RemodelSlot) == 0)
\r
532 var now = _nowFunc();
\r
533 var values = HttpUtility.ParseQueryString(request);
\r
534 var id = int.Parse(values["api_slot_id"]);
\r
535 var name = _itemInfo.GetName(id);
\r
536 var level = _itemInfo.GetStatus(id).Level;
\r
537 var success = (int)json.api_remodel_flag == 1 ? "○" : "×";
\r
538 var certain = int.Parse(values["api_certain_flag"]) == 1 ? "○" : "";
\r
541 if (json.api_use_slot_id())
\r
543 var use = (int[])json.api_use_slot_id;
\r
544 useName = _itemInfo.GetName(use[0]);
\r
545 useNum = use.Length.ToString();
\r
547 var after = (int[])json.api_after_material;
\r
548 var diff = new int[after.Length];
\r
549 for (var i = 0; i < after.Length; i++)
\r
550 diff[i] = _currentMaterial[i] - after[i];
\r
551 var ship1 = Secretary();
\r
553 var ships = _shipInfo.GetShipStatuses(0);
\r
554 if (ships.Length >= 2)
\r
555 ship2 = ships[1].Name + "(" + ships[1].Level + ")";
\r
557 FormatDateTime(now) + "," +
\r
558 string.Join(",", name, level, success, certain, useName, useNum,
\r
559 diff[(int)Material.Fuel], diff[(int)Material.Bullet], diff[(int)Material.Steal],
\r
560 diff[(int)Material.Bouxite],
\r
561 diff[(int)Material.Development], diff[(int)Material.Screw],
\r
563 "日付,改修装備,レベル,成功,確実化,消費装備,消費数,燃料,弾薬,鋼材,ボーキ,開発資材,改修資材,秘書艦,二番艦");
\r
567 public class LogWriter
\r
569 private readonly IFile _file;
\r
570 private readonly string _outputDir;
\r
572 public interface IFile
\r
574 string ReadAllText(string path);
\r
575 void AppendAllText(string path, string text);
\r
576 void Delete(string path);
\r
577 bool Exists(string path);
\r
580 private class FileWrapper : IFile
\r
582 // Shift_JISでないとExcelで文字化けする
\r
583 private readonly Encoding _encoding = Encoding.GetEncoding("Shift_JIS");
\r
585 public string ReadAllText(string path) => File.ReadAllText(path, _encoding);
\r
587 public void AppendAllText(string path, string text)
\r
589 File.AppendAllText(path, text, _encoding);
\r
592 public void Delete(string path)
\r
597 public bool Exists(string path) => File.Exists(path);
\r
600 public LogWriter(string outputDir = null, IFile file = null)
\r
602 _outputDir = outputDir ?? AppDomain.CurrentDomain.BaseDirectory;
\r
603 _file = file ?? new FileWrapper();
\r
606 public void Write(string file, string s, string header)
\r
608 var path = Path.Combine(_outputDir, file);
\r
609 var csv = path + ".csv";
\r
610 var tmp = path + ".tmp";
\r
611 if (_file.Exists(tmp))
\r
615 _file.AppendAllText(csv, _file.ReadAllText(tmp));
\r
618 catch (IOException)
\r
622 if (!_file.Exists(csv))
\r
623 s = header + "\r\n" + s;
\r
624 foreach (var f in new[] {csv, tmp})
\r
628 _file.AppendAllText(f, s + "\r\n");
\r
631 catch (IOException e)
\r
634 throw new LogIOException("報告書の出力中にエラーが発生しました。", e);
\r
640 public class LogIOException : Exception
\r
642 public LogIOException()
\r
646 public LogIOException(string message) : base(message)
\r
650 public LogIOException(string message, Exception inner) : base(message, inner)
\r