Browse Source

Official Icewall implementation and other fixes
- Reverted all the icewall-related changes done in SVN r15777 and following as testing shows they aren't official and are actually pretty exploitable (bugreport:7412)
- Instead implemented the official icewall characteristic that monsters can only leave an icewall cell to the west or south, the changes include:
* The "sight" path check no longer checks for the current cell so standing on an icewall allows you to see/attack into any direction
* The path finding will still ignore the current cell as before but the walk routine will not allow to walk east or north while standing on an icewall cell
* This leads monsters in the situation where they go through an AI loop not allowing them to escape the icewall (if their target is north or east of them)
* Monster in this situation will use idle skills and if they get attacked will use their rudeattacked skills if available, similar to traps like Spiderweb
* Added a configuration icewall_walk_block that allows to configure how long a monster should go through the AI loop before the server allows it any movement, this "safety" system is official and seems to equal about 75 AI loops; if you want to disable the whole icewall system so that monsters don't get stuck in icewall at all, just set this to 0
* Here are videos from jRO showing how this system works: http://ragdo.blog56.fc2.com/blog-entry-763.html
- Implemented the official calculation for "direction"; now you will be considered horizontal/vertical/diagonally aligned with a target cell in the exact same way as on official servers, this is for example used to determine whether an icewall or a firewall should be horizontal, vertical or diagonal; the only thing that is still unofficial is the default direction (officially always "west"); effectively now there are more situations considered diagonal than before
- Fixed a compiler warning (converting double to float)
- Further cleanups on the idle skill use code for immobile monsters and monsters near a player but without a target (now skill using will always go via mob_unlocktarget)
* This also fixes that monsters switched to idle mode and start to use idle skills one second too late

Playtester 10 năm trước cách đây
mục cha
commit
5540d89cb0
11 tập tin đã thay đổi với 58 bổ sung78 xóa
  1. 9 0
      conf/battle/skill.conf
  2. 1 1
      src/map/atcommand.c
  3. 2 1
      src/map/battle.c
  4. 1 0
      src/map/battle.h
  5. 12 13
      src/map/map.c
  6. 0 7
      src/map/map.h
  7. 13 22
      src/map/mob.c
  8. 1 0
      src/map/mob.h
  9. 2 5
      src/map/path.c
  10. 0 27
      src/map/skill.c
  11. 17 2
      src/map/unit.c

+ 9 - 0
conf/battle/skill.conf

@@ -313,3 +313,12 @@ cart_revo_knockback: yes
 
 // On official servers, Arrow Shower blow direction always rely on skill placed location to target instead of caster to target
 arrow_shower_knockback: yes
+
+// How many attempts should a monster need until it can escape from an icewall casted directly on it?
+// On official servers, monsters can only leave an icewall to the west and south. If their target is north or east of them
+// they will continously try to chase it but fail doing so. This brings them into a loop during which they will cast idle
+// and rudeattacked skills (if attacked). Official servers have a safety system that eventually allows monsters to escape
+// when their walk routine failed many times in row so they won't stay on the loop endlessly. The time for this seems to be
+// around 15 seconds for fast monsters and 35 seconds for slow monsters, this equals about 75 attempts.
+// Set this to 0 if you don't want monsters to be stuck in icewalls at all.
+icewall_walk_block: 75

+ 1 - 1
src/map/atcommand.c

@@ -7057,7 +7057,7 @@ ACMD_FUNC(mobinfo)
 				if (mob->mvpitem[i].nameid <= 0 || (item_data = itemdb_exists(mob->mvpitem[i].nameid)) == NULL)
 					continue;
 				//Because if there are 3 MVP drops at 50%, the first has a chance of 50%, the second 25% and the third 12.5%
-				mvppercent = (float)mob->mvpitem[i].p * mvpremain / 10000.0;
+				mvppercent = (float)mob->mvpitem[i].p * mvpremain / 10000.0f;
 				if(battle_config.item_drop_mvp_mode == 0) {
 					mvpremain -= mvppercent;
 				}

+ 2 - 1
src/map/battle.c

@@ -7900,7 +7900,8 @@ static const struct _battle_data {
 	{ "arrow_shower_knockback",             &battle_config.arrow_shower_knockback,          1,      0,      1,              },
 	{ "devotion_rdamage_skill_only",        &battle_config.devotion_rdamage_skill_only,     1,      0,      1,              },
 	{ "max_extended_aspd",                  &battle_config.max_extended_aspd,               193,    100,    199,            },
-	{ "monster_chase_refresh",              &battle_config.mob_chase_refresh,               1,      0,      30,             }
+	{ "monster_chase_refresh",              &battle_config.mob_chase_refresh,               1,      0,      30,             },
+	{ "icewall_walk_block",                 &battle_config.icewall_walk_block,              75,     0,      255,            }
 };
 
 #ifndef STATS_OPT_OUT

+ 1 - 0
src/map/battle.h

@@ -572,6 +572,7 @@ extern struct Battle_Config
 	int devotion_rdamage_skill_only;
 	int max_extended_aspd;
 	int mob_chase_refresh; //How often a monster should refresh its chase [Playtester]
+	int icewall_walk_block; //How long a monster should stay trapped in icewall [Playtester]
 } battle_config;
 
 void do_init_battle(void);

+ 12 - 13
src/map/map.c

@@ -2585,28 +2585,27 @@ uint8 map_calc_dir(struct block_list* src, int16 x, int16 y)
 	}
 	else if( dx >= 0 && dy >=0 )
 	{	// upper-right
-		if( dx*2 <= dy )      dir = 0;	// up
-		else if( dx > dy*2 )  dir = 6;	// right
-		else                  dir = 7;	// up-right
+		if( dx*2 < dy || dx == 0 )         dir = 0;	// up
+		else if( dx > dy*2+1 || dy == 0 )  dir = 6;	// right
+		else                               dir = 7;	// up-right
 	}
 	else if( dx >= 0 && dy <= 0 )
 	{	// lower-right
-		if( dx*2 <= -dy )     dir = 4;	// down
-		else if( dx > -dy*2 ) dir = 6;	// right
-		else                  dir = 5;	// down-right
+		if( dx*2 < -dy || dx == 0 )        dir = 4;	// down
+		else if( dx > -dy*2+1 || dy == 0 ) dir = 6;	// right
+		else                               dir = 5;	// down-right
 	}
 	else if( dx <= 0 && dy <= 0 )
 	{	// lower-left
-		if( dx*2 >= dy )      dir = 4;	// down
-		else if( dx < dy*2 )  dir = 2;	// left
-		else                  dir = 3;	// down-left
+		if( dx*2 > dy || dx == 0 )         dir = 4;	// down
+		else if( dx < dy*2-1 || dy == 0 )  dir = 2;	// left
+		else                               dir = 3;	// down-left
 	}
 	else
 	{	// upper-left
-		if( -dx*2 <= dy )     dir = 0;	// up
-		else if( -dx > dy*2 ) dir = 2;	// left
-		else                  dir = 1;	// up-left
-
+		if( -dx*2 < dy || dx == 0 )        dir = 0;	// up
+		else if( -dx > dy*2+1 || dy == 0)  dir = 2;	// left
+		else                               dir = 1;	// up-left
 	}
 	return dir;
 }

+ 0 - 7
src/map/map.h

@@ -703,13 +703,6 @@ struct map_data {
 #ifdef ADJUST_SKILL_DAMAGE
 	struct s_skill_damage skill_damage[MAX_MAP_SKILL_MODIFIER];
 #endif
-	/**
-	 * Ice wall reference counter for bugreport:3574
-	 * - since there are a thounsand mobs out there in a lot of maps checking on,
-	 * - every targetting for icewall on attack path would just be a waste, so,
-	 * - this counter allows icewall checking be only run when there is a actual ice wall on the map
-	 **/
-	int icewall_num;
 	// Instance Variables
 	int instance_id;
 	int instance_src_map;

+ 13 - 22
src/map/mob.c

@@ -1092,15 +1092,6 @@ static int mob_ai_sub_hard_activesearch(struct block_list *bl,va_list ap)
 			((*target) == NULL || !check_distance_bl(&md->bl, *target, dist)) &&
 			battle_check_range(&md->bl,bl,md->db->range2)
 		) { //Pick closest target?
-
-			if( map[bl->m].icewall_num &&
-				!path_search_long(NULL,bl->m,md->bl.x,md->bl.y,bl->x,bl->y,CELL_CHKICEWALL) ) {
-
-				if( !check_distance_bl(&md->bl, bl, status_get_range(&md->bl) ) )
-					return 0;
-
-			}
-
 			(*target) = bl;
 			md->target_id=bl->id;
 			md->min_chase= dist + md->db->range3;
@@ -1306,8 +1297,7 @@ int mob_unlocktarget(struct mob_data *md, unsigned int tick)
 		md->state.skillstate = MSS_IDLE;
 	case MSS_IDLE:
 		// Idle skill.
-		if ((md->target_id || !(++md->ud.walk_count%IDLE_SKILL_INTERVAL)) &&
-			mobskill_use(md, tick, -1))
+		if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL) && mobskill_use(md, tick, -1))
 			break;
 		//Random walk.
 		if (!md->master_id &&
@@ -1474,6 +1464,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
 					|| md->sc.data[SC_BITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNSTRAP]
 					|| md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target.
 					|| !mob_can_reach(md, tbl, md->min_chase, MSS_RUSH)
+					|| md->walktoxy_fail_count > 0
 					)
 			&&  md->state.attacked_count++ >= RUDE_ATTACKED_COUNT
 			&&  !mobskill_use(md, tick, MSC_RUDEATTACKED) // If can't rude Attack
@@ -1497,6 +1488,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
 					|| md->sc.data[SC_BITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNSTRAP]
 					|| md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target.
 					|| !mob_can_reach(md, abl, dist+md->db->range3, MSS_RUSH)
+					|| md->walktoxy_fail_count > 0
 					)
 				) )
 			{ // Rude attacked
@@ -1573,7 +1565,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
 			}
 		}
 
-		//This handles triggering idle walk/skill.
+		//This handles triggering idle/walk skill.
 		mob_unlocktarget(md, tick);
 		return true;
 	}
@@ -1586,14 +1578,14 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
 			return true; //Already locked.
 		if (md->lootitem == NULL)
 		{	//Can't loot...
-			mob_unlocktarget (md, tick);
+			mob_unlocktarget(md, tick);
 			return true;
 		}
 		if (!check_distance_bl(&md->bl, tbl, 1))
 		{	//Still not within loot range.
 			if (!(mode&MD_CANMOVE))
 			{	//A looter that can't move? Real smart.
-				mob_unlocktarget(md,tick);
+				mob_unlocktarget(md, tick);
 				return true;
 			}
 			if (!can_move) //Stuck. Wait before walking.
@@ -1626,8 +1618,8 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
 			unit_set_walkdelay(&md->bl, tick, md->status.amotion, 1);
 		}
 		//Clear item.
-		map_clearflooritem (tbl);
-		mob_unlocktarget (md,tick);
+		map_clearflooritem(tbl);
+		mob_unlocktarget(md, tick);
 		return true;
 	}
 
@@ -1656,12 +1648,11 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
 
 	//Out of range...
 	if (!(mode&MD_CANMOVE) || (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0))
-	{	//Can't chase. Immobile and trapped mobs should unlock target and use an idle skill on next interval.
-		if ((md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER)) 
-		{ //Only unlock target to use idle skill if no more attack left
-			md->ud.walk_count = (md->ud.walk_count+1)%250;
-			if (!(md->ud.walk_count%IDLE_SKILL_INTERVAL))
-				mob_unlocktarget(md,tick);
+	{	//Can't chase. Immobile and trapped mobs should unlock target and use an idle skill.
+		if (md->ud.attacktimer == INVALID_TIMER)
+		{ //Only unlock target if no more attack delay left
+			//This handles triggering idle/walk skill.
+			mob_unlocktarget(md,tick);
 		}
 		return true;
 	}

+ 1 - 0
src/map/mob.h

@@ -168,6 +168,7 @@ struct mob_data {
 	short move_fail_count;
 	short lootitem_count;
 	short min_chase;
+	unsigned char walktoxy_fail_count; //Pathfinding succeeds but the actual walking failed (e.g. Icewall lock)
 
 	int deletetimer;
 	int master_id,master_dist;

+ 2 - 5
src/map/path.c

@@ -143,9 +143,6 @@ bool path_search_long(struct shootpath_data *spd,int16 m,int16 x0,int16 y0,int16
 	spd->x[0] = x0;
 	spd->y[0] = y0;
 
-	if (map_getcellp(md,x1,y1,cell))
-		return false;
-
 	if (dx > abs(dy)) {
 		weight = dx;
 		spd->ry = 1;
@@ -156,8 +153,6 @@ bool path_search_long(struct shootpath_data *spd,int16 m,int16 x0,int16 y0,int16
 
 	while (x0 != x1 || y0 != y1)
 	{
-		if (map_getcellp(md,x0,y0,cell))
-			return false;
 		wx += dx;
 		wy += dy;
 		if (wx >= weight) {
@@ -177,6 +172,8 @@ bool path_search_long(struct shootpath_data *spd,int16 m,int16 x0,int16 y0,int16
 			spd->y[spd->len] = y0;
 			spd->len++;
 		}
+		if (map_getcellp(md,x0,y0,cell))
+			return false;
 	}
 
 	return true;

+ 0 - 27
src/map/skill.c

@@ -11852,28 +11852,6 @@ static bool skill_dance_switch(struct skill_unit* unit, int flag)
 
 	return true;
 }
-/**
- * Upon Ice Wall cast it checks all nearby mobs to find any who may be blocked by the IW
- */
-static int skill_icewall_block(struct block_list *bl,va_list ap) {
-	struct block_list *target = NULL;
-	struct mob_data *md = ((TBL_MOB*)bl);
-
-	nullpo_ret(bl);
-	nullpo_ret(md);
-	if( !md->target_id || ( target = map_id2bl(md->target_id) ) == NULL )
-		return 0;
-
-	if( path_search_long(NULL,bl->m,bl->x,bl->y,target->x,target->y,CELL_CHKICEWALL) )
-		return 0;
-
-	if( !check_distance_bl(bl, target, status_get_range(bl) ) ) {
-		mob_unlocktarget(md,gettick());
-		mob_stop_walking(md,1);
-	}
-
-	return 0;
-}
 
 /**
  * Initializes and sets a ground skill / skill unit. Usually called after skill_casted_pos() or skill_castend_map()
@@ -12365,9 +12343,6 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 
 	//success, unit created.
 	switch( skill_id ) {
-		case WZ_ICEWALL:
-			map_foreachinrange(skill_icewall_block, src, AREA_SIZE, BL_MOB);
-			break;
 		case NJ_TATAMIGAESHI: //Store number of tiles.
 			group->val1 = group->alive_count;
 			break;
@@ -16894,7 +16869,6 @@ struct skill_unit *skill_initunit(struct skill_unit_group *group, int idx, int x
 			map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,5);
 			clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,5,AREA);
 			skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,true);
-			map[unit->bl.m].icewall_num++;
 			break;
 		case SA_LANDPROTECTOR:
 			skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,true);
@@ -16952,7 +16926,6 @@ int skill_delunit(struct skill_unit* unit)
 			map_setgatcell(unit->bl.m,unit->bl.x,unit->bl.y,unit->val2);
 			clif_changemapcell(0,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2,ALL_SAMEMAP); // hack to avoid clientside cell bug
 			skill_unitsetmapcell(unit,WZ_ICEWALL,group->skill_lv,CELL_ICEWALL,false);
-			map[unit->bl.m].icewall_num--;
 			break;
 		case SA_LANDPROTECTOR:
 			skill_unitsetmapcell(unit,SA_LANDPROTECTOR,group->skill_lv,CELL_LANDPROTECTOR,false);

+ 17 - 2
src/map/unit.c

@@ -369,6 +369,19 @@ static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data
 	if(map_getcell(bl->m,x+dx,y+dy,CELL_CHKNOPASS))
 		return unit_walktoxy_sub(bl);
 
+	//Monsters can only leave icewalls to the west and south
+	//But if movement fails more than icewall_walk_block times, they can ignore this rule
+	if(md && md->walktoxy_fail_count < battle_config.icewall_walk_block && map_getcell(bl->m,x,y,CELL_CHKICEWALL) && (dx > 0 || dy > 0)) {
+		//Needs to be done here so that rudeattack skills are invoked
+		md->walktoxy_fail_count++;
+		clif_fixpos(bl);
+		mob_unlocktarget(md, tick);
+		//Use idle skill at this point
+		if (!(++ud->walk_count%WALK_SKILL_INTERVAL))
+			mobskill_use(md, tick, -1);
+		return 0;
+	}
+
 	// Refresh view for all those we lose sight
 	map_foreachinmovearea(clif_outsight, bl, AREA_SIZE, dx, dy, sd?BL_ALL:BL_PC, bl);
 
@@ -398,6 +411,8 @@ static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data
 			pc_cell_basilica(sd);
 			break;
 		case BL_MOB:
+			//Movement was successful, reset walktoxy_fail_count
+			md->walktoxy_fail_count = 0;
 			if( map_getcell(bl->m,x,y,CELL_CHKNPC) ) {
 				if( npc_touch_areanpc2(md) )
 					return 0; // Warped
@@ -2463,8 +2478,8 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, unsigned int t
 	sstatus = status_get_status_data(src);
 	range = sstatus->rhw.range;
 
-	if( unit_is_walking(target) )
-		range++; // Extra range when chasing
+	if( unit_is_walking(target) && (target->type == BL_PC || !map_getcell(target->m,target->x,target->y,CELL_CHKICEWALL)) )
+		range++; // Extra range when chasing (does not apply to mobs locked in an icewall)
 
 	if(sd && !check_distance_client_bl(src,target,range)) {
 		// Player tries to attack but target is too far, notify client