implement monster moves, update log tracking to "action codes" for storage, add notices for actions taken
This commit is contained in:
parent
0d3afffb1e
commit
9e17ab9bea
252
data/fights.json
Normal file
252
data/fights.json
Normal file
@ -0,0 +1,252 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"monster_id": 2,
|
||||
"monster_hp": 0,
|
||||
"monster_max_hp": 6,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 0,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": true,
|
||||
"turn": 7,
|
||||
"ran_away": false,
|
||||
"victory": true,
|
||||
"won": true,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [
|
||||
{
|
||||
"t": 2
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 11,
|
||||
"n": "Red Slime"
|
||||
}
|
||||
],
|
||||
"created": 1755264713,
|
||||
"updated": 1755270326
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"user_id": 1,
|
||||
"monster_id": 5,
|
||||
"monster_hp": 7,
|
||||
"monster_max_hp": 10,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 1,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": true,
|
||||
"turn": 3,
|
||||
"ran_away": false,
|
||||
"victory": true,
|
||||
"won": false,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 3,
|
||||
"n": "Shadow"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 2,
|
||||
"n": "Shadow"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 3,
|
||||
"n": "Shadow"
|
||||
}
|
||||
],
|
||||
"created": 1755270342,
|
||||
"updated": 1755270352
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"user_id": 1,
|
||||
"monster_id": 5,
|
||||
"monster_hp": 6,
|
||||
"monster_max_hp": 10,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 1,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": true,
|
||||
"turn": 4,
|
||||
"ran_away": false,
|
||||
"victory": true,
|
||||
"won": false,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 2,
|
||||
"n": "Shadow"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 3,
|
||||
"n": "Shadow"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 3,
|
||||
"n": "Shadow"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 3,
|
||||
"n": "Shadow"
|
||||
}
|
||||
],
|
||||
"created": 1755270581,
|
||||
"updated": 1755270585
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"user_id": 1,
|
||||
"monster_id": 2,
|
||||
"monster_hp": 0,
|
||||
"monster_max_hp": 6,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 0,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": false,
|
||||
"turn": 6,
|
||||
"ran_away": false,
|
||||
"victory": true,
|
||||
"won": true,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 8,
|
||||
"d": 1,
|
||||
"n": "Red Slime"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"d": 1
|
||||
},
|
||||
{
|
||||
"t": 11,
|
||||
"n": "Red Slime"
|
||||
}
|
||||
],
|
||||
"created": 1755270614,
|
||||
"updated": 1755270620
|
||||
}
|
||||
]
|
@ -2,24 +2,64 @@ package actions
|
||||
|
||||
import (
|
||||
"dk/internal/models/fights"
|
||||
"dk/internal/models/monsters"
|
||||
"dk/internal/models/spells"
|
||||
"dk/internal/models/towns"
|
||||
"dk/internal/models/users"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func HandleAttack(fight *fights.Fight, user *users.User) {
|
||||
// 20% chance to miss
|
||||
if rand.Float32() < 0.2 {
|
||||
fight.AddActionAttackMiss()
|
||||
// Load monster data to get armor
|
||||
monster, err := monsters.Find(fight.MonsterID)
|
||||
if err != nil {
|
||||
fight.AddAction("Monster not found!")
|
||||
return
|
||||
}
|
||||
|
||||
fight.DamageMonster(1)
|
||||
fight.AddActionAttackHit(1)
|
||||
// Player attack damage calculation
|
||||
attackPower := float64(user.Attack)
|
||||
minAttack := attackPower * 0.75
|
||||
maxAttack := attackPower
|
||||
tohit := math.Ceil(rand.Float64()*(maxAttack-minAttack)+minAttack) / 3
|
||||
|
||||
// Critical hit chance based on strength
|
||||
criticalRoll := rand.Intn(150) + 1
|
||||
if float64(criticalRoll) <= math.Sqrt(float64(user.Strength)) {
|
||||
tohit *= 2 // Critical hit
|
||||
}
|
||||
|
||||
// Monster defense calculation
|
||||
armor := float64(monster.Armor)
|
||||
minBlock := armor * 0.75
|
||||
maxBlock := armor
|
||||
toblock := math.Ceil(rand.Float64()*(maxBlock-minBlock)+minBlock) / 3
|
||||
|
||||
// Calculate final damage
|
||||
damage := tohit - toblock
|
||||
if damage < 1 {
|
||||
damage = 1 // Minimum damage
|
||||
}
|
||||
|
||||
// Apply uber damage bonus
|
||||
if fight.UberDamage > 0 {
|
||||
bonus := math.Ceil(damage * float64(fight.UberDamage) / 100)
|
||||
damage += bonus
|
||||
}
|
||||
|
||||
finalDamage := int(damage)
|
||||
|
||||
// Apply damage and add action
|
||||
fight.DamageMonster(finalDamage)
|
||||
fight.AddActionAttackHit(finalDamage)
|
||||
|
||||
// Check if monster is defeated
|
||||
if fight.MonsterHP <= 0 {
|
||||
fight.WinFight(10, 5)
|
||||
fight.AddActionMonsterDeath(monster.Name)
|
||||
fight.WinFight(fight.RewardGold, fight.RewardExp)
|
||||
HandleFightWin(fight, user)
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,3 +121,108 @@ func HandleRun(fight *fights.Fight, user *users.User) {
|
||||
fight.AddAction("You failed to run away!")
|
||||
}
|
||||
}
|
||||
|
||||
func HandleMonsterAttack(fight *fights.Fight, user *users.User) {
|
||||
// Load monster data
|
||||
monster, err := monsters.Find(fight.MonsterID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Monster attack damage calculation
|
||||
attackPower := float64(monster.MaxDmg)
|
||||
minAttack := attackPower * 0.75
|
||||
maxAttack := attackPower
|
||||
tohit := math.Ceil(rand.Float64()*(maxAttack-minAttack)+minAttack) / 3
|
||||
|
||||
// User defense calculation
|
||||
defense := float64(user.Defense)
|
||||
minBlock := defense * 0.75
|
||||
maxBlock := defense
|
||||
toblock := math.Ceil(rand.Float64()*(maxBlock-minBlock)+minBlock) / 3
|
||||
|
||||
// Calculate final damage
|
||||
damage := tohit - toblock
|
||||
if damage < 1 {
|
||||
damage = 1 // Minimum damage
|
||||
}
|
||||
|
||||
// Apply uber defense bonus (reduces damage taken)
|
||||
if fight.UberDefense > 0 {
|
||||
reduction := math.Ceil(damage * float64(fight.UberDefense) / 100)
|
||||
damage -= reduction
|
||||
if damage < 1 {
|
||||
damage = 1 // Still minimum 1 damage
|
||||
}
|
||||
}
|
||||
|
||||
finalDamage := int(damage)
|
||||
|
||||
// Apply damage to user
|
||||
user.HP -= finalDamage
|
||||
if user.HP < 0 {
|
||||
user.HP = 0
|
||||
}
|
||||
|
||||
// Add monster attack action using memory-optimized format
|
||||
fight.AddActionMonsterAttack(monster.Name, finalDamage)
|
||||
|
||||
// Check if user is defeated
|
||||
if user.HP <= 0 {
|
||||
fight.LoseFight()
|
||||
HandleFightLoss(fight, user)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleFightWin(fight *fights.Fight, user *users.User) {
|
||||
// Add rewards to user
|
||||
user.Exp += fight.RewardExp
|
||||
user.Gold += fight.RewardGold
|
||||
|
||||
// Reset fight state
|
||||
user.FightID = 0
|
||||
user.Currently = "Exploring"
|
||||
|
||||
fight.Save()
|
||||
user.Save()
|
||||
}
|
||||
|
||||
func HandleFightLoss(fight *fights.Fight, user *users.User) {
|
||||
// Find closest town to user's position
|
||||
closestTown := findClosestTown(user.X, user.Y)
|
||||
if closestTown != nil {
|
||||
user.X = closestTown.X
|
||||
user.Y = closestTown.Y
|
||||
}
|
||||
|
||||
// Apply death penalties
|
||||
user.HP = user.MaxHP / 4 // 25% of max health
|
||||
user.Gold = (user.Gold * 3) / 4 // 75% of gold
|
||||
|
||||
// Reset fight state
|
||||
user.FightID = 0
|
||||
user.Currently = "In Town"
|
||||
|
||||
fight.Save()
|
||||
user.Save()
|
||||
}
|
||||
|
||||
func findClosestTown(x, y int) *towns.Town {
|
||||
allTowns, err := towns.All()
|
||||
if err != nil || len(allTowns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var closest *towns.Town
|
||||
var minDistance float64
|
||||
|
||||
for _, town := range allTowns {
|
||||
distance := town.DistanceFromSquared(x, y)
|
||||
if closest == nil || distance < minDistance {
|
||||
closest = town
|
||||
minDistance = distance
|
||||
}
|
||||
}
|
||||
|
||||
return closest
|
||||
}
|
||||
|
140
internal/models/fights/action_codes.go
Normal file
140
internal/models/fights/action_codes.go
Normal file
@ -0,0 +1,140 @@
|
||||
package fights
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ActionEntry represents a compacted fight action log. This allows us to store more logs
|
||||
// in the same space as a single string.
|
||||
type ActionEntry struct {
|
||||
Type int `json:"t"`
|
||||
Data int `json:"d,omitempty"`
|
||||
Name string `json:"n,omitempty"` // For spell names
|
||||
}
|
||||
|
||||
// Action type constants
|
||||
const (
|
||||
ActionAttackHit = 1
|
||||
ActionAttackMiss = 2
|
||||
ActionSpellHeal = 3
|
||||
ActionSpellHurt = 4
|
||||
ActionRunSuccess = 5
|
||||
ActionRunFail = 6
|
||||
ActionGeneric = 7
|
||||
ActionMonsterAttack = 8
|
||||
ActionMonsterMiss = 9
|
||||
ActionMonsterSpell = 10
|
||||
ActionMonsterDeath = 11
|
||||
)
|
||||
|
||||
func (f *Fight) AddAction(action string) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionGeneric, Name: action})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionAttackHit(damage int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackHit, Data: damage})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionAttackMiss() {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackMiss})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionSpellHeal(spellName string, healAmount int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHeal, Data: healAmount, Name: spellName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionSpellHurt(spellName string, damage int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHurt, Data: damage, Name: spellName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionRunSuccess() {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunSuccess})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionRunFail() {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunFail})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
// Convert actions to human-readable strings
|
||||
func (f *Fight) GetActions() []string {
|
||||
result := make([]string, len(f.Actions))
|
||||
for i, action := range f.Actions {
|
||||
result[i] = f.actionToString(action)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Fight) GetLastAction() string {
|
||||
if len(f.Actions) == 0 {
|
||||
return ""
|
||||
}
|
||||
return f.actionToString(f.Actions[len(f.Actions)-1])
|
||||
}
|
||||
|
||||
func (f *Fight) ClearActions() {
|
||||
f.Actions = make([]ActionEntry, 0)
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionMonsterAttack(monsterName string, damage int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterAttack, Data: damage, Name: monsterName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionMonsterMiss(monsterName string) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterMiss, Name: monsterName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionMonsterSpell(monsterName, spellName string, damage int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterSpell, Data: damage, Name: monsterName + "|" + spellName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionMonsterDeath(monsterName string) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterDeath, Name: monsterName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
// Update actionToString method - add these cases
|
||||
func (f *Fight) actionToString(action ActionEntry) string {
|
||||
switch action.Type {
|
||||
case ActionAttackHit:
|
||||
return fmt.Sprintf("You attacked for %d damage!", action.Data)
|
||||
case ActionAttackMiss:
|
||||
return "You missed your attack!"
|
||||
case ActionSpellHeal:
|
||||
return fmt.Sprintf("You cast %s and healed %d HP!", action.Name, action.Data)
|
||||
case ActionSpellHurt:
|
||||
return fmt.Sprintf("You cast %s and dealt %d damage!", action.Name, action.Data)
|
||||
case ActionRunSuccess:
|
||||
return "You successfully ran away!"
|
||||
case ActionRunFail:
|
||||
return "You failed to run away!"
|
||||
case ActionGeneric:
|
||||
return action.Name
|
||||
case ActionMonsterAttack:
|
||||
return fmt.Sprintf("%s attacks for %d damage!", action.Name, action.Data)
|
||||
case ActionMonsterMiss:
|
||||
return fmt.Sprintf("%s missed its attack!", action.Name)
|
||||
case ActionMonsterSpell:
|
||||
parts := strings.Split(action.Name, "|")
|
||||
if len(parts) == 2 {
|
||||
return fmt.Sprintf("%s casts %s for %d damage!", parts[0], parts[1], action.Data)
|
||||
}
|
||||
return fmt.Sprintf("%s casts a spell for %d damage!", action.Name, action.Data)
|
||||
case ActionMonsterDeath:
|
||||
return fmt.Sprintf("%s has been defeated!", action.Name)
|
||||
default:
|
||||
return "Unknown action"
|
||||
}
|
||||
}
|
@ -6,24 +6,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ActionEntry represents a compact fight action
|
||||
type ActionEntry struct {
|
||||
Type int `json:"t"`
|
||||
Data int `json:"d,omitempty"`
|
||||
Name string `json:"n,omitempty"` // For spell names
|
||||
}
|
||||
|
||||
// Action type constants
|
||||
const (
|
||||
ActionAttackHit = 1
|
||||
ActionAttackMiss = 2
|
||||
ActionSpellHeal = 3
|
||||
ActionSpellHurt = 4
|
||||
ActionRunSuccess = 5
|
||||
ActionRunFail = 6
|
||||
ActionGeneric = 7
|
||||
)
|
||||
|
||||
// Fight represents a fight, past or present
|
||||
type Fight struct {
|
||||
ID int `json:"id"`
|
||||
@ -104,84 +86,6 @@ func (f *Fight) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Action methods for backward compatibility
|
||||
func (f *Fight) AddAction(action string) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionGeneric, Name: action})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionAttackHit(damage int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackHit, Data: damage})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionAttackMiss() {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackMiss})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionSpellHeal(spellName string, healAmount int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHeal, Data: healAmount, Name: spellName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionSpellHurt(spellName string, damage int) {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHurt, Data: damage, Name: spellName})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionRunSuccess() {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunSuccess})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddActionRunFail() {
|
||||
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunFail})
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
// Convert actions to human-readable strings
|
||||
func (f *Fight) GetActions() []string {
|
||||
result := make([]string, len(f.Actions))
|
||||
for i, action := range f.Actions {
|
||||
result[i] = f.actionToString(action)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Fight) actionToString(action ActionEntry) string {
|
||||
switch action.Type {
|
||||
case ActionAttackHit:
|
||||
return fmt.Sprintf("You attacked for %d damage!", action.Data)
|
||||
case ActionAttackMiss:
|
||||
return "You missed your attack!"
|
||||
case ActionSpellHeal:
|
||||
return fmt.Sprintf("You cast %s and healed %d HP!", action.Name, action.Data)
|
||||
case ActionSpellHurt:
|
||||
return fmt.Sprintf("You cast %s and dealt %d damage!", action.Name, action.Data)
|
||||
case ActionRunSuccess:
|
||||
return "You successfully ran away!"
|
||||
case ActionRunFail:
|
||||
return "You failed to run away!"
|
||||
case ActionGeneric:
|
||||
return action.Name
|
||||
default:
|
||||
return "Unknown action"
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Fight) GetLastAction() string {
|
||||
if len(f.Actions) == 0 {
|
||||
return ""
|
||||
}
|
||||
return f.actionToString(f.Actions[len(f.Actions)-1])
|
||||
}
|
||||
|
||||
func (f *Fight) ClearActions() {
|
||||
f.Actions = make([]ActionEntry, 0)
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
// FightStore with enhanced BaseStore
|
||||
type FightStore struct {
|
||||
*store.BaseStore[Fight]
|
||||
|
@ -76,15 +76,8 @@ func processLogin(ctx router.Ctx, _ []string) {
|
||||
// showRegister displays the registration form
|
||||
func showRegister(ctx router.Ctx, _ []string) {
|
||||
sess := ctx.UserValue("session").(*session.Session)
|
||||
var errorHTML string
|
||||
var username, email string
|
||||
|
||||
if flash, exists := sess.GetFlash("error"); exists {
|
||||
if msg, ok := flash.(string); ok {
|
||||
errorHTML = fmt.Sprintf(`<div style="color: red; margin-bottom: 1rem;">%s</div>`, msg)
|
||||
}
|
||||
}
|
||||
|
||||
if formData, exists := sess.Get("form_data"); exists {
|
||||
if data, ok := formData.(map[string]string); ok {
|
||||
username = data["username"]
|
||||
@ -95,7 +88,6 @@ func showRegister(ctx router.Ctx, _ []string) {
|
||||
session.Store(sess)
|
||||
|
||||
components.RenderPage(ctx, "Register", "auth/register.html", map[string]any{
|
||||
"error_message": errorHTML,
|
||||
"username": username,
|
||||
"email": email,
|
||||
})
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"dk/internal/models/spells"
|
||||
"dk/internal/models/users"
|
||||
"dk/internal/router"
|
||||
"dk/internal/session"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
)
|
||||
@ -25,6 +27,7 @@ func RegisterFightRoutes(r *router.Router) {
|
||||
}
|
||||
|
||||
func showFight(ctx router.Ctx, _ []string) {
|
||||
sess := ctx.UserValue("session").(*session.Session)
|
||||
user := ctx.UserValue("user").(*users.User)
|
||||
|
||||
fight, err := fights.Find(user.FightID)
|
||||
@ -68,17 +71,20 @@ func showFight(ctx router.Ctx, _ []string) {
|
||||
}
|
||||
|
||||
components.RenderPage(ctx, "Fighting", "fight/fight.html", map[string]any{
|
||||
"fight": fight,
|
||||
"user": user,
|
||||
"monster": monster,
|
||||
"mon_hppct": monHpPct,
|
||||
"mon_hpcol": monHpColor,
|
||||
"spells": spellMap.ToSlice(),
|
||||
"fight": fight,
|
||||
"user": user,
|
||||
"monster": monster,
|
||||
"mon_hppct": monHpPct,
|
||||
"mon_hpcol": monHpColor,
|
||||
"spells": spellMap.ToSlice(),
|
||||
"action": sess.GetFlashMessage("action"),
|
||||
"mon_action": sess.GetFlashMessage("mon_action"),
|
||||
})
|
||||
}
|
||||
|
||||
func handleFightAction(ctx router.Ctx, _ []string) {
|
||||
user := ctx.UserValue("user").(*users.User)
|
||||
sess := ctx.UserValue("session").(*session.Session)
|
||||
|
||||
fight, err := fights.Find(user.FightID)
|
||||
if err != nil {
|
||||
@ -88,19 +94,78 @@ func handleFightAction(ctx router.Ctx, _ []string) {
|
||||
}
|
||||
|
||||
action := string(ctx.FormValue("action"))
|
||||
var userAction string
|
||||
|
||||
switch action {
|
||||
case "attack":
|
||||
actions.HandleAttack(fight, user)
|
||||
userAction = fight.GetLastAction()
|
||||
case "spell":
|
||||
spellIDStr := string(ctx.FormValue("spell_id"))
|
||||
if spellID, err := strconv.Atoi(spellIDStr); err == nil {
|
||||
actions.HandleSpell(fight, user, spellID)
|
||||
userAction = fight.GetLastAction()
|
||||
}
|
||||
case "run":
|
||||
actions.HandleRun(fight, user)
|
||||
userAction = fight.GetLastAction()
|
||||
|
||||
// If successfully ran away, redirect to explore
|
||||
if fight.RanAway {
|
||||
user.Currently = "Exploring"
|
||||
user.Save()
|
||||
sess.SetFlash("success", "You successfully escaped!")
|
||||
ctx.Redirect("/explore", 302)
|
||||
return
|
||||
}
|
||||
default:
|
||||
fight.AddAction("Invalid action!")
|
||||
userAction = "Invalid action!"
|
||||
}
|
||||
|
||||
// Flash user action
|
||||
sess.SetFlash("action", userAction)
|
||||
|
||||
// Check if fight ended due to user action
|
||||
if fight.Victory {
|
||||
if fight.Won {
|
||||
// Player won
|
||||
sess.SetFlash("success", fmt.Sprintf("Victory! You gained %d gold and %d experience!", fight.RewardGold, fight.RewardExp))
|
||||
sess.DeleteFlash("action")
|
||||
sess.DeleteFlash("mon_action")
|
||||
ctx.Redirect("/explore", 302)
|
||||
} else {
|
||||
// Player lost
|
||||
sess.SetFlash("error", "You have been defeated! You lost some gold and were sent to the nearest town.")
|
||||
sess.DeleteFlash("action")
|
||||
sess.DeleteFlash("mon_action")
|
||||
ctx.Redirect("/town", 302)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Monster attacks back if fight is still active
|
||||
if fight.IsActive() && user.HP > 0 {
|
||||
actions.HandleMonsterAttack(fight, user)
|
||||
|
||||
// Check if fight ended due to monster attack
|
||||
if fight.Victory {
|
||||
if fight.Won {
|
||||
sess.SetFlash("success", fmt.Sprintf("Victory! You gained %d gold and %d experience!", fight.RewardGold, fight.RewardExp))
|
||||
sess.DeleteFlash("action")
|
||||
sess.DeleteFlash("mon_action")
|
||||
ctx.Redirect("/explore", 302)
|
||||
} else {
|
||||
sess.SetFlash("error", "You have been defeated! You lost some gold and were sent to the nearest town.")
|
||||
sess.DeleteFlash("action")
|
||||
sess.DeleteFlash("mon_action")
|
||||
ctx.Redirect("/town", 302)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
monsterAction := fight.GetLastAction()
|
||||
sess.SetFlash("mon_action", monsterAction)
|
||||
}
|
||||
|
||||
fight.IncrementTurn()
|
||||
|
@ -81,6 +81,11 @@ func (s *Session) GetFlashMessage(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeleteFlash removes a flash from the session.
|
||||
func (s *Session) DeleteFlash(key string) {
|
||||
s.GetFlash(key)
|
||||
}
|
||||
|
||||
// RegenerateID creates a new session ID and updates storage
|
||||
func (s *Session) RegenerateID() {
|
||||
oldID := s.ID
|
||||
|
@ -8,6 +8,9 @@
|
||||
<img id="monster-image" src="/assets/images/monsters/{monster.Name}.png" alt="{monster.Name}" title="{monster.Name}">
|
||||
</div>
|
||||
|
||||
<div>{action}</div>
|
||||
<div>{mon_action}</div>
|
||||
|
||||
<span>{fight.MonsterHP}/{fight.MonsterMaxHP}</span>
|
||||
<div id="monster-health" class="mb-1">
|
||||
<div class="bar {mon_hpcol}" style="width: {mon_hppct}%;"></div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user