/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2012-2018 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 . */ #define HERCULES_CORE #include "battleground.h" #include "map/battle.h" #include "map/clif.h" #include "map/guild.h" #include "map/homunculus.h" #include "map/map.h" #include "map/mapreg.h" #include "map/mercenary.h" #include "map/mob.h" // struct mob_data #include "map/npc.h" #include "map/party.h" #include "map/pc.h" #include "map/pet.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/HPM.h" #include "common/memmgr.h" #include "common/nullpo.h" #include "common/showmsg.h" #include "common/socket.h" #include "common/strlib.h" #include "common/timer.h" #include #include static struct battleground_interface bg_s; struct battleground_interface *bg; /// Search a BG Team using bg_id static struct battleground_data* bg_team_search(int bg_id) { if( !bg_id ) return NULL; return (struct battleground_data *)idb_get(bg->team_db, bg_id); } static struct map_session_data* bg_getavailablesd(struct battleground_data *bgd) { int i; nullpo_retr(NULL, bgd); ARR_FIND(0, MAX_BG_MEMBERS, i, bgd->members[i].sd != NULL); return( i < MAX_BG_MEMBERS ) ? bgd->members[i].sd : NULL; } /// Deletes BG Team from db static bool bg_team_delete(int bg_id) { int i; struct battleground_data *bgd = bg->team_search(bg_id); if( bgd == NULL ) return false; for( i = 0; i < MAX_BG_MEMBERS; i++ ) { struct map_session_data *sd = bgd->members[i].sd; if (sd == NULL) continue; bg->send_dot_remove(sd); sd->bg_id = 0; } idb_remove(bg->team_db, bg_id); return true; } /// Warps a Team static bool bg_team_warp(int bg_id, unsigned short map_index, short x, short y) { int i; struct battleground_data *bgd = bg->team_search(bg_id); if( bgd == NULL ) return false; for( i = 0; i < MAX_BG_MEMBERS; i++ ) if( bgd->members[i].sd != NULL ) pc->setpos(bgd->members[i].sd, map_index, x, y, CLR_TELEPORT); return true; } static void bg_send_dot_remove(struct map_session_data *sd) { if( sd && sd->bg_id ) clif->bg_xy_remove(sd); } /// Player joins team static bool bg_team_join(int bg_id, struct map_session_data *sd) { int i; struct battleground_data *bgd = bg->team_search(bg_id); if( bgd == NULL || sd == NULL || sd->bg_id ) return false; ARR_FIND(0, MAX_BG_MEMBERS, i, bgd->members[i].sd == NULL); if( i == MAX_BG_MEMBERS ) return false; // No free slots sd->bg_id = bg_id; bgd->members[i].sd = sd; bgd->members[i].x = sd->bl.x; bgd->members[i].y = sd->bl.y; /* populate 'where i came from' */ if(map->list[sd->bl.m].flag.nosave || map->list[sd->bl.m].instance_id >= 0) { struct map_data *m=&map->list[sd->bl.m]; if(m->save.map) memcpy(&bgd->members[i].source,&m->save,sizeof(struct point)); else memcpy(&bgd->members[i].source,&sd->status.save_point,sizeof(struct point)); } else memcpy(&bgd->members[i].source,&sd->status.last_point,sizeof(struct point)); bgd->count++; guild->send_dot_remove(sd); for( i = 0; i < MAX_BG_MEMBERS; i++ ) { struct map_session_data *pl_sd = bgd->members[i].sd; if (pl_sd != NULL && pl_sd != sd) clif->hpmeter_single(sd->fd, pl_sd->bl.id, pl_sd->battle_status.hp, pl_sd->battle_status.max_hp); } clif->bg_hp(sd); clif->bg_xy(sd); return true; } /// Single Player leaves team static int bg_team_leave(struct map_session_data *sd, enum bg_team_leave_type flag) { int i, bg_id; struct battleground_data *bgd; if( sd == NULL || !sd->bg_id ) return 0; bg->send_dot_remove(sd); bg_id = sd->bg_id; sd->bg_id = 0; if( (bgd = bg->team_search(bg_id)) == NULL ) return 0; ARR_FIND(0, MAX_BG_MEMBERS, i, bgd->members[i].sd == sd); if( i < MAX_BG_MEMBERS ) { // Removes member from BG if( sd->bg_queue.arena ) { bg->queue_pc_cleanup(sd); pc->setpos(sd,bgd->members[i].source.map, bgd->members[i].source.x, bgd->members[i].source.y, CLR_OUTSIGHT); } memset(&bgd->members[i], 0, sizeof(bgd->members[0])); } if (--bgd->count != 0) { char output[128]; switch (flag) { default: case BGTL_QUIT: sprintf(output, msg_txt(464), sd->status.name); // Server : %s has quit the game... break; case BGTL_LEFT: sprintf(output, msg_txt(454), sd->status.name); // Server : %s is leaving the battlefield... break; case BGTL_AFK: sprintf(output, msg_txt(455), sd->status.name); // Server : %s has been afk-kicked from the battlefield... break; } clif->bg_message(bgd, 0, "Server", output); } if( bgd->logout_event[0] && flag ) npc->event(sd, bgd->logout_event, 0); if( sd->bg_queue.arena ) { bg->queue_pc_cleanup(sd); } return bgd->count; } /// Respawn after killed static bool bg_member_respawn(struct map_session_data *sd) { struct battleground_data *bgd; if( sd == NULL || !pc_isdead(sd) || !sd->bg_id || (bgd = bg->team_search(sd->bg_id)) == NULL ) return false; if( bgd->mapindex == 0 ) return false; // Respawn not handled by Core pc->setpos(sd, bgd->mapindex, bgd->x, bgd->y, CLR_OUTSIGHT); status->revive(&sd->bl, 1, 100); return true; // Warped } static int bg_create(unsigned short map_index, short rx, short ry, const char *ev, const char *dev) { struct battleground_data *bgd; bg->team_counter++; CREATE(bgd, struct battleground_data, 1); bgd->bg_id = bg->team_counter; bgd->count = 0; bgd->mapindex = map_index; bgd->x = rx; bgd->y = ry; safestrncpy(bgd->logout_event, ev, sizeof(bgd->logout_event)); safestrncpy(bgd->die_event, dev, sizeof(bgd->die_event)); memset(&bgd->members, 0, sizeof(bgd->members)); idb_put(bg->team_db, bg->team_counter, bgd); return bgd->bg_id; } static int bg_team_get_id(struct block_list *bl) { nullpo_ret(bl); switch( bl->type ) { case BL_PC: { const struct map_session_data *sd = BL_UCCAST(BL_PC, bl); return sd->bg_id; } case BL_PET: { const struct pet_data *pd = BL_UCCAST(BL_PET, bl); if (pd->msd != NULL) return pd->msd->bg_id; } break; case BL_MOB: { const struct mob_data *md = BL_UCCAST(BL_MOB, bl); const struct map_session_data *msd; if (md->special_state.ai != AI_NONE && (msd = map->id2sd(md->master_id)) != NULL) return msd->bg_id; return md->bg_id; } case BL_HOM: { const struct homun_data *hd = BL_UCCAST(BL_HOM, bl); if (hd->master != NULL) return hd->master->bg_id; } break; case BL_MER: { const struct mercenary_data *md = BL_UCCAST(BL_MER, bl); if (md->master != NULL) return md->master->bg_id; } break; case BL_SKILL: { const struct skill_unit *su = BL_UCCAST(BL_SKILL, bl); if (su->group != NULL) return su->group->bg_id; } break; } return 0; } static bool bg_send_message(struct map_session_data *sd, const char *mes) { struct battleground_data *bgd; nullpo_ret(sd); nullpo_ret(mes); if( sd->bg_id == 0 || (bgd = bg->team_search(sd->bg_id)) == NULL ) return false; // Couldn't send message clif->bg_message(bgd, sd->bl.id, sd->status.name, mes); return true; } /** * @see DBApply */ static int bg_send_xy_timer_sub(union DBKey key, struct DBData *data, va_list ap) { struct battleground_data *bgd = DB->data2ptr(data); struct map_session_data *sd; int i; nullpo_ret(bgd); for( i = 0; i < MAX_BG_MEMBERS; i++ ) { if( (sd = bgd->members[i].sd) == NULL ) continue; if( sd->bl.x != bgd->members[i].x || sd->bl.y != bgd->members[i].y ) { // xy update bgd->members[i].x = sd->bl.x; bgd->members[i].y = sd->bl.y; clif->bg_xy(sd); } } return 0; } static int bg_send_xy_timer(int tid, int64 tick, int id, intptr_t data) { bg->team_db->foreach(bg->team_db, bg->send_xy_timer_sub, tick); return 0; } static enum bg_queue_types bg_str2teamtype(const char *str) { char temp[200], *parse; enum bg_queue_types type = BGQT_INVALID; nullpo_retr(type, str); safestrncpy(temp, str, 200); parse = strtok(temp,"|"); while (parse != NULL) { normalize_name(parse," "); if( strcmpi(parse,"all") == 0 ) type |= BGQT_INDIVIDUAL|BGQT_PARTY|BGQT_GUILD; else if( strcmpi(parse,"party") == 0 ) type |= BGQT_PARTY; else if( strcmpi(parse,"guild") == 0 ) type |= BGQT_GUILD; else if( strcmpi(parse,"solo") == 0 ) type |= BGQT_INDIVIDUAL; else { ShowError("bg_str2teamtype: '%s' unknown type, skipping...\n",parse); } parse = strtok(NULL,"|"); } return type; } static void bg_config_read(void) { struct config_t bg_conf; struct config_setting_t *data = NULL; const char *config_filename = "conf/battlegrounds.conf"; // FIXME hardcoded name if (!libconfig->load_file(&bg_conf, config_filename)) return; data = libconfig->lookup(&bg_conf, "battlegrounds"); if (data != NULL) { struct config_setting_t *settings = libconfig->setting_get_elem(data, 0); struct config_setting_t *arenas; const char *delay_var; int offline = 0; if( !libconfig->setting_lookup_string(settings, "global_delay_var", &delay_var) ) delay_var = "BG_Delay_Tick"; safestrncpy(bg->gdelay_var, delay_var, BG_DELAY_VAR_LENGTH); libconfig->setting_lookup_int(settings, "maximum_afk_seconds", &bg->mafksec); libconfig->setting_lookup_bool(settings, "feature_off", &offline); if( offline == 0 ) bg->queue_on = true; if( (arenas = libconfig->setting_get_member(settings, "arenas")) != NULL ) { int i; int arena_count = libconfig->setting_length(arenas); CREATE( bg->arena, struct bg_arena *, arena_count ); for(i = 0; i < arena_count; i++) { struct config_setting_t *arena = libconfig->setting_get_elem(arenas, i); struct config_setting_t *reward; const char *aName, *aEvent, *aDelayVar, *aTeamTypes; int minLevel = 0, maxLevel = 0; int prizeWin, prizeLoss, prizeDraw; int minPlayers, maxPlayers, minTeamPlayers; int maxDuration; int fillup_duration = 0, pregame_duration = 0; enum bg_queue_types allowedTypes; bg->arena[i] = NULL; if( !libconfig->setting_lookup_string(arena, "name", &aName) ) { ShowError("bg_config_read: failed to find 'name' for arena #%d\n",i); continue; } if( !libconfig->setting_lookup_string(arena, "event", &aEvent) ) { ShowError("bg_config_read: failed to find 'event' for arena #%d\n",i); continue; } libconfig->setting_lookup_int(arena, "minLevel", &minLevel); libconfig->setting_lookup_int(arena, "maxLevel", &maxLevel); if( minLevel < 0 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' minLevel\n",minLevel,aName); minLevel = 0; } if( maxLevel > MAX_LEVEL ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' maxLevel\n",maxLevel,aName); maxLevel = MAX_LEVEL; } if( !(reward = libconfig->setting_get_member(arena, "reward")) ) { ShowError("bg_config_read: failed to find 'reward' for arena '%s'/#%d\n",aName,i); continue; } libconfig->setting_lookup_int(reward, "win", &prizeWin); libconfig->setting_lookup_int(reward, "loss", &prizeLoss); libconfig->setting_lookup_int(reward, "draw", &prizeDraw); if( prizeWin < 0 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' reward:win\n",prizeWin,aName); prizeWin = 0; } if( prizeLoss < 0 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' reward:loss\n",prizeLoss,aName); prizeLoss = 0; } if( prizeDraw < 0 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' reward:draw\n",prizeDraw,aName); prizeDraw = 0; } libconfig->setting_lookup_int(arena, "minPlayers", &minPlayers); libconfig->setting_lookup_int(arena, "maxPlayers", &maxPlayers); libconfig->setting_lookup_int(arena, "minTeamPlayers", &minTeamPlayers); if( minPlayers < 0 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' minPlayers\n",minPlayers,aName); minPlayers = 0; } if( maxPlayers > MAX_BG_MEMBERS * 2 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' maxPlayers, change #define MAX_BG_MEMBERS\n",maxPlayers,aName); maxPlayers = 0; } if( minTeamPlayers < 0 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' minTeamPlayers\n",minTeamPlayers,aName); minTeamPlayers = 0; } if( !libconfig->setting_lookup_string(arena, "delay_var", &aDelayVar) ) { ShowError("bg_config_read: failed to find 'delay_var' for arena '%s'/#%d\n",aName,i); continue; } if( !libconfig->setting_lookup_string(arena, "allowedTypes", &aTeamTypes) ) { ShowError("bg_config_read: failed to find 'allowedTypes' for arena '%s'/#%d\n",aName,i); continue; } libconfig->setting_lookup_int(arena, "maxDuration", &maxDuration); if( maxDuration < 0 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' maxDuration\n",maxDuration,aName); maxDuration = 30; } libconfig->setting_lookup_int(arena, "fillDuration", &fillup_duration); libconfig->setting_lookup_int(arena, "pGameDuration", &pregame_duration); if( fillup_duration < 20 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' fillDuration, minimum has to be 20, defaulting to 20.\n",fillup_duration,aName); fillup_duration = 20; } if( pregame_duration < 20 ) { ShowWarning("bg_config_read: invalid %d value for arena '%s' pGameDuration, minimum has to be 20, defaulting to 20.\n",pregame_duration,aName); pregame_duration = 20; } allowedTypes = bg->str2teamtype(aTeamTypes); CREATE( bg->arena[i], struct bg_arena, 1 ); bg->arena[i]->id = i; safestrncpy(bg->arena[i]->name, aName, NAME_LENGTH); safestrncpy(bg->arena[i]->npc_event, aEvent, EVENT_NAME_LENGTH); bg->arena[i]->min_level = minLevel; bg->arena[i]->max_level = maxLevel; bg->arena[i]->prize_win = prizeWin; bg->arena[i]->prize_loss = prizeLoss; bg->arena[i]->prize_draw = prizeDraw; bg->arena[i]->min_players = minPlayers; bg->arena[i]->max_players = maxPlayers; bg->arena[i]->min_team_players = minTeamPlayers; safestrncpy(bg->arena[i]->delay_var, aDelayVar, NAME_LENGTH); bg->arena[i]->maxDuration = maxDuration; bg->arena[i]->queue_id = script->queue_create(); bg->arena[i]->begin_timer = INVALID_TIMER; bg->arena[i]->fillup_timer = INVALID_TIMER; bg->arena[i]->pregame_duration = pregame_duration; bg->arena[i]->fillup_duration = fillup_duration; bg->arena[i]->ongoing = false; bg->arena[i]->allowed_types = allowedTypes; } bg->arenas = arena_count; } } libconfig->destroy(&bg_conf); } static struct bg_arena *bg_name2arena(const char *name) { int i; nullpo_retr(NULL, name); for(i = 0; i < bg->arenas; i++) { if( strcmpi(bg->arena[i]->name,name) == 0 ) return bg->arena[i]; } return NULL; } /** * Returns a character's position in a battleground's queue. * * @param queue_id The ID of the battleground's queue. * @param account_id The character's account ID. * * @return the position (starting at 1). * @retval 0 if the queue doesn't exist or the given account ID isn't present in it. */ static int bg_id2pos(int queue_id, int account_id) { struct script_queue *queue = script->queue(queue_id); if (queue) { int i; ARR_FIND(0, VECTOR_LENGTH(queue->entries), i, VECTOR_INDEX(queue->entries, i) == account_id); if (i != VECTOR_LENGTH(queue->entries)) { return i+1; } } return 0; } static void bg_queue_ready_ack(struct bg_arena *arena, struct map_session_data *sd, bool response) { nullpo_retv(arena); nullpo_retv(sd); if( arena->begin_timer == INVALID_TIMER || !sd->bg_queue.arena || sd->bg_queue.arena != arena ) { bg->queue_pc_cleanup(sd); return; } if( !response ) bg->queue_pc_cleanup(sd); else { struct script_queue *queue = script->queue(arena->queue_id); int i, count = 0; sd->bg_queue.ready = 1; for (i = 0; i < VECTOR_LENGTH(queue->entries); i++) { struct map_session_data *tsd = map->id2sd(VECTOR_INDEX(queue->entries, i)); if (tsd != NULL && tsd->bg_queue.ready == 1) count++; } /* check if all are ready then cancel timer, and start game */ if (count == VECTOR_LENGTH(queue->entries)) { timer->delete(arena->begin_timer,bg->begin_timer); arena->begin_timer = INVALID_TIMER; bg->begin(arena); } } } static void bg_queue_player_cleanup(struct map_session_data *sd) { nullpo_retv(sd); if ( sd->bg_queue.client_has_bg_data ) { if( sd->bg_queue.arena ) clif->bgqueue_notice_delete(sd,BGQND_CLOSEWINDOW,sd->bg_queue.arena->name); else clif->bgqueue_notice_delete(sd,BGQND_FAIL_NOT_QUEUING,bg->arena[0]->name); } if( sd->bg_queue.arena ) script->queue_remove(sd->bg_queue.arena->queue_id,sd->status.account_id); sd->bg_queue.arena = NULL; sd->bg_queue.ready = 0; sd->bg_queue.client_has_bg_data = 0; sd->bg_queue.type = 0; } static void bg_match_over(struct bg_arena *arena, bool canceled) { struct script_queue *queue = script->queue(arena->queue_id); int i; nullpo_retv(arena); if( !arena->ongoing ) return; arena->ongoing = false; for (i = 0; i < VECTOR_LENGTH(queue->entries); i++) { struct map_session_data *sd = map->id2sd(VECTOR_INDEX(queue->entries, i)); if (sd == NULL) continue; if (sd->bg_queue.arena) { bg->team_leave(sd, 0); bg->queue_pc_cleanup(sd); } if (canceled) clif->messagecolor_self(sd->fd, COLOR_RED, "BG Match Canceled: not enough players"); else pc_setglobalreg(sd, script->add_variable(arena->delay_var), (unsigned int)time(NULL)); } arena->begin_timer = INVALID_TIMER; arena->fillup_timer = INVALID_TIMER; /* reset queue */ script->queue_clear(arena->queue_id); } static void bg_begin(struct bg_arena *arena) { struct script_queue *queue = script->queue(arena->queue_id); int i, count = 0; nullpo_retv(arena); for (i = 0; i < VECTOR_LENGTH(queue->entries); i++) { struct map_session_data *sd = map->id2sd(VECTOR_INDEX(queue->entries, i)); if (sd == NULL) continue; if (sd->bg_queue.ready == 1) count++; else bg->queue_pc_cleanup(sd); } /* TODO/FIXME? I *think* it should check what kind of queue the player used, then check if his party/guild * (his team) still meet the join criteria (sort of what bg->can_queue does) */ if( count < arena->min_players ) { bg->match_over(arena,true); } else { arena->ongoing = true; if( bg->afk_timer_id == INVALID_TIMER && bg->mafksec > 0 ) bg->afk_timer_id = timer->add(timer->gettick()+10000,bg->afk_timer,0,0); /* TODO: make this a arena-independent var? or just .@? */ mapreg->setreg(script->add_variable("$@bg_queue_id"),arena->queue_id); mapreg->setregstr(script->add_variable("$@bg_delay_var$"),bg->gdelay_var); count = 0; for (i = 0; i < VECTOR_LENGTH(queue->entries); i++) { struct map_session_data *sd = map->id2sd(VECTOR_INDEX(queue->entries, i)); if (sd == NULL || sd->bg_queue.ready != 1) continue; mapreg->setreg(reference_uid(script->add_variable("$@bg_member"), count), sd->status.account_id); mapreg->setreg(reference_uid(script->add_variable("$@bg_member_group"), count), sd->bg_queue.type == BGQT_GUILD ? sd->status.guild_id : sd->bg_queue.type == BGQT_PARTY ? sd->status.party_id : 0 ); mapreg->setreg(reference_uid(script->add_variable("$@bg_member_type"), count), sd->bg_queue.type == BGQT_GUILD ? 1 : sd->bg_queue.type == BGQT_PARTY ? 2 : 0 ); count++; } mapreg->setreg(script->add_variable("$@bg_member_size"),count); npc->event_do(arena->npc_event); } } static int bg_begin_timer(int tid, int64 tick, int id, intptr_t data) { bg->begin(bg->arena[id]); bg->arena[id]->begin_timer = INVALID_TIMER; return 0; } static int bg_afk_timer(int tid, int64 tick, int id, intptr_t data) { struct s_mapiterator* iter; struct map_session_data* sd; int count = 0; iter = mapit_getallusers(); for (sd = BL_UCAST(BL_PC, mapit->first(iter)); mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) { if( !sd->bg_queue.arena || !sd->bg_id ) continue; if( DIFF_TICK(sockt->last_tick, sd->idletime) > bg->mafksec ) bg->team_leave(sd,BGTL_AFK); count++; } mapit->free(iter); if( count ) bg->afk_timer_id = timer->add(timer->gettick()+10000,bg->afk_timer,0,0); else bg->afk_timer_id = INVALID_TIMER; return 0; } static void bg_queue_pregame(struct bg_arena *arena) { struct script_queue *queue; int i; nullpo_retv(arena); queue = script->queue(arena->queue_id); for (i = 0; i < VECTOR_LENGTH(queue->entries); i++) { struct map_session_data *sd = map->id2sd(VECTOR_INDEX(queue->entries, i)); if (sd != NULL) { clif->bgqueue_battlebegins(sd,arena->id,SELF); } } arena->begin_timer = timer->add( timer->gettick() + (arena->pregame_duration*1000), bg->begin_timer, arena->id, 0 ); } static int bg_fillup_timer(int tid, int64 tick, int id, intptr_t data) { bg->queue_pregame(bg->arena[id]); bg->arena[id]->fillup_timer = INVALID_TIMER; return 0; } static void bg_queue_check(struct bg_arena *arena) { int count; struct script_queue *queue; nullpo_retv(arena); queue = script->queue(arena->queue_id); count = VECTOR_LENGTH(queue->entries); if( count == arena->max_players ) { if( arena->fillup_timer != INVALID_TIMER ) { timer->delete(arena->fillup_timer,bg->fillup_timer); arena->fillup_timer = INVALID_TIMER; } bg->queue_pregame(arena); } else if( count >= arena->min_players && arena->fillup_timer == INVALID_TIMER ) { arena->fillup_timer = timer->add( timer->gettick() + (arena->fillup_duration*1000), bg->fillup_timer, arena->id, 0 ); } } static void bg_queue_add(struct map_session_data *sd, struct bg_arena *arena, enum bg_queue_types type) { enum BATTLEGROUNDS_QUEUE_ACK result = bg->can_queue(sd,arena,type); struct script_queue *queue = NULL; int i, count = 0; nullpo_retv(sd); nullpo_retv(arena); if( arena->begin_timer != INVALID_TIMER || arena->ongoing ) { clif->bgqueue_ack(sd,BGQA_FAIL_QUEUING_FINISHED,arena->id); return; } if( result != BGQA_SUCCESS ) { clif->bgqueue_ack(sd,result,arena->id); return; } switch( type ) { /* guild/party already validated in can_queue */ case BGQT_PARTY: { struct party_data *p = party->search(sd->status.party_id); for( i = 0; i < MAX_PARTY; i++ ) { if( !p->data[i].sd || p->data[i].sd->bg_queue.arena != NULL ) continue; count++; } } break; case BGQT_GUILD: for ( i=0; iguild->max_member; i++ ) { if ( !sd->guild->member[i].sd || sd->guild->member[i].sd->bg_queue.arena != NULL ) continue; count++; } break; case BGQT_INDIVIDUAL: count = 1; break; } if( !(queue = script->queue(arena->queue_id)) || (VECTOR_LENGTH(queue->entries)+count) > arena->max_players ) { clif->bgqueue_ack(sd,BGQA_FAIL_PPL_OVERAMOUNT,arena->id); return; } switch( type ) { case BGQT_INDIVIDUAL: sd->bg_queue.type = type; sd->bg_queue.arena = arena; sd->bg_queue.ready = 0; script->queue_add(arena->queue_id,sd->status.account_id); clif->bgqueue_joined(sd, VECTOR_LENGTH(queue->entries)); clif->bgqueue_update_info(sd,arena->id, VECTOR_LENGTH(queue->entries)); break; case BGQT_PARTY: { struct party_data *p = party->search(sd->status.party_id); for( i = 0; i < MAX_PARTY; i++ ) { if( !p->data[i].sd || p->data[i].sd->bg_queue.arena != NULL ) continue; p->data[i].sd->bg_queue.type = type; p->data[i].sd->bg_queue.arena = arena; p->data[i].sd->bg_queue.ready = 0; script->queue_add(arena->queue_id,p->data[i].sd->status.account_id); clif->bgqueue_joined(p->data[i].sd, VECTOR_LENGTH(queue->entries)); clif->bgqueue_update_info(p->data[i].sd,arena->id, VECTOR_LENGTH(queue->entries)); } } break; case BGQT_GUILD: for ( i=0; iguild->max_member; i++ ) { if ( !sd->guild->member[i].sd || sd->guild->member[i].sd->bg_queue.arena != NULL ) continue; sd->guild->member[i].sd->bg_queue.type = type; sd->guild->member[i].sd->bg_queue.arena = arena; sd->guild->member[i].sd->bg_queue.ready = 0; script->queue_add(arena->queue_id,sd->guild->member[i].sd->status.account_id); clif->bgqueue_joined(sd->guild->member[i].sd, VECTOR_LENGTH(queue->entries)); clif->bgqueue_update_info(sd->guild->member[i].sd,arena->id, VECTOR_LENGTH(queue->entries)); } break; } clif->bgqueue_ack(sd,BGQA_SUCCESS,arena->id); bg->queue_check(arena); } static enum BATTLEGROUNDS_QUEUE_ACK bg_canqueue(struct map_session_data *sd, struct bg_arena *arena, enum bg_queue_types type) { int tick; unsigned int tsec; nullpo_retr(BGQA_FAIL_TYPE_INVALID, sd); nullpo_retr(BGQA_FAIL_TYPE_INVALID, arena); if( !(arena->allowed_types & type) ) return BGQA_FAIL_TYPE_INVALID; if ( sd->status.base_level > arena->max_level || sd->status.base_level < arena->min_level ) return BGQA_FAIL_LEVEL_INCORRECT; if ((sd->job & JOBL_2) == 0) /* TODO: maybe make this a per-arena setting, so users may make custom arenas like baby-only,whatever. */ return BGQA_FAIL_CLASS_INVALID; tsec = (unsigned int)time(NULL); if ( ( tick = pc_readglobalreg(sd, script->add_variable(bg->gdelay_var)) ) && tsec < tick ) { char response[100]; if( (tick-tsec) > 60 ) sprintf(response, msg_sd(sd, 456), (tick - tsec) / 60); // You are a deserter! Wait %u minute(s) before you can apply again else sprintf(response, msg_sd(sd, 457), (tick - tsec)); // You are a deserter! Wait %u seconds before you can apply again clif->messagecolor_self(sd->fd, COLOR_RED, response); return BGQA_FAIL_DESERTER; } if ( ( tick = pc_readglobalreg(sd, script->add_variable(arena->delay_var)) ) && tsec < tick ) { char response[100]; if( (tick-tsec) > 60 ) sprintf(response, msg_sd(sd, 458), (tick - tsec) / 60); // You can't reapply to this arena so fast. Apply to the different arena or wait %u minute(s) else sprintf(response, msg_sd(sd, 459), (tick - tsec)); // You can't reapply to this arena so fast. Apply to the different arena or wait %u seconds clif->messagecolor_self(sd->fd, COLOR_RED, response); return BGQA_FAIL_COOLDOWN; } if( sd->bg_queue.arena != NULL ) return BGQA_DUPLICATE_REQUEST; switch(type) { case BGQT_GUILD: if( !sd->guild || !sd->state.gmaster_flag ) return BGQA_NOT_PARTY_GUILD_LEADER; else { int i, count = 0; for ( i=0; iguild->max_member; i++ ) { if ( !sd->guild->member[i].sd || sd->guild->member[i].sd->bg_queue.arena != NULL ) continue; count++; } if ( count < arena->min_team_players ) { char response[121]; if( count != sd->guild->connect_member && sd->guild->connect_member >= arena->min_team_players ) sprintf(response, msg_sd(sd, 460), arena->min_team_players); // Can't apply: not enough members in your team/guild that have not entered the queue in individual mode, minimum is %d else sprintf(response, msg_sd(sd, 461), arena->min_team_players); // Can't apply: not enough members in your team/guild, minimum is %d clif->messagecolor_self(sd->fd, COLOR_RED, response); return BGQA_FAIL_TEAM_COUNT; } } break; case BGQT_PARTY: if( !sd->status.party_id ) return BGQA_NOT_PARTY_GUILD_LEADER; else { struct party_data *p; if( (p = party->search(sd->status.party_id) ) ) { int i, count = 0; bool is_leader = false; for(i = 0; i < MAX_PARTY; i++) { if( !p->data[i].sd ) continue; if( p->party.member[i].leader && sd == p->data[i].sd ) is_leader = true; if( p->data[i].sd->bg_queue.arena == NULL ) count++; } if( !is_leader ) return BGQA_NOT_PARTY_GUILD_LEADER; if( count < arena->min_team_players ) { char response[121]; if( count != p->party.count && p->party.count >= arena->min_team_players ) sprintf(response, msg_sd(sd, 462), arena->min_team_players); // Can't apply: not enough members in your team/party that have not entered the queue in individual mode, minimum is %d else sprintf(response, msg_sd(sd, 463), arena->min_team_players); // Can't apply: not enough members in your team/party, minimum is %d clif->messagecolor_self(sd->fd, COLOR_RED, response); return BGQA_FAIL_TEAM_COUNT; } } else return BGQA_NOT_PARTY_GUILD_LEADER; } break; case BGQT_INDIVIDUAL:/* already did */ break; default: ShowDebug("bg_canqueue: unknown/unsupported type %u\n", type); return BGQA_DUPLICATE_REQUEST; } return BGQA_SUCCESS; } static void do_init_battleground(bool minimal) { if (minimal) return; bg->team_db = idb_alloc(DB_OPT_RELEASE_DATA); timer->add_func_list(bg->send_xy_timer, "bg_send_xy_timer"); timer->add_interval(timer->gettick() + battle_config.bg_update_interval, bg->send_xy_timer, 0, 0, battle_config.bg_update_interval); bg->config_read(); } /** * @see DBApply */ static int bg_team_db_final(union DBKey key, struct DBData *data, va_list ap) { struct battleground_data* bgd = DB->data2ptr(data); HPM->data_store_destroy(&bgd->hdata); return 0; } static void do_final_battleground(void) { bg->team_db->destroy(bg->team_db,bg->team_db_final); if (bg->arena) { int i; for (i = 0; i < bg->arenas; i++) { if (bg->arena[i]) aFree(bg->arena[i]); } aFree(bg->arena); } } void battleground_defaults(void) { bg = &bg_s; bg->queue_on = false; bg->mafksec = 0; bg->afk_timer_id = INVALID_TIMER; bg->arena = NULL; bg->arenas = 0; /* */ bg->team_db = NULL; bg->team_counter = 0; /* */ bg->init = do_init_battleground; bg->final = do_final_battleground; /* */ bg->name2arena = bg_name2arena; bg->queue_add = bg_queue_add; bg->can_queue = bg_canqueue; bg->id2pos = bg_id2pos; bg->queue_pc_cleanup = bg_queue_player_cleanup; bg->begin = bg_begin; bg->begin_timer = bg_begin_timer; bg->queue_pregame = bg_queue_pregame; bg->fillup_timer = bg_fillup_timer; bg->queue_ready_ack = bg_queue_ready_ack; bg->match_over = bg_match_over; bg->queue_check = bg_queue_check; bg->team_search = bg_team_search; bg->getavailablesd = bg_getavailablesd; bg->team_delete = bg_team_delete; bg->team_warp = bg_team_warp; bg->send_dot_remove = bg_send_dot_remove; bg->team_join = bg_team_join; bg->team_leave = bg_team_leave; bg->member_respawn = bg_member_respawn; bg->create = bg_create; bg->team_get_id = bg_team_get_id; bg->send_message = bg_send_message; bg->send_xy_timer_sub = bg_send_xy_timer_sub; bg->send_xy_timer = bg_send_xy_timer; bg->afk_timer = bg_afk_timer; bg->team_db_final = bg_team_db_final; /* */ bg->str2teamtype = bg_str2teamtype; /* */ bg->config_read = bg_config_read; }