PHP Code:
/**
* Ghost Recorder by TeddyDesTodes
*
* No CVars / Only Defines
*
* Thx to Empī for helping with casting
*
* How to:
* copy recorder.amxx to addons/amxmodx/plugins/
* insert recorder.amxx into plugins.ini
* create a folder called "kzrecord/" in your mod folder
*
* Ingame Commands:
* say /demomenu opens the menu
*
* Defines:
* MAXSLOTS = Maximum saveslots per player and map
* SAVEPATH = Folder for storing ghosts (dont forget to create it)
* RECLIMITER = Limits the amount of data written to file higher means less size but less accurate results should be something between 0.05 and 0.01
*
**/
#include <amxmodx>
#include <amxmisc>
#include <fakemeta>
#include <fakemeta_util>
#define PLUGIN "Ghost Recorder"
#define AUTHOR "TeddyDesTodes"
#define VERSION "0.21"
#define MAXPLAYERS 33
#define RECLIMITER 0.03
#define MAXSLOTS 6
#define SAVEPATH "kzrecord/"
#define RECORDKEYS MENU_KEY_1|MENU_KEY_2|MENU_KEY_3|MENU_KEY_4|MENU_KEY_0
#define SLOTKEYS MENU_KEY_1|MENU_KEY_2|MENU_KEY_3|MENU_KEY_4|MENU_KEY_5|MENU_KEY_6|MENU_KEY_7|MENU_KEY_8|MENU_KEY_9|MENU_KEY_0
new g_FileHandler[MAXPLAYERS]
new Float:g_LastThink[MAXPLAYERS]
new Float:g_LastAngle[MAXPLAYERS][3]
new Float:g_LastOrigin[MAXPLAYERS][3]
new Float:g_LastVelocity[MAXPLAYERS][3]
new g_isRecording[MAXPLAYERS],g_isPause[MAXPLAYERS],g_SaveSlot[MAXPLAYERS],g_MenuOpen[MAXPLAYERS]
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
register_forward(FM_Think,"fm_ghost_think",0)
register_forward(FM_PlayerPreThink,"fm_plr_prethink",0)
register_clcmd("say /demomenu","menu_show")
register_clcmd("say /test","choose_slot")
register_menucmd(register_menuid("Demo Recorder"), RECORDKEYS, "menuhandler")
register_menucmd(register_menuid("Slot Chooser"), SLOTKEYS, "slothandler")
register_cvar("ghost_recorder", VERSION, FCVAR_SERVER);
//create dir if needed
if(!dir_exists(SAVEPATH)){
log_amx("Save dir doe not exsit, creating for you =)")
if(mkdir(SAVEPATH) != 0){
log_amx("Couldn't create dir do it yourself")
}
}
}
public plugin_precache(){
precache_model( "models/player/vip/vip.mdl" );
}
public client_putinserver(id){
g_FileHandler[id] = 0
g_isRecording[id] = 0
g_isPause[id] = 0
g_LastThink[id] = get_gametime()
g_SaveSlot[id] = 0
}
public plugin_end(){
for(new i = 0; i < MAXPLAYERS; i++){
if(g_FileHandler[i] != 0){
fclose(g_FileHandler[i])
}
}
}
public client_disconnect(id){
if(g_FileHandler[id] != 0){
fclose(g_FileHandler[id]);
}
}
public menu_show(id){
g_MenuOpen[id] = 1;
new menu[128],filename[128]
new size[10]
new playpause[3],stop[3],record[19],choose[3];
get_filename(id,g_SaveSlot[id],filename)
if(file_exists(filename)){
formatex(size,9,"%dKb",file_size(filename)/1024)
formatex(playpause,2,"\y")
}else{
formatex(size,9,"FREE")
formatex(playpause,2,"\d")
}
if(g_isRecording[id]){
formatex(playpause,2,"\d")
formatex(record,18,"\y Stop Recording")
}else{
formatex(record,18,"\y Start Recording")
}
if(g_isRecording[id] == 0 && g_FileHandler[id] != 0){
formatex(stop,2,"\y")
formatex(record,18,"\d Start Recording")
}else{
formatex(stop,2,"\d")
}
if(g_FileHandler[id] != 0){
formatex(choose,2,"\d")
}else{
formatex(choose,2,"\y")
}
format(menu,127,"Active Slot: \r%d \d(%s)^n\r1.%s Choose Slot^n\r2.%s^n\r3.%s Play/Pause^n\r4.%s Stop^n\r^n^n0. \wEXIT",g_SaveSlot[id],size,choose,record,playpause,stop)
show_menu(id,RECORDKEYS,menu, -1, "Demo Recorder")
}
public choose_slot(id){
new menu[256]
format(menu,255,"\yChoose Slot");
for( new i = 0 ; i < MAXSLOTS ; i++){
static filename[128]
get_filename(id,i,filename)
if(file_exists(filename)){
format(menu,255,"%s^n\r%d. \w SLOT %d (%dKb)",menu,i+1,i,file_size(filename)/1024)
}else{
format(menu,255,"%s^n\r%d. \w SLOT %d (FREE)",menu,i+1,i)
}
}
show_menu(id,SLOTKEYS,menu, -1, "Slot Chooser")
}
public slothandler(id,key){
if(key < MAXSLOTS){
g_SaveSlot[id] = key
menu_show(id)
}else{
choose_slot(id)
}
}
public menuhandler(id,key){
switch (key) {
case 9: {
g_MenuOpen[id] = 0
return PLUGIN_CONTINUE
}
case 0: {
if(g_FileHandler[id] == 0){
choose_slot(id);
}else{
menu_show(id)
}
return PLUGIN_CONTINUE;
}
case 1: {
if(g_isRecording[id] == 1){
stop_record(id);
}else if(g_FileHandler[id] == 0){
start_record(id);
}
menu_show(id)
}
case 2: {
if(g_isRecording[id] == 0 && g_FileHandler[id] == 0){
start_replay(id);
}else if(g_isRecording[id] == 0){
if(g_isPause[id]){
g_isPause[id] = 0
client_print(id,print_chat,"[Recorder] Playback resumed")
}else{
g_isPause[id] = 1
client_print(id,print_chat,"[Recorder] Playback paused")
}
}
menu_show(id)
}
case 3: {
if(g_isRecording[id] == 0 && g_FileHandler[id] != 0){
client_print(id,print_chat,"[Recorder] Stopping playback")
g_isPause[id] = 0;
g_FileHandler[id] = 0;
}
menu_show(id)
}
}
return PLUGIN_HANDLED
}
public fm_ghost_think(id){
static szClassname[13]
pev(id,pev_classname, szClassname,12)
if( !equal(szClassname, "ghost_player") )
return FMRES_IGNORED;
static Float:ago,Float:origin[3],Float:angles[3],Float:veloc[3];
static owner,data,sequence,gaitsequence
owner = pev(id,pev_owner);
if(!is_user_connected(owner)) return FMRES_IGNORED;
if(g_isPause[owner] == 1){
set_pev(id,pev_nextthink,get_gametime()+0.5)
return FMRES_IGNORED
}
fread(g_FileHandler[owner],_:angles[0],BLOCK_INT)
fread(g_FileHandler[owner],_:angles[1],BLOCK_INT)
fread(g_FileHandler[owner],_:angles[2],BLOCK_INT)
fread(g_FileHandler[owner],_:origin[0],BLOCK_INT)
fread(g_FileHandler[owner],_:origin[1],BLOCK_INT)
fread(g_FileHandler[owner],_:origin[2],BLOCK_INT)
fread(g_FileHandler[owner],_:veloc[0],BLOCK_INT)
fread(g_FileHandler[owner],_:veloc[1],BLOCK_INT)
fread(g_FileHandler[owner],_:veloc[2],BLOCK_INT)
fread(g_FileHandler[owner],sequence,BLOCK_INT)
fread(g_FileHandler[owner],gaitsequence,BLOCK_INT)
data = fread(g_FileHandler[owner],_:ago,BLOCK_INT)
if(data != 1){
client_print(owner,print_chat,"[Recorder] Playback Finished")
if(g_FileHandler[owner] != 0)fclose(g_FileHandler[owner])
g_FileHandler[owner] = 0
if(g_MenuOpen[owner]){
menu_show(owner)
}
fm_remove_entity(id);
return FMRES_IGNORED;
}
set_pev(id,pev_nextthink,get_gametime()+ago)
set_pev(id,pev_origin,origin)
set_pev(id,pev_angles,angles)
set_pev(id,pev_velocity,veloc)
set_pev(id,pev_gaitsequence,sequence)
set_pev(id,pev_sequence,gaitsequence)
return FMRES_HANDLED;
}
public start_replay(id){
log_amx("starting replay")
if(g_FileHandler[id] != 0){
client_print(id,print_chat,"[Recorder] Can't open File, maybe you are still recording")
return PLUGIN_CONTINUE;
}
new fileName[128]
get_filename(id,g_SaveSlot[id],fileName)
log_amx(fileName);
g_FileHandler[id] = fopen(fileName,"rb");
if(g_FileHandler[id] == 0){
client_print(id,print_chat,"[Recorder] Couldn't open file mybe none existent")
}else{
client_print(id,print_chat,"[Recorder] Playback started")
fnCreateGhost(id)
new dummy
fread(g_FileHandler[id],dummy,BLOCK_INT)
}
return PLUGIN_HANDLED
}
public fm_plr_prethink(id){
if(is_user_alive(id) && g_isRecording[id] != 0){
static Float:ago,Float:origin[3],Float:angles[3],Float:veloc[3];
static sequence,gaitsequence
ago = get_gametime()-g_LastThink[id]
if(ago < RECLIMITER) return FMRES_IGNORED
pev(id,pev_origin,origin)
pev(id,pev_velocity,veloc)
pev(id,pev_angles,angles)
sequence = pev(id,pev_sequence)
gaitsequence = pev(id,pev_gaitsequence)
if((g_LastAngle[id][0] == angles[0] && g_LastAngle[id][1] == angles[1] && g_LastAngle[id][2] == angles[2] ) && (g_LastOrigin[id][0] == origin[0] && g_LastOrigin[id][1] == origin[1] && g_LastOrigin[id][2] == origin[2] ) && (g_LastVelocity[id][0] == veloc[0] && g_LastVelocity[id][1] == veloc[1] && g_LastVelocity[id][2] == veloc[2])) return FMRES_IGNORED
g_LastAngle[id][0] = angles[0]
g_LastAngle[id][1] = angles[1]
g_LastAngle[id][2] = angles[2]
g_LastOrigin[id][0] = origin[0]
g_LastOrigin[id][1] = origin[1]
g_LastOrigin[id][2] = origin[2]
g_LastVelocity[id][0] = veloc[0]
g_LastVelocity[id][1] = veloc[1]
g_LastVelocity[id][2] = veloc[2]
g_LastThink[id] = get_gametime()
fwrite(g_FileHandler[id],_:ago,BLOCK_INT)
fwrite(g_FileHandler[id],_:angles[0],BLOCK_INT)
fwrite(g_FileHandler[id],_:angles[1],BLOCK_INT)
fwrite(g_FileHandler[id],_:angles[2],BLOCK_INT)
fwrite(g_FileHandler[id],_:origin[0],BLOCK_INT)
fwrite(g_FileHandler[id],_:origin[1],BLOCK_INT)
fwrite(g_FileHandler[id],_:origin[2],BLOCK_INT)
fwrite(g_FileHandler[id],_:veloc[0],BLOCK_INT)
fwrite(g_FileHandler[id],_:veloc[1],BLOCK_INT)
fwrite(g_FileHandler[id],_:veloc[2],BLOCK_INT)
fwrite(g_FileHandler[id],sequence,BLOCK_INT)
fwrite(g_FileHandler[id],gaitsequence,BLOCK_INT)
return FMRES_HANDLED
}
return FMRES_IGNORED
}
public start_record(id){
new fileName[128]
get_filename(id,g_SaveSlot[id],fileName)
log_amx(fileName)
g_FileHandler[id] = fopen(fileName,"wb");
g_isRecording[id] = 1;
client_print(id,print_chat,"[Recorder] Recording started")
}
public stop_record(id){
g_isRecording[id] = 0
fclose(g_FileHandler[id])
g_FileHandler[id] = 0
client_print(id,print_chat,"[Recorder] Recording stoped")
}
public get_filename(id,slot,fileName[]){
new mapname[33],steamid[35]
get_mapname(mapname,32)
get_user_authid(id,steamid,34)
replace_all(steamid,34,":","_")
format(fileName,127,"%s%s_%d_%s",SAVEPATH,steamid,slot,mapname)
}
public fnCreateGhost( iOwner ) {
new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"))
//make sure entity was created successfully
if (pev_valid(ent)) {
dllfunc(DLLFunc_Spawn,ent)
engfunc(EngFunc_SetModel,ent,"models/player/vip/vip.mdl")
set_pev(ent, pev_classname, "ghost_player")
set_pev(ent, pev_solid, SOLID_NOT)
set_pev(ent,pev_movetype,MOVETYPE_PUSHSTEP)
set_pev(ent, pev_owner, iOwner)
set_pev(ent,pev_animtime, 2.0)
set_pev(ent,pev_framerate, 1.0)
set_pev(ent,pev_flags, FL_MONSTER)
set_pev(ent,pev_controller_0, 125)
set_pev(ent,pev_controller_1, 125)
set_pev(ent,pev_controller_2, 125)
set_pev(ent,pev_controller_3, 125)
fm_set_rendering(ent, kRenderFxGlowShell, 255, 255, 255, kRenderTransAlpha, 150)
set_pev(ent, pev_nextthink, get_gametime() + 0.1)
}else{
client_print(iOwner,print_chat,"[Recorder] Ghost couldn't be created")
fclose(g_FileHandler[iOwner])
}
}