MatchAtmosphere.cs

Presentation helper for a soccer match. Watches MatchSimulation state and plays crowd audio, whistles, kicks, headers and card sounds, and tracks home/away/boo reaction values plus a pulse timer.

using Sandbox;
using Socer;
using System;

// Presentation only. Simulation results and career rewards never depend on audio or crowd state.
public sealed class MatchAtmosphere
{
    MatchSimulation observed;
    readonly float[] kicks=new float[22];
    readonly float[] headers=new float[22];
    int lastEffect,lastHomeGoals,lastAwayGoals;
    bool openingWhistle,halfWhistle,finalWhistle;
    float shotCooldown,whistleCooldown,actionCooldown;
    public float HomeReaction {get;private set;}
    public float AwayReaction {get;private set;}
    public float BooReaction {get;private set;}
    public float Pulse {get;private set;}

    public void PreviewReaction(string kind)
    {
        if(kind=="cheer")HomeReaction=1;
        else if(kind=="away-cheer")AwayReaction=1;
        else if(kind=="boo")BooReaction=1;
        else if(kind=="reset")HomeReaction=AwayReaction=BooReaction=0;
    }

    public void Update(MatchController game,float delta)
    {
        var match=game.Match;
        if(observed!=match)
        {
            observed=match;lastEffect=0;
            lastHomeGoals=match.Goals[0];lastAwayGoals=match.Goals[1];
            for(int i=0;i<kicks.Length;i++)
            {
                kicks[i]=i<match.Players.Count?match.Players[i].LastKickAt:0;
                headers[i]=i<match.Players.Count?match.Players[i].HeaderAt:0;
            }
            openingWhistle=match.Seconds>0;halfWhistle=match.SecondHalf;finalWhistle=match.Finished;
            HomeReaction=AwayReaction=BooReaction=0;
        }
        delta=Math.Clamp(delta,0,.1f);
        HomeReaction=Math.Max(0,HomeReaction-delta*.22f);
        AwayReaction=Math.Max(0,AwayReaction-delta*.22f);
        BooReaction=Math.Max(0,BooReaction-delta*.34f);
        Pulse+=delta;
        shotCooldown=Math.Max(0,shotCooldown-delta);
        whistleCooldown=Math.Max(0,whistleCooldown-delta);
        actionCooldown=Math.Max(0,actionCooldown-delta);

        bool live=!game.ClubOpen && !game.StrategyLabOpen && (!game.IsRulesPreview || game.MediaLeagueActive);
        bool textBroadcast=game.QuickBroadcastOpen && !game.IsRulesPreview;
        GameAudio.UpdateCrowd(textBroadcast?0:match.Danger,Math.Max(HomeReaction,AwayReaction),live || textBroadcast,delta);
        if(!live || game.Replay.Playing)return;
        if(!openingWhistle && match.Seconds>0){openingWhistle=true;GameAudio.Whistle();}
        if(!halfWhistle && match.SecondHalf){halfWhistle=true;GameAudio.Whistle();}
        if(!finalWhistle && match.Finished){finalWhistle=true;GameAudio.Whistle();}
        if(match.Goals[0]>lastHomeGoals){HomeReaction=1;BooReaction=.3f;GameAudio.Cheer();}
        if(match.Goals[1]>lastAwayGoals){AwayReaction=1;BooReaction=.55f;GameAudio.Cheer();}
        lastHomeGoals=match.Goals[0];lastAwayGoals=match.Goals[1];

        for(int i=0;i<match.Players.Count && i<kicks.Length;i++)
        {
            var player=match.Players[i];
            if(player.HeaderAt>headers[i]){headers[i]=player.HeaderAt;GameAudio.Header();}
            if(player.LastKickAt<=kicks[i])continue;
            kicks[i]=player.LastKickAt;
            if(player.LastKickWasShot)
            {
                if(shotCooldown>0)continue;
                shotCooldown=.22f;GameAudio.Kick(true,player.LastKickKind);
                if(player.Team==0)HomeReaction=Math.Max(HomeReaction,.4f);
                else AwayReaction=Math.Max(AwayReaction,.4f);
            }
            else if(actionCooldown<=0){actionCooldown=.16f;GameAudio.Kick(false,player.LastKickKind);}
        }
        foreach(var effect in match.Effects)
        {
            if(effect.Id<=lastEffect)continue;
            lastEffect=effect.Id;
            switch(effect.Kind)
            {
                case "offside":
                case "yellow-card":
                case "red-card":
                    BooReaction=Math.Max(BooReaction,effect.Kind=="red-card"?1:.7f);
                    if(whistleCooldown<=0){GameAudio.Whistle();whistleCooldown=1.2f;}
                    if(effect.Kind is "yellow-card" or "red-card")GameAudio.Card(effect.Kind=="red-card");
                    break;
                case "save":
                case "block":
                    if(actionCooldown<=0){GameAudio.Save();actionCooldown=.16f;}
                    if(effect.Kind=="save")GameAudio.Cheer();
                    if(effect.Player>=0 && effect.Player<match.Players.Count)
                    {
                        if(match.Players[effect.Player].Team==0)HomeReaction=Math.Max(HomeReaction,.55f);
                        else AwayReaction=Math.Max(AwayReaction,.55f);
                    }
                    break;
                case "tackle":
                case "intercept":
                    if(actionCooldown<=0){GameAudio.Tackle();actionCooldown=.13f;}
                    break;
            }
        }
    }
}