Trying to create a dynamic skill log, not sure how to iterate through entries?


#1

Hello, everyone,

I’m trying to build a dynamic skill library that makes it easy for us to build out skills as our server develops, and one of those core functions is simply getting the number of skills the PC knows. It seems like it would be the easiest part of this whole thing. However, I’ve no idea how to run down a list of variables that are defined as they’re meant to be.

As written, my entries are:

SkillCount (number of skills the player has in total. This is iterated every time they pick up a new one.)

SKILL(X) these are the names of the skills I want to add to the character’s journal. The string of this, uh, string would be the title of that particular skill which is run through its own script. This is to prevent me from adding a bunch of dummy strings to the skill journal and just hoping we have enough as well as bogging down the CPU with variables.

SKILL_(X)_RANKS a 1-100 variable that will be used for skill checks when the skill is invoked.

This seems like it’d be really easy to implement-- iterate the SkillCount variable by one, add new skill name/rank strings to the skill journal when a PC gains them and you’re done. But when I go to make a script for the player to get that information, I’m completely lost as to how to get those strings.

Some possible solutions that come to mind is to write those strings as something like Skill1, Skill2, etc. Using the SkillCount variable to know how many skills the item has written to it, I just don’t know how to use wildcards to get the right skill number in the list since these would be picked up and checked randomly. There’s no way to know that say, ‘Bread Making’ will be in slot 2 at all times, or ever.

I think ‘get string right’ would get me what I want, but every time I try to use it it keeps returning " " instead of a value.

Any thoughts would be greatly appreciated!


#2

To establish the initial skill count, you could use Get2DAString to read sequentially through all skills in skills.2da, obtaining the skill name, until it returns a null field at end of file. GetHasSkill will tell you whether the player has a skill or not, which you can count and tabulate.

See also GetSkillRank and EffectSkillIncrease.


#3

I probably should’ve clarified; I’m using 100% custom skills for my project. For mechanical purposes, they’re basically just strings written to an item and will get invoked at various skill benches and things of that nature.

Sorry about that!


#4

You can use SKILLn for n = 1,2,3…
This holds the name of each skill the PC learns (in the order they are learned but that doesn’t matter that much, unless you want to display them in order). In SKILL1 you store “breadmaking” for example.

Keep SkillCount as you described.

Then you keep the ranks of skill X as you described SKILL_X_RANKS

SKILL_breadmaking_RANKS for example. Then when you use the skill you can just check this variable. If 0 the PC does not have that skill.

To display the all the skills you can walk from SKILLn from 1 to SkillCount to get the name and then
SKILL_name_RANKS to get the number of ranks.

Or you could walk a list of all your skills by name in alphabetical order and check the PC for SKILL_name_RANKS > 0. If you only want to do it this way you would not need the SKILLn and SkillCount on the PC but you might have a complete list of the names on the module as variables 1 through SkillCount.

My 2 cents anyway.


#5

Adding slot numbers to a string and parsing them out again is also a thing. StringParse and StringRemoveParsed are in x3_inc_string.

Putting together a “taken slots” string:

    // Determine first free slot number:
    int i;
    string sNum = "0";
    string sCheck = GetLocalString(OBJECT_SELF, "SKILL_0");
    while (sCheck != "")
        {
        sNum = IntToString(++i);        
        sCheck = GetLocalString(OBJECT_SELF, "SKILL_"+sNum);
        }

    // Add the newly-claimed slot number to the slots list:
    string sSlotsList = GetLocalString(OBJECT_SELF, "SKILLS_SLOTS_LIST");
    SetLocalString(OBJECT_SELF, "SKILLS_SLOTS_LIST", sNum+" "+sSlotsList);

    // Also make sure to add something to the local string we used 
    // to check for free slots, so it'll be taken next time.
    SetLocalString(OBJECT_SELF, "SKILL_"+sNum, "Cake Decorating");

And to retrieve them:

    // Retrieve the string that contains all taken slot numbers.
    string sSlotsList = GetLocalString(OBJECT_SELF, "SKILLS_SLOT_LIST");
    while (sSlotsList != "")
        {
        // Get as many symbols from the left as it takes to hit a blank space 
        // (there is a parameter for changing the delimiter if you want to use something else).
        string sNum = StringParse(sSlotsList);
        // Remove the amount of symbols that we just retrieved via StringParse, cutting sSlotsList down by one slot.
        sSlotsList = StringRemoveParsed(sSlotsList, sNum);

        // Use the extracted slot number to find stored variables.
        int nSkill = GetLocalInt(OBJECT_SELF, "SKILL_"+sNum);
        string sName = GetLocalString(OBJECT_SELF, "SKILL_"+sNum);
        }

NWShacker implemented struct lists over here, too, with random-shuffling and inserting at specific positions and everything.


#6

Um, okay :slight_smile:

How is your while (sCheck != “”) loop better than just keeping the count as variable? It has the same effect.

And the SKILLS_SLOTS_LIST seems to be unneeded completely. You are keeping the slots used on the PC by simply setting them to non-empty strings and filling them in in order. A given PC in your code will always have skills SKILL_0 … SKILL_(count - 1) and SKILLS_SLOTS_LIST will be “0 1 2 3 … count-1”
for every PC.

I don’t see how that’s functionally different from what I suggested except more complicated. There’s no need to bother with the parsing, even though it is a “thing” :slight_smile:

If you wanted to make it so that Cake Decorating was always SKILL_11 on every PC you’d have a sparse list of used SKILL_* variables on any given PC, then a SKILLS_SLOTS_LIST could be useful for iterating the skills of a given PC. But this code does not do that. And even then I’d probably have a list on the module of all the skills and just use SKILL_cake_decorating_RANKS on the PC.

Two more cents… in another couple months at this rate you can buy a cup of coffee.


#7

I’m just adding extra solution paths to the topic. OP’s query was for ways to iterate through variable entries, after all. :slight_smile: Gotta spread teh knowledgs.

The approach can be very useful when listing and checking through temporarily-stored stored valuables that may be removed during or before the next check-through. In cases like that, this way of storing and iterating through local variables prevents the “index” from growing into infinity, as the newly-empty slot would be up for re-filling next time something gets stored, and the part that reads the entries is indifferent to the order in which it’s meant to check through them.

Re-comprise the index string on check-through, only adding non-empty or otherwise qualifying slots to the new string, and you get scenarios like this:

Starting slots:

8 7 6 5 4 3 2 1 0

Now, something happens that drops 3 and 7 on checkthrough, erasing the stored variables, most importantly the variable that’s being checked through to find the first free slot. New “taken slots” list:

8 6 5 4 2 1 0

A newly-added entry would recognize that slot 3 is empty, again, and be added there:

3 8 6 5 4 2 1 0

It’s very useful when you’ve got variables that need to be able to be added and removed in irregular intervals, like if you’re putting objects on a tracking list to scan through regularly for some type of action or event.

As a practical example, picture a combat AI handler that assigns actions to NPCs who are involved in a fight. If the MELEE_DDS list is empty, you can try to add some NPCs who are currently on the HEALERS list to the MELEE_DDS list, making them switch their action sets mid-fight.

Likewise, dead NPCs might be dropped from the combat entirely, no longer qualifying for being re-added to their list on scanthrough. Or, they could be added to a NEEDS_RESURRECTING list that the healers would attempt to take care of, during their own action set.

It’s not strictly necessary for OP’s purpose, but it’s something that opens doors to a lot of interesting things. A complementary addition to the topic, showing another possible route, with it’s own advantages and disadvantages, if you will.


#8

Do you need to know whether a character knows a skill or which skills the character knows? For the former, you only to store the ranks variable for a skill. E.g. if the character learns bread making, you only need to store SKILL_BREAD_MAKING_RANKS.

If you want to see if the player has the skill you check the value SKILL_BREAD_MAKING_RANKS variable. If it’s 0 (i.e. the variable hasn’t been set), the player doesn’t have the skill. If it’s greater than 0, then you know the player has the skill and its rank.

Depending on the number of skills you have and how often that information is required, you just write a function to check every skill and count up the number of skills known. e.g.
int GetNumSkills(object oPC)
{
int iNumSkills=0;
if (GetLocalVariable(“SKILL_BREAD_MAKING_RANKS”, oPC)) iNumSkills++;
if (GetLocalVariable(“SKILL_BASKET_WEAVING_RANKS”, oPC)) iNumSkills++;

return iNumSkills;
}

This approach should require storing the minimum amount of information (1 local int per skill known), but will have the highest computational cost. That said, assuming you’re under 100 skills, the extra computation shouldn’t be an issue.

Another approach would be to use a couple of integers as bit fields to keep track of which skills the character knows. In that case the GetNumSkills function would be counting the number of 1s in those variables to determine the number of skills known and checking the positions of those 1s to determine which skills are known instead of accessing local variables (you would still have to store the skill rank variables and access them if you want that information). It would be faster than checking a list of local variables and considerably less overhead than setting up a pseudo linked-list of variables.