/* * Copyright (C) 2004-2015 L2J Server * * This file is part of L2J Server. * * L2J Server 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. * * L2J Server 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 . */ package com.l2jserver.gameserver.instancemanager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.time.Duration; import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ScheduledFuture; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import com.l2jserver.commons.database.pool.impl.ConnectionFactory; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.enums.ItemLocation; import com.l2jserver.gameserver.enums.MailType; import com.l2jserver.gameserver.model.actor.L2Npc; import com.l2jserver.gameserver.model.actor.instance.CommissionManagerInstance; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.commission.CommissionItem; import com.l2jserver.gameserver.model.entity.Message; import com.l2jserver.gameserver.model.itemcontainer.Inventory; import com.l2jserver.gameserver.model.itemcontainer.Mail; import com.l2jserver.gameserver.model.items.L2Item; import com.l2jserver.gameserver.model.items.instance.L2ItemInstance; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.commission.ExResponseCommissionBuyItem; import com.l2jserver.gameserver.network.serverpackets.commission.ExResponseCommissionDelete; import com.l2jserver.gameserver.network.serverpackets.commission.ExResponseCommissionInfo; import com.l2jserver.gameserver.network.serverpackets.commission.ExResponseCommissionList; import com.l2jserver.gameserver.network.serverpackets.commission.ExResponseCommissionList.CommissionListReplyType; import com.l2jserver.gameserver.network.serverpackets.commission.ExResponseCommissionRegister; /** * @author NosBit */ public final class CommissionManager { private static final Logger _log = Logger.getLogger(CommissionManager.class.getName()); private static final int INTERACTION_DISTANCE = 250; private static final int ITEMS_LIMIT_PER_REQUEST = 999; private static final int MAX_ITEMS_REGISTRED_PER_PLAYER = 10; private static final long MIN_REGISTRATION_AND_SALE_FEE = 1000; private static final double REGISTRATION_FEE_PER_DAY = 0.001; private static final double SALE_FEE_PER_DAY = 0.005; private static final String SELECT_ALL_ITEMS = "SELECT * FROM `items` WHERE `loc` = ?"; private static final String SELECT_ALL_COMMISSION_ITEMS = "SELECT * FROM `commission_items`"; private static final String INSERT_COMMISSION_ITEM = "INSERT INTO `commission_items`(`item_object_id`, `price_per_unit`, `start_time`, `duration_in_days`) VALUES (?, ?, ?, ?)"; private static final String DELETE_COMMISSION_ITEM = "DELETE FROM `commission_items` WHERE `commission_id` = ?"; private final Map _commissionItems = new ConcurrentSkipListMap<>(); protected CommissionManager() { final Map itemInstances = new HashMap<>(); try (Connection con = ConnectionFactory.getInstance().getConnection()) { try (PreparedStatement ps = con.prepareStatement(SELECT_ALL_ITEMS)) { ps.setString(1, ItemLocation.COMMISSION.toString()); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { final int itemOwnerId = rs.getInt("owner_id"); final int itemObjectId = rs.getInt("object_id"); final L2ItemInstance itemInstance = L2ItemInstance.restoreFromDb(itemOwnerId, rs); if (itemInstance == null) { _log.warning(getClass().getSimpleName() + ": Failed loading item instance with item object id " + itemObjectId + " and owner id " + itemOwnerId + "."); continue; } itemInstances.put(itemObjectId, itemInstance); } } } try (Statement st = con.createStatement(); ResultSet rs = st.executeQuery(SELECT_ALL_COMMISSION_ITEMS)) { while (rs.next()) { final long commissionId = rs.getLong("commission_id"); final L2ItemInstance itemInstance = itemInstances.get(rs.getInt("item_object_id")); if (itemInstance == null) { _log.warning(getClass().getSimpleName() + ": Failed loading commission item with commission id " + commissionId + " because item instance does not exist or failed to load."); continue; } final CommissionItem commissionItem = new CommissionItem(commissionId, itemInstance, rs.getLong("price_per_unit"), rs.getTimestamp("start_time").toInstant(), rs.getByte("duration_in_days")); _commissionItems.put(commissionItem.getCommissionId(), commissionItem); if (commissionItem.getEndTime().isBefore(Instant.now())) { expireSale(commissionItem); } else { commissionItem.setSaleEndTask(ThreadPoolManager.getInstance().scheduleGeneral(() -> expireSale(commissionItem), Duration.between(Instant.now(), commissionItem.getEndTime()).toMillis())); } } } } catch (SQLException e) { _log.log(Level.WARNING, getClass().getSimpleName() + ": Failed loading commission items.", e); } } /** * Shows the player the auctions filtered by filter. * @param player the player * @param filter the filter */ public void showAuctions(L2PcInstance player, Predicate filter) { //@formatter:off final List commissionItems = _commissionItems.values().stream() .filter(c -> filter.test(c.getItemInfo().getItem())) .limit(ITEMS_LIMIT_PER_REQUEST) .collect(Collectors.toList()); //@formatter:on if (commissionItems.isEmpty()) { player.sendPacket(new ExResponseCommissionList(CommissionListReplyType.ITEM_DOES_NOT_EXIST)); return; } int chunks = commissionItems.size() / ExResponseCommissionList.MAX_CHUNK_SIZE; if (commissionItems.size() > (chunks * ExResponseCommissionList.MAX_CHUNK_SIZE)) { chunks++; } for (int i = chunks - 1; i >= 0; i--) { player.sendPacket(new ExResponseCommissionList(CommissionListReplyType.AUCTIONS, commissionItems, i, i * ExResponseCommissionList.MAX_CHUNK_SIZE)); } } /** * Shows the player his auctions. * @param player the player */ public void showPlayerAuctions(L2PcInstance player) { //@formatter:off final List commissionItems = _commissionItems.values().stream() .filter(c -> c.getItemInstance().getOwnerId() == player.getObjectId()) .limit(MAX_ITEMS_REGISTRED_PER_PLAYER) .collect(Collectors.toList()); //@formatter:on if (!commissionItems.isEmpty()) { player.sendPacket(new ExResponseCommissionList(CommissionListReplyType.PLAYER_AUCTIONS, commissionItems)); } else { player.sendPacket(new ExResponseCommissionList(CommissionListReplyType.PLAYER_AUCTIONS_EMPTY)); } } /** * Registers an item for the given player. * @param player the player * @param itemObjectId the item object id * @param itemCount the item count * @param pricePerUnit the price per unit * @param durationInDays the duration in days */ public void registerItem(L2PcInstance player, int itemObjectId, long itemCount, long pricePerUnit, byte durationInDays) { if (itemCount < 1) { player.sendPacket(SystemMessageId.THE_ITEM_HAS_FAILED_TO_BE_REGISTERED); player.sendPacket(ExResponseCommissionRegister.FAILED); return; } final long totalPrice = itemCount * pricePerUnit; if (totalPrice <= MIN_REGISTRATION_AND_SALE_FEE) { player.sendPacket(SystemMessageId.THE_ITEM_CANNOT_BE_REGISTERED_BECAUSE_REQUIREMENTS_ARE_NOT_MET); player.sendPacket(ExResponseCommissionRegister.FAILED); return; } L2ItemInstance itemInstance = player.getInventory().getItemByObjectId(itemObjectId); if ((itemInstance == null) || !itemInstance.isAvailable(player, false, false) || (itemInstance.getCount() < itemCount)) { player.sendPacket(SystemMessageId.THE_ITEM_HAS_FAILED_TO_BE_REGISTERED); player.sendPacket(ExResponseCommissionRegister.FAILED); return; } synchronized (this) { //@formatter:off final long playerRegisteredItems = _commissionItems.values().stream() .filter(c -> c.getItemInstance().getOwnerId() == player.getObjectId()) .count(); //@formatter:on if (playerRegisteredItems >= MAX_ITEMS_REGISTRED_PER_PLAYER) { player.sendPacket(SystemMessageId.THE_ITEM_HAS_FAILED_TO_BE_REGISTERED); player.sendPacket(ExResponseCommissionRegister.FAILED); return; } final long registrationFee = (long) Math.max(MIN_REGISTRATION_AND_SALE_FEE, (totalPrice * REGISTRATION_FEE_PER_DAY) * durationInDays); if (!player.getInventory().reduceAdena("Commission Registration Fee", registrationFee, player, null)) { player.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA_TO_REGISTER_THE_ITEM); player.sendPacket(ExResponseCommissionRegister.FAILED); return; } itemInstance = player.getInventory().detachItem("Commission Registration", itemInstance, itemCount, ItemLocation.COMMISSION, player, null); if (itemInstance == null) { player.getInventory().addAdena("Commission error refund", registrationFee, player, null); player.sendPacket(SystemMessageId.THE_ITEM_HAS_FAILED_TO_BE_REGISTERED); player.sendPacket(ExResponseCommissionRegister.FAILED); return; } try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement ps = con.prepareStatement(INSERT_COMMISSION_ITEM, Statement.RETURN_GENERATED_KEYS)) { final Instant startTime = Instant.now(); ps.setInt(1, itemInstance.getObjectId()); ps.setLong(2, pricePerUnit); ps.setTimestamp(3, Timestamp.from(startTime)); ps.setByte(4, durationInDays); ps.executeUpdate(); try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) { final CommissionItem commissionItem = new CommissionItem(rs.getLong(1), itemInstance, pricePerUnit, startTime, durationInDays); final ScheduledFuture saleEndTask = ThreadPoolManager.getInstance().scheduleGeneral(() -> expireSale(commissionItem), Duration.between(Instant.now(), commissionItem.getEndTime()).toMillis()); commissionItem.setSaleEndTask(saleEndTask); _commissionItems.put(commissionItem.getCommissionId(), commissionItem); player.getLastCommissionInfos().put(itemInstance.getId(), new ExResponseCommissionInfo(itemInstance.getId(), pricePerUnit, itemCount, (byte) ((durationInDays - 1) / 2))); player.sendPacket(SystemMessageId.THE_ITEM_HAS_BEEN_SUCCESSFULLY_REGISTERED); player.sendPacket(ExResponseCommissionRegister.SUCCEED); } } } catch (SQLException e) { _log.log(Level.WARNING, getClass().getSimpleName() + ": Failed inserting commission item. ItemInstance: " + itemInstance, e); player.sendPacket(SystemMessageId.THE_ITEM_HAS_FAILED_TO_BE_REGISTERED); player.sendPacket(ExResponseCommissionRegister.FAILED); } } } /** * Deletes an item and returns it to the player. * @param player the player * @param commissionId the commission id */ public void deleteItem(L2PcInstance player, long commissionId) { final CommissionItem commissionItem = getCommissionItem(commissionId); if (commissionItem == null) { player.sendPacket(SystemMessageId.CANCELLATION_OF_SALE_HAS_FAILED_BECAUSE_REQUIREMENTS_ARE_NOT_MET); player.sendPacket(ExResponseCommissionDelete.FAILED); return; } if (commissionItem.getItemInstance().getOwnerId() != player.getObjectId()) { player.sendPacket(ExResponseCommissionDelete.FAILED); return; } if ((player.getInventory().getSize(false) >= (player.getInventoryLimit() * 0.8)) || (player.getWeightPenalty() >= 3)) { player.sendPacket(SystemMessageId.IF_THE_WEIGHT_IS_80_OR_MORE_AND_THE_INVENTORY_NUMBER_IS_90_OR_MORE_PURCHASE_CANCELLATION_IS_NOT_POSSIBLE); player.sendPacket(SystemMessageId.CANCELLATION_OF_SALE_HAS_FAILED_BECAUSE_REQUIREMENTS_ARE_NOT_MET); player.sendPacket(ExResponseCommissionDelete.FAILED); return; } if ((_commissionItems.remove(commissionId) == null) || !commissionItem.getSaleEndTask().cancel(false)) { player.sendPacket(SystemMessageId.CANCELLATION_OF_SALE_HAS_FAILED_BECAUSE_REQUIREMENTS_ARE_NOT_MET); player.sendPacket(ExResponseCommissionDelete.FAILED); return; } if (deleteItemFromDB(commissionId)) { player.getInventory().addItem("Commission Cancellation", commissionItem.getItemInstance(), player, null); player.sendPacket(SystemMessageId.CANCELLATION_OF_SALE_FOR_THE_ITEM_IS_SUCCESSFUL); player.sendPacket(ExResponseCommissionDelete.SUCCEED); } else { player.sendPacket(SystemMessageId.CANCELLATION_OF_SALE_HAS_FAILED_BECAUSE_REQUIREMENTS_ARE_NOT_MET); player.sendPacket(ExResponseCommissionDelete.FAILED); } } /** * Buys the item for the given player. * @param player the player * @param commissionId the commission id */ public void buyItem(L2PcInstance player, long commissionId) { final CommissionItem commissionItem = getCommissionItem(commissionId); if (commissionItem == null) { player.sendPacket(SystemMessageId.ITEM_PURCHASE_HAS_FAILED); player.sendPacket(ExResponseCommissionBuyItem.FAILED); return; } final L2ItemInstance itemInstance = commissionItem.getItemInstance(); if (itemInstance.getOwnerId() == player.getObjectId()) { player.sendPacket(SystemMessageId.ITEM_PURCHASE_HAS_FAILED); player.sendPacket(ExResponseCommissionBuyItem.FAILED); return; } if ((player.getInventory().getSize(false) >= (player.getInventoryLimit() * 0.8)) || (player.getWeightPenalty() >= 3)) { player.sendPacket(SystemMessageId.IF_THE_WEIGHT_IS_80_OR_MORE_AND_THE_INVENTORY_NUMBER_IS_90_OR_MORE_PURCHASE_CANCELLATION_IS_NOT_POSSIBLE); player.sendPacket(ExResponseCommissionBuyItem.FAILED); return; } final long totalPrice = itemInstance.getCount() * commissionItem.getPricePerUnit(); if (!player.getInventory().reduceAdena("Commission Registration Fee", totalPrice, player, null)) { player.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA); player.sendPacket(ExResponseCommissionBuyItem.FAILED); return; } if ((_commissionItems.remove(commissionId) == null) || !commissionItem.getSaleEndTask().cancel(false)) { player.getInventory().addAdena("Commission error refund", totalPrice, player, null); player.sendPacket(SystemMessageId.ITEM_PURCHASE_HAS_FAILED); player.sendPacket(ExResponseCommissionBuyItem.FAILED); return; } if (deleteItemFromDB(commissionId)) { final long saleFee = (long) Math.max(MIN_REGISTRATION_AND_SALE_FEE, (totalPrice * SALE_FEE_PER_DAY) * commissionItem.getDurationInDays()); final Message mail = new Message(itemInstance.getOwnerId(), itemInstance, MailType.COMMISSION_ITEM_SOLD); final Mail attachement = mail.createAttachments(); attachement.addItem("Commission Item Sold", Inventory.ADENA_ID, totalPrice - saleFee, player, null); MailManager.getInstance().sendMessage(mail); player.sendPacket(new ExResponseCommissionBuyItem(commissionItem)); player.getInventory().addItem("Commission Buy Item", commissionItem.getItemInstance(), player, null); } else { player.getInventory().addAdena("Commission error refund", totalPrice, player, null); player.sendPacket(ExResponseCommissionBuyItem.FAILED); } } /** * Deletes a commission item from database. * @param commissionId the commission item * @return {@code true} if the item was deleted successfully, {@code false} otherwise */ private boolean deleteItemFromDB(long commissionId) { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement ps = con.prepareStatement(DELETE_COMMISSION_ITEM)) { ps.setLong(1, commissionId); if (ps.executeUpdate() > 0) { return true; } } catch (SQLException e) { _log.log(Level.WARNING, getClass().getSimpleName() + ": Failed deleting commission item. Commission ID: " + commissionId, e); } return false; } /** * Expires the sale of a commission item and sends the item back to the player. * @param commissionItem the comission item */ private void expireSale(CommissionItem commissionItem) { if ((_commissionItems.remove(commissionItem.getCommissionId()) != null) && deleteItemFromDB(commissionItem.getCommissionId())) { final Message mail = new Message(commissionItem.getItemInstance().getOwnerId(), commissionItem.getItemInstance(), MailType.COMMISSION_ITEM_RETURNED); MailManager.getInstance().sendMessage(mail); } } /** * Gets the commission item. * @param commissionId the commission id to get * @return the commission item if it exists, {@code null} otherwise */ public CommissionItem getCommissionItem(long commissionId) { return _commissionItems.get(commissionId); } /** * Checks if the player is allowed to interact with commission manager. * @param player the player * @return {@code true} if the player is allowed to interact, {@code false} otherwise */ public static boolean isPlayerAllowedToInteract(L2PcInstance player) { final L2Npc npc = player.getLastFolkNPC(); if ((npc != null) && (npc instanceof CommissionManagerInstance)) { return npc.calculateDistance(player, true, false) <= INTERACTION_DISTANCE; } return false; } /** * Gets the single instance. * @return the single instance */ public static CommissionManager getInstance() { return SingletonHolder._instance; } private static class SingletonHolder { protected static final CommissionManager _instance = new CommissionManager(); } }