summaryrefslogtreecommitdiff
path: root/src/map/npc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/map/npc.c')
-rw-r--r--src/map/npc.c680
1 files changed, 556 insertions, 124 deletions
diff --git a/src/map/npc.c b/src/map/npc.c
index cc588e52c..d369aca82 100644
--- a/src/map/npc.c
+++ b/src/map/npc.c
@@ -1457,7 +1457,11 @@ static int npc_cashshop_buylist(struct map_session_data *sd, int points, struct
return ERROR_TYPE_NPC;
if( nd->subtype != CASHSHOP ) {
- if (nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type != NST_ZENY && nd->u.scr.shop->type != NST_MARKET && nd->u.scr.shop->type != NST_BARTER) {
+ if (nd->subtype == SCRIPT && nd->u.scr.shop &&
+ nd->u.scr.shop->type != NST_ZENY &&
+ nd->u.scr.shop->type != NST_MARKET &&
+ nd->u.scr.shop->type != NST_BARTER &&
+ nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
shop = nd->u.scr.shop->item;
shop_size = nd->u.scr.shop->items;
} else {
@@ -1623,7 +1627,7 @@ static void npc_market_tosql(struct npc_data *nd, int index)
{
nullpo_retv(nd);
Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
- if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s','%d','%u')",
+ if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s','%d','%d')",
map->npc_market_data_db, nd->exname, nd->u.scr.shop->item[index].nameid, nd->u.scr.shop->item[index].qty))
Sql_ShowDebug(map->mysql_handle);
}
@@ -1716,7 +1720,9 @@ static void npc_barter_tosql(struct npc_data *nd, int index)
nullpo_retv(nd);
Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
const struct npc_item_list *const item = &nd->u.scr.shop->item[index];
- if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s', '%d', '%u', '%u', '%d')",
+ if (item->qty == -1)
+ return;
+ if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s', '%d', '%d', '%u', '%d')",
map->npc_barter_data_db, nd->exname, item->nameid, item->qty, item->value, item->value2)) {
Sql_ShowDebug(map->mysql_handle);
}
@@ -1753,6 +1759,178 @@ static void npc_barter_delfromsql(struct npc_data *nd, int index)
}
}
+
+/**
+ * Loads persistent NPC Expanded Barter Data from SQL
+ **/
+static void npc_expanded_barter_fromsql(void)
+{
+ struct SqlStmt *stmt = SQL->StmtMalloc(map->mysql_handle);
+ char name[NAME_LENGTH + 1];
+ int itemid;
+ int amount;
+ int zeny;
+ StringBuf buf;
+
+ StrBuf->Init(&buf);
+ StrBuf->AppendStr(&buf, "SELECT `name`, `itemId`, `amount`, `zeny`");
+ for (int k = 1; k < 11; k ++) {
+ StrBuf->Printf(&buf, ", `currencyId%d`, `currencyAmount%d`, `currencyRefine%d`", k, k, k);
+ }
+ StrBuf->Printf(&buf, " FROM `%s`", map->npc_expanded_barter_data_db);
+
+ if (SQL_ERROR == SQL->StmtPrepareStr(stmt, StrBuf->Value(&buf))
+ || SQL_ERROR == SQL->StmtExecute(stmt)
+ ) {
+ SqlStmt_ShowDebug(stmt);
+ SQL->StmtFree(stmt);
+ return;
+ }
+
+ struct npc_barter_currency tempCurrency[10];
+ SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &name, sizeof name, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &itemid, sizeof itemid, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 2, SQLDT_UINT32, &amount, sizeof amount, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 3, SQLDT_UINT32, &zeny, sizeof zeny, NULL, NULL);
+ for (int k = 0; k < 10; k ++) {
+ SQL->StmtBindColumn(stmt, k * 3 + 4, SQLDT_INT, &tempCurrency[k].nameid, sizeof tempCurrency[k].nameid, NULL, NULL);
+ SQL->StmtBindColumn(stmt, k * 3 + 5, SQLDT_INT, &tempCurrency[k].amount, sizeof tempCurrency[k].amount, NULL, NULL);
+ SQL->StmtBindColumn(stmt, k * 3 + 6, SQLDT_INT, &tempCurrency[k].refine, sizeof tempCurrency[k].refine, NULL, NULL);
+ }
+
+ while (SQL_SUCCESS == SQL->StmtNextRow(stmt)) {
+ struct npc_data *nd = NULL;
+ unsigned short i;
+
+ if ((nd = npc->name2id(name)) == NULL) {
+ ShowError("npc_expanded_barter_fromsql: NPC '%s' not found! skipping...\n",name);
+ npc->expanded_barter_delfromsql_sub(name, INT_MAX, 0, 0, NULL);
+ continue;
+ } else if (nd->subtype != SCRIPT || nd->u.scr.shop == NULL || nd->u.scr.shop->items == 0 || nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ ShowError("npc_expanded_barter_fromsql: NPC '%s' is not proper for barter, skipping...\n",name);
+ npc->expanded_barter_delfromsql_sub(name, INT_MAX, 0, 0, NULL);
+ continue;
+ }
+
+ for (i = 0; i < nd->u.scr.shop->items; i++) {
+ struct npc_item_list *const item = &nd->u.scr.shop->item[i];
+ if (item->nameid == itemid && item->value == zeny) {
+ int count = nd->u.scr.shop->item[i].value2;
+ if (count > 10)
+ count = 10;
+ int curIndex;
+ for (curIndex = 0; curIndex < count; curIndex ++) {
+ struct npc_barter_currency *currency = &nd->u.scr.shop->item[i].currency[curIndex];
+ struct npc_barter_currency *currency2 = &tempCurrency[curIndex];
+ if (currency->nameid != currency2->nameid ||
+ currency->amount != currency2->amount ||
+ currency->refine != currency2->refine) {
+ break;
+ }
+ }
+ if (curIndex == count) {
+ item->qty = amount;
+ break;
+ }
+ }
+ }
+
+ if (i == nd->u.scr.shop->items) {
+ ShowError("npc_expanded_barter_fromsql: NPC '%s' does not sell item %d (qty %d), deleting...\n", name, itemid, amount);
+ npc->expanded_barter_delfromsql_sub(name, itemid, zeny, 10, &tempCurrency[0]);
+ continue;
+ }
+ }
+ SQL->StmtFree(stmt);
+ StrBuf->Destroy(&buf);
+}
+
+/**
+ * Saves persistent NPC Expanded Barter Data into SQL
+ **/
+static void npc_expanded_barter_tosql(struct npc_data *nd, int index)
+{
+ nullpo_retv(nd);
+ Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
+ const struct npc_item_list *const item = &nd->u.scr.shop->item[index];
+ if (item->qty == -1)
+ return;
+
+ npc->expanded_barter_delfromsql(nd, index);
+
+ StringBuf buf;
+ StrBuf->Init(&buf);
+ StrBuf->Printf(&buf, "INSERT INTO `%s` VALUES ('%s', '%d', '%d', '%u'", map->npc_expanded_barter_data_db, nd->exname, item->nameid, item->qty, item->value);
+ int currencyCount = item->value2;
+ if (currencyCount > 10)
+ currencyCount = 10;
+ int k;
+ for (k = 0; k < currencyCount; k++) {
+ struct npc_barter_currency *currency = &item->currency[k];
+ StrBuf->Printf(&buf, ", '%d', '%d', '%d'", currency->nameid, currency->amount, currency->refine);
+ }
+ for (; k < 10; k ++) {
+ StrBuf->Printf(&buf, ", '0', '0', '0'");
+ }
+ StrBuf->AppendStr(&buf, ")");
+
+ if (SQL_ERROR == SQL->QueryStr(map->mysql_handle, StrBuf->Value(&buf))) {
+ Sql_ShowDebug(map->mysql_handle);
+ }
+ StrBuf->Destroy(&buf);
+}
+
+/**
+ * Removes persistent NPC Expanded Barter Data from SQL
+ */
+static void npc_expanded_barter_delfromsql_sub(const char *npcname, int itemId, int zeny, int currencyCount, struct npc_barter_currency* currency)
+{
+ if (itemId == INT_MAX) {
+ if (SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `name`='%s'", map->npc_expanded_barter_data_db, npcname))
+ Sql_ShowDebug(map->mysql_handle);
+ } else {
+ StringBuf buf;
+
+ StrBuf->Init(&buf);
+ StrBuf->Printf(&buf, "DELETE FROM `%s` WHERE `name`='%s' AND `itemId`='%d' AND `zeny`='%d'",
+ map->npc_expanded_barter_data_db, npcname, itemId, zeny);
+ int k = 0;
+ if (currencyCount > 10)
+ currencyCount = 10;
+ for (k = 0; k < currencyCount; k++) {
+ struct npc_barter_currency *currency1 = &currency[k];
+ StrBuf->Printf(&buf, " AND currencyId%d='%d' and currencyAmount%d='%d' and currencyRefine%d='%d'",
+ k + 1, currency1->nameid, k + 1, currency1->amount, k + 1, currency1->refine);
+ }
+ for (; k < 10; k ++) {
+ StrBuf->Printf(&buf, " AND currencyId%d='0' and currencyAmount%d='0' and currencyRefine%d='0'",
+ k + 1, k + 1, k + 1);
+ }
+ StrBuf->AppendStr(&buf, " LIMIT 1");
+
+ if (SQL_ERROR == SQL->QueryStr(map->mysql_handle, StrBuf->Value(&buf))) {
+ Sql_ShowDebug(map->mysql_handle);
+ }
+ StrBuf->Destroy(&buf);
+ }
+}
+
+
+/**
+ * Removes persistent NPC Expanded Barter Data from SQL
+ **/
+static void npc_expanded_barter_delfromsql(struct npc_data *nd, int index)
+{
+ nullpo_retv(nd);
+ if (index == INT_MAX) {
+ npc->expanded_barter_delfromsql_sub(nd->exname, INT_MAX, 0, 0, NULL);
+ } else {
+ Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
+ const struct npc_item_list *const item = &nd->u.scr.shop->item[index];
+ npc->expanded_barter_delfromsql_sub(nd->exname, item->nameid, item->value, item->value2, &item->currency[0]);
+ }
+}
+
/**
* Judges whether to allow and spawn a trader's window.
**/
@@ -1788,6 +1966,9 @@ static bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd)
case NST_BARTER:
clif->npc_barter_open(sd, nd);
break;
+ case NST_EXPANDED_BARTER:
+ clif->npc_expanded_barter_open(sd, nd);
+ break;
default:
clif->cashshop_show(sd,nd);
break;
@@ -1914,7 +2095,11 @@ static int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount,
return ERROR_TYPE_ITEM_ID; // Invalid Item
if( nd->subtype != CASHSHOP ) {
- if (nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type != NST_ZENY && nd->u.scr.shop->type != NST_MARKET && nd->u.scr.shop->type != NST_BARTER) {
+ if (nd->subtype == SCRIPT && nd->u.scr.shop &&
+ nd->u.scr.shop->type != NST_ZENY &&
+ nd->u.scr.shop->type != NST_MARKET &&
+ nd->u.scr.shop->type != NST_BARTER &&
+ nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
shop = nd->u.scr.shop->item;
shop_size = nd->u.scr.shop->items;
} else {
@@ -2262,6 +2447,9 @@ static int npc_barter_buylist(struct map_session_data *sd, struct barteritemlist
if (n < 0 || n >= sd->status.inventorySize)
return 11; // wrong inventory index
+ if (entry->addAmount <= 0)
+ return 14; // not enough item amount in inventory
+
int removeId = sd->status.inventory[n].nameid;
const int j = entry->shopIndex;
if (j < 0 || j >= shop_size)
@@ -2355,6 +2543,152 @@ static int npc_barter_buylist(struct map_session_data *sd, struct barteritemlist
return 12;
}
+
+/**
+ * Processes incoming npc expanded barter purchase list
+ **/
+static int npc_expanded_barter_buylist(struct map_session_data *sd, struct barteritemlist *item_list)
+{
+ nullpo_retr(1, sd);
+ nullpo_retr(1, item_list);
+
+ struct npc_data* nd = npc->checknear(sd, map->id2bl(sd->npc_shopid));
+
+ if (nd == NULL || nd->subtype != SCRIPT || VECTOR_LENGTH(*item_list) == 0 ||
+ !nd->u.scr.shop || nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ return 11;
+ }
+
+ struct npc_item_list *shop = nd->u.scr.shop->item;
+ unsigned short shop_size = nd->u.scr.shop->items;
+ int w = 0;
+ int new_ = 0;
+ int64 z = 0;
+ int items[MAX_INVENTORY] = { 0 };
+
+ // process entries in buy list, one by one
+ for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) {
+ struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i);
+
+ if (entry->addAmount <= 0)
+ return 14; // not enough item amount in inventory
+
+ const int j = entry->shopIndex;
+ if (j < 0 || j >= shop_size)
+ return 13; // no such item in shop
+ if (entry->addId != shop[j].nameid && entry->addId != itemdb_viewid(shop[j].nameid))
+ return 13; // no such item in shop
+ entry->addId = shop[j].nameid; // item_avail replacement
+ if (!itemdb->exists(entry->addId))
+ return 13; // item no longer in itemdb
+
+ if ((int)shop[j].qty != -1 && entry->addAmount > (int)shop[j].qty)
+ return 14; // not enough item amount in shop
+
+ int currencyCount = shop[j].value2;
+ for (int currencyIndex = 0; currencyIndex < currencyCount; currencyIndex ++) {
+ struct npc_barter_currency *currency = &shop[j].currency[currencyIndex];
+ const int currencyItemId = currency->nameid;
+ const int currencyRefine = currency->refine;
+ int removeAmount = currency->amount * entry->addAmount;
+ if (removeAmount <= 0)
+ continue;
+ for (int n = 0; n < sd->status.inventorySize && removeAmount > 0; ++n) {
+ // check item id and existing amount
+ if (sd->status.inventory[n].nameid == currencyItemId && sd->status.inventory[n].amount > 0) {
+ // check item refine level
+ if (currencyRefine != -1 && sd->status.inventory[n].refine != currencyRefine)
+ continue;
+ if (sd->status.inventory[n].amount >= removeAmount) {
+ items[n] += removeAmount;
+ removeAmount = 0;
+ w -= itemdb_weight(currencyItemId) * removeAmount;
+ break;
+ } else {
+ items[n] += sd->status.inventory[n].amount;
+ removeAmount -= sd->status.inventory[n].amount;
+ w -= itemdb_weight(currencyItemId) * sd->status.inventory[n].amount;
+ }
+ }
+ if (items[n] > sd->status.inventory[n].amount)
+ return 14; // not enough item amount in inventory
+ }
+ if (removeAmount != 0) {
+ return 14; // not enough item amount in inventory
+ }
+ }
+
+ entry->addId = shop[j].nameid; //item_avail replacement
+
+ npc_market_qty[i] = j;
+
+ if (!itemdb->isstackable(entry->addId) && entry->addAmount > 1) {
+ //Exploit? You can't buy more than 1 of equipment types o.O
+ ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of non-stackable item %d!\n",
+ sd->status.name, sd->status.account_id, sd->status.char_id, entry->addAmount, entry->addId);
+ entry->addAmount = 1;
+ }
+
+ switch (pc->checkadditem(sd, entry->addId, entry->addAmount)) {
+ case ADDITEM_EXIST:
+ break;
+ case ADDITEM_NEW:
+ new_++;
+ break;
+ case ADDITEM_OVERAMOUNT: /* TODO find official response for this */
+ return 1;
+ }
+
+ z += (int64)shop[j].value * entry->addAmount;
+ w += itemdb_weight(entry->addId) * entry->addAmount;
+ }
+
+ if (z > sd->status.zeny)
+ return 3; // Not enough Zeny
+
+ if ((int64)w + sd->weight > sd->max_weight)
+ return 2; // Too heavy
+
+ if (pc->inventoryblank(sd) < new_)
+ return 3; // Not enough space to store items
+
+ for (int i = 0; i < sd->status.inventorySize; ++i) {
+ const int removeAmountTotal = items[i];
+ if (removeAmountTotal == 0)
+ continue;
+ if (pc->delitem(sd, i, removeAmountTotal, 0, DELITEM_SOLD, LOG_TYPE_NPC) != 0) {
+ return 11; // unknown exploit
+ }
+ }
+
+ pc->payzeny(sd, (int)z, LOG_TYPE_NPC, NULL);
+
+ for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) {
+ struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i);
+ const int shopIdx = npc_market_qty[i];
+
+ if ((int)shop[shopIdx].qty != -1) {
+ if (entry->addAmount > (int)shop[shopIdx].qty) /* wohoo someone tampered with the packet. */
+ return 14;
+ shop[shopIdx].qty -= entry->addAmount;
+ }
+
+ npc->expanded_barter_tosql(nd, shopIdx);
+
+ if (itemdb_type(entry->addId) == IT_PETEGG) {
+ pet->create_egg(sd, entry->addId);
+ } else {
+ struct item item_tmp;
+ memset(&item_tmp, 0, sizeof(item_tmp));
+ item_tmp.nameid = entry->addId;
+ item_tmp.identify = 1;
+ pc->additem(sd, &item_tmp, entry->addAmount, LOG_TYPE_NPC);
+ }
+ }
+
+ return 12;
+}
+
/// npc_selllist for script-controlled shops
static int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, struct npc_data *nd)
{
@@ -2601,121 +2935,193 @@ static int npc_unload_ev_label(union DBKey key, struct DBData *data, va_list ap)
return 0;
}
-//Chk if npc matches src_id, then unload.
-//Sub-function used to find duplicates.
+/**
+ * Unloads a NPC if it's a duplicate of the passed one.
+ *
+ * @param nd The NPC to check.
+ * @param args List of arguments.
+ * @return Always 0.
+ *
+ **/
static int npc_unload_dup_sub(struct npc_data *nd, va_list args)
{
- int src_id;
-
nullpo_ret(nd);
- src_id = va_arg(args, int);
+
+ const int src_id = va_arg(args, int);
+ const int unload_mobs = va_arg(args, int);
+
if (nd->src_id == src_id)
- npc->unload(nd, true);
+ npc->unload(nd, true, (unload_mobs == 1));
+
return 0;
}
-//Removes all npcs that are duplicates of the passed one. [Skotlex]
-static void npc_unload_duplicates(struct npc_data *nd)
+/**
+ * Unloads all NPCs which are duplicates of the passed one.
+ *
+ * @param nd The source NPC.
+ * @param unload_mobs If true, mobs spawned by duplicates will be removed.
+ *
+ * @author Skotlex
+ *
+ **/
+static void npc_unload_duplicates(struct npc_data *nd, bool unload_mobs)
{
nullpo_retv(nd);
- map->foreachnpc(npc->unload_dup_sub,nd->bl.id);
+
+ map->foreachnpc(npc->unload_dup_sub, nd->bl.id, unload_mobs);
}
-//Removes an npc from map and db.
-//Single is to free name (for duplicates).
-static int npc_unload(struct npc_data *nd, bool single)
+/**
+ * Removes a mob, which was spawned by a NPC (monster/areamonster/guardian/bg_monster/atcommand("@monster xy")).
+ *
+ * @param md The mob to remove.
+ * @param args List of arguments.
+ * @return 1 on success, 0 on failure.
+ *
+ * @author Kenpachi
+ *
+ **/
+static int npc_unload_mob(struct mob_data *md, va_list args)
+{
+ nullpo_ret(md);
+
+ const int npc_id = va_arg(args, int);
+
+ if (md->npc_id == npc_id) {
+ md->state.npc_killmonster = 1;
+ status_kill(&md->bl);
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Removes a NPC from map and database.
+ *
+ * @param nd The NPC which should be removed.
+ * @param single If true, names are freed. (For duplicates.)
+ * @param unload_mobs If true, mobs spawned by the NPC will be removed.
+ * @return Always 0.
+ *
+ **/
+static int npc_unload(struct npc_data *nd, bool single, bool unload_mobs)
{
nullpo_ret(nd);
- if( nd->ud && nd->ud != &npc->base_ud ) {
+ if (nd->ud != NULL && nd->ud != &npc->base_ud)
skill->clear_unitgroup(&nd->bl);
- }
npc->remove_map(nd);
map->deliddb(&nd->bl);
- if( single )
+
+ if (single)
strdb_remove(npc->name_db, nd->exname);
- if (nd->chat_id) // remove npc chatroom object and kick users
+ if (nd->chat_id != 0) /// Remove NPC chatroom object and kick users.
chat->delete_npc_chat(nd);
- npc_chat->finalize(nd); // deallocate npc PCRE data structures
+ npc_chat->finalize(nd); /// Deallocate NPC PCRE data structures.
if (single && nd->path != NULL) {
npc->releasepathreference(nd->path);
nd->path = NULL;
}
- if (single && nd->bl.m != -1)
+ if (single && nd->bl.m != INDEX_NOT_FOUND)
map->remove_questinfo(nd->bl.m, nd);
+
npc->questinfo_clear(nd);
- if (nd->src_id == 0 && ( nd->subtype == SHOP || nd->subtype == CASHSHOP)) {
- //src check for duplicate shops [Orcao]
- aFree(nd->u.shop.shop_item);
+ if (nd->src_id == 0 && (nd->subtype == SHOP || nd->subtype == CASHSHOP)) {
+ aFree(nd->u.shop.shop_item); /// src check for duplicate shops. [Orcao]
} else if (nd->subtype == SCRIPT) {
- struct s_mapiterator *iter;
- struct map_session_data *sd = NULL;
+ char evname[EVENT_NAME_LENGTH];
+
+ snprintf(evname, ARRAYLENGTH(evname), "%s::OnNPCUnload", nd->exname);
+
+ struct event_data *ev = strdb_get(npc->ev_db, evname);
+
+ if (ev != NULL)
+ script->run_npc(nd->u.scr.script, ev->pos, 0, nd->bl.id); /// Run OnNPCUnload.
- if( single ) {
- npc->ev_db->foreach(npc->ev_db,npc->unload_ev,nd->exname); //Clean up all events related
- npc->ev_label_db->foreach(npc->ev_label_db,npc->unload_ev_label,nd);
+ if (single) {
+ npc->ev_db->foreach(npc->ev_db, npc->unload_ev, nd->exname); /// Clean up all related events.
+ npc->ev_label_db->foreach(npc->ev_label_db, npc->unload_ev_label, nd);
}
- iter = mapit_geteachpc();
- for (sd = BL_UCAST(BL_PC, mapit->first(iter)); mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) {
- if (sd->npc_timer_id != INVALID_TIMER ) {
+ struct s_mapiterator *iter = mapit_geteachpc();
+ struct map_session_data *sd = BL_UCAST(BL_PC, mapit->first(iter));
+
+ for (; mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) {
+ if (sd->npc_timer_id != INVALID_TIMER) {
const struct TimerData *td = timer->get(sd->npc_timer_id);
- if( td && td->id != nd->bl.id )
+ if (td != NULL && td->id != nd->bl.id)
continue;
- if( td && td->data )
+ if (td != NULL && td->data != 0)
ers_free(npc->timer_event_ers, (void*)td->data);
+
timer->delete(sd->npc_timer_id, npc->timerevent);
sd->npc_timer_id = INVALID_TIMER;
}
}
+
mapit->free(iter);
if (nd->u.scr.timerid != INVALID_TIMER) {
- const struct TimerData *td;
- td = timer->get(nd->u.scr.timerid);
- if (td && td->data)
+ const struct TimerData *td = timer->get(nd->u.scr.timerid);
+
+ if (td != NULL && td->data != 0)
ers_free(npc->timer_event_ers, (void*)td->data);
+
timer->delete(nd->u.scr.timerid, npc->timerevent);
}
- if (nd->u.scr.timer_event)
+
+ if (nd->u.scr.timer_event != NULL)
aFree(nd->u.scr.timer_event);
+
if (nd->src_id == 0) {
- if(nd->u.scr.script) {
+ if (nd->u.scr.script != NULL) {
script->free_code(nd->u.scr.script);
nd->u.scr.script = NULL;
}
- if (nd->u.scr.label_list) {
+
+ if (nd->u.scr.label_list != NULL) {
aFree(nd->u.scr.label_list);
nd->u.scr.label_list = NULL;
nd->u.scr.label_list_num = 0;
}
- if(nd->u.scr.shop) {
- if(nd->u.scr.shop->item)
+
+ if (nd->u.scr.shop != NULL) {
+ if (nd->u.scr.shop->item != NULL) {
+ for (int i = 0; i < nd->u.scr.shop->items; i ++) {
+ if (nd->u.scr.shop->item[i].currency != NULL)
+ aFree(nd->u.scr.shop->item[i].currency);
+ }
aFree(nd->u.scr.shop->item);
+ }
+
aFree(nd->u.scr.shop);
}
}
- if( nd->u.scr.guild_id )
+
+ if (nd->u.scr.guild_id > 0)
guild->flag_remove(nd);
}
- if( nd->ud && nd->ud != &npc->base_ud ) {
+ if (nd->ud != NULL && nd->ud != &npc->base_ud) {
aFree(nd->ud);
nd->ud = NULL;
}
- HPM->data_store_destroy(&nd->hdata);
+ if (unload_mobs)
+ map->foreachmob(npc->unload_mob, nd->bl.id);
+ HPM->data_store_destroy(&nd->hdata);
aFree(nd);
-
return 0;
}
@@ -4092,18 +4498,23 @@ static const char *npc_parse_function(const char *w1, const char *w2, const char
return end;
}
-/*==========================================
- * Parse Mob 1 - Parse mob list into each map
- * Parse Mob 2 - Actually Spawns Mob
- * [Wizputer]
- *------------------------------------------*/
+/**
+ * Spawns a mob by using the passed spawn data. (Permanent mob spawns.)
+ * npc_parse_mob() - Parses mob list into each map.
+ * npc_parse_mob2() - Actually spawns mob.
+ *
+ * @param mobspawn The mobs spawn data.
+ *
+ * @author Wizputer
+ *
+ **/
static void npc_parse_mob2(struct spawn_data *mobspawn)
{
- int i;
-
nullpo_retv(mobspawn);
- for( i = mobspawn->active; i < mobspawn->num; ++i ) {
- struct mob_data* md = mob->spawn_dataset(mobspawn);
+
+ for (int i = mobspawn->active; i < mobspawn->num; ++i) {
+ struct mob_data *md = mob->spawn_dataset(mobspawn, 0);
+
md->spawn = mobspawn;
md->spawn->active++;
mob->spawn(md);
@@ -5160,128 +5571,143 @@ static void npc_process_files(int npc_min)
npc->npc_id - npc_min, npc->npc_warp, npc->npc_shop, npc->npc_script, npc->npc_mob, npc->npc_cache_mob, npc->npc_delay_mob);
}
-//Clear then reload npcs files
+/**
+ * Clears and then reloads all NPC files.
+ *
+ * @return Always 0.
+ *
+ **/
static int npc_reload(void)
{
- int npc_new_min = npc->npc_id;
- struct s_mapiterator* iter;
- struct block_list* bl;
-
- if (map->retval == EXIT_FAILURE)
- map->retval = EXIT_SUCCESS; // Clear return status in case something failed before.
-
- /* clear guild flag cache */
- guild->flags_clear();
+ if (map->retval == EXIT_FAILURE) /// Clear return status in case something failed before.
+ map->retval = EXIT_SUCCESS;
+ guild->flags_clear(); /// Clear guild flag cache.
npc->path_db->clear(npc->path_db, npc->path_db_clear_sub);
-
db_clear(npc->name_db);
db_clear(npc->ev_db);
npc->ev_label_db->clear(npc->ev_label_db, npc->ev_label_db_clear_sub);
-
npc->npc_last_npd = NULL;
npc->npc_last_path = NULL;
npc->npc_last_ref = NULL;
+
+ const int npc_new_min = npc->npc_id;
+ struct s_mapiterator *iter = mapit_geteachiddb();
- //Remove all npcs/mobs. [Skotlex]
- iter = mapit_geteachiddb();
- for (bl = mapit->first(iter); mapit->exists(iter); bl = mapit->next(iter)) {
- switch(bl->type) {
- case BL_NPC:
- if( bl->id != npc->fake_nd->bl.id )// don't remove fake_nd
- npc->unload(BL_UCAST(BL_NPC, bl), false);
- break;
- case BL_MOB:
- unit->free(bl,CLR_OUTSIGHT);
- break;
+ /** Remove all NPCs/mobs. [Skotlex] **/
+ for (struct block_list *bl = mapit->first(iter); mapit->exists(iter); bl = mapit->next(iter)) {
+ switch (bl->type) {
+ case BL_NPC:
+ if (bl->id != npc->fake_nd->bl.id) /// Don't remove fake_nd.
+ npc->unload(BL_UCAST(BL_NPC, bl), false, false);
+
+ break;
+ case BL_MOB:
+ unit->free(bl, CLR_OUTSIGHT);
+ break;
+ default:
+ break;
}
}
+
mapit->free(iter);
- if(battle_config.dynamic_mobs) {// dynamic check by [random]
- int16 m;
- for (m = 0; m < map->count; m++) {
- int16 i;
- for (i = 0; i < MAX_MOB_LIST_PER_MAP; i++) {
+ if (battle_config.dynamic_mobs) { /// Dynamic check. [random]
+ for (int m = 0; m < map->count; m++) {
+ for (int i = 0; i < MAX_MOB_LIST_PER_MAP; i++) {
if (map->list[m].moblist[i] != NULL) {
aFree(map->list[m].moblist[i]);
map->list[m].moblist[i] = NULL;
}
- if( map->list[m].mob_delete_timer != INVALID_TIMER )
- { // Mobs were removed anyway,so delete the timer [Inkfish]
+
+ if (map->list[m].mob_delete_timer != INVALID_TIMER) { /// Mobs were removed anyway, so delete the timer. [Inkfish]
timer->delete(map->list[m].mob_delete_timer, map->removemobs_timer);
map->list[m].mob_delete_timer = INVALID_TIMER;
}
}
+
if (map->list[m].npc_num > 0)
- ShowWarning("npc_reload: %d npcs weren't removed at map %s!\n", map->list[m].npc_num, map->list[m].name);
+ ShowWarning("npc_reload: %d NPCs weren't removed from map %s!\n",
+ map->list[m].npc_num, map->list[m].name);
}
}
- // clear mob spawn lookup index
mob->clear_spawninfo();
-
- npc->npc_warp = npc->npc_shop = npc->npc_script = 0;
- npc->npc_mob = npc->npc_cache_mob = npc->npc_delay_mob = 0;
-
- // reset mapflags
+ npc->npc_warp = 0;
+ npc->npc_shop = 0;
+ npc->npc_script = 0;
+ npc->npc_mob = 0;
+ npc->npc_cache_mob = 0;
+ npc->npc_delay_mob = 0;
map->zone_reload();
map->flags_init();
-
- // Reprocess npc files and reload constants
itemdb->name_constants();
clan->set_constants();
- npc_process_files( npc_new_min );
-
+ npc_process_files(npc_new_min);
instance->reload();
-
map->zone_init();
-
- npc->motd = npc->name2id("HerculesMOTD"); /* [Ind/Hercules] */
-
- //Re-read the NPC Script Events cache.
+ npc->motd = npc->name2id("HerculesMOTD"); /// [Ind/Hercules]
npc->read_event_script();
- // Execute main initialisation events
- // The correct initialisation order is:
- // OnInit -> OnInterIfInit -> OnInterIfInitOnce -> OnAgitInit -> OnAgitInit2
- npc->event_do_oninit( true );
+ /**
+ * Execute main initialization events
+ * The correct initialization order is:
+ * OnInit -> OnInterIfInit -> OnInterIfInitOnce -> OnAgitInit -> OnAgitInit2
+ *
+ **/
+ npc->event_do_oninit(true);
+
npc->market_fromsql();
npc->barter_fromsql();
- // Execute rest of the startup events if connected to char-server. [Lance]
- // Executed when connection is established with char-server in chrif_connectack
- if( !intif->CheckForCharServer() ) {
- ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc->event_doall("OnInterIfInit"));
- ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc->event_doall("OnInterIfInitOnce"));
- }
- // Refresh guild castle flags on both woe setups
- // These events are only executed after receiving castle information from char-server
+ npc->expanded_barter_fromsql();
+
+ /*
+ * Execute rest of the startup events if connected to char-server. [Lance]
+ * Executed when connection is established with char-server in chrif_connectack().
+ */
+ if (intif->CheckForCharServer() == 0) {
+ ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n",
+ npc->event_doall("OnInterIfInit"));
+ ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n",
+ npc->event_doall("OnInterIfInitOnce"));
+ }
+
+ /*
+ * Refresh guild castle flags on both WoE setups.
+ * These events are only executed after receiving castle information from char-server.
+ */
npc->event_doall("OnAgitInit");
npc->event_doall("OnAgitInit2");
return 0;
}
-//Unload all npc in the given file
-static bool npc_unloadfile(const char *filepath)
+/**
+ * Unloads all NPCs in the given file.
+ *
+ * @param filepath Path to the file which should be unloaded.
+ * @param unload_mobs If true, mobs spawned by NPCs in the file will be removed.
+ * @return true if at least one NPC was unloaded, otherwise false.
+ *
+ **/
+static bool npc_unloadfile(const char *filepath, bool unload_mobs)
{
+ nullpo_retr(false, filepath);
+
struct DBIterator *iter = db_iterator(npc->name_db);
- struct npc_data* nd = NULL;
bool found = false;
- nullpo_retr(false, filepath);
-
- for( nd = dbi_first(iter); dbi_exists(iter); nd = dbi_next(iter) ) {
- if( nd->path && strcasecmp(nd->path,filepath) == 0 ) { // FIXME: This can break in case-sensitive file systems
+ for (struct npc_data *nd = dbi_first(iter); dbi_exists(iter); nd = dbi_next(iter)) {
+ if (nd->path != NULL && strcasecmp(nd->path, filepath) == 0) { // FIXME: This can break in case-sensitive file systems.
found = true;
- npc->unload_duplicates(nd);/* unload any npcs which could duplicate this but be in a different file */
- npc->unload(nd, true);
+ npc->unload_duplicates(nd, unload_mobs); /// Unload any NPC which could duplicate this but be in a different file.
+ npc->unload(nd, true, unload_mobs);
}
}
dbi_destroy(iter);
- if( found ) /* refresh event cache */
+ if (found) /// Refresh event cache.
npc->read_event_script();
return found;
@@ -5514,6 +5940,7 @@ void npc_defaults(void)
npc->unload_ev_label = npc_unload_ev_label;
npc->unload_dup_sub = npc_unload_dup_sub;
npc->unload_duplicates = npc_unload_duplicates;
+ npc->unload_mob = npc_unload_mob;
npc->unload = npc_unload;
npc->clearsrcfile = npc_clearsrcfile;
npc->addsrcfile = npc_addsrcfile;
@@ -5567,6 +5994,7 @@ void npc_defaults(void)
npc->trader_update = npc_trader_update;
npc->market_buylist = npc_market_buylist;
npc->barter_buylist = npc_barter_buylist;
+ npc->expanded_barter_buylist = npc_expanded_barter_buylist;
npc->trader_open = npc_trader_open;
npc->market_fromsql = npc_market_fromsql;
npc->market_tosql = npc_market_tosql;
@@ -5576,6 +6004,10 @@ void npc_defaults(void)
npc->barter_tosql = npc_barter_tosql;
npc->barter_delfromsql = npc_barter_delfromsql;
npc->barter_delfromsql_sub = npc_barter_delfromsql_sub;
+ npc->expanded_barter_fromsql = npc_expanded_barter_fromsql;
+ npc->expanded_barter_tosql = npc_expanded_barter_tosql;
+ npc->expanded_barter_delfromsql = npc_expanded_barter_delfromsql;
+ npc->expanded_barter_delfromsql_sub = npc_expanded_barter_delfromsql_sub;
npc->db_checkid = npc_db_checkid;
npc->refresh = npc_refresh;
npc->questinfo_clear = npc_questinfo_clear;