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
This commit is contained in:
parent
5110ddbaf4
commit
5540d89cb0
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user