NPC_RUN, Monster Skill Interval (#8302)
- Monsters will now properly run away when using NPC_RUN (fixes #7941) - Introduced a new function unit_get_walkpath_time that returns the time the unit needs to walk its current walkpath * This is now used for NPC_RUN and random walking - Fixed an issue with mismatching timer warnings when a monster casts NPC_RUN multiple times in row - Monsters will now always attempt to use non-berserk-state skills once per second (fixes #1700) * This completely replaces the "ugly" solution to use a walk_count for idle, walk and chase skills * This interval is now a lot more accurate and no longer influenced by external factors such as canact delay * This interval is now also used for lazy monsters rather than MIN_MOBTHINKTIME*10 so that MIN_MOBTHINKTIME can be reduced without having to worry about skills being cast more often * Angry skills no longer replace the normal attack and now follow the once per second rule; they will always first be attempted at the end of the walk delay after a normal attack - The special follow-up attack skill monsters use when you move out of their attack range, now is only used when they are in Angry state * Also fixed a bug that this was checked every 100ms until the monster used a skill, instead of just once per second - Monsters now can use chase skills even before they start moving (assuming one second has already passed since last skill check) - Removed "hack" to make monsters cast chase skills when trapped in icewall * This was solved by implementing checking for chase skills before starting to move - Monsters will now receive an aMotion walk delay after having used a skill - A monster that could not walk randomly because of walk delay will now walk immediately once the walk delay expires - Using angry or berserk skills will now set a monster's attack delay
This commit is contained in:
parent
bcb34695a5
commit
ffe40def4a
@ -26,8 +26,8 @@ min_skill_delay_limit: 100
|
||||
|
||||
// This delay is the min 'can't walk delay' of all skills.
|
||||
// NOTE: Do not set this too low, if a character starts moving too soon after
|
||||
// doing a skill, the client will not update this, and the player/mob will
|
||||
// appear to "teleport" afterwards.
|
||||
// doing a skill, the client will not update this, and the player will appear
|
||||
// to "teleport" afterwards. Monsters use AttackMotion instead.
|
||||
default_walk_delay: 300
|
||||
|
||||
// Completely disable skill delay of the following types (Note 3)
|
||||
|
@ -45,8 +45,6 @@ using namespace rathena;
|
||||
|
||||
#define ACTIVE_AI_RANGE 2 //Distance added on top of 'AREA_SIZE' at which mobs enter active AI mode.
|
||||
|
||||
#define IDLE_SKILL_INTERVAL 10 //Active idle skills should be triggered every 1 second (1000/MIN_MOBTHINKTIME)
|
||||
|
||||
const t_tick MOB_MAX_DELAY = 24 * 3600 * 1000;
|
||||
#define RUDE_ATTACKED_COUNT 1 //After how many rude-attacks should the skill be used?
|
||||
|
||||
@ -1165,6 +1163,7 @@ int mob_spawn (struct mob_data *md)
|
||||
md->dmgtick = tick - 5000;
|
||||
md->last_pcneartime = 0;
|
||||
md->last_canmove = tick;
|
||||
md->last_skillcheck = 0;
|
||||
|
||||
t_tick c = tick - MOB_MAX_DELAY;
|
||||
|
||||
@ -1522,14 +1521,17 @@ int mob_unlocktarget(struct mob_data *md, t_tick tick)
|
||||
break;
|
||||
}
|
||||
// Idle skill.
|
||||
if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL) && mobskill_use(md, tick, -1))
|
||||
if (DIFF_TICK(tick, md->last_skillcheck) >= MOB_SKILL_INTERVAL && mobskill_use(md, tick, -1))
|
||||
break;
|
||||
//Random walk.
|
||||
if (!md->master_id &&
|
||||
DIFF_TICK(md->next_walktime, tick) <= 0 &&
|
||||
!mob_randomwalk(md,tick))
|
||||
//Delay next random walk when this one failed.
|
||||
md->next_walktime = tick+rnd()%1000;
|
||||
if (md->next_walktime < md->ud.canmove_tick)
|
||||
md->next_walktime = md->ud.canmove_tick;
|
||||
else
|
||||
md->next_walktime = tick+rnd()%1000;
|
||||
break;
|
||||
default:
|
||||
mob_stop_attack(md);
|
||||
@ -1563,8 +1565,7 @@ int mob_unlocktarget(struct mob_data *md, t_tick tick)
|
||||
int mob_randomwalk(struct mob_data *md,t_tick tick)
|
||||
{
|
||||
const int d=7;
|
||||
int i,c,r,rdir,dx,dy,max;
|
||||
int speed;
|
||||
int i,r,rdir,dx,dy,max;
|
||||
|
||||
nullpo_ret(md);
|
||||
|
||||
@ -1648,16 +1649,9 @@ int mob_randomwalk(struct mob_data *md,t_tick tick)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
speed=status_get_speed(&md->bl);
|
||||
for(i=c=0;i<md->ud.walkpath.path_len;i++){ // The next walk start time is calculated.
|
||||
if( direction_diagonal( md->ud.walkpath.path[i] ) )
|
||||
c+=speed*MOVE_DIAGONAL_COST/MOVE_COST;
|
||||
else
|
||||
c+=speed;
|
||||
}
|
||||
md->state.skillstate=MSS_WALK;
|
||||
md->move_fail_count=0;
|
||||
md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME+c;
|
||||
md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME + unit_get_walkpath_time(md->bl);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1917,8 +1911,23 @@ static bool mob_ai_sub_hard(struct mob_data *md, t_tick tick)
|
||||
return true;
|
||||
}
|
||||
|
||||
//Attempt to attack.
|
||||
//At this point we know the target is attackable, we just gotta check if the range matches.
|
||||
// At this point we know the target is attackable, attempt to attack
|
||||
|
||||
// Monsters in angry state, after having used a normal attack, will always attempt a skill
|
||||
if (md->ud.walktimer == INVALID_TIMER && md->state.skillstate == MSS_ANGRY && md->ud.skill_id == 0)
|
||||
{
|
||||
// Only use skill if able to walk on next tick and not attempted a skill the last second
|
||||
if (DIFF_TICK(md->ud.canmove_tick, tick) <= MIN_MOBTHINKTIME && DIFF_TICK(tick, md->last_skillcheck) >= MOB_SKILL_INTERVAL){
|
||||
if (mobskill_use(md, tick, -1)) {
|
||||
// After the monster used an angry skill, it will not attack for aDelay
|
||||
// Setting the delay here because not all monster skill use situations will cause an attack delay
|
||||
md->ud.attackabletime = tick + md->status.adelay;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal attack / berserk skill is only used when target is in range
|
||||
if (battle_check_range(&md->bl, tbl, md->status.rhw.range) && !(md->sc.option&OPTION_HIDE))
|
||||
{ //Target within range and able to use normal attack, engage
|
||||
if (md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER)
|
||||
@ -1940,16 +1949,6 @@ static bool mob_ai_sub_hard(struct mob_data *md, t_tick tick)
|
||||
return true;
|
||||
}
|
||||
|
||||
//Monsters in berserk state, unable to use normal attacks, will always attempt a skill
|
||||
if(md->ud.walktimer == INVALID_TIMER && (md->state.skillstate == MSS_BERSERK || md->state.skillstate == MSS_ANGRY))
|
||||
{
|
||||
if (DIFF_TICK(md->ud.canmove_tick, tick) <= MIN_MOBTHINKTIME && DIFF_TICK(md->ud.canact_tick, tick) < -MIN_MOBTHINKTIME*IDLE_SKILL_INTERVAL)
|
||||
{ //Only use skill if able to walk on next tick and not used a skill the last second
|
||||
if (mobskill_use(md, tick, -1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Target still in attack range, no need to chase the target
|
||||
if(battle_check_range(&md->bl, tbl, md->status.rhw.range))
|
||||
return true;
|
||||
@ -1971,7 +1970,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, t_tick tick)
|
||||
else {
|
||||
// Use idle skill but keep target for now
|
||||
md->state.skillstate = MSS_IDLE;
|
||||
if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL))
|
||||
if (DIFF_TICK(tick, md->last_skillcheck) >= MOB_SKILL_INTERVAL)
|
||||
mobskill_use(md, tick, -1);
|
||||
}
|
||||
}
|
||||
@ -1986,10 +1985,24 @@ static bool mob_ai_sub_hard(struct mob_data *md, t_tick tick)
|
||||
return true;
|
||||
|
||||
//Follow up if possible.
|
||||
//Hint: Chase skills are handled in the walktobl routine
|
||||
if(!mob_can_reach(md, tbl, md->min_chase) ||
|
||||
!unit_walktobl(&md->bl, tbl, md->status.rhw.range, 2))
|
||||
mob_unlocktarget(md,tick);
|
||||
if (!mob_can_reach(md, tbl, md->min_chase)) {
|
||||
mob_unlocktarget(md, tick);
|
||||
return true;
|
||||
}
|
||||
// Monsters can use chase skills before starting to walk
|
||||
// So we need to change the state and check for a skill here already
|
||||
// But only use skill if able to walk on next tick and not attempted a skill the last second
|
||||
// Skills during movement are handled in the walktobl routine
|
||||
if (md->ud.walktimer == INVALID_TIMER
|
||||
&& DIFF_TICK(md->ud.canmove_tick, tick) <= MIN_MOBTHINKTIME
|
||||
&& DIFF_TICK(tick, md->last_skillcheck) >= MOB_SKILL_INTERVAL) {
|
||||
md->state.skillstate = md->state.aggressive ? MSS_FOLLOW : MSS_RUSH;
|
||||
if (mobskill_use(md, tick, -1))
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!unit_walktobl(&md->bl, tbl, md->status.rhw.range, 2))
|
||||
mob_unlocktarget(md, tick);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2063,7 +2076,7 @@ static int mob_ai_sub_lazy(struct mob_data *md, va_list args)
|
||||
//Clean the spotted log
|
||||
mob_clean_spotted(md);
|
||||
|
||||
if(DIFF_TICK(tick,md->last_thinktime)< 10*MIN_MOBTHINKTIME)
|
||||
if(DIFF_TICK(tick,md->last_thinktime) < MOB_SKILL_INTERVAL)
|
||||
return 0;
|
||||
|
||||
md->last_thinktime=tick;
|
||||
@ -3752,8 +3765,9 @@ int mobskill_use(struct mob_data *md, t_tick tick, int event, int64 damage)
|
||||
if (!battle_config.mob_skill_rate || md->ud.skilltimer != INVALID_TIMER || ms.empty() || status_has_mode(&md->status,MD_NOCAST))
|
||||
return 0;
|
||||
|
||||
if (event == -1 && DIFF_TICK(md->ud.canact_tick, tick) > 0)
|
||||
return 0; //Skill act delay only affects non-event skills.
|
||||
// Monsters check their non-attack-state skills once per second, but we ignore this for events for now
|
||||
if (event == -1)
|
||||
md->last_skillcheck = tick;
|
||||
|
||||
//Pick a starting position and loop from that.
|
||||
i = battle_config.mob_ai&0x100?rnd()%ms.size():0;
|
||||
|
@ -38,6 +38,9 @@ const t_tick MIN_MOBLINKTIME = 1000;
|
||||
//Min time between random walks
|
||||
const t_tick MIN_RANDOMWALKTIME = 4000;
|
||||
|
||||
// How often a monster will check for using a skill on non-attack states (in ms)
|
||||
const t_tick MOB_SKILL_INTERVAL = 1000;
|
||||
|
||||
//Distance that slaves should keep from their master.
|
||||
#define MOB_SLAVEDISTANCE 2
|
||||
|
||||
@ -359,7 +362,7 @@ struct mob_data {
|
||||
int areanpc_id; //Required in OnTouchNPC (to avoid multiple area touchs)
|
||||
int bg_id; // BattleGround System
|
||||
|
||||
t_tick next_walktime,last_thinktime,last_linktime,last_pcneartime,dmgtick,last_canmove;
|
||||
t_tick next_walktime,last_thinktime,last_linktime,last_pcneartime,dmgtick,last_canmove,last_skillcheck;
|
||||
short move_fail_count;
|
||||
short lootitem_count;
|
||||
short min_chase;
|
||||
|
@ -9938,7 +9938,15 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
|
||||
if (tbl) {
|
||||
md->state.can_escape = 1;
|
||||
mob_unlocktarget(md, tick);
|
||||
unit_escape(src, tbl, skill_lv > 1 ? skill_lv : AREA_SIZE, 2); // Send distance in skill level > 1
|
||||
// Official distance is 7, if level > 1, distance = level
|
||||
t_tick time = unit_escape(src, tbl, skill_lv > 1 ? skill_lv : 7, 2);
|
||||
|
||||
if (time) {
|
||||
// Need to set state here as it's not set otherwise
|
||||
md->state.skillstate = MSS_WALK;
|
||||
// Set AI to inactive for the duration of this movement
|
||||
md->last_thinktime = tick + time;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -13421,8 +13429,14 @@ TIMER_FUNC(skill_castend_id){
|
||||
}
|
||||
}
|
||||
}
|
||||
if (skill_get_state(ud->skill_id) != ST_MOVE_ENABLE)
|
||||
unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1);
|
||||
if (skill_get_state(ud->skill_id) != ST_MOVE_ENABLE) {
|
||||
// When monsters used a skill they won't walk for amotion, this does not apply to players
|
||||
// This is also important for monster skill usage behavior
|
||||
if (src->type == BL_MOB)
|
||||
unit_set_walkdelay(src, tick, max((int)status_get_amotion(src), skill_get_walkdelay(ud->skill_id, ud->skill_lv)), 1);
|
||||
else
|
||||
unit_set_walkdelay(src, tick, battle_config.default_walk_delay + skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1);
|
||||
}
|
||||
|
||||
if(battle_config.skill_log && battle_config.skill_log&src->type)
|
||||
ShowInfo("Type %d, ID %d skill castend id [id =%d, lv=%d, target ID %d]\n",
|
||||
@ -13636,7 +13650,12 @@ TIMER_FUNC(skill_castend_pos){
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
unit_set_walkdelay(src, tick, battle_config.default_walk_delay+skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1);
|
||||
// When monsters used a skill they won't walk for amotion, this does not apply to players
|
||||
// This is also important for monster skill usage behavior
|
||||
if (src->type == BL_MOB)
|
||||
unit_set_walkdelay(src, tick, max((int)status_get_amotion(src), skill_get_walkdelay(ud->skill_id, ud->skill_lv)), 1);
|
||||
else
|
||||
unit_set_walkdelay(src, tick, battle_config.default_walk_delay + skill_get_walkdelay(ud->skill_id, ud->skill_lv), 1);
|
||||
map_freeblock_lock();
|
||||
skill_castend_pos2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv,tick,0);
|
||||
|
||||
|
@ -182,10 +182,6 @@ enum e_skill_unit_flag : uint8 {
|
||||
UF_MAX,
|
||||
};
|
||||
|
||||
/// Walk intervals at which chase-skills are attempted to be triggered.
|
||||
/// If you change this, make sure it's an odd value (for icewall block behavior).
|
||||
#define WALK_SKILL_INTERVAL 5
|
||||
|
||||
/// Time that's added to canact delay on castbegin and substracted on castend
|
||||
/// This is to prevent hackers from sending a skill packet after cast but before a timer triggers castend
|
||||
const t_tick SECURITY_CASTTIME = 100;
|
||||
|
@ -448,11 +448,10 @@ static TIMER_FUNC(unit_walktoxy_timer)
|
||||
//Needs to be done here so that rudeattack skills are invoked
|
||||
md->walktoxy_fail_count++;
|
||||
clif_fixpos(bl);
|
||||
//Monsters in this situation first use a chase skill, then unlock target and then use an idle skill
|
||||
if (!(++ud->walk_count%WALK_SKILL_INTERVAL))
|
||||
mobskill_use(md, tick, -1);
|
||||
// Monsters in this situation will unlock target and then attempt an idle skill
|
||||
// When they start chasing again, they will check for a chase skill before returning here
|
||||
mob_unlocktarget(md, tick);
|
||||
if (!(++ud->walk_count%WALK_SKILL_INTERVAL))
|
||||
if (DIFF_TICK(tick, md->last_skillcheck) >= MOB_SKILL_INTERVAL)
|
||||
mobskill_use(md, tick, -1);
|
||||
return 0;
|
||||
}
|
||||
@ -463,7 +462,6 @@ static TIMER_FUNC(unit_walktoxy_timer)
|
||||
x += dx;
|
||||
y += dy;
|
||||
map_moveblock(bl, x, y, tick);
|
||||
ud->walk_count++; // Walked cell counter, to be used for walk-triggered skills. [Skotlex]
|
||||
|
||||
if (bl->x != x || bl->y != y || ud->walktimer != INVALID_TIMER)
|
||||
return 0; // map_moveblock has altered the object beyond what we expected (moved/warped it)
|
||||
@ -542,8 +540,11 @@ static TIMER_FUNC(unit_walktoxy_timer)
|
||||
md->min_chase--;
|
||||
// Walk skills are triggered regardless of target due to the idle-walk mob state.
|
||||
// But avoid triggering on stop-walk calls.
|
||||
// Monsters use walk/chase skills every second, but we only get here every "speed" ms
|
||||
// To make sure we check one skill per second on average, we substract half the speed as ms
|
||||
if(!ud->state.force_walk && tid != INVALID_TIMER &&
|
||||
!(ud->walk_count%WALK_SKILL_INTERVAL) &&
|
||||
DIFF_TICK(tick, md->last_skillcheck) > MOB_SKILL_INTERVAL - md->status.speed / 2 &&
|
||||
DIFF_TICK(tick, md->last_thinktime) > 0 &&
|
||||
map[bl->m].users > 0 &&
|
||||
mobskill_use(md, tick, -1)) {
|
||||
if (!(ud->skill_id == NPC_SELFDESTRUCTION && ud->skilltimer != INVALID_TIMER)
|
||||
@ -616,6 +617,12 @@ static TIMER_FUNC(unit_walktoxy_timer)
|
||||
speed = status_get_speed(bl);
|
||||
|
||||
if(speed > 0) {
|
||||
// For some reason sometimes the walk timer is not empty here
|
||||
// TODO: Need to check why (e.g. when the monster spams NPC_RUN)
|
||||
if (ud->walktimer != INVALID_TIMER) {
|
||||
delete_timer(ud->walktimer, unit_walktoxy_timer);
|
||||
ud->walktimer = INVALID_TIMER;
|
||||
}
|
||||
ud->walktimer = add_timer(tick+speed,unit_walktoxy_timer,id,speed);
|
||||
if( md && DIFF_TICK(tick,md->dmgtick) < 3000 ) // Not required not damaged recently
|
||||
clif_move(ud);
|
||||
@ -1004,22 +1011,47 @@ bool unit_run(struct block_list *bl, map_session_data *sd, enum sc_type type)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns duration of an object's current walkpath
|
||||
* @param bl: Object that is moving
|
||||
* @return Duration of the walkpath
|
||||
*/
|
||||
t_tick unit_get_walkpath_time(struct block_list& bl)
|
||||
{
|
||||
t_tick time = 0;
|
||||
unsigned short speed = status_get_speed(&bl);
|
||||
struct unit_data* ud = unit_bl2ud(&bl);
|
||||
|
||||
// The next walk start time is calculated.
|
||||
for (uint8 i = 0; i < ud->walkpath.path_len; i++) {
|
||||
if (direction_diagonal(ud->walkpath.path[i]))
|
||||
time += speed * MOVE_DIAGONAL_COST / MOVE_COST;
|
||||
else
|
||||
time += speed;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes unit attempt to run away from target using hard paths
|
||||
* @param bl: Object that is running away from target
|
||||
* @param target: Target
|
||||
* @param dist: How far bl should run
|
||||
* @param flag: unit_walktoxy flag
|
||||
* @return 1: Success 0: Fail
|
||||
* @return The duration the unit will run (0 on fail)
|
||||
*/
|
||||
int unit_escape(struct block_list *bl, struct block_list *target, short dist, uint8 flag)
|
||||
t_tick unit_escape(struct block_list *bl, struct block_list *target, short dist, uint8 flag)
|
||||
{
|
||||
uint8 dir = map_calc_dir(target, bl->x, bl->y);
|
||||
|
||||
while( dist > 0 && map_getcell(bl->m, bl->x + dist*dirx[dir], bl->y + dist*diry[dir], CELL_CHKNOREACH) )
|
||||
dist--;
|
||||
|
||||
return ( dist > 0 && unit_walktoxy(bl, bl->x + dist*dirx[dir], bl->y + dist*diry[dir], flag) );
|
||||
if (dist > 0 && unit_walktoxy(bl, bl->x + dist * dirx[dir], bl->y + dist * diry[dir], flag))
|
||||
return unit_get_walkpath_time(*bl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2784,10 +2816,14 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, t_tick tick)
|
||||
unit_stop_walking(src,1);
|
||||
|
||||
if(md) {
|
||||
//First attack is always a normal attack
|
||||
if(md->state.skillstate == MSS_ANGRY || md->state.skillstate == MSS_BERSERK) {
|
||||
if (mobskill_use(md,tick,-1))
|
||||
// Berserk skills can replace normal attacks except for the first attack
|
||||
// If this is the first attack, the state is not Berserk yet, so the skill check is skipped
|
||||
if(md->state.skillstate == MSS_BERSERK) {
|
||||
if (mobskill_use(md, tick, -1)) {
|
||||
// Setting the delay here because not all monster skill use situations will cause an attack delay
|
||||
ud->attackabletime = tick + sstatus->adelay;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Set mob's ANGRY/BERSERK states.
|
||||
md->state.skillstate = md->state.aggressive?MSS_ANGRY:MSS_BERSERK;
|
||||
@ -2818,6 +2854,7 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, t_tick tick)
|
||||
return 1;
|
||||
|
||||
ud->attackabletime = tick + sstatus->adelay;
|
||||
ud->skill_id = 0;
|
||||
|
||||
// You can't move if you can't attack neither.
|
||||
if (src->type&battle_config.attack_walk_delay)
|
||||
|
@ -44,7 +44,6 @@ struct unit_data {
|
||||
t_tick canmove_tick;
|
||||
bool immune_attack; ///< Whether the unit is immune to attacks
|
||||
uint8 dir;
|
||||
unsigned char walk_count;
|
||||
unsigned char target_count;
|
||||
struct s_udState {
|
||||
unsigned change_walk_target : 1 ;
|
||||
@ -122,7 +121,8 @@ bool unit_can_move(struct block_list *bl);
|
||||
int unit_is_walking(struct block_list *bl);
|
||||
int unit_set_walkdelay(struct block_list *bl, t_tick tick, t_tick delay, int type);
|
||||
|
||||
int unit_escape(struct block_list *bl, struct block_list *target, short dist, uint8 flag = 0);
|
||||
t_tick unit_get_walkpath_time(struct block_list& bl);
|
||||
t_tick unit_escape(struct block_list *bl, struct block_list *target, short dist, uint8 flag = 0);
|
||||
|
||||
// Instant unit changes
|
||||
bool unit_movepos(struct block_list *bl, short dst_x, short dst_y, int easy, bool checkpath);
|
||||
|
Loading…
x
Reference in New Issue
Block a user