Developing Add-Ons for CC3+ – Part 4: Interacting with CC3

This is the fourth article in my series about XP development. To understand this article properly, you should be familiar with the contents of the previous articles.

In this article, I’ll be taking a closer look at how to interface with some of CC3+’s own functionality, in this case how to set CC3+ variables and how to call native CC3+ commands from an XP. I’ll be showing you how to use the SetVar and ExecScriptCopy API calls.

For all the examples below, I do assume that you know how to declare the functions in a .h file, and turn them into actual CC3+ commands by adding a new command to the CList array and the reference to the function to PList, if not, you should re-read the previous articles in this series.

You can also download my example code.

Setting Variables

In contrast to C++, variables in CC3+ doesn’t have a type. In practice, this means that we need to handle all CC3+ variables as a null-terminated character array (C-string). Other than that, handling them is pretty straight-forward. Note that even if we treat the variables as strings in our XP, CC3+ happily uses them as numeric where that is appropriate.

Let us start with a simple example

void XPCALL SimpleVar() {
	char vName[] = "TestVariable";
	char vValue[] = "25";
	SetVar(vName, vValue);
	CmdEnd();
}

Not much to describe here, this function declares two strings, and then use the SetVar() function to create a variable named TestVariable with a value of 25. After running this command in CC3+, you can use the LISTVARS command inside CC3+ to verify that the new variable has been set, or you can use it as an argument to the command line, for example the command TSPECH TestVariable will set the text height for new text entities to 25.

Let’s get a little more advanced. This time with an example that asks the user for the name of the variable, and then counts the number of sheets in the drawing and store the result in the supplied variable. Refer back to part 2 and part 3 of this series for more information about walking the drawing list and requesting information from the user.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
char varName[512];
int sCount;
FORMST(lpszVarName, "Variable Name:\0")
RDATA varReq =
{ sizeof(RDATA), RD_TxWord, NULL, RDF_C, (DWORD*)&varName,
  (DWORD*)&lpszVarName,RDC_NONE, CountSheetsReqCallback, NULL, NULL, 0, NULL, 0 };

void XPCALL CountSheets() {

	ReqData(&varReq);
}

void XPCALL CountSheetsReqCallback(int Result, int Result2, int Result3) {

	UNREFERENCED_PARAMETER(Result2);
	UNREFERENCED_PARAMETER(Result3);

	if (Result != X_OK) { CmdEnd(); return; }

	sCount = 0;
	DLScan(0, CountSheetsScanCallback, DLS_SHEET, 0, 0);

	char cnt[3];
	_itoa_s(sCount, cnt, 3, 10);
	
	SetVar(varName, cnt);
	
	CmdEnd();
}

pENTREC XPCALL CountSheetsScanCallback(void* hDList, const pENTREC pEntRec, const DWORD parm1, DWORD parm2) {

	UNREFERENCED_PARAMETER(parm1);
	UNREFERENCED_PARAMETER(parm2);
	UNREFERENCED_PARAMETER(hDList);

	const auto sheet = XP_SHEET_CAST(pEntRec);

	if (sheet) {
		sCount++;
	}

	return 0;
}

lines 1-6 sets up the variables, the FormSt packet and data request we need

lines 8-11 is the function being called by our CC3+ command. It doesn’t do anything but doing a data request, which will cause the callback function being called

lines 13-29 is the callback function for the data request. The interesting part here is line 21 which initiates the scan of the drawing list, which results in the callback being called once for each entity in the list. Note that the scan has the DLS_SHEET flag set, which will cause it to include the sheets as regular entities in the drawing list instead of scanning their sublist. Lines 23-24 converts the sheet count to a string representation, and finally line 26 does the actual call that stores the count under the variable name we requested from the user.

lines 31-44 is the callback for the drawing list scan. It checks each entity if it is of type sheet, and if so, increment our counter by 1.

As earlier, if we ran this command in CC3+ and supplied the variable name of SHEETCOUNT we can use LISTVARS to see that the variables has been set. Remember that the Common Sheet isn’t an actual sheet at all, and will not be included in the count.

Running CC3+ Commands

Often, CC3+ does have an exact command for what you want to do, the question is just how to call it from an XP. There are actually a couple of ways to do this, but the simplest way is to use ExecScriptCopy. This command takes a simple string of commands (as in a macro or script file) and just runs them just as if you had typed them on the CC3+ command line. There is also a version of this command, ExecScriptFile that loads the commands from a script file instead and executes that (equivalent of the SCRIPT command in CC3+)

This is also an easy command to use, as shown in the following basic example

void XPCALL SimpleCommand() {
	ExecScriptCopy("LINE 0,0 100,100;;");
	CmdEnd();
}

This example simply draws a line from 0,0 to 100,100, just as would happen if you typed the same on the CC3+ command line directly. This example only have a single command, but you can have multiple, just separate them with ;. You probably already know that you can put many commands on the same line in a macro using the same trick, and if you have input in a macro that can contain spaces, you also need to use ; as a separator, basically the ; separator is the same as pressing enter on the command line. You can even use it all the time if you prefer, the avove line could have been written like this instead:

ExecScriptCopy("LINE;0,0;100,100;;");

You have probably noticed the double ; at the end. This is not a general requirement, but if you have used the LINE command, you may remember that it keeps asking you for segments until you cancel the command. Giving it a blank input is one way of cancelling this command, and that is what the final ; does in this case. For commands that does not need to be terminated like this, you should only have a single ;, because a blank input on the CC3+ command line when it is waiting for a new command means to repeat the previous command. This is the same things that happens if you have an extra blank line in a macro.

For slightly more advanced example, we’ll go for a command that asks for some text, then sets the text properties to write huge text (the map height) and output the text the user provided. This is all done by calling on built-in commands in CC3+ instead of trying to manipulate entities from the XP.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
char outputText[512];
FORMST(lpszText, "Text:\0")
RDATA varReq2 =
{ sizeof(RDATA), RD_TxWord, NULL, RDF_C, (DWORD*)&outputText,
  (DWORD*)&lpszText,RDC_NONE, ExecuteCommandCallback, NULL, NULL, 0, NULL, 0 };

void XPCALL ExecuteCommand() {

	ReqData(&varReq2);
}

void XPCALL ExecuteCommandCallback(int Result, int Result2, int Result3) {

	UNREFERENCED_PARAMETER(Result2);
	UNREFERENCED_PARAMETER(Result3);

	if (Result != X_OK) { CmdEnd(); return; }

	std::string command = "GETEXTY var1;";
	command.append("TSPECJ 0;");
	command.append("TSPECH var1;");
	command.append("GOSHEET TEXTNEWSHEET;");
	command.append("TEXM ");
	command.append(outputText);
	command.append(";0,0;");

	char cmd[512];
	strcpy_s(cmd, 512, command.c_str());

	ExecScriptCopy(cmd);
	
	CmdEnd();
}

lines 1-5 is variable, FormSt and Request setup, like in our variable example.

lines 7-10 is the function we call with our CC3+ command, and as previously, it does nothing but issue a request for data, leading to our callback method being called.

lines 12-33 is our callback method

lines 19-25 builds up a string with out commands. The final function call needs a C-String, but it is easier to build the commands this way. I’ve deliberately appended the commands one by one just to make the whole thing clearer, but there are obviously no requirements for doing it like this. Basically, line 19 includes the GETEXTY command which gets the height of the map, and stores in in the specified variable (which I called var1 here). Line 20 sets text alignment to bottom right, and line 21 sets the text height. Instead of using an absolute value, I take advantage of the variable from line 19 here. Line 22 just makes a new sheet and goes to it, this new sheet will appear at the bottom of the list, and thus, anything we put on it will be on top of everything else (If the specified sheet already existed, we would just have changed to that instead of creating a new one). The odd lines out here are 23-25 because this is a single command split over three lines. Again, there are other ways to do this, but I did it to get the text the user specified at the prompt into the command line. Be careful about spacing/separators here.

lines 27-28 just takes our string and converts it to a null-terminated C-string.

line 30 then actually calls the ExecScriptCopy function with our list of commands. and CC3+ runs it. If all the commands are correct, it should run without issue.

Combining the Examples

Just to round this off, we’ll write a small function that combines the two examples. It will get the number of sheets in the drawing, and then feed this into the second command so it will write this number in large letters all over our map.

void XPCALL WriteSheetNumber() {
	ExecScriptCopy("COUNTSHEETS v1;WRITEBIGTEXT v1;");
	CmdEnd();
}

This function takes advantage of the ExecScriptCopy function we just learned about to call our two new commands. First, it calls our COUNTSHEETS command. As you may remember, this asks for a variable name to store the number of sheets in, so we give it the name v1 to use as a variable. Then, we call WRITEBIGTEXT, again with v1 as the argument. Now, this second function doesn’t ask for a variable, but remember that in CC3+, you can always supply a variable instead of the requested input, and CC3+ will take the input from the variable instead, so what is happening here is that v1 will expand to the content of the variable, which by the time it gets around to this command, will contain the number of sheets in the drawing.

Reading Variables

There is no ready to use API call for reading a variable from the CC3+ environment so it is slightly more tricky than setting one as we did earlier. But, thanks to what we just learned abut ExecScriptCopy, we can access the variable by having CC3+ respond to a request with them. This is a very simple example for doing this. Note that the variable name is hardcoded to read the variable we set using our first SetVar example, but we’ve already discussed above how to read in a variable name from the command prompt if we wish to ask the user, or perhaps we pull it from somewhere else. Note that this will only work if the variable in question has been set, if not it will treat the variable name as text. If we asked for anything but text, this will mean that we get a non-OK status code, and the callback method will just abort gracefully and silently, but if we asked for any kind of text, then we would end up storing the variable name as that text.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int theNumber;
FORMST(lpszValPrompt, "Value:\0")
RDATA valReq =
{ sizeof(RDATA), RD_Int4, NULL, RDF_C, (DWORD*)&theNumber,
  (DWORD*)&lpszValPrompt,RDC_NONE, ReadVarCallback, NULL, NULL, 0, NULL, 0 };

void XPCALL ReadVar() {

	ExecScriptCopy("TestVariable;");
	ReqData(&valReq);
}

void XPCALL ReadVarCallback(int Result, int Result2, int Result3) {
	UNREFERENCED_PARAMETER(Result2);
	UNREFERENCED_PARAMETER(Result3);

	if (Result != X_OK) { CmdEnd(); return; }

	theNumber++;

	char vName[] = "TestVariable2";
	char vValue[4];
	_itoa_s(theNumber, vValue, 4, 10);

	SetVar(vName, vValue);

	CmdEnd();
}

Nothing really new in the code above, but notice lines 9 and 10. Here we first run a new command, which you may notice is our test variable from the very first example. Assuming this is set, it gets converted into it’s value (25) when sent to the command line. Now, the command “25” doesn’t mean anything to CC3+, but if you look at the next line, we are immediately requesting some data. This data will be taken from the command buffer which we just filled with the number 25 above. So here, the command won’t be interpreted as a command at all, but rather as data to our request. So, instead of the user getting a prompt to supply the data, CC3+ will just use what we just sent it.

So, assuming we first ran our SimpleVar command to set the test variable (or set it ourselves manually), running our new ReadVar command should cause another variable to be set in CC3+, this one at a one higher number than the one we read in, just to demonstrate manipulation from code. Obviously, if we just needed it for our use in the XP, we might not need to send it back to CC3+ at all, that’s just proof-of-concept code.

Help and Discussions

The ProFantasy Community Forum has a dedicated category for Macros and XPs. I recommend you post any questions you may have over here. Comments on this blog entry is not a good place to get help, in part because I don’t get alerted when new comments are posted, and I don’t go around checking old posts for comments.

Leave a Reply