KeeperDistribution.cs

Part of the football match simulation. Runs goalkeeper kick distribution: picks a receiver, computes the targeted kick with error based on GK kicking skill and the KeeperDistributor unique skill, plays the wind-up animation, then launches the ball as a punt or long goal kick.

using System;
using System.Linq;
namespace Socer;

public sealed partial class MatchSimulation
{
    public int KeeperKickPlayer=-1;
    public float KeeperKickAt;
    public Point KeeperKickTarget;
    public bool KeeperKickFromHands;
    public int KeeperPunts,KeeperLongGoalKicks;
    public const float KeeperKickDuration=.96f,KeeperDropAt=.50f;
    public void BeginKeeperKick(Footballer keeper,Footballer[] receivers)
    {
        if(KeeperKickPlayer>=0 || Owner!=keeper.Id || !keeper.Keeper)return;
        bool distributor=SkillActive(keeper,UniqueSkill.KeeperDistributor) || ActivateSkill(keeper,UniqueSkill.KeeperDistributor);
        var receiver=receivers.OrderByDescending(p=>World(p.Position,keeper.Team).X-
            Math.Abs(World(p.Position,keeper.Team).Y-34)*.08f+
            (distributor && SkillNeighbors(keeper,p)?Math.Max(SkillStrength(p,UniqueSkill.OutletRunner),SkillStrength(p,UniqueSkill.SpaceHunter))*7:0)).FirstOrDefault();
        var local=receiver==null?new Point(60,Next()<.5f?21:47):World(receiver.Position+receiver.Velocity*.8f,keeper.Team);
        float error=(3+(1-keeper.Profile.GkKicking)*14)*(distributor?1-.30f*keeper.ActiveSkillPower:1);
        local.X=Math.Clamp(local.X+4,58,70);local.Y=Math.Clamp(local.Y+(Next()-.5f)*error,12,56);
        KeeperKickTarget=World(local,keeper.Team);
        if(IsMediaLeague)PlanMediaKeeperKick(keeper);
        KeeperKickPlayer=keeper.Id;KeeperKickAt=Seconds;KeeperKickFromHands=!GoalKickPossession;
        keeper.KickFoot=keeper.StrongFoot;keeper.Velocity=new();keeper.BurstRemaining=0;
        keeper.DiveRemaining=0;keeper.DiveEndedAt=-10;
        keeper.Action=KeeperKickFromHands?"Готовит удар с рук":"Готовит дальний удар от ворот";
        Say(keeper,"Вперёд, подбирайте!",true);
    }
    bool AdvanceKeeperKick()
    {
        if(KeeperKickPlayer<0)return false;
        var p=Players[KeeperKickPlayer];
        if(Owner!=p.Id || p.SentOff){KeeperKickPlayer=-1;return false;}
        float age=Seconds-KeeperKickAt;
        var aim=(KeeperKickTarget-p.Position).Unit;
        var direction=new Point(MathF.Cos(p.Facing),MathF.Sin(p.Facing));
        if(age<.3f && Point.Dot(direction,aim)<.98f){KeeperKickAt=Seconds;age=0;}
        float drop=Math.Clamp(age-KeeperDropAt,0,KeeperKickDuration-KeeperDropAt);
        float dropDuration=KeeperKickDuration-KeeperDropAt;
        float initialVertical=(.34f-1.2f+4.905f*dropDuration*dropDuration)/dropDuration;
        Ball=p.Position+direction*(KeeperKickFromHands?.50f:.35f);
        BallHeight=KeeperKickFromHands?1.2f+initialVertical*drop-4.905f*drop*drop:0;
        BallVelocity=new();VerticalSpeed=0;
        p.Action=KeeperKickFromHands?(drop>0?"Удар с полулёта":"Выпускает мяч из рук"):"Разбег от ворот";
        if(age<KeeperKickDuration)return false;
        var origin=Ball;float contactHeight=BallHeight;
        bool fromHands=KeeperKickFromHands;
        KeeperKickPlayer=-1;
        Kick(p,KeeperKickTarget,27,false);
        if(SkillActive(p,UniqueSkill.KeeperDistributor)){BallUniqueSkill=UniqueSkill.KeeperDistributor;BallSkillGrade=p.ActiveSkillGrade;}
        Ball=origin;BallHeight=Math.Max(.11f,contactHeight);
        float distance=(KeeperKickTarget-Ball).Length;
        float duration=Math.Clamp(distance/18,2.7f,3.8f);
        BallVelocity=(KeeperKickTarget-Ball)*(.08f/(1-MathF.Exp(-.08f*duration)));
        VerticalSpeed=(.11f-BallHeight)/duration+4.905f*duration;
        BallSpin=0;Strike=StrikeKind.Cross;p.LastKickKind=StrikeKind.Cross;
        p.LastPuntAt=fromHands?Seconds:-10;
        Passes[p.Team]++;LongPasses[p.Team]++;LoftedPasses++;
        if(fromHands)KeeperPunts++;else KeeperLongGoalKicks++;
        PrepareSecondBall(p,KeeperKickTarget,duration+.8f);
        AddEvent($"{TeamName(p.Team)} · вратарь: {(fromHands?"удар с рук":"дальний удар от ворот")} на чужую половину");
        if(IsMediaLeague)NoteMediaKeeperRelease(p);
        return true;
    }
}