PDA

View Full Version : Why are define macro function thingies bad?


Chdata
10-20-2014, 16:43
I have yet for someone to give me a good explanation or example of why the following is bad:

#define DOWHILE_ENTFOUND(%1,%2) %1 = -1; while ((%1 = FindEntityByClassname2(%1, %2)) != -1)

#define IsValidClient(%1) (0 < %1 && %1 <= MaxClients && IsClientInGame(%1) && !GetEntProp(%1, Prop_Send, "m_bIsCoaching"))

#define IsReplayClient(%1) (IsClientSourceTV(%1) || IsClientReplay(%1))

#define RemoveCond(%1,%2) if (TF2_IsPlayerInCondition(%1, %2)) TF2_RemoveCondition(%1, %2)
#define InsertCond(%1,%2,%3) if (!TF2_IsPlayerInCondition(%1, %2)) TF2_AddCondition(%1, %2, %3)

#define IsMediUber(%1) TF2_IsPlayerInCondition(%1, TFCond_Ubercharged)
#define IsVacUber(%1) (TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(%1, TFCond_UberFireResist))
#define IsQuickUber(%1) TF2_IsPlayerInCondition(%1, TFCond_MegaHeal)
#define IsAnyUber(%1) (IsMediUber(%1) || IsVacUber(%1) || IsQuickUber(%1))

#define IsPlayerOwner(%1) GetAdminFlag(GetUserAdmin(%1), Admin_Root)
#define IsPlayerAdmin(%1) GetAdminFlag(GetUserAdmin(%1), Admin_Slay)

As opposed to

stock FindSomeEnts()
{
decl ent;

ent = -1;
while (ent = FindEntityByClassname2(ent, "func_breakable") != -1)
{
//thing
}

ent = -1;
while (ent = FindEntityByClassname2(ent, "obj_dispenser") != -1)
{
//thingy
}
}

stock bool:IsValidClient(client, bool:replaycheck = true)
{
if (client <= 0 || client > MaxClients) return false;
if (!IsClientInGame(client)) return false;
if (GetEntProp(client, Prop_Send, "m_bIsCoaching")) return false;
if (replaycheck)
{
if (IsClientSourceTV(client) || IsClientReplay(client)) return false;
}
return true;
}

stock RemoveCond(client, cond)
{
if (TF2_IsPlayerInCondition(client, cond)) // I'm also curious, is it even necessary to check if they're in the condition before removing it?
{
TF2_RemoveCondition(client, cond); // Can I just call this even if they're not in the condition?
}
}

So far all I've heard is that "I just shouldn't" and the discussion from this thread: https://forums.alliedmods.net/showthread.php?p=2175576#post2175576

Which I guess makes me wary of doing var++ inside of one of these functions but doesn't apply to how I use that dowhile in every single case I use it... so is the dowhile still bad?

I kinda prefer inliney macros, especially for things like that FindEntity loop that I use very commonly in the same exact way that's less of a pain to type out with the macro.

And afaik shouldn't macros be faster (I know caring about micro-optimizations is pointless but I have OCD for it because I used to work a lot with assembly with lots of limits on how many cycles you want to be using up) than pushing a return and whatnot.

The only other thing I've heard is that... I guess the defines would be harder to read and comment? And I guess when I have stuff like IsVacUber that are really long, readability is also lost. But is that it? And is it really a big deal in the programming world?

Edit: Just read this: https://wiki.alliedmods.net/Introduction_to_sourcepawn#decl

Note: decl should not be used on single cell variables. There is almost never any benefit.

Why isn't there? Does it mean to say 'the benefit is so extremely negligible so who cares' or 'It's literally detrimental in some cases'.

Additionally, does making something 'static' actually change performance in any way? I have a friend who's all OCD about putting things in their own files and making everything statics, and I'm not sure if that's only to avoid making global variables or if static does anything else besides making it local scope only.

Leonardo
10-20-2014, 17:18
I think it just a bit slows down the compilation time, nothing else.

I don't see defined functions very useful tho.
Also, 0 < %1 <= MaxClients instead of 0 < %1 && %1 <= MaxClients.

Powerlord
10-20-2014, 17:22
A few reasons:


Macros are inserted literally everywhere you use them. This adds to the overall size of the compiled function. While function calls do add some overhead, they do so while only being present in the compiled file once.
Macros are a lot less readable than functions are. They also don't get syntax highlighted in most editors.


Decl:

As it says, there's no benefit to decling non-array values. Not only that, but doing so on certain types (such as Handles) may end up with unexpected behavior. Remember: decl means the initial value of your value is whatever was in memory before it got allocated. The only time you should use decl is large arrays/strings where you're going to immediately populate it. Incidentally, decl is likely going away in a future SourceMod version, so you really should be using new instead.

Static:

To my knowledge, static doesn't change performance in any way if you do it at the file level... it's solely a visibility operator.

At the function level, making a variable static has the side effect of making it never leave memory until your plugin is unloaded. This is because function-level static variables are actually global variables with the visibility locked to your function.

Chdata
10-20-2014, 23:28
I think it just a bit slows down the compilation time, nothing else.

I don't see defined functions very useful tho.
Also, 0 < %1 <= MaxClients instead of 0 < %1 && %1 <= MaxClients.

0 < i <= MaxClients

true <= MaxClients

??

So does the overall size of a plugin have an impact on RAM? I don't think whole plugin files are loaded into RAM or something, are they? o.o (Not that I'm trying to argue for macros being better, this is just me being curious).

I already knew it inserts it everywhere which I figured was fine because ttttterabytes of space.

If decl is removed, does that mean

new String:string[256] = "as"; // will not initiate everything beyond what was initiated just like that wiki link says a decl'd string with initiation does right now?

ô.o?

xerox8521
10-22-2014, 09:18
decl is likely going away in a future SourceMod version

Why that ? I mean it would reduce certain private plugins performance if you have to use new instead of decl when using big strings

Altough this is more or less SA-MP specific http://forum.sa-mp.com/showthread.php?t=166680

rswallen
10-22-2014, 09:21
Future SM is also getting proper strings (as opposed to the char arrays currently in use)

friagram
10-22-2014, 09:59
Dunno how sp works, but decl is supposed to be faster, since you don't initialize the data.
Static is supposed to be faster as well, since it is pre allocated and initilized, and their address is available directly.. When using other variables you will have to be grabbing them off of the stack, putting it into a register... which is slower.

splewis
10-22-2014, 16:25
So does the overall size of a plugin have an impact on RAM? I don't think whole plugin files are loaded into RAM or something, are they? o.o (Not that I'm trying to argue for macros being better, this is just me being curious).


I'm hardly an expert, but I believe plugins are executed entirely out of memory. This is why you can delete a plugin from the plugins/ directory and it keeps working until it gets unloaded from memory on the map change.

Powerlord
10-22-2014, 17:10
I'm hardly an expert, but I believe plugins are executed entirely out of memory. This is why you can delete a plugin from the plugins/ directory and it keeps working until it gets unloaded from memory on the map change.

I'm not an expert either, but I can tell you that's how programs traditionally work... for instance, on UNIX systems use the fork-exec (http://en.wikipedia.org/wiki/Fork-exec) model, with initial processes forking from the init/systemd process. The Exec family of system calls replace the currently running program with one it loads into memory.

While it could be different for plugins, it's easier to load the entire thing into memory if it's not using resource forks or the like.

Chdata
10-22-2014, 21:53
Alright I was confused because I figure programs do work that way, but I was introduced to programming via SNES assembly where all of the code is read straight from ROM.

And of course in that case we also kept track of variables by looking at individual RAM address like $0000 or $1EF9 or $7F0050 etc.

Leonardo
10-23-2014, 04:34
0 < i <= MaxClients

true <= MaxClients

??

not ( 0 < i ) <= MaxClients, it checks if value is in between the given values.

wait, just tested:
new a = 0, b = 1, c = 2, d = 4, e = 8;
if( a < b < c < d < e )
PrintToServer( "hi" );
else
PrintToServer( "bye" );
and it'll work. that's weird.

Chdata
11-25-2014, 09:30
Okay so I've resolved to using macros for things I just wanna type faster or shorten like...

#define PLYR MAXPLAYERS+1 // lol hi nergal
#define PATHX PLATFORM_MAX_PATH

#define IsPlayerOwner(%1) GetAdminFlag(GetUserAdmin(%1), Admin_Root)
#define IsPlayerAdmin(%1) GetAdminFlag(GetUserAdmin(%1), Admin_Slay)

But using functions for things that look more 'lengthy' to reduce file size, like so:

#define IsMediUber(%1) TF2_IsPlayerInCondition(%1, TFCond_Ubercharged)
#define IsQuickUber(%1) TF2_IsPlayerInCondition(%1, TFCond_MegaHeal)

#define IsBlastUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist)
#define IsBulletUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist)
#define IsFireUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberFireResist)

#define IsVacUbered(%1) (IsBlastUber(%1) || IsBulletUber(%1) || IsFireUber(%1))
#define IsUbered(%1) (IsMediUber(%1) || IsQuickUber(%1) || IsVacUber(%1))

stock bool:IsVacUber(iClient)
{
return IsVacUbered(iClient);
}

stock bool:IsAnyUber(iClient)
{
return IsUbered(iClient);
}

Not length as in 'boy TF2_IsPlayerInCondition is a lot of characters' but rather 'let's imprint just 1 function instead of 3 everywhere.

Powerlord
11-25-2014, 10:14
Okay so I've resolved to using macros for things I just wanna type faster or shorten like...

#define PLYR MAXPLAYERS+1 // lol hi nergal
#define PATHX PLATFORM_MAX_PATH

#define IsPlayerOwner(%1) GetAdminFlag(GetUserAdmin(%1), Admin_Root)
#define IsPlayerAdmin(%1) GetAdminFlag(GetUserAdmin(%1), Admin_Slay)[/php]

But using functions for things that look more 'lengthy' to reduce file size, like so:

#define IsMediUber(%1) TF2_IsPlayerInCondition(%1, TFCond_Ubercharged)
#define IsQuickUber(%1) TF2_IsPlayerInCondition(%1, TFCond_MegaHeal)

#define IsBlastUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist)
#define IsBulletUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist)
#define IsFireUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberFireResist)

#define IsVacUbered(%1) (IsBlastUber(%1) || IsBulletUber(%1) || IsFireUber(%1))
#define IsUbered(%1) (IsMediUber(%1) || IsQuickUber(%1) || IsVacUber(%1))

stock bool:IsVacUber(iClient)
{
return IsVacUbered(iClient);
}

stock bool:IsAnyUber(iClient)
{
return IsUbered(iClient);
}

Not length as in 'boy TF2_IsPlayerInCondition is a lot of characters' but rather 'let's imprint just 1 function instead of 3 everywhere.

Pretty sure I mentioned this already, but define are processed by the compiler's preprocessor. In other words, the literal values of whatever you're doing in the defines replace the usage of the DEFINE.

As an example, this code:

new String:path[PATHX];
becomes
new String:path[255];
during the preprocessor phase (255 being the current value of PLATFORM_MAX_PATH).

Likewise,
stock bool:IsAnyUber(iClient)
{
return IsUbered(iClient);
}

becomes

stock bool:IsAnyUber(iClient)
{
return (TF2_IsPlayerInCondition(%1, TFCond_Ubercharged) || TF2_IsPlayerInCondition(%1, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(%1, TFCond_UberFireResist)));
}
(iirc stock is resolved after the preprocessor)

In other words, you reduce the source file size while doing nothing to the size of the compiled plugin (or slightly increasing it) while at the same time increasing compile time (due to multiple preprocessor passes) and making the code harder to read in general (I'm not sure I'd call it obfuscation).

Incidentally, if you're only using a macro once, you're actually increasing the size of the source file too. As a side note, I'm not sure about Pawn, but in C this won't work: IsUbered(GetClientOfUserId(userid)) because you can't use functions as an argument to a macro.

Macros biggest advantage are that they're faster than function calls.

Chdata
11-25-2014, 11:13
In other words, you reduce the source file size while doing nothing to the size of the compiled plugin (or slightly increasing it) while at the same time increasing compile time (due to multiple preprocessor passes) and making the code harder to read in general (I'm not sure I'd call it obfuscation).

I don't undrastand.


stock bool:IsAnyUber(iClient)
{
return (TF2_IsPlayerInCondition(%1, TFCond_Ubercharged) || TF2_IsPlayerInCondition(%1, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(%1, TFCond_UberFireResist)));
}

public Action:tSomeTimer(Handle:hTimer, UserId)
{
new iClient = GetClientOfUserId(UserId);
if (IsValidClient(iClient) && IsAnyUber(iClient)){}
}

func2(iClient)
{
if (IsAnyUber(iClient)){}
}

OnRaged(iClient)
{
if (IsAnyUber(iClient)){return;}
}

func4(iClient)
{
if (IsAnyUber(iClient)){}
}

func5(iClient) // etc
{
if (IsAnyUber(iClient)){}
}

// Compared to

// More source file size
// More compiled file size

public Action:tSomeTimer(Handle:hTimer, UserId)
{
new iClient = GetClientOfUserId(UserId);
if (IsValidClient(iClient) && (TF2_IsPlayerInCondition(iClient, TFCond_Ubercharged) || TF2_IsPlayerInCondition(iClient, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(iClient, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberFireResist)))){}
}

func2(iClient)
{
if ((TF2_IsPlayerInCondition(iClient, TFCond_Ubercharged) || TF2_IsPlayerInCondition(iClient, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(iClient, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberFireResist)))){}
}

OnRaged(iClient)
{
if ((TF2_IsPlayerInCondition(iClient, TFCond_Ubercharged) || TF2_IsPlayerInCondition(iClient, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(iClient, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberFireResist)))){return;}
}

func4(iClient)
{
if ((TF2_IsPlayerInCondition(iClient, TFCond_Ubercharged) || TF2_IsPlayerInCondition(iClient, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(iClient, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberFireResist)))){}
}

func5(iClient) // etc
{
if ((TF2_IsPlayerInCondition(iClient, TFCond_Ubercharged) || TF2_IsPlayerInCondition(iClient, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(iClient, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberFireResist)))){}
}

Also... compile time is a problem? You compile once, but isn't runtime what matters?

Also...

Maybe comparing IsAnyUber to

stock bool:IsValidClient(iClient)
{
return (0 < iClient && iClient <= MaxClients
&& IsClientInGame(iClient)
&& !IsClientReplay(iClient)
&& !IsClientSourceTV(iClient)
&& !GetEntProp(iClient, Prop_Send, "m_bIsCoaching")
);
}

you lose readability, but for

#define IsBlastUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist)
#define IsBulletUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist)
#define IsFireUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberFireResist)
#define IsPlayerOwner(%1) GetAdminFlag(GetUserAdmin(%1), Admin_Root)

?

Also I'm pretty sure in pawn, %1 gets replaced with the entirety of whatever's put there.

If it's %1,%2,%3 it explodes based on the commas.

These are all macros I use that seem to work just fine.

rswallen
11-25-2014, 14:23
You seem to under the impression that "stock" is synonymous to "inline" of C++ (on compile, replace function prototype with body). It is not.
When you declare a function "stock", it tells the compiler to only include the function if it is used (no replacing of anything).

Good:

stock bool:IsAnyUber(iClient)
{
return (TF2_IsPlayerInCondition(%1, TFCond_Ubercharged) || TF2_IsPlayerInCondition(%1, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(%1, TFCond_UberFireResist)));
}

func2(iClient)
{
if (IsAnyUber(iClient)){}
}

OnRaged(iClient)
{
if (IsAnyUber(iClient)){return;}
}Bad:

func2(iClient)
{
if ((TF2_IsPlayerInCondition(iClient, TFCond_Ubercharged) || TF2_IsPlayerInCondition(iClient, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(iClient, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberFireResist)))){}
}

OnRaged(iClient)
{
if ((TF2_IsPlayerInCondition(iClient, TFCond_Ubercharged) || TF2_IsPlayerInCondition(iClient, TFCond_MegaHeal) || (TF2_IsPlayerInCondition(iClient, TFCond_UberBlastResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberBulletResist) || TF2_IsPlayerInCondition(iClient, TFCond_UberFireResist)))){return;}
}
Pointless:

#define IsVacUbered(%1) (IsBlastUber(%1) || IsBulletUber(%1) || IsFireUber(%1))
#define IsUbered(%1) (IsMediUber(%1) || IsQuickUber(%1) || IsVacUber(%1))

stock bool:IsVacUber(iClient)
{
return IsVacUbered(iClient);
}

stock bool:IsAnyUber(iClient)
{
return IsUbered(iClient);
}


As for that IsValidClient stock, you should consider splitting it into two separate stocks:

stock bool:IsValidClient(client)
{
if ((0 < client) && (client <= MaxClients))
{
return IsClientInGame(client);
}
return false;
}

stock IsValidBoss(client)
{
if (IsValidClient(client))
{
if (IsClientReplay(client) || IsClientSourceTV(client))
{
return false;
}
return !GetEntProp(iClient, Prop_Send, "m_bIsCoaching");
}
return false;
}

Reasoning:


Easier to read without significantly hampering execution time
Fits description better (coaching clients are valid clients, just not valid boss clients) [VSH]
Optimised (We shouldn't care if a client is coaching when they are opening a menu [and replay clients don't call custom commands]) [VSH]

So many people use bloated IsValidClient stocks, when it makes more sense to make a second (if not third) client validation function that is more selective.

Chdata
11-25-2014, 17:41
Yeah, I was talking to flaminsarge about that and considering putting the replaychecks in its own thing.

How are coaching clients valid clients? Aren't coachers like spectators?

ddhoward
11-25-2014, 17:42
Aren't coachers like spectators?
No.

BAILOPAN
11-25-2014, 17:43
FYI: Macro-functions are slated for removal. Probably in SM 1.9. In 1.8 will give you a deprecation warning (I figured 1.7 has enough syntax changes as it is.)

Basically #define is a nightmare since it can materially change the token stream of the source file, and that greatly complicates the parsing process. When I tried to implement a cleaner pipeline for this, probably 2,000 lines of code were ultimately dedicated to handling token-expansion.

It makes it harder for any tools that want to do source-level analysis or rewriting. It makes backtraces weird. It's easy to run into bugs where tokens don't expand in the way you'd expect (for example, passing anything with side effects into a macro is very risky since the macro could expand its input twice). Stylistically, they're a little gross as others have pointed out.

And macros don't create linkage. For example, if plugin A uses a #define for its API, and plugin B is compiled against it, plugin B must be recompiled if plugin A fixes a bug. Functions create linkage, avoiding this problem.

Macros do allow primitive, albeit often unsafe generic behavior - and they can very slightly reduce overhead that would be introduced by a call frame or condition check. But the benefit is so small for all the complexity and problems they introduce. I'd rather find other solutions to the problems they solve.

Chdata
11-25-2014, 17:45
No.

You sure?

https://www.youtube.com/watch?v=73dwKjPvK7w

You seem to under the impression that "stock" is synonymous to "inline" of C++ (on compile, replace function prototype with body). It is not.
When you declare a function "stock", it tells the compiler to only include the function if it is used (no replacing of anything).

Nope. I understood that about stocks already. Not sure how you came up with me having that impression.

friagram
11-25-2014, 21:18
Don't think I've ever had need to make a macro in sp.

ddhoward
11-25-2014, 21:21
You sure?
I guess I was mistaken. I recall seeing a coach running around with his coached player, but I think that may have been part of an exploit that has since been patched.

friagram
11-25-2014, 21:33
Why would you ever need to check if a player is coaching? It doesn't matter.
Likewise, why do you need to check if something is replay or sourcetv? 99.99% of the time it doesn't matter, because important things will filter them out anyways.. Getclientteam, isplayerralive, any hooked event....

Checking for that crap is almost as bad as checking the MaxClients+1 - MaxPlayers range against clients.

Leonardo
11-25-2014, 23:49
Likewise, why do you need to check if something is replay or sourcetv?

some games/mods/plugins spawns players instanly on join (e.x.: No More Room in Hell)

friagram
11-26-2014, 06:02
some games/mods/plugins spawns players instanly on join (e.x.: No More Room in Hell)

Then they will be alive and team will be > 1
Ok.

Chdata
11-26-2014, 08:17
Yeah, I was about to ask what the properties of replay clients are.

Can replay be index 19 in a 18-19 slot server? Or is it always 33? Is MaxClients 33 in a 33 slot server that's 33 just for replay?

Can replay even be spawned as a class?

Not to mention SCTV bots or players or w/e sourcetv clients are.

Powerlord
11-26-2014, 10:16
replay and SourceTV usually steal the first player slot, but its possible for them to have higher slot numbers, particularly if you toggled them off and back on or kicked them at some point.

Once a client has a particular client index, afaik they keep said index until they leave the server (i.e. it stays the same across map changes).

Incidentally, using -replay from the command-line will add an additional slot, but not show it in the server browser.

Chdata
11-26-2014, 12:31
Ah, so that's why we only care to check for replay during the FindNextHale routine, right?

Since that's where VSH determines who to /spawn/ as the next boss.

And hale_select, of course.

So basically... we only need to check for that during there?

Also meanwhile I changed the vacuber thing

#define IsMediUber(%1) TF2_IsPlayerInCondition(%1, TFCond_Ubercharged)
#define IsQuickUber(%1) TF2_IsPlayerInCondition(%1, TFCond_MegaHeal)

#define IsBlastUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBlastResist)
#define IsBulletUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberBulletResist)
#define IsFireUber(%1) TF2_IsPlayerInCondition(%1, TFCond_UberFireResist)

stock bool:IsVacUber(iClient)
{
return (IsBlastUber(iClient) || IsBulletUber(iClient) || IsFireUber(iClient));
}

stock bool:IsAnyUber(iClient)
{
return (IsMediUber(iClient) || IsQuickUber(iClient) || IsVacUber(iClient));
}

Since this feature is being removed, will a perhaps better alternative be given? Like actual inline functions or something. I just really like making aliases for stuff (easier to ctrl f or ctrl d sometimes).

friagram
11-26-2014, 12:51
Iirc replay doesn't even work for vsh/ff2 servers, all the replays are corrupted.
And sourcetv is just.. Ugh.

Chdata
11-26-2014, 12:57
Nope. Replay works.

http://youtu.be/bK2xnEPb7Ls?t=1m31s

friagram
11-26-2014, 13:00
Ah, too bad it still lags/stalls the servers.

Chdata
11-26-2014, 13:02
Yeah lol. And a lot of the replays do fail anyway.

So basically... if replay is being spawned... the server has other issues it needs to deal with.

I'm gonna try playing around with coaching some time.