Add message natives
The goal of this PR is to make the usage of message_begin functions a lot easier, especially the SVC_TEMPENTITY ones.
Instead of having to do all of this just to create a single beam:
new iStartPos[3], iEndPos[3];
get_user_origin(id, iStartPos);
get_user_origin(id, iEndPos, 3);
message_begin(MSG_ALL, SVC_TEMPENTITY);
write_byte(TE_BEAMPOINTS);
write_coord(iStartPos[0]);
write_coord(iStartPos[1]);
write_coord(iStartPos[2]);
write_coord(iEndPos[0]);
write_coord(iEndPos[1]);
write_coord(iEndPos[2]);
write_short(g_iSprite);
write_byte(0);
write_byte(30);
write_byte(10);
write_byte(10);
write_byte(0);
write_byte(0);
write_byte(0);
write_byte(255);
write_byte(75);
write_byte(0);
message_end();
With using the new natives, we can do it as simple as this:
new iStartPos[3], iEndPos[3];
get_user_origin(id, iStartPos);
get_user_origin(id, iEndPos, 3);
te_create_beam_between_points(iStartPos, iEndPos, g_iSprite)
This saves a lot of time that you would spend searching for the write_ arguments, experimenting with the numbers and having to write all 20 arguments 1 by 1 and having a huge chance of messing some of them up.
Only the mandatory arguments in the natives are required (e.g. starting position, sprite index, etc) and all other ones have a default value and are optional.
All of the natives share 2 same arguments - receiver and bool:reliable - both optional and the second one is always the last argument in the natives. The first one controls who will see the message and the second one controls whether or not to use the reliable channel. With this being said, if receiver is set to 0 and reliable is true, the native will use MSG_ALL as the message destination.
I also spent some time creating a video that previews all of the te_ functions to make it easier for users to understand how they work and what they look like - https://youtu.be/szW-bSMPuyQ
In addition, I found some mistakes while testing in the message_const.inc file that I fixed, e.g. the TE_DLIGHT parameter had a non-existant brightness argument that will crash the server if used (I found this out the hard way).
For anyone willing to test things out, my fantastically coded plugin that I used for testing this PR can be found here - https://pastebin.com/6z5rhc3d
A list of all added natives and can be found in the generated documentation here - http://amxx-bg.info/api/msgstocks/__functions (this is a stock version of the same functions for people that want to use them in 1.8.2)
I skipped TE_LIGHTNING and TE_FIZZ because I couldn't find a working example.
I skipped TE_TEXTMESSAGE as well because of obvious reasons.
Regarding naming:
Only enumerations that actually represent flags should be named <something>Flags. The naming usually implies that you can do bitwise operations on them.
Enumerations that represent "types" or similar that you can choose one of should be singular. TracerColors > TraceColor. You can see that in cstrike_const.inc (with an ancient exception of CsTeams, which is awful).
Suggestions:
-
StatusIconFlags>StatusIconDisplay -
TrainControlFlags>TrainControlSpeed -
BreakModelFlags>BreakModelType -
TracerColors>TracerColor
@rsKliPPy You're right. I updated the enum names.
If you plan to make a lot of changes in a PR, you should
- Discuss beforehand
- Split the PR in smaller PRs if possible.
- Avoid adding all your new changes in one commit. Make smaller and logical commits.
- Fix separately any original bugs found in another PR.
Random thoughts to be discussed:
- About
te_natives. This doesn't feel right to have them as natives. The only reason you would want to keep them is to error on invalid player's index. Looking atmessage_begin, even though the engine will skip if the player is not connected, it will crash if the index is incorrect. For some reasons, this native doesn't error on invalid player. If this native is fixed, I would likely put all thosete_natives into stocks instead. - Same for the others natives, this kind of shortcut to just wrap things should be likely stocks.
- Should we use emessage?
- About
set_user_fov. This is not the proper way to do that. The message is associated tom_iFOV,m_iClientFOVandpev->fov. Those should be set as well. Also, you have to care if a mod do something different. For example, CS sends also a HLTV message [1]. A CS version might be needed, I don't know. Also, since using class members, it might be a good idea to avoid to put such stuff into the core. - About
hide_hud_elements, same as above. It's associated tom_iHideHUDandm_iHideClientHUD. ReGameDLL added some fix as well [2] Same remark as above. - About
shake_user_screen, it could be more interesting to base the code onUTIL_ScreenShake[3], maybe. - About
send_geiger_signal, I don't know if it's really useful to send the message alone. It's also associated tom_flgeigerDelay,m_flgeigerRange, andm_igeigerRangePrev. And to trigger the message, you just need to set a valuem_igeigerRange!= 1000 [4]. This is the same behavior for fov and hidehud btw where the game already checks if a message needs to be sent. Though the geiger message is just about sending a short sound (from 30ms to 300ms max), so it would be kind of silly to redo the logic to send several message when it's already available. Don't know. - About
cs_set_hud_icon: Are you saying you can provide an interval without providing the associated icon? If not active, only the first byte should be sent. - About
cs_reload_sound, maybe more logic could be added like doesCBasePlayer::ReloadSound[5]. - About
cs_draw_progress_bar, you have them_progressStartand 'm_progressEnd' members associated. You should also care for spectators. CS has already two functions for that [6] - About the style, please put a space after
if,for, etc. Also, put the native header above the function and finally put brackets even if one line for consistency. __
1 CS code example with SetFOV ↩
Details
if (m_iFOV != m_iClientFOV)
{
// cache FOV change at end of function, so weapon updates can see that FOV has changed
pev->fov = m_iFOV;
MESSAGE_BEGIN(MSG_ONE, gmsgSetFOV, nullptr, pev);
WRITE_BYTE(m_iFOV);
MESSAGE_END();
MESSAGE_BEGIN(MSG_SPEC, gmsgHLTV);
WRITE_BYTE(ENTINDEX(edict()));
WRITE_BYTE(m_iFOV);
MESSAGE_END();
}
__
2 CS code example with HideWeapon ↩
Details
if (m_iHideHUD != m_iClientHideHUD)
{
MESSAGE_BEGIN(MSG_ONE, gmsgHideWeapon, nullptr, pev);
WRITE_BYTE(m_iHideHUD);
MESSAGE_END();
#ifdef REGAMEDLL_FIXES
if (m_iHideHUD && !(m_iHideHUD & HIDEHUD_OBSERVER_CROSSHAIR))
{
if (m_iClientHideHUD < 0)
m_iClientHideHUD = 0;
int hudChanged = m_iClientHideHUD ^ m_iHideHUD;
if (hudChanged & (HIDEHUD_FLASHLIGHT | HIDEHUD_HEALTH | HIDEHUD_TIMER | HIDEHUD_MONEY))
{
MESSAGE_BEGIN(MSG_ONE, gmsgCrosshair, nullptr, pev);
WRITE_BYTE(0);
MESSAGE_END();
}
}
#endif
m_iClientHideHUD = m_iHideHUD;
}
__
3 UTIL_ScreenShake` ↩
Details
void UTIL_ScreenShake(const Vector ¢er, float amplitude, float frequency, float duration, float radius)
{
int i;
float localAmplitude;
ScreenShake shake;
shake.duration = FixedUnsigned16(duration, (1<<12));
shake.frequency = FixedUnsigned16(frequency, (1<<8));
for (i = 1; i <= gpGlobals->maxClients; i++)
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex(i);
if (!pPlayer || !(pPlayer->pev->flags & FL_ONGROUND))
continue;
localAmplitude = 0;
if (radius > 0)
{
Vector delta = center - pPlayer->pev->origin;
float distance = delta.Length();
if (distance < radius)
localAmplitude = amplitude;
}
else
localAmplitude = amplitude;
if (localAmplitude)
{
shake.amplitude = FixedUnsigned16(localAmplitude, 1<<12);
MESSAGE_BEGIN(MSG_ONE, gmsgShake, nullptr, pPlayer->edict());
WRITE_SHORT(shake.amplitude);
WRITE_SHORT(shake.duration);
WRITE_SHORT(shake.frequency);
MESSAGE_END();
}
}
}
__
4 UpdateGeigerCounter` ↩
Details
#define GEIGERDELAY 0.25
void CBasePlayer :: UpdateGeigerCounter( void )
{
BYTE range;
// delay per update ie: don't flood net with these msgs
if (gpGlobals->time < m_flgeigerDelay)
return;
m_flgeigerDelay = gpGlobals->time + GEIGERDELAY;
// send range to radition source to client
range = (BYTE) (m_flgeigerRange / 4);
if (range != m_igeigerRangePrev)
{
m_igeigerRangePrev = range;
MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev );
WRITE_BYTE( range );
MESSAGE_END();
}
// reset counter and semaphore
if (!RANDOM_LONG(0,3))
m_flgeigerRange = 1000;
}
__
5 ReloadSound` ↩
Details
void CBasePlayerWeapon::ReloadSound()
{
CBasePlayer *pPlayer = nullptr;
while ((pPlayer = UTIL_FindEntityByClassname(pPlayer, "player")))
{
if (pPlayer->IsDormant())
break;
if (pPlayer == m_pPlayer)
continue;
float distance = (m_pPlayer->pev->origin - pPlayer->pev->origin).Length();
if (distance <= MAX_DIST_RELOAD_SOUND)
{
MESSAGE_BEGIN(MSG_ONE, gmsgReloadSound, nullptr, pPlayer->pev);
WRITE_BYTE(int((1.0f - (distance / MAX_DIST_RELOAD_SOUND)) * 255.0f));
if (!Q_strcmp(STRING(pev->classname), "weapon_m3") || !Q_strcmp(STRING(pev->classname), "weapon_xm1014"))
WRITE_BYTE(0);
else
WRITE_BYTE(1);
MESSAGE_END();
}
}
}
__
6 SetProgressBarTimeandSetProgressBarTime2` ↩
Details
void CBasePlayer::SetProgressBarTime(int time)
{
if (time)
{
m_progressStart = gpGlobals->time;
m_progressEnd = time + gpGlobals->time;
}
else
{
m_progressStart = 0;
m_progressEnd = 0;
}
MESSAGE_BEGIN(MSG_ONE, gmsgBarTime, nullptr, pev);
WRITE_SHORT(time);
MESSAGE_END();
int playerIndex = entindex();
CBaseEntity *pEntity = nullptr;
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
{
if (FNullEnt(pEntity->edict()))
break;
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
if (pPlayer->GetObserverMode() == OBS_IN_EYE && pPlayer->pev->iuser2 == playerIndex)
{
MESSAGE_BEGIN(MSG_ONE, gmsgBarTime, nullptr, pPlayer->pev);
WRITE_SHORT(time);
MESSAGE_END();
}
}
}
void CBasePlayer::SetProgressBarTime2(int time, float timeElapsed)
{
if (time)
{
m_progressStart = gpGlobals->time - timeElapsed;
m_progressEnd = time + gpGlobals->time - timeElapsed;
}
else
{
timeElapsed = 0;
m_progressStart = 0;
m_progressEnd = 0;
}
short iTimeElapsed = (timeElapsed * 100.0 / (m_progressEnd - m_progressStart));
MESSAGE_BEGIN(MSG_ONE, gmsgBarTime2, nullptr, pev);
WRITE_SHORT(time);
WRITE_SHORT(iTimeElapsed);
MESSAGE_END();
int playerIndex = entindex();
CBaseEntity *pEntity = nullptr;
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
{
if (FNullEnt(pEntity->edict()))
break;
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
if (pPlayer->GetObserverMode() == OBS_IN_EYE && pPlayer->pev->iuser2 == playerIndex)
{
MESSAGE_BEGIN(MSG_ONE, gmsgBarTime2, nullptr, pPlayer->pev);
WRITE_SHORT(time);
WRITE_SHORT(iTimeElapsed);
MESSAGE_END();
}
}
}
@Arkshine anchors don't work.
@Arkshine I first started making these as stocks but then I decided to remake them as natives. If I'm not mistaken, natives are called faster than stocks, right? If you want to have them as stocks instead, I have the stock version uploaded here, but I think we should stick to natives instead. I'll look at the other things you suggested tomorrow and I'll try to make them. I'm not that great at C++ but I'll see what I can do. I thought that MESSAGE_BEGIN will change all other things associated. For emessage, we can also include another optional parameter at the end of every native (e.g. bool:hookable).
I'm not asking, we need to discuss it before. I don't have the universal answer to everything. The PR has too many changes, we need to figure out first what to keep, what to trash, what to change, etc.
The performance should be the least of your concern when deciding stock vs native, especially when it's something as insignificant as this which you won't (and can't) execute too much of. The primary concern is whether you want it to be compiled into plugins (stock) or have the implementation separate so it can be changed at any time without compilation (native).
I honestly think these deserve to be stock functions as well. These are after all little utility functions that are unlikely to ever change if we get them right the first time, and that shouldn't be hard to do with this.
@Arkshine @rsKliPPy Okay then. I can convert them to stocks if you believe there won't be any performance improvements if they are natives. I'll wait for the discussion before I do that.
Hard work man, how much time did it take? This will definitely reduce the size of the future plugins.. and increase the execution speed..
@ClaudiuHKS It took about 10 days. The weather was bad on my vacation so I had nothing smarter to do. I don't know about the improved size and speed if we decide to make them as stocks instead.
bump
Still waiting
Why still not merged? @Arkshine