AlliedModders

AlliedModders (https://forums.alliedmods.net/index.php)
-   Coding MM:S Plugins & SM Extensions (https://forums.alliedmods.net/forumdisplay.php?f=75)
-   -   [HOWTO] Make a nice say-hook (https://forums.alliedmods.net/showthread.php?t=37058)

PM 01-05-2005 15:09

[HOWTO] Make a nice say-hook
 
I don't know whether you guys already know this, but I found out how to make a nice, pretty mod-independent say command hook.
As you have probably noticed, doing a CON_COMMAND_F("say", FCVAR_GAMEDLL) makes the original command completly disappear. So, this is what I came up with:

Code:

// Special hook for say commands
class CSayHook : public ConCommand
{
        // This will hold the pointer original gamedll say command
        ConCommand *m_pGameDLLSayCommand;
public:
        CSayHook() : ConCommand("say", NULL, "say messages", FCVAR_GAMEDLL), m_pGameDLLSayCommand(NULL)
        { }

        // Override Init
        void Init()
        {
                // Try to find the gamedll say command
                ConCommandBase *pPtr = g_pICvar->GetCommands();
                while (pPtr)
                {
                        if (pPtr != this && pPtr->IsCommand() && strcmp(pPtr->GetName(), "say") == 0)
                                break;
                        // Nasty
                        pPtr = const_cast<ConCommandBase*>(pPtr->GetNext());
                }
                if (!pPtr)
                        OMGOMGOMG ! PRINT ERROR1!1!1!!!!

                m_pGameDLLSayCommand = pPtr;

                // Call base class' init function
                ConCommand::Init();
        }

        void Dispatch()
        {
                // Do the normal stuff, return if you want to override the say

                // Forward to gamedll
                m_pGameDLLSayCommand->Dispatch();
        }
};

// Don't forget to make an instance
CSayHook g_SayHook;

(almost copy & paste from sourcemod)


EDIT: Fixed. Forgot m_pGameDLLSayCommand = pPtr; Thanks to u/d/theqizmo for reporting this :D

blaubaer 01-05-2005 15:38

nice ! ;)

Manip 01-05-2005 16:09

..wow... How over-programmed ..

Here is mine:

Code:

CON_COMMAND_F( say, "Hooks say", FCVAR_GAMEDLL )
{
}


BeetleFart 01-05-2005 16:26

Quote:

Originally Posted by Manip
..wow... How over-programmed ..

Here is mine:

Code:

CON_COMMAND_F( say, "Hooks say", FCVAR_GAMEDLL )
{
}


but that doesnt dispatch the say messages to the clients

vancelorgin 01-06-2005 06:03

I like the concept. Like I said I was trying to do this with slot commands but those are apparently done differently. I don't, however, use it for say. The code for the say command is right in the sdk :x.

Code:

const ConVar* mp_teamplay = NULL;

void Handle_Say(int iMode){
        if(!mp_teamplay)
                mp_teamplay = cvar->FindVar("mp_teamplay");

        if(!mp_teamplay)
                return;

        edict_t* pEdict = engine->PEntityOfEntIndex(g_nCommandClientIndex + 1);

        CPlayer* pPlayer = CPlayer::Find(pEdict);

        int j;
        char* p;
        char text[AMP_MAX_LINE_LEN];
        char szTemp[AMP_MAX_LINE_LEN];
       
        const char* pcmd = engine->Cmd_Argv(0);
        int argc = engine->Cmd_Argc();

        if(argc == 0)
                return;

        if(!stricmp(pcmd, "say") || !stricmp(pcmd, "say_team") || !stricmp(pcmd, "say_admin"))
                if(argc >= 2)
                        p = (char *)engine->Cmd_Args();
                else
                        return; // say with a blank message, nothing to do
       
        else{  // Raw text, need to prepend argv[0]
                if(argc >= 2)
                        _snprintf(szTemp,sizeof(szTemp), "%s %s", (char*)pcmd, (char*)engine->Cmd_Args());
                else
                        _snprintf(szTemp,sizeof(szTemp), "%s", (char*)pcmd); // Just a one word command, use the first word...sigh
               
                p = szTemp;
        }

        p = CheckChatText(p); // make sure the text has vlaid content

        if(!p)
                return;

        if(*p == CONTROLCHAR && pPlayer)
                if(cmds.Execute(p, pEdict, true))
                        return;
                else{
                        msgs.SayTo(pEdict, "Invalid command \n");

                        return;
                }

        const char* pszPrefix = NULL;

        if(iMode == 1)
                pszPrefix = "(TEAM) ";
        else if(iMode == 2)
                pszPrefix = "(ADMIN) ";

        if(pszPrefix && strlen( pszPrefix ) > 0)
                _snprintf(text, sizeof(text), "%s %s: ", pszPrefix, pPlayer? pPlayer->GetName() : "Server");
        else
                _snprintf(text, sizeof(text), "%s: ", pPlayer? pPlayer->GetName() : "Server");

        j = sizeof(text) - 2 - strlen(text);  // -2 for /n and null terminator
       
        if((int)strlen(p) > j)
                p[j] = 0;

        strncat(text, p, sizeof(text));

        if(iMode == 2){
                //admins only - player access levels are still being implimented
        }else
                msgs.SayFrom(pEdict, text, (mp_teamplay->GetInt() > 1));
}

CON_COMMAND_F(say, "Sends shit to everyone.", FCVAR_GAMEDLL){
        Handle_Say(0);
}

CON_COMMAND_F(say_team, "Sends shit to your team.", FCVAR_GAMEDLL){
        Handle_Say(1);
}

CON_COMMAND_F(say_admin, "Sends shit other nimdas.", FCVAR_GAMEDLL){
        Handle_Say(2);
}

COMMAND_(me, 0, "emote [<3 ltfxdude :]"){
        if(!pPlayer)
                return;

        std::string strText = VASTR(pPlayer->GetName());

        strText += " ";

        strText += cmds.Args();

        msgs.Say((char*)strText.c_str());
}

Thanks to my command manager all my commands are available through chat too. Woo-hah :/

PM 01-06-2005 07:43

Heh, I didn't want to copy the SDK code into my plugin - this seems to be far more mod-independent and easier :)
(I also didn't want to use offset hacks)

Manip: Like BeetleFart said, you would have to recode the whole say thing in order to dispatch the message to all the clients, like vancelorgin did.

XAD 01-06-2005 13:06

Quote:

Originally Posted by PM
Heh, I didn't want to copy the SDK code into my plugin - this seems to be far more mod-independent and easier :)
(I also didn't want to use offset hacks)

I agree that if you don't need use "engine-hooks" you shouldn't (and I like your implementation) but the offset hack is not that bad as it is based on the "official" engine API. If Valve would change the engine API by reordering any virtual functions then all existing mods would stop working (which is not realistic)... They will do it the same way as in CS1.6, by adding new functions at the end which won't change any existing function offsets... Remember that anyone creating a mod will also use the offset in the current interface eventhough it's calculated by the compiler...

/X

BAILOPAN 01-06-2005 17:13

You underestimate valve. They would readily break API to promote something different (just look at the past).

Mod and engine independence is a huge plus no matter how cool hacked code is ;]

XAD 01-06-2005 19:15

Quote:

Originally Posted by BAILOPAN
You underestimate valve. They would readily break API to promote something different (just look at the past).

Mod and engine independence is a huge plus no matter how cool hacked code is ;]

Well if you think about eiface change, that was a misstake by MetaMod (if it should have set the function pointer struct to zeroes when allocating it and before sending it to the mods there would have not been any need to change the mods)...

The engine is mod independent, it's the g#d d¤%m engine (:wink:)??!!
All HL2DS mods are engine API dependent or otherwise it would NOT be a HL2DS based game!!?? If the engine API would change that much (that the offset "hack" wouldn't work) then you still would have to recompile all mods and server plugins... so what's the difference (except that you would have to re-create a new function pointer struct when using offsets)??

Remember that valve is in the business of getting money from licenses of their engine. If they would change it (the engine API) so it would require recompiles of mods then you would have a lot of unhappy licensors (ie BIG customers). The last engine API changes on HLDS were done to support CS:CZ and it was only new functions appended to the end of the function pointer list... which won't cause a problem when using offsets and should have not been an issue in the past if MetaMod would have been implemented "correctly"...

Unless it will relink the objects internal virtual functions at runtime??

BUT again... everyone is, and will, choose their own way to implement their solutions (unless you are on a development team :wink:)...

/X

vancelorgin 01-06-2005 20:10

The offsets, bailo, PM is referring to, are offsets in the Interfaces' virtual function tables - publicly accessible, mod universal, fundamental classes, like XAD said. When you hear 'offset' you instantly think hardcoded address - which is a terrible mistake :P I never hardcode addresses in my projects. Offsets are unnecessary here (in fact I don't see how you'd even be able to squeeze them in somewhere). Besides, the modified standard class / interface method can only be used in rare situations where the plugin is called or given a chance to inject itself somewhere. Vtable hooks can be used anywhere. Just try to overload IVEngineServer without it :/

The 'offset' method XAD and I are using is just as mod universal as the server plugin interface, nay, the engine interface. Unless a mod releases it's own engine binaries, the method will work flawlessly.


Though I use this method elsewhere, I prefer mine for say hooking, for the time being, as it allows me to add say text tokens, like @n for name, @l for location, @h for health, etc, and otherwise modify what the client says. Doing that with your method would require somehow overwriting the engine's argument list, which would be, by your standards, ugly ^_^


All times are GMT -4. The time now is 06:23.

Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.