/src/map/mob.c
C | 5162 lines | 4030 code | 610 blank | 522 comment | 1590 complexity | f32c89bc52be7585eb3ac97e712f712c MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.0
Large files files are truncated, but you can click here to view the full file
- /**
- * This file is part of Hercules.
- * http://herc.ws - http://github.com/HerculesWS/Hercules
- *
- * Copyright (C) 2012-2015 Hercules Dev Team
- * Copyright (C) Athena Dev Teams
- *
- * Hercules is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #define HERCULES_CORE
- #include "config/core.h" // AUTOLOOT_DISTANCE, DBPATH, DEFTYPE_MAX, DEFTYPE_MIN, RENEWAL_DROP, RENEWAL_EXP
- #include "mob.h"
- #include "map/atcommand.h"
- #include "map/battle.h"
- #include "map/clif.h"
- #include "map/date.h"
- #include "map/elemental.h"
- #include "map/guild.h"
- #include "map/homunculus.h"
- #include "map/intif.h"
- #include "map/itemdb.h"
- #include "map/log.h"
- #include "map/map.h"
- #include "map/mercenary.h"
- #include "map/npc.h"
- #include "map/party.h"
- #include "map/path.h"
- #include "map/pc.h"
- #include "map/pet.h"
- #include "map/quest.h"
- #include "map/script.h"
- #include "map/skill.h"
- #include "map/status.h"
- #include "common/HPM.h"
- #include "common/cbasetypes.h"
- #include "common/db.h"
- #include "common/ers.h"
- #include "common/memmgr.h"
- #include "common/nullpo.h"
- #include "common/random.h"
- #include "common/showmsg.h"
- #include "common/socket.h"
- #include "common/strlib.h"
- #include "common/timer.h"
- #include "common/utils.h"
- #include <math.h>
- #include <stdarg.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- struct mob_interface mob_s;
- struct mob_interface *mob;
- #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)
- // Probability for mobs far from players from doing their IDLE skill. (rate of 1000 minute)
- // in Aegis, this is 100% for mobs that have been activated by players and none otherwise.
- #define MOB_LAZYSKILLPERC(md) (md->state.spotted?1000:0)
- // Move probability for mobs away from players (rate of 1000 minute)
- // in Aegis, this is 100% for mobs that have been activated by players and none otherwise.
- #define MOB_LAZYMOVEPERC(md) ((md)->state.spotted?1000:0)
- #define MOB_MAX_DELAY (24*3600*1000)
- #define MAX_MINCHASE 30 //Max minimum chase value to use for mobs.
- #define RUDE_ATTACKED_COUNT 2 //After how many rude-attacks should the skill be used?
- //Dynamic item drop ratio database for per-item drop ratio modifiers overriding global drop ratios.
- #define MAX_ITEMRATIO_MOBS 10
- struct item_drop_ratio {
- int drop_ratio;
- int mob_id[MAX_ITEMRATIO_MOBS];
- };
- static struct item_drop_ratio *item_drop_ratio_db[MAX_ITEMDB];
- static struct eri *item_drop_ers; //For loot drops delay structures.
- static struct eri *item_drop_list_ers;
- static struct {
- int qty;
- int class_[350];
- } summon[MAX_RANDOMMONSTER];
- struct mob_db *mob_db(int index) {
- if (index < 0 || index > MAX_MOB_DB || mob->db_data[index] == NULL)
- return mob->dummy;
- return mob->db_data[index];
- }
- struct mob_chat *mob_chat(short id) {
- if(id <= 0 || id > MAX_MOB_CHAT || mob->chat_db[id] == NULL)
- return NULL;
- return mob->chat_db[id];
- }
- /*==========================================
- * Mob is searched with a name.
- *------------------------------------------*/
- int mobdb_searchname(const char *str)
- {
- int i;
- nullpo_ret(str);
- for(i=0;i<=MAX_MOB_DB;i++){
- struct mob_db *monster = mob->db(i);
- if(monster == mob->dummy) //Skip dummy mobs.
- continue;
- if(strcmpi(monster->name,str)==0 || strcmpi(monster->jname,str)==0)
- return i;
- if(battle_config.case_sensitive_aegisnames && strcmp(monster->sprite,str)==0)
- return i;
- if(!battle_config.case_sensitive_aegisnames && strcasecmp(monster->sprite,str)==0)
- return i;
- }
- return 0;
- }
- int mobdb_searchname_array_sub(struct mob_db* monster, const char *str, int flag) {
- nullpo_ret(monster);
- if (monster == mob->dummy)
- return 1;
- if(!monster->base_exp && !monster->job_exp && monster->spawn[0].qty < 1)
- return 1; // Monsters with no base/job exp and no spawn point are, by this criteria, considered "slave mobs" and excluded from search results
- nullpo_ret(str);
- if( !flag ) {
- if(stristr(monster->jname,str))
- return 0;
- if(stristr(monster->name,str))
- return 0;
- } else {
- if(strcmpi(monster->jname,str) == 0)
- return 0;
- if(strcmpi(monster->name,str) == 0)
- return 0;
- }
- if (battle_config.case_sensitive_aegisnames)
- return strcmp(monster->sprite,str);
- return strcasecmp(monster->sprite,str);
- }
- /*==========================================
- * MvP Tomb [GreenBox]
- *------------------------------------------*/
- void mvptomb_create(struct mob_data *md, char *killer, time_t time)
- {
- struct npc_data *nd;
- nullpo_retv(md);
- if ( md->tomb_nid )
- mob->mvptomb_destroy(md);
- nd = npc->create_npc(TOMB, md->bl.m, md->bl.x, md->bl.y, md->ud.dir, MOB_TOMB);
- md->tomb_nid = nd->bl.id;
- safestrncpy(nd->name, msg_txt(856), sizeof(nd->name)); // "Tomb"
- nd->u.tomb.md = md;
- nd->u.tomb.kill_time = time;
- if (killer)
- safestrncpy(nd->u.tomb.killer_name, killer, NAME_LENGTH);
- else
- nd->u.tomb.killer_name[0] = '\0';
- map->addnpc(nd->bl.m, nd);
- map->addblock(&nd->bl);
- status->set_viewdata(&nd->bl, nd->class_);
- clif->spawn(&nd->bl);
- }
- void mvptomb_destroy(struct mob_data *md) {
- struct npc_data *nd;
- nullpo_retv(md);
- if ( (nd = map->id2nd(md->tomb_nid)) ) {
- int16 m, i;
- m = nd->bl.m;
- clif->clearunit_area(&nd->bl,CLR_OUTSIGHT);
- map->delblock(&nd->bl);
- ARR_FIND( 0, map->list[m].npc_num, i, map->list[m].npc[i] == nd );
- if( !(i == map->list[m].npc_num) ) {
- map->list[m].npc_num--;
- map->list[m].npc[i] = map->list[m].npc[map->list[m].npc_num];
- map->list[m].npc[map->list[m].npc_num] = NULL;
- }
- map->deliddb(&nd->bl);
- aFree(nd);
- }
- md->tomb_nid = 0;
- }
- /*==========================================
- * Founds up to N matches. Returns number of matches [Skotlex]
- *------------------------------------------*/
- int mobdb_searchname_array(struct mob_db** data, int size, const char *str, int flag)
- {
- int count = 0, i;
- struct mob_db* monster;
- nullpo_ret(data);
- for(i=0;i<=MAX_MOB_DB;i++){
- monster = mob->db(i);
- if (monster == mob->dummy || mob->is_clone(i) ) //keep clones out (or you leak player stats)
- continue;
- if (!mob->db_searchname_array_sub(monster, str, flag)) {
- if (count < size)
- data[count] = monster;
- count++;
- }
- }
- return count;
- }
- /*==========================================
- * Id Mob is checked.
- *------------------------------------------*/
- int mobdb_checkid(const int id)
- {
- if (mob->db(id) == mob->dummy)
- return 0;
- if (mob->is_clone(id)) //checkid is used mostly for random ID based code, therefore clone mobs are out of the question.
- return 0;
- return id;
- }
- /*==========================================
- * Returns the view data associated to this mob class.
- *------------------------------------------*/
- struct view_data * mob_get_viewdata(int class_)
- {
- if (mob->db(class_) == mob->dummy)
- return 0;
- return &mob->db(class_)->vd;
- }
- /*==========================================
- * Cleans up mob-spawn data to make it "valid"
- *------------------------------------------*/
- int mob_parse_dataset(struct spawn_data *data)
- {
- size_t len;
- nullpo_ret(data);
- if ((!mob->db_checkid(data->class_) && !mob->is_clone(data->class_)) || !data->num)
- return 0;
- if( ( len = strlen(data->eventname) ) > 0 )
- {
- if( data->eventname[len-1] == '"' )
- data->eventname[len-1] = '\0'; //Remove trailing quote.
- if( data->eventname[0] == '"' ) //Strip leading quotes
- memmove(data->eventname, data->eventname+1, len-1);
- }
- if(strcmp(data->name,"--en--")==0)
- safestrncpy(data->name, mob->db(data->class_)->name, sizeof(data->name));
- else if(strcmp(data->name,"--ja--")==0)
- safestrncpy(data->name, mob->db(data->class_)->jname, sizeof(data->name));
- return 1;
- }
- /*==========================================
- * Generates the basic mob data using the spawn_data provided.
- *------------------------------------------*/
- struct mob_data* mob_spawn_dataset(struct spawn_data *data) {
- struct mob_data *md = NULL;
- nullpo_retr(NULL, data);
- CREATE(md, struct mob_data, 1);
- md->bl.id= npc->get_new_npc_id();
- md->bl.type = BL_MOB;
- md->bl.m = data->m;
- md->bl.x = data->x;
- md->bl.y = data->y;
- md->class_ = data->class_;
- md->state.boss = data->state.boss;
- md->db = mob->db(md->class_);
- if (data->level > 0 && data->level <= MAX_LEVEL)
- md->level = data->level;
- memcpy(md->name, data->name, NAME_LENGTH);
- if (data->state.ai)
- md->special_state.ai = data->state.ai;
- if (data->state.size)
- md->special_state.size = data->state.size;
- if (data->eventname[0] && strlen(data->eventname) >= 4)
- memcpy(md->npc_event, data->eventname, 50);
- if(md->db->status.mode&MD_LOOTER)
- md->lootitem = (struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item));
- md->spawn_timer = INVALID_TIMER;
- md->deletetimer = INVALID_TIMER;
- md->skill_idx = -1;
- status->set_viewdata(&md->bl, md->class_);
- status->change_init(&md->bl);
- unit->dataset(&md->bl);
- map->addiddb(&md->bl);
- return md;
- }
- /*==========================================
- * Fetches a random mob_id [Skotlex]
- * type: Where to fetch from:
- * 0: dead branch list
- * 1: poring list
- * 2: bloody branch list
- * flag:
- * &1: Apply the summon success chance found in the list (otherwise get any monster from the db)
- * &2: Apply a monster check level.
- * &4: Selected monster should not be a boss type
- * &8: Selected monster must have normal spawn.
- * lv: Mob level to check against
- *------------------------------------------*/
- int mob_get_random_id(int type, int flag, int lv)
- {
- struct mob_db *monster;
- int i=0, class_;
- if(type < 0 || type >= MAX_RANDOMMONSTER) {
- ShowError("mob_get_random_id: Invalid type (%d) of random monster.\n", type);
- return 0;
- }
- Assert_ret(type >= 0 && type < MAX_RANDOMMONSTER);
- do {
- if (type)
- class_ = summon[type].class_[rnd()%summon[type].qty];
- else //Dead branch
- class_ = rnd() % MAX_MOB_DB;
- monster = mob->db(class_);
- } while ((monster == mob->dummy ||
- mob->is_clone(class_) ||
- (flag&1 && monster->summonper[type] <= rnd() % 1000000) ||
- (flag&2 && lv < monster->lv) ||
- (flag&4 && monster->status.mode&MD_BOSS) ||
- (flag&8 && monster->spawn[0].qty < 1)
- ) && (i++) < MAX_MOB_DB);
- if(i >= MAX_MOB_DB) // no suitable monster found, use fallback for given list
- class_ = mob->db_data[0]->summonper[type];
- return class_;
- }
- /*==========================================
- * Kill Steal Protection [Zephyrus]
- *------------------------------------------*/
- bool mob_ksprotected(struct block_list *src, struct block_list *target) {
- struct block_list *s_bl, *t_bl;
- struct map_session_data
- *sd, // Source
- *pl_sd, // Owner
- *t_sd; // Mob Target
- struct mob_data *md;
- int64 tick = timer->gettick();
- char output[128];
- if( !battle_config.ksprotection )
- return false; // KS Protection Disabled
- if( !(md = BL_CAST(BL_MOB,target)) )
- return false; // Target is not MOB
- if( (s_bl = battle->get_master(src)) == NULL )
- s_bl = src;
- if( !(sd = BL_CAST(BL_PC,s_bl)) )
- return false; // Master is not PC
- t_bl = map->id2bl(md->target_id);
- if( !t_bl || (s_bl = battle->get_master(t_bl)) == NULL )
- s_bl = t_bl;
- t_sd = BL_CAST(BL_PC,s_bl);
- do {
- struct status_change_entry *sce;
- if( map->list[md->bl.m].flag.allowks || map_flag_ks(md->bl.m) )
- return false; // Ignores GVG, PVP and AllowKS map flags
- if( md->db->mexp || md->master_id )
- return false; // MVP, Slaves mobs ignores KS
- if( (sce = md->sc.data[SC_KSPROTECTED]) == NULL )
- break; // No KS Protected
- if( sd->bl.id == sce->val1 || // Same Owner
- (sce->val2 == KSPROTECT_PARTY && sd->status.party_id && sd->status.party_id == sce->val3) || // Party KS allowed
- (sce->val2 == KSPROTECT_GUILD && sd->status.guild_id && sd->status.guild_id == sce->val4) ) // Guild KS allowed
- break;
- if( t_sd && (
- (sce->val2 == KSPROTECT_SELF && sce->val1 != t_sd->bl.id) ||
- (sce->val2 == KSPROTECT_PARTY && sce->val3 && sce->val3 != t_sd->status.party_id) ||
- (sce->val2 == KSPROTECT_GUILD && sce->val4 && sce->val4 != t_sd->status.guild_id)) )
- break;
- if( (pl_sd = map->id2sd(sce->val1)) == NULL || pl_sd->bl.m != md->bl.m )
- break;
- if( !pl_sd->state.noks )
- return false; // No KS Protected, but normal players should be protected too
- // Message to KS
- if( DIFF_TICK(sd->ks_floodprotect_tick, tick) <= 0 )
- {
- sprintf(output, "[KS Warning!! - Owner : %s]", pl_sd->status.name);
- clif_disp_onlyself(sd, output, strlen(output));
- sd->ks_floodprotect_tick = tick + 2000;
- }
- // Message to Owner
- if( DIFF_TICK(pl_sd->ks_floodprotect_tick, tick) <= 0 )
- {
- sprintf(output, "[Watch out! %s is trying to KS you!]", sd->status.name);
- clif_disp_onlyself(pl_sd, output, strlen(output));
- pl_sd->ks_floodprotect_tick = tick + 2000;
- }
- return true;
- } while(0);
- status->change_start(NULL, target, SC_KSPROTECTED, 10000, sd->bl.id, sd->state.noks,
- sd->status.party_id, sd->status.guild_id, battle_config.ksprotection, SCFLAG_NONE);
- return false;
- }
- struct mob_data *mob_once_spawn_sub(struct block_list *bl, int16 m, int16 x, int16 y, const char *mobname, int class_, const char *event, unsigned int size, unsigned int ai)
- {
- struct spawn_data data;
- memset(&data, 0, sizeof(struct spawn_data));
- data.m = m;
- data.num = 1;
- data.class_ = class_;
- data.state.size = size;
- data.state.ai = ai;
- if (mobname)
- safestrncpy(data.name, mobname, sizeof(data.name));
- else
- if (battle_config.override_mob_names == 1)
- strcpy(data.name, "--en--");
- else
- strcpy(data.name, "--ja--");
- if (event)
- safestrncpy(data.eventname, event, sizeof(data.eventname));
- // Locate spot next to player.
- if (bl && (x < 0 || y < 0))
- map->search_freecell(bl, m, &x, &y, 1, 1, 0);
- // if none found, pick random position on map
- if (x <= 0 || x >= map->list[m].xs || y <= 0 || y >= map->list[m].ys)
- map->search_freecell(NULL, m, &x, &y, -1, -1, 1);
- data.x = x;
- data.y = y;
- if (!mob->parse_dataset(&data))
- return NULL;
- return mob->spawn_dataset(&data);
- }
- /*==========================================
- * Spawn a single mob on the specified coordinates.
- *------------------------------------------*/
- int mob_once_spawn(struct map_session_data* sd, int16 m, int16 x, int16 y, const char* mobname, int class_, int amount, const char* event, unsigned int size, unsigned int ai) {
- struct mob_data* md = NULL;
- int count, lv;
- bool no_guardian_data = false;
- if( ai && ai&0x200 ) {
- no_guardian_data = true;
- ai &=~ 0x200;
- }
- if (m < 0 || amount <= 0)
- return 0; // invalid input
- lv = (sd) ? sd->status.base_level : 255;
- for (count = 0; count < amount; count++) {
- int c = (class_ >= 0) ? class_ : mob->get_random_id(-class_ - 1, (battle_config.random_monster_checklv) ? 3 : 1, lv);
- md = mob->once_spawn_sub((sd) ? &sd->bl : NULL, m, x, y, mobname, c, event, size, ai);
- if (!md)
- continue;
- if (class_ == MOBID_EMPELIUM && !no_guardian_data) {
- struct guild_castle* gc = guild->mapindex2gc(map_id2index(m));
- struct guild* g = (gc) ? guild->search(gc->guild_id) : NULL;
- if( gc ) {
- md->guardian_data = (struct guardian_data*)aCalloc(1, sizeof(struct guardian_data));
- md->guardian_data->castle = gc;
- md->guardian_data->number = MAX_GUARDIANS;
- if( g )
- md->guardian_data->g = g;
- else if( gc->guild_id ) //Guild not yet available, retry in 5.
- timer->add(timer->gettick()+5000,mob->spawn_guardian_sub,md->bl.id,gc->guild_id);
- }
- } // end addition [Valaris]
- mob->spawn(md);
- if (class_ < 0 && battle_config.dead_branch_active) {
- //Behold Aegis's masterful decisions yet again...
- //"I understand the "Aggressive" part, but the "Can Move" and "Can Attack" is just stupid" - Poki#3
- sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE|MD_CANATTACK|MD_CANMOVE|MD_ANGRY, 0, 60000);
- }
- }
- return (md) ? md->bl.id : 0; // id of last spawned mob
- }
- /*==========================================
- * Spawn mobs in the specified area.
- *------------------------------------------*/
- int mob_once_spawn_area(struct map_session_data* sd, int16 m, int16 x0, int16 y0, int16 x1, int16 y1, const char* mobname, int class_, int amount, const char* event, unsigned int size, unsigned int ai)
- {
- int i, max, id = 0;
- int lx = -1, ly = -1;
- if (m < 0 || amount <= 0)
- return 0; // invalid input
- // normalize x/y coordinates
- if (x0 > x1)
- swap(x0, x1);
- if (y0 > y1)
- swap(y0, y1);
- // choose a suitable max. number of attempts
- max = (y1 - y0 + 1)*(x1 - x0 + 1)*3;
- if (max > 1000)
- max = 1000;
- // spawn mobs, one by one
- for (i = 0; i < amount; i++)
- {
- int x, y;
- int j = 0;
- // find a suitable map cell
- do {
- x = rnd()%(x1-x0+1)+x0;
- y = rnd()%(y1-y0+1)+y0;
- j++;
- } while (map->getcell(m, NULL, x, y, CELL_CHKNOPASS) && j < max);
- if (j == max)
- {// attempt to find an available cell failed
- if (lx == -1 && ly == -1)
- return 0; // total failure
- // fallback to last good x/y pair
- x = lx;
- y = ly;
- }
- // record last successful coordinates
- lx = x;
- ly = y;
- id = mob->once_spawn(sd, m, x, y, mobname, class_, 1, event, size, ai);
- }
- return id; // id of last spawned mob
- }
- /**
- * Sets a guardian's guild data and liberates castle if couldn't retrieve guild data
- * @param data (int)guild_id
- * @retval Always 0
- * @author Skotlex
- **/
- int mob_spawn_guardian_sub(int tid, int64 tick, int id, intptr_t data) {
- //Needed because the guild data may not be available at guardian spawn time.
- struct block_list* bl = map->id2bl(id);
- struct mob_data* md;
- struct guild* g;
- if( bl == NULL ) //It is possible mob was already removed from map when the castle has no owner. [Skotlex]
- return 0;
- Assert_ret(bl->type == BL_MOB);
- md = BL_UCAST(BL_MOB, bl);
- nullpo_ret(md->guardian_data);
- g = guild->search((int)data);
- if( g == NULL ) { //Liberate castle, if the guild is not found this is an error! [Skotlex]
- ShowError("mob_spawn_guardian_sub: Couldn't load guild %d!\n", (int)data);
- //Not sure this is the best way, but otherwise we'd be invoking this for ALL guardians spawned later on.
- if (md->class_ == MOBID_EMPELIUM && md->guardian_data) {
- md->guardian_data->g = NULL;
- if( md->guardian_data->castle->guild_id ) {//Free castle up.
- ShowNotice("Clearing ownership of castle %d (%s)\n", md->guardian_data->castle->castle_id, md->guardian_data->castle->castle_name);
- guild->castledatasave(md->guardian_data->castle->castle_id, 1, 0);
- }
- } else {
- if( md->guardian_data && md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS
- && md->guardian_data->castle->guardian[md->guardian_data->number].visible )
- guild->castledatasave(md->guardian_data->castle->castle_id, 10+md->guardian_data->number,0);
- unit->free(&md->bl,CLR_OUTSIGHT); // Remove guardian.
- }
- return 0;
- }
- if( guild->checkskill(g,GD_GUARDUP) )
- status_calc_mob(md, SCO_NONE); // Give bonuses.
- return 0;
- }
- //++++++++
- /*==========================================
- * Summoning Guardians [Valaris]
- *------------------------------------------*/
- int mob_spawn_guardian(const char* mapname, short x, short y, const char* mobname, int class_, const char* event, int guardian, bool has_index)
- {
- struct mob_data *md=NULL;
- struct spawn_data data;
- struct guild *g=NULL;
- struct guild_castle *gc;
- int16 m;
- memset(&data, 0, sizeof(struct spawn_data));
- data.num = 1;
- m=map->mapname2mapid(mapname);
- if(m<0)
- {
- ShowWarning("mob_spawn_guardian: Map [%s] not found.\n", mapname);
- return 0;
- }
- data.m = m;
- data.num = 1;
- if(class_<=0) {
- class_ = mob->get_random_id(-class_-1, 1, 99);
- if (!class_) return 0;
- }
- data.class_ = class_;
- if( !has_index ) {
- guardian = -1;
- } else if( guardian < 0 || guardian >= MAX_GUARDIANS ) {
- ShowError("mob_spawn_guardian: Invalid guardian index %d for guardian %d (castle map %s)\n", guardian, class_, map->list[m].name);
- return 0;
- }
- if((x<=0 || y<=0) && !map->search_freecell(NULL, m, &x, &y, -1,-1, 1)) {
- ShowWarning("mob_spawn_guardian: Couldn't locate a spawn cell for guardian class %d (index %d) at castle map %s\n",class_, guardian, map->list[m].name);
- return 0;
- }
- data.x = x;
- data.y = y;
- safestrncpy(data.name, mobname, sizeof(data.name));
- safestrncpy(data.eventname, event, sizeof(data.eventname));
- if (!mob->parse_dataset(&data))
- return 0;
- gc=guild->mapname2gc(map->list[m].name);
- if (gc == NULL) {
- ShowError("mob_spawn_guardian: No castle set at map %s\n", map->list[m].name);
- return 0;
- }
- if (!gc->guild_id)
- ShowWarning("mob_spawn_guardian: Spawning guardian %d on a castle with no guild (castle map %s)\n", class_, map->list[m].name);
- else
- g = guild->search(gc->guild_id);
- if( has_index && gc->guardian[guardian].id ) {
- //Check if guardian already exists, refuse to spawn if so.
- struct block_list *bl2 = map->id2bl(gc->guardian[guardian].id); // TODO: Why does this not use map->id2md?
- struct mob_data *md2 = BL_CAST(BL_MOB, bl2);
- if (md2 != NULL && md2->guardian_data != NULL && md2->guardian_data->number == guardian) {
- ShowError("mob_spawn_guardian: Attempted to spawn guardian in position %d which already has a guardian (castle map %s)\n", guardian, map->list[m].name);
- return 0;
- }
- }
- md = mob->spawn_dataset(&data);
- md->guardian_data = (struct guardian_data*)aCalloc(1, sizeof(struct guardian_data));
- md->guardian_data->number = guardian;
- md->guardian_data->castle = gc;
- if( has_index )
- {// permanent guardian
- gc->guardian[guardian].id = md->bl.id;
- }
- else
- {// temporary guardian
- int i;
- ARR_FIND(0, gc->temp_guardians_max, i, gc->temp_guardians[i] == 0);
- if( i == gc->temp_guardians_max )
- {
- ++(gc->temp_guardians_max);
- RECREATE(gc->temp_guardians, int, gc->temp_guardians_max);
- }
- gc->temp_guardians[i] = md->bl.id;
- }
- if( g )
- md->guardian_data->g = g;
- else if( gc->guild_id )
- timer->add(timer->gettick()+5000,mob->spawn_guardian_sub,md->bl.id,gc->guild_id);
- mob->spawn(md);
- return md->bl.id;
- }
- /*==========================================
- * Summoning BattleGround [Zephyrus]
- *------------------------------------------*/
- int mob_spawn_bg(const char* mapname, short x, short y, const char* mobname, int class_, const char* event, unsigned int bg_id)
- {
- struct mob_data *md = NULL;
- struct spawn_data data;
- int16 m;
- if( (m = map->mapname2mapid(mapname)) < 0 ) {
- ShowWarning("mob_spawn_bg: Map [%s] not found.\n", mapname);
- return 0;
- }
- memset(&data, 0, sizeof(struct spawn_data));
- data.m = m;
- data.num = 1;
- if( class_ <= 0 )
- {
- class_ = mob->get_random_id(-class_-1,1,99);
- if( !class_ ) return 0;
- }
- data.class_ = class_;
- if( (x <= 0 || y <= 0) && !map->search_freecell(NULL, m, &x, &y, -1,-1, 1) ) {
- ShowWarning("mob_spawn_bg: Couldn't locate a spawn cell for guardian class %d (bg_id %d) at map %s\n",class_, bg_id, map->list[m].name);
- return 0;
- }
- data.x = x;
- data.y = y;
- safestrncpy(data.name, mobname, sizeof(data.name));
- safestrncpy(data.eventname, event, sizeof(data.eventname));
- if( !mob->parse_dataset(&data) )
- return 0;
- md = mob->spawn_dataset(&data);
- mob->spawn(md);
- md->bg_id = bg_id; // BG Team ID
- return md->bl.id;
- }
- /*==========================================
- * Reachability to a Specification ID existence place
- * state indicates type of 'seek' mob should do:
- * - MSS_LOOT: Looking for item, path must be easy.
- * - MSS_RUSH: Chasing attacking player, path is complex
- * - MSS_FOLLOW: Initiative/support seek, path is complex
- *------------------------------------------*/
- int mob_can_reach(struct mob_data *md,struct block_list *bl,int range, int state)
- {
- int easy = 0;
- nullpo_ret(md);
- nullpo_ret(bl);
- switch (state) {
- case MSS_RUSH:
- case MSS_FOLLOW:
- easy = 0; //(battle_config.mob_ai&0x1?0:1);
- break;
- case MSS_LOOT:
- default:
- easy = 1;
- break;
- }
- return unit->can_reach_bl(&md->bl, bl, range, easy, NULL, NULL);
- }
- /*==========================================
- * Links nearby mobs (supportive mobs)
- *------------------------------------------*/
- int mob_linksearch(struct block_list *bl,va_list ap)
- {
- struct mob_data *md = NULL;
- int class_ = va_arg(ap, int);
- struct block_list *target = va_arg(ap, struct block_list *);
- int64 tick = va_arg(ap, int64);
- nullpo_ret(bl);
- Assert_ret(bl->type == BL_MOB);
- md = BL_UCAST(BL_MOB, bl);
- if (md->class_ == class_ && DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME
- && !md->target_id)
- {
- md->last_linktime = tick;
- if (mob->can_reach(md,target,md->db->range2, MSS_FOLLOW)) {
- // Reachability judging
- md->target_id = target->id;
- md->min_chase=md->db->range3;
- return 1;
- }
- }
- return 0;
- }
- /*==========================================
- * mob spawn with delay (timer function)
- *------------------------------------------*/
- int mob_delayspawn(int tid, int64 tick, int id, intptr_t data) {
- struct block_list* bl = map->id2bl(id); // TODO: Why does this not use map->bl2md?
- struct mob_data* md = BL_CAST(BL_MOB, bl);
- if( md )
- {
- if( md->spawn_timer != tid )
- {
- ShowError("mob_delayspawn: Timer mismatch: %d != %d\n", tid, md->spawn_timer);
- return 0;
- }
- md->spawn_timer = INVALID_TIMER;
- mob->spawn(md);
- }
- return 0;
- }
- /*==========================================
- * spawn timing calculation
- *------------------------------------------*/
- int mob_setdelayspawn(struct mob_data *md)
- {
- unsigned int spawntime, mode;
- struct mob_db *db;
- if (!md->spawn) //Doesn't has respawn data!
- return unit->free(&md->bl,CLR_DEAD);
- spawntime = md->spawn->delay1; //Base respawn time
- if (md->spawn->delay2) //random variance
- spawntime+= rnd()%md->spawn->delay2;
- //Apply the spawn delay fix [Skotlex]
- db = mob->db(md->spawn->class_);
- mode = db->status.mode;
- if (mode & MD_BOSS) {
- //Bosses
- if (battle_config.boss_spawn_delay != 100) {
- // Divide by 100 first to prevent overflows
- //(precision loss is minimal as duration is in ms already)
- spawntime = spawntime/100*battle_config.boss_spawn_delay;
- }
- } else if (mode&MD_PLANT) {
- //Plants
- if (battle_config.plant_spawn_delay != 100) {
- spawntime = spawntime/100*battle_config.plant_spawn_delay;
- }
- } else if (battle_config.mob_spawn_delay != 100) {
- //Normal mobs
- spawntime = spawntime/100*battle_config.mob_spawn_delay;
- }
- if (spawntime < 5000) //Monsters should never respawn faster than within 5 seconds
- spawntime = 5000;
- if( md->spawn_timer != INVALID_TIMER )
- timer->delete(md->spawn_timer, mob->delayspawn);
- md->spawn_timer = timer->add(timer->gettick()+spawntime, mob->delayspawn, md->bl.id, 0);
- return 0;
- }
- int mob_count_sub(struct block_list *bl, va_list ap) {
- int mobid[10] = { 0 }, i;
- ARR_FIND(0, 10, i, (mobid[i] = va_arg(ap, int)) == 0); //fetch till 0
- if (mobid[0]) { //if there one let's check it otherwise go backward
- struct mob_data *md = BL_CAST(BL_MOB, bl);
- nullpo_ret(md);
- ARR_FIND(0, 10, i, md->class_ == mobid[i]);
- return (i < 10) ? 1 : 0;
- }
- return 1; //backward compatibility
- }
- /*==========================================
- * Mob spawning. Initialization is also variously here.
- *------------------------------------------*/
- int mob_spawn (struct mob_data *md)
- {
- int i=0;
- int64 tick = timer->gettick();
- int64 c = 0;
- md->last_thinktime = tick;
- if (md->bl.prev != NULL)
- unit->remove_map(&md->bl,CLR_RESPAWN,ALC_MARK);
- else if (md->spawn && md->class_ != md->spawn->class_) {
- md->class_ = md->spawn->class_;
- status->set_viewdata(&md->bl, md->class_);
- md->db = mob->db(md->class_);
- memcpy(md->name,md->spawn->name,NAME_LENGTH);
- }
- if (md->spawn) { //Respawn data
- md->bl.m = md->spawn->m;
- md->bl.x = md->spawn->x;
- md->bl.y = md->spawn->y;
- if( (md->bl.x == 0 && md->bl.y == 0) || md->spawn->xs || md->spawn->ys ) {
- //Monster can be spawned on an area.
- if( !map->search_freecell(&md->bl, -1, &md->bl.x, &md->bl.y, md->spawn->xs, md->spawn->ys, battle_config.no_spawn_on_player?4:0) ) {
- // retry again later
- if( md->spawn_timer != INVALID_TIMER )
- timer->delete(md->spawn_timer, mob->delayspawn);
- md->spawn_timer = timer->add(tick+5000,mob->delayspawn,md->bl.id,0);
- return 1;
- }
- } else if( battle_config.no_spawn_on_player > 99 && map->foreachinrange(mob->count_sub, &md->bl, AREA_SIZE, BL_PC) ) {
- // retry again later (players on sight)
- if( md->spawn_timer != INVALID_TIMER )
- timer->delete(md->spawn_timer, mob->delayspawn);
- md->spawn_timer = timer->add(tick+5000,mob->delayspawn,md->bl.id,0);
- return 1;
- }
- }
- memset(&md->state, 0, sizeof(md->state));
- status_calc_mob(md, SCO_FIRST);
- md->attacked_id = 0;
- md->target_id = 0;
- md->move_fail_count = 0;
- md->ud.state.attack_continue = 0;
- md->ud.target_to = 0;
- md->ud.dir = 0;
- if( md->spawn_timer != INVALID_TIMER )
- {
- timer->delete(md->spawn_timer, mob->delayspawn);
- md->spawn_timer = INVALID_TIMER;
- }
- //md->master_id = 0;
- md->master_dist = 0;
- md->state.aggressive = (md->status.mode&MD_ANGRY) ? 1 : 0;
- md->state.skillstate = MSS_IDLE;
- md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME;
- md->last_linktime = tick;
- md->dmgtick = tick - 5000;
- md->last_pcneartime = 0;
- for (i = 0, c = tick-MOB_MAX_DELAY; i < MAX_MOBSKILL; i++)
- md->skilldelay[i] = c;
- memset(md->dmglog, 0, sizeof(md->dmglog));
- md->tdmg = 0;
- if (md->lootitem)
- memset(md->lootitem, 0, sizeof(*md->lootitem));
- md->lootitem_count = 0;
- if(md->db->option)
- // Added for carts, falcons and pecos for cloned monsters. [Valaris]
- md->sc.option = md->db->option;
- // MvP tomb [GreenBox]
- if ( md->tomb_nid )
- mob->mvptomb_destroy(md);
- map->addblock(&md->bl);
- if( map->list[md->bl.m].users )
- clif->spawn(&md->bl);
- skill->unit_move(&md->bl,tick,1);
- mob->skill_use(md, tick, MSC_SPAWN);
- return 0;
- }
- /*==========================================
- * Determines if the mob can change target. [Skotlex]
- *------------------------------------------*/
- int mob_can_changetarget(struct mob_data* md, struct block_list* target, int mode)
- {
- // if the monster was provoked ignore the above rule [celest]
- if(md->state.provoke_flag)
- {
- if (md->state.provoke_flag == target->id)
- return 1;
- else if (!(battle_config.mob_ai&0x4))
- return 0;
- }
- switch (md->state.skillstate) {
- case MSS_BERSERK:
- if (!(mode&MD_CHANGETARGET_MELEE))
- return 0;
- return (battle_config.mob_ai&0x4 || check_distance_bl(&md->bl, target, 3));
- case MSS_RUSH:
- return (mode&MD_CHANGETARGET_CHASE);
- case MSS_FOLLOW:
- case MSS_ANGRY:
- case MSS_IDLE:
- case MSS_WALK:
- case MSS_LOOT:
- return 1;
- default:
- return 0;
- }
- }
- /*==========================================
- * Determination for an attack of a monster
- *------------------------------------------*/
- int mob_target(struct mob_data *md,struct block_list *bl,int dist)
- {
- nullpo_ret(md);
- nullpo_ret(bl);
- // Nothing will be carried out if there is no mind of changing TAGE by TAGE ending.
- if(md->target_id && !mob->can_changetarget(md, bl, status_get_mode(&md->bl)))
- return 0;
- if(!status->check_skilluse(&md->bl, bl, 0, 0))
- return 0;
- md->target_id = bl->id; // Since there was no disturbance, it locks on to target.
- if (md->state.provoke_flag && bl->id != md->state.provoke_flag)
- md->state.provoke_flag = 0;
- md->min_chase=dist+md->db->range3;
- if(md->min_chase>MAX_MINCHASE)
- md->min_chase=MAX_MINCHASE;
- return 0;
- }
- /*==========================================
- * The ?? routine of an active monster
- *------------------------------------------*/
- int mob_ai_sub_hard_activesearch(struct block_list *bl,va_list ap)
- {
- struct mob_data *md;
- struct block_list **target;
- int mode;
- int dist;
- nullpo_ret(bl);
- md=va_arg(ap,struct mob_data *);
- target= va_arg(ap,struct block_list**);
- mode= va_arg(ap,int);
- //If can't seek yet, not an enemy, or you can't attack it, skip.
- if (md->bl.id == bl->id || (*target) == bl || !status->check_skilluse(&md->bl, bl, 0, 0))
- return 0;
- if ((mode&MD_TARGETWEAK) && status->get_lv(bl) >= md->level-5)
- return 0;
- if(battle->check_target(&md->bl,bl,BCT_ENEMY)<=0)
- return 0;
- switch (bl->type) {
- case BL_PC:
- if (BL_UCCAST(BL_PC, bl)->state.gangsterparadise && !(status_get_mode(&md->bl)&MD_BOSS))
- return 0; //Gangster paradise protection.
- default:
- if (battle_config.hom_setting&0x4 &&
- (*target) && (*target)->type == BL_HOM && bl->type != BL_HOM)
- return 0; //For some reason Homun targets are never overridden.
- dist = distance_bl(&md->bl, bl);
- if(
- ((*target) == NULL || !check_distance_bl(&md->bl, *target, dist)) &&
- battle->check_range(&md->bl,bl,md->db->range2)
- ) { //Pick closest target?
- #ifdef ACTIVEPATHSEARCH
- struct walkpath_data wpd;
- if (!path->search(&wpd, &md->bl, md->bl.m, md->bl.x, md->bl.y, bl->x, bl->y, 0, CELL_CHKNOPASS)) // Count walk path cells
- return 0;
- //Standing monsters use range2, walking monsters use range3
- if ((md->ud.walktimer == INVALID_TIMER && wpd.path_len > md->db->range2)
- || (md->ud.walktimer != INVALID_TIMER && wpd.path_len > md->db->range3))
- return 0;
- #endif
- (*target) = bl;
- md->target_id=bl->id;
- md->min_chase= dist + md->db->range3;
- if(md->min_chase>MAX_MINCHASE)
- md->min_chase=MAX_MINCHASE;
- return 1;
- }
- break;
- }
- return 0;
- }
- /*==========================================
- * chase target-change routine.
- *------------------------------------------*/
- int mob_ai_sub_hard_changechase(struct block_list *bl,va_list ap) {
- struct mob_data *md;
- struct block_list **target;
- nullpo_ret(bl);
- md=va_arg(ap,struct mob_data *);
- target= va_arg(ap,struct block_list**);
- //If can't seek yet, not an enemy, or you can't attack it, skip.
- if( md->bl.id == bl->id || *target == bl
- || battle->check_target(&md->bl,bl,BCT_ENEMY) <= 0
- || !status->check_skilluse(&md->bl, bl, 0, 0)
- )
- return 0;
- if(battle->check_range (&md->bl, bl, md->status.rhw.range)) {
- (*target) = bl;
- md->target_id=bl->id;
- md->min_chase= md->db->range3;
- }
- return 1;
- }
- /*==========================================
- * finds nearby bg ally for guardians looking for users to follow.
- *------------------------------------------*/
- int mob_ai_sub_hard_bg_ally(struct block_list *bl,va_list ap) {
- struct mob_data *md;
- struct block_list **target;
- nullpo_ret(bl);
- md=va_arg(ap,struct mob_data *);
- target= va_arg(ap,struct block_list**);
- if( status->check_skilluse(&md->bl, bl, 0, 0) && battle->check_target(&md->bl,bl,BCT_ENEMY)<=0 ) {
- (*target) = bl;
- }
- return 1;
- }
- /*==========================================
- * loot monster item search
- *------------------------------------------*/
- int mob_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap)
- {
- struct mob_data* md;
- struct block_list **target;
- int dist;
- md=va_arg(ap,struct mob_data *);
- target= va_arg(ap,struct block_list**);
- dist=distance_bl(&md->bl, bl);
- if(mob->can_reach(md,bl,dist+1, MSS_LOOT) &&
- ((*target) == NULL || !check_distance_bl(&md->bl, *target, dist)) //New target closer than previous one.
- ) {
- (*target) = bl;
- md->target_id=bl->id;
- md->min_chase=md->db->range3;
- }
- return 0;
- }
- int mob_warpchase_sub(struct block_list *bl,va_list ap) {
- int cur_distance;
- struct block_list *target = va_arg(ap, struct block_list *);
- struct npc_data **target_nd = va_arg(ap, struct npc_data **);
- int *min_distance = va_arg(ap, int *);
- struct npc_data *nd = NULL;
- nullpo_ret(bl);
- Assert_ret(bl->type == BL_NPC);
- nd = BL_UCAST(BL_NPC, bl);
- if(nd->subtype != WARP)
- return 0; //Not a warp
- if(nd->u.warp.mapindex != map_id2index(target->m))
- return 0; //Does not lead to the same map.
- cur_distance = distance_blxy(target, nd->u.warp.x, nd->u.warp.y);
- if (cur_distance < *min_distance) {
- //Pick warp that leads closest to target.
- *target_nd = nd;
- *min_distance = cur_distance;
- return 1;
- }
- return 0;
- }
- /*==========================================
- * Processing of slave monsters
- *------------------------------------------*/
- int mob_ai_sub_hard_slavemob(struct mob_data *md, int64 tick) {
- struct block_list *bl;
- bl=map->id2bl(md->master_id);
- if (!bl || status->isdead(bl)) {
- status_kill(&md->bl);
- return 1;
- }
- if (bl->prev == NULL)
- return 0; //Master not on a map? Could be warping, do not process.
- if (status_get_mode(&md->bl)&MD_CANMOVE) {
- //If the mob can move, follow around. [Check by Skotlex]
- int old_dist;
- // Distance with between slave and master is measured.
- old_dist=md->master_dist;
- md->master_dist=distance_bl(&md->bl, bl);
- // Since the master was in near immediately before, teleport is carried out and it pursues.
- if(bl->m != md->bl.m ||
- (old_dist<10 && md->master_dist>18) ||
- md->master_dist > MAX_MINCHASE
- ){
- md->master_dist = 0;
- unit->warp(&md->bl,bl->m,bl->x,bl->y,CLR_TELEPORT);
- return 1;
- }
- if(md->target_id) //Slave is busy with a target.
- return 0;
- // Approach master if within view range, chase back to Master's area also if standing on top of the master.
- if( (md->master_dist>MOB_SLAVEDISTANCE || md->master_dist == 0)
- && unit->can_move(&md->bl)
- ) {
- short x = bl->x, y = bl->y;
- mob_stop_attack(md);
- if(map->search_freecell(&md->bl, bl->m, &x, &y, MOB_SLAVEDISTANCE, MOB_SLAVEDISTANCE, 1)
- && unit->walktoxy(&md->bl, x, y, 0))
- return 1;
- }
- } else if (bl->m != md->bl.m && map_flag_gvg(md->bl.m)) {
- //Delete the summoned mob if it's in a gvg ground and the master is elsewhere. [Skotlex]
- status_kill(&md->bl);
- return 1;
- }
- //Avoid attempting to lock the master's target too often to avoid unnecessary overload. [Skotlex]
- if (DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME && !md->target_id) {
- struct unit_data *ud = unit->bl2ud(bl);
- md->last_linktime = tick;
- if (ud) {
- struct block_list *tbl=NULL;
- if (ud->target && ud->state.attack_continue)
- tbl=map->id2bl(ud->target);
- else if (ud->skilltarget) {
- tbl = map->id2bl(ud->skilltarget);
- //Required check as skilltarget is not always an enemy. [Skotlex]
- if (tbl && battle->check_target(&md->bl, tbl, BCT_ENEMY) <= 0)
- tbl = NULL;
- }
- if (tbl && status->check_skilluse(&md->bl, tbl, 0, 0)) {
- md->target_id=tbl->id;
- md->min_chase=md->db->range3+distance_bl(&md->bl, tbl);
- if(md->min_chase>MAX_MINCHASE)
- md->min_chase=MAX_MINCHASE;
- return 1;
- }
- }
- }
- return 0;
- }
- /*==========================================
- * A lock of target is stopped and mob moves to a standby state.
- * This also triggers idle skill/movement since the AI can get stuck
- * when trying to pick new targets when the current chosen target is
- * unreachable.
- *------------------------------------------*/
- int mob_unlocktarget(struct mob_data *md, int64 tick) {
- nullpo_ret(md);
- switch (md->state.skillstate) {
- case MSS_WALK:
- if (md->ud.walktimer != INVALID_TIMER)
- break;
- //Because it is not unset when the mob finishes walking.
- md->state.skillstate = MSS_IDLE;
- case MSS_IDLE:
- // Idle skill.
- if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL) && mob->skill_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;
- break;
- default:
- mob_stop_attack(md);
- mob_stop_walking(md, STOPWALKING_FLAG_FIXPOS); //Stop chasing.
- md->state.skillstate = MSS_IDLE;
- if(battle_config.mob_ai&0x8) //Walk instantly after dropping target
- md->next_walktime = tick+rnd()%1000;
- else
- md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME;
- break;
- }
- if (md->target_id) {
- md->target_id=0;
- md->ud.target_to = 0;
- unit->set_target(&md->ud, 0);
- }
- if(battle_config.official_cell_stack_limit && map->count_oncell(md->bl.m, md->bl.x, md->bl.y, BL_CHAR|BL_NPC, 1) > battle_config.official_cell_stack_limit) {
- unit->walktoxy(&md->bl, md->bl.x, md->bl.y, 8);
- }
- return 0;
- }
- /*==========================================
- * Random walk
- *------------------------------------------*/
- int mob_randomwalk(struct mob_data *md, int64 tick) {
- const int retrycount=20;
- int i,c,d;
- int speed;
- nullpo_ret(md);
- if(DIFF_TICK(md->next_walktime,tick)>0 ||
- !unit->can_move(&md->bl) ||
- !(status_get_mode(&md->bl)&MD_CANMOVE))
- return 0;
- d =12-md->move_fail_count;
- if(d<5) d=5;
- if(d>7) d=7;
- for (i = 0; i < retrycount; i++) {
- // Search of a movable place
- int r=rnd();
- int x=r%(d*2+1)-d;
- int y=r/(d*2+1)%(d*2+1)-d;
- x+=md->bl.x;
- y+=md->bl.y;
- if (((x != md->bl.x) || (y != md->bl.y)) && map->getcell(md->bl.m, &md->bl, x, y, CELL_CHKPASS) && unit->walktoxy(&md->bl, x, y, 8)) {
- break;
- }
- }
- if(i==retrycount){
- md->move_fail_count++;
- if(md->move_fail_count>1000){
- ShowWarning("MOB can't move. random spawn %d, class = %d, at %s (%d,%d)\n",md->bl.id,md->class_,map->list[md->bl.m].name, md->bl.x, md->bl.y);
- md->move_fail_count=0;
- mob->spawn(md);
- }
- 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(md->ud.walkpath.path[i]&1)
- 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;
- return 1;
- }
- int mob_warpchase(struct mob_data *md, struct block_list *target)
- {
- struct npc_data *warp = NULL;
- int distance = AREA_SIZE;
- if (!(target && battle_config.mob_ai&0x40 && battle_config.mob_warp&1))
- return 0; //Can't warp chase.
- if (target->m == md->bl.m && check_distance_bl(&md->bl, target, AREA_SIZE))
- return 0; //No need to do a warp chase.
- if (md->ud.walktimer != INVALID_TIMER &&
- map->getcell(md->bl.m, &md->bl, md->ud.to_x, md->ud.to_y, CELL_CHKNPC))
- return 1; //Already walking to a warp.
- //Search for warps within mob's viewing range.
- map->foreachinrange(mob->warpchase_sub, &md->bl,
- md->db->range2, BL_NPC, target, &warp, &distance);
- if (warp && unit->walktobl(&md->bl, &warp->bl, 1, 1))
- return 1;
- return 0;
- }
- /*==========================================
- * 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;
- // 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]
- struct map_session_data *tsd = NULL;
- tbl = map->id2bl(md->target_id);
- tsd = BL_CAST(BL_PC, tbl);
- if (tbl == NULL || 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))
- || (tsd != NULL && ((tsd->state.gangsterparadise && !(mode&MD_BOSS)) || tsd->invincible_timer != INVALID_TIMER))
- ) {
- //No valid target
- if (mob->warpchase(md, tbl))
- return true; //Chasing this target.
- if(md->ud.walktimer != INVALID_TIMER && (!can_move || md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh)
- && (tbl || md->ud.walkpath.path_pos == 0))
- return true; //Walk at least "mob_chase_refresh" cells before dropping the target unless target is non-existent
- mob->unlocktarget(md, tick); //Unlock target
- 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.
- || md->walktoxy_fail_count > 0)
- )
- || !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.
- || md->walktoxy_fail_count > 0)
- )
- || !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 = BL_UCAST(BL_ITEM, tbl);
- 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…
Large files files are truncated, but you can click here to view the full file