2024-05-21 23:08:51 +03:00
|
|
|
package rpg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/rand"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Stat int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Used as a default value in cases where no other stat could be determined.
|
|
|
|
// Should never be used except in cases of error handling
|
|
|
|
Stat_NonExtant Stat = -1
|
|
|
|
|
|
|
|
Stat_Attributes_Strength Stat = 0
|
|
|
|
Stat_Attributes_Dexterity Stat = 10
|
|
|
|
Stat_Attributes_Intelligence Stat = 20
|
|
|
|
Stat_Attributes_Constitution Stat = 30
|
|
|
|
|
|
|
|
Stat_PhysicalPrecisionBonus Stat = 5
|
|
|
|
Stat_EvasionBonus Stat = 15
|
|
|
|
Stat_MagicPrecisionBonus Stat = 25
|
|
|
|
Stat_TotalPrecisionBonus Stat = 35
|
|
|
|
|
|
|
|
Stat_DamageBonus_Physical_Unarmed Stat = 40
|
|
|
|
Stat_DamageBonus_Physical_Slashing Stat = 50
|
|
|
|
Stat_DamageBonus_Physical_Piercing Stat = 60
|
|
|
|
Stat_DamageBonus_Physical_Bludgeoning Stat = 70
|
|
|
|
|
|
|
|
Stat_DamageBonus_Magic_Fire Stat = 80
|
|
|
|
Stat_DamageBonus_Magic_Cold Stat = 90
|
|
|
|
Stat_DamageBonus_Magic_Necrotic Stat = 100
|
|
|
|
Stat_DamageBonus_Magic_Thunder Stat = 110
|
|
|
|
Stat_DamageBonus_Magic_Acid Stat = 120
|
|
|
|
Stat_DamageBonus_Magic_Poison Stat = 130
|
|
|
|
|
|
|
|
Stat_MaxHealthBonus Stat = 140
|
|
|
|
)
|
|
|
|
|
2024-05-31 23:37:06 +03:00
|
|
|
func StatLongName(stat Stat) string {
|
|
|
|
switch stat {
|
|
|
|
case Stat_Attributes_Strength:
|
|
|
|
return "Strength"
|
|
|
|
case Stat_Attributes_Intelligence:
|
|
|
|
return "Intelligence"
|
|
|
|
case Stat_Attributes_Dexterity:
|
|
|
|
return "Dexterity"
|
|
|
|
case Stat_Attributes_Constitution:
|
|
|
|
return "Constitution"
|
|
|
|
default:
|
|
|
|
return "Unknown"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-21 23:08:51 +03:00
|
|
|
type StatModifierId string
|
|
|
|
|
|
|
|
type StatModifier struct {
|
|
|
|
Id StatModifierId
|
|
|
|
Stat Stat
|
|
|
|
Bonus int
|
|
|
|
}
|
|
|
|
|
|
|
|
// RPG system is based off of dice rolls
|
|
|
|
|
|
|
|
func rollDice(times, sides int) int {
|
|
|
|
acc := 0
|
|
|
|
|
|
|
|
for range times {
|
|
|
|
acc += 1 + rand.Intn(sides+1)
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc
|
|
|
|
}
|
|
|
|
|
|
|
|
func RollD100(times int) int {
|
|
|
|
return rollDice(times, 100)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RollD20(times int) int {
|
|
|
|
return rollDice(times, 20)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RollD12(times int) int {
|
|
|
|
return rollDice(times, 12)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RollD10(times int) int {
|
|
|
|
return rollDice(times, 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RollD8(times int) int {
|
|
|
|
return rollDice(times, 8)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RollD6(times int) int {
|
|
|
|
return rollDice(times, 6)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RollD4(times int) int {
|
|
|
|
return rollDice(times, 4)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Contests are "meets it, beats it"
|
|
|
|
//
|
|
|
|
// Luck roll = 1d10
|
|
|
|
//
|
|
|
|
// 2 rolls per attack:
|
|
|
|
//
|
|
|
|
// BASIC ATTACKS ( spells and abilities can have special rules, or in leu of special rules, these are used ):
|
|
|
|
//
|
|
|
|
// 1. Attack roll ( determines if the attack lands ). Contest between Evasion and Precision.
|
|
|
|
// Evasion = Dexterity + Luck roll.
|
|
|
|
// Precision = ( Strength | Intelligence ) + Luck roll ( intelligence for magic, strength for melee ).
|
|
|
|
//
|
|
|
|
// 2. Damage roll ( only if the previous was successful ). Each spell, ability and weapon has its own damage calculation.
|
|
|
|
|
|
|
|
type DamageType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
DamageType_Physical_Unarmed DamageType = 0
|
|
|
|
DamageType_Physical_Slashing DamageType = 1
|
|
|
|
DamageType_Physical_Piercing DamageType = 2
|
|
|
|
DamageType_Physical_Bludgeoning DamageType = 3
|
|
|
|
|
|
|
|
DamageType_Magic_Fire DamageType = 4
|
|
|
|
DamageType_Magic_Cold DamageType = 5
|
|
|
|
DamageType_Magic_Necrotic DamageType = 6
|
|
|
|
DamageType_Magic_Thunder DamageType = 7
|
|
|
|
DamageType_Magic_Acid DamageType = 8
|
|
|
|
DamageType_Magic_Poison DamageType = 9
|
|
|
|
)
|
|
|
|
|
|
|
|
func DamageTypeToBonusStat(dmgType DamageType) Stat {
|
|
|
|
switch dmgType {
|
|
|
|
case DamageType_Physical_Unarmed:
|
|
|
|
return Stat_DamageBonus_Physical_Unarmed
|
|
|
|
case DamageType_Physical_Slashing:
|
|
|
|
return Stat_DamageBonus_Physical_Slashing
|
|
|
|
case DamageType_Physical_Piercing:
|
|
|
|
return Stat_DamageBonus_Physical_Piercing
|
|
|
|
case DamageType_Physical_Bludgeoning:
|
|
|
|
return Stat_DamageBonus_Physical_Bludgeoning
|
|
|
|
case DamageType_Magic_Fire:
|
|
|
|
return Stat_DamageBonus_Magic_Fire
|
|
|
|
case DamageType_Magic_Cold:
|
|
|
|
return Stat_DamageBonus_Magic_Fire
|
|
|
|
case DamageType_Magic_Necrotic:
|
|
|
|
return Stat_DamageBonus_Magic_Necrotic
|
|
|
|
case DamageType_Magic_Thunder:
|
|
|
|
return Stat_DamageBonus_Magic_Thunder
|
|
|
|
case DamageType_Magic_Acid:
|
|
|
|
return Stat_DamageBonus_Magic_Acid
|
|
|
|
case DamageType_Magic_Poison:
|
|
|
|
return Stat_DamageBonus_Magic_Poison
|
|
|
|
default:
|
|
|
|
return Stat_NonExtant
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func LuckRoll() int {
|
|
|
|
return RollD10(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TotalModifierForStat(entity RPGEntity, stat Stat) int {
|
|
|
|
agg := 0
|
|
|
|
|
|
|
|
for _, m := range entity.CollectModifiersForStat(stat) {
|
|
|
|
agg += m.Bonus
|
|
|
|
}
|
|
|
|
|
|
|
|
return agg
|
|
|
|
}
|
|
|
|
|
|
|
|
func StatValue(entity RPGEntity, stat Stat) int {
|
|
|
|
return entity.BaseStat(stat) + TotalModifierForStat(entity, stat)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Base Max Health is determined from constitution:
|
2024-05-30 23:39:54 +03:00
|
|
|
// 5*Constitution + Max Health Bonus
|
2024-05-21 23:08:51 +03:00
|
|
|
func BaseMaxHealth(entity RPGEntity) int {
|
2024-05-30 23:39:54 +03:00
|
|
|
return 5*StatValue(entity, Stat_Attributes_Constitution) + StatValue(entity, Stat_MaxHealthBonus)
|
2024-05-21 23:08:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Dexterity + Evasion bonus + luck roll
|
|
|
|
func EvasionRoll(victim RPGEntity) int {
|
|
|
|
return StatValue(victim, Stat_Attributes_Dexterity) + StatValue(victim, Stat_EvasionBonus) + LuckRoll()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strength + Precision bonus ( melee + total ) + luck roll
|
|
|
|
func PhysicalPrecisionRoll(attacker RPGEntity) int {
|
|
|
|
return StatValue(attacker, Stat_Attributes_Strength) + StatValue(attacker, Stat_PhysicalPrecisionBonus) + StatValue(attacker, Stat_TotalPrecisionBonus) + LuckRoll()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Intelligence + Precision bonus ( magic + total ) + luck roll
|
|
|
|
func MagicPrecisionRoll(attacker RPGEntity) int {
|
|
|
|
return StatValue(attacker, Stat_Attributes_Intelligence) + StatValue(attacker, Stat_MagicPrecisionBonus) + StatValue(attacker, Stat_TotalPrecisionBonus) + LuckRoll()
|
|
|
|
}
|
|
|
|
|
|
|
|
// true = hit lands, false = hit does not land
|
|
|
|
func MagicHitRoll(attacker RPGEntity, victim RPGEntity) bool {
|
|
|
|
return hitRoll(EvasionRoll(victim), MagicPrecisionRoll(attacker))
|
|
|
|
}
|
|
|
|
|
|
|
|
// true = hit lands, false = hit does not land
|
|
|
|
func PhysicalHitRoll(attacker RPGEntity, victim RPGEntity) bool {
|
|
|
|
return hitRoll(EvasionRoll(victim), PhysicalPrecisionRoll(attacker))
|
|
|
|
}
|
|
|
|
|
|
|
|
func hitRoll(evasionRoll, precisionRoll int) bool {
|
|
|
|
return evasionRoll < precisionRoll
|
|
|
|
}
|
|
|
|
|
|
|
|
func UnarmedDamage(attacker RPGEntity) int {
|
|
|
|
return RollD4(1) + StatValue(attacker, Stat_DamageBonus_Physical_Unarmed)
|
|
|
|
}
|