/*========================================== * AI of MOB whose is near a Player *------------------------------------------*/bool mob_ai_sub_hard(struct mob_data *md, int64 tick) { struct block_list *tbl = NULL, *abl = NULL; int mode; int view_range, can_move; if(md->bl.prev == NULL || md->status.hp <= 0) return false; if (DIFF_TICK(tick, md->last_thinktime) < MIN_MOBTHINKTIME) return false; md->last_thinktime = tick; if (md->ud.skilltimer != INVALID_TIMER) return false; if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= 3) return false; // Abnormalities if(( md->sc.opt1 > 0 && md->sc.opt1 != OPT1_STONEWAIT && md->sc.opt1 != OPT1_BURNING && md->sc.opt1 != OPT1_CRYSTALIZE ) || md->sc.data[SC_DEEP_SLEEP] || md->sc.data[SC_BLADESTOP] || md->sc.data[SC__MANHOLE] || md->sc.data[SC_CURSEDCIRCLE_TARGET]) { //Should reset targets. md->target_id = md->attacked_id = 0; return false; } if (md->sc.count && md->sc.data[SC_BLIND]) view_range = 3; else view_range = md->db->range2; mode = status_get_mode(&md->bl); can_move = (mode&MD_CANMOVE)&&unit->can_move(&md->bl); if (md->target_id) { //Check validity of current target. [Skotlex] tbl = map->id2bl(md->target_id); if (!tbl || tbl->m != md->bl.m || (md->ud.attacktimer == INVALID_TIMER && !status->check_skilluse(&md->bl, tbl, 0, 0)) || (md->ud.walktimer != INVALID_TIMER && !(battle_config.mob_ai&0x1) && !check_distance_bl(&md->bl, tbl, md->min_chase)) || ( tbl->type == BL_PC && ((((TBL_PC*)tbl)->state.gangsterparadise && !(mode&MD_BOSS)) || ((TBL_PC*)tbl)->invincible_timer != INVALID_TIMER) ) ) { //Unlock current target. if (mob->warpchase(md, tbl)) return true; //Chasing this target. mob->unlocktarget(md, tick-(battle_config.mob_ai&0x8?3000:0)); //Immediately do random walk. tbl = NULL; } } // Check for target change. if( md->attacked_id && mode&MD_CANATTACK ) { if( md->attacked_id == md->target_id ) { //Rude attacked check. if( !battle->check_range(&md->bl, tbl, md->status.rhw.range) && ( //Can't attack back and can't reach back. (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1) || md->sc.data[SC_WUGBITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNS_TRAP] || md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target. || !mob->can_reach(md, tbl, md->min_chase, MSS_RUSH) ) && md->state.attacked_count++ >= RUDE_ATTACKED_COUNT && !mob->skill_use(md, tick, MSC_RUDEATTACKED) // If can't rude Attack && can_move && unit->escape(&md->bl, tbl, rnd()%10 +1)) // Attempt escape { //Escaped md->attacked_id = 0; return true; } } else if( (abl = map->id2bl(md->attacked_id)) && (!tbl || mob->can_changetarget(md, abl, mode) || (md->sc.count && md->sc.data[SC__CHAOS]))) { int dist; if( md->bl.m != abl->m || abl->prev == NULL || (dist = distance_bl(&md->bl, abl)) >= MAX_MINCHASE // Attacker longer than visual area || battle->check_target(&md->bl, abl, BCT_ENEMY) <= 0 // Attacker is not enemy of mob || (battle_config.mob_ai&0x2 && !status->check_skilluse(&md->bl, abl, 0, 0)) // Cannot normal attack back to Attacker || (!battle->check_range(&md->bl, abl, md->status.rhw.range) // Not on Melee Range and ... && ( // Reach check (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0 && (battle_config.mob_ai&0x2 || (md->sc.data[SC_SPIDERWEB] && md->sc.data[SC_SPIDERWEB]->val1) || md->sc.data[SC_WUGBITE] || md->sc.data[SC_VACUUM_EXTREME] || md->sc.data[SC_THORNS_TRAP] || md->sc.data[SC__MANHOLE])) // Not yet confirmed if boss will teleport once it can't reach target. || !mob->can_reach(md, abl, dist+md->db->range3, MSS_RUSH) ) ) ) { // Rude attacked if (md->state.attacked_count++ >= RUDE_ATTACKED_COUNT && !mob->skill_use(md, tick, MSC_RUDEATTACKED) && can_move && !tbl && unit->escape(&md->bl, abl, rnd()%10 +1)) { //Escaped. //TODO: Maybe it shouldn't attempt to run if it has another, valid target? md->attacked_id = 0; return true; } } else if (!(battle_config.mob_ai&0x2) && !status->check_skilluse(&md->bl, abl, 0, 0)) { //Can't attack back, but didn't invoke a rude attacked skill... } else { //Attackable if (!tbl || dist < md->status.rhw.range || !check_distance_bl(&md->bl, tbl, dist) || battle->get_target(tbl) != md->bl.id) { //Change if the new target is closer than the actual one //or if the previous target is not attacking the mob. [Skotlex] md->target_id = md->attacked_id; // set target if (md->state.attacked_count) md->state.attacked_count--; //Should we reset rude attack count? md->min_chase = dist+md->db->range3; if(md->min_chase>MAX_MINCHASE) md->min_chase=MAX_MINCHASE; tbl = abl; //Set the new target } } } //Clear it since it's been checked for already. md->attacked_id = 0; } // Processing of slave monster if (md->master_id > 0 && mob->ai_sub_hard_slavemob(md, tick)) return true; // Scan area for targets if (!tbl && mode&MD_LOOTER && md->lootitem && DIFF_TICK(tick, md->ud.canact_tick) > 0 && (md->lootitem_count < LOOTITEM_SIZE || battle_config.monster_loot_type != 1) ) { // Scan area for items to loot, avoid trying to loot if the mob is full and can't consume the items. map->foreachinrange (mob->ai_sub_hard_lootsearch, &md->bl, view_range, BL_ITEM, md, &tbl); } if ((!tbl && mode&MD_AGGRESSIVE) || md->state.skillstate == MSS_FOLLOW) { map->foreachinrange (mob->ai_sub_hard_activesearch, &md->bl, view_range, DEFAULT_ENEMY_TYPE(md), md, &tbl, mode); } else if ((mode&MD_CHANGECHASE && (md->state.skillstate == MSS_RUSH || md->state.skillstate == MSS_FOLLOW)) || (md->sc.count && md->sc.data[SC__CHAOS])) { int search_size; search_size = view_range<md->status.rhw.range ? view_range:md->status.rhw.range; map->foreachinrange (mob->ai_sub_hard_changechase, &md->bl, search_size, DEFAULT_ENEMY_TYPE(md), md, &tbl); } if (!tbl) { //No targets available. if (mode&MD_ANGRY && !md->state.aggressive) md->state.aggressive = 1; //Restore angry state when no targets are available. /* bg guardians follow allies when no targets nearby */ if( md->bg_id && mode&MD_CANATTACK ) { if( md->ud.walktimer != INVALID_TIMER ) return true;/* we are already moving */ map->foreachinrange (mob->ai_sub_hard_bg_ally, &md->bl, view_range, BL_PC, md, &tbl, mode); if( tbl ) { if( distance_blxy(&md->bl, tbl->x, tbl->y) <= 3 || unit->walktobl(&md->bl, tbl, 1, 1) ) return true;/* we're moving or close enough don't unlock the target. */ } } //This handles triggering idle walk/skill. mob->unlocktarget(md, tick); return true; } //Target exists, attack or loot as applicable. if (tbl->type == BL_ITEM) { //Loot time. struct flooritem_data *fitem; if (md->ud.target == tbl->id && md->ud.walktimer != INVALID_TIMER) return true; //Already locked. if (md->lootitem == NULL) { //Can't loot... mob->unlocktarget (md, tick); return true; } if (!check_distance_bl(&md->bl, tbl, 1)) { //Still not within loot range. if (!(mode&MD_CANMOVE)) { //A looter that can't move? Real smart. mob->unlocktarget(md,tick); return true; } if (!can_move) //Stuck. Wait before walking. return true; md->state.skillstate = MSS_LOOT; if (!unit->walktobl(&md->bl, tbl, 1, 1)) mob->unlocktarget(md, tick); //Can't loot... return true; } //Within looting range. if (md->ud.attacktimer != INVALID_TIMER) return true; //Busy attacking? fitem = (struct flooritem_data *)tbl; //Logs items, taken by (L)ooter Mobs [Lupus] logs->pick_mob(md, LOG_TYPE_LOOT, fitem->item_data.amount, &fitem->item_data, NULL); if (md->lootitem_count < LOOTITEM_SIZE) { memcpy (&md->lootitem[md->lootitem_count++], &fitem->item_data, sizeof(md->lootitem[0])); } else { //Destroy first looted item... if (md->lootitem[0].card[0] == CARD0_PET) intif->delete_petdata( MakeDWord(md->lootitem[0].card[1],md->lootitem[0].card[2]) ); memmove(&md->lootitem[0], &md->lootitem[1], (LOOTITEM_SIZE-1)*sizeof(md->lootitem[0])); memcpy (&md->lootitem[LOOTITEM_SIZE-1], &fitem->item_data, sizeof(md->lootitem[0])); } if (pcdb_checkid(md->vd->class_)) { //Give them walk act/delay to properly mimic players. [Skotlex] clif->takeitem(&md->bl,tbl); md->ud.canact_tick = tick + md->status.amotion; unit->set_walkdelay(&md->bl, tick, md->status.amotion, 1); } //Clear item. map->clearflooritem (tbl); mob->unlocktarget (md,tick); return true; } //Attempt to attack. //At this point we know the target is attackable, we just gotta check if the range matches. if (md->ud.target == tbl->id && md->ud.attacktimer != INVALID_TIMER) //Already locked. return true; if (battle->check_range (&md->bl, tbl, md->status.rhw.range)) { //Target within range, engage if(tbl->type == BL_PC) mob->log_damage(md, tbl, 0); //Log interaction (counts as 'attacker' for the exp bonus) if(!(mode&MD_RANDOMTARGET)) unit->attack(&md->bl,tbl->id,1); else { // Attack once and find new random target int search_size = (view_range < md->status.rhw.range) ? view_range : md->status.rhw.range; unit->attack(&md->bl,tbl->id,0); tbl = battle->get_enemy(&md->bl, DEFAULT_ENEMY_TYPE(md), search_size); // If no target was found, keep atacking the old one if( tbl ) { md->target_id = tbl->id; md->min_chase = md->db->range3; } } return true; } //Out of range... if (!(mode&MD_CANMOVE)) { //Can't chase. Attempt an idle skill before unlocking. md->state.skillstate = MSS_IDLE; if (!mob->skill_use(md, tick, -1)) mob->unlocktarget(md,tick); return true; } if (!can_move) { //Stuck. Attempt an idle skill md->state.skillstate = MSS_IDLE; if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL)) mob->skill_use(md, tick, -1); return true; } if (md->ud.walktimer != INVALID_TIMER && md->ud.target == tbl->id && ( !(battle_config.mob_ai&0x1) || check_distance_blxy(tbl, md->ud.to_x, md->ud.to_y, md->status.rhw.range) )) //Current target tile is still within attack range. return true; //Follow up if possible. if(!mob->can_reach(md, tbl, md->min_chase, MSS_RUSH) || !unit->walktobl(&md->bl, tbl, md->status.rhw.range, 2)) mob->unlocktarget(md,tick); return true;}