Переглянути джерело

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 роки тому
батько
коміт
5540d89cb0
11 змінених файлів з 58 додано та 78 видалено
  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