diff --git a/pkg/data/mode/npc_mode.go b/pkg/data/mode/npc_mode.go new file mode 100644 index 0000000..537225c --- /dev/null +++ b/pkg/data/mode/npc_mode.go @@ -0,0 +1,22 @@ +package mode + +type NpcMode uint32 + +const ( + NpcDeath NpcMode = iota + NpcStandingStill + NpcWalking + NpcGettingHit + NpcAttacking1 + NpcAttacking2 + NpcBlocking + NpcCastingSpell + NpcUsingSkill1 + NpcUsingSkill2 + NpcUsingSkill3 + NpcUsingSkill4 + NpcDead + NpcKnockedBack + NpcActionSequence + NpcRunning +) diff --git a/pkg/data/npc.go b/pkg/data/npc.go index ff2d4c4..ee87100 100644 --- a/pkg/data/npc.go +++ b/pkg/data/npc.go @@ -1,6 +1,7 @@ package data import ( + "github.com/hectorgimenez/d2go/pkg/data/mode" "github.com/hectorgimenez/d2go/pkg/data/npc" "github.com/hectorgimenez/d2go/pkg/data/stat" "github.com/hectorgimenez/d2go/pkg/data/state" @@ -22,6 +23,7 @@ type Monster struct { Stats map[stat.ID]int Type MonsterType States state.States + Mode mode.NpcMode } type Monsters []Monster @@ -177,9 +179,26 @@ func (m Monster) IsMonsterRaiser() bool { // IsSkip monster can not be killed as a normal enemy, for example can not be targeted func (m Monster) IsSkip() bool { switch m.Name { - case npc.WaterWatcherLimb, npc.WaterWatcherHead, npc.BaalTaunt, npc.Act5Combatant, npc.Act5Combatant2, npc.BarricadeTower: + case npc.WaterWatcherLimb, npc.WaterWatcherHead, npc.BaalTaunt, npc.Act5Combatant, npc.Act5Combatant2, npc.BarricadeTower, npc.DarkWanderer, npc.POW: return true } return false } +func (m Monster) IsSealBoss() bool { + + return (m.Type == MonsterTypeSuperUnique || m.Type == MonsterTypeMinion) && (m.Name == npc.OblivionKnight || (m.Name == npc.DoomKnight) || // Lord De Seis + m.Name == npc.VenomLord || // Infector of Souls + m.Name == npc.StormCaster) // Grand Vizier of Chaos +} + +// IsEscapingType Monster cannot be attacked when airborne or hide in water (NpcMode 8) +func (m Monster) IsEscapingType() bool { + switch m.Name { + case npc.CarrionBird, npc.CarrionBird2, npc.WaterWatcherLimb, npc.RiverStalkerLimb, npc.StygianWatcherLimb, + npc.WaterWatcherHead, npc.RiverStalkerHead, npc.StygianWatcherHead, npc.CloudStalker, npc.Sucker, npc.UndeadScavenger, npc.FoulCrow: + + return true + } + return false +} diff --git a/pkg/data/object/portal_type.go b/pkg/data/object/portal_type.go new file mode 100644 index 0000000..79006b0 --- /dev/null +++ b/pkg/data/object/portal_type.go @@ -0,0 +1,8 @@ +package object + +import "github.com/hectorgimenez/d2go/pkg/data/area" + +type PortalData struct { + DestArea area.ID + //there is more to discover +} diff --git a/pkg/data/objects.go b/pkg/data/objects.go index ad740f7..14321b8 100644 --- a/pkg/data/objects.go +++ b/pkg/data/objects.go @@ -15,6 +15,7 @@ type Object struct { Position Position Owner string Mode mode.ObjectMode + PortalData object.PortalData } type Objects []Object @@ -25,7 +26,6 @@ func (o Objects) FindOne(name object.Name) (Object, bool) { return obj, true } } - return Object{}, false } @@ -35,7 +35,6 @@ func (o Objects) FindByID(id UnitID) (Object, bool) { return obj, true } } - return Object{}, false } @@ -67,7 +66,6 @@ func (o Object) IsShrine() bool { } return false } - func (o Object) IsWaypoint() bool { switch o.Name { case object.WaypointPortal, @@ -88,7 +86,6 @@ func (o Object) IsWaypoint() bool { object.ExpansionWaypoint: return true } - return false } @@ -110,7 +107,6 @@ func (o Object) IsChest() bool { 502, 504, 505, 518, 524, 525, 526, 529, 530, 531, 532, 533, 534, 535, 540, 541, 544, 545, 556, 580, 581: return true } - return false } func (o Object) IsDoor() bool { @@ -145,7 +141,6 @@ func (o Object) IsDoor() bool { object.ExpansionTownGate: return true } - return false } @@ -154,6 +149,5 @@ func (o Object) IsSuperChest() bool { case 104, 105, 106, 107, 181, 183, 580, 397, 387, 389, 390, 391, 455: return true } - return false } diff --git a/pkg/memory/monsters.go b/pkg/memory/monsters.go index 14e5fda..9a01eed 100644 --- a/pkg/memory/monsters.go +++ b/pkg/memory/monsters.go @@ -3,6 +3,8 @@ package memory import ( "sort" + "github.com/hectorgimenez/d2go/pkg/data/mode" + "github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data/npc" "github.com/hectorgimenez/d2go/pkg/data/stat" @@ -18,44 +20,40 @@ func (gd *GameReader) Monsters(playerPosition data.Position, hover data.HoverDat monsterOffset := 8 * i monsterUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(monsterOffset), Uint64)) for monsterUnitPtr > 0 { - monsterDataBuffer := gd.Process.ReadBytesFromMemory(monsterUnitPtr, 144) + // Quick corpse check first + isCorpse := gd.Process.ReadUInt(monsterUnitPtr+0x1A6, Uint8) + if isCorpse != 0 { + monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64)) + continue + } - //monsterType := ReadUIntFromBuffer(monsterDataBuffer, 0x00, Uint32) + monsterDataBuffer := gd.Process.ReadBytesFromMemory(monsterUnitPtr, 144) txtFileNo := ReadUIntFromBuffer(monsterDataBuffer, 0x04, Uint32) 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 - } - + // Get stats early for filtering 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) - states := gd.GetStates(statsListExPtr) - // This excludes good NPCs but includes Mercs if !gd.shouldBeIgnored(txtFileNo) || stats[stat.Experience] > 0 { - m := data.Monster{ + monsterMode := mode.NpcMode(gd.Process.ReadUInt(monsterUnitPtr+0x0c, Uint32)) + unitDataPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x10, Uint64)) + flag := gd.Process.ReadBytesFromMemory(unitDataPtr+0x1A, Uint8)[0] + //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) + + states := gd.GetStates(statsListExPtr) + + monsters = append(monsters, data.Monster{ UnitID: data.UnitID(unitID), Name: npc.ID(int(txtFileNo)), - IsHovered: hovered, + IsHovered: hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID), Position: data.Position{ X: int(posX), Y: int(posY), @@ -63,27 +61,89 @@ func (gd *GameReader) Monsters(playerPosition data.Position, hover data.HoverDat Stats: stats, Type: getMonsterType(flag), States: states, - } - - if isCorpse == 0 { - monsters = append(monsters, m) - } + Mode: monsterMode, + }) } 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 - }) + if len(monsters) > 0 { + 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 (gd *GameReader) Corpses(playerPosition data.Position, hover data.HoverData) data.Monsters { + baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024 + unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) + + corpses := data.Monsters{} + + for i := 0; i < 128; i++ { + monsterOffset := 8 * i + monsterUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(monsterOffset), Uint64)) + for monsterUnitPtr > 0 { + isCorpse := gd.Process.ReadUInt(monsterUnitPtr+0x1A6, Uint8) + if isCorpse == 0 { + monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64)) + continue + } + + monsterDataBuffer := gd.Process.ReadBytesFromMemory(monsterUnitPtr, 144) + txtFileNo := ReadUIntFromBuffer(monsterDataBuffer, 0x04, Uint32) + + 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) + + if !gd.shouldBeIgnored(txtFileNo) || stats[stat.Experience] > 0 { + unitID := ReadUIntFromBuffer(monsterDataBuffer, 0x08, Uint32) + unitDataPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x10, Uint64)) + flag := gd.Process.ReadBytesFromMemory(unitDataPtr+0x1A, Uint8)[0] + + pathPtr := uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x38, Uint64)) + posX := gd.Process.ReadUInt(pathPtr+0x02, Uint16) + posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16) + + states := gd.GetStates(statsListExPtr) + + corpses = append(corpses, data.Monster{ + UnitID: data.UnitID(unitID), + Name: npc.ID(int(txtFileNo)), + IsHovered: hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID), + Position: data.Position{ + X: int(posX), + Y: int(posY), + }, + Stats: stats, + Type: getMonsterType(flag), + States: states, + }) + } + + monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64)) + } + } + + if len(corpses) > 0 { + sort.SliceStable(corpses, func(i, j int) bool { + distanceI := utils.DistanceFromPoint(playerPosition, corpses[i].Position) + distanceJ := utils.DistanceFromPoint(playerPosition, corpses[j].Position) + return distanceI < distanceJ + }) + } + + return corpses +} + func getMonsterType(typeFlag byte) data.MonsterType { switch typeFlag { case 10: @@ -95,7 +155,6 @@ func getMonsterType(typeFlag byte) data.MonsterType { case 1 << 4: return data.MonsterTypeMinion } - return data.MonsterTypeNone } @@ -115,73 +174,6 @@ func (gd *GameReader) getMonsterStats(statCount uint, statPtr uintptr) map[stat. return stats } -func (gd *GameReader) Corpses(playerPosition data.Position, hover data.HoverData) data.Monsters { - baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024 - unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) - - corpses := 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) - - txtFileNo := ReadUIntFromBuffer(monsterDataBuffer, 0x04, Uint32) - unitID := ReadUIntFromBuffer(monsterDataBuffer, 0x08, Uint32) - - unitDataPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x10, Uint64)) - flag := gd.Process.ReadBytesFromMemory(unitDataPtr+0x1A, Uint8)[0] - isCorpse := gd.Process.ReadUInt(monsterUnitPtr+0x1A6, Uint8) - - if isCorpse == 0 { - monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64)) - continue - } - - pathPtr := uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x38, Uint64)) - posX := gd.Process.ReadUInt(pathPtr+0x02, Uint16) - posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16) - - 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) - states := gd.GetStates(statsListExPtr) - - hovered := hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID) - - if (!gd.shouldBeIgnored(txtFileNo) || stats[stat.Experience] > 0) && isCorpse != 0 { - 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), - States: states, - } - - corpses = append(corpses, m) - } - - monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64)) - } - } - - sort.SliceStable(corpses, func(i, j int) bool { - distanceI := utils.DistanceFromPoint(playerPosition, corpses[i].Position) - distanceJ := utils.DistanceFromPoint(playerPosition, corpses[j].Position) - return distanceI < distanceJ - }) - - return corpses -} - func (gd *GameReader) shouldBeIgnored(txtNo uint) bool { switch npc.ID(txtNo) { case npc.Chicken, @@ -247,9 +239,23 @@ func (gd *GameReader) shouldBeIgnored(txtNo uint) bool { npc.InjuredBarbarian, npc.InjuredBarbarian2, npc.InjuredBarbarian3, + npc.FrenziedHellSpawn, + npc.FrenziedIceSpawn, + npc.CatapultSpotterE, + npc.CatapultSpotterS, + npc.CatapultSpotterW, + npc.CatapultW, + npc.CatapultE, + npc.CatapultS, + npc.CatapultSiege, + npc.BarricadeWallRight, + npc.BarricadeWallLeft, + npc.BarricadeDoor, + npc.BarricadeDoor2, + npc.BarricadeTower, + npc.EvilHut, npc.DemonHole: return true } - return false } diff --git a/pkg/memory/object.go b/pkg/memory/object.go index eb29b4f..718bc56 100644 --- a/pkg/memory/object.go +++ b/pkg/memory/object.go @@ -1,53 +1,73 @@ package memory import ( - "github.com/hectorgimenez/d2go/pkg/data/mode" "sort" + "github.com/hectorgimenez/d2go/pkg/data/mode" + "github.com/hectorgimenez/d2go/pkg/data" + "github.com/hectorgimenez/d2go/pkg/data/area" "github.com/hectorgimenez/d2go/pkg/data/object" "github.com/hectorgimenez/d2go/pkg/utils" ) +func isPortal(txtFileNo int) bool { + desc, ok := object.Desc[txtFileNo] + return ok && desc.Name == "Portal" +} + func (gd *GameReader) Objects(playerPosition data.Position, hover data.HoverData) []data.Object { baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (2 * 1024) unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) var objects []data.Object + for i := 0; i < 128; i++ { objectOffset := 8 * i objectUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(objectOffset), Uint64)) - for objectUnitPtr > 0 { - objectType := gd.Process.ReadUInt(objectUnitPtr+0x00, Uint32) - if objectType == 2 { - txtFileNo := gd.Process.ReadUInt(objectUnitPtr+0x04, Uint32) - unitID := gd.Process.ReadUInt(objectUnitPtr+0x08, Uint32) - // Read the object mode + for objectUnitPtr > 0 { + // Read minimal data first to check object type + objectType := gd.Process.ReadUInt(objectUnitPtr+0x00, Uint32) + + if objectType == 2 { + rawTxtFileNo := gd.Process.ReadUInt(objectUnitPtr+0x04, Uint32) // Extract actual txtFileNo + txtFileNo := rawTxtFileNo & 0xFFFF + unitID := gd.Process.ReadUInt(objectUnitPtr+0x08, Uint32) objectMode := mode.ObjectMode(gd.Process.ReadUInt(objectUnitPtr+0x0c, Uint32)) - // Coordinates (X, Y) + unitDataPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x10, Uint64)) + + //This offset gives timer for each mode to keep progress in real time. exemple: Mode.Operating fresh timer then Mode.Opened new timer (for objects) + // timerValue := uint32(gd.Process.ReadUInt(objectUnitPtr+0x5C, Uint32)) + + // Path and position data pathPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x38, Uint64)) + // Coordinates (X, Y) posX := gd.Process.ReadUInt(pathPtr+0x10, Uint16) posY := gd.Process.ReadUInt(pathPtr+0x14, Uint16) - unitDataPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x10, Uint64)) - // checking if this is a shrine - shrineTextPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x0A, Uint64)) - shrineType := uint(0) - interactType := uint(0) - shrineData := object.ShrineData{} - if shrineTextPtr > 0 { - shrineType = gd.Process.ReadUInt(unitDataPtr+0x08, Uint8) - shrineData = object.ShrineData{ - ShrineName: object.ShrineTypeNames[object.ShrineType(shrineType)], - ShrineType: object.ShrineType(shrineType), - } - } else { - interactType = gd.Process.ReadUInt(unitDataPtr+0x08, Uint8) - } + var shrineData object.ShrineData + var portalData object.PortalData + interactType := gd.Process.ReadUInt(unitDataPtr+0x08, Uint8) owner := gd.Process.ReadStringFromMemory(unitDataPtr+0x34, 32) - obj := data.Object{ + // Handle portals + if isPortal(int(txtFileNo)) { + destArea := area.ID(gd.Process.ReadUInt(unitDataPtr+0x08, Uint8)) + portalData.DestArea = destArea + // Handle Shrines + } else { + shrineTextPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x0A, Uint64)) + if shrineTextPtr > 0 { + shrineType := gd.Process.ReadUInt(unitDataPtr+0x08, Uint8) + shrineData = object.ShrineData{ + ShrineName: object.ShrineTypeNames[object.ShrineType(shrineType)], + ShrineType: object.ShrineType(shrineType), + } + } + } + // Handle objects + objects = append(objects, data.Object{ ID: data.UnitID(unitID), Name: object.Name(int(txtFileNo)), IsHovered: data.UnitID(unitID) == hover.UnitID && hover.UnitType == 2 && hover.IsHovered, @@ -58,21 +78,22 @@ func (gd *GameReader) Objects(playerPosition data.Position, hover data.HoverData X: int(posX), Y: int(posY), }, - Owner: owner, - Mode: objectMode, - } - objects = append(objects, obj) + Owner: owner, + Mode: objectMode, + PortalData: portalData, + }) } objectUnitPtr = uintptr(gd.Process.ReadUInt(objectUnitPtr+0x150, Uint64)) } } - sort.SliceStable(objects, func(i, j int) bool { - distanceI := utils.DistanceFromPoint(playerPosition, objects[i].Position) - distanceJ := utils.DistanceFromPoint(playerPosition, objects[j].Position) - - return distanceI < distanceJ - }) + if len(objects) > 0 { + sort.SliceStable(objects, func(i, j int) bool { + distanceI := utils.DistanceFromPoint(playerPosition, objects[i].Position) + distanceJ := utils.DistanceFromPoint(playerPosition, objects[j].Position) + return distanceI < distanceJ + }) + } return objects }