TTRPG Companion
Bir masaüstü kampanyası için gerçek zamanlı masa — ve kimsenin güvenmek zorunda olmadığı bir sunucu.

- Rolüm
- Tek geliştirici — API, gerçek zamanlı hub, kural motoru ve istemci.
- Durum
- Canlı
Problem
Bir masaüstü kampanyası yürütmek; canları, inisiyatif sırasını, envanteri, durum etkilerini ve ilişkileri kâğıt fişler ile grup sohbeti arasında takip etmek demek. Masanın bir kısmı uzaktayken bütün o ortak durum tek bir kişinin aklında yaşıyor — her zar atışı da öyle, ve geri kalan herkesin buna inanmaktan başka seçeneği yok.
Ne yaptım
Arkadaşlarımla yürüttüğüm kampanya için sıra tabanlı bir yardımcı. Tek bir Expo istemcisi — iOS, Android ve react-native-web sayesinde düz bir tarayıcı bağlantısı — SignalR hub'ı üzerinden bir .NET 8 API ile konuşuyor; böylece her oyuncu aynı fişleri, aynı inisiyatif sırasını ve aynı savaş turunu olduğu anda görüyor. Kurallar sunucuda: zar, savaş fazları, özellikler, durum etkileri, tetiklenen tepkiler, arena düzenleyicileri ve bir gün-dinlenme döngüsünü kapsayan kırk civarı domain servisi.
Nasıl görünüyor

Devam eden bir çekişmeli saldırı. Saldıran zarını giriyor, savunma karşı oyuncunun cihazında açılıyor ve sonucu sunucu belirliyor. 
Üç oynanabilir karakter; her birinin statları ve hikâyenin içinden geldiği kısım. 
Her oyuncunun canlı güncellendiğini gördüğü sayfa — köşedeki rozet süs değil, SignalR bağlantısı.
Mimari
İstemci
iOS, Android ve tarayıcı için tek kod tabanı. Asıl kullanılan web sürümü: grup sohbetine atılan bir bağlantı, beş kişiden bir şey kurmasını istemekten iyidir.
- Expo
- React Native
- expo-router
- react-native-web
Gerçek zamanlı hub
Masadaki her eylem — katılmak, zar atmak, saldırmak, turu bitirmek — odaya yayınlanan bir hub metodu. Yeniden katılmak, kaçırılanı tekrar oynatmak yerine güncel durumu okuyor.
- SignalR
- .NET 8
Kural motoru
Zar, savaş fazları, özellikler, durum etkileri, tetiklenen tepkiler ve arena düzenleyicileri. Her kural bir servis; böylece yeni bir mekanik, mevcut bir dosyada yeni bir dal değil yeni bir dosya oluyor.
- C#
- Domain services
Kalıcılık
Odalar, karakterler, inisiyatif ve yürüyen savaş turu bellekte değil satırlarda. Lokalde SQLite, dağıtımda PostgreSQL; aynı context'in arkasında.
- EF Core 8
- PostgreSQL
- SQLite
Kararlar ve ödünleşmeler
Zarı sunucu atıyor
Kendi sayısını üreten bir istemci, daha iyi sayılar üretmeye ikna edilebilen bir istemcidir. Ham d20 sunucuda atılıyor; stat, ekipman ve ilişki düzenleyicileri de orada uygulanıyor — böylece masaya ulaşan sonuç, yolda kimsenin düzenlemiş olamayacağı bir sonuç oluyor.
Ama gerçek zar da sayılıyor
Fiziksel masadaki insanlar zarlarını kaldırmayacak ve onlara kaldırmalarını söyleyen bir uygulama kapatılan bir uygulamadır. Sunucu atışının yanında, kendi attığınız sayıyı girmek için bir yol var; aynı kurallar ona da işliyor. Hile koruması uzaktaki oyuncuları, odadakileri disipline etmeden savunuyor.
Bağlantılar bellekte, oyun veritabanında
SignalR bağlantıları tek kullanımlık — telefon uyur, tünel düşer, bir dağıtım süreci yeniden başlatır. Onları doğruluk kaynağı saymak, kopan bir bağlantının kaybedilen bir oturum olması demekti. Bunun yerine oda durumu kalıcılaştırılıyor; böylece yeniden bağlanmak bir kurtarma değil, bir okuma.
Oyun yöneticisinin ne yapabileceği bir sunucu kuralı
İzleyici, oyuncu ve yönetici; istemcinin kimsenin bulamayacağını umduğu butonları gizlemesi yerine, kendi erişim seviyeleri olan ayrı hub metotlarından giriyor. Yalnızca arayüzde var olan yetki, herkesin geliştirici araçlarıyla kendine verebileceği yetkidir.
Sonuç
Arkadaşlar hiçbir şey kurmadan bir bağlantıyla katılıyor, oturum oyun ortasındaki bir dağıtımı atlatıyor ve kimse bir zar atışı için kimsenin sözüne güvenmek zorunda kalmıyor.