add action handlers, update action tracking, add monster images, fix form
This commit is contained in:
parent
de0381f668
commit
0d3afffb1e
@ -212,6 +212,11 @@ button.btn {
|
||||
background-color: #00c6ff;
|
||||
background-image: url("/assets/images/overlay.png"), linear-gradient(to bottom, #00c6ff, #0072ff);
|
||||
border-color: #0072ff;
|
||||
|
||||
&:hover {
|
||||
background-color: #49d6fd;
|
||||
background-image: url("/assets/images/overlay.png"), linear-gradient(to bottom, #49d6fd, #2987fa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,7 +416,7 @@ img#move-compass-disabled {
|
||||
|
||||
img#monster-image {
|
||||
display: block;
|
||||
max-height: 360px;
|
||||
max-width: 80%;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
@ -453,7 +458,7 @@ select.styled-select {
|
||||
color: white;
|
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);
|
||||
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1);
|
||||
height: 28px;
|
||||
height: 29px;
|
||||
|
||||
&:hover {
|
||||
background-color: #909090;
|
||||
@ -468,4 +473,4 @@ div#battle-window {
|
||||
h2, h3 {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
assets/images/monsters/Critter.png
Normal file
BIN
assets/images/monsters/Critter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/monsters/Drake.png
Normal file
BIN
assets/images/monsters/Drake.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 335 KiB |
@ -1,86 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"monster_id": 10,
|
||||
"monster_hp": 16,
|
||||
"monster_max_hp": 16,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 0,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": false,
|
||||
"turn": 1,
|
||||
"ran_away": false,
|
||||
"victory": false,
|
||||
"won": false,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [],
|
||||
"created": 1755215734,
|
||||
"updated": 1755215734
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"user_id": 1,
|
||||
"monster_id": 7,
|
||||
"monster_hp": 12,
|
||||
"monster_max_hp": 12,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 1,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": true,
|
||||
"turn": 1,
|
||||
"ran_away": false,
|
||||
"victory": false,
|
||||
"won": false,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [],
|
||||
"created": 1755215776,
|
||||
"updated": 1755215776
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"user_id": 1,
|
||||
"monster_id": 8,
|
||||
"monster_hp": 14,
|
||||
"monster_max_hp": 14,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 0,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": true,
|
||||
"turn": 1,
|
||||
"ran_away": false,
|
||||
"victory": false,
|
||||
"won": false,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [],
|
||||
"created": 1755215777,
|
||||
"updated": 1755215777
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"user_id": 1,
|
||||
"monster_id": 4,
|
||||
"monster_hp": 10,
|
||||
"monster_max_hp": 10,
|
||||
"monster_sleep": 0,
|
||||
"monster_immune": 0,
|
||||
"uber_damage": 0,
|
||||
"uber_defense": 0,
|
||||
"first_strike": false,
|
||||
"turn": 1,
|
||||
"ran_away": false,
|
||||
"victory": false,
|
||||
"won": false,
|
||||
"reward_gold": 0,
|
||||
"reward_exp": 0,
|
||||
"actions": [],
|
||||
"created": 1755222893,
|
||||
"updated": 1755222893
|
||||
}
|
||||
]
|
83
internal/actions/fight.go
Normal file
83
internal/actions/fight.go
Normal file
@ -0,0 +1,83 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"dk/internal/models/fights"
|
||||
"dk/internal/models/spells"
|
||||
"dk/internal/models/users"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func HandleAttack(fight *fights.Fight, user *users.User) {
|
||||
// 20% chance to miss
|
||||
if rand.Float32() < 0.2 {
|
||||
fight.AddActionAttackMiss()
|
||||
return
|
||||
}
|
||||
|
||||
fight.DamageMonster(1)
|
||||
fight.AddActionAttackHit(1)
|
||||
|
||||
if fight.MonsterHP <= 0 {
|
||||
fight.WinFight(10, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSpell(fight *fights.Fight, user *users.User, spellID int) {
|
||||
spell, err := spells.Find(spellID)
|
||||
if err != nil {
|
||||
fight.AddAction("Spell not found!")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has enough MP
|
||||
if user.MP < spell.MP {
|
||||
fight.AddAction("Not enough MP to cast " + spell.Name + "!")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user knows this spell
|
||||
if !user.HasSpell(spellID) {
|
||||
fight.AddAction("You don't know that spell!")
|
||||
return
|
||||
}
|
||||
|
||||
// Deduct MP
|
||||
user.MP -= spell.MP
|
||||
|
||||
switch spell.Type {
|
||||
case spells.TypeHealing:
|
||||
// Heal user
|
||||
healAmount := spell.Attribute
|
||||
user.HP += healAmount
|
||||
if user.HP > user.MaxHP {
|
||||
user.HP = user.MaxHP
|
||||
}
|
||||
fight.AddAction("You cast " + spell.Name + " and healed " + strconv.Itoa(healAmount) + " HP!")
|
||||
|
||||
case spells.TypeHurt:
|
||||
// Damage monster
|
||||
damage := spell.Attribute
|
||||
fight.DamageMonster(damage)
|
||||
fight.AddAction("You cast " + spell.Name + " and dealt " + strconv.Itoa(damage) + " damage!")
|
||||
|
||||
// Check if monster is defeated
|
||||
if fight.MonsterHP <= 0 {
|
||||
fight.WinFight(10, 5) // Basic rewards
|
||||
}
|
||||
|
||||
default:
|
||||
fight.AddAction("You cast " + spell.Name + " but nothing happened!")
|
||||
}
|
||||
}
|
||||
|
||||
func HandleRun(fight *fights.Fight, user *users.User) {
|
||||
// 20% chance to successfully run away
|
||||
if rand.Float32() < 0.2 {
|
||||
fight.RunAway()
|
||||
user.FightID = 0
|
||||
fight.AddAction("You successfully ran away!")
|
||||
} else {
|
||||
fight.AddAction("You failed to run away!")
|
||||
}
|
||||
}
|
@ -28,8 +28,6 @@ import (
|
||||
|
||||
"dk/internal/router"
|
||||
"dk/internal/session"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -182,8 +180,10 @@ func Middleware() router.Middleware {
|
||||
// Only validate CSRF for state-changing methods
|
||||
if method == "POST" || method == "PUT" || method == "PATCH" || method == "DELETE" {
|
||||
if !ValidateFormToken(ctx) {
|
||||
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
||||
ctx.WriteString("CSRF validation failed")
|
||||
fmt.Println("Failed CSRF validation.")
|
||||
RotateToken(ctx)
|
||||
currentPath := string(ctx.Path())
|
||||
ctx.Redirect(currentPath, 302)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,45 @@ 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"`
|
||||
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"`
|
||||
Actions []string `json:"actions"`
|
||||
Created int64 `json:"created"`
|
||||
Updated int64 `json:"updated"`
|
||||
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"`
|
||||
Actions []ActionEntry `json:"actions"`
|
||||
Created int64 `json:"created"`
|
||||
Updated int64 `json:"updated"`
|
||||
}
|
||||
|
||||
func (f *Fight) Save() error {
|
||||
@ -57,7 +75,7 @@ func New(userID, monsterID int) *Fight {
|
||||
Won: false,
|
||||
RewardGold: 0,
|
||||
RewardExp: 0,
|
||||
Actions: make([]string, 0),
|
||||
Actions: make([]ActionEntry, 0),
|
||||
Created: now,
|
||||
Updated: now,
|
||||
}
|
||||
@ -86,6 +104,84 @@ 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]
|
||||
@ -311,24 +407,3 @@ func (f *Fight) DamageMonster(damage int) {
|
||||
}
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) AddAction(action string) {
|
||||
f.Actions = append(f.Actions, action)
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (f *Fight) GetActions() []string {
|
||||
return f.Actions
|
||||
}
|
||||
|
||||
func (f *Fight) GetLastAction() string {
|
||||
if len(f.Actions) == 0 {
|
||||
return ""
|
||||
}
|
||||
return f.Actions[len(f.Actions)-1]
|
||||
}
|
||||
|
||||
func (f *Fight) ClearActions() {
|
||||
f.Actions = make([]string, 0)
|
||||
f.Updated = time.Now().Unix()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"dk/internal/actions"
|
||||
"dk/internal/auth"
|
||||
"dk/internal/components"
|
||||
"dk/internal/helpers"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
"dk/internal/models/users"
|
||||
"dk/internal/router"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RegisterFightRoutes(r *router.Router) {
|
||||
@ -19,6 +21,7 @@ func RegisterFightRoutes(r *router.Router) {
|
||||
group.Use(middleware.RequireFighting())
|
||||
|
||||
group.Get("/", showFight)
|
||||
group.Post("/", handleFightAction)
|
||||
}
|
||||
|
||||
func showFight(ctx router.Ctx, _ []string) {
|
||||
@ -46,13 +49,13 @@ func showFight(ctx router.Ctx, _ []string) {
|
||||
fight.Save()
|
||||
}
|
||||
|
||||
hpPct := helpers.ClampPct(float64(user.HP), float64(user.MaxHP), 0, 100)
|
||||
monHpPct := helpers.ClampPct(float64(fight.MonsterHP), float64(fight.MonsterMaxHP), 0, 100)
|
||||
|
||||
hpColor := ""
|
||||
if hpPct < 35 {
|
||||
hpColor = "danger"
|
||||
} else if hpPct < 75 {
|
||||
hpColor = "warning"
|
||||
monHpColor := ""
|
||||
if monHpPct < 35 {
|
||||
monHpColor = "danger"
|
||||
} else if monHpPct < 75 {
|
||||
monHpColor = "warning"
|
||||
}
|
||||
|
||||
spellMap := helpers.NewOrderedMap[int, *spells.Spell]()
|
||||
@ -68,8 +71,42 @@ func showFight(ctx router.Ctx, _ []string) {
|
||||
"fight": fight,
|
||||
"user": user,
|
||||
"monster": monster,
|
||||
"mon_hppct": hpPct,
|
||||
"mon_hpcol": hpColor,
|
||||
"mon_hppct": monHpPct,
|
||||
"mon_hpcol": monHpColor,
|
||||
"spells": spellMap.ToSlice(),
|
||||
})
|
||||
}
|
||||
|
||||
func handleFightAction(ctx router.Ctx, _ []string) {
|
||||
user := ctx.UserValue("user").(*users.User)
|
||||
|
||||
fight, err := fights.Find(user.FightID)
|
||||
if err != nil {
|
||||
ctx.SetContentType("text/plain")
|
||||
ctx.SetBodyString("Fight not found")
|
||||
return
|
||||
}
|
||||
|
||||
action := string(ctx.FormValue("action"))
|
||||
|
||||
switch action {
|
||||
case "attack":
|
||||
actions.HandleAttack(fight, user)
|
||||
case "spell":
|
||||
spellIDStr := string(ctx.FormValue("spell_id"))
|
||||
if spellID, err := strconv.Atoi(spellIDStr); err == nil {
|
||||
actions.HandleSpell(fight, user, spellID)
|
||||
}
|
||||
case "run":
|
||||
actions.HandleRun(fight, user)
|
||||
default:
|
||||
fight.AddAction("Invalid action!")
|
||||
}
|
||||
|
||||
fight.IncrementTurn()
|
||||
fight.Save()
|
||||
user.Save()
|
||||
|
||||
// Redirect back to fight page
|
||||
ctx.Redirect("/fight", 302)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{block "content"}
|
||||
<div id="battle-window">
|
||||
<h2>Fighting {monster.Name}</h2>
|
||||
<h2>{monster.Name}</h2>
|
||||
<h3>Level {monster.Level}</h3>
|
||||
|
||||
<img id="monster-image" src="/assets/images/monsters/{monster.Name}.png" alt="{monster.Name}" title="{monster.Name}">
|
||||
@ -14,17 +14,24 @@
|
||||
</div>
|
||||
|
||||
<form action="/fight" method="post">
|
||||
{csrf}
|
||||
|
||||
<div class="mb-05">
|
||||
<button class="btn btn-primary">Attack</button>
|
||||
<button type="submit" name="action" value="attack" class="btn btn-primary">Attack</button>
|
||||
</div>
|
||||
{if user.Spells != ""}
|
||||
<select id="spell-select" class="styled-select">
|
||||
{for spell in spells}
|
||||
<option value="{spell.ID}">({spell.MP} MP) {spell.Name}</option>
|
||||
{/for}
|
||||
</select>
|
||||
<div class="mb-05">
|
||||
<select id="spell-select" class="styled-select" name="spell_id">
|
||||
{for spell in spells}
|
||||
<option value="{spell.ID}">({spell.MP} MP) {spell.Name}</option>
|
||||
{/for}
|
||||
</select>
|
||||
|
||||
<button class="btn btn-blue">Spell</button>
|
||||
<button type="submit" name="action" value="spell" class="btn btn-blue">Spell</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<button type="submit" name="action" value="run" class="btn">Run</button>
|
||||
</div>
|
||||
</form>
|
||||
{/block}
|
||||
|
Loading…
x
Reference in New Issue
Block a user