module Match.State.AI(doAI) where import Data.List import Data.Function import FVector import qualified Gen import Match.Player import Match.Ball import Match.State.MatchState import Match.State.MatchBase import Match.State.Actions import Match.State.Formation offBallAI :: MatchState -> Player -> PlAction offBallAI m pl | plpos pl == Gen.Goalkeeper = if dist2 (ballCoords m) (ownGoalAbs m pl) < 16 && inCatchDistance m pl && not (canDribble m pl) && opponentLastTouched m pl then (pl, HoldBall) else if nearestOwnToBall m pl == pl && kicktimer pl <= 0 then (pl, Goto (ballCoords m)) else (pl, Goto (formationPositionAbs m pl)) offBallAI m pl | plpos (nearestOppToBall m pl) == Gen.Goalkeeper && dist2 (ballCoords m) (oppositeGoalAbs m pl) < 24 = -- leave the goalkeeper alone (pl, Goto (awayFromNearbyGoal m 24 (formationPositionAbs m pl))) offBallAI m pl | nearestOwnToBall m pl == pl = (pl, Goto (ballCoords m)) offBallAI m pl | supportingDefense m pl = (pl, Goto (defenseSupporterCoords m pl)) offBallAI m pl | plpos (nearestOwnToBall m pl) == Gen.Goalkeeper && dist2 (ballCoords m) (plposition $ nearestOppToBall m pl) > 16 = -- leave the goalkeeper alone (pl, Goto (awayFromNearbyGoal m 12 (formationPositionAbs m pl))) offBallAI m pl | otherwise = (pl, Goto (formationPositionAbs m pl)) awayFromNearbyGoal :: MatchState -> Float -> FRange -> FRange awayFromNearbyGoal m r (fx, fy) = (fx, if fy > snd (pitchsize m) / 2 then fy - r else fy + r) supportingOffense :: MatchState -> Player -> Bool supportingOffense m pl = not (nearestIsHP m) /= playerHome pl && supportingPlayer m pl supportingDefense :: MatchState -> Player -> Bool supportingDefense m pl = plpos pl /= Gen.Goalkeeper && nearestIsHP m /= playerHome pl && supportingPlayer m pl supportingPlayer :: MatchState -> Player -> Bool supportingPlayer m pl = pl == (opsToBallByDist m pl !! 1) defenseSupporterCoords :: MatchState -> Player -> FRange defenseSupporterCoords m pl = let og = ownGoalAbs m pl op = plposition $ nearestOppToBall m pl in onLine 3 op og -- point between points v1 and v2, -- with distance n from v1. onLine :: Float -> FRange -> FRange -> FRange onLine n v1 v2 = let dv = v1 `diff2` v2 in v1 `diff2` ((normalize2 (neg2 dv)) `mul2` n) getPassPower :: Float -> Float -> Float -> Float -> FRange -> Player -> FVector3 getPassPower plen lpp hpp hpzp recv pl = let dv = recv `diff2` (plposition pl) in if len2 dv < plen then to3D ((recv `diff2` (plposition pl)) `mul2` lpp) 0 else to3D ((recv `diff2` (plposition pl)) `mul2` hpp) (len2 dv * hpzp) pass :: Float -> Float -> Float -> Float -> Player -> Player -> PlAction pass plen lpp hpp hpzp receiver passer = let passpwr = getPassPower plen lpp hpp hpzp (plposition receiver) passer in (passer, Kick passpwr) bestPassTarget :: MatchState -> Player -> (Float, PlAction) bestPassTarget m pl = (passpts, pass (aimaxlowpasslen (params m)) (ailowpasspower (params m)) (aihighpasspower (params m)) (aihighpasszpower (params m)) passtarget pl) where (passpts, passtarget) = head $ sortBy (flipCompare `on` fst) $ map (passValue m pl) (filter (/= pl) $ ownPlayers m pl) flipCompare :: (Ord a) => a -> a -> Ordering flipCompare a b | a < b = GT | a > b = LT | otherwise = EQ passValue :: MatchState -> Player -> Player -> (Float, Player) passValue m passer receiver = let sval = 1.0 * shootPositionValue m (homeattacksup m == playerHome receiver) (plposition receiver) dist = dist2 (plposition passer) (plposition receiver) in (sval + 0.5 * ratePassDist dist, receiver) -- range: -50 .. 50 ratePassDist :: Float -> Float ratePassDist d | d < 20 = 0.5 * (10 * d - 100) | otherwise = 0.5 * (200 / (-80) * d + 150) -- range: 0 .. 100 shootPositionValue :: MatchState -> Bool -> FRange -> Float shootPositionValue _ attup pos = max 0 (100 - (1.5 * (dist2 pos (oppositeGoalAbs' pos attup)))) beforeKickoffAI :: MatchState -> [PlAction] beforeKickoffAI m = forAIPlayers m $ \pl -> if shouldDoKickoff m pl then (pl, Goto (relToAbs m (0.5, 0.5))) else if shouldAssistKickoff m pl then (pl, Goto (relToAbs m (0.52, 0.5))) else (pl, Goto (kickoffPositionAbs m pl)) forAIPlayers :: MatchState -> (Player -> a) -> [a] forAIPlayers m f = map f (filter (\pl -> aiControlled m (playerid pl)) (allPlayers m)) doAI :: MatchState -> [PlAction] doAI m = case ballplay m of BeforeKickoff -> beforeKickoffAI m WaitForKickoff _ -> beforeKickoffAI m DoKickoff -> forAIPlayers m $ \pl -> if shouldDoKickoff m pl then kickoff m pl else if shouldAssistKickoff m pl then (pl, Goto (relToAbs m (0.52, 0.5))) else (pl, Goto (kickoffPositionAbs m pl)) InPlay -> forAIPlayers m $ \pl -> if inKickDistance m pl && kicktimer pl <= 0 then onBallAI m pl else offBallAI m pl OutOfPlayWaiting _ _ -> [] OutOfPlay _ r -> beforeRestartAI m r RestartPlay r -> forAIPlayers m $ \pl -> if shouldRestart m r pl then restart m r pl else restartLookout m r pl Finished timer -> let (px, py) = pitchsize m in forAIPlayers m $ \pl -> if timer < 1000 then (pl, Idle) else (pl, Goto (px - 10, py / 2)) shouldRestart :: MatchState -> Restart -> Player -> Bool shouldRestart m (ThrowIn p) pl = pl == nearestOPToPointwoGK m p pl && (homeRestarts m == playerHome pl) shouldRestart m (CornerKick p) pl = pl == nearestOPToPointwoGK m p pl && (homeRestarts m == playerHome pl) shouldRestart m (GoalKick _) pl = plpos pl == Gen.Goalkeeper && (homeRestarts m == playerHome pl) beforeRestartAI :: MatchState -> Restart -> [PlAction] beforeRestartAI m r = forAIPlayers m $ \pl -> if shouldRestart m r pl then (pl, Goto (ballCoords m)) else restartLookout m r pl restartLookout :: MatchState -> Restart -> Player -> PlAction restartLookout m _ pl = (pl, Goto (awayFromNearbyGoal m 16 (formationPositionAbs m pl))) restart :: MatchState -> Restart -> Player -> PlAction restart m _ pl = if not (inKickDistance m pl) then (pl, Goto (to2D (ballposition (ball m)))) else snd $ bestPassTarget m pl onBallAI :: MatchState -> Player -> PlAction onBallAI m pl | plpos pl == Gen.Goalkeeper = if distanceToBall m (nearestOppToBall m pl) < 10 then if dist2 (ballCoords m) (ownGoalAbs m pl) < 16 then (pl, Idle) else generalOnBallAI m pl else generalOnBallAI m pl onBallAI m pl | otherwise = generalOnBallAI m pl generalOnBallAI m pl = let passact = bestPassTarget m pl dribbleact = bestDribbleTarget m pl shootact = shootScore m pl in snd $ head $ sortBy (flipCompare `on` fst) [passact, dribbleact, shootact] shootScore :: MatchState -> Player -> (Float, PlAction) shootScore m pl = (scorepts, (pl, Kick kickpwr)) where vecttogoal = oppositeGoalAbs m pl `diff2` plposition pl scorepts = 2 * shootPositionValue m (homeattacksup m == playerHome pl) (plposition pl) kickpwr = to3D (vecttogoal `mul2` 5) 8 bestDribbleTarget :: MatchState -> Player -> (Float, PlAction) bestDribbleTarget m pl = (dpts, dribble (oppositeGoalAbs m pl) pl) where dpts | not (canDribble m pl) = 0 | otherwise = 5 * distanceToBall m (nearestOppToBall m pl) dribble :: FRange -> Player -> PlAction dribble p pl = (pl, Goto p) kickoff :: MatchState -> Player -> PlAction kickoff m p = if not (inKickDistance m p) then (p, Goto (to2D (ballposition (ball m)))) else (p, Kick (20, 0, 0))