diff options
Diffstat (limited to 'src/map/unit.c')
-rw-r--r-- | src/map/unit.c | 565 |
1 files changed, 431 insertions, 134 deletions
diff --git a/src/map/unit.c b/src/map/unit.c index af0c0a948..a5bd282a9 100644 --- a/src/map/unit.c +++ b/src/map/unit.c @@ -95,9 +95,18 @@ int unit_walktoxy_sub(struct block_list *bl) ud = unit->bl2ud(bl); if(ud == NULL) return 0; + memset(&wpd, 0, sizeof(wpd)); + if( !path->search(&wpd,bl->m,bl->x,bl->y,ud->to_x,ud->to_y,ud->state.walk_easy,CELL_CHKNOPASS) ) return 0; +#ifdef OFFICIAL_WALKPATH + if( !path->search_long(NULL, bl->m, bl->x, bl->y, ud->to_x, ud->to_y, CELL_CHKNOPASS) // Check if there is an obstacle between + && wpd.path_len > 14 // Official number of walkable cells is 14 if and only if there is an obstacle between. [malufett] + && (bl->type != BL_NPC) ) // If type is a NPC, please disregard. + return 0; +#endif + memcpy(&ud->walkpath,&wpd,sizeof(wpd)); if (ud->target_to && ud->chaserange>1) { @@ -106,11 +115,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]; @@ -137,9 +146,79 @@ int unit_walktoxy_sub(struct block_list *bl) return 1; } +/** + * Triggered on full step if stepaction is true and executes remembered action. + * @param tid: Timer ID + * @param tick: Unused + * @param id: ID of bl to do the action + * @param data: Not used + * @return 1: Success 0: Fail (No valid bl) + */ +int unit_step_timer(int tid, int64 tick, int id, intptr_t data) +{ + struct block_list *bl; + struct unit_data *ud; + int target_id; + + bl = map->id2bl(id); + + if (!bl || bl->prev == NULL) + return 0; + + ud = unit->bl2ud(bl); + + if(!ud) + return 0; + + if(ud->steptimer != tid) { + ShowError("unit_step_timer mismatch %d != %d\n",ud->steptimer,tid); + return 0; + } + + ud->steptimer = INVALID_TIMER; + + if(!ud->stepaction) + return 0; + + //Set to false here because if an error occurs, it should not be executed again + ud->stepaction = false; + + if(!ud->target_to) + return 0; + + //Flush target_to as it might contain map coordinates which should not be used by other functions + target_id = ud->target_to; + ud->target_to = 0; + + //If stepaction is set then we remembered a client request that should be executed on the next step + //Execute request now if target is in attack range + if(ud->stepskill_id && skill->get_inf(ud->stepskill_id) & INF_GROUND_SKILL) { + //Execute ground skill + struct map_data *md = &map->list[bl->m]; + unit->skilluse_pos(bl, target_id%md->xs, target_id/md->xs, ud->stepskill_id, ud->stepskill_lv); + } else { + //If a player has target_id set and target is in range, attempt attack + struct block_list *tbl = map->id2bl(target_id); + if (!tbl || !status->check_visibility(bl, tbl)) { + return 0; + } + if(ud->stepskill_id == 0) { + //Execute normal attack + unit->attack(bl, tbl->id, (ud->state.attack_continue) + 2); + } else { + //Execute non-ground skill + unit->skilluse_id(bl, tbl->id, ud->stepskill_id, ud->stepskill_lv); + } + } + + return 1; +} + + int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { int i; int x,y,dx,dy; + unsigned char icewall_walk_block; uint8 dir; struct block_list *bl; struct map_session_data *sd; @@ -178,9 +257,34 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { dx = dirx[(int)dir]; dy = diry[(int)dir]; - if(map->getcell(bl->m,x+dx,y+dy,CELL_CHKNOPASS)) + //Get icewall walk block depending on boss mode (players can't be trapped) + if(md && md->status.mode&MD_BOSS) + icewall_walk_block = battle_config.boss_icewall_walk_block; + else if(md) + icewall_walk_block = battle_config.mob_icewall_walk_block; + else + icewall_walk_block = 0; + + //Monsters will walk into an icewall from the west and south if they already started walking + if(map->getcell(bl->m,x+dx,y+dy,CELL_CHKNOPASS) + && (icewall_walk_block == 0 || !map->getcell(bl->m,x+dx,y+dy,CELL_CHKICEWALL) || dx < 0 || dy < 0)) 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 < 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); + //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)) + mob->skill_use(md, tick, -1); + mob->unlocktarget(md, tick); + if (!(++ud->walk_count%WALK_SKILL_INTERVAL)) + mob->skill_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); @@ -205,7 +309,7 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { if (bl->prev == NULL) //Script could have warped char, abort remaining of the function. return 0; } else - sd->areanpc_id=0; + npc->untouch_areanpc(sd, bl->m, x, y); if( sd->md ) { // mercenary should be warped after being 3 seconds too far from the master [greenbox] if( !check_distance_bl(&sd->bl, &sd->md->bl, MAX_MER_DISTANCE) ) { @@ -217,7 +321,6 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { } } else // reset the tick, he is not far anymore sd->md->masterteleport_timer = 0; - } if( sd->hd ) { if( homun_alive(sd->hd) && !check_distance_bl(&sd->bl, &sd->hd->bl, MAX_MER_DISTANCE) ) { @@ -231,6 +334,8 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { sd->hd->masterteleport_timer = 0; } } else if (md) { + //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 } else @@ -238,12 +343,15 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { if (md->min_chase > md->db->range3) md->min_chase--; //Walk skills are triggered regardless of target due to the idle-walk mob state. //But avoid triggering on stop-walk calls. - if(tid != INVALID_TIMER && - !(ud->walk_count%WALK_SKILL_INTERVAL) && - mob->skill_use(md, tick, -1)) - { - if (!(ud->skill_id == NPC_SELFDESTRUCTION && ud->skilltimer != INVALID_TIMER)) - { //Skill used, abort walking + if (tid != INVALID_TIMER + && !(ud->walk_count%WALK_SKILL_INTERVAL) + && map->list[bl->m].users > 0 + && mob->skill_use(md, tick, -1) + ) { + if (!(ud->skill_id == NPC_SELFDESTRUCTION && ud->skilltimer != INVALID_TIMER) + && md->state.skillstate != MSS_WALK //Walk skills are supposed to be used while walking + ) { + //Skill used, abort walking clif->fixpos(bl); //Fix position as walk has been canceled. return 0; } @@ -275,8 +383,29 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { if(tid == INVALID_TIMER) //A directly invoked timer is from battle_stop_walking, therefore the rest is irrelevant. return 0; - if(ud->state.change_walk_target) - return unit->walktoxy_sub(bl); + //If stepaction is set then we remembered a client request that should be executed on the next step + if (ud->stepaction && ud->target_to) { + //Delete old stepaction even if not executed yet, the latest command is what counts + if(ud->steptimer != INVALID_TIMER) { + timer->delete(ud->steptimer, unit->step_timer); + ud->steptimer = INVALID_TIMER; + } + //Delay stepactions by half a step (so they are executed at full step) + if(ud->walkpath.path[ud->walkpath.path_pos]&1) + i = status->get_speed(bl)*14/20; + else + i = status->get_speed(bl)/2; + ud->steptimer = timer->add(tick+i, unit->step_timer, bl->id, 0); + } + + if(ud->state.change_walk_target) { + if(unit->walktoxy_sub(bl)) { + return 1; + } else { + clif->fixpos(bl); + return 0; + } + } ud->walkpath.path_pos++; if(ud->walkpath.path_pos>=ud->walkpath.path_len) @@ -294,7 +423,7 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { //Keep trying to run. if ( !(unit->run(bl, NULL, SC_RUN) || unit->run(bl, sd, SC_WUGDASH)) ) ud->state.running = 0; - } else if (ud->target_to) { + } else if (!ud->stepaction && ud->target_to) { //Update target trajectory. struct block_list *tbl = map->id2bl(ud->target_to); if (!tbl || !status->check_visibility(bl, tbl)) { @@ -308,21 +437,31 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { } if (tbl->m == bl->m && check_distance_bl(bl, tbl, ud->chaserange)) { //Reached destination. - if (ud->state.attack_continue) - { //Aegis uses one before every attack, we should + if (ud->state.attack_continue) { + //Aegis uses one before every attack, we should //only need this one for syncing purposes. [Skotlex] ud->target_to = 0; clif->fixpos(bl); unit->attack(bl, tbl->id, ud->state.attack_continue); } } else { //Update chase-path - unit->walktobl(bl, tbl, ud->chaserange, ud->state.walk_easy|(ud->state.attack_continue?2:0)); + unit->walktobl(bl, tbl, ud->chaserange, ud->state.walk_easy|(ud->state.attack_continue? 1 : 0)); return 0; } } else { //Stopped walking. Update to_x and to_y to current location [Skotlex] ud->to_x = bl->x; ud->to_y = bl->y; + + if(map->count_oncell(bl->m, x, y, BL_CHAR|BL_NPC, 1) > battle_config.official_cell_stack_limit) { + //Walked on occupied cell, call unit_walktoxy again + if(ud->steptimer != INVALID_TIMER) { + //Execute step timer on next step instead + timer->delete(ud->steptimer, unit->step_timer); + ud->steptimer = INVALID_TIMER; + } + return unit->walktoxy(bl, x, y, 8); + } } return 0; } @@ -340,6 +479,7 @@ int unit_delay_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { //&1 -> 1/0 = easy/hard //&2 -> force walking //&4 -> Delay walking if the reason you can't walk is the canwalk delay +//&8 -> Search for an unoccupied cell and cancel if none available int unit_walktoxy( struct block_list *bl, short x, short y, int flag) { struct unit_data* ud = NULL; @@ -352,6 +492,9 @@ int unit_walktoxy( struct block_list *bl, short x, short y, int flag) if( ud == NULL) return 0; + if ((flag&8) && !map->closest_freecell(bl->m, &x, &y, BL_CHAR|BL_NPC, 1)) //This might change x and y + return 0; + if (!path->search(&wpd, bl->m, bl->x, bl->y, x, y, flag&1, CELL_CHKNOPASS)) // Count walk path cells return 0; @@ -377,7 +520,7 @@ int unit_walktoxy( struct block_list *bl, short x, short y, int flag) ud->state.walk_easy = flag&1; ud->to_x = x; ud->to_y = y; - unit->set_target(ud, 0); + unit->stop_attack(bl); //Sets target to 0 sc = status->get_sc(bl); if( sc ) { @@ -394,11 +537,6 @@ int unit_walktoxy( struct block_list *bl, short x, short y, int flag) return 1; } - if(ud->attacktimer != INVALID_TIMER) { - timer->delete( ud->attacktimer, unit->attack_timer ); - ud->attacktimer = INVALID_TIMER; - } - return unit->walktoxy_sub(bl); } @@ -430,8 +568,8 @@ int unit_walktobl_sub(int tid, int64 tick, int id, intptr_t data) { // if flag&2, start attacking upon arrival within range, otherwise just walk to that character. int unit_walktobl(struct block_list *bl, struct block_list *tbl, int range, int flag) { - struct unit_data *ud = NULL; - struct status_change *sc = NULL; + struct unit_data *ud = NULL; + struct status_change *sc = NULL; nullpo_ret(bl); nullpo_ret(tbl); @@ -447,13 +585,17 @@ int unit_walktobl(struct block_list *bl, struct block_list *tbl, int range, int ud->to_y = bl->y; ud->target_to = 0; return 0; + } else if (range == 0) { + //Should walk on the same cell as target (for looters) + ud->to_x = tbl->x; + ud->to_y = tbl->y; } ud->state.walk_easy = flag&1; ud->target_to = tbl->id; ud->chaserange = range; //Note that if flag&2, this SHOULD be attack-range ud->state.attack_continue = flag&2?1:0; //Chase to attack. - unit->set_target(ud, 0); + unit->stop_attack(bl); //Sets target to 0 sc = status->get_sc(bl); if (sc && (sc->data[SC_CONFUSION] || sc->data[SC__CHAOS])) //Randomize the target position @@ -465,8 +607,8 @@ int unit_walktobl(struct block_list *bl, struct block_list *tbl, int range, int return 1; } - if(DIFF_TICK(ud->canmove_tick, timer->gettick()) > 0) - { //Can't move, wait a bit before invoking the movement. + if (DIFF_TICK(ud->canmove_tick, timer->gettick()) > 0) { + //Can't move, wait a bit before invoking the movement. timer->add(ud->canmove_tick+1, unit->walktobl_sub, bl->id, ud->target); return 1; } @@ -474,11 +616,6 @@ int unit_walktobl(struct block_list *bl, struct block_list *tbl, int range, int if(!unit->can_move(bl)) return 0; - if(ud->attacktimer != INVALID_TIMER) { - timer->delete( ud->attacktimer, unit->attack_timer ); - ud->attacktimer = INVALID_TIMER; - } - if (unit->walktoxy_sub(bl)) { set_mobstate(bl, flag&2); return 1; @@ -548,7 +685,7 @@ bool unit_run( struct block_list *bl, struct map_session_data *sd, enum sc_type break; //if sprinting and there's a PC/Mob/NPC, block the path [Kevin] - if( map->count_oncell(bl->m, to_x+dir_x, to_y+dir_y, BL_PC|BL_MOB|BL_NPC) ) + if ( map->count_oncell(bl->m, to_x + dir_x, to_y + dir_y, BL_PC | BL_MOB | BL_NPC, 0x2) ) break; to_x += dir_x; @@ -630,7 +767,7 @@ int unit_movepos(struct block_list *bl, short dst_x, short dst_y, int easy, bool if (bl->prev == NULL) //Script could have warped char, abort remaining of the function. return 0; } else - sd->areanpc_id=0; + npc->untouch_areanpc(sd, bl->m, bl->x, bl->y); if( sd->status.pet_id > 0 && sd->pd && sd->pd->pet.intimate > 0 ) { // Check if pet needs to be teleported. [Skotlex] @@ -666,7 +803,7 @@ int unit_setdir(struct block_list *bl,unsigned char dir) uint8 unit_getdir(struct block_list *bl) { struct unit_data *ud; nullpo_ret(bl); - + if( bl->type == BL_NPC ) return ((TBL_NPC*)bl)->dir; ud = unit->bl2ud(bl); @@ -700,6 +837,7 @@ int unit_blown(struct block_list* bl, int dx, int dy, int count, int flag) } if( sd ) { + unit->stop_stepaction(bl); //Stop stepaction when knocked back sd->ud.to_x = nx; sd->ud.to_y = ny; } @@ -729,7 +867,7 @@ int unit_blown(struct block_list* bl, int dx, int dy, int count, int flag) if(map->getcell(bl->m, bl->x, bl->y, CELL_CHKNPC)) { npc->touch_areanpc(sd, bl->m, bl->x, bl->y); } else { - sd->areanpc_id = 0; + npc->untouch_areanpc(sd, bl->m, bl->x, bl->y);; } } } @@ -930,7 +1068,7 @@ int unit_can_move(struct block_list *bl) { || sc->data[SC_ELECTRICSHOCKER] || sc->data[SC_WUGBITE] || sc->data[SC_THORNS_TRAP] - || sc->data[SC_MAGNETICFIELD] + || ( sc->data[SC_MAGNETICFIELD] && !sc->data[SC_HOVERING] ) || sc->data[SC__MANHOLE] || sc->data[SC_CURSEDCIRCLE_ATKER] || sc->data[SC_CURSEDCIRCLE_TARGET] @@ -939,7 +1077,6 @@ int unit_can_move(struct block_list *bl) { || (sc->data[SC_CAMOUFLAGE] && sc->data[SC_CAMOUFLAGE]->val1 < 3 && !(sc->data[SC_CAMOUFLAGE]->val3&1)) || sc->data[SC_MEIKYOUSISUI] || sc->data[SC_KG_KAGEHUMI] - || sc->data[SC_KYOUGAKU] || sc->data[SC_NEEDLE_OF_PARALYZE] || sc->data[SC_VACUUM_EXTREME] || (sc->data[SC_FEAR] && sc->data[SC_FEAR]->val2 > 0) @@ -956,7 +1093,6 @@ int unit_can_move(struct block_list *bl) { ) ) return 0; - if (sc->opt1 > 0 && sc->opt1 != OPT1_STONEWAIT && sc->opt1 != OPT1_BURNING && !(sc->opt1 == OPT1_CRYSTALIZE && bl->type == BL_MOB)) return 0; @@ -965,6 +1101,17 @@ int unit_can_move(struct block_list *bl) { return 0; } + + // Icewall walk block special trapped monster mode + if(bl->type == BL_MOB) { + struct mob_data *md = BL_CAST(BL_MOB, bl); + if(md && ((md->status.mode&MD_BOSS && battle_config.boss_icewall_walk_block == 1 && map->getcell(bl->m,bl->x,bl->y,CELL_CHKICEWALL)) + || (!(md->status.mode&MD_BOSS) && battle_config.mob_icewall_walk_block == 1 && map->getcell(bl->m,bl->x,bl->y,CELL_CHKICEWALL)))) { + md->walktoxy_fail_count = 1; //Make sure rudeattacked skills are invoked + return 0; + } + } + return 1; } @@ -1000,25 +1147,26 @@ int unit_set_walkdelay(struct block_list *bl, int64 tick, int delay, int type) { struct unit_data *ud = unit->bl2ud(bl); if (delay <= 0 || !ud) return 0; - /** - * MvP mobs have no walk delay - **/ - if( bl->type == BL_MOB && (((TBL_MOB*)bl)->status.mode&MD_BOSS) ) - return 0; - if (type) { + //Bosses can ignore skill induced walkdelay (but not damage induced) + if(bl->type == BL_MOB && (((TBL_MOB*)bl)->status.mode&MD_BOSS)) + return 0; + //Make sure walk delay is not decreased if (DIFF_TICK(ud->canmove_tick, tick+delay) > 0) return 0; } else { //Don't set walk delays when already trapped. if (!unit->can_move(bl)) return 0; + //Immune to being stopped for double the flinch time + if (DIFF_TICK(ud->canmove_tick, tick-delay) > 0) + return 0; } ud->canmove_tick = tick + delay; - if (ud->walktimer != INVALID_TIMER) - { //Stop walking, if chasing, readjust timers. - if (delay == 1) - { //Minimal delay (walk-delay) disabled. Just stop walking. + if (ud->walktimer != INVALID_TIMER) { + //Stop walking, if chasing, readjust timers. + if (delay == 1) { + //Minimal delay (walk-delay) disabled. Just stop walking. unit->stop_walking(bl,4); } else { //Resume running after can move again [Kevin] @@ -1028,7 +1176,7 @@ int unit_set_walkdelay(struct block_list *bl, int64 tick, int delay, int type) { } else { - unit->stop_walking(bl,2|4); + unit->stop_walking(bl,4); if(ud->target) timer->add(ud->canmove_tick+1, unit->walktobl_sub, bl->id, ud->target); } @@ -1086,7 +1234,8 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui if(skill->not_ok(skill_id, sd)) // [MouseJstr] return 0; - switch(skill_id) { //Check for skills that auto-select target + switch (skill_id) { + //Check for skills that auto-select target case MO_CHAINCOMBO: if (sc && sc->data[SC_BLADESTOP]) { if ((target=map->id2bl(sc->data[SC_BLADESTOP]->val4)) == NULL) @@ -1103,6 +1252,17 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui return 0; } break; + case GC_WEAPONCRUSH: + if( sc && sc->data[SC_COMBOATTACK] && sc->data[SC_COMBOATTACK]->val1 == GC_WEAPONBLOCKING ) { + if( (target=map->id2bl(sc->data[SC_COMBOATTACK]->val2)) == NULL ) { + clif->skill_fail(sd,skill_id,USESKILL_FAIL_GC_WEAPONBLOCKING,0); + return 0; + } + } else { + clif->skill_fail(sd,skill_id,USESKILL_FAIL_GC_WEAPONBLOCKING,0); + return 0; + } + break; } if (target) target_id = target->id; @@ -1138,33 +1298,48 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui if(!status->check_skilluse(src, target, skill_id, 0)) return 0; + if( src != target && status->isdead(target) ) { + /** + * Skills that may be cast on dead targets + **/ + switch( skill_id ) { + case NPC_WIDESOULDRAIN: + case PR_REDEMPTIO: + case ALL_RESURRECTION: + case WM_DEADHILLHERE: + break; + default: + return 1; + } + } + tstatus = status->get_status_data(target); // Record the status of the previous skill) - if(sd) { + if (sd) { - if( (skill->get_inf2(skill_id)&INF2_ENSEMBLE_SKILL) && skill->check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 1 ) { - clif->skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + if ((skill->get_inf2(skill_id)&INF2_ENSEMBLE_SKILL) && skill->check_pc_partner(sd, skill_id, &skill_lv, 1, 0) < 1) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return 0; } - - switch(skill_id){ + + switch (skill_id){ case SA_CASTCANCEL: - if(ud->skill_id != skill_id){ + if (ud->skill_id != skill_id){ sd->skill_id_old = ud->skill_id; sd->skill_lv_old = ud->skill_lv; } break; case BD_ENCORE: //Prevent using the dance skill if you no longer have the skill in your tree. - if(!sd->skill_id_dance || pc->checkskill(sd,sd->skill_id_dance)<=0){ - clif->skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + if (!sd->skill_id_dance || pc->checkskill(sd, sd->skill_id_dance) <= 0){ + clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); return 0; } sd->skill_id_old = skill_id; break; case WL_WHITEIMPRISON: - if( battle->check_target(src,target,BCT_SELF|BCT_ENEMY) < 0 ) { - clif->skill_fail(sd,skill_id,USESKILL_FAIL_TOTARGET,0); + if (battle->check_target(src, target, BCT_SELF | BCT_ENEMY) < 0) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_TOTARGET, 0); return 0; } break; @@ -1175,13 +1350,20 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui sd->skill_lv_old = skill_lv; break; } - /* temporarily disabled, awaiting for kenpachi to detail this so we can make it work properly */ + } + + if (sd || src->type == BL_HOM){ + if (!sd && (target = battle->get_master(src))) + sd = map->id2sd(target->id); + if (sd){ + /* temporarily disabled, awaiting for kenpachi to detail this so we can make it work properly */ #if 0 - if ( sd->skillitem != skill_id && !skill->check_condition_castbegin(sd, skill_id, skill_lv) ) + if (sd->skillitem != skill_id && !skill->check_condition_castbegin(sd, skill_id, skill_lv)) #else - if ( !skill->check_condition_castbegin(sd, skill_id, skill_lv) ) + if (!skill->check_condition_castbegin(sd, skill_id, skill_lv)) #endif - return 0; + return 0; + } } if( src->type == BL_MOB ) @@ -1198,6 +1380,18 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui else range = skill->get_range2(src, skill_id, skill_lv); // Skill cast distance from database + // New action request received, delete previous action request if not executed yet + if(ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(src); + // Remember the skill request from the client while walking to the next cell + if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && !battle->check_range(src, target, range-1)) { + ud->stepaction = true; + ud->target_to = target_id; + ud->stepskill_id = skill_id; + ud->stepskill_lv = skill_lv; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } + //Check range when not using skill on yourself or is a combo-skill during attack //(these are supposed to always have the same range as your attack) if( src->id != target_id && (!temp || ud->attacktimer == INVALID_TIMER) ) { @@ -1241,6 +1435,25 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui casttime = -1; temp = 1; break; + case CR_DEVOTION: + if (sd) { + int i = 0, count = min(skill_lv, 5); + ARR_FIND(0, count, i, sd->devotion[i] == target_id); + if (i == count) { + ARR_FIND(0, count, i, sd->devotion[i] == 0); + if(i == count) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0); + return 0; // Can't cast on other characters when limit is reached + } + } + } + break; + case AB_CLEARANCE: + if( target->type != BL_MOB && battle->check_target(src,target,BCT_PARTY) <= 0 && sd ) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_TOTARGET, 0); + return 0; + } + break; case SR_GATEOFHELL: case SR_TIGERCANNON: if (sc && sc->data[SC_COMBOATTACK] && @@ -1277,7 +1490,7 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui case RA_WUGDASH: if (sc && sc->data[SC_WUGDASH]) casttime = -1; - break; + break; case EL_WIND_SLASH: case EL_HURRICANE: case EL_TYPOON_MIS: @@ -1320,7 +1533,7 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui /** * why the if else chain: these 3 status do not stack, so its efficient that way. **/ - if( sc->data[SC_CLOAKING] && !(sc->data[SC_CLOAKING]->val4&4) && skill_id != AS_CLOAKING ) { + if( sc->data[SC_CLOAKING] && !(sc->data[SC_CLOAKING]->val4&4) && skill_id != AS_CLOAKING ) { status_change_end(src, SC_CLOAKING, INVALID_TIMER); if (!src->prev) return 0; //Warped away! } else if( sc->data[SC_CLOAKINGEXCEED] && !(sc->data[SC_CLOAKINGEXCEED]->val4&4) && skill_id != GC_CLOAKINGEXCEED ) { @@ -1328,10 +1541,10 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui if (!src->prev) return 0; } } - + if(!ud->state.running) //need TK_RUN or WUGDASH handler to be done before that, see bugreport:6026 unit->stop_walking(src,1);// even though this is not how official works but this will do the trick. bugreport:6829 - + // in official this is triggered even if no cast time. clif->skillcasting(src, src->id, target_id, 0,0, skill_id, skill->get_ele(skill_id, skill_lv), casttime); if( casttime > 0 || temp ) @@ -1434,13 +1647,11 @@ int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, ui if( skill->not_ok(skill_id, sd) || !skill->check_condition_castbegin(sd, skill_id, skill_lv) ) return 0; /** - * "WHY IS IT HEREE": pneuma cannot be canceled past this point, the client displays the animation even, - * if we cancel it from nodamage_id, so it has to be here for it to not display the animation. + * "WHY IS IT HEREE": ice wall cannot be canceled past this point, the client displays the animation even, + * if we cancel it from castend_pos, so it has to be here for it to not display the animation. **/ - if( skill_id == AL_PNEUMA && map->getcell(src->m, skill_x, skill_y, CELL_CHKLANDPROTECTOR) ) { - clif->skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0); + if ( skill_id == WZ_ICEWALL && map->getcell(src->m, skill_x, skill_y, CELL_CHKNOICEWALL) ) return 0; - } } if (!status->check_skilluse(src, NULL, skill_id, 0)) @@ -1452,7 +1663,7 @@ int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, ui return 0; } - /* Check range and obstacle */ + /* Check range and obstacle */ bl.type = BL_NUL; bl.m = src->m; bl.x = skill_x; @@ -1463,10 +1674,24 @@ int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, ui else range = skill->get_range2(src, skill_id, skill_lv); // Skill cast distance from database + // New action request received, delete previous action request if not executed yet + if(ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(src); + // Remember the skill request from the client while walking to the next cell + if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && !battle->check_range(src, &bl, range-1)) { + struct map_data *md = &map->list[src->m]; + // Convert coordinates to target_to so we can use it as target later + ud->stepaction = true; + ud->target_to = (skill_x + skill_y*md->xs); + ud->stepskill_id = skill_id; + ud->stepskill_lv = skill_lv; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } + if( skill->get_state(ud->skill_id) == ST_MOVE_ENABLE ) { if( !unit->can_reach_bl(src, &bl, range + 1, 1, NULL, NULL) ) return 0; //Walk-path check failed. - } else if( !battle->check_range(src, &bl, range + 1) ) + } else if( !battle->check_range(src, &bl, range) ) return 0; //Arrow-path check failed. unit->stop_attack(src); @@ -1486,14 +1711,14 @@ int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, ui ud->state.skillcastcancel = castcancel&&casttime>0?1:0; if( !sd || sd->skillitem != skill_id || skill->get_cast(skill_id,skill_lv) ) ud->canact_tick = tick + casttime + 100; -// if( sd ) -// { -// switch( skill_id ) -// { -// case ????: -// sd->canequip_tick = tick + casttime; -// } -// } +#if 0 + if (sd) { + switch (skill_id) { + case ????: + sd->canequip_tick = tick + casttime; + } + } +#endif // 0 ud->skill_id = skill_id; ud->skill_lv = skill_lv; ud->skillx = skill_x; @@ -1548,18 +1773,51 @@ int unit_set_target(struct unit_data* ud, int target_id) return 0; } -int unit_stop_attack(struct block_list *bl) +/** + * Stop a unit's attacks + * @param bl: Object to stop + */ +void unit_stop_attack(struct block_list *bl) { - struct unit_data *ud = unit->bl2ud(bl); - nullpo_ret(bl); + struct unit_data *ud; + nullpo_retv(bl); + ud = unit->bl2ud(bl); + nullpo_retv(ud); - if(!ud || ud->attacktimer == INVALID_TIMER) - return 0; + //Clear target + unit->set_target(ud, 0); + + if(ud->attacktimer == INVALID_TIMER) + return; - timer->delete( ud->attacktimer, unit->attack_timer ); + //Clear timer + timer->delete(ud->attacktimer, unit->attack_timer); ud->attacktimer = INVALID_TIMER; - unit->set_target(ud, 0); - return 0; +} + +/** + * Stop a unit's step action + * @param bl: Object to stop + */ +void unit_stop_stepaction(struct block_list *bl) +{ + struct unit_data *ud; + nullpo_retv(bl); + ud = unit->bl2ud(bl); + nullpo_retv(ud); + + //Clear remembered step action + ud->stepaction = false; + ud->target_to = 0; + ud->stepskill_id = 0; + ud->stepskill_lv = 0; + + if(ud->steptimer == INVALID_TIMER) + return; + + //Clear timer + timer->delete(ud->steptimer, unit->step_timer); + ud->steptimer = INVALID_TIMER; } //Means current target is unattackable. For now only unlocks mobs. @@ -1568,6 +1826,7 @@ int unit_unattackable(struct block_list *bl) struct unit_data *ud = unit->bl2ud(bl); if (ud) { ud->state.attack_continue = 0; + ud->state.step_attack = 0; unit->set_target(ud, 0); } @@ -1585,6 +1844,7 @@ int unit_unattackable(struct block_list *bl) int unit_attack(struct block_list *src,int target_id,int continuous) { struct block_list *target; struct unit_data *ud; + int range; nullpo_ret(ud = unit->bl2ud(src)); @@ -1613,19 +1873,30 @@ int unit_attack(struct block_list *src,int target_id,int continuous) { unit->unattackable(src); return 1; } - ud->state.attack_continue = continuous; + ud->state.attack_continue = (continuous&1)?1:0; + ud->state.step_attack = (continuous&2)?1:0; unit->set_target(ud, target_id); + range = status_get_range(src); + if (continuous) //If you're to attack continuously, set to auto-case character - ud->chaserange = status_get_range(src); + ud->chaserange = range; //Just change target/type. [Skotlex] if(ud->attacktimer != INVALID_TIMER) return 0; - //Set Mob's ANGRY/BERSERK states. - if(src->type == BL_MOB) - ((TBL_MOB*)src)->state.skillstate = ((TBL_MOB*)src)->state.aggressive?MSS_ANGRY:MSS_BERSERK; + // New action request received, delete previous action request if not executed yet + if(ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(src); + // Remember the attack request from the client while walking to the next cell + if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && !battle->check_range(src, target, range-1)) { + ud->stepaction = true; + ud->target_to = ud->target; + ud->stepskill_id = 0; + ud->stepskill_lv = 0; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } if(DIFF_TICK(ud->attackabletime, timer->gettick()) > 0) //Do attack next time it is possible. [Skotlex] @@ -1710,7 +1981,7 @@ bool unit_can_reach_bl(struct block_list *bl,struct block_list *tbl, int range, /*========================================== * Calculates position of Pet/Mercenary/Homunculus/Elemental *------------------------------------------*/ -int unit_calc_pos(struct block_list *bl, int tx, int ty, uint8 dir) +int unit_calc_pos(struct block_list *bl, int tx, int ty, uint8 dir) { int dx, dy, x, y, i, k; struct unit_data *ud = unit->bl2ud(bl); @@ -1827,15 +2098,19 @@ int unit_attack_timer_sub(struct block_list* src, int tid, int64 tick) { } sstatus = status->get_status_data(src); - range = sstatus->rhw.range + 1; + range = sstatus->rhw.range; - if( unit->is_walking(target) ) - range++; //Extra range when chasing - if( !check_distance_bl(src,target,range) ) { //Chase if required. - if(sd) - clif->movetoattack(sd,target); - else if(ud->state.attack_continue) - unit->walktobl(src,target,ud->chaserange,ud->state.walk_easy|2); + if( (unit->is_walking(target) || ud->state.step_attack) + && (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 + clif->movetoattack(sd,target); + return 1; + } else if(md && !check_distance_bl(src,target,range)) { + // Monster: Chase if required + unit->walktobl(src,target,ud->chaserange,ud->state.walk_easy|2); return 1; } if( !battle->check_range(src,target,range) ) { @@ -1857,10 +2132,17 @@ int unit_attack_timer_sub(struct block_list* src, int tid, int64 tick) { if(ud->walktimer != INVALID_TIMER) unit->stop_walking(src,1); if(md) { - if (mob->skill_use(md,tick,-1)) - return 1; - if (sstatus->mode&MD_ASSIST && DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME) - { // Link monsters nearby [Skotlex] + //First attack is always a normal attack + if(md->state.skillstate == MSS_ANGRY || md->state.skillstate == MSS_BERSERK) { + if (mob->skill_use(md,tick,-1)) + return 1; + } else { + // Set mob's ANGRY/BERSERK states. + md->state.skillstate = md->state.aggressive?MSS_ANGRY:MSS_BERSERK; + } + + if (sstatus->mode&MD_ASSIST && DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME) { + // Link monsters nearby [Skotlex] md->last_linktime = tick; map->foreachinrange(mob->linksearch, src, md->db->range2, BL_MOB, md->class_, target, tick); } @@ -1882,7 +2164,7 @@ int unit_attack_timer_sub(struct block_list* src, int tid, int64 tick) { return 1; ud->attackabletime = tick + sstatus->adelay; -// You can't move if you can't attack neither. + // You can't move if you can't attack neither. if (src->type&battle_config.attack_walk_delay) unit->set_walkdelay(src, tick, sstatus->amotion, 1); } @@ -1927,8 +2209,8 @@ int unit_skillcastcancel(struct block_list *bl,int type) if (!ud->state.skillcastcancel) return 0; - if (sd && (sd->special_state.no_castcancel2 || - ( sd->special_state.no_castcancel && !map_flag_gvg(bl->m) && !map->list[bl->m].flag.battleground))) //fixed flags being read the wrong way around [blackhole89] + if (sd && (sd->special_state.no_castcancel2 + || (sd->special_state.no_castcancel && !map_flag_gvg(bl->m) && !map->list[bl->m].flag.battleground))) //fixed flags being read the wrong way around [blackhole89] return 0; } @@ -1975,6 +2257,7 @@ void unit_dataset(struct block_list *bl) { ud->walktimer = INVALID_TIMER; ud->skilltimer = INVALID_TIMER; ud->attacktimer = INVALID_TIMER; + ud->steptimer = INVALID_TIMER; ud->attackabletime = ud->canact_tick = ud->canmove_tick = timer->gettick(); @@ -2040,15 +2323,19 @@ int unit_remove_map(struct block_list *bl, clr_type clrtype, const char* file, i map->freeblock_lock(); - unit->set_target(ud, 0); - if (ud->walktimer != INVALID_TIMER) unit->stop_walking(bl,0); - if (ud->attacktimer != INVALID_TIMER) - unit->stop_attack(bl); if (ud->skilltimer != INVALID_TIMER) unit->skillcastcancel(bl,0); + //Clear target even if there is no timer + if (ud->target || ud->attacktimer != INVALID_TIMER) + unit->stop_attack(bl); + + //Clear stepaction even if there is no timer + if (ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(bl); + // Do not reset can-act delay. [Skotlex] ud->attackabletime = ud->canmove_tick /*= ud->canact_tick*/ = timer->gettick(); if(sc && sc->count ) { //map-change/warp dispells. @@ -2172,7 +2459,7 @@ int unit_remove_map(struct block_list *bl, clr_type clrtype, const char* file, i sd->debug_file, sd->debug_line, sd->debug_func, file, line, func); } else if (--map->list[bl->m].users == 0 && battle_config.dynamic_mobs) //[Skotlex] map->removemobs(bl->m); - if( !(sd->sc.option&OPTION_INVISIBLE) ) { + if (!(pc_isinvisible(sd))) { // decrement the number of active pvp players on the map --map->list[bl->m].users_pvp; } @@ -2267,10 +2554,10 @@ int unit_remove_map(struct block_list *bl, clr_type clrtype, const char* file, i void unit_remove_map_pc(struct map_session_data *sd, clr_type clrtype) { unit->remove_map(&sd->bl,clrtype,ALC_MARK); - + //CLR_RESPAWN is the warp from logging out, CLR_TELEPORT is the warp from teleporting, but pets/homunc need to just 'vanish' instead of showing the warping animation. if (clrtype == CLR_RESPAWN || clrtype == CLR_TELEPORT) clrtype = CLR_OUTSIGHT; - + if(sd->pd) unit->remove_map(&sd->pd->bl, clrtype, ALC_MARK); if(homun_alive(sd->hd)) @@ -2299,7 +2586,7 @@ int unit_free(struct block_list *bl, clr_type clrtype) { nullpo_ret(ud); map->freeblock_lock(); - if( bl->prev ) //Players are supposed to logout with a "warp" effect. + if( bl->prev ) //Players are supposed to logout with a "warp" effect. unit->remove_map(bl, clrtype, ALC_MARK); switch( bl->type ) { @@ -2310,7 +2597,7 @@ int unit_free(struct block_list *bl, clr_type clrtype) { unsigned int k; sd->state.loggingout = 1; - + if( status->isdead(bl) ) pc->setrestartvalue(sd,2); @@ -2332,8 +2619,8 @@ int unit_free(struct block_list *bl, clr_type clrtype) { pc->cleareventtimer(sd); pc->inventory_rental_clear(sd); pc->delspiritball(sd,sd->spiritball,1); - for(i = 1; i < 5; i++) - pc->del_charm(sd, sd->charm[i], i); + for(i = SPIRITS_TYPE_CHARM_WATER; i < SPIRITS_TYPE_SPHERE; i++) + pc->del_charm(sd, sd->spiritcharm[i], i); if( sd->st && sd->st->state != RUN ) {// free attached scripts that are waiting script->free_state(sd->st); @@ -2369,7 +2656,7 @@ int unit_free(struct block_list *bl, clr_type clrtype) { sd->quest_log = NULL; sd->num_quests = sd->avail_quests = 0; } - + for( k = 0; k < sd->hdatac; k++ ) { if( sd->hdata[k]->flag.free ) { aFree(sd->hdata[k]->data); @@ -2393,10 +2680,7 @@ int unit_free(struct block_list *bl, clr_type clrtype) { if( pd->s_skill ) { if (pd->s_skill->timer != INVALID_TIMER) { - if (pd->s_skill->id) - timer->delete(pd->s_skill->timer, pet->skill_support_timer); - else - timer->delete(pd->s_skill->timer, pet->heal_timer); + timer->delete(pd->s_skill->timer, pet->skill_support_timer); } aFree(pd->s_skill); pd->s_skill = NULL; @@ -2423,10 +2707,10 @@ int unit_free(struct block_list *bl, clr_type clrtype) { aFree (pd->loot); pd->loot = NULL; } - if( pd->pet.intimate > 0 ) + if (pd->pet.intimate > 0) { intif->save_petdata(pd->pet.account_id,&pd->pet); - else - { //Remove pet. + } else { + //Remove pet. intif->delete_petdata(pd->pet.pet_id); if (sd) sd->status.pet_id = 0; } @@ -2436,6 +2720,7 @@ int unit_free(struct block_list *bl, clr_type clrtype) { } case BL_MOB: { + unsigned int k; struct mob_data *md = (struct mob_data*)bl; if( md->spawn_timer != INVALID_TIMER ) { @@ -2490,6 +2775,15 @@ int unit_free(struct block_list *bl, clr_type clrtype) { mob->clone_delete(md); if( md->tomb_nid ) mob->mvptomb_destroy(md); + + for (k = 0; k < md->hdatac; k++) { + if( md->hdata[k]->flag.free ) { + aFree(md->hdata[k]->data); + } + aFree(md->hdata[k]); + } + if (md->hdata) + aFree(md->hdata); break; } case BL_HOM: @@ -2561,6 +2855,7 @@ int do_init_unit(bool minimal) { timer->add_func_list(unit->walktoxy_timer,"unit_walktoxy_timer"); timer->add_func_list(unit->walktobl_sub, "unit_walktobl_sub"); timer->add_func_list(unit->delay_walktoxy_timer,"unit_delay_walktoxy_timer"); + timer->add_func_list(unit->step_timer,"unit_step_timer"); return 0; } @@ -2571,7 +2866,7 @@ int do_final_unit(void) { void unit_defaults(void) { unit = &unit_s; - + unit->init = do_init_unit; unit->final = do_final_unit; /* */ @@ -2594,6 +2889,8 @@ void unit_defaults(void) { unit->warp = unit_warp; unit->stop_walking = unit_stop_walking; unit->skilluse_id = unit_skilluse_id; + unit->step_timer = unit_step_timer; + unit->stop_stepaction = unit_stop_stepaction; unit->is_walking = unit_is_walking; unit->can_move = unit_can_move; unit->resume_running = unit_resume_running; |