Parcourir la source

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)
Playtester il y a 3 ans
Parent
commit
414d43dd05
6 fichiers modifiés avec 63 ajouts et 35 suppressions
  1. 5 0
      src/map/elemental.cpp
  2. 5 0
      src/map/homunculus.cpp
  3. 5 0
      src/map/mercenary.cpp
  4. 3 0
      src/map/pc.cpp
  5. 43 34
      src/map/status.cpp
  6. 2 1
      src/map/status.hpp

+ 5 - 0
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();

+ 5 - 0
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);
 

+ 5 - 0
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;

+ 3 - 0
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;
 

+ 43 - 34
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;
 }
 

+ 2 - 1
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