Mercenary, Regen and Frenzy Improvements (#8185)

- When a Mercenary gains a bonus, there will now be an animation
- Mercenary bonuses now last for 5 minutes instead of 10 minutes
- Fixed HP/SP recovery values of Mercenaries (and Elementals)
- Mercenaries now recover HP when walking
- Homunculi no longer recover SP when walking
- Mercenary natural recovery interval is 8s for HP and 6s for SP
- Homunculus natural recovery interval is 2s for HP and 4s for SP
- MER_CRASH now only deals 1 hit and can be cast-cancelled
- Frenzy now drains HP every 10 seconds instead of every 15 in pre-renewal
- Fixed SP cost Mercenary Frenzy (100 -> 200 SP)
- Killing monsters exactly 2 times below you base level now still counts as mercenary kill
- Fixes #8184 
- Fixes #7663
This commit is contained in:
Playtester 2024-03-27 11:48:05 +01:00 committed by GitHub
parent ed2d03d811
commit 1a004f0164
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 45 additions and 21 deletions

View File

@ -9641,7 +9641,7 @@ Body:
Hit: Single Hit: Single
HitCount: 1 HitCount: 1
Duration1: 300000 Duration1: 300000
Duration2: 15000 Duration2: 10000
Requires: Requires:
SpCost: 200 SpCost: 200
Status: Berserk Status: Berserk
@ -32467,9 +32467,9 @@ Body:
Hit: Single Hit: Single
HitCount: 1 HitCount: 1
Duration1: 300000 Duration1: 300000
Duration2: 15000 Duration2: 10000
Requires: Requires:
SpCost: 100 SpCost: 200
Status: Berserk Status: Berserk
- Id: 8207 - Id: 8207
Name: MA_DOUBLE Name: MA_DOUBLE
@ -33068,9 +33068,10 @@ Body:
Type: Weapon Type: Weapon
TargetType: Attack TargetType: Attack
Range: 1 Range: 1
Hit: Multi_Hit Hit: Single
HitCount: 3 HitCount: 1
Element: Weapon Element: Weapon
CastCancel: true
CastTime: 1000 CastTime: 1000
AfterCastActDelay: 2000 AfterCastActDelay: 2000
Duration2: 5000 Duration2: 5000

View File

@ -2712,6 +2712,7 @@ Body:
CalcFlags: CalcFlags:
Flee: true Flee: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2720,6 +2721,7 @@ Body:
CalcFlags: CalcFlags:
Watk: true Watk: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2728,6 +2730,7 @@ Body:
CalcFlags: CalcFlags:
MaxHp: true MaxHp: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2736,6 +2739,7 @@ Body:
CalcFlags: CalcFlags:
MaxSp: true MaxSp: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2744,6 +2748,7 @@ Body:
CalcFlags: CalcFlags:
Hit: true Hit: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true

View File

@ -46490,9 +46490,10 @@ Body:
Type: Weapon Type: Weapon
TargetType: Attack TargetType: Attack
Range: 1 Range: 1
Hit: Multi_Hit Hit: Single
HitCount: 3 HitCount: 1
Element: Weapon Element: Weapon
CastCancel: true
CastTime: 1000 CastTime: 1000
AfterCastActDelay: 2000 AfterCastActDelay: 2000
Duration2: 4500 Duration2: 4500

View File

@ -2822,6 +2822,7 @@ Body:
CalcFlags: CalcFlags:
Flee: true Flee: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2830,6 +2831,7 @@ Body:
CalcFlags: CalcFlags:
Watk: true Watk: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2838,6 +2840,7 @@ Body:
CalcFlags: CalcFlags:
MaxHp: true MaxHp: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2846,6 +2849,7 @@ Body:
CalcFlags: CalcFlags:
MaxSp: true MaxSp: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true
@ -2854,6 +2858,7 @@ Body:
CalcFlags: CalcFlags:
Hit: true Hit: true
Flags: Flags:
BlEffect: true
NoDispell: true NoDispell: true
NoBanishingBuster: true NoBanishingBuster: true
NoClearance: true NoClearance: true

View File

@ -6579,7 +6579,8 @@ void clif_status_change(struct block_list *bl, int type, int flag, t_tick tick,
sd = BL_CAST(BL_PC, bl); sd = BL_CAST(BL_PC, bl);
if (!(status_efst_get_bl_type((efst_type)type)&bl->type)) // only send status changes that actually matter to the client // Check if current bl type is in the returned bitmask and only send status changes that actually matter to the client
if (!(status_efst_get_bl_type(static_cast<efst_type>(type)) & bl->type))
return; return;
clif_status_change_sub(bl, bl->id, type, flag, tick, val1, val2, val3, ((sd ? (pc_isinvisible(sd) ? SELF : AREA) : AREA_WOS))); clif_status_change_sub(bl, bl->id, type, flag, tick, val1, val2, val3, ((sd ? (pc_isinvisible(sd) ? SELF : AREA) : AREA_WOS)));

View File

@ -421,7 +421,7 @@ bool mercenary_dead(s_mercenary_data *md) {
void mercenary_killbonus(s_mercenary_data *md) { void mercenary_killbonus(s_mercenary_data *md) {
std::vector<sc_type> scs = { SC_MERC_FLEEUP, SC_MERC_ATKUP, SC_MERC_HPUP, SC_MERC_SPUP, SC_MERC_HITUP }; std::vector<sc_type> scs = { SC_MERC_FLEEUP, SC_MERC_ATKUP, SC_MERC_HPUP, SC_MERC_SPUP, SC_MERC_HITUP };
sc_start(&md->bl,&md->bl, util::vector_random(scs), 100, rnd_value(1, 5), 600000); sc_start(&md->bl,&md->bl, util::vector_random(scs), 100, rnd_value(1, 5), 300000); //Bonus lasts for 5 minutes
} }
/** /**

View File

@ -3109,8 +3109,8 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
achievement_update_objective(sd, AG_BATTLE, 1, md->mob_id); achievement_update_objective(sd, AG_BATTLE, 1, md->mob_id);
} }
// The master or Mercenary can increase the kill count // The master or Mercenary can increase the kill count, if the monster level is greater or equal than half the baselevel of the master
if (sd->md && src && (src->type == BL_PC || src->type == BL_MER) && mob->lv > sd->status.base_level / 2) if (sd->md && src && (src->type == BL_PC || src->type == BL_MER) && mob->lv >= sd->status.base_level / 2)
mercenary_kills(sd->md); mercenary_kills(sd->md);
} }

View File

@ -5261,16 +5261,16 @@ void status_calc_regen(struct block_list *bl, struct status_data *status, struct
regen->sp = cap_value(val, 1, SHRT_MAX); regen->sp = cap_value(val, 1, SHRT_MAX);
} }
} else if( bl->type == BL_MER ) { } else if( bl->type == BL_MER ) {
val = (status->max_hp * status->vit / 10000 + 1) * 6; val = static_cast<decltype(val)>((status->max_hp * status->vit / 10000.0 + 1.0) * 6.0);
regen->hp = cap_value(val, 1, SHRT_MAX); regen->hp = cap_value(val, 1, SHRT_MAX);
val = (status->max_sp * (status->int_ + 10) / 750) + 1; val = static_cast<decltype(val)>((status->max_sp * (status->int_ + 10.0) / 750.0) + 1.0);
regen->sp = cap_value(val, 1, SHRT_MAX); regen->sp = cap_value(val, 1, SHRT_MAX);
} else if( bl->type == BL_ELEM ) { } else if( bl->type == BL_ELEM ) {
val = (status->max_hp * status->vit / 10000 + 1) * 6; val = static_cast<decltype(val)>((status->max_hp * status->vit / 10000.0 + 1.0) * 6.0);
regen->hp = cap_value(val, 1, SHRT_MAX); regen->hp = cap_value(val, 1, SHRT_MAX);
val = (status->max_sp * (status->int_ + 10) / 750) + 1; val = static_cast<decltype(val)>((status->max_sp * (status->int_ + 10.0) / 750.0) + 1.0);
regen->sp = cap_value(val, 1, SHRT_MAX); regen->sp = cap_value(val, 1, SHRT_MAX);
} }
} }
@ -15237,10 +15237,14 @@ static int status_natural_heal(struct block_list* bl, va_list args)
ud = unit_bl2ud(bl); ud = unit_bl2ud(bl);
if (flag&(RGN_HP|RGN_SHP|RGN_SSP) && ud && ud->walktimer != INVALID_TIMER) { if (ud && ud->walktimer != INVALID_TIMER) {
flag &= ~(RGN_SHP|RGN_SSP); flag &= ~(RGN_SHP|RGN_SSP);
if(!regen->state.walk) //Mercenaries recover HP even while walking
if(bl->type != BL_MER && !regen->state.walk)
flag &= ~RGN_HP; flag &= ~RGN_HP;
//Homunculus don't recover SP while walking
if (bl->type == BL_HOM && !regen->state.walk)
flag &= ~RGN_SP;
} }
if (flag&(RGN_HP|RGN_SP)) { if (flag&(RGN_HP|RGN_SP)) {
@ -15256,11 +15260,15 @@ static int status_natural_heal(struct block_list* bl, va_list args)
if (flag&RGN_HP) { if (flag&RGN_HP) {
// Interval to next recovery tick // Interval to next recovery tick
rate = (int)(battle_config.natural_healhp_interval / (regen->rate.hp/100. * multi)); rate = (int)(battle_config.natural_healhp_interval / (regen->rate.hp/100. * multi));
if (ud && ud->walktimer != INVALID_TIMER) // Half recovery while moving only applies to players with certain traits
if (sd && ud && ud->walktimer != INVALID_TIMER)
rate *= 2; rate *= 2;
// Homun HP regen fix (they should regen as if they were sitting (twice as fast) // Homun HP regen fix (2 seconds instead of 6 seconds)
if(bl->type == BL_HOM) if(bl->type == BL_HOM)
rate /= 2; rate /= 3;
// Mercenary HP regen fix (8 seconds instead of 6 seconds)
if (bl->type == BL_MER)
rate = (rate * 4) / 3;
// Our timer system isn't 100% accurate so make sure we use the closest interval // Our timer system isn't 100% accurate so make sure we use the closest interval
rate -= NATURAL_HEAL_INTERVAL / 2; rate -= NATURAL_HEAL_INTERVAL / 2;
@ -15281,9 +15289,12 @@ static int status_natural_heal(struct block_list* bl, va_list args)
if(flag&RGN_SP) { if(flag&RGN_SP) {
// Interval to next recovery tick // Interval to next recovery tick
rate = (int)(battle_config.natural_healsp_interval / (regen->rate.sp/100. * multi)); rate = (int)(battle_config.natural_healsp_interval / (regen->rate.sp/100. * multi));
// Homun SP regen fix (they should regen as if they were sitting (twice as fast) // Homun SP regen fix (4 seconds instead of 8 seconds)
if(bl->type==BL_HOM) if(bl->type==BL_HOM)
rate /= 2; rate /= 2;
// Mercenary SP regen fix (6 seconds instead of 8 seconds)
if (bl->type == BL_MER)
rate = (rate * 3) / 4;
#ifdef RENEWAL #ifdef RENEWAL
if (sd && (sd->class_&MAPID_UPPERMASK) == MAPID_MONK && if (sd && (sd->class_&MAPID_UPPERMASK) == MAPID_MONK &&
sc && sc->getSCE(SC_EXPLOSIONSPIRITS) && (!sc->getSCE(SC_SPIRIT) || sc->getSCE(SC_SPIRIT)->val2 != SL_MONK)) sc && sc->getSCE(SC_EXPLOSIONSPIRITS) && (!sc->getSCE(SC_SPIRIT) || sc->getSCE(SC_SPIRIT)->val2 != SL_MONK))