Knockback, stop effects and Skid Trap reworked, monster chase and direction updates, code optimizations
- Created a new function unit_blown_immune that will now serve as the central function to determine if an object can be knocked back or stopped (bugreport:7637) * Moved the check code from skill_blown to unit_blown_immune * Several stopping effects and traps will now use unit_blown_immune to check if the object can be stopped, if not, the object will always move to its target cell before stopping * Bosses and monsters immune to knockback will now no longer be stopped by such traps * Expanded the configuration skill_trap_type and moved its checks into unit_blown_immune, so it's possible to switch the "no stop" behavior off for GVG/BG and monsters individually * Long-term we should make all skills use this function to check for knockback immunity, it will make the checks a lot cleaner and more centralized - Monster chase range updates (bugreport:7637) * Updated monster_chase_range in monster.conf from 1 to 3; I originally thought official value is 1, but doing some in-depth tests myself I realized it's 3 for the most important situations * When a monster cannot issue new "move" commands because it was affected by a status change, but is still moving due to knockback immunity, it will no longer unlock its target and stop * Fixed a bug that always caused the chase path monsters calculated to be 1 cell too short causing them to recalculate their path one cell before their goal every single time - Fixed the direction calculation once again and optimized it at the same time (bugreport:9373) * Now the calculated direction is 100% official, really truly, checked it myself with every single cell and various skills * Added a new function map_calc_dir_xy that allows to check for a direction between two cells without the need of a block_list * map_calc_dir will now just use map_calc_dir_xy to avoid duplicate code - Implemented Skid Trap properly (bugreport:9373) * The direction of the knockback will now be "away from position of the caster during cast" rather than "away from trap" * Skid Trap will now stop the target for 3 seconds; this works even in GVG/BG and on bosses, even though the actual knockback doesn't happen
This commit is contained in:
parent
b735103505
commit
902c920b73
@ -52,14 +52,14 @@ monster_ai: 0
|
||||
|
||||
// How often should a monster rethink its chase?
|
||||
// 0: Every 100ms (MIN_MOBTHINKTIME)
|
||||
// 1: Every cell moved (official)
|
||||
// 1: Every cell moved
|
||||
// 2: Every 2 cells moved
|
||||
// 3: Every 3 cells moved (previous setting)
|
||||
// 3: Every 3 cells moved (official)
|
||||
// x: Every x cells moved
|
||||
// Regardless of this setting, a monster will always rethink its chase if it has
|
||||
// reached its target. Increase this value if you want to make monsters continue
|
||||
// moving after they lost their target (hide, loot picked, etc.).
|
||||
monster_chase_refresh: 1
|
||||
monster_chase_refresh: 3
|
||||
|
||||
// Should mobs be able to be warped (add as needed)?
|
||||
// 0: Disable.
|
||||
|
@ -279,9 +279,15 @@ invincible.nodamage: no
|
||||
// Default: yes
|
||||
dancing_weaponswitch_fix: yes
|
||||
|
||||
// Skill Trap Type (GvG)
|
||||
// 0: (official) Traps in GvG only make player stop moving after its walk path is complete, and it activates other traps on the way.
|
||||
// 1: Traps in GvG make player stop moving right when stepping over it.
|
||||
// Skill Trap Type
|
||||
// On official servers if a unit is completely immune to knockback, it will still walk to the last target tile before
|
||||
// stopping when inflicted by a stopping status effect (including traps like Ankle Snare and Spiderweb). All traps on
|
||||
// the way will be activated.
|
||||
// This does NOT include being immune to knock back from equip. This bonus only helps against knockback skills.
|
||||
// 0: (official)
|
||||
// 1: Stop effects in GvG/WoE make units stop immediately.
|
||||
// 2: Stop effects make monsters immune to knockback / bosses stop immediately.
|
||||
// 3: 1+2
|
||||
skill_trap_type: 0
|
||||
|
||||
// Area of Bowling Bash chain reaction
|
||||
|
@ -199,7 +199,7 @@
|
||||
|
||||
//===== Hunter =============================
|
||||
//-- HT_SKIDTRAP
|
||||
115,0,0,0,300000:240000:180000:120000:60000,0,0
|
||||
115,0,0,0,300000:240000:180000:120000:60000,3000,0
|
||||
//-- HT_LANDMINE
|
||||
116,0,0,0,200000:160000:120000:80000:40000,5000,0
|
||||
//-- HT_ANKLESNARE
|
||||
|
@ -200,7 +200,7 @@
|
||||
|
||||
//===== Hunter =============================
|
||||
//-- HT_SKIDTRAP
|
||||
115,0,0,0,300000:240000:180000:120000:60000,0,0,0
|
||||
115,0,0,0,300000:240000:180000:120000:60000,3000,0,0
|
||||
//-- HT_LANDMINE
|
||||
116,0,1000,0,200000:160000:120000:80000:40000,5000,0,1000
|
||||
//-- HT_ANKLESNARE
|
||||
|
@ -7844,7 +7844,7 @@ static const struct _battle_data {
|
||||
{ "homunculus_max_level", &battle_config.hom_max_level, 99, 0, MAX_LEVEL, },
|
||||
{ "homunculus_S_max_level", &battle_config.hom_S_max_level, 150, 0, MAX_LEVEL, },
|
||||
{ "mob_size_influence", &battle_config.mob_size_influence, 0, 0, 1, },
|
||||
{ "skill_trap_type", &battle_config.skill_trap_type, 0, 0, 1, },
|
||||
{ "skill_trap_type", &battle_config.skill_trap_type, 0, 0, 3, },
|
||||
{ "allow_consume_restricted_item", &battle_config.allow_consume_restricted_item, 1, 0, 1, },
|
||||
{ "allow_equip_restricted_item", &battle_config.allow_equip_restricted_item, 1, 0, 1, },
|
||||
{ "max_walk_path", &battle_config.max_walk_path, 17, 1, MAX_WALKPATH, },
|
||||
|
@ -2573,40 +2573,52 @@ int map_check_dir(int s_dir,int t_dir)
|
||||
uint8 map_calc_dir(struct block_list* src, int16 x, int16 y)
|
||||
{
|
||||
uint8 dir = 0;
|
||||
int dx, dy;
|
||||
|
||||
nullpo_ret(src);
|
||||
|
||||
dx = x-src->x;
|
||||
dy = y-src->y;
|
||||
dir = map_calc_dir_xy(src->x, src->y, x, y, unit_getdir(src));
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/*==========================================
|
||||
* Returns the direction of the given cell, relative to source cell
|
||||
* Use this if you don't have a block list available to check against
|
||||
*------------------------------------------*/
|
||||
uint8 map_calc_dir_xy(int16 srcx, int16 srcy, int16 x, int16 y, uint8 srcdir) {
|
||||
uint8 dir = 0;
|
||||
int dx, dy;
|
||||
|
||||
dx = x-srcx;
|
||||
dy = y-srcy;
|
||||
if( dx == 0 && dy == 0 )
|
||||
{ // both are standing on the same spot
|
||||
// aegis-style, makes knockback default to the left
|
||||
// athena-style, makes knockback default to behind 'src'
|
||||
dir = (battle_config.knockback_left ? 6 : unit_getdir(src));
|
||||
dir = (battle_config.knockback_left ? 6 : srcdir);
|
||||
}
|
||||
else if( dx >= 0 && dy >=0 )
|
||||
{ // upper-right
|
||||
if( dx*2 < dy || dx == 0 ) dir = 0; // up
|
||||
else if( dx > dy*2+1 || dy == 0 ) dir = 6; // right
|
||||
if( dx >= dy*3 ) dir = 6; // right
|
||||
else if( dx*3 < dy ) dir = 0; // up
|
||||
else dir = 7; // up-right
|
||||
}
|
||||
else if( dx >= 0 && dy <= 0 )
|
||||
{ // lower-right
|
||||
if( dx*2 < -dy || dx == 0 ) dir = 4; // down
|
||||
else if( dx > -dy*2+1 || dy == 0 ) dir = 6; // right
|
||||
if( dx >= -dy*3 ) dir = 6; // right
|
||||
else if( dx*3 < -dy ) dir = 4; // down
|
||||
else dir = 5; // down-right
|
||||
}
|
||||
else if( dx <= 0 && dy <= 0 )
|
||||
{ // lower-left
|
||||
if( dx*2 > dy || dx == 0 ) dir = 4; // down
|
||||
else if( dx < dy*2-1 || dy == 0 ) dir = 2; // left
|
||||
if( dx*3 >= dy ) dir = 4; // down
|
||||
else if( dx < dy*3 ) dir = 2; // left
|
||||
else dir = 3; // down-left
|
||||
}
|
||||
else
|
||||
{ // upper-left
|
||||
if( -dx*2 < dy || dx == 0 ) dir = 0; // up
|
||||
else if( -dx > dy*2+1 || dy == 0) dir = 2; // left
|
||||
if( -dx*3 <= dy ) dir = 0; // up
|
||||
else if( -dx > dy*3 ) dir = 2; // left
|
||||
else dir = 1; // up-left
|
||||
}
|
||||
return dir;
|
||||
|
@ -857,6 +857,7 @@ bool mapit_exists(struct s_mapiterator* mapit);
|
||||
|
||||
int map_check_dir(int s_dir,int t_dir);
|
||||
uint8 map_calc_dir(struct block_list *src,int16 x,int16 y);
|
||||
uint8 map_calc_dir_xy(int16 srcx, int16 srcy, int16 x, int16 y, uint8 srcdir);
|
||||
int map_random_dir(struct block_list *bl, short *x, short *y); // [Skotlex]
|
||||
|
||||
int cleanup_sub(struct block_list *bl, va_list ap);
|
||||
|
@ -1446,7 +1446,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
||||
)) { //No valid target
|
||||
if (mob_warpchase(md, tbl))
|
||||
return true; //Chasing this target.
|
||||
if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh)
|
||||
if(md->ud.walktimer != INVALID_TIMER && (!can_move || md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh))
|
||||
return true; //Walk at least "mob_chase_refresh" cells before dropping the target
|
||||
mob_unlocktarget(md, tick); //Unlock target
|
||||
tbl = NULL;
|
||||
@ -1661,6 +1661,10 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
||||
if(battle_check_range(&md->bl, tbl, md->status.rhw.range))
|
||||
return true;
|
||||
|
||||
//Only update target cell / drop target after having moved at least "mob_chase_refresh" cells
|
||||
if(md->ud.walktimer != INVALID_TIMER && (!can_move || md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh))
|
||||
return true;
|
||||
|
||||
//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.
|
||||
@ -1679,10 +1683,6 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
||||
)) //Current target tile is still within attack range.
|
||||
return true;
|
||||
|
||||
//Only update target cell after having moved at least "mob_chase_refresh" cells
|
||||
if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh)
|
||||
return true;
|
||||
|
||||
//Follow up if possible.
|
||||
//Hint: Chase skills are handled in the walktobl routine
|
||||
if(!mob_can_reach(md, tbl, md->min_chase, MSS_RUSH) ||
|
||||
|
@ -2381,6 +2381,7 @@ static int skill_area_temp[8];
|
||||
short skill_blown(struct block_list* src, struct block_list* target, char count, int8 dir, unsigned char flag)
|
||||
{
|
||||
int dx = 0, dy = 0;
|
||||
int reason = 0, checkflag = 0;
|
||||
|
||||
nullpo_ret(src);
|
||||
nullpo_ret(target);
|
||||
@ -2388,34 +2389,23 @@ short skill_blown(struct block_list* src, struct block_list* target, char count,
|
||||
if (!count)
|
||||
return count; // Actual knockback distance is 0.
|
||||
|
||||
if (src != target && (map_flag_gvg(target->m) || map[target->m].flag.battleground))
|
||||
return ((flag&0x04) ? count : 0); // No knocking back in WoE
|
||||
// Create flag needed in unit_blown_immune
|
||||
if(src != target)
|
||||
checkflag |= 0x1; // Offensive
|
||||
if(!(flag&0x2))
|
||||
checkflag |= 0x2; // Knockback type
|
||||
if(is_boss(src))
|
||||
checkflag |= 0x4; // Boss attack
|
||||
|
||||
switch (target->type) {
|
||||
case BL_MOB: {
|
||||
struct mob_data* md = BL_CAST(BL_MOB, target);
|
||||
if( md->mob_id == MOBID_EMPERIUM )
|
||||
return count;
|
||||
// Bosses or imune can't be knocked-back
|
||||
if(src != target && status_get_mode(target)&(MD_KNOCKBACK_IMMUNE|MD_BOSS))
|
||||
return ((flag&0x08) ? count : 0);
|
||||
}
|
||||
break;
|
||||
case BL_PC: {
|
||||
struct map_session_data *sd = BL_CAST(BL_PC, target);
|
||||
if( sd->sc.data[SC_BASILICA] && sd->sc.data[SC_BASILICA]->val4 == sd->bl.id && !is_boss(src))
|
||||
return ((flag&0x20) ? count : 0); // Basilica caster can't be knocked-back by normal monsters.
|
||||
if( !(flag&0x2) && src != target && sd->special_state.no_knockback )
|
||||
return ((flag&0x10) ? count : 0);
|
||||
}
|
||||
break;
|
||||
case BL_SKILL: {
|
||||
struct skill_unit* su = NULL;
|
||||
su = (struct skill_unit *)target;
|
||||
if (su && su->group && skill_get_unit_flag(su->group->skill_id)&UF_NOKNOCKBACK)
|
||||
return count; // Cannot be knocked back
|
||||
}
|
||||
break;
|
||||
// Get reason and check for flags
|
||||
reason = unit_blown_immune(target, checkflag);
|
||||
switch(reason) {
|
||||
case 1: return ((flag&0x04) ? count : 0); // No knocking back in WoE / BG
|
||||
case 2: return count; // Emperium can't be knocked back
|
||||
case 3: return ((flag&0x08) ? count : 0); // Bosses or immune can't be knocked back
|
||||
case 4: return ((flag&0x20) ? count : 0); // Basilica caster can't be knocked-back by normal monsters.
|
||||
case 5: return ((flag&0x10) ? count : 0); // Target has special_state.no_knockback (equip)
|
||||
case 6: return count; // Trap cannot be knocked back
|
||||
}
|
||||
|
||||
if (dir == -1) // <optimized>: do the computation here instead of outside
|
||||
@ -2429,7 +2419,6 @@ short skill_blown(struct block_list* src, struct block_list* target, char count,
|
||||
return unit_blown(target, dx, dy, count, flag); // Send over the proper flag
|
||||
}
|
||||
|
||||
|
||||
// Checks if 'bl' should reflect back a spell cast by 'src'.
|
||||
// type is the type of magic attack: 0: indirect (aoe), 1: direct (targetted)
|
||||
// In case of success returns type of reflection, otherwise 0
|
||||
@ -11955,13 +11944,14 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
|
||||
break;
|
||||
case HT_ANKLESNARE:
|
||||
if( flag&2 ) val3 = SC_ESCAPE;
|
||||
case HT_SKIDTRAP:
|
||||
case MA_SKIDTRAP:
|
||||
//Save position of caster
|
||||
val1 = ((src->x)<<16)|(src->y);
|
||||
case HT_SHOCKWAVE:
|
||||
val1=skill_lv*15+10;
|
||||
case HT_SANDMAN:
|
||||
case MA_SANDMAN:
|
||||
case HT_CLAYMORETRAP:
|
||||
case HT_SKIDTRAP:
|
||||
case MA_SKIDTRAP:
|
||||
case HT_LANDMINE:
|
||||
case MA_LANDMINE:
|
||||
case HT_FLASHER:
|
||||
@ -12824,10 +12814,14 @@ int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, uns
|
||||
break;
|
||||
|
||||
case UNT_SKIDTRAP: {
|
||||
skill_blown(&unit->bl,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),unit_getdir(bl),0);
|
||||
//Knockback away from position of user during placement [Playtester]
|
||||
skill_blown(&unit->bl,bl,skill_get_blewcount(sg->skill_id,sg->skill_lv),
|
||||
(map_calc_dir_xy(sg->val1>>16,sg->val1&0xFFFF,bl->x,bl->y,6)+4)%8,0);
|
||||
sg->unit_id = UNT_USED_TRAPS;
|
||||
clif_changetraplook(&unit->bl, UNT_USED_TRAPS);
|
||||
sg->limit=DIFF_TICK(tick,sg->tick)+1500;
|
||||
//Target will be stopped for 3 seconds
|
||||
sc_start(ss,bl,SC_STOP,100,0,skill_get_time2(sg->skill_id,sg->skill_lv));
|
||||
}
|
||||
break;
|
||||
|
||||
@ -12841,7 +12835,7 @@ int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, uns
|
||||
|
||||
if( td )
|
||||
sec = DIFF_TICK(td->tick, tick);
|
||||
if( sg->unit_id == UNT_MANHOLE || battle_config.skill_trap_type || !map_flag_gvg(unit->bl.m) ) {
|
||||
if( !unit_blown_immune(bl,0x1) ) {
|
||||
unit_movepos(bl, unit->bl.x, unit->bl.y, 0, 0);
|
||||
clif_fixpos(bl);
|
||||
}
|
||||
|
@ -10042,10 +10042,9 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
|
||||
case SC_KYOUGAKU:
|
||||
case SC_PARALYSIS:
|
||||
case SC_MAGNETICFIELD:
|
||||
unit_stop_walking(bl,1);
|
||||
break;
|
||||
case SC_ANKLE:
|
||||
if( battle_config.skill_trap_type || !map_flag_gvg(bl->m) )
|
||||
case SC_VACUUM_EXTREME:
|
||||
if (!unit_blown_immune(bl,0x1))
|
||||
unit_stop_walking(bl,1);
|
||||
break;
|
||||
case SC_HIDING:
|
||||
@ -10068,10 +10067,6 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
|
||||
if (battle_config.sc_castcancel&bl->type)
|
||||
unit_skillcastcancel(bl, 0);
|
||||
break;
|
||||
case SC_VACUUM_EXTREME:
|
||||
if (!map_flag_gvg(bl->m))
|
||||
unit_stop_walking(bl, 1);
|
||||
break;
|
||||
case SC_ITEMSCRIPT: // Shows Buff Icons
|
||||
if (sd && val2 != SI_BLANK)
|
||||
clif_status_change(bl, (enum si_type)val2, 1, tick, 0, 0, 0);
|
||||
|
@ -100,11 +100,11 @@ int unit_walktoxy_sub(struct block_list *bl)
|
||||
uint8 dir;
|
||||
// Trim the last part of the path to account for range,
|
||||
// but always move at least one cell when requested to move.
|
||||
for (i = ud->chaserange*10; i > 0 && ud->walkpath.path_len>1;) {
|
||||
for (i = (ud->chaserange*10)-10; i > 0 && ud->walkpath.path_len>1;) {
|
||||
ud->walkpath.path_len--;
|
||||
dir = ud->walkpath.path[ud->walkpath.path_len];
|
||||
if(dir&1)
|
||||
i -= MOVE_DIAGONAL_COST;
|
||||
i -= MOVE_COST*20; //When chasing, units will target a diamond-shaped area in range [Playtester]
|
||||
else
|
||||
i -= MOVE_COST;
|
||||
ud->to_x -= dirx[dir];
|
||||
@ -1061,6 +1061,64 @@ int unit_blown(struct block_list* bl, int dx, int dy, int count, int flag)
|
||||
return count; // Return amount of knocked back cells
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if unit can be knocked back / stopped by skills.
|
||||
* @param bl: Object to check
|
||||
* @param flag
|
||||
* 0x1 - Offensive (not set: self skill, e.g. Backslide)
|
||||
* 0x2 - Knockback type (not set: Stop type, e.g. Ankle Snare)
|
||||
* 0x4 - Boss attack
|
||||
* @return reason for immunity
|
||||
* 0 - can be knocked back / stopped
|
||||
* 1 - at WOE/BG map;
|
||||
* 2 - target is emperium
|
||||
* 3 - target is MD_KNOCKBACK_IMMUNE|MD_BOSS;
|
||||
* 4 - target is in Basilica area;
|
||||
* 5 - target has 'special_state.no_knockback';
|
||||
* 6 - target is trap that cannot be knocked back
|
||||
*/
|
||||
int unit_blown_immune(struct block_list* bl, int flag)
|
||||
{
|
||||
if ((flag&0x1) && (map_flag_gvg(bl->m) || map[bl->m].flag.battleground)
|
||||
&& ((flag&0x2) || !(battle_config.skill_trap_type&0x1)))
|
||||
return 1; // No knocking back in WoE / BG
|
||||
|
||||
switch (bl->type) {
|
||||
case BL_MOB: {
|
||||
struct mob_data* md = BL_CAST(BL_MOB, bl);
|
||||
// Emperium can't be knocked back
|
||||
if( md->mob_id == MOBID_EMPERIUM )
|
||||
return 2;
|
||||
// Bosses or immune can't be knocked back
|
||||
if((flag&0x1) && status_get_mode(bl)&(MD_KNOCKBACK_IMMUNE|MD_BOSS)
|
||||
&& ((flag&0x2) || !(battle_config.skill_trap_type&0x2)))
|
||||
return 3;
|
||||
}
|
||||
break;
|
||||
case BL_PC: {
|
||||
struct map_session_data *sd = BL_CAST(BL_PC, bl);
|
||||
// Basilica caster can't be knocked-back by normal monsters.
|
||||
if( sd->sc.data[SC_BASILICA] && sd->sc.data[SC_BASILICA]->val4 == sd->bl.id && !(flag&0x4))
|
||||
return 4;
|
||||
// Target has special_state.no_knockback (equip)
|
||||
if( (flag&0x1) && (flag&0x2) && sd->special_state.no_knockback )
|
||||
return 5;
|
||||
}
|
||||
break;
|
||||
case BL_SKILL: {
|
||||
struct skill_unit* su = NULL;
|
||||
su = (struct skill_unit *)bl;
|
||||
// Trap cannot be knocked back
|
||||
if (su && su->group && skill_get_unit_flag(su->group->skill_id)&UF_NOKNOCKBACK)
|
||||
return 6;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//Object can be knocked back / stopped
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warps a unit to a map/position
|
||||
* pc_setpos is used for player warping
|
||||
|
@ -99,6 +99,7 @@ int unit_warp(struct block_list *bl, short map, short x, short y, clr_type type)
|
||||
int unit_setdir(struct block_list *bl, unsigned char dir);
|
||||
uint8 unit_getdir(struct block_list *bl);
|
||||
int unit_blown(struct block_list* bl, int dx, int dy, int count, int flag);
|
||||
int unit_blown_immune(struct block_list* bl, int flag);
|
||||
|
||||
// Can-reach checks
|
||||
bool unit_can_reach_pos(struct block_list *bl,int x,int y,int easy);
|
||||
|
Loading…
x
Reference in New Issue
Block a user