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;
}
}