The
  Way Of
    The Wombat
Functions

Contents

5.0 Functions
5.1 Using Functions
5.2 Nested Functions
5.3 User-Defined Functions
5.4 Function-based Code
5.5 Events as Functions
5.0 - Functions

Activision Function reference

You may have noticed in the last few chapters, functions have been creeping in slowly so that I can achieve something with my code. Functions are really the things that have greatest effect on the outcome of the code. If events provide the input, the x on which to fire, then functions provide the output, the y which you make happen.

Functions come in two types, as I believe they do in C. Integer functions (int_f) return a number, and void functions (void_f) don't return anything, they just execute some action.

I think that in simple terms, with SLIC as a way to code reactions to events, the void_f functions will be the reaction, and the int_f functions will be a way of narrowing down where the reaction occurs.



5.1 Using Functions

In reality, it is nearly impossible to code anything without using functions, but some code is more based on functions than others. Going back to this example:

HandleEvent(SleepUnit) 'kill_the_unit' pre { unit_t tmpUnit; tmpUnit = unit[0]; player[0] = unit[0].owner; if(player[0].cities > 10){ KillUnit(tmpUnit); for(i = 0; i < player[0].cities; i = i + 1){ city_t tmpCity; GetCityByIndex(player[0], i, tmpCity); if(CityHasBuilding(tmpCity, "IMPROVE_SILO")){ AddGold(player[0], 5000); } else { AddGold(player[0], 1000); } } } else{ Heal(tmpUnit); } }

In the emboldened section are the functions GetCityByIndex, CityHasBuilding and AddGold.
GetCityByIndex and AddGold are void_f functions, they do not return any integers, they just react.

AddGold is a simple action function, you input a player and a value, and it adds 'value' amount of gold to the player's treasury.

GetCityByIndex is more complicated, though it doesn't return a number, it does act as if it did. What it does is return a city, in the form of a variable defined beforehand, in this case tmpCity. tmpCity can now be used inside the for loop to act on that city number i. After the for loop, it will only apply to the last city found in the for loop: the most recent city that player has acquired by settlement or conquest.

CityHasBuilding is an int_f function, so is usually used in a if statement.
IF(CityHasBuilding(tmpCity, "IMPROVE_SILO")) THEN { do something. Just like normal IF statements, the thing within it has to be testable true or false, and return a 1 or 0. If the building is present, a 1 is returned, the if statement is completed, if the building is not present a 0 is returned, and the if statement is skipped. Any elseif statements would be tested, or in this case, the else statement is followed.

The int_f has limited the number of cases where the reaction (AddGold) applies.

NB: Usually when you want to identify a building, you would use BuildingDB(IMPROVE_SILO) as with the UniDB on the previous page, but this function is a hangover from CtP1, so uses the string form of the building.



5.2 Nested Functions

It is possible to use functions inside other functions to save the trouble of defining and using variables all the time. Here I have used "1" to indicate that the player is player 1. tmpPlayer, player[0] or any other variable within range would be equally valid.
eg.

city_t tmpCity; GetCityByIndex(1, random(Cities(1)), tmpCity);
This will get a city belonging to player[0] with a random index number. The random number is chosen from between 0 and player 1 cities - 1, so will always return a valid city. The city is then stored as tmpCity.
This saved the bother of storing Cities(1) as a variable, then finding random(theVariable) and storing that, and then plugging that into the GetCityByIndex function.



5.3 User-Defined Functions

As well as the built-in functions, SLIC allows you to define your own functions and use them in handlers.
A good example of a custom function is Dale's NearestCity function. This function is used inside the main Withdraw script handler to find the nearest city that the aeroplane should return to.


int_f WD_NearestCity(location_t tLoc,int_t tPlayer){ int_t tmpPlayer; int_t i; int_t min; int_t val; int_t city; int_t cities; location_t tmpLoc; city_t tmpCity; tmpPlayer = tPlayer; tmpLoc = tLoc; min = 10000; city = 0; cities = Cities(tmpPlayer); for(i = 0; i < cities; i = i + 1) { GetCityByIndex(tmpPlayer, i, tmpCity); val = SquaredDistance(tmpCity.location, tmpLoc); if(val < min) { min = val; city = i; } } RETURN city; }
You can see how it is defined in a similar way to Event Handlers. The first thing to enter is whether it is an int_f function or a void_f function. As explained earlier, int_f functions have to RETURN a value to their original context, whereas void_f functions do not.

Then the name is written. This can be anything you like, except for a name already used by a function, event handler or variable. I would advise you to make it easy to spell and short enough to remember. If you are mixing SLICs, add a prefix on it to ensure it doesn't have the same name as any other.

After this comes the parenteses with the types of variables you are expecting when the function is called. In this instance, the function requires a location followed by a player in order to run properly.

Once the function has found the value you want, you can put RETURN int; and it will return that. If you are using teh function as a validity test, this could just be a 1 or 0 to indicate true or false to an IF statement, or it could be a player number, or in this case, a city index number. This function returns the index number of the city belonging to thePlayer, closest to theLoc.

Inside another block of code (handler or another user-defined function), the name of the function triggers the function, so putting it into the example handler from above:

HandleEvent(SleepUnit) 'kill_the_unit' pre { unit_t tmpUnit; tmpUnit = unit[0]; player[0] = unit[0].owner; if(player[0].cities > 10){ KillUnit(tmpUnit); for(i = 0; i < player[0].cities; i = i + 1){ city_t tmpCity; GetCityByIndex(player[0], i, tmpCity); if(CityHasBuilding(tmpCity, "IMPROVE_SILO")){ AddGold(player[0], 5000); } else { AddGold(player[0], 1000); } } int_t i; location_t tmpLoc; tmpLoc = unit[0].location; i = WD_NearestCity(tmpLoc, player[0]); GetCityByIndex(player[0], i, tmpCity); tmpLoc = tmpCity.location; CreateUnit(player[0], unit[0].type, tmpLoc, 0); } else{ Heal(tmpUnit); } }

Note also in the above example, I have redefined tmpLoc to a different location having used it. This means I don't need two different location variables.

That is an example of using a new function as a time and space saving device inside a handler. It is especially useful when you need to do a fairly large piece of code many times. You can simply call the function whenever you need to, in place of a large block. In some cases, the majority of the code can be made into one function, and simply called on half a dozen different events.



5.4 Function-Based Code

The above example is an example of using a new function as a time and space saving device inside a handler. But this is especially useful when you need to do a fairly large piece of code many times. You can simply call the function whenever you need to, in place of a large block. In some cases, the majority of the code can be made into one function, and simply called on half a dozen different events.

One of the best examples of this type of code is Locutus' Capitol Code for the MedMod. He uses a function to rebuild a capitol, and the handlers merely as triggers to run the function.

I have simplified this slightly for demonstration purposes.

// function that actually finds a replacement city // for the capital and makes the AI build a new capitol int_f MM2_CreateCapital(int_t thePlayer) { city_t tmpCity; city_t largestCity; int_t i; int_t size; int_t tmpPlayer; tmpPlayer = thePlayer; player[0] = tmpPlayer; if (player[0].cities > 1) { size = -1; for (i = 0; i < player[3].cities; i = i + 1) { GetCityByIndex(tmpPlayer, 0, tmpCity); if (tmpCity.population > size ) { largestCity = tmpCity; size = largestCity.population; } } if (size > -1) { ClearBuildQueue(largestCity); AddBuildingToBuildList(largestCity, BuildingDB(IMPROVE_CAPITOL)); Event:BuyFront(largestCity); } } }
and the handlers that fire it when a capitol is destroyed:

// detect the capture of a Capital // and call CreateCapital function HandleEvent(CaptureCity) 'MM2_CapitalCaptured' pre { int_t tmpPlayer; city_t tmpCity; tmpCity = city[0]; tmpPlayer = tmpCity.owner; player[3] = tmpPlayer; if (CityHasBuilding(tmpCity, "IMPROVE_CAPITOL")) { if (!IsHumanPlayer(tmpPlayer)) { MM2_CreateCapital(tmpPlayer); } } } //ditto for // KillCity event, // GiveCity event, // DisbandCity event, and // CreatePark event.

With the function: 24 + 5*12 = 84 lines
Without function: 5*(12+24 - overlap(3)) = 115 lines
And obviously the longer the function, the more worthwhile it is to take it out of the main handlers.

Using functions separate from the main code means that the code is only written out once, rather than once for each event. This makes the file shorter, easier to manage and debug, and not as spacious for uploading or downloading.


5.5 Events as Functions

Activision Events Listing

As well as using events to trigger reaction code, events can be forced to happen, just as functions can cause things to happen.
eg.
Taken a bit from example handler.

int_t i; location_t tmpLoc; tmpLoc = unit[0].location; i = GetNearestCity(tmpLoc, player[0]); GetCityByIndex(player[0], i, tmpCity); tmpLoc = tmpCity.location; CreateUnit(player[0], unit[0].type, tmpLoc,0, tmpUnit); Event:EntrenchUnit(tmpUnit);
This forces the game to create a fortify event, as if someone pressed the F button.
Most events in the events listing can be used like this. The ones that can't are the ones where one of the variables has a GEA_ prefix in the list. These variables are unit paths and trade routes, and are too complicated to define in SLIC.

back close forward