Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Messages - qwerty195

ABNORMAL202 SCRIPTING TUTORIAL 8: ARRAYS
See ALL Tutorials

DESCRIPTION

Arrays are an essential part to programming, and for me, they were one of the biggest hurdles for scripting. So take this tutorial slowly, read it over multiple times if you have to, but I will try to do my best to explain them.

CREATING AN ARRAY

Arrays are basically a way of storing multiple entities inside one variable. You can define an array multiple ways:

defining it like this:
watermelons = [];
and then adding the respective elements to the arrays like this:
melon_0 = GetEnt("watelon","targetname");
watermelons[0] = melon_0;
OR
by using array functions that return/create arrays for you:
melon_phrases = array( "green & red, big as my head","round and plump, give me some","hard yet soft, let me crunch");
watermelon_triggers = GetEntArray("melon_trigger","targetname");
Don't worry about understanding all of the above just yet; just wanted to show how there are multiple correct ways to create an array and you may encounter all of these.

PARTS OF AN ARRAY

One of the most common ways arrays are used is for grabbing multiple ents with a common targetname using GetEntArray().
For example, if I had script_model with the targetname: "watermelon", I could grab it using:
melon = GetEnt("watermelon", "targetname");
Now I have that one model stored in the variable I called "melon". But what if I wanted to grab more than one watermelon with that targetname? GetEnt() will not work if more than one entity has the targetname it's looking for, because it doesn't know which ent you want.
So instead I can use:
melons = GetEntArray("watermelon", "targetname");
this will find all entities with the targetname "watermelon" and store them in an array, which is stored in the variable "melons". Notice how I pluralized this variable as opposed to just calling it melon like the above. This is because I want to think of it as multiple entities instead of one.
Let's say in this example there were 3 models with the targetname "watermelon". That means there are 3 entities in the array and we can access each one like this:
melon_0 = melons[0];
melon_1 = melons[1];
melon_2 = melons[2];
notice:
  • an array counts starting at 0, not 1. So even though there are 3 entities, they're essentially labeled 0,1,2.
  • the [ ]
  • the number inside the [ ]. This number is known as the index. the index goes as high as the size of the array, minus one (due to starting at 0). When using GetEntArray() each melon entity is stored at a certain index. One model will be index 0, a different one will be index 1, and so on. Due to not knowing how the computer decides which script_model goes first, then next, etc. the entities and their respective indexes will essentially be random using GetEntArray(), however there are other situations in which you can decide which index an entity will be under.
this concept of the index is important to understand, because it's essentially the only way we can interact with the actual entities in the array. 
For example this would Not do anything:
melons MoveZ(50, 2);
but this will:
melons[0] MoveZ(50, 2);
A very handy thing to know: Arrays always have a property stored on them called size, that is how big it is, or how many entities are stored inside it. You can access it like this:
amount_of_melons = melons.size;
this is very neat because the size of the current array will always be what the next index value would be if another entity were to be added to it. In our example with the 3 melons, the size of the array would be 3, and the already used indexes are 0,1, and 2 - so the next value of the index would be 3, same as our size.

FOR LOOPS

For loops can be used for many things, but they're mostly used in conjunction with arrays, which is why I have waited until now to talk about them. A For loop is basically a while loop that automatically stops after a certain amount of times, by checking a variable.
This is what a common For Loop might look like:
for(i=0; i<10; i++)
{
   
}
Notice:
  • the word for
  • the ()
  • the init. In this example it is i = 0. This is where we define a variable and set it to something, typically a number, and often 0. Usually people call the variable "i", but it could be anything.
  • the condition. In this example it is i < 10. This is where we put a condition that has to be true in order for the For Loop to loop again. In this case it will keep looping as long as i is less than 10, but if it were to say, equal 10 or something higher, the loop would stop running and the code below it would activate.
  • the increment. In this example it is i ++. This code will occur at the end of every loop of the For Loop. So in this case, i will start as 0, then for the next loop it will equal 1, then 2, etc. However when i = 9 the next loop will not occur because adding 1 to i would make it equal to 10, which is not < 10, ending the For Loop.
  • the ; in between the init & condition, and in between the condition & increment.
  • the {}. In between these is where, similar to while loop, the code we want to loop occurs.
Using the concept of a For Loop, we can easily perform actions to all entities in an array at once. For example:
for(i=0; i<melons.size; i++)
{
   melons MoveZ(50, 2);
}
Notice:
  • I'm using melons.size as what I want i to always be less than in order to keep looping. Because the .size will always be one more than the last index of the array, this means the for loop will run once for every entity in the array.
  • for the index I'm using i. Every time the code inside the for loop is run it will substitute the current value for i inside there. so the first loop around it will be melons[0] than melons[1] etc.
  • I don't need to have a wait in my For Loop. This is because it is not being run infinitely unlike the while loops, so it will not freeze the computer from me trying to run it without rest.
This above array will successfully move all script_models part of the melons array 50 units higher in 2 seconds. While it may seem like it took a lot of code just to do that, the great thing about it is the flexibility. This code will work no matter how many models I have with the targetname: "watermelon" on them in radiant. This makes it so much more efficient and professional than:
melon_0 = GetEnt("watermelon_0", "targetname");
melon_0 MoveZ( 50, 2);
melon_1 = GetEnt("watermelon_1", "targetname");
melon_1 MoveZ( 50, 2);
melon_2 = GetEnt("watermelon_2", "targetname");
melon_2 MoveZ( 50, 2);

FOREACH LOOPS

Whereas For Loops can have use outside of arrays, Foreach loops are designed specifically for arrays. Though a for loop can accomplish anything a Foreach loop can (and not vice versa), foreach can be a great alternative, which will save a lot of typing and look nicer.
Here is a foreach loop that accomplishes the exact same thing as the above For Loop:
foreach(melon in melons)
{
   melon MoveZ( 50, 2);
}
Notice:
  • the word foreach (don't forget it's all one word)
  • the ()
  • the 1st word, melon. We can call this whatever we want, but this will represent each item in the array. We must use the same word we set for it inside the () as inside the {} to reference the entity in the array.
  • the word in.
  • the 2nd word, melons. This must be an already defined array, and it will be the array that foreach will loop every entity inside of through the code in the {} below.
The foreach code is much cleaner, easier to remember, and less likely to screw up when typing. However, there are certain limitations with this format, as there is no longer any sort of variable being incremented with each loop. This means that if we, say, wanted to do something special when the 2nd melon is moved we would have to use a For Loop:
for(i=0; i<melons.size; i++)
{
   if(i == 1)
   {
      IPrintLnBold("I'm a special Melon, who must be higher than the others");
      melons MoveZ(70, 2);
   }
   else
   {
      melons MoveZ(50, 2);
   }
}
Because there is no variable "i" we can check with foreach loops.

ADDING TO/REMOVING FROM AN ARRAY

Thankfully, our arrays are not set in stone once we create them. We are always able to add values to them or remove values from them using the below functions:
ArrayInsert(, , );
ArrayRemoveIndex(,,[preserve_keys]);
ArrayRemoveValue(,,[preserve_keys]);
Note that using ArrayRemoveValue() will remove any of the value you put that is in the array, no matter the index, meaning it can remove more than one item from the array at once if they are the same value.
There are even more useful array functions, and since a lot of them are engine functions, you can find them in the Modme ScriptDocs. I feel the above functions are pretty self-explanatory, so I won't spend any more time on them.

NON-ENTITY ARRAYS

Though probably most of my use of arrays comes the function GetEntArray(), there are plenty of useful ways to use arrays that aren't necessarily of objects in radiant.
For example we might want to create an array of strings, and we can't use GetEntArray() to accomplish that, so instead we use:
melon_phrases = array( "green &amp; red, big as my head","round and plump, give me some","hard yet soft, let me crunch");
note that this will preserve the order the strings were put in, so the "green & red" phrase will be at index 0, the "round and plump" phrase will be at index 1, and the "hard yet soft" phrase will be at index 2.
This means if I were to use a For or Foreach Loop, I would know exactly what order these strings would be run in:
foreach(phrase in melon_phrases)
{
   IPrintLnBold(phrase);
   wait(4);
}
If I wanted to randomize the order however, I could use a handy function from array_shared.gsc (#namespace array) called randomize():
randomized_array = array::randomize( original_array );
Speaking of random, what if I wanted to just use one random value from an array, instead of all of them in a random order? Since indexes are always integers, I can use:
integer = RandomInt();
like this:
random_integer = RandomInt(melon_phrases.size);
IPrintLnBold(melon_phrases[random_integer]);
While RandomInt() definitely has uses outside of arrays, I feel it is important to point out as I commonly use it with arrays. It will always return a value from 0 to the number you put in minus 1, so it feels designed for putting an array's size in there, because as I have mentioned before an array's size will always be 1 more than the last used index in the array.
There are still plenty more cool things you can do with arrays, and I can't point out all of them, so you'll just have to find them yourselves. I know arrays can be confusing, but they are an absolute must to understand if you wish to create more efficient code.
See ALL Tutorials

735 days ago
ABNORMAL202 SCRIPTING TUTORIAL 7: WHILE LOOPS
See ALL Tutorials

DESCRIPTION

While Loops are the first type of loops we'll be looking at. Loops essentially allow you to repeat the same code over & over again multiple times, or even an infinite amount of time so long as the game is running.

I will also talk about notifies, endons, and waittills, which are all a form of loop manipulation, however they can be used outside of loops as well.

CREATING A WHILE LOOP

A while loop looks like this:

while(condition)
{
   IPrintLnBold("Hello");
   wait(0.05);
}
notice:
  • the word while
  • the ()
  • the condition. You insert a boolean in between the (), so just like in an if statement it will check if it's true or false. If the condition is true the loop continues; if it is false the loop stops and the code below it is run.
  • the {}. Similar to a function or if statement, you should put all code you want to be in the while in between these two {}.
  • the wait. ALL WHILE LOOPS NEED A WAIT OR WAITTILL INSIDE THEM. The smallest amount of time you can wait is 0.05 seconds, which is often used in while loops. If you forget to put a wait inside your while loop, launcher will not give you an error. Instead you will find that in-game when the code begins to read the while loop without a wait, the game will freeze and give a "Connection Interrupted" error, and you will have to force quit to get out usually.
So how can we use While Loops? We can use them to repeat code over and over as long as the condition inside the () remains true. Or if we just want it to always repeat, we can literally input true into the condition, or 1, since as we remember 1 = true, 0 = false:
while(1)
{
   IPrintLnBold("Are we there yet?");
   wait(2);
}
In this example, the line "Are we there yet?" will be printed to the screen every 2 seconds once this while loop is initiated, and will never stop. Just like those damn children.
Perhaps we don't want it go infinitely though? Here's an example of how one can use a While Loop for a set amount of times:
times = 0;
while(times < 10)
{
   if(times == 1)
   {
      IPrintLnBold(times + " minute has passed"); //account for pluralization
   }
   else
   {
      IPrintLnBold(times + " minutes have passed");
   }

   times ++;
   wait(60);
}
In the above case the code will run 10 times before it stops, and the code below it can begin. It should be noted that this specific example could probably be better done using a for loop, but we'll talk about that later.

NOTIFIES

Notifies send, well, a notify on a specific entity to the when they are used. These notifies are picked up by endons and waitills, which I will talk about in a moment.
A standard notify might look like this:
steve notify("stop_eating_my_sandwich");
assuming steve is an already defined variable.
notice: 
  • the word notify
  • the ()
  • the string inside the (). This string should be unique, as it specifies what you are notifying the entity of, so the correct endons and waitills who share that string will go off
just like calling functions, notifies can also pass arguments, to be picked up by waittills. For example:
door = GetEnt("specific_magical_door","targetname");

level notify("magic_door_opened", door, (0,0,50));
notice how you can also use that level variable on notifies, waittills, and endons.
There are a lot of very useful Treyarch notifies that you can use in your code, that notify when a specific event occurs:
level notify( "end_of_round" ); //used when round ends
level notify( "start_of_round" ); //used when next round starts
level notify( "all_players_connected" ); //used when all players have connected into the game
player notify( "bled_out" ); //used when a player bleeds out, on a specific player
player notify( "player_revived" ); //used when a player is revived, on the revived player
entity notify("death"); //used when some character, such as a player or zombie, dies
entity notify("movedone"); //used when an entity is done moving after being give some sort of move command
player notify("weapon_fired", weapon); //used when a player fires a weapon. also passes the weapon entity
entity notify( "damage", amount, attacker, direction_vec, point, type, tagName, ModelName, Partname, weapon ); //used when an entity takes damage. Passes a whole bunch of useful arguments. Note that you can use this on more than just players/zombies, so long as you use the function SetCanDamage() on the entity first.

trigger notify("trigger", player); //used when a player activates a trigger. Also passes the player who activated it as an argument. VERY USEFUL
There are definitely more Treyarch notifies than these that you can use, you'll just have to look for them.

WAITTILLS

Waittills are exactly how they sound: they wait until a notify happens before they allow the code below them to continue.
for example:
steve waittill("stop_eating_my_sandwich");
IPrintLnBold("oh, sorry");
waittills can also accept those useful arguments you may have passed in the notify:
level waittill("magic_door_opened", entity, vector);
entity MoveTo(entity.origin + vector, 5);
but perhaps one of the most used waittills is for waiting until a use_trigger is triggered:
trig = GetEnt("button_trig","targetname");
trig SetHintString( "Press and Hold ^3[{+activate}]^7 to Press Button [Cost: 500 ]");
trig.activated = false;
while(trig.activated == false)
{
   trig waittill("trigger", player);
   if(player.score >= 500)
   {
      player zm_score::minus_to_player_score( 500 );
      trig.activated = true;
   }
   else
   {
      IPrintLnBold("You need some more cash");
   }
}
IPrintLnBold("Button Activated");
The code above is a good example of how while loops and waittills can work together, in this case in what is known as a purchase loop.

ENDONS

Endons will basically break the code below them once the notify they are waiting for is passed. This is very useful for getting out of while loops.
level endon("intermission");
player endon("death");
while(1)
{
   if(player IsTouching(trigger))
   {
      player zm_score::add_to_player_score( 10 );
      wait(0.95);
   }
   wait(0.05);
}
In the example above, I have two different endons, placed above my while loop. for the first one, the while loop will break when game ends, and the level variable is given the "intermission" notify. For the second one, if the player is given the notify "death" (which should automatically happen when a player dies), the while loop will also break. 
If either of these notifies happen, the while loop will "end".
See ALL Tutorials

735 days ago
ABNORMAL202 SCRIPTING TUTORIAL 6: PROPERTIES
See ALL Tutorials

DESCRIPTION

Properties are very similar to variables, and I probably would've talked about them in the variables tutorial, however I thought that one was long enough and didn't want to overwhelm.

Properties are just more ways we can store information into a variable, and you'll see them quite a lot.


CREATING A PROPERTY

You can create a property on a variable simply by putting a "." at the end of it and then adding the name to your property, and finally defining it.

andrew = GetEnt("andrew","targetname");

andrew.gender = "male";
In the above example, I made a variable andrew (with some ent in radiant stored in it), and then gave it the property of "gender", which I defined as "male".
Just like with variables, properties can store numbers, strings, booleans, and even entities.
andrew.clip = GetEnt("andrew_clip","targetname");
We can do all the same things we can do with variables with properties. For example using in functions:
andrew.clip Delete();
It's important to note if this code was executed, the entity in radiant with targetname: "andrew_clip" would be deleted, not the entity with targetname: "andrew", because the property is being sent to the Delete() function, not the actual andrew variable.
Properties are also often used to check for certain conditions in If Statements:
if(isdefined(andrew.gender) &amp;&amp; andrew.gender == "male")
{
   IPrintLnBold("By golly, you're a man!");
}
Really the sky is limit. You can even make properties of properties:
andrew = GetEnt("andrew","targetname");
andrew.clothing = "on";
andrew.clothing.top = "t_shirt";
andrew.clothing.top.color = "red";
It should be noted that passing a variable that has properties on it will retain all properties on it in the function it is being passed to.
for example:
   andrew = GetEnt("andrew","targetname");
   andrew.clothing = "on";
   andrew.clothing.top = "t_shirt";
   andrew.clothing.top.color = "red";
   andrew thread clothing_appraisal();
}
function clothing_appraisal()
{
   if(isdefined(self.clothing) &amp;&amp; self.clothing == "on")
   {
      if(isdefined(self.clothing.top) &amp;&amp; self.clothing.top == "t_shirt")
      {
         if(isdefined(self.clothing.top.color) &amp;&amp; self.clothing.top.color == "red")
         {
            IPrintLnBold("I think red looks rather nice on you");
         }
      }
   }
}

LEVEL PROPERTIES

Remember that variable I talked about earlier, that can be accessed from anywhere and is the same entity according to all scripts? that variable is called level, and there's a reason Sublime highlights it in purple.
Because it's the same everywhere, setting properties on the level variable makes it easy to access said properties wherever, from any script/function/you name it. Using level properties is the quick & easy solution to passing data, without having to call, thread, or pass arguments.
However there's a reason I've introduced this a bit late, in tutorial #6, because I see a lot of novice scripters overuse to death the level properties, myself included. Because of how easy it was to use, I used it for a bunch of things that using self, or arguments would have done fine for, and that led to me not knowing how to pass data in other ways than level properties.
Other than that, Why shouldn't you use level properties?
- You don't want to accidentally create a property that has the same name as one already defined in Treyarch scripts, as you could really break stuff by overriding a Treyarch property, or having yours overridden. (though this is also a problem with setting properties on players, or any common entity that's going to be in the game regardless of the map)
- Setting tons of level properties (for things that don't need it) can make your scripts a lot harder to follow, for other people or yourself later. 
See ALL Tutorials

735 days ago
ABNORMAL202 SCRIPTING TUTORIAL 5: SCRIPT-RADIANT RELATION
See ALL Tutorials

DESCRIPTION

We've done a lot of talking about script theory, but how can we actually apply this to our maps? There are a lot of cool tricks as well as simple essentials to know about how we can use radiant and our GSC together.


KVPS

You've no doubt heard of or used KVPs before, for example for making zombie_door's or setting up your zones. A KVP is a very simple but very powerful tool. It's simply two peices of data paired together: a key and a value.

These can be set in radiant on pretty much everything. You can access an entity's KVPs by pressing "n" while having it selected. 

It should be noted that if you want to use a model in script, in radiant it should be a script_model, and if you want to use a brushmodel in script, in radiant it should be a script_brushmodel.

When you make something a script_model or script_brushmodel, you'll notice it already starts out with some KVPs:



We can access these KVP's in script. But first we must grab the entity in script.

The most common way to grab an entity in script is to give it a custom value for the targetname (in radiant) and then use GetEnt() (in script):


function joseph()
{
   my_main_man = GetEnt("joseph","targetname");
}
there are other keys that also work with GetEnt, such as script_noteworthy, target, classname, or script_string. Not all keys work with GetEnt() however. But that doesn't mean we can't use our custom keys in script.

PROPERTIES

When we get an entity from radiant in script, it stores all the values on that entity into properties based on the keys of the KVPs it has. For example, if I set a custom KVP in radiant such as:

the value is stored just like this, automatically in script:
my_main_man.gender = male;
this is great for If Statements. We could use it like this:
my_main_man = GetEnt("joseph","targetname");
if(my_main_man.gender == "male")
{
   IPrintLnBold("Joseph... is a man");
}
or another common usage, storing vectors:

my_main_man = GetEnt("joseph","targetname");
my_main_man MoveTo( my_main_man.origin + my_main_man.script_vector, 5);

ORIGINS/ANGLES

All entities start out with an origin key and angles key. While they aren't particularly special, they are used quite a lot so I wanted to make a quick section about them.
The origin is the XYZ coordinates of where the entity's origin is located in the map. You'll notice as you move the object in radiant, the value for origin changes automatically in the KVPs.
it's important to note it is specifically the entity's origin. You can see each entity's origin as a hollow blue cube in the 3D view:

For the most part, the origin is located somewhere in the middle of the object, or where it would touch the ground.
The angles are the Roll, Pitch, and Yaw rotation of the entity that will change as you rotate the entity.
the roll changes as you rotate around the X-Axis, the pitch if you rotate around the Y-Axis, and the Yaw if you rotate around the Z-Axis.
Knowing the object's angles/origin is often important for using functions such as:
moveto(,,[acceleration time],[deceleration time]);
rotateto(,,[acceleration time],[deceleration time]);
which are very commonly used.

RADIANT'S AUTO-TARGETING SYSTEM

You may have noticed in making zombie_doors that you can have certain entities "target" other entities, by giving the targeting entity the key "target" with the value of that key being the targetname of the entity being targeted.


As you can see, when an entity is targeting another, a red line appears between the two with the arrow pointing towards the entity being targeted.
this makes accessing the targeted entity simple in script if you already have the targeting entity:
headless_soldier = GetEnt("dead_soldier","targetname");
joseph = GetEnt(headless_soldier.target,"targetname");
notice I only typed in one actual targetname, "dead_soldier". The other targetname was gathered through using the target of the other entity.
This brings me to Radiant's automatic targeting system. If you select two entities then press "w" it will make the first entity target the second, assigning an auto-targetname for the targeted entity that looks like this:

the number will change each time you do it, so no two entities using the auto-target system share the same targetname.
Other things to note:
  • you can chain targets: an entity can target another entity which targets another entity and so on
  • an entity can target multiple entities if they all share the same targetname
  • multiple entities can target the same entity if they share the same target

TRIGGERS

Triggers are very useful in script, and I will go into more detail on them later. Triggers can be placed in radiant and there are several different types of triggers.
Here are some of the most common ones I use:
  • trigger_use : You can give it a hintstring. Is "triggered" when a player presses their interact button while looking at it.
  • trigger_multiple : "triggered" by a player touching the trigger_multiple. Also useful to use the IsTouching() function with.
  • trigger_damage : "triggered" by something dealing damage inside it.
  • trigger_hurt : hurts players while they are touching it.
  • trigger_radius : creates a perfect sphere around it, that is "triggered" when a player enters it. Also useful to use the IsTouching() function with.
When a trigger is "triggered", it passes the "trigger" notify on itself in script. Meaning if we use a waittill, we can make the code effectively wait until the trigger is triggered, before doing something:
trig = GetEnt("my_trigger","targetname");
trig waittill("trigger");
IPrintLnBold("TRIGGERD");
See ALL Tutorials

735 days ago
ABNORMAL202 SCRIPTING TUTORIAL 4: IF STATEMENTS
See ALL Tutorials

DESCRIPTION

If Statements after very important in coding. They let you check if a certain condition is met, and then do something. Or if that something is not met, do another something. OR if that something is not met but this something is do this something....

If Statements are pretty easy to get the hang of, and are very powerful. So let's hop in:

CREATING AN IF STATEMENT

In order to create an if statement, you first have to have something that you will be checking for if it's true or not. Remember earlier when I talked about booleans? basically we'll need some sort of variable that can store true or false (or 1 or 0).

so let's set a simple variable to a boolean:
im_a_good_student = true;
and make an if statement checking if that is indeed true:
if(im_a_good_student)
{
   IPrintLnBold("Good for you. What, you want a cookie or something?");
}
notice:
  • the word if
  • the ()
  • where you put what you're checking is true (inside the ())
  • there is no ; 
  • the {}
so if the variable im_a_good_student is in fact equal to true, the code inside the {} of the if statement will be run, and this case print that string to the screen. If it is not true, the if statement will simply be skipped over.
It's important to note you don't always have to directly set and check a variable to be true. you can actually call functions inside an if statement, provided they will return a boolean.
lot of different Engine functions (and I mean a lot) return booleans. for example pretty much anything function that starts with the word "is" returns a boolean, such as: IsMeleeing(), IsPlayer(), IsWeapon(), etc.
so instead of doing it the long way like this:
weapon = GetWeapon("smg_fastfire");

this_is_a_weapon = IsWeapon(weapon);
if(this_is_a_weapon)
{
   IPrintLnBold("that's a weapon alright");
}
we can take the shortcut:
weapon = GetWeapon("smg_fastfire");

if(IsWeapon(weapon))
{
   IPrintLnBold("that's a weapon alright");
}

ELSE

Sometimes if a certain condition in our if statement is not met, we don't just want to skip over the code, but do something else entirely...
lucky for you that can be accomplished pretty easily:
if(im_a_good_student)
{
   IPrintLnBold("Good for you. What, you want a cookie or something?");
}
else
{
   IPrintLnBold("I expected better from you");
}
notice:
  • the word else
  • there is no condition I'm checking for is true or not inside the else statement. This is because the code in the else statement is only activated it the if statement above is not true
  • the {}
  • you must have an if statement before you have an else.

ELSE IF

So far these all make pretty logical sense if we look at them English-wise. if something, do something. else do this other thing. now let's keep that going with the hybrid baby of if and else, the else if().
just like the else, there must be an if statement before you can have an else if. the else if is similar to the if statement, as it checks if a condition is met, and will be skipped over if it isn't. 
if(im_a_good_student)
{
   IPrintLnBold("Good for you. What, you want a cookie or something?");
}
else if(math_is_hard)
{
   IPrintLnBold("okay I'll give you that. But that's no excuse");
}
else
{
   IPrintLnBold("I expected better from you");
}
in this case first it will check if im_a_good_student is true. if it is, it will execute the code inside the first if statement, and nothing else.
if im_a_good_student is not true, it will then move to the else if statement and check if math_is_hard is true. If it is, then it will execute the code inside the else if statement, and nothing else.
finally if neither the if statment or any else if's are met, than the else code will activate.
note that you can have as many else if's in a chain as you want, just remember it needs to start with an if statement:
if(im_a_good_student)
{
   IPrintLnBold("Good for you. What, you want a cookie or something?");
}
else if(math_is_hard)
{
   IPrintLnBold("okay I'll give you that. But that's no excuse");
}
else if(life_is_hard)
{
   IPrintLnBold("let me play the world's tiniest violin for you");
}
else if(im_a_victim_of_circumstance)
{
   IPrintLnBold("uh huh...");
}
else
{
   IPrintLnBold("I expected better from you");
}

AND'S/OR'S

We can also check for more than one condition to be met inside an if statement before the if code is executed.
in GSC language:
and = &&
or = ||
note that for or those are not lowercase L's or capital i's, but the vertical line which you can make by holding shift and pressing the "\" button on your keyboard.
so as you might expect, having an and means all conditions must be met, and having an or means just one of the conditions has to be met:
if(the_month_is_may &amp;&amp; the_day_is_the_twenty_ninth)
{
   IPrintLnBold("HAPPY BIRTHDAY!!!!");
}
if(the_month_is_may || the_day_is_the_twenty_ninth)
{
   IPrintLnBold("what? I'm not celebrating your birthday. it's 3 months away...");
}
 in the above case, the first code with the and in the if statement will only get activated once a year.
for the bottom case with the or in it, it would get run on the 29th of every month, and on every day in the month of may, because just one of those conditions has to be met.
it should be noted you can chain more than one and or or in the same if statement:
if(you_brought_the_cake &amp;&amp; I_brought_the_balloons &amp;&amp; she_brought_the_drinks)
{
   IPrintLnBold("what a fun party...");
}
if(you_provide_gas || you_provide_grass || you_provide_ass )
{
   IPrintLnBold("come on in");
}
See ALL Tutorials

735 days ago
ABNORMAL202 SCRIPTING TUTORIAL 3: VARIABLES
See ALL Tutorials

DESCRIPTION

Variables can be a lot of different things in .gsc. Frankly I'm not even sure if the term "variable" is the appropriate term in this language, but it's the one I've heard used the most.

Variables can store:
- strings
- numbers
- objects
- booleans
- and maybe some others I can't think of off the top of my head

defining a variable is as simple as this:
my_variable = WHAT_IM_SETTING_IT_TO;
and in this tutorial I'm gonna go over the various things we can set it to and how we can manipulate the variables

GENERAL RULES

  • you must define a variable before you use it
  • a variable can only be used in the function it is defined - unless we pass it somehow, which I'll talk about later
  • you can effectively overwrite a variable by simply defining again as something different

STRINGS

strings are pretty simple. They store text, and you create them using quotation marks "". Strings are mostly used for either setting text you want to see in-game (such as in a hintstring or print), or for describing or getting something in script.
to store a string in a variable it's like this:
jblundell_quote = "fog rolling in";
now because we've stored the string inside that variable, we can use that variable in place of where we would normally use a string. for example in the IPrintLnBold() function:
jblundell_quote = "fog rolling in";
IPrintLnBold(jblundell_quote);
note this is the same as:
IPrintLnBold("fog rolling in");

NUMBERS

Just like what you learned in Algebra, numbers can be letters too! yay!
let's define a variable as a number:
push_ups_performed = 0;
if we want to do math operations on a variable, we can do it like this:
push_ups_performed = push_ups_performed += 3; //adds 3
push_ups_performed = push_ups_performed -= 3; //subtracts 3
push_ups_performed = push_ups_performed * 3; //multiplies by 3
push_ups_performed = push_ups_performed / 3; //divides by 3
notice we have to type it twice, because we want to define it as itself (whatever it used to be) plus the 3, not just as 3.
there is shorthand however for simply adding 1 to it or subtracting 1 from it:
push_ups_performed ++; //adds 1
push_ups_performed --; //subtracts 1
POP QUIZ: What number will this print to the screen?
push_ups_performed = 5;
push_ups_performed --;
push_ups_performed = push_ups_performed * 4;
push_ups_performed = push_ups_performed - 7;
push_ups_performed = push_ups_performed / 3;
push_ups_performed ++;

IPrintLnBold(push_ups_performed);
if you said 
[Spoilers] Show / Hide
4
then you are correct!

OBJECTS/ENTITIES

Perhaps the most useful use of a variable is storing an object/entity. Using certain functions allows use to get an actual in-game/in-radiant entity that we can store in script. For example one of the most important  Engine Functions:
entity GetEnt(,,[ignore spawners])
notice how when we look up the engine functions in the Modme ScriptDocs, the word entity appears before it. This means that using this function will return us an entity, which we can store in a variable. So using this function would look like this:
watermelon_model = GetEnt("watermelon","targetname");
assuming that in radiant we have a script_model with the KVP of targetname: watermelon. And preferably it looks like a watermelon.
You won't be able to print a variable with an entity stored in it, like you can with a string, number, or boolean, because well, what would it print? that variable is a representation of a physical radiant entity, not a word or number.

BOOLEAN

A boolean is basically something that can have 1 of 2 values: etiher true or false.
we can set a variable as true simply like this:
ur_gay = true;
please note that true and false do not go inside quotation marks "", or else you would be setting it equal to a string that simply is the word "true".
Also important to note is that true and false can also be represented by 1 and 0, respectively.
booleans don't mean much to us yet because we don't know what if statements or while loops are. For now, just remember we're allowed to store them in variables like this.
PASSING VARIABLESthere are many ways we can pass variables, so we're not limited to using it in the same function we define it in.
these ways consist of:
- setting parameters/using arguments
- returns
- self

PARAMETERS/ARGUMENTS

You may have noticed when looking through the Modme ScriptDocs that most of the engine functions ask for something, or multiple things. These are called parameters are they can either be mandatory (you must pass them if you want the function to work) or optional (you can pass them, but the function is capable of working without them).
for example let's look back at that engine function, GetEnt():
entity GetEnt(,,[ignore spawners])
the first parameter is required and it is the name you gave the key of the entity in radiant. The second is also required and it is the key you are using of the entity in radiant.
the third parameter is not required (I can tell because it uses [] instead of <>;) and that is asking whether or not to include spawners in the search for the entity.
So in order to use this function, I need to pass at least two arguments to it.
value = "watermelon";
key = "targetname";
include_spawners = false;
watermelon_model = GetEnt( value, key, include_spawners);
notice that I don't have to give the arguments the same exact names as the parameters being asked for.
So, knowing that, when we create function we can have them ask for specific parameters. For example:
function calculate_bmi( weight, height )
{
   bmi = weight / height / height;

   IPrintLnBold("your BMI is " + bmi);
}
In this example the function is asking for two parameters: a weight, and a height.
it then calculates bmi by dividing the variable weight by height, and then dividing it by height again.
because I used the word weight and the word height as my parameters, any arguments that get passed to this function will be stored in a variable called weight and a variable called height, which I use for the calculation.
so if I wanted to use this function, I could do this:
calculate_bmi( 70, 1.8 ); //weight is in kg and height is in m 

SELF

using self is very similar to passing a variable using parameters/arguments. It is basically a shortcut.
You can only pass one self per function and you do it by simply putting the variable before calling or threading the function. like this:
watermelon_model thread fly_away();
provided we've already defined the watermelon model above, most likely using GetEnt() as I talked about before.
now to use the variable watermelon_model in our function fly_away() we just type self where we want to use it:
function fly_away()
{
   self MoveZ(1000, 10, 5);
}
this will make the watermelon fly upward by 1000 units in 10 seconds.

RETURNS

Finally, one other way we can pass data back is with returns. a return passes back data to where it was called from, and is stored in a variable there.
for example the function GetEnt() returns the entity we are looking for, so we can then use it in the same function we called GetEnt() from.
Let's modify the bmi function to return the bmi value instead of print it:
function calculate_bmi( weight, height )
{
   bmi = weight / height / height;

   return bmi;
}
it's as simple as putting the word return before the value we want to return. Now when calling this function, in order to receive the bmi value we must set a variable for it to be stored in:
my_bmi = calculate_bmi( 60, 1.5);
now we can use the variable my_bmi which should have stored in it the calculated value using the data 60 for the weight and 1.5 for the height.
 See ALL Tutorials

735 days ago
ABNORMAL202 SCRIPTING TUTORIAL 2: USING OTHER GSC FILES

See ALL Tutorials

DESCRIPTION

In the previous tutorial, I talked about starting your script in the yourmapname.gsc file, because it will be run when you map is run. However, we don't want to have to put everything we script inside that one .gsc file.

Though it is personal preference, the only things I keep inside the yourmapname.gsc file are very small scripts that are map-specific. Everything else I have in a separate .gsc file.


CREATING A GSC FILE

The easiest way to create a .gsc file is to copy an existing one and rename it. Though we can have .gsc files in other directories than (Bo3Root\usermaps\yourmapname\scripts\zm) to keep things simple we'll stay inside that directory.

Copy any .gsc there, such as yourmapname.gsc, and rename it to anything you like. In this example, I will call it my_external_script :



Next thing we will have to do is parse it in the yourmapname.zone. This will let launcher know that we'd like to use this file at some point when our map is run.

Let's open our yourmapname.zone (located in Bo3Root\usermaps\yourmapname\zone_source). It should look something like this:
>class,zm_mod_level
>group,modtools

xmodel,skybox_default_day
material,luts_t7_default
   
// BSP
col_map,maps/zm/zm_yourmapname.d3dbsp
gfx_map,maps/zm/zm_yourmapname.d3dbsp

// Audio
sound,zm_yourmapname

scriptparsetree,scripts/zm/zm_yourmapname.gsc
scriptparsetree,scripts/zm/zm_yourmapname.csc
To parse our script, we add the line:
scriptparsetree,scripts/zm/my_external_script.gsc
to the bottom.

CALLING AN EXTERNAL FUNCTION

Now we have our script recognized by launcher, however this does not mean it will be run. We still need to call it from a script that will already be run, such yourmapname.gsc.
Before we get to that however, let's give this script something to do. Delete everything inside the .gsc (you can select all with ctrl + a) so we can start from scratch.
Let's add another simple print function like in the last tutorial.
function my_cool_print_function()
{
   IPrintLnBold("Your Mom looks like a Turtle");
}
this can be it for our external script. Now let's find a way to call it.
Go back to yourmapname.gsc and back into the function main(). It should look something like this, after the last tutorial:
function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;

   level waittill("all_players_connected");
   wait(10);

   thread my_awesome_function();
}
function my_awesome_function()
{
   IPrintLnBold("Hello Everybody");
}
Since we also want this function to be called after all players have connected, and then 10 seconds after that, we should call it below the wait(10); line.
the way you call an external function is basically the same as a normal function:
my_external_script::my_cool_print_function();
notice:
  • the name of the .gsc file is first
  • followed by two colons ::
  • followed by the name of the specific function within the .gsc file we want to call.
We can also thread it like we did with normal functions:
thread my_external_script::my_cool_print_function();
So, let's show it all in place:
function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;

   level waittill("all_players_connected");
   wait(10);

   thread my_awesome_function();
   thread my_external_script::my_cool_print_function();
}
function my_awesome_function()
{
   IPrintLnBold("Hello Everybody");
}

#USING A SCRIPT

This is almost complete, however if we try to link now, launcher will throw an error like this:
********************************************************************************
UNRECOVERABLE ERROR:
 ^1SCRIPT ERROR: No generated data for 'scripts/zm/zm_yourmapname.gsc'
ERR(6E) scripts/zm/zm_yourmapname.gsc (93,0)  : Compiler Internal Error :  Unresolved external 'my_external_script::my_cool_print_function'



Linker will now terminate.
********************************************************************************
this is because anytime you call a function from an external .gsc file, you must include that file in the #using's at the stop of the script that's calling it.
A #using looks like this:
#using scripts\zm\my_external_script;
notice:
  • the #using
  • the filepath (automatically assumes we are starting from the folder Bo3Root\usermaps\yourmapname OR Bo3Root\Share\Raw)
  • a #using does not include the .gsc at the end of the filename
  • uses a ;
Let's slap that bad boy in the preamble of yourmapname.gsc and we should be good to go!
#using scripts\codescripts\struct;

#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\compass;
#using scripts\shared\exploder_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\laststand_shared;
#using scripts\shared\math_shared;
#using scripts\shared\scene_shared;
#using scripts\shared\util_shared;

#insert scripts\shared\shared.gsh;
#insert scripts\shared\version.gsh;

#insert scripts\zm\_zm_utility.gsh;

#using scripts\zm\_load;
#using scripts\zm\_zm;
#using scripts\zm\_zm_audio;
#using scripts\zm\_zm_powerups;
#using scripts\zm\_zm_utility;
#using scripts\zm\_zm_weapons;
#using scripts\zm\_zm_zonemgr;

#using scripts\shared\ai\zombie_utility;

//Perks
#using scripts\zm\_zm_pack_a_punch;
#using scripts\zm\_zm_pack_a_punch_util;
#using scripts\zm\_zm_perk_additionalprimaryweapon;
#using scripts\zm\_zm_perk_doubletap2;
#using scripts\zm\_zm_perk_deadshot;
#using scripts\zm\_zm_perk_juggernaut;
#using scripts\zm\_zm_perk_quick_revive;
#using scripts\zm\_zm_perk_sleight_of_hand;
#using scripts\zm\_zm_perk_staminup;

//Powerups
#using scripts\zm\_zm_powerup_double_points;
#using scripts\zm\_zm_powerup_carpenter;
#using scripts\zm\_zm_powerup_fire_sale;
#using scripts\zm\_zm_powerup_free_perk;
#using scripts\zm\_zm_powerup_full_ammo;
#using scripts\zm\_zm_powerup_insta_kill;
#using scripts\zm\_zm_powerup_nuke;
//#using scripts\zm\_zm_powerup_weapon_minigun;

//Traps
#using scripts\zm\_zm_trap_electric;

#using scripts\zm\zm_usermap;

#using scripts\zm\my_external_script;

//*****************************************************************************
// MAIN
//*****************************************************************************

function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;

   level waittill("all_players_connected");
   wait(10);

   thread my_awesome_function();
   thread my_external_script::my_cool_print_function();
}
function my_awesome_function()
{
   IPrintLnBold("Hello Everybody");
}

function usermap_test_zone_init()
{
   level flag::init( "always_on" );
   level flag::set( "always_on" );
}   

function custom_add_weapons()
{
   zm_weapons::load_weapon_spec_from_table("gamedata/weapons/zm/zm_levelcommon_weapons.csv", 1);
}

Now in-game you should notice both the print function inside yourmapname.gsc and the print function in the my_external_script.gsc being called, at pretty much the same exact time.

#NAMESPACE'S

You may see in some scripts a #namespace. These basically designates a different name for the script than its actual filename. A #namespace goes in the preamble and looks like this:
#namespace zombie_utility;
it's as simple as putting #namespace followed by the name you want to give the script.
Note that:
  • You can only use one #namespace per script. (that wouldn't make sense anyway)
  • the #namespace only changes the name you use when calling the external function. When parsing the script in the mapname.zone or when putting the script as a #using, you must still use the actual filename.
  • Treyarch uses these all the time

USING TREYARCH'S CODE

There are a lot of great things you can do with the list of Engine Functions, however now that we know how to use external functions, we have access to so much more.
Treyarch has tons of scripts that we can look through and use the functions of in (Bo3Root\share\raw\scripts). For example, one function that I like to use often for EE's and such is the spawn_zombie function, located in (Bo3Root\share\raw\scripts\shared\ai\zombie_utility.gsc)
This lets me spawn a zombie at a specific struct, and I can use it from any .gsc like this:
zom = zombie_utility::spawn_zombie( spawner, target_name, location, round_number );
I just have to remember to have the #using for the script at the top of my script:
#using scripts\shared\ai\zombie_utility;
and remember to make sure I'm calling the function by whatever #namespace Treyarch gave that script, not necessarily the filename (though in this case they are the same)
See ALL Tutorials
735 days ago
ABNORMAL202 SCRIPTING TUTORIAL 1: FUNCTIONS

See ALL Tutorials

DESCRIPTION

I'm lean, mean, and ready to code! but where exactly can I do that?

The bulk of all code is contained with functions. The only time you will be scripting outside of a function in a .gsc is when you loading things in the preamble of the script. This is the place where you can see all the #using's, #insert's, #precache's to name a few, which I will get to later.

So how do I make a function?

FUNCTION RULES

You can create a function by typing:
function my_function_name_here()
{
 
}
notice the parts:
  • the word function before everything
  • the name of the function (in this case I called it my_function_name_here but it can be anything, so long as there are no spaces, and it uses basic alphanumeric characters)
  • the () (later we'll see that we can actually fit things inside these, as arguments. But for now we won't have any)
  • the {} (These define where my function is occuring. Everything I type must be in between these two {}, much like how everything I'm typing right now is in between two parenthesis)
There are some quick no-no's with functions that I see sometimes:
  • you can't put a function inside another function. Ever.
  • you can't have more than one function with the exact same name in the same .gsc file.
  • you don't put a semicolon ; at the end of declaring a function, like you do with most lines.

ENGINE FUNCTIONS

Alright alright hold up. I don't even know what file to open up yet. Where should I start to script?
When your map is first loading, one file that will always be read is yourmapname.gsc. You can find this in (Bo3Root\usermaps\yourmapname\scripts\zm). Open this up with Sublime. You Should Something Similar to this:
#using scripts\codescripts\struct;

#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\compass;
#using scripts\shared\exploder_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\laststand_shared;
#using scripts\shared\math_shared;
#using scripts\shared\scene_shared;
#using scripts\shared\util_shared;

#insert scripts\shared\shared.gsh;
#insert scripts\shared\version.gsh;

#insert scripts\zm\_zm_utility.gsh;

#using scripts\zm\_load;
#using scripts\zm\_zm;
#using scripts\zm\_zm_audio;
#using scripts\zm\_zm_powerups;
#using scripts\zm\_zm_utility;
#using scripts\zm\_zm_weapons;
#using scripts\zm\_zm_zonemgr;

#using scripts\shared\ai\zombie_utility;

//Perks
#using scripts\zm\_zm_pack_a_punch;
#using scripts\zm\_zm_pack_a_punch_util;
#using scripts\zm\_zm_perk_additionalprimaryweapon;
#using scripts\zm\_zm_perk_doubletap2;
#using scripts\zm\_zm_perk_deadshot;
#using scripts\zm\_zm_perk_juggernaut;
#using scripts\zm\_zm_perk_quick_revive;
#using scripts\zm\_zm_perk_sleight_of_hand;
#using scripts\zm\_zm_perk_staminup;

//Powerups
#using scripts\zm\_zm_powerup_double_points;
#using scripts\zm\_zm_powerup_carpenter;
#using scripts\zm\_zm_powerup_fire_sale;
#using scripts\zm\_zm_powerup_free_perk;
#using scripts\zm\_zm_powerup_full_ammo;
#using scripts\zm\_zm_powerup_insta_kill;
#using scripts\zm\_zm_powerup_nuke;
//#using scripts\zm\_zm_powerup_weapon_minigun;

//Traps
#using scripts\zm\_zm_trap_electric;

#using scripts\zm\zm_usermap;

//*****************************************************************************
// MAIN
//*****************************************************************************

function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;
}

function usermap_test_zone_init()
{
   level flag::init( "always_on" );
   level flag::set( "always_on" );
}   

function custom_add_weapons()
{
   zm_weapons::load_weapon_spec_from_table("gamedata/weapons/zm/zm_levelcommon_weapons.csv", 1);
}

You should see:
  • A whole bunch of #using's and #insert's up top (ignore these for now)
  • the function main() (this is run on map startup)
  • the function usermap_test_zone_init() (this is for setting up zones)
  • the function custom_add_weapons() (this is a simple one-line function for loading your map's weapontable)
We'll leave the bottom two functions alone, as they serve their own purposes, and don't need to be interfered with.
However we will need to mess with function main(), because it is run during map startup, and we'll also want our function to be run on map startup.
if you haven't already, place your new function below the preamble, anywhere that's not inside another function. For example like this:
//*****************************************************************************
// MAIN
//*****************************************************************************

function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;
}
function my_awesome_function()
{

}

function usermap_test_zone_init()
{
   level flag::init( "always_on" );
   level flag::set( "always_on" );
}   

function custom_add_weapons()
{
   zm_weapons::load_weapon_spec_from_table("gamedata/weapons/zm/zm_levelcommon_weapons.csv", 1);
}
Before we even get to calling it, let's give it something simple it can actually do, since currently it does nothing. Let's use a very common Engine Function which we can call from anywhere. The engine function I'm talking about is IPrintLnBold():
void IPrintLnBold()
You can find all the Engine functions in the Modme ScriptDocs. These can be used anywhere. The IPrintLnBold() is simple. It takes one argument: a string of text, and when the line is read by the computer it will print that text to the screen in-game. This is very useful for testing scripts, as it let's us see where the computer is reading lines and where it isn't.
Let's add this line to our function, and give it some text:
function my_awesome_function()
{
   IPrintLnBold("Hello Everybody");
}
notice:
  • I put the words Hello Everybody within quotation marks "". The actual quotation marks will not get printed to the screen.
  • I put a semicolon ; at the end of the line. By default, get into the habit of putting these at the end of every line. there are some exceptions however, such as when declaring functions, as I said earlier.
if you forget a ; launcher will throw an error similar to this one when linking:
********************************************************************************
UNRECOVERABLE ERROR:
 ^1SCRIPT ERROR: No generated data for 'scripts/zm/zm_yourmapname.gsc'
ERR(0) scripts/zm/zm_yourmapname.gsc (76,1) in "my_awesome_function()" : syntax error, unexpected TOKEN_RIGHT_CURLY : }


Linker will now terminate.
********************************************************************************
You see, when computers read .gsc code, they ignore spaces and newlines. That way I can have as many spaces and newlines to make myself comfortable. However this means we must manually define where the line ends using a ; or launcher will keep reading everything as one line and get confused.

CALLING FUNCTIONS

If I run my script as-is right now, nothing new will happen. This is because while the function is there, it's not getting called. We can tell the computer to call a function like this:
my_awesome_function();
if we place this line inside a function that we know will be run before the start of the game, we can effectively call it:
function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;

   my_awesome_function();
}
function my_awesome_function()
{
   IPrintLnBold("Hello Everybody");
}
There's another way to call a function though, and that's called threading. Bascially if we do the same thing except type the word thread before it, we can thread the function:
thread my_awesome_function();
but what does it mean to thread? This can be a difficult concept to understand immediately. Basically when we thread, we split the computer up into two instances: one which will jump to and start reading the function we threaded; and the other which will keep on reading the lines below it as normal. if we don't thread however, then the computer will see the function call, jump to the function, finish reading that function, then jump back to where it was it the other function it was in and keep reading as normal.
In this case it doesn't matter if we thread or not because there's nothing at the bottom of function main() for it to read anyway.

WAITS & WAITTILLS

I realize this is a bit off topic from functions, but in order to make this function work, we need to understand waits and waittills.
Because if we try to run the code as-is, yes our function is called, and the line will print to the screen, but it will all happen before the player's blackscreen has even gone away and before they can start moving.
if only there was a way we could make the computer waittill the blackscreen was gone?
There are many notifies that Treyarch sends when certain events happen that we can waittill it happens. For example the waittill "all_players_connected":
level waittill("all_players_connected");
notice:
  • There is the word level before the waittill. This designates we are waiting for the entity known as the "level" to be notified that all players have connected. The entity "level" is special as it can be accessed from any .gsc with ease and is the same entity touched by all scripts. We'll use it more later.
  • the word waittill
  • the string "all_players_connected" inside the (). This string is specific to this waittill and is set by Treyarch when all players have connected.
  • the ;
if we put this before our function is called, then it will not get called until all players have connected:
function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;

   level waittill("all_players_connected");
   
   thread my_awesome_function();
}
function my_awesome_function()
{
   IPrintLnBold("Hello Everybody");
}
We're almost ready to run the code and see the results. One more thing though.
From my experience, the blackscreen still remains for a fair amount of time after all players have connected. So to guarantee that we will be able to see our print to the screen, let's just have the computer wait some more seconds after players have loaded in before our function is called.
we can do this by using a wait:
wait(10);
notice:
  • the word wait
  • the number 10 is the amount of seconds I want it to wait.
  • the ;
now let's place that accordingly:
function main()
{
   zm_usermap::main();
   
   level._zombie_custom_add_weapons =&amp;amp;custom_add_weapons;
   
   //Setup the levels Zombie Zone Volumes
   level.zones = ;
   level.zone_manager_init_func =&amp;amp;usermap_test_zone_init;
   init_zones[0] = "start_zone";
   level thread zm_zonemgr::manage_zones( init_zones );

   level.pathdist_type = PATHDIST_ORIGINAL;

   level waittill("all_players_connected");
   wait(10);

   thread my_awesome_function();
}
function my_awesome_function()
{
   IPrintLnBold("Hello Everybody");
}
Now if we save, link in launcher, and run the map, we should see our line print to the screen shortly after starting the game!
See ALL Tutorials
735 days ago

ABNORMAL202 SCRIPTING TUTORIAL 0: Installing Sublime

See ALL Tutorials

This one shouldn't take long, as its not actual scripting but something pretty much everyone should do before starting scripting.

Basically when we are scripting, we have to open up many different files such as .gsc's, .csc's, .gsh's, etc. using a text editor. Sublime will be our text editor.

Most people choose Sublime for this because Treyarch was kind enough to leave us with a Sublime Package that allows auto-highlighting for .gsc files in Sublime which is really nice.

HOW TO INSTALL

First, download the text editor Sublime for free from the Sublime Website.

Once you have that installed, locate the package Treyarch left us, called gsc_sublime.zip. This is located in (Bo3Root\docs_modtools)

In a separate folder window, locate where Sublime is installed (most likely in C:\Program Files\Sublime Text 3\Packages). copy & paste the gsc_sublime.zip folder into this folder.

Rename gsc_sublime.zip to GSC.sublime-package

You will have to restart Sublime if you have it open. Then whenever you open a .gsc file (or any file you want the GSC highlighting for) you can select "GSC" in the bottom right corner of Sublime:



And you should be good & ready to script!

From now on when opening files for text editing, make sure to use Sublime and not notepad or whatever default text editor it may recommend.

See ALL Tutorials
735 days ago
ABNORMAL202 SCRIPTING TUTORIALS

DESCRIPTION

So after discovering that scripting video tutorials are not my forte, I decided to just make good 'ol reading tutorials. I'll try to be as clear as possible, because I realize it can be a little harder to understand when there isn't a video showing all the tests from opening launcher to playing the map. Personally I think I learn better from reading than watching, so I hope this will help more people out.

Also for you pro-scripters out there, please let me know if I get something wrong. Just don't be too mean about it okay? or do. I can handle it.

OTHER LINKS

There is already a relatively short tutorial on some GSC scripting basics done by NSZ, which you can find here. I like this tutorial, however personally I would like to go into more depth than this one does.


TUTORIALS

Tutorial 0: Installing Sublime

Tutorial 1: Functions

Tutorial 2: Using Other GSC Files

Tutorial 3: Variables

Tutorial 4: If Statements

Tutorial 5: Script-Radiant Relation

Tutorial 6: Properties

Tutorial 7: While Loops

Tutorial 8: Arrays
735 days ago
ABNORMAL202'S CRYO-SLIDE SODA (v1.21) [A BO3 Custom Perk]
 
Videos
 

 

 

What Is Cryo-Slide Soda?
 

Whenever you slide or knife, all nearby zombies will become frozen for 5 seconds. While frozen they instantly shatter to any damage. After 5 seconds they thaw out, but remain slowed by 25% for another 10 seconds. The Freeze goes on cooldown for 12 seconds. (though all these numbers can be adjusted)

 

Cryo-Slide Soda comes with it's own original model made by Lethal Peelz, as well as it's own shader, bottle, FX, Vulture Aid Waypoint, and perk jingle, and even a poster you can put in you map.

 

How Do I Install It In My Map?
 

The download includes instructions for installation within the instructions.txt file.

 

IMPORTANT: MAKE SURE YOU HAVE HARRYBO21'S PERKS (v2.1 or higher) INSTALLED FIRST!!!


Shaders
 

Cryo-Slide Soda originally came with a Black Ops 2 style shader made by Lethal Peelz, but I also made one to fit the Black Ops 3 theme. Both are included in the download.

 

In order to change the shader, you'll have to edit the customperkicons.lua (located in Root/usermaps/yourmapname/ui/uieditor/widgets/hud/zm_perks). You must change this line:


cryo 								= "i_cryo_shader"


so it is set equal to whatever IMAGE asset you want to be the shader.

 

NOTES
 

If you happen to make custom perks too, Cryo-Slide Soda uses the specialty: "specialty_stunprotection", so I recommend not using that, if you want it to be compatible with Cryo-Slide Soda.

 
This Perk debuted in the non-award-winning map Frostbite.


You can change many of the settings of Cryo-Slide Soda within the _zm_perk_cryo.gsh

Credits
 

PLEASE CREDIT THESE PEOPLE IF YOU AREN'T ALREADY!

 

-Abnormal202

-Harrybo21

-Lethal Peelz

 

Download
 

885 days ago
can I put in waw? the archive is for bo3 i need to put in world at war pls the scripts of bo3 cant use in waw
oops sorry, didn't realize.
1122 days ago
I would definitely switch to Black Ops 3. I've made maps on both of the tools and Black Ops 3 tools are far better and easier to use. The community for Black Ops 3 is also much larger then WaW or Bo1 (is that even a thing  :gusta:). The only reason anyone should stay on WaW modding is if they really like the style of WaW maps, and don't care about all the new possibilites with Black Ops 3.
1132 days ago

Login

or login with an authentication provider below
Sign In with Google
Sign In with Twitter
Sign In with Discord
Sign In with Steam
Sign In with Facebook
Sign In with Twitch
Loading ...