/* * 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.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import com.l2jserver.Config; import com.l2jserver.commons.database.pool.impl.ConnectionFactory; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.enums.ManorMode; import com.l2jserver.gameserver.model.CropProcure; import com.l2jserver.gameserver.model.L2Clan; import com.l2jserver.gameserver.model.L2ClanMember; import com.l2jserver.gameserver.model.L2Seed; import com.l2jserver.gameserver.model.SeedProduction; import com.l2jserver.gameserver.model.StatsSet; import com.l2jserver.gameserver.model.entity.Castle; import com.l2jserver.gameserver.model.interfaces.IStorable; import com.l2jserver.gameserver.model.itemcontainer.ItemContainer; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.util.Rnd; import com.l2jserver.util.data.xml.IXmlReader; /** * Castle manor system. * @author malyelfik */ public final class CastleManorManager implements IXmlReader, IStorable { // SQL queries private static final String INSERT_PRODUCT = "INSERT INTO castle_manor_production VALUES (?, ?, ?, ?, ?, ?)"; private static final String INSERT_CROP = "INSERT INTO castle_manor_procure VALUES (?, ?, ?, ?, ?, ?, ?)"; // Current manor status private ManorMode _mode = ManorMode.APPROVED; // Temporary date private Calendar _nextModeChange = null; // Seeds holder private static final Map _seeds = new HashMap<>(); // Manor period settings private final Map> _procure = new HashMap<>(); private final Map> _procureNext = new HashMap<>(); private final Map> _production = new HashMap<>(); private final Map> _productionNext = new HashMap<>(); public CastleManorManager() { if (Config.ALLOW_MANOR) { load(); // Load seed data (XML) loadDb(); // Load castle manor data (DB) // Set mode and start timer final Calendar currentTime = Calendar.getInstance(); final int hour = currentTime.get(Calendar.HOUR_OF_DAY); final int min = currentTime.get(Calendar.MINUTE); final int maintenanceMin = Config.ALT_MANOR_REFRESH_MIN + Config.ALT_MANOR_MAINTENANCE_MIN; if (((hour >= Config.ALT_MANOR_REFRESH_TIME) && (min >= maintenanceMin)) || (hour < Config.ALT_MANOR_APPROVE_TIME) || ((hour == Config.ALT_MANOR_APPROVE_TIME) && (min <= Config.ALT_MANOR_APPROVE_MIN))) { _mode = ManorMode.MODIFIABLE; } else if ((hour == Config.ALT_MANOR_REFRESH_TIME) && ((min >= Config.ALT_MANOR_REFRESH_MIN) && (min < maintenanceMin))) { _mode = ManorMode.MAINTENANCE; } // Schedule mode change scheduleModeChange(); // Schedule autosave if (!Config.ALT_MANOR_SAVE_ALL_ACTIONS) { ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(this::storeMe, Config.ALT_MANOR_SAVE_PERIOD_RATE, Config.ALT_MANOR_SAVE_PERIOD_RATE, TimeUnit.HOURS); } // Send debug message if (Config.DEBUG) { LOGGER.info("{}: Current mode {}", getClass().getSimpleName(), _mode.toString()); } } else { _mode = ManorMode.DISABLED; LOGGER.info("{}: Manor system is deactivated.", getClass().getSimpleName()); } } @Override public final void load() { parseDatapackFile("data/seeds.xml"); LOGGER.info("{}: Loaded {} seeds.", getClass().getSimpleName(), _seeds.size()); } @Override public final void parseDocument(Document doc) { StatsSet set; NamedNodeMap attrs; Node att; for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling()) { if ("list".equalsIgnoreCase(n.getNodeName())) { for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) { if ("castle".equalsIgnoreCase(d.getNodeName())) { final int castleId = parseInteger(d.getAttributes(), "id"); for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) { if ("crop".equalsIgnoreCase(c.getNodeName())) { set = new StatsSet(); set.set("castleId", castleId); attrs = c.getAttributes(); for (int i = 0; i < attrs.getLength(); i++) { att = attrs.item(i); set.set(att.getNodeName(), att.getNodeValue()); } _seeds.put(set.getInt("seedId"), new L2Seed(set)); } } } } } } } private final void loadDb() { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement stProduction = con.prepareStatement("SELECT * FROM castle_manor_production WHERE castle_id=?"); PreparedStatement stProcure = con.prepareStatement("SELECT * FROM castle_manor_procure WHERE castle_id=?")) { for (Castle castle : CastleManager.getInstance().getCastles()) { final int castleId = castle.getResidenceId(); // Clear params stProduction.clearParameters(); stProcure.clearParameters(); // Seed production final List pCurrent = new ArrayList<>(); final List pNext = new ArrayList<>(); stProduction.setInt(1, castleId); try (ResultSet rs = stProduction.executeQuery()) { while (rs.next()) { final int seedId = rs.getInt("seed_id"); if (_seeds.containsKey(seedId)) // Don't load unknown seeds { final SeedProduction sp = new SeedProduction(seedId, rs.getLong("amount"), rs.getLong("price"), rs.getInt("start_amount")); if (rs.getBoolean("next_period")) { pNext.add(sp); } else { pCurrent.add(sp); } } else { LOGGER.warn("{}: Unknown seed ID: {}!", getClass().getSimpleName(), seedId); } } } _production.put(castleId, pCurrent); _productionNext.put(castleId, pNext); // Seed procure final List current = new ArrayList<>(); final List next = new ArrayList<>(); stProcure.setInt(1, castleId); try (ResultSet rs = stProcure.executeQuery()) { final Set cropIds = getCropIds(); while (rs.next()) { final int cropId = rs.getInt("crop_id"); if (cropIds.contains(cropId)) // Don't load unknown crops { final CropProcure cp = new CropProcure(cropId, rs.getLong("amount"), rs.getInt("reward_type"), rs.getLong("start_amount"), rs.getLong("price")); if (rs.getBoolean("next_period")) { next.add(cp); } else { current.add(cp); } } else { LOGGER.warn("{}: Unknown crop ID: {}!", getClass().getSimpleName(), cropId); } } } _procure.put(castleId, current); _procureNext.put(castleId, next); } LOGGER.info("{}: Manor data loaded.", getClass().getSimpleName()); } catch (Exception e) { LOGGER.warn("{}: Unable to load manor data!", getClass().getSimpleName(), e); } } // ------------------------------------------------------- // Manor methods // ------------------------------------------------------- private final void scheduleModeChange() { // Calculate next mode change _nextModeChange = Calendar.getInstance(); _nextModeChange.set(Calendar.SECOND, 0); switch (_mode) { case MODIFIABLE: _nextModeChange.set(Calendar.HOUR_OF_DAY, Config.ALT_MANOR_APPROVE_TIME); _nextModeChange.set(Calendar.MINUTE, Config.ALT_MANOR_APPROVE_MIN); if (_nextModeChange.before(Calendar.getInstance())) { _nextModeChange.add(Calendar.DATE, 1); } break; case MAINTENANCE: _nextModeChange.set(Calendar.HOUR_OF_DAY, Config.ALT_MANOR_REFRESH_TIME); _nextModeChange.set(Calendar.MINUTE, Config.ALT_MANOR_REFRESH_MIN + Config.ALT_MANOR_MAINTENANCE_MIN); break; case APPROVED: _nextModeChange.set(Calendar.HOUR_OF_DAY, Config.ALT_MANOR_REFRESH_TIME); _nextModeChange.set(Calendar.MINUTE, Config.ALT_MANOR_REFRESH_MIN); break; } // Schedule mode change ThreadPoolManager.getInstance().scheduleGeneral(this::changeMode, (_nextModeChange.getTimeInMillis() - System.currentTimeMillis())); } public final void changeMode() { switch (_mode) { case APPROVED: { // Change mode _mode = ManorMode.MAINTENANCE; // Update manor period for (Castle castle : CastleManager.getInstance().getCastles()) { final L2Clan owner = castle.getOwner(); if (owner == null) { continue; } final int castleId = castle.getResidenceId(); final ItemContainer cwh = owner.getWarehouse(); for (CropProcure crop : _procure.get(castleId)) { if (crop.getStartAmount() > 0) { // Adding bought crops to clan warehouse if (crop.getStartAmount() != crop.getAmount()) { long count = (long) ((crop.getStartAmount() - crop.getAmount()) * 0.9); if ((count < 1) && (Rnd.nextInt(99) < 90)) { count = 1; } if (count > 0) { cwh.addItem("Manor", getSeedByCrop(crop.getId()).getMatureId(), count, null, null); } } // Reserved and not used money giving back to treasury if (crop.getAmount() > 0) { castle.addToTreasuryNoTax(crop.getAmount() * crop.getPrice()); } } } // Change next period to current and prepare next period data final List _nextProduction = _productionNext.get(castleId); final List _nextProcure = _procureNext.get(castleId); _production.put(castleId, _nextProduction); _procure.put(castleId, _nextProcure); if (castle.getTreasury() < getManorCost(castleId, false)) { _productionNext.put(castleId, Collections.emptyList()); _procureNext.put(castleId, Collections.emptyList()); } else { final List production = new ArrayList<>(_nextProduction); for (SeedProduction s : production) { s.setAmount(s.getStartAmount()); } _productionNext.put(castleId, production); final List procure = new ArrayList<>(_nextProcure); for (CropProcure cr : procure) { cr.setAmount(cr.getStartAmount()); } _procureNext.put(castleId, procure); } } // Save changes storeMe(); break; } case MAINTENANCE: { // Notify clan leader about manor mode change for (Castle castle : CastleManager.getInstance().getCastles()) { final L2Clan owner = castle.getOwner(); if (owner != null) { final L2ClanMember clanLeader = owner.getLeader(); if ((clanLeader != null) && clanLeader.isOnline()) { clanLeader.getPlayerInstance().sendPacket(SystemMessageId.THE_MANOR_INFORMATION_HAS_BEEN_UPDATED); } } } _mode = ManorMode.MODIFIABLE; break; } case MODIFIABLE: { _mode = ManorMode.APPROVED; for (Castle castle : CastleManager.getInstance().getCastles()) { final L2Clan owner = castle.getOwner(); if (owner == null) { continue; } int slots = 0; final int castleId = castle.getResidenceId(); final ItemContainer cwh = owner.getWarehouse(); for (CropProcure crop : _procureNext.get(castleId)) { if ((crop.getStartAmount() > 0) && (cwh.getItemsByItemId(getSeedByCrop(crop.getId()).getMatureId()) == null)) { slots++; } } final long manorCost = getManorCost(castleId, true); if (!cwh.validateCapacity(slots) && (castle.getTreasury() < manorCost)) { _productionNext.get(castleId).clear(); _procureNext.get(castleId).clear(); // Notify clan leader final L2ClanMember clanLeader = owner.getLeader(); if ((clanLeader != null) && clanLeader.isOnline()) { clanLeader.getPlayerInstance().sendPacket(SystemMessageId.THE_AMOUNT_IS_NOT_SUFFICIENT_AND_SO_THE_MANOR_IS_NOT_IN_OPERATION); } } else { castle.addToTreasuryNoTax(-manorCost); } } // Store changes if (Config.ALT_MANOR_SAVE_ALL_ACTIONS) { storeMe(); } break; } } scheduleModeChange(); if (Config.DEBUG) { LOGGER.info("{}: Manor mode changed to {}!", getClass().getSimpleName(), _mode); } } public final void setNextSeedProduction(List list, int castleId) { _productionNext.put(castleId, list); if (Config.ALT_MANOR_SAVE_ALL_ACTIONS) { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement dps = con.prepareStatement("DELETE FROM castle_manor_production WHERE castle_id = ? AND next_period = 1"); PreparedStatement ips = con.prepareStatement(INSERT_PRODUCT)) { // Delete old data dps.setInt(1, castleId); dps.executeUpdate(); // Insert new data if (!list.isEmpty()) { for (SeedProduction sp : list) { ips.setInt(1, castleId); ips.setInt(2, sp.getId()); ips.setLong(3, sp.getAmount()); ips.setLong(4, sp.getStartAmount()); ips.setLong(5, sp.getPrice()); ips.setBoolean(6, true); ips.addBatch(); } ips.executeBatch(); } } catch (Exception e) { LOGGER.error("{}: Unable to store manor data!", getClass().getSimpleName(), e); } } } public final void setNextCropProcure(List list, int castleId) { _procureNext.put(castleId, list); if (Config.ALT_MANOR_SAVE_ALL_ACTIONS) { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement dps = con.prepareStatement("DELETE FROM castle_manor_procure WHERE castle_id = ? AND next_period = 1"); PreparedStatement ips = con.prepareStatement(INSERT_CROP)) { // Delete old data dps.setInt(1, castleId); dps.executeUpdate(); // Insert new data if (!list.isEmpty()) { for (CropProcure cp : list) { ips.setInt(1, castleId); ips.setInt(2, cp.getId()); ips.setLong(3, cp.getAmount()); ips.setLong(4, cp.getStartAmount()); ips.setLong(5, cp.getPrice()); ips.setInt(6, cp.getReward()); ips.setBoolean(7, true); ips.addBatch(); } ips.executeBatch(); } } catch (Exception e) { LOGGER.error("{}: Unable to store manor data!", getClass().getSimpleName(), e); } } } public final void updateCurrentProduction(int castleId, Collection items) { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement ps = con.prepareStatement("UPDATE castle_manor_production SET amount = ? WHERE castle_id = ? AND seed_id = ? AND next_period = 0")) { for (SeedProduction sp : items) { ps.setLong(1, sp.getAmount()); ps.setInt(2, castleId); ps.setInt(3, sp.getId()); ps.addBatch(); } ps.executeBatch(); } catch (Exception e) { LOGGER.info("{}: Unable to store manor data!", getClass().getSimpleName(), e); } } public final void updateCurrentProcure(int castleId, Collection items) { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement ps = con.prepareStatement("UPDATE castle_manor_procure SET amount = ? WHERE castle_id = ? AND crop_id = ? AND next_period = 0")) { for (CropProcure sp : items) { ps.setLong(1, sp.getAmount()); ps.setInt(2, castleId); ps.setInt(3, sp.getId()); ps.addBatch(); } ps.executeBatch(); } catch (Exception e) { LOGGER.info("{}: Unable to store manor data!", getClass().getSimpleName(), e); } } public final List getSeedProduction(int castleId, boolean nextPeriod) { return (nextPeriod) ? _productionNext.get(castleId) : _production.get(castleId); } public final SeedProduction getSeedProduct(int castleId, int seedId, boolean nextPeriod) { for (SeedProduction sp : getSeedProduction(castleId, nextPeriod)) { if (sp.getId() == seedId) { return sp; } } return null; } public final List getCropProcure(int castleId, boolean nextPeriod) { return (nextPeriod) ? _procureNext.get(castleId) : _procure.get(castleId); } public final CropProcure getCropProcure(int castleId, int cropId, boolean nextPeriod) { for (CropProcure cp : getCropProcure(castleId, nextPeriod)) { if (cp.getId() == cropId) { return cp; } } return null; } public final long getManorCost(int castleId, boolean nextPeriod) { final List procure = getCropProcure(castleId, nextPeriod); final List production = getSeedProduction(castleId, nextPeriod); long total = 0; for (SeedProduction seed : production) { final L2Seed s = getSeed(seed.getId()); total += (s == null) ? 1 : (s.getSeedReferencePrice() * seed.getStartAmount()); } for (CropProcure crop : procure) { total += (crop.getPrice() * crop.getStartAmount()); } return total; } @Override public final boolean storeMe() { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement ds = con.prepareStatement("DELETE FROM castle_manor_production"); PreparedStatement is = con.prepareStatement(INSERT_PRODUCT); PreparedStatement dp = con.prepareStatement("DELETE FROM castle_manor_procure"); PreparedStatement ip = con.prepareStatement(INSERT_CROP)) { // Delete old seeds ds.executeUpdate(); // Current production for (Map.Entry> entry : _production.entrySet()) { for (SeedProduction sp : entry.getValue()) { is.setInt(1, entry.getKey()); is.setInt(2, sp.getId()); is.setLong(3, sp.getAmount()); is.setLong(4, sp.getStartAmount()); is.setLong(5, sp.getPrice()); is.setBoolean(6, false); is.addBatch(); } } // Next production for (Map.Entry> entry : _productionNext.entrySet()) { for (SeedProduction sp : entry.getValue()) { is.setInt(1, entry.getKey()); is.setInt(2, sp.getId()); is.setLong(3, sp.getAmount()); is.setLong(4, sp.getStartAmount()); is.setLong(5, sp.getPrice()); is.setBoolean(6, true); is.addBatch(); } } // Execute production batch is.executeBatch(); // Delete old procure dp.executeUpdate(); // Current procure for (Map.Entry> entry : _procure.entrySet()) { for (CropProcure cp : entry.getValue()) { ip.setInt(1, entry.getKey()); ip.setInt(2, cp.getId()); ip.setLong(3, cp.getAmount()); ip.setLong(4, cp.getStartAmount()); ip.setLong(5, cp.getPrice()); ip.setInt(6, cp.getReward()); ip.setBoolean(7, false); ip.addBatch(); } } // Next procure for (Map.Entry> entry : _procureNext.entrySet()) { for (CropProcure cp : entry.getValue()) { ip.setInt(1, entry.getKey()); ip.setInt(2, cp.getId()); ip.setLong(3, cp.getAmount()); ip.setLong(4, cp.getStartAmount()); ip.setLong(5, cp.getPrice()); ip.setInt(6, cp.getReward()); ip.setBoolean(7, true); ip.addBatch(); } } // Execute procure batch ip.executeBatch(); return true; } catch (Exception e) { LOGGER.error("{}: Unable to store manor data!", getClass().getSimpleName(), e); return false; } } public final void resetManorData(int castleId) { _procure.get(castleId).clear(); _procureNext.get(castleId).clear(); _production.get(castleId).clear(); _productionNext.get(castleId).clear(); if (Config.ALT_MANOR_SAVE_ALL_ACTIONS) { try (Connection con = ConnectionFactory.getInstance().getConnection(); PreparedStatement ds = con.prepareStatement("DELETE FROM castle_manor_production WHERE castle_id = ?"); PreparedStatement dc = con.prepareStatement("DELETE FROM castle_manor_procure WHERE castle_id = ?")) { // Delete seeds ds.setInt(1, castleId); ds.executeUpdate(); // Delete procure dc.setInt(1, castleId); dc.executeUpdate(); } catch (Exception e) { LOGGER.error("{}: Unable to store manor data!", getClass().getSimpleName(), e); } } } public final boolean isUnderMaintenance() { return _mode.equals(ManorMode.MAINTENANCE); } public final boolean isManorApproved() { return _mode.equals(ManorMode.APPROVED); } public final boolean isModifiablePeriod() { return _mode.equals(ManorMode.MODIFIABLE); } public final String getCurrentModeName() { return _mode.toString(); } public final String getNextModeChange() { return new SimpleDateFormat("dd/MM HH:mm:ss").format(_nextModeChange.getTime()); } // ------------------------------------------------------- // Seed methods // ------------------------------------------------------- public final List getCrops() { final List seeds = new ArrayList<>(); final List cropIds = new ArrayList<>(); for (L2Seed seed : _seeds.values()) { if (!cropIds.contains(seed.getCropId())) { seeds.add(seed); cropIds.add(seed.getCropId()); } } cropIds.clear(); return seeds; } public final Set getSeedsForCastle(int castleId) { return _seeds.values().stream().filter(s -> s.getCastleId() == castleId).collect(Collectors.toSet()); } public final Set getSeedIds() { return _seeds.keySet(); } public final Set getCropIds() { return _seeds.values().stream().map(L2Seed::getCropId).collect(Collectors.toSet()); } public final L2Seed getSeed(int seedId) { return _seeds.get(seedId); } public final L2Seed getSeedByCrop(int cropId, int castleId) { for (L2Seed s : getSeedsForCastle(castleId)) { if (s.getCropId() == cropId) { return s; } } return null; } public final L2Seed getSeedByCrop(int cropId) { for (L2Seed s : _seeds.values()) { if (s.getCropId() == cropId) { return s; } } return null; } // ------------------------------------------------------- // Static methods // ------------------------------------------------------- public static final CastleManorManager getInstance() { return SingletonHolder._instance; } private static class SingletonHolder { protected static final CastleManorManager _instance = new CastleManorManager(); } }