22.05.2012, 14:25
(
Last edited by Joe Staff; 22/05/2012 at 04:38 PM.
)
The following code is used to generate .REC files for NPCs to follow. In essence, this is a pawn scripted NPC controller which gives you the ability to create NPCs that can go directly from point A to point B while (client sided) attempting to maintain standard physics. What that means is the NPC will actually stick to the floor and be stopped by walls, but only momentarily. If their recording has them passing a certain threshold, they will simply walk through the wall or fly off of the ground.
Important Notes:
When creating a path that is too short, the NPC will simply be stuck there for an undetermined amount of time. I believe this to be connected with a Divide By Zero error, but I never got around to fixing it.
I've left it up to you to find a method to get the .REC file from the Scriptfiles folder to the NPC Recordings folder. I used a FILE MANAGER. Please note however; I ran into an issue with the moving function in that particular plugin which required me to delete the previous .rec file before moving it.
I highly suggest spending time finding out the exact speeds of motion when having the NPC follow it. Otherwise the NPC will teleport every now and then. A good speed for running (not sprinting) was 0.006.
On average, a created .REC file will be in the 5KB size range, about 60-100 separate blocks of motion.
Lastly, I hadn't spent a lot of time optimizing the process, so I highly suggest you do so before using it.
Video:
[ame]http://www.youtube.com/watch?v=FH6vteZCOgM[/ame]
Code:
Feel free to take this code, release it, and claim it as your own, I don't care.
*I say 'claim it as your own' as this code is simply one of the only ways to actually perform this task. I don't wish to claim that if you created your very own system that you copied mine.
Example:
Filterscript -- USES JaTochNietDan's File Manager
Makes all NPCs follow a single player if he is within range, only stops when player spawns.
NPC Mode
Important Notes:
When creating a path that is too short, the NPC will simply be stuck there for an undetermined amount of time. I believe this to be connected with a Divide By Zero error, but I never got around to fixing it.
I've left it up to you to find a method to get the .REC file from the Scriptfiles folder to the NPC Recordings folder. I used a FILE MANAGER. Please note however; I ran into an issue with the moving function in that particular plugin which required me to delete the previous .rec file before moving it.
I highly suggest spending time finding out the exact speeds of motion when having the NPC follow it. Otherwise the NPC will teleport every now and then. A good speed for running (not sprinting) was 0.006.
On average, a created .REC file will be in the 5KB size range, about 60-100 separate blocks of motion.
Lastly, I hadn't spent a lot of time optimizing the process, so I highly suggest you do so before using it.
Video:
[ame]http://www.youtube.com/watch?v=FH6vteZCOgM[/ame]
Code:
pawn Code:
#include <a_samp>
#define ONFOOT_RATE 100 //milliseconds (Not equal to that in Server.cfg, but similar point) -- Numbers reaching the 500ms range will have issues with repeating recordings
#define PI 3.14159265
stock BuildRecording(name[],Float:x1,Float:y1,Float:z1,Float:x2,Float:y2,Float:z2, Float:speed)
{
new time;
new steps;
new Float:angle=atan2(y2-y1,x2-x1);
while(angle<0)angle+=360.0;
while(angle>360)angle-=360.0;
new Float:xvel= (speed * floatcos(angle, degrees));
new Float:yvel= (speed * floatsin(angle, degrees));
new Float:distance=floatsqroot( (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1) );
if(speed<0.00001)time=0;
else time=floatround(distance/speed,floatround_ceil);
//printf("Total Time = %d, Distance=%f, Angle=%f, File Name: %s",time,distance,angle,name);
steps=time/ONFOOT_RATE;
new Float:xrate=xvel*ONFOOT_RATE;//(x2-x1)/steps;
new Float:yrate=yvel*ONFOOT_RATE;//(y2-y1)/steps;
new Float:zrate=(z2-z1)/steps;
if(steps==0)time=0;
else time=time/steps;
//printf("Time=%d, Steps=%d, Rate:%f,%f,%f",time,steps,xrate,yrate,zrate);
new end=0;
new process=0;
new step=0;
new input;
new piece=1;
new File:file=fopen(name,io_write);
//Angle Quaternion
new Float:w,Float:x,Float:y,Float:z;
EulerToQuaternion(-angle+90,w,x,y,z);
//print("Created File -- Writing header");
fputchar(file,0xE8,false);
fputchar(file,0x03,false);
fputchar(file,0x00,false);
fputchar(file,0x00,false);
fputchar(file,0x02,false);
fputchar(file,0x00,false);
fputchar(file,0x00,false);
fputchar(file,0x00,false);
//print("Header written, creating content");
while(!end)
{
switch(process)
{
////////////////////////////////////////////////////////
//Information taken from wiki.sa-mp.com/wiki/.rec_file//
////////////////////////////////////////////////////////
//I had done most of the research, I had almost //
//finished when someone turned me to that link which //
//had all the information found, so I used it. //
////////////////////////////////////////////////////////
case 0..3: //Time before performing the immediately following step (is incremental, step 1 = 0ms, step 2 = 100ms, etc)
{
input=(time*step)<<(32-(piece*8))>>24;
if(piece>3)piece=1;
else piece++;
}
case 4..5: //LeftRight
{
input=0<<(32-(piece*8))>>24;
if(piece>1)piece=1;
else piece++;
}
case 6..7: //UpDown Keys
{
if(step==steps)input=0x0000; //Don't make the NPC walk on the last step or he will walk in place.
else input=0x00FF<<(32-(piece*8))>>24; //0x00FF = Forward, 0xFF00=Backward
if(piece>1)piece=1;
else piece++;
}
case 8..9: //Special Keys
{
input=0;
}
//Position
case 10..13: //X1
{
input=_:floatadd( x1,floatmul(xrate,step) )<<(32-(piece*8))>>24; //Need to convert Floats to Standard Integers (use _:)
if(piece>3)piece=1;
else piece++;
}
case 14..17: //Y1
{
input=_:floatadd( y1,floatmul(yrate,step) )<<(32-(piece*8))>>24;
if(piece>3)piece=1;
else piece++;
}
case 18..21: //Z1
{
input=_:floatadd( z1,floatmul(zrate,step) )<<(32-(piece*8))>>24;
if(piece>3)piece=1;
else piece++;
}
//
//Angle Quaternion
case 22..25: //Quaternion W
{
input=_:w<<32-(piece*8)>>24;
if(piece>3)piece=1;
else piece++;
}
case 26..29: //Quaternion X
{
input=_:x<<32-(piece*8)>>24;
if(piece>3)piece=1;
else piece++;
}
case 30..33: //Quaternion Y
{
input=_:y<<32-(piece*8)>>24;
if(piece>3)piece=1;
else piece++;
}
case 34..37: //Quaternion Z
{
input=_:z<<32-(piece*8)>>24;
if(piece>3)piece=1;
else piece++;
}
case 38: //Health
{
input=0xFF;
}
case 39: //Armor
{
input=0xFF;
}
//Weapon ID
case 40:
{
input=0;
}
//SPECIAL_ACTION
case 41:
{
input=0;
}
//Velocities
case 42..45: //X Velocity
{
if(step==steps)input=0;
else input=_:xvel<<32-(piece*8)>>24;
if(piece>3)piece=1;
else piece++;
}
case 46..49: //Y Velocity
{
if(step==steps)input=0;
else input=_:yvel<<32-(piece*8)>>24;
if(piece>3)piece=1;
else piece++;
}
case 50..53: //Z Velocity
{
input=0<<32-(piece*8)>>24;
if(piece>3)piece=1;
else piece++;
}
//
case 54..67:input=0; //Surfing variables
//Animation
case 68..69:input=0;
case 70..71:input=0; //Animation parameters
default: input=0x00;
}
fputchar(file,input,false);
process++;
if (process==72)
{
step++; //Reset for next step
process=0;
}
if(step>steps)
{
//print("Ending file");
break;
}
}
fclose(file);
//printf("File closed - %d steps created",steps);
}
stock Float:ToRadian(Float:Degrees)return Degrees*(PI/180);
stock EulerToQuaternion(Float:angle,&Float:w,&Float:x,&Float:y,&Float:z) //Requires radian angle
{
new Float:c1,Float:c2,Float:c3,Float:s1,Float:s2,Float:s3;
c1=floatcos(0.0);
c2=floatcos(ToRadian(angle)/2);
c3=floatcos(0.0);
s1=floatsin(0.0);
s2=floatsin(ToRadian(angle)/2);
s3=floatsin(0.0);
w=(c1*c2*c3)-(s1*s2*s3);
x=(s1*s2*c3)+(c1*c2*s3);
y=(s1*c2*c3)+(c1*s2*s3);
z=(c1*s2*c3)-(s1*c2*s3);
}
*I say 'claim it as your own' as this code is simply one of the only ways to actually perform this task. I don't wish to claim that if you created your very own system that you copied mine.
Example:
Filterscript -- USES JaTochNietDan's File Manager
Makes all NPCs follow a single player if he is within range, only stops when player spawns.
pawn Code:
#define FILTERSCRIPT
#include <a_samp>
#include <FileManager>
new NPCTarget[MAX_PLAYERS];
public OnFilterScriptInit()
{
SetTimer("NPCFollow",1000,1);
return 1;
}
public OnPlayerConnect(playerid)
{
if(IsPlayerNPC(playerid))
{
NPCTarget[playerid]=INVALID_PLAYER_ID; //Reset NPCs target
return 1;
}
return 1;
}
public OnPlayerStateChange(playerid, newstate, oldstate)
{
if(newstate==PLAYER_STATE_SPAWNED) //Reset NPCs target when player dies
for(new npc;npc<MAX_PLAYERS;npc++)if(NPCTarget[npc]==playerid)NPCTarget[npc]=INVALID_PLAYER_ID;
return 1;
}
public OnPlayerText(playerid, text[])
{
if(IsPlayerNPC(playerid))
{
if(!strcmp(text,"Command Accepted: npc",false,21)) //Only method I could think of to communicate between NPC Mode and Filterscript
{
new tmps[48];
format(tmps,48,"npcmodes\\recordings\\npc%03d.rec",strval(text[21]));
file_delete(tmps);
return 0;
}
}
return 1;
}
forward NPCFollow();
public NPCFollow()
{
new Float:npx,Float:npy,Float:npz;
new Float:px,Float:py,Float:pz;
new tmps[48];
new tmps2[48];
for(new npc=0;npc<MAX_PLAYERS;npc++)
{
if(!IsPlayerNPC(npc)||!IsPlayerConnected(npc))continue;
GetPlayerPos(npc,npx,npy,npz);
if(NPCTarget[npc]==INVALID_PLAYER_ID) //Has no Target
{
for(new playerid=0;playerid<MAX_PLAYERS;playerid++) //Search for targets
{
if(IsPlayerNPC(playerid)||!IsPlayerConnected(playerid)||(npc==playerid))continue;
if(IsPlayerInRangeOfPoint(playerid,30,npx,npy,npz))
{
NPCTarget[npc]=playerid;
printf("NPC[%d]: Target Player %d",npc,playerid);
}
}
}else{ //Has Target
GetPlayerPos(NPCTarget[npc],px,py,pz);
if(IsPlayerInRangeOfPoint(npc,0.5,px,py,pz))continue; //Poor attempt at preventing lock up
format(tmps2,48,"npcmodes\\recordings\\npc%03d.rec",npc);
if(!file_exists(tmps2))
{
format(tmps,48,"npc%03d.rec",npc);
BuildRecording(tmps,npx,npy,npz,px,py,pz, 0.006);
format(tmps,48,"scriptfiles\\npc%03d.rec",npc);
format(tmps2,48,"npcmodes\\recordings\\npc%03d.rec",npc);
file_move(tmps,tmps2);
SendClientMessage(npc,0,"GO TO FUNCTION CALLED"); //Communicate to NPC
}
}
}
}
pawn Code:
#include <a_npc>
new MYID;
main(){}
public OnNPCConnect(myplayerid)
{
MYID=myplayerid;
}
public OnClientMessage(color, text[])
{
if (!strcmp(text,"GO TO FUNCTION CALLED",false)) //Received communica from server
{
new tmps[32];
format(tmps,32,"npc%03d",MYID);
StartRecordingPlayback(PLAYER_RECORDING_TYPE_ONFOOT,tmps);
format(tmps,32,"Command Accepted: %s",tmps);
SendChat(tmps); //Return communica to server.
}
}