feat: add codex system for monster/item/event tracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
store/codex.go
Normal file
88
store/codex.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var bucketCodex = []byte("codex")
|
||||
|
||||
type Codex struct {
|
||||
Monsters map[string]bool `json:"monsters"`
|
||||
Items map[string]bool `json:"items"`
|
||||
Events map[string]bool `json:"events"`
|
||||
}
|
||||
|
||||
func newCodex() Codex {
|
||||
return Codex{
|
||||
Monsters: make(map[string]bool),
|
||||
Items: make(map[string]bool),
|
||||
Events: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DB) RecordCodexEntry(fingerprint, category, id string) error {
|
||||
return d.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketCodex)
|
||||
key := []byte(fingerprint)
|
||||
codex := newCodex()
|
||||
|
||||
v := b.Get(key)
|
||||
if v != nil {
|
||||
if err := json.Unmarshal(v, &codex); err != nil {
|
||||
return err
|
||||
}
|
||||
// Ensure maps are initialized after unmarshal
|
||||
if codex.Monsters == nil {
|
||||
codex.Monsters = make(map[string]bool)
|
||||
}
|
||||
if codex.Items == nil {
|
||||
codex.Items = make(map[string]bool)
|
||||
}
|
||||
if codex.Events == nil {
|
||||
codex.Events = make(map[string]bool)
|
||||
}
|
||||
}
|
||||
|
||||
switch category {
|
||||
case "monster":
|
||||
codex.Monsters[id] = true
|
||||
case "item":
|
||||
codex.Items[id] = true
|
||||
case "event":
|
||||
codex.Events[id] = true
|
||||
}
|
||||
|
||||
data, err := json.Marshal(codex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DB) GetCodex(fingerprint string) (Codex, error) {
|
||||
codex := newCodex()
|
||||
err := d.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketCodex)
|
||||
v := b.Get([]byte(fingerprint))
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if err := json.Unmarshal(v, &codex); err != nil {
|
||||
return err
|
||||
}
|
||||
if codex.Monsters == nil {
|
||||
codex.Monsters = make(map[string]bool)
|
||||
}
|
||||
if codex.Items == nil {
|
||||
codex.Items = make(map[string]bool)
|
||||
}
|
||||
if codex.Events == nil {
|
||||
codex.Events = make(map[string]bool)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return codex, err
|
||||
}
|
||||
71
store/codex_test.go
Normal file
71
store/codex_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRecordCodexEntry(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
db, err := Open(dir + "/test_codex.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Record some entries
|
||||
db.RecordCodexEntry("fp1", "monster", "goblin")
|
||||
db.RecordCodexEntry("fp1", "monster", "skeleton")
|
||||
db.RecordCodexEntry("fp1", "item", "health_potion")
|
||||
db.RecordCodexEntry("fp1", "event", "altar")
|
||||
|
||||
codex, err := db.GetCodex("fp1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(codex.Monsters) != 2 {
|
||||
t.Errorf("expected 2 monsters, got %d", len(codex.Monsters))
|
||||
}
|
||||
if !codex.Monsters["goblin"] {
|
||||
t.Error("expected goblin in monsters")
|
||||
}
|
||||
if !codex.Monsters["skeleton"] {
|
||||
t.Error("expected skeleton in monsters")
|
||||
}
|
||||
if len(codex.Items) != 1 {
|
||||
t.Errorf("expected 1 item, got %d", len(codex.Items))
|
||||
}
|
||||
if !codex.Items["health_potion"] {
|
||||
t.Error("expected health_potion in items")
|
||||
}
|
||||
if len(codex.Events) != 1 {
|
||||
t.Errorf("expected 1 event, got %d", len(codex.Events))
|
||||
}
|
||||
if !codex.Events["altar"] {
|
||||
t.Error("expected altar in events")
|
||||
}
|
||||
|
||||
// Duplicate entry should not increase count
|
||||
db.RecordCodexEntry("fp1", "monster", "goblin")
|
||||
codex2, _ := db.GetCodex("fp1")
|
||||
if len(codex2.Monsters) != 2 {
|
||||
t.Errorf("expected still 2 monsters after duplicate, got %d", len(codex2.Monsters))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCodexEmpty(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
db, err := Open(dir + "/test_codex_empty.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
codex, err := db.GetCodex("fp_unknown")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(codex.Monsters) != 0 || len(codex.Items) != 0 || len(codex.Events) != 0 {
|
||||
t.Error("expected empty codex for unknown player")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user