Page 1 of 1

Experience Points Add-On for MacroQuest (v1.0)

Posted: Sun Apr 20, 2003 12:52 am
by Clawed
:arrow: PROJECT NAME: Experience Points Add-On
:arrow: STATUS: Release v1.0 (fully tested)

:arrow: BACKGROUND
In the past, EverQuest servers informed the client of the player character's actual experience points at any given time. This information could be extracted from the datastream (and probably from the _CHARINFO struct) and displayed by third party software, most notably by SEQ. Since then, the protocol has been modified to remove such detailed experience information from the client memory space and datastream. Instead, the actual value was replaced by a pseudo-percentage, based on 330 instead of 100. So if you are at 0% of your level, your experience will internally be reported as 0. If you are at 99%, it will be reported as 329. Attaining one more point will ding, and will reset the counter back to 0. This is true both for regular level experience and for alternate advancement (AA) experience. It is theorized that using a 330-based system is probably related to the way in which the new UI draws its graphical elements.

While this means we can't extract your actual experience points from anywhere, it is my opinion that having a resolution of 330 instead of 100 is useful for gameplay in that it gives you slightly more information regarding how much experience a given mob or set of mobs is giving you. This lets you more accurately examine your experience rate, particularly as it pertains to situations like charming, dire charming, and power leveling. Therefore, I have created this add-on for MacroQuest that provides an automatic system for reporting experience gain based on the higher-resolution 330 value, and an extra command for the arbitrary reporting of your current experience values.

I add this code into every new version of the MacroQuest code, as I find it very useful. Whether this should be added to the official codebase is up for debate. If enough people are interested in adding this feature to the codebase, perhaps it should be implemented as:

a) An option in macroquest.ini, such as

Code: Select all

[MacroQuest]
ExpChecker=1
and b) a command-controlled toggle, such as

Code: Select all

/expchecker on
Experience checker has been ENABLED.
For more information on the history of EverQuest's experience reporting system, please see these threads on the SEQ boards:

http://seq.sourceforge.net/forums/showt ... readid=168
http://seq.sourceforge.net/forums/showt ... eadid=1896
http://seq.sourceforge.net/forums/showt ... eadid=1233

:arrow: USAGE
The code checks 5 times a second for any changes to your current level experience and AA experience. It does this by comparing your current experience values to globally saved values. When you first zone into the game, it sets the global values. If you want to see your current experience, or if you suspect that the global experience values have been corrupted somehow (this is not possible unless MQ's mainloop somehow stops working), you may run the /exp command. This displays your current experience, and forces the global values to reset.

(Note that this code requires an accurate _CHARINFO struct, and an accurate CharInfo offset. However, if either of these things are not correct, this code most likely will not generate any crashes. It will just behave rather oddly. Only if the CharInfo offset is somehow outside of eqgame.exe's memory space will this cause an actual crash.)


:arrow: CODE MODIFICATIONS


MQ.h ------

In the DLL export function prototype section, add:

Code: Select all

extern "C" EQLIB_API VOID  Exp            (PSPAWNINFO, PCHAR);

EQLib.cpp ------

Add these two global variables:

Code: Select all

DWORD global_exp, global_aaexp;
Somewhere before InsertCommands, add this prototype:

Code: Select all

VOID ExpChecker();
In InsertCommands, change

Code: Select all

	while (!gbUnload) {
		Sleep(1000);
	}
to

Code: Select all

	while (!gbUnload) {
		Sleep(200);
		ExpChecker();
	}
In CChatHook::Detour, immediately following this code:

Code: Select all

			if (gZoning) {
				gDelayZoning += gZoneDelay;
				gZoning=FALSE;
			}
but before this code:

Code: Select all

			gbInGame=TRUE;
add:

Code: Select all

			if (!strncmp(szMsg,"You have entered ",17) && !gbInGame) {
				PCHARINFO pCharInfo = NULL;
				if ((pCharInfo = GetCharInfo()) != NULL) {
					global_exp = pCharInfo->Exp;
					global_aaexp = pCharInfo->AAExp;
				}
			}
In DetermineWhoFirst, add this token to the list:

Code: Select all

		{ "/exp",		CMD_MQ },
In TakeControlOfCommandList, add this token to the list:

Code: Select all

		{"/exp",		"Exp"},
Add these two functions anywhere:

Code: Select all

VOID Exp(PSPAWNINFO pChar, PCHAR szLine)
{
	CHAR szBuffer[MAX_STRING] = {0};
	PCHARINFO pCharInfo = NULL;
	if (NULL == (pCharInfo = GetCharInfo())) return;

    sprintf(szBuffer, "You currently have %ld experience and %ld AA experience.", pCharInfo->Exp, pCharInfo->AAExp);
	WriteChatColor(szBuffer, USERCOLOR_DUELS);
	global_exp = pCharInfo->Exp;
	global_aaexp = pCharInfo->AAExp;
}

VOID ExpChecker()
{
	CHAR szBuffer[MAX_STRING] = {0};
	PCHARINFO pCharInfo = NULL;
	long expdiff = 0, aaexpdiff = 0;

	if (!gbInGame) return;

	if (NULL == (pCharInfo = GetCharInfo())) return;

	if (pCharInfo->Exp > global_exp) expdiff = pCharInfo->Exp - global_exp;
	if (pCharInfo->Exp < global_exp) expdiff = (330 - global_exp) + pCharInfo->Exp;
	if (pCharInfo->AAExp > global_aaexp) aaexpdiff = pCharInfo->AAExp - global_aaexp;
	if (pCharInfo->AAExp < global_aaexp) aaexpdiff = (330 - global_aaexp) + pCharInfo->AAExp;

	if (expdiff > 0 && expdiff < 340) {
		sprintf(szBuffer, "You have gained %ld experience points.", expdiff);
		WriteChatColor(szBuffer, USERCOLOR_DUELS);
	}
	if (aaexpdiff > 0 && aaexpdiff < 340) {
		sprintf(szBuffer, "You have gained %ld AA experience points.", aaexpdiff);
		WriteChatColor(szBuffer, USERCOLOR_DUELS);
	}

	global_exp = pCharInfo->Exp;
	global_aaexp = pCharInfo->AAExp;
}

:arrow: CONCLUSION
I have tested this code fairly conclusively, and all appears to work correctly. The SEQ forums report behavior where every time you zone you appear to receive something like negative two million experience points. This is probably the server sending the absolute experience value to the client. In the future, with more analysis, we may be able to make use of this value in some way. For now, the code simply supresses the value, and only reports ordinary experience gains in the 330-resolution frame.

If you have questions, comments, additions or bugs, please post them in this thread.

Posted: Sun Apr 20, 2003 3:25 am
by keflex
Wonderful work! :D

Posted: Sun Apr 20, 2003 5:34 am
by Clawed
:arrow: SIDEBAR
To forestall any discussion about this, I am aware that similar functionality could be created using a macro and $char(exp)/$char(aa,exp). However, there are two reasons I feel hardcoding this is superior:

a) You can run other macros and still get handy exp reports
b) Parsing a macro is way slower and more resource-intensive than hardcoding it in C++

Posted: Tue Apr 22, 2003 12:28 pm
by loadingpleasewait
I dunno, too much work.. = )

I just add this to my fight macro which fires off when the event "experience" shows up..

Code: Select all

sub event_exp
/delay 1s
|/echo EVENT FIRED OFF
/varset v57 $char(exp)
/varcalc v58 $v57-$v56
/varcalc v60 100-$char(exp)
/varcalc v59 $v60/$v58
  /if n $v58>0 {
  /echo and you've gained $v58% exp, bringing your total exp to $char(exp)%!!
  /echo Don't forget, $v60% left till level, with only $v59 of these mobs to kill.
  /varset v73 0
  } else {
  /echo Exp Gain Not Displayed with This Kill, Sorry.. yer still at $char(exp)% sorry..
  /varadd v73 1
  /echo exp has not been calculated for $v73 kill(s).. = (  
  }
  /varset v56 $char(exp)
/delay 1s
|/echo EVENT FIRED OFF
/varset v67 $char(aa,exp)
/varcalc v68 $v57-$v56
/varcalc v70 100-$char(aa,exp)
/varcalc v69 $v60/$v58
  /if n $v68>0 {
  /echo and you've gained $v68% AAxp, bringing your total AAxp to $char(aa,exp)%!!
  /echo Don't forget, $v70% left till next aa point, with only $v59 of these mobs to kill.
  /varset v83 0
  } else {
  /echo AAxp Gain Not Displayed with This Kill, Sorry.. yer still at $char(aa,exp)% sorry..
  /varadd v83 1
  /echo AAxp has not been calculated for $83 kill(s).. = (  
  }
  /varset v66 $char(aa,exp)
/return
and it works fine (tho I just added the AAxp part <untested aaxp as I didnt know there was a $char(aa,exp)>) :D


BTW, long time, /em is back.. thanks to the fine friend of mine who actually had a copy of VC6 8)

Posted: Tue Apr 22, 2003 12:34 pm
by Mckorr
Maybe in addition to a Macro Depot we need a board for MQ code bytes such as this. Especially if Clawed pushes through his modular MQ approach :lol:

Posted: Tue Apr 22, 2003 3:47 pm
by L124RD
Salutations,
Well, if we're going to modulate it, lets modularize the whole dagnab thing so it can easily be ported to a new system
macroquest.ini wrote:[System]
Offsets=eqgame.ini
CommandHooker=eqcommandhook