/// <summary> /// 構築 /// </summary> /// <param name="kind">結果種別</param> /// <param name="pitchingBallData">投球データ</param> /// <param name="battedBoll">打球</param> public PitchingBollResult(PitchingBollResultKind kind, PitchingBallData pitchingBallData, BattedBoll battedBoll) { PitchingBollResultKind = kind; PitchingBallData = pitchingBallData; BattedBoll = battedBoll; // 打球はnullで呼び出されることがあるため、nullの場合はインスタンスのみ作成しておく(NullException回避) if (BattedBoll == null) { BattedBoll = new BattedBoll(); } }
/// <summary> /// 守備動作を再開する /// </summary> /// <param name="battedBoll">打球データ</param> private void ContinueDefense(BattedBoll battedBoll) { if (GameData.DefenseTermKind == DefenseTermKind.ChasingBoll) { // 打球が生きている場合 // 打球と野手の移動 MoveBattedBollAndDefense(battedBoll); // ベースカバーへ移動 MoveForBaseCover(); } else if (GameData.DefenseTermKind == DefenseTermKind.KeepingBoll) { // すでに捕球している場合 // ベースカバーへ移動 MoveForBaseCover(); // 送球準備完了か // (ファウルの場合は送球以降の動作を行わない) if (FinishedKeepingBoll() && !IsFoul) { // 送球先の決定(合わせて回転の要否も取得) bool needTurnForThrow; bool decided = DecideThrowBase(out needTurnForThrow); // 送球先を決定済み if (decided) { if (needTurnForThrow) { // 回転の必要があれば回転開始 GameData.DefenseTermKind = DefenseTermKind.TurningforThrow; } else { // 回転の必要がない場合はそのまま送球開始 ExecuteThrow(); } } } } else if (GameData.DefenseTermKind == DefenseTermKind.TurningforThrow) { // 送球のための回転中の場合 // ベースカバーへ移動 MoveForBaseCover(); // 送球のための回転完了か // (ファウルの場合は送球以降の動作を行わない) if (FinishedTurningForThrow() && !IsFoul) { // 送球先は決定済みのため、そのまま送球する ExecuteThrow(); } } else if (GameData.DefenseTermKind == DefenseTermKind.Throwing) { // 送球中の場合 // ベースカバーへ移動 MoveForBaseCover(); // 送球中のボール位置更新 MoveThrowBoll(); } else if (GameData.DefenseTermKind == DefenseTermKind.ReadyingTouch) { // ベースカバーへ移動(他のベースへの移動が未完了の場合の備え) MoveForBaseCover(); // タッチアウト体制への準備中 ReadyTouchOut(); } else if (GameData.DefenseTermKind == DefenseTermKind.WaitingRunner) { // 走者へのタッチ待ち // (ランナーがベースに到達時点でアウトになるため、ここでは何もしない) // ベースカバーへ移動(他のベースへの移動が未完了の場合の備え) MoveForBaseCover(); } else if (GameData.DefenseTermKind == DefenseTermKind.Finished) { // 完了 } else { throw new Exception("Invalid DefenseTermKind"); } }
/// <summary> /// 打球を追う際の野手の連携方法の設定 /// </summary> /// <param name="battedBoll">打球データ</param> private void SetDefenseCombination(BattedBoll battedBoll) { // 盗塁モードの場合は別処理 if (IsStealMode) { // ベースカバーの設定のみ実施する SetBaseCoverPosition(battedBoll); return; } // 打球予測で経過時間+1としている理由は // ターンの最後に時間経過しているため、+1しないと実際の移動とずれるため // フライとして捕球できるか判定 CanCatchFly = false; if (battedBoll.DropTime > 0) { bool catched = false; bool fenseReflect; bool fenseOver; MPoint fitureBollPoint = GetFitureBollPoint(battedBoll, battedBoll.DropTime + 1, out fenseReflect, out fenseOver); DropPoint = fitureBollPoint; // 一番早く捕球できる野手を特定するために、1つずつ時間経過させる for (int second = Constants.MinDefenseCatchSecond; second <= battedBoll.DropTime; second++) { // N秒後に各メンバが捕球できるか判定 foreach (DefensePosition defensePosition in DefensePositionHelper.GetEnums) { catched = CatchedBoll(GameData.GetDefenseMemberAbility(defensePosition), fitureBollPoint, second + 1); // 捕球出来る場合は、その守備メンバとボール位置を記憶 if (catched) { CatchPosition = defensePosition; CatchPoint = fitureBollPoint; CatchTime = battedBoll.DropTime; if (!fenseOver) { // ホームランは捕球できないので、ホームランでないときのみ以下を設定 CanCatchFly = true; MoveDropPointDeltaTime = battedBoll.DropTime - second; } break; } } if (catched) { break; } } } // フライとして捕球できない場合 if (!CanCatchFly) { // ゴロに対し、捕球位置を設定する SetCatchPointForGroundBoll(battedBoll, Constants.MinDefenseCatchSecond); } // ベースカバーに入るメンバの設定 SetBaseCoverPosition(battedBoll); }
/// <summary> /// ゴロの打球に対して、捕球予定位置を設定する /// </summary> /// <param name="battedBoll">打球</param> /// <param name="minCatchSecond">捕球開始できるようになるための最小時間</param> private void SetCatchPointForGroundBoll(BattedBoll battedBoll, int minCatchSecond) { for (int second = minCatchSecond; second < int.MaxValue; second++) { // フライとして捕球できないため、フライが落下するまでは判定不要 if (GameData.RemainingDropTime - second >= 0) { continue; } // N秒後の打球位置を取得 bool catched = false; bool fenseReflect; bool fenseOver; MPoint fitureBollPoint = GetFitureBollPoint(battedBoll, second + 1, out fenseReflect, out fenseOver); // N秒後に各メンバが捕球できるか判定 foreach (DefensePosition defensePosition in DefensePositionHelper.GetEnums) { // 捕球できるか判定 catched = CatchedBoll(GameData.GetDefenseMemberAbility(defensePosition), fitureBollPoint, second); // 捕球出来る場合は、その守備メンバとボール位置を記憶 if (catched) { CatchPosition = defensePosition; CatchPoint = fitureBollPoint; CatchTime = second + GameData.DefenseTermPassedSecond; break; } } if (catched) { break; } } }
/// <summary> /// 打撃結果の設定(本塁打) /// </summary> /// <param name="battedBoll"></param> private void SetBattingResultByHomerun(BattedBoll battedBoll) { if (battedBoll.SideDirection >= 15) { GameData.LatestBattingResult = BattingResultKind.RightHomerun; } else if (battedBoll.SideDirection >= -15) { GameData.LatestBattingResult = BattingResultKind.CenterHomerun; } else { GameData.LatestBattingResult = BattingResultKind.LeftHomerun; } }
/// <summary> /// ベースカバーに入るメンバの設定 /// </summary> /// <param name="battedBoll"></param> private void SetBaseCoverPosition(BattedBoll battedBoll) { // 捕球する野手を起点に、それ以外のメンバがベースカバーに入る switch (CatchPosition) { case DefensePosition.Pitcher: case DefensePosition.Center: SetBaseCoverPosition(1, DefensePosition.First); SetBaseCoverPosition(3, DefensePosition.Third); SetBaseCoverPosition(4, DefensePosition.Catcher); // 二塁は打球から遠い方がカバーする if (battedBoll.SideDirection > 0) { SetBaseCoverPosition(2, DefensePosition.Short); } else { SetBaseCoverPosition(2, DefensePosition.Second); } break; case DefensePosition.Catcher: SetBaseCoverPosition(1, DefensePosition.First); SetBaseCoverPosition(3, DefensePosition.Third); SetBaseCoverPosition(4, DefensePosition.Pitcher); // 二塁は打球から遠い方がカバーする if (battedBoll.SideDirection > 0) { SetBaseCoverPosition(2, DefensePosition.Short); } else { SetBaseCoverPosition(2, DefensePosition.Second); } break; case DefensePosition.First: if (GameData.BuntShift == false) { SetBaseCoverPosition(2, DefensePosition.Short); SetBaseCoverPosition(3, DefensePosition.Third); SetBaseCoverPosition(4, DefensePosition.Catcher); // 一塁は、深い打球なら投手、浅い打球なら二塁手がカバーする if (CatchPoint.Y > Constants.PointPitcherMound.Y) { SetBaseCoverPosition(1, DefensePosition.Pitcher); } else { SetBaseCoverPosition(1, DefensePosition.Second); } } else { // バントシフト(二塁手が一塁に近いので常に一塁は二塁手がカバー) SetBaseCoverPosition(1, DefensePosition.Second); SetBaseCoverPosition(2, DefensePosition.Short); SetBaseCoverPosition(3, DefensePosition.Third); SetBaseCoverPosition(4, DefensePosition.Catcher); } break; case DefensePosition.Second: SetBaseCoverPosition(1, DefensePosition.First); SetBaseCoverPosition(2, DefensePosition.Short); SetBaseCoverPosition(3, DefensePosition.Third); SetBaseCoverPosition(4, DefensePosition.Catcher); break; case DefensePosition.Third: if (GameData.BuntShift == false) { SetBaseCoverPosition(1, DefensePosition.First); SetBaseCoverPosition(2, DefensePosition.Second); SetBaseCoverPosition(4, DefensePosition.Catcher); // 三塁は、深い打球なら投手、浅い打球なら遊撃手がカバーする if (CatchPoint.Y > Constants.PointPitcherMound.Y) { SetBaseCoverPosition(3, DefensePosition.Pitcher); } else { SetBaseCoverPosition(3, DefensePosition.Short); } } else { // バントシフト(遊撃手が三塁に近いので常に三塁は遊撃手がカバー) SetBaseCoverPosition(1, DefensePosition.First); SetBaseCoverPosition(2, DefensePosition.Second); SetBaseCoverPosition(3, DefensePosition.Short); SetBaseCoverPosition(4, DefensePosition.Catcher); } break; case DefensePosition.Short: SetBaseCoverPosition(1, DefensePosition.First); SetBaseCoverPosition(2, DefensePosition.Second); SetBaseCoverPosition(3, DefensePosition.Third); SetBaseCoverPosition(4, DefensePosition.Catcher); break; case DefensePosition.Right: // 一塁手も打球を追いかけるように一塁は投手がカバーする SetBaseCoverPosition(1, DefensePosition.Pitcher); SetBaseCoverPosition(2, DefensePosition.Short); SetBaseCoverPosition(3, DefensePosition.Third); SetBaseCoverPosition(4, DefensePosition.Catcher); break; case DefensePosition.Left: // 三塁手も打球を追いかけるように三塁は投手がカバーする SetBaseCoverPosition(1, DefensePosition.First); SetBaseCoverPosition(2, DefensePosition.Second); SetBaseCoverPosition(3, DefensePosition.Pitcher); SetBaseCoverPosition(4, DefensePosition.Catcher); break; default: throw new Exception("Invalid"); } // ベースカバー完了までの残り時間を設定する for (int targetBase = 1; targetBase <= 4; targetBase++) { DefensePosition baseCoverPosition = GetBaseCoverPosition(targetBase); // 硬直時間の算出 int stiffSecond = GetStiffSecond(GameData.GetDefenseMemberAbility(baseCoverPosition)); // 移動時間の算出 MPoint baseCoverPoint = GameData.GetDefensePoint(baseCoverPosition); MPoint basePoint = GameData.GetBasePoint(targetBase); double officialBaseCoverLength = (baseCoverPoint - basePoint).Length; double runSpeed = GameData.GetDefenseMemberAbility(baseCoverPosition).RunSpeed; int coverReachTime = (int)Math.Ceiling(officialBaseCoverLength / runSpeed); // ベースカバー完了までの残り時間を設定 BaseCoverRemainingTime[targetBase] = stiffSecond + coverReachTime; } }
/// <summary> /// ホームランの発生 /// </summary> /// <param name="battedBoll">打球</param> private void OccurHomerun(BattedBoll battedBoll) { // 打球速度を0にしてボールを止める battedBoll.Speed = 0; // ランナーの進塁 foreach (RunnerStatus runnerStatus in GameData.AllRunnerStatus.OrderByDescending(r => r.RunnerNumber)) { runnerStatus.OverBaseCount = 4; // 得点の追加 AddScore(runnerStatus.RunnerNumber); } // 打撃結果の設定 SetBattingResultByHomerun(battedBoll); // 守備動作種別の変更 GameData.DefenseTermKind = DefenseTermKind.Finished; }
/// <summary> /// 打球と野手を移動させる /// </summary> /// <param name="battedBoll">打球</param> private void MoveBattedBollAndDefense(BattedBoll battedBoll) { #region 打球移動 // ボール位置の移動 if (battedBoll.Speed > 0) { // 打球落下していればファウルの判定 if (IsBattedBollMode && battedBoll.IsFoulDirection && !GameData.IsBattedBollFlying && !IsFoul) { SetFoul(); } // フライで後方のフェンスを超えている場合はその時点で守備動作終了にする(本塁打と同じ) if (GameData.IsBattedBollFlying && GameData.BollPoint.Y < Constants.MinScreenPoint.Y) { SetFoul(); // 守備動作種別の変更 GameData.DefenseTermKind = DefenseTermKind.Finished; return; } bool fenseReflect; bool fenseOver; GameData.BollPoint = GetFitureBollPoint(battedBoll, Constants.PassSecondByTurn, out fenseReflect, out fenseOver); // 打球速度の減速 bool isFly = GameData.IsBattedBollFlying; battedBoll.Speed = GetBollSpeed(isFly, GameData.DefenseTermPassedSecond, GameData.BollPoint, battedBoll.Speed); // フェンスに当たった場合は速度0にする if (fenseReflect) { battedBoll.Speed = 0; // イベント発行 if (ReflectedFense != null) { ReflectedFense(); } } // ホームラン // (フェンスを超えて着地した時点で打球速度を0にし、それ以上ボールが移動しないようにする) if (fenseOver && !battedBoll.IsFoulDirection && GameData.DefenseTermPassedSecond == battedBoll.DropTime) { // ホームラン時の設定(得点加算など) OccurHomerun(battedBoll); return; } } #endregion #region 野手移動 // 野手の移動 foreach (DefensePosition defensePosition in DefensePositionHelper.GetEnums) { GameMemberAbility memberAbility = GameData.GetDefenseMemberAbility(defensePosition); int stiffSecond = GetStiffSecond(memberAbility); if (stiffSecond > 0) { // 硬直時間中は何もしない continue; } if (defensePosition == CatchPosition) { // 捕球予定の野手は捕球予定位置へ移動 MPoint movePoint = GetDefenseMovePoint(GameData.GetDefenseMemberAbility(defensePosition), CatchPoint, Constants.PassSecondByTurn); GameData.SetDefensePoint(defensePosition, movePoint); } else if (!IsBaseCover(defensePosition)) { // 捕球予定でなくベースカバーでもない野手の移動 // (ベースカバーの移動はMoveForBaseCoverメソッドで実行) // フライ中に、すでに捕球位置に捕球予定の野手が達していれば移動しない // (一箇所に何人も集まりすぎてしまうため) if (GameData.DefenseTermPassedSecond < battedBoll.DropTime && CanCatchFly && GameData.GetDefensePoint(CatchPosition) == CatchPoint) { continue; } // フライなら落下予定位置へ移動、ゴロなら現在のボール位置へ移動 // (エラー時にカバーが早過ぎるとエラーの被害がない場合があるため、 // あえて最適な位置でなく現在のボール位置へ移動する) MPoint goalPoint; if (GameData.IsBattedBollFlying) { goalPoint = DropPoint; } else { goalPoint = GameData.BollPoint; } MPoint movePoint = GetDefenseMovePoint(GameData.GetDefenseMemberAbility(defensePosition), goalPoint, Constants.PassSecondByTurn); GameData.SetDefensePoint(defensePosition, movePoint); } // フライが落下する前までは捕球できない if (GameData.DefenseTermPassedSecond < battedBoll.DropTime) { continue; } // 捕球範囲の取得 double catchArea = GetCatchArea(memberAbility); // ボールとメンバの距離の取得 double memberToBollLength = (GameData.GetDefensePoint(defensePosition) - GameData.BollPoint).Length; // 捕球判定 // (捕球予測野手で捕球予測時間になった場合に捕球する // フェールセーフとして、それ以降の時間で捕球範囲にボールがある場合も捕球する) if ((GameData.DefenseTermPassedSecond == CatchTime && defensePosition == CatchPosition) || (GameData.DefenseTermPassedSecond > CatchTime && memberToBollLength < catchArea)) { // ボールに飛びついた感を表すために // メンバ位置をボール位置に設定する(エラーの場合でも設定する) MPoint beforeDefensePoint = GameData.GetDefensePoint(defensePosition); GameData.SetDefensePoint(defensePosition, GameData.BollPoint); // エラー判定 // 守備成功率=(固定4/5) +(守備力1/5)=固定80% + 守備力20% // ただし、すでにエラー発生したか、打球速度が遅い場合はエラーしない double validJudgmentVallue = (Constants.MaxAbilityValue * 4 / 5) + memberAbility.Defense / 5; int failDelta; if (!IsError && battedBoll.Speed > Constants.MinBattedBollErrorSpeed && !memberAbility.JudgByRandom(validJudgmentVallue, out failDelta)) { // エラー発生 IsError = true; ErrorPosition = CatchPosition; ErrorTime = CatchTime; IsErrorOnOut = CanCatchFly || DefensePositionHelper.IsInFielder(ErrorPosition); // 成績更新 GameData.AddError(memberAbility.GameMember); // イベント発行 if (OccurError != null) { OccurError(); } // 捕球位置と捕球野手を再設定 SetCatchPointForGroundBoll(battedBoll, Constants.ErrorStiffSecond); // 進塁停止していたランナーの進塁の最判断 foreach (RunnerStatus runnerStatus in GameData.AllRunnerStatus) { if (runnerStatus.IsStop) { UpdateRunnerStatusForForwardOrBack(runnerStatus); } } } else { // 捕球成功 GameData.BollKeepingPosition = defensePosition; // フライの場合は即アウト if (GameData.DefenseTermPassedSecond == battedBoll.DropTime) { CurrentFlyOut = true; SetOutRunner(GameData.BatterRunnerStatus, defensePosition, false); // ランナーの走塁方向の再判断(3アウト以外) if (GameData.OutCount < 3) { foreach (RunnerStatus runnerStatus in GameData.AllRunnerStatus) { if (runnerStatus.OverBaseCount == runnerStatus.RunnerNumber && runnerStatus.IsStop) { // 走塁停止していたランナーはタッチアップできる可能性があるため進塁の再判断 UpdateRunnerStatusForForwardOrBack(runnerStatus); } else { // 帰塁が必要なランナー帰塁方向に進路変更 runnerStatus.NeedBackRun = true; SetRunnerStatusForStopOrBack(runnerStatus); } } } } // 守備動作種別の変更 GameData.DefenseTermKind = DefenseTermKind.KeepingBoll; // イベント発行 if (CatchBoll != null) { CatchBoll(beforeDefensePoint); } // 捕球成功したら、それ以外の野手移動は不要 break; } } } #endregion }
/// <summary> /// 守備動作の開始前の初期化 /// </summary> /// <param name="battedBoll">打球</param> /// <param name="isOnlySteal">盗塁のみの実行か</param> private void InitializeDefenseAction(BattedBoll battedBoll, bool isOnlySteal) { // ボール位置の初期化 GameData.BollPoint = new MPoint(0, 0); // 経過時間の初期化 GameData.AllDefensePassedSecond = 0; // フィールドの初期化 CurrentFlyOut = false; CurrentBatterOutCount = 0; CurrentBatterForceOutCount = 0; LastRunnerStopTime = 0; CatchTime = 0; // フラグのクリア IsError = false; IsFoul = false; // 打球処理か捕逸/盗塁か設定 IsBattedBollMode = !battedBoll.IsPassBoll && !isOnlySteal; // 盗塁モードの設定 IsStealMode = isOnlySteal; // 守備動作種別の初期化 if (!isOnlySteal) { GameData.DefenseTermKind = DefenseTermKind.ChasingBoll; } else { // 盗塁送球時は最初から捕球済み CatchPosition = DefensePosition.Catcher; GameData.BollKeepingPosition = DefensePosition.Catcher; GameData.BollPoint = GameData.GetDefensePoint(DefensePosition.Catcher); GameData.DefenseTermKind = DefenseTermKind.KeepingBoll; } // 以下、ロジックを含む初期化(以下の処理の実行前に守備動作種別が適切に設定されている必要あり) // 守備の連携動作の設定 SetDefenseCombination(battedBoll); // ランナーの初期化 InitializeRnner(!IsBattedBollMode); }
/// <summary> /// 指定時間後の打球のボール位置を取得する /// </summary> /// <param name="battedBoll">打球</param> /// <param name="fitureSecond">指定時間</param> /// <param name="fenseReflect">フェンスに当たったか</param> /// <param name="fenseOver">フェンスを超えたか</param> /// <returns>ボール位置</returns> private MPoint GetFitureBollPoint(BattedBoll battedBoll, int fitureSecond, out bool fenseReflect, out bool fenseOver) { fenseReflect = false; fenseOver = false; double bollSpeed = battedBoll.Speed; MPoint fitureBollPoint = GameData.BollPoint; for (int second = 0; second < fitureSecond; second++) { Vector fitureMove = new Vector(bollSpeed * Math.Sin(battedBoll.SideDirection * Math.PI / 180), bollSpeed * Math.Cos(battedBoll.SideDirection * Math.PI / 180)); // ゴロかフライか bool isFly = GameData.RemainingDropTime - second >= 0; // フェンスの補正 fitureBollPoint = ChangePointByFense(fitureBollPoint, fitureMove, isFly, out fenseReflect, out fenseOver); // 打球速度の減速 bollSpeed = GetBollSpeed(isFly, GameData.DefenseTermPassedSecond + second, fitureBollPoint, bollSpeed); if (isFly) { // フライの場合 // フェンスを超えたらその時点の位置を返す // (ゴロになるまで計算するとフェンスに跳ね返る可能性があるため) if (fenseOver) { return fitureBollPoint; } } else { // ゴロの場合 // フェンスに当たった場合は速度0になるため、その時点の位置を返す if (fenseReflect) { return fitureBollPoint; } } } return fitureBollPoint; }
/// <summary> /// 打球データを取得 /// </summary> /// <param name="actualBall"></param> /// <returns></returns> private BattedBoll GetBattedBoll(PitchingBallData actualBall) { #region 事前準備 BattedBoll battedBoll = new BattedBoll(); GameMemberAbility memberAbility = GameData.CurrentBatterAbirity; // パワーは一発長打を表すためにランダムに変動させる double powerWithOffset = GetVallueByRandomOffset(memberAbility.Power); #endregion #region 芯を捉えるか // 芯を捉える率 // =(固定60%)±(変動30%) // =(固定6/10)+{(ミート8/10)+(パワー2/10)-(球速2/10)-(変化球7/10)-(制球3/10)} * 3 / 10 // 直球の中央値 (減算0.6):60% + 15% = 75% // 変化球の中央値(減算1.0):60% + 0% = 60% bool isHitCore; { double abilityValue = Constants.MaxAbilityValue * 6 / 10 + (memberAbility.Meet * 8 / 10 + memberAbility.Power * 2 / 10 - actualBall.BollSpeed * 2 / 10 - actualBall.BreakingAmount * 7 / 10 - actualBall.CourseValue * 3 / 10) * 3 / 10; double percent = GameMemberAbility.GetHigherValue(abilityValue, 100); // 芯を捉えるか確率で判定 isHitCore = JudgByRandom(percent); } #endregion #region インパクト率 // インパクト率 // =(固定50%)±(変動30%) // =(固定5/10)±{(ミート5/10)+(パワー5/10)-(球速10/10)-(制球0/10)} * 3 / 10 // ※制球はファウル率で十分効果が出るため、空振り率とインパクト率への寄与は小さい // ※パワーは0%~400%の幅で分散した値とする(パワーだけでも稀に長打が出るようにするため) // 直球の中央値 (減算1.0):固定 - 0% = 50% // 変化球の中央値(減算0.7):固定 + 30% = 80% { double abilityRasio = memberAbility.Meet * 5 / 10 + powerWithOffset * 5 / 10 - actualBall.BollSpeed * 10 / 10 - actualBall.CourseValue * 0 / 10; double abilityValue = Constants.MaxAbilityValue * 5 / 10 + abilityRasio * 3 / 10; double impactPercent = GameMemberAbility.GetHigherValue(abilityValue, 100); // インパクト率をランダム値で補正しない(パワー値でランダム性があるため) //impactPercent += RandomCreater.GetRandomValue(-5, 5); // 芯をとらえていなければインパクト率は半減 if (!isHitCore) { impactPercent *= 0.5; } // インパクト率の設定 impactPercent = Math.Max(impactPercent, 0); impactPercent = Math.Min(impactPercent, 100); battedBoll.ImpactPercent = impactPercent; } #endregion #region 打球速度 // 打球速度(通常時とバント時で別処理) if (!GameData.CurrentBattingBunt) { // 通常の打球速度 // ={(固定8/10)+(変動2/10)}×{(固定8/10)+(インパクト率2/10)} // ※変動2/10={(ミート0/10)(パワー10/10)}* 2 / 10 double battedSpeedRasioValue = Constants.MaxAbilityValue * 8 / 10 + (memberAbility.Meet * 0 / 10 + powerWithOffset * 10 / 10) * 2 / 10; battedBoll.Speed = (battedSpeedRasioValue * (80 + battedBoll.ImpactPercent * 2/ 10) / 100) * Constants.BattedBollSpeedOffSet; } else { // バントかスクイズの打球速度 // =(最善速度)×(変動率0.2倍~1.6倍) // ※強すぎると遊ゴロになるため、強すぎ補正はあまりひどくしない代わりに、強すぎの確率を低くする double errorPercent; if (RandomCreater.GetPercentValue() < 33) { // 強過ぎ補正(任意の比率をプラスする) // 能力による変動が30%、能力に限らない変動が30%で、最善速度に最大60%分が加算される errorPercent = GameMemberAbility.GetLowerValueOverBaseline(memberAbility.Wisdom, Constants.MinPlusErrorPercentByBunt); errorPercent += Constants.MinPlusErrorPercentByBunt; errorPercent = RandomCreater.GetRandomValue(0, (int)errorPercent); } else { // 弱過ぎ補正(任意の比率をマイナスする) // 能力による変動が40%、能力に限らない変動が40%で、最善速度に最大80%分が減算される errorPercent = -GameMemberAbility.GetLowerValueOverBaseline(memberAbility.Wisdom, Constants.MinMinusErrorPercentByBunt); errorPercent -= Constants.MinMinusErrorPercentByBunt; errorPercent = RandomCreater.GetRandomValue((int)errorPercent, 0); } battedBoll.Speed = Constants.BestBuntBollSpeed * (100 + errorPercent) / 100; } #endregion #region ファウル方向か // ファウル方向への打球になるか bool isFoulDirection = false; { // ファウル率 double foulPercent = 0; // 通常時とバントで別処理 if (!GameData.CurrentBattingBunt) { if (isHitCore) { // 芯を捉えた場合、コースが厳しいほどファウルになりやすい // ストライクのファウル率 // =(固定15%)±(変動40%) // =(固定15%)±(40%)×{(コースの厳しさ)-(ミート)}/(能力値のMAX値) double abilityValue = actualBall.CourseValue - memberAbility.Meet; foulPercent = 15 + Constants.FoulPercentByControll * (abilityValue / Constants.MaxAbilityValue); if (!PitchingCourseHelper.IsStrike(actualBall.PitchingCourse) && actualBall.MistakeLevel == 0) { // 失投でないボール球は芯を捉えた場合にファウルになりやすい(凡打はファウルになりにくい) // (失投の場合は効果なし) foulPercent += Constants.FoutPercentByBoll; } } else { // 芯を捉えなかった場合、ミートが高いほどファウルになりやすい // 芯を外した際のファウル率 // =(固定25%)±(変動0%) // =(固定25%)±(0%)×{(ミート)- (コースの厳しさ)}/(能力値のMAX値) // (制球の効果を抑制するため変動なし) double abilityValue = memberAbility.Meet - actualBall.CourseValue; foulPercent = 25 + 0 * (abilityValue / Constants.MaxAbilityValue); } } else { // バント時のファウル率 // =(固定15%)±(変動30%)+(ボール球のファウル率) // =(固定15%)±(30%)×{(智力)-(球速)-(変化球)}+(ボール球のファウル率) double abilityValue = memberAbility.Wisdom - actualBall.BollSpeed - actualBall.BreakingAmount; foulPercent = 15 + Constants.FoulPercentByBunt * (abilityValue / Constants.MaxAbilityValue); if (!PitchingCourseHelper.IsStrike(actualBall.PitchingCourse) && actualBall.MistakeLevel == 0) { // 失投でないボール球はファウル確率アップ foulPercent += Constants.FoutPercentByBoll; } } // マイナスにならないように補正 foulPercent = Math.Max(foulPercent, 0); if (GameData.CurrentBattingSwingData.BattingSwingKind == BattingSwingKind.Cut) { // カット打法の場合は必ずファウル // (ファウルフライで凡退する可能性もあるため、方向はせめて100%にする) foulPercent = 100; } // ファウル方向になるか確率判定 if (RandomCreater.GetPercentValue() < foulPercent) { // ファウル方向確定 isFoulDirection = true; // 通常時とバントで別処理 if (!GameData.CurrentBattingBunt) { // 通常時のファウル方向設定 if (!isHitCore) { // インパクト率が低い場合は後方へファウル if (RandomCreater.GetBool()) { battedBoll.SideDirection = RandomCreater.GetRandomValue(140, 180); } else { battedBoll.SideDirection = RandomCreater.GetRandomValue(-180, -140); } } else { // インパクト率が高ければ一塁線か三塁線にファウル if (RandomCreater.GetBool()) { battedBoll.SideDirection = RandomCreater.GetRandomValue(46, 60); } else { battedBoll.SideDirection = RandomCreater.GetRandomValue(-60, -46); } } } else { // バント時のファウル方向設定 bool firstBaseDirection = DecideDirectionForBunt(); if (firstBaseDirection) { battedBoll.SideDirection = RandomCreater.GetRandomValue(46, 60); } else { battedBoll.SideDirection = RandomCreater.GetRandomValue(-60, -46); } } } } #endregion #region フライかゴロか // フライかゴロか設定する bool isFly = RandomCreater.GetPercentValue() < Constants.FlyPercent; // 作戦指示があればフライかゴロを上書きする { // 作戦通りにフライまたはゴロになる成功率 // =(固定60%)+(変動40%) // =(固定6/10)+(智力4/10) double wisdomValue = Constants.MaxAbilityValue * 6 / 10 + memberAbility.Wisdom * 4 / 10; double percent = GameMemberAbility.GetHigherValue(wisdomValue, 100); if (RandomCreater.GetPercentValue() < percent) { if (GameData.CurrentBattingSwingData.IsFly != null) { isFly = (bool)GameData.CurrentBattingSwingData.IsFly; } } } #endregion #region フライ/ゴロの特性で打球データ作成 // フライとゴロのそれぞれの打球データの作成 if (isFly) { // フライの場合 // 打球速度の低下 // (ゴロは内野の隙間を抜く必要があるから速い必要があり、ゴロと同じだと本塁打増えすぎる) battedBoll.Speed *= Constants.DownBattedBollSpeedByFly; // 落下時間を設定 // (この時点で設定する落下時間は45度の理想的な角度とする) double dropTimeBasic = battedBoll.Speed * Constants.DropTimeOffSet; double downRasio = 0; if (!isHitCore) { // 芯をとらえなかった場合はポップフライ downRasio = 50; battedBoll.Speed *= downRasio / 100; battedBoll.DropTime = (int)(dropTimeBasic * 100 / downRasio); } else { // それ以外は「ライナー性の弾道」か「高く上がり過ぎ」がランダムに発生 // (本塁打率はあまり変わらないが安打率はライナー性の弾道の方が高い) // ライナー性の弾道確率 // =(固定10%)+(40%)*(ミート&パワー) // ※守備側の能力が高いほどフライがとられやすくなるための対策 double plusPercent = GameMemberAbility.GetHigherValue(memberAbility.MeetAndPower, 40); double percent = Constants.MinLinerPercent + plusPercent; bool isLiner = JudgByRandom(percent); if (isLiner) { // ライナー性の弾道は、インパクト率が高いほど落下時間を増やす int randamValue = RandomCreater.GetRandomValue(-10, 10); battedBoll.DropTime = (int)(dropTimeBasic * (50 + battedBoll.ImpactPercent * 5 / 10 + randamValue) / 100); } else { // 高く上がり過ぎの場合、打球速度低下(ほとんど外野フライか本塁打になる) downRasio = 50 + battedBoll.ImpactPercent * 5 / 10; battedBoll.Speed *= downRasio / 100; battedBoll.DropTime = (int)(dropTimeBasic * 100 / downRasio); } } // 落下時間を一定以上に補正 battedBoll.DropTime = Math.Max(battedBoll.DropTime, Constants.MinDropTime); // フライの場合、打球方向はランダム) if (!isFoulDirection) { battedBoll.SideDirection = RandomCreater.GetRandomValue(-45, 45); // 特殊能力「プルヒッター」は超引っ張りの確率アップ if (GameData.CurrentBatter.Player.FielderAbility.SpecialFilederAbility == SpecialFilederAbility.PullHitter && RandomCreater.GetPercentValue() < 5) { battedBoll.SideDirection = RandomCreater.GetRandomValue(-45, -40); } } } else { // ゴロ battedBoll.DropTime = 0; // パワーが高くてもボテボテの凡打を時々打つように // インパクト率が小さい場合はランダムで打球速度を減速(進塁打や内安打になる可能性はある) if (!isHitCore && !GameData.CurrentBattingBunt) { battedBoll.Speed *= (double)RandomCreater.GetRandomValue(10, 70) / 100; } // ファウルでなければ打球方向を設定 if (!isFoulDirection) { if (!GameData.CurrentBattingBunt) { // 打球方向(左右)は内野の間を狙う // 守備の中間地点の角度を算出して間を狙う // ミート力が一定以上ある場合のみ、角度の誤差が小さくなる int abilityErrorValue = GameMemberAbility.GetRandamRoundValue(GameMemberAbility.GetLowerValueOverBaseline(memberAbility.MeetAndPower, Constants.MaxBattedBollSideDirectionError)); int errorValue = abilityErrorValue + Constants.MinBattedBollSideDirectionError; // 内野前進の場合はヒットがでやすいように角度誤差を小さくする if (GameData.InFielderDrawIn) { errorValue -= 1; } // 打球方向の設定 // (二塁手と遊撃手へのゴロ依存度が高くなりすぎないように若干二遊間への確率を下げる) int percentValue = RandomCreater.GetPercentValue(); BattingGroundDirection direction; if (percentValue < 25) { // 二遊間:25% direction = BattingGroundDirection.Center; } else if (percentValue < 55) { // 一二塁間:30% direction = BattingGroundDirection.Right; } else if (percentValue < 85) { // 三遊間:30% direction = BattingGroundDirection.Left; } else if (percentValue < 90) { // 一塁ベース際:5% direction = BattingGroundDirection.Left; } else { // 三塁ベース際:10% direction = BattingGroundDirection.ThirdBase; } // 特殊能力「プルヒッター」は超引っ張りの確率アップ if (GameData.CurrentBatter.Player.FielderAbility.SpecialFilederAbility == SpecialFilederAbility.PullHitter && RandomCreater.GetPercentValue() < 5) { direction = BattingGroundDirection.ThirdBase; } // 右打ちの場合は一二塁間に打つ if (GameData.CurrentBattingSwingData.BattingSwingKind == BattingSwingKind.RightBatting) { direction = BattingGroundDirection.Right; } // エンドランの場合は一二塁間か三遊間に打つ if (GameData.CurrentBattingSwingData.BattingSwingKind == BattingSwingKind.EndRun) { direction = RandomCreater.GetBool() ? BattingGroundDirection.Right : BattingGroundDirection.Left; } // デバッグ用に打球方向が指定されている場合はその方向に設定する if (DebugBattingDirection != BattingGroundDirection.Invalid) { direction = DebugBattingDirection; } // 方向ごとに角度の算出 double angle; switch (direction) { case BattingGroundDirection.Center: // 二遊間(センター返し)約0度 // (二遊間は安打になりやすいので角度の誤差を大きくする) errorValue += Constants.BattedBollSideDirectionErrorByCenter; angle = Vector.GetAngle(GameData.Defense4Point, GameData.Defense6Point); break; case BattingGroundDirection.Right: // 一二塁間(流し打ち)約22.5度 angle = Vector.GetAngle(GameData.Defense3Point, GameData.Defense4Point); break; case BattingGroundDirection.Left: // 三遊間(引っ張り)約22.5度 angle = Vector.GetAngle(GameData.Defense5Point, GameData.Defense6Point); break; case BattingGroundDirection.FirstBase: // 低確率で一塁線(超流し打ち)約45度 angle = 45; break; case BattingGroundDirection.ThirdBase: // 低確率で三塁線(超引っ張り)約45度 // (引っ張ることで打球速度が上がるため安打になりやすく長打にもなりやすい) angle = -45; break; default: throw new Exception("Invalid BattingGroundDirection"); } // 角度の誤差の取得 int errorDirection = RandomCreater.GetRandomValue(errorValue * (-1), errorValue); // 超流し打ちか超引っ張りの場合はファウルにならず誤差も小さい if (direction == BattingGroundDirection.FirstBase) { errorDirection = Math.Abs(errorDirection / 2) * (-1); } else if (direction == BattingGroundDirection.ThirdBase) { errorDirection = Math.Abs(errorDirection / 2); } // 角度の設定 battedBoll.SideDirection = angle + errorDirection; // 左打者なら角度を反転する if (memberAbility.GameMember.Player.IsLeftBox) { battedBoll.SideDirection *= -1; } } else { // バントの場合の打球方向 // 打球方向(左右)は一塁線か三塁線を狙う // 智力が一定以上ある場合のみ、角度の誤差が小さくなる int errorValue = (int)GameMemberAbility.GetLowerValueOverBaseline(memberAbility.Wisdom, Constants.MaxBuntBollSideDirectionError); errorValue += Constants.MinBattedBollSideDirectionError; int errorDirection = RandomCreater.GetRandomValue(0, errorValue); // 状況によって打球方向を決定する bool firstBaseDirection = DecideDirectionForBunt(); if (firstBaseDirection) { // 一塁線 battedBoll.SideDirection = 45 - errorDirection; } else { // 三塁線 battedBoll.SideDirection = -45 + errorDirection; } } } } #endregion #region 角度により打球速度補正 // 左右の角度によって打球速度を変更 // (バントの場合は補正しない) if (!GameData.CurrentBattingBunt) { if ((!memberAbility.GameMember.Player.IsLeftBox && battedBoll.SideDirection > 0) || (memberAbility.GameMember.Player.IsLeftBox && battedBoll.SideDirection < 0)) { // 流し打ちによる減速 if (GameData.CurrentBatter.Player.FielderAbility.SpecialFilederAbility != SpecialFilederAbility.HitTheOtherWay) { // 「流し打ち○」の特殊能力がない場合は減速 battedBoll.Speed *= 1.0 - ((double)Math.Abs(battedBoll.SideDirection) / 45) * Constants.BattedBollSpeedDownRasioBySideDirection; } } else { // 引っ張りよる速度増加 battedBoll.Speed *= 1.0 + ((double)Math.Abs(battedBoll.SideDirection) / 45) * Constants.BattedBollSpeedUpRasioBySideDirection; } } #endregion #region 異常値にならないように補正 // 打球速度を最小値以上に補正 battedBoll.Speed = Math.Max(battedBoll.Speed, Constants.MinBattedBollSpeed); // 事前にファウルと判定しない場合はファウルにしない if (!isFoulDirection) { battedBoll.SideDirection = Math.Min(45, battedBoll.SideDirection); battedBoll.SideDirection = Math.Max(-45, battedBoll.SideDirection); } #endregion return battedBoll; }