create initial fights structure
This commit is contained in:
parent
b42f4fc983
commit
d2d4927314
@ -34,7 +34,7 @@ func (d Direction) String() string {
|
||||
func Move(user *users.User, dir Direction) (string, int, int, error) {
|
||||
control := control.Get()
|
||||
|
||||
newX, newY := user.X, user.Y
|
||||
newX, newY := user.GetPosition()
|
||||
switch dir {
|
||||
case North:
|
||||
newY++
|
||||
|
311
internal/models/fights/fights.go
Normal file
311
internal/models/fights/fights.go
Normal file
@ -0,0 +1,311 @@
|
||||
package fights
|
||||
|
||||
import (
|
||||
"dk/internal/store"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Fight represents a fight, past or present
|
||||
type Fight struct {
|
||||
ID int `json:"id"`
|
||||
UserID int `json:"user_id"`
|
||||
MonsterID int `json:"monster_id"`
|
||||
MonsterHP int `json:"monster_hp"`
|
||||
MonsterMaxHP int `json:"monster_max_hp"`
|
||||
MonsterSleep int `json:"monster_sleep"`
|
||||
MonsterImmune int `json:"monster_immune"`
|
||||
UberDamage int `json:"uber_damage"`
|
||||
UberDefense int `json:"uber_defense"`
|
||||
FirstStrike bool `json:"first_strike"`
|
||||
Turn int `json:"turn"`
|
||||
RanAway bool `json:"ran_away"`
|
||||
Victory bool `json:"victory"`
|
||||
Won bool `json:"won"`
|
||||
RewardGold int `json:"reward_gold"`
|
||||
RewardExp int `json:"reward_exp"`
|
||||
Created int64 `json:"created"`
|
||||
Updated int64 `json:"updated"`
|
||||
}
|
||||
|
||||
func (f *Fight) Save() error {
|
||||
f.Updated = time.Now().Unix()
|
||||
return GetStore().UpdateWithRebuild(f.ID, f)
|
||||
}
|
||||
|
||||
func (f *Fight) Delete() error {
|
||||
GetStore().RemoveWithRebuild(f.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(userID, monsterID int) *Fight {
|
||||
now := time.Now().Unix()
|
||||
return &Fight{
|
||||
UserID: userID,
|
||||
MonsterID: monsterID,
|
||||
MonsterHP: 0,
|
||||
MonsterMaxHP: 0,
|
||||
MonsterSleep: 0,
|
||||
MonsterImmune: 0,
|
||||
UberDamage: 0,
|
||||
UberDefense: 0,
|
||||
FirstStrike: false,
|
||||
Turn: 1,
|
||||
RanAway: false,
|
||||
Victory: false,
|
||||
Won: false,
|
||||
RewardGold: 0,
|
||||
RewardExp: 0,
|
||||
Created: now,
|
||||
Updated: now,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks if fight has valid values
|
||||
func (f *Fight) Validate() error {
|
||||
if f.UserID <= 0 {
|
||||
return fmt.Errorf("fight UserID must be positive")
|
||||
}
|
||||
if f.MonsterID <= 0 {
|
||||
return fmt.Errorf("fight MonsterID must be positive")
|
||||
}
|
||||
if f.Turn < 1 {
|
||||
return fmt.Errorf("fight Turn must be at least 1")
|
||||
}
|
||||
if f.MonsterHP < 0 {
|
||||
return fmt.Errorf("fight MonsterHP cannot be negative")
|
||||
}
|
||||
if f.Created <= 0 {
|
||||
return fmt.Errorf("fight Created timestamp must be positive")
|
||||
}
|
||||
if f.Updated <= 0 {
|
||||
return fmt.Errorf("fight Updated timestamp must be positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FightStore with enhanced BaseStore
|
||||
type FightStore struct {
|
||||
*store.BaseStore[Fight]
|
||||
}
|
||||
|
||||
// Global store with singleton pattern
|
||||
var GetStore = store.NewSingleton(func() *FightStore {
|
||||
fs := &FightStore{BaseStore: store.NewBaseStore[Fight]()}
|
||||
|
||||
// Register indices
|
||||
fs.RegisterIndex("byUserID", store.BuildIntGroupIndex(func(f *Fight) int {
|
||||
return f.UserID
|
||||
}))
|
||||
|
||||
fs.RegisterIndex("byMonsterID", store.BuildIntGroupIndex(func(f *Fight) int {
|
||||
return f.MonsterID
|
||||
}))
|
||||
|
||||
fs.RegisterIndex("activeFights", store.BuildFilteredIntGroupIndex(
|
||||
func(f *Fight) bool {
|
||||
return !f.RanAway && !f.Victory
|
||||
},
|
||||
func(f *Fight) int {
|
||||
return f.UserID
|
||||
},
|
||||
))
|
||||
|
||||
fs.RegisterIndex("allByCreated", store.BuildSortedListIndex(func(a, b *Fight) bool {
|
||||
if a.Created != b.Created {
|
||||
return a.Created > b.Created // DESC
|
||||
}
|
||||
return a.ID > b.ID // DESC
|
||||
}))
|
||||
|
||||
fs.RegisterIndex("allByUpdated", store.BuildSortedListIndex(func(a, b *Fight) bool {
|
||||
if a.Updated != b.Updated {
|
||||
return a.Updated > b.Updated // DESC
|
||||
}
|
||||
return a.ID > b.ID // DESC
|
||||
}))
|
||||
|
||||
return fs
|
||||
})
|
||||
|
||||
// Enhanced CRUD operations
|
||||
func (fs *FightStore) AddFight(fight *Fight) error {
|
||||
return fs.AddWithRebuild(fight.ID, fight)
|
||||
}
|
||||
|
||||
func (fs *FightStore) RemoveFight(id int) {
|
||||
fs.RemoveWithRebuild(id)
|
||||
}
|
||||
|
||||
func (fs *FightStore) UpdateFight(fight *Fight) error {
|
||||
return fs.UpdateWithRebuild(fight.ID, fight)
|
||||
}
|
||||
|
||||
// Data persistence
|
||||
func LoadData(dataPath string) error {
|
||||
fs := GetStore()
|
||||
return fs.BaseStore.LoadData(dataPath)
|
||||
}
|
||||
|
||||
func SaveData(dataPath string) error {
|
||||
fs := GetStore()
|
||||
return fs.BaseStore.SaveData(dataPath)
|
||||
}
|
||||
|
||||
// Query functions using enhanced store
|
||||
func Find(id int) (*Fight, error) {
|
||||
fs := GetStore()
|
||||
fight, exists := fs.Find(id)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("fight with ID %d not found", id)
|
||||
}
|
||||
return fight, nil
|
||||
}
|
||||
|
||||
func All() ([]*Fight, error) {
|
||||
fs := GetStore()
|
||||
return fs.AllSorted("allByCreated"), nil
|
||||
}
|
||||
|
||||
func ByUserID(userID int) ([]*Fight, error) {
|
||||
fs := GetStore()
|
||||
return fs.GroupByIndex("byUserID", userID), nil
|
||||
}
|
||||
|
||||
func ByMonsterID(monsterID int) ([]*Fight, error) {
|
||||
fs := GetStore()
|
||||
return fs.GroupByIndex("byMonsterID", monsterID), nil
|
||||
}
|
||||
|
||||
func ActiveByUserID(userID int) ([]*Fight, error) {
|
||||
fs := GetStore()
|
||||
return fs.GroupByIndex("activeFights", userID), nil
|
||||
}
|
||||
|
||||
func Active() ([]*Fight, error) {
|
||||
fs := GetStore()
|
||||
result := fs.FilterByIndex("allByCreated", func(f *Fight) bool {
|
||||
return !f.RanAway && !f.Victory
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func Recent(within time.Duration) ([]*Fight, error) {
|
||||
fs := GetStore()
|
||||
cutoff := time.Now().Add(-within).Unix()
|
||||
|
||||
result := fs.FilterByIndex("allByCreated", func(f *Fight) bool {
|
||||
return f.Created >= cutoff
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Insert with ID assignment
|
||||
func (f *Fight) Insert() error {
|
||||
fs := GetStore()
|
||||
if f.ID == 0 {
|
||||
f.ID = fs.GetNextID()
|
||||
}
|
||||
f.Updated = time.Now().Unix()
|
||||
return fs.AddFight(f)
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
func (f *Fight) CreatedTime() time.Time {
|
||||
return time.Unix(f.Created, 0)
|
||||
}
|
||||
|
||||
func (f *Fight) UpdatedTime() time.Time {
|
||||
return time.Unix(f.Updated, 0)
|
||||
}
|
||||
|
||||
func (f *Fight) IsActive() bool {
|
||||
return !f.RanAway && !f.Victory
|
||||
}
|
||||
|
||||
func (f *Fight) IsComplete() bool {
|
||||
return f.RanAway || f.Victory
|
||||
}
|
||||
|
||||
func (f *Fight) GetStatus() string {
|
||||
if f.Won {
|
||||
return "Won"
|
||||
}
|
||||
if f.Victory && !f.Won {
|
||||
return "Lost"
|
||||
}
|
||||
if f.RanAway {
|
||||
return "Ran Away"
|
||||
}
|
||||
return "Active"
|
||||
}
|
||||
|
||||
func (f *Fight) GetMonsterHealthPercent() float64 {
|
||||
if f.MonsterMaxHP <= 0 {
|
||||
return 0.0
|
||||
}
|
||||
return float64(f.MonsterHP) / float64(f.MonsterMaxHP) * 100.0
|
||||
}
|
||||
|
||||
func (f *Fight) IsMonsterSleeping() bool {
|
||||
return f.MonsterSleep > 0
|
||||
}
|
||||
|
||||
func (f *Fight) IsMonsterImmune() bool {
|
||||
return f.MonsterImmune > 0
|
||||
}
|
||||
|
||||
func (f *Fight) HasUberBonus() bool {
|
||||
return f.UberDamage > 0 || f.UberDefense > 0
|
||||
}
|
||||
|
||||
func (f *Fight) GetDuration() time.Duration {
|
||||
return time.Unix(f.Updated, 0).Sub(time.Unix(f.Created, 0))
|
||||
}
|
||||
|
||||
func (f *Fight) EndFight(victory bool) {
|
||||
f.Victory = victory
|
||||
f.RanAway = !victory
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) WinFight(goldReward, expReward int) {
|
||||
f.Victory = true
|
||||
f.Won = true
|
||||
f.RanAway = false
|
||||
f.RewardGold = goldReward
|
||||
f.RewardExp = expReward
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) LoseFight() {
|
||||
f.Victory = true
|
||||
f.Won = false
|
||||
f.RanAway = false
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) RunAway() {
|
||||
f.RanAway = true
|
||||
f.Victory = false
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) IncrementTurn() {
|
||||
f.Turn++
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) SetMonsterHP(hp int) {
|
||||
f.MonsterHP = hp
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) DamageMonster(damage int) {
|
||||
f.MonsterHP -= damage
|
||||
if f.MonsterHP < 0 {
|
||||
f.MonsterHP = 0
|
||||
}
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
@ -13,56 +13,49 @@ import (
|
||||
|
||||
// User represents a user in the game
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Verified int `json:"verified"`
|
||||
Token string `json:"token"`
|
||||
Registered int64 `json:"registered"`
|
||||
LastOnline int64 `json:"last_online"`
|
||||
Auth int `json:"auth"`
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
ClassID int `json:"class_id"`
|
||||
Currently string `json:"currently"`
|
||||
Fighting int `json:"fighting"`
|
||||
MonsterID int `json:"monster_id"`
|
||||
MonsterHP int `json:"monster_hp"`
|
||||
MonsterSleep int `json:"monster_sleep"`
|
||||
MonsterImmune int `json:"monster_immune"`
|
||||
UberDamage int `json:"uber_damage"`
|
||||
UberDefense int `json:"uber_defense"`
|
||||
HP int `json:"hp"`
|
||||
MP int `json:"mp"`
|
||||
TP int `json:"tp"`
|
||||
MaxHP int `json:"max_hp"`
|
||||
MaxMP int `json:"max_mp"`
|
||||
MaxTP int `json:"max_tp"`
|
||||
Level int `json:"level"`
|
||||
Gold int `json:"gold"`
|
||||
Exp int `json:"exp"`
|
||||
GoldBonus int `json:"gold_bonus"`
|
||||
ExpBonus int `json:"exp_bonus"`
|
||||
Strength int `json:"strength"`
|
||||
Dexterity int `json:"dexterity"`
|
||||
Attack int `json:"attack"`
|
||||
Defense int `json:"defense"`
|
||||
WeaponID int `json:"weapon_id"`
|
||||
ArmorID int `json:"armor_id"`
|
||||
ShieldID int `json:"shield_id"`
|
||||
Slot1ID int `json:"slot_1_id"`
|
||||
Slot2ID int `json:"slot_2_id"`
|
||||
Slot3ID int `json:"slot_3_id"`
|
||||
WeaponName string `json:"weapon_name"`
|
||||
ArmorName string `json:"armor_name"`
|
||||
ShieldName string `json:"shield_name"`
|
||||
Slot1Name string `json:"slot_1_name"`
|
||||
Slot2Name string `json:"slot_2_name"`
|
||||
Slot3Name string `json:"slot_3_name"`
|
||||
DropCode int `json:"drop_code"`
|
||||
Spells string `json:"spells"`
|
||||
Towns string `json:"towns"`
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Verified int `json:"verified"`
|
||||
Token string `json:"token"`
|
||||
Registered int64 `json:"registered"`
|
||||
LastOnline int64 `json:"last_online"`
|
||||
Auth int `json:"auth"`
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
ClassID int `json:"class_id"`
|
||||
Currently string `json:"currently"`
|
||||
FightID int `json:"fight_id"`
|
||||
HP int `json:"hp"`
|
||||
MP int `json:"mp"`
|
||||
TP int `json:"tp"`
|
||||
MaxHP int `json:"max_hp"`
|
||||
MaxMP int `json:"max_mp"`
|
||||
MaxTP int `json:"max_tp"`
|
||||
Level int `json:"level"`
|
||||
Gold int `json:"gold"`
|
||||
Exp int `json:"exp"`
|
||||
GoldBonus int `json:"gold_bonus"`
|
||||
ExpBonus int `json:"exp_bonus"`
|
||||
Strength int `json:"strength"`
|
||||
Dexterity int `json:"dexterity"`
|
||||
Attack int `json:"attack"`
|
||||
Defense int `json:"defense"`
|
||||
WeaponID int `json:"weapon_id"`
|
||||
ArmorID int `json:"armor_id"`
|
||||
ShieldID int `json:"shield_id"`
|
||||
Slot1ID int `json:"slot_1_id"`
|
||||
Slot2ID int `json:"slot_2_id"`
|
||||
Slot3ID int `json:"slot_3_id"`
|
||||
WeaponName string `json:"weapon_name"`
|
||||
ArmorName string `json:"armor_name"`
|
||||
ShieldName string `json:"shield_name"`
|
||||
Slot1Name string `json:"slot_1_name"`
|
||||
Slot2Name string `json:"slot_2_name"`
|
||||
Slot3Name string `json:"slot_3_name"`
|
||||
Spells string `json:"spells"`
|
||||
Towns string `json:"towns"`
|
||||
}
|
||||
|
||||
func (u *User) Save() error {
|
||||
@ -86,7 +79,7 @@ func New() *User {
|
||||
Y: 0,
|
||||
ClassID: 1,
|
||||
Currently: "In Town",
|
||||
Fighting: 0,
|
||||
FightID: 0,
|
||||
HP: 10,
|
||||
MP: 10,
|
||||
TP: 10,
|
||||
@ -289,7 +282,7 @@ func (u *User) IsModerator() bool {
|
||||
}
|
||||
|
||||
func (u *User) IsFighting() bool {
|
||||
return u.Fighting == 1
|
||||
return u.FightID > 0
|
||||
}
|
||||
|
||||
func (u *User) IsAlive() bool {
|
||||
@ -333,19 +326,17 @@ func (u *User) GetEquipment() map[string]any {
|
||||
|
||||
func (u *User) GetStats() map[string]int {
|
||||
return map[string]int{
|
||||
"level": u.Level,
|
||||
"hp": u.HP,
|
||||
"mp": u.MP,
|
||||
"tp": u.TP,
|
||||
"max_hp": u.MaxHP,
|
||||
"max_mp": u.MaxMP,
|
||||
"max_tp": u.MaxTP,
|
||||
"strength": u.Strength,
|
||||
"dexterity": u.Dexterity,
|
||||
"attack": u.Attack,
|
||||
"defense": u.Defense,
|
||||
"uber_damage": u.UberDamage,
|
||||
"uber_defense": u.UberDefense,
|
||||
"level": u.Level,
|
||||
"hp": u.HP,
|
||||
"mp": u.MP,
|
||||
"tp": u.TP,
|
||||
"max_hp": u.MaxHP,
|
||||
"max_mp": u.MaxMP,
|
||||
"max_tp": u.MaxTP,
|
||||
"strength": u.Strength,
|
||||
"dexterity": u.Dexterity,
|
||||
"attack": u.Attack,
|
||||
"defense": u.Defense,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ func Move(ctx router.Ctx, _ []string) {
|
||||
}
|
||||
|
||||
user.Currently = currently
|
||||
user.X, user.Y = newX, newY
|
||||
user.SetPosition(newX, newY)
|
||||
|
||||
if currently == "In Town" {
|
||||
ctx.Redirect("/town", 303)
|
||||
@ -95,7 +95,7 @@ func Teleport(ctx router.Ctx, params []string) {
|
||||
}
|
||||
|
||||
user.TP -= town.TPCost
|
||||
user.X, user.Y = town.X, town.Y
|
||||
user.SetPosition(town.X, town.Y)
|
||||
user.Currently = "In Town"
|
||||
user.Save()
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@ -484,3 +484,43 @@ func (bs *BaseStore[T]) SaveData(dataPath string) error {
|
||||
fmt.Printf("Saved %d items to %s\n", len(bs.items), dataPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildFilteredIntGroupIndex creates int-to-[]ID mapping for items passing filter
|
||||
func BuildFilteredIntGroupIndex[T any](filterFunc func(*T) bool, keyFunc func(*T) int) IndexBuilder[T] {
|
||||
return func(allItems map[int]*T) any {
|
||||
index := make(map[int][]int)
|
||||
for id, item := range allItems {
|
||||
if filterFunc(item) {
|
||||
key := keyFunc(item)
|
||||
index[key] = append(index[key], id)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort each group by ID
|
||||
for key := range index {
|
||||
sort.Ints(index[key])
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
// BuildFilteredStringGroupIndex creates string-to-[]ID mapping for items passing filter
|
||||
func BuildFilteredStringGroupIndex[T any](filterFunc func(*T) bool, keyFunc func(*T) string) IndexBuilder[T] {
|
||||
return func(allItems map[int]*T) any {
|
||||
index := make(map[string][]int)
|
||||
for id, item := range allItems {
|
||||
if filterFunc(item) {
|
||||
key := keyFunc(item)
|
||||
index[key] = append(index[key], id)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort each group by ID
|
||||
for key := range index {
|
||||
sort.Ints(index[key])
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
23
main.go
23
main.go
@ -15,6 +15,7 @@ import (
|
||||
"dk/internal/models/babble"
|
||||
"dk/internal/models/control"
|
||||
"dk/internal/models/drops"
|
||||
"dk/internal/models/fights"
|
||||
"dk/internal/models/forum"
|
||||
"dk/internal/models/items"
|
||||
"dk/internal/models/monsters"
|
||||
@ -52,9 +53,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func loadModels() error {
|
||||
dataDir := "data"
|
||||
|
||||
func loadModels(dataDir string) error {
|
||||
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create data directory: %w", err)
|
||||
}
|
||||
@ -99,12 +98,14 @@ func loadModels() error {
|
||||
return fmt.Errorf("failed to load control data: %w", err)
|
||||
}
|
||||
|
||||
if err := fights.LoadData(filepath.Join(dataDir, "fights.json")); err != nil {
|
||||
return fmt.Errorf("failed to load fights data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveModels() error {
|
||||
dataDir := "data"
|
||||
|
||||
func saveModels(dataDir string) error {
|
||||
if err := users.SaveData(filepath.Join(dataDir, "users.json")); err != nil {
|
||||
return fmt.Errorf("failed to save users data: %w", err)
|
||||
}
|
||||
@ -145,6 +146,10 @@ func saveModels() error {
|
||||
return fmt.Errorf("failed to save control data: %w", err)
|
||||
}
|
||||
|
||||
if err := fights.SaveData(filepath.Join(dataDir, "fights.json")); err != nil {
|
||||
return fmt.Errorf("failed to save fights data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -163,11 +168,11 @@ func start(port string) error {
|
||||
|
||||
template.InitializeCache(cwd)
|
||||
|
||||
if err := loadModels(); err != nil {
|
||||
if err := loadModels(filepath.Join(cwd, "data")); err != nil {
|
||||
return fmt.Errorf("failed to load models: %w", err)
|
||||
}
|
||||
|
||||
session.Init("data/_sessions.json")
|
||||
session.Init(filepath.Join(cwd, "data/_sessions.json"))
|
||||
|
||||
r := router.New()
|
||||
r.Use(middleware.Timing())
|
||||
@ -238,7 +243,7 @@ func start(port string) error {
|
||||
|
||||
// Save all model data before shutdown
|
||||
log.Println("Saving model data...")
|
||||
if err := saveModels(); err != nil {
|
||||
if err := saveModels(filepath.Join(cwd, "data")); err != nil {
|
||||
log.Printf("Error saving model data: %v", err)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user