diff --git a/conf/battle/skill.conf b/conf/battle/skill.conf index 735b3d6243..c1a5171156 100644 --- a/conf/battle/skill.conf +++ b/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 diff --git a/src/map/atcommand.c b/src/map/atcommand.c index 259f6d8887..dea1f1c6a5 100644 --- a/src/map/atcommand.c +++ b/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; } diff --git a/src/map/battle.c b/src/map/battle.c index aff81f4258..fa61a664f4 100644 --- a/src/map/battle.c +++ b/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 diff --git a/src/map/battle.h b/src/map/battle.h index fe9d987f12..4bae5fa559 100644 --- a/src/map/battle.h +++ b/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); diff --git a/src/map/map.c b/src/map/map.c index 17a641f2a7..56cd3098d5 100644 --- a/src/map/map.c +++ b/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; } diff --git a/src/map/map.h b/src/map/map.h index 7d7a46b3f2..0101db96d6 100644 --- a/src/map/map.h +++ b/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; diff --git a/src/map/mob.c b/src/map/mob.c index a00af523be..7e889582c4 100644 --- a/src/map/mob.c +++ b/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; } diff --git a/src/map/mob.h b/src/map/mob.h index e455a9363f..b8dbcfcc01 100644 --- a/src/map/mob.h +++ b/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; diff --git a/src/map/path.c b/src/map/path.c index 0161fffa78..2c4dbedc75 100644 --- a/src/map/path.c +++ b/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; diff --git a/src/map/skill.c b/src/map/skill.c index 110f658231..1ae951b390 100755 --- a/src/map/skill.c +++ b/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); diff --git a/src/map/unit.c b/src/map/unit.c index 8818da07f5..68c2326d73 100644 --- a/src/map/unit.c +++ b/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