mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-26 09:33:02 +01:00
feat(diva): implement Diva Defense (UD) system
Add full Diva Defense / United Defense system: schema, repo layer, i18n bead names, and RE-verified packet handler implementations. Schema (0011_diva.sql): diva_beads, diva_beads_assignment, diva_beads_points, diva_prizes tables; interception_maps/points columns on guilds and guild_characters. Seed (DivaDefaults.sql): 26 prize milestones for personal and guild reward tracks (item_type=26 diva coins). Repo (DivaRepo): 11 new methods covering bead assignment, point accumulation, interception point tracking, prize queries, and cleanup. Mocks wired in test_helpers_test.go. i18n: Bead struct with EN/JP names for all 18 bead types (IDs 1–25). Session tracks currentBeadIndex (-1 = none assigned). Packet handlers corrected against mhfo-hd.dll RE findings: - GetKijuInfo: u8 count, 512-byte desc, color_id+bead_type per entry - SetKiju: 1-byte ACK; persists bead assignment to DB - GetUdMyPoint: 8×18-byte entries, no count prefix - GetUdTotalPointInfo: u8 error + u64[64] + u8[64] + u64 (~585 B) - GetUdSelectedColorInfo: u8 error + u8[8] = 9 bytes - GetUdDailyPresentList: correct u16 count format (was wrong hex) - GetUdNormaPresentList: correct u16 count format (was wrong hex) - GetUdRankingRewardList: correct u16 count with u32 item_id/qty - GetRewardSong: 22-byte layout with 0xFFFFFFFF prayer_end sentinel - AddRewardSongCount: parse implemented (was NOT IMPLEMENTED stub)
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
@@ -75,3 +78,149 @@ func (r *DivaRepository) GetTotalPoints(eventID uint32) (int64, int64, error) {
|
||||
}
|
||||
return qp, bp, nil
|
||||
}
|
||||
|
||||
// GetBeads returns all active bead types from the diva_beads table.
|
||||
func (r *DivaRepository) GetBeads() ([]int, error) {
|
||||
var types []int
|
||||
err := r.db.Select(&types, "SELECT type FROM diva_beads ORDER BY id")
|
||||
return types, err
|
||||
}
|
||||
|
||||
// AssignBead inserts a bead assignment for a character, replacing any existing one for that bead slot.
|
||||
func (r *DivaRepository) AssignBead(characterID uint32, beadIndex int, expiry time.Time) error {
|
||||
_, err := r.db.Exec(`
|
||||
INSERT INTO diva_beads_assignment (character_id, bead_index, expiry)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT DO NOTHING`,
|
||||
characterID, beadIndex, expiry)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddBeadPoints records a bead point contribution for a character.
|
||||
func (r *DivaRepository) AddBeadPoints(characterID uint32, beadIndex int, points int) error {
|
||||
_, err := r.db.Exec(
|
||||
"INSERT INTO diva_beads_points (character_id, bead_index, points) VALUES ($1, $2, $3)",
|
||||
characterID, beadIndex, points)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCharacterBeadPoints returns the summed points per bead_index for a character.
|
||||
func (r *DivaRepository) GetCharacterBeadPoints(characterID uint32) (map[int]int, error) {
|
||||
rows, err := r.db.Query(
|
||||
"SELECT bead_index, COALESCE(SUM(points),0) FROM diva_beads_points WHERE character_id=$1 GROUP BY bead_index",
|
||||
characterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
result := make(map[int]int)
|
||||
for rows.Next() {
|
||||
var idx, pts int
|
||||
if err := rows.Scan(&idx, &pts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[idx] = pts
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// GetTotalBeadPoints returns the sum of all points across all characters and bead slots.
|
||||
func (r *DivaRepository) GetTotalBeadPoints() (int64, error) {
|
||||
var total int64
|
||||
err := r.db.QueryRow("SELECT COALESCE(SUM(points),0) FROM diva_beads_points").Scan(&total)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// GetTopBeadPerDay returns the bead_index with the most points contributed on day offset `day`
|
||||
// (0 = today, 1 = yesterday, etc.). Returns 0 if no data exists for that day.
|
||||
func (r *DivaRepository) GetTopBeadPerDay(day int) (int, error) {
|
||||
var beadIndex int
|
||||
err := r.db.QueryRow(`
|
||||
SELECT bead_index
|
||||
FROM diva_beads_points
|
||||
WHERE timestamp >= (NOW() - ($1 + 1) * INTERVAL '1 day')
|
||||
AND timestamp < (NOW() - $1 * INTERVAL '1 day')
|
||||
GROUP BY bead_index
|
||||
ORDER BY SUM(points) DESC
|
||||
LIMIT 1`,
|
||||
day).Scan(&beadIndex)
|
||||
if err != nil {
|
||||
return 0, nil // no data for this day is not an error
|
||||
}
|
||||
return beadIndex, nil
|
||||
}
|
||||
|
||||
// CleanupBeads deletes all rows from diva_beads, diva_beads_assignment, and diva_beads_points.
|
||||
func (r *DivaRepository) CleanupBeads() error {
|
||||
if _, err := r.db.Exec("DELETE FROM diva_beads_points"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := r.db.Exec("DELETE FROM diva_beads_assignment"); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.db.Exec("DELETE FROM diva_beads")
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPersonalPrizes returns all prize rows with type='personal', ordered by points_req.
|
||||
func (r *DivaRepository) GetPersonalPrizes() ([]DivaPrize, error) {
|
||||
return r.getPrizesByType("personal")
|
||||
}
|
||||
|
||||
// GetGuildPrizes returns all prize rows with type='guild', ordered by points_req.
|
||||
func (r *DivaRepository) GetGuildPrizes() ([]DivaPrize, error) {
|
||||
return r.getPrizesByType("guild")
|
||||
}
|
||||
|
||||
func (r *DivaRepository) getPrizesByType(prizeType string) ([]DivaPrize, error) {
|
||||
rows, err := r.db.Query(`
|
||||
SELECT id, type, points_req, item_type, item_id, quantity, gr, repeatable
|
||||
FROM diva_prizes
|
||||
WHERE type=$1
|
||||
ORDER BY points_req`,
|
||||
prizeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
var prizes []DivaPrize
|
||||
for rows.Next() {
|
||||
var p DivaPrize
|
||||
if err := rows.Scan(&p.ID, &p.Type, &p.PointsReq, &p.ItemType, &p.ItemID, &p.Quantity, &p.GR, &p.Repeatable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prizes = append(prizes, p)
|
||||
}
|
||||
return prizes, rows.Err()
|
||||
}
|
||||
|
||||
// GetCharacterInterceptionPoints returns the interception_points JSON map from guild_characters.
|
||||
func (r *DivaRepository) GetCharacterInterceptionPoints(characterID uint32) (map[string]int, error) {
|
||||
var raw []byte
|
||||
err := r.db.QueryRow(
|
||||
"SELECT interception_points FROM guild_characters WHERE char_id=$1",
|
||||
characterID).Scan(&raw)
|
||||
if err != nil {
|
||||
return map[string]int{}, nil
|
||||
}
|
||||
result := make(map[string]int)
|
||||
if len(raw) > 0 {
|
||||
if err := json.Unmarshal(raw, &result); err != nil {
|
||||
return map[string]int{}, nil
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AddInterceptionPoints increments the interception points for a quest file ID in guild_characters.
|
||||
func (r *DivaRepository) AddInterceptionPoints(characterID uint32, questFileID int, points int) error {
|
||||
_, err := r.db.Exec(`
|
||||
UPDATE guild_characters
|
||||
SET interception_points = interception_points || jsonb_build_object(
|
||||
$2::text,
|
||||
COALESCE((interception_points->>$2::text)::int, 0) + $3
|
||||
)
|
||||
WHERE char_id=$1`,
|
||||
characterID, questFileID, points)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user