AttackingExpression.cs

Part of the Socer match simulation. Holds the attack urgency score, decides when AI players call for a pass (and when an ignored call makes them frustrated), and resolves first-time shots when a moving ball crosses within reach of an attacker.

Rough Code
using System;
using System.Linq;
namespace Socer;

public sealed partial class MatchSimulation
{
    public int LongRangeShots,MovingShots;
    // Score pressure grows late in the game; it does not change accuracy or physics.
    float AttackUrgency(int team)=>Math.Max(team==0 && FieldAttackUntil>Seconds?(FieldShootUntil>Seconds?.95f:.55f):0,Goals[team]<Goals[1-team]
        ?Math.Clamp(.3f+Minute/90f*.55f+(Goals[1-team]-Goals[team]-1)*.12f,0,1):0);

    void UpdatePassCalls()
    {
        foreach(var p in ActivePlayers.Where(p=>!p.Keeper))
        {
            if(p.IsAcrobaticCommitted(Seconds)){p.PassCallUntil=0;continue;}
            if(Owner==p.Id){p.PassCallUntil=0;p.IgnoredPassCalls=0;p.PassCallOwner=-1;continue;}
            if(p.PassCallOwner>=0 && Seconds>=p.PassCallUntil)
            {
                if(Owner>=0 && Players[Owner].Team==p.Team && PassReceiver!=p.Id)
                {
                    p.IgnoredPassCalls++;
                    if(p.IgnoredPassCalls>=2)
                    {
                        p.Frustration=Math.Min(1,p.Frustration+.16f);
                        p.AngryPassCall=true;p.PassCallUntil=Seconds+3;
                        SpeakEmotion(p,p.Profile.Composure<.5f?"Да блин, я же открыт!":"Почему без паса?!");
                    }
                }
                p.PassCallOwner=-1;
            }
            if(Seconds<p.NextPassCallAt || Seconds<p.PassCallUntil || Owner<0 || Players[Owner].Team!=p.Team ||
                Seconds<p.FallUntil || Seconds-p.LastSlideAt<2.1f || p.Stamina<.2f)continue;
            var goal=World(new Point(105,34),p.Team);
            if((goal-p.Position).Length>27 || (Ball-p.Position).Length<5 || (Ball-p.Position).Length>30 ||
                !IsOnside(p,Ball) || PerceivedSpace(p,p.Position)<3 || PerceivedLane(p,goal,true)<1.2f)continue;
            // A call is information for the coach, not omniscience for the carrier.
            if(ActivePlayers.Count(q=>q.Team==p.Team && q.PassCallUntil>Seconds)>=2)continue;
            p.AngryPassCall=false;p.PassCallUntil=Seconds+3;p.NextPassCallAt=Seconds+10;
            p.PassCallOwner=Owner;
        }
    }

    bool TryOpenPlayFirstTime(Point before,float beforeHeight)
    {
        if(RehearsingStrategy || Strategy is {Done:false} || shot || PickupDelay>0 || Passer<0 || BallSpeed<5)return false;
        var segment=Ball-before;float length2=Point.Dot(segment,segment);
        foreach(var p in ActivePlayers.Where(p=>p.Team==LastTouch && p.Id!=Passer && !p.Keeper && Seconds>=p.FallUntil && Seconds-p.LastSlideAt>=2.1f && !p.IsAcrobaticCommitted(Seconds)))
        {
            float t=length2<.0001f?0:Math.Clamp(Point.Dot(p.Position-before,segment)/length2,0,1);
            var contact=before+segment*t;
            if((contact-p.Position).Length>DribbleFootReach || beforeHeight+(BallHeight-beforeHeight)*t>.45f)continue;
            var aim=World(new Point(105,34),p.Team)-p.Position;
            if(aim.Length>24 || Point.Dot(new Point(MathF.Cos(p.Facing),MathF.Sin(p.Facing)),aim.Unit)<.65f ||
                EvaluateBallDecision(p,CarrierPressure(p)).Choice!=BallChoice.Shoot || Next()>.3f+p.Profile.Finishing*.45f)continue;
            if(CheckOffside(p))return true;
            float incoming=BallSpeed;Completed[p.Team]++;ClearOffside();
            Ball=contact;BallHeight=0;Owner=p.Id;
            BeginShot(p,aim.Length);
            float contactTechnique=p.Profile.Volleys*.6f+p.Profile.Finishing*.25f+p.Skill*.15f;
            float contactSpread=.25f+(1-contactTechnique)*(2+incoming*.12f)+incoming*.015f+
                (1-p.Profile.Composure)*.35f;
            contactSpread*=1-.22f*SkillStrength(p,UniqueSkill.VolleyMaster);
            ShotTarget.Y+=(Next()-.5f)*contactSpread;
            WindupPlayer=-1;ReleaseShot(p);FirstTimeShots++;p.Action="Удар с ходу";
            return true;
        }
        return false;
    }
}