bugfixing, performance improvements and added casting frames formula
This commit is contained in:
@@ -10,11 +10,11 @@ imported, along with data structures and some other tools.
|
|||||||
- [data](https://github.com/hectorgimenez/d2go/tree/main/pkg/data) - D2R Game data structures
|
- [data](https://github.com/hectorgimenez/d2go/tree/main/pkg/data) - D2R Game data structures
|
||||||
- [memory](https://github.com/hectorgimenez/d2go/tree/main/pkg/memory) - D2R memory reader (it provides the data
|
- [memory](https://github.com/hectorgimenez/d2go/tree/main/pkg/memory) - D2R memory reader (it provides the data
|
||||||
structures)
|
structures)
|
||||||
- [nip](https://github.com/hectorgimenez/d2go/tree/main/pkg/nip) - A very basic NIP file parser
|
- [nip](https://github.com/hectorgimenez/d2go/tree/main/pkg/nip) - [NIP](https://github.com/blizzhackers/pickits/blob/master/NipGuide.md) file parser and rule evaluator, used by the itemwatcher item filter.
|
||||||
- [itemfilter](https://github.com/hectorgimenez/d2go/tree/main/pkg/itemfilter) - Based on game data, it provides an item
|
|
||||||
pickup filtering
|
|
||||||
|
|
||||||
### Tools
|
### Tools
|
||||||
|
|
||||||
- [cmd/itemwatcher](https://github.com/hectorgimenez/d2go/tree/main/cmd/itemwatcher) - Small tool that plays a sound
|
- [cmd/itemwatcher](https://github.com/hectorgimenez/d2go/tree/main/cmd/itemwatcher) - Small tool that plays a sound
|
||||||
when an item passing the filtering process is dropped
|
when an item passing the filtering process is dropped
|
||||||
|
- [cmd/txttocode](https://github.com/hectorgimenez/d2go/tree/main/cmd/txttocode) - Static code generator, takes game .txt files and generates Go code to be used by the data package, it provides
|
||||||
|
static data like item names, item types, skill details, etc.
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/hectorgimenez/d2go
|
|||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/expr-lang/expr v1.16.6
|
github.com/expr-lang/expr v1.16.7
|
||||||
github.com/faiface/beep v1.1.0
|
github.com/faiface/beep v1.1.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/sys v0.20.0
|
golang.org/x/sys v0.20.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -2,8 +2,8 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
|
|||||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/expr-lang/expr v1.16.6 h1:u1mrPXbwHtWAih5ZP24ZDG+ht8CB5xB0aBvagkAWPY0=
|
github.com/expr-lang/expr v1.16.7 h1:gCIiHt5ODA0xIaDbD0DPKyZpM9Drph3b3lolYAYq2Kw=
|
||||||
github.com/expr-lang/expr v1.16.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
github.com/expr-lang/expr v1.16.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||||
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||||
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/quest"
|
"github.com/hectorgimenez/d2go/pkg/data/quest"
|
||||||
@@ -137,8 +138,8 @@ type PlayerUnit struct {
|
|||||||
ID UnitID
|
ID UnitID
|
||||||
Area area.ID
|
Area area.ID
|
||||||
Position Position
|
Position Position
|
||||||
Stats map[stat.ID]int
|
Stats stat.Stats
|
||||||
BaseStats map[stat.ID]int
|
BaseStats stat.Stats
|
||||||
Skills map[skill.ID]skill.Points
|
Skills map[skill.ID]skill.Points
|
||||||
States state.States
|
States state.States
|
||||||
Class Class
|
Class Class
|
||||||
@@ -147,21 +148,76 @@ type PlayerUnit struct {
|
|||||||
AvailableWaypoints []area.ID // Is only filled when WP menu is open and only for the specific selected tab
|
AvailableWaypoints []area.ID // Is only filled when WP menu is open and only for the specific selected tab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pu PlayerUnit) FindStat(id stat.ID, layer int) (stat.Data, bool) {
|
||||||
|
st, found := pu.Stats.FindStat(id, layer)
|
||||||
|
if found {
|
||||||
|
return st, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return pu.BaseStats.FindStat(id, layer)
|
||||||
|
}
|
||||||
|
|
||||||
func (pu PlayerUnit) MaxGold() int {
|
func (pu PlayerUnit) MaxGold() int {
|
||||||
return goldPerLevel * pu.Stats[stat.Level]
|
lvl, _ := pu.FindStat(stat.Level, 0)
|
||||||
|
return goldPerLevel * lvl.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalGold returns the amount of gold, including inventory and stash
|
// TotalGold returns the amount of gold, including inventory and stash
|
||||||
func (pu PlayerUnit) TotalGold() int {
|
func (pu PlayerUnit) TotalGold() int {
|
||||||
return pu.Stats[stat.Gold] + pu.Stats[stat.StashGold]
|
gold, _ := pu.FindStat(stat.Gold, 0)
|
||||||
|
stashGold, _ := pu.FindStat(stat.StashGold, 0)
|
||||||
|
|
||||||
|
return gold.Value + stashGold.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pu PlayerUnit) HPPercent() int {
|
func (pu PlayerUnit) HPPercent() int {
|
||||||
return int((float64(pu.Stats[stat.Life]) / float64(pu.Stats[stat.MaxLife])) * 100)
|
life, _ := pu.FindStat(stat.Life, 0)
|
||||||
|
maxLife, _ := pu.FindStat(stat.MaxLife, 0)
|
||||||
|
|
||||||
|
return int((float64(life.Value) / float64(maxLife.Value)) * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pu PlayerUnit) MPPercent() int {
|
func (pu PlayerUnit) MPPercent() int {
|
||||||
return int((float64(pu.Stats[stat.Mana]) / float64(pu.Stats[stat.MaxMana])) * 100)
|
mana, _ := pu.FindStat(stat.Mana, 0)
|
||||||
|
maxMana, _ := pu.FindStat(stat.MaxMana, 0)
|
||||||
|
|
||||||
|
return int((float64(mana.Value) / float64(maxMana.Value)) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pu PlayerUnit) CastingFrames() int {
|
||||||
|
// Formula detailed here: https://diablo.fandom.com/wiki/Faster_Cast_Rate
|
||||||
|
fcr, _ := pu.FindStat(stat.FasterCastRate, 0)
|
||||||
|
baseAnimation := pu.getCastingBaseSpeed()
|
||||||
|
|
||||||
|
ecfr := math.Floor(float64(fcr.Value*120) / float64(fcr.Value+120))
|
||||||
|
if ecfr > 75 {
|
||||||
|
ecfr = 75
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation speed for druids is different
|
||||||
|
cf := math.Ceil(256*baseAnimation/math.Floor(float64(256*(100+ecfr))/100.0)) - 1
|
||||||
|
|
||||||
|
return int(cf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pu PlayerUnit) getCastingBaseSpeed() float64 {
|
||||||
|
// TODO: Implement logic for Lightning?
|
||||||
|
switch pu.Class {
|
||||||
|
case Amazon:
|
||||||
|
return 20
|
||||||
|
case Assassin:
|
||||||
|
return 17
|
||||||
|
case Barbarian:
|
||||||
|
return 14
|
||||||
|
case Necromancer, Paladin:
|
||||||
|
return 16
|
||||||
|
case Druid:
|
||||||
|
return 15
|
||||||
|
case Sorceress:
|
||||||
|
return 14
|
||||||
|
}
|
||||||
|
|
||||||
|
return 16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pu PlayerUnit) HasDebuff() bool {
|
func (pu PlayerUnit) HasDebuff() bool {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ type Item struct {
|
|||||||
Quality item.Quality
|
Quality item.Quality
|
||||||
Position Position
|
Position Position
|
||||||
Location item.Location
|
Location item.Location
|
||||||
|
Page int // Used for shared stash
|
||||||
Ethereal bool
|
Ethereal bool
|
||||||
IsHovered bool
|
IsHovered bool
|
||||||
BaseStats stat.Stats
|
BaseStats stat.Stats
|
||||||
|
|||||||
@@ -23,10 +23,14 @@ func (gd *GameReader) GetData() data.Data {
|
|||||||
gd.offset = calculateOffsets(gd.Process)
|
gd.offset = calculateOffsets(gd.Process)
|
||||||
}
|
}
|
||||||
|
|
||||||
roster := gd.getRoster()
|
rawPlayerUnits := gd.GetRawPlayerUnits()
|
||||||
playerUnitPtr, corpse := gd.GetPlayerUnitPtr(roster)
|
roster := gd.getRoster(rawPlayerUnits)
|
||||||
|
mainPlayerUnit := rawPlayerUnits.GetMainPlayer()
|
||||||
|
if mainPlayerUnit.Address == 0 {
|
||||||
|
return gd.previousRead
|
||||||
|
}
|
||||||
|
|
||||||
pu := gd.GetPlayerUnit(playerUnitPtr)
|
pu := gd.GetPlayerUnit(mainPlayerUnit)
|
||||||
hover := gd.hoveredData()
|
hover := gd.hoveredData()
|
||||||
|
|
||||||
// Quests
|
// Quests
|
||||||
@@ -36,11 +40,16 @@ func (gd *GameReader) GetData() data.Data {
|
|||||||
|
|
||||||
gameQuestsBytes = gameQuestsBytes[3:]
|
gameQuestsBytes = gameQuestsBytes[3:]
|
||||||
|
|
||||||
|
corpseUnit := rawPlayerUnits.GetCorpse()
|
||||||
d := data.Data{
|
d := data.Data{
|
||||||
Corpse: corpse,
|
Corpse: data.Corpse{
|
||||||
|
Found: corpseUnit.Address != 0,
|
||||||
|
IsHovered: corpseUnit.IsHovered,
|
||||||
|
Position: corpseUnit.Position,
|
||||||
|
},
|
||||||
Monsters: gd.Monsters(pu.Position, hover),
|
Monsters: gd.Monsters(pu.Position, hover),
|
||||||
PlayerUnit: pu,
|
PlayerUnit: pu,
|
||||||
Items: gd.Items(pu, hover),
|
Items: gd.Items(rawPlayerUnits, hover),
|
||||||
Objects: gd.Objects(pu.Position, hover),
|
Objects: gd.Objects(pu.Position, hover),
|
||||||
OpenMenus: gd.openMenus(),
|
OpenMenus: gd.openMenus(),
|
||||||
Roster: roster,
|
Roster: roster,
|
||||||
@@ -50,19 +59,15 @@ func (gd *GameReader) GetData() data.Data {
|
|||||||
KeyBindings: gd.GetKeyBindings(),
|
KeyBindings: gd.GetKeyBindings(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if playerUnitPtr == 0 {
|
|
||||||
return gd.previousRead
|
|
||||||
}
|
|
||||||
|
|
||||||
gd.previousRead = d
|
gd.previousRead = d
|
||||||
|
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *GameReader) InGame() bool {
|
func (gd *GameReader) InGame() bool {
|
||||||
pu, _ := gd.GetPlayerUnitPtr([]data.RosterMember{})
|
playerUnits := gd.GetRawPlayerUnits()
|
||||||
|
|
||||||
return pu > 0
|
return playerUnits.GetMainPlayer().UnitID > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *GameReader) openMenus() data.OpenMenus {
|
func (gd *GameReader) openMenus() data.OpenMenus {
|
||||||
@@ -108,8 +113,9 @@ func (gd *GameReader) hoveredData() data.HoverData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gd *GameReader) getStatsList(statListPtr uintptr) stat.Stats {
|
func (gd *GameReader) getStatsList(statListPtr uintptr) stat.Stats {
|
||||||
statList := gd.Process.ReadUInt(statListPtr, Uint64)
|
statsListBuffer := gd.ReadBytesFromMemory(statListPtr, 0x10)
|
||||||
statCount := gd.Process.ReadUInt(statListPtr+0x8, Uint64)
|
statList := ReadUIntFromBuffer(statsListBuffer, 0, Uint64)
|
||||||
|
statCount := ReadUIntFromBuffer(statsListBuffer, 0x08, Uint64)
|
||||||
if statCount == 0 {
|
if statCount == 0 {
|
||||||
return []stat.Data{}
|
return []stat.Data{}
|
||||||
}
|
}
|
||||||
@@ -129,12 +135,38 @@ func (gd *GameReader) getStatsList(statListPtr uintptr) stat.Stats {
|
|||||||
stat.Mana,
|
stat.Mana,
|
||||||
stat.MaxMana,
|
stat.MaxMana,
|
||||||
stat.Stamina,
|
stat.Stamina,
|
||||||
stat.LifePerLevel,
|
stat.MaxStamina:
|
||||||
stat.ManaPerLevel:
|
|
||||||
value = int(statValue >> 8)
|
value = int(statValue >> 8)
|
||||||
case stat.ColdLength,
|
case stat.ColdLength,
|
||||||
stat.PoisonLength:
|
stat.PoisonLength:
|
||||||
value = int(statValue / 25)
|
value = int(statValue / 25)
|
||||||
|
case stat.DeadlyStrikePerLevel:
|
||||||
|
value = int(float64(statValue) / .8)
|
||||||
|
case stat.HitCausesMonsterToFlee:
|
||||||
|
value = int(float64(statValue) / 1.28)
|
||||||
|
case stat.AttackRatingUndeadPerLevel:
|
||||||
|
value = int(statValue / 2)
|
||||||
|
case stat.MagicFindPerLevel,
|
||||||
|
stat.ExtraGoldPerLevel,
|
||||||
|
stat.DamageDemonPerLevel,
|
||||||
|
stat.DamageUndeadPerLevel,
|
||||||
|
stat.DefensePerLevel,
|
||||||
|
stat.MaxDamagePerLevel,
|
||||||
|
stat.MaxDamagePercentPerLevel,
|
||||||
|
stat.StrengthPerLevel,
|
||||||
|
stat.DexterityPerLevel,
|
||||||
|
stat.VitalityPerLevel,
|
||||||
|
stat.ThornsPerLevel:
|
||||||
|
value = int(statValue / 8)
|
||||||
|
case stat.LifePerLevel,
|
||||||
|
stat.ManaPerLevel:
|
||||||
|
value = int(statValue / 2048)
|
||||||
|
case stat.ReplenishDurability, stat.ReplenishQuantity:
|
||||||
|
value = 2 / int(statValue)
|
||||||
|
case stat.RegenStaminaPerLevel:
|
||||||
|
value = int(statValue) * 10
|
||||||
|
case stat.LevelRequirePercent:
|
||||||
|
value = int(statValue) * -1
|
||||||
}
|
}
|
||||||
|
|
||||||
stats = append(stats, stat.Data{
|
stats = append(stats, stat.Data{
|
||||||
|
|||||||
20
pkg/memory/game_reader_test.go
Normal file
20
pkg/memory/game_reader_test.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkDataReader(b *testing.B) {
|
||||||
|
process, err := NewProcess()
|
||||||
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
gr := NewGameReader(process)
|
||||||
|
gr.GetData()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
gr.GetData()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,32 @@
|
|||||||
package memory
|
package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/hectorgimenez/d2go/pkg/data"
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/item"
|
"github.com/hectorgimenez/d2go/pkg/data/item"
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/state"
|
||||||
"github.com/hectorgimenez/d2go/pkg/utils"
|
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gd *GameReader) Items(pu data.PlayerUnit, hover data.HoverData) data.Items {
|
func (gd *GameReader) Items(rawPlayerUnits RawPlayerUnits, hover data.HoverData) data.Items {
|
||||||
|
mainPlayer := rawPlayerUnits.GetMainPlayer()
|
||||||
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
|
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
|
||||||
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
||||||
|
|
||||||
|
stashPlayerUnits := make(map[uint]RawPlayerUnit)
|
||||||
|
stashPlayerUnitOrder := make([]uint, 0)
|
||||||
|
for _, pu := range rawPlayerUnits {
|
||||||
|
if pu.States.HasState(state.Sharedstash) {
|
||||||
|
order := gd.ReadUInt(pu.Address+0xD8, Uint64)
|
||||||
|
stashPlayerUnitOrder = append(stashPlayerUnitOrder, order)
|
||||||
|
stashPlayerUnits[order] = pu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(stashPlayerUnitOrder)
|
||||||
|
|
||||||
items := data.Items{}
|
items := data.Items{}
|
||||||
belt := data.Belt{}
|
belt := data.Belt{}
|
||||||
for i := 0; i < 128; i++ {
|
for i := 0; i < 128; i++ {
|
||||||
@@ -73,31 +87,31 @@ func (gd *GameReader) Items(pu data.PlayerUnit, hover data.HoverData) data.Items
|
|||||||
location := item.LocationUnknown
|
location := item.LocationUnknown
|
||||||
switch itemLoc {
|
switch itemLoc {
|
||||||
case 0:
|
case 0:
|
||||||
if 0x00002000&flags != 0 || (data.UnitID(itemOwnerNPC) != pu.ID) {
|
// Offline only
|
||||||
|
if itemOwnerNPC == 2 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[0]].UnitID) {
|
||||||
|
location = item.LocationSharedStash1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if itemOwnerNPC == 3 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[1]].UnitID) {
|
||||||
|
location = item.LocationSharedStash2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if itemOwnerNPC == 4 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[2]].UnitID) {
|
||||||
|
location = item.LocationSharedStash3
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0x00002000&flags != 0 && itemOwnerNPC == 4294967295 {
|
||||||
location = item.LocationVendor
|
location = item.LocationVendor
|
||||||
break
|
break
|
||||||
} else if invPage == 0 {
|
} else if invPage == 0 {
|
||||||
location = item.LocationInventory
|
location = item.LocationInventory
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if data.UnitID(itemOwnerNPC) == pu.ID || itemOwnerNPC == 1 {
|
if data.UnitID(itemOwnerNPC) == mainPlayer.UnitID || itemOwnerNPC == 1 {
|
||||||
location = item.LocationStash
|
location = item.LocationStash
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offline only
|
|
||||||
if itemOwnerNPC == 2 {
|
|
||||||
location = item.LocationSharedStash1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if itemOwnerNPC == 3 {
|
|
||||||
location = item.LocationSharedStash2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if itemOwnerNPC == 4 {
|
|
||||||
location = item.LocationSharedStash3
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 1:
|
case 1:
|
||||||
location = item.LocationEquipped
|
location = item.LocationEquipped
|
||||||
if itm.Type().Code == item.TypeBelt {
|
if itm.Type().Code == item.TypeBelt {
|
||||||
@@ -115,10 +129,13 @@ func (gd *GameReader) Items(pu data.PlayerUnit, hover data.HoverData) data.Items
|
|||||||
|
|
||||||
itm.Location = location
|
itm.Location = location
|
||||||
|
|
||||||
if location == item.LocationBelt {
|
// We don't care about the items we don't know where they are, probably previous games or random crap
|
||||||
belt.Items = append(belt.Items, itm)
|
if location != item.LocationUnknown {
|
||||||
} else {
|
if location == item.LocationBelt {
|
||||||
items.AllItems = append(items.AllItems, itm)
|
belt.Items = append(belt.Items, itm)
|
||||||
|
} else {
|
||||||
|
items.AllItems = append(items.AllItems, itm)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x150, Uint64))
|
itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x150, Uint64))
|
||||||
@@ -128,8 +145,8 @@ func (gd *GameReader) Items(pu data.PlayerUnit, hover data.HoverData) data.Items
|
|||||||
items.Belt = belt
|
items.Belt = belt
|
||||||
|
|
||||||
sort.SliceStable(items.AllItems, func(i, j int) bool {
|
sort.SliceStable(items.AllItems, func(i, j int) bool {
|
||||||
distanceI := utils.DistanceFromPoint(pu.Position, items.AllItems[i].Position)
|
distanceI := utils.DistanceFromPoint(mainPlayer.Position, items.AllItems[i].Position)
|
||||||
distanceJ := utils.DistanceFromPoint(pu.Position, items.AllItems[j].Position)
|
distanceJ := utils.DistanceFromPoint(mainPlayer.Position, items.AllItems[j].Position)
|
||||||
|
|
||||||
return distanceI < distanceJ
|
return distanceI < distanceJ
|
||||||
})
|
})
|
||||||
@@ -142,8 +159,8 @@ func (gd *GameReader) getItemStats(statsListExPtr uintptr) (stat.Stats, stat.Sta
|
|||||||
baseStats := gd.getStatsList(statsListExPtr + 0x30)
|
baseStats := gd.getStatsList(statsListExPtr + 0x30)
|
||||||
|
|
||||||
flags := gd.Process.ReadUInt(statsListExPtr+0x1C, Uint64)
|
flags := gd.Process.ReadUInt(statsListExPtr+0x1C, Uint64)
|
||||||
|
|
||||||
lastStatsList := uintptr(gd.Process.ReadUInt(statsListExPtr+0x70, Uint64))
|
lastStatsList := uintptr(gd.Process.ReadUInt(statsListExPtr+0x70, Uint64))
|
||||||
|
|
||||||
if (flags & 0x80000000) == 0 {
|
if (flags & 0x80000000) == 0 {
|
||||||
return baseStats, fullStats
|
return baseStats, fullStats
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,137 +6,74 @@ import (
|
|||||||
"github.com/hectorgimenez/d2go/pkg/data"
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/area"
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/skill"
|
"github.com/hectorgimenez/d2go/pkg/data/skill"
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/state"
|
"github.com/hectorgimenez/d2go/pkg/data/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gd *GameReader) GetPlayerUnitPtr(roster data.Roster) (playerUnitPtr uintptr, corpse data.Corpse) {
|
func (gd *GameReader) GetRawPlayerUnits() RawPlayerUnits {
|
||||||
|
rawPlayerUnits := make(RawPlayerUnits, 0)
|
||||||
|
hover := gd.hoveredData()
|
||||||
for i := 0; i < 128; i++ {
|
for i := 0; i < 128; i++ {
|
||||||
unitOffset := gd.offset.UnitTable + uintptr(i*8)
|
unitOffset := gd.offset.UnitTable + uintptr(i*8)
|
||||||
playerUnitAddr := gd.Process.moduleBaseAddressPtr + unitOffset
|
playerUnitAddr := gd.Process.moduleBaseAddressPtr + unitOffset
|
||||||
playerUnit := uintptr(gd.Process.ReadUInt(playerUnitAddr, Uint64))
|
playerUnit := uintptr(gd.Process.ReadUInt(playerUnitAddr, Uint64))
|
||||||
for playerUnit > 0 {
|
for playerUnit > 0 {
|
||||||
|
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
|
||||||
pInventory := playerUnit + 0x90
|
pInventory := playerUnit + 0x90
|
||||||
inventoryAddr := uintptr(gd.Process.ReadUInt(pInventory, Uint64))
|
inventoryAddr := uintptr(gd.Process.ReadUInt(pInventory, Uint64))
|
||||||
|
|
||||||
pPath := playerUnit + 0x38
|
pPath := playerUnit + 0x38
|
||||||
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
|
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
|
||||||
|
room1Ptr := uintptr(gd.Process.ReadUInt(pathAddress+0x20, Uint64))
|
||||||
|
room2Ptr := uintptr(gd.Process.ReadUInt(room1Ptr+0x18, Uint64))
|
||||||
|
levelPtr := uintptr(gd.Process.ReadUInt(room2Ptr+0x90, Uint64))
|
||||||
|
levelNo := gd.Process.ReadUInt(levelPtr+0x1F8, Uint32)
|
||||||
|
|
||||||
xPos := gd.Process.ReadUInt(pathAddress+0x02, Uint16)
|
xPos := gd.Process.ReadUInt(pathAddress+0x02, Uint16)
|
||||||
yPos := gd.Process.ReadUInt(pathAddress+0x06, Uint16)
|
yPos := gd.Process.ReadUInt(pathAddress+0x06, Uint16)
|
||||||
|
pUnitData := playerUnit + 0x10
|
||||||
|
playerNameAddr := uintptr(gd.Process.ReadUInt(pUnitData, Uint64))
|
||||||
|
name := gd.Process.ReadStringFromMemory(playerNameAddr, 0)
|
||||||
|
|
||||||
// Only current player has inventory
|
expCharPtr := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+gd.offset.Expansion, Uint64))
|
||||||
if inventoryAddr > 0 && xPos > 0 && yPos > 0 {
|
expChar := gd.Process.ReadUInt(expCharPtr+0x5C, Uint16)
|
||||||
expCharPtr := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+gd.offset.Expansion, Uint64))
|
isMainPlayer := gd.Process.ReadUInt(inventoryAddr+0x30, Uint16)
|
||||||
expChar := gd.Process.ReadUInt(expCharPtr+0x5C, Uint16)
|
if expChar > 0 {
|
||||||
baseCheck := gd.Process.ReadUInt(inventoryAddr+0x30, Uint16)
|
isMainPlayer = gd.Process.ReadUInt(inventoryAddr+0x70, Uint16)
|
||||||
if expChar > 0 {
|
|
||||||
baseCheck = gd.Process.ReadUInt(inventoryAddr+0x70, Uint16)
|
|
||||||
}
|
|
||||||
|
|
||||||
isCorpse := gd.Process.ReadUInt(playerUnit+0x1A6, Uint8)
|
|
||||||
if isCorpse == 1 {
|
|
||||||
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
|
|
||||||
hover := gd.hoveredData()
|
|
||||||
corpse = data.Corpse{
|
|
||||||
Found: true,
|
|
||||||
IsHovered: hover.IsHovered && hover.UnitID == data.UnitID(unitID) && hover.UnitType == 0,
|
|
||||||
Position: data.Position{
|
|
||||||
X: int(xPos),
|
|
||||||
Y: int(yPos),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if baseCheck > 0 {
|
|
||||||
playerUnitPtr = playerUnit
|
|
||||||
} else {
|
|
||||||
pUnitData := playerUnit + 0x10
|
|
||||||
playerNameAddr := uintptr(gd.Process.ReadUInt(pUnitData, Uint64))
|
|
||||||
name := gd.Process.ReadStringFromMemory(playerNameAddr, 0)
|
|
||||||
for k, rm := range roster {
|
|
||||||
if name != rm.Name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
roster[k] = data.RosterMember{
|
|
||||||
Name: name,
|
|
||||||
Area: rm.Area,
|
|
||||||
Position: data.Position{
|
|
||||||
X: int(xPos),
|
|
||||||
Y: int(yPos),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
isCorpse := gd.Process.ReadUInt(playerUnit+0x1A6, Uint8)
|
||||||
|
|
||||||
|
statsListExPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x88, Uint64))
|
||||||
|
states := gd.getStates(statsListExPtr)
|
||||||
|
|
||||||
|
rawPlayerUnits = append(rawPlayerUnits, RawPlayerUnit{
|
||||||
|
UnitID: data.UnitID(unitID),
|
||||||
|
Address: playerUnit,
|
||||||
|
Name: name,
|
||||||
|
IsMainPlayer: isMainPlayer > 0,
|
||||||
|
IsCorpse: isCorpse == 1 && inventoryAddr > 0 && xPos > 0 && yPos > 0,
|
||||||
|
Area: area.ID(levelNo),
|
||||||
|
Position: data.Position{
|
||||||
|
X: int(xPos),
|
||||||
|
Y: int(yPos),
|
||||||
|
},
|
||||||
|
IsHovered: hover.IsHovered && hover.UnitID == data.UnitID(unitID) && hover.UnitType == 0,
|
||||||
|
States: states,
|
||||||
|
})
|
||||||
playerUnit = uintptr(gd.Process.ReadUInt(playerUnit+0x150, Uint64))
|
playerUnit = uintptr(gd.Process.ReadUInt(playerUnit+0x150, Uint64))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return rawPlayerUnits
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
|
func (gd *GameReader) GetPlayerUnit(mainPlayerUnit RawPlayerUnit) data.PlayerUnit {
|
||||||
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
|
|
||||||
|
|
||||||
// Read X and Y Positions
|
|
||||||
pPath := playerUnit + 0x38
|
|
||||||
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
|
|
||||||
xPos := gd.Process.ReadUInt(pathAddress+0x02, Uint16)
|
|
||||||
yPos := gd.Process.ReadUInt(pathAddress+0x06, Uint16)
|
|
||||||
|
|
||||||
// Player name
|
|
||||||
pUnitData := playerUnit + 0x10
|
|
||||||
playerNameAddr := uintptr(gd.Process.ReadUInt(pUnitData, Uint64))
|
|
||||||
name := gd.Process.ReadStringFromMemory(playerNameAddr, 0)
|
|
||||||
|
|
||||||
// Get Stats
|
// Get Stats
|
||||||
statsListExPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x88, Uint64))
|
statsListExPtr := uintptr(gd.Process.ReadUInt(mainPlayerUnit.Address+0x88, Uint64))
|
||||||
baseStatPtr := gd.Process.ReadUInt(statsListExPtr+0x30, Uint64)
|
baseStats := gd.getStatsList(statsListExPtr + 0x30)
|
||||||
baseStatsCount := gd.Process.ReadUInt(statsListExPtr+0x38, Uint64)
|
stats := gd.getStatsList(statsListExPtr + 0x88)
|
||||||
statPtr := gd.Process.ReadUInt(statsListExPtr+0x88, Uint64)
|
|
||||||
statCount := gd.Process.ReadUInt(statsListExPtr+0x90, Uint64)
|
|
||||||
|
|
||||||
stats := map[stat.ID]int{}
|
|
||||||
for j := 0; j < int(statCount); j++ {
|
|
||||||
statOffset := uintptr(statPtr) + 0x2 + uintptr(j*8)
|
|
||||||
statNumber := gd.Process.ReadUInt(statOffset, Uint16)
|
|
||||||
statValue := gd.Process.ReadUInt(statOffset+0x02, Uint32)
|
|
||||||
|
|
||||||
switch stat.ID(statNumber) {
|
|
||||||
case stat.Life,
|
|
||||||
stat.MaxLife,
|
|
||||||
stat.Mana,
|
|
||||||
stat.MaxMana:
|
|
||||||
stats[stat.ID(statNumber)] = int(uint32(statValue) >> 8)
|
|
||||||
default:
|
|
||||||
stats[stat.ID(statNumber)] = int(statValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
baseStats := map[stat.ID]int{}
|
|
||||||
for j := 0; j < int(baseStatsCount); j++ {
|
|
||||||
statOffset := uintptr(baseStatPtr) + 0x2 + uintptr(j*8)
|
|
||||||
statNumber := gd.Process.ReadUInt(statOffset, Uint16)
|
|
||||||
statValue := gd.Process.ReadUInt(statOffset+0x02, Uint32)
|
|
||||||
|
|
||||||
switch stat.ID(statNumber) {
|
|
||||||
case stat.Life,
|
|
||||||
stat.MaxLife,
|
|
||||||
stat.Mana,
|
|
||||||
stat.MaxMana:
|
|
||||||
baseStats[stat.ID(statNumber)] = int(uint32(statValue) >> 8)
|
|
||||||
default:
|
|
||||||
baseStats[stat.ID(statNumber)] = int(statValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// States (Buff, Debuff, Auras)
|
|
||||||
states := gd.getStates(statsListExPtr)
|
|
||||||
|
|
||||||
// Skills
|
// Skills
|
||||||
skillListPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x100, Uint64))
|
skillListPtr := uintptr(gd.Process.ReadUInt(mainPlayerUnit.Address+0x100, Uint64))
|
||||||
skills := gd.getSkills(skillListPtr)
|
skills := gd.getSkills(skillListPtr)
|
||||||
|
|
||||||
leftSkillPtr := gd.Process.ReadUInt(skillListPtr+0x08, Uint64)
|
leftSkillPtr := gd.Process.ReadUInt(skillListPtr+0x08, Uint64)
|
||||||
@@ -148,14 +85,7 @@ func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
|
|||||||
rightSkillId := uintptr(gd.Process.ReadUInt(rightSkillTxtPtr, Uint16))
|
rightSkillId := uintptr(gd.Process.ReadUInt(rightSkillTxtPtr, Uint16))
|
||||||
|
|
||||||
// Class
|
// Class
|
||||||
class := data.Class(gd.Process.ReadUInt(playerUnit+0x174, Uint32))
|
class := data.Class(gd.Process.ReadUInt(mainPlayerUnit.Address+0x174, Uint32))
|
||||||
|
|
||||||
// Level number
|
|
||||||
pathPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x38, Uint64))
|
|
||||||
room1Ptr := uintptr(gd.Process.ReadUInt(pathPtr+0x20, Uint64))
|
|
||||||
room2Ptr := uintptr(gd.Process.ReadUInt(room1Ptr+0x18, Uint64))
|
|
||||||
levelPtr := uintptr(gd.Process.ReadUInt(room2Ptr+0x90, Uint64))
|
|
||||||
levelNo := gd.Process.ReadUInt(levelPtr+0x1F8, Uint32)
|
|
||||||
|
|
||||||
availableWPs := make([]area.ID, 0)
|
availableWPs := make([]area.ID, 0)
|
||||||
// Probably there is a better place to pick up those values, since this seems to be very tied to the UI
|
// Probably there is a better place to pick up those values, since this seems to be very tied to the UI
|
||||||
@@ -163,22 +93,20 @@ func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
|
|||||||
for i := 0; i < 0x48; i = i + 8 {
|
for i := 0; i < 0x48; i = i + 8 {
|
||||||
a := binary.LittleEndian.Uint32(wpList[i : i+4])
|
a := binary.LittleEndian.Uint32(wpList[i : i+4])
|
||||||
available := binary.LittleEndian.Uint32(wpList[i+4 : i+8])
|
available := binary.LittleEndian.Uint32(wpList[i+4 : i+8])
|
||||||
if available == 1 || area.ID(levelNo) == area.ID(a) {
|
if available == 1 || mainPlayerUnit.Area == area.ID(a) {
|
||||||
availableWPs = append(availableWPs, area.ID(a))
|
availableWPs = append(availableWPs, area.ID(a))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d := data.PlayerUnit{
|
d := data.PlayerUnit{
|
||||||
Name: name,
|
Name: mainPlayerUnit.Name,
|
||||||
ID: data.UnitID(unitID),
|
ID: mainPlayerUnit.UnitID,
|
||||||
Area: area.ID(levelNo),
|
Area: mainPlayerUnit.Area,
|
||||||
Position: data.Position{
|
Position: mainPlayerUnit.Position,
|
||||||
X: int(xPos),
|
|
||||||
Y: int(yPos),
|
|
||||||
},
|
|
||||||
Stats: stats,
|
Stats: stats,
|
||||||
|
BaseStats: baseStats,
|
||||||
Skills: skills,
|
Skills: skills,
|
||||||
States: states,
|
States: mainPlayerUnit.States,
|
||||||
Class: class,
|
Class: class,
|
||||||
LeftSkill: skill.ID(leftSkillId),
|
LeftSkill: skill.ID(leftSkillId),
|
||||||
RightSkill: skill.ID(rightSkillId),
|
RightSkill: skill.ID(rightSkillId),
|
||||||
|
|||||||
39
pkg/memory/player_unit.go
Normal file
39
pkg/memory/player_unit.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawPlayerUnit struct {
|
||||||
|
data.UnitID
|
||||||
|
Address uintptr
|
||||||
|
Name string
|
||||||
|
IsMainPlayer bool
|
||||||
|
IsCorpse bool
|
||||||
|
Area area.ID
|
||||||
|
Position data.Position
|
||||||
|
IsHovered bool
|
||||||
|
States state.States
|
||||||
|
}
|
||||||
|
|
||||||
|
type RawPlayerUnits []RawPlayerUnit
|
||||||
|
|
||||||
|
func (pu RawPlayerUnits) GetMainPlayer() RawPlayerUnit {
|
||||||
|
for _, p := range pu {
|
||||||
|
if p.IsMainPlayer {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RawPlayerUnit{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pu RawPlayerUnits) GetCorpse() RawPlayerUnit {
|
||||||
|
for _, p := range pu {
|
||||||
|
if p.IsCorpse {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RawPlayerUnit{}
|
||||||
|
}
|
||||||
@@ -4,10 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
const moduleName = "d2r.exe"
|
const moduleName = "d2r.exe"
|
||||||
|
|||||||
@@ -5,23 +5,41 @@ import (
|
|||||||
"github.com/hectorgimenez/d2go/pkg/data/area"
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gd *GameReader) getRoster() (roster []data.RosterMember) {
|
func (gd *GameReader) getRoster(rawPlayerUnits RawPlayerUnits) (roster []data.RosterMember) {
|
||||||
partyStruct := uintptr(gd.Process.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.RosterOffset, Uint64))
|
partyStruct := uintptr(gd.Process.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.RosterOffset, Uint64))
|
||||||
|
|
||||||
|
// We skip the first position because it's the main player, and we already have the information (+0x148 is the next party member)
|
||||||
|
partyStruct = uintptr(gd.Process.ReadUInt(partyStruct+0x148, Uint64))
|
||||||
for partyStruct > 0 {
|
for partyStruct > 0 {
|
||||||
name := gd.Process.ReadStringFromMemory(partyStruct, 16)
|
name := gd.Process.ReadStringFromMemory(partyStruct, 16)
|
||||||
a := gd.Process.ReadUInt(partyStruct+0x5C, Uint32)
|
a := area.ID(gd.Process.ReadUInt(partyStruct+0x5C, Uint32))
|
||||||
|
|
||||||
xPos := gd.Process.ReadUInt(partyStruct+0x60, Uint32)
|
xPos := int(gd.Process.ReadUInt(partyStruct+0x60, Uint32))
|
||||||
yPos := gd.Process.ReadUInt(partyStruct+0x64, Uint32)
|
yPos := int(gd.Process.ReadUInt(partyStruct+0x64, Uint32))
|
||||||
|
|
||||||
|
// When the player is in town, roster data is not updated, so we need to get the area from the player unit that match the same name
|
||||||
|
for _, pu := range rawPlayerUnits {
|
||||||
|
if pu.Name == name {
|
||||||
|
xPos = pu.Position.X
|
||||||
|
yPos = pu.Position.Y
|
||||||
|
a = pu.Area
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
roster = append(roster, data.RosterMember{
|
roster = append(roster, data.RosterMember{
|
||||||
Name: name,
|
Name: name,
|
||||||
Area: area.ID(a),
|
Area: a,
|
||||||
Position: data.Position{X: int(xPos), Y: int(yPos)},
|
Position: data.Position{X: xPos, Y: yPos},
|
||||||
})
|
})
|
||||||
partyStruct = uintptr(gd.Process.ReadUInt(partyStruct+0x148, Uint64))
|
partyStruct = uintptr(gd.Process.ReadUInt(partyStruct+0x148, Uint64))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
mainPlayerUnit := rawPlayerUnits.GetMainPlayer()
|
||||||
|
|
||||||
|
return append([]data.RosterMember{{
|
||||||
|
Name: mainPlayerUnit.Name,
|
||||||
|
Area: mainPlayerUnit.Area,
|
||||||
|
Position: mainPlayerUnit.Position,
|
||||||
|
}}, roster...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,13 +175,6 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
|||||||
return RuleResultNoMatch, fmt.Errorf("property %s is not valid or not supported", statName)
|
return RuleResultNoMatch, fmt.Errorf("property %s is not valid or not supported", statName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exception for enhanceddefense, is not accurate
|
|
||||||
if strings.EqualFold(statName, "enhanceddefense") {
|
|
||||||
enhancedDefense := r.calculateEnhancedDefense(it)
|
|
||||||
stage2Props[statName] = enhancedDefense
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
layer := 0
|
layer := 0
|
||||||
if len(statData) > 1 {
|
if len(statData) > 1 {
|
||||||
layer = statData[1]
|
layer = statData[1]
|
||||||
@@ -249,32 +242,3 @@ func getRequiredStatsForRule(line string) []string {
|
|||||||
func (r Rule) MaxQuantity() int {
|
func (r Rule) MaxQuantity() int {
|
||||||
return r.maxQuantity
|
return r.maxQuantity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Rule) calculateEnhancedDefense(i data.Item) int {
|
|
||||||
defenseStat, found := i.FindStat(stat.Defense, 0)
|
|
||||||
|
|
||||||
// This is special case for weapons and other stuff without base Defense and with EnhancedDefense, we can return it directly
|
|
||||||
// since EnhancedDefense it's a stat.
|
|
||||||
if !found {
|
|
||||||
if edStat, edStatFound := i.FindStat(stat.EnhancedDefense, 0); edStatFound {
|
|
||||||
return edStat.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make it only work for white base items
|
|
||||||
if i.Quality != item.QualitySuperior {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if !i.Type().IsType(item.TypeArmor) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
itemTypeMaxDefense := i.Desc().MaxDefense
|
|
||||||
|
|
||||||
if defenseStat.Value <= itemTypeMaxDefense {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(float64(defenseStat.Value-itemTypeMaxDefense) / float64(itemTypeMaxDefense) * 100)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ func TestRule_Evaluate(t *testing.T) {
|
|||||||
Name: "mageplate",
|
Name: "mageplate",
|
||||||
Quality: item.QualitySuperior,
|
Quality: item.QualitySuperior,
|
||||||
Stats: []stat.Data{
|
Stats: []stat.Data{
|
||||||
|
{ID: stat.EnhancedDefense, Value: 15},
|
||||||
{ID: stat.Defense, Value: 301},
|
{ID: stat.Defense, Value: 301},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user