Jump to content

[TUT]Good programming habits Warcraft 3: Expansion


`Rοmeο
 Share

Recommended Posts

Since the release of my source code for Warcraft 3: Expansion, I've gotten a lot of praise from people saying things like "wow that's the cleanest code I have ever seen!", and "where did you learn to code like that?", and my favorite, "that must have taken you forever to get your code that neat", etc etc...

 

...but this is simply not the case.. If you adapt to good programing habits at the start of a project, your code will look clean and professional at the very end as well. And that's what I'm here to help everyone with today.

 

Now let me start by saying that I've looked at the source code to lots of plugins, and overall they are very sloppy. This is not to say they were not coded well, more that they could have been layed out better. This is helpful when admins try to alter bits and pieces for their server(s), or if someone is looking to revamp or convert a script over to another platform.

 

-- (BEGIN BABBLING) --

 

I'll let you all in on a secret.. I used to play War3mod (by spacedude) and i LOVED it, however, crit nades, teleportation, and levitation really hurt the server(s) I played on because (especially with levitation and teleport) these skills were used and abused by the players, and there were not many admins available at peak times. My intention was simply to modify these skills so that they re-balanced the game while keeping it fun, but I took one look at the source code and decided I NEEDED to write it from scratch. So I did. And so WAR3X was born.

 

-- (END BABBLING) --

 

So let's get down to it. Below is a list of things that can be done to improve your programing habits, which will make them easier for you and others to edit in the long run.

Meaningful Variable Names (MODIFIED)

Many people have a problem with variable names. I've seen variables hit with such a bad case of shorthand that by looking at them, it is a mystery where they came from and what they do. Don't be afraid to use more than 10 characters in your variable names! The key is READABILITY!

 

One good habit to get into is using Hungarian Notation (HN) where applicable. For example, all global variables should start with "g_". This will let anyone know that looks at your code that "hey, this is a global variable!", without needing to see where and how it was initialized.

 

It is also helpful to prefix any variable that will be used in mathematical equasions with HN, ie- integers with "i", floats with "f", etc, etc. Even though Small is different from other languages in that every variable, no matter the "label", is actually an integer (even floats!), by using a little bit of HN you're almost guaranteed never to get a tag mismatch warning on your compile.

 

Don't overdo it, though!!! The key once again is READABILITY. If you don't know which HN to prefix with, then don't bother. Come up with your own naming convention. A good example is player indexes. using "id" is great. but if you're using 2 or more different player indexes in a bit of your code, differenciate them a bit, like "attackerId" and "victimId" or "killerId", etc.

 

Remember the phrase KISS - Keep it simple, stupid!

Naming Schemes

Good naming schemes are always a sight for sore eyes. Prefixing similar functions/variables with a common word makes for easy reading and editing of source code.

 

For example, If you have in your script events for damage and death, avoid naming them Damage_Event() and Death_Event(). Instead, name them Event_Damage(), and Event_Death(). For text editors that have function lists (Ultraedit32 is what I use), these will appear right on top of each other, adding to the organization of the plugin.

 

The same goes for variables. If you are trying to store a player's current health and armor as global variables, try naming them something like g_iPlayerHealth and g_iPlayerArmor.

Multiple Files

Multiple files will help you keep your plugin(s) very organized. This is not necessary for small scripts, however, when your script begins to grow very large, this is definately a good idea. You'd be amazed at how much throwing all of your events into their own file really cleans up your code.

 

There is one minor problem with combining files, though, and that is the inaccurate display of runtime errors (if they exist). If your script is made up of 5 files, and in the 5th file there is a runtime on line 10, AMXX will report your error on line 10, even if there are 500 lines of code in file 4 alone.

 

To fix this problem for war3x, Willyumyum came up with an INLINE FILE COMBINER, which combines all of our inline files into one large file, which we then compile. This program will be made public, complete with a nice looking GUI frontend, within the next month or so

Use constants

Another thing you want to avoid while programing, is hard-coding things into your scripts. Use constants, and #defines instead. Even if something is used only once in your plugin, it might be worth making a constant for it. Why? because constants take normal things, like numbers, and make them into something named, which are far easier to remember. They also make changing an aspect of your plugin as easy as changing one constant, instead of having to change EVERY occurance of the number '3' in your code, for example.

 

Here's another example... You want to write a plugin that acts like an RPG, and will have 4 races, with 3 classes per race. You want to save a couple of constants like so:

 

#define TOTAL_RACES   4
#define TOTAL_CLASSES 3 

 

This will make performing loops much easier, and if you decide to add an additional race in the future, All you'd need to do is change TOTAL_RACES to 5.

Make extensive use of comments!

Even though you may know every inch of your plugin, a foreign set of eyes may not, and as your script gets bigger, you may forget about things yourself.

 

If there is ever something you can not finish, or don't quite know how to implement, a comment can be a useful tool. Putting one in your code may catch a fellow coder's attention that has a solution to a problem you may be having, etc etc. For example..

 

/* for some reason, this sequence screws up if this
    variable is not initialized to 0 */
new iVariable = 0;

 

This could prevent another programer from looking at your plugin and removing this seemingly redundant initialization, in turn causing untraceable problems in the plugin down the road. So do yourself a favor, and add the comments in. You will thank yourself in the long run.

Close with semicolons (MODIFIED)

I want to emphasise that this is NOT REQUIRED by the Small language, but a good habit to get into is closing out your lines with semicolons. This is because if you have any intentions of learning any other language, you're almost guaranteed that you will need to close out your lines with them. So again, not required, but overall a good habit.

 

/* For people that want to force the compiler to look for closing semi's,
    you can force them with the following: */
#pragma semicolon 1 

 

 

Conformity (NEW)

If you pick a style, stick with it. Don't make multiple coding standards in one document. (From: BAILOPAN)

Indentation, PLEASE!

I've seen a LOT of scripts that do not do a single bit of indentation. This is NIGHTMARE to debug!!! Every time you open a brace "{" INDENT! this means at the start of functions, the start of if/then conditionals, switches, etc, etc, etc, etc, etc, etc, etc, etc, etc! Please no more of this!...

 

public some_function(id){
new szVariable[10]
format(szVariable,9,"hello!!!!")
if(id==0){
server_print(szVariable)
}else{
client_print(id,print_chat,szVariable)
}
return PLUGIN_HANDLED
}

 

Take the time to make it READABLE! Like so..

 

public some_function( id ) {

    new szVariable[10];
    format( szVariable, 9, "hello!!!!" );

    if ( id == 0 )
    {
        server_print( szVariable );
    }

    else
    {
        client_print( id, print_chat, szVariable );
    }

    return PLUGIN_HANDLED;
}

 

 

Whitespace, Whitespace, Whitespace!!!

This I saved for last because it is the MOST IMPORTANT!!! whitespace does not add to the size of your compiled code (because it is overlooked by the compiler), but adds a whole new dimension of readability to your plugin. Avoid clumping things together. Use common sense and separate things from each other. This means adding spaces after every function argument, indents after every conditional statement, and linefeeds to separate unrelated actions. Also use comments, and be sure to space out all arguments inside of parenthesis ()'s. (see example above).

 

I'm going to use a function right out of one of the default .sma files that come with both AMX and AMXMODX, originally written by OLO.

 

Original Code

 

 

public cmdSlay(id,level,cid) {
  if (!cmd_access(id,level,cid,2))
    return PLUGIN_HANDLED
  new arg[32]
  read_argv(1,arg,31)
  new player = cmd_target(id,arg,5)
  if (!player) return PLUGIN_HANDLED
  user_kill(player)
  new authid[32],name2[32],authid2[32],name[32]
  get_user_authid(id,authid,31)
  get_user_name(id,name,31)
  get_user_authid(player,authid2,31)
  get_user_name(player,name2,31)
  log_amx("Cmd: ^"%s<%d><%s><>^" slay ^"%s<%d><%s><>^"",
    name,get_user_userid(id),authid, name2,get_user_userid(player),authid2 )
    
  switch (get_cvar_num("amx_show_activity")) {
    case 2: client_print(0,print_chat,"%L",LANG_PLAYER,"ADMIN_SLAY_2",name,name2)
    case 1: client_print(0,print_chat,"%L",LANG_PLAYER,"ADMIN_SLAY_1",name2)
  }   
    
  console_print(id,"[AMXX] %L",id,"CLIENT_SLAYED",name2)
  return PLUGIN_HANDLED
}

 

Polished Code

 

public cmdSlay( id, level, cid ) {

  if ( !cmd_access( id, level, cid, 2 ) )
    return PLUGIN_HANDLED;

  new szArg[32];
  read_argv( 1, szArg, 31 );

  // Get ID of player to be slayed

  new targetId = cmd_target( id, szArg, 5 );

  if ( !targetId )
    return PLUGIN_HANDLED;

  user_kill( targetId );

  // Grab admin/target names/authids

  new szAuthid[32], szName[32], szAuthid2[32], szName2[32];

  get_user_authid( id, szAuthid, 31 );
  get_user_name( id, szName, 31 );
  get_user_authid( targetId, szAuthid2, 31 );
  get_user_name( targetId, szName2, 31 );

  // Log command

  log_amx( "Cmd: ^"%s<%d><%s><>^" slay ^"%s<%d><%s><>^"", 
    szName, get_user_userid( id ), szAuthid, szName2, get_user_userid( targetId ), azAuthid2 );
    
  // Display slay to all other players

  switch ( get_cvar_num( "amx_show_activity" ) )
  {
    case 2: client_print( 0, print_chat, "%L", LANG_PLAYER, "ADMIN_SLAY_2", szName, szName2 );
    case 1: client_print( 0, print_chat, "%L", LANG_PLAYER, "ADMIN_SLAY_1", szName2 );
  }   

  // Confirm slay to admin
    
  console_print( id, "[AMXX] %L", id, "CLIENT_SLAYED", szName2 );

  return PLUGIN_HANDLED;
}

 

 

I hope this information is helpful to everyone that wants to clean up their code a little bit! (or a lot)

 

Take care

 

By: Ryan

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share



×
×
  • Create New...

AdBlock Extension Detected!

Our website is made possible by displaying online advertisements to our members.

Please disable AdBlock browser extension first, to be able to use our community.

I've Disabled AdBlock