<commit_before>﻿using System;
using AGS.API;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Diagnostics;
using System.Collections.Concurrent;

namespace AGS.Engine
{
    public class AGSWalkComponent : AGSComponent, IWalkComponent
    {
        private WalkInstruction _currentWalk;
        private ConcurrentQueue<WalkInstruction> _incomingInstructions;
        private IPathFinder _pathFinder;
        private List<IObject> _debugPath;
        private IFaceDirectionComponent _faceDirection;
        private IOutfitComponent _outfit;
        private IHasRoomComponent _room;
        private IDrawableInfoComponent _drawable;
        private IObjectFactory _objFactory;
        private ITranslate _translate;
        private ICutscene _cutscene;
        private IGameState _state;
        private IGameEvents _events;
        private IAnimationComponent _animation;
        private readonly IGLUtils _glUtils;

        public AGSWalkComponent(IPathFinder pathFinder, IObjectFactory objFactory, IGame game, IGLUtils glUtils)
        {
            _incomingInstructions = new ConcurrentQueue<WalkInstruction>();
            _state = game.State;
            _cutscene = _state.Cutscene;
            _events = game.Events;
            _pathFinder = pathFinder;
            _objFactory = objFactory;
            _glUtils = glUtils;

            _debugPath = new List<IObject>();
            AdjustWalkSpeedToScaleArea = true;
            MovementLinkedToAnimation = true;
            WalkStep = new PointF(8f, 8f);

            _events.OnRepeatedlyExecute.Subscribe(onRepeatedlyExecute);
        }

        public static int WalkLineTimeoutInMilliseconds = 15000; //15 seconds

        public override void Dispose()
        {
            base.Dispose();
            _events.OnRepeatedlyExecute.Unsubscribe(onRepeatedlyExecute);
            var instructions = _incomingInstructions;
            if (instructions != null)
            {
                while (instructions.TryDequeue(out var instruction))
                {
                    onWalkLineCompleted(instruction, instruction.CurrentLine, false);
                }
            }
        }

        public override void Init()
        {
            base.Init();
            Entity.Bind<IAnimationComponent>(c => _animation = c, _ => _animation = null);
            Entity.Bind<ITranslateComponent>(c => _translate = c, _ => _translate = null);
            Entity.Bind<IOutfitComponent>(c => _outfit = c, _ => _outfit = null);
            Entity.Bind<IHasRoomComponent>(c => _room = c, _ => _room = null);
            Entity.Bind<IDrawableInfoComponent>(c => _drawable = c, _ => _drawable = null);
            Entity.Bind<IFaceDirectionComponent>(c => _faceDirection = c, _ => _faceDirection = null);
        }

        #region IWalkBehavior implementation

        public async Task<bool> WalkAsync(Position position, bool walkAnywhere = false)
        {
            return await walkAsync(position, walkAnywhere, walkAnywhere);
        }

        public async Task<bool> WalkStraightAsync(Position position)
        {
            return await walkAsync(position, true, false);
        }

        public async Task StopWalkingAsync()
        {
            await addNewInstructionAsync((0, 0), false, false, true);
        }

        public void PlaceOnWalkableArea()
        {
            PointF current = new PointF(_translate.X, _translate.Y);
            PointF? closestPoint = getClosestWalkablePoint(current);
            if (closestPoint != null)
            {
                _translate.Position = (closestPoint.Value.X, closestPoint.Value.Y);
            }
        }

        public PointF WalkStep { get; set; }

        public bool AdjustWalkSpeedToScaleArea { get; set; }

        public bool MovementLinkedToAnimation { get; set; }

        public bool IsWalking
        {
            get
            {
                Task task = _currentWalk?.OnCompletion.Task;
                if (task == null) return false;
                return (!task.IsCompleted && !task.IsCanceled && !task.IsFaulted);
            }
        }

        public Position WalkDestination { get; private set; }

        public bool DebugDrawWalkPath { get; set; }

        #endregion

        private void onRepeatedlyExecute()
        {
            WalkInstruction currentWalk = getWalkInstruction();
            WalkLineInstruction currentLine = currentWalk?.CurrentLine;
            if (currentLine == null) return;

            if (currentWalk.CancelToken.IsCancellationRequested ||
                (!currentWalk.WalkAnywhere && !isWalkable(_translate.Position)) || _room?.Room != currentWalk.Room)
            {
                onWalkLineCompleted(currentWalk, currentLine, false);
                return;
            }
            if (currentLine.NumSteps <= 1f)
            {
                onWalkLineCompleted(currentWalk, currentLine, true);
                return;
            }
            if (_cutscene.IsSkipping)
            {
                _translate.Position = currentLine.Destination;
                onWalkLineCompleted(currentWalk, currentLine, true);
                return;
            }
            PointF walkSpeed = adjustWalkSpeed(WalkStep);
            float xStep = currentLine.XStep * walkSpeed.X;
            float yStep = currentLine.YStep * walkSpeed.Y;
            if (MovementLinkedToAnimation && _animation != null && _animation.Animation.Frames.Count > 1 &&
                _animation.Animation.Sprite == currentLine.LastFrame)
            {
                //If the movement is linked to the animation and the animation speed is slower the the viewport movement, it can lead to flickering
                //so we do a smooth movement for this scenario.
                (var compensateX, var compensateY) = currentLine.Compensate;
                (var lastViewportX, var lastViewportY) = currentLine.Viewport;
                var candidateX = _translate.X + compensateForViewScrollIfNeeded(_state.Viewport.X, xStep, ref compensateX, ref lastViewportX);
                var candidateY = _translate.Y + compensateForViewScrollIfNeeded(_state.Viewport.Y, yStep, ref compensateY, ref lastViewportY);
                if (currentWalk.WalkAnywhere || isWalkable(new Position(candidateX, candidateY)))
                {
                    currentLine.Compensate = (compensateX, compensateY);
                    _translate.Position = (candidateX, candidateY);
                    return;
                }
            }
            currentLine.LastFrame = _animation?.Animation.Sprite;
            currentLine.Viewport = (_state.Viewport.X, _state.Viewport.Y);

            currentLine.NumSteps -= Math.Abs(currentLine.IsBaseStepX ? xStep : yStep);
            if (currentLine.NumSteps >= 0f)
            {
                var candidateX = _translate.X + (xStep - currentLine.Compensate.x);
                var candidateY = _translate.Y + (yStep - currentLine.Compensate.y);
                if (currentWalk.WalkAnywhere || isWalkable(new Position(candidateX, candidateY)))
                {
                    _translate.Position = (candidateX, candidateY);
                }
                else
                {
                    onWalkLineCompleted(currentWalk, currentLine, false);
                    return;
                }
            }
            currentLine.Compensate = (0f, 0f);
        }

        private WalkInstruction getWalkInstruction()
        {
            WalkInstruction instruction = _currentWalk;
            while (_incomingInstructions.TryDequeue(out var nextInstruction))
            {
                if (instruction != null)
                {
                    onWalkLineCompleted(instruction, instruction.CurrentLine, false);
                    instruction.OnCompletion.TrySetResult(false);
                }
                if (nextInstruction != null)
                {<commit_msg>Revert the previous "fix" for the freeze<commit_after>﻿using System;
using AGS.API;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Diagnostics;
using System.Collections.Concurrent;

namespace AGS.Engine
{
    public class AGSWalkComponent : AGSComponent, IWalkComponent
    {
        private WalkInstruction _currentWalk;
        private ConcurrentQueue<WalkInstruction> _incomingInstructions;
        private IPathFinder _pathFinder;
        private List<IObject> _debugPath;
        private IFaceDirectionComponent _faceDirection;
        private IOutfitComponent _outfit;
        private IHasRoomComponent _room;
        private IDrawableInfoComponent _drawable;
        private IObjectFactory _objFactory;
        private ITranslate _translate;
        private ICutscene _cutscene;
        private IGameState _state;
        private IGameEvents _events;
        private IAnimationComponent _animation;
        private readonly IGLUtils _glUtils;

        public AGSWalkComponent(IPathFinder pathFinder, IObjectFactory objFactory, IGame game, IGLUtils glUtils)
        {
            _incomingInstructions = new ConcurrentQueue<WalkInstruction>();
            _state = game.State;
            _cutscene = _state.Cutscene;
            _events = game.Events;
            _pathFinder = pathFinder;
            _objFactory = objFactory;
            _glUtils = glUtils;

            _debugPath = new List<IObject>();
            AdjustWalkSpeedToScaleArea = true;
            MovementLinkedToAnimation = true;
            WalkStep = new PointF(8f, 8f);

            _events.OnRepeatedlyExecute.Subscribe(onRepeatedlyExecute);
        }

        public static int WalkLineTimeoutInMilliseconds = 15000; //15 seconds

        public override void Dispose()
        {
            base.Dispose();
            _events.OnRepeatedlyExecute.Unsubscribe(onRepeatedlyExecute);
            var instructions = _incomingInstructions;
            if (instructions != null)
            {
                while (instructions.TryDequeue(out var instruction))
                {
                    onWalkLineCompleted(instruction, instruction.CurrentLine, false);
                }
            }
        }

        public override void Init()
        {
            base.Init();
            Entity.Bind<IAnimationComponent>(c => _animation = c, _ => _animation = null);
            Entity.Bind<ITranslateComponent>(c => _translate = c, _ => _translate = null);
            Entity.Bind<IOutfitComponent>(c => _outfit = c, _ => _outfit = null);
            Entity.Bind<IHasRoomComponent>(c => _room = c, _ => _room = null);
            Entity.Bind<IDrawableInfoComponent>(c => _drawable = c, _ => _drawable = null);
            Entity.Bind<IFaceDirectionComponent>(c => _faceDirection = c, _ => _faceDirection = null);
        }

        #region IWalkBehavior implementation

        public async Task<bool> WalkAsync(Position position, bool walkAnywhere = false)
        {
            return await walkAsync(position, walkAnywhere, walkAnywhere);
        }

        public async Task<bool> WalkStraightAsync(Position position)
        {
            return await walkAsync(position, true, false);
        }

        public async Task StopWalkingAsync()
        {
            await addNewInstructionAsync((0, 0), false, false, true);
        }

        public void PlaceOnWalkableArea()
        {
            PointF current = new PointF(_translate.X, _translate.Y);
            PointF? closestPoint = getClosestWalkablePoint(current);
            if (closestPoint != null)
            {
                _translate.Position = (closestPoint.Value.X, closestPoint.Value.Y);
            }
        }

        public PointF WalkStep { get; set; }

        public bool AdjustWalkSpeedToScaleArea { get; set; }

        public bool MovementLinkedToAnimation { get; set; }

        public bool IsWalking
        {
            get
            {
                Task task = _currentWalk?.OnCompletion.Task;
                if (task == null) return false;
                return (!task.IsCompleted && !task.IsCanceled && !task.IsFaulted);
            }
        }

        public Position WalkDestination { get; private set; }

        public bool DebugDrawWalkPath { get; set; }

        #endregion

        private void onRepeatedlyExecute()
        {
            WalkInstruction currentWalk = getWalkInstruction();
            WalkLineInstruction currentLine = currentWalk?.CurrentLine;
            if (currentLine == null) return;

            if (currentWalk.CancelToken.IsCancellationRequested ||
                (!currentWalk.WalkAnywhere && !isWalkable(_translate.Position)) || _room?.Room != currentWalk.Room)
            {
                onWalkLineCompleted(currentWalk, currentLine, false);
                return;
            }
            if (currentLine.NumSteps <= 1f)
            {
                onWalkLineCompleted(currentWalk, currentLine, true);
                return;
            }
            if (_cutscene.IsSkipping)
            {
                _translate.Position = currentLine.Destination;
                onWalkLineCompleted(currentWalk, currentLine, true);
                return;
            }
            PointF walkSpeed = adjustWalkSpeed(WalkStep);
            float xStep = currentLine.XStep * walkSpeed.X;
            float yStep = currentLine.YStep * walkSpeed.Y;
            if (MovementLinkedToAnimation && _animation != null && _animation.Animation.Frames.Count > 1 &&
                _animation.Animation.Sprite == currentLine.LastFrame)
            {
                //If the movement is linked to the animation and the animation speed is slower the the viewport movement, it can lead to flickering
                //so we do a smooth movement for this scenario.
                (var compensateX, var compensateY) = currentLine.Compensate;
                (var lastViewportX, var lastViewportY) = currentLine.Viewport;
                var candidateX = _translate.X + compensateForViewScrollIfNeeded(_state.Viewport.X, xStep, ref compensateX, ref lastViewportX);
                var candidateY = _translate.Y + compensateForViewScrollIfNeeded(_state.Viewport.Y, yStep, ref compensateY, ref lastViewportY);
                if (currentWalk.WalkAnywhere || isWalkable(new Position(candidateX, candidateY)))
                {
                    currentLine.Compensate = (compensateX, compensateY);
                    _translate.Position = (candidateX, candidateY);
                    return;
                }
            }
            currentLine.LastFrame = _animation?.Animation.Sprite;
            currentLine.Viewport = (_state.Viewport.X, _state.Viewport.Y);

            currentLine.NumSteps -= Math.Abs(currentLine.IsBaseStepX ? xStep : yStep);
            if (currentLine.NumSteps >= 0f)
            {
                var candidateX = _translate.X + (xStep - currentLine.Compensate.x);
                var candidateY = _translate.Y + (yStep - currentLine.Compensate.y);
                if (currentWalk.WalkAnywhere || isWalkable(new Position(candidateX, candidateY)))
                {
                    _translate.Position = (candidateX, candidateY);
                }
                else
                {
                    onWalkLineCompleted(currentWalk, currentLine, false);
                    return;
                }
            }
            currentLine.Compensate = (0f, 0f);
        }

        private WalkInstruction getWalkInstruction()
        {
            WalkInstruction instruction = _currentWalk;
            while (_incomingInstructions.TryDequeue(out var nextInstruction))
            {
                if (instruction != null)
                {
                    onWalkLineCompleted(instruction, instruction.CurrentLine, false);
                }
                if (nextInstruction != null)
                {
{'fraction_non_alphanumeric': 0.07884966333144547, 'fraction_numerical': 0.0020137184569882323, 'mean_word_length': 1.9321033210332104, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5397359', 'n_tokens_mistral': 4067, 'n_tokens_neox': 3927, 'n_words': 903}