#include "party.hpp" // party.cpp - Small groups of temporary allies. // // Copyright © ????-2004 Athena Dev Teams // Copyright © 2004-2011 The Mana World Development Team // Copyright © 2011-2014 Ben Longbons // Copyright © 2013 Freeyorp // // This file is part of The Mana World (Athena server) // // This program 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 . #include "../compat/nullpo.hpp" #include "../strings/xstring.hpp" #include "../generic/db.hpp" #include "../io/cxxstdio.hpp" #include "../net/timer.hpp" #include "../mmo/ids.hpp" #include "../high/mmo.hpp" #include "battle.hpp" #include "battle_conf.hpp" #include "clif.hpp" #include "globals.hpp" #include "intif.hpp" #include "map.hpp" #include "pc.hpp" #include "../poison.hpp" namespace tmwa { namespace map { // 座標やHP送信の間隔 constexpr interval_t PARTY_SEND_XYHP_INVERVAL = 1_s; static void party_check_conflict(dumb_ptr sd); static void party_send_xyhp_timer(TimerData *tid, tick_t tick); // 初期化 void do_init_party(void) { Timer(gettick() + PARTY_SEND_XYHP_INVERVAL, party_send_xyhp_timer, PARTY_SEND_XYHP_INVERVAL ).detach(); } // 検索 Option party_search(PartyId party_id) { Option> party_most_ = party_db.search(party_id); return party_most_.map([party_id](P party_most) { return PartyPair{party_id, party_most}; }); } static void party_searchname_sub(PartyPair p, PartyName str, Borrowed> dst) { if (p->name == str) *dst = Some(p); } // パーティ名検索 Option party_searchname(PartyName str) { Option p = None; for (auto& pair : party_db) { PartyPair tmp{pair.first, borrow(pair.second)}; party_searchname_sub(tmp, str, borrow(p)); } return p; } /* Process a party creation request. */ int party_create(dumb_ptr sd, PartyName name) { nullpo_retz(sd); name = stringish(name.strip()); /* The party name is empty/invalid. */ if (!name) clif_party_created(sd, 1); /* Make sure the character isn't already in a party. */ if (!sd->status.party_id) intif_create_party(sd, name); else clif_party_created(sd, 2); return 0; } /* Relay the result of a party creation request. */ void party_created(AccountId account_id, int fail, PartyId party_id, PartyName name) { dumb_ptr sd; sd = map_id2sd(account_to_block(account_id)); nullpo_retv(sd); /* The party name is valid and not already taken. */ if (!fail) { sd->status.party_id = party_id; if (party_search(party_id).is_some()) { PRINTF("party_created(): ID already exists!\n"_fmt); exit(1); } Borrowed p = party_db.init(party_id); p->name = name; /* The party was created successfully. */ clif_party_created(sd, 0); } else clif_party_created(sd, 1); } // 情報要求 void party_request_info(PartyId party_id) { intif_request_partyinfo(party_id); } // 所属キャラの確認 static int party_check_member(PartyPair p) { for (io::FD i : iter_fds()) { Session *s = get_session(i); if (!s) continue; map_session_data *sd = static_cast(s->session_data.get()); if (sd && sd->state.auth) { if (sd->status.party_id == p.party_id) { int j, f = 1; for (j = 0; j < MAX_PARTY; j++) { // パーティにデータがあるか確認 if (p->member[j].account_id == sd->status_key.account_id) { if (p->member[j].name == sd->status_key.name) f = 0; // データがある else { // I can prove it was already zeroed // p->member[j].sd = nullptr; // 同垢別キャラだった } } } if (f) { sd->status.party_id = PartyId(); if (battle_config.error_log) PRINTF("party: check_member %d[%s] is not member\n"_fmt, sd->status_key.account_id, sd->status_key.name); } } } } return 0; } // 情報所得失敗(そのIDのキャラを全部未所属にする) int party_recv_noinfo(PartyId party_id) { for (io::FD i : iter_fds()) { Session *s = get_session(i); if (!s) continue; map_session_data *sd = static_cast(s->session_data.get()); if (sd && sd->state.auth) { if (sd->status.party_id == party_id) sd->status.party_id = PartyId(); } } return 0; } static PartyPair handle_info(const PartyPair sp) { Option p_ = party_search(sp.party_id); OMATCH_BEGIN_SOME (p, p_) { *p.party_most = *sp.party_most; return p; } OMATCH_END (); { PartyPair p{sp.party_id, party_db.init(sp.party_id)}; // 最初のロードなのでユーザーのチェックを行う *p.party_most = *sp.party_most; party_check_member(p); return p; } } // 情報所得 int party_recv_info(const PartyPair sp) { int i; PartyPair p = handle_info(sp); for (i = 0; i < MAX_PARTY; i++) { // sdの設定 dumb_ptr sd = map_id2sd(account_to_block(p->member[i].account_id)); p->member[i].sd = (sd != nullptr && sd->status.party_id == p.party_id) ? sd.operator->() : nullptr; } clif_party_info(p, nullptr); for (i = 0; i < MAX_PARTY; i++) { // 設定情報の送信 // dumb_ptr sd = map_id2sd(p->member[i].account_id); dumb_ptr sd = dumb_ptr(p->member[i].sd); if (sd != nullptr && sd->party_sended == 0) { clif_party_option(p, sd, 0x100); sd->party_sended = 1; } } return 0; } /* Process party invitation from sd to account_id. */ int party_invite(dumb_ptr sd, AccountId account_id) { dumb_ptr tsd = map_id2sd(account_to_block(account_id)); PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); int i; int full = 1; /* Indicates whether or not there's room for one more. */ nullpo_retz(sd); if (!tsd || !tsd->sess) return 0; if (!battle_config.invite_request_check) { /* Disallow the invitation under these conditions. */ if (tsd->trade_partner || tsd->npc_id || tsd->npc_shopid || pc_checkskill(tsd, SkillID::NV_PARTY) < 1) { clif_party_inviteack(sd, tsd->status_key.name, 1); return 0; } } /* The target player is already in a party, or has a pending invitation. */ if (tsd->status.party_id || tsd->party_invite) { clif_party_inviteack(sd, tsd->status_key.name, 0); return 0; } for (i = 0; i < MAX_PARTY; i++) { /* * A character from the target account is already in the same party. * The response isn't strictly accurate, as they're separate * characters, but we're making do with what was already in place and * leaving this (mostly) alone for now. */ if (p->member[i].account_id == account_id) { clif_party_inviteack(sd, tsd->status_key.name, 1); return 0; } if (!p->member[i].account_id) full = 0; } /* There isn't enough room for a new member. */ if (full) { clif_party_inviteack(sd, tsd->status_key.name, 3); return 0; } /* Otherwise, relay the invitation to the target player. */ tsd->party_invite = sd->status.party_id; tsd->party_invite_account = sd->status_key.account_id; clif_party_invite(sd, tsd); return 0; } /* Process response to party invitation. */ int party_reply_invite(dumb_ptr sd, AccountId account_id, int flag) { nullpo_retz(sd); /* There is no pending invitation. */ if (!sd->party_invite || !sd->party_invite_account) return 0; /* * Only one invitation can be pending, so these have to be the same. Since * the client continues to send the wrong ID, and it's already managed on * this side of things, the sent ID is being ignored. */ account_id = sd->party_invite_account; /* The invitation was accepted. */ if (flag == 1) intif_party_addmember(sd->party_invite, sd->status_key.account_id); /* The invitation was rejected. */ else { /* This is the player who sent the invitation. */ dumb_ptr tsd = nullptr; sd->party_invite = PartyId(); sd->party_invite_account = AccountId(); if ((tsd = map_id2sd(account_to_block(account_id)))) clif_party_inviteack(tsd, sd->status_key.name, 1); } return 0; } // パーティが追加された int party_member_added(PartyId party_id, AccountId account_id, int flag) { dumb_ptr sd = map_id2sd(account_to_block(account_id)), sd2; if (sd == nullptr) { if (flag == 0) { if (battle_config.error_log) PRINTF("party: member added error %d is not online\n"_fmt, account_id); intif_party_leave(party_id, account_id); // キャラ側に登録できなかったため脱退要求を出す } return 0; } sd2 = map_id2sd(account_to_block(sd->party_invite_account)); sd->party_invite = PartyId(); sd->party_invite_account = AccountId(); PartyPair p = TRY_UNWRAP(party_search(party_id), { PRINTF("party_member_added: party %d not found.\n"_fmt, party_id); intif_party_leave(party_id, account_id); return 0; }); if (flag == 1) { // 失敗 if (sd2 != nullptr) clif_party_inviteack(sd2, sd->status_key.name, 0); return 0; } // 成功 sd->party_sended = 0; sd->status.party_id = party_id; if (sd2 != nullptr) clif_party_inviteack(sd2, sd->status_key.name, 2); // いちおう競合確認 party_check_conflict(sd); party_send_xy_clear(p); return 0; } // パーティ除名要求 int party_removemember(dumb_ptr sd, AccountId account_id) { int i; nullpo_retz(sd); PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); for (i = 0; i < MAX_PARTY; i++) { // リーダーかどうかチェック if (p->member[i].account_id == sd->status_key.account_id) { if (p->member[i].leader == 0) return 0; } } for (i = 0; i < MAX_PARTY; i++) { // 所属しているか調べる if (p->member[i].account_id == account_id) { intif_party_leave(p.party_id, account_id); return 0; } } return 0; } // パーティ脱退要求 int party_leave(dumb_ptr sd) { int i; nullpo_retz(sd); PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); for (i = 0; i < MAX_PARTY; i++) { // 所属しているか if (p->member[i].account_id == sd->status_key.account_id) { intif_party_leave(p.party_id, sd->status_key.account_id); return 0; } } return 0; } // パーティメンバが脱退した int party_member_leaved(PartyId party_id, AccountId account_id, CharName name) { dumb_ptr sd = map_id2sd(account_to_block(account_id)); Option p_ = party_search(party_id); OMATCH_BEGIN_SOME (p, p_) { int i; for (i = 0; i < MAX_PARTY; i++) if (p->member[i].account_id == account_id) { clif_party_leaved(p, sd, account_id, name, 0x00); p->member[i].account_id = AccountId(); p->member[i].sd = nullptr; } } OMATCH_END (); if (sd != nullptr && sd->status.party_id == party_id) { sd->status.party_id = PartyId(); sd->party_sended = 0; } return 0; } // パーティ解散通知 int party_broken(PartyId party_id) { int i; PartyPair p = TRY_UNWRAP(party_search(party_id), return 0); for (i = 0; i < MAX_PARTY; i++) { if (p->member[i].sd != nullptr) { clif_party_leaved(p, dumb_ptr(p->member[i].sd), p->member[i].account_id, p->member[i].name, 0x10); p->member[i].sd->status.party_id = PartyId(); p->member[i].sd->party_sended = 0; } } party_db.erase(party_id); return 0; } // パーティの設定変更要求 int party_changeoption(dumb_ptr sd, int exp, int item) { nullpo_retz(sd); if (!sd->status.party_id) return 0; if (party_search(sd->status.party_id).is_none()) return 0; intif_party_changeoption(sd->status.party_id, sd->status_key.account_id, exp, item); return 0; } // パーティの設定変更通知 int party_optionchanged(PartyId party_id, AccountId account_id, int exp, int item, int flag) { dumb_ptr sd = map_id2sd(account_to_block(account_id)); PartyPair p = TRY_UNWRAP(party_search(party_id), return 0); if (!(flag & 0x01)) p->exp = exp; if (!(flag & 0x10)) p->item = item; clif_party_option(p, sd, flag); return 0; } // パーティメンバの移動通知 void party_recv_movemap(PartyId party_id, AccountId account_id, MapName mapname, int online, int lv) { int i; PartyPair p = TRY_UNWRAP(party_search(party_id), return); for (i = 0; i < MAX_PARTY; i++) { PartyMember *m = &p->member[i]; if (m == nullptr) { PRINTF("party_recv_movemap nullpo?\n"_fmt); return; } if (m->account_id == account_id) { m->map = mapname; m->online = online; m->lv = lv; break; } } if (i == MAX_PARTY) { if (battle_config.error_log) PRINTF("party: not found member %d on %d[%s]"_fmt, account_id, party_id, p->name); return; } for (i = 0; i < MAX_PARTY; i++) { // sd再設定 dumb_ptr sd = map_id2sd(account_to_block(p->member[i].account_id)); p->member[i].sd = (sd != nullptr && sd->status.party_id == p.party_id) ? sd.operator->() : nullptr; } party_send_xy_clear(p); // 座標再通知要請 clif_party_info(p, nullptr); } // パーティメンバの移動 int party_send_movemap(dumb_ptr sd) { nullpo_retz(sd); if (!sd->status.party_id) return 0; intif_party_changemap(sd, 1); if (sd->party_sended != 0) // もうパーティデータは送信済み return 0; // 競合確認 party_check_conflict(sd); // あるならパーティ情報送信 PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); { party_check_member(p); // 所属を確認する if (sd->status.party_id == p.party_id) { clif_party_info(p, sd->sess); clif_party_option(p, sd, 0x100); sd->party_sended = 1; } } return 0; } // パーティメンバのログアウト int party_send_logout(dumb_ptr sd) { nullpo_retz(sd); if (sd->status.party_id) intif_party_changemap(sd, 0); // sdが無効になるのでパーティ情報から削除 PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); { int i; for (i = 0; i < MAX_PARTY; i++) if (dumb_ptr(p->member[i].sd) == sd) p->member[i].sd = nullptr; } return 0; } // パーティメッセージ送信 void party_send_message(dumb_ptr sd, XString mes) { if (!sd->status.party_id) return; intif_party_message(sd->status.party_id, sd->status_key.account_id, mes); } // パーティメッセージ受信 void party_recv_message(PartyId party_id, AccountId account_id, XString mes) { PartyPair p = TRY_UNWRAP(party_search(party_id), return); clif_party_message(p, account_id, mes); } // パーティ競合確認 void party_check_conflict(dumb_ptr sd) { nullpo_retv(sd); intif_party_checkconflict(sd->status.party_id, sd->status_key.account_id, sd->status_key.name); } // 位置やHP通知用 static void party_send_xyhp_timer_sub(PartyPair p) { int i; for (i = 0; i < MAX_PARTY; i++) { dumb_ptr sd = dumb_ptr(p->member[i].sd); if (sd != nullptr) { // 座標通知 if (sd->party_x != sd->bl_x || sd->party_y != sd->bl_y) { clif_party_xy(p, sd); sd->party_x = sd->bl_x; sd->party_y = sd->bl_y; } // HP通知 if (sd->party_hp != sd->status.hp) { clif_party_hp(p, sd); sd->party_hp = sd->status.hp; } } } } // 位置やHP通知 void party_send_xyhp_timer(TimerData *, tick_t) { for (auto& pair : party_db) { PartyPair tmp{pair.first, borrow(pair.second)}; party_send_xyhp_timer_sub(tmp); } } // 位置通知クリア void party_send_xy_clear(PartyPair p) { int i; for (i = 0; i < MAX_PARTY; i++) { dumb_ptr sd = dumb_ptr(p->member[i].sd); if (sd != nullptr) { sd->party_x = -1; sd->party_y = -1; sd->party_hp = -1; } } } // HP通知の必要性検査用(map_foreachinmoveareaから呼ばれる) void party_send_hp_check(dumb_ptr bl, PartyId party_id, int *flag) { dumb_ptr sd; nullpo_retv(bl); sd = bl->is_player(); if (sd->status.party_id == party_id) { *flag = 1; sd->party_hp = -1; } } // 経験値公平分配 int party_exp_share(PartyPair p, Borrowed mapid, int base_exp, int job_exp) { dumb_ptr sd; int i, c; for (i = c = 0; i < MAX_PARTY; i++) { sd = dumb_ptr(p->member[i].sd); if (sd != nullptr && sd->bl_m == mapid) c++; } if (c == 0) return 0; for (i = 0; i < MAX_PARTY; i++) { sd = dumb_ptr(p->member[i].sd); if (sd != nullptr && sd->bl_m == mapid) pc_gainexp_reason(sd, base_exp / c + 1, job_exp / c + 1, PC_GAINEXP_REASON::SHARING); } return 0; } // 同じマップのパーティメンバー全体に処理をかける // type==0 同じマップ // !=0 画面内 void party_foreachsamemap(std::function)> func, dumb_ptr sd, int type) { int i; int x0, y0, x1, y1; dumb_ptr list[MAX_PARTY]; int blockcount = 0; nullpo_retv(sd); PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return); x0 = sd->bl_x - AREA_SIZE; y0 = sd->bl_y - AREA_SIZE; x1 = sd->bl_x + AREA_SIZE; y1 = sd->bl_y + AREA_SIZE; for (i = 0; i < MAX_PARTY; i++) { PartyMember *m = &p->member[i]; if (m->sd != nullptr) { if (sd->bl_m != m->sd->bl_m) continue; if (type != 0 && (m->sd->bl_x < x0 || m->sd->bl_y < y0 || m->sd->bl_x > x1 || m->sd->bl_y > y1)) continue; list[blockcount++] = dumb_ptr(m->sd); } } MapBlockLock lock; for (i = 0; i < blockcount; i++) if (list[i]->bl_prev) // 有効かどうかチェック func(list[i]); } } // namespace map } // namespace tmwa