aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_attack.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/cmd_attack.go')
-rw-r--r--internal/game/cmd_attack.go394
1 files changed, 171 insertions, 223 deletions
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index 77cd569..b0ce214 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -6,6 +6,7 @@ import (
"strconv"
"strings"
+ "thehouseoficarus/internal/action"
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
@@ -15,7 +16,7 @@ import (
)
func (g *Game) doAttack(sess *net.Session, input string) {
- p := sess.Player.(*player.Player)
+ p := sess.Player
if combat.GetCombat(p.Name) != nil {
sess.WriteLine("You are already in combat!")
@@ -23,7 +24,7 @@ func (g *Game) doAttack(sess *net.Session, input string) {
}
if p.Action != nil {
- g.CancelAction(p)
+ g.cancelAction(p)
}
mob := g.findMob(sess, input, p.RoomID)
@@ -125,17 +126,17 @@ func (g *Game) findMob(sess *net.Session, input string, roomID int) *world.MobIn
func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) {
g.cancelRest(p.Name)
- g.CancelBackgroundAction(p)
+ g.cancelBackgroundAction(p)
combat.EnterCombat(p.Name, mob.InstanceID)
- autocastActive := p.AutocastMod != ""
+ autotriggerActive := p.AutotriggerMod != ""
playerSpeed := g.playerWeaponSpeed(p)
- if autocastActive {
+ if autotriggerActive {
playerSpeed = 5.0
}
- if !autocastActive {
+ if !autotriggerActive {
attBonus, strBonus, defBonus := combat.AttackStyleBonus(string(p.AttackStyle))
var styleParts []string
if attBonus > 0 {
@@ -153,8 +154,8 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn
}
sess.WriteLine(fmt.Sprintf("\nYou attack %s!%s", g.colorize(sess, "mob_name", mobDisplayName(mob, true)), styleStr))
} else {
- mod := GetMod(p.AutocastMod)
- modName := p.AutocastMod
+ mod := GetMod(p.AutotriggerMod)
+ modName := p.AutotriggerMod
if mod != nil {
modName = mod.Name
}
@@ -182,16 +183,16 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn
g.endCombat(sess, p, currentMob)
return false
}
- if p.AutocastMod != "" {
- mod := GetMod(p.AutocastMod)
+ if p.AutotriggerMod != "" {
+ mod := GetMod(p.AutotriggerMod)
if mod != nil {
if !g.scienceAttack(sess, p, currentMob, mod) {
- p.AutocastMod = ""
+ p.AutotriggerMod = ""
sess.WriteLine("You've run out of junk. Switching to melee.")
g.playerAttack(sess, p, currentMob)
}
} else {
- p.AutocastMod = ""
+ p.AutotriggerMod = ""
g.playerAttack(sess, p, currentMob)
}
} else {
@@ -228,8 +229,17 @@ func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobI
return
}
- attackType := "crush"
- var weaponType object.WeaponType
+ attackType, weaponType, attRoll, defRoll, maxHit := g.calculatePlayerAttackRoll(p, mob)
+
+ if combat.HitCheck(attRoll, defRoll) {
+ g.applyPlayerHit(sess, p, mob, attackType, weaponType, maxHit)
+ } else {
+ g.applyPlayerMiss(sess, p, mob, weaponType)
+ }
+}
+
+func (g *Game) calculatePlayerAttackRoll(p *player.Player, mob *world.MobInstance) (attackType string, weaponType object.WeaponType, attRoll int, defRoll int, maxHit int) {
+ attackType = "crush"
if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
if def, err := g.ItemStore.Load(itemID); err == nil {
if def.AttackType != "" {
@@ -246,8 +256,6 @@ func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobI
totals := g.playerEquipBonuses(p)
isRanged := weaponType == object.WeaponRanged
- var attRoll, defRoll, maxHit int
-
if isRanged {
rangedBonus, _ := combat.RangedStyleBonus(string(p.AttackStyle))
equipAttack := totals.RangedAttack
@@ -273,90 +281,99 @@ func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobI
defRoll = combat.DefenseRoll(mob.Defense, 0, mobDefBonus)
maxHit = combat.MaxHit(p.Level(player.Strength)+g.techLevelBonus(p, "strength")+g.buffLevelBonus(p, "strength"), strBonus, totals.StrengthBonus)
}
+ return
+}
- if combat.HitCheck(attRoll, defRoll) {
- dmg := combat.RollDamage(maxHit)
+func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.MobInstance, attackType string, weaponType object.WeaponType, maxHit int) {
+ dmg := combat.RollDamage(maxHit)
- mob.HP -= dmg
- if mob.HP < 0 {
- mob.HP = 0
- }
- if mob.FinishingBlow != "" && mob.HP <= 0 {
- mob.HP = 1
- }
- if mob.HP < mob.MaxHP && mob.HP > 0 {
- mob.StartRegen()
- }
+ mob.HP -= dmg
+ if mob.HP < 0 {
+ mob.HP = 0
+ }
+ if mob.FinishingBlow != "" && mob.HP <= 0 {
+ mob.HP = 1
+ }
+ if mob.HP < mob.MaxHP && mob.HP > 0 {
+ mob.StartRegen()
+ }
- if mob.FinishingBlow != "" && mob.HP == 1 {
- fbDef, _ := g.ItemStore.Load(mob.FinishingBlow)
- fbName := mob.FinishingBlow
- if fbDef != nil {
- fbName = fbDef.Name
- }
- sess.WriteLine(g.colorize(sess, "warning",
- fmt.Sprintf(" %s resists death! Use %s on it to finish it off.",
- mobDisplayName(mob, false), fbName)))
+ if mob.FinishingBlow != "" && mob.HP == 1 {
+ fbDef, _ := g.ItemStore.Load(mob.FinishingBlow)
+ fbName := mob.FinishingBlow
+ if fbDef != nil {
+ fbName = fbDef.Name
}
- gains, leveled := g.awardCombatXP(p, dmg, isRanged)
+ sess.WriteLine(g.colorize(sess, "warning",
+ fmt.Sprintf(" %s resists death! Use %s on it to finish it off.",
+ mobDisplayName(mob, false), fbName)))
+ }
- if isRanged {
- p.AmmoQty--
- if p.AmmoQty <= 0 {
- delete(p.Equipment, object.SlotAmmo)
- p.AmmoQty = 0
- }
- g.AccountStore.SaveCharacter(p)
- }
+ isRanged := weaponType == object.WeaponRanged
+ gains := g.awardCombatXP(sess, p, dmg, isRanged)
- for _, skill := range leveled {
- sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", p.Level(skill), skill)))
+ if isRanged {
+ p.AmmoQty--
+ if p.AmmoQty <= 0 {
+ delete(p.Equipment, object.SlotAmmo)
+ p.AmmoQty = 0
}
+ g.AccountStore.SaveCharacter(p)
+ }
- mobName := mobDisplayName(mob, true)
- attacker := mob.Name
- if !mob.Unique {
- attacker = "The " + mob.Name
- }
- w := len(fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg))
- if w2 := len(fmt.Sprintf(" %s hits you for %d damage.", attacker, dmg)); w2 > w {
- w = w2
- }
- g.combatPadWidth = w
- prefix := fmt.Sprintf(" You hit %s for %s damage.", g.colorize(sess, "mob_name", mobName), g.colorize(sess, "damage", fmt.Sprint(dmg)))
- hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "enemy_hp", fmt.Sprint(mob.HP)), mob.MaxHP)
- line := fmt.Sprintf("%-*s %s", g.combatPadWidth, prefix, hpPart)
- if p.OptionBool("xp_drops") && len(gains) > 0 {
- var parts []string
- for _, gain := range gains {
- parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
- }
- line += g.colorize(sess, "xp", " ("+strings.Join(parts, ", ")+")")
+ mobName := mobDisplayName(mob, true)
+ attacker := mob.Name
+ if !mob.Unique {
+ attacker = "The " + mob.Name
+ }
+ w := len(fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg))
+ if w2 := len(fmt.Sprintf(" %s hits you for %d damage.", attacker, dmg)); w2 > w {
+ w = w2
+ }
+ prefix := fmt.Sprintf(" You hit %s for %s damage.", g.colorize(sess, "mob_name", mobName), g.colorize(sess, "damage", fmt.Sprint(dmg)))
+ hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "enemy_hp", fmt.Sprint(mob.HP)), mob.MaxHP)
+ line := fmt.Sprintf("%-*s %s", w, prefix, hpPart)
+ if p.OptionBool("xp_drops") && len(gains) > 0 {
+ var parts []string
+ for _, gain := range gains {
+ parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
}
- sess.WriteLine(line)
+ line += g.colorize(sess, "xp", " ("+strings.Join(parts, ", ")+")")
+ }
+ sess.WriteLine(line)
- if isRanged && p.AmmoQty <= 0 {
+ if isRanged && p.AmmoQty <= 0 {
+ sess.WriteLine("You've run out of ammo!")
+ combat.LeaveCombat(p.Name)
+ }
+}
+
+func (g *Game) applyPlayerMiss(sess *net.Session, p *player.Player, mob *world.MobInstance, weaponType object.WeaponType) {
+ sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" You miss %s.", mobDisplayName(mob, true))))
+
+ if weaponType == object.WeaponRanged {
+ p.AmmoQty--
+ 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)
}
- } else {
- sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" You miss %s.", mobDisplayName(mob, true))))
-
- if isRanged {
- p.AmmoQty--
- 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) mobAttack(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- cs := combat.GetCombat(p.Name)
+ attRoll, defRoll := g.calculateMobAttack(p, mob)
+
+ if combat.HitCheck(attRoll, defRoll) {
+ g.applyMobHit(sess, p, mob)
+ } else {
+ g.applyMobMiss(sess, mob)
+ }
+}
+
+func (g *Game) calculateMobAttack(p *player.Player, mob *world.MobInstance) (attRoll int, defRoll int) {
_, _, defStyleBonus := combat.AttackStyleBonus(string(p.AttackStyle))
mobAttackType := mob.AttackType
@@ -364,87 +381,89 @@ func (g *Game) mobAttack(sess *net.Session, p *player.Player, mob *world.MobInst
mobAttackType = "crush"
}
- attRoll := combat.AttackRoll(mob.Attack, 0, mob.AttackBonus)
+ attRoll = combat.AttackRoll(mob.Attack, 0, mob.AttackBonus)
totals := g.playerEquipBonuses(p)
equipDef := combat.SelectDefenseBonus(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.DefenseRoll(p.Level(player.Defense)+g.techLevelBonus(p, "defense")+g.buffLevelBonus(p, "defense"), defStyleBonus, equipDef)
+ return
+}
- if combat.HitCheck(attRoll, defRoll) {
- maxHit := combat.MaxHit(mob.Strength, 0, mob.StrengthBonus)
- dmg := combat.RollDamage(maxHit)
- dmg = g.applyTechProtection(p, mob, dmg)
-
- if mob.DamageWithout != "" {
- hasProtection := false
- for _, itemID := range p.Equipment {
- if itemID == mob.DamageWithout {
- hasProtection = true
- break
- }
+func (g *Game) applyMobHit(sess *net.Session, p *player.Player, mob *world.MobInstance) {
+ maxHit := combat.MaxHit(mob.Strength, 0, mob.StrengthBonus)
+ dmg := combat.RollDamage(maxHit)
+ dmg = g.applyTechProtection(p, mob, dmg)
+
+ if mob.DamageWithout != "" {
+ hasProtection := false
+ for _, itemID := range p.Equipment {
+ if itemID == mob.DamageWithout {
+ hasProtection = true
+ break
+ }
+ }
+ if !hasProtection {
+ dmg = dmg * 3 / 2
+ if dmg < 1 {
+ dmg = 1
}
- if !hasProtection {
- dmg = dmg * 3 / 2
- if dmg < 1 {
- dmg = 1
+ cs := combat.GetCombat(p.Name)
+ if cs != nil && !cs.DamageWarningShown {
+ cs.DamageWarningShown = true
+ fbDef, _ := g.ItemStore.Load(mob.DamageWithout)
+ fbName := mob.DamageWithout
+ if fbDef != nil {
+ fbName = fbDef.Name
}
- if cs != nil && !cs.DamageWarningShown {
- cs.DamageWarningShown = true
- fbDef, _ := g.ItemStore.Load(mob.DamageWithout)
- fbName := mob.DamageWithout
- if fbDef != nil {
- fbName = fbDef.Name
- }
- attacker := mob.Name
- if !mob.Unique {
- attacker = "The " + mob.Name
- }
- sess.WriteLine(g.colorize(sess, "warning",
- fmt.Sprintf(" %s's attack is extra effective! Equip %s for protection.",
- attacker, fbName)))
+ attacker := mob.Name
+ if !mob.Unique {
+ attacker = "The " + mob.Name
}
+ sess.WriteLine(g.colorize(sess, "warning",
+ fmt.Sprintf(" %s's attack is extra effective! Equip %s for protection.",
+ attacker, fbName)))
}
}
+ }
- p.HP -= dmg
- if p.HP < 0 {
- p.HP = 0
- }
- p.StartRegen()
- g.AccountStore.SaveCharacter(p)
+ p.HP -= dmg
+ if p.HP < 0 {
+ p.HP = 0
+ }
+ p.StartRegen()
+ g.AccountStore.SaveCharacter(p)
- attacker := mob.Name
- if !mob.Unique {
- attacker = "The " + mob.Name
- }
- mobName := mobDisplayName(mob, true)
- w := len(fmt.Sprintf(" %s hits you for %d damage.", attacker, dmg))
- if w2 := len(fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg)); w2 > w {
- w = w2
- }
- g.combatPadWidth = w
- prefix := fmt.Sprintf(" %s hits you for %s damage.", g.colorize(sess, "mob_name", attacker), g.colorize(sess, "damage", fmt.Sprint(dmg)))
- hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "character_hp", fmt.Sprint(p.HP)), p.MaxHP())
- sess.WriteLine(fmt.Sprintf("%-*s %s", g.combatPadWidth, prefix, hpPart))
+ attacker := mob.Name
+ if !mob.Unique {
+ attacker = "The " + mob.Name
+ }
+ mobName := mobDisplayName(mob, true)
+ w := len(fmt.Sprintf(" %s hits you for %d damage.", attacker, dmg))
+ if w2 := len(fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg)); w2 > w {
+ w = w2
+ }
+ prefix := fmt.Sprintf(" %s hits you for %s damage.", g.colorize(sess, "mob_name", attacker), g.colorize(sess, "damage", fmt.Sprint(dmg)))
+ hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "character_hp", fmt.Sprint(p.HP)), p.MaxHP())
+ sess.WriteLine(fmt.Sprintf("%-*s %s", w, prefix, hpPart))
- if p.MoveTicks > 0 {
- p.ClearMoveState()
- p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name}
- sess.WriteLine("Can't escape!")
- }
- } else {
- attacker := mob.Name
- if !mob.Unique {
- attacker = "The " + mob.Name
- }
- sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" %s misses you.", attacker)))
+ if p.MoveTicks > 0 {
+ p.ClearMoveState()
+ p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name}
+ sess.WriteLine("Can't escape!")
+ }
+}
+
+func (g *Game) applyMobMiss(sess *net.Session, mob *world.MobInstance) {
+ attacker := mob.Name
+ if !mob.Unique {
+ attacker = "The " + mob.Name
}
+ sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" %s misses you.", attacker)))
}
func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- g.combatPadWidth = 0
combat.LeaveCombat(p.Name)
p.ActionState = nil
@@ -486,7 +505,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
if g.Hub != nil {
for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
if other != sess && other.Player != nil {
- op := other.Player.(*player.Player)
+ op := other.Player
mobLvl := mobCombatLevel(mob)
levelStr := g.levelColorize(other, op.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl))
other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s has slain %s %s!", p.Name, mobDisplayName(mob, false), levelStr)))
@@ -510,7 +529,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
}
if len(mob.Drops.Loot) > 0 {
- entry := g.BehaviorStore.ResolveDrop(mob.Drops.Loot)
+ entry := action.ResolveDrop(g.DataDir, mob.Drops.Loot)
if entry != nil && entry.ItemID != "" {
qty := entry.Quantity
if qty <= 0 {
@@ -548,10 +567,9 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
}
}
-func (g *Game) awardCombatXP(p *player.Player, dmg int, isRanged bool) ([]xpGain, []player.SkillName) {
+func (g *Game) awardCombatXP(sess *net.Session, p *player.Player, dmg int, isRanged bool) []xpGain {
baseXP := dmg * 4
var gains []xpGain
- var leveledUp []player.SkillName
if isRanged {
gains = []xpGain{
@@ -578,71 +596,10 @@ func (g *Game) awardCombatXP(p *player.Player, dmg int, isRanged bool) ([]xpGain
}
for _, gain := range gains {
- if newLevel := p.AddSkillXP(player.SkillName(gain.Skill), gain.XP); newLevel > 0 {
- leveledUp = append(leveledUp, player.SkillName(gain.Skill))
- }
+ g.awardSkillXP(sess, p, player.SkillName(gain.Skill), gain.XP)
}
g.AccountStore.SaveCharacter(p)
- return gains, leveledUp
-}
-
-func (g *Game) dropItemsOnDeath(p *player.Player) {
- roomID := p.RoomID
-
- if p.Credits > 0 {
- g.World.AddGroundItem(roomID, "credits", p.Credits)
- p.Credits = 0
- }
-
- var items []deathDrop
-
- for slot, inv := range p.Inventory {
- if inv == nil || inv.Quantity <= 0 {
- continue
- }
- val := 0
- if def, err := g.ItemStore.Load(inv.ItemID); err == nil {
- val = def.Value * inv.Quantity
- }
- items = append(items, deathDrop{
- itemID: inv.ItemID,
- quantity: inv.Quantity,
- totalVal: val,
- invSlot: slot,
- })
- }
-
- for eqSlot, itemID := range p.Equipment {
- val := 0
- if def, err := g.ItemStore.Load(itemID); err == nil {
- val = def.Value
- }
- items = append(items, deathDrop{
- itemID: itemID,
- quantity: 1,
- totalVal: val,
- isEquip: true,
- equipSlot: eqSlot,
- })
- }
-
- if len(items) <= 3 {
- return
- }
-
- sort.Slice(items, func(i, j int) bool {
- return items[i].totalVal > items[j].totalVal
- })
-
- for i := 3; i < len(items); i++ {
- it := items[i]
- if it.isEquip {
- delete(p.Equipment, it.equipSlot)
- } else {
- p.SetInvSlot(it.invSlot, nil)
- }
- g.World.AddGroundItem(roomID, it.itemID, it.quantity)
- }
+ return gains
}
func (g *Game) stopCombat(playerName string) {
@@ -663,7 +620,7 @@ func (g *Game) respawnMob(instanceID string) {
if g.Hub != nil {
for _, sess := range g.Hub.PlayersInRoom(homeRoom) {
- if p, ok := sess.Player.(*player.Player); ok && p.OptionBool("mob_spawn") {
+ if p := sess.Player; p != nil && p.OptionBool("mob_spawn") {
mobLvl := mobCombatLevel(inst)
levelStr := g.levelColorize(sess, p.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl))
sess.WriteLine(fmt.Sprintf("\n%s %s spawns in the area.", g.colorize(sess, "mob_name", mobDisplayName(inst, false)), levelStr))
@@ -672,12 +629,3 @@ func (g *Game) respawnMob(instanceID string) {
}
}
-func (g *Game) playerWeaponSpeed(p *player.Player) float64 {
- if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- def, err := g.ItemStore.Load(itemID)
- if err == nil && def.Speed > 0 {
- return def.Speed
- }
- }
- return 5.0
-}