#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);
}
#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
}
}
}
}
#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.
}
}
I think (for future versions) it would be good to make a plugin out of it.
With the plugin, it maybe would be faster (if this is important) and the plugin could directly write the file into the correct folder. But for now, this is just awesome. I will test it right away! |
In most cases, I'd prefer to give something away.
My philosophy is, If you're doing something for money, you're no longer doing it for fun. |