diff options
| author | historia <[not public]> | 2026-06-19 21:20:32 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-19 21:20:32 -0400 |
| commit | 5ea082ab4e82409aebc889b496f43e52b003d4f7 (patch) | |
| tree | 942f2c4a18ecbee42ba25726048d09827ea68894 /internal | |
| parent | 8ee8982b8759bcae116647cc993867f4c8b0a6ce (diff) | |
| download | thehouseoficarus-5ea082ab4e82409aebc889b496f43e52b003d4f7.tar.gz | |
feat: science combat/triggers overhauled. decks now use autotrigger attacks.
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/game/cmd_attack.go | 85 | ||||
| -rw-r--r-- | internal/game/cmd_autotrigger.go | 5 | ||||
| -rw-r--r-- | internal/game/cmd_registry.go | 2 | ||||
| -rw-r--r-- | internal/game/cmd_trigger.go | 16 | ||||
| -rw-r--r-- | internal/game/cmd_trigger_combat.go | 3 | ||||
| -rw-r--r-- | internal/game/sys_science.go | 53 | ||||
| -rw-r--r-- | internal/player/player.go | 2 |
7 files changed, 139 insertions, 27 deletions
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go index 222e3c3..f9cfba2 100644 --- a/internal/game/cmd_attack.go +++ b/internal/game/cmd_attack.go @@ -129,12 +129,9 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn g.cancelBackgroundAction(p) combat.EnterCombat(p.Name, mob.InstanceID) + p.AttackTimer = 0 autotriggerActive := p.AutotriggerMod != "" - playerSpeed := g.playerWeaponSpeed(p) - if autotriggerActive { - playerSpeed = 5.0 - } if !autotriggerActive { attBonus, strBonus, defBonus := combat.AttackStyleBonus(string(p.AttackStyle)) @@ -173,7 +170,7 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn } } - g.Ticks.Subscribe(engine.ToTicks(playerSpeed), func() bool { + g.Ticks.Subscribe(1, func() bool { cs := combat.GetCombat(p.Name) if cs == nil || !cs.Active { return false @@ -183,21 +180,46 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn g.endCombat(sess, p, currentMob) return false } - if p.AutotriggerMod != "" { - mod := GetMod(p.AutotriggerMod) - if mod != nil { - if !g.scienceAttack(sess, p, currentMob, mod) { - p.AutotriggerMod = "" - sess.WriteLine("You've run out of junk. Switching to melee.") - g.playerAttack(sess, p, currentMob) + + 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 { - p.AutotriggerMod = "" - g.playerAttack(sess, p, currentMob) + 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) + } } - } else { - g.playerAttack(sess, p, currentMob) } + if currentMob.HP <= 0 { g.endCombat(sess, p, currentMob) return false @@ -284,6 +306,37 @@ func (g *Game) calculatePlayerAttackRoll(p *player.Player, mob *world.MobInstanc return } +func (g *Game) playerAttackUnarmed(sess *net.Session, p *player.Player, mob *world.MobInstance) { + if g.processConsumeQueue(p, sess) { + p.ActionState = &ActionState{Type: ActionEating, TargetName: "food"} + 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)) + effectiveAttack := p.Level(player.Attack) + g.techLevelBonus(p, "attack") + g.buffLevelBonus(p, "attack") + attRoll = combat.AttackRoll(effectiveAttack, attBonus, 0) + + mobDefBonus := combat.SelectDefenseBonus(attackType, + mob.StabDefense, mob.SlashDefense, mob.CrushDefense, + mob.ScienceDefense, mob.RangedDefense) + defRoll = combat.DefenseRoll(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) diff --git a/internal/game/cmd_autotrigger.go b/internal/game/cmd_autotrigger.go index e2ecd1a..acfa76e 100644 --- a/internal/game/cmd_autotrigger.go +++ b/internal/game/cmd_autotrigger.go @@ -27,13 +27,14 @@ func (g *Game) doAutotrigger(sess *net.Session, input string) { return } - if strings.ToLower(input) == "off" { + lower := strings.ToLower(input) + if lower == "off" || lower == "none" { p.AutotriggerMod = "" sess.WriteLine("Autotrigger disabled.") return } - mod := FindMod(strings.ToLower(input)) + mod := FindModForPlayer(lower, p.Level(player.Science)) if mod == nil { sess.WriteLine("Unknown mod.") return diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go index 0439cc2..c4f7700 100644 --- a/internal/game/cmd_registry.go +++ b/internal/game/cmd_registry.go @@ -83,6 +83,8 @@ var commandRegistry = map[string]commandDef{ "mods": {(*Game).executeMods, ClassInstant}, "modlist": {(*Game).executeMods, ClassInstant}, "mod": {(*Game).executeMods, ClassInstant}, + "modules": {(*Game).executeMods, ClassInstant}, + "module": {(*Game).executeMods, ClassInstant}, "sneak": {(*Game).executeSneak, ClassInstant}, "trigger": {(*Game).executeTrigger, ClassActive}, "talk": {(*Game).executeTalk, ClassActive}, diff --git a/internal/game/cmd_trigger.go b/internal/game/cmd_trigger.go index 7d637ca..0acbeff 100644 --- a/internal/game/cmd_trigger.go +++ b/internal/game/cmd_trigger.go @@ -22,7 +22,7 @@ func (g *Game) doTrigger(sess *net.Session, input string) { return } - mod, targetArg := g.parseTriggerArgs(input) + mod, targetArg := g.parseTriggerArgs(input, p.Level(player.Science)) if mod == nil { sess.WriteLine("Unknown mod. Type 'mods' to see available mods.") return @@ -50,12 +50,12 @@ func (g *Game) doTrigger(sess *net.Session, input string) { } } -func (g *Game) parseTriggerArgs(input string) (*ModDef, string) { +func (g *Game) parseTriggerArgs(input string, sciLevel int) (*ModDef, string) { lower := strings.ToLower(input) words := strings.Fields(lower) for i := len(words); i > 0; i-- { candidate := strings.Join(words[:i], " ") - mod := FindMod(candidate) + mod := FindModForPlayer(candidate, sciLevel) if mod != nil { target := strings.TrimSpace(strings.Join(words[i:], " ")) return mod, target @@ -66,8 +66,8 @@ func (g *Game) parseTriggerArgs(input string) (*ModDef, string) { func (g *Game) triggerCombatMod(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { if cs := combat.GetCombat(p.Name); cs != nil { - p.AutotriggerMod = mod.ID - sess.WriteLine(fmt.Sprintf("You switch to triggering %s.", mod.Name)) + p.QueuedTrigger = mod.ID + sess.WriteLine(fmt.Sprintf("You queue %s for your next attack.", mod.Name)) return } @@ -106,6 +106,10 @@ func (g *Game) triggerCombatMod(sess *net.Session, p *player.Player, mod *ModDef return } - p.AutotriggerMod = mod.ID + if g.hasDeckEquipped(p) { + p.AutotriggerMod = mod.ID + } else { + p.QueuedTrigger = mod.ID + } g.startCombat(sess, p, mob) } diff --git a/internal/game/cmd_trigger_combat.go b/internal/game/cmd_trigger_combat.go index 545a117..62c5083 100644 --- a/internal/game/cmd_trigger_combat.go +++ b/internal/game/cmd_trigger_combat.go @@ -15,9 +15,6 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob sess.WriteLine(fmt.Sprintf("You need level %d science to trigger %s.", mod.Level, mod.Name)) return false } - if !g.hasJunkCost(p, mod) { - return false - } if g.processConsumeQueue(p, sess) { p.ActionState = &ActionState{Type: ActionEating, TargetName: "food"} diff --git a/internal/game/sys_science.go b/internal/game/sys_science.go index 23aeef4..e35105a 100644 --- a/internal/game/sys_science.go +++ b/internal/game/sys_science.go @@ -81,6 +81,59 @@ func FindMod(input string) *ModDef { return nil } +func findAllModMatches(input string) []*ModDef { + inputWords := strings.Fields(strings.ToLower(input)) + if len(inputWords) == 0 { + return nil + } + var matches []*ModDef + for _, m := range AllMods { + lowerName := strings.ToLower(m.Name) + lowerID := strings.ToLower(m.ID) + allMatch := true + for _, word := range inputWords { + if !strings.Contains(lowerName, word) && !strings.Contains(lowerID, word) { + allMatch = false + break + } + } + if allMatch { + matches = append(matches, m) + } + } + return matches +} + +func FindModForPlayer(input string, sciLevel int) *ModDef { + if m, ok := modByID[input]; ok { + return m + } + lower := strings.ToLower(strings.ReplaceAll(input, " ", "_")) + for _, m := range AllMods { + if strings.HasPrefix(m.ID, lower) { + return m + } + } + + matches := findAllModMatches(input) + if len(matches) == 0 { + return nil + } + if len(matches) == 1 { + return matches[0] + } + + sort.Slice(matches, func(i, j int) bool { + return matches[i].Level > matches[j].Level + }) + for _, m := range matches { + if m.Level <= sciLevel { + return m + } + } + return matches[len(matches)-1] +} + type chipEntry struct { Input string Output string diff --git a/internal/player/player.go b/internal/player/player.go index 3842348..1ddb3ea 100644 --- a/internal/player/player.go +++ b/internal/player/player.go @@ -180,6 +180,8 @@ type Player struct { MoveTarget int `yaml:"-"` VisualTickCurrent int `yaml:"-"` AutotriggerMod string `yaml:"-"` + QueuedTrigger string `yaml:"-"` + AttackTimer int `yaml:"-"` Battery float64 `yaml:"battery"` ActiveTechs map[string]bool `yaml:"-"` QuickTech string `yaml:"quick_tech,omitempty"` |
