aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_attack.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-21 22:19:17 -0400
committerhistoria <[not public]>2026-06-21 22:19:17 -0400
commita3ae1e6aad696f584d138d9363c05dedff136a65 (patch)
tree81e615f7227bc9d1ec79ceefc7311ecde917b074 /internal/game/cmd_attack.go
parent84072ffe6365ad57c4976e9272762aa6db41de7e (diff)
downloadthehouseoficarus-a3ae1e6aad696f584d138d9363c05dedff136a65.tar.gz
feat: safespot system implemented
Diffstat (limited to 'internal/game/cmd_attack.go')
-rw-r--r--internal/game/cmd_attack.go88
1 files changed, 65 insertions, 23 deletions
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index 703b9ac..ce396e8 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -2,6 +2,7 @@ package game
import (
"fmt"
+ "math/rand"
"sort"
"strconv"
"strings"
@@ -248,6 +249,15 @@ func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobI
attackType, weaponType, attRoll, defRoll, maxHit := g.calculatePlayerAttackRoll(p, mob)
+ if g.isSafespotted(p.Name) && attackType != "ranged" && attackType != "science" {
+ g.safespotMu.Lock()
+ ss, ok := g.safespotStates[p.Name]
+ g.safespotMu.Unlock()
+ if ok {
+ g.forceLeaveSafespot(sess, p, ss, "You leave your cover to close in for melee!")
+ }
+ }
+
if combat.HitCheck(attRoll, defRoll) {
g.applyPlayerHit(sess, p, mob, attackType, weaponType, maxHit)
} else {
@@ -277,25 +287,25 @@ func (g *Game) calculatePlayerAttackRoll(p *player.Player, mob *world.MobInstanc
rangedBonus, _ := combat.RangedStyleBonus(string(p.AttackStyle))
equipAttack := totals.RangedAttack
effectiveRanged := p.Level(player.Ranged) + g.techLevelBonus(p, "ranged") + g.buffLevelBonus(p, "ranged")
- attRoll = combat.AttackRoll(effectiveRanged, rangedBonus, equipAttack)
+ attRoll = combat.EffectiveRoll(effectiveRanged, rangedBonus, equipAttack)
- mobDefBonus := combat.SelectDefenseBonus(attackType,
+ mobDefBonus := combat.SelectBonus(attackType,
mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
mob.ScienceDefense, mob.RangedDefense)
- defRoll = combat.DefenseRoll(mob.Defense, 0, mobDefBonus)
+ defRoll = combat.EffectiveRoll(mob.Defense, 0, mobDefBonus)
maxHit = combat.MaxHit(effectiveRanged, rangedBonus, totals.RangedStrength)
} else {
attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
- equipAttack := combat.SelectAttackBonus(attackType,
+ equipAttack := combat.SelectBonus(attackType,
totals.StabAttack, totals.SlashAttack, totals.CrushAttack,
totals.ScienceAttack, totals.RangedAttack)
effectiveAttack := p.Level(player.Attack) + g.techLevelBonus(p, "attack") + g.buffLevelBonus(p, "attack")
- attRoll = combat.AttackRoll(effectiveAttack, attBonus, equipAttack)
+ attRoll = combat.EffectiveRoll(effectiveAttack, attBonus, equipAttack)
- mobDefBonus := combat.SelectDefenseBonus(attackType,
+ mobDefBonus := combat.SelectBonus(attackType,
mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
mob.ScienceDefense, mob.RangedDefense)
- defRoll = combat.DefenseRoll(mob.Defense, 0, mobDefBonus)
+ defRoll = combat.EffectiveRoll(mob.Defense, 0, mobDefBonus)
maxHit = combat.MaxHit(p.Level(player.Strength)+g.techLevelBonus(p, "strength")+g.buffLevelBonus(p, "strength"), strBonus, totals.StrengthBonus)
}
return
@@ -322,12 +332,12 @@ func (g *Game) calculateUnarmedAttackRoll(p *player.Player, mob *world.MobInstan
attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
effectiveAttack := p.Level(player.Attack) + g.techLevelBonus(p, "attack") + g.buffLevelBonus(p, "attack")
- attRoll = combat.AttackRoll(effectiveAttack, attBonus, 0)
+ attRoll = combat.EffectiveRoll(effectiveAttack, attBonus, 0)
- mobDefBonus := combat.SelectDefenseBonus(attackType,
+ mobDefBonus := combat.SelectBonus(attackType,
mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
mob.ScienceDefense, mob.RangedDefense)
- defRoll = combat.DefenseRoll(mob.Defense, 0, mobDefBonus)
+ defRoll = combat.EffectiveRoll(mob.Defense, 0, mobDefBonus)
maxHit = combat.MaxHit(p.Level(player.Strength)+g.techLevelBonus(p, "strength")+g.buffLevelBonus(p, "strength"), strBonus, 0)
return
}
@@ -361,12 +371,7 @@ func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.Mo
gains := g.awardCombatXP(sess, p, dmg, isRanged)
if isRanged {
- p.AmmoQty--
- if p.AmmoQty <= 0 {
- delete(p.Equipment, object.SlotAmmo)
- p.AmmoQty = 0
- }
- g.AccountStore.SaveCharacter(p)
+ g.consumeAmmo(sess, p)
}
mobName := mobDisplayName(mob, true)
@@ -404,18 +409,39 @@ func (g *Game) applyPlayerMiss(sess *net.Session, p *player.Player, mob *world.M
sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("You miss %s.", mobDisplayName(mob, true))))
if weaponType == object.WeaponRanged {
- p.AmmoQty--
+ g.consumeAmmo(sess, p)
if p.AmmoQty <= 0 {
- delete(p.Equipment, object.SlotAmmo)
- p.AmmoQty = 0
- g.AccountStore.SaveCharacter(p)
sess.WriteLine("You've run out of ammo!")
combat.LeaveCombat(p.Name)
}
}
}
+func (g *Game) consumeAmmo(sess *net.Session, p *player.Player) {
+ ammoID := p.Equipment[object.SlotAmmo]
+
+ p.AmmoQty--
+ if p.AmmoQty <= 0 {
+ delete(p.Equipment, object.SlotAmmo)
+ p.AmmoQty = 0
+ }
+
+ if ammoID != "" {
+ if def, err := g.ItemStore.Load(ammoID); err == nil && def.Recoverable {
+ if rand.Float64() < 0.80 {
+ g.World.AddGroundItem(p.RoomID, def.ID, 1)
+ }
+ }
+ }
+
+ g.AccountStore.SaveCharacter(p)
+}
+
func (g *Game) mobAttack(sess *net.Session, p *player.Player, mob *world.MobInstance) {
+ if g.isSafespotted(p.Name) && g.safespotBlocksMob(p, mob) {
+ return
+ }
+
attRoll, defRoll := g.calculateMobAttack(p, mob)
if combat.HitCheck(attRoll, defRoll) {
@@ -433,13 +459,13 @@ func (g *Game) calculateMobAttack(p *player.Player, mob *world.MobInstance) (att
mobAttackType = "crush"
}
- attRoll = combat.AttackRoll(mob.Attack, 0, mob.AttackBonus)
+ attRoll = combat.EffectiveRoll(mob.Attack, 0, mob.AttackBonus)
totals := g.playerEquipBonuses(p)
- equipDef := combat.SelectDefenseBonus(mobAttackType,
+ equipDef := combat.SelectBonus(mobAttackType,
totals.StabDefense, totals.SlashDefense, totals.CrushDefense,
totals.ScienceDefense, totals.RangedDefense)
- defRoll = combat.DefenseRoll(p.Level(player.Defense)+g.techLevelBonus(p, "defense")+g.buffLevelBonus(p, "defense"), defStyleBonus, equipDef)
+ defRoll = combat.EffectiveRoll(p.Level(player.Defense)+g.techLevelBonus(p, "defense")+g.buffLevelBonus(p, "defense"), defStyleBonus, equipDef)
return
}
@@ -504,6 +530,15 @@ func (g *Game) applyMobHit(sess *net.Session, p *player.Player, mob *world.MobIn
hpSuffix := fmt.Sprintf("%s [%2d/%d]", g.hpBar(sess, p.HP, p.MaxHP()), p.HP, p.MaxHP())
sess.WriteLine(prefix + hpSuffix)
+ g.safespotMu.Lock()
+ ss, hasSS := g.safespotStates[p.Name]
+ if hasSS && ss.HideCountdown > 0 {
+ delete(g.safespotStates, p.Name)
+ p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name}
+ sess.WriteLine(g.colorize(sess, "error", "You're hit while repositioning! You failed to hide."))
+ }
+ g.safespotMu.Unlock()
+
if p.MoveTicks > 0 {
p.ClearMoveState()
p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name}
@@ -542,6 +577,13 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
p.Stats.RecordDeath()
sess.WriteLine(g.colorize(sess, "death", "\nOh dear, you are dead!"))
p.ClearMoveState()
+ g.safespotMu.Lock()
+ if ss, ok := g.safespotStates[p.Name]; ok {
+ g.safespotMu.Unlock()
+ g.forceLeaveSafespot(sess, p, ss, "")
+ } else {
+ g.safespotMu.Unlock()
+ }
g.dropItemsOnDeath(p)
p.HP = p.MaxHP()
p.RoomID = 1