Exp Sharing and instanced loot

Soly

Member
Gender
Male
I moved the monster.dead out of the check for character level just in case
That's totally right... I haven't had caffeine today ;)

Also.. there is a problem with your modification (specifically the part about reducing XP)
1) When you reduce the XP you are reassigning the variable so any successful "not the last who hit" will reduce the XP value more and more.
2) You are checking for each client's packet data for the last client who hit, but this fails because you are checking the first "hey mob is dead" packet... the other clients will have data not relevant to this code because packets are executed as soon as they are received entirely.

To fix 1, easy enough use a new variable inside the scope of the level check

To fix 2, you can ignore any calls to this function if it's not the client who killed the monster.
Only execute when the client who sent the packet was the last one who hit and then give full xp to this client and reduced xp to the rest.

Here is an updated version that accounts for this
Code:
CLIENT *c; // This is the client who's packet is being executed, in teth it will be 'client' iirc
int32_t i1;

// Somewhere at the top of the function
if (mid < 0xB50)
{
    if (l->monsterData[mid].dead[c->clientID] == 0 && c->decbuf[0x10] != 0)
    {
...

for (i1 = 0; i1 < 4; i1++)
{
    // we don't have full parties every time
    if (l->client[i1] != NULL)
    {
        if (l->client[i1]->character.level < 199)
        {
            // Is this iteration the client who killed it?
            if (l->client[i1] == c)
            {
                ClientAddExp(l->client[i1], xp);
            }
            else
            {
                ClientAddExp(l->client[i1], (XP * 77) / 100L);
            }
        }
    }
    // Just to be totally sure ;)
    l->monsterData[mid].dead[i1] = 1;
}

...
    }
}
 

Fire AKA Drazn

Loves Games
Gender
Male
Intriguing. Would you be covering this with the patcher? Or will you leave it as something you can insert yourself?
 

Muckbeast

Member
Gender
Male
This is really awesome and is how I wish XP worked in PSO. It is really rough on support oriented players or people not using a lot of AoE to earn XP. And when you play with the same people constantly it is a bummer when one person gets consistently left behind in XP.
 

PalasX

Member
Slightly modified to work in the released Tethealla source.

The problem with my current code is that players that tag the monster but do not kill it only get 33%. this is because by the time the that player sends their own 0xC8 packet, they monster has already been set "dead" for them, and they dont process ANY of this, as there is a check for it earlier in the code for 0xC8 packets.

But if we dont set the monster as dead for all ClientID's (players), then you could award 33% exp to players multiple times, once for each player that tags the monster.

so the question is, how can i award 100% exp to the player that kills the monster, 77% to anyone that tags it, and 33% to everyone else?

C:
            //shared XP
            int32_t i1;
            for (i1 = 0; i1 < 4; i1++)
            {
                // we don't have full parties every time
                if (l->client[i1] != NULL)
                {
                    if (l->client[i1]->character.level < 199)
                    {
                        // Is this iteration the client who killed it?
                        if (l->client[i1] == client)
                        {
                            if ( l->client[i1]->decryptbuf[0x10] != 1 ) // Not the last player who hit?
                            {
                                AddExp ( (XP * 77) / 100L, l->client[i1] );
                            }
                            else
                            {
                                AddExp ( XP, l->client[i1] );
                            }
                        }
                        else
                        {
                                AddExp ( (XP * 33) / 100L, l->client[i1] );
                        }
                    }
                }
                // Just to be totally sure ;)
                l->monsterData[mid].dead[i1] = 1;
            }
 
Last edited:

PalasX

Member
Here's what i came up with (drop in replacement for case 0xC8):

C:
    case 0xC8:
      // Monster is dead
      if ( client->lobbyNum > 0x0F )
      {
        mid = *(uint16_t*) &client->decryptbuf[0x0A];
        mid &= 0xFFF;
        if ( mid < 0xB50 )
        {
          int32_t bExpAlreadyGiven = 0;
          if ( l->monsterData[mid].dead[client->clientID] > 1 )
          {
            bExpAlreadyGiven = l->monsterData[mid].dead[client->clientID];
            l->monsterData[mid].dead[client->clientID] = 0;
          }
          if ( l->monsterData[mid].dead[client->clientID] == 0 )
          {
            l->monsterData[mid].dead[client->clientID] = 1;

            XP = l->mapData[mid].exp * EXPERIENCE_RATE;

            if (!l->quest_loaded)
            {
              mid_mismatch = 0;

              switch ( l->episode )
              {
              case 0x01:
                if ( l->floor[client->clientID] > 10 )
                {
                  switch ( l->floor[client->clientID] )
                  {
                  case 11:
                    // Dragon
                    if ( l->mapData[mid].base != 192 )
                      mid_mismatch = 1;
                    break;
                  case 12:
                    // De Rol Le
                    if ( l->mapData[mid].base != 193 )
                      mid_mismatch = 1;
                    break;
                  case 13:
                    // Vol Opt
                    if ( ( l->mapData[mid].base != 197 ) && ( l->mapData[mid].base != 194 ) )
                      mid_mismatch = 1;
                    break;
                  case 14:
                    // Dark Falz
                    if ( l->mapData[mid].base != 200 )
                      mid_mismatch = 1;
                    break;
                  }
                }
                break;
              case 0x02:
                if ( l->floor[client->clientID] > 10 )
                {
                  switch ( l->floor[client->clientID] )
                  {
                  case 12:
                    // Gal Gryphon
                    if ( l->mapData[mid].base != 192 )
                      mid_mismatch = 1;
                    break;
                  case 13:
                    // Olga Flow
                    if ( l->mapData[mid].base != 202 )
                      mid_mismatch = 1;
                    break;
                  case 14:
                    // Barba Ray
                    if ( l->mapData[mid].base != 203 )
                      mid_mismatch = 1;
                    break;
                  case 15:
                    // Gol Dragon
                    if ( l->mapData[mid].base != 204 )
                      mid_mismatch = 1;
                    break;
                  }
                }
                break;
              case 0x03:
                if ( ( l->floor[client->clientID] == 9 ) &&
                  ( l->mapData[mid].base != 280 ) &&
                  ( l->mapData[mid].base != 281 ) &&
                  ( l->mapData[mid].base != 41 ) )
                  mid_mismatch = 1;
                break;
              }

              if ( mid_mismatch )
              {
                SendEE ("Client/server data synchronization error.  Please reinstall your client and all patches.", client);
                client->todc = 1;
              }
            }

            //debug ("mid death: %u  base: %u, skin: %u, reserved11: %f, exp: %u", mid, l->mapData[mid].base, l->mapData[mid].skin, l->mapData[mid].reserved11, XP);

            //if ( client->decryptbuf[0x10] != 1 ) // Not the last player who hit?
            //  XP = ( XP * 77 ) / 100L;

            //if ( client->character.level < 199 )
            //  AddExp ( XP, client );

            //shared XP
            int32_t i1;
            for (i1 = 0; i1 < 4; i1++)
            {
                // we don't have full parties every time
                if (l->client[i1] != NULL)
                {
                    if (l->client[i1]->character.level < 199)
                    {
                        // Is this iteration the client who caused the 0xC8 packet?
                        if (l->client[i1] == client && !bExpAlreadyGiven )
                        {
                            if ( l->client[i1]->decryptbuf[0x10] != 1 ) // Not the last player who hit?
                            {
                                AddExp ( (XP * 77) / 100L, l->client[i1] );
                            }
                            else
                            {
                                AddExp ( XP, l->client[i1] );
                            }
                        }
                        else
                        {
                            //check if the player has already been given exp
                            if ( l->monsterData[mid].dead[i1] == 0 )
                            {
                                // Set everyone else that DID NOT initiate 0xC8 packet to dead=2 and award them 33% exp.
                                l->monsterData[mid].dead[i1] = 2;
                                AddExp ( (XP * 33) / 100L, l->client[i1] );
                            }
                        }
                        if (l->client[i1] == client && bExpAlreadyGiven == 2)
                        {
                            if ( l->client[i1]->decryptbuf[0x10] != 1 ) // Not the last player who hit?
                            {
                                AddExp ( (XP * 44) / 100L, l->client[i1] );
                            }
                            else
                            {
                                AddExp ( (XP * 67) / 100L, l->client[i1] );
                            }
                        }
                    }
                }
            }

            // Increase kill counters for SJS, Lame d'Argent, Limiter and Swordsman Lore

            for (ch=0;ch<client->character.inventoryUse;ch++)
            {
              if (client->character.inventory[ch].flags & 0x08)
              {
                counter = 0;
                switch (client->character.inventory[ch].item.data[0])
                {
                case 0x00:
                  if ((client->character.inventory[ch].item.data[1] == 0x33) ||
                    (client->character.inventory[ch].item.data[1] == 0xAB))
                    counter = 1;
                  break;
                case 0x01:
                  if ((client->character.inventory[ch].item.data[1] == 0x03) &&
                    ((client->character.inventory[ch].item.data[2] == 0x4D) ||
                    (client->character.inventory[ch].item.data[2] == 0x4F)))
                    counter = 1;
                  break;
                default:
                  break;
                }
                if (counter)
                {
                  counter = *(uint16_t*) &client->character.inventory[ch].item.data[10];
                  if (counter < 0x8000)
                    counter = 0x8000;
                  counter++;
                  *(uint16_t*) &client->character.inventory[ch].item.data[10] = counter;
                }
              }
            }
          }
        }
      }
      else
        client->todc = 1;
      break;

This will give players that kill the monster 100% xp (actually, theres a good chance they get 33%+67%), players that tag the monster 77%, and everyone else 33%.

There ARE a couple of "race conditions" that could cause players that DID NOT tag the monster to get 33%+33% exp, if the server receives the 0xC8 packet from two other players very VERY close together and the monster isnt set as dead for a player in one pass through before passing a check for that same thing in another pass through.

i stepped through many but not ALL possiblle combinations of players killing/tagging/nothing the monster in a little truth table to follow the logic checks manually, which im including just for funsies, so it should all work out.

Any thoughts or commends on the code would be VERY VERY appreciated <3 THANKS!!!

Code:
player            dead            balreadyexp                xp %
0*                1                    0                    77
1                2                                        33
2k                1                    2                    33+67
3*                1                    2                    33+44
 
Last edited:

Sodaboy

K-RAD!
Staff member
Gender
Male
Guildcard
11111111
Here's what i came up with (drop in replacement for case 0xC8):

I think you're overthinking it.

If you have exp share turned on, just don't process the experience portion until you get the C8 packet that is the last hit packet.

You can do the other stuff like kill counters normally.

Here's a chunk of code from how Ephinea does it (100% to last hit player, 80% to everyone else.):

C:
if ((l->expshare) && (!isBoss)) // New experience sharing
                    {
                        if ((client->decryptbuf[0x10] == 1) && (l->monsterData[mid].exp_shared == 0)) // Only process on last hit
                        {
                            l->monsterData[mid].exp_shared = 1;

                            for (ch = 0; ch < 4; ch++)
                            {
                                if (l->client[ch])
                                {
                                    XP = (unsigned)floor(saveXP * l->client[ch]->expRatio);

                                    if (l->client[ch] != client)
                                    {
                                        // Floor Check

                                        if (l->floor[ch] != l->floor[client->clientID])
                                            continue;

                                        // Range Check

                                        if ((pow(l->clientx[client->clientID] - l->clientx[ch], 2) + pow(l->clienty[client->clientID] - l->clienty[ch], 2)) > (double)(PSO2_DROP_RADIUS * PSO2_DROP_RADIUS))
                                            continue;

                                        // Death check

                                        if (l->client[ch]->dead)
                                            continue;

                                        XP = (XP * 80) / 100L; // Reduce experience for non-final blow.
                                    }

                                    AddExp((int)XP, l->client[ch]);

                                    l->client[ch]->kills++;
                                    l->client[ch]->total_kill_counter++;

                                    ProcessSeasonalKills(l->client[ch], l, mid);
                                }
                            }
                        }
                    }
                    else
                    {
                        if (client->decryptbuf[0x10] != 1) // Not the last player who hit?
                            XP = (XP * 80) / 100L;

                        AddExp((int)XP, client);

                        client->kills++;
                        client->total_kill_counter++;

                        ProcessSeasonalKills(client, l, mid);
                    }
 

PalasX

Member
right, and thats how i did it initially as per the suggestions in this thread, but i ran into two issues:
I wanted to give players that tagged the mob more EXP than people who didnt, but less than the player who killed it.
i had NO IDEA AT ALL if the little purple "EXP + XXXX" message shown in the client when a monster dies that you tagged/killed requires a response to the 0xC8 packet that you sent, or if that is all handled locally. I assumed it HAD to come from the server, since it shows the correct EXP amount even when there is an experience multiplier being applied server side.

my next step, of course, is to wrap this all in in a conditional check for a local per-game flag, such as $EXP_SHARE_ENABLED or somethingm and add another chat command to enable/disable it. Any advice on the best place to store those type of per-game instanced variables, or where/when in the code those things get constructed/destroyed/garbage-collected?

If i can figure that out, i can make a local $EXP_MULTIPLIER too and a chat command to change that, and have it override the global inside the 0xC8 case :)

:EDIT: OH DANG! can i just add random variables to the monster like that? having a .exp_shared would probably have been easier than overloading the .dead variable to use it as both a dead indicator and an "already processed exp share" checker :D guess i'll go check out where monster types are declared and see what other fun methods and variables are available for them :)
 

Sodaboy

K-RAD!
Staff member
Gender
Male
Guildcard
11111111
So if you want to give players who tagged the monster more experience, you can set a flag whenever they send a 60 0A command (not 60 C8).

60 0A is the packet they send whenever they interact with a monster and do damage to it or debuff it.

I can't remember if the base Tethealla already had a "hit" value in the monster structure that got set whenever a player hit the monster. If it doesn't, you can just add it yourself and set that value to 1 when a player hits the monster with the 60 0A packet. (Make sure this is reset back to 0 whenever a game is initialized or a new quest is loaded.)

You could then check the hit flag for the monster based on the client ID to distribute extra experience for tagging as appropriate.

Though, to be honest, the main reason for experience sharing should be to get rid of requiring people to tag monsters. Seems you're making things complicated for no good reason.

If you want people to be forced to tag monsters to get experience, you should just forego the idea of experience sharing. I feel you're still over complicating things. But, hey, it's your server. Lol.
 

PalasX

Member
good suggestions on the 60 0A, thank you! i could just set a monster[mid].tagged[clientID] and keep tag of which clients have tagged a monster, wait for the last C8 packet, and do all the exp then. still not sure about the floaty purple "EXP + 1234" text that comes up on the client requiring an answer from the server to happen, i'll have to test later this weekend as im actually at work now :)

as for exp share being a solution to the problem of tagging in the first place.... heh you're probably right. :) i'd like to reward a player that actively has a helping hand in monster murder, but not punish those players that are keeping S&D up and healing people and what not either. I'll probably bump that 33% up to at least 50% in the future. if i find i need to keep tweaking it, i's gonna end up being a .INI variable :)

As for it being "my server", dude, bro, guy, hahaha. you and the rest of the people that put dev time and effort into the software are the SOLE reason i get to run this little slice of heaven, from dreamcast days through GC and into BB. half of the fun for me is getting it all setup, playing with the code, making little adjustments, etc. my server has had a MAX of 6 unique human beings log into it, and im blood related to 3 of them :) It's way more for the love of running it. Thanks again for putting the initial Teth source out there. Can't wait for the distant distant future when Ephinea's may be released "as-is", and let the users sort it out if they cant use it :D and if not, well, more Teth code to rummage through and update and change, so win-win :)
 
Top