aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_attack.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-25 15:40:48 -0400
committerhistoria <[not public]>2026-06-25 15:40:48 -0400
commitabd612c15799f604e671e83dc7c410ed2b44185f (patch)
tree0597ca92350de73aac2a7cf26b2f2d6595dac0cc /internal/game/cmd_attack.go
parent2725e2927a1595c7b100d942d1f14146252adeb7 (diff)
downloadthehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz
slop refactor
Diffstat (limited to 'internal/game/cmd_attack.go')
-rw-r--r--internal/game/cmd_attack.go730
1 files changed, 0 insertions, 730 deletions
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index abaefbb..5635fd0 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -2,740 +2,11 @@ package game
import (
"fmt"
- "math/rand"
- "sort"
- "strconv"
"strings"
- "thehouseoficarus/internal/action"
- "thehouseoficarus/internal/color"
- "thehouseoficarus/internal/combat"
- "thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
- "thehouseoficarus/internal/object"
- "thehouseoficarus/internal/player"
- "thehouseoficarus/internal/world"
)
-func (g *Game) doAttack(sess *net.Session, input string) {
- p := sess.Player
-
- if combat.GetCombat(p.Name) != nil {
- sess.WriteLine("You are already in combat!")
- return
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- mob := g.findMob(sess, input, p.RoomID)
- if mob == nil {
- return
- }
-
- if mob.HP <= 0 {
- if mob.IsTask() {
- sess.WriteLine(fmt.Sprintf("%s is already complete.", mobDisplayName(mob, true)))
- } else {
- sess.WriteLine("That is already dead.")
- }
- return
- }
-
- if mob.Protected {
- sess.WriteLine(fmt.Sprintf("You can't attack %s!", mobDisplayName(mob, true)))
- return
- }
-
- if mob.AssassinLevel > 0 && p.Level(player.Assassin) < mob.AssassinLevel {
- sess.WriteLine(fmt.Sprintf("You need Assassin level %d to attack %s.", mob.AssassinLevel, mobDisplayName(mob, true)))
- return
- }
-
- if combat.IsMobInCombat(mob.InstanceID) {
- sess.WriteLine(fmt.Sprintf("%s is already engaged in combat!", mobDisplayName(mob, false)))
- return
- }
-
- if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil && def.WeaponType == object.WeaponRanged {
- if _, hasAmmo := p.Equipment[object.SlotAmmo]; !hasAmmo || p.AmmoQty <= 0 {
- sess.WriteLine("You don't have any ammo equipped.")
- return
- }
- }
- }
-
- g.startCombat(sess, p, mob)
-}
-
-func (g *Game) findMob(sess *net.Session, input string, roomID int) *world.MobInstance {
- lower := strings.ToLower(input)
- mobs := g.MobStore.MobsInRoom(roomID)
-
- idx := -1
- name := lower
- if dotPos := strings.Index(lower, "."); dotPos > 0 {
- if n, err := strconv.Atoi(lower[:dotPos]); err == nil && n > 0 {
- idx = n
- name = lower[dotPos+1:]
- }
- }
-
- var exact []*world.MobInstance
- var prefix []*world.MobInstance
- for _, m := range mobs {
- q := m.MatchQuality(name)
- if q == world.MatchExact {
- exact = append(exact, m)
- } else if q == world.MatchPrefix {
- prefix = append(prefix, m)
- }
- }
-
- candidates := exact
- if len(candidates) == 0 {
- candidates = prefix
- }
-
- if len(candidates) == 0 {
- sess.WriteLine(fmt.Sprintf("There's no '%s' here.", input))
- return nil
- }
-
- sort.Slice(candidates, func(i, j int) bool {
- return candidates[i].InstanceID < candidates[j].InstanceID
- })
-
- if idx > 0 {
- if idx-1 < len(candidates) {
- return candidates[idx-1]
- }
- return nil
- }
-
- if len(candidates) == 1 {
- return candidates[0]
- }
-
- seen := make(map[string]bool)
- for _, m := range candidates {
- seen[mobDisplayName(m, false)] = true
- }
- if len(seen) > 1 {
- sess.WriteLine("Which one?")
- return nil
- }
- return candidates[0]
-}
-
-func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- g.cancelRest(p.Name)
- g.cancelBgAction(p)
-
- combat.EnterCombat(p.Name, mob.InstanceID)
- p.AttackTimer = 0
-
- isTask := mob.IsTask()
- autotriggerActive := p.AutotriggerMod != ""
-
- if !autotriggerActive {
- attBonus, strBonus, defBonus := combat.AttackStyleBonus(string(p.AttackStyle))
- var styleParts []string
- if attBonus > 0 {
- styleParts = append(styleParts, fmt.Sprintf("+%d atk", attBonus))
- }
- if strBonus > 0 {
- styleParts = append(styleParts, fmt.Sprintf("+%d str", strBonus))
- }
- if defBonus > 0 {
- styleParts = append(styleParts, fmt.Sprintf("+%d def", defBonus))
- }
- styleStr := ""
- if len(styleParts) > 0 {
- styleStr = " Style: " + string(p.AttackStyle) + " (" + strings.Join(styleParts, ", ") + ")"
- }
- if isTask {
- sess.WriteLine(fmt.Sprintf("You begin to %s %s.%s", taskWorkVerb(mob), g.colorize(sess, "mob", mobDisplayName(mob, true)), styleStr))
- } else {
- sess.WriteLine(fmt.Sprintf("You attack %s!%s", g.colorize(sess, "mob", mobDisplayName(mob, true)), styleStr))
- }
- } else {
- mod := GetMod(p.AutotriggerMod)
- modName := p.AutotriggerMod
- if mod != nil {
- modName = mod.Name
- }
- if isTask {
- sess.WriteLine(fmt.Sprintf("You begin to %s %s using %s!",
- taskWorkVerb(mob),
- g.colorize(sess, "mob", mobDisplayName(mob, true)),
- g.colorize(sess, "science_mod", modName)))
- } else {
- sess.WriteLine(fmt.Sprintf("You attack %s with %s!",
- g.colorize(sess, "mob", mobDisplayName(mob, true)),
- g.colorize(sess, "science_mod", modName)))
- }
- }
- if isTask {
- g.broadcastAction(sess, "\n%s begins working on %s.", p.Name, mobDisplayName(mob, true))
- } else {
- g.broadcastAction(sess, "\n%s attacks %s!", p.Name, mobDisplayName(mob, true))
- }
-
- g.Ticks.Subscribe(1, func() bool {
- cs := combat.GetCombat(p.Name)
- if cs == nil || !cs.Active {
- return false
- }
- currentMob := g.MobStore.GetInstance(cs.MobID)
- if currentMob == nil || currentMob.HP <= 0 {
- g.endCombat(sess, p, currentMob)
- return false
- }
-
- p.AttackTimer++
- if float64(p.AttackTimer) >= g.playerWeaponSpeed(p) {
- p.AttackTimer = 0
-
- if g.hasDeckEquipped(p) {
- if p.AutotriggerMod != "" {
- mod := GetMod(p.AutotriggerMod)
- if mod != nil && g.hasJunkCost(p, mod) {
- g.scienceAttack(sess, p, currentMob, mod)
- } else if mod != nil {
- sess.WriteLine(g.colorize(sess, "error",
- fmt.Sprintf("\nYou're out of junk for %s!! You flail with your fists in desperation!", mod.Name)))
- g.playerAttackUnarmed(sess, p, currentMob)
- } else {
- p.AutotriggerMod = ""
- sess.WriteLine(g.colorize(sess, "error",
- "\nNo autotrigger set! Set a module with the 'autotrigger' command."))
- g.playerAttackUnarmed(sess, p, currentMob)
- }
- } else {
- sess.WriteLine(g.colorize(sess, "error",
- "\nNo autotrigger set! Set a module with the 'autotrigger' command."))
- g.playerAttackUnarmed(sess, p, currentMob)
- }
- } else {
- if p.QueuedTrigger != "" {
- mod := GetMod(p.QueuedTrigger)
- p.QueuedTrigger = ""
- if mod != nil && g.hasJunkCost(p, mod) {
- g.scienceAttack(sess, p, currentMob, mod)
- } else {
- g.playerAttack(sess, p, currentMob)
- }
- } else {
- g.playerAttack(sess, p, currentMob)
- }
- }
- }
-
- if currentMob.HP <= 0 {
- g.endCombat(sess, p, currentMob)
- return false
- }
- return true
- })
-
- if !isTask {
- g.Ticks.Subscribe(engine.ToTicks(mob.Speed), func() bool {
- cs := combat.GetCombat(p.Name)
- if cs == nil || !cs.Active {
- return false
- }
- currentMob := g.MobStore.GetInstance(cs.MobID)
- if currentMob == nil || currentMob.HP <= 0 {
- return false
- }
- g.mobAttack(sess, p, currentMob)
- if p.HP <= 0 {
- g.endCombat(sess, p, currentMob)
- return false
- }
- return true
- })
- }
-}
-
-func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- if g.processConsumeQueue(p, sess) {
- return
- }
-
- attackType, weaponType, attRoll, defRoll, maxHit := g.calculatePlayerAttackRoll(p, mob)
-
- if g.isSafespotted(p.Name) && attackType != "ranged" && attackType != "science" {
- if ss, ok := g.safespot.Get(p.Name); 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 {
- 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 != "" {
- attackType = def.AttackType
- } else if def.WeaponType == object.WeaponRanged {
- attackType = "ranged"
- } else if def.WeaponType == object.WeaponScience {
- attackType = "science"
- }
- weaponType = def.WeaponType
- }
- }
-
- totals := g.playerEquipBonuses(p)
- isRanged := weaponType == object.WeaponRanged
-
- if isRanged {
- rangedBonus, _ := combat.RangedStyleBonus(string(p.AttackStyle))
- equipAttack := totals.RangedAttack
- effectiveRanged := p.Level(player.Ranged) + g.techLevelBonus(p, "ranged") + g.buffLevelBonus(p, "ranged")
- attRoll = combat.EffectiveRoll(effectiveRanged, rangedBonus, equipAttack)
-
- mobDefBonus := combat.SelectBonus(attackType,
- mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
- mob.ScienceDefense, mob.RangedDefense)
- defRoll = combat.EffectiveRoll(mob.Defense, 0, mobDefBonus)
- maxHit = combat.MaxHit(effectiveRanged, rangedBonus, totals.RangedStrength)
- } else {
- attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
- equipAttack := combat.SelectBonus(attackType,
- totals.StabAttack, totals.SlashAttack, totals.CrushAttack,
- totals.ScienceAttack, totals.RangedAttack)
- effectiveAccuracy := p.Level(player.Accuracy) + g.techLevelBonus(p, "accuracy") + g.buffLevelBonus(p, "accuracy")
- attRoll = combat.EffectiveRoll(effectiveAccuracy, attBonus, equipAttack)
-
- mobDefBonus := combat.SelectBonus(attackType,
- mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
- mob.ScienceDefense, mob.RangedDefense)
- 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
-}
-
-func (g *Game) playerAttackUnarmed(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- if g.processConsumeQueue(p, sess) {
- return
- }
-
- attackType, _, attRoll, defRoll, maxHit := g.calculateUnarmedAttackRoll(p, mob)
-
- if combat.HitCheck(attRoll, defRoll) {
- g.applyPlayerHit(sess, p, mob, attackType, object.WeaponMelee, maxHit)
- } else {
- g.applyPlayerMiss(sess, p, mob, object.WeaponMelee)
- }
-}
-
-func (g *Game) calculateUnarmedAttackRoll(p *player.Player, mob *world.MobInstance) (attackType string, weaponType object.WeaponType, attRoll int, defRoll int, maxHit int) {
- attackType = "crush"
- weaponType = object.WeaponMelee
-
- attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
- effectiveAccuracy := p.Level(player.Accuracy) + g.techLevelBonus(p, "accuracy") + g.buffLevelBonus(p, "accuracy")
- attRoll = combat.EffectiveRoll(effectiveAccuracy, attBonus, 0)
-
- mobDefBonus := combat.SelectBonus(attackType,
- mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
- mob.ScienceDefense, mob.RangedDefense)
- 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
-}
-
-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()
- }
-
- 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)))
- }
-
- isRanged := weaponType == object.WeaponRanged
- gains := g.awardCombatXP(sess, p, dmg, isRanged)
-
- if isRanged {
- g.consumeAmmo(sess, p)
- }
-
- if mob.IsTask() {
- g.writeTaskProgress(sess, p, mob, dmg, gains)
- } else {
- 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, 999))
- if w2 := len(fmt.Sprintf("%s hits you for %d damage.", attacker, 999)); w2 > w {
- w = w2
- }
- prefix := fmt.Sprintf("You hit %s for %s damage.", g.colorize(sess, "mob", mobName), g.colorize(sess, "damage_dealt", fmt.Sprint(dmg)))
- visLen := color.VisibleLen(prefix)
- if visLen < w {
- prefix += strings.Repeat(" ", w-visLen+1)
- }
- hpSuffix := fmt.Sprintf("%s [%2d/%d]", g.hpBar(sess, mob.HP, mob.MaxHP), mob.HP, mob.MaxHP)
- line := prefix + hpSuffix + g.formatXpDrop(sess, p, gains)
- sess.WriteLine(line)
- }
-
- 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 {
- g.consumeAmmo(sess, p)
- if p.AmmoQty <= 0 {
- 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) {
- 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
- if mobAttackType == "" {
- mobAttackType = "crush"
- }
-
- attRoll = combat.EffectiveRoll(mob.Attack, 0, mob.AttackBonus)
-
- totals := g.playerEquipBonuses(p)
- equipDef := combat.SelectBonus(mobAttackType,
- totals.StabDefense, totals.SlashDefense, totals.CrushDefense,
- totals.ScienceDefense, totals.RangedDefense)
- defRoll = combat.EffectiveRoll(p.Level(player.Defense)+g.techLevelBonus(p, "defense")+g.buffLevelBonus(p, "defense"), defStyleBonus, equipDef)
- return
-}
-
-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
- }
- 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
- }
- 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)
-
- 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, 999))
- if w2 := len(fmt.Sprintf("You hit %s for %d damage.", mobName, 999)); w2 > w {
- w = w2
- }
- prefix := fmt.Sprintf("%s hits you for %s damage.", g.colorize(sess, "mob", attacker), g.colorize(sess, "damage_taken", fmt.Sprint(dmg)))
- visLen := color.VisibleLen(prefix)
- if visLen < w {
- prefix += strings.Repeat(" ", w-visLen+1)
- }
- hpSuffix := fmt.Sprintf("%s [%2d/%d]", g.hpBar(sess, p.HP, p.MaxHP()), p.HP, p.MaxHP())
- sess.WriteLine(prefix + hpSuffix)
-
- if ss, hasSS := g.safespot.Get(p.Name); hasSS && ss.HideCountdown > 0 {
- g.safespot.Delete(p.Name)
- sess.WriteLine(g.colorize(sess, "error", "You're hit while repositioning! You failed to hide."))
- }
-
- if p.MoveTicks > 0 {
- p.ClearMoveState()
- 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) {
- combat.LeaveCombat(p.Name)
-
- if p.HP <= 0 {
- g.killPlayer(sess, p, mob)
- return
- }
-
- if mob != nil && mob.HP <= 0 {
- isTask := mob.IsTask()
- p.Stats.RecordMobKill(mob.DefID)
-
- if isTask {
- complete := mob.CompleteMessage
- if complete == "" {
- complete = fmt.Sprintf("You finish your work on %s!", mobDisplayName(mob, true))
- }
- sess.WriteLine(g.colorize(sess, "victory", "\n"+complete))
- } else {
- sess.WriteLine(g.colorize(sess, "victory", fmt.Sprintf("\nYou have defeated %s!", mobDisplayName(mob, true))))
- g.onAssassinKill(sess, p, mob)
- }
-
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- if isTask {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s finishes working on %s.", p.Name, mobDisplayName(mob, false))))
- } else {
- 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)))
- }
- }
- }
- }
-
- dropLabel := func() string {
- if isTask {
- return "You receive:"
- }
- dropper := mob.Name
- if !mob.Unique {
- dropper = "The " + mob.Name
- }
- return dropper + " drops:"
- }()
-
- if mob.Drops.Remains != "" {
- g.World.AddReservedItem(p.RoomID, mob.Drops.Remains, 1, p.Name)
- def, _ := g.ItemStore.Load(mob.Drops.Remains)
- name := mob.Drops.Remains
- if def != nil {
- name = def.Name
- }
- coloredName := g.itemColorize(sess, def, name)
- sess.WriteLine(fmt.Sprintf("%s %s", g.colorize(sess, "drop_message", dropLabel), coloredName))
- }
-
- if len(mob.Drops.Loot) > 0 {
- entry := action.ResolveDrop(g.DataDir, mob.Drops.Loot)
- if entry != nil && entry.ItemID != "" {
- qty := entry.Quantity
- if qty <= 0 {
- qty = 1
- }
- g.World.AddReservedItem(p.RoomID, entry.ItemID, qty, p.Name)
- def, _ := g.ItemStore.Load(entry.ItemID)
- name := entry.ItemID
- if def != nil {
- name = def.Name
- }
- coloredName := g.itemColorize(sess, def, name)
- if qty > 1 {
- sess.WriteLine(fmt.Sprintf("%s %d x %s", g.colorize(sess, "drop_message", dropLabel), qty, coloredName))
- } else {
- sess.WriteLine(fmt.Sprintf("%s %s", g.colorize(sess, "drop_message", dropLabel), coloredName))
- }
- }
- }
-
- respawnTicks := engine.ToTicks(mob.RespawnTicks)
- if respawnTicks <= 0 {
- respawnTicks = engine.ToTicks(30)
- }
- instanceID := mob.InstanceID
- g.Ticks.Subscribe(respawnTicks, func() bool {
- g.respawnMob(instanceID)
- return false
- })
- g.writePrompt(sess)
- }
-}
-
-// killPlayer handles a player death from any source (combat or a room hazard).
-// mob may be nil (e.g. a hazard kill); it is only used for Dead Man's Switch
-// retribution.
-func (g *Game) killPlayer(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- combat.LeaveCombat(p.Name)
-
- if p.HasActiveTech("retribution") {
- retDef := GetTechDef("retribution")
- if retDef != nil && mob != nil && mob.HP > 0 {
- retDmg := int(float64(p.MaxHP()) * retDef.Effects.RetributionPct)
- if retDmg > 0 {
- mob.HP -= retDmg
- if mob.HP < 0 {
- mob.HP = 0
- }
- sess.WriteLine(fmt.Sprintf("\nDead Man's Switch activates! %s takes %d damage!",
- mobDisplayName(mob, true), retDmg))
- }
- }
- }
- p.DeactivateAllTechs()
- p.Stats.RecordDeath()
- sess.WriteLine(g.colorize(sess, "death", "\nOh dear, you are dead!"))
- p.ClearMoveState()
- p.HazardTimer = 0
- if ss, ok := g.safespot.Get(p.Name); ok {
- g.forceLeaveSafespot(sess, p, &ss, "")
- }
- g.dropItemsOnDeath(p)
- p.HP = p.MaxHP()
- p.RoomID = 1001
- g.AccountStore.SaveCharacter(p)
- if g.Hub != nil {
- g.Hub.EnterRoom(sess, p.RoomID)
- }
- g.doLook(sess)
- g.writePrompt(sess)
-}
-
-func (g *Game) awardCombatXP(sess *net.Session, p *player.Player, dmg int, isRanged bool) []xpGain {
- baseXP := dmg * 4
- var gains []xpGain
-
- if isRanged {
- gains = []xpGain{
- {string(player.Ranged), baseXP * 3 / 4},
- {string(player.Hitpoints), baseXP / 4},
- }
- } else {
- switch p.AttackStyle {
- case player.Accurate:
- gains = []xpGain{{string(player.Accuracy), baseXP * 3 / 4}, {string(player.Hitpoints), baseXP / 4}}
- case player.Aggressive:
- gains = []xpGain{{string(player.Strength), baseXP * 3 / 4}, {string(player.Hitpoints), baseXP / 4}}
- case player.Defensive:
- gains = []xpGain{{string(player.Defense), baseXP * 3 / 4}, {string(player.Hitpoints), baseXP / 4}}
- case player.Balanced:
- quarter := baseXP / 4
- gains = []xpGain{
- {string(player.Accuracy), quarter},
- {string(player.Strength), quarter},
- {string(player.Defense), quarter},
- {string(player.Hitpoints), quarter},
- }
- }
- }
-
- for _, gain := range gains {
- g.awardSkillXP(sess, p, player.SkillName(gain.Skill), gain.XP)
- }
- g.AccountStore.SaveCharacter(p)
- return gains
-}
-
-func (g *Game) stopCombat(playerName string) {
- if cs := combat.GetCombat(playerName); cs != nil {
- combat.LeaveCombat(playerName)
- }
-}
-
func (g *Game) executeAttack(sess *net.Session, args []string, rawInput string) {
if len(args) == 0 {
p := sess.Player
@@ -786,4 +57,3 @@ func (g *Game) respawnMob(instanceID string) {
}
}
}
-