196 lines
6.1 KiB
GDScript
196 lines
6.1 KiB
GDScript
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()
|