From 414d43dd050673b79b28137e7bfc7b84384d6186 Mon Sep 17 00:00:00 2001 From: Playtester Date: Wed, 30 Mar 2022 12:13:42 +0200 Subject: [PATCH] Natural recovery formula and interval behavior (#6755) * Implemented official natural recovery formula and interval behavior (fixes #6754) * Fixed HP recovery per tick being 1 too high (after reaching 200 HP) * The interval will now work similar to official servers where it remembers the time of the last recovery and checks if the interval has passed since that time * The natural recovery interval will now be continuous even when at full health (i.e. if you are full while the interval passes and afterwards you lose HP/SP, you will have to wait for the next interval) * Fixed anything blocking recovery, such as walking, not resetting the natural recovery tick (e.g. you have to wait 6 seconds to recover HP after you stop walking, unless you have moving recovery) * Applies to players, homunculus, mercenaries and elementals * Note: This also works with custom intervals, but you should make sure they are multiples of 4*NATURAL_HEAL_INTERVAL, otherwise it will round to the closest possible interval (you can reduce the timer interval in map.hpp when needed) --- src/map/elemental.cpp | 5 +++ src/map/homunculus.cpp | 5 +++ src/map/mercenary.cpp | 5 +++ src/map/pc.cpp | 3 ++ src/map/status.cpp | 77 +++++++++++++++++++++++------------------- src/map/status.hpp | 3 +- 6 files changed, 63 insertions(+), 35 deletions(-) diff --git a/src/map/elemental.cpp b/src/map/elemental.cpp index 0031b4a048..e7200c32ae 100644 --- a/src/map/elemental.cpp +++ b/src/map/elemental.cpp @@ -227,6 +227,7 @@ void elemental_summon_init(s_elemental_data *ed) { */ int elemental_data_received(s_elemental *ele, bool flag) { map_session_data *sd; + t_tick tick = gettick(); if( (sd = map_charid2sd(ele->char_id)) == NULL ) return 0; @@ -260,6 +261,10 @@ int elemental_data_received(s_elemental *ele, bool flag) { ed->bl.x = ed->ud.to_x; ed->bl.y = ed->ud.to_y; + // Ticks need to be initialized before adding bl to map_addiddb + ed->regen.tick.hp = tick; + ed->regen.tick.sp = tick; + map_addiddb(&ed->bl); status_calc_elemental(ed,SCO_FIRST); ed->last_spdrain_time = ed->last_thinktime = gettick(); diff --git a/src/map/homunculus.cpp b/src/map/homunculus.cpp index 1480bf6488..80d93cef88 100644 --- a/src/map/homunculus.cpp +++ b/src/map/homunculus.cpp @@ -1081,6 +1081,7 @@ void hom_alloc(struct map_session_data *sd, struct s_homunculus *hom) { struct homun_data *hd; int i = 0; + t_tick tick = gettick(); nullpo_retv(sd); @@ -1115,6 +1116,10 @@ void hom_alloc(struct map_session_data *sd, struct s_homunculus *hom) hd->bl.x = hd->ud.to_x; hd->bl.y = hd->ud.to_y; + // Ticks need to be initialized before adding bl to map_addiddb + hd->regen.tick.hp = tick; + hd->regen.tick.sp = tick; + map_addiddb(&hd->bl); status_calc_homunculus(hd, SCO_FIRST); diff --git a/src/map/mercenary.cpp b/src/map/mercenary.cpp index 8594f4228c..95c919d40f 100644 --- a/src/map/mercenary.cpp +++ b/src/map/mercenary.cpp @@ -325,6 +325,7 @@ void merc_contract_init(s_mercenary_data *md) { bool mercenary_recv_data(s_mercenary *merc, bool flag) { map_session_data *sd; + t_tick tick = gettick(); if( (sd = map_charid2sd(merc->char_id)) == NULL ) return false; @@ -359,6 +360,10 @@ bool mercenary_recv_data(s_mercenary *merc, bool flag) md->bl.x = md->ud.to_x; md->bl.y = md->ud.to_y; + // Ticks need to be initialized before adding bl to map_addiddb + md->regen.tick.hp = tick; + md->regen.tick.sp = tick; + map_addiddb(&md->bl); status_calc_mercenary(md, SCO_FIRST); md->contract_timer = INVALID_TIMER; diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 7fb83267b0..080fff1f07 100755 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -1684,6 +1684,9 @@ bool pc_authok(struct map_session_data *sd, uint32 login_id2, time_t expiration_ sd->cansendmail_tick = tick; sd->idletime = last_tick; + sd->regen.tick.hp = tick; + sd->regen.tick.sp = tick; + for(int i = 0; i < MAX_SPIRITBALL; i++) sd->spirit_timer[i] = INVALID_TIMER; diff --git a/src/map/status.cpp b/src/map/status.cpp index 7cd0840c8f..a0bf0c7638 100644 --- a/src/map/status.cpp +++ b/src/map/status.cpp @@ -4510,7 +4510,7 @@ void status_calc_regen(struct block_list *bl, struct status_data *status, struct sd = BL_CAST(BL_PC,bl); sc = status_get_sc(bl); - val = 1 + (status->vit/5) + (status->max_hp/200); + val = (status->vit/5) + max(1, status->max_hp/200); if( sd && sd->hprecov_rate != 100 ) val = val*sd->hprecov_rate/100; @@ -14285,10 +14285,15 @@ static int status_natural_heal(struct block_list* bl, va_list args) sd = BL_CAST(BL_PC,bl); flag = regen->flag; - if (flag&RGN_HP && (status->hp >= status->max_hp || regen->state.block&1)) + if (flag&RGN_HP && (regen->state.block&1)) flag &= ~(RGN_HP|RGN_SHP); - if (flag&RGN_SP && (status->sp >= status->max_sp || regen->state.block&2)) + if (flag&RGN_SP && (regen->state.block&2)) flag &= ~(RGN_SP|RGN_SSP); + // Only skill-based regen is disabled at max HP/SP + if (flag&RGN_SHP && (status->hp >= status->max_hp)) + flag &= ~RGN_SHP; + if (flag&RGN_SSP && (status->sp >= status->max_sp)) + flag &= ~RGN_SSP; if (flag && ( status_isdead(bl) || @@ -14315,7 +14320,7 @@ static int status_natural_heal(struct block_list* bl, va_list args) while(sregen->tick.hp >= (unsigned int)battle_config.natural_heal_skill_interval) { sregen->tick.hp -= battle_config.natural_heal_skill_interval; if(status_heal(bl, sregen->hp, 0, 3) < sregen->hp) { // Full - flag &= ~(RGN_HP|RGN_SHP); + flag &= ~RGN_SHP; break; } } @@ -14328,7 +14333,7 @@ static int status_natural_heal(struct block_list* bl, va_list args) while(sregen->tick.sp >= (unsigned int)battle_config.natural_heal_skill_interval) { sregen->tick.sp -= battle_config.natural_heal_skill_interval; if(status_heal(bl, 0, sregen->sp, 3) < sregen->sp) { // Full - flag &= ~(RGN_SP|RGN_SSP); + flag &= ~RGN_SSP; break; } } @@ -14346,9 +14351,6 @@ static int status_natural_heal(struct block_list* bl, va_list args) flag &= ~RGN_HP; } - if (!flag) - return 0; - if (flag&(RGN_HP|RGN_SP)) { if(!vd) vd = status_get_viewdata(bl); @@ -14360,49 +14362,56 @@ static int status_natural_heal(struct block_list* bl, va_list args) // Natural Hp regen if (flag&RGN_HP) { - rate = (int)(natural_heal_diff_tick * (regen->rate.hp/100. * multi)); + // Interval to next recovery tick + rate = (int)(battle_config.natural_healhp_interval / (regen->rate.hp/100. * multi)); if (ud && ud->walktimer != INVALID_TIMER) - rate /= 2; + rate *= 2; // Homun HP regen fix (they should regen as if they were sitting (twice as fast) if(bl->type == BL_HOM) - rate *= 2; + rate /= 2; - regen->tick.hp += rate; + // Our timer system isn't 100% accurate so make sure we use the closest interval + rate -= NATURAL_HEAL_INTERVAL / 2; - if(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval) { - int val = 0; - do { - val += regen->hp; - regen->tick.hp -= battle_config.natural_healhp_interval; - } while(regen->tick.hp >= (unsigned int)battle_config.natural_healhp_interval); - if (status_heal(bl, val, 0, 1) < val) - flag &= ~RGN_SHP; // Full. + if(regen->tick.hp + rate <= natural_heal_prev_tick) { + regen->tick.hp = natural_heal_prev_tick; + if (status->hp >= status->max_hp) + flag &= ~(RGN_HP | RGN_SHP); + else if (status_heal(bl, regen->hp, 0, 1) < regen->hp) + flag &= ~RGN_SHP; // Full } } + else { + regen->tick.hp = natural_heal_prev_tick; + } // Natural SP regen if(flag&RGN_SP) { - rate = (int)(natural_heal_diff_tick * (regen->rate.sp/100. * multi)); + // Interval to next recovery tick + 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) if(bl->type==BL_HOM) - rate *= 2; + rate /= 2; #ifdef RENEWAL - if (bl->type == BL_PC && (((TBL_PC*)bl)->class_&MAPID_UPPERMASK) == MAPID_MONK && + if (sd && (sd->class_&MAPID_UPPERMASK) == MAPID_MONK && sc && sc->data[SC_EXPLOSIONSPIRITS] && (!sc->data[SC_SPIRIT] || sc->data[SC_SPIRIT]->val2 != SL_MONK)) - rate /= 2; // Tick is doubled in Fury state + rate *= 2; // Tick is doubled in Fury state #endif - regen->tick.sp += rate; - if(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval) { - int val = 0; - do { - val += regen->sp; - regen->tick.sp -= battle_config.natural_healsp_interval; - } while(regen->tick.sp >= (unsigned int)battle_config.natural_healsp_interval); - if (status_heal(bl, 0, val, 1) < val) - flag &= ~RGN_SSP; // full. + // Our timer system isn't 100% accurate so make sure we use the closest interval + rate -= NATURAL_HEAL_INTERVAL / 2; + + if(regen->tick.sp + rate <= natural_heal_prev_tick) { + regen->tick.sp = natural_heal_prev_tick; + if (status->sp >= status->max_sp) + flag &= ~(RGN_SP | RGN_SSP); + else if (status_heal(bl, 0, regen->sp, 1) < regen->sp) + flag &= ~RGN_SSP; // Full } } + else { + regen->tick.sp = natural_heal_prev_tick; + } if (!regen->sregen) return flag; @@ -14456,8 +14465,8 @@ static int status_natural_heal(struct block_list* bl, va_list args) */ static TIMER_FUNC(status_natural_heal_timer){ natural_heal_diff_tick = DIFF_TICK(tick,natural_heal_prev_tick); - map_foreachregen(status_natural_heal); natural_heal_prev_tick = tick; + map_foreachregen(status_natural_heal); return 0; } diff --git a/src/map/status.hpp b/src/map/status.hpp index 5bfec24169..96f34e1ac5 100644 --- a/src/map/status.hpp +++ b/src/map/status.hpp @@ -3012,7 +3012,8 @@ struct regen_data { //tick accumulation before healing. struct { - unsigned int hp,sp,shp,ssp; + t_tick hp, sp; //time of last natural recovery + unsigned int shp,ssp; } tick; //Regen rates. n/100