package memory import ( "sort" "github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data/npc" "github.com/hectorgimenez/d2go/pkg/data/stat" "github.com/hectorgimenez/d2go/pkg/utils" ) func (gd *GameReader) Monsters(playerPosition data.Position, hover data.HoverData) data.Monsters { baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024 unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) monsters := data.Monsters{} for i := 0; i < 128; i++ { monsterOffset := 8 * i monsterUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(monsterOffset), Uint64)) for monsterUnitPtr > 0 { monsterDataBuffer := gd.Process.ReadBytesFromMemory(monsterUnitPtr, 144) //monsterType := ReadUIntFromBuffer(monsterDataBuffer, 0x00, Uint32) txtFileNo := ReadUIntFromBuffer(monsterDataBuffer, 0x04, Uint32) if !gd.shouldBeIgnored(txtFileNo) { unitID := ReadUIntFromBuffer(monsterDataBuffer, 0x08, Uint32) //mode := ReadUIntFromBuffer(monsterDataBuffer, 0x0C, Uint32) unitDataPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x10, Uint64)) //isUnique := gd.Process.ReadUInt(unitDataPtr+0x18, Uint16) flag := gd.Process.ReadBytesFromMemory(unitDataPtr+0x1A, Uint8)[0] isCorpse := gd.Process.ReadUInt(monsterUnitPtr+0x1A6, Uint8) //unitDataBuffer := gd.Process.ReadBytesFromMemory(unitDataPtr, 144) // Coordinates (X, Y) pathPtr := uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x38, Uint64)) posX := gd.Process.ReadUInt(pathPtr+0x02, Uint16) posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16) hovered := false if hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID) { hovered = true } statsListExPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x88, Uint64)) statPtr := uintptr(gd.Process.ReadUInt(statsListExPtr+0x30, Uint64)) statCount := gd.Process.ReadUInt(statsListExPtr+0x38, Uint64) stats := gd.getMonsterStats(statCount, statPtr) m := data.Monster{ UnitID: data.UnitID(unitID), Name: npc.ID(int(txtFileNo)), IsHovered: hovered, Position: data.Position{ X: int(posX), Y: int(posY), }, Stats: stats, Type: getMonsterType(flag), } if isCorpse == 0 { monsters = append(monsters, m) } } monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64)) } } sort.SliceStable(monsters, func(i, j int) bool { distanceI := utils.DistanceFromPoint(playerPosition, monsters[i].Position) distanceJ := utils.DistanceFromPoint(playerPosition, monsters[j].Position) return distanceI < distanceJ }) return monsters } func getMonsterType(typeFlag byte) data.MonsterType { switch typeFlag { case 10: return data.MonsterTypeSuperUnique case 1 << 2: return data.MonsterTypeChampion case 1 << 3: return data.MonsterTypeUnique case 1 << 4: return data.MonsterTypeMinion } return data.MonsterTypeNone } func (gd *GameReader) getMonsterStats(statCount uint, statPtr uintptr) map[stat.ID]int { stats := map[stat.ID]int{} if statCount > 0 { statBuffer := gd.Process.ReadBytesFromMemory(statPtr+0x2, statCount*8) for i := 0; i < int(statCount); i++ { offset := uint(i * 8) statEnum := ReadUIntFromBuffer(statBuffer, offset, Uint16) statValue := ReadUIntFromBuffer(statBuffer, offset+0x2, Uint32) stats[stat.ID(statEnum)] = int(statValue) } } return stats } func (gd *GameReader) shouldBeIgnored(txtNo uint) bool { switch npc.ID(txtNo) { case npc.Chicken, npc.Rat, npc.Rogue, npc.HellMeteor, npc.Bird, npc.Bird2, npc.Bat, npc.Act2Male, npc.Act2Female, npc.Act2Child, npc.Cow, npc.Camel, npc.Act2Guard, npc.Act2Vendor, npc.Act2Vendor2, npc.Maggot, npc.Bug, npc.Scorpion, npc.Rogue2, npc.Rogue3, npc.Larva, npc.Familiar, npc.Act3Male, npc.ClayGolem, npc.BloodGolem, npc.IronGolem, npc.FireGolem, npc.Act3Female, npc.Snake, npc.Parrot, npc.Fish, npc.EvilHole, npc.EvilHole2, npc.EvilHole3, npc.EvilHole4, npc.EvilHole5, npc.FireboltTrap, npc.HorzMissileTrap, npc.VertMissileTrap, npc.PoisonCloudTrap, npc.LightningTrap, npc.InvisoSpawner, //338, //Guard npc.MiniSper, npc.BoneWall, npc.Hydra, npc.Hydra2, npc.Hydra3, npc.SevenTombs, npc.Valkyrie, npc.IronWolf, npc.NecroSkeleton, npc.NecroMage, npc.CompellingOrbNpc, npc.SpiritMummy, npc.Act2Guard4, npc.Act2Guard5, npc.Window, npc.Window2, npc.MephistoSpirit, npc.WakeOfDestruction, npc.ChargedBoltSentry, npc.LightningSentry, npc.InvisiblePet, npc.InfernoSentry, npc.DeathSentry, npc.ShadowWarrior, npc.ShadowMaster, npc.DruHawk, npc.DruSpiritWolf, npc.DruFenris, npc.HeartOfWolverine, npc.OakSage, npc.DruBear, npc.BaalThrone, npc.InjuredBarbarian, npc.InjuredBarbarian2, npc.InjuredBarbarian3, npc.DemonHole: return true } return false }