Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 39 | Next >>

menu

  • Home/News
  • ModSource
  • Compiling
  • Help!!!
  • Submission
  • Contributors
  • Staff
  • Downloads

    Tutorials
    < Index >
    1. Mod making 101
    2. Up 'n running
    3. Hello, QWorld!
    4. Infinite Haste
    5. Armor Piercing Rails
    6. Bouncing Rockets
    7. Cloaking
    8. Ladders
    9. Favourite Server
    10. Flame Thrower
    11. Vortex Grenades
    12. Grapple
    13. Lightning Discharge
    14. Locational Damage
    15. Leg Shots
    16. Weapon Switching
    17. Scoreboard frag-rate
    18. Vortex Grenades II
    19. Vulnerable Missiles
    20. Creating Classes
    21. Scrolling Credits
    22. Weapon Dropping
    23. Anti-Gravity Boots
    24. HUD scoreboard
    25. Flashlight and laser
    26. Weapon Positioning
    27. Weapon Reloading
    28. Progressive Zooming
    29. Rotating Doors
    30. Beheading (headshot!)
    31. Alt Weapon Fire
    32. Popup Menus I
    33. Popup Menus II
    34. Cluster Grenades
    35. Homing Rockets
    36. Spreadfire Powerup
    37. Instagib gameplay
    38. Accelerating rockets
    39. Server only Instagib
    40. Advanced Grapple Hook
    41. Unlagging your mod


    Articles
    < Index >
    1. Entities
    2. Vectors
    3. Good Coding
    4. Compilers I
    5. Compilers II
    6. UI Menu Primer I
    7. UI Menu Primer II
    8. UI Menu Primer III
    9. QVM Communication, Cvars, commands
    10. Metrowerks CodeWarrior
    11. 1.27g code, bugs, batch


    Links

  • Quake3 Files
  • Quake3 Forums
  • Q3A Editing Message Board
  • Quake3 Editing


    Feedback

  • SumFuka
  • Calrathan
  • HypoThermia
  • WarZone





    Site Design by:
    ICEmosis Design


  • TUTORIAL 39 - Server only InstaGib
    by Steven Conway

    Well in the original tutorial, Malcolm Lim presented the bare bones of what to do to remove all of the items without breaking the bot code. In this tutorial, I will show you how to break the bot code, make the whole mod server side only, make sure that we can still play CTF, and then fix up the bot code. I will be assuming that we all read the first tutorial.

    As it stands at the moment, this is railgun only gameplay. Since health counts down from 125, the first hit on a just spawned player won't be fatal. To remedy this, you'll need to add section 3 of Malcolm's tutorial.

    Files that will change:

    g_client.c
    g_items.c
    g_combat.c
    ai_dmq3.c
    ai_main.c

     

    1. G_CLIENT.C

    As we already know Malcolm gave us all 999 slugs to go play with. This is the maximum amount that the client can display (it's limited to 3 digits), hence the reason why people like to use 999 slugs. So another way of doing this is the following...

    void ClientSpawn(gentity_t *ent) {
    
    *** Code Snipped ***
    
    	client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET );
    	client->ps.ammo[WP_GAUNTLET] = -1;
    	client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
    
    
    	//SCO
    	client->ps.stats[STAT_WEAPONS] = (1 << WP_RAILGUN );
    	client->ps.ammo[WP_RAILGUN] = INFINITE;
    	//end SCO
    
    
    	// health will count down towards max_health

    *** Code Snipped ***

    As Malcolm said, using -1 breaks the bot code (but this is an easy fix) and more annoyingly clients would have the "LOW AMMO" message printed on their display permanently. Getting rid of the "LOW AMMO" message would require a client modification, so thats out of the question for a server side mod.

    If you set it to 999 the client will still predict taking away ammo for every shot, and this will be displayed. I don't like this method since the client will display some changes in the railgun ammo.

    My method is to use a number that is big enough so that it doesn't overload in memory, and won't display any ammo changes. The bonus with this method is that all clients will see that they have 999 slugs, but they will actually have 1,000,000 (one million) slugs. Now if any one can use all of them, without dying they really need help since it will take them over 17 days straight to run out of ammo.

     

    2. G_ITEMS.C

    Well Malcolm did suggest there was room for improvement, so here it is. In the function Touch_Item() touching items was prevented by just returning. But what it we touched a flag like in CTF? Well a simple check for the type of object will fix this.

    
    void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
    	int			respawn;
    	qboolean	predict;
    
    	//SCO if ent-item is some sort of team item.
    	if (ent->item->giType != IT_TEAM)
    		return;
    
    	if (!other->client)
    		return;
    
    ***Code Snipped ***
    
    
    Well that was easy enough, but what about when the items spawn? Now Malcolm hasn't spawned CTF flags for us to pick up, so let's check this bit of code. In G_SpawnItem() the spawning of the items was disabled, but they were also registered too. i.e. when you load the map it loaded the plasma gun, health, and all the rest. We'll fix this too.

    void G_SpawnItem (gentity_t *ent, gitem_t *item) {
    	G_SpawnFloat( "random", "0", &ent->random );
    	G_SpawnFloat( "wait", "0", &ent->wait );
    
    
    	//SCO
    	//If it's a team item then we want to spwan it. Otherwise make it invisible.
    	if (item->giType == IT_TEAM) {
    
    		RegisterItem( item );
    		if ( G_ItemDisabled(item) )
    			return;
    
    	}
    	else {
    		//Ahh what does this do then
    		ent->r.svFlags = SVF_NOCLIENT;
    		//setting this flag makes the item invisible.
    		ent->s.eFlags |= EF_NODRAW;
    	}
    
    
    	ent->item = item;
    *** Code snipped ***
    

    Well it's pretty self explanatory with the comments. But what's the chop with this ent->r.svFlags = SVF_NOCLIENT? If we do a search for it we find this in g_public.h...

    // entity->svFlags
    // the server does not know how to interpret most of the values
    // in entityStates (level eType), so the game must explicitly flag
    // special server behaviors
    
    // don't send entity to clients,
    //even if it has effects
    //bots also can't see this item
    #define	SVF_NOCLIENT		0x00000001
    
    // set if the entity is a bot
    #define SVF_BOT			0x00000008
    
    // send to all connected clients
    #define	SVF_BROADCAST		0x00000020
    
    // merge a second pvs at origin2 into snapshots
    #define	SVF_PORTAL		0x00000040
    
    // entity->r.currentOrigin instead of entity->s.origin
    // for link position (missiles and movers)
    #define	SVF_USE_CURRENT_ORIGIN	0x00000080
    
    // only send to a single client
    #define SVF_SINGLECLIENT	0x00000100
    

    I've moved the comments so that they take up less line width.

    Ahhh! So when we set SVF_NOCLIENT, all of the clients won't even know the item exists. It also means that this item is not sent to the clients for item prediction. As an added "bonus" of doing this, we've managed to break the bot code. What a great excuse to do some bot coding.

    Now we head over to ClearRegisteredItems to register the railgun when the map loads. We do this to just tidy up the mod.

    void ClearRegisteredItems( void ) {
    	memset( itemRegistered, 0, sizeof( itemRegistered ) );
    	// players always start with the base weapon
    	//We don't want the machinegun or the Gauntlet
    	//RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) );
    	//RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) );
    
    	//register that rail gun
    	RegisterItem( BG_FindItemForWeapon( WP_RAILGUN ) );
    
    *** Code Snipped ***
    

    That will register the rail gun and also remove the machinegun and the gauntlet from being registered.

    Now remember those powerups that are in normal Quake3? They don't get spawned with all the other items when the map starts. They are deferred a little, and are spawned about 45 seconds into the game. With the changes made so far the powerups will still respawn later in the game. So lets head on over to the FinishSpawningItem() function to fix this problem.

    void FinishSpawningItem( gentity_t *ent ) {
    
    *** Code Snipped ***
    
    	// team slaves and targeted items aren't present at start
    	if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
    		ent->s.eFlags |= EF_NODRAW;
    		ent->r.contents = 0;
    		return;
    	}
    //SCO We don't want the powerups to be spawning later in the game.
    	/*
    	// powerups don't spawn in for a while
    	if ( ent->item->giType == IT_POWERUP ) {
    		float	respawn;
    
    		respawn = 45 + crandom() * 15;
    		ent->s.eFlags |= EF_NODRAW;
    		ent->r.contents = 0;
    		ent->nextthink = level.time + respawn * 1000;
    		ent->think = RespawnItem;
    		return;
    	}*/
    //end SCO
    
    	trap_LinkEntity (ent);
    }
    
    

    By commenting out this statement you will be making sure that the powerups don't respawn later on in the game.

    Now we can finally save g_items.c and move onto the next file.

     

    3. G_COMBAT.C

    Just to keep things clean on the map, we're going to remove all dropping of weapons. So now we have to look at the TossClientItems() function.

    void TossClientItems( gentity_t *self ) {
    	gitem_t		*item;
    	//SCO	int	weapon;
    	float		angle;
    	int		i;
    	gentity_t	*drop;
    //SCO commented out so that we don't drop any weapons
    	/*
    	// drop the weapon if not a gauntlet or machinegun
    weapon = self->s.weapon;

    *** Code Snipped *** // spawn the item Drop_Item( self, item, 0 ); } */ // drop all the powerups if not in teamplay

    Since we're not spawning powerups, the only items that will be dropped are the CTF flags.

    Summary of what we have done so far:

    • made the CTF flags touchable, and left it predictable on the client,
    • ensured that weapons aren't dropped,
    • made all modifications server side only,
    • managed to break the bot code.

     

    4. AI_MAIN.C

    Now it's time to fix up that bot code. If compiled and ran at the moment, the bots would stand still (until they see you).

    Let's start by thinking what the bot code is trying to achieve. It is trying to provide similar processes to what a human would do without accessing information that a normal player wouldn't have (i.e. health of other clients, exact location of players and items such as CTF flags). Knowing such information would be classed as cheating.

    An example of how the bot reacts like a human would be the respawn sound of a powerup. If we (as humans) hear the powerup respawn sound we head for the powerup (generally).

    We can see this in the bot code, pretty much exactly the same way a human gets this information. It comes through events (specifically in this case EV_GLOBAL_SOUND). The client receives this information and plays the appropriate sound, and we have the intelligence (cough...) to make the decision whether or not to go and get the powerup.

    Bots do exactly the same thing. Have a look in ai_q3dm.c in the BotCheckEvents() function. It shows us that for most of the events that are broadcasted to clients (such as EV_GLOBAL_SOUND) the bot will do something. This can be seen from the extract from the code...

    
    	case EV_GLOBAL_SOUND:
    	{
    *** Code Snipped ***
    	//Was the sound the powerup respawn sound
    	if (!strcmp(buf, "sound/items/poweruprespawn.wav")) {
    
    		//powerup respawned... go get it
    		BotGoForPowerups(bs);
    	}
    	break;
    *** Code Snipped ***
    

    The same goes for items spread around the map. Even when they're waiting to respawn, we still know where they are. But if the item (or rather the entity) had the SVF_NOCLIENT bit set, then the item never appears to the client. So the bots should have the same sort of thinking and not go for these items.

    So in a nut shell, the bots simply don't move because they don't see an item that can be used as an objective. This is why the bots don't move until they spot another player to kill.

    So we do a little search for SVF_NOCLIENT, and we find two instances of it in the bot code. They both appear in ai_main.c. So let's see what's happening in BotAI_GetEntityState():

    
    int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
    	gentity_t	*ent;
    	ent = &g_entities[entityNum];
    	memset( state, 0, sizeof(entityState_t) );
    	if (!ent->inuse) return qfalse;
    	if (!ent->r.linked) return qfalse;
    	//if (ent->r.svFlags & SVF_NOCLIENT) return qfalse;//Comment out this line
    
    	memcpy( state, &ent->s, sizeof(entityState_t) );
    	return qtrue;
    }
    

    Why did we comment out that line for? Because the bot thinks that the item/entity is not there so it returns qfalse, which means that the item has isn't on the map. But we still want the bot to move around so it will now try and use these items as objectives.

    The second one is found in BotAIStartFrame():

    int BotAIStartFrame(int time) {
    
    *** Code snipped ***
    
    		trap_BotLibUpdateEntity(i, NULL);
    		continue;
    	}
    
    //SCO
    	/*if (ent->r.svFlags & SVF_NOCLIENT) {
    		trap_BotLibUpdateEntity(i, NULL);
    		continue;
    	}*/ //comment out the entire if statement
    
    	// do not update missiles
    	if (ent->s.eType == ET_MISSILE) {
    
    *** Code snipped ***
    

    Why did we do that one as well? The bots keep all the information about all of the items in what is known as the bot library. Just like humans, we know where all the items are in the map, and we know how many players that are playing etc.. We also can remember that when we see an item and if its not there we would move on. In the bots case, if the SVF_NOCLIENT bit is set, then the entity is removed from the library permanently.

     

    5. Extra Bot Coding

    The following is optional, but it will make your bot code a little more efficient. BotChooseWeapon() is of course a function that could be edited, since we only have one weapon. We could get rid of it entirely, but I have chosen not to since I want to demonstrate something. If you've ever seen the message "ERROR: Weapon out of range", then this function where this message occurs. You've probably done a search for the error message and couldn't find it. Well the error message is actually sent from the executable (quake3.exe). So to eliminate this problem we would do the following.

    void BotChooseWeapon(bot_state_t *bs) {
    	int newweaponnum;
    	if (bs->cur_ps.weaponstate == WEAPON_RAISING ||
    		bs->cur_ps.weaponstate == WEAPON_DROPPING) {
    		trap_EA_SelectWeapon(bs->client, bs->weaponnum);
    	}
    	else {
    //SCO   we want to comment out this line since we only have the railgun
    	//newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory);
    
    	newweaponnum = WP_RAILGUN; //We only have one weapon so let's choose it.
    	if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime();
    		bs->weaponnum = newweaponnum;
    		//BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
    		trap_EA_SelectWeapon(bs->client, bs->weaponnum);
    	}
    }
    
    You shouldn't get the error message any more.

    Now a lot of the bot code could be cut back quite drastically since we only have one weapon, unlimited ammo, and no real need for health and armor. The following functions are examples of what I have done to optimize the code. Even if they don't make any kind of a speed difference, they're useful for when you're trying to teach bots about new weapons.

    I have choosen to edit the BotAggression() function since it is called in all of the major decision making parts of the code, and is therefore pretty important. I've edited mine to look like this. It is a complete replacement of the original BotAggression function.

    
    float BotAggression(bot_state_t *bs) {
    	//if the enemy is located way higher than the bot
    	if (bs->inventory[ENEMY_HEIGHT] > 200) return 0;
    	return 100;
    }
    
    The BotAggression() function was full of if statements checking health, weapons ammo and the Quad. So as I mentioned before it is cut back quite drastically. I suggest commenting out iD's code instead of deleting it.

    Another function that can be cut back would be BotSelectActivateWeapon. Here's what I think it should do...

    
    int BotSelectActivateWeapon(bot_state_t *bs) {
    	return WEAPONINDEX_RAILGUN;
    }
    

    There are many other functions that are easily edited and concern weapons. Some functions that could undergo some modification are...

    • BotHasPersistantPowerupAndWeapon,
    • BotFellingBad (this function is only used in a Team Arena Mod),
    • BotCheckAttack (Removing any signs of Radial damage weapons),
    • BotAimAtEnemy (Only if you feel adventurous).

    Editing most of these functions will make the bot code more efficient rather than make the bots harder.

     

    6. Conclusion:

    Well that moves the whole instagib mod into a server only version. I hope you learnt something new, and even implement some of the features in your mod. If you do use any of this code please give credit to me (Steven Conway a.k.a Coners) and Malcolm Lim (a.k.a CyberKewl).

    If you have any problems about this tutorial contact me at niq3@planetquake.com, Oh, and don't forget to checkout my mod NIQ3.

    PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 39 | Next >>