AI Code

  • Thread starter Thread starter jewcifer_666
  • Start date Start date
J

jewcifer_666

Guest
What I want to do is make a mod where the player and some citizens (with a new skin I have made) will be one gang. Instead of fighting Combine/Metropolice, I want them to be fighting other citizens (with yet another skin I have made) which will represent another gang. I would like to edit the AI code to accomplish this so that the player and citizens can kill other citizens, but if that is too hard I could settle on simply replacing the combine models with citizen models and using the current AI code. Does anybody know which files or parts of files I should focus on to accomplish either of this. If you have other ideas to accomplish the same task, I'm very open to suggestions. Thanks
 
Might you be able to do this via the map with ai_relationship?

--

Otherwise, I recomend hunting through the Antlion code.

It appears you have a AddEntityRelationship. Your'll need change the relationships, proberbly based on a spawn flag.
 
Just derive two classes from the original Civilian class, define two more relationship groups (Team 1, and team 2), and make the two classes hate one another. Finally, Make Team 1's relationship to the player as 'friendly'

It's pretty simple. Just look for the relationship table, and the subroutine 'Classify'

-Angry Lawyer
 
Angry Lawyer said:
Just derive two classes from the original Civilian class, define two more relationship groups (Team 1, and team 2), and make the two classes hate one another. Finally, Make Team 1's relationship to the player as 'friendly'

It's pretty simple. Just look for the relationship table, and the subroutine 'Classify'

-Angry Lawyer

Could you maybe elaborate on this? It seems exactly like what I want to do, but I don't know where to start. (i'm very new to HL2 coding) Like, what are some files I might need to focus on. Just give me a little bit more. Thanks
 
I don't have my SDK in front of me right now, so I'm acting on memory.

How good are you at general C++? Do you know what deriving a class is?

Look for something like Civilian.cpp, or citizen.cpp, and the associated header file, and create a derived class of the basic citizen. It doesn't have to have any functions overrided in it at all (so your new class will be only a few lines) except for Classify. In the overrided Classify, just look at what the original Classify function did, copy it across, and replace the CLASS_PLAYER_ALLY with something like CLASS_COMBINE.

After that works, you can start doing things like changing their models -again, done by overriding some very basic functions.

-Angry Lawyer
 
well, you could make the Skin for the other Citizen, and replace the Combines with them, so its really combines, but looks like citizen.

Then change the sound so they dont talk like combines. :)
 
They'd act too much like professional military troops, that way. Besides, it also rules out the ability to spawn Combine to fight off the two other gangs when you're in the mood for some silliness!

Derived classes are the way to go. And adding an extra thing to the Classify table.

-Angry Lawyer
 
That sounds perfect. Thank you so much for your help. I'll give it a go.

Edit:

Just tried it and it works like a bloody charm. Thanks again, you're a genius, Angry Lawyer!!!
 
Ok. Turns out I don't know what deriving a class is. I made the change directly in npc_citizen17.cpp and it worked as I wanted. Now, how do I do that deriving thing to have an 'angry citizen' and a normal one?
 
Hmm, looks like someone skipped a lesson in C++...

Anyways, here's the solution. I'm not making it cut-and-paste, because then you wouldn't learn. Read the notes, understand inheritance.

First thing we have to do is define the extra classifications.

In baseentity.h, there's a long list of NPC relationship classes. We're going to need two more. So, if you look at somewhere near line 88, you should find this:

Code:
// For CLASSIFY
enum Class_T
{
	CLASS_NONE=0,				
	CLASS_PLAYER,			
	CLASS_PLAYER_ALLY,
	CLASS_PLAYER_ALLY_VITAL,
	CLASS_ANTLION,
	CLASS_BARNACLE,
	CLASS_BULLSEYE,
	//CLASS_BULLSQUID,	
	CLASS_CITIZEN_PASSIVE,	
	CLASS_CITIZEN_REBEL,
	CLASS_COMBINE,
	CLASS_COMBINE_GUNSHIP,
	CLASS_CONSCRIPT,
	CLASS_HEADCRAB,
	//CLASS_HOUNDEYE,
	CLASS_MANHACK,
	CLASS_METROPOLICE,		
	CLASS_MILITARY,		
	CLASS_SCANNER,		
	CLASS_STALKER,		
	CLASS_VORTIGAUNT,
	CLASS_ZOMBIE,
	CLASS_PROTOSNIPER,
	CLASS_MISSILE,
	CLASS_FLARE,
	CLASS_EARTH_FAUNA,

	NUM_AI_CLASSES
};

Just before, NUM_AI_CLASSES, add the lines CLASS_GOODGUYS and CLASS_BADGUYS, or something. We now have two extra classes.

The next bit you need to modify is hl2_gamerules.cpp. You should see on line 324-ish loads of relationship table things. Now, there's a relationship table for each classify, relating it to each other classify. So, to the end of each of the tables, add an appropriate relationship:

Code:
CBaseCombatCharacter::SetDefaultRelationship(CLASS_ANTLION,CLASS_GOODGUYS,D_HT, 0);

And the same for the badguys. As you can see, the relationship blocks define first, the class in question (CLASS_ANTLION), second, the one you want it to relate to (CLASS_GOODGUYS), third, the kind of relationship(D_HT - hatred. D_LI will produce friendship) and finally, the priority (larger numbers will mean that they will shoot this enemy over ones of lower numbers).

So, now all of the other relationship tables are updated, we need to create one for each of CLASS_GOODGUYS and CLASS_BADGUYS. So, copy-paste one of the blocks to the end of that function (around line 1111), and change the relationships to what you want each of the new teams to be. So, CLASS_BADGUYS will hate the player, and pretty much everything else. CLASS_GOODGUYS will like the player, and hate the badguys.

The final thing for the classification table is to add it to the class-text. Around line 1146, add a case for each of your newly defined classifications.

Code:
case CLASS_GOODGUYS: return "CLASS_GOODGUYS";

I have no idea what that function does, but it looks important.

Now you have a new relationship defined, we can produce the actual derived Citizens.

Som first thing we need to do is add to the header. in NPC_CITIZEN17.h, at the end of the file, just before the endif, add the following:

Code:
class CNPC_Citizen_Goodguy : public CNPC_Citizen
{
	DECLARE_CLASS( CNPC_Citizen,_Goodguy CNPC_Citizen );
public:

	Class_T 		Classify();

	DECLARE_DATADESC();
};

So, we now have the framework of a goodguy. What we have is an extra class, based on NPC_CITIZEN - it inherits all of the features of the Citizen, except for those that you want to override. The three features we want to overide and the precache, the spawn, and the classify - so we can set a new model, and change the way they react to everything.

Do the same for a Citizen_Badguy.

So, the next step is to fill in those overridden functions. So add the following to the very end of NPC_CITIZEN17.cpp

Code:
Class_T	CNPC_Citizen_Goodguy::Classify()
{
	return CLASS_GOODGUY;
}

Do the same for the bad guy.

Final thing to do is to assign an entity to it.

Code:
LINK_ENTITY_TO_CLASS( npc_citizen_goodguy, CNPC_Citizen_Goodguy );

Do the same for the bad guy, and now they have an entity! Compile, and then they can be added to the game as normal.

I can't remember what I set out to do, now.

-Angry Lawyer
 
I haven't given it a whirl yet, but it looks bloody brilliant. You're my new best friend!! :D
 
I can't quite get it to work. I only did the goodguys for now, and here's my error:

Code:
Compiling...
npc_citizen17.cpp
Linking...
   Creating library .\Release_hl2/server.lib and object .\Release_hl2/server.exp
npc_citizen17.obj : error LNK2001: unresolved external symbol "public: virtual struct datamap_t * __thiscall CNPC_Citizen_Goodguy::GetDataDescMap(void)" (?GetDataDescMap@CNPC_Citizen_Goodguy@@UAEPAUdatamap_t@@XZ)
Release_hl2/server.dll : fatal error LNK1120: 1 unresolved externals

Also... I assumed that when you typed:

Code:
DECLARE_CLASS( CNPC_Citizen,_Goodguy CNPC_Citizen );

You meant

Code:
DECLARE_CLASS( CNPC_Citizen_Goodguy, CNPC_Citizen );

Now... when I change

Code:
LINK_ENTITY_TO_CLASS( npc_citizen_goodguy, CNPC_Citizen_Goodguy );

to

Code:
LINK_ENTITY_TO_CLASS( npc_citizen_goodguy, CNPC_Citizen );

it returns no errors while compiling, but it makes no npc_citizen_goodguy entity. Please help, oh holy Angry Lawyer!!!

EDIT:

I'm just wondering if it would be possible to add a seperate .h and .cpp file to the solution that are identical to npc_citizen17.h and npc_citizen17.cpp and then just change all references to CNPC_CITIZEN and npc_citizen to CNPC_CITIZEN_GOODGUY and npc_citizen_goodguy?

-Craig
 
LINK_ENTITY_TO_CLASS( npc_citizen_goodguy, CNPC_Citizen_Goodguy ); is correct - the first word is the name you want hammer to use, and the second word is the code object you want it to use. So, what you did in the above example was link your new entity name to the existing one.

Try commenting out Datadesc; in the header, as I think that's the thing that's not resolving.

-Angry Lawyer
 
Whoops, I think I might have killed him with an overload of programming...

Any luck with this? I pulled it out of the top of my head, and didn't actually test it (since it's such a simple thing to do). I'd like to know if my ramblings amount to anything.

-Angry Lawyer
 
I appologise for my noob-ness with both C++ and modding... but I was working on a similar mod and used your awsomly awsome post as a guide. I ran into a problem when I was trying to build "npc_citizen17.h" tho.

The error I got was:
"syntax error : missing ';' before identifier 'ThisClass'"
and
"'CNPC_Citizen' : no members defined using this type"

My code was this:

--------------------

class CNPC_Militiaman : public CNPC_Citizen
{
DECLARE_CLASS( CNPC_Citizen_Militiaman, CNPC_Citizen );
public:

Class_T Classify();

DECLARE_DATADESC();
};

-----------------------------

if you could help point me in the right direction as to what i'm doing wrong that would be great... thx.
 
well actually I was looking at the code and messing arround. I think the reason why it's not working is because the "npc_citizen17.h" and ".cpp" declare all kinds of citizens and the format of what you're code had was different.

Basicly I'm playing with the fact that the actual entity of the citizens is "npc_citizen" with different types.

What i'm currently experimenting with is making a copy of the "npc_citizen17.h" and ".cpp" called "npc_underground.h" and ".cpp" (because the npc's i'm trying to make are called the underground) and changing all of the "citizen"s to "underground"s. so that I can have a copy of the "npc_citizen" entity that does the exact same stuff but has different alignments and skins.

It's taking some time tough because I want to make sure that I keep all the refrences to citizen AI (because I want them to behave the same). I'm still working on it but I think it'll will work out nicely :cheers: .

An added benefit of this is that I will have an entirely different class of npcs that have the same available types (medic, ammo supply, etc..).
 
Angry Lawyer said:
Whoops, I think I might have killed him with an overload of programming...

Any luck with this? I pulled it out of the top of my head, and didn't actually test it (since it's such a simple thing to do). I'd like to know if my ramblings amount to anything.

-Angry Lawyer

No, sir, I'm certainly not dead. In fact, I've been very busy continuing my mod as this is working almost perfectly. I have made citizen clones successfully, and am now toying with assigning different models to them. Can I just add the following inside the new npc definition?

Code:
//---------------------------------------------------------
// Citizen models
//---------------------------------------------------------

static const char *g_ppszRandomHeads[] = 
{
	"male_01.mdl",
	"male_02.mdl",
	"male_03.mdl",
	"male_04.mdl",
	"male_05.mdl",
	"male_06.mdl",
	"male_07.mdl",
	"male_08.mdl",
	"male_09.mdl",
	"female_01.mdl",
	"female_02.mdl",
	"female_03.mdl",
	"female_04.mdl",
	"female_06.mdl",
	"female_07.mdl",
	
};

static const char *g_ppszModelLocs[] =
{
	"Group01",
	"Group01",
	"Group02",
	"Group03%s",
};

This appears earlier in the file and, I assume, says which models to use. Is there more I need to know to make the new npc's use new models? (I have the models and successfully swapped them with the current citizen model, so I know they work)
 
:bounce: WOOO HOOO! :bounce:

Well Angry Lawer, I would like to thank you verry much for your inspiration. I've finally succeeded in creating a copy of the "npc_citizen" entity.

I can now mod my "npc_underground" in every way I would change the "npc_citizen" entity but they are two different entities. I can change their AI behavior, skins, models... everything.

What I did was make a copy of the files "npc_citizen17.cpp" and "npc_citizen17.h" then replace everything that didn't work.

To do this, I started editing "BaseEntitiy.h" just like you said, then followed that up with "hl2_gamerules.cpp" so that I could have my classes and alignments.

I began with these replacements (make sure that "match case" is on) to both the npc_citizen.cpp and the npc_citizen.h:
Code:
			CITIZEN	--> UNDERGROUND
			Citizen --> Underground
			citizen	--> underground
			CIT	--> UND
			CT_	--> UN_                        ***
			CCommandPoint	--> UCommandPoint
			CLASS_UNDERGROUND_PASSIVE	--> CLASS_UNDERGROUND
			player_squad_autosummon_time	--> player_underground_squad_autosummon_time
			szExpressionTypes	--> unExpressionTypes

(*** watch out for this one... some things end in "CT_" that you don't want to change so make sure you dont' change those)

I then went down to the code in the new .cpp file:
Code:
Class_T	CNPC_Underground::Classify()
{
	if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
		return CLASS_UNDERGROUND_PASSIVE;

	if (GlobalEntity_GetState("citizens_passive") == GLOBAL_ON)
		return CLASS_UNDERGROUND_PASSIVE;

	return CLASS_PLAYER_ALLY;
}

and changed that too:
Code:
Class_T	CNPC_Underground::Classify()
{
	return CLASS_UNDERGROUND;
}
because there's no need to have them be passive at any time during my mod.

After I finished that there was only some minor tweaking to do that I don't really remember but the compile errors should point ppl in the right direction if they want to try it. SWEET! :smoking:
 
GAH! damnit... ok there's some more stuff to do that I will post soon regarding spesific responses to cirtan situations but that's at least the first half...
 
:flame: DAMN! :flame:

The good news is that I got all of the relationships for this NPC class down....

What I did was search the entire solution for "npc_citizen17.h" and copy every referance to the citizens to make another one for the underground.... your way keeps looking better and better.

The bad news is that it still isn't an entity for some reason.

Can someone tell me how to import my compiled data into my mod? I think that's where I'm screwing up.
 
Kobnar said:
Can someone tell me how to import my compiled data into my mod? I think that's where I'm screwing up.

If you mean you can't add your entity to the map in hammer, then here is the solution. You have to edit (or create a new) data file (.fgd located in sourcesdk\bin) and add the entity to that. What I did was duplicate the entry for the citizen, and rename things.
 
To add extra models, you'll have to define them, and override the bits that they get applied to the model in the code.

You'll need to define it too.
#DEFINE GOODGUY_MODEL "goodguy.mdl",

It's done in much the same way as I did the 'Classify' override. Just override Precache, and Spawn, and the bit where the models are applied (which is in an odd place in the Citizen code), and stick your new model in there.

this->model = GOODGUY_MODEL;

Or something.

-Angry Lawyer
 
Ok, Angry Lawyer... here's a tough one for yeh. Is there a way to keep track of whose side I'm on, and let it change throughout the game? I want to start off as a goodguy, but through my decisions have the potential to become a badguy and have the AI change accordingly. I would like to do this without using AI Relationships in the map, because I want a decision I make in Chapter 2 to have an effect on chapter 10, for example. My best guess is I will either have to create an entity to hold the AI Relationships throughout the game, or I will need some sort of environment variable, but I'm not sure which way is best. If you could give me some direction, I would be even further in debt to you.
 
Or just store it in the player?

Add an extra variable to the Player (something like Bool m_bKarma) in the hl2_player.h (make sure you make it a public variable), and initialise it to 0. Add it to the fields list of the hl2_player.cpp (important, otherwise it won't save between games, line 110 if you want to know).

Now we start the real work...

Code:
Class_T  CHL2_Player::Classify ( void )
{
	// If player controlling another entity?  If so, return this class
	if (m_nControlClass != CLASS_NONE)
	{
		return m_nControlClass;
	}
	else
	{
		if(IsInAVehicle())
		{
			IServerVehicle *pVehicle = GetVehicle();
			return pVehicle->ClassifyPassenger( this, CLASS_PLAYER );
		}
		else
		{
			return CLASS_PLAYER;
		}
	}
}

Look for something like that.

Replace it with the following:

Code:
Class_T  CHL2_Player::Classify ( void )
{
	int iClassify;
	//LAWYER:  first, we need to check the player's alliance status
	if (this->m_bKarma == true) //LAWYER: If we've been made evil...
	{
		iClassify = CLASS_BADGUY; //LAWYER: set the variable to evil!
	}
	else
	{
		iClassify = CLASS_GOODGUY; //LAWYER: We're still on the path of good.
	}

	// If player controlling another entity?  If so, return this class
	if (m_nControlClass != CLASS_NONE)
	{
		return m_nControlClass;
	}
	else
	{
		if(IsInAVehicle())
		{
			IServerVehicle *pVehicle = GetVehicle();
			return pVehicle->ClassifyPassenger( this, iClassify ); //LAWYER: Changed
		}
		else
		{
			return iClassify; //LAWYER: Changed
		}
	}
}

Now, all you need is to add in whatever task makes your character evil, and change the bool on the player. You'll need to figure out how the game handles the transference of variables between levels though, because I think it'll reset between levels.

-Angry Lawyer
 
You never cease to amaze me, angry lawyer. I swear I'll stump you one of these days!
 
Can I make an entity that will change the value of m_bKarma using the inputs/outputs in hammer? I think I'd like to do it this way, mostly because I can't figure out how to do it in the code. I want the value to change when I kill somebody on my team, but I don't know how/where to trigger that in the code.
 
Probably. When I get onto my code computer, I'll look into it. So, you want a 'relay' type of entity, that can be targeted by the I/O system, to change the relationship, then?
This is a singleplayer mod, right? Because otherwise I've gotta add loops to iterate on only certain players, and other stuff.

-Angry Lawyer
 
Back
Top