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:
Playtester 2014-10-30 20:12:18 +01:00
parent b735103505
commit 902c920b73
12 changed files with 140 additions and 73 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, },

View File

@ -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;

View File

@ -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);

View File

@ -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) ||

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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);