#include <amxmodx>
#include <amxmisc>
#include <fakemeta>
#include <fakemeta_util>
#define ADMIN_LADDER ADMIN_MENU
#define USE_TASK
#define LADDER_DELAY 0.2
enum (+= 1000)
{
TASK_ID_MOVE = 1000,
TASK_ID_CHECK
};
enum _:Angles
{
ANGLE_VERT1,
ANGLE_VERT2,
ANGLE_HORIZ
};
new const LADDER_CLASS[] = "fake_ladder";
new const LADDER_MODEL[] = "models/ladder.mdl";
#define IsFakeLadder(%1) fm_is_ent_classname(%1, LADDER_CLASS)
new const Float:LADDER_MINS[Angles][3] =
{
{ -4.0, -32.0, -32.0},
{-32.0, -4.0, -32.0},
{-32.0, -32.0, -4.0}
};
new const Float:LADDER_MAXS[Angles][3] =
{
{ 4.0, 32.0, 32.0},
{ 32.0, 4.0, 32.0},
{ 32.0, 32.0, 4.0}
};
new const Float:LADDER_ANGLES[Angles][3] =
{
{ 90.0, 0.0, 0.0},
{ 90.0, 0.0, 90.0},
{ 0.0, 0.0, 0.0}
};
new g_ladder_file[64];
new bool:g_snapping[33];
new Float:g_move_dist[33];
new Float:g_move_offset[33][3];
new g_ladder_ent[33];
new info_target;
public plugin_precache()
{
precache_model(LADDER_MODEL);
}
public plugin_init()
{
register_plugin("Ladder Maker", "0.1", "Exolent");
register_clcmd("say /ladder", "CmdLadder");
register_clcmd("+move_ladder", "CmdStartMove");
register_clcmd("-move_ladder", "CmdStopMove");
register_forward(FM_Touch, "FwdTouch");
register_forward(FM_PlayerPreThink, "FwdPlayerPreThink");
info_target = engfunc(EngFunc_AllocString, "info_target");
get_datadir(g_ladder_file, sizeof(g_ladder_file) - 1);
add(g_ladder_file, sizeof(g_ladder_file) - 1, "/laddermaker");
if( !dir_exists(g_ladder_file) )
{
mkdir(g_ladder_file);
}
new mapname[64];
get_mapname(mapname, sizeof(mapname) - 1);
strtolower(mapname);
format(g_ladder_file, sizeof(g_ladder_file) - 1, "%s/%s.txt", g_ladder_file, mapname);
Load(false);
}
public client_connect(client)
{
g_snapping[client] = true;
}
public client_disconnect(client)
{
remove_task(client + TASK_ID_MOVE);
}
public CmdLadder(client)
{
ShowLadderMenu(client);
return PLUGIN_HANDLED;
}
public CmdStartMove(client)
{
if( !access(client, ADMIN_LADDER) ) return PLUGIN_HANDLED;
new taskid = client + TASK_ID_MOVE;
if( task_exists(taskid) ) return PLUGIN_HANDLED;
new ent, body;
g_move_dist[client] = get_user_aiming(client, ent, body);
if( IsFakeLadder(ent) )
{
new Float:aim_origin[3];
fm_get_aim_origin(client, aim_origin);
pev(ent, pev_origin, g_move_offset[client]);
xs_vec_sub(g_move_offset[client], aim_origin, g_move_offset[client]);
new params[2];
params[0] = ent;
set_task(0.1, "TaskMoveLadder", taskid, params, 2, "b");
}
return PLUGIN_HANDLED;
}
public CmdStopMove(client)
{
if( !access(client, ADMIN_LADDER) ) return PLUGIN_HANDLED;
remove_task(client + TASK_ID_MOVE);
return PLUGIN_HANDLED;
}
public FwdTouch(ent, client)
{
if( !IsFakeLadder(ent)
|| !is_user_alive(client) ) return;
g_ladder_ent[client] = ent;
#if defined USE_TASK
remove_task(client + TASK_ID_CHECK);
set_task(LADDER_DELAY, "TaskRemoveLadder", client + TASK_ID_CHECK);
#endif
}
public FwdPlayerPreThink(client)
{
if( !pev_valid(g_ladder_ent[client]) ) return;
set_pev(client, pev_movetype, MOVETYPE_FLY);
static Float:maxspeed;
pev(client, pev_maxspeed, maxspeed);
if( pev(client, pev_flags) & FL_DUCKING )
{
// I don't know the real calculation for ducking velocity
// but this will have to do for now
maxspeed /= 2.0;
}
static Float:aim_velocity[3];
velocity_by_aim(client, floatround(maxspeed), aim_velocity);
static Float:client_origin[3], Float:stop[3];
pev(client, pev_origin, client_origin);
// find the side the ladder is in relation to the client
// and remove the velocity in the direction away/towards the ladder
static side;
for( new i = 0; i < 6; i++ )
{
side = i / 2;
stop[0] = client_origin[0];
stop[1] = client_origin[1];
stop[2] = client_origin[2];
stop[side] += (i % 2 == 0) ? 50.0 : -50.0;
engfunc(EngFunc_TraceLine, client_origin, stop, 0, client, 0);
if( get_tr2(0, TR_pHit) == g_ladder_ent[client] )
{
aim_velocity[side] = 0.0;
side = i;
break;
}
}
if( (side / 2) == 2 )
{
new Float:aim_origin[3];
fm_get_aim_origin(client, aim_origin);
new Float:head_origin[3];
engfunc(EngFunc_GetBonePosition, client, 8, head_origin, stop); // stop isnt used, just a place holder for the function
xs_vec_sub(aim_origin, head_origin, aim_velocity);
aim_velocity[2] = 0.0;
xs_vec_normalize(aim_velocity, aim_velocity);
xs_vec_mul_scalar(aim_velocity, maxspeed, aim_velocity);
}
new button = pev(client, pev_button);
new Float:velocity[3];
if( button & IN_JUMP )
{
new dim = side / 2;
if( dim == 2 ) // jumping up or down
{
velocity[0] = 0.0;
velocity[1] = 0.0;
velocity[2] = (side % 2) == 0 ? -maxspeed : maxspeed;
}
else
{
velocity[dim] = (side % 2 == 0) ? -maxspeed : maxspeed;
velocity[(dim + 1) % 2] = 0.0;
velocity[2] = 0.0;
}
}
else
{
if( button & IN_FORWARD )
{
velocity[0] += aim_velocity[0];
velocity[1] += aim_velocity[1];
velocity[2] += aim_velocity[2];
}
if( button & IN_BACK )
{
velocity[0] -= aim_velocity[0];
velocity[1] -= aim_velocity[1];
velocity[2] -= aim_velocity[2];
}
if( button & IN_MOVERIGHT )
{
// angle velocity_aim -90 degrees
//velocity[0] += ((floatcos(-90.0, degrees) * velocity_aim[0]) - (floatsin(-90.0, degrees) * velocity_aim[1]));
// cos 90 = 0 sin -90 = -1
// (( 0 * velocity_aim[0] ) - ( -1 * velocity_aim[1] ))
// velocity_aim[1]
velocity[0] += aim_velocity[1];
//velocity[1] += ((floatsin(-90.0, degrees) * velocity_aim[0]) - (floatcos(-90.0, degrees) * velocity_aim[1]));
// sin -90 = -1 cos -90 = 0
// (( -1 * velocity_aim[0] ) - ( 0 * velocity_aim[1] ))
// -velocity_aim[0]
velocity[1] -= aim_velocity[0];
}
if( button & IN_MOVELEFT )
{
// angle velocity_aim 90 degrees
//velocity[0] += ((floatcos(90.0, degrees) * velocity_aim[0]) - (floatsin(90.0, degrees) * velocity_aim[1]));
// cos 90 = 0 sin 90 = 1
// (( 0 * velocity_aim[0] ) - ( 1 * velocity_aim[1] ))
// -velocity_aim[1]
velocity[0] -= aim_velocity[1];
//velocity[1] += ((floatsin(90.0, degrees) * velocity_aim[0]) - (floatcos(90.0, degrees) * velocity_aim[1]));
// sin 90 = 1 cos 90 = 0
// (( 1 * velocity_aim[0] ) - ( 0 * velocity_aim[1] ))
// velocity_aim[0]
velocity[1] += aim_velocity[0];
}
if( (side / 2) == 2 )
{
velocity[2] = 0.0;
}
}
set_pev(client, pev_velocity, velocity);
#if !defined USE_TASK
g_ladder_ent[client] = 0;
#endif
}
#if defined USE_TASK
public TaskRemoveLadder(client)
{
g_ladder_ent[client - TASK_ID_CHECK] = 0;
}
#endif
public TaskMoveLadder(params[], taskid)
{
new ent = params[0];
if( !pev_valid(ent) )
{
remove_task(taskid);
return;
}
new client = taskid - TASK_ID_MOVE;
new Float:head_origin[3], Float:aim_origin[3];
engfunc(EngFunc_GetBonePosition, client, 8, head_origin, aim_origin);
fm_get_aim_origin(client, aim_origin);
new Float:ent_origin[3];
xs_vec_sub(aim_origin, head_origin, ent_origin);
xs_vec_normalize(ent_origin, ent_origin);
xs_vec_mul_scalar(ent_origin, g_move_dist[client], ent_origin);
xs_vec_add(ent_origin, head_origin, ent_origin);
xs_vec_add(ent_origin, g_move_offset[client], ent_origin);
ent_origin[2] = float(floatround(ent_origin[2], floatround_floor));
if( g_snapping[client] )
{
new Float:start[3], Float:end[3], Float:ret[3];
new Float:mins[3], Float:maxs[3];
pev(ent, pev_mins, mins);
pev(ent, pev_maxs, maxs);
new Float:dist, Float:closestDist = 9999.9;
new trace, closestEnt;
new side;
for( new i = 0; i < 6; i++ )
{
xs_vec_copy(ent_origin, start);
start[i / 2] += ((i % 2) == 0) ? mins[i / 2] : maxs[i / 2];
xs_vec_copy(start, end);
end[i / 2] += ((i % 2) == 0) ? -10 : 10;
trace = fm_trace_line(ent, start, end, ret);
if( IsFakeLadder(trace) )
{
dist = get_distance_f(start, ret);
if( dist < closestDist )
{
closestDist = dist;
closestEnt = trace;
side = i;
}
}
}
if( closestEnt )
{
pev(closestEnt, pev_origin, ent_origin);
pev(closestEnt, pev_mins, start);
pev(closestEnt, pev_maxs, end);
ent_origin[side / 2] += ((side % 2) == 0) ? (end[side / 2] + maxs[side / 2]) : (start[side / 2] + mins[side / 2]);
}
}
engfunc(EngFunc_SetOrigin, ent, ent_origin);
SetRotation(ent, GetLadderAngle(ent));
}
ShowLadderMenu(client)
{
new menu = menu_create("Ladder Menu", "MenuLadder");
menu_additem(menu, "Létra készítése", "1", ADMIN_LADDER);
menu_additem(menu, "Létra törlése", "2", ADMIN_LADDER);
menu_additem(menu, "Létra forgatása^n", "3", ADMIN_LADDER);
menu_additem(menu, "Mozgatás", "4", ADMIN_LADDER);
menu_additem(menu, "Mentés", "5", ADMIN_LADDER);
menu_display(client, menu);
}
public MenuLadder(client, menu, item)
{
if( item == MENU_EXIT )
{
menu_destroy(menu);
return;
}
new _access, info[2], callback;
menu_item_getinfo(menu, item, _access, info, sizeof(info) - 1, _, _, callback);
menu_destroy(menu);
switch( str_to_num(info) )
{
case 1:
{
static Float:origin[3];
fm_get_aim_origin(client, origin);
if( !CreateLadder(origin) )
{
client_print(client, print_chat, "[Ladder] A létra elemet nem sikerült létrehozni.");
}
}
case 2:
{
new ent = fm_get_aim_ent(client);
if( ent && IsFakeLadder(ent) )
{
engfunc(EngFunc_RemoveEntity, ent);
client_print(client, print_chat, "[Ladder] Létra elem törölve.");
}
else
{
client_print(client, print_chat, "[Ladder] Nem sikerült megtalálni a létra elemet.");
}
}
case 3:
{
new ent = fm_get_aim_ent(client);
if( ent && IsFakeLadder(ent) )
{
SetRotation(ent, (GetLadderAngle(ent) + 1) % Angles);
}
else
{
client_print(client, print_chat, "[Ladder] Nem sikerült megtalálni a létra elemet.");
}
}
case 4:
{
ShowMoveMenu(client);
return;
}
case 5:
{
ShowSaveMenu(client);
return;
}
}
ShowLadderMenu(client);
}
ShowMoveMenu(client)
{
new menu = menu_create("Mozgatás menü", "MenuMove");
menu_additem(menu, g_snapping[client] ? "Megfogás: \yBe^n" : "Megfogás: \rKi^n", "1", ADMIN_LADDER);
menu_additem(menu, "Mozgatás X+", "2", ADMIN_LADDER);
menu_additem(menu, "Mozgatás X-", "3", ADMIN_LADDER);
menu_additem(menu, "Mozgatás Y+", "4", ADMIN_LADDER);
menu_additem(menu, "Mozgatás Y-", "5", ADMIN_LADDER);
menu_additem(menu, "Mozgatás Z+", "6", ADMIN_LADDER);
menu_additem(menu, "Mozgatás Z-", "7", ADMIN_LADDER);
menu_setprop(menu, MPROP_EXITNAME, "Létra Menü");
menu_display(client, menu);
}
public MenuMove(client, menu, item)
{
if( item == MENU_EXIT )
{
menu_destroy(menu);
ShowLadderMenu(client);
return;
}
new _access, info[2], callback;
menu_item_getinfo(menu, item, _access, info, sizeof(info) - 1, _, _, callback);
menu_destroy(menu);
new num = str_to_num(info);
if( num == 1 )
{
g_snapping[client] = !g_snapping[client];
}
else
{
num -= 2;
new ent = fm_get_aim_ent(client);
if( ent && IsFakeLadder(ent) )
{
new Float:origin[3];
pev(ent, pev_origin, origin);
origin[num / 2] += ((num % 2) == 0) ? 1 : -1;
set_pev(ent, pev_origin, origin);
}
}
ShowMoveMenu(client);
}
ShowSaveMenu(client)
{
new menu = menu_create("Save Menu", "MenuSave");
menu_additem(menu, "Minden létra mentése", "1", ADMIN_LADDER);
menu_additem(menu, "Minden létra betöltése", "2", ADMIN_LADDER);
menu_additem(menu, "Minden létra törlése", "3", ADMIN_LADDER);
menu_setprop(menu, MPROP_EXITNAME, "Létra menü");
menu_display(client, menu);
}
public MenuSave(client, menu, item)
{
if( item == MENU_EXIT )
{
menu_destroy(menu);
ShowLadderMenu(client);
return;
}
new _access, info[2], callback;
menu_item_getinfo(menu, item, _access, info, sizeof(info) - 1, _, _, callback);
menu_destroy(menu);
switch( str_to_num(info) )
{
case 1:
{
Save();
}
case 2:
{
Load(true);
}
case 3:
{
new ent = -1;
while( (ent = engfunc(EngFunc_FindEntityByString, ent, "classname", LADDER_CLASS)) )
{
engfunc(EngFunc_RemoveEntity, ent);
}
new name[32];
get_user_name(client, name, sizeof(name) - 1);
client_print(0, print_chat, "[Ladder] %s törölte az összes létra elemet.", name);
}
}
ShowSaveMenu(client);
}
CreateLadder(Float:origin[3])
{
static const DEFAULT_ANGLE = ANGLE_VERT1;
new ent = engfunc(EngFunc_CreateNamedEntity, info_target);
if( !pev_valid(ent) ) return 0;
set_pev(ent, pev_classname, LADDER_CLASS);
set_pev(ent, pev_solid, SOLID_BBOX);
set_pev(ent, pev_movetype, MOVETYPE_NONE);
engfunc(EngFunc_SetModel, ent, LADDER_MODEL);
SetRotation(ent, DEFAULT_ANGLE);
engfunc(EngFunc_SetOrigin, ent, origin);
return ent;
}
SetRotation(ent, angle)
{
set_pev(ent, pev_angles, LADDER_ANGLES[angle]);
engfunc(EngFunc_SetSize, ent, LADDER_MINS[angle], LADDER_MAXS[angle]);
}
GetLadderAngle(ent)
{
new Float:angles[3];
pev(ent, pev_angles, angles);
for( new angle = 0; angle < Angles; angle++ )
{
if( xs_vec_equal(LADDER_ANGLES[angle], angles) )
{
return angle;
}
}
return 0; // should never happen, but use 0 so "index out of bounds" doesnt occur
}
Load(bool:deleteall)
{
if( deleteall )
{
new ent = -1;
while( (ent = engfunc(EngFunc_FindEntityByString, ent, "classname", LADDER_CLASS)) )
{
engfunc(EngFunc_RemoveEntity, ent);
}
}
if( !file_exists(g_ladder_file) ) return -1;
new count;
new f = fopen(g_ladder_file, "rt");
new data[64];
new origin[3][16];
new Float:_origin[3];
new angle[2];
new ent;
while( !feof(f) )
{
fgets(f, data, sizeof(data) - 1);
if( !data[0] ) continue;
parse(data,\
origin[0], sizeof(origin[]) - 1,\
origin[1], sizeof(origin[]) - 1,\
origin[2], sizeof(origin[]) - 1,\
angle, sizeof(angle) - 1
);
for( new i = 0; i < 3; i++ )
{
_origin[i] = str_to_float(origin[i]);
}
ent = CreateLadder(_origin);
if( ent )
{
SetRotation(ent, str_to_num(angle));
count++;
}
}
fclose(f);
return count;
}
Save()
{
new f = fopen(g_ladder_file, "wt");
new Float:origin[3];
new ent = -1;
while( (ent = engfunc(EngFunc_FindEntityByString, ent, "classname", LADDER_CLASS)) )
{
pev(ent, pev_origin, origin);
fprintf(f, "%f %f %f %i^n",\
origin[0],\
origin[1],\
origin[2],\
GetLadderAngle(ent)
);
}
fclose(f);
}
// modified fm_get_aim_origin
stock fm_get_aim_ent(index) {
new Float:start[3], Float:view_ofs[3]
pev(index, pev_origin, start)
pev(index, pev_view_ofs, view_ofs)
xs_vec_add(start, view_ofs, start)
new Float:dest[3]
pev(index, pev_v_angle, dest)
engfunc(EngFunc_MakeVectors, dest)
global_get(glb_v_forward, dest)
xs_vec_mul_scalar(dest, 9999.0, dest)
xs_vec_add(start, dest, dest)
engfunc(EngFunc_TraceLine, start, dest, 0, index, 0)
new ent = get_tr2(0, TR_pHit);
return pev_valid(ent) ? ent : 0;
}