/** * */ package logbook.internal; import java.util.Date; /** * 疲労回復時刻計算 * @author Nekopanda */ public class CondTiming { private static long COND_CYCLE = 3 * 60 * 1000; private static long MAX_ERROR = 2 * 1000; // ±2秒までの誤差を許容する private boolean ignoreNextPort = false; private TimeSpan updateTiming = null; private Date from = new Date(); public static class TimeSpan { private long offset; private long mills; public TimeSpan() { // } public TimeSpan(Date from, long mills) { this.offset = from.getTime() % COND_CYCLE; this.mills = mills; } public void intersection(Date ofrom, long omills) { if (omills >= COND_CYCLE) { return; } long from1 = this.offset; long to1 = this.offset + this.mills; long to2 = (((ofrom.getTime() + omills) - this.offset) % COND_CYCLE) + this.offset; long from2 = to2 - omills; long from = Math.max(from1, from2); long to = Math.min(to1, to2); if (from < to) { // 重なりがあるのでその部分で更新 this.offset = from; this.mills = to - from; } else { // 重なりがないので新しいので更新 // 通信ラグなどの誤差を考慮して十分起こりうる精度の良くないデータは無視する long diff = Math.min(from2 - to1, (this.offset + COND_CYCLE) - to2); if ((diff > MAX_ERROR) || (omills <= this.mills)) { this.offset = from2; this.mills = omills; } } } // 次の回復までの残り時間 public long getNext(Date from) { long mid = this.offset + (this.mills / 2); return COND_CYCLE - ((from.getTime() - mid) % COND_CYCLE); } /** * @return mills */ public long getMills() { return this.mills; } /** * @return offset */ public long getOffset() { return this.offset; } /** * @param offset セットする offset */ public void setOffset(long offset) { this.offset = offset; } /** * @param mills セットする mills */ public void setMills(long mills) { this.mills = mills; } } /** * 疲労が回復する時刻を計算 * @param latestCond 最新データの疲労度 * @param targetCond 目標疲労度 * @return 回復する時刻 */ public Date calcCondClearTime(int latestCond, int targetCond) { return this.calcCondClearTime(this.from, latestCond, targetCond); } /** * 疲労が回復する時刻を計算 * @param latestTime 最新データの時間 * @param latestCond 最新データの疲労度 * @param targetCond 目標疲労度 * @return 回復する時刻 */ public Date calcCondClearTime(Date latestTime, int latestCond, int targetCond) { if (latestCond >= targetCond) { return null; } int requiredCycles = (int) Math.ceil((targetCond - latestCond) / 3.0); if (this.updateTiming == null) { return new Date((latestTime.getTime() + (requiredCycles * COND_CYCLE)) - (COND_CYCLE / 2)); } long next = this.updateTiming.getNext(latestTime); return new Date(latestTime.getTime() + next + ((requiredCycles - 1) * COND_CYCLE)); } /** * 疲労が回復する時刻を計算(お風呂から上がる時間も考慮) * @param ndockComplete お風呂から上がる時間 * @param latestCond 最新データの疲労度 * @param targetCond 目標疲労度 * @return 回復する時刻 */ public Date calcCondClearTime2(Date ndockComplete, int latestCond, int targetCond) { Date t1 = this.calcCondClearTime(latestCond, targetCond); if (ndockComplete == null) { // 入渠中でない場合はそのまま return t1; } // お風呂から上がってから回復する時間を計算 Date t2 = this.calcCondClearTime(ndockComplete, 40, targetCond); if (t2 == null) { // targetCondが40以下の場合はお風呂から上あがる時間でOK t2 = ndockComplete; } // 早い方を返す return t1.before(t2) ? t1 : t2; } /** * 何回疲労回復タイミングが過ぎたか計算 * @return 経過した疲労回復回数 */ public int calcPastCycles() { long pastMills = new Date().getTime() - this.from.getTime(); if (this.updateTiming == null) { return (int) ((pastMills + (COND_CYCLE / 2)) / COND_CYCLE); } long next = this.updateTiming.getNext(this.from); if (pastMills < next) { return 0; } return (int) ((pastMills - next) / COND_CYCLE) + 1; } /** * 次の疲労回復タイミング * @param time * @return 不明な場合はnull */ public Date getNextUpdateTime(Date time) { if (this.updateTiming == null) { return null; } long next = this.updateTiming.getNext(time); return new Date(time.getTime() + next); } /** * in milliseconds * @return */ public long getCurrentAccuracy() { if (this.updateTiming == null) { return COND_CYCLE; } return this.updateTiming.getMills(); } public void ignoreNext() { this.ignoreNextPort = true; } public void onPort(boolean updated) { if ((updated == false) || this.ignoreNextPort) { this.ignoreNextPort = false; } else { // 区間更新 long mills = new Date().getTime() - this.from.getTime(); if (this.updateTiming == null) { this.updateTiming = new TimeSpan(this.from, mills); } else { this.updateTiming.intersection(this.from, mills); } } // 起点を更新 this.from = new Date(); } /** * @return updateTiming */ public TimeSpan getUpdateTiming() { return this.updateTiming; } /** * @param updateTiming セットする updateTiming */ public void setUpdateTiming(TimeSpan updateTiming) { this.updateTiming = updateTiming; } }