Another day, another stab at ISXEQ

Discussion of Inner Space

Moderators: Lavish Software Team, Moderators

Post Reply
jabon
Non-Subscriber
Posts: 17
Joined: Tue Aug 31, 2004 8:19 pm

Another day, another stab at ISXEQ

Post by jabon » Wed Feb 16, 2005 2:33 pm

I want to do this, I think I can do it. I have an intermediate knowledge of C++ as well as experience with PHP and Java and a spattering of assembly, so I can navigate your code. What I am having trouble with is understanding Macroquest's API sufficiently to trace the program logic and convert it to IS's API. So that I can know I am not wasting time doing things out of order I have the following questions, some of which I've asked before.

Do the DataTypes and TLO's need to be converted before the plugins? (I know it probably depends on which plugin, for now I'm working on the Map so just consider that particular plugin)

Could you provide a walkthrough of logic in how /keypress and /bind were converted, assuming that the same process applies to all command covertions. (I need to know where I need to change syntax and naming conventions, where stuff overlaps, why 'pISInterface->AddCommand' is being overridden by 'COMMAND(name,cmd,parse,hide)')

What exactly are Services in IS? Are they the new ...API (MQ2MapAPI, PLUGIN_API)? There doesn't seem to be a clear overlap, which is good since IS is supposed to be a rethinking, I just need a map going from the old to the new.


The more verbose you can be the better, as once I am done I intend to write documentation for everything I do. I'm a writer/designer first and a coder second, so I think I can write pretty good technical documentation for the audience you're going for. You don't seem to be too interested in the MQ2 codebase anymore, relying on us to bring it into IS, that just isn't going to happen so long as people like me keep having to fight the code.

Lax
Owner
Posts: 6634
Joined: Fri Jun 18, 2004 6:08 pm

Post by Lax » Wed Feb 16, 2005 4:49 pm

99% of the MQ2 codebase can be reused in ISXEQ. Some parts are stripped out, mostly to do with plugins as well as the stuff covered by LavishScript (commands, datatypes, TLOs).

Services are basically dynamic callbacks. Instead of the "OnPulse" function in a plugin, for example, there is a system service called "Pulse". To use Pulse, you make a message handler for that service, and request a connection to the service. If successful (e.g. the service exists and the plugin is not already connected to it), the service master is notified of the connection (and also of the disconnection later). Just one of the major benefits to this is that services can clean up after sloppy plugins automatically -- for example, if you forget to remove a detour, the memory service removes it for you because it is notified of your plugin's disconnection. Services can have any number of associated messages, but always provide some internal messages such as client connect and disconnect. They can be shared with other extensions simply by sharing the messages and the data each needs (if you follow my example in isxdk, you can provide static functions or define macros to handle all service requests).

Datatypes and TLOs are not generally required by plugins, so you can actually convert MQ2Map before anything else if you wish.

As far as commands, there are very few differences between MQ2's /keypress and ISXEQ's. WriteChatf and/or WriteChatColor are converted to printf, which is actually routed to pISInterface->Printf via a #define macro. It *can* actually use WriteChatf/WriteChatColor and that also prints to the console, I just hadnt written it at the time. Also a chat extension needs to be made to do the chat window via the "EQ Chat Service" service. Anyway past that, it's all about parsing. MQ2's commands all had to do their own command parsing, but with IS using LavishScript it's all parsed already.

MQ2:

Code: Select all

VOID DoMappable(PSPAWNINFO pChar, PCHAR szLine)
{
	if (szLine[0]==0)
	{
		SyntaxError&#40;"Usage&#58; /keypress <eqcommand|keycombo> [hold|chat]"&#41;;
		return;
	&#125;
	CHAR szArg1[MAX_STRING]=&#123;0&#125;;
	CHAR szArg2[MAX_STRING]=&#123;0&#125;;

    GetArg&#40;szArg1,szLine,1&#41;;
    GetArg&#40;szArg2,szLine,2&#41;;
	BOOL Hold=&#40;stricmp&#40;szArg2,"hold"&#41;==0&#41;;

	if &#40;!PressMQ2KeyBind&#40;szArg1,Hold&#41;&#41;
	&#123;
		int N=FindMappableCommand&#40;szArg1&#41;;
		if &#40;N>=0&#41;
		&#123;
			ExecuteCmd&#40;N,1,0&#41;;
			if &#40;!Hold&#41;
				ExecuteCmd&#40;N,0,0&#41;;
			return;
		&#125;
		KeyCombo Temp;
		if &#40;ParseKeyCombo&#40;szArg1,Temp&#41;&#41;
		&#123;
			if &#40;!stricmp&#40;szArg2,"chat"&#41;&#41;
			&#123;
				pWndMgr->HandleKeyboardMsg&#40;Temp.Data[3],1&#41;;
				pWndMgr->HandleKeyboardMsg&#40;Temp.Data[3],0&#41;;
			&#125;
			else
			&#123;
				MQ2HandleKeyDown&#40;Temp&#41;;
				if &#40;!Hold&#41;
					MQ2HandleKeyUp&#40;Temp&#41;;
			&#125;
			return;
		&#125;

		MacroError&#40;"Invalid mappable command or key combo '%s'",szArg1&#41;;
		return;
	&#125;
&#125;
ISXEQ:

Code: Select all

int CMD_Keypress&#40;int argc, char *argv[]&#41;
&#123;
	if &#40;argc<2&#41;
	&#123;
		printf&#40;"Syntax&#58; %s <eqcommand|keycombo> [hold|chat]",argv[0]&#41;;
		return 0;
	&#125;
	bool bHold=false;
	bool bChat=false;
	if &#40;argc==3&#41;
	&#123;
		if &#40;!stricmp&#40;argv[2],"hold"&#41;&#41;
		&#123;
			bHold=true;
		&#125;
		else if &#40;!stricmp&#40;argv[2],"chat"&#41;&#41;
		&#123;
			bChat=true;
		&#125;
	&#125;
	if &#40;!PressMQ2KeyBind&#40;argv[1],bHold&#41;&#41;
	&#123;
		int N=FindMappableCommand&#40;argv[1]&#41;;
		if &#40;N>=0&#41;
		&#123;
			ExecuteCmd&#40;N,1,0&#41;;
			if &#40;!bHold&#41;
				ExecuteCmd&#40;N,0,0&#41;;
			return 0;
		&#125;
		KeyCombo Temp;
		if &#40;ParseKeyCombo&#40;argv[1],Temp&#41;&#41;
		&#123;
			if &#40;bChat&#41;
			&#123;
				pWndMgr->HandleKeyboardMsg&#40;Temp.Data[3],1&#41;;
				pWndMgr->HandleKeyboardMsg&#40;Temp.Data[3],0&#41;;
			&#125;
			else
			&#123;
				MQ2HandleKeyDown&#40;Temp&#41;;
				if &#40;!bHold&#41;
					MQ2HandleKeyUp&#40;Temp&#41;;
			&#125;
			return 0;
		&#125;

		printf&#40;"Invalid mappable command or key combo '%s'",argv[1]&#41;;
		return -1;
	&#125;
	return 0;
&#125;
Checking for "chat" was moved to the same spot "hold" is looked for, and the parameter parsing is already done.. so instead of using GetArg 1 and 2, it simply checks if the number of arguments is 3 (command arg1 arg2) and checks if the extra parameter is chat or hold.

The bind command is identical other than parameter parsing. Parameter parsing is the ONLY necessary change in nearly every command, datatype, and TLO.

The command list file uses lines like this...

Code: Select all

COMMAND&#40;"Keypress",CMD_Keypress,true,false&#41;;
COMMAND&#40;"EQExecute",CMD_EQExecute,true,false&#41;;
COMMAND&#40;"MQ2Bind",CMD_MQ2Bind,true,false&#41;;
So that the file itself can be used like this..

Code: Select all

#define COMMAND&#40;name,cmd,parse,hide&#41; EQLIB_API int cmd&#40;int argc, char *argv[]&#41;
#include "ISXEQCommandList.h"
#undef COMMAND
And also in the Command API like this...

Code: Select all

#define COMMAND&#40;name,cmd,parse,hide&#41; pISInterface->AddCommand&#40;name,cmd,parse,hide&#41;
#include "ISXEQCommandList.h"
#undef COMMAND
.
.
#define COMMAND&#40;name,cmd,parse,hide&#41; pISInterface->RemoveCommand&#40;name&#41;
#include "ISXEQCommandList.h"
#undef COMMAND
This way, each command only needs to be listed exactly once, and the file is included multiple times, each for different uses. Once defines them for external use, one adds them to the command list, and another removes them from the command list. This is preferable, when working with many commands, than having to type the command information 3 times. If you want to remove a command, simply comment it out of the list file and all traces other than the function itself are removed... if you want to add a command, create the function and add it to the list file, and it's automatically set up. It's just easier to work with. "hide" will just hide the command from the "commands" command list. "parse" is like MQ2 and allows ${} to be parsed.

As far as converting MQ2 plugins to IS extensions, though, I haven't created a "MQ2Plugin.h" type of thing yet, which would set up the extension to use the MQ2 API exported from ISXEQ, as well as helpers for using the services provided by ISXEQ (mkplugin could also be updated to create ISXEQ stuff). Feel free to give it a shot. Most of the work converting MQ2Map would be creating the extension itself, converting the commands and TLO, and converting the PLUGIN_API stuff to use services. SetGameState will use "EQ Gamestate Service", the zone stuff uses "EQ Zone Service", spawn and grounditems use "EQ Spawn Service" and the UI uses "EQ UI Service". Other than that, the same MQ2Map code can be used. In fact, I would make it a point to use the original .cpp and .h files wherever possible, so that any necessary updates for ISXEQ (and ISXEQMap, etc) are already done when MQ2 is updated.

Lax
Owner
Posts: 6634
Joined: Fri Jun 18, 2004 6:08 pm

Post by Lax » Wed Feb 16, 2005 4:54 pm

Oh, I forgot to mention.

In MQ2, "MacroError" and "FatalError" end macros. They can still be used to automatically color things red and display errors, but the return value from the command is actually what dictates what will end scripts in IS. Returning <0 indicates an error that must end the script. Returning anything >=0 will allow the script to continue. You should plan for the possibility that the integer return values from functions may play a part in future scripts (unless it returns <0, which would end the script).

Post Reply