From 0ea4eec5dd2122c6704216971eb1249276297867 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 19 Jun 2026 18:20:26 -0400 Subject: shop fixes, 'toggle' completely removed, movement speed increased --- internal/game/cmd_attack.go | 394 +++++++++++++++++++------------------------- 1 file changed, 171 insertions(+), 223 deletions(-) (limited to 'internal/game/cmd_attack.go') 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 -} -- cgit v1.2.3