Attack Script

From NPC for VCMP 0.4 Servers
Revision as of 17:41, 1 January 2023 by Habi (talk | contribs)
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 latest version 1.5 patch2 may be incompatible with this script.(ConnectNPC, ConnectNPCEx functions parameters changed)

Download Links

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);
}
}
*/