using UnityEngine; using GeoSus.Client; using System; using System.Collections; namespace Subsystems { internal class CoroutineHost : MonoBehaviour { public CoroutineHost() { } } internal enum GPSState { Uninitialized, Initializing, Running, Failed } public static class PositonExtensions { public static Position ToLocal(this Position position, Position center) { double latDiff = position.Lat - center.Lat; double lonDiff = position.Lon - center.Lon; double metersPerDegreeLat = 111320.0; double metersPerDegreeLon = 111320.0 * Math.Cos(center.Lat * Math.PI / 180.0); float x = (float)(lonDiff * metersPerDegreeLon); float z = (float)(latDiff * metersPerDegreeLat); return new Position(z, x); } public static Vector3 ToLocalVector3(this Position position, Position center) { return position.ToLocal(center).ToVector3(); //TODO: Implementace v subsystemech } public static Vector3 ToVector3(this Position position) { return new Vector3((float)position.Lon, 0, (float)position.Lat); //TODO: Implementace v subsystemech } public static double DistanceTo(this Vector3 pos, Vector3 other) { return Math.Sqrt((other.x - pos.x) * (other.x - pos.x) + (other.z - pos.z) * (other.z - pos.z)); } } public class GameManager_Input { private GameClient _gameClient; private Position _currentPosition; private Position _lastSentPosition; private GameObject _player; private bool _testMode; private GPSState _GPSState = GPSState.Uninitialized; private float _speed = 0.00001f; private Position _mapCenter; private CoroutineHost _coroutineHost; private int _gpsRetryCount = 0; private const int _maxGpsRetries = 5; private float _lastPositionSendTime; private const float _positionKeepAliveSeconds = 1.0f; /// Last known GPS position (for CreateLobby centre point) public Position? LastKnownPosition => _currentPosition.Lat != 0 || _currentPosition.Lon != 0 ? _currentPosition : (Position?)null; public GameManager_Input(GameClient gameClient, GameObject player, bool testMode) { _gameClient = gameClient; _player = player; _testMode = testMode; // CoroutineHost needs a MonoBehaviour on a real GameObject var hostGO = new UnityEngine.GameObject("_CoroutineHost"); UnityEngine.Object.DontDestroyOnLoad(hostGO); _coroutineHost = hostGO.AddComponent(); } /// Called from OnSceneLoaded when Client.unity loads so the /// Player capsule (which lives in Client.unity) can be wired at runtime. public void SetPlayerObject(GameObject player) { _player = player; } /// /// Kick off GPS initialization if it hasn't started yet. Safe to call /// repeatedly. Hosts must call this from the lobby setup screen so /// that by the time they click "Create Lobby" we have a real GPS /// fix to use as the play-area center, instead of falling back to /// the hardcoded coordinates. /// public void EnsureGPSStarted() { if (_testMode) return; if (_coroutineHost == null) return; if (_GPSState == GPSState.Uninitialized) _coroutineHost.StartCoroutine(InitiallizeGPS()); } public void positionCheck() { var state = _gameClient?.CurrentLobbyState; if (state == null || state.Phase != GamePhase.Playing) return; try { if (_testMode) { if (_currentPosition == new Position(0, 0)) { if (state.MapData == null) return; //Init blok _currentPosition = state.MapData.Center; _mapCenter = state.MapData.Center; _lastSentPosition = _currentPosition; } TestPlayerPosition(); } else { if (_GPSState == GPSState.Uninitialized) { _coroutineHost.StartCoroutine(InitiallizeGPS()); return; } else if (_GPSState == GPSState.Initializing) { return; } else if (_GPSState == GPSState.Running) { EnsureMapCenter(); TrySendCurrentPosition(); } else { Debug.Log("GPS failed, trying again..."); if (_gpsRetryCount < _maxGpsRetries) { _gpsRetryCount++; _GPSState = GPSState.Uninitialized; } else { Debug.LogWarning("GPS unavailable after max retries. Using last known position."); // Keep _GPSState = Failed so we stop retrying } } } } catch (Exception ex) { Debug.LogWarning($"[Input] positionCheck failed: {ex.Message}"); } } private void EnsureMapCenter() { if (_mapCenter.Lat != 0 || _mapCenter.Lon != 0) return; var md = _gameClient?.CurrentLobbyState?.MapData; if (md != null) _mapCenter = md.Center; } private void TrySendCurrentPosition() { bool moved = _currentPosition != _lastSentPosition; bool keepAliveDue = (Time.time - _lastPositionSendTime) >= _positionKeepAliveSeconds; if (!moved && !keepAliveDue) return; var previous = _lastSentPosition; _gameClient.UpdatePosition(_currentPosition); _lastSentPosition = _currentPosition; _lastPositionSendTime = Time.time; if (_player == null || (_mapCenter.Lat == 0 && _mapCenter.Lon == 0)) return; var localCurrent = _currentPosition.ToLocalVector3(_mapCenter); _player.transform.position = localCurrent; if (previous.Lat == 0 && previous.Lon == 0) return; var heading = CalculateHeading(previous.ToLocalVector3(_mapCenter), localCurrent); if (heading.HasValue) _player.transform.rotation = Quaternion.Euler(0, (float)heading.Value, 0); } private void TestPlayerPosition() { double x = Input.GetAxis("Horizontal"); double y = Input.GetAxis("Vertical"); Debug.Log($"Input: {x}, {y}"); _currentPosition = new Position( _lastSentPosition.Lat + y * _speed, _lastSentPosition.Lon + x * _speed); Debug.Log($"Current Position: {_currentPosition.Lat}, {_currentPosition.Lon}"); var localCurrent = _currentPosition.ToLocalVector3(_mapCenter); Debug.Log($"Local Current Position: {localCurrent}"); var heading = CalculateHeading(_lastSentPosition.ToLocalVector3(_mapCenter), localCurrent); if (heading != null) { Debug.Log($"Heading: {heading}"); _player.transform.rotation = Quaternion.Euler(0, (float)heading, 0); } _player.transform.position = localCurrent; try { TrySendCurrentPosition(); } catch { _gameClient.UpdatePosition(_currentPosition); _lastSentPosition = _currentPosition; } } private double? CalculateHeading(Vector3 first, Vector3 second) { if ((first - second).magnitude < 0.0001f) return null; float dx = second.x - first.x; float dz = second.z - first.z; float heading = Mathf.Atan2(dx, dz) * Mathf.Rad2Deg; if (heading < 0) heading += 360f; return heading; } IEnumerator InitiallizeGPS() { _GPSState = GPSState.Initializing; #if UNITY_ANDROID // Request fine location permission if not already granted if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.FineLocation)) { UnityEngine.Android.Permission.RequestUserPermission(UnityEngine.Android.Permission.FineLocation); // Wait up to 10 seconds for user to respond to the permission dialog float waited = 0f; while (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.FineLocation) && waited < 10f) { yield return new WaitForSeconds(0.5f); waited += 0.5f; } } #endif if (!Input.location.isEnabledByUser) { Debug.LogError("Location not enabled on device or app does not have permission to access location"); _GPSState = GPSState.Failed; yield break; } // Starts the location service. float desiredAccuracyInMeters = 5f; float updateDistanceInMeters = 1f; Input.location.Start(desiredAccuracyInMeters, updateDistanceInMeters); // Waits until the location service initializes int maxWait = 20; while (Input.location.status == LocationServiceStatus.Initializing && maxWait > 0) { yield return new WaitForSeconds(1); maxWait--; } // If the service didn't initialize in 20 seconds this cancels location service use. if (maxWait < 1) { _GPSState = GPSState.Failed; Debug.LogError("Timed out"); yield break; } if (Input.location.status == LocationServiceStatus.Failed) { _GPSState = GPSState.Failed; Debug.LogError("Unable to determine device location"); yield break; } _GPSState = GPSState.Running; _gpsRetryCount = 0; _coroutineHost.StartCoroutine(GPSService()); } IEnumerator GPSService() { while (_GPSState == GPSState.Running) { if (Input.location.status == LocationServiceStatus.Failed) { _GPSState = GPSState.Failed; Debug.LogError("Unable to determine device location"); yield break; } // Keep current GPS position fresh; sending is throttled in positionCheck(). var data = Input.location.lastData; _currentPosition = new Position(data.latitude, data.longitude); yield return new WaitForSeconds(0.5f); } } } }