feat(channelserver): implement monthly guild item claim tracking

Players could never claim monthly guild items because the handler
always returned 0x01 (claimed). Now tracks per-character per-type
(standard/HLC/EXC) claim timestamps in the stamps table, comparing
against the current month boundary to determine claim eligibility.

Adds MonthStart() to gametime, extends StampRepo with
GetMonthlyClaimed/SetMonthlyClaimed, and includes schema migration
31-monthly-items.sql.
This commit is contained in:
Houmgaor
2026-02-22 16:46:57 +01:00
parent 302453ce8e
commit 2acbb5d03a
11 changed files with 187 additions and 4 deletions

View File

@@ -6,6 +6,7 @@ import (
"time"
cfg "erupe-ce/config"
"erupe-ce/network/mhfpacket"
)
// TestGuildCreation tests basic guild creation
@@ -822,3 +823,104 @@ func TestGuildAllianceRelationship(t *testing.T) {
})
}
}
// --- handleMsgMhfCheckMonthlyItem tests ---
func TestCheckMonthlyItem_NotClaimed(t *testing.T) {
server := createMockServer()
stampMock := &mockStampRepoForItems{
monthlyClaimedErr: errNotFound,
}
server.stampRepo = stampMock
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCheckMonthlyItem{AckHandle: 100, Type: 0}
handleMsgMhfCheckMonthlyItem(session, pkt)
select {
case p := <-session.sendPackets:
if len(p.data) < 4 {
t.Fatalf("Response too short: %d bytes", len(p.data))
}
default:
t.Error("No response packet queued")
}
}
func TestCheckMonthlyItem_ClaimedThisMonth(t *testing.T) {
server := createMockServer()
stampMock := &mockStampRepoForItems{
monthlyClaimed: TimeAdjusted(), // claimed right now (within this month)
}
server.stampRepo = stampMock
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCheckMonthlyItem{AckHandle: 100, Type: 0}
handleMsgMhfCheckMonthlyItem(session, pkt)
select {
case <-session.sendPackets:
// Response received — claimed this month should return 1
default:
t.Error("No response packet queued")
}
}
func TestCheckMonthlyItem_ClaimedLastMonth(t *testing.T) {
server := createMockServer()
stampMock := &mockStampRepoForItems{
monthlyClaimed: TimeMonthStart().Add(-24 * time.Hour), // before this month
}
server.stampRepo = stampMock
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCheckMonthlyItem{AckHandle: 100, Type: 1}
handleMsgMhfCheckMonthlyItem(session, pkt)
select {
case <-session.sendPackets:
// Response received — last month claim should return 0 (unclaimed)
default:
t.Error("No response packet queued")
}
}
func TestCheckMonthlyItem_UnknownType(t *testing.T) {
server := createMockServer()
stampMock := &mockStampRepoForItems{}
server.stampRepo = stampMock
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCheckMonthlyItem{AckHandle: 100, Type: 99}
handleMsgMhfCheckMonthlyItem(session, pkt)
select {
case <-session.sendPackets:
// Unknown type returns 0 (unclaimed) without DB call
default:
t.Error("No response packet queued")
}
}
func TestAcquireMonthlyItem_MarksAsClaimed(t *testing.T) {
server := createMockServer()
stampMock := &mockStampRepoForItems{}
server.stampRepo = stampMock
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAcquireMonthlyItem{AckHandle: 100, Unk0: 2}
handleMsgMhfAcquireMonthlyItem(session, pkt)
if !stampMock.monthlySetCalled {
t.Error("SetMonthlyClaimed should be called")
}
if stampMock.monthlySetType != "monthly_ex" {
t.Errorf("SetMonthlyClaimed type = %q, want %q", stampMock.monthlySetType, "monthly_ex")
}
select {
case <-session.sendPackets:
default:
t.Error("No response packet queued")
}
}