thephrozenkeep / d2moo Goto Github PK
View Code? Open in Web Editor NEWReimplementation of the Diablo2 game coupled with patching facilities for modders.
License: MIT License
Reimplementation of the Diablo2 game coupled with patching facilities for modders.
License: MIT License
D2MOO/source/D2Common/src/Drlg/DrlgPreset.cpp
Line 1842 in 0b1835a
There is a cycle that apparently loops infinitely. Located in this function DRLGPRESET_UpdatePops
This is caused by a typo in the "j" counter.
Check the end of that FOR, it says "++i" when it should be "++j"
//D2Game.0x6FCD0840
int32_t __fastcall AITTACTICS_WalkCloseToUnit(D2GameStrc* pGame, D2UnitStrc* pUnit, int32_t nMaxDistance);
//D2Game.0x6FCD0D00
int32_t __fastcall AITTACTICS_RunCloseToTargetUnit(D2GameStrc* pGame, D2UnitStrc* pUnit, D2UnitStrc* pTarget, int32_t nMaxDistance);
spelling error with double T
//D2Common.0x6FDB9C10 (#10166)
D2ActiveRoomStrc* __stdcall PATH_GetRoom(D2DynamicPathStrc* pDynamicPath);
//D2Common.0x6FDB9C10 (#11265)
int __stdcall STATLIST_GetSkillId(D2StatListStrc* pStatList);
duplicate address listed
It seems that the lava animation is currently broken.
This is probably due to one of the DRLG related functions having a bug.
First step would be to identify what function(s) that is currently with PatchAction::FunctionReplacePatchByOriginal
in https://github.com/ThePhrozenKeep/D2MOO/blob/master/D2.Detours.patches/1.10f/D2Common.patch.cpp#L30 causes the issue.
if (pCubeMainTxtRecord->bOp = 0)-> the variable (bOpConditionsFulfilled) is always = false.
while the function below has the condition variable (bOpConditionsFulfilled >= 1)
repair
....
switch (pCubeMainTxtRecord->nOp)
{
...
default:
break;
}
to
....
switch (pCubeMainTxtRecord->nOp)
{
...
default:
{
bOpConditionsFulfilled = true;
break;
}
}
From Conqueror on the PK discord:
There are more bugs I discovered in 3 DRLG functions
//D2Common.0x6FD85A10
DRLGPRESET_ParseDS1File
Doesn't control MonPreset ID properly
The Objects I rewrote it completely to implement Extended Object in my Mod, this works fine, still adding more than 150 objects per act. So for this part the function is perfect.
In MonPresets the error is that it does not correctly calculate the ID for SuperUniques, NPC and Champion / Unique monsters
The original game as a lot of bugs, and we need to decide what to do about it.
Since D2MOO's main objective is to implement the vanilla game, we must keep the code of those bugs, and document them.
We could probably still fix them, and use a macro for such code to enable/disable fixes.
I'm adding a new Label for bugs reported that might have an impact to modders, but were already present in the original game, thus not due to D2MOO.
Such bugs must be both labeled and reference this issue.
Need to capture pNextItem before removing i (Current item) otherwise INVENTORY_GetNextItem(i) will return nullptr and terminate the loop prematurely
D2ClientStrc* pClient = SUNIT_GetClient(pUnit);
if (pUnit->pInventory)
{
D2UnitStrc* pNext = nullptr;
for (D2UnitStrc* i = D2COMMON_INVENTORY_GetFirstItem(pUnit->pInventory); i; i = pNext)
{
D2UnitStrc* pCheckedItem = D2COMMON_INVENTORY_UnitIsItem(i);
pNext = D2COMMON_INVENTORY_GetNextItem(i);
if (pCheckedItem && ITEMS_GetInvPage(pCheckedItem) == INVPAGE_CUBE)
{
D2COMMON_ITEMS_SetItemCell(pCheckedItem, INVPAGE_CUBE);
D2GAME_SCMD_UpdateItem(pClient, pUnit, pCheckedItem, ITEMCMDFLAG_DELETE);
D2GAME_ITEMMOD_RemoveItem(pGame, pUnit, pCheckedItem, 0);
}
}
}
From Conqueror on the PK discord:
//D2Common.0x6FD87760
DRLGPRESET_BuildPresetArea//D2Common.0x6FD78C10
DRLGWARP_GetWaypointRoomExFromLevelThese 2 functions generate kernel errors
I do not have more details on these functions.21:45:52.872 Address Frame Module Return Stack
21:45:52.872 75ec1c3b 0019f908 KERNEL32.DLL KERNEL32!InterlockedDecrement+000B [omap]
21:45:52.872 6f647faf 00000000 D2Common.dll D2Common!Ordinal10022+1AAF [omap]
21:45:52.872 Stack walk error: 299
21:45:52.872 2 frames dumped
D2Common.0x6FDBE510 (#10349)
This function doesn't correctly calculate the frame count for Players, Monsters & objects.
The error log periodically marks this on each frame (which is obviously an error in that sense):
(ptCelContext->dwCel < ptGfxBlock->bNumCelsPerDir)
Couldn't get cel handle in for:(null) TYPE:0 CLASS:1 MODE:3 FILE:C:\projects\D2\head\Diablo2\Source\D2CMP\Src\SpriteCache.cpp LINE:1833
(ptCelContext->dwCel < ptGfxBlock->bNumCelsPerDir)
(ptCelContext->dwCel < ptGfxBlock->bNumCelsPerDir)
[...]
...
void __fastcall MONSTER_InitializeStatsAndSkills(D2GameStrc* pGame, D2ActiveRoomStrc* pRoom, D2UnitStrc* pUnit, D2MonRegDataStrc* pMonRegData)
{
if (!pUnit || pUnit->dwUnitType != UNIT_PLAYER || !pUnit->pMonsterData || !pUnit->pMonsterData->pMonstatsTxt)
{
return;
}
should be UNIT_MONSTER
Change D2DamageStrc* pDamage to D2DamageStrc pDamage
Currently the debugger only displays a bit of information related to the current game and units animation data.
Any suggestion / work on the debugger would be more than welcome, and I believe a great way to discover the codebase.
The idea is that we should be able to use the debugger to help with the reverse engineering, but also for modders !
This function is missing UNIT_TILE and caves don't seem to work.
Origin:
else if (nUnitType == UNIT_ITEM)
{
...
}
Fix:
else if (nUnitType == UNIT_ITEM)
{
...
}
else if ((nUnitType - 1) > 0)
{
pUnit = SUNIT_AllocUnitData(nUnitType, nClassId, nX, nY, pGame, pRoom, 1, nMode, 0);
}
The following 2 calls to memcpy
in MONSTERREGION_InitializeAll
use the incorrect size, as the monster id's are a uint16_t
but memcpy
takes a byte count:
these calls should be pLevelsTxtRecord->nNumNMon * sizeof(uint16_t)
and pLevelsTxtRecord->nNumNormMon * sizeof(uint16_t)
respectively, else std::copy
should be used in place of memcpy
.
Additionally the default array initializer of monsterIds
does not exist in the assembly code (apart from being redundant due to the memcpy
). the compiler should elide this, but there may be scenarios where a pointless memset
may be done.
This was already present in the original game (#14 ).
MISSILE_SetDamageStats
( D2Common.0x6FDBB2E0 (#11218)
) will try to set the STAT_FIRELENGTH
stat twice:
D2MOO/source/D2Common/src/Units/Missile.cpp
Line 962 in 4358959
D2MOO/source/D2Common/src/Units/Missile.cpp
Line 983 in 4358959
nFireLength
and nBurnLength
are not 0
, since otherwise STATLIST_SetUnitStat
does not actually set the value.Events timers (from Event.h
) and SUnitEvent.h
used to be mixed up.
This needs cleaning up and clarification.
I think historically there were confusions between
D2EventTimerStrc
(which uses the somewhat badly namedD2C_UnitEventCallbackTypes
) andD2UnitEventStrc
which saysuint8_t nTimerType; //0x00 D2C_UnitEventCallbackTypes
but actually should be usingD2C_EventTypes
according to what I'm seeing (D2Game.0x6FD156A0
callsSUNITEVENT_AllocTimer
and setsnTimerType
to a value fromD2ItemStatCostTxt::wItemEvent
fromD2SkillsTxt::wAuraEvent
which are both indices intoevents.txt
...events.txt:
event *desc hitbymissile hit by a missile damagedinmelee damaged in melee damagedbymissile damaged by missile attackedinmelee melee attack atttempt doactive do active state skill domeleedamage do damage in melee domissiledamage do missile damage domeleeattack do melee attack domissileattack do missile attack kill killed something killed killed by something absorbdamage dealt damage levelup gain a level
This confirms
SUNITEVENT_EventFunc_Handler
event is an id from events.Txt (thusD2C_EventTypes
)...Would you be ok with fixing the comment and name of the field in
D2UnitEventStrc
? And also documenting value 13 with above information from necrolis.
Originally posted by @Lectem in #129 (comment)
@Lectem TBH the whole of SUnitEvent.h has poor naming since nothing about it relates to timers (its all trigger based). Might be best to shift that renaming to its own pull request.
Originally posted by @Necrolis in #129 (comment)
The errors remain exactly the same.
This was added that LoadItemStatCost does not read the OPs 0/8/9/10/11 that correspond to the same OPs that fail with the STATLIST_UpdateUnitNewStat function and STATLIST_InsertStatModOrFail
(As a side note, LoadISC from the pre-bulk changes version worked fine (it's the one I'm using))
Sorry for my slowness, I still haven't finished rearranging all my code, it's very long and I didn't check even half of the D2Moo functions (corresponding to ItemsFunctions, DRLG, Path and Missiles).
The good news is that I have made several fixes to the Path and Skills functions to make them work much better (and in some cases, to work directly).
And with Statlist I can't fix those errors.
I also don't have enough time to repair all the maximum details, but I would like to get it since it is very useful to handle all the statlists to try to expand them a bit with some custom code (in addition to expanding the ISC OPs which are also very useful)
In D2Net Client.cpp
, the size of the packet sizes table (std::size(gServerPacketSizeTable)
) is hardcoded (0xB4
).
We should probably have a constant for this, or put the table in its own header since it should only be included in D2Net's Client.cpp
and Server.cpp
.
Originally posted by @Lectem in #96 (comment)
Flavie (and probably other harcoded units) is currently misplaced. She is spawned near the Den of Evil.
While waiting for a fix, D2Moo's DRLGPRESET_SpawnHardcodedPresetUnits
has been detoured to call the original D2Common.dll
function in c324972.
DUNGEON_ChangeClientRoom
has been marked as broken since it calls DRLGPRESET_SpawnHardcodedPresetUnits
, but once it is fixed we can put it back as checked.
i am testing with function sub_6FC84D40, to send a notice to join the room, and it error like this
I fixed it like this
int i = 0;
while (pPacket5A->szText[i])
{
if (++i >= 16)
return;
}
GAME_SendPacketToAllConnectedClients(pGame, j_D2GAME_SendPacket0x5A_6FC3DEC0, pPacket5A);
the following functions work perfectly:
GAME_SendPacketToAllConnectedClients
j_D2GAME_SendPacket0x5A_6FC3DEC0
D2GAME_PACKETS_SendPacket0x5A_6FC3DEC0
D2UnitStrc* __fastcall SUNIT_AllocUnitData(int32_t nUnitType, int32_t nClassId, int32_t nX, int32_t nY, D2GameStrc* pGame, D2ActiveRoomStrc* pRoom, char a7, int32_t nMode, DWORD a3)
{
...
switch (nUnitGUID)
{
case UNIT_PLAYER:
{
...
}
should be nUnitType
Originally posted by @Lectem in #96 (comment)
D2MOO/source/D2Common/src/Drlg/DrlgPreset.cpp
Line 1842 in 0b1835a
There is a cycle that apparently loops infinitely. Located in this function DRLGPRESET_UpdatePops
This is caused by a typo in the "j" counter.
Check the end of that FOR, it says "++i" when it should be "++j"
Although `D2CharacterPreviewInfoStrc* pPreviewInfoStrc` is correct from a memory layout, the parameter is a `const char*` as the data is treated as a variable length text string with a nil terminator (you can also see this from the func that fills it out in pClient).
The experience args are also likely a spanned uint64_t
rather than two uint32_t
's, though @Lectem will likely need to weigh in if we want to aggregate them back to uint64_t
or not.
Originally posted by @Necrolis in #157 (comment)
Right now I did not choose the way we want to support mods for the D2Moo integration.
In the long term, I think the best option is to use a fork of D2Moo, but it's not necessarily simple to migrate from an existing mod (for example using D2Template) as you would have 2 different patching methods.
In my opinion this should be a discussion with other modders / community, so I listed a few ideas, their advantages and drawbacks in the following table.
Please give your opinion!
D2MOO as | Method | Integration with existing mod | Ease of update | Can share includes | Compatible with D2Template | Can easily change implementation of an ordinal | Mod redistribution | Can contribute/share easily (bugs) | Is own project bugged or is it D2Moo? |
---|---|---|---|---|---|---|---|---|---|
subproject | Configure CMake to use your own patch files and .def | medium | easy | only unpatched | +/- | yes, but only from the original dll pov | D2Moo launcher + .dlls | yes | easy to know |
subproject | Import D2MOO .dlls and patch the functions you want | easy | easy | only unpatched | yes | yes, but only from the original dll pov | Whatever you already had | yes | easy to know |
subproject | Link D2Moo statically | medium | easy | only unpatched | yes | yes, but only from the original dll pov | Whatever you already had | yes | mostly easy to know |
fork | Fork D2Moo and work directly on it | needs some transition (start by importing dll?) | easy, might need resolve | all | +/- | yes | D2Moo launcher + .dlls (OR simply .dlls if we use forwarding DLLs for unpatched ordinals) | yes | just need to change branch |
source | Copy paste (please don't) | "easy" but leads to lot of issues | hard | copy-paste... | yes | depends on your project patching facilities | ? | no | no |
int32_t __fastcall sub_6FCFEDD0(D2GameStrc* pGame, D2UnitStrc* pUnit, D2UnitStrc* pTarget, int32_t nTimeout, int32_t nSkillId, int32_t nSkillLevel)
{
...
const int32_t nRange = SKILLS_GetProgressiveSkillMissileId(pUnit, nSkillId);
if (nRange <= 0)
{
return 1;
}
D2MissileStrc missileParams = {};
missileParams.nMissile = nRange;
missileParams.nRange = nTimeout;```
Naming for nRange seems out of place
Currently those names are not very practical.
D2RoomExStrc
holds pretty much all the static data, and is always kept in memory until level is destroyed
D2RoomStrc
D2RoomStrc
contains mostly dynamic data / temporary data, and can be streamed in/out (see https://github.com/ThePhrozenKeep/D2MOO/blob/master/source/D2Common/src/Drlg/DrlgActivate.cpp#L90 and https://github.com/ThePhrozenKeep/D2MOO/blob/master/source/D2Game/src/GAME/Game.cpp#L2081)
For D2RoomExStrc
:
D2RoomStrc
!DrlgTestClient (DrlgRoomGetDrlg (ptRoom))
, and Drlg is the prefix for all functions related to DRLG.D2DrlgRoomStrc
D2StaticRoomStrc
For D2RoomStrc
:
D2UnitRoomStrc
D2ActiveRoomStrc
D2TiledRoomStrc
D2DynamicRoomStrc
D2Common.0x6FDB64A0 and LoadItemStatCost Now they seem to work properly, however the STATLIST_InsertStatModOrFail_6FDB7690 and D2Common.0x6FDB5830 functions are not working properly.
STATLIST_InsertStatModOrFail_6FDB7690 it's wrong in some ISC, example: Cold Skill Damage %
D2Common.0x6FDB5830 it has a problem with the Auras. The auras of nearby friendly units do not share it with other friendly units (disabling it works perfectly).
Error:
if (pItem->dwUnitType != UNIT_ITEM || !pItem->pItemData || pItem->pItemData->dwQualityNo < ITEMQUAL_MAGIC || pItem->pItemData->dwQualityNo > ITEMQUAL_TEMPERED)
Fix:
if (pItem->dwUnitType == UNIT_ITEM || pItem->pItemData || pItem->pItemData->dwQualityNo < ITEMQUAL_MAGIC || pItem->pItemData->dwQualityNo > ITEMQUAL_TEMPERED)
This was already present in the original game (#14 ).
As can be seen in void __fastcall AITHINK_Fn116_Minion(D2GameStrc* pGame, D2UnitStrc* pUnit, D2AiTickParamStrc* pAiTickParam)
, the param used to determine if attack 1 or 2 is used is 1
instead of 4
as described by MonAi.txt
.
It was confirmed this bug was present in the original game in v1.10f.
See the code here :
D2MOO/source/D2Game/src/AI/AiThink.cpp
Lines 6919 to 6926 in a12c763
I did an exhaustive verification of all the code related to StatList (basically because my intention is to expand some sections, but before doing so I must verify that all the vanilla code works perfectly).
The testing was carried out in 3 phases:
- I checked all the functions together in the same HOOK (Exactly the same from the D2Moo code). The result was bad (obviously) since there were endless errors when starting a game. After this result I started the second phase.
- I checked the code from the latest D2Moo update (with the massive changes) and compared it to the code I used previously (with the first version). In this phase I rechecked all the code with its changes, and the result was exactly the same. Negative, only this time I noticed corrected functions and functions with helpers (which did not exist before). For this reason, when exposing phase 3 of my tests, I included a partial result when using the Helper functions, but I am not 100% sure of their correct operation, however 50% of them work well at first glance. And regarding the rest of the functions, I did a third test to analyze them one by one.
In this phase, I did an individual test and changed the syntax of some functions (for my convenience, but the result is theoretically the same). This phase is the most extensive and consists of trying to repair what I can and report everything I got (with my corrections).
Here I got the following result:29 Wrong Functions (Short functions that depend on these long functions I don't consider "wrong" because they are literally the same function.)
77 functions that work fine (within my limited, but extreme tests).
A function I could not verify in any way that I have no idea where or when it is executed (in D2Game and D2Client).
Also, I proposed some names for the unknown functions and some changes to other functions (although the latter is not important). And, Most of the improvements, I made were control for IFs (if you use pUnit->pStatListEx I added if pUnit and other things like that). I did this because some functions just fixed like this
I clarify that there are also functions that internally call the functions that "do not work", this is because I did a complete stability test, I added vanilla PTRS in each function and I was verifying one by one (its structure, loops and other characteristics). That is why there are functions that "work". This is an attempt to isolate the error.
And one last clarification regarding the Names: I mean "new name" with respect to the first name they had, some I updated them with the Name you gave them and others I invented them.
Work Functions:
Functions that exclusively depend on wrong functions to Work:
WRONG functions:
HELPER Functions:
Work Functions:
Wrong Functions:
This function also requires that pAiControl is nullptr or not
//D2Game.0x6FCCF320
...
if (pAiControl->pGame)
To
//D2Game.0x6FCCF320
...
if (pAiControl && pAiControl->pGame)
struct D2QuestDataStrc
{...
D2QuestGUIDStrc tPlayerGUIDs; //0x1C
uint16_t dw9E; //0x9E
struct D2QuestGUIDStrc //sizeof 0x84
{
uint32_t nPlayerGUIDs[32]; //0x00 - players that have entered the quest zone
uint16_t nPlayerCount; //0x80
uint8_t pad0x82[2]; //0x82
};
If the padding of 2 bytes is in D2QuestGUIDStrc and the size 0x84, then it shouldn't also be in D2QuestData, right?
D2Net_10006
Should probably be SERVER_Send
or SERVER_SendToClient
. (if option n°2, then rename CLIENT_Send
to CLIENT_SendToServer
)
Here are names from @Necrolis we may want to get inspiration from
D2Net.#10000 - InitSystem
D2Net.#10001 - ShutdownSystem
D2Net.#10002 - Wait
D2Net.#10003 - CreateQServer
D2Net.#10004 - CloseQServer
D2Net.#10005 - SendToServer
D2Net.#10006 - SendToClient
D2Net.#10007 - RecvFromBufferClt
D2Net.#10008 - RecvFromHiPriorityBufferClt
D2Net.#10009 - <Missing?????> -> assuming this is RecvFromLoPriorityBufferClt
D2Net.#10010 - RecvFromBufferSrv
D2Net.#10011 - RecvFromHiPriorityBufferSrv
D2Net.#10012 - RecvFromLoPriorityBufferSrv
D2Net.#10013 - GetSocketName
D2Net.#10014 - GetIPAddress
D2Net.#10015 - CreateConnection
D2Net.#10016 - CloseConnection
D2Net.#10017 - Connect
D2Net.#10018 - SetQServerData(0xBD8)
D2Net.#10019 - SetConnectionCallback
D2Net.#10020 - SetConnectionData
D2Net.#10021 - GetConnectionData
D2Net.#10022 - Wait(0xBD4)
D2Net.#10023 - SetQServerMode
D2Net.#10024 - GetLastWSAError
D2Net.#10025 - CreatePollThread
D2Net.#10026 - SetMaxClients
D2Net.#10027 - GetMaxClients
D2Net.#10028 - Return
D2Net.#10029 - Return
D2Net.#10030 - GetClientPacketSize
D2Net.#10031 - GetServerPacketSize
D2Net.#10032 - CloseAllConnections
D2Net.#10033 - GetQServerIP
D2Net.#10034
D2Net.#10035
D2Net.#10036
D2Net.#10037
D2Net.#10038
D2Net.#10039 - GetSendFunction
D2Net.#10040 - GetDataFunction
Originally posted by @Lectem in #96 (comment)
//D2Common.0x6FDC0FC0 (#10436)
int __stdcall UNITS_GetFrameBonus(D2UnitStrc* pUnit)
This feature has timing issues in "hth" mode when the character dies.
Verified with Sorceress.
The head of the Sorceress goes "crazy" and does not show the proper location as she walks after dying.
Possibly it is related to an internal function, I have not verified exactly which one, although my suspicions point to this other:
//D2Common.0x6FDB30A0 (#11013)
void __stdcall D2COMMON_11013_ConvertMode(D2UnitStrc* pUnit, int* pType, int* pClass, int* pMode, char* szFile, int nLine)
When we use the sword in mercenaries act 3, the item will disappear because the function only checks the item on the right hand side, not on the left hand side, and so are the other entries it seems to work only in the right hand.
Origin:
uint8_t nTargetBodyLoc = nBodyLoc1;
if (pMerc->dwClassId == MONSTER_ACT3HIRE && ITEMS_CheckItemTypeId(pItem, ITEMTYPE_SHIELD))
{
pExchangeItem = pTempItem;
nTargetBodyLoc = nBodyLoc2;
}
Fix:
uint8_t nTargetBodyLoc = nBodyLoc1;
if (pMerc->dwClassId == MONSTER_ACT3HIRE && ITEMS_CheckItemTypeId(pItem, ITEMTYPE_SHIELD))
{
pExchangeItem = pTempItem;
nTargetBodyLoc = nBodyLoc2;
}
else
{
nBodyLoc2 = nTargetBodyLoc;
}
void __fastcall D2GAME_BOSSES_AssignUMod_6FC6FF10(D2GameStrc* pGame, D2UnitStrc* pUnit, int32_t nUMod, int32_t bUnique)
{
...
for (int32_t i = 0; i < 9; ++i)
{
if (!pUMods[i])
{
pUMods[i] = nUMod;
sub_6FC6F670(pUnit, nUMod, bUnique);
}
}
should be a break or return after the call, otherwise it fills the entire list with the same UMod
Many statlist functions use Dichotomy
in their name, this is not actually the correct form of the word (both linguistically and in the CS context).
These need to be amended to Dichotomic
, see: https://en.wikipedia.org/wiki/Dichotomic_search & https://en.wiktionary.org/wiki/dichotomic
From @Necrolis
At
D2MOO/source/D2Game/src/GAME/Clients.cpp
Line 641 in 6f02d8f
CLIENTS_RemoveClientFromListWithId
which removes from the the "global" static linked list instead of the game's linked list (D2ClientStrc+0x4AC
instead of D2ClientStrc+0x4A8
).
The helper function should be told what list to remove from.
Game List Unlink
0533B927 . 8B90 A8040000 MOV EDX,DWORD PTR DS:[EAX+4A8]
Global List Unlink
0533B89D . 8B86 AC040000 MOV EAX,DWORD PTR DS:[ESI+4AC]
Global List Unlink (By Name)
0533B9CA . 8B82 B0040000 MOV EAX,DWORD PTR DS:[EDX+4B0]
We might want to rename D2ClientStrc+0x4AC
or D2ClientStrc+0x4A8
to avoid confusion in the future.
There are many types of coordinates systems with various granularity.
It would be great to have a clear and concise documentation of what those are, what functions let us convert from one to another, and chose a naming convention for variables / struct members so that we can easily identify what type of coordinates we are talking about.
It might even be interesting to use typedefs (or even strongly typed structs?) to replace the plain int
/ D2CoordStrc
.
A first draft of the documentation is available here https://github.com/ThePhrozenKeep/D2MOO/blob/master/doc/Coordinates.md.
gdwBitMasks[nState % 32] -> gdwBitMasks[nState % 31]
bool bStateMaskDisguise = pStatesTxtRecord->dwStateFlags & gdwBitMasks[STATEMASK_DISGUISE];
Fix
bool bStateMaskNoSend = pStatesTxtRecord->dwStateFlags & gdwBitMasks[STATEMASK_NOSEND];
few edits that I think are correct looking at the ASM
int32_t __fastcall AIUTIL_CanUnitSwitchAi(D2UnitStrc* pUnit, int32_t nSpecialState)
{
if (!pUnit || pUnit->dwUnitType != UNIT_MONSTER || nSpecialState > 19 || STATES_CheckState(pUnit, STATE_UNINTERRUPTABLE) || !UNITS_CanSwitchAI(pUnit->dwClassId))
{
return 0;
}
if (!(pUnit->dwFlags & UNITFLAG_CANBEATTACKED) && !SUNIT_IsDead(pUnit))
{
return 0;
}
if (MONSTERUNIQUE_CheckMonTypeFlag(pUnit, MONTYPEFLAG_UNIQUE | MONTYPEFLAG_SUPERUNIQUE))
{
return 0;
}
if (nSpecialState == 19)
{
return 1;
}
return AITHINK_CanUnitSwitchAi(pUnit, MONSTERMODE_GetMonStatsTxtRecord(pUnit->dwClassId), nSpecialState, 1);
}```
pGame+0x20
is a D2ClientInfoStrc*
set by game server
This bug was discovered by Silvermane#7177 in PK.
Can someone clarify if I'm just having a brain fart or if the == UNIT_PLAYER should be != UNIT_PLAYER
//D2Common.0x6FDAA1E0 (#10186)
void __stdcall PATH_ResetToPreviousType(D2DynamicPathStrc* pDynamicPath)
{
D2_ASSERT(pDynamicPath->pUnit);
if (pDynamicPath->dwFlags & 0x8000)
{
pDynamicPath->dwVelocity = pDynamicPath->unk0x80;
}
if (pDynamicPath->pUnit->dwUnitType == UNIT_PLAYER)
{
if (pDynamicPath->dwFlags & 0x2000)
{
PATH_SetType(pDynamicPath, pDynamicPath->dwPrevPathType);
}
}
else
{
PATH_SetType(pDynamicPath, 7);
}
}
6F66A21A 8339 00 CMP DWORD PTR DS:[ECX],0
6F66A21D 5E POP ESI
6F66A21E 75 0B JNZ SHORT D2Common.6F66A22B
6F66A220 6A 07 PUSH 7
6F66A222 50 PUSH EAX
6F66A223 E8 B8FEFFFF CALL D2Common.6F66A0E0
6F66A228 C2 0400 RETN 4
6F66A22B F6C6 20 TEST DH,20
6F66A22E 74 0A JE SHORT D2Common.6F66A23A
6F66A230 8B48 40 MOV ECX,DWORD PTR DS:[EAX+40]
6F66A233 51 PUSH ECX
6F66A234 50 PUSH EAX
6F66A235 E8 A6FEFFFF CALL D2Common.6F66A0E0
6F66A23A C2 0400 RETN 4
From me, I found more errors in the Internal Subfunction, Apparently they misplaced the variables (or I had them wrong in that version, maybe).
My Solution
//D2Common.0x6FDAA1E0 (#10186) - WRONG D2Moo
// != UNIT_PLAYER Corrected
void __stdcall PATH_ResetToPreviousType(D2DynamicPathStrc* pDynamicPath)
{
if (pDynamicPath && pDynamicPath->pUnit)
{
if (pDynamicPath->dwFlags & PATHFLAG_SETSPEED)
{
pDynamicPath->dwVelocity = pDynamicPath->dwPrevVelocity;
}
if (pDynamicPath->pUnit->dwUnitType != UNIT_PLAYER)
{
if (pDynamicPath->dwFlags & PATHFLAG_SETTYPE)
{
PATH_SetType(pDynamicPath, pDynamicPath->dwPrevPathType);
}
}
else
{
PATH_SetType(pDynamicPath, 7);
}
}
}
//D2Common.0x6FDAA0E0 (#10185) - WRONG D2Moo
//nValueType corrected
void __stdcall PATH_SetType(D2DynamicPathStrc* pDynamicPath, int nPathType)
{
int nFlag = 0;
D2_ASSERT(!(pDynamicPath->pUnit && pDynamicPath->pUnit->dwUnitType == UNIT_PLAYER && nPathType == PATHTYPE_TOWARD));
nFlag = dword_6FDD2088[nPathType];
if (nFlag & PATHFLAG_SETTYPE && !(pDynamicPath->dwFlags & PATHFLAG_KEEPTYPE))
{
pDynamicPath->dwPrevPathType = pDynamicPath->dwPathType;
}
if (nFlag & PATHFLAG_SETSPEED && !(pDynamicPath->dwFlags & PATHFLAG_KEEPSPEED))
{
pDynamicPath->dwPrevVelocity = pDynamicPath->dwVelocity;
}
pDynamicPath->dwPathType = nPathType;
pDynamicPath->dwFlags = nFlag | pDynamicPath->dwFlags & 0xFFF800FF;
/*pDynamicPath->PathPoints[0].X = word_6FDD20D0[2 * nPathType]; <<<<<<<<<<<<<< error
pDynamicPath->PathPoints[0].Y = word_6FDD20D0[2 * nPathType + 1];*/
pDynamicPath->nValueType = gnValueTypes[nPathType];
D2_ASSERT(pDynamicPath->dwPrevPathType != PATHTYPE_KNOCKBACK_CLIENT);
D2_ASSERT(pDynamicPath->dwPrevPathType != PATHTYPE_KNOCKBACK_SERVER);
D2_ASSERT((nPathType != PATHTYPE_MISSILE) || (pDynamicPath->nDistMax < MAXPATHLEN));
}
//6FDD20D0
static int32_t gnValueTypes[] =
{
0,
0,
0,
0,
0,
2,
-2,
0,
0,
0,
0,
0,
-4,
0,
0,
0,
0,
0
};
for (int32_t i = 0; i < 2; ++i)
{
const int16_t nExcludeMonType = pMonUModTxtRecord->wExclude[i];
if (pUnit && pUnit->dwUnitType == UNIT_MONSTER)
{
D2MonStatsTxt* pMonStatsTxtRecord = MONSTERMODE_GetMonStatsTxtRecord(pUnit->dwClassId);
if (pMonStatsTxtRecord && pMonStatsTxtRecord->wMonType >= 0 && pMonStatsTxtRecord->wMonType < sgptDataTables->nMonTypeTxtRecordCount
&& nExcludeMonType >= 0 && nExcludeMonType < sgptDataTables->nMonTypeTxtRecordCount
&& sgptDataTables->pMonTypeNest[(pMonStatsTxtRecord->wMonType >> 5) + nExcludeMonType * sgptDataTables->nMonTypeIndex] & gdwBitMasks[pMonStatsTxtRecord->wMonType & 0x1F])
{
return 0;
}
}
}
This looks like inlined SUNITDMG_CheckMonType
for (int32_t i = 0; i < 2; ++i)
{
const int16_t nExcludeMonType = pMonUModTxtRecord->wExclude[i];
if (pUnit && pUnit->dwUnitType == UNIT_MONSTER)
{
D2MonStatsTxt* pMonStatsTxtRecord = MONSTERMODE_GetMonStatsTxtRecord(pUnit->dwClassId);
if (SUNITDMG_CheckMonType(nExcludeMonType, pMonStatsTxtRecord->wMonType);
{
return 0;
}
}
}
or
D2MonStatsTxt* pMonStatsTxtRecord = MONSTERMODE_GetMonStatsTxtRecord(pUnit->dwClassId);
if (pMonStatsTxtRecord)
{
if (SUNITDMG_CheckMonType(pMonUModTxtRecord->wExclude[0], pMonStatsTxtRecord->wMonType) || SUNITDMG_CheckMonType(pMonUModTxtRecord->wExclude[1], pMonStatsTxtRecord->wMonType))
return;
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.