package game import ( "fmt" "math/rand" "sort" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) 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, totalValue: 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, totalValue: val, isEquip: true, equipSlot: eqSlot, }) } if len(items) <= 3 { return } sort.Slice(items, func(i, j int) bool { return items[i].totalValue > items[j].totalValue }) 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) } } // aggroChancePerTick is the probability per engine tick that an aggressive mob // in the player's room initiates combat against them. The first roll happens // inline on arrival (so a player who enters and immediately moves again — the // 1-tick single-move case — still risks being caught ~33% of the time); if the // initial roll fails, a per-tick retry subscriber keeps rolling for any // subsequent tick the player lingers in the room. const aggroChancePerTick = 0.33 func (g *Game) checkAggro(sess *net.Session) { p := sess.Player if p.GodMode { return } if g.Combat.Get(p.Name) != nil { return } playerLevel := p.CombatLevel() mobs := g.MobStore.MobsInRoom(p.RoomID) for _, mob := range mobs { if !mob.Aggressive || mob.HP <= 0 || mob.Protected { continue } if g.Combat.IsMobInCombat(mob.InstanceID) { continue } mobLevel := mobCombatLevel(mob) if playerLevel > mobLevel*2 { continue } if g.isSafespotted(p.Name) && g.safespotBlocksMob(p, mob) { continue } aggMob := mob // rollAggro attempts to start combat this instant. It returns true // when aggro should stop retrying that mob for this player — either // because it succeeded, because the mob/player is no longer eligible, // or because the roll failed and the caller subscribed a per-tick // retry. The retry subscriber returns the negation of the inline // call's "stop" so it stays subscribed only while the player is // still a valid target the roll hasn't yet hit. rollAggro := func() (stop bool) { if g.Combat.Get(p.Name) != nil { return true } if aggMob.HP <= 0 || aggMob.RoomID != p.RoomID { return true } if g.Combat.IsMobInCombat(aggMob.InstanceID) { return true } if rand.Float64() >= aggroChancePerTick { return false } attacker := aggMob.Name if !aggMob.Unique { attacker = "The " + aggMob.Name } sess.WriteLine(fmt.Sprintf("%s attacks you!", g.colorize(sess, "mob", attacker))) g.startCombat(sess, p, aggMob) return true } if rollAggro() { break } g.Ticks.Subscribe(1, func() bool { return !rollAggro() }) break } }