aboutsummaryrefslogtreecommitdiff
path: root/internal/combat
diff options
context:
space:
mode:
Diffstat (limited to 'internal/combat')
-rw-r--r--internal/combat/formulas.go44
-rw-r--r--internal/combat/types.go36
-rw-r--r--internal/combat/types_test.go30
3 files changed, 97 insertions, 13 deletions
diff --git a/internal/combat/formulas.go b/internal/combat/formulas.go
index f4e1ba2..f165923 100644
--- a/internal/combat/formulas.go
+++ b/internal/combat/formulas.go
@@ -2,8 +2,24 @@ package combat
import "math/rand"
-func EffectiveRoll(level int, styleBonus int, equipBonus int) int {
- effective := level + styleBonus + 8
+func PlayerEffective(level, styleBonus int) int {
+ return level + styleBonus + 8
+}
+
+func NPCEffective(level int) int {
+ return level + 9
+}
+
+// ScienceEffective is the effective-level formula for science attacks. It is
+// intentionally the same as NPCEffective (level + 9) and is applied to BOTH the
+// attacker and defender of a science-mod exchange (see scienceAttack), giving a
+// symmetric science-vs-science model with no attack styles. It is kept as a
+// distinct function to document that intent independently of NPCEffective.
+func ScienceEffective(level int) int {
+ return level + 9
+}
+
+func AttackRoll(effective, equipBonus int) int {
return effective * (equipBonus + 64)
}
@@ -17,13 +33,11 @@ func HitChance(attackRoll, defenseRoll int) float64 {
}
func HitCheck(attackRoll, defenseRoll int) bool {
- chance := HitChance(attackRoll, defenseRoll)
- return rand.Float64() < chance
+ return rand.Float64() < HitChance(attackRoll, defenseRoll)
}
-func MaxHit(level int, styleBonus int, equipBonus int) int {
- effective := level + styleBonus + 8
- hit := (effective * (equipBonus + 64)) / 512
+func MaxHit(effective, equipStrBonus int) int {
+ hit := (effective*(equipStrBonus+64) + 320) / 640
if hit < 1 {
hit = 1
}
@@ -34,7 +48,11 @@ func RollDamage(maxHit int) int {
if maxHit <= 0 {
return 0
}
- return 1 + rand.Intn(maxHit)
+ d := rand.Intn(maxHit + 1)
+ if d < 1 {
+ d = 1
+ }
+ return d
}
func AttackStyleBonus(style string) (attack, strength, defense int) {
@@ -69,15 +87,15 @@ func RangedStyleBonus(style string) (ranged, defense int) {
func SelectBonus(attackType string, stab, slash, crush, science, ranged int) int {
switch attackType {
- case "stab":
+ case AttackStab:
return stab
- case "slash":
+ case AttackSlash:
return slash
- case "crush":
+ case AttackCrush:
return crush
- case "science":
+ case AttackScience:
return science
- case "ranged":
+ case AttackRanged:
return ranged
default:
return crush
diff --git a/internal/combat/types.go b/internal/combat/types.go
new file mode 100644
index 0000000..bb2a897
--- /dev/null
+++ b/internal/combat/types.go
@@ -0,0 +1,36 @@
+package combat
+
+// Attack type identifiers shared across combat, mobs, weapons, hazards and
+// validation. These are the canonical string values stored in YAML.
+const (
+ AttackStab = "stab"
+ AttackSlash = "slash"
+ AttackCrush = "crush"
+ AttackRanged = "ranged"
+ AttackScience = "science"
+
+ // DefaultAttackType is the fallback when no attack type is specified.
+ DefaultAttackType = AttackCrush
+)
+
+// MeleeAttackTypes lists the melee attack types (exactly one of which a mob
+// must have).
+var MeleeAttackTypes = []string{AttackStab, AttackSlash, AttackCrush}
+
+// AllAttackTypes lists every valid attack type.
+var AllAttackTypes = []string{AttackStab, AttackSlash, AttackCrush, AttackRanged, AttackScience}
+
+// IsMeleeType reports whether t is a melee attack type.
+func IsMeleeType(t string) bool {
+ return t == AttackStab || t == AttackSlash || t == AttackCrush
+}
+
+// IsValidAttackType reports whether t is any recognized attack type.
+func IsValidAttackType(t string) bool {
+ switch t {
+ case AttackStab, AttackSlash, AttackCrush, AttackRanged, AttackScience:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/internal/combat/types_test.go b/internal/combat/types_test.go
new file mode 100644
index 0000000..a999eb6
--- /dev/null
+++ b/internal/combat/types_test.go
@@ -0,0 +1,30 @@
+package combat
+
+import "testing"
+
+func TestIsMeleeType(t *testing.T) {
+ melee := []string{AttackStab, AttackSlash, AttackCrush}
+ for _, m := range melee {
+ if !IsMeleeType(m) {
+ t.Errorf("IsMeleeType(%q) = false, want true", m)
+ }
+ }
+ for _, nm := range []string{AttackRanged, AttackScience, "", "magic"} {
+ if IsMeleeType(nm) {
+ t.Errorf("IsMeleeType(%q) = true, want false", nm)
+ }
+ }
+}
+
+func TestIsValidAttackType(t *testing.T) {
+ for _, v := range AllAttackTypes {
+ if !IsValidAttackType(v) {
+ t.Errorf("IsValidAttackType(%q) = false, want true", v)
+ }
+ }
+ for _, iv := range []string{"", "magic", "melee"} {
+ if IsValidAttackType(iv) {
+ t.Errorf("IsValidAttackType(%q) = true, want false", iv)
+ }
+ }
+}