Skip to content

Commit

Permalink
Knockback, stop effects and Skid Trap reworked, monster chase and dir…
Browse files Browse the repository at this point in the history
…ection 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
  • Loading branch information
Playtester committed Oct 30, 2014
1 parent b735103 commit 902c920
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 74 deletions.
6 changes: 3 additions & 3 deletions conf/battle/monster.conf
Expand Up @@ -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.
Expand Down
12 changes: 9 additions & 3 deletions conf/battle/skill.conf
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion db/pre-re/skill_cast_db.txt
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion db/re/skill_cast_db.txt
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/map/battle.c
Expand Up @@ -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, },
Expand Down
44 changes: 28 additions & 16 deletions src/map/map.c
Expand Up @@ -2573,41 +2573,53 @@ 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
else dir = 7; // up-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
else dir = 5; // down-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
else dir = 3; // down-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
else dir = 1; // up-left
if( -dx*3 <= dy ) dir = 0; // up
else if( -dx > dy*3 ) dir = 2; // left
else dir = 1; // up-left
}
return dir;
}
Expand Down
3 changes: 2 additions & 1 deletion src/map/map.h
Expand Up @@ -856,7 +856,8 @@ bool mapit_exists(struct s_mapiterator* mapit);
#define mapit_geteachiddb() mapit_alloc(MAPIT_NORMAL,BL_ALL)

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(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);
Expand Down
10 changes: 5 additions & 5 deletions src/map/mob.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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) ||
Expand Down
62 changes: 28 additions & 34 deletions src/map/skill.c
Expand Up @@ -2381,41 +2381,31 @@ 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);

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

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

// 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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;

Expand All @@ -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);
}
Expand Down
9 changes: 2 additions & 7 deletions src/map/status.c
Expand Up @@ -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);

This comment has been minimized.

Copy link
@cydh

cydh Jan 16, 2015

Contributor

just noticed this, thank to @raynra
the check here sounds ridiculous, if unit cannot be knocked because of GVG/BG map, so don't stop them. xD

This comment has been minimized.

Copy link
@Playtester

Playtester Jan 19, 2015

Author Member

But that's how it works officially. If you get stopped in GVG/BG maps you will still walk to the last target cell. You can even activate more traps on the way. Same for boss monsters.

break;
case SC_HIDING:
Expand All @@ -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);
Expand Down
62 changes: 60 additions & 2 deletions src/map/unit.c
Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/map/unit.h
Expand Up @@ -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);
Expand Down

1 comment on commit 902c920

@tmav94
Copy link

@tmav94 tmav94 commented on 902c920 Nov 3, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im here...(only to know where i am when i update my serv.)

Please sign in to comment.