Jump to content
  • 0

Devotion and Defender Buff Inheritance Issue


agamanaros

Question


  • Group:  Members
  • Topic Count:  15
  • Topics Per Day:  0.00
  • Content Count:  46
  • Reputation:   0
  • Joined:  03/26/16
  • Last Seen:  

Hello everyone,

I'm currently working on a custom fix for an issue with the Devotion skill and its interaction with Defender on my private server. After numerous tests, I've run into a roadblock that I can't seem to resolve.

The problem is that on the first cast of Devotion while Defender is active, the party member receiving Devotion does not inherit the Defender buff. For the buff to apply, I need to deactivate and reactivate Defender after casting Devotion. This behavior is inconsistent and problematic during gameplay, particularly when the party member is dispelled by Clown or Professor. Even if I recast Devotion immediately, Defender is not inherited until I toggle it off and on again.

Here are the key tests I've performed:

  1. Initial Devotion with Defender Active: The party member does not receive the Defender buff on the first cast.
  2. Toggling Defender Off and On: After manually toggling Defender, the party member successfully inherits the buff.
  3. Post-Dispel Scenario: If the party member is dispelled, reapplying Devotion alone does not reinstate Defender; I need to recast Defender first.

I have tried integrating various code adjustments, including forced status updates and reapplications, but the issue persists.

If anyone has encountered a similar problem or has suggestions on how to ensure Defender properly inherits on the first Devotion cast, I would greatly appreciate the assistance.

Thanks in advance for your help!

Here is my status.cpp BEFORE:

		case SC_DEVOTION:
		{
			struct block_list *d_bl;
			struct status_change *d_sc;

			if( (d_bl = map_id2bl(val1)) && (d_sc = status_get_sc(d_bl)) && d_sc->count ) { // Inherits Status From Source
				const enum sc_type types[] = { SC_AUTOGUARD, SC_DEFENDER, SC_REFLECTSHIELD, SC_ENDURE };
				int i = (map_flag_gvg2(bl->m) || map_getmapflag(bl->m, MF_BATTLEGROUND))?2:3;
				while( i >= 0 ) {
					enum sc_type type2 = types[i];
					if( d_sc->data[type2] )
						status_change_start(d_bl, bl, type2, 10000, d_sc->data[type2]->val1, 0, 0, (type2 == SC_REFLECTSHIELD ? 1 : 0), skill_get_time(status_sc2skill(type2),d_sc->data[type2]->val1), (type2 == SC_DEFENDER) ? SCSTART_NOAVOID : SCSTART_NOAVOID|SCSTART_NOICON);
					i--;
				}
			}
			break;
		}

 

And my status.cpp AFTER:

		case SC_DEVOTION:
		{
		    struct block_list *d_bl;
		    struct status_change *d_sc;

		    // Get the devotion target (party member) and their status change structure
		    if ((d_bl = map_id2bl(val1)) && (d_sc = status_get_sc(d_bl)) && d_sc->count) {
		        struct status_change *src_sc = status_get_sc(bl); // Paladin's status changes
		        if (src_sc) {
		            // Check if Paladin has Defender active
		            if (src_sc->data[SC_DEFENDER]) {
		                // Get the level of Paladin's Defender
		                int defender_level = src_sc->data[SC_DEFENDER]->val1;

		                // Check if the devoted target already has Defender
		                if (!d_sc->data[SC_DEFENDER]) {
		                    // Apply Defender to the devotion target on the first cast
		                    status_change_start(
		                        d_bl, bl, SC_DEFENDER, 10000, defender_level, 0, 0, 0,
		                        skill_get_time(status_sc2skill(SC_DEFENDER), defender_level),
		                        SCSTART_NOAVOID
		                    );
		                } else {
		                    // If target already has Defender, update the timer and level
		                    struct status_change_entry *sce = d_sc->data[SC_DEFENDER];
		                    if (sce->timer != INVALID_TIMER) {
		                        // If the timer is active, update the Defender level and reset the timer
		                        delete_timer(sce->timer, status_change_timer);
		                    }
		                    sce->val1 = defender_level;  // Update Defender level
		                    sce->timer = add_timer(
		                        gettick() + skill_get_time(status_sc2skill(SC_DEFENDER), defender_level),
		                        status_change_timer,
		                        d_bl->id, SC_DEFENDER
		                    );
		                }
		            }

		            // Check and apply other buffs (Autoguard, Reflectshield, Endure)
		            const enum sc_type types[] = { SC_AUTOGUARD, SC_REFLECTSHIELD, SC_ENDURE };
		            int i = 0;

		            while (i < sizeof(types)/sizeof(types[0])) {
		                enum sc_type type = types[i];

		                if (src_sc->data[type]) {
		                    struct status_change_entry *sce = src_sc->data[type];
		                    int level = sce->val1;  // Get the level of the buff

		                    // Check if the target already has the status
		                    if (!d_sc->data[type]) {
		                        // Apply the status to the target (1st time cast)
		                        status_change_start(
		                            d_bl, bl, type, 10000, level, 0, 0, (type == SC_REFLECTSHIELD ? 1 : 0),
		                            skill_get_time(status_sc2skill(type), level),
		                            SCSTART_NOAVOID
		                        );
		                    } else {
		                        // Update the status on target (for all buffs except Defender)
		                        struct status_change_entry *target_sce = d_sc->data[type];
		                        target_sce->val1 = level;
		                        if (target_sce->timer != INVALID_TIMER) {
		                            delete_timer(target_sce->timer, status_change_timer);
		                        }
		                        target_sce->timer = add_timer(
		                            gettick() + skill_get_time(status_sc2skill(type), level),
		                            status_change_timer,
		                            d_bl->id, type
		                        );
		                    }
		                }
		                i++;
		            }
		        }
		    }
		    break;
		}

 

Link to comment
Share on other sites

4 answers to this question

Recommended Posts

  • 0

  • Group:  Members
  • Topic Count:  0
  • Topics Per Day:  0
  • Content Count:  2
  • Reputation:   0
  • Joined:  02/04/24
  • Last Seen:  

I'm going to be looking into this too as I also want to fix it for my server. This behaviour surprised me when I found out about it. I'm not sure if it's vanilla or not.

I looked into it for a little, but I couldn't understand why even when using the same code from the SC_DEFENDER section where it is properly applying Defender wasn't working when ported into the SC_DEVOTION section. If I remember correctly, they had the icon but not the damage reduction effect but I would have to re-test to be 100% certain on that.

Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  15
  • Topics Per Day:  0.00
  • Content Count:  46
  • Reputation:   0
  • Joined:  03/26/16
  • Last Seen:  

3 hours ago, Kins said:

I'm going to be looking into this too as I also want to fix it for my server. This behaviour surprised me when I found out about it. I'm not sure if it's vanilla or not.

I looked into it for a little, but I couldn't understand why even when using the same code from the SC_DEFENDER section where it is properly applying Defender wasn't working when ported into the SC_DEVOTION section. If I remember correctly, they had the icon but not the damage reduction effect but I would have to re-test to be 100% certain on that.

Thanks for sharing your observations! Initially, I encountered the same issue where SC_DEFENDER wasn’t inherited by the party member on the first cast of Devotion. The Paladin needed to manually recast Defender for the buff to apply properly.

Through further testing, I noticed another peculiar behavior: when spamming Devotion, the Defender icon appears on the party member, but the damage reduction effect doesn’t trigger. Instead of reduced damage, the party member takes 100% damage, suggesting that the effect itself isn't being properly inherited, only the visual status icon.

My current exploration has led me to suspect that there's a deeper issue with how SC_DEFENDER is initialized or propagated through Devotion. It might require a forced recalculation or proper flag handling to ensure that both the icon and effects apply consistently.

 

Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  0
  • Topics Per Day:  0
  • Content Count:  2
  • Reputation:   0
  • Joined:  02/04/24
  • Last Seen:  

Okay, I've got a solution in place, and I'll share it here. I think a better solution is probably possible, and I think this might be an actual bug that should be discussed with rAthena devs.

I thought similarly about the "propagation" when I noticed it seemed like the status effect of Defender was indeed being applied properly (had icon), but some of the effects of it were not being applied. I confirmed that when I cast Devotion on a target while I have Defender active (in both cases of it being the first devotion cast, and a recast) the target _will_ get the defender status icon, _will_ walk slow, but _will not_ get the damage reduction and _will not_ get the ASPD reduction. When Defender is recast, the damage reduction and aspd reduction are properly applied (I ran some tests with level 1 Defender for the ASPD check). The fact that some effects are being applied is what makes me think this is bug-level. If it was all or nothing, I could think "expected behaviour".

So, this meant the target is properly being devoted, but is not getting the effects properly. So I dug a little deeper. I tried to find where the effects of Defender are applied.

I think the place that the devotion target loses ASPD is also in status.cpp:

if (sc->getSCE(SC_DEFENDER))
    bonus -= sc->getSCE(SC_DEFENDER)->val4 / 10;

Since it doesn't work, maybe val4 probably isn't populated correctly.

There's another ASPD related bit lower:

if(sc->getSCE(SC_DEFENDER))
    aspd_rate += sc->getSCE(SC_DEFENDER)->val4;

Also relying on val4.

There's a damage related bit using val2 in battle.cpp:

damage -= damage * tsc->getSCE(SC_DEFENDER)->val2 / 100;

I think the place that the devotion target loses speed is in status.cpp:

if( sc->getSCE(SC_DEFENDER) )
    speed = max(speed, 200);

Note it does not rely on val2/val3/val4 from the status and is working as it should be.

But when you cast Devotion, the status_change_start call is setting them to 0 (or in the case of val4, (type2 == SC_REFLECTSHIELD ? 1 : 0) for some reason). val2 is being passed as 0 but it's used as the damage reduction variable, val3 is being passed as 0 which is fine since it's deprecated for defender afaik, and I'm not sure what's happening with val4 but that should be the ASPD reduction factor.

status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, 0, 0, (type2 == SC_REFLECTSHIELD ? 1 : 0), skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);

When you cast Defender, you can see these values being properly passed:

case SC_DEFENDER:
            if (!(flag&SCSTART_NOAVOID)) {
                val2 = 5 + 15*val1; // Damage reduction
                val3 = 0; // Unused, previously speed adjustment
                val4 = 250 - 50*val1; // Aspd adjustment

                if (sd) {
                    map_session_data *tsd;
                    int32 i;
                    for (i = 0; i < MAX_DEVOTION; i++) { // See if there are devoted characters, and pass the status to them. [Skotlex]
                        if (sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])))
                            status_change_start(src,&tsd->bl,type,10000,val1,val2,val3,val4,tick,SCSTART_NOAVOID);
                    }
                }
            }

 

Now, for the solution I've just put in place. It's working, but I think there are probably better solutions. You can revert all of your SC_DEVOTION code from the first post back to it's original state, and just modify the status_change_start call to:

status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val2:0), (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val3:0), (type2 == SC_REFLECTSHIELD ? 1 : (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val4:0)), skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);


From here, I think it would also be good to know how val1/val2/val3/val4 are being used by the other status' to be sure they aren't being broken somehow as well. Since I didn't take the time to understand how SC_AUTOGUARD, SC_REFLECTSHIELD and SC_ENDURE are working, I've just separated the DEFENDER portion from those to fix it separately. It might actually be possible to do something like this, instead:

status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, d_sc->getSCE(type2)->val2, d_sc->getSCE(type2)->val3,d_sc->getSCE(type2)->val4, skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);

(Also please note the last argument I changed to give icons for all the passed status', but you can use

(type2 == SC_DEFENDER) ? SCSTART_NOAVOID : SCSTART_NOAVOID|SCSTART_NOICON

if you'd like).

Edited by Kins
Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  15
  • Topics Per Day:  0.00
  • Content Count:  46
  • Reputation:   0
  • Joined:  03/26/16
  • Last Seen:  

13 hours ago, Kins said:

Okay, I've got a solution in place, and I'll share it here. I think a better solution is probably possible, and I think this might be an actual bug that should be discussed with rAthena devs.

I thought similarly about the "propagation" when I noticed it seemed like the status effect of Defender was indeed being applied properly (had icon), but some of the effects of it were not being applied. I confirmed that when I cast Devotion on a target while I have Defender active (in both cases of it being the first devotion cast, and a recast) the target _will_ get the defender status icon, _will_ walk slow, but _will not_ get the damage reduction and _will not_ get the ASPD reduction. When Defender is recast, the damage reduction and aspd reduction are properly applied (I ran some tests with level 1 Defender for the ASPD check). The fact that some effects are being applied is what makes me think this is bug-level. If it was all or nothing, I could think "expected behaviour".

So, this meant the target is properly being devoted, but is not getting the effects properly. So I dug a little deeper. I tried to find where the effects of Defender are applied.

I think the place that the devotion target loses ASPD is also in status.cpp:

if (sc->getSCE(SC_DEFENDER))
    bonus -= sc->getSCE(SC_DEFENDER)->val4 / 10;

Since it doesn't work, maybe val4 probably isn't populated correctly.

There's another ASPD related bit lower:

if(sc->getSCE(SC_DEFENDER))
    aspd_rate += sc->getSCE(SC_DEFENDER)->val4;

Also relying on val4.

There's a damage related bit using val2 in battle.cpp:

damage -= damage * tsc->getSCE(SC_DEFENDER)->val2 / 100;

I think the place that the devotion target loses speed is in status.cpp:

if( sc->getSCE(SC_DEFENDER) )
    speed = max(speed, 200);

Note it does not rely on val2/val3/val4 from the status and is working as it should be.

But when you cast Devotion, the status_change_start call is setting them to 0 (or in the case of val4, (type2 == SC_REFLECTSHIELD ? 1 : 0) for some reason). val2 is being passed as 0 but it's used as the damage reduction variable, val3 is being passed as 0 which is fine since it's deprecated for defender afaik, and I'm not sure what's happening with val4 but that should be the ASPD reduction factor.

status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, 0, 0, (type2 == SC_REFLECTSHIELD ? 1 : 0), skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);

When you cast Defender, you can see these values being properly passed:

case SC_DEFENDER:
            if (!(flag&SCSTART_NOAVOID)) {
                val2 = 5 + 15*val1; // Damage reduction
                val3 = 0; // Unused, previously speed adjustment
                val4 = 250 - 50*val1; // Aspd adjustment

                if (sd) {
                    map_session_data *tsd;
                    int32 i;
                    for (i = 0; i < MAX_DEVOTION; i++) { // See if there are devoted characters, and pass the status to them. [Skotlex]
                        if (sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])))
                            status_change_start(src,&tsd->bl,type,10000,val1,val2,val3,val4,tick,SCSTART_NOAVOID);
                    }
                }
            }

 

Now, for the solution I've just put in place. It's working, but I think there are probably better solutions. You can revert all of your SC_DEVOTION code from the first post back to it's original state, and just modify the status_change_start call to:

status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val2:0), (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val3:0), (type2 == SC_REFLECTSHIELD ? 1 : (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val4:0)), skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);


From here, I think it would also be good to know how val1/val2/val3/val4 are being used by the other status' to be sure they aren't being broken somehow as well. Since I didn't take the time to understand how SC_AUTOGUARD, SC_REFLECTSHIELD and SC_ENDURE are working, I've just separated the DEFENDER portion from those to fix it separately. It might actually be possible to do something like this, instead:

status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, d_sc->getSCE(type2)->val2, d_sc->getSCE(type2)->val3,d_sc->getSCE(type2)->val4, skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);

(Also please note the last argument I changed to give icons for all the passed status', but you can use

(type2 == SC_DEFENDER) ? SCSTART_NOAVOID : SCSTART_NOAVOID|SCSTART_NOICON

if you'd like).

I got it to work now sir, thank you @kins

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...