Attack Script

From NPC for VCMP 0.4 Servers
Jump to navigation Jump to search

Introduction

This is a server along with necessary npcscripts and plugins. This is the script used for Public Test (Video)(Thanks Sebastian) of a recent version. The npcclient version is 1.5 patch1. The later versions 1.5 patch2, 1.6 are incompatible with this script.(ConnectNPC, ConnectNPCEx functions parameters changed)

Download Links

Server with all necessary scripts, plugins and npc program and directories.

Linux x64 [Zip]

[Tar Ball] || [Zip-Link2]

Windows x32 [Zip] [Zip-Link2]

The npc-Script

File: npcscripts/attack_script.nut

enum CONSTANTS{
SHOOT_INTERVAL = 60,
//MY_WEAPON = 27, //we request to server for this weapon by command /givemeweapon 27
IS_FIRST_PERSON = 0, //0 or 1
FIRING_RANGE = 60, //If target player within this range, it will shoot!
INVINCIBLE = 0, //responds to bullets by dec of health
FIGHTBACK = 1 //If a player shoot, shoothim back
}

enum SERVER{ 
ALLOCATE_TARGET=0x0a0a0a, //rgb=10 10 10 //Tell a target ( player) so it can attack
DEALLOCATE_TARGET=0x0b0b0b, //Tell to stop attacking a target (player)
COMPILE_SCRIPT=0x0c0c0c,  //Pass a string and program will compile it. If it contains functions, it will be called from the program
SHOT_BY_WEAPON = 0x0d0d0d, //Server tells using client data that npc has been shot. So decrease health!
PROCESS_LINE_OF_SIGHT = 0x0e0e0e, //Unable to know whether player has shot npc. So asking server to raytrace in a client computer
NPC_COMMUNICATION = 0x0f0f0f, //This is meant to be a communication between npcs. AI.
CALL_REMOTE_FUNCTION = 0x1a1a1a //What is this?
}

dofile("npcscripts/weapons.nut");
//myTarget<- null; //The ID of the target player
Enemies<-[];
//An array containing current weapon details so that a timer can be used to firing it taking into 
//account its clipsize, reloading time etc. Highly developed..
class WEPDATA
{
	weaponid = 0;
	range = 2.4;
	firingrate = 250;
	reloadingtime = 400; //ms
	clipsize = 1000;
	lastfired = 0.0;
	isreloading = false;
	shotsfired = 0;// used for calculating reloading time
	constructor( weaponid, range, clipsize, reloadingtime, firingrate )
	{
		this.weaponid = weaponid;
		this.range= range;
        this.firingrate=firingrate;
        this.reloadingtime=reloadingtime;
        this.clipsize=clipsize;
	}
}; 
//Below is a class.
myWeapon <- null;

//Below is an integer
PrimaryWeapon <- 0;
//Initialize timer
function OnNPCScriptLoad(params)
{
	timer<- null;
	if(params.len()>0 && params[0]=="Sniper")
		PrimaryWeapon = 29;
	else 
		PrimaryWeapon = 27;
}

//Grab npc ID as a player
function OnNPCConnect(myplayerid) 
{ 
     npcid<-myplayerid;
}

function OnNPCSpawn() 
{ 
    //SendCommand("givemeweapon "+CONSTANTS.MY_WEAPON);// command /givemeweapon 27
    if(!CONSTANTS.INVINCIBLE)
		SendCommand("SYNCSHOTS");
}
//When an npc is shot down, that is the end of the script. In next version it may be implemented to spawn
//them again. Pretty simple though.
function OnPlayerDeath(playerid)
{
	if( playerid == npcid )
	{	if(timer)
		{
			KillTimer( timer );
			timer = null;
		}
		
	}
}

//Shoot at a point. Most important function
function ShootAt(tPos, isReloading = false, isHeadShot=false)
{
	if(isHeadShot)tPos.z+=0.627299;
	local Pos= GetMyPos();//print(GetPlayerArmedWeapon(npcid));
	local angle= atan2(-(tPos.x-Pos.x), tPos.y-Pos.y);

	/* This will always work*/
		local aimPos= Vector(tPos.x,tPos.y,tPos.z);
		local aimDir = Vector( PI, PI, -angle );
	//local aimPos= Pos;
	//local aimDir= GetAimDir( aimPos, tPos );
	local keys= KEY_ONFOOT_FIRE + CONSTANTS.IS_FIRST_PERSON ;
	SetLocalValue(I_KEYS, keys );
	local ammo = GetLocalValue(I_CURWEP_AMMO );
	SendOnFootSyncData( keys, Pos.x,Pos.y, Pos.z, angle, GetPlayerHealth( npcid ), GetPlayerArmour( npcid ), GetPlayerArmedWeapon( npcid ),ammo, 0.0, 0.0, 0.0, aimPos.x, aimPos.y, aimPos.z, PI, PI, -angle, false, isReloading ); 
}

//Sniper Rifle
function SnipeAt(tPos, isReloading=false, isHeadShot=false)
{
	if(isHeadShot)tPos.z+=0.627299;
	local Pos= GetMyPos();
	local aimPos = Vector( Pos.x, Pos.y, Pos.z-0.2);
	local angle= atan2(-(tPos.x-aimPos.x), tPos.y-aimPos.y);
	local alpha = atan2( tPos.z - aimPos.z, sqrt( pow( tPos.y - aimPos.y, 2 ) + pow( tPos.x - aimPos.x , 2 ) ) );
	local dx = cos(alpha) * sin(-angle);
	local dy = cos(alpha) * cos(-angle);
	local dz = sin(alpha);
	local keys= 577 ;
	local ammo = GetLocalValue(I_CURWEP_AMMO );
	aimPos+=Vector( dx, dy, dz);
	SendOnFootSyncData( keys, Pos.x,Pos.y, Pos.z, angle, GetPlayerHealth( npcid ), GetPlayerArmour( npcid ), GetPlayerArmedWeapon( npcid ),ammo, 0.0, 0.0, 0.0, aimPos.x, aimPos.y, aimPos.z, dx, dy, dz, false, isReloading ); 
	SetTimerEx("FireSniperRifle", 60, 1, GetPlayerArmedWeapon(npcid), Pos.x, Pos.y, Pos.z, 16.0*dx, 16.0*dy, 16.0*dz );
}

function DoMeleeAttack( pid )
{
	local ppos = GetPlayerPos( pid );
	local mpos = GetMyPos( );
	local angle = atan2( - (ppos.x- mpos.x), ppos.y-mpos.y);
	SetLocalValue( F_ANGLE , angle );
	SetLocalValue( I_KEYS, 576 );
	SendOnFootSyncDataLV();
}

function AttackPrimaryTarget()
{
	/*if(myTarget==null)
		return;
	local pid = myTarget;*/
	if( Enemies.len() == 0 )
		return;
	local pid = Enemies[0]; //Primary Target at position Zero
	if(IsPlayerStreamedIn(pid) && GetPlayerState(pid)!=PLAYER_STATE_WASTED)
	{
			local pos = GetPlayerPos(pid);
			if(GetDistanceFromMeToPoint(pos) < myWeapon.range )
			{
				if( myWeapon.weaponid <= 11 )
				{	
					DoMeleeAttack( pid );
				}
				else if( myWeapon.weaponid>=17 && myWeapon.weaponid<= 29 )
				{
					
					if( ! myWeapon.isreloading )
					{
						local t=GetTickCount();
						if( (t - myWeapon.lastfired) >= ( myWeapon.firingrate) ) //time comparison in milliseconds
						{
							SetLocalValue(I_CURWEP_AMMO, GetLocalValue(I_CURWEP_AMMO)-1 > 0?GetLocalValue(I_CURWEP_AMMO)-1 :1);
							if(myWeapon.weaponid<=27)
								ShootAt( pos );
							else 
								SnipeAt( pos, false, true );
							myWeapon.shotsfired++;
							if(myWeapon.shotsfired % myWeapon.clipsize == 0 )
								myWeapon.isreloading=true;
							myWeapon.lastfired = GetTickCount();	
						}else 
						{
							//Send a packet, same as last one.
							if(myWeapon.weaponid<= 27)
								SendOnFootSyncDataLV();
						}
					}else 
					{
						//Weapon is reloading
						if( (GetTickCount() - myWeapon.lastfired) >= (myWeapon.reloadingtime) )
						{
							//reloading done !
							myWeapon.isreloading=false;
							SetLocalValue(I_CURWEP_AMMO, GetLocalValue(I_CURWEP_AMMO)-1 > 0?GetLocalValue(I_CURWEP_AMMO)-1 :1);
							if(myWeapon.weaponid<=27)
								ShootAt( pos );
							else 
								SnipeAt( pos, false, true );
							myWeapon.shotsfired++;
							if( (myWeapon.shotsfired % myWeapon.clipsize) == 0 )
								myWeapon.isreloading=true;
						}else 
						{
							//still reloading...
							if(myWeapon.weaponid<= 27)
								ShootAt( pos, true );
						}
					}
				}
			}else 
			{
				SetLocalValue(I_KEYS, 0 );
				//Face NPC towards player if near
				if( GetDistanceFromMeToPoint( pos ) < 30 )
				{
					local turnhead = WhereIsPlayer( pid );
					if( abs( turnhead ) > 0.1 )
						SetLocalValue( F_ANGLE, atan2(-(pos.x-GetMyPosX()), pos.y-GetMyPosY()) );
				}
				SendOnFootSyncDataLV();
				
			}
	}
}
function AddEnemy( pid )
{
	if(Enemies.find( pid ) == null )
		Enemies.push( pid );
}
function SetTargetPlayerForShooting(pid, weaponid=-1)
{
	weaponid= weaponid==-1?PrimaryWeapon:weaponid;
	if( Enemies.find( pid ) != null )
	{
		//If it is at index 0?
		Enemies.remove( Enemies.find( pid ) );
		Enemies.insert( 0, pid );
	}else Enemies.insert(0, pid ); //Set as primary target. (index 0)
 	local wid = GetPlayerSlotWeapon( npcid, GetSlotFromWeaponId(weaponid) );
	local s = SetLocalValue(I_CURWEP, wid );//s=true means server given that weapon already
	if(!s)return ;
	if( wid != 0 )
	{
		local range = Weapon[wid].range;
		local  clipsize = Weapon[wid].clipsize; //30
		local reloadingtime = Weapon[wid].reload; //1000 ms
		local firingrate = Weapon[wid].firingrate; //150 ms
		myWeapon = WEPDATA( wid, range, clipsize, reloadingtime, firingrate )
	}else myWeapon = WEPDATA( 0, 2.4, 250, 400, 1000 );
	if(!timer)
	{
		timer = SetTimerEx( "AttackPrimaryTarget", 60, 0 );
		print(format("Weapon: %d, Range: %0.1f, Clipsize: %d, reloadingtime: %d, firingrate: %d \n", wid,
		myWeapon.range, myWeapon.clipsize, myWeapon.reloadingtime, myWeapon.firingrate) );
	}
}

function UnsetTarget()
{
	//myTarget = null;
	KillTimer( timer );
	timer = null;
	return true;
}
function ToInteger(s)
{
	try{return s.tointeger();}catch(e){return null;}
}
function OnClientMessage(r,g,b,message)
{
	local hex = ( r << 16 ) + ( g << 8 ) + b;
	switch(hex)
	{
		case SERVER.ALLOCATE_TARGET:
			local pid = ToInteger(message);
			if( pid!=null && IsPlayerConnected( pid ))
			{
				SetTargetPlayerForShooting( pid );
			}
			break;
		case SERVER.DEALLOCATE_TARGET:
			local pid = ToInteger(message);
			if( pid!=null && IsPlayerConnected( pid ))
			{
				if( Enemies.find( pid ) != null )
				Enemies.remove( Enemies.find( pid ) );
				
			}
			break;
		case SERVER.SHOT_BY_WEAPON://ClientMessage("pid damage bodypart boolheadshot",r,g,b);
			//If headshot, it assumes npc is shot down by ruger or etc.( and not with colt 75)
			//print("data arrived\n");
			local idx= [0];
   			local plrid = strtok( message, idx );
			local weapon = strtok( message, idx );
   			local damage = strtok( message, idx );
			local bodypart = strtok( message, idx );
			
			try 
			{
				plrid = plrid.tointeger();
				bodypart = bodypart.tointeger() ; 
				damage = damage.tointeger() ;
				weapon = weapon.tointeger() ;
			}catch(e){
				break;
			}
			
			PlayerAttackedNPC( plrid, bodypart, damage, weapon);
			break;
		case SERVER.COMPILE_SCRIPT:
			try
			{
				local script = compilestring(message );
				script();
			}catch(e)
			{
				print(e);
			}
			break;
		case SERVER.NPC_COMMUNICATION:
		{
			local idx=[0];
			local cmd = strtok( message, idx );
			switch( cmd )
			{
				case "ATTACKED" :
				{
					local npcid = strtok( message, idx );
					local plrid = strtok( message, idx );
					//if(myTarget==null)
					try{
					plrid = plrid.tointeger();
					
					}catch(e){ return;}
					if(Enemies.len()==0)
					{
						if(IsPlayerConnected( plrid ))
						{
							SetTargetPlayerForShooting( plrid );
						}
					}else if( Enemies.find( plrid ) == null )
					{
						//Enemy of another npc is still enemey..right?
						Enemies.push( plrid );
					}
				}
				break;
			}
		}
		break;
		case SERVER.CALL_REMOTE_FUNCTION:
		{
			local idx=[0];
			local funcname = strtok( message, idx );
			local f = strtok( message, idx );
			local params=[]; //empty array for passing arguments
			if( funcname!="" )
			{
				try
				{
				for(local i=0; i < f.len(); i++ )
				{
					local parameter = strtok( message, idx );
					if( parameter=="" && f[i]!='s')break;
					switch( f[i] )
					{
						case 'f': params.push( parameter.tofloat() );
						break;
						case 'i': params.push( parameter.tointeger() );
						break;
						case 's': params.push( parameter );
						break;
						default: return;
					}
				}
				}catch(e)
				{
					print(e);
				}
				
				if( rawin( funcname ) )
				{
					local functionObj = rawget( funcname );
					params.insert(0, this);
					functionObj.acall(params );
				}
			}
		}
		break;
		default:
			break;
	}
}
function PlayerAttackedNPC(plrid, bodypart, damage, weapon)
{
	local health= GetPlayerHealth(npcid);
	local armour= GetPlayerArmour(npcid);
	if(armour>0)
		armour-=damage;
	else health-=damage;
	if(armour< 0)
	{
		health+=armour;
		armour=0;	
	}	
	if( health <= 0 || damage == 255 )
	{		
		local anim;
		switch( bodypart )
		{
			case BODYPART_BODY: anim = 0xd; break;
			case BODYPART_HEAD: anim = 17; break;
			case BODYPART_TORSO : anim = 13; break;
			case BODYPART_LEFTARM	: anim = 19; break;
			case BODYPART_RIGHTARM	: anim = 20; break;
			case BODYPART_LEFTLEG: anim = 21; break;
			case BODYPART_RIGHTLEG	: anim = 22; break;
			default: anim = 0xd;break;
		}
		//clear targets
		//myTarget = null;
		Enemies.clear();
		SendShotInfo( bodypart, anim );
		SetTimerEx("SendOnFootSyncDataLV", 100, 1);
		SetTimerEx("SendDeathInfo", 2000, 1, weapon, plrid, bodypart );
		
	}else	
	{
		SetLocalValue( I_HEALTH, health );
		SetLocalValue( I_ARMOUR, armour );
		SendOnFootSyncDataLV();
		//Small Artificial intelligence
		if( CONSTANTS.FIGHTBACK && Enemies.len()==0 )
		{
			if( weapon<= 11 )
			{
				local wep = GetPlayerSlotWeapon( npcid, 1 );
				if(wep == 0 )wep= GetPlayerSlotWeapon( npcid, 0 );//Brass knuckes available??
				SetTargetPlayerForShooting( plrid, wep );
			}else 
			{
				SetTargetPlayerForShooting( plrid );
				SendCommand("NPC_COMMU ATTACKED "+npcid+" "+ plrid );
			}
		}
	}
}

function OnSniperRifleFired( playerid, weaponid, x, y, z, dx, dy, dz )
{
	local outputstr=format("Sniper Rifle, x,y,z= %f, %f, %f and dir = ( %f, %f, %f )\n", x,y,z, dx/16, dy/16, dz/16);
	local command= format("PLOS %d %.4f %.4f %.4f %.4f %.4f %.4f %d %d", playerid, x, y, z, dx/16, dy/16, dz/16, 2, weaponid );
	SendCommand( command );
}
//Attack every player coming into that area.
function OnPlayerStreamIn(playerid)
{
	if( GetPlayerName(playerid).len()!=2 )
	{
		if(GetPlayerState(npcid) != PLAYER_STATE_WASTED )
		{
			if( Enemies.len() == 0 )
				SetTargetPlayerForShooting( playerid );
			else AddEnemy( playerid );
		}
	}
}
function OnPlayerUpdate( playerid, updateType )
{
	if(updateType == PLAYERUPDATE_NORMAL || updateType == PLAYERUPDATE_AIMING)
	{
		if( (GetPlayerKeys(playerid) & 576)== 576 ) //On server and player on same pc, health may be reduced before hit
		{
			local mypos = GetMyPos();
			local plrangle = GetPlayerFacingAngle( playerid );
			local plrpos = GetPlayerPos( playerid );
			local _angle = atan2( -(mypos.x - plrpos.x ), mypos.y - plrpos.y );
			if( _angle < 0 && plrangle > 0 )_angle+=2*PI;
			else if( plrangle <0 && _angle > 0 )_angle-=2*PI;
			if( abs( _angle - plrangle ) < PI/2 ) //take PI/2. 0 means straight to npc
			{
				//player is facing npc
				local weapon = GetPlayerArmedWeapon( playerid );
				if( weapon >=0 && weapon <= 11 )
				{
					local range = Weapon[ weapon ].range ;
					if( GetDistanceFromMeToPoint( plrpos ) <= range )
					{
						//player is in range of weapon
						local damage = Weapon[ weapon ].damage /5 ;
						//many packets arrive. so damage must be controlled
						PlayerAttackedNPC(playerid, 0, damage, weapon);
					}
				}
			}
		}		
	}
}
function strtok(string,array)
{
 local index=array[0];
 local length=string.len();
 /* Skip the leading white space */
 while(index<length && string[index]<=' ')
  index++;
 local result="";
 local offset = index;
 while( index < length && string[index] > ' ' && index-offset < 20-1)
 {
  result+=string.slice(index,index+1);
  index++;
 }
 array[0]=index;
 return result;
}

File: npcscripts/weapons.nut

class WEAPON
{
    range=80;
    firingrate=250; //interval ms
    reload=500; //ms
    clipsize=30;
    damage=25;
    constructor( range, firingrate, reload, clipsize, damage)
    {
        this.range= range;
        this.firingrate=firingrate;
        this.reload=reload;
        this.clipsize=clipsize;
        this.damage=damage;
    }
};
Weapon<-array( 34 ); //minigun id is 33. plus 1 for weapon 0
Weapon[0] = WEAPON(2.4,250, 100, 1000, 8);
Weapon[1] = WEAPON(2.0,450, 100, 1000, 16);
Weapon[2] = WEAPON(1.8,250, 100, 1000, 45);
Weapon[3] = WEAPON(1.5,450, 100, 1000, 21);
Weapon[4] = WEAPON(1.5,450, 100, 1000, 21);
Weapon[5] = WEAPON(1.8,250, 100, 1000, 21);
Weapon[6] = WEAPON(2.0,450, 100, 1000, 21);
Weapon[7] = WEAPON(1.5,450, 100, 1000, 21);
Weapon[8] = WEAPON(1.9,250, 100, 1000, 24);
Weapon[9] = WEAPON(2.0,250, 100, 1000, 24);
Weapon[10] = WEAPON(2.1,250, 100, 1000, 30);
Weapon[11] = WEAPON(1.7,900, 100, 1000, 35);
Weapon[12] = WEAPON(30.0,100, 1, 1, 75);
Weapon[13] = WEAPON(30.0,100, 1, 1, 75);
Weapon[14] = WEAPON(30.0,100, 1, 1, 75);
Weapon[15] = WEAPON(25.0,100, 1, 1, 75);
Weapon[16] = WEAPON(30.0,100, 1, 1, 75);
Weapon[17] = WEAPON(30.0,210, 450, 17, 25);
Weapon[18] = WEAPON(30.0,630, 1000, 6, 135);
Weapon[19] = WEAPON(20.0,250, 450, 1, 80);
Weapon[20] = WEAPON(15.0,250, 750, 7, 100);
Weapon[21] = WEAPON(10.0,250, 250, 1, 120);
Weapon[22] = WEAPON(30.0,119, 500, 50, 20);
Weapon[23] = WEAPON(45.0,59, 500, 30, 20);
Weapon[24] = WEAPON(30.0,60, 500, 30, 15);
Weapon[25] = WEAPON(45.0,90, 500, 30, 35);
Weapon[26] = WEAPON(90.0,90, 1000, 30, 40);
Weapon[27] = WEAPON(90.0,150, 1000, 30, 35);
Weapon[28] = WEAPON(100.0,89, 1401, 1, 125);
Weapon[29] = WEAPON(100.0,180, 1401, 7, 125);
Weapon[30] = WEAPON(55.0,100, 1200, 1, 75);
Weapon[31] = WEAPON(5.1,100, 100, 500, 25);
Weapon[32] = WEAPON(75.0,119, 500, 100, 130);
Weapon[33] = WEAPON(75.0,30, 350, 500, 140);

/* Generated from server side code
function makecode()
{
for(local i=0; i< 34; i++)
{
    local string=format("Weapon[%d] = WEAPON(%0.1f,%d, %d, %d, %d);", i, 
    GetWeaponDataValue( i, 2 ), GetWeaponDataValue( i, 3 ), 
    GetWeaponDataValue( i, 4 ), GetWeaponDataValue( i, 5 ), 
    GetWeaponDataValue( i, 6 ) );
    print(string);
}
}
*/