extends Node ## Pedometer soyutlama katmanı. ## ## Platforma göre kaynak: ## - Android: "StepCounter" singleton (TYPE_STEP_COUNTER, reboot'tan beri kümülatif) ## - iOS: "Pedometer" singleton (CMPedometer, oturum kümülatif) ## - Editör / masaüstü: simülasyon (manuel/otomatik adım üretir) ## ## Native plugin sözleşmesi (her iki platform da aynı): ## bool is_available() ## void start() ## void stop() ## int get_step_count() # kümülatif sayaç (artar, reboot'ta sıfırlanabilir) ## ## İstemci kümülatif sayaçtan delta hesaplar, son senkronize değeri kalıcı ## tutar (user://pedometer.cfg) ve sunucuya yalnızca delta gönderir. ## Otorite sunucuda — sunucu dakika başı tavan uygular (anti-cheat). signal steps_synced(accepted: int, rejected: int, balance: int) signal raw_step_count(total: int) const SAVE_PATH := "user://pedometer.cfg" const POLL_INTERVAL := 5.0 # sn — sayaç okuma const SYNC_INTERVAL := 30.0 # sn — sunucuya gönderme # Editör/masaüstü simülasyonu: otomatik yürüyüş hızı (adım/dakika). # 0 = kapalı (sadece sim_add ile manuel). ~100/dk normal yürüyüş. var sim_walk_per_min := 0 var _plugin: Object = null var _platform := "" var _last_total := 0 # en son sunucuya senkronize edilen kümülatif değer var _current_total := 0 # cihazdan okunan güncel kümülatif değer var _pending := 0 # henüz gönderilmemiş delta var _running := false var _sim_total := 0 # editör simülasyon sayacı var _poll_timer: Timer var _sync_timer: Timer func _ready() -> void: _platform = Config.current_platform() _load_state() _init_plugin() _poll_timer = Timer.new() _poll_timer.wait_time = POLL_INTERVAL _poll_timer.timeout.connect(_on_poll) add_child(_poll_timer) _sync_timer = Timer.new() _sync_timer.wait_time = SYNC_INTERVAL _sync_timer.timeout.connect(_on_sync_tick) add_child(_sync_timer) func _init_plugin() -> void: var singleton_name := "" if _platform == Config.PLATFORM_ANDROID: singleton_name = "StepCounter" elif _platform == Config.PLATFORM_IOS: singleton_name = "Pedometer" if singleton_name != "" and Engine.has_singleton(singleton_name): _plugin = Engine.get_singleton(singleton_name) print("[Pedometer] native plugin yüklendi: ", singleton_name) else: _plugin = null print("[Pedometer] native plugin yok — simülasyon modu (", _platform, ")") func is_native() -> bool: return _plugin != null and _plugin.has_method("is_available") and _plugin.is_available() ## Adım dinlemeyi başlat (login + karakter yüklendikten sonra çağrılır). func start() -> void: if _running: return _running = true # Android 10+ çalışma zamanı izni if _platform == Config.PLATFORM_ANDROID: if not OS.request_permission("android.permission.ACTIVITY_RECOGNITION"): push_warning("[Pedometer] ACTIVITY_RECOGNITION izni reddedildi/bekliyor") if is_native() and _plugin.has_method("start"): _plugin.start() _poll_timer.start() _sync_timer.start() _on_poll() func stop() -> void: if not _running: return _running = false if is_native() and _plugin.has_method("stop"): _plugin.stop() _poll_timer.stop() _sync_timer.stop() func _read_device_total() -> int: if is_native() and _plugin.has_method("get_step_count"): return int(_plugin.get_step_count()) # simülasyon — otomatik yürüyüş açıksa her poll'da hız oranında ekle if sim_walk_per_min > 0: _sim_total += int(round(sim_walk_per_min * POLL_INTERVAL / 60.0)) return _sim_total ## Editör/test: sürekli yürüyüş simülasyonu aç/kapat (adım/dakika). ## Örn. Pedometer.sim_walk(100) → dakikada ~100 adım, otomatik sync'lenir. func sim_walk(steps_per_min: int) -> void: sim_walk_per_min = max(0, steps_per_min) ## Editör/test: manuel adım enjekte et (simülasyon sayacını ilerletir). func sim_add(steps: int) -> void: _sim_total += max(0, steps) _on_poll() func _on_poll() -> void: var total := _read_device_total() # reboot tespiti: sayaç geriye gittiyse delta'yı ham total kabul et if total < _last_total: _last_total = 0 _current_total = total _pending = max(0, _current_total - _last_total) raw_step_count.emit(_current_total) func _on_sync_tick() -> void: if _pending > 0: await sync_now() ## Bekleyen delta'yı sunucuya gönder. last_sync sonrası kalıcı kaydet. func sync_now() -> Dictionary: _on_poll() if _pending <= 0: return {"ok": true, "accepted": 0} if Auth.token == "": return {"ok": false, "error": "no_auth"} var delta := _pending var src := _platform if is_native() else "sim" var res: Dictionary = await Api.request("POST", "/world/sync-steps", {"delta": delta, "source": src}, true) if res.ok and res.body is Dictionary: var b: Dictionary = res.body # gönderdiğimiz delta'yı senkronize sayılan total'a işle _last_total += delta _pending = max(0, _current_total - _last_total) _save_state() if GameState.has_character(): GameState.character["step_balance"] = int(b["step_balance"]) GameState.character["step_lifetime"] = int(b.get("step_lifetime", 0)) GameState.character_loaded.emit(GameState.character) var acc: int = int(b.get("accepted", 0)) var rej: int = int(b.get("rejected", 0)) steps_synced.emit(acc, rej, int(b["step_balance"])) if rej > 0: push_warning("[Pedometer] %d adım reddedildi (tavan). reason=%s" % [rej, str(b.get("reason"))]) return {"ok": true, "accepted": acc, "rejected": rej} return {"ok": false, "error": "sync_failed", "status": res.get("status", 0)} func _load_state() -> void: var cfg := ConfigFile.new() if cfg.load(SAVE_PATH) == OK: _last_total = int(cfg.get_value("pedometer", "last_total", 0)) _sim_total = int(cfg.get_value("pedometer", "sim_total", 0)) func _save_state() -> void: var cfg := ConfigFile.new() cfg.set_value("pedometer", "last_total", _last_total) cfg.set_value("pedometer", "sim_total", _sim_total) cfg.save(SAVE_PATH) # Uygulama arka plandan döndüğünde hemen senkronize et (background adımları toparla) func _notification(what: int) -> void: if what == NOTIFICATION_APPLICATION_RESUMED: if _running: await sync_now() elif what == NOTIFICATION_APPLICATION_PAUSED: if _running: await sync_now()