new spell system
This commit is contained in:
parent
63dabb9e54
commit
2e3a977530
BIN
data/dk.db
BIN
data/dk.db
Binary file not shown.
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module dk
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sharkk.net/Sharkk/Sashimi v1.1.3
|
git.sharkk.net/Sharkk/Sashimi v1.1.4
|
||||||
git.sharkk.net/Sharkk/Sushi v1.2.0
|
git.sharkk.net/Sharkk/Sushi v1.2.0
|
||||||
github.com/valyala/fasthttp v1.65.0
|
github.com/valyala/fasthttp v1.65.0
|
||||||
)
|
)
|
||||||
|
4
go.sum
4
go.sum
@ -1,5 +1,5 @@
|
|||||||
git.sharkk.net/Sharkk/Sashimi v1.1.3 h1:fY63Zn//A1EffFkoKjCQseRmLFNRibNDZYPUur5SF1s=
|
git.sharkk.net/Sharkk/Sashimi v1.1.4 h1:aULzzz4Qqpl69Vtpbi7zYYvay4J/HzButYXLwPzB/xw=
|
||||||
git.sharkk.net/Sharkk/Sashimi v1.1.3/go.mod h1:wTMnO6jo34LIjpDJ0qToq14RbwP6Uf4HtdWDmqxrdAM=
|
git.sharkk.net/Sharkk/Sashimi v1.1.4/go.mod h1:wTMnO6jo34LIjpDJ0qToq14RbwP6Uf4HtdWDmqxrdAM=
|
||||||
git.sharkk.net/Sharkk/Sushi v1.2.0 h1:RwOCZmgaOqtkmuK2Z7/esdLbhSXJZphsOsWEHni4Sss=
|
git.sharkk.net/Sharkk/Sushi v1.2.0 h1:RwOCZmgaOqtkmuK2Z7/esdLbhSXJZphsOsWEHni4Sss=
|
||||||
git.sharkk.net/Sharkk/Sushi v1.2.0/go.mod h1:S84ACGkuZ+BKzBO4lb5WQnm5aw9+l7VSO2T1bjzxL3o=
|
git.sharkk.net/Sharkk/Sushi v1.2.0/go.mod h1:S84ACGkuZ+BKzBO4lb5WQnm5aw9+l7VSO2T1bjzxL3o=
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
|
@ -2,7 +2,6 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"dk/internal/database"
|
"dk/internal/database"
|
||||||
"dk/internal/helpers/exp"
|
|
||||||
"dk/internal/models/fightlogs"
|
"dk/internal/models/fightlogs"
|
||||||
"dk/internal/models/fights"
|
"dk/internal/models/fights"
|
||||||
"dk/internal/models/monsters"
|
"dk/internal/models/monsters"
|
||||||
@ -147,22 +146,24 @@ func HandleSpell(fight *fights.Fight, user *users.User, spellID int) *FightResul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("new MP is %d\n", user.MP-spell.MP)
|
||||||
|
|
||||||
result := &FightResult{
|
result := &FightResult{
|
||||||
UserUpdates: map[string]any{"mp": user.MP - spell.MP},
|
UserUpdates: map[string]any{"mp": user.MP - spell.MP},
|
||||||
}
|
}
|
||||||
|
|
||||||
switch spell.Type {
|
switch spell.Type {
|
||||||
case spells.TypeHealing:
|
case spells.TypeHeal:
|
||||||
newHP := min(user.HP+spell.Attribute, user.MaxHP)
|
newHP := min(user.HP+spell.Power, user.MaxHP)
|
||||||
result.UserUpdates["hp"] = newHP
|
result.UserUpdates["hp"] = newHP
|
||||||
result.ActionText = fmt.Sprintf("You cast %s and healed %d HP!", spell.Name, spell.Attribute)
|
result.ActionText = fmt.Sprintf("You cast %s and healed %d HP!", spell.Name, spell.Power)
|
||||||
result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Attribute) }
|
result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Power) }
|
||||||
|
|
||||||
case spells.TypeHurt:
|
case spells.TypeDamage:
|
||||||
newMonsterHP := max(fight.MonsterHP-spell.Attribute, 0)
|
newMonsterHP := max(fight.MonsterHP-spell.Power, 0)
|
||||||
result.FightUpdates = map[string]any{"monster_hp": newMonsterHP}
|
result.FightUpdates = map[string]any{"monster_hp": newMonsterHP}
|
||||||
result.ActionText = fmt.Sprintf("You cast %s and dealt %d damage!", spell.Name, spell.Attribute)
|
result.ActionText = fmt.Sprintf("You cast %s and dealt %d damage!", spell.Name, spell.Power)
|
||||||
result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Attribute) }
|
result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Power) }
|
||||||
|
|
||||||
if newMonsterHP <= 0 {
|
if newMonsterHP <= 0 {
|
||||||
monster, err := monsters.Find(fight.MonsterID)
|
monster, err := monsters.Find(fight.MonsterID)
|
||||||
@ -172,6 +173,33 @@ func HandleSpell(fight *fights.Fight, user *users.User, spellID int) *FightResul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case spells.TypeSleep:
|
||||||
|
// For now, sleep spells do damage like the old system
|
||||||
|
newMonsterHP := max(fight.MonsterHP-spell.Power, 0)
|
||||||
|
result.FightUpdates = map[string]any{"monster_hp": newMonsterHP}
|
||||||
|
result.ActionText = fmt.Sprintf("You cast %s and dealt %d damage!", spell.Name, spell.Power)
|
||||||
|
result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Power) }
|
||||||
|
|
||||||
|
if newMonsterHP <= 0 {
|
||||||
|
monster, err := monsters.Find(fight.MonsterID)
|
||||||
|
if err == nil {
|
||||||
|
result.EndFightWithVictory(monster, user)
|
||||||
|
result.ActionText += " " + fmt.Sprintf("%s has been defeated!", monster.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case spells.TypeUberAttack:
|
||||||
|
// Apply attack buff for next attack
|
||||||
|
result.FightUpdates = map[string]any{"uber_damage": spell.Power}
|
||||||
|
result.ActionText = fmt.Sprintf("You cast %s! Your next attack will be %d%% stronger!", spell.Name, spell.Power)
|
||||||
|
result.LogAction = func() error { return fightlogs.AddAction(fight.ID, result.ActionText) }
|
||||||
|
|
||||||
|
case spells.TypeUberDefense:
|
||||||
|
// Apply defense buff
|
||||||
|
result.FightUpdates = map[string]any{"uber_defense": spell.Power}
|
||||||
|
result.ActionText = fmt.Sprintf("You cast %s! You will take %d%% less damage!", spell.Name, spell.Power)
|
||||||
|
result.LogAction = func() error { return fightlogs.AddAction(fight.ID, result.ActionText) }
|
||||||
|
|
||||||
default:
|
default:
|
||||||
result.ActionText = "You cast " + spell.Name + " but nothing happened!"
|
result.ActionText = "You cast " + spell.Name + " but nothing happened!"
|
||||||
result.LogAction = func() error { return fightlogs.AddAction(fight.ID, result.ActionText) }
|
result.LogAction = func() error { return fightlogs.AddAction(fight.ID, result.ActionText) }
|
||||||
@ -266,31 +294,6 @@ func HandleMonsterAttack(fight *fights.Fight, user *users.User) *FightResult {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
type LevelStats struct {
|
|
||||||
Strength int
|
|
||||||
Dexterity int
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateLevelUp(currentLevel, newExp, currentStr, currentDex int) (int, LevelStats) {
|
|
||||||
level := currentLevel
|
|
||||||
str := currentStr
|
|
||||||
dex := currentDex
|
|
||||||
nexp := newExp
|
|
||||||
|
|
||||||
for {
|
|
||||||
expNeeded := exp.Calc(level + 1)
|
|
||||||
if nexp < expNeeded {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
level++
|
|
||||||
str++
|
|
||||||
dex++
|
|
||||||
nexp -= expNeeded
|
|
||||||
}
|
|
||||||
|
|
||||||
return level, LevelStats{Strength: str, Dexterity: dex}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findClosestTown(x, y int) *towns.Town {
|
func findClosestTown(x, y int) *towns.Town {
|
||||||
allTowns, err := towns.All()
|
allTowns, err := towns.All()
|
||||||
if err != nil || len(allTowns) == 0 {
|
if err != nil || len(allTowns) == 0 {
|
||||||
|
@ -2,7 +2,6 @@ package components
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"dk/internal/helpers"
|
"dk/internal/helpers"
|
||||||
"dk/internal/models/spells"
|
|
||||||
"dk/internal/models/towns"
|
"dk/internal/models/towns"
|
||||||
"dk/internal/models/users"
|
"dk/internal/models/users"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -66,11 +65,12 @@ func RightAside(ctx sushi.Ctx) map[string]any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build known healing spells list
|
// Build known healing spells list
|
||||||
if user.Spells != "" {
|
userSpells, err := user.GetSpells()
|
||||||
|
if err == nil {
|
||||||
spellMap := helpers.NewOrderedMap[int, string]()
|
spellMap := helpers.NewOrderedMap[int, string]()
|
||||||
for _, id := range user.GetSpellIDs() {
|
for _, spell := range userSpells {
|
||||||
if spell, err := spells.Find(id); err == nil {
|
if spell.IsHeal() {
|
||||||
spellMap.Set(id, spell.Name)
|
spellMap.Set(spell.ID, spell.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data["_spells"] = spellMap.ToSlice()
|
data["_spells"] = spellMap.ToSlice()
|
||||||
|
@ -8,29 +8,33 @@ import (
|
|||||||
|
|
||||||
// Spell represents a spell in the game
|
// Spell represents a spell in the game
|
||||||
type Spell struct {
|
type Spell struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Type int
|
||||||
MP int
|
Name string
|
||||||
Attribute int
|
Lore string
|
||||||
Type int
|
Icon string
|
||||||
|
MP int
|
||||||
|
Power int
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpellType constants for spell types
|
// SpellType constants for spell types
|
||||||
const (
|
const (
|
||||||
TypeHealing = 1
|
TypeHeal = 0
|
||||||
TypeHurt = 2
|
TypeDamage = 1
|
||||||
TypeSleep = 3
|
TypeSleep = 2
|
||||||
TypeAttackBoost = 4
|
TypeUberAttack = 3
|
||||||
TypeDefenseBoost = 5
|
TypeUberDefense = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new Spell with sensible defaults
|
// New creates a new Spell with sensible defaults
|
||||||
func New() *Spell {
|
func New() *Spell {
|
||||||
return &Spell{
|
return &Spell{
|
||||||
Name: "",
|
Type: TypeHeal,
|
||||||
MP: 5,
|
Name: "",
|
||||||
Attribute: 10,
|
Lore: "",
|
||||||
Type: TypeHealing,
|
Icon: "",
|
||||||
|
MP: 5,
|
||||||
|
Power: 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,10 +46,10 @@ func (s *Spell) Validate() error {
|
|||||||
if s.MP < 0 {
|
if s.MP < 0 {
|
||||||
return fmt.Errorf("spell MP cannot be negative")
|
return fmt.Errorf("spell MP cannot be negative")
|
||||||
}
|
}
|
||||||
if s.Attribute < 0 {
|
if s.Power < 0 {
|
||||||
return fmt.Errorf("spell Attribute cannot be negative")
|
return fmt.Errorf("spell Power cannot be negative")
|
||||||
}
|
}
|
||||||
if s.Type < TypeHealing || s.Type > TypeDefenseBoost {
|
if s.Type < TypeHeal || s.Type > TypeUberDefense {
|
||||||
return fmt.Errorf("invalid spell type: %d", s.Type)
|
return fmt.Errorf("invalid spell type: %d", s.Type)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -108,39 +112,83 @@ func ByName(name string) (*Spell, error) {
|
|||||||
return &spell, nil
|
return &spell, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper methods
|
// Spell unlock functions
|
||||||
func (s *Spell) IsHealing() bool {
|
func UnlocksForClassAtLevel(classID, level int) ([]*Spell, error) {
|
||||||
return s.Type == TypeHealing
|
var spells []*Spell
|
||||||
|
query := `
|
||||||
|
SELECT s.* FROM spells s
|
||||||
|
JOIN spell_unlocks u ON s.id = u.spell_id
|
||||||
|
WHERE u.class_id = %d AND u.level = %d
|
||||||
|
ORDER BY s.type ASC, s.mp ASC, s.id ASC`
|
||||||
|
err := database.Select(&spells, query, classID, level)
|
||||||
|
return spells, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spell) IsHurt() bool {
|
func UserSpells(userID int) ([]*Spell, error) {
|
||||||
return s.Type == TypeHurt
|
var spells []*Spell
|
||||||
|
query := `
|
||||||
|
SELECT s.* FROM spells s
|
||||||
|
JOIN user_spells us ON s.id = us.spell_id
|
||||||
|
WHERE us.user_id = %d
|
||||||
|
ORDER BY s.type ASC, s.mp ASC, s.id ASC`
|
||||||
|
err := database.Select(&spells, query, userID)
|
||||||
|
return spells, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GrantSpell(userID, spellID int) error {
|
||||||
|
return database.Exec("INSERT OR IGNORE INTO user_spells (user_id, spell_id) VALUES (%d, %d)", userID, spellID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GrantSpells(userID int, spellIDs []int) error {
|
||||||
|
return database.Transaction(func() error {
|
||||||
|
for _, spellID := range spellIDs {
|
||||||
|
if err := GrantSpell(userID, spellID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasSpell(userID, spellID int) bool {
|
||||||
|
var count int
|
||||||
|
err := database.Get(&count, "SELECT COUNT(*) FROM user_spells WHERE user_id = %d AND spell_id = %d", userID, spellID)
|
||||||
|
return err == nil && count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
func (s *Spell) IsHeal() bool {
|
||||||
|
return s.Type == TypeHeal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spell) IsDamage() bool {
|
||||||
|
return s.Type == TypeDamage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spell) IsSleep() bool {
|
func (s *Spell) IsSleep() bool {
|
||||||
return s.Type == TypeSleep
|
return s.Type == TypeSleep
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spell) IsAttackBoost() bool {
|
func (s *Spell) IsUberAttack() bool {
|
||||||
return s.Type == TypeAttackBoost
|
return s.Type == TypeUberAttack
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spell) IsDefenseBoost() bool {
|
func (s *Spell) IsUberDefense() bool {
|
||||||
return s.Type == TypeDefenseBoost
|
return s.Type == TypeUberDefense
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spell) TypeName() string {
|
func (s *Spell) TypeName() string {
|
||||||
switch s.Type {
|
switch s.Type {
|
||||||
case TypeHealing:
|
case TypeHeal:
|
||||||
return "Healing"
|
return "Heal"
|
||||||
case TypeHurt:
|
case TypeDamage:
|
||||||
return "Hurt"
|
return "Damage"
|
||||||
case TypeSleep:
|
case TypeSleep:
|
||||||
return "Sleep"
|
return "Sleep"
|
||||||
case TypeAttackBoost:
|
case TypeUberAttack:
|
||||||
return "Attack Boost"
|
return "Uber Attack"
|
||||||
case TypeDefenseBoost:
|
case TypeUberDefense:
|
||||||
return "Defense Boost"
|
return "Uber Defense"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
@ -154,13 +202,13 @@ func (s *Spell) Efficiency() float64 {
|
|||||||
if s.MP == 0 {
|
if s.MP == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return float64(s.Attribute) / float64(s.MP)
|
return float64(s.Power) / float64(s.MP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spell) IsOffensive() bool {
|
func (s *Spell) IsOffensive() bool {
|
||||||
return s.Type == TypeHurt || s.Type == TypeSleep
|
return s.Type == TypeDamage || s.Type == TypeSleep
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spell) IsSupport() bool {
|
func (s *Spell) IsSupport() bool {
|
||||||
return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost
|
return s.Type == TypeHeal || s.Type == TypeUberAttack || s.Type == TypeUberDefense
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"dk/internal/helpers"
|
"dk/internal/helpers"
|
||||||
"dk/internal/helpers/exp"
|
"dk/internal/helpers/exp"
|
||||||
"dk/internal/models/classes"
|
"dk/internal/models/classes"
|
||||||
|
"dk/internal/models/spells"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User represents a user in the game
|
// User represents a user in the game
|
||||||
@ -208,16 +209,34 @@ func (u *User) IsAlive() bool {
|
|||||||
return u.HP > 0
|
return u.HP > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) GetSpellIDs() []int {
|
func (u *User) GetSpells() ([]*spells.Spell, error) {
|
||||||
return helpers.StringToInts(u.Spells)
|
return spells.UserSpells(u.ID)
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) SetSpellIDs(spells []int) {
|
|
||||||
u.Spells = helpers.IntsToString(spells)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) HasSpell(spellID int) bool {
|
func (u *User) HasSpell(spellID int) bool {
|
||||||
return slices.Contains(u.GetSpellIDs(), spellID)
|
return spells.HasSpell(u.ID, spellID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GrantSpell(spellID int) error {
|
||||||
|
return spells.GrantSpell(u.ID, spellID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GrantSpells(spellIDs []int) error {
|
||||||
|
return spells.GrantSpells(u.ID, spellIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) LearnNewSpells() error {
|
||||||
|
newSpells, err := spells.UnlocksForClassAtLevel(u.ClassID, u.Level)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var spellIDs []int
|
||||||
|
for _, spell := range newSpells {
|
||||||
|
spellIDs = append(spellIDs, spell.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.GrantSpells(spellIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) GetTownIDs() []int {
|
func (u *User) GetTownIDs() []int {
|
||||||
@ -273,6 +292,7 @@ func (u *User) ExpNeededForNextLevel() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) GrantExp(expAmount int) map[string]any {
|
func (u *User) GrantExp(expAmount int) map[string]any {
|
||||||
|
oldLevel := u.Level
|
||||||
newLevel, newStr, newDex, newExp := u.CalculateLevelUp(expAmount)
|
newLevel, newStr, newDex, newExp := u.CalculateLevelUp(expAmount)
|
||||||
|
|
||||||
updates := map[string]any{
|
updates := map[string]any{
|
||||||
@ -280,10 +300,18 @@ func (u *User) GrantExp(expAmount int) map[string]any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only include level/stats if they actually changed
|
// Only include level/stats if they actually changed
|
||||||
if newLevel > u.Level {
|
if newLevel > oldLevel {
|
||||||
updates["level"] = newLevel
|
updates["level"] = newLevel
|
||||||
updates["strength"] = newStr
|
updates["strength"] = newStr
|
||||||
updates["dexterity"] = newDex
|
updates["dexterity"] = newDex
|
||||||
|
|
||||||
|
// Learn new spells for each level gained
|
||||||
|
for level := oldLevel + 1; level <= newLevel; level++ {
|
||||||
|
if err := u.learnSpellsForLevel(level); err != nil {
|
||||||
|
// Don't fail the level up if spells fail
|
||||||
|
fmt.Printf("Failed to grant spells for level %d to user %d: %v\n", level, u.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return updates
|
return updates
|
||||||
@ -333,3 +361,17 @@ func (u *User) Class() *classes.Class {
|
|||||||
}
|
}
|
||||||
return class
|
return class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) learnSpellsForLevel(level int) error {
|
||||||
|
newSpells, err := spells.UnlocksForClassAtLevel(u.ClassID, level)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var spellIDs []int
|
||||||
|
for _, spell := range newSpells {
|
||||||
|
spellIDs = append(spellIDs, spell.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.GrantSpells(spellIDs)
|
||||||
|
}
|
||||||
|
@ -165,6 +165,12 @@ func processRegister(ctx sushi.Ctx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grant level 1 spells for their class
|
||||||
|
if err := user.LearnNewSpells(); err != nil {
|
||||||
|
// Don't fail registration if spells fail, just log it
|
||||||
|
fmt.Printf("Failed to grant initial spells to user %d: %v\n", user.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-login after registration
|
// Auto-login after registration
|
||||||
ctx.Login(user.ID, user)
|
ctx.Login(user.ID, user)
|
||||||
|
|
||||||
|
@ -89,13 +89,10 @@ func showFight(ctx sushi.Ctx) {
|
|||||||
monHpColor = "warning"
|
monHpColor = "warning"
|
||||||
}
|
}
|
||||||
|
|
||||||
spellMap := helpers.NewOrderedMap[int, *spells.Spell]()
|
var userSpells []*spells.Spell
|
||||||
if user.Spells != "" {
|
spellList, err := user.GetSpells()
|
||||||
for _, id := range user.GetSpellIDs() {
|
if err == nil {
|
||||||
if spell, err := spells.Find(id); err == nil {
|
userSpells = spellList
|
||||||
spellMap.Set(id, spell)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get recent fight actions
|
// Get recent fight actions
|
||||||
@ -107,7 +104,7 @@ func showFight(ctx sushi.Ctx) {
|
|||||||
"monster": monster,
|
"monster": monster,
|
||||||
"mon_hppct": monHpPct,
|
"mon_hppct": monHpPct,
|
||||||
"mon_hpcol": monHpColor,
|
"mon_hpcol": monHpColor,
|
||||||
"spells": spellMap.ToSlice(),
|
"spells": userSpells,
|
||||||
"action": sess.GetFlashMessage("action"),
|
"action": sess.GetFlashMessage("action"),
|
||||||
"mon_action": sess.GetFlashMessage("mon_action"),
|
"mon_action": sess.GetFlashMessage("mon_action"),
|
||||||
"last_action": lastAction,
|
"last_action": lastAction,
|
||||||
|
57
sql/3_new_spell_system.sql
Normal file
57
sql/3_new_spell_system.sql
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
-- Migration 3: new spell system
|
||||||
|
-- Created: 2025-08-25 22:13:03
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS spells;
|
||||||
|
|
||||||
|
CREATE TABLE spells (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`type` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`name` TEXT NOT NULL,
|
||||||
|
`lore` TEXT DEFAULT '',
|
||||||
|
`icon` TEXT DEFAULT '',
|
||||||
|
`mp` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`power` INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Types: 0 (Heal), 1 (Damage), 2 (Sleep), 3 (Uber Attack), 4 (Uber Defense)
|
||||||
|
INSERT INTO spells VALUES
|
||||||
|
(1, 0, 'Heal', '', '', 5, 10),
|
||||||
|
(2, 0, 'Revive', '', '', 10, 25),
|
||||||
|
(3, 0, 'Life', '', '', 25, 50),
|
||||||
|
(4, 0, 'Breath', '', '', 50, 100),
|
||||||
|
(5, 0, 'Gaia', '', '', 75, 150),
|
||||||
|
(6, 1, 'Hurt', '', '', 5, 15),
|
||||||
|
(7, 1, 'Pain', '', '', 12, 35),
|
||||||
|
(8, 1, 'Maim', '', '', 25, 70),
|
||||||
|
(9, 1, 'Rend', '', '', 40, 100),
|
||||||
|
(10, 1, 'Chaos', '', '', 50, 130),
|
||||||
|
(11, 2, 'Sleep', '', '', 10, 5),
|
||||||
|
(12, 2, 'Dream', '', '', 30, 9),
|
||||||
|
(13, 2, 'Nightmare', '', '', 60, 13),
|
||||||
|
(14, 3, 'Craze', '', '', 10, 10),
|
||||||
|
(15, 3, 'Rage', '', '', 20, 25),
|
||||||
|
(16, 3, 'Fury', '', '', 30, 50),
|
||||||
|
(17, 4, 'Ward', '', '', 10, 10),
|
||||||
|
(18, 4, 'Fend', '', '', 20, 25),
|
||||||
|
(19, 4, 'Barrier', '', '', 30, 50),
|
||||||
|
(20, 2, 'Spark', 'Small jolt of electric energy.', '', 5, 10),
|
||||||
|
(21, 2, 'Firebolt', 'Blast of concentrated fire.', '', 10, 30),
|
||||||
|
(22, 2, 'Geyser', 'Explosion of high-pressure water.', '', 15, 60),
|
||||||
|
(23, 2, 'Magic Missile', 'Fast, tracking bolt of arcane force.', '', 20, 85);
|
||||||
|
|
||||||
|
CREATE TABLE spell_unlocks (
|
||||||
|
`spell_id` INTEGER NOT NULL,
|
||||||
|
`class_id` INTEGER NOT NULL,
|
||||||
|
`level` INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Classes: 1 (Adventurer), 2 (Mage), 3 (Warrior), 4 (Paladin)
|
||||||
|
INSERT INTO spell_unlocks VALUES
|
||||||
|
(1, 1, 3), (6, 1, 3), (11, 1, 7), (14, 1, 7), (17, 1, 7),
|
||||||
|
(20, 2, 1), (21, 2, 5), (22, 2, 12), (23, 2, 22), (11, 2, 7), (17, 2, 10), (19, 2, 24),
|
||||||
|
(1, 4, 1), (2, 4, 5), (3, 4, 10), (4, 4, 20);
|
||||||
|
|
||||||
|
CREATE TABLE user_spells (
|
||||||
|
`user_id` INTEGER NOT NULL,
|
||||||
|
`spell_id` INTEGER NOT NULL
|
||||||
|
);
|
@ -22,7 +22,7 @@
|
|||||||
<div class="mb-05">
|
<div class="mb-05">
|
||||||
<button type="submit" name="action" value="attack" class="btn btn-primary">Attack</button>
|
<button type="submit" name="action" value="attack" class="btn btn-primary">Attack</button>
|
||||||
</div>
|
</div>
|
||||||
{if user.Spells != ""}
|
{if spells}
|
||||||
<div class="mb-05">
|
<div class="mb-05">
|
||||||
<select id="spell-select" class="styled-select" name="spell_id">
|
<select id="spell-select" class="styled-select" name="spell_id">
|
||||||
{for spell in spells}
|
{for spell in spells}
|
||||||
|
5
templates/fight/victory.html
Normal file
5
templates/fight/victory.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{include "layout.html"}
|
||||||
|
|
||||||
|
{block "content"}
|
||||||
|
<h1>Victory!</h1>
|
||||||
|
{/block}
|
Loading…
x
Reference in New Issue
Block a user