lots of changes
This commit is contained in:
@@ -0,0 +1,599 @@
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Leader3 effects: xray shell, overhead sprite, beacon rings, +use view tracer.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
void Effects_Resume(int client)
|
||||
{
|
||||
if (!IsEffectsActive(client))
|
||||
return;
|
||||
|
||||
if (g_iEffectFlags[client] & EFFECT_XRAY)
|
||||
Xray_Create(client);
|
||||
|
||||
if (g_iSpriteChoice[client] != SPRITE_NONE)
|
||||
Sprite_Create(client);
|
||||
|
||||
if ((g_iEffectFlags[client] & EFFECT_BEACON) && g_iBeaconSerial[client] == 0)
|
||||
Beacon_Start(client);
|
||||
}
|
||||
|
||||
void Effects_Suspend(int client)
|
||||
{
|
||||
Xray_Kill(client);
|
||||
Sprite_Kill(client);
|
||||
Beacon_Stop(client);
|
||||
g_iEffectFlags[client] &= ~EFFECT_TRACER_TOGGLED;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Xray shell: prop_dynamic_override with the shipped xray model ($additive + $ignorez material,
|
||||
// so it shows through walls), bonemerged onto the player and tinted with the tier colour.
|
||||
// SetTransmit hides it from zombies, from the leader themselves, and from first-person spectators
|
||||
// of the leader.
|
||||
//
|
||||
// $ignorez only helps once the client has the entity; CheckTransmit culls edicts against the
|
||||
// viewer's PVS first, so the shell (and the leader) vanished whenever the viewer was in another
|
||||
// vis area - thick walls, other side of the map. Fix: strip the shell's edict state down to
|
||||
// FL_EDICT_FULLCHECK so the engine calls ShouldTransmit() every frame, and DHook that to return
|
||||
// FL_EDICT_ALWAYS. The engine then calls SetTransmit(pInfo, true), which marks the shell AND its
|
||||
// move parent (the leader) for sending regardless of PVS, and still runs Hook_XrayShellTransmit,
|
||||
// so zombies keep seeing nothing.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Xray_SetupForceTransmit()
|
||||
{
|
||||
// SDKHooks' gamedata keeps the SetTransmit vtable index current; ShouldTransmit sits two
|
||||
// slots before it (ShouldTransmit, UpdateTransmitState, SetTransmit in baseentity.h).
|
||||
Handle gc = LoadGameConfigFile("sdkhooks.games/engine.ep2v");
|
||||
if (gc == null)
|
||||
{
|
||||
LogError("Could not load sdkhooks.games/engine.ep2v; leader xray stays PVS-limited.");
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = GameConfGetOffset(gc, "SetTransmit");
|
||||
delete gc;
|
||||
if (offset == -1)
|
||||
{
|
||||
LogError("No SetTransmit offset in sdkhooks.games/engine.ep2v; leader xray stays PVS-limited.");
|
||||
return;
|
||||
}
|
||||
|
||||
g_hXrayShouldTransmit = DHookCreate(offset - 2, HookType_Entity, ReturnType_Int, ThisPointer_Ignore, Hook_XrayShouldTransmit);
|
||||
DHookAddParam(g_hXrayShouldTransmit, HookParamType_ObjectPtr); // const CCheckTransmitInfo *
|
||||
}
|
||||
|
||||
public MRESReturn Hook_XrayShouldTransmit(Handle hReturn, Handle hParams)
|
||||
{
|
||||
DHookSetReturn(hReturn, FL_EDICT_ALWAYS);
|
||||
return MRES_Supercede;
|
||||
}
|
||||
|
||||
void Xray_Create(int client)
|
||||
{
|
||||
Xray_Kill(client);
|
||||
|
||||
if (!IsEffectsActive(client))
|
||||
return;
|
||||
|
||||
int ent = CreateEntityByName("prop_dynamic_override");
|
||||
if (ent == -1)
|
||||
return;
|
||||
|
||||
DispatchKeyValue(ent, "model", XRAY_MODEL);
|
||||
DispatchKeyValue(ent, "solid", "0");
|
||||
DispatchKeyValue(ent, "spawnflags", "256");
|
||||
DispatchKeyValue(ent, "disableshadows", "1");
|
||||
DispatchKeyValue(ent, "disablereceiveshadows", "1");
|
||||
if (!DispatchSpawn(ent))
|
||||
{
|
||||
AcceptEntityInput(ent, "Kill");
|
||||
return;
|
||||
}
|
||||
SetEntProp(ent, Prop_Send, "m_CollisionGroup", 11); // COLLISION_GROUP_WEAPON: never collides with players
|
||||
|
||||
int colour[4];
|
||||
GetLeaderColour(client, colour);
|
||||
SetEntityRenderMode(ent, RENDER_TRANSADD);
|
||||
SetEntityRenderColor(ent, colour[0], colour[1], colour[2], g_cvXrayAlpha.IntValue);
|
||||
|
||||
// CBaseEntity::FollowEntity() recipe. The client only bone-merges when the entity has
|
||||
// EF_BONEMERGE, a move parent AND MOVETYPE_NONE (C_BaseEntity::IsFollowingEntity) --
|
||||
// prop_dynamic is MOVETYPE_PUSH and SetParent doesn't change that, hence t-posing shells.
|
||||
float origin[3], angles[3];
|
||||
GetClientAbsOrigin(client, origin);
|
||||
GetClientAbsAngles(client, angles);
|
||||
TeleportEntity(ent, origin, angles, NULL_VECTOR);
|
||||
|
||||
SetVariantString("!activator");
|
||||
AcceptEntityInput(ent, "SetParent", client, ent);
|
||||
SetEntityMoveType(ent, MOVETYPE_NONE);
|
||||
SetEntProp(ent, Prop_Send, "m_fEffects",
|
||||
EF_BONEMERGE|EF_BONEMERGE_FASTCULL|EF_NOSHADOW|EF_NORECEIVESHADOW|EF_PARENT_ANIMATES);
|
||||
|
||||
SetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity", client);
|
||||
SDKHook(ent, SDKHook_SetTransmit, Hook_XrayShellTransmit);
|
||||
|
||||
if (g_hXrayShouldTransmit != null)
|
||||
{
|
||||
DHookEntity(g_hXrayShouldTransmit, false, ent);
|
||||
// FULLCHECK is the absence of the other transmit-state flags.
|
||||
SetEdictFlags(ent, GetEdictFlags(ent) & ~(FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_DONTSEND));
|
||||
}
|
||||
|
||||
g_iXrayRef[client] = EntIndexToEntRef(ent);
|
||||
}
|
||||
|
||||
public Action Hook_XrayShellTransmit(int ent, int client)
|
||||
{
|
||||
// Anything that recomputes transmit state (DispatchUpdateTransmitState) puts PVSCHECK
|
||||
// back on the edict; strip it so Hook_XrayShouldTransmit resumes overriding PVS culling.
|
||||
if (g_hXrayShouldTransmit != null)
|
||||
{
|
||||
int flags = GetEdictFlags(ent);
|
||||
if (flags & (FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK))
|
||||
SetEdictFlags(ent, flags & ~(FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK));
|
||||
}
|
||||
|
||||
int owner = GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity");
|
||||
if (owner == client)
|
||||
return Plugin_Handled;
|
||||
|
||||
// Humans only: never reveal the leader's through-wall xray to zombies.
|
||||
if (IsLivingZombie(client))
|
||||
return Plugin_Handled;
|
||||
|
||||
// First-person spectators of the owner would get the shell in their face too.
|
||||
if (!IsPlayerAlive(client) && owner != -1
|
||||
&& GetEntProp(client, Prop_Send, "m_iObserverMode") == 4
|
||||
&& GetEntPropEnt(client, Prop_Send, "m_hObserverTarget") == owner)
|
||||
return Plugin_Handled;
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
void Xray_Kill(int client)
|
||||
{
|
||||
int ent = EntRefToEntIndex(g_iXrayRef[client]);
|
||||
g_iXrayRef[client] = INVALID_ENT_REFERENCE;
|
||||
|
||||
if (ent == INVALID_ENT_REFERENCE || !IsValidEdict(ent))
|
||||
return;
|
||||
|
||||
char classname[64];
|
||||
GetEdictClassname(ent, classname, sizeof(classname));
|
||||
if (StrContains(classname, "prop_dynamic") == 0)
|
||||
AcceptEntityInput(ent, "Kill");
|
||||
}
|
||||
|
||||
// Re-create the xray with the right tier colour (used when primary changes).
|
||||
void Effects_RefreshXray(int client)
|
||||
{
|
||||
if ((g_iEffectFlags[client] & EFFECT_XRAY) && IsEffectsActive(client))
|
||||
Xray_Create(client);
|
||||
}
|
||||
|
||||
void Xray_Toggle(int client)
|
||||
{
|
||||
g_iEffectFlags[client] ^= EFFECT_XRAY;
|
||||
if (g_iEffectFlags[client] & EFFECT_XRAY)
|
||||
{
|
||||
Xray_Create(client);
|
||||
PrintToChat(client, "%sXray enabled.", PREFIX);
|
||||
}
|
||||
else
|
||||
{
|
||||
Xray_Kill(client);
|
||||
PrintToChat(client, "%sXray disabled.", PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Overhead sprite: 'Defend here' / 'Follow me'
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Sprite_Set(int client, int choice)
|
||||
{
|
||||
g_iSpriteChoice[client] = choice;
|
||||
Sprite_Kill(client);
|
||||
|
||||
if (choice == SPRITE_NONE)
|
||||
{
|
||||
PrintToChat(client, "%sSprite removed.", PREFIX);
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite_Create(client);
|
||||
PrintToChat(client, "%sSprite changed to '%s'.", PREFIX, choice == SPRITE_CHOICE_DEFEND ? "Defend Here" : "Follow Me");
|
||||
}
|
||||
|
||||
void Sprite_Create(int client)
|
||||
{
|
||||
Sprite_Kill(client);
|
||||
|
||||
if (!IsEffectsActive(client) || g_iSpriteChoice[client] == SPRITE_NONE)
|
||||
return;
|
||||
|
||||
char mat[PLATFORM_MAX_PATH];
|
||||
strcopy(mat, sizeof(mat), g_iSpriteChoice[client] == SPRITE_CHOICE_DEFEND ? SPRITE_DEFEND : SPRITE_FOLLOW);
|
||||
|
||||
g_iSpriteRef[client] = AttachSpriteEnt(client, mat, "0.1", "1", "255 255 255", 45.0);
|
||||
}
|
||||
|
||||
void Sprite_Kill(int client)
|
||||
{
|
||||
KillSpriteRef(g_iSpriteRef[client]);
|
||||
g_iSpriteRef[client] = INVALID_ENT_REFERENCE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// env_sprite attach for the overhead sprite (Leader2's targetname-rename parenting trick).
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
int AttachSpriteEnt(int client, const char[] material, const char[] scale, const char[] rendermode, const char[] colour, float zOffset)
|
||||
{
|
||||
if (!IsPlayerAlive(client))
|
||||
return INVALID_ENT_REFERENCE;
|
||||
|
||||
char targetname[16], oldTargetname[64];
|
||||
GetEntPropString(client, Prop_Data, "m_iName", oldTargetname, sizeof(oldTargetname));
|
||||
Format(targetname, sizeof(targetname), "Client%d", client);
|
||||
DispatchKeyValue(client, "targetname", targetname);
|
||||
|
||||
float origin[3];
|
||||
GetClientEyePosition(client, origin);
|
||||
origin[2] += zOffset;
|
||||
|
||||
int ent = CreateEntityByName("env_sprite");
|
||||
if (ent == -1)
|
||||
{
|
||||
DispatchKeyValue(client, "targetname", oldTargetname);
|
||||
return INVALID_ENT_REFERENCE;
|
||||
}
|
||||
|
||||
DispatchKeyValue(ent, "model", material);
|
||||
DispatchKeyValue(ent, "classname", "env_sprite");
|
||||
DispatchKeyValue(ent, "spawnflags", "1");
|
||||
DispatchKeyValue(ent, "scale", scale);
|
||||
DispatchKeyValue(ent, "rendermode", rendermode);
|
||||
DispatchKeyValue(ent, "rendercolor", colour);
|
||||
DispatchSpawn(ent);
|
||||
TeleportEntity(ent, origin, NULL_VECTOR, NULL_VECTOR);
|
||||
SetVariantString(targetname);
|
||||
AcceptEntityInput(ent, "SetParent", ent, ent, 0);
|
||||
|
||||
DispatchKeyValue(client, "targetname", oldTargetname);
|
||||
|
||||
return EntIndexToEntRef(ent);
|
||||
}
|
||||
|
||||
void KillSpriteRef(int ref)
|
||||
{
|
||||
if (ref == INVALID_ENT_REFERENCE)
|
||||
return;
|
||||
|
||||
int ent = EntRefToEntIndex(ref);
|
||||
if (ent == INVALID_ENT_REFERENCE || !IsValidEdict(ent))
|
||||
return;
|
||||
|
||||
char classname[64];
|
||||
GetEdictClassname(ent, classname, sizeof(classname));
|
||||
if (strcmp(classname, "env_sprite") == 0)
|
||||
AcceptEntityInput(ent, "Kill");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Beacon (serial-guarded repeating timer, Leader2/Couch ring pattern)
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Beacon_Toggle(int client)
|
||||
{
|
||||
g_iEffectFlags[client] ^= EFFECT_BEACON;
|
||||
if (g_iEffectFlags[client] & EFFECT_BEACON)
|
||||
{
|
||||
if (IsEffectsActive(client))
|
||||
Beacon_Start(client);
|
||||
PrintToChat(client, "%sBeacon enabled.", PREFIX);
|
||||
LogAction(client, client, "\"%L\" set a leader beacon on himself", client);
|
||||
}
|
||||
else
|
||||
{
|
||||
Beacon_Stop(client);
|
||||
PrintToChat(client, "%sBeacon disabled.", PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
void Beacon_Start(int client)
|
||||
{
|
||||
g_iBeaconSerial[client] = ++g_iSerialGen;
|
||||
CreateTimer(g_cvBeaconInterval.FloatValue, Timer_Beacon, client | (g_iSerialGen << 7), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
|
||||
}
|
||||
|
||||
void Beacon_Stop(int client)
|
||||
{
|
||||
g_iBeaconSerial[client] = 0;
|
||||
}
|
||||
|
||||
public Action Timer_Beacon(Handle timer, any value)
|
||||
{
|
||||
int client = value & 0x7f;
|
||||
int serial = value >> 7;
|
||||
|
||||
if (g_iBeaconSerial[client] != serial || !IsClientInGame(client) || !IsPlayerAlive(client))
|
||||
{
|
||||
if (g_iBeaconSerial[client] == serial)
|
||||
g_iBeaconSerial[client] = 0;
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
float vec[3];
|
||||
GetClientAbsOrigin(client, vec);
|
||||
vec[2] += 10.0;
|
||||
|
||||
float radius = g_cvBeaconRadius.FloatValue;
|
||||
|
||||
int tierColour[4];
|
||||
GetLeaderColour(client, tierColour);
|
||||
TE_SetupBeamRingPoint(vec, 10.0, radius, g_iBeamSprite, g_iHaloSprite, 0, 15, 0.5, 5.0, 0.0, tierColour, 10, 0);
|
||||
TE_SendToAll();
|
||||
|
||||
int rainbowColour[4];
|
||||
float gameTime = GetGameTime();
|
||||
rainbowColour[0] = RoundFloat(Sine(2.5 * gameTime + 0.0) * 127.0 + 128.0);
|
||||
rainbowColour[1] = RoundFloat(Sine(2.5 * gameTime + 2.0943951) * 127.0 + 128.0);
|
||||
rainbowColour[2] = RoundFloat(Sine(2.5 * gameTime + 4.1887902) * 127.0 + 128.0);
|
||||
rainbowColour[3] = 255;
|
||||
|
||||
TE_SetupBeamRingPoint(vec, 10.0, radius, g_iBeamSprite, g_iHaloSprite, 0, 10, 0.6, 10.0, 0.5, rainbowColour, 10, 0);
|
||||
TE_SendToAll();
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
void GetLeaderColour(int client, int colour[4])
|
||||
{
|
||||
if (g_bHasCustomColour[client])
|
||||
{
|
||||
colour[0] = g_iCustomColour[client][0];
|
||||
colour[1] = g_iCustomColour[client][1];
|
||||
colour[2] = g_iCustomColour[client][2];
|
||||
}
|
||||
else if (IsPrimaryLeader(client))
|
||||
{
|
||||
colour[0] = g_iXrayColourPrimary[0];
|
||||
colour[1] = g_iXrayColourPrimary[1];
|
||||
colour[2] = g_iXrayColourPrimary[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
colour[0] = g_iXrayColourSub[0];
|
||||
colour[1] = g_iXrayColourSub[1];
|
||||
colour[2] = g_iXrayColourSub[2];
|
||||
}
|
||||
colour[3] = 255;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Custom leader colour: !leadercolour <name|RRGGBB|default>. Applies to xray, beacon and tracer.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
static const char g_sColourNames[][] = {
|
||||
"red", "orange", "yellow", "lime", "green", "cyan",
|
||||
"blue", "purple", "magenta", "pink", "white"
|
||||
};
|
||||
static const int g_iColourValues[][3] = {
|
||||
{255, 0, 0}, {255, 128, 0}, {255, 255, 0}, {128, 255, 0}, {0, 255, 0}, {0, 255, 255},
|
||||
{0, 100, 255}, {160, 0, 255}, {255, 0, 255}, {255, 105, 180}, {255, 255, 255}
|
||||
};
|
||||
|
||||
public Action Cmd_LeaderColour(int client, int args)
|
||||
{
|
||||
if (client == 0)
|
||||
{
|
||||
ReplyToCommand(client, "%s This command can only be used in-game.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (!IsLeader(client))
|
||||
{
|
||||
PrintToChat(client, "%sOnly leaders can set a leader colour.", PREFIX);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (args == 0)
|
||||
{
|
||||
LeaderColour_PrintUsage(client);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
char arg[32];
|
||||
GetCmdArg(1, arg, sizeof(arg));
|
||||
TrimString(arg);
|
||||
|
||||
if (StrEqual(arg, "default", false) || StrEqual(arg, "reset", false))
|
||||
{
|
||||
g_bHasCustomColour[client] = false;
|
||||
LeaderColour_SaveCookie(client);
|
||||
Effects_RefreshXray(client);
|
||||
PrintToChat(client, "%sColour reset to the default %s colour.", PREFIX,
|
||||
IsPrimaryLeader(client) ? "primary leader" : "sub-leader");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
int rgb[3];
|
||||
if (!ParseCustomColour(arg, rgb))
|
||||
{
|
||||
LeaderColour_PrintUsage(client);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
// Refuse colours too dark to see as a xray tint or beam.
|
||||
if (rgb[0] < 40 && rgb[1] < 40 && rgb[2] < 40)
|
||||
{
|
||||
PrintToChat(client, "%sThat colour is too dark to be visible. Pick a brighter one.", PREFIX);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
g_iCustomColour[client][0] = rgb[0];
|
||||
g_iCustomColour[client][1] = rgb[1];
|
||||
g_iCustomColour[client][2] = rgb[2];
|
||||
g_bHasCustomColour[client] = true;
|
||||
LeaderColour_SaveCookie(client);
|
||||
Effects_RefreshXray(client);
|
||||
|
||||
PrintToChat(client, "%sColour set to \x07%02X%02X%02X#%02X%02X%02X\x01.", PREFIX,
|
||||
rgb[0], rgb[1], rgb[2], rgb[0], rgb[1], rgb[2]);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public void OnClientCookiesCached(int client)
|
||||
{
|
||||
if (!IsFakeClient(client))
|
||||
LeaderColour_LoadCookie(client);
|
||||
}
|
||||
|
||||
void LeaderColour_LoadCookie(int client)
|
||||
{
|
||||
char buf[16];
|
||||
g_hColourCookie.Get(client, buf, sizeof(buf));
|
||||
|
||||
int rgb[3];
|
||||
if (buf[0] && ParseCustomColour(buf, rgb))
|
||||
{
|
||||
g_iCustomColour[client][0] = rgb[0];
|
||||
g_iCustomColour[client][1] = rgb[1];
|
||||
g_iCustomColour[client][2] = rgb[2];
|
||||
g_bHasCustomColour[client] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void LeaderColour_SaveCookie(int client)
|
||||
{
|
||||
if (g_bHasCustomColour[client])
|
||||
{
|
||||
char buf[8];
|
||||
Format(buf, sizeof(buf), "%02X%02X%02X",
|
||||
g_iCustomColour[client][0], g_iCustomColour[client][1], g_iCustomColour[client][2]);
|
||||
g_hColourCookie.Set(client, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_hColourCookie.Set(client, "");
|
||||
}
|
||||
}
|
||||
|
||||
void LeaderColour_PrintUsage(int client)
|
||||
{
|
||||
char names[256];
|
||||
for (int i = 0; i < sizeof(g_sColourNames); i++)
|
||||
{
|
||||
if (names[0])
|
||||
StrCat(names, sizeof(names), ", ");
|
||||
StrCat(names, sizeof(names), g_sColourNames[i]);
|
||||
}
|
||||
PrintToChat(client, "%sUsage: \x04!leadercolour <colour|hex|default>\x01", PREFIX);
|
||||
PrintToChat(client, "%sColours: \x04%s\x01, or a hex code like \x04FF66CC\x01.", PREFIX, names);
|
||||
}
|
||||
|
||||
bool ParseCustomColour(const char[] str, int rgb[3])
|
||||
{
|
||||
// Colour name
|
||||
for (int i = 0; i < sizeof(g_sColourNames); i++)
|
||||
{
|
||||
if (StrEqual(str, g_sColourNames[i], false))
|
||||
{
|
||||
rgb[0] = g_iColourValues[i][0];
|
||||
rgb[1] = g_iColourValues[i][1];
|
||||
rgb[2] = g_iColourValues[i][2];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Hex code: RRGGBB, optionally prefixed with # or 0x
|
||||
int offset = 0;
|
||||
if (str[0] == '#')
|
||||
offset = 1;
|
||||
else if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
|
||||
offset = 2;
|
||||
|
||||
if (strlen(str[offset]) != 6)
|
||||
return false;
|
||||
|
||||
int value;
|
||||
if (StringToIntEx(str[offset], value, 16) != 6)
|
||||
return false;
|
||||
|
||||
rgb[0] = (value >> 16) & 0xFF;
|
||||
rgb[1] = (value >> 8) & 0xFF;
|
||||
rgb[2] = value & 0xFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// View tracer: hold +use to show, tap +use to toggle. Visual only; buttons are never modified.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Effects_OnPlayerRunCmd(int client, int buttons)
|
||||
{
|
||||
bool pressed = (buttons & IN_USE) && !(g_iLastButtons[client] & IN_USE);
|
||||
bool released = !(buttons & IN_USE) && (g_iLastButtons[client] & IN_USE);
|
||||
g_iLastButtons[client] = buttons;
|
||||
|
||||
if (!IsEffectsActive(client) || g_iTracerMode[client] == TRACER_MODE_OFF)
|
||||
return;
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
g_fUsePressedAt[client] = GetGameTime();
|
||||
}
|
||||
else if (released && g_iTracerMode[client] == TRACER_MODE_TAP
|
||||
&& GetGameTime() - g_fUsePressedAt[client] < g_cvTracerTapTime.FloatValue)
|
||||
{
|
||||
g_iEffectFlags[client] ^= EFFECT_TRACER_TOGGLED;
|
||||
}
|
||||
|
||||
bool active = (buttons & IN_USE) != 0 || (g_iEffectFlags[client] & EFFECT_TRACER_TOGGLED) != 0;
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
float now = GetGameTime();
|
||||
if (now < g_fNextTracerSend[client])
|
||||
return;
|
||||
g_fNextTracerSend[client] = now + g_cvTracerInterval.FloatValue;
|
||||
|
||||
Tracer_Draw(client);
|
||||
}
|
||||
|
||||
void Tracer_Draw(int client)
|
||||
{
|
||||
float endPos[3];
|
||||
if (!TracePlayerAngles(client, endPos))
|
||||
return;
|
||||
|
||||
float startPos[3];
|
||||
GetClientEyePosition(client, startPos);
|
||||
startPos[2] -= 5.0; // keep the beam out of the leader's own crosshair
|
||||
|
||||
int colour[4];
|
||||
GetLeaderColour(client, colour);
|
||||
|
||||
TE_SetupBeamPoints(startPos, endPos, g_iBeamSprite, g_iHaloSprite, 0, 0,
|
||||
g_cvTracerLife.FloatValue, g_cvTracerWidth.FloatValue, g_cvTracerWidth.FloatValue,
|
||||
0, 0.0, colour, 0);
|
||||
TE_SendToAll();
|
||||
}
|
||||
|
||||
void Tracer_CycleMode(int client)
|
||||
{
|
||||
// Rotate Hold + Tap-toggle -> Hold only -> Off -> Hold + Tap-toggle.
|
||||
switch (g_iTracerMode[client])
|
||||
{
|
||||
case TRACER_MODE_TAP: g_iTracerMode[client] = TRACER_MODE_HOLD;
|
||||
case TRACER_MODE_HOLD: g_iTracerMode[client] = TRACER_MODE_OFF;
|
||||
default: g_iTracerMode[client] = TRACER_MODE_TAP;
|
||||
}
|
||||
g_iEffectFlags[client] &= ~EFFECT_TRACER_TOGGLED;
|
||||
|
||||
char mode[40];
|
||||
switch (g_iTracerMode[client])
|
||||
{
|
||||
case TRACER_MODE_TAP: mode = "hold +USE OR tap to toggle";
|
||||
case TRACER_MODE_HOLD: mode = "hold +USE only";
|
||||
default: mode = "off";
|
||||
}
|
||||
PrintToChat(client, "%sTracer mode: \x04%s\x01.", PREFIX, mode);
|
||||
}
|
||||
Reference in New Issue
Block a user