275 lines
5.4 KiB
Go
275 lines
5.4 KiB
Go
package data
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/hectorgimenez/d2go/pkg/data/quest"
|
|
|
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
|
"github.com/hectorgimenez/d2go/pkg/data/skill"
|
|
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
|
"github.com/hectorgimenez/d2go/pkg/data/state"
|
|
)
|
|
|
|
const (
|
|
goldPerLevel = 10000
|
|
|
|
// Monster Types
|
|
MonsterTypeNone MonsterType = "None"
|
|
MonsterTypeChampion MonsterType = "Champion"
|
|
MonsterTypeMinion MonsterType = "Minion"
|
|
MonsterTypeUnique MonsterType = "Unique"
|
|
MonsterTypeSuperUnique MonsterType = "SuperUnique"
|
|
)
|
|
|
|
type Data struct {
|
|
AreaOrigin Position
|
|
Corpse Corpse
|
|
Monsters Monsters
|
|
// First slice represents X and second Y
|
|
CollisionGrid [][]bool
|
|
PlayerUnit PlayerUnit
|
|
NPCs NPCs
|
|
Items Items
|
|
Objects Objects
|
|
AdjacentLevels []Level
|
|
Rooms []Room
|
|
OpenMenus OpenMenus
|
|
Roster Roster
|
|
HoverData HoverData
|
|
TerrorZones []area.ID
|
|
Quests quest.Quests
|
|
KeyBindings KeyBindings
|
|
}
|
|
|
|
type Room struct {
|
|
Position
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
type HoverData struct {
|
|
IsHovered bool
|
|
UnitID
|
|
UnitType int
|
|
}
|
|
|
|
func (r Room) GetCenter() Position {
|
|
return Position{
|
|
X: r.Position.X + r.Width/2,
|
|
Y: r.Position.Y + r.Height/2,
|
|
}
|
|
}
|
|
|
|
func (r Room) IsInside(p Position) bool {
|
|
if p.X >= r.X && p.X <= r.X+r.Width {
|
|
return p.Y >= r.Y && p.Y <= r.Y+r.Height
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (d Data) MercHPPercent() int {
|
|
for _, m := range d.Monsters {
|
|
if m.IsMerc() {
|
|
// Hacky thing to read merc life properly
|
|
maxLife := m.Stats[stat.MaxLife] >> 8
|
|
life := float64(m.Stats[stat.Life] >> 8)
|
|
if m.Stats[stat.Life] <= 32768 {
|
|
life = float64(m.Stats[stat.Life]) / 32768.0 * float64(maxLife)
|
|
}
|
|
|
|
return int(life / float64(maxLife) * 100)
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
type RosterMember struct {
|
|
Name string
|
|
Area area.ID
|
|
Position Position
|
|
}
|
|
type Roster []RosterMember
|
|
|
|
func (r Roster) FindByName(name string) (RosterMember, bool) {
|
|
for _, rm := range r {
|
|
if strings.EqualFold(rm.Name, name) {
|
|
return rm, true
|
|
}
|
|
}
|
|
|
|
return RosterMember{}, false
|
|
}
|
|
|
|
type Level struct {
|
|
Area area.ID
|
|
Position Position
|
|
IsEntrance bool // This means the area can not be accessed just walking through it, needs to be clicked
|
|
}
|
|
|
|
type Class uint
|
|
|
|
const (
|
|
Amazon Class = iota
|
|
Sorceress
|
|
Necromancer
|
|
Paladin
|
|
Barbarian
|
|
Druid
|
|
Assassin
|
|
)
|
|
|
|
type Corpse struct {
|
|
Found bool
|
|
IsHovered bool
|
|
Position Position
|
|
}
|
|
|
|
type Position struct {
|
|
X int
|
|
Y int
|
|
}
|
|
|
|
type PlayerUnit struct {
|
|
Name string
|
|
ID UnitID
|
|
Area area.ID
|
|
Position Position
|
|
Stats stat.Stats
|
|
BaseStats stat.Stats
|
|
Skills map[skill.ID]skill.Points
|
|
States state.States
|
|
Class Class
|
|
LeftSkill skill.ID
|
|
RightSkill skill.ID
|
|
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 {
|
|
lvl, _ := pu.FindStat(stat.Level, 0)
|
|
return goldPerLevel * lvl.Value
|
|
}
|
|
|
|
// TotalGold returns the amount of gold, including inventory and stash
|
|
func (pu PlayerUnit) TotalGold() int {
|
|
gold, _ := pu.FindStat(stat.Gold, 0)
|
|
stashGold, _ := pu.FindStat(stat.StashGold, 0)
|
|
|
|
return gold.Value + stashGold.Value
|
|
}
|
|
|
|
func (pu PlayerUnit) HPPercent() int {
|
|
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 {
|
|
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 {
|
|
debuffs := []state.State{
|
|
state.Amplifydamage,
|
|
state.Attract,
|
|
state.Confuse,
|
|
state.Conversion,
|
|
state.Decrepify,
|
|
state.Dimvision,
|
|
state.Ironmaiden,
|
|
state.Lifetap,
|
|
state.Lowerresist,
|
|
state.Terror,
|
|
state.Weaken,
|
|
state.Convicted,
|
|
state.Poison,
|
|
state.Cold,
|
|
state.Slowed,
|
|
state.BloodMana,
|
|
state.DefenseCurse,
|
|
}
|
|
|
|
for _, s := range pu.States {
|
|
for _, d := range debuffs {
|
|
if s == d {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type PointOfInterest struct {
|
|
Name string
|
|
Position Position
|
|
}
|
|
|
|
type OpenMenus struct {
|
|
Inventory bool
|
|
LoadingScreen bool
|
|
NPCInteract bool
|
|
NPCShop bool
|
|
Stash bool
|
|
Waypoint bool
|
|
MapShown bool
|
|
SkillTree bool
|
|
Character bool
|
|
QuitMenu bool
|
|
Cube bool
|
|
SkillSelect bool
|
|
Anvil bool
|
|
}
|