mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
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.
246 lines
7.2 KiB
Go
246 lines
7.2 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
// GachaRepository centralizes all database access for gacha-related tables
|
|
// (gacha_shop, gacha_entries, gacha_items, gacha_stepup, gacha_box).
|
|
type GachaRepository struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
// NewGachaRepository creates a new GachaRepository.
|
|
func NewGachaRepository(db *sqlx.DB) *GachaRepository {
|
|
return &GachaRepository{db: db}
|
|
}
|
|
|
|
// GetEntryForTransaction reads the cost type/amount and roll count for a gacha transaction.
|
|
func (r *GachaRepository) GetEntryForTransaction(gachaID uint32, rollID uint8) (itemType uint8, itemNumber uint16, rolls int, err error) {
|
|
err = r.db.QueryRowx(
|
|
`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`,
|
|
gachaID, rollID,
|
|
).Scan(&itemType, &itemNumber, &rolls)
|
|
return
|
|
}
|
|
|
|
// GetRewardPool returns the entry_type=100 reward pool for a gacha, ordered by weight descending.
|
|
func (r *GachaRepository) GetRewardPool(gachaID uint32) ([]GachaEntry, error) {
|
|
var entries []GachaEntry
|
|
rows, err := r.db.Queryx(
|
|
`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`,
|
|
gachaID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
for rows.Next() {
|
|
var entry GachaEntry
|
|
if err := rows.StructScan(&entry); err == nil {
|
|
entries = append(entries, entry)
|
|
}
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// GetItemsForEntry returns the items associated with a gacha entry ID.
|
|
func (r *GachaRepository) GetItemsForEntry(entryID uint32) ([]GachaItem, error) {
|
|
var items []GachaItem
|
|
rows, err := r.db.Queryx(
|
|
`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`,
|
|
entryID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
for rows.Next() {
|
|
var item GachaItem
|
|
if err := rows.StructScan(&item); err == nil {
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
// GetGuaranteedItems returns items for the entry matching a roll type and gacha ID.
|
|
func (r *GachaRepository) GetGuaranteedItems(rollType uint8, gachaID uint32) ([]GachaItem, error) {
|
|
var items []GachaItem
|
|
rows, err := r.db.Queryx(
|
|
`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`,
|
|
rollType, gachaID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
for rows.Next() {
|
|
var item GachaItem
|
|
if err := rows.StructScan(&item); err == nil {
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
// Stepup methods
|
|
|
|
// GetStepupStep returns the current stepup step for a character on a gacha.
|
|
func (r *GachaRepository) GetStepupStep(gachaID uint32, charID uint32) (uint8, error) {
|
|
var step uint8
|
|
err := r.db.QueryRow(
|
|
`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`,
|
|
gachaID, charID,
|
|
).Scan(&step)
|
|
return step, err
|
|
}
|
|
|
|
// GetStepupWithTime returns the current step and creation time for a stepup entry.
|
|
// Returns sql.ErrNoRows if no entry exists.
|
|
func (r *GachaRepository) GetStepupWithTime(gachaID uint32, charID uint32) (uint8, time.Time, error) {
|
|
var step uint8
|
|
var createdAt time.Time
|
|
err := r.db.QueryRow(
|
|
`SELECT step, COALESCE(created_at, '2000-01-01'::timestamptz) FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`,
|
|
gachaID, charID,
|
|
).Scan(&step, &createdAt)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return 0, time.Time{}, err
|
|
}
|
|
return step, createdAt, err
|
|
}
|
|
|
|
// HasEntryType returns whether a gacha has any entries of the given type.
|
|
func (r *GachaRepository) HasEntryType(gachaID uint32, entryType uint8) (bool, error) {
|
|
var count int
|
|
err := r.db.QueryRow(
|
|
`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`,
|
|
gachaID, entryType,
|
|
).Scan(&count)
|
|
return count > 0, err
|
|
}
|
|
|
|
// DeleteStepup removes the stepup state for a character on a gacha.
|
|
func (r *GachaRepository) DeleteStepup(gachaID uint32, charID uint32) error {
|
|
_, err := r.db.Exec(
|
|
`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`,
|
|
gachaID, charID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// InsertStepup records a new stepup step for a character on a gacha.
|
|
func (r *GachaRepository) InsertStepup(gachaID uint32, step uint8, charID uint32) error {
|
|
_, err := r.db.Exec(
|
|
`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`,
|
|
gachaID, step, charID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// Box gacha methods
|
|
|
|
// GetBoxEntryIDs returns the entry IDs already drawn for a box gacha.
|
|
func (r *GachaRepository) GetBoxEntryIDs(gachaID uint32, charID uint32) ([]uint32, error) {
|
|
var ids []uint32
|
|
rows, err := r.db.Queryx(
|
|
`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`,
|
|
gachaID, charID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
for rows.Next() {
|
|
var id uint32
|
|
if err := rows.Scan(&id); err == nil {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
// InsertBoxEntry records a drawn entry in a box gacha.
|
|
func (r *GachaRepository) InsertBoxEntry(gachaID uint32, entryID uint32, charID uint32) error {
|
|
_, err := r.db.Exec(
|
|
`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`,
|
|
gachaID, entryID, charID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// DeleteBoxEntries resets all drawn entries for a box gacha.
|
|
func (r *GachaRepository) DeleteBoxEntries(gachaID uint32, charID uint32) error {
|
|
_, err := r.db.Exec(
|
|
`DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`,
|
|
gachaID, charID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// Shop listing methods
|
|
|
|
// ListShop returns all gacha shop definitions.
|
|
func (r *GachaRepository) ListShop() ([]Gacha, error) {
|
|
var gachas []Gacha
|
|
rows, err := r.db.Queryx(
|
|
`SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop`,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
for rows.Next() {
|
|
var g Gacha
|
|
if err := rows.StructScan(&g); err == nil {
|
|
gachas = append(gachas, g)
|
|
}
|
|
}
|
|
return gachas, nil
|
|
}
|
|
|
|
// GetShopType returns the gacha_type for a gacha shop ID.
|
|
func (r *GachaRepository) GetShopType(shopID uint32) (int, error) {
|
|
var gachaType int
|
|
err := r.db.QueryRow(
|
|
`SELECT gacha_type FROM gacha_shop WHERE id = $1`,
|
|
shopID,
|
|
).Scan(&gachaType)
|
|
return gachaType, err
|
|
}
|
|
|
|
// GetAllEntries returns all entries for a gacha, ordered by weight descending.
|
|
func (r *GachaRepository) GetAllEntries(gachaID uint32) ([]GachaEntry, error) {
|
|
var entries []GachaEntry
|
|
rows, err := r.db.Queryx(
|
|
`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, COALESCE(name, '') AS name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`,
|
|
gachaID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
for rows.Next() {
|
|
var entry GachaEntry
|
|
if err := rows.StructScan(&entry); err == nil {
|
|
entries = append(entries, entry)
|
|
}
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// GetWeightDivisor returns the total weight / 100000 for probability display.
|
|
func (r *GachaRepository) GetWeightDivisor(gachaID uint32) (float64, error) {
|
|
var divisor float64
|
|
err := r.db.QueryRow(
|
|
`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`,
|
|
gachaID,
|
|
).Scan(&divisor)
|
|
return divisor, err
|
|
}
|