Dynamic Weapon Spawns (Advanced): Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
(yawn)
Line 412: Line 412:


=== CWeaponSetEnt ===
=== CWeaponSetEnt ===
Okay, this is the class that represents the Hammer entity Level Designers are going to use. Since we gave most of the work to other classes this class looks very clean.
Okay, this is the class that represents the Hammer entity Level Designers are going to use. Since we gave most of the work to other structures this class is cleaner.


  //Logical entities are Point Entities that exist on the server
  //Logical entities are Point Entities that exist on the server
Line 496: Line 496:


{{todo}} The creation of CWeaponSet instance should be at "main()". Then point that out at SDK Modifications.
{{todo}} The creation of CWeaponSet instance should be at "main()". Then point that out at SDK Modifications.


=== ConCommands ===
=== ConCommands ===
Imagine we loaded the file weaponsets.txt presented at the beginning of the article. So WeaponSetH::CWeaponSet::weaponSetFile is pointing to a KeyValues structure with the contents of weaponsets.txt.
'''weaponset_modslot <itemSlot> <itemNick>'''<br>
:Modifies existing slot of current set.


'''weaponset_modslot <itemSlot> <itemNick>'''
'''weaponset_load <setname>'''<br>
Modifies existing slot of current set.
:Reload Weapon Set <setname> Information from weaponsets.txt, overwrites unsaved changes done to sets during the session. If param is "<all>" then load all weapon sets.<br>
:{{todo}} Game crashes after the command finishes.


'''weaponset_load <setname>'''
'''weaponset_save <setname>'''<br>
Reload Weapon Set <setname> Information from weaponsets.txt, overwrites unsaved changes done to sets during the session. If param is "<all>" then load all weapon sets.
:Save weapon sets <setname>, saves modifications done during the session to weaponsets.txt. If param is "<all>" it saves all the weapon sets.<br>
{{todo}} Game crashes after the command finishes.
:{{todo}} Game crashes if the param isnt "<all>"


'''weaponset_save <setname>'''
'''weaponset_use <setname>'''<br>
:Switch to <setname> weapon set.


Save weapon sets <setname>, saves modifications done during the session to weaponsets.txt. If param is "<all>" it saves all the weapon sets.
'''weaponset_renameto <setnewname>'''<br>
:Rename current weapon set to <setnewname>.


{{todo}} Game crashes if the param isnt "<all>"
'''weaponset_addslot <itemnick>'''<br>
:Add a new slot to current weapon set and assign <itemnick> to it.


'''weaponset_use <setname>'''
'''weaponset_removeslot <slotindex>'''<br>
:Remove existing slot with index <slotindex>.


Switch to <setname> weapon set.
==== AutoComplete ====
Someone new to the mod would use '''help weaponset_modslot''' and while he would have the idea of the parameters, printing '''weaponset_modslot <itemSlot> <itemNick>''' alone wouldn't help. That's why an AutoComplete function is neccessary.


'''weaponset_renameto <setnewname>'''
The autocomplete answers of weaponset_modslot are smart ones due to the nature of strstr() looking within a string. For example while typing pistol it would also show ammo_pistol and ammo_pistol_large as other options.


Rename current weapon set to <setnewname>.
[Image]
static int WeaponSetModSlotAutoComplete ( char const *partial,
char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) {
The constants here are global.
//1. Split by spaces
char arg[4][COMMAND_COMPLETION_ITEM_LENGTH] = {'\0'};
Init the string with '\0's so after using sscanf the string can be tested against "". It could be replaced by arg[i][0] = '\0'; Since COMMAND_etc is a global restriction we enforce it too :)
int argsReceived = sscanf(partial, "%s %s %s %s", arg[0], arg[1], arg[2], arg[3]);
The command structure is weaponset_modslot <slot> <itemnick>, that's 3 args, we use four just to know when the user has typed a fourth arg (or more). In that case the program should tell him that's not a correct string. sscanf will return the number of arguments actually read, we will use that number to know what to print on screen.
arg[0][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';
arg[1][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';
arg[2][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';
arg[3][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';
If the user entered a very long param then it will overflow the buffer, we close the string just in case.
const char *lastChar = &partial[strlen(partial)-1];
if(FStrEq(lastChar, " ")) ++argsReceived; //and the last char is " "
When the user enters "weaponset_modslot <something>" argsReceived will be 2 and we should be autocompleting arg[1].
However when they enter "weaponset_modslot <something> " (<-it has a space) we should be autocompleting arg[2] but argsReceived will still be 2. So if the last char is " " we add one to argsReceived.
//So we are autocompleting the first arg
if(argsReceived <= 2) {
//do stuff, here we initialize maxSlotNo
//copy a string to commands[0] while enforcing the restriction on length
Q_snprintf(commands[0], COMMAND_COMPLETION_ITEM_LENGTH,
"weaponset_modslot <slotindex maxvalue: %d> <itemnick>", maxSlotNo);
return 1; //we are only returning one result
};
  //So they managed to enter the first arg <slot>, get it
int slot = atoi(arg[1]);
//Too many args, tell him he is wrong by returning the "correct" command entered so far
//if slot is not a number it will show zero there forcing the user to check why
//the value is different than what he is typing
if(argsReceived >= 4) {
Q_snprintf(commands[0], COMMAND_COMPLETION_ITEM_LENGTH,
"weaponset_modslot %d %s", slot, arg[2]);
return 1;
};
//So we are completing <itemnick>
//if(argsReceived == 3)
int j = WeaponSetH::itemListSize; //first get how many items we have
int n = 0; //that's different than how many we are going to return, right now, none
WeaponSetH::Item *it;
for(int i = 0; i < j; i++) { //navigate through all the items
it = WeaponSetH::GetItemByArrayIndex(i);
if(!FStrEq(arg[2], "")) {
char *c = Q_strstr(  it->itemNick,  arg[2] );
if(!c) continue;
};
If the user has typed nothing on this arg yet (has typed "<arg0> <arg1> " <-space) then skip this and just add all the itemnicks to the list. However, if he has entered something already, if the itemnick entered isn't a substring of the item then just go to the next item. Otherwise add to the results.
Q_snprintf(commands[n], COMMAND_COMPLETION_ITEM_LENGTH,
"weaponset_modslot %d %s", slot, it->itemNick);
n++;
}
    return n; // finally return the number of entries found
}


'''weaponset_addslot <itemnick>'''


add a new slot to current weapon set and assign <itemnick> to it.
=== SDK Modifications ===
====BaseCombatCharacter.cpp====
void CBaseCombatCharacter::Weapon_Equip( CBaseCombatWeapon *pWeapon )
After the first for() there's this line:
  // Weapon is now on my team
pWeapon->ChangeTeam( GetTeamNumber() );
There we go:
// MOD
//We are setting the ammo at spawn, why should we set it again? Modify this.
//CWeaponSetEnt will set clips to 0. It will be primary/secondary ammo the ones keeping the counts
// ----------------------
//  Give Primary Ammo
// ----------------------
// If gun doesn't use clips, just give ammo
int primaryAmmo = pWeapon->GetPrimaryAmmoCount();
int secondAmmo = pWeapon->GetSecondaryAmmoCount();
pWeapon->SetPrimaryAmmoCount(0);
pWeapon->SetSecondaryAmmoCount(0);
if (pWeapon->GetMaxClip1() == -1) {
/* delete the Single Player RPG Hack */
GiveAmmo(primaryAmmo, pWeapon->m_iPrimaryAmmoType);
}
// If default ammo given is greater than clip
// size, fill clips and give extra ammo
else if (primaryAmmo >  pWeapon->GetMaxClip1() ) {
pWeapon->m_iClip1 = pWeapon->GetMaxClip1();
GiveAmmo( (primaryAmmo - pWeapon->GetMaxClip1()), pWeapon->m_iPrimaryAmmoType);
} else {
pWeapon->m_iClip1 = primaryAmmo;
}
// ----------------------
//  Give Secondary Ammo
// ----------------------
// If gun doesn't use clips, just give ammo
if (pWeapon->GetMaxClip2() == -1) {
GiveAmmo(secondAmmo, pWeapon->m_iSecondaryAmmoType);
}
// If default ammo given is greater than clip
// size, fill clips and give extra ammo
else if ( secondAmmo > pWeapon->GetMaxClip2() ) {
pWeapon->m_iClip2 = pWeapon->GetMaxClip2();
GiveAmmo( (secondAmmo - pWeapon->GetMaxClip2()), pWeapon->m_iSecondaryAmmoType);
} else {
pWeapon->m_iClip2 = secondAmmo;
}
// End MOD


'''weaponset_removeslot <slotindex>'''
Then it gets followed by the regular contents starting at:
pWeapon->Equip( this );
// Players don't automatically holster their current weapon
if ( IsPlayer() == false )


Remove existing slot with index <slotindex>.


==== AutoComplete ====
Now at bool CBaseCombatCharacter::Weapon_EquipAmmoOnly( CBaseCombatWeapon *pWeapon )
Someone new to the mod would use '''help weaponset_modslot''' and while he would have the idea of the parameters, printing '''weaponset_modslot <itemSlot> <itemNick>''' alone wouldn't help. That's why an AutoComplete function is neccessary.
bool CBaseCombatCharacter::Weapon_EquipAmmoOnly( CBaseCombatWeapon *pWeapon )
{
// Check for duplicates
for (int i=0;i<MAX_WEAPONS;i++)
{
if ( m_hMyWeapons[i].Get()
&& FClassnameIs(m_hMyWeapons[i], pWeapon->GetClassname()) )
{
// MOD
// Just give the ammo
if( pWeapon->UsesClipsForAmmo1() )
pWeapon->SetPrimaryAmmoCount(  pWeapon->GetPrimaryAmmoCount()
+ pWeapon->m_iClip1  );
if( pWeapon->UsesClipsForAmmo2() )
pWeapon->SetSecondaryAmmoCount(  pWeapon->GetSecondaryAmmoCount()
+ pWeapon->m_iClip2 );
int primaryGiven = pWeapon->GetPrimaryAmmoCount();
int secondaryGiven = pWeapon->GetSecondaryAmmoCount();
int takenPrimary
= GiveAmmo( primaryGiven, pWeapon->m_iPrimaryAmmoType);
int takenSecondary
= GiveAmmo( secondaryGiven,  pWeapon->m_iSecondaryAmmoType);
//Only succeed if we get to take ammo from the weapon
if ( takenPrimary == 0 && takenSecondary == 0 ) return false;
pWeapon->SetPrimaryAmmoCount( pWeapon->GetPrimaryAmmoCount()
- takenPrimary  );
pWeapon->SetSecondaryAmmoCount( pWeapon->GetSecondaryAmmoCount()
-  takenSecondary );
// End MOD
return true;
}
}
return false;
}


The autocomplete answers of weaponset_modslot are smart ones due to the nature of strstr() looking within a string. For example while typing pistol it would also show ammo_pistol and ammo_pistol_large as other options.
==== basecombatweapon.cpp ====
void CBaseCombatWeapon::Materialize( void )
RemoveEffects( EF_NODRAW );
DoMuzzleFlash();
}
#ifdef HL2MP
// MOD 'ed out
//if ( HasSpawnFlags( SF_NORESPAWN ) == false )
{
VPhysicsInitNormal( SOLID_BBOX, GetSolidFlags() | FSOLID_TRIGGER, false );
SetMoveType( MOVETYPE_VPHYSICS );
HL2MPRules()->AddLevelDesignerPlacedObject( this );
}
// End MOD


==== basecombatweapon_shared.cpp ==== //also add the prototipes to the .h
// MOD : Add Functions
void CBaseCombatWeapon::SetClip1(int amount) {
if(UsesClipsForAmmo1()) m_iClip1 = min(max(0, amount), GetMaxClip1());
}
void CBaseCombatWeapon::SetClip2(int amount) {
if(UsesClipsForAmmo2()) m_iClip2 = min(max(0, amount), GetMaxClip2());
}
// End MOD


=== SDK Modifications ===
==== KeyValues.cpp ==== //also add the prototypes to the .h
{{todo}} Modify the function names :)
/**
  * Long MOD
  */
//-----------------------------------------------------------------------------
// Purpose: Will Kill all my tree. Will detach me from my  parent and brothers first if parent != NULL
//-----------------------------------------------------------------------------
void KeyValues::ActualKillMyTree(KeyValues* parent) {
if(parent) parent->RemoveSubKey(this); //Left by father and brothers
RecursiveDelete();
}
//-----------------------------------------------------------------------------
// Purpose: Will Kill all my tree.
//-----------------------------------------------------------------------------
void KeyValues::RecursiveDelete() {
KeyValues *dat;
KeyValues *datPrev = NULL;
dat = m_pSub;
while(true) {
if(datPrev != NULL) {
datPrev->RecursiveDelete();
delete datPrev;
};
datPrev = dat;
if((dat == NULL) && (datPrev == NULL)) break;
dat = dat->m_pPeer;
}
}
char* KeyValues::GetMyStringDammit(void) {
// convert the data to string form then return it
char *answer = NULL;
char buf[64];
int len = 0;
switch(m_iDataType) {
case TYPE_FLOAT:
Q_snprintf( buf, sizeof( buf ), "%f", m_flValue );
// allocate memory for the new value and copy it in
len = Q_strlen(buf);
answer = new char[len + 1];
Q_memcpy( answer, buf, len+1 );
break;
case TYPE_INT:
Q_snprintf( buf, sizeof( buf ), "%d", m_iValue );
// allocate memory for the new value and copy it in
len = Q_strlen(buf);
answer = new char[len + 1];
Q_memcpy( answer, buf, len+1 );
break;
case TYPE_PTR:
Q_snprintf( buf, sizeof( buf ), "%d", m_iValue );
len = Q_strlen(buf);
answer = new char[len + 1];
Q_memcpy( answer, buf, len+1 );
break;
case TYPE_WSTRING:
{
#ifdef _WIN32
// convert the string to char *, set it for future use, and return it
static char buf[512];
::WideCharToMultiByte(CP_UTF8, 0, m_wsValue, -1, buf, 512, NULL, NULL);
len = Q_strlen(buf);
answer = new char[len + 1];
Q_memcpy( answer, buf, len+1 );
#endif
break;
}
case TYPE_STRING:
len = strlen(m_sValue);
answer = new char[len+1];
Q_memcpy(answer, m_sValue, len+1);
break;
default:
break;
};
return answer;
}
int KeyValues::GetMyIntDammit(void) {
switch(m_iDataType) {
case TYPE_STRING:
return atoi(m_sValue);
case TYPE_WSTRING:
#ifdef _WIN32
return _wtoi(m_wsValue);
#else
DevMsg( "TODO: implement _wtoi\n");
return 0;
#endif
case TYPE_FLOAT:
return (int)m_flValue;
case TYPE_INT:
case TYPE_PTR:
default:
return m_iValue;
};
  }
float KeyValues::GetMyFloatDammit(void) {
switch ( m_iDataType ) {
case TYPE_STRING:
return (float)atof(m_sValue);
case TYPE_WSTRING:
return 0.0f; // no wtof
case TYPE_FLOAT:
return m_flValue;
case TYPE_INT:
return (float)m_iValue;
case TYPE_PTR:
  default:
return 0.0f;
};
return -1.0f;
}
void* KeyValues::GetMyPtrDammit() {
switch (m_iDataType) {
case TYPE_PTR:
return m_pValue;
case TYPE_WSTRING:
case TYPE_STRING:
case TYPE_FLOAT:
case TYPE_INT:
default:
return NULL;
}
}
void KeyValues::SetMyStringDammit(const char* newValue) {
// delete the old value
delete [] this->m_sValue;
// make sure we're not storing the WSTRING  - as we're converting over to STRING
delete [] this->m_wsValue;
m_wsValue = NULL;
if (!newValue) newValue = "";
// allocate memory for the new value and copy it in
int len = Q_strlen( newValue );
m_sValue = new char[len + 1];
Q_memcpy( m_sValue, newValue, len+1 );
m_iDataType = TYPE_STRING;
}
void KeyValues::SetMyIntDammit(int newValue) {
m_iValue = newValue;
m_iDataType = TYPE_INT;
}
void KeyValues::SetMyPtrDammit(void *newValue) {
m_pValue = newValue;
m_iDataType = TYPE_PTR;
}
//-----------------------------------------------------------------------------
// Purpose: Set the float value of a keyName.
//-----------------------------------------------------------------------------
void KeyValues::SetMyFloatDammit(float newValue) {
m_flValue = newValue;
m_iDataType = TYPE_FLOAT;
}
int KeyValues::NumberOfChild(void) {
KeyValues *dat;
int i = 0;
for(dat = m_pSub; dat; dat = dat->m_pPeer) {
++i;
}
return i;
}
//If Child Keys have Integer names, return the max of em, min is 1
int KeyValues::maxIntNameOnChilds(void){
int ID = 1;
// search for any key with higher values
for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) {
// case-insensitive string compare
int val = atoi(dat->GetName());
if (ID < val) ID = val;
}
return ID;
}
/**
  * End MOD
  */


== How to Improve ==


== How to Improve ==


[[Category:Tutorials]] [[Category:Programming]]
[[Category:Tutorials]] [[Category:Programming]]

Revision as of 22:11, 19 February 2006

Template:WIP Template:WIP Template:WIP Template:WIP Check discussion first please --gia 14:08, 19 Feb 2006 (PST)


Introduction

Here we will expand on Draco's tutorial Dynamic_Weapon_Spawns. The mechanic used was to have a model entity that spawned a set of weapons depending on the values of six ConVars, all of this at the beginning of the round.

Each ConVar would represent an slot of a Weapon Set. Weapon Sets may not just spawn weapons but other items like ammo boxes, armor or health.

Objectives

This tutorial works for HL2MP, you will have to modify some steps to get it to work on another game. We will try to accomplish the following:

  • Use a resource file with the contents of Weapon Sets: weaponsets.txt
  • Ability to modify, switch, load or save Weapon Sets at any time during the round using ConCommands with their respective AutoComplete functions.
  • Use a Point_Entity CWeaponSetEnt to spawn other entities. Weapons or other Items like armor or health.
  • Inputs/Ouputs as in any Weapon and working. ie. must have OnPlayerPickUp and it must work when the player picks up the spawned weapon.
  • Ability to set the ammo for the items to spawn (amount of health or armor to recover in the other cases).
  • Ability to force the spawning of a specific item regardless what the weapon set says.
  • Inclusion of 4 special spawn rules:
    • <none> which will spawn nothing
    • <random>
    • <allweaps-old> will divide the items available in tiers, like High, Medium and Lower tier Weapons. And only spawn certain tier depending of the slot number.
    • <allweaps-new> will use the same tier division but will spawn more than one tier per slot, each tier has a different chance of being spawned at each slot.
  • If the Weapon Set doesn't have enough slots defined to cover the needs of a map then the values will wrap around the Weapon Set. (ie. A set with only 2 slots and a map entity that requires slot 7, after wrapping 7 around 2 it would end up using slot 1).


The Resource File weaponsets.txt

The file could look like this.

"WeaponSets"
{
	"WeaponSet"
	{
		"Name"			"Pistols"
		"Slots"
		{
			"1"		"357"
			"2"		"ammo_pistol_large"
			"3"		"pistol"
			"4"		"pistol"
			"5"		"ammo_pistol"
		}
	}
	"WeaponSet"
	{
		"Slots"
		{
			"1"		"357"			//only 1 slot
		}
		"Name"			"357s"			//order doesnt matter, Name is last
	}
	"WeaponSet"
	{
		"Name"			"Automatics"
		"Slots"
		{
			"1"		"ar2"
			"2"		"ammo_ar2_alt"
			"5"		"ammo_ar2"		//there's no 4
			"3"		"smg1"			//3 is after 5
			"6"		"ammo_smg1"
			"7"		"<none>"		//same effect as not listing it
			"8"		"ammo_smg1_grenade"
		}
	}
}

The Entity FGD Definition

Let's define the FGD entry to use as a base for the coding.

@include "hl2mp.fgd"

@PointClass base(Weapon) studio("models/items/357ammo.mdl")
		= item_weaponset : "A weapon spawn point that uses weapon sets"
[
	spawnitem(choices) : "Spawn what item?" : -20 : "What item should spawn?" =
	[
		-20:	"Use Weapon Set"	//only this option uses weaponsets.txt
		-3:	"All Weapons Classic"	//this and the following are forced values
		-2:	"All Weapons Modern"
		-1:	"Spawn Random Items"
		0:	"Spawn Nothing"
		1:	"weapon_ar2"
		2:	"weapon_pistol"
		3:	"weapon_smg1"
		4:	"weapon_357"
		5:	"weapon_xbow"
		6:	"weapon_shotgun"
		7:	"weapon_ml"
		8:	"weapon_stunstick"
		9:	"item_grenade (1)"
		10:	"weapon_slam (1)"
		11:	"item_ammo_ar2 (20)"
		12:	"item_ammo_ar2_secondary (1)"
		13:	"item_ammo_pistol (20)"
		14:	"item_ammo_smg1 (45)"
		15:	"item_ammo_357 (6)"
		16:	"item_xbow_bolt (1)"
		17:	"item_box_buckshot (20)"
		18:	"item_ml_grenade (1)"
		19:	"item_smg1_grenade (1)"
		20:	"item_ammo_pistol_large (100)"
		21:	"item_ammo_smg1_large (225)"
		22:	"item_ammo_ar2_large (100)"
		23:	"item_ammo_357_large (20)"
		24:	"item_health (1)"
		25:	"item_battery (100)"
	]

	weaponsetslot(integer)	: "For what weapon slot? [1,n]" : 1
			: "If using Weapon Set what slot to use? [1,n]"

	ammotogive(choices) : "How will ammo to give be calculated?" :	0
			: "Consider health, armor or whatever the item gives as ammo." =
	[
		0:	"0-Use Default for item"
		1:	"1-Use AmmoValues as Absolute Value"
		2:	"2-Use AmmoValues as %Max-Ammo allowed by Recipient"
		3:	"3-Take values from Custom Ammo String"
	]

	ammovalue(string) : "Amount of Ammo" : ""
			: "Read as float, only affects the first type of ammo given by item."
			"The only one usually."

	customammostring(string) : "Custom Ammo String" : ""
			: "For items giving two types of ammo, weapons, with different values." +
			" <GiveIndex>,<value>;<GiveIndex>,<value>... ie. Primary set as Default" +
			" and Secondary set as Relative 0.5: '0,0;2,0.5'"

	//spawnflags(Flags) = [ ] the base class weapon has a flag Start constrained
]

base(Weapon)

By using Weapon as the base then this entity inherits the inputs and ouputs and Name and Orientation parameters. It also gets the Start Constrained flag.


ammotogive(choices) and ammovalue(string)

These are so level designers can use this entity without having to deal with the Custom Ammo String, however they are not neccessary as the custom ammo string can do the same and more.

  • 0: Default - If Box of Rounds always gives 10 ammo then do the same... This option doesnt care about AmmoValues
  • 1: Absolute - If AmmoValues says to give 1 ammo give 1 ammo despite the default 10
  • 2: %Max-Ammo - If the pistol that will receive the ammo takes up to 500 rounds and 50 in mag and Ammovalues says 0.5 then give 500+50 = 550 * 0.5 = 275 rounds. In the case of health we can say the recipient is the player, so he would get 50% health recovered. Same for Armor, and other items.


customammostring(string)

It could work for items that give 'n' types of ammo, provided you modify the code to accept that. When using index 0 for Default the ammo value used doesn't really matter, it could be blank, but it would be better to use another 0 there.


The Code

The whole thing is here?. Now onto the main sections of it.

Main structures

class CWeaponSetEnt;	//Map Entity, cant belong to a namespace because of macros
			//defining their own inside
class CItem_ModAmmoCrate;	//Another entity, this is an ammo box that holds any type
				//of ammo, I use it to make my life simple.
				//You can use hl2mp's ammo box entities, after adding the
				//ability to modify the ammount of ammo they give.

namespace WeaponSetH { //Namespace, just to keep things in order
	struct ItemDef; //Holds constants to make things easier to read
	struct Item;	//Holds data about items
	class CWeaponSet; //Static class that does all the coordination

	struct AllWeapChance;	//Holds data of slots and tiers associated to them
				//to be used by <allweaps-old> and <allweaps-new>
};

Obviously they are using a namespace, so just pretend I used namespace WeaponSetH in the following code blocks.

  • CWeaponSetEnt will use the entity named "item_weaponset"
  • CItem_ModAmmoCrate will use the entity named "item_modammocrate"

[Todo] change the "ModAmmoCrate" to some better name... Mod-ified is fine for the prototype but not for final code


ItemDef

ItemDef is just a bunch of integer constants in a struct instead of a enumeration. Why a struct? because the constants do not belong to a single, continous, enumeration. Also, it allows the use of ItemDef:: to quickly access the constants.

The SPAWN constants are used as itemIndexes (IDs). Despite the un-optimization we could use string indexes over integers (itemNicks), but we require the integers to obtain the Ammo Index (some hl2mp var, see hl2mp_gamerules.cpp::GetAmmoDef, more on this later) and also a way to map the indexes the Hammer entity will use to Item instances (see FGD::spawnitem).

The TIER constants define Item Tiers, the way they are defined it only makes possible to link an item to only one tier unless we used arrays. To allow items to belong to multiple tiers without complicating things, you could make them bit flags and did the proper modifications to the couple of functions that uses this.

Three more constants, they are utility constants, see the code-comments to understand a bit about them.


Item

Ok we know that items (name Weapons, Ammo Boxes, Batteries, anything!) won't change in the middle of the game. So we just need to load the data once (initialize) and read from there without modifications, so it only needs getter functions.

//Contains an item definition
struct Item {
	int itemIndex;	//This Item Index is according to WeaponSetH::ItemDef SPAWN constants
	int ammoCount;	//Base ammo count, used for Ammo Boxes
	char* entityName;	//The entity name associated to this item
	char* itemNick;	//A nick, ent names are long, this way names are easier to users
	int itemTier;	//specific for All Weapons, but may be used for other things
};
//const int itemListSize;	defined in next block
//WeaponSetH::Item itemList[];	defined in next block

WeaponSetH::Item* GetItemByItemIndex(int index);	//ItemIndex is a member of the struct
WeaponSetH::Item* GetItemByNick(const char* itemNick);	//ItemNick is another member
WeaponSetH::Item* GetItemByArrayIndex(int index);	//The array index comes from
							//itemList[] above

//slot = -1 means no slot, so take as forced item.
//If spawnType equals SPAWN_SET then return whatever the set defines for slot
WeaponSetH::Item* GetBoundItem(int spawnType, int slot = -1);

//Return an item that is valid to spawn (no <set>, <random>, <allweaps-old or new>)
//Process if not spawnable.
WeaponSetH::Item* GetSpawnableItem(int spawnType, int slot);

WeaponSetH::Item* GetItemInAllWeaponsClassicMode(int slot); //<allweaps-old>
WeaponSetH::Item* GetItemInAllWeaponsModernMode(int slot); //<allweaps-new>

If you check the code (weaponset.h), itemList[] and itemListSize are the way they are because it's easier to do maintenance to that initialization matrix. A struct that only defined members can be initialized that way, they are called Aggregates (Initializing Aggregates).

If you added a new weapon/item to your mod and had to include it on the list you would just add a line and compile. The itemList would initialize and itemListSize recalculate. You might have to add a SPAWN and/or TIER constant to WeaponSetH::ItemDef as well.

// {itemType, ammoCount, entityName, nickName, itemTier },
WeaponSetH::Item itemList[] = {
   {ItemDef::SPAWN_ALLWEAPONS_CLASSIC,	0,
		"",			"<allweaps-old>",	ItemDef::TIER_NOSPAWNABLE	},

   {ItemDef::SPAWN_NOTHING,		0,
		"",			"<none>",		ItemDef::TIER_NOTHING		},

   {ItemDef::SPAWN_PISTOL,		0,
		"weapon_pistol",	"pistol",		ItemDef::TIER_LOWWEAPON		},

   {ItemDef::SPAWN_AMMO_PISTOL,	20,
		"item_mod_ammocrate",	"ammo_pistol",		ItemDef::TIER_LOWAMMO		},

   {ItemDef::SPAWN_HEALTH,		25,
		"item_health",		"health",		ItemDef::TIER_OTHERS		},

   {ItemDef::SPAWN_ARMOR,		100,
		"item_battery",		"armor",		ItemDef::TIER_OTHERS_ARMOR	},
};
const int itemListSize = sizeof(WeaponSetH::itemList) / sizeof(WeaponSetH::Item);

As for the getters they return a pointer to a member of the itemList array. Now, in case of failure they would return NULL, but I set them to return the Item NOTHING (which would spawn nothing). Wether this is right or not to you it is just a matter or adding NULL checks where appropiate.


AllWeapChance

AllWeapons is just about game mechanics, including it or not won't prevent you from having Dynamic Weapon Spawns. You can skip this if you want.

Ok remember the structure of a Weapon Set?, A weapon set has a name and numbered slots with items. Since AllWeapons are a special case we make a structure for it, it has slots but doesn't require a name, instead it has probabilities.

struct AllWeapChance {
	int slot;
	int itemTier;
	bool classicChance;	//classic(old), belongs here or not. All items part
				//of the Tiers of this slot here have equal chance of spawning
	float modernChance;	//modern(new) Actual chances of spawning the Tier
};
//AllWeapChance allWeapChanceList[];	see next block
//const int allweapbasestructNumSlots;	see next block
//const int allweapchanceListSize;	see next block

AllWeaps Classic Mechanics

Let's pretend we define that according to the base structure for <allweaps-old> any "item_weaponset" entity marked as slot 1 would be able to spawn items with Tier TIER_HIGHWEAPON and TIER_MEDWEAPON. There would be entries for this struct with slot = 1, the appropiate itemTiers and classicChance = true.

Now during spawn a random number is generated. We have two Tiers competing for this slot, so we FIRST collect all the Items assigned to these Tiers, AFTER-THAT all the Items collected compete with equal chances. ie. There are 4 items assigned to TIER_HIGHWEAPON and 6 to TIER_MEDWEAPON, so each item has 0.1 chance of spawning.

AllWeaps Modern Mechanics

Now we have the same case (HIGH and MED at slot 1). Since we are defining Modern chances we don't care about AllWeapChance::classicChance, instead we will set AllWeapChance::modernChance. The sum of modernChances for a single slot should be 1.

Here we generate a random number too. We FIRST select a Tier depending on their modernChance value, AFTER-THAT we must select between the many items that can be spawned at that Tier, they all compete with equal chances.

ie. At slot 1, TIER_HIGHWEAPON which contains 2 items has a modernChance of 0.8, TIER_MEDWEAPON contains 4 items and a chance of 0.2. Doing he math, the overall chance for an item of HIGHWEAPON to be selected would be 0.4 and the chances for an item of MEDWEAPON 0.05.

Initialization

During initialization, if you miss an entry for a TIER then the both chances will be 0, good shortcut.

// {slot, tier, classicChance, modernChance },
AllWeapChance allWeapChanceList[] = {
	{1,		ItemDef::TIER_HIGHWEAPON,	true,	0.80f},
	{1,		ItemDef::TIER_MEDWEAPON,	false,	0.15f},
	{1,		ItemDef::TIER_LOWWEAPON,	false,	0.05f},
	//etc up to slot 20
};
const int allweapbasestructNumSlots = 20; //Equal to the max slot of the base struct, 20
const int allweapchanceListSize = sizeof(WeaponSetH::allWeapChanceList)
		/ sizeof(WeaponSetH::AllWeapChance); //calculates the size of the array


CItem_ModAmmoCrate

Ok back to Dynamic Spawns, this is a class that prevents some boring editing of Valve classes. You can find them at items_world.cpp.

This entity will hold any kind of ammo as defined in AmmoDef. Read items.h, hl2mp_gamerules.cpp, ammodef.h, etc.

#include "items.h" //required for CItem

class CItem_ModAmmoCrate : public CItem {
	
	DECLARE_CLASS(CItem_ModAmmoCrate, CItem);
	DECLARE_DATADESC();
	
	int m_iAmmoType;	//Ammo Type, an ammo index/ID used by hl2mp
	int m_iAmmoAmount;	//Amount of ammo to give when picked up
				//The default ammo crates don't have this.
	
public:
	void Spawn(void);
	void Precache(void);
	bool MyTouch(CBasePlayer *pPlayer);
	void SetAmmoType(int type);	//setters
	void SetAmmoAmount(int ammo);	
};
BEGIN_DATADESC( CItem_ModAmmoCrate )
	DEFINE_KEYFIELD( m_iAmmoType, FIELD_INTEGER, "AmmoType" ),
	DEFINE_KEYFIELD( m_iAmmoAmount, FIELD_INTEGER, "AmmoAmount" ),
END_DATADESC()

LINK_ENTITY_TO_CLASS(item_mod_ammocrate, CItem_ModAmmoCrate);

//Different from items_world.cpp::ITEM_GiveAmmo() in that it gives a forced value
//CItem_ModAmmoCrate::m_iAmmoAmount and doesn't make a query depending on Ammo Type.
int ITEM_GiveForcedAmmo(CBasePlayer *pPlayer, float flCount,
		   const char *pszAmmoName, bool bSuppressSound = false);

This entity isn't included in the FGD since it is more of a utility entity and by using item_weaponset you rule out every item entity from Hammer (ideally you would remove them all from the FGD).


CWeaponSet

What this class has to store is a collection of WeaponSets, which have a Name and a collection of Slots. Each Slot containing an index and an Item. Now instead recreating the structure the solution presented only uses KeyValues.

The functions are designed so it is possible to be applied not only to the current Weapon Set but to any set loaded. The current ConCommands do not make use of this ability. The defaulting to NULL on most arguments means "use current set".

There are extra functions that aren't listed here, they are not used (like NewWeaponSet()) or overloaded functions.

class WeaponSetH::CWeaponSet {
public:
	class EntList;	//container util class
	static EntList *wsEnts;	//weaponset-Entities references CWeaponSetEnt entities created
	
	//Pointer to the root of the KeyValues structure loaded from weaponsets.txt
	static KeyValues *weaponSetFile;

	//Pointer to the root of the WeaponSet selected right now
	static KeyValues *currentSet;

	//Since this is a static class it uses a refCount to call Init, and delete pointers
	static int refCountOfMe;

	CWeaponSet();
	~CWeaponSet();

	static void Init(void);

	//Here we got what we wanted, load, save, modify, add, remove and rename
	static void LoadAllWeaponSetsFromFile(KeyValues** loadHere = NULL);
	static void SaveAllWeaponSetsToFile(void);

	static bool RenameWeaponSet(const char* from, const char* to);
	static bool AddSlotToWeaponSet(WeaponSetH::Item* item, const char* setName = NULL);
	static bool RemoveSlotFromWeaponSet(int slot, const char* setName = NULL);
	static bool ModifySlotOfWeaponSet(int slot, WeaponSetH::Item* item,
								const char* setName = NULL);


	static int GetNumberOfSets(void); //Number of weapon sets loaded

	//Given a Weapon Set and slot get the Item assigned to them
	static WeaponSetH::Item* GetItemForSlotOnWeaponSet(int slot, const char* setName =  NULL);

	//The index is the order in which they appear on the KeyValues, starts at 1
	static KeyValues* GetWeaponSetByIndex(int index);
	static KeyValues* GetWeaponSetByName(const char* setName,
					KeyValues* fromHere =  weaponSetFile);

	static bool SetCurrentWeaponSet(const char* setName);
	static void SetFirstAvailableAsCurrentSet(void); //When we load the weapon sets
							//select the first weapon set available

	//Functions to keep track of CWeaponSetEnt using (EntList)wsEnt member
	static void RegisterNewEntity(CWeaponSetEnt* registerMe);
	static void UnRegisterEntity(CWeaponSetEnt* unregisterMe);
	static void NotifyCurrentSetModificationToEntities();
};

CWeaponSet::EntList

Ok here I used my own class because I don't trust CUtl_Vector, this is just a linked list so... It's purpose: During a round several CWeaponSetEnt (the actual entities) will be initialized, since CWeaponSet has to be able to notify them of changes to the weapon set then it needs a list with references to them.

[Todo] Should these be pointers or EHandles, haven't had problems with it though.

//container util class, just keeps a reference, doesn't do anyting else
class EntList {
	EntList* next;
	CWeaponSetEnt* ent;

public:
	EntList();
	EntList(CWeaponSetEnt *element);
	EntList(CWeaponSetEnt *element, EntList *next);
	~EntList() { ent = NULL; delete next; } //doesn't delete CWeaponSetEnt

	CWeaponSetEnt* add(CWeaponSetEnt* element);
	CWeaponSetEnt* add(CWeaponSetEnt* element, int index);
	CWeaponSetEnt* remove(CWeaponSetEnt* element);
	CWeaponSetEnt* remove(int index);
	CWeaponSetEnt* get(int index);
	int size();
};


CWeaponSetEnt

Okay, this is the class that represents the Hammer entity Level Designers are going to use. Since we gave most of the work to other structures this class is cleaner.

//Logical entities are Point Entities that exist on the server
class CWeaponSetEnt : public CLogicalEntity {
	DECLARE_CLASS(CWeaponSetEnt, CLogicalEntity);
	DECLARE_DATADESC();

	//These appear on the FGD so you know what they do
	int m_iSpawnItem;
	int m_iWeaponSlot;
	
	int m_iAmmoToGive;
	string_t m_sAmmoValues;
	string_t m_sCustomAmmoString;

	//For internal use, not on the Hammer entity
	WeaponSetH::Item *m_pItemUsed;	//points to the last Item used, that way,
					//if this entity gets notified of a change
					//to the current weapon set it will do nothing if
					//the bound item is the same as the one already spawned

	EHANDLE m_hlastEntitySpawned;	//Required to keep track of the entity spawned
 					//This way we can call UTIL_Remove when notified of change
 					//It will remove the last entity and spawn the new one
public:
	//Related to m_iAmmoToGive
	enum HowToGiveAmmo {
		GIVE_AMMO_DEFAULT = 0,
		GIVE_AMMO_ABSOLUTE = 1,
		GIVE_AMMO_RELATIVETOMAX = 2,
		GIVE_AMMO_CUSTOMSTRING = 3,
	};

	void Spawn(void);

	void WeaponSetHasChanged();	//This function will be called by CWeaponSet
					//when the current set changes. Calls InitSpawnNewItem

	//Will start a Think to SpawnNewItem. delayed=false means near inmediate spawn
	//delayed=true waits ConVar sv_hl2mp_weapon_respawn_time
	void InitSpawnNewItem(bool delayed);	

	//Will decide, depending on what item it must spawn, on a Spawn* function
	void SpawnNewItem(void);

	//Haven't implemented a function to spawn batteries or health
	//implemented functions are:
	void SpawnCItem(WeaponSetH::Item*); //for CItem_ModAmmoCrate
	void SpawnCBaseCombatWeapon(WeaponSetH::Item*); //for any CBaseCombatWeapon

	int AmmoToSet(int);	//Receives a default value of ammo, and depending on m_iAmmoToGive
				//will return the amount of ammo to set
	

	//Internal Input to connect Level Designer events placed
	//on this entity to the events on the actual items to spawn
	void InputPickedUp(inputdata_t &inputData);	

private:
	//Ouputs to be fired
	COutputEvent	m_OnPlayerPickUp;
	COutputEvent	m_OnNPCPickUp;
	COutputEvent	m_OnPlayerUse;
};
BEGIN_DATADESC( CWeaponSetEnt )
	DEFINE_KEYFIELD( m_iForceWeapon, FIELD_INTEGER, "forceitem" ),	
	DEFINE_KEYFIELD( m_iWeaponSlot, FIELD_INTEGER, "weaponsetslot" ),
	DEFINE_KEYFIELD( m_iAmmoToGive, FIELD_INTEGER, "ammotogive" ),
	DEFINE_KEYFIELD( m_sAmmoValues, FIELD_STRING, "ammovalues" ),

	// Since it's for internal use no need to have it linked to Hammer
	//DEFINE_INPUTFUNC(FIELD_STRING, "PickedUp", InputPickedUp ),

	// Links our output member to the output name used by Hammer
	DEFINE_OUTPUT( m_OnPlayerPickUp, "OnPlayerPickUp" ),
	DEFINE_OUTPUT( m_OnNPCPickUp, "OnNPCPickUp" ),
	DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ),
END_DATADESC()

LINK_ENTITY_TO_CLASS(item_weaponset, CWeaponSetEnt);

Something to note here, the Spawn function of this class is the one in charge of calling CWeaponSet::Init() by creating an instance of CWeaponSet. That means weaponsets.txt doesn't get loaded until we start a map.

[Todo] The creation of CWeaponSet instance should be at "main()". Then point that out at SDK Modifications.


ConCommands

weaponset_modslot <itemSlot> <itemNick>

Modifies existing slot of current set.

weaponset_load <setname>

Reload Weapon Set <setname> Information from weaponsets.txt, overwrites unsaved changes done to sets during the session. If param is "<all>" then load all weapon sets.
[Todo] Game crashes after the command finishes.

weaponset_save <setname>

Save weapon sets <setname>, saves modifications done during the session to weaponsets.txt. If param is "<all>" it saves all the weapon sets.
[Todo] Game crashes if the param isnt "<all>"

weaponset_use <setname>

Switch to <setname> weapon set.

weaponset_renameto <setnewname>

Rename current weapon set to <setnewname>.

weaponset_addslot <itemnick>

Add a new slot to current weapon set and assign <itemnick> to it.

weaponset_removeslot <slotindex>

Remove existing slot with index <slotindex>.

AutoComplete

Someone new to the mod would use help weaponset_modslot and while he would have the idea of the parameters, printing weaponset_modslot <itemSlot> <itemNick> alone wouldn't help. That's why an AutoComplete function is neccessary.

The autocomplete answers of weaponset_modslot are smart ones due to the nature of strstr() looking within a string. For example while typing pistol it would also show ammo_pistol and ammo_pistol_large as other options.

[Image]

static int WeaponSetModSlotAutoComplete ( char const *partial,
	char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) {

The constants here are global.

	//1. Split by spaces
	char arg[4][COMMAND_COMPLETION_ITEM_LENGTH] = {'\0'};

Init the string with '\0's so after using sscanf the string can be tested against "". It could be replaced by arg[i][0] = '\0'; Since COMMAND_etc is a global restriction we enforce it too :)

	int argsReceived = sscanf(partial, "%s %s %s %s", arg[0], arg[1], arg[2], arg[3]);

The command structure is weaponset_modslot <slot> <itemnick>, that's 3 args, we use four just to know when the user has typed a fourth arg (or more). In that case the program should tell him that's not a correct string. sscanf will return the number of arguments actually read, we will use that number to know what to print on screen.

		arg[0][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';
		arg[1][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';
		arg[2][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';
		arg[3][COMMAND_COMPLETION_ITEM_LENGTH-1] = '\0';

If the user entered a very long param then it will overflow the buffer, we close the string just in case.

	const char *lastChar = &partial[strlen(partial)-1];
		if(FStrEq(lastChar, " ")) ++argsReceived; //and the last char is " "

When the user enters "weaponset_modslot <something>" argsReceived will be 2 and we should be autocompleting arg[1]. However when they enter "weaponset_modslot <something> " (<-it has a space) we should be autocompleting arg[2] but argsReceived will still be 2. So if the last char is " " we add one to argsReceived.

	//So we are autocompleting the first arg
	if(argsReceived <= 2) {
		//do stuff, here we initialize maxSlotNo

		//copy a string to commands[0] while enforcing the restriction on length
		Q_snprintf(commands[0], COMMAND_COMPLETION_ITEM_LENGTH,
			"weaponset_modslot <slotindex maxvalue: %d> <itemnick>", maxSlotNo);
		return 1; //we are only returning one result
	};

 	//So they managed to enter the first arg <slot>, get it
	int slot = atoi(arg[1]);

	//Too many args, tell him he is wrong by returning the "correct" command entered so far
	//if slot is not a number it will show zero there forcing the user to check why
	//the value is different than what he is typing
	if(argsReceived >= 4) {
		Q_snprintf(commands[0], COMMAND_COMPLETION_ITEM_LENGTH,
					"weaponset_modslot %d %s", slot, arg[2]);
		return 1;
	};

	//So we are completing <itemnick>
	//if(argsReceived == 3)
	int j = WeaponSetH::itemListSize; //first get how many items we have
	int n = 0; //that's different than how many we are going to return, right now, none

	WeaponSetH::Item *it;
	for(int i = 0; i < j; i++) { //navigate through all the items
		it = WeaponSetH::GetItemByArrayIndex(i);

		if(!FStrEq(arg[2], "")) {
			char *c = Q_strstr(  it->itemNick,  arg[2] );
			if(!c) continue;
		};

If the user has typed nothing on this arg yet (has typed "<arg0> <arg1> " <-space) then skip this and just add all the itemnicks to the list. However, if he has entered something already, if the itemnick entered isn't a substring of the item then just go to the next item. Otherwise add to the results.

		Q_snprintf(commands[n], COMMAND_COMPLETION_ITEM_LENGTH,
				"weaponset_modslot %d %s", slot, it->itemNick);
		n++;
	}
    return n; // finally return the number of entries found
}


SDK Modifications

BaseCombatCharacter.cpp

void CBaseCombatCharacter::Weapon_Equip( CBaseCombatWeapon *pWeapon )

After the first for() there's this line:

 	// Weapon is now on my team
	pWeapon->ChangeTeam( GetTeamNumber() );

There we go:

// MOD
//We are setting the ammo at spawn, why should we set it again? Modify this.
//CWeaponSetEnt will set clips to 0. It will be primary/secondary ammo the ones keeping the counts
	// ----------------------
	//  Give Primary Ammo
	// ----------------------
	// If gun doesn't use clips, just give ammo
	int primaryAmmo = pWeapon->GetPrimaryAmmoCount();
	int secondAmmo = pWeapon->GetSecondaryAmmoCount();
	pWeapon->SetPrimaryAmmoCount(0);
	pWeapon->SetSecondaryAmmoCount(0);

	if (pWeapon->GetMaxClip1() == -1) {
	 	/* delete the Single Player RPG Hack */
		GiveAmmo(primaryAmmo, pWeapon->m_iPrimaryAmmoType); 
	}
	// If default ammo given is greater than clip
	// size, fill clips and give extra ammo
	else if (primaryAmmo >  pWeapon->GetMaxClip1() ) {
		pWeapon->m_iClip1 = pWeapon->GetMaxClip1();
		GiveAmmo( (primaryAmmo - pWeapon->GetMaxClip1()), pWeapon->m_iPrimaryAmmoType); 
	} else {
		pWeapon->m_iClip1 = primaryAmmo;
	}

	// ----------------------
	//  Give Secondary Ammo
	// ----------------------
	// If gun doesn't use clips, just give ammo
	if (pWeapon->GetMaxClip2() == -1) {
		GiveAmmo(secondAmmo, pWeapon->m_iSecondaryAmmoType); 
	}
	// If default ammo given is greater than clip
	// size, fill clips and give extra ammo
	else if ( secondAmmo > pWeapon->GetMaxClip2() )	{
		pWeapon->m_iClip2 = pWeapon->GetMaxClip2();
		GiveAmmo( (secondAmmo - pWeapon->GetMaxClip2()), pWeapon->m_iSecondaryAmmoType); 
	} else {
		pWeapon->m_iClip2 = secondAmmo;
	}
// End MOD

Then it gets followed by the regular contents starting at:

	pWeapon->Equip( this );

	// Players don't automatically holster their current weapon
	if ( IsPlayer() == false )


Now at bool CBaseCombatCharacter::Weapon_EquipAmmoOnly( CBaseCombatWeapon *pWeapon )

bool CBaseCombatCharacter::Weapon_EquipAmmoOnly( CBaseCombatWeapon *pWeapon )
{
	// Check for duplicates
	for (int i=0;i<MAX_WEAPONS;i++) 
	{
		if ( m_hMyWeapons[i].Get()
			&& FClassnameIs(m_hMyWeapons[i], pWeapon->GetClassname()) )
		{
			// MOD 
			// Just give the ammo
			if( pWeapon->UsesClipsForAmmo1() )
				pWeapon->SetPrimaryAmmoCount(  pWeapon->GetPrimaryAmmoCount()
									+ pWeapon->m_iClip1  );
			if( pWeapon->UsesClipsForAmmo2() )
				pWeapon->SetSecondaryAmmoCount(  pWeapon->GetSecondaryAmmoCount()
									+ pWeapon->m_iClip2 );

			int primaryGiven	= pWeapon->GetPrimaryAmmoCount();
			int secondaryGiven	= pWeapon->GetSecondaryAmmoCount();

			int takenPrimary
				= GiveAmmo( primaryGiven, pWeapon->m_iPrimaryAmmoType); 
			int takenSecondary
				= GiveAmmo( secondaryGiven,  pWeapon->m_iSecondaryAmmoType); 

			//Only succeed if we get to take ammo from the weapon
			if ( takenPrimary == 0 && takenSecondary == 0 ) return false;

			pWeapon->SetPrimaryAmmoCount( pWeapon->GetPrimaryAmmoCount()
									- takenPrimary  );
			pWeapon->SetSecondaryAmmoCount( pWeapon->GetSecondaryAmmoCount()
									-  takenSecondary );
			// End MOD
						
			return true;
		}
	}

	return false;
}

basecombatweapon.cpp

void CBaseCombatWeapon::Materialize( void )

		RemoveEffects( EF_NODRAW );
		DoMuzzleFlash();
	}
#ifdef HL2MP
	// MOD 'ed out
	//if ( HasSpawnFlags( SF_NORESPAWN ) == false )
	{
		VPhysicsInitNormal( SOLID_BBOX, GetSolidFlags() | FSOLID_TRIGGER, false );
		SetMoveType( MOVETYPE_VPHYSICS );

		HL2MPRules()->AddLevelDesignerPlacedObject( this );
	}
	// End MOD

==== basecombatweapon_shared.cpp ==== //also add the prototipes to the .h

// MOD : Add Functions

void CBaseCombatWeapon::SetClip1(int amount) {
	if(UsesClipsForAmmo1()) m_iClip1 = min(max(0, amount), GetMaxClip1());
}
void CBaseCombatWeapon::SetClip2(int amount) {
	if(UsesClipsForAmmo2()) m_iClip2 = min(max(0, amount), GetMaxClip2());
}

// End MOD

==== KeyValues.cpp ==== //also add the prototypes to the .h [Todo] Modify the function names :)

/**
 * Long MOD 
 */
//-----------------------------------------------------------------------------
// Purpose: Will Kill all my tree. Will detach me from my  parent and brothers first if parent != NULL
//-----------------------------------------------------------------------------
void KeyValues::ActualKillMyTree(KeyValues* parent) {
	if(parent) parent->RemoveSubKey(this); //Left by father and brothers
	RecursiveDelete();
}
//-----------------------------------------------------------------------------
// Purpose: Will Kill all my tree.
//-----------------------------------------------------------------------------
void KeyValues::RecursiveDelete() {
	KeyValues *dat;
	KeyValues *datPrev = NULL;
	
	dat = m_pSub;
	while(true) {
		if(datPrev != NULL) {
			datPrev->RecursiveDelete();
			delete datPrev;
		};
		
		datPrev = dat;
		if((dat == NULL) && (datPrev == NULL)) break;

		dat = dat->m_pPeer;
	}
}
char* KeyValues::GetMyStringDammit(void) {
	// convert the data to string form then return it
	char *answer = NULL;
	char buf[64];
	int len = 0;
	switch(m_iDataType) {
		case TYPE_FLOAT:
			Q_snprintf( buf, sizeof( buf ), "%f", m_flValue );
		// allocate memory for the new value and copy it in
			len = Q_strlen(buf);
			answer = new char[len + 1];
			Q_memcpy( answer, buf, len+1 );
			break;
		case TYPE_INT:
			Q_snprintf( buf, sizeof( buf ), "%d", m_iValue );
		// allocate memory for the new value and copy it in
			len = Q_strlen(buf);
			answer = new char[len + 1];
			Q_memcpy( answer, buf, len+1 );
			break;
		case TYPE_PTR:
			Q_snprintf( buf, sizeof( buf ), "%d", m_iValue );
			len = Q_strlen(buf);
			answer = new char[len + 1];
			Q_memcpy( answer, buf, len+1 );
			break;
	case TYPE_WSTRING:
		{
#ifdef _WIN32
			// convert the string to char *, set it for future use, and return it
			static char buf[512];
			::WideCharToMultiByte(CP_UTF8, 0, m_wsValue, -1, buf, 512, NULL, NULL);
			len = Q_strlen(buf);
			answer = new char[len + 1];
			Q_memcpy( answer, buf, len+1 );
#endif
			break;
		}
	case TYPE_STRING:
		len = strlen(m_sValue);
		answer = new char[len+1];
		Q_memcpy(answer, m_sValue, len+1);
		break;
	default:
		break;
	};
	
	return answer;
}
int KeyValues::GetMyIntDammit(void) {
	switch(m_iDataType) {
		case TYPE_STRING:
			return atoi(m_sValue);
		case TYPE_WSTRING:
#ifdef _WIN32
			return _wtoi(m_wsValue);
#else
			DevMsg( "TODO: implement _wtoi\n");
			return 0;
#endif
		case TYPE_FLOAT:
			return (int)m_flValue;
		case TYPE_INT:
		case TYPE_PTR:
		default:
			return m_iValue;
	};
 }
float KeyValues::GetMyFloatDammit(void) {
	switch ( m_iDataType )	{
		case TYPE_STRING:
			return (float)atof(m_sValue);
		case TYPE_WSTRING:
			return 0.0f;		// no wtof
		case TYPE_FLOAT:
			return m_flValue;
		case TYPE_INT:
			return (float)m_iValue;
		case TYPE_PTR:
 		default:
			return 0.0f;
	};
	return -1.0f;
}
void* KeyValues::GetMyPtrDammit() {
	switch (m_iDataType) {
		case TYPE_PTR:
			return m_pValue;

		case TYPE_WSTRING:
		case TYPE_STRING:
		case TYPE_FLOAT:
		case TYPE_INT:
		default:
			return NULL;
	}
}
void KeyValues::SetMyStringDammit(const char* newValue) {
	// delete the old value
	delete [] this->m_sValue;
	// make sure we're not storing the WSTRING  - as we're converting over to STRING
	delete [] this->m_wsValue;
	m_wsValue = NULL;

	if (!newValue)	newValue = "";

	// allocate memory for the new value and copy it in
	int len = Q_strlen( newValue );
	m_sValue = new char[len + 1];
	Q_memcpy( m_sValue, newValue, len+1 );

	m_iDataType = TYPE_STRING;
}
void KeyValues::SetMyIntDammit(int newValue) {
	m_iValue = newValue;
	m_iDataType = TYPE_INT;
}
void KeyValues::SetMyPtrDammit(void *newValue) {
	m_pValue = newValue;
	m_iDataType = TYPE_PTR;
}

//-----------------------------------------------------------------------------
// Purpose: Set the float value of a keyName. 
//-----------------------------------------------------------------------------
void KeyValues::SetMyFloatDammit(float newValue) {
	m_flValue = newValue;
	m_iDataType = TYPE_FLOAT;
}

int KeyValues::NumberOfChild(void) {
	KeyValues *dat;
	int i = 0;

	for(dat = m_pSub; dat; dat = dat->m_pPeer) {
		++i;
	}

	return i;
}

//If Child Keys have Integer names, return the max of em, min is 1
int KeyValues::maxIntNameOnChilds(void){
	int ID = 1;

	// search for any key with higher values
	for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) {
		// case-insensitive string compare
		int val = atoi(dat->GetName());
		if (ID < val) ID = val;
	}

	return ID;
}

/**
 *	End MOD
 */

How to Improve