Files
Erupe/server/channelserver/repo_tower.go
Houmgaor ba7ec122f8 revert: remove SQLite support
An MMO server without multiplayer defeats the purpose. PostgreSQL
is the right choice and Docker Compose already solves the setup
pain. This reverts the common/db wrapper, SQLite schema, config
Driver field, modernc.org/sqlite dependency, and all repo type
changes while keeping the dashboard, wizard, and CI improvements
from the previous commit.
2026-03-05 23:05:55 +01:00

153 lines
5.4 KiB
Go

package channelserver
import (
"fmt"
"github.com/jmoiron/sqlx"
)
// TowerRepository centralizes all database access for tower-related tables
// (tower, guilds tower columns, guild_characters tower columns).
type TowerRepository struct {
db *sqlx.DB
}
// NewTowerRepository creates a new TowerRepository.
func NewTowerRepository(db *sqlx.DB) *TowerRepository {
return &TowerRepository{db: db}
}
// TowerData holds the core tower stats for a character.
type TowerData struct {
TR int32
TRP int32
TSP int32
Block1 int32
Block2 int32
Skills string
}
// GetTowerData returns tower stats for a character, creating the row if it doesn't exist.
func (r *TowerRepository) GetTowerData(charID uint32) (TowerData, error) {
var td TowerData
err := r.db.QueryRow(
`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), COALESCE(skills, $1) FROM tower WHERE char_id=$2`,
EmptyTowerCSV(64), charID,
).Scan(&td.TR, &td.TRP, &td.TSP, &td.Block1, &td.Block2, &td.Skills)
if err != nil {
_, err = r.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, charID)
return TowerData{Skills: EmptyTowerCSV(64)}, err
}
return td, nil
}
// GetSkills returns the skills CSV string for a character.
func (r *TowerRepository) GetSkills(charID uint32) (string, error) {
var skills string
err := r.db.QueryRow(`SELECT COALESCE(skills, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(64), charID).Scan(&skills)
return skills, err
}
// UpdateSkills updates a single skill and deducts TSP cost.
func (r *TowerRepository) UpdateSkills(charID uint32, skills string, cost int32) error {
_, err := r.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, skills, cost, charID)
return err
}
// UpdateProgress updates tower progress (TR, TRP, TSP, block1).
func (r *TowerRepository) UpdateProgress(charID uint32, tr, trp, cost, block1 int32) error {
_, err := r.db.Exec(
`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, tsp=COALESCE(tsp, 0)+$3, block1=COALESCE(block1, 0)+$4 WHERE char_id=$5`,
tr, trp, cost, block1, charID,
)
return err
}
// GetGems returns the gems CSV string for a character.
func (r *TowerRepository) GetGems(charID uint32) (string, error) {
var gems string
err := r.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), charID).Scan(&gems)
return gems, err
}
// UpdateGems saves the gems CSV string for a character.
func (r *TowerRepository) UpdateGems(charID uint32, gems string) error {
_, err := r.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, gems, charID)
return err
}
// TenrouiraiProgressData holds the guild's tenrouirai (sky corridor) progress.
type TenrouiraiProgressData struct {
Page uint8
Mission1 uint16
Mission2 uint16
Mission3 uint16
}
// GetTenrouiraiProgress returns the guild's tower mission page and aggregated mission scores.
func (r *TowerRepository) GetTenrouiraiProgress(guildID uint32) (TenrouiraiProgressData, error) {
var p TenrouiraiProgressData
if err := r.db.QueryRow(`SELECT tower_mission_page FROM guilds WHERE id=$1`, guildID).Scan(&p.Page); err != nil {
return p, err
}
_ = r.db.QueryRow(
`SELECT SUM(tower_mission_1) AS _, SUM(tower_mission_2) AS _, SUM(tower_mission_3) AS _ FROM guild_characters WHERE guild_id=$1`,
guildID,
).Scan(&p.Mission1, &p.Mission2, &p.Mission3)
return p, nil
}
// GetTenrouiraiMissionScores returns per-character scores for a specific mission index (1-3).
func (r *TowerRepository) GetTenrouiraiMissionScores(guildID uint32, missionIndex uint8) ([]TenrouiraiCharScore, error) {
if missionIndex < 1 || missionIndex > 3 {
missionIndex = (missionIndex % 3) + 1
}
rows, err := r.db.Query(
fmt.Sprintf(
`SELECT name, tower_mission_%d FROM guild_characters gc INNER JOIN characters c ON gc.character_id = c.id WHERE guild_id=$1 AND tower_mission_%d IS NOT NULL ORDER BY tower_mission_%d DESC`,
missionIndex, missionIndex, missionIndex,
),
guildID,
)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
var scores []TenrouiraiCharScore
for rows.Next() {
var cs TenrouiraiCharScore
if err := rows.Scan(&cs.Name, &cs.Score); err == nil {
scores = append(scores, cs)
}
}
return scores, nil
}
// GetGuildTowerRP returns the guild's tower RP.
func (r *TowerRepository) GetGuildTowerRP(guildID uint32) (uint32, error) {
var rp uint32
err := r.db.QueryRow(`SELECT tower_rp FROM guilds WHERE id=$1`, guildID).Scan(&rp)
return rp, err
}
// GetGuildTowerPageAndRP returns the guild's tower mission page and donated RP.
func (r *TowerRepository) GetGuildTowerPageAndRP(guildID uint32) (page int, donated int, err error) {
err = r.db.QueryRow(`SELECT tower_mission_page, tower_rp FROM guilds WHERE id=$1`, guildID).Scan(&page, &donated)
return
}
// AdvanceTenrouiraiPage increments the guild's tower mission page and resets member mission progress.
func (r *TowerRepository) AdvanceTenrouiraiPage(guildID uint32) error {
if _, err := r.db.Exec(`UPDATE guilds SET tower_mission_page=tower_mission_page+1 WHERE id=$1`, guildID); err != nil {
return err
}
_, err := r.db.Exec(`UPDATE guild_characters SET tower_mission_1=NULL, tower_mission_2=NULL, tower_mission_3=NULL WHERE guild_id=$1`, guildID)
return err
}
// DonateGuildTowerRP adds RP to the guild's tower total.
func (r *TowerRepository) DonateGuildTowerRP(guildID uint32, rp uint16) error {
_, err := r.db.Exec(`UPDATE guilds SET tower_rp=tower_rp+$1 WHERE id=$2`, rp, guildID)
return err
}