Coding help needed for PMPreCache plugin Created 11 months ago2023-11-28 22:15:30 UTC by MegaBrutal MegaBrutal

Created 11 months ago2023-11-28 22:15:30 UTC by MegaBrutal MegaBrutal

Posted 11 months ago2023-11-28 22:15:30 UTC Post #348103
Previously I announced my simple Metamod plugin which helps to spread player models. I've been using it ever since then and it works, but it has certain aspects that I'm dissatisfied with, and I'm not an experienced plugin author yet to fix them.
  1. On Linux, file names are case-sensitive, and if the user provides the model with a different case, my plugin doesn't find it. Not sure if the engine is prepared to look up files case-insensitively, but it leads to another problem: I use the access() system call to check for the existence of files and it is obviously not a good idea. Once, it completely bypasses the engine, so even if the engine has a solution to look up files case-insensitively, I don't utilize that. Moreover, it makes the code platform-dependent, as it won't compile on Windows. I know there is an engine callback, LoadFileForMe, but I don't want to load the model files at that point, just check for their existence. I also don't know the lifetime of the buffer returned by this function, is the engine smart enough to not load this file again when it is precached the next round, or would I waste memory, how to ask the engine to free it if I need to.
  2. The plugin is supposed to inform players whether the server has their player model. The problem is that the message is sent when the player has not yet fully entered the game, so it's not shown to them. When I was experimenting with this feature, I found like 3 methods to send messages to the player, and the maximum I could achieve is that the message appeared on the console, which is easy to miss. I somehow need to delay the sending of the message, but I don't know how to approach this problem. Somehow I should set up a timer.
  3. Currently I allocate my own buffer for storing strings because I'm afraid ALLOC_STRING() would redundantly allocate the same strings for the same player models over time and I don't know when these strings get freed. So I felt like this is the only way to control the lifetimes of these strings, but I'm curious for better ideas.
I really hope some HLSDK gods are around to help me out with this; thanks in advance! :D
Posted 11 months ago2023-11-30 11:31:11 UTC Post #348122
I'm not sure how hard it is to load VFileSystem009 from a plugin but IFileSystem in public/FileSystem.h has a FileExists method. I'm not sure how LoadFileForMe works but I imagine the buffer has a long life, and I believe it memory maps the file.

I think there are callbacks for when a player connects in the engine? Which one are you using to check? ClientConnect or something else in DLL_FUNCTIONS should hopefully let you hook the player joining a game.

I think strings allocated by ALLOC_STRING last as long as the engine is running but I don't believe it's smart enough to check for already allocated strings.
Posted 11 months ago2023-11-30 12:45:46 UTC Post #348123
I decided to hook ClientPutInServer which runs when the player enters the game, but unfortunately it is too early to send messages from here.
void ClientPutInServer( edict_t *pEntity ) {
    const char* playername = STRING(pEntity->v.netname);
    char* modelname = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pEntity ), "model" );
    char modelfile[256];
    snprintf(modelfile, sizeof(modelfile), "models/player/%s/%s.mdl", modelname, modelname);
    LOG_MESSAGE(PLID, "Player %s is using model %s", playername, modelname);
    if (fileExists( modelfile )) {
        LOG_MESSAGE(PLID, "Model %s is present on server", modelname);
        SAY(pEntity, "Your model is present on the server, it will be precached for the next round!");
        if (addPrecacheEntry( playername, modelname ))
            LOG_MESSAGE(PLID, "Added model %s to precache list", modelname);
        else
            LOG_MESSAGE(PLID, "Model %s will not be precached - reduntant or precache list is full", modelname);
    }
    else {
        LOG_MESSAGE(PLID, "Unable to precache due to FILE MISSING: %s!", modelfile);
        SAY(pEntity, "Your model is not present on the server, can't distribute for other players!");
    }
    RETURN_META(MRES_IGNORED);
}
If I send a message to other players who are already in game, or all players, they receive the message, so it's not that SAY() doesn't work. I think somehow I should add some timing, so the message would be sent several seconds later when they are already playing. (Just an irrelevant sidenote: the log message "Model %s will not be precached - reduntant or precache list is full" is confusing, because if the model is already added to the list, it WILL be precached for the next round, but won't if the list is full.)

SAY() is a macro I defined to send raw messages – I remember there are other methods I tried, but all of them effectively do this at their core.
#define SAY(pEntity, text) {\
    MESSAGE_BEGIN( MSG_ONE, GET_USER_MSG_ID(PLID, "SayText", NULL), NULL, pEntity );\
        WRITE_BYTE( ENTINDEX(pEntity) );\
        WRITE_STRING( text );\
    MESSAGE_END();\
}
And fileExists() here is just a wrapper for calling access() (this is the other thing I have problem with):
bool fileExists(const char* path) {
    char fullpath[256];
    snprintf(fullpath, sizeof(fullpath), "%s/%s", &gGamedir[0], path);
    return access(fullpath, R_OK) == 0;
}
I highly doubt that LoadFileForMe() would mmap() the file – it would be somewhat better because it's faster and the kernel could at least unload it if it's not used, but it would still be better to not load files in the middle of a game when they are not needed in memory.

IFileSystem in public/FileSystem.h is interesting, I think it would do what I need but I have no idea what it links to.
Posted 11 months ago2023-12-04 04:00:10 UTC Post #348140
I'm thinking that maybe I should create an entity and define a Think() callback for it with a sufficient delay set for nextthink. Now I'm wondering if this is the elegant way of delaying actions in a plugin or is there a more straightforward way.
You must be logged in to post a response.