@@ -63,15 +63,11 @@ func (e *Engine) Tick(delta time.Duration) {
6363 if e .State .MatchTimeStart .After (time .Unix (0 , 0 )) {
6464 e .State .MatchDuration = e .TimeProvider ().Sub (e .State .MatchTimeStart )
6565 }
66- if e .State .LackOfProgressDeadline .After (time .Unix (0 , 0 )) {
67- e .State .LackOfProgressTimeRemaining = e .RemainingLackOfProgressTime ( )
66+ if e .State .CurrentActionDeadline .After (time .Unix (0 , 0 )) {
67+ e .State .CurrentActionTimeRemaining = e .State . CurrentActionDeadline . Sub ( e . TimeProvider () )
6868 }
6969}
7070
71- func (e * Engine ) RemainingLackOfProgressTime () time.Duration {
72- return e .State .LackOfProgressDeadline .Sub (e .TimeProvider ())
73- }
74-
7571func (e * Engine ) updateTimes (delta time.Duration ) {
7672 if e .countStageTime () {
7773 e .State .StageTimeElapsed += delta
@@ -123,26 +119,33 @@ func (e *Engine) SendCommand(command RefCommand, forTeam Team) {
123119 }
124120
125121 if command .ContinuesGame () {
122+ // reset game events
126123 if len (e .State .GameEvents ) > 0 {
127124 e .State .GameEvents = []* GameEvent {}
128125 }
126+ // reset game event proposals
129127 if len (e .State .GameEventProposals ) > 0 {
130128 e .State .GameEventProposals = []* GameEventProposal {}
131129 }
130+ // reset ball placement pos and follow ups
132131 e .State .PlacementPos = nil
133132 e .State .NextCommand = CommandUnknown
134133 e .State .NextCommandFor = TeamUnknown
135134
136- if command != CommandKickoff && command != CommandPenalty {
137- e .State .LackOfProgressTimeRemaining = e .config .LackOfProgressTimeout
138- if command == CommandIndirect || command == CommandDirect {
139- e .State .LackOfProgressTimeRemaining = e .config .LackOfProgressFreeKickTimeout [e .State .Division ]
140- }
141- e .State .LackOfProgressDeadline = e .TimeProvider ().Add (e .State .LackOfProgressTimeRemaining )
135+ // update current action timeout
136+ if command == CommandIndirect || command == CommandDirect {
137+ e .setCurrentActionTimeout (e .config .FreeKickTime [e .State .Division ])
138+ } else if command != CommandKickoff && command != CommandPenalty {
139+ e .setCurrentActionTimeout (e .config .GeneralTime )
142140 }
143141 }
144142}
145143
144+ func (e * Engine ) setCurrentActionTimeout (timeout time.Duration ) {
145+ e .State .CurrentActionTimeRemaining = timeout
146+ e .State .CurrentActionDeadline = e .TimeProvider ().Add (e .State .CurrentActionTimeRemaining )
147+ }
148+
146149func (e * Engine ) AddGameEvent (gameEvent GameEvent ) {
147150 e .State .GameEvents = append (e .State .GameEvents , & gameEvent )
148151 e .LogGameEvent (gameEvent )
@@ -172,57 +175,57 @@ func (e *Engine) CommandForEvent(event *GameEvent) (command RefCommand, forTeam
172175 return
173176 }
174177
175- forTeam = event .ByTeam ().Opposite ()
176-
177- switch event .Type {
178- case
179- GameEventBallLeftFieldTouchLine ,
180- GameEventAimlessKick ,
181- GameEventBotKickedBallTooFast ,
182- GameEventBotDribbledBallTooFar ,
183- GameEventAttackerDoubleTouchedBall ,
184- GameEventAttackerInDefenseArea ,
185- GameEventAttackerTouchedKeeper ,
186- GameEventKickTimeout ,
187- GameEventKeeperHeldBall ,
188- GameEventPlacementFailedByTeamInFavor :
178+ if e .State .bothTeamsCanPlaceBall () && e .State .ballPlacementFailedBefore () && event .Type .resultsFromBallLeavingField () {
179+ // A failed placement will result in an indirect free kick for the opposing team.
189180 command = CommandIndirect
190- case
191- GameEventBallLeftFieldGoalLine ,
192- GameEventIndirectGoal ,
193- GameEventPossibleGoal ,
194- GameEventChippedGoal ,
195- GameEventDefenderInDefenseAreaPartially ,
196- GameEventAttackerTooCloseToDefenseArea ,
197- GameEventBotTippedOver ,
198- GameEventBotCrashUnique ,
199- GameEventBotPushedBot ,
200- GameEventBotHeldBallDeliberately :
201- command = CommandDirect
202- case
203- GameEventGoal :
204- command = CommandKickoff
205- case
206- GameEventBotCrashDrawn ,
207- GameEventNoProgressInGame :
208- command = CommandForceStart
209- case
210- GameEventDefenderInDefenseArea ,
211- GameEventMultipleCards :
212- command = CommandPenalty
213- case
214- GameEventBotInterferedPlacement ,
215- GameEventDefenderTooCloseToKickPoint ,
216- GameEventPlacementFailedByOpponent :
217- command , err = e .LastGameStartCommand ()
218- default :
219- err = errors .Errorf ("Unhandled game event: %v" , e .State .GameEvents )
220- }
181+ forTeam = event .ByTeam ()
182+ } else {
183+ forTeam = event .ByTeam ().Opposite ()
184+ switch event .Type {
185+ case
186+ GameEventBallLeftFieldTouchLine ,
187+ GameEventAimlessKick ,
188+ GameEventBotKickedBallTooFast ,
189+ GameEventBotDribbledBallTooFar ,
190+ GameEventAttackerDoubleTouchedBall ,
191+ GameEventAttackerInDefenseArea ,
192+ GameEventAttackerTouchedKeeper ,
193+ GameEventKickTimeout ,
194+ GameEventKeeperHeldBall :
195+ command = CommandIndirect
196+ case
197+ GameEventBallLeftFieldGoalLine ,
198+ GameEventIndirectGoal ,
199+ GameEventPossibleGoal ,
200+ GameEventChippedGoal ,
201+ GameEventDefenderInDefenseAreaPartially ,
202+ GameEventAttackerTooCloseToDefenseArea ,
203+ GameEventBotTippedOver ,
204+ GameEventBotCrashUnique ,
205+ GameEventBotPushedBot ,
206+ GameEventBotHeldBallDeliberately :
207+ command = CommandDirect
208+ case
209+ GameEventGoal :
210+ command = CommandKickoff
211+ case
212+ GameEventBotCrashDrawn ,
213+ GameEventNoProgressInGame :
214+ command = CommandForceStart
215+ case
216+ GameEventDefenderInDefenseArea ,
217+ GameEventMultipleCards :
218+ command = CommandPenalty
219+ default :
220+ err = errors .Errorf ("Unhandled game event: %v" , e .State .GameEvents )
221+ }
221222
222- if e .State .Division == config .DivA && command .IsFreeKick () && ! e .State .TeamState [forTeam ].CanPlaceBall {
223- // in division A, if the team in favor can not place the ball (because of too many failures), free kicks are awarded to the other team
224- forTeam = forTeam .Opposite ()
225- command = CommandIndirect
223+ if e .State .Division == config .DivA && // For division A
224+ ! e .State .TeamState [forTeam ].CanPlaceBall && // If team in favor can not place the ball
225+ event .Type .resultsFromBallLeavingField () { // event is caused by the ball leaving the field
226+ // All free kicks that were a result of the ball leaving the field, are awarded to the opposing team.
227+ forTeam = forTeam .Opposite ()
228+ }
226229 }
227230
228231 if command .NeedsTeam () && forTeam .Unknown () {
@@ -234,18 +237,35 @@ func (e *Engine) CommandForEvent(event *GameEvent) (command RefCommand, forTeam
234237 return
235238}
236239
237- func (e * Engine ) LastGameStartCommand () (RefCommand , error ) {
238- for i := len (e .UiProtocol ) - 1 ; i >= 0 ; i -- {
239- event := e .UiProtocol [i ]
240- if event .Type == UiProtocolCommand {
241- cmd := RefCommand (event .Name )
242- switch cmd {
243- case CommandPenalty , CommandKickoff , CommandIndirect , CommandDirect :
244- return cmd , nil
245- }
240+ func (g GameEventType ) resultsFromBallLeavingField () bool {
241+ switch g {
242+ case
243+ GameEventBallLeftFieldTouchLine ,
244+ GameEventBallLeftFieldGoalLine ,
245+ GameEventAimlessKick ,
246+ GameEventIndirectGoal ,
247+ GameEventPossibleGoal ,
248+ GameEventChippedGoal :
249+ return true
250+ }
251+ return false
252+ }
253+
254+ func (s * State ) bothTeamsCanPlaceBall () bool {
255+ return s .TeamState [TeamYellow ].CanPlaceBall && s .TeamState [TeamBlue ].CanPlaceBall
256+ }
257+
258+ func (s * State ) noTeamCanPlaceBall () bool {
259+ return ! s .TeamState [TeamYellow ].CanPlaceBall && ! s .TeamState [TeamBlue ].CanPlaceBall
260+ }
261+
262+ func (s * State ) ballPlacementFailedBefore () bool {
263+ for _ , gameEvent := range s .GameEvents {
264+ if gameEvent .Type == GameEventPlacementFailedByTeamInFavor {
265+ return true
246266 }
247267 }
248- return "" , errors . New ( "No last game start command found." )
268+ return false
249269}
250270
251271func (e * Engine ) Process (event Event ) error {
@@ -613,23 +633,20 @@ func (e *Engine) processGameEvent(event *GameEvent) error {
613633 e .State .TeamState [team ].Goals ++
614634 }
615635
636+ if event .Type == GameEventBotInterferedPlacement {
637+ // reset ball placement timer
638+ e .setCurrentActionTimeout (e .config .BallPlacementTime )
639+ }
640+
616641 e .State .PlacementPos = e .BallPlacementPos ()
617642
618- if e .State .GameState () != GameStateHalted && ! event .IsSkipped () && ! event .IsSecondary () {
619- if event .Type == GameEventPossibleGoal || event .Type == GameEventPlacementFailedByOpponent || e .allTeamsFailedPlacement () {
620- e .SendCommand (CommandHalt , "" )
621- } else if e .State .PlacementPos != nil && (event .ByTeam () == TeamBlue || event .ByTeam () == TeamYellow ) {
622- teamInFavor := event .ByTeam ().Opposite ()
623- if e .State .TeamState [teamInFavor ].CanPlaceBall {
624- e .SendCommand (CommandBallPlacement , teamInFavor )
625- } else if e .State .TeamState [teamInFavor .Opposite ()].CanPlaceBall {
626- e .SendCommand (CommandBallPlacement , teamInFavor .Opposite ())
627- } else if e .State .GameState () != GameStateStopped {
628- e .SendCommand (CommandStop , "" )
629- }
630- } else if e .State .GameState () != GameStateStopped {
631- e .SendCommand (CommandStop , "" )
632- }
643+ if e .State .GameState () == GameStateHalted {
644+ log .Printf ("Warn: Received a game event while halted: %v" , event )
645+ } else if event .Type == GameEventDefenderTooCloseToKickPoint {
646+ // stop the game and let bots move away from the ball first. The autoRef will continue the game afterwards
647+ e .SendCommand (CommandStop , "" )
648+ } else if ! event .IsSkipped () && ! event .IsSecondary () {
649+ e .placeBall (event )
633650 } else if e .State .AutoContinue && event .IsContinueGame () {
634651 e .Continue ()
635652 }
@@ -638,6 +655,30 @@ func (e *Engine) processGameEvent(event *GameEvent) error {
638655 return nil
639656}
640657
658+ func (e * Engine ) placeBall (event * GameEvent ) {
659+ teamInFavor := event .ByTeam ().Opposite ()
660+ if e .State .PlacementPos == nil || teamInFavor .Unknown () || e .State .noTeamCanPlaceBall () {
661+ // placement not possible, human ref must help out
662+ e .SendCommand (CommandHalt , "" )
663+ return
664+ } else if e .State .Division == config .DivB && // For division B
665+ ! e .State .TeamState [teamInFavor ].CanPlaceBall { // If team in favor can not place the ball
666+ // Rule: [...] the team is allowed to bring the ball into play, after the ball was placed by the opposing team.
667+ e .SendCommand (CommandBallPlacement , teamInFavor .Opposite ())
668+ } else if e .State .bothTeamsCanPlaceBall () && e .State .ballPlacementFailedBefore () && event .Type .resultsFromBallLeavingField () {
669+ // Rule: A failed placement will result in an indirect free kick for the opposing team.
670+ e .SendCommand (CommandBallPlacement , teamInFavor .Opposite ())
671+ } else if e .State .Division == config .DivA && // For division A
672+ ! e .State .TeamState [teamInFavor ].CanPlaceBall && // If team in favor can not place the ball
673+ event .Type .resultsFromBallLeavingField () {
674+ // Rule: All free kicks that were a result of the ball leaving the field, are awarded to the opposing team.
675+ e .SendCommand (CommandBallPlacement , teamInFavor .Opposite ())
676+ } else {
677+ e .SendCommand (CommandBallPlacement , teamInFavor )
678+ }
679+ e .setCurrentActionTimeout (e .config .BallPlacementTime )
680+ }
681+
641682func (e * Engine ) allTeamsFailedPlacement () bool {
642683 possibleFailures := 0
643684 if e .State .TeamState [TeamYellow ].CanPlaceBall {
0 commit comments