Partial MatchSimulation class that adds a coach command. Three orders (press, attack, conserve) change player target positions and stamina for 12 seconds and add a match event when issued.
using System;
namespace Socer;
public sealed partial class MatchSimulation
{
public int CoachOrder;
public float CoachUntil, CoachReadyAt;
public string CoachCaption => Seconds<CoachUntil ? CoachOrder==1?"ПРЕССИНГ":CoachOrder==2?"В АТАКУ":"БЕРЕЧЬ СИЛЫ" : "КОМАНДЫ ТРЕНЕРА";
public bool Command(int order)
{
if(!CanFieldCall || order<1 || order>3 || Seconds<CoachReadyAt) return false;
CoachOrder=order;CoachUntil=Seconds+12;CoachReadyAt=Seconds+25;
AddEvent("Тренер: "+CoachCaption.ToLowerInvariant());return true;
}
Point ApplyCoach(Footballer p,Point target,int possessionTeam,float dt)
{
if(p.Keeper || p.Id==Owner || WindupPlayer==p.Id || DuelDefender==p.Id || p.BoxRole>0) return target;
int dir=Direction(p.Team);
if(Owner!=p.Id)
{
if(p.Instruction==1 && possessionTeam==p.Team)
{
// Hold behind the attack; do not abandon the penalty area to
// stand on the original touchline when defending.
var local=World(target,p.Team);
local.X=Math.Min(local.X,Math.Max(p.Home.X,World(Ball,p.Team).X-16));
target=World(local,p.Team);p.RunUntil=0;
}
if(p.Instruction==2 && possessionTeam==p.Team) target.X+=dir*6;
if(p.Instruction==3) target.Y+=p.Home.Y<34?-6:6;
}
if(p.Team==0 && Seconds<CoachUntil)
{
if(CoachOrder==1 && possessionTeam!=0 && Owner>=0 && !Players[Owner].Keeper)
{
var carrier=Players[Owner];var goal=World(new Point(0,34),0);
if(p.Id==chasers[0])
{
target=carrier.Position+(goal-carrier.Position).Unit*.85f;
p.Action="Тренер · давление на мяч";
}
else if(p.Id==secondChasers[0])
{
if(p.AutoMarkId>=0 && Players[p.AutoMarkId].InPlay &&
(Players[p.AutoMarkId].Position-goal).Length<28)
return target; // Keep the dangerous runner covered during a press.
Footballer outlet=null;float best=float.MaxValue;
foreach(var q in ActivePlayers)
{
if(q.Team==0 || q.Keeper || q.Id==Owner)continue;
float d=(q.Position-carrier.Position).Length;
if(d>3 && d<best && d<19){outlet=q;best=d;}
}
target=outlet==null?carrier.Position+(goal-carrier.Position).Unit*3:
carrier.Position+(outlet.Position-carrier.Position).Unit*Math.Min(4,best*.4f);
p.Action="Тренер · перекрывает выход из прессинга";
}
}
if(CoachOrder==2 && possessionTeam==0 && Owner>=0 && !BackLine(p) &&
p.RunUntil<=Seconds && p.FootballRunUntil<=Seconds)
{
var carrier=Players[Owner];int closer=0;
foreach(var q in ActivePlayers)
if(q.Team==0 && q.Id!=Owner && !q.Keeper && !BackLine(q) &&
(q.Position-carrier.Position).Length<(p.Position-carrier.Position).Length)closer++;
if(closer<2 && (p.Position-carrier.Position).Length<27)
{
float side=p.Home.Y<34?-1:1;
var option=Clamp(carrier.Position+new Point(dir*(closer==0?-4:9),side*(closer==0?7:10)));
if(PassSafety(carrier,p,option)>.45f)
{target=option;p.Action=closer==0?"Тренер · поддерживает треугольник":"Тренер · открывается в свободную зону";}
}
}
if(CoachOrder==3) {p.BurstRemaining=0;p.Stamina=Math.Min(1,p.Stamina+dt*.0015f);if(possessionTeam==0)target.X-=dir*3;}
else p.Stamina=Math.Max(.08f,p.Stamina-dt*.002f);
}
return target;
}
}