Files
d2go/pkg/memory/monsters.go
elb 2eeee0cf1f Npc/Monsters Mode and Portals destination + optimization (#49)
* Added States for monsters and corpses

Now can properly identify if a corpse is interactable with a skill ( barbarian, necromancer)
Also getting states about monsters  : Exemple  Soul Killer with Conviction aura:   State  28 // conviction

Also can tell if monster has a state after you used a skill on it  Exemple:   State 56  Terror ( After using Barbarian skill Howl to fear )

Another Exemple  using Grief on Mephistos he now have  states :    2 poison
    52  Preventheal

will be very useful for further development.

* Added modes to player

Now accessible in koolo using : s.data.PlayerUnit.Mode

Those are Modes for player:
const Death: 0;
        const StandingOutsideTown: 1;
        const Walking: 2;
        const Running: 3;
        const GettingHit: 4;
        const StandingInTown: 5;
        const WalkingInTown: 6;
        const Attacking1: 7;
        const Attacking2: 8;
        const Blocking: 9;
        const CastingSkill: 10;
        const ThrowingItem: 11;
        const Kicking: 12;
        const UsingSkill1: 13;
        const UsingSkill2: 14;
        const UsingSkill3: 15;
        const UsingSkill4: 16;
        const Dead: 17;
        const SkillActionSequence: 18;
        const KnockedBack: 19;

* update Added alias(const) for player modes

can be used like this s.data.PlayerUnit.Mode == mode.StandingInTown

* added modes to object

0 = Object idle
1 = Object operating
2 = Object opened
3 = Object special 1    ?? to be determined what it does exactly
4 = Object special 2    ?? to be determined what it does exactly
5 = Object special 3    ?? to be determined what it does exactly
6 = Object special 4    ?? to be determined what it does exactly
7 = Object special 5    ?? to be determined what it does exactly

* update object mode strings

* Update object_mode.go

* Update object_mode.go

* update object modes again. accurate now

* Added IsSealBoss()  for Chaos Run

* Npc/Monster Modes and Portals destination
also IsEscapingType()  Monster cannot be attacked when airborne or hide in water (NpcMode 8)

* Npc/Monster Modes and Portals destination
also IsEscapingType()  Monster cannot be attacked when airborne or hide in water (NpcMode 8)

* Optimized monsters.go and object.go
also catapults have been added to ignore list

* Tz Frigid Higland ingores added
2024-12-01 22:54:57 +09:00

262 lines
7.3 KiB
Go

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"
"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 {
// Quick corpse check first
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)
unitID := ReadUIntFromBuffer(monsterDataBuffer, 0x08, Uint32)
// 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)
if !gd.shouldBeIgnored(txtFileNo) || stats[stat.Experience] > 0 {
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: 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,
Mode: monsterMode,
})
}
monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64))
}
}
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:
return data.MonsterTypeSuperUnique
case 1 << 2, 12:
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.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,
npc.Guard,
npc.MiniSper,
npc.BoneWall,
npc.Hydra,
npc.Hydra2,
npc.Hydra3,
npc.SevenTombs,
npc.IronWolf,
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.BaalThrone,
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
}