浏览代码

Correct Determination of Endure Effect (#9211)

- Damage motion (dmotion) is no longer set to 0 if the unit has the endure effect
  * This is because even on Endure the dmotion value sent to the client should remain unchanged
- Instead, added a function status_isendure that determines if a unit has the endure effect
  * The function can also return if the unit can be stopped
- Added a new variable endure_tick that contains the time until which a unit cannot be stopped
  * It is not used for everything yet, SC_ENDURE and no_walk_delay are still separate from it
  * When a fire or undead unit is hit by firewall, it now has the endure effect for 2000ms
  * When a unit uses backslide, it now has the endure effect for 200ms if it didn't have it before
  * NPC_BARRIER and NPC_KEEPING also grant endure for their duration (only affects display)
- Damage packet sent to client is now accurate in regards to damage type and dmotion
  * Bosses no longer have infinite endure (but still cannot be stopped in renewal)
  * SC_RUN and SC_WUGDASH also don't grant endure anymore, just prevent stopping
- Moved hardcoded Endure in GVG check to status_disabled.txt
  * Infinite endure from items will no longer be started on the corresponding maps
- Fixes #9068
Playtester 1 月之前
父节点
当前提交
420ebb272b
共有 8 个文件被更改,包括 97 次插入79 次删除
  1. 5 1
      db/status_disabled.txt
  2. 8 2
      src/map/battle.cpp
  3. 11 22
      src/map/clif.cpp
  4. 4 0
      src/map/skill.cpp
  5. 62 51
      src/map/status.cpp
  6. 1 0
      src/map/status.hpp
  7. 5 3
      src/map/unit.cpp
  8. 1 0
      src/map/unit.hpp

+ 5 - 1
db/status_disabled.txt

@@ -25,10 +25,14 @@
 // Example:
 // SC_ENDURE,4   // Endure status will be removed when the player enters GvG and WoE Castle maps; Also cannot be inflicted again.
 
+//----------------------------------------------------------------------------
+// Disabled/Removed on all GVG and battleground maps and in WoE:TE Castles
+//----------------------------------------------------------------------------
+SC_ENDURE,28
+
 //----------------------------------------------------------------------------
 // Disabled/Removed statuses in WoE:TE Castles (16)
 //----------------------------------------------------------------------------
-SC_ENDURE,16
 SC_BERSERK,16
 SC_ASSUMPTIO,16
 

+ 8 - 2
src/map/battle.cpp

@@ -8153,12 +8153,18 @@ struct Damage battle_calc_magic_attack(struct block_list *src,struct block_list
 
 	switch(skill_id) {
 		case MG_FIREWALL:
-			if ( tstatus->def_ele == ELE_FIRE || battle_check_undead(tstatus->race, tstatus->def_ele) )
+			if (tstatus->def_ele == ELE_FIRE || battle_check_undead(tstatus->race, tstatus->def_ele)) {
 				ad.blewcount = 0; //No knockback
+				// Fire and undead units hit by firewall cannot be stopped for 2 seconds
+				if (unit_data* ud = unit_bl2ud(target); ud != nullptr)
+					ud->endure_tick = gettick() + 2000;
+				break;
+			}
 			[[fallthrough]];
 		case NJ_KAENSIN:
 		case PR_SANCTUARY:
-			ad.dmotion = 1; //No flinch animation.
+			// TODO: This code is a temporary workaround because right now we stop monsters for their dmotion which is not official
+			ad.dmotion = 1;
 			break;
 	}
 

+ 11 - 22
src/map/clif.cpp

@@ -5119,14 +5119,12 @@ void clif_getareachar_unit( map_session_data* sd,struct block_list *bl ){
 
 //Modifies the type of damage according to target status changes [Skotlex]
 //Aegis data specifies that: 4 endure against single hit sources, 9 against multi-hit.
-static enum e_damage_type clif_calc_delay(block_list& bl, e_damage_type type, int32 div, int64 damage, int32 delay) {
+static enum e_damage_type clif_calc_delay(block_list& bl, e_damage_type type, int32 div, int64 damage, int32 delay, t_tick tick) {
 	if (damage < 1)
 		return type;
 
-	// Currently we set dmotion to 0 to mark situations that should use the endure effect
-	// However, this also impacts units that naturally have 0 dmotion
-	// TODO: Collect all possible situations that create the endure effect and implement function
-	if (delay != 0)
+	// Check if unit has endure
+	if (!status_isendure(bl, tick, true))
 		return type;
 
 	// General change of type based on div against target with endure effect
@@ -5158,16 +5156,13 @@ static enum e_damage_type clif_calc_delay(block_list& bl, e_damage_type type, in
 /*==========================================
  * Estimates walk delay based on the damage criteria. [Skotlex]
  *------------------------------------------*/
-static int32 clif_calc_walkdelay( block_list &bl, int32 delay, e_damage_type type, int64 damage, int32 div_ ) {
+static int32 clif_calc_walkdelay(block_list &bl, int32 delay, e_damage_type type, int64 damage, int32 div_, t_tick tick) {
 	if (damage < 1)
 		return 0;
 
-	switch( type ) {
-		case DMG_ENDURE:
-		case DMG_MULTI_HIT_ENDURE:
-		case DMG_SPLASH_ENDURE:
-			return 0;
-	}
+	// Check if unit can be stopped by damage
+	if (status_isendure(bl, tick, false))
+		return 0;
 
 	if (bl.type == BL_PC) {
 		if (battle_config.pc_walk_delay_rate != 100)
@@ -5244,8 +5239,7 @@ int32 clif_damage(block_list& src, block_list& dst, t_tick tick, int32 sdelay, i
 	int32 damage = (int32)cap_value(sdamage,INT_MIN,INT_MAX);
 	int32 damage2 = (int32)cap_value(sdamage2,INT_MIN,INT_MAX);
 
-	if (type != DMG_MULTI_HIT_CRITICAL)
-		type = clif_calc_delay( dst, type, div, damage+damage2, ddelay );
+	type = clif_calc_delay(dst, type, div, damage+damage2, ddelay, tick);
 
 	damage = static_cast<decltype(damage)>(clif_hallucination_damage(dst, damage));
 	damage2 = static_cast<decltype(damage2)>(clif_hallucination_damage(dst, damage2));
@@ -5316,13 +5310,8 @@ int32 clif_damage(block_list& src, block_list& dst, t_tick tick, int32 sdelay, i
 	if(&src == &dst) 
 		unit_setdir(&src, unit_getdir(&src));
 
-	// In case this assignment is bypassed by DMG_MULTI_HIT_CRITICAL
-	if( type == DMG_MULTI_HIT_CRITICAL ){
-		type = clif_calc_delay( dst, type, div, damage+damage2, ddelay );
-	}
-
 	//Return adjusted can't walk delay for further processing.
-	return clif_calc_walkdelay(dst, ddelay, type, damage+damage2, div);
+	return clif_calc_walkdelay(dst, ddelay, type, damage+damage2, div, tick);
 }
 
 /*==========================================
@@ -6042,7 +6031,7 @@ void clif_skill_cooldown( map_session_data &sd, uint16 skill_id, t_tick tick ){
 /// 0114 <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.W <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL)
 /// 01de <skill id>.W <src id>.L <dst id>.L <tick>.L <src delay>.L <dst delay>.L <damage>.L <level>.W <div>.W <type>.B (ZC_NOTIFY_SKILL2)
 int32 clif_skill_damage( block_list& src, block_list& dst, t_tick tick, int32 sdelay, int32 ddelay, int64 sdamage, int32 div, uint16 skill_id, uint16 skill_lv, e_damage_type type ){
-	type = clif_calc_delay( dst, type, div, sdamage, ddelay );
+	type = clif_calc_delay(dst, type, div, sdamage, ddelay, tick);
 	sdamage = clif_hallucination_damage( dst, sdamage );
 
 	PACKET_ZC_NOTIFY_SKILL packet{};
@@ -6095,7 +6084,7 @@ int32 clif_skill_damage( block_list& src, block_list& dst, t_tick tick, int32 sd
 	}
 
 	//Because the damage delay must be synced with the client, here is where the can-walk tick must be updated. [Skotlex]
-	return clif_calc_walkdelay( dst, ddelay, type, damage, div );
+	return clif_calc_walkdelay(dst, ddelay, type, damage, div, tick);
 }
 
 

+ 4 - 0
src/map/skill.cpp

@@ -10079,6 +10079,10 @@ int32 skill_castend_nodamage_id (struct block_list *src, struct block_list *bl,
 
 	case TF_BACKSLIDING: //This is the correct implementation as per packet logging information. [Skotlex]
 		{
+			// Backsliding makes you immune to being stopped for 200ms, but only if you don't have the endure effect yet
+			if (unit_data* ud = unit_bl2ud(bl); ud != nullptr && !status_isendure(*bl, tick, true))
+				ud->endure_tick = tick + 200;
+
 			int16 blew_count = skill_blown(src,bl,skill_get_blewcount(skill_id,skill_lv),unit_getdir(bl),(enum e_skill_blown)(BLOWN_IGNORE_NO_KNOCKBACK
 #ifdef RENEWAL
 			|BLOWN_DONT_SEND_PACKET

+ 62 - 51
src/map/status.cpp

@@ -81,7 +81,6 @@ static defType status_calc_mdef(struct block_list *bl, status_change *sc, int32)
 static int16 status_calc_mdef2(struct block_list *,status_change *,int32);
 static uint16 status_calc_speed(struct block_list *,status_change *,int32);
 static int16 status_calc_aspd_rate(struct block_list *,status_change *,int32);
-static uint16 status_calc_dmotion(struct block_list *bl, status_change *sc, int32 dmotion);
 #ifdef RENEWAL_ASPD
 static int16 status_calc_aspd(struct block_list *bl, status_change *sc, bool fixed);
 #endif
@@ -4297,8 +4296,10 @@ int32 status_calc_pc_sub(map_session_data* sd, uint8 opt)
 				sc->getSCE(SC_ENDURE)->val4 = 0;
 			status_change_end(&sd->bl, SC_ENDURE);
 		}
-		clif_status_load(&sd->bl, EFST_ENDURE, 1);
-		base_status->mdef++;
+		if (sd->bl.m >= 0 && !status_change_isDisabledOnMap(SC_ENDURE, map_getmapdata(sd->bl.m))) {
+			clif_status_load(&sd->bl, EFST_ENDURE, 1);
+			base_status->mdef++;
+		}
 	}
 
 // ----- CONCENTRATION CALCULATION -----
@@ -6411,22 +6412,15 @@ void status_calc_bl_main(struct block_list& bl, std::bitset<SCB_MAX> flag)
 	if(flag[SCB_DSPD]) {
 		int32 dmotion;
 		if( bl.type == BL_PC ) {
-			if (b_status->agi == status->agi)
-				status->dmotion = status_calc_dmotion(&bl, sc, b_status->dmotion);
-			else {
+			if (b_status->agi != status->agi) {
 				dmotion = 800-status->agi*4;
 				status->dmotion = cap_value(dmotion, 400, 800);
 				if(battle_config.pc_damage_delay_rate != 100)
 					status->dmotion = status->dmotion*battle_config.pc_damage_delay_rate/100;
-				// It's safe to ignore b_status->dmotion since no bonus affects it.
-				status->dmotion = status_calc_dmotion(&bl, sc, status->dmotion);
 			}
 		} else if( bl.type == BL_HOM ) {
 			dmotion = 800-status->agi*4;
 			status->dmotion = cap_value(dmotion, 400, 800);
-			status->dmotion = status_calc_dmotion(&bl, sc, b_status->dmotion);
-		} else { // Mercenary and mobs
-			status->dmotion = status_calc_dmotion(&bl, sc, b_status->dmotion);
 		}
 	}
 
@@ -8522,34 +8516,6 @@ static int16 status_calc_aspd_rate(struct block_list *bl, status_change *sc, int
 	return (int16)cap_value(aspd_rate,0,SHRT_MAX);
 }
 
-/**
- * Modifies the damage delay time based on status changes
- * The lower your delay, the quicker you can act after taking damage
- * @param bl: Object to change aspd [PC|MOB|HOM|MER|ELEM]
- * @param sc: Object's status change information
- * @param dmotion: Object's current damage delay
- * @return modified delay rate
- */
-static uint16 status_calc_dmotion(struct block_list *bl, status_change *sc, int32 dmotion)
-{
-	/// It has been confirmed on official servers that MvP mobs have no dmotion even without endure
-	if( bl->type == BL_MOB && status_get_class_(bl) == CLASS_BOSS )
-		return 0;
-
-	if (bl->type == BL_PC) {
-		if (map_flag_gvg2(bl->m) || map_getmapflag(bl->m, MF_BATTLEGROUND))
-			return (uint16)cap_value(dmotion, 0, USHRT_MAX);
-
-		if (((TBL_PC *)bl)->special_state.no_walk_delay)
-			return 0;
-	}
-
-	if (sc != nullptr && !sc->empty() && (sc->getSCE(SC_ENDURE) || sc->getSCE(SC_RUN) || sc->getSCE(SC_WUGDASH)))
-		return 0;
-
-	return (uint16)cap_value(dmotion,0,USHRT_MAX);
-}
-
 /**
 * Adds power atk modifications based on status changes
 * @param bl: Object to change patk [PC|MOB|HOM|MER|ELEM]
@@ -9295,6 +9261,47 @@ int32 status_isimmune(struct block_list *bl)
 	return 0;
 }
 
+/**
+ * Returns whether object can be stopped by damage or not
+ * This does not only include the status change "Endure" but also all other endure effects
+ * @param bl: Object to check [PC|MOB|HOM|MER|ELEM]
+ * @param tick: Current tick
+ * @param visible: If true, function returns if the endure effect should be shown on the client
+ * @return Whether object can be stopped (false) or not (true)
+ */
+bool status_isendure(block_list& bl, t_tick tick, bool visible)
+{
+	// Officially the bonus "no_walk_delay" is actually just SC_ENDURE with unlimited duration.
+	// Endure is forbidden on some maps, but the bonus is still set to true on such maps.
+	// That's why we need to check it here and disable the bonus to correctly mimic official behavior.
+	if (bl.m >= 0 && bl.type == BL_PC && !status_change_isDisabledOnMap(SC_ENDURE, map_getmapdata(bl.m))) {
+		if (reinterpret_cast<map_session_data&>(bl).special_state.no_walk_delay)
+			return true;
+	}
+
+	if (unit_data* ud = unit_bl2ud(&bl); ud != nullptr && DIFF_TICK(ud->endure_tick, tick) > 0)
+		return true;
+
+	if (status_change* sc = status_get_sc(&bl); sc != nullptr && !sc->empty()) {
+		// Officially endure also sets endure_tick
+		// However, we have a lot of extra logic for infinite endure, so we use the status change for now
+		if (sc->getSCE(SC_ENDURE) != nullptr)
+			return true;
+
+		// These status changes don't send "Endure" to the client, but still prevent being stopped
+		if (!visible && (sc->getSCE(SC_RUN) != nullptr || sc->getSCE(SC_WUGDASH) != nullptr))
+			return true;
+	}
+
+#ifdef RENEWAL
+	// Bosses in renewal (episode 20+) cannot be stopped by damage, but still show damage normally
+	if (!visible && bl.type == BL_MOB && status_get_class_(&bl) == CLASS_BOSS)
+		return true;
+#endif
+
+	return false;
+}
+
 /**
  * Get view data of an object 
  * @param bl: Object whose view data to get [PC|MOB|PET|HOM|MER|ELEM|NPC]
@@ -10685,12 +10692,14 @@ int32 status_change_start(struct block_list* src, struct block_list* bl,enum sc_
 			break;
 
 		case SC_KEEPING:
-		case SC_BARRIER: {
-			unit_data *ud = unit_bl2ud(bl);
-
-			if (ud)
-				ud->attackabletime = ud->canact_tick = ud->canmove_tick = gettick() + tick;
-		}
+		case SC_BARRIER:
+			if (unit_data* ud = unit_bl2ud(bl); ud != nullptr) {
+				t_tick endtick = gettick() + tick;
+				ud->attackabletime = endtick;
+				ud->canact_tick = endtick;
+				ud->canmove_tick = endtick;
+				ud->endure_tick = endtick;
+			}
 			break;
 		case SC_DECREASEAGI:
 		case SC_INCREASEAGI:
@@ -13357,12 +13366,14 @@ int32 status_change_end( struct block_list* bl, enum sc_type type, int32 tid ){
 
 	switch(type) {
 		case SC_KEEPING:
-		case SC_BARRIER: {
-			unit_data *ud = unit_bl2ud(bl);
-
-			if (ud)
-				ud->attackabletime = ud->canact_tick = ud->canmove_tick = gettick();
-		}
+		case SC_BARRIER:
+			if (unit_data* ud = unit_bl2ud(bl); ud != nullptr) {
+				t_tick endtick = gettick();
+				ud->attackabletime = endtick;
+				ud->canact_tick = endtick;
+				ud->canmove_tick = endtick;
+				ud->endure_tick = endtick;
+			}
 			break;
 		case SC_GRANITIC_ARMOR:
 			{

+ 1 - 0
src/map/status.hpp

@@ -3619,6 +3619,7 @@ status_change *status_get_sc(struct block_list *bl);
 
 bool status_isdead(block_list &bl);
 int32 status_isimmune(struct block_list *bl);
+bool status_isendure(block_list& bl, t_tick tick, bool visible);
 
 t_tick status_get_sc_def(struct block_list *src,struct block_list *bl, enum sc_type type, int32 rate, t_tick tick, unsigned char flag);
 int32 status_change_start(struct block_list* src, struct block_list* bl,enum sc_type type,int32 rate,int32 val1,int32 val2,int32 val3,int32 val4,t_tick duration,unsigned char flag, int32 delay = 0);

+ 5 - 3
src/map/unit.cpp

@@ -3419,9 +3419,11 @@ void unit_dataset(struct block_list *bl)
 	ud->skilltimer     = INVALID_TIMER;
 	ud->attacktimer    = INVALID_TIMER;
 	ud->steptimer      = INVALID_TIMER;
-	ud->attackabletime =
-	ud->canact_tick    =
-	ud->canmove_tick   = gettick();
+	t_tick tick = gettick();
+	ud->attackabletime = tick;
+	ud->canact_tick = tick;
+	ud->canmove_tick = tick;
+	ud->endure_tick = tick;
 	ud->sx = 8;
 	ud->sy = 8;
 }

+ 1 - 0
src/map/unit.hpp

@@ -43,6 +43,7 @@ struct unit_data {
 	t_tick attackabletime;
 	t_tick canact_tick;
 	t_tick canmove_tick;
+	t_tick endure_tick; // Time until which unit cannot be stopped
 	bool immune_attack; ///< Whether the unit is immune to attacks
 	uint8 dir;
 	unsigned char target_count;