[GSC] Scripting Tutorial #1 - Welcome message



> >

Created 3 years ago

[GSC] Scripting Tutorial #1 - Welcome message

How to create welcome messages in call of duty 1

Tutorial Information


Time required: Less than 15 minutes
What does it do? This code will print a welcome message everytime someone connects to the server.

Info: This tutorial has originally been published by cheese on wy6.org, which has closed in january 2015. He kindly gave us permission to keep this tutorial alive on think-clan.com. Note that some images in this tutorial might be broken because at the time of re-publishing this tutorial on think-clan.com, they did not exist anymore on the original domain.


Introduction


So here's my *second* GSC tutorial for you all. It's a very simple one - a welcome message.
I'll show you how to make the script, and explain in detail how it works as well as give you
some pointers for optimising and improving your code.

Table of Contents
  1. Prerequisites
  2. Getting started
  3. Testing it out!
  4. Improving our code
  5. Final notes


1. Prerequisites


Open up your Call of Duty/main folder. If you don't have one already, make a folder called 'maps'. Navigate inside of maps, and then make another folder called 'mp'. Navigate inside mp, and then make a final folder called 'gametypes'. You should have a directory structure like this:

Call of Duty/main/maps/mp/gametypes

1.1 is really neat in that you do not have to create a PK3 file EVERYTIME you want to test your scripts. We can actually just place the .gsc files in the gametypes folder, run CoD, update the scripts, restart the map and everything will be updated nice and easy. :)

I HIGHLY suggest using a text editor such as Notepad++ or Programmer's Notepad for ANY scripting! It will make your life much easier than just using notepad! Notepad++ and Programmer's Notepad. Once you open a script, I suggest you set the code formatting/language to 'C', as CoD scripting is based off of it.


2. Getting started


After we've created our folders, the next thing we need to do is extract the 'dm.gsc' file from pak5.pk3. Navigate to your main folder and open pak5.pk3 with your favorite archiver (I use Winrar, but Winzip or 7zip work as well). Open the maps/mp/gametypes folder and extract 'dm.gsc' to the gametypes folder you made in your main folder. Close pak5.pk3.

Now you should have dm.gsc in your main/maps/mp/gametypes folder. Let's open it up.

As soon as you open it, you'll be greeted by this:

/*
    Deathmatch
    Objective:     Score points by eliminating other players
    Map ends:    When one player reaches the score limit, or time limit is reached
    Respawning:    No wait / Away from other players

    Level requirements
    ------------------
        Spawnpoints:
            classname        mp_deathmatch_spawn
            All players spawn from these. The spawnpoint chosen is dependent on the current locations of enemies at the time of spawn.
            Players generally spawn away from enemies.

        Spectator Spawnpoints:
            classname        mp_deathmatch_intermission
            Spectators spawn from these and intermission is viewed from these positions.
            Atleast one is required, any more and they are randomly chosen between.

    Level script requirements
    -------------------------
        Team Definitions:
            game["allies"] = "american";
            game["axis"] = "german";
            Because Deathmatch doesn't have teams with regard to gameplay or scoring, this effectively sets the available weapons.
    
        If using minefields or exploders:
            maps\mp\_load::main();
...

No need to pay any attention to this -- although it is good to read through. This comment will tell you everything you need to know about creating a map that works properly with the DM gametype. We don't need to know this though -- so skip down a bit until you find the main() function.

main()
{
    level.callbackStartGameType = ::Callback_StartGameType;
    level.callbackPlayerConnect = ::Callback_PlayerConnect;
    level.callbackPlayerDisconnect = ::Callback_PlayerDisconnect;
    level.callbackPlayerDamage = ::Callback_PlayerDamage;
    level.callbackPlayerKilled = ::Callback_PlayerKilled;

    maps\mp\gametypes\_callbacksetup::SetupCallbacks();
    
    allowed[0] = "dm";
    maps\mp\gametypes\_gameobjects::main(allowed);
    
    if(getcvar("scr_dm_timelimit") == "")        // Time limit per map
        setcvar("scr_dm_timelimit", "30");
    else if(getcvarfloat("scr_dm_timelimit") > 1440)
        setcvar("scr_dm_timelimit", "1440");
    level.timelimit = getcvarfloat("scr_dm_timelimit");

    if(getcvar("scr_dm_scorelimit") == "")        // Score limit per map
        setcvar("scr_dm_scorelimit", "50");
    level.scorelimit = getcvarint("scr_dm_scorelimit");

    if(getcvar("scr_forcerespawn") == "")        // Force respawning
        setcvar("scr_forcerespawn", "0");

    if(getcvar("g_allowvote") == "")
        setcvar("g_allowvote", "1");
    level.allowvote = getcvarint("g_allowvote");
    setcvar("scr_allow_vote", level.allowvote);

    if(!isdefined(game["state"]))
        game["state"] = "playing";

    level.QuickMessageToAll = true;
    level.mapended = false;
    level.healthqueue = [];
    level.healthqueuecurrent = 0;

    spawnpointname = "mp_deathmatch_spawn";
    spawnpoints = getentarray(spawnpointname, "classname");

    if(spawnpoints.size > 0)
    {
        for(i = 0; i < spawnpoints.size; i++)
            spawnpoints[i] placeSpawnpoint();
    }
    else
        maps\mp\_utility::error("NO " + spawnpointname + " SPAWNPOINTS IN MAP");
        
    setarchive(true);
}

This is the entry point for our gametype. CoD calls this function and expects it to exist. If you renamed main(), it would break the gametype. So don't do that.

What we want to create is a welcome message that is displayed to the player when they join. Just below main() and Callback_StartGametype() is a function called Callback_PlayerConnect(). This is the function we want.

Callback_PlayerConnect()
{
    self.statusicon = "gfx/hud/hud@status_connecting.tga";
    self waittill("begin");
    self.statusicon = "";

    iprintln(&"MPSCRIPT_CONNECTED", self);

    lpselfnum = self getEntityNumber();
    logPrint("J;" + lpselfnum + ";" + self.name + "\n");
    
    if(game["state"] == "intermission")
    {
        spawnIntermission();
        return;
    }

    level endon("intermission");
...

Right below the line iprintln(&"MPSCRIPT_CONNECTED", self);, let's add a variable to our player.

...
iprintln(&"MPSCRIPT_CONNECTED", self);

self.wasWelcomed = false;
...

We'll use this later, but for right now all this code says is self.wasWelcomed is false. :)

Now, let's scroll on down to the function spawnPlayer() (or you can do a CTRL-F and search for it :)).


spawnPlayer()
{
    self notify("spawned");
    self notify("end_respawn");

    resettimeout();

//    if(isdefined(self.shocked))
//    {
//        self stopShellshock();
//        self.shocked = undefined;
//    }

    self.sessionteam = "none";
    self.sessionstate = "playing";
    self.spectatorclient = -1;
    self.archivetime = 0;
        
    spawnpointname = "mp_deathmatch_spawn";
    spawnpoints = getentarray(spawnpointname, "classname");
    spawnpoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_DM(spawnpoints);

    if(isdefined(spawnpoint))
        self spawn(spawnpoint.origin, spawnpoint.angles);
    else
        maps\mp\_utility::error("NO " + spawnpointname + " SPAWNPOINTS IN MAP");

    self.statusicon = "";
    self.maxhealth = 100;
    self.health = self.maxhealth;

    if(!isdefined(self.pers["savedmodel"]))
        maps\mp\gametypes\_teams::model();
    else
        maps\mp\_utility::loadModel(self.pers["savedmodel"]);

    maps\mp\gametypes\_teams::loadout();

    self giveWeapon(self.pers["weapon"]);
    self giveMaxAmmo(self.pers["weapon"]);
    self setSpawnWeapon(self.pers["weapon"]);
    
    self setClientCvar("cg_objectiveText", &"DM_KILL_OTHER_PLAYERS");
}

Let's add a little bit to the end right before the final closing brace '}'.

...
    self setClientCvar("cg_objectiveText", &"DM_KILL_OTHER_PLAYERS");
    
    if ( !self.wasWelcomed )
    {
        self iPrintLnBold( "Welcome to our server!" );
        self.wasWelcomed = true;
    }
}

Our code, translated into psuedocode, does this:

if NOT self.wasWelcomed equals true then
    self print to center screen "Welcome to our server!"
    set self.wasWelcomed to TRUE
end if

In other words, if self.wasWelcomed is equal to false, then we know he has not been welcomed, so we print a message to his screen, and then set our variable to true so we later know he WAS welcomed. Easy, right?


3. Testing it out!


Save your work and let's open CoD. Once open, type this into your console:

/devmap mp_harbor

It will load harbor with cheats (which is helpful when testing code). If you did everything right, you should see this:

http://www.cheesebox.net/shot0044.jpg

If not, you'll see something like this:

http://www.cheesebox.net/broken.jpg

All this means is that there's an error in your code. To see what the problem was, type /developer 1 in the console, and /devmap mp_harbor again. It will show the same screen again, but this time, bring the console back down and you'll see this:

http://www.cheesebox.net/broken2.jpg


So now we know something's wrong on line 625. Let's take a look at that line and the surrounding area:

...
    self setClientCvar("cg_objectiveText", &"DM_KILL_OTHER_PLAYERS");
    
    if ( !self.wasWelcomed )
    {
        self iPrintLnBold( "Welcome to our server!" );
        self.wasWelcomed = true
    }
}

The second to last curly brace '}' is the line it's saying, but if we look at the line above it, we'll see that we are missing a semicolon.

...
        self iPrintLnBold( "Welcome to our server!" );
        self.wasWelcomed = true // <-- missing semicolon here
    }
}

Add it, and test again and if you get another error, keep checking with /developer 1 to see what your problem is.

If you ever get one of these:

http://www.cheesebox.net/broken3.jpg

Your code is working fine! Don't freak out. This error means very little, and will go away once you set /developer 0. Only, and I repeat ONLY use developer mode when you have a SCRIPT COMPILE ERROR. EVER. :) Seriously, if you try to fix every 'script runtime error' you'll go nuts.



4. Improving our code



One of the most important things about coding (in any language) is optimisation. If you can do something in 5 lines of code instead of 20... DO IT. If instead of copy and pasting code, you can make a function that does the same thing, DO IT -- this type of code is called recursive. You always will want to do this. It will make your code better, and you can more easily debug and find issues this way.

So, taking a look at our code that we added (which isn't much, by the way), I see a few ways we can improve this. For one, we don't need to have our code in the dm.gsc. I like to keep all of my actual code in separate files so it's easier to debug than having to scroll through 1000's of lines of code. Let's make a file called _welcome.gsc and put it in the gametypes folder next to dm.gsc.

In it, we'll create a function called 'main()', which is to be called when the player connects.



_welcome.gsc:
main()
{
    self waittill( "spawned_player" );
    
    self iPrintLnBold( "Welcome to our server!" );
}

Now let's remove our code from spawnPlayer() in dm.gsc:
...
    self setClientCvar("cg_objectiveText", &"DM_KILL_OTHER_PLAYERS");
/* 
    if ( !self.wasWelcomed )
    {
        self iPrintLnBold( "Welcome to our server!" );
        self.wasWelcomed = true
    }
*/
}

And at the beginning of spawnPlayer(), add this:
spawnPlayer()
{
    self notify("spawned");
    self notify("end_respawn");
// add
    self notify( "spawned_player" );
...

And instead of having this in Callback_PlayerConnect():
...
iprintln(&"MPSCRIPT_CONNECTED", self);

self.wasWelcomed = false;
...

Let's do this:
...
iprintln(&"MPSCRIPT_CONNECTED", self);

self thread maps\mp\gametypes\_welcome::main();
...

Basically, what this will do is start a 'thread' on a player. The thread is our main() function in _welcome, and it will run it for us.

So now when we test, it looks exactly the same. But notice the code is slightly different. Let's take a look at how this works.

_welcome.gsc:
main()
{
    self waittill( "spawned_player" );
    
    self iPrintLnBold( "Welcome to our server!" );
}

This function is called when the player connects. We're using a more advanced feature of CoD on the third line -- notify/waittill. What you can do with this is notify an object of an event, in this case, the spawnPlayer() function notifies our player "spawned", and you can tell code to WAIT UNTIL that event happens, hence "waittill spawned".

Any code you put above the waittill will execute immediately, but the code afterwards will only execute once the event happens. Basically, instead of having to check a variable every time the player spawns, we're only running this code ONCE. Not only that, but we have our code nice and neatly in a new file for easy debugging.

This also allows us to add more extravagant things to our code, such as the ability to have the server owner set their own welcome messages, like this:
main()
{
    messages = [];
    i = 0;
    while ( getCvar( "welcome_message" + i ) != "" )
    {
        messages[ messages.size ] = getCvar( "welcome_message" + i );
        i++;
    }
    
    self waittill( "spawned_player" );
    
    for ( i = 0; i < messages.size; i++ )
    {
        self iPrintLnBold( messages[ i ] );
        wait 2;
    }
}

In the server config, you could then set this:
set welcome_message0 "Hey there."
set welcome_message1 "Welcome to our server."
set welcome_message2 "We like bacon."

And the game would do this:

http://www.cheesebox.net/shot0045.jpg

Pretty cool, eh? :D


5. Final notes


I know this is a lot of information for such a simple tutorial, but once you have a grasp of it, it will be easy for you. No coding is ever simple, especially game coding. But have some faith -- with a little practice you'll get the hang of it.

If you have any specific tutorials you want me to show, like how to integrate stuff with CoDaM, or how to make people into cows, etc. LET ME KNOW! I'll be glad to post some specific examples of how things work. ;)


Tutorial Details

Created: 3 years ago by ^w Y 6 | Cheese

Views: 4980 Views

Keywords: Quakec, scripting, modding, coding, gsc,

Share this Knowledge with your friends!

Direct Link:

0 Comments

Please sign in or create an account to post a comment.