package game import ( "fmt" "strings" "thehouseoficarus/internal/combat" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func (g *Game) doTrigger(sess *net.Session, input string) { p := sess.Player.(*player.Player) input = strings.TrimSpace(input) if input == "" { sess.WriteLine("Trigger what? Type 'mods' to see available mods.") return } mod, targetArg := g.parseTriggerArgs(input) if mod == nil { sess.WriteLine("Unknown mod. Type 'mods' to see available mods.") return } if p.Level(player.Science) < mod.Level { sess.WriteLine(fmt.Sprintf("You need level %d science to trigger %s.", mod.Level, mod.Name)) return } if !g.hasJunkCost(p, mod) { sess.WriteLine(fmt.Sprintf("You don't have enough junk to trigger %s.", mod.Name)) return } switch mod.Category { case ModCombat: g.triggerCombatMod(sess, p, mod, targetArg) case ModTransport: g.triggerTransport(sess, p, mod) case ModProcessing: g.triggerProcessing(sess, p, mod, targetArg) case ModUtility: g.triggerUtility(sess, p, mod, targetArg) case ModEnchant: g.triggerEnchant(sess, p, mod, targetArg) } } func (g *Game) parseTriggerArgs(input string) (*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) if mod != nil { target := strings.TrimSpace(strings.Join(words[i:], " ")) return mod, target } } return nil, "" } func (g *Game) triggerCombatMod(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { if cs := combat.GetCombat(p.Name); cs != nil { p.AutocastMod = mod.ID sess.WriteLine(fmt.Sprintf("You switch to triggering %s.", mod.Name)) return } if p.Action != nil { g.CancelAction(p) } var mobTarget string if targetArg == "" { mobTarget = g.resolveDefaultMob(p.RoomID) if mobTarget == "" { sess.WriteLine("Trigger on what?") return } } else { mobTarget = targetArg } mob := g.findMob(sess, mobTarget, p.RoomID) if mob == nil { return } if mob.HP <= 0 { sess.WriteLine("That is already dead.") return } if mob.Protected { sess.WriteLine(fmt.Sprintf("You can't attack %s!", mobDisplayName(mob, true))) return } if combat.IsMobInCombat(mob.InstanceID) { sess.WriteLine(fmt.Sprintf("%s is already engaged in combat!", mobDisplayName(mob, false))) return } p.AutocastMod = mod.ID g.startCombat(sess, p, mob) } func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef) { if combat.GetCombat(p.Name) != nil { sess.WriteLine("You can't teleport during combat!") return } if p.Action != nil { g.CancelAction(p) } g.consumeJunkCost(p, mod) sciXP := int(mod.BaseXP) if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d science! ***", p.Level(player.Science)))) } if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) } g.AccountStore.SaveCharacter(p) if g.Hub != nil { for _, other := range g.Hub.PlayersInRoom(p.RoomID) { if other != sess { other.WriteLine(fmt.Sprintf("\n%s teleports away.", p.Name)) } } } sess.WriteLine(fmt.Sprintf("\nYou activate %s...", mod.Name)) p.RoomID = mod.Destination if g.Hub != nil { g.Hub.LeaveRoom(sess) g.Hub.EnterRoom(sess, p.RoomID) } room, _ := g.World.LoadRoom(p.RoomID) destName := fmt.Sprintf("room %d", p.RoomID) if room != nil { destName = room.Name } sess.WriteLine(fmt.Sprintf("You materialize at %s.", destName)) g.AccountStore.SaveCharacter(p) g.doLook(sess) } func (g *Game) triggerProcessing(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { if combat.GetCombat(p.Name) != nil { sess.WriteLine("You can't do that during combat!") return } if targetArg == "" { sess.WriteLine(fmt.Sprintf("Usage: trigger %s ", strings.ReplaceAll(mod.ID, "_", " "))) return } slot, inv := g.findInventoryItem(p, targetArg) if slot < 0 { sess.WriteLine("You don't have that item.") return } itemDef, err := g.ItemStore.Load(inv.ItemID) if err != nil || itemDef.Value <= 0 { sess.WriteLine("That item has no value.") return } if p.Action != nil { g.CancelAction(p) } g.consumeJunkCost(p, mod) creditValue := itemDef.Value if mod.ID == "low_process" { creditValue = itemDef.Value / 2 if creditValue < 1 { creditValue = 1 } } if inv.Quantity > 1 { inv.Quantity-- } else { p.SetInvSlot(slot, nil) } p.Credits += creditValue sciXP := int(mod.BaseXP) var leveledUp []player.SkillName if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { leveledUp = append(leveledUp, player.Science) } g.AccountStore.SaveCharacter(p) for _, skill := range leveledUp { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", p.Level(skill), skill))) } sess.WriteLine(fmt.Sprintf("You process the %s. You receive %s credits.", itemDef.Name, g.colorize(sess, "credits_pickup", fmt.Sprint(creditValue)))) if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) } } func (g *Game) triggerUtility(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { switch mod.ID { case "bones_to_nutrients": g.triggerBonesToNutrients(sess, p, mod) case "em_grab": g.triggerEmGrab(sess, p, mod, targetArg) case "superheat": g.triggerSuperheat(sess, p, mod, targetArg) } } func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod *ModDef) { if combat.GetCombat(p.Name) != nil { sess.WriteLine("You can't do that during combat!") return } boneCount := p.CountItem("bones") if boneCount == 0 { sess.WriteLine("You don't have any bones.") return } if p.Action != nil { g.CancelAction(p) } g.consumeJunkCost(p, mod) for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == "bones" { slot.ItemID = "nutrient_bar" } } sciXP := int(mod.BaseXP) * boneCount if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d science! ***", p.Level(player.Science)))) } g.AccountStore.SaveCharacter(p) sess.WriteLine(fmt.Sprintf("You convert %d bones into nutrient bars.", boneCount)) if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) } } func (g *Game) triggerEmGrab(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { if targetArg == "" { sess.WriteLine("Grab what? Usage: trigger em grab ") return } if p.FirstFreeSlot() < 0 { sess.WriteLine("Your inventory is full.") return } groundItems := g.World.GroundItems(p.RoomID) var matchedID string for itemID := range groundItems { def, _ := g.ItemStore.Load(itemID) if def != nil && def.MatchesName(targetArg) { matchedID = itemID break } if strings.HasPrefix(itemID, strings.ToLower(targetArg)) { matchedID = itemID break } } if matchedID == "" { sess.WriteLine("You don't see that here.") return } if p.Action != nil { g.CancelAction(p) } g.consumeJunkCost(p, mod) qty := groundItems[matchedID] g.World.RemoveGroundItem(p.RoomID, matchedID, qty) def, _ := g.ItemStore.Load(matchedID) name := matchedID if def != nil { name = def.Name } freeSlot := p.FirstFreeSlot() p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: matchedID, Quantity: qty}) sciXP := int(mod.BaseXP) if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d science! ***", p.Level(player.Science)))) } g.AccountStore.SaveCharacter(p) sess.WriteLine(fmt.Sprintf("You magnetically pull the %s toward you.", name)) if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) } } func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { if combat.GetCombat(p.Name) != nil { sess.WriteLine("You can't do that during combat!") return } if targetArg == "" { sess.WriteLine("Superheat what? Usage: trigger superheat ") return } slot, inv := g.findInventoryItem(p, targetArg) if slot < 0 { sess.WriteLine("You don't have that item.") return } recipe := g.RecipeStore.FindByInput("smelt", inv.ItemID) if recipe == nil { sess.WriteLine("You can't superheat that.") return } if recipe.Level > 0 && p.Level(player.SkillName(recipe.Skill)) < recipe.Level { sess.WriteLine(fmt.Sprintf("You need level %d %s to smelt that.", recipe.Level, recipe.Skill)) return } for _, e := range recipe.Consume { for _, itemID := range e.Items { qty := e.Qty if qty <= 0 { qty = 1 } if p.CountItem(itemID) < qty { def, _ := g.ItemStore.Load(itemID) name := itemID if def != nil { name = def.Name } sess.WriteLine(fmt.Sprintf("You need %d %s.", qty, name)) return } } } if p.Action != nil { g.CancelAction(p) } g.consumeJunkCost(p, mod) for _, e := range recipe.Consume { for _, itemID := range e.Items { qty := e.Qty if qty <= 0 { qty = 1 } p.RemoveItem(itemID, qty) } } outputQty := recipe.OutputQty if outputQty <= 0 { outputQty = 1 } placed := false for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == recipe.Output { slot.Quantity += outputQty placed = true break } } if !placed { freeSlot := p.FirstFreeSlot() p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty}) } sciXP := int(mod.BaseXP) if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d science! ***", p.Level(player.Science)))) } if recipe.XP > 0 { if newLevel := p.AddSkillXP(player.SkillName(recipe.Skill), recipe.XP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", p.Level(player.SkillName(recipe.Skill)), recipe.Skill))) } } g.AccountStore.SaveCharacter(p) outputDef, _ := g.ItemStore.Load(recipe.Output) outputName := recipe.Output if outputDef != nil { outputName = outputDef.Name } inputDef, _ := g.ItemStore.Load(inv.ItemID) inputName := inv.ItemID if inputDef != nil { inputName = inputDef.Name } sess.WriteLine(fmt.Sprintf("You superheat the %s and produce a %s.", inputName, outputName)) if p.OptionBool("xp_drops") { parts := []string{fmt.Sprintf("+%dxp sci", sciXP)} if recipe.XP > 0 { parts = append(parts, fmt.Sprintf("+%dxp %s", recipe.XP, player.SkillAbbr[player.SkillName(recipe.Skill)])) } sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")")) } } func (g *Game) triggerEnchant(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { if chipInfo, ok := chipMap[mod.ID]; ok { g.triggerChipBolts(sess, p, mod, chipInfo) return } enchants, ok := enchantMap[mod.ID] if !ok { sess.WriteLine("That enchantment has no known recipes.") return } if targetArg == "" { sess.WriteLine(fmt.Sprintf("Enchant what? Use: trigger %s ", strings.ReplaceAll(mod.ID, "_", " "))) return } slot, inv := g.findInventoryItem(p, targetArg) if slot < 0 { sess.WriteLine("You don't have that item.") return } outputID, ok := enchants[inv.ItemID] if !ok { sess.WriteLine("You can't enchant that with this mod.") return } if p.Action != nil { g.CancelAction(p) } g.consumeJunkCost(p, mod) inv.ItemID = outputID sciXP := int(mod.BaseXP) if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d science! ***", p.Level(player.Science)))) } g.AccountStore.SaveCharacter(p) outputDef, _ := g.ItemStore.Load(outputID) outputName := outputID if outputDef != nil { outputName = outputDef.Name } inputDef, _ := g.ItemStore.Load(inv.ItemID) inputName := inv.ItemID if inputDef != nil { inputName = inputDef.Name } sess.WriteLine(fmt.Sprintf("You enchant the %s and it becomes a %s!", inputName, outputName)) if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) } } func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef, chip chipEntry) { count := p.CountItem(chip.Input) if count < chip.Qty { inputDef, _ := g.ItemStore.Load(chip.Input) name := chip.Input if inputDef != nil { name = inputDef.Name } sess.WriteLine(fmt.Sprintf("You need at least %d %s.", chip.Qty, name)) return } if p.Action != nil { g.CancelAction(p) } g.consumeJunkCost(p, mod) p.RemoveItem(chip.Input, chip.Qty) placed := false for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == chip.Output { slot.Quantity += chip.Qty placed = true break } } if !placed { freeSlot := p.FirstFreeSlot() p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: chip.Output, Quantity: chip.Qty}) } sciXP := int(mod.BaseXP) if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d science! ***", p.Level(player.Science)))) } g.AccountStore.SaveCharacter(p) inputDef, _ := g.ItemStore.Load(chip.Input) name := chip.Input if inputDef != nil { name = inputDef.Name } sess.WriteLine(fmt.Sprintf("You chip %d %s with arcane circuitry.", chip.Qty, name)) if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) } } func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.MobInstance, mod *ModDef) bool { if p.Level(player.Science) < mod.Level { 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"} return true } g.consumeJunkCost(p, mod) equipSciBonus := g.totalEquipScienceAttack(p) attRoll := (p.Level(player.Science) + 8) * (equipSciBonus + 64) mobSciDef := mob.ScienceDefense defRoll := (mob.Defense + 9) * (mobSciDef + 64) if mob.Weakness == mod.Element { attRoll = attRoll * 13 / 10 } if combat.HitCheck(attRoll, defRoll) { dmg := combat.RollDamage(mod.MaxHit) if dmg < 0 { dmg = 0 } mob.HP -= dmg if mob.HP < 0 { mob.HP = 0 } if mob.HP < mob.MaxHP && mob.HP > 0 { mob.StartRegen() } sciXP := int(mod.BaseXP) hpXP := int(mod.BaseXP * 0.33) var gains []xpGain var leveledUp []player.SkillName if newLevel := p.AddSkillXP(player.Science, sciXP); newLevel > 0 { leveledUp = append(leveledUp, player.Science) } gains = append(gains, xpGain{string(player.Science), sciXP}) if newLevel := p.AddSkillXP(player.Hitpoints, hpXP); newLevel > 0 { leveledUp = append(leveledUp, player.Hitpoints) } gains = append(gains, xpGain{string(player.Hitpoints), hpXP}) g.AccountStore.SaveCharacter(p) for _, skill := range leveledUp { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", p.Level(skill), skill))) } mobName := mobDisplayName(mob, true) prefix := fmt.Sprintf(" %s hits %s for %s damage.", g.colorize(sess, "science_mod", mod.Name), 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 := 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, ", ")+")") } sess.WriteLine(line) } else { sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" %s fails to connect.", mod.Name))) } return true } func (g *Game) findInventoryItem(p *player.Player, input string) (int, *player.InventorySlot) { lower := strings.ToLower(strings.TrimSpace(input)) for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot == nil { continue } def, err := g.ItemStore.Load(slot.ItemID) if err != nil { continue } if def.MatchesName(lower) { return i, slot } } return -1, nil }