Rico
03-21-2004, 03:45 PM
Due to popular request I have decided to post this for everyone to see instead of answering with a PM:
I will now explain to all willing ears how to create a basic enemy that will try to attack the player whenever he is in melee range. Take heed and remember that this code I am about to share with you is in beta stage and as a consequence it will contain several bugs and flaws, among them the fact that enemies will not attack the player if he stands completely still (no keys are pressed) and the fact that the enemy will sometimes attack VERY fast if the player moves too much. With all these things explained I will now explain how it is that I made a melee only enemy (and other enemies will also attack using melee if they are close enough).
First I must explain the logic behind the method. I started with the idea that enemies should try to get close to the player, as close as possible with the settings we were supplied by Remedy for the AI. After managing to this the idea then progressed into actually making the enemies attack when they were in close range and not running away (they always think they have a gun in their hands, very dumb). I thought about it for a while and eventually I came up with what I call a "melee aura". Basically this is an area around the player that signals the enemy to attack whenever he comes into range. Now I will go on to the in-depth explanation of how to code this:
We will start by changing an enemy so that he comes close to you instead of running away. The simplest way of doing this is to just use the same settings as the Boozehound NPC and then applying them to the desired enemy. The result is then an enemy who follows you around wherever you try to go. This is done by lowering the FOV to low ranges as well as changing the enemy's cone of fire (the angle at which they start firing). You really shouldn't bother with this, just use the boozehound settings as they are the most appropriate at the moment.
You now have an enemy that follows you around.
Here comes the crucial part: You want to create an aura around the player that makes the enemies want to attack you.
To do this we will start by making a projectile. Make a new projectile called PlayerAura.txt in the projectiles folder. Don't forget to create a folder for it so your game doesn't crash (place empty dat file inside, I expect you to know this already). Inside you will paste the following code:
#include <..\projectiles.h>
#include <..\projectileanimid.h>
#include <..\object_lods.h>
// -------------------------------------------------------------------
// Baseballbat melee hit projectile
// -------------------------------------------------------------------
[LOD]
{
[Geometry] exportdata = ..\dummy\dummy_l0.kf2;
//[Geometry] exportdata = ..\..\weapons\molotov\Molotov_L0.kf2;
Distance = 0.001 ;
}
[Animationset]
{
[Animation] Index = PROJECTILEANIM_DEFAULT; Filename = ..\dummy\dummy_l0.kf2;
[Animation] Index = PROJECTILEANIM_RICOCHET; Filename = ..\dummy\dummy_l0.kf2; // ..\dummy\dummy_l0.kf2;
[Animation] Index = PROJECTILEANIM_EXPLODE; Filename = ..\dummy\dummy_l0.kf2; // ..\dummy\dummy_l0.kf2;
[Animation] Index = PROJECTILEANIM_FROZEN; Filename = ..\dummy\dummy_l0.kf2; // ..\dummy\dummy_l0.kf2;
}
[Attributes]
Direction = ( 0 , 0 , 1 );
OffsetType = USE_PROJECTILEOFFSET_BULLET; //none
Accuracy = 125;
Speed = 60; //20
SpeedRandom = 0%;
Damage = 0.0001; // small amount of damage... you don't want to kill the enemy
RicochetMaximumAngle = 0;
RicochetSpeedMultiplier = 0.00001;
RicochetMinimumSpeed = 1;
RicochetUntilOnGround = false; // true for projectiles that create level items
VisibilityMaximumSpeed = 100; // put this to 100 to see visualization
// (0..1) interpolator for skin target penetration multipliers
PenetrationValue = 1.0;
SmallProjectile = TRUE;
Debris = FALSE;
RandomizeDirection = TRUE;
RandomizeRotation = FALSE;
CollisionExplosion = TRUE;
DelayedExplosion = TRUE;
ExplosionDelay = 0.07;//0.009
GravityMultiplier = 0.0;//0.0
UseLookAt = FALSE;
DamagesCharacter = true;//true
ItemsToCache = 0;
MeshesToCache = 0;
DeathCause = PROJECTILEDEATH_RANDOM;
BloodProjectile = FALSE; // uses same detail level settings as debris but is not debris
DamageType = DAMAGETYPE_SMALL; // change to a damagetype you aren't using already
DamageTypeWithArmor = DAMAGETYPE_SMALL; // change to a damagetype you aren't using already
Now the explanation: The projectile is there so that the enemy has some way to receive the input from the player. These projectiles will be emitted from all around the player and will be what triggers the reaction from the enemy. The damagetype we are using for this projectile CAN ONLY BE USED FOR THIS PURPOSE ALONE. This means it cannot be used by any other projectiles unless you want the enemy to attack you with a melee attack when hit. I will move on to explain why we used a damagetype further along.
Now we must move on to the player skin file and make some changes to the modifiers present there. Basically what you want is to add the following code to most of the animations used for the player (running, crouching, jumping, standing, turning, etc). These will be the chunks where the projectiles are generated. The aura is created by a lot of projectiles which are created at random directions from the bone of origin, in which case I chose Bip01 Spine. Add in this chunk of code:
(I added these changes to male_player.h)
[Message] Frame = 0; String = "this->P_CreateProjectileToBone( PlayerAura, 20, \"Bip01 Spine\");";
I added this exact same line to most of the animation chunks I already mentioned before. This method is ackward because if the player doesn't move it means the aura won't be generated (the stand animation doesn't seem to be generating the projectiles, pose doesn't work either). However I have already thought of a way of fixing this by creating a subanimation that executes the projectile generation code instead of creating them right in the animation chunk. The subanim will contain little to no changes in motion (maybe a facial animation, who knows). However take heed as I have not tried this method yet due to lack of new animations so try it at your own risk.
Right... Now lets move on to the actual attack being executed:
(male.txt skeleton file)
[Animation] Index = CHARANIM_AURADAMAGE; Filename = "anim\GetHit_Very_Small.kf2";
[Properties]
{
[Reference] Name = GetHit_Aura;
Message] Frame = 0; String = "this->c_subanimate(Sub_Punch_Player);";
}
Now, remember we cannot make new damagetypes so in reality CHARANIM_AURADAMAGE cannot really be used. Instead replace this with whichever damagetype animation you decided to change(CHARANIM_SMALLDAMAGE for example). Then go up to your subanimation chunk in the male.txt and add in the following new subanimation:
[Animation] Index = SUBANIM_SHOOTPUNCH; Filename = "anim\shoot_grenade_forward.kf2";
[Properties]
{
[Reference] Name = Sub_Punch_Player;
[Message] Frame = 0; String = "this->cam_animateplayerparented(BT_Circle_kick);";
//[Message] Frame = 0; String = "maxpayne_gamemode->GM_SetPlayerControls(false);";
[Message] Frame = 0; String = "RightHandWeapon->WS_UsePrimary( true );";
[Message] Frame = 0; String = "RightHandWeapon->WS_Hide( true );";
//[Message] Frame = 0; String = "LeftHandWeapon->WS_Hide( true );";
[Message] Frame = 20; String = "RightHandWeapon->WS_AnimateShooting(" WEAPONANIM_SHOOT "," WEAPONANIM_SHOOTEMPTY ");";
// new change [Message] Frame = 20; String = "this->P_CreateProjectileToBone( BulletMelee3, 1, \"Bip01 R Hand\");";
[Message] Frame = 20; String = "this->A_Play3DSound( weapons, player_shoot_melee, \"\" );";
[Message] Frame = 22; String = "RightHandWeapon->WS_AnimateShooting(" WEAPONANIM_SHOOT "," WEAPONANIM_SHOOTEMPTY ");";
// new change [Message] Frame = 22; String = "this->P_CreateProjectileToBone( BulletMelee3, 1, \"Bip01 R Hand\");";
//[Message] Frame = 35; String = "maxpayne_gamemode->GM_SetPlayerControls(true);";
[Message] Frame = 35; String = "RightHandWeapon->WS_Hide( false );"; //used to be frame 40
//[Message] Frame = 40; String = "LeftHandWeapon->WS_Hide( false );";
}
Basically what you need to know here is that you need to add the actual melee attack to this subanimation chunk. BulletMelee3 are my projectiles that I used to cause damage (the enemy punches the player with this subanimation). I used the throwing animation to simulate a punch without using new animations and the projectiles are created much like you would for any other melee attack. If you are reading this you should probably be able to tell what each line in the above code means.
Why this worked:
MP2 coding is very limited in what you can do, however there are many little tricks you can use to confuse the game into doing what you want it to do and therefore doing interesting things and getting similarly interesting results. What I did here was trick the game into having the PlayerAura projectile tell the enemy to perform a subanimation whenever the projectile caused damage. This command could not have been passed on through the actual projectile file since you cannot execute commands like c_subanimate while inside a projectile because the projectile itself would try to execute the command, and non NPC objects cannot execute animations or C_ commands of any type (character commands). What I did instead was have the projectile perform a specific damage animation after hitting the enemy (very small amount of damage) and then, while inside the damage animation chunk, telling the character to actually perform the desired subanimation (in this case a melee punch attack). So in reality the projectile acts as a messenger, the enemy receives the message after taking damage and then does as he's told and attacks the player.
The aura is generated because the projectiles in PlayerAura explode after 0.07 seconds or on collision. So an enemy across the room does not react to the aura but an enemy close to you does.
Obviously this method needs some refinement but I believe this could help the modding community in some way or another (this will actually help you do a LOT if you're smart about using it, this is not even close to the only use it can be given). I also expect people to use this for hand-to-hand combat mods (I hate kung-fu describing it) and such... you could create enemies that block your attacks and enemies that react to your attacks with counter-attacks. The uses for this code are only limited by your imagination and your capability as a coder.
As a small token of gratitude I expect that anyone who uses my method (or tutorial) to at least include me in the credits for your mod. I do not expect people to ask permission to use it because I shared it to help the modding community advance and create better things. Just mention me in your credits and I will be overjoyed.
If anyone has anything to share about the topic at hand please do it here. I want to turn this thread into a general coding breakthrough thread of sorts. If you have any interesting things you have been able to do or any ideas or questions regarding tough coding issues post them here so we can work on things as a community. I believe there are many coders out there who are just keeping stuff like this to themselves instead of sharing it and helping the community.
I encourage everyone to contribute to this thread.
I will now explain to all willing ears how to create a basic enemy that will try to attack the player whenever he is in melee range. Take heed and remember that this code I am about to share with you is in beta stage and as a consequence it will contain several bugs and flaws, among them the fact that enemies will not attack the player if he stands completely still (no keys are pressed) and the fact that the enemy will sometimes attack VERY fast if the player moves too much. With all these things explained I will now explain how it is that I made a melee only enemy (and other enemies will also attack using melee if they are close enough).
First I must explain the logic behind the method. I started with the idea that enemies should try to get close to the player, as close as possible with the settings we were supplied by Remedy for the AI. After managing to this the idea then progressed into actually making the enemies attack when they were in close range and not running away (they always think they have a gun in their hands, very dumb). I thought about it for a while and eventually I came up with what I call a "melee aura". Basically this is an area around the player that signals the enemy to attack whenever he comes into range. Now I will go on to the in-depth explanation of how to code this:
We will start by changing an enemy so that he comes close to you instead of running away. The simplest way of doing this is to just use the same settings as the Boozehound NPC and then applying them to the desired enemy. The result is then an enemy who follows you around wherever you try to go. This is done by lowering the FOV to low ranges as well as changing the enemy's cone of fire (the angle at which they start firing). You really shouldn't bother with this, just use the boozehound settings as they are the most appropriate at the moment.
You now have an enemy that follows you around.
Here comes the crucial part: You want to create an aura around the player that makes the enemies want to attack you.
To do this we will start by making a projectile. Make a new projectile called PlayerAura.txt in the projectiles folder. Don't forget to create a folder for it so your game doesn't crash (place empty dat file inside, I expect you to know this already). Inside you will paste the following code:
#include <..\projectiles.h>
#include <..\projectileanimid.h>
#include <..\object_lods.h>
// -------------------------------------------------------------------
// Baseballbat melee hit projectile
// -------------------------------------------------------------------
[LOD]
{
[Geometry] exportdata = ..\dummy\dummy_l0.kf2;
//[Geometry] exportdata = ..\..\weapons\molotov\Molotov_L0.kf2;
Distance = 0.001 ;
}
[Animationset]
{
[Animation] Index = PROJECTILEANIM_DEFAULT; Filename = ..\dummy\dummy_l0.kf2;
[Animation] Index = PROJECTILEANIM_RICOCHET; Filename = ..\dummy\dummy_l0.kf2; // ..\dummy\dummy_l0.kf2;
[Animation] Index = PROJECTILEANIM_EXPLODE; Filename = ..\dummy\dummy_l0.kf2; // ..\dummy\dummy_l0.kf2;
[Animation] Index = PROJECTILEANIM_FROZEN; Filename = ..\dummy\dummy_l0.kf2; // ..\dummy\dummy_l0.kf2;
}
[Attributes]
Direction = ( 0 , 0 , 1 );
OffsetType = USE_PROJECTILEOFFSET_BULLET; //none
Accuracy = 125;
Speed = 60; //20
SpeedRandom = 0%;
Damage = 0.0001; // small amount of damage... you don't want to kill the enemy
RicochetMaximumAngle = 0;
RicochetSpeedMultiplier = 0.00001;
RicochetMinimumSpeed = 1;
RicochetUntilOnGround = false; // true for projectiles that create level items
VisibilityMaximumSpeed = 100; // put this to 100 to see visualization
// (0..1) interpolator for skin target penetration multipliers
PenetrationValue = 1.0;
SmallProjectile = TRUE;
Debris = FALSE;
RandomizeDirection = TRUE;
RandomizeRotation = FALSE;
CollisionExplosion = TRUE;
DelayedExplosion = TRUE;
ExplosionDelay = 0.07;//0.009
GravityMultiplier = 0.0;//0.0
UseLookAt = FALSE;
DamagesCharacter = true;//true
ItemsToCache = 0;
MeshesToCache = 0;
DeathCause = PROJECTILEDEATH_RANDOM;
BloodProjectile = FALSE; // uses same detail level settings as debris but is not debris
DamageType = DAMAGETYPE_SMALL; // change to a damagetype you aren't using already
DamageTypeWithArmor = DAMAGETYPE_SMALL; // change to a damagetype you aren't using already
Now the explanation: The projectile is there so that the enemy has some way to receive the input from the player. These projectiles will be emitted from all around the player and will be what triggers the reaction from the enemy. The damagetype we are using for this projectile CAN ONLY BE USED FOR THIS PURPOSE ALONE. This means it cannot be used by any other projectiles unless you want the enemy to attack you with a melee attack when hit. I will move on to explain why we used a damagetype further along.
Now we must move on to the player skin file and make some changes to the modifiers present there. Basically what you want is to add the following code to most of the animations used for the player (running, crouching, jumping, standing, turning, etc). These will be the chunks where the projectiles are generated. The aura is created by a lot of projectiles which are created at random directions from the bone of origin, in which case I chose Bip01 Spine. Add in this chunk of code:
(I added these changes to male_player.h)
[Message] Frame = 0; String = "this->P_CreateProjectileToBone( PlayerAura, 20, \"Bip01 Spine\");";
I added this exact same line to most of the animation chunks I already mentioned before. This method is ackward because if the player doesn't move it means the aura won't be generated (the stand animation doesn't seem to be generating the projectiles, pose doesn't work either). However I have already thought of a way of fixing this by creating a subanimation that executes the projectile generation code instead of creating them right in the animation chunk. The subanim will contain little to no changes in motion (maybe a facial animation, who knows). However take heed as I have not tried this method yet due to lack of new animations so try it at your own risk.
Right... Now lets move on to the actual attack being executed:
(male.txt skeleton file)
[Animation] Index = CHARANIM_AURADAMAGE; Filename = "anim\GetHit_Very_Small.kf2";
[Properties]
{
[Reference] Name = GetHit_Aura;
Message] Frame = 0; String = "this->c_subanimate(Sub_Punch_Player);";
}
Now, remember we cannot make new damagetypes so in reality CHARANIM_AURADAMAGE cannot really be used. Instead replace this with whichever damagetype animation you decided to change(CHARANIM_SMALLDAMAGE for example). Then go up to your subanimation chunk in the male.txt and add in the following new subanimation:
[Animation] Index = SUBANIM_SHOOTPUNCH; Filename = "anim\shoot_grenade_forward.kf2";
[Properties]
{
[Reference] Name = Sub_Punch_Player;
[Message] Frame = 0; String = "this->cam_animateplayerparented(BT_Circle_kick);";
//[Message] Frame = 0; String = "maxpayne_gamemode->GM_SetPlayerControls(false);";
[Message] Frame = 0; String = "RightHandWeapon->WS_UsePrimary( true );";
[Message] Frame = 0; String = "RightHandWeapon->WS_Hide( true );";
//[Message] Frame = 0; String = "LeftHandWeapon->WS_Hide( true );";
[Message] Frame = 20; String = "RightHandWeapon->WS_AnimateShooting(" WEAPONANIM_SHOOT "," WEAPONANIM_SHOOTEMPTY ");";
// new change [Message] Frame = 20; String = "this->P_CreateProjectileToBone( BulletMelee3, 1, \"Bip01 R Hand\");";
[Message] Frame = 20; String = "this->A_Play3DSound( weapons, player_shoot_melee, \"\" );";
[Message] Frame = 22; String = "RightHandWeapon->WS_AnimateShooting(" WEAPONANIM_SHOOT "," WEAPONANIM_SHOOTEMPTY ");";
// new change [Message] Frame = 22; String = "this->P_CreateProjectileToBone( BulletMelee3, 1, \"Bip01 R Hand\");";
//[Message] Frame = 35; String = "maxpayne_gamemode->GM_SetPlayerControls(true);";
[Message] Frame = 35; String = "RightHandWeapon->WS_Hide( false );"; //used to be frame 40
//[Message] Frame = 40; String = "LeftHandWeapon->WS_Hide( false );";
}
Basically what you need to know here is that you need to add the actual melee attack to this subanimation chunk. BulletMelee3 are my projectiles that I used to cause damage (the enemy punches the player with this subanimation). I used the throwing animation to simulate a punch without using new animations and the projectiles are created much like you would for any other melee attack. If you are reading this you should probably be able to tell what each line in the above code means.
Why this worked:
MP2 coding is very limited in what you can do, however there are many little tricks you can use to confuse the game into doing what you want it to do and therefore doing interesting things and getting similarly interesting results. What I did here was trick the game into having the PlayerAura projectile tell the enemy to perform a subanimation whenever the projectile caused damage. This command could not have been passed on through the actual projectile file since you cannot execute commands like c_subanimate while inside a projectile because the projectile itself would try to execute the command, and non NPC objects cannot execute animations or C_ commands of any type (character commands). What I did instead was have the projectile perform a specific damage animation after hitting the enemy (very small amount of damage) and then, while inside the damage animation chunk, telling the character to actually perform the desired subanimation (in this case a melee punch attack). So in reality the projectile acts as a messenger, the enemy receives the message after taking damage and then does as he's told and attacks the player.
The aura is generated because the projectiles in PlayerAura explode after 0.07 seconds or on collision. So an enemy across the room does not react to the aura but an enemy close to you does.
Obviously this method needs some refinement but I believe this could help the modding community in some way or another (this will actually help you do a LOT if you're smart about using it, this is not even close to the only use it can be given). I also expect people to use this for hand-to-hand combat mods (I hate kung-fu describing it) and such... you could create enemies that block your attacks and enemies that react to your attacks with counter-attacks. The uses for this code are only limited by your imagination and your capability as a coder.
As a small token of gratitude I expect that anyone who uses my method (or tutorial) to at least include me in the credits for your mod. I do not expect people to ask permission to use it because I shared it to help the modding community advance and create better things. Just mention me in your credits and I will be overjoyed.
If anyone has anything to share about the topic at hand please do it here. I want to turn this thread into a general coding breakthrough thread of sorts. If you have any interesting things you have been able to do or any ideas or questions regarding tough coding issues post them here so we can work on things as a community. I believe there are many coders out there who are just keeping stuff like this to themselves instead of sharing it and helping the community.
I encourage everyone to contribute to this thread.