stepstead-client/autoload/pedometer.gd
jts 6a1706051d İlk commit: Stepstead Godot istemci (4.6 Mobile)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:31:35 +03:00

196 lines
6.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()