create drops package
This commit is contained in:
parent
0210e7dd28
commit
ace43e1053
147
internal/drops/doc.go
Normal file
147
internal/drops/doc.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
Package drops is the active record implementation for drop items in the game.
|
||||||
|
|
||||||
|
# Basic Usage
|
||||||
|
|
||||||
|
To retrieve a drop by ID:
|
||||||
|
|
||||||
|
drop, err := drops.Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Found drop: %s (level: %d)\n", drop.Name, drop.Level)
|
||||||
|
|
||||||
|
To get all drops:
|
||||||
|
|
||||||
|
allDrops, err := drops.All(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, drop := range allDrops {
|
||||||
|
fmt.Printf("Drop: %s\n", drop.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
To filter drops by level (items available at or below a level):
|
||||||
|
|
||||||
|
availableDrops, err := drops.ByLevel(db, 25)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
To filter drops by type:
|
||||||
|
|
||||||
|
consumables, err := drops.ByType(db, drops.TypeConsumable)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Creating Drops with Builder Pattern
|
||||||
|
|
||||||
|
The package provides a fluent builder interface for creating new drops:
|
||||||
|
|
||||||
|
drop, err := drops.NewBuilder(db).
|
||||||
|
WithName("Ruby").
|
||||||
|
WithLevel(50).
|
||||||
|
WithType(drops.TypeConsumable).
|
||||||
|
WithAtt("maxhp,150").
|
||||||
|
Create()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Created drop with ID: %d\n", drop.ID)
|
||||||
|
|
||||||
|
# Updating Drops
|
||||||
|
|
||||||
|
Drops can be modified and saved back to the database:
|
||||||
|
|
||||||
|
drop, _ := drops.Find(db, 1)
|
||||||
|
drop.Name = "Enhanced Life Pebble"
|
||||||
|
drop.Level = 5
|
||||||
|
drop.Att = "maxhp,15"
|
||||||
|
|
||||||
|
err := drop.Save()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deleting Drops
|
||||||
|
|
||||||
|
Drops can be removed from the database:
|
||||||
|
|
||||||
|
drop, _ := drops.Find(db, 1)
|
||||||
|
err := drop.Delete()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Drop Types
|
||||||
|
|
||||||
|
The package defines drop type constants:
|
||||||
|
|
||||||
|
drops.TypeConsumable = 1 // Consumable items like potions, gems, etc.
|
||||||
|
|
||||||
|
Helper methods are available to check drop types:
|
||||||
|
|
||||||
|
if drop.IsConsumable() {
|
||||||
|
fmt.Println("This is a consumable item")
|
||||||
|
}
|
||||||
|
fmt.Printf("Drop type: %s\n", drop.TypeName())
|
||||||
|
|
||||||
|
# Database Schema
|
||||||
|
|
||||||
|
The drops table has the following structure:
|
||||||
|
|
||||||
|
CREATE TABLE drops (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
att TEXT NOT NULL DEFAULT ''
|
||||||
|
)
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- id: Unique identifier
|
||||||
|
- name: Display name of the drop
|
||||||
|
- level: Minimum monster level to find this drop
|
||||||
|
- type: Drop type (1=consumable)
|
||||||
|
- att: Comma-separated attributes in "key,value,key,value" format
|
||||||
|
|
||||||
|
# Drop Attributes
|
||||||
|
|
||||||
|
The att field contains attribute bonuses in comma-separated "key,value" pairs:
|
||||||
|
|
||||||
|
"maxhp,10" // +10 max health
|
||||||
|
"maxmp,25" // +25 max mana
|
||||||
|
"strength,50" // +50 strength
|
||||||
|
"defensepower,25" // +25 defense power
|
||||||
|
"expbonus,10" // +10% experience bonus
|
||||||
|
"goldbonus,5" // +5% gold bonus
|
||||||
|
|
||||||
|
Many drops have multiple attributes in a single field:
|
||||||
|
|
||||||
|
drop.Att = "maxhp,25,strength,25" // +25 max health AND +25 strength
|
||||||
|
drop.Att = "maxmp,-50,strength,100" // -50 max mana AND +100 strength
|
||||||
|
|
||||||
|
The attributes are parsed as alternating key-value pairs separated by commas.
|
||||||
|
|
||||||
|
# Level Requirements
|
||||||
|
|
||||||
|
Drops have level requirements that determine when players can use them:
|
||||||
|
|
||||||
|
// Get all drops available from level 10 and above monsters
|
||||||
|
availableDrops, err := drops.ByLevel(db, 10)
|
||||||
|
|
||||||
|
// This returns drops with level <= 10
|
||||||
|
for _, drop := range availableDrops {
|
||||||
|
fmt.Printf("%s (level %d)\n", drop.Name, drop.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error Handling
|
||||||
|
|
||||||
|
All functions return appropriate errors for common failure cases:
|
||||||
|
- Drop not found (Find returns error for non-existent IDs)
|
||||||
|
- Database connection issues
|
||||||
|
- Invalid operations (e.g., saving/deleting drops without IDs)
|
||||||
|
*/
|
||||||
|
package drops
|
240
internal/drops/drops.go
Normal file
240
internal/drops/drops.go
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
package drops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"dk/internal/database"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Drop represents a drop item in the database
|
||||||
|
type Drop struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Att string `json:"att"`
|
||||||
|
|
||||||
|
db *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropType constants for drop types
|
||||||
|
const (
|
||||||
|
TypeConsumable = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find retrieves a drop by ID
|
||||||
|
func Find(db *database.DB, id int) (*Drop, error) {
|
||||||
|
drop := &Drop{db: db}
|
||||||
|
|
||||||
|
query := "SELECT id, name, level, type, att FROM drops WHERE id = ?"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
drop.ID = stmt.ColumnInt(0)
|
||||||
|
drop.Name = stmt.ColumnText(1)
|
||||||
|
drop.Level = stmt.ColumnInt(2)
|
||||||
|
drop.Type = stmt.ColumnInt(3)
|
||||||
|
drop.Att = stmt.ColumnText(4)
|
||||||
|
return nil
|
||||||
|
}, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find drop: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if drop.ID == 0 {
|
||||||
|
return nil, fmt.Errorf("drop with ID %d not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return drop, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// All retrieves all drops
|
||||||
|
func All(db *database.DB) ([]*Drop, error) {
|
||||||
|
var drops []*Drop
|
||||||
|
|
||||||
|
query := "SELECT id, name, level, type, att FROM drops ORDER BY id"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
drop := &Drop{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Name: stmt.ColumnText(1),
|
||||||
|
Level: stmt.ColumnInt(2),
|
||||||
|
Type: stmt.ColumnInt(3),
|
||||||
|
Att: stmt.ColumnText(4),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
drops = append(drops, drop)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve all drops: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return drops, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByLevel retrieves drops by minimum level requirement
|
||||||
|
func ByLevel(db *database.DB, minLevel int) ([]*Drop, error) {
|
||||||
|
var drops []*Drop
|
||||||
|
|
||||||
|
query := "SELECT id, name, level, type, att FROM drops WHERE level <= ? ORDER BY level, id"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
drop := &Drop{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Name: stmt.ColumnText(1),
|
||||||
|
Level: stmt.ColumnInt(2),
|
||||||
|
Type: stmt.ColumnInt(3),
|
||||||
|
Att: stmt.ColumnText(4),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
drops = append(drops, drop)
|
||||||
|
return nil
|
||||||
|
}, minLevel)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve drops by level: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return drops, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByType retrieves drops by type
|
||||||
|
func ByType(db *database.DB, dropType int) ([]*Drop, error) {
|
||||||
|
var drops []*Drop
|
||||||
|
|
||||||
|
query := "SELECT id, name, level, type, att FROM drops WHERE type = ? ORDER BY level, id"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
drop := &Drop{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Name: stmt.ColumnText(1),
|
||||||
|
Level: stmt.ColumnInt(2),
|
||||||
|
Type: stmt.ColumnInt(3),
|
||||||
|
Att: stmt.ColumnText(4),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
drops = append(drops, drop)
|
||||||
|
return nil
|
||||||
|
}, dropType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve drops by type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return drops, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder provides a fluent interface for creating drops
|
||||||
|
type Builder struct {
|
||||||
|
drop *Drop
|
||||||
|
db *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new drop builder
|
||||||
|
func NewBuilder(db *database.DB) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
drop: &Drop{db: db},
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName sets the drop name
|
||||||
|
func (b *Builder) WithName(name string) *Builder {
|
||||||
|
b.drop.Name = name
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLevel sets the drop level requirement
|
||||||
|
func (b *Builder) WithLevel(level int) *Builder {
|
||||||
|
b.drop.Level = level
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithType sets the drop type
|
||||||
|
func (b *Builder) WithType(dropType int) *Builder {
|
||||||
|
b.drop.Type = dropType
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAtt sets the attributes
|
||||||
|
func (b *Builder) WithAtt(att string) *Builder {
|
||||||
|
b.drop.Att = att
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create saves the drop to the database and returns it
|
||||||
|
func (b *Builder) Create() (*Drop, error) {
|
||||||
|
// Use a transaction to ensure we can get the ID
|
||||||
|
var drop *Drop
|
||||||
|
err := b.db.Transaction(func(tx *database.Tx) error {
|
||||||
|
query := `INSERT INTO drops (name, level, type, att)
|
||||||
|
VALUES (?, ?, ?, ?)`
|
||||||
|
|
||||||
|
if err := tx.Exec(query, b.drop.Name, b.drop.Level, b.drop.Type, b.drop.Att); err != nil {
|
||||||
|
return fmt.Errorf("failed to insert drop: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last inserted ID within the same transaction
|
||||||
|
var lastID int
|
||||||
|
err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error {
|
||||||
|
lastID = stmt.ColumnInt(0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get last insert ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the drop with the ID
|
||||||
|
drop = &Drop{
|
||||||
|
ID: lastID,
|
||||||
|
Name: b.drop.Name,
|
||||||
|
Level: b.drop.Level,
|
||||||
|
Type: b.drop.Type,
|
||||||
|
Att: b.drop.Att,
|
||||||
|
db: b.db,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create drop: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return drop, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save updates an existing drop in the database
|
||||||
|
func (d *Drop) Save() error {
|
||||||
|
if d.ID == 0 {
|
||||||
|
return fmt.Errorf("cannot save drop without ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `UPDATE drops SET name = ?, level = ?, type = ?, att = ? WHERE id = ?`
|
||||||
|
return d.db.Exec(query, d.Name, d.Level, d.Type, d.Att, d.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the drop from the database
|
||||||
|
func (d *Drop) Delete() error {
|
||||||
|
if d.ID == 0 {
|
||||||
|
return fmt.Errorf("cannot delete drop without ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "DELETE FROM drops WHERE id = ?"
|
||||||
|
return d.db.Exec(query, d.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConsumable returns true if the drop is a consumable item
|
||||||
|
func (d *Drop) IsConsumable() bool {
|
||||||
|
return d.Type == TypeConsumable
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeName returns the string representation of the drop type
|
||||||
|
func (d *Drop) TypeName() string {
|
||||||
|
switch d.Type {
|
||||||
|
case TypeConsumable:
|
||||||
|
return "Consumable"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
270
internal/drops/drops_test.go
Normal file
270
internal/drops/drops_test.go
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
package drops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"dk/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestDB(t *testing.T) *database.DB {
|
||||||
|
testDB := "test_drops.db"
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove(testDB)
|
||||||
|
})
|
||||||
|
|
||||||
|
db, err := database.Open(testDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to open test database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create drops table
|
||||||
|
createTable := `CREATE TABLE drops (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
att TEXT NOT NULL DEFAULT ''
|
||||||
|
)`
|
||||||
|
|
||||||
|
if err := db.Exec(createTable); err != nil {
|
||||||
|
t.Fatalf("Failed to create drops table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert test data
|
||||||
|
testDrops := `INSERT INTO drops (name, level, type, att) VALUES
|
||||||
|
('Life Pebble', 1, 1, 'maxhp,10'),
|
||||||
|
('Magic Stone', 10, 1, 'maxmp,25'),
|
||||||
|
('Dragon''s Scale', 10, 1, 'defensepower,25'),
|
||||||
|
('Angel''s Joy', 25, 1, 'maxhp,25,strength,25')`
|
||||||
|
|
||||||
|
if err := db.Exec(testDrops); err != nil {
|
||||||
|
t.Fatalf("Failed to insert test drops: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFind(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Test finding existing drop
|
||||||
|
drop, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if drop.ID != 1 {
|
||||||
|
t.Errorf("Expected ID 1, got %d", drop.ID)
|
||||||
|
}
|
||||||
|
if drop.Name != "Life Pebble" {
|
||||||
|
t.Errorf("Expected name 'Life Pebble', got '%s'", drop.Name)
|
||||||
|
}
|
||||||
|
if drop.Level != 1 {
|
||||||
|
t.Errorf("Expected level 1, got %d", drop.Level)
|
||||||
|
}
|
||||||
|
if drop.Type != TypeConsumable {
|
||||||
|
t.Errorf("Expected type %d, got %d", TypeConsumable, drop.Type)
|
||||||
|
}
|
||||||
|
if drop.Att != "maxhp,10" {
|
||||||
|
t.Errorf("Expected att1 'maxhp,10', got '%s'", drop.Att)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test finding non-existent drop
|
||||||
|
_, err = Find(db, 999)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when finding non-existent drop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
drops, err := All(db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get all drops: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(drops) != 4 {
|
||||||
|
t.Errorf("Expected 4 drops, got %d", len(drops))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check first drop
|
||||||
|
if drops[0].Name != "Life Pebble" {
|
||||||
|
t.Errorf("Expected first drop to be 'Life Pebble', got '%s'", drops[0].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByLevel(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Test drops available at level 10
|
||||||
|
drops, err := ByLevel(db, 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get drops by level: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(drops) != 3 {
|
||||||
|
t.Errorf("Expected 3 drops at level 10, got %d", len(drops))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify they are ordered by level
|
||||||
|
if drops[0].Level != 1 {
|
||||||
|
t.Errorf("Expected first drop level 1, got %d", drops[0].Level)
|
||||||
|
}
|
||||||
|
if drops[1].Level != 10 {
|
||||||
|
t.Errorf("Expected second drop level 10, got %d", drops[1].Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test drops available at level 1
|
||||||
|
lowLevelDrops, err := ByLevel(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get drops by level 1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lowLevelDrops) != 1 {
|
||||||
|
t.Errorf("Expected 1 drop at level 1, got %d", len(lowLevelDrops))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByType(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
consumables, err := ByType(db, TypeConsumable)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get consumable drops: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(consumables) != 4 {
|
||||||
|
t.Errorf("Expected 4 consumable drops, got %d", len(consumables))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify they are ordered by level, then ID
|
||||||
|
if consumables[0].Level > consumables[1].Level {
|
||||||
|
t.Error("Expected drops to be ordered by level")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Create new drop using builder
|
||||||
|
drop, err := NewBuilder(db).
|
||||||
|
WithName("Test Drop").
|
||||||
|
WithLevel(15).
|
||||||
|
WithType(TypeConsumable).
|
||||||
|
WithAtt("strength,20,dexterity,15").
|
||||||
|
Create()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create drop with builder: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if drop.ID == 0 {
|
||||||
|
t.Error("Expected non-zero ID after creation")
|
||||||
|
}
|
||||||
|
if drop.Name != "Test Drop" {
|
||||||
|
t.Errorf("Expected name 'Test Drop', got '%s'", drop.Name)
|
||||||
|
}
|
||||||
|
if drop.Level != 15 {
|
||||||
|
t.Errorf("Expected level 15, got %d", drop.Level)
|
||||||
|
}
|
||||||
|
if drop.Type != TypeConsumable {
|
||||||
|
t.Errorf("Expected type %d, got %d", TypeConsumable, drop.Type)
|
||||||
|
}
|
||||||
|
if drop.Att != "strength,20,dexterity,15" {
|
||||||
|
t.Errorf("Expected att 'strength,20,dexterity,15', got '%s'", drop.Att)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it was saved to database
|
||||||
|
foundDrop, err := Find(db, drop.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find created drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundDrop.Name != "Test Drop" {
|
||||||
|
t.Errorf("Created drop not found in database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSave(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
drop, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify drop
|
||||||
|
drop.Name = "Updated Life Pebble"
|
||||||
|
drop.Level = 5
|
||||||
|
drop.Att = "maxhp,15"
|
||||||
|
|
||||||
|
// Save changes
|
||||||
|
err = drop.Save()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify changes were saved
|
||||||
|
updatedDrop, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find updated drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedDrop.Name != "Updated Life Pebble" {
|
||||||
|
t.Errorf("Expected updated name 'Updated Life Pebble', got '%s'", updatedDrop.Name)
|
||||||
|
}
|
||||||
|
if updatedDrop.Level != 5 {
|
||||||
|
t.Errorf("Expected updated level 5, got %d", updatedDrop.Level)
|
||||||
|
}
|
||||||
|
if updatedDrop.Att != "maxhp,15" {
|
||||||
|
t.Errorf("Expected updated att 'maxhp,15', got '%s'", updatedDrop.Att)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
drop, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete drop
|
||||||
|
err = drop.Delete()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to delete drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify drop was deleted
|
||||||
|
_, err = Find(db, 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when finding deleted drop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDropMethods(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
pebble, _ := Find(db, 1)
|
||||||
|
|
||||||
|
// Test IsConsumable
|
||||||
|
if !pebble.IsConsumable() {
|
||||||
|
t.Error("Expected pebble to be consumable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test TypeName
|
||||||
|
if pebble.TypeName() != "Consumable" {
|
||||||
|
t.Errorf("Expected pebble type name 'Consumable', got '%s'", pebble.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -78,8 +78,7 @@ func createTables(db *database.DB) error {
|
|||||||
name TEXT NOT NULL DEFAULT '',
|
name TEXT NOT NULL DEFAULT '',
|
||||||
level INTEGER NOT NULL DEFAULT 0,
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
type INTEGER NOT NULL DEFAULT 0,
|
type INTEGER NOT NULL DEFAULT 0,
|
||||||
att1 TEXT NOT NULL DEFAULT '',
|
att TEXT NOT NULL DEFAULT ''
|
||||||
att2 TEXT NOT NULL DEFAULT ''
|
|
||||||
)`},
|
)`},
|
||||||
{"forum", `CREATE TABLE forum (
|
{"forum", `CREATE TABLE forum (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@ -204,38 +203,38 @@ func populateData(db *database.DB) error {
|
|||||||
fmt.Println("✓ control table populated")
|
fmt.Println("✓ control table populated")
|
||||||
|
|
||||||
dropsSQL := `INSERT INTO drops VALUES
|
dropsSQL := `INSERT INTO drops VALUES
|
||||||
(1, 'Life Pebble', 1, 1, 'maxhp,10', ''),
|
(1, 'Life Pebble', 1, 1, 'maxhp,10'),
|
||||||
(2, 'Life Stone', 10, 1, 'maxhp,25', ''),
|
(2, 'Life Stone', 10, 1, 'maxhp,25'),
|
||||||
(3, 'Life Rock', 25, 1, 'maxhp,50', ''),
|
(3, 'Life Rock', 25, 1, 'maxhp,50'),
|
||||||
(4, 'Magic Pebble', 1, 1, 'maxmp,10', ''),
|
(4, 'Magic Pebble', 1, 1, 'maxmp,10'),
|
||||||
(5, 'Magic Stone', 10, 1, 'maxmp,25', ''),
|
(5, 'Magic Stone', 10, 1, 'maxmp,25'),
|
||||||
(6, 'Magic Rock', 25, 1, 'maxmp,50', ''),
|
(6, 'Magic Rock', 25, 1, 'maxmp,50'),
|
||||||
(7, 'Dragon''s Scale', 10, 1, 'defensepower,25', ''),
|
(7, 'Dragon''s Scale', 10, 1, 'defensepower,25'),
|
||||||
(8, 'Dragon''s Plate', 30, 1, 'defensepower,50', ''),
|
(8, 'Dragon''s Plate', 30, 1, 'defensepower,50'),
|
||||||
(9, 'Dragon''s Claw', 10, 1, 'attackpower,25', ''),
|
(9, 'Dragon''s Claw', 10, 1, 'attackpower,25'),
|
||||||
(10, 'Dragon''s Tooth', 30, 1, 'attackpower,50', ''),
|
(10, 'Dragon''s Tooth', 30, 1, 'attackpower,50'),
|
||||||
(11, 'Dragon''s Tear', 35, 1, 'strength,50', ''),
|
(11, 'Dragon''s Tear', 35, 1, 'strength,50'),
|
||||||
(12, 'Dragon''s Wing', 35, 1, 'dexterity,50', ''),
|
(12, 'Dragon''s Wing', 35, 1, 'dexterity,50'),
|
||||||
(13, 'Demon''s Sin', 35, 1, 'maxhp,-50', 'strength,50'),
|
(13, 'Demon''s Sin', 35, 1, 'maxhp,-50,strength,50'),
|
||||||
(14, 'Demon''s Fall', 35, 1, 'maxmp,-50', 'strength,50'),
|
(14, 'Demon''s Fall', 35, 1, 'maxmp,-50,strength,50'),
|
||||||
(15, 'Demon''s Lie', 45, 1, 'maxhp,-100', 'strength,100'),
|
(15, 'Demon''s Lie', 45, 1, 'maxhp,-100,strength,100'),
|
||||||
(16, 'Demon''s Hate', 45, 1, 'maxmp,-100', 'strength,100'),
|
(16, 'Demon''s Hate', 45, 1, 'maxmp,-100,strength,100'),
|
||||||
(17, 'Angel''s Joy', 25, 1, 'maxhp,25', 'strength,25'),
|
(17, 'Angel''s Joy', 25, 1, 'maxhp,25,strength,25'),
|
||||||
(18, 'Angel''s Rise', 30, 1, 'maxhp,50', 'strength,50'),
|
(18, 'Angel''s Rise', 30, 1, 'maxhp,50,strength,50'),
|
||||||
(19, 'Angel''s Truth', 35, 1, 'maxhp,75', 'strength,75'),
|
(19, 'Angel''s Truth', 35, 1, 'maxhp,75,strength,75'),
|
||||||
(20, 'Angel''s Love', 40, 1, 'maxhp,100', 'strength,100'),
|
(20, 'Angel''s Love', 40, 1, 'maxhp,100,strength,100'),
|
||||||
(21, 'Seraph''s Joy', 25, 1, 'maxmp,25', 'dexterity,25'),
|
(21, 'Seraph''s Joy', 25, 1, 'maxmp,25,dexterity,25'),
|
||||||
(22, 'Seraph''s Rise', 30, 1, 'maxmp,50', 'dexterity,50'),
|
(22, 'Seraph''s Rise', 30, 1, 'maxmp,50,dexterity,50'),
|
||||||
(23, 'Seraph''s Truth', 35, 1, 'maxmp,75', 'dexterity,75'),
|
(23, 'Seraph''s Truth', 35, 1, 'maxmp,75,dexterity,75'),
|
||||||
(24, 'Seraph''s Love', 40, 1, 'maxmp,100', 'dexterity,100'),
|
(24, 'Seraph''s Love', 40, 1, 'maxmp,100,dexterity,100'),
|
||||||
(25, 'Ruby', 50, 1, 'maxhp,150', ''),
|
(25, 'Ruby', 50, 1, 'maxhp,150'),
|
||||||
(26, 'Pearl', 50, 1, 'maxmp,150', ''),
|
(26, 'Pearl', 50, 1, 'maxmp,150'),
|
||||||
(27, 'Emerald', 50, 1, 'strength,150', ''),
|
(27, 'Emerald', 50, 1, 'strength,150'),
|
||||||
(28, 'Topaz', 50, 1, 'dexterity,150', ''),
|
(28, 'Topaz', 50, 1, 'dexterity,150'),
|
||||||
(29, 'Obsidian', 50, 1, 'attackpower,150', ''),
|
(29, 'Obsidian', 50, 1, 'attackpower,150'),
|
||||||
(30, 'Diamond', 50, 1, 'defensepower,150', ''),
|
(30, 'Diamond', 50, 1, 'defensepower,150'),
|
||||||
(31, 'Memory Drop', 5, 1, 'expbonus,10', ''),
|
(31, 'Memory Drop', 5, 1, 'expbonus,10'),
|
||||||
(32, 'Fortune Drop', 5, 1, 'goldbonus,10', '')`
|
(32, 'Fortune Drop', 5, 1, 'goldbonus,10')`
|
||||||
if err := db.Exec(dropsSQL); err != nil {
|
if err := db.Exec(dropsSQL); err != nil {
|
||||||
return fmt.Errorf("failed to populate drops table: %w", err)
|
return fmt.Errorf("failed to populate drops table: %w", err)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user