About adding randomness to HL Created 5 days ago2020-05-20 19:04:20 UTC by Alexis_of_Steel Alexis_of_Steel

Created 5 days ago2020-05-20 19:04:20 UTC by Alexis_of_Steel Alexis_of_Steel

Posted 5 days ago2020-05-20 19:04:20 UTC Post #344252
Hey there. I'm seeking out the way to add randomness to HL for triggering events. I wanna do it through editing its source code (using Microsoft Visual Studio 2010 and official Valve's Half-Life 1 SDK from GitHub). But I coudn't figure out how to do it :cry: . So, I'm wondering if you guys know how to do it. I was thinking about creating a new entity, like trigger_random or so.

I would appreciate your help of course. Greetings from Argentina!
Alexis.
Posted 5 days ago2020-05-20 21:03:44 UTC Post #344253
Hello there.

First of all, I'd recommend you Visual Studio 2019 with Solokiller's updated HL SDK for it.
Here's an example trigger_random class which basically acts like a trigger_relay but it has a trigger percentage keyvalue.
class CTriggerRandom : public CBaseDelay
{
public:
    void Spawn();
    void KeyValue( KeyValueData* pkvd ); // this will load your map editor keyvalues/properties
    void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); // this is called when the entity gets triggered by another one

    int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }

private:
    float triggerChance = 0.5f; // 50% trigger chance by default
}

LINK_ENTITY_TO_CLASS( trigger_random, CTriggerRandom );

void CTriggerRandom::Spawn()
{
    // nothing here
}

void CTriggerRandom::KeyValue( KeyValueData* pkvd )
{
    if ( FStrEq( pkvd->szKeyName, "chance" ) )
    {
        triggerChance = atof( pkvd->szValue ); // read the map property and assign it to a variable, it's in range between 0.0 and 1.0
        pkvd->fHandled = TRUE;
    }

    else
    {
        CBaseDelay::KeyValue( pkvd );
    }
}

void CTriggerRandom::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
    if ( RANDOM_FLOAT( 0.0, 1.0 ) <= triggerChance )
        return;

    SUB_UseTargets( this, USE_TOGGLE, 0 );
}
Now, I gotta say one thing. This entity has to be triggered first. If you want it to try to automatically trigger something every 0.1s, for example, you will have to modify the code a little bit, but for now, I think this will show you the basic idea of how to do it.
Admer456 Admer456If it ain't broken, don't fox it!
Posted 5 days ago2020-05-20 21:14:24 UTC Post #344255
That's not the kind of random trigger he's looking for.

I wrote an implementation based on Source's logic_case:
const int MaxRandomTargets = 16;

const char TargetKeyValuePrefix[] = "target";

class CTriggerRandom : public CBaseDelay
{
public:
    void KeyValue(KeyValueData* pkvd);

    void Spawn();

    void EXPORT RandomUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value);

    int ObjectCaps() { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }

    virtual int        Save(CSave& save);
    virtual int        Restore(CRestore& restore);

    static    TYPEDESCRIPTION m_SaveData[];

    string_t m_iszTargets[MaxRandomTargets];

private:
    int BuildMap(std::array<std::uint8_t, MaxRandomTargets>& map);
};

TYPEDESCRIPTION    CTriggerRandom::m_SaveData[] =
{
    DEFINE_ARRAY(CTriggerRandom, m_iszTargets, FIELD_STRING, MaxRandomTargets),
};

IMPLEMENT_SAVERESTORE(CTriggerRandom, CBaseDelay);

LINK_ENTITY_TO_CLASS(trigger_random, CTriggerRandom);

void CTriggerRandom::KeyValue(KeyValueData* pkvd)
{
    const size_t prefixLength = ARRAYSIZE(TargetKeyValuePrefix) - 1;

    if (!strncmp(TargetKeyValuePrefix, pkvd->szKeyName, prefixLength))
    {
        const char* indexString = pkvd->szKeyName + prefixLength;

        char* end;

        int index = strtol(indexString, &end, 10);

        //Must be a number and end after the number, e.g. "target1"
        //index is 1 based [1, MaxRandomTargets]
        if (end != indexString && *end == '\0')
        {
            --index;

            if (index >= 0 && index < MaxRandomTargets)
            {
                if (FStringNull(m_iszTargets[index]))
                {
                    m_iszTargets[index] = ALLOC_STRING(pkvd->szValue);
                }
                else
                {
                    ALERT(at_error, "CTriggerRandom::KeyValue: target \"%s\" already set to \"%s\"", pkvd->szKeyName, STRING(m_iszTargets[index]));
                }
            }
            else
            {
                ALERT(at_error, "CTriggerRandom::KeyValue: invalid target index \"%s\", must be in range [1, %d]", pkvd->szKeyName, MaxRandomTargets);
            }
        }
        else
        {
            ALERT(at_error, "CTriggerRandom::KeyValue: invalid target format \"%s\"", pkvd->szKeyName);
        }
    }
    else
    {
        CBaseDelay::KeyValue(pkvd);
    }
}

void CTriggerRandom::Spawn()
{
    SetUse(&CTriggerRandom::RandomUse);
}

void CTriggerRandom::RandomUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value)
{
    std::array<std::uint8_t, MaxRandomTargets> map;

    const int count = BuildMap(map);

    if (count > 0)
    {
        const int index = RANDOM_LONG(0, count - 1);

        const char* target = STRING(m_iszTargets[index]);

        FireTargets(target, pActivator, pCaller, useType, 0);
    }
}

int CTriggerRandom::BuildMap(std::array<std::uint8_t, MaxRandomTargets>& map)
{
    int targetCount = 0;

    for (int i = 0; i < MaxRandomTargets; ++i)
    {
        if (!FStringNull(m_iszTargets[i]))
        {
            ++targetCount;
            map[i] = i;
        }
    }

    return targetCount;
}
Add it to the bottom of triggers.cpp.

Also add this as the first include files:
#include <array>
#include <cstdint>
FGD entry:
@PointClass base(Targetname) = trigger_random : "Random Trigger"
[
    target1(target_destination) : "Target 1"
    target2(target_destination) : "Target 2"
    target3(target_destination) : "Target 3"
    target4(target_destination) : "Target 4"
    target5(target_destination) : "Target 5"
    target6(target_destination) : "Target 6"
    target7(target_destination) : "Target 7"
    target8(target_destination) : "Target 8"
    target9(target_destination) : "Target 9"
    target10(target_destination) : "Target 10"
    target11(target_destination) : "Target 11"
    target12(target_destination) : "Target 12"
    target13(target_destination) : "Target 13"
    target14(target_destination) : "Target 14"
    target15(target_destination) : "Target 15"
    target16(target_destination) : "Target 16"
]
I haven't tested it but it should work fine. It logs invalid keyvalue inputs and will treat the "target" keyvalue as an invalid input since it isn't meant to be used with this entity.

It works just like logic_case does when using the PickRandom input. All target keyvalues that have been set will be considered and one is randomly chosen. You can turn it into a random do/do nothing setup by setting targets that don't exist, e.g. "dummytarget".
Posted 4 days ago2020-05-21 18:04:42 UTC Post #344261
Thank you guys for answering. Solokiller's method worked perfectly! I've tested myself.
This thread is solved.
I'm in debt to you!
Alexis.
You must be logged in to post a response.