This commit is contained in:
MobiusDev
2016-10-21 21:26:21 +00:00
parent 4247fae039
commit 34fc592ced
25699 changed files with 2534454 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius;
/**
* This class used to be the starter class, since LS/GS split, it only retains server mode
*/
public class Server
{
// constants for the server mode
private static final int MODE_NONE = 0;
public static final int MODE_GAMESERVER = 1;
public static final int MODE_LOGINSERVER = 2;
public static int serverMode = MODE_NONE;
}

View File

@ -0,0 +1,135 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.database;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* Database Factory implementation.
* @author Zoey76
*/
public class DatabaseFactory
{
private static final Logger _log = Logger.getLogger(DatabaseFactory.class.getName());
private final ComboPooledDataSource _dataSource;
public DatabaseFactory()
{
if (Config.DATABASE_MAX_CONNECTIONS < 2)
{
Config.DATABASE_MAX_CONNECTIONS = 2;
_log.warning("A minimum of 2 connections are required.");
}
_dataSource = new ComboPooledDataSource();
_dataSource.setAutoCommitOnClose(true);
_dataSource.setInitialPoolSize(10);
_dataSource.setMinPoolSize(10);
_dataSource.setMaxPoolSize(Math.max(10, Config.DATABASE_MAX_CONNECTIONS));
_dataSource.setAcquireRetryAttempts(0); // try to obtain connections indefinitely (0 = never quit)
_dataSource.setAcquireRetryDelay(500); // 500 milliseconds wait before try to acquire connection again
_dataSource.setCheckoutTimeout(0); // 0 = wait indefinitely for new connection if pool is exhausted
_dataSource.setAcquireIncrement(5); // if pool is exhausted, get 5 more connections at a time cause there is
// a "long" delay on acquire connection so taking more than one connection at once will make connection pooling more effective.
// this "connection_test_table" is automatically created if not already there
_dataSource.setAutomaticTestTable("connection_test_table");
_dataSource.setTestConnectionOnCheckin(false);
// testing OnCheckin used with IdleConnectionTestPeriod is faster than testing on checkout
_dataSource.setIdleConnectionTestPeriod(3600); // test idle connection every 60 sec
_dataSource.setMaxIdleTime(Config.DATABASE_MAX_IDLE_TIME); // 0 = idle connections never expire
// *THANKS* to connection testing configured above but I prefer to disconnect all connections not used for more than 1 hour
// enables statement caching, there is a "semi-bug" in c3p0 0.9.0 but in 0.9.0.2 and later it's fixed
_dataSource.setMaxStatementsPerConnection(100);
_dataSource.setBreakAfterAcquireFailure(false); // never fail if any way possible setting this to true will make c3p0 "crash"
// and refuse to work till restart thus making acquire errors "FATAL" ... we don't want that it should be possible to recover
try
{
_dataSource.setDriverClass(Config.DATABASE_DRIVER);
}
catch (PropertyVetoException e)
{
e.printStackTrace();
}
_dataSource.setJdbcUrl(Config.DATABASE_URL);
_dataSource.setUser(Config.DATABASE_LOGIN);
_dataSource.setPassword(Config.DATABASE_PASSWORD);
/* Test the connection */
try
{
_dataSource.getConnection().close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public Connection getConnection()
{
Connection con = null;
while (con == null)
{
try
{
con = _dataSource.getConnection();
}
catch (SQLException e)
{
_log.warning(getClass().getSimpleName() + ": Unable to get a connection: " + e.getMessage());
}
}
return con;
}
public void close()
{
try
{
_dataSource.close();
}
catch (Exception e)
{
_log.info(e.getMessage());
}
}
public static DatabaseFactory getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final DatabaseFactory INSTANCE = new DatabaseFactory();
}
}

View File

@ -0,0 +1,48 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver;
/**
* @author HorridoJoho
*/
public final class Cell
{
/** East NSWE flag */
public static final byte NSWE_EAST = 1 << 0;
/** West NSWE flag */
public static final byte NSWE_WEST = 1 << 1;
/** South NSWE flag */
public static final byte NSWE_SOUTH = 1 << 2;
/** North NSWE flag */
public static final byte NSWE_NORTH = 1 << 3;
/** North-East NSWE flags */
public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST;
/** North-West NSWE flags */
public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST;
/** South-East NSWE flags */
public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST;
/** South-West NSWE flags */
public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST;
/** All directions NSWE flags */
public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH;
private Cell()
{
}
}

View File

@ -0,0 +1,162 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicReferenceArray;
import com.l2jmobius.commons.geodriver.regions.NullRegion;
import com.l2jmobius.commons.geodriver.regions.Region;
/**
* @author HorridoJoho
*/
public final class GeoDriver
{
// world dimensions: 1048576 * 1048576 = 1099511627776
private static final int WORLD_MIN_X = -655360;
private static final int WORLD_MAX_X = 393215;
private static final int WORLD_MIN_Y = -589824;
private static final int WORLD_MAX_Y = 458751;
/** Regions in the world on the x axis */
public static final int GEO_REGIONS_X = 32;
/** Regions in the world on the y axis */
public static final int GEO_REGIONS_Y = 32;
/** Region in the world */
public static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y;
/** Blocks in the world on the x axis */
public static final int GEO_BLOCKS_X = GEO_REGIONS_X * IRegion.REGION_BLOCKS_X;
/** Blocks in the world on the y axis */
public static final int GEO_BLOCKS_Y = GEO_REGIONS_Y * IRegion.REGION_BLOCKS_Y;
/** Blocks in the world */
public static final int GEO_BLOCKS = GEO_REGIONS * IRegion.REGION_BLOCKS;
/** Cells in the world on the x axis */
public static final int GEO_CELLS_X = GEO_BLOCKS_X * IBlock.BLOCK_CELLS_X;
/** Cells in the world in the y axis */
public static final int GEO_CELLS_Y = GEO_BLOCKS_Y * IBlock.BLOCK_CELLS_Y;
/** The regions array */
private final AtomicReferenceArray<IRegion> _regions = new AtomicReferenceArray<>(GEO_REGIONS);
public GeoDriver()
{
for (int i = 0; i < _regions.length(); i++)
{
_regions.set(i, NullRegion.INSTANCE);
}
}
private void checkGeoX(int geoX)
{
if ((geoX < 0) || (geoX >= GEO_CELLS_X))
{
throw new IllegalArgumentException();
}
}
private void checkGeoY(int geoY)
{
if ((geoY < 0) || (geoY >= GEO_CELLS_Y))
{
throw new IllegalArgumentException();
}
}
private IRegion getRegion(int geoX, int geoY)
{
checkGeoX(geoX);
checkGeoY(geoY);
return _regions.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y));
}
public void loadRegion(Path filePath, int regionX, int regionY) throws IOException
{
final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY;
try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r"))
{
_regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).order(ByteOrder.LITTLE_ENDIAN)));
}
}
public void unloadRegion(int regionX, int regionY)
{
_regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE);
}
public boolean hasGeoPos(int geoX, int geoY)
{
return getRegion(geoX, geoY).hasGeo();
}
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe)
{
return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe);
}
public int getNearestZ(int geoX, int geoY, int worldZ)
{
return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ);
}
public int getNextLowerZ(int geoX, int geoY, int worldZ)
{
return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ);
}
public int getNextHigherZ(int geoX, int geoY, int worldZ)
{
return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ);
}
public int getGeoX(int worldX)
{
if ((worldX < WORLD_MIN_X) || (worldX > WORLD_MAX_X))
{
throw new IllegalArgumentException();
}
return (worldX - WORLD_MIN_X) / 16;
}
public int getGeoY(int worldY)
{
if ((worldY < WORLD_MIN_Y) || (worldY > WORLD_MAX_Y))
{
throw new IllegalArgumentException();
}
return (worldY - WORLD_MIN_Y) / 16;
}
public int getWorldX(int geoX)
{
checkGeoX(geoX);
return (geoX * 16) + WORLD_MIN_X + 8;
}
public int getWorldY(int geoY)
{
checkGeoY(geoY);
return (geoY * 16) + WORLD_MIN_Y + 8;
}
}

View File

@ -0,0 +1,42 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver;
/**
* @author HorridoJoho
*/
public interface IBlock
{
int TYPE_FLAT = 0;
int TYPE_COMPLEX = 1;
int TYPE_MULTILAYER = 2;
/** Cells in a block on the x axis */
int BLOCK_CELLS_X = 8;
/** Cells in a block on the y axis */
int BLOCK_CELLS_Y = 8;
/** Cells in a block */
int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y;
boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe);
int getNearestZ(int geoX, int geoY, int worldZ);
int getNextLowerZ(int geoX, int geoY, int worldZ);
int getNextHigherZ(int geoX, int geoY, int worldZ);
}

View File

@ -0,0 +1,47 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver;
/**
* @author HorridoJoho
*/
public interface IRegion
{
/** Blocks in a region on the x axis. */
int REGION_BLOCKS_X = 256;
/** Blocks in a region on the y axis. */
int REGION_BLOCKS_Y = 256;
/** Blocks in a region. */
int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y;
/** Cells in a region on the x axis. */
int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X;
/** Cells in a regioin on the y axis. */
int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y;
/** Cells in a region. */
int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y;
boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe);
int getNearestZ(int geoX, int geoY, int worldZ);
int getNextLowerZ(int geoX, int geoY, int worldZ);
int getNextHigherZ(int geoX, int geoY, int worldZ);
boolean hasGeo();
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver.blocks;
import java.nio.ByteBuffer;
import com.l2jmobius.commons.geodriver.IBlock;
/**
* @author HorridoJoho
*/
public final class ComplexBlock implements IBlock
{
private final short[] _data;
public ComplexBlock(ByteBuffer bb)
{
_data = new short[IBlock.BLOCK_CELLS];
for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++)
{
_data[cellOffset] = bb.getShort();
}
}
private short _getCellData(int geoX, int geoY)
{
return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)];
}
private byte _getCellNSWE(int geoX, int geoY)
{
return (byte) (_getCellData(geoX, geoY) & 0x000F);
}
private int _getCellHeight(int geoX, int geoY)
{
return (short) (_getCellData(geoX, geoY) & 0x0FFF0) >> 1;
}
@Override
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe)
{
return (_getCellNSWE(geoX, geoY) & nswe) == nswe;
}
@Override
public int getNearestZ(int geoX, int geoY, int worldZ)
{
return _getCellHeight(geoX, geoY);
}
@Override
public int getNextLowerZ(int geoX, int geoY, int worldZ)
{
final int cellHeight = _getCellHeight(geoX, geoY);
return cellHeight <= worldZ ? cellHeight : worldZ;
}
@Override
public int getNextHigherZ(int geoX, int geoY, int worldZ)
{
final int cellHeight = _getCellHeight(geoX, geoY);
return cellHeight >= worldZ ? cellHeight : worldZ;
}
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver.blocks;
import java.nio.ByteBuffer;
import com.l2jmobius.commons.geodriver.IBlock;
/**
* @author HorridoJoho
*/
public class FlatBlock implements IBlock
{
private final short _height;
public FlatBlock(ByteBuffer bb)
{
_height = bb.getShort();
}
@Override
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe)
{
return true;
}
@Override
public int getNearestZ(int geoX, int geoY, int worldZ)
{
return _height;
}
@Override
public int getNextLowerZ(int geoX, int geoY, int worldZ)
{
return _height <= worldZ ? _height : worldZ;
}
@Override
public int getNextHigherZ(int geoX, int geoY, int worldZ)
{
return _height >= worldZ ? _height : worldZ;
}
}

View File

@ -0,0 +1,186 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver.blocks;
import java.nio.ByteBuffer;
import com.l2jmobius.commons.geodriver.IBlock;
/**
* @author HorridoJoho
*/
public class MultilayerBlock implements IBlock
{
private final byte[] _data;
/**
* Initializes a new instance of this block reading the specified buffer.
* @param bb the buffer
*/
public MultilayerBlock(ByteBuffer bb)
{
final int start = bb.position();
for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++)
{
final byte nLayers = bb.get();
if ((nLayers <= 0) || (nLayers > 125))
{
throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!");
}
bb.position(bb.position() + (nLayers * 2));
}
_data = new byte[bb.position() - start];
bb.position(start);
bb.get(_data);
}
private short _getNearestLayer(int geoX, int geoY, int worldZ)
{
final int startOffset = _getCellDataOffset(geoX, geoY);
final byte nLayers = _data[startOffset];
final int endOffset = startOffset + 1 + (nLayers * 2);
// 1 layer at least was required on loading so this is set at least once on the loop below
int nearestDZ = 0;
short nearestData = 0;
for (int offset = startOffset + 1; offset < endOffset; offset += 2)
{
final short layerData = _extractLayerData(offset);
final int layerZ = _extractLayerHeight(layerData);
if (layerZ == worldZ)
{
// exact z
return layerData;
}
final int layerDZ = Math.abs(layerZ - worldZ);
if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ))
{
nearestDZ = layerDZ;
nearestData = layerData;
}
}
return nearestData;
}
private int _getCellDataOffset(int geoX, int geoY)
{
final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y);
int cellDataOffset = 0;
// move index to cell, we need to parse on each request, OR we parse on creation and save indexes
for (int i = 0; i < cellLocalOffset; i++)
{
cellDataOffset += 1 + (_data[cellDataOffset] * 2);
}
// now the index points to the cell we need
return cellDataOffset;
}
private short _extractLayerData(int dataOffset)
{
return (short) ((_data[dataOffset] & 0xFF) | (_data[dataOffset + 1] << 8));
}
private int _getNearestNSWE(int geoX, int geoY, int worldZ)
{
return _extractLayerNswe(_getNearestLayer(geoX, geoY, worldZ));
}
private int _extractLayerNswe(short layer)
{
return (byte) (layer & 0x000F);
}
private int _extractLayerHeight(short layer)
{
layer = (short) (layer & 0x0fff0);
return layer >> 1;
}
@Override
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe)
{
return (_getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe;
}
@Override
public int getNearestZ(int geoX, int geoY, int worldZ)
{
return _extractLayerHeight(_getNearestLayer(geoX, geoY, worldZ));
}
@Override
public int getNextLowerZ(int geoX, int geoY, int worldZ)
{
final int startOffset = _getCellDataOffset(geoX, geoY);
final byte nLayers = _data[startOffset];
final int endOffset = startOffset + 1 + (nLayers * 2);
int lowerZ = Integer.MIN_VALUE;
for (int offset = startOffset + 1; offset < endOffset; offset += 2)
{
final short layerData = _extractLayerData(offset);
final int layerZ = _extractLayerHeight(layerData);
if (layerZ == worldZ)
{
// exact z
return layerZ;
}
if ((layerZ < worldZ) && (layerZ > lowerZ))
{
lowerZ = layerZ;
}
}
return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ;
}
@Override
public int getNextHigherZ(int geoX, int geoY, int worldZ)
{
final int startOffset = _getCellDataOffset(geoX, geoY);
final byte nLayers = _data[startOffset];
final int endOffset = startOffset + 1 + (nLayers * 2);
int higherZ = Integer.MAX_VALUE;
for (int offset = startOffset + 1; offset < endOffset; offset += 2)
{
final short layerData = _extractLayerData(offset);
final int layerZ = _extractLayerHeight(layerData);
if (layerZ == worldZ)
{
// exact z
return layerZ;
}
if ((layerZ > worldZ) && (layerZ < higherZ))
{
higherZ = layerZ;
}
}
return higherZ == Integer.MAX_VALUE ? worldZ : higherZ;
}
}

View File

@ -0,0 +1,57 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver.regions;
import com.l2jmobius.commons.geodriver.IRegion;
/**
* @author HorridoJoho
*/
public final class NullRegion implements IRegion
{
public static final NullRegion INSTANCE = new NullRegion();
@Override
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe)
{
return true;
}
@Override
public int getNearestZ(int geoX, int geoY, int worldZ)
{
return worldZ;
}
@Override
public int getNextLowerZ(int geoX, int geoY, int worldZ)
{
return worldZ;
}
@Override
public int getNextHigherZ(int geoX, int geoY, int worldZ)
{
return worldZ;
}
@Override
public boolean hasGeo()
{
return false;
}
}

View File

@ -0,0 +1,98 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.geodriver.regions;
import java.nio.ByteBuffer;
import com.l2jmobius.commons.geodriver.IBlock;
import com.l2jmobius.commons.geodriver.IRegion;
import com.l2jmobius.commons.geodriver.blocks.ComplexBlock;
import com.l2jmobius.commons.geodriver.blocks.FlatBlock;
import com.l2jmobius.commons.geodriver.blocks.MultilayerBlock;
/**
* @author HorridoJoho
*/
public final class Region implements IRegion
{
private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS];
public Region(ByteBuffer bb)
{
for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++)
{
final int blockType = bb.get();
switch (blockType)
{
case IBlock.TYPE_FLAT:
{
_blocks[blockOffset] = new FlatBlock(bb);
break;
}
case IBlock.TYPE_COMPLEX:
{
_blocks[blockOffset] = new ComplexBlock(bb);
break;
}
case IBlock.TYPE_MULTILAYER:
{
_blocks[blockOffset] = new MultilayerBlock(bb);
break;
}
default:
{
throw new RuntimeException("Invalid block type " + blockType + "!");
}
}
}
}
private IBlock getBlock(int geoX, int geoY)
{
return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)];
}
@Override
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe)
{
return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe);
}
@Override
public int getNearestZ(int geoX, int geoY, int worldZ)
{
return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ);
}
@Override
public int getNextLowerZ(int geoX, int geoY, int worldZ)
{
return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ);
}
@Override
public int getNextHigherZ(int geoX, int geoY, int worldZ)
{
return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ);
}
@Override
public boolean hasGeo()
{
return true;
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: Redistributions of source code
* must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. Neither the name of the Sun Microsystems nor the names of
* is contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.l2jmobius.commons.javaengine;
public class CompilationException extends Exception
{
public CompilationException(String message)
{
super(message);
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: Redistributions of source code
* must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. Neither the name of the Sun Microsystems nor the names of
* is contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.l2jmobius.commons.javaengine;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
/**
* Simple interface to Java compiler using JSR 199 Compiler API.
* @author A. Sundararajan
*/
public class JavaCompiler
{
private final javax.tools.JavaCompiler tool;
public JavaCompiler()
{
tool = new EclipseCompiler();
}
public Map<String, byte[]> compile(String source, String fileName)
{
return compile(source, fileName, new PrintWriter(System.err), null, null);
}
public Map<String, byte[]> compile(String fileName, String source, Writer err)
{
return compile(fileName, source, err, null, null);
}
public Map<String, byte[]> compile(String fileName, String source, Writer err, String sourcePath)
{
return compile(fileName, source, err, sourcePath, null);
}
/**
* compile given String source and return bytecodes as a Map.
* @param fileName source fileName to be used for error messages etc.
* @param source Java source as String
* @param err error writer where diagnostic messages are written
* @param sourcePath location of additional .java source files
* @param classPath location of additional .class files
* @return
*/
public Map<String, byte[]> compile(String fileName, String source, Writer err, String sourcePath, String classPath)
{
// to collect errors, warnings etc.
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
// create a new memory JavaFileManager
final MemoryJavaFileManager manager = new MemoryJavaFileManager();
// prepare the compilation unit
final List<JavaFileObject> compUnits = new ArrayList<>(1);
compUnits.add(MemoryJavaFileManager.makeStringSource(fileName, source));
// javac options
final List<String> options = new ArrayList<>();
options.add("-warn:-enumSwitch");
options.add("-g");
options.add("-deprecation");
options.add("-1.8");
if (sourcePath != null)
{
options.add("-sourcepath");
options.add(sourcePath);
}
if (classPath != null)
{
options.add("-classpath");
options.add(classPath);
}
// create a compilation task
final CompilationTask task = tool.getTask(err, manager, diagnostics, options, null, compUnits);
if (!task.call())
{
final PrintWriter perr = new PrintWriter(err);
for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics())
{
perr.println(diagnostic.getMessage(Locale.getDefault()));
}
perr.flush();
return null;
}
final Map<String, byte[]> classBytes = manager.getClassBytes();
manager.close();
return classBytes;
}
}

View File

@ -0,0 +1,447 @@
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: Redistributions of source code
* must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. Neither the name of the Sun Microsystems nor the names of
* is contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.l2jmobius.commons.javaengine;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
/**
* This is script engine for Java programming language.
* @author A. Sundararajan
*/
public class JavaScriptEngine extends AbstractScriptEngine implements Compilable
{
// Java compiler
private final JavaCompiler compiler;
public JavaScriptEngine()
{
compiler = new JavaCompiler();
}
// my factory, may be null
private ScriptEngineFactory factory;
// my implementation for CompiledScript
private static class JavaCompiledScript extends CompiledScript implements Serializable
{
private final transient JavaScriptEngine _engine;
private transient Class<?> _class;
private final Map<String, byte[]> _classBytes;
private final String _classPath;
JavaCompiledScript(JavaScriptEngine engine, Map<String, byte[]> classBytes, String classPath)
{
_engine = engine;
_classBytes = classBytes;
_classPath = classPath;
}
@Override
public ScriptEngine getEngine()
{
return _engine;
}
@Override
public Object eval(ScriptContext ctx) throws ScriptException
{
if (_class == null)
{
final Map<String, byte[]> classBytesCopy = new HashMap<>();
classBytesCopy.putAll(_classBytes);
_class = JavaScriptEngine.parseMain(new MemoryClassLoader(classBytesCopy, _classPath, JavaScriptEngine.getParentLoader(ctx)), ctx);
}
return JavaScriptEngine.evalClass(_class, ctx);
}
}
@Override
public CompiledScript compile(String script) throws ScriptException
{
return compile(script, context);
}
@Override
public CompiledScript compile(Reader reader) throws ScriptException
{
return compile(readFully(reader));
}
@Override
public Object eval(String str, ScriptContext ctx) throws ScriptException
{
return evalClass(parse(str, ctx), ctx);
}
@Override
public Object eval(Reader reader, ScriptContext ctx) throws ScriptException
{
return eval(readFully(reader), ctx);
}
@Override
public ScriptEngineFactory getFactory()
{
synchronized (this)
{
if (factory == null)
{
factory = new JavaScriptEngineFactory();
}
}
return factory;
}
@Override
public Bindings createBindings()
{
return new SimpleBindings();
}
void setFactory(ScriptEngineFactory factory)
{
this.factory = factory;
}
// Internals only below this point
private Class<?> parse(String str, ScriptContext ctx) throws ScriptException
{
final String fileName = getFileName(ctx);
final String sourcePath = getSourcePath(ctx);
final String classPath = getClassPath(ctx);
final Writer err = ctx.getErrorWriter() == null ? new StringWriter() : ctx.getErrorWriter();
final Map<String, byte[]> classBytes = compiler.compile(fileName, str, err, sourcePath, classPath);
if (classBytes == null)
{
throw err instanceof StringWriter ? new ScriptException(((StringWriter) err).toString()) : new ScriptException("compilation failed");
}
// create a ClassLoader to load classes from MemoryJavaFileManager
return parseMain(new MemoryClassLoader(classBytes, classPath, getParentLoader(ctx)), ctx);
}
protected static Class<?> parseMain(MemoryClassLoader loader, ScriptContext ctx) throws ScriptException
{
final String mainClassName = getMainClassName(ctx);
if (mainClassName != null)
{
try
{
final Class<?> clazz = loader.load(mainClassName);
final Method mainMethod = findMainMethod(clazz);
if (mainMethod == null)
{
throw new ScriptException("no main method in " + mainClassName);
}
return clazz;
}
catch (ClassNotFoundException cnfe)
{
cnfe.printStackTrace();
throw new ScriptException(cnfe);
}
}
// no main class configured - load all compiled classes
Iterable<Class<?>> classes;
try
{
classes = loader.loadAll();
}
catch (ClassNotFoundException exp)
{
throw new ScriptException(exp);
}
// search for class with main method
final Class<?> c = findMainClass(classes);
if (c != null)
{
return c;
}
// if class with "main" method, then
// return first class
final Iterator<Class<?>> itr = classes.iterator();
return itr.hasNext() ? itr.next() : null;
}
private JavaCompiledScript compile(String str, ScriptContext ctx) throws ScriptException
{
final String fileName = getFileName(ctx);
final String sourcePath = getSourcePath(ctx);
final String classPath = getClassPath(ctx);
final Writer err = ctx.getErrorWriter() == null ? new StringWriter() : ctx.getErrorWriter();
final Map<String, byte[]> classBytes = compiler.compile(fileName, str, err, sourcePath, classPath);
if (classBytes == null)
{
throw err instanceof StringWriter ? new ScriptException(((StringWriter) err).toString()) : new ScriptException("compilation failed");
}
return new JavaCompiledScript(this, classBytes, classPath);
}
private static Class<?> findMainClass(Iterable<Class<?>> classes)
{
// find a public class with public static main method
for (Class<?> clazz : classes)
{
if (Modifier.isPublic(clazz.getModifiers()) && (findMainMethod(clazz) != null))
{
return clazz;
}
}
// okay, try to find package private class that
// has public static main method
for (Class<?> clazz : classes)
{
if (findMainMethod(clazz) != null)
{
return clazz;
}
}
// no main class found!
return null;
}
// find public static void main(String[]) method, if any
private static Method findMainMethod(Class<?> clazz)
{
try
{
final Method mainMethod = clazz.getMethod("main", new Class[]
{
String[].class
});
final int modifiers = mainMethod.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers))
{
return mainMethod;
}
}
catch (NoSuchMethodException nsme)
{
}
return null;
}
// find public static void setScriptContext(ScriptContext) method, if any
private static Method findSetScriptContextMethod(Class<?> clazz)
{
try
{
final Method setCtxMethod = clazz.getMethod("setScriptContext", new Class[]
{
ScriptContext.class
});
final int modifiers = setCtxMethod.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers))
{
return setCtxMethod;
}
}
catch (NoSuchMethodException nsme)
{
}
return null;
}
private static String getFileName(ScriptContext ctx)
{
final int scope = ctx.getAttributesScope("javax.script.filename");
return scope != -1 ? ctx.getAttribute("javax.script.filename", scope).toString() : "$unnamed.java";
}
// for certain variables, we look for System properties. This is
// the prefix used for such System properties
private static final String SYSPROP_PREFIX = "com.sun.script.java.";
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String ARGUMENTS = "arguments";
private static String[] getArguments(ScriptContext ctx)
{
final int scope = ctx.getAttributesScope(ARGUMENTS);
if (scope != -1)
{
final Object obj = ctx.getAttribute(ARGUMENTS, scope);
if (obj instanceof String[])
{
return (String[]) obj;
}
}
// return zero length array
return EMPTY_STRING_ARRAY;
}
private static final String SOURCEPATH = "sourcepath";
private static String getSourcePath(ScriptContext ctx)
{
return ctx.getAttributesScope(SOURCEPATH) != -1 ? ctx.getAttribute(SOURCEPATH).toString() : System.getProperty(SYSPROP_PREFIX + SOURCEPATH);
}
private static final String CLASSPATH = "classpath";
private static String getClassPath(ScriptContext ctx)
{
final int scope = ctx.getAttributesScope(CLASSPATH);
if (scope != -1)
{
return ctx.getAttribute(CLASSPATH).toString();
}
// look for "com.sun.script.java.classpath"
String res = System.getProperty(SYSPROP_PREFIX + CLASSPATH);
if (res == null)
{
res = System.getProperty("java.class.path");
}
return res;
}
private static final String MAINCLASS = "mainClass";
private static String getMainClassName(ScriptContext ctx)
{
return ctx.getAttributesScope(MAINCLASS) != -1 ? ctx.getAttribute(MAINCLASS).toString() : System.getProperty("com.sun.script.java.mainClass");
}
private static final String PARENTLOADER = "parentLoader";
protected static ClassLoader getParentLoader(ScriptContext ctx)
{
final int scope = ctx.getAttributesScope(PARENTLOADER);
if (scope != -1)
{
final Object loader = ctx.getAttribute(PARENTLOADER);
if (loader instanceof ClassLoader)
{
return (ClassLoader) loader;
}
}
return ClassLoader.getSystemClassLoader();
}
protected static Object evalClass(Class<?> clazz, ScriptContext ctx) throws ScriptException
{
// JSR-223 requirement
ctx.setAttribute("context", ctx, 100);
if (clazz == null)
{
return null;
}
try
{
final boolean isPublicClazz = Modifier.isPublic(clazz.getModifiers());
// find the setScriptContext method
final Method setCtxMethod = findSetScriptContextMethod(clazz);
// call setScriptContext and pass current ctx variable
if (setCtxMethod != null)
{
if (!isPublicClazz)
{
// try to relax access
setCtxMethod.setAccessible(true);
}
setCtxMethod.invoke(null, new Object[]
{
ctx
});
}
// find the main method
final Method mainMethod = findMainMethod(clazz);
if (mainMethod != null)
{
if (!isPublicClazz)
{
// try to relax access
mainMethod.setAccessible(true);
}
// call main method
mainMethod.invoke(null, new Object[]
{
getArguments(ctx)
});
}
// return main class as eval's result
return clazz;
}
catch (Exception exp)
{
exp.printStackTrace();
throw new ScriptException(exp);
}
}
// read a Reader fully and return the content as string
private String readFully(Reader reader) throws ScriptException
{
final char[] arr = new char[8 * 1024]; // 8K at a time
final StringBuilder buf = new StringBuilder();
int numChars;
try
{
while ((numChars = reader.read(arr, 0, arr.length)) > 0)
{
buf.append(arr, 0, numChars);
}
}
catch (IOException exp)
{
throw new ScriptException(exp);
}
return buf.toString();
}
}

View File

@ -0,0 +1,221 @@
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: Redistributions of source code
* must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. Neither the name of the Sun Microsystems nor the names of
* is contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.l2jmobius.commons.javaengine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
/**
* This is script engine factory for "Java" script engine.
* @author A. Sundararajan
*/
public class JavaScriptEngineFactory implements ScriptEngineFactory
{
@Override
public String getEngineName()
{
return "java";
}
@Override
public String getEngineVersion()
{
return "1.8";
}
@Override
public List<String> getExtensions()
{
return extensions;
}
@Override
public String getLanguageName()
{
return "java";
}
@Override
public String getLanguageVersion()
{
return "1.8";
}
@Override
public String getMethodCallSyntax(String obj, String m, String... args)
{
final StringBuilder buf = new StringBuilder();
buf.append(obj);
buf.append('.');
buf.append(m);
buf.append('(');
if (args.length != 0)
{
int i = 0;
for (; i < (args.length - 1); i++)
{
buf.append(args[i] + ", ");
}
buf.append(args[i]);
}
buf.append(')');
return buf.toString();
}
@Override
public List<String> getMimeTypes()
{
return mimeTypes;
}
@Override
public List<String> getNames()
{
return names;
}
@Override
public String getOutputStatement(String toDisplay)
{
final StringBuilder buf = new StringBuilder();
buf.append("System.out.print(\"");
final int len = toDisplay.length();
for (int i = 0; i < len; i++)
{
final char ch = toDisplay.charAt(i);
switch (ch)
{
case 34: // '"'
{
buf.append("\\\"");
break;
}
case 92: // '\\'
{
buf.append("\\\\");
break;
}
default:
{
buf.append(ch);
break;
}
}
}
buf.append("\");");
return buf.toString();
}
@Override
public String getParameter(String key)
{
if (key.equals("javax.script.engine"))
{
return getEngineName();
}
if (key.equals("javax.script.engine_version"))
{
return getEngineVersion();
}
if (key.equals("javax.script.name"))
{
return getEngineName();
}
if (key.equals("javax.script.language"))
{
return getLanguageName();
}
if (key.equals("javax.script.language_version"))
{
return getLanguageVersion();
}
if (key.equals("THREADING"))
{
return "MULTITHREADED";
}
return null;
}
@Override
public String getProgram(String... statements)
{
// we generate a Main class with main method
// that contains all the given statements
final StringBuilder buf = new StringBuilder();
buf.append("class ");
buf.append(getClassName());
buf.append(" {\n");
buf.append(" public static void main(String[] args) {\n");
if (statements.length != 0)
{
for (String statement : statements)
{
buf.append(" ");
buf.append(statement);
buf.append(";\n");
}
}
buf.append(" }\n");
buf.append("}\n");
return buf.toString();
}
@Override
public ScriptEngine getScriptEngine()
{
final JavaScriptEngine engine = new JavaScriptEngine();
engine.setFactory(this);
return engine;
}
// used to generate a unique class name in getProgram
private String getClassName()
{
return "com_sun_script_java_Main$" + getNextClassNumber();
}
private static synchronized long getNextClassNumber()
{
return nextClassNum++;
}
private static long nextClassNum = 0L;
private static List<String> names;
private static List<String> extensions;
private static List<String> mimeTypes;
static
{
names = new ArrayList<>(1);
names.add("java");
names = Collections.unmodifiableList(names);
extensions = names;
mimeTypes = new ArrayList<>(0);
mimeTypes = Collections.unmodifiableList(mimeTypes);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: Redistributions of source code
* must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. Neither the name of the Sun Microsystems nor the names of
* is contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.l2jmobius.commons.javaengine;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* ClassLoader that loads .class bytes from memory.
* @author A. Sundararajan
*/
public final class MemoryClassLoader extends URLClassLoader
{
private final Map<String, byte[]> classBytes;
public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath, ClassLoader parent)
{
super(toURLs(classPath), parent);
this.classBytes = classBytes;
}
public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath)
{
this(classBytes, classPath, null);
}
public Class<?> load(String className) throws ClassNotFoundException
{
return loadClass(className);
}
public Iterable<Class<?>> loadAll() throws ClassNotFoundException
{
final List<Class<?>> classes = new ArrayList<>(classBytes.size());
for (String name : classBytes.keySet())
{
classes.add(loadClass(name));
}
return classes;
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException
{
final byte buf[] = classBytes.get(className);
if (buf != null)
{
// clear the bytes in map -- we don't need it anymore
classBytes.put(className, null);
return defineClass(className, buf, 0, buf.length);
}
return super.findClass(className);
}
private static URL[] toURLs(String classPath)
{
if (classPath == null)
{
return new URL[0];
}
final List<URL> list = new ArrayList<>();
final StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
while (st.hasMoreTokens())
{
final String token = st.nextToken();
final File file = new File(token);
if (file.exists())
{
try
{
list.add(file.toURI().toURL());
}
catch (MalformedURLException mue)
{
//
}
}
else
{
try
{
list.add(new URL(token));
}
catch (MalformedURLException mue)
{
//
}
}
}
final URL res[] = new URL[list.size()];
list.toArray(res);
return res;
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: Redistributions of source code
* must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. Neither the name of the Sun Microsystems nor the names of
* is contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.l2jmobius.commons.javaengine;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager;
/**
* JavaFileManager that keeps compiled .class bytes in memory.
* @author A. Sundararajan
*/
public final class MemoryJavaFileManager extends EclipseFileManager
{
private static final String EXT = ".java";
protected Map<String, byte[]> classBytes;
public MemoryJavaFileManager()
{
super(null, null);
classBytes = new HashMap<>();
}
public Map<String, byte[]> getClassBytes()
{
return classBytes;
}
@Override
public void close()
{
classBytes = new HashMap<>();
}
@Override
public void flush()
{
}
/**
* A file object used to represent Java source coming from a string.
*/
private static class StringInputBuffer extends SimpleJavaFileObject
{
final String code;
StringInputBuffer(String name, String code)
{
super(toURI(name), Kind.SOURCE);
this.code = code;
}
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors)
{
return CharBuffer.wrap(code);
}
}
/**
* A file object that stores Java bytecode into the classBytes map.
*/
private class ClassOutputBuffer extends SimpleJavaFileObject
{
protected final String name;
ClassOutputBuffer(String name)
{
super(toURI(name), Kind.CLASS);
this.name = name;
}
@Override
public OutputStream openOutputStream()
{
return new FilterOutputStream(new ByteArrayOutputStream())
{
@Override
public void close() throws IOException
{
out.close();
classBytes.put(name, ((ByteArrayOutputStream) out).toByteArray());
}
};
}
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, Kind kind, FileObject sibling) throws IOException
{
if (kind == Kind.CLASS)
{
return new ClassOutputBuffer(className.replace('/', '.'));
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
static JavaFileObject makeStringSource(String name, String code)
{
return new StringInputBuffer(name, code);
}
static URI toURI(String name)
{
final File file = new File(name);
if (file.exists())
{
return file.toURI();
}
try
{
final StringBuilder newUri = new StringBuilder();
newUri.append("file:///");
newUri.append(name.replace('.', '/'));
if (name.endsWith(EXT))
{
newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
}
return URI.create(newUri.toString());
}
catch (Exception exp)
{
return URI.create("file:///com/sun/script/java/java_source");
}
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public abstract class AbstractPacket<T extends MMOClient<?>>
{
protected ByteBuffer _buf;
T _client;
public final T getClient()
{
return _client;
}
}

View File

@ -0,0 +1,27 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.nio.channels.SocketChannel;
/**
* @author KenM
*/
public interface IAcceptFilter
{
boolean accept(SocketChannel sc);
}

View File

@ -0,0 +1,26 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
/**
* @author KenM
* @param <T>
*/
public interface IClientFactory<T extends MMOClient<?>>
{
T create(MMOConnection<T> con);
}

View File

@ -0,0 +1,26 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
/**
* @author KenM
* @param <T>
*/
public interface IMMOExecutor<T extends MMOClient<?>>
{
void execute(ReceivablePacket<T> packet);
}

View File

@ -0,0 +1,28 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public interface IPacketHandler<T extends MMOClient<?>>
{
ReceivablePacket<T> handlePacket(ByteBuffer buf, T client);
}

View File

@ -0,0 +1,46 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public abstract class MMOClient<T extends MMOConnection<?>>
{
private final T _con;
public MMOClient(T con)
{
_con = con;
}
public T getConnection()
{
return _con;
}
public abstract boolean decrypt(ByteBuffer buf, int size);
public abstract boolean encrypt(ByteBuffer buf, int size);
protected abstract void onDisconnection();
protected abstract void onForcedDisconnection();
}

View File

@ -0,0 +1,283 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;
/**
* @author KenM
* @param <T>
*/
public class MMOConnection<T extends MMOClient<?>>
{
private final SelectorThread<T> _selectorThread;
private final Socket _socket;
private final InetAddress _address;
private final ReadableByteChannel _readableByteChannel;
private final WritableByteChannel _writableByteChannel;
private final int _port;
private final NioNetStackList<SendablePacket<T>> _sendQueue;
private final SelectionKey _selectionKey;
private ByteBuffer _readBuffer;
private ByteBuffer _primaryWriteBuffer;
private ByteBuffer _secondaryWriteBuffer;
private volatile boolean _pendingClose;
private T _client;
public MMOConnection(SelectorThread<T> selectorThread, Socket socket, SelectionKey key, boolean tcpNoDelay)
{
_selectorThread = selectorThread;
_socket = socket;
_address = socket.getInetAddress();
_readableByteChannel = socket.getChannel();
_writableByteChannel = socket.getChannel();
_port = socket.getPort();
_selectionKey = key;
_sendQueue = new NioNetStackList<>();
try
{
_socket.setTcpNoDelay(tcpNoDelay);
}
catch (SocketException e)
{
e.printStackTrace();
}
}
final void setClient(T client)
{
_client = client;
}
public final T getClient()
{
return _client;
}
public final void sendPacket(SendablePacket<T> sp)
{
sp._client = _client;
if (_pendingClose)
{
return;
}
synchronized (getSendQueue())
{
_sendQueue.addLast(sp);
}
if (!_sendQueue.isEmpty())
{
try
{
_selectionKey.interestOps(_selectionKey.interestOps() | SelectionKey.OP_WRITE);
}
catch (CancelledKeyException e)
{
// ignore
}
}
}
final SelectionKey getSelectionKey()
{
return _selectionKey;
}
public final InetAddress getInetAddress()
{
return _address;
}
public final int getPort()
{
return _port;
}
final void close() throws IOException
{
_socket.close();
}
final int read(ByteBuffer buf) throws IOException
{
return _readableByteChannel.read(buf);
}
final int write(ByteBuffer buf) throws IOException
{
return _writableByteChannel.write(buf);
}
final void createWriteBuffer(ByteBuffer buf)
{
if (_primaryWriteBuffer == null)
{
_primaryWriteBuffer = _selectorThread.getPooledBuffer();
_primaryWriteBuffer.put(buf);
}
else
{
final ByteBuffer temp = _selectorThread.getPooledBuffer();
temp.put(buf);
final int remaining = temp.remaining();
_primaryWriteBuffer.flip();
final int limit = _primaryWriteBuffer.limit();
if (remaining >= _primaryWriteBuffer.remaining())
{
temp.put(_primaryWriteBuffer);
_selectorThread.recycleBuffer(_primaryWriteBuffer);
}
else
{
_primaryWriteBuffer.limit(remaining);
temp.put(_primaryWriteBuffer);
_primaryWriteBuffer.limit(limit);
_primaryWriteBuffer.compact();
_secondaryWriteBuffer = _primaryWriteBuffer;
}
_primaryWriteBuffer = temp;
}
}
final boolean hasPendingWriteBuffer()
{
return _primaryWriteBuffer != null;
}
final void movePendingWriteBufferTo(ByteBuffer dest)
{
_primaryWriteBuffer.flip();
dest.put(_primaryWriteBuffer);
_selectorThread.recycleBuffer(_primaryWriteBuffer);
_primaryWriteBuffer = _secondaryWriteBuffer;
_secondaryWriteBuffer = null;
}
final void setReadBuffer(ByteBuffer buf)
{
_readBuffer = buf;
}
final ByteBuffer getReadBuffer()
{
return _readBuffer;
}
public final boolean isClosed()
{
return _pendingClose;
}
final NioNetStackList<SendablePacket<T>> getSendQueue()
{
return _sendQueue;
}
/*
* final SendablePacket<T> getClosePacket() { return _closePacket; }
*/
@SuppressWarnings("unchecked")
public final void close(SendablePacket<T> sp)
{
close(new SendablePacket[]
{
sp
});
}
public final void close(SendablePacket<T>[] closeList)
{
if (_pendingClose)
{
return;
}
synchronized (getSendQueue())
{
if (!_pendingClose)
{
_pendingClose = true;
_sendQueue.clear();
for (SendablePacket<T> sp : closeList)
{
_sendQueue.addLast(sp);
}
}
}
try
{
_selectionKey.interestOps(_selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
}
catch (CancelledKeyException e)
{
// ignore
}
// _closePacket = sp;
_selectorThread.closeConnection(this);
}
final void releaseBuffers()
{
if (_primaryWriteBuffer != null)
{
_selectorThread.recycleBuffer(_primaryWriteBuffer);
_primaryWriteBuffer = null;
if (_secondaryWriteBuffer != null)
{
_selectorThread.recycleBuffer(_secondaryWriteBuffer);
_secondaryWriteBuffer = null;
}
}
if (_readBuffer != null)
{
_selectorThread.recycleBuffer(_readBuffer);
_readBuffer = null;
}
}
}

View File

@ -0,0 +1,101 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
/**
* @author Forsaiken
* @param <E>
*/
public final class NioNetStackList<E>
{
private final NioNetStackNode _start = new NioNetStackNode();
private final NioNetStackNodeBuf _buf = new NioNetStackNodeBuf();
private NioNetStackNode _end = new NioNetStackNode();
public NioNetStackList()
{
clear();
}
public final void addLast(E elem)
{
final NioNetStackNode newEndNode = _buf.removeFirst();
_end._value = elem;
_end._next = newEndNode;
_end = newEndNode;
}
public final E removeFirst()
{
final NioNetStackNode old = _start._next;
final E value = old._value;
_start._next = old._next;
_buf.addLast(old);
return value;
}
public final boolean isEmpty()
{
return _start._next == _end;
}
public final void clear()
{
_start._next = _end;
}
protected final class NioNetStackNode
{
protected NioNetStackNode _next;
protected E _value;
}
private final class NioNetStackNodeBuf
{
private final NioNetStackNode _start = new NioNetStackNode();
private NioNetStackNode _end = new NioNetStackNode();
NioNetStackNodeBuf()
{
_start._next = _end;
}
final void addLast(NioNetStackNode node)
{
node._next = null;
node._value = null;
_end._next = node;
_end = node;
}
final NioNetStackNode removeFirst()
{
if (_start._next == _end)
{
return new NioNetStackNode();
}
final NioNetStackNode old = _start._next;
_start._next = old._next;
return old;
}
}
}

View File

@ -0,0 +1,61 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.nio.BufferOverflowException;
/**
* @author Forsaiken
*/
public final class NioNetStringBuffer
{
private final char[] _buf;
private final int _size;
private int _len;
public NioNetStringBuffer(int size)
{
_buf = new char[size];
_size = size;
_len = 0;
}
public final void clear()
{
_len = 0;
}
public final void append(char c)
{
if (_len < _size)
{
_buf[_len++] = c;
}
else
{
throw new BufferOverflowException();
}
}
@Override
public final String toString()
{
return new String(_buf, 0, _len);
}
}

View File

@ -0,0 +1,139 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public abstract class ReceivablePacket<T extends MMOClient<?>>extends AbstractPacket<T> implements Runnable
{
NioNetStringBuffer _sbuf;
protected ReceivablePacket()
{
}
protected abstract boolean read();
@Override
public abstract void run();
/**
* Reads <B>byte[]</B> from the buffer. <BR>
* Reads as many bytes as the length of the array.
* @param dst : the byte array which will be filled with the data.
*/
protected final void readB(byte[] dst)
{
_buf.get(dst);
}
/**
* Reads <B>byte[]</B> from the buffer. <BR>
* Reads as many bytes as the given length (len). Starts to fill the byte array from the given offset to <B>offset</B> + <B>len</B>.
* @param dst : the byte array which will be filled with the data.
* @param offset : starts to fill the byte array from the given offset.
* @param len : the given length of bytes to be read.
*/
protected final void readB(byte[] dst, int offset, int len)
{
_buf.get(dst, offset, len);
}
/**
* Reads <B>byte</B> from the buffer. <BR>
* 8bit integer (00)
* @return
*/
protected final int readC()
{
return _buf.get() & 0xFF;
}
/**
* Reads <B>short</B> from the buffer. <BR>
* 16bit integer (00 00)
* @return
*/
protected final int readH()
{
return _buf.getShort() & 0xFFFF;
}
/**
* Reads <B>int</B> from the buffer. <BR>
* 32bit integer (00 00 00 00)
* @return
*/
protected final int readD()
{
return _buf.getInt();
}
/**
* Reads <B>long</B> from the buffer. <BR>
* 64bit integer (00 00 00 00 00 00 00 00)
* @return
*/
protected final long readQ()
{
return _buf.getLong();
}
/**
* Reads <B>double</B> from the buffer. <BR>
* 64bit double precision float (00 00 00 00 00 00 00 00)
* @return
*/
protected final double readF()
{
return _buf.getDouble();
}
/**
* Reads <B>String</B> from the buffer.
* @return
*/
protected final String readS()
{
_sbuf.clear();
char ch;
while ((ch = _buf.getChar()) != 0)
{
_sbuf.append(ch);
}
return _sbuf.toString();
}
/**
* packet forge purpose
* @param data
* @param client
* @param sBuffer
*/
public void setBuffers(ByteBuffer data, T client, NioNetStringBuffer sBuffer)
{
_buf = data;
_client = client;
_sbuf = sBuffer;
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
/**
* @author KenM
*/
public final class SelectorConfig
{
public int READ_BUFFER_SIZE = 64 * 1024;
public int WRITE_BUFFER_SIZE = 64 * 1024;
public int HELPER_BUFFER_COUNT = 20;
public int HELPER_BUFFER_SIZE = 64 * 1024;
/**
* Server will try to send MAX_SEND_PER_PASS packets per socket write call<br>
* however it may send less if the write buffer was filled before achieving this value.
*/
public int MAX_SEND_PER_PASS = 10;
/**
* Server will try to read MAX_READ_PER_PASS packets per socket read call<br>
* however it may read less if the read buffer was empty before achieving this value.
*/
public int MAX_READ_PER_PASS = 10;
/**
* Defines how much time (in milis) should the selector sleep, an higher value increases throughput but also increases latency(to a max of the sleep value itself).<BR>
* Also an extremely high value(usually > 100) will decrease throughput due to the server not doing enough sends per second (depends on max sends per pass).<BR>
* <BR>
* Recommended values:<BR>
* 1 for minimal latency.<BR>
* 10-30 for an latency/troughput trade-off based on your needs.<BR>
*/
public int SLEEP_TIME = 10;
/**
* Used to enable/disable TCP_NODELAY which disable/enable Nagle's algorithm.<BR>
* <BR>
* Nagle's algorithm try to conserve bandwidth by minimizing the number of segments that are sent. When applications wish to decrease network latency and increase performance, they can disable Nagle's algorithm (that is enable TCP_NODELAY). Data will be sent earlier, at the cost of an increase
* in bandwidth consumption. The Nagle's algorithm is described in RFC 896.<BR>
* <BR>
* Summary, data will be sent earlier, thus lowering the ping, at the cost of a small increase in bandwidth consumption.
*/
public boolean TCP_NODELAY = true;
}

View File

@ -0,0 +1,714 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
/**
* Parts of design based on network core from WoodenGil
* @param <T>
* @author KenM
*/
public final class SelectorThread<T extends MMOClient<?>>extends Thread
{
// default BYTE_ORDER
private static final ByteOrder BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
// default HEADER_SIZE
private static final int HEADER_SIZE = 2;
// Selector
private final Selector _selector;
// Implementations
private final IPacketHandler<T> _packetHandler;
private final IMMOExecutor<T> _executor;
private final IClientFactory<T> _clientFactory;
private final IAcceptFilter _acceptFilter;
// Configurations
private final int HELPER_BUFFER_SIZE;
private final int HELPER_BUFFER_COUNT;
private final int MAX_SEND_PER_PASS;
private final int MAX_READ_PER_PASS;
private final long SLEEP_TIME;
public boolean TCP_NODELAY;
// Main Buffers
private final ByteBuffer DIRECT_WRITE_BUFFER;
private final ByteBuffer WRITE_BUFFER;
private final ByteBuffer READ_BUFFER;
// String Buffer
private final NioNetStringBuffer STRING_BUFFER;
// ByteBuffers General Purpose Pool
private final LinkedList<ByteBuffer> _bufferPool;
// Pending Close
private final NioNetStackList<MMOConnection<T>> _pendingClose;
private boolean _shutdown;
public SelectorThread(SelectorConfig sc, IMMOExecutor<T> executor, IPacketHandler<T> packetHandler, IClientFactory<T> clientFactory, IAcceptFilter acceptFilter) throws IOException
{
super.setName("SelectorThread-" + super.getId());
HELPER_BUFFER_SIZE = sc.HELPER_BUFFER_SIZE;
HELPER_BUFFER_COUNT = sc.HELPER_BUFFER_COUNT;
MAX_SEND_PER_PASS = sc.MAX_SEND_PER_PASS;
MAX_READ_PER_PASS = sc.MAX_READ_PER_PASS;
SLEEP_TIME = sc.SLEEP_TIME;
TCP_NODELAY = sc.TCP_NODELAY;
DIRECT_WRITE_BUFFER = ByteBuffer.allocateDirect(sc.WRITE_BUFFER_SIZE).order(BYTE_ORDER);
WRITE_BUFFER = ByteBuffer.wrap(new byte[sc.WRITE_BUFFER_SIZE]).order(BYTE_ORDER);
READ_BUFFER = ByteBuffer.wrap(new byte[sc.READ_BUFFER_SIZE]).order(BYTE_ORDER);
STRING_BUFFER = new NioNetStringBuffer(64 * 1024);
_pendingClose = new NioNetStackList<>();
_bufferPool = new LinkedList<>();
for (int i = 0; i < HELPER_BUFFER_COUNT; i++)
{
_bufferPool.addLast(ByteBuffer.wrap(new byte[HELPER_BUFFER_SIZE]).order(BYTE_ORDER));
}
_acceptFilter = acceptFilter;
_packetHandler = packetHandler;
_clientFactory = clientFactory;
_executor = executor;
_selector = Selector.open();
}
public final void openServerSocket(InetAddress address, int tcpPort) throws IOException
{
final ServerSocketChannel selectable = ServerSocketChannel.open();
selectable.configureBlocking(false);
final ServerSocket ss = selectable.socket();
if (address != null)
{
ss.bind(new InetSocketAddress(address, tcpPort));
}
else
{
ss.bind(new InetSocketAddress(tcpPort));
}
selectable.register(_selector, SelectionKey.OP_ACCEPT);
}
final ByteBuffer getPooledBuffer()
{
if (_bufferPool.isEmpty())
{
return ByteBuffer.wrap(new byte[HELPER_BUFFER_SIZE]).order(BYTE_ORDER);
}
return _bufferPool.removeFirst();
}
final void recycleBuffer(ByteBuffer buf)
{
if (_bufferPool.size() < HELPER_BUFFER_COUNT)
{
buf.clear();
_bufferPool.addLast(buf);
}
}
@SuppressWarnings("unchecked")
@Override
public final void run()
{
int selectedKeysCount = 0;
SelectionKey key;
MMOConnection<T> con;
Iterator<SelectionKey> selectedKeys;
while (!_shutdown)
{
try
{
selectedKeysCount = _selector.selectNow();
}
catch (IOException e)
{
e.printStackTrace();
}
if (selectedKeysCount > 0)
{
selectedKeys = _selector.selectedKeys().iterator();
while (selectedKeys.hasNext())
{
key = selectedKeys.next();
selectedKeys.remove();
con = (MMOConnection<T>) key.attachment();
switch (key.readyOps())
{
case SelectionKey.OP_CONNECT:
{
finishConnection(key, con);
break;
}
case SelectionKey.OP_ACCEPT:
{
acceptConnection(key, con);
break;
}
case SelectionKey.OP_READ:
{
readPacket(key, con);
break;
}
case SelectionKey.OP_WRITE:
{
writePacket(key, con);
break;
}
case SelectionKey.OP_READ | SelectionKey.OP_WRITE:
{
writePacket(key, con);
if (key.isValid())
{
readPacket(key, con);
}
break;
}
}
}
}
synchronized (_pendingClose)
{
while (!_pendingClose.isEmpty())
{
try
{
con = _pendingClose.removeFirst();
writeClosePacket(con);
closeConnectionImpl(con.getSelectionKey(), con);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
try
{
Thread.sleep(SLEEP_TIME);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
closeSelectorThread();
}
private final void finishConnection(SelectionKey key, MMOConnection<T> con)
{
try
{
((SocketChannel) key.channel()).finishConnect();
}
catch (IOException e)
{
con.getClient().onForcedDisconnection();
closeConnectionImpl(key, con);
}
// key might have been invalidated on finishConnect()
if (key.isValid())
{
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
key.interestOps(key.interestOps() & ~SelectionKey.OP_CONNECT);
}
}
private final void acceptConnection(SelectionKey key, MMOConnection<T> con)
{
final ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc;
try
{
while ((sc = ssc.accept()) != null)
{
if ((_acceptFilter == null) || _acceptFilter.accept(sc))
{
sc.configureBlocking(false);
final SelectionKey clientKey = sc.register(_selector, SelectionKey.OP_READ);
con = new MMOConnection<>(this, sc.socket(), clientKey, TCP_NODELAY);
con.setClient(_clientFactory.create(con));
clientKey.attach(con);
}
else
{
sc.socket().close();
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
private final void readPacket(SelectionKey key, MMOConnection<T> con)
{
if (con.isClosed())
{
return;
}
ByteBuffer buf = con.getReadBuffer();
if (buf == null)
{
buf = READ_BUFFER;
}
// if we try to to do a read with no space in the buffer it will read 0 bytes going into infinite loop
if (buf.position() == buf.limit())
{
System.exit(0);
}
int result = -2;
try
{
result = con.read(buf);
}
catch (IOException e)
{
// error handling goes bellow
}
if (result > 0)
{
buf.flip();
final T client = con.getClient();
for (int i = 0; i < MAX_READ_PER_PASS; i++)
{
if (!tryReadPacket(key, client, buf, con))
{
return;
}
}
// only reachable if MAX_READ_PER_PASS has been reached
// check if there are some more bytes in buffer
// and allocate/compact to prevent content lose.
if (buf.remaining() > 0)
{
// did we use the READ_BUFFER ?
if (buf == READ_BUFFER)
{
// move the pending byte to the connections READ_BUFFER
allocateReadBuffer(con);
}
else
{
// move the first byte to the beginning :)
buf.compact();
}
}
}
else
{
switch (result)
{
case 0:
case -1:
{
closeConnectionImpl(key, con);
break;
}
case -2:
{
con.getClient().onForcedDisconnection();
closeConnectionImpl(key, con);
break;
}
}
}
}
private final boolean tryReadPacket(SelectionKey key, T client, ByteBuffer buf, MMOConnection<T> con)
{
switch (buf.remaining())
{
case 0:
{
// buffer is full nothing to read
return false;
}
case 1:
{
// we don`t have enough data for header so we need to read
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
// did we use the READ_BUFFER ?
if (buf == READ_BUFFER)
{
// move the pending byte to the connections READ_BUFFER
allocateReadBuffer(con);
}
else
{
// move the first byte to the beginning :)
buf.compact();
}
return false;
}
default:
{
// data size excluding header size :>
final int dataPending = (buf.getShort() & 0xFFFF) - HEADER_SIZE;
// do we got enough bytes for the packet?
if (dataPending <= buf.remaining())
{
// avoid parsing dummy packets (packets without body)
if (dataPending > 0)
{
final int pos = buf.position();
parseClientPacket(pos, buf, dataPending, client);
buf.position(pos + dataPending);
}
if (buf.hasRemaining())
{
return true;
}
if (buf != READ_BUFFER)
{
con.setReadBuffer(null);
recycleBuffer(buf);
}
else
{
READ_BUFFER.clear();
}
return false;
}
// we don`t have enough bytes for the dataPacket so we need to read
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
// move it`s position
buf.position(buf.position() - HEADER_SIZE);
// did we use the READ_BUFFER ?
if (buf == READ_BUFFER)
{
// move the pending byte to the connections READ_BUFFER
allocateReadBuffer(con);
}
else
{
buf.compact();
}
return false;
}
}
}
private final void allocateReadBuffer(MMOConnection<T> con)
{
con.setReadBuffer(getPooledBuffer().put(READ_BUFFER));
READ_BUFFER.clear();
}
private final void parseClientPacket(int pos, ByteBuffer buf, int dataSize, T client)
{
if (!client.decrypt(buf, dataSize) || !buf.hasRemaining())
{
return;
}
// apply limit
final int limit = buf.limit();
buf.limit(pos + dataSize);
final ReceivablePacket<T> cp = _packetHandler.handlePacket(buf, client);
if (cp != null)
{
cp._buf = buf;
cp._sbuf = STRING_BUFFER;
cp._client = client;
if (cp.read())
{
_executor.execute(cp);
}
cp._buf = null;
cp._sbuf = null;
}
buf.limit(limit);
}
private final void writeClosePacket(MMOConnection<T> con)
{
SendablePacket<T> sp;
synchronized (con.getSendQueue())
{
if (con.getSendQueue().isEmpty())
{
return;
}
while ((sp = con.getSendQueue().removeFirst()) != null)
{
WRITE_BUFFER.clear();
putPacketIntoWriteBuffer(con.getClient(), sp);
WRITE_BUFFER.flip();
try
{
con.write(WRITE_BUFFER);
}
catch (IOException e)
{
// error handling goes on the if bellow
}
}
}
}
protected final void writePacket(SelectionKey key, MMOConnection<T> con)
{
if (!prepareWriteBuffer(con))
{
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
return;
}
DIRECT_WRITE_BUFFER.flip();
final int size = DIRECT_WRITE_BUFFER.remaining();
int result = -1;
try
{
result = con.write(DIRECT_WRITE_BUFFER);
}
catch (IOException e)
{
// error handling goes on the if bellow
}
// check if no error happened
if (result >= 0)
{
// check if we written everything
if (result == size)
{
// complete write
synchronized (con.getSendQueue())
{
if (con.getSendQueue().isEmpty() && !con.hasPendingWriteBuffer())
{
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
}
}
}
else
{
// incomplete write
con.createWriteBuffer(DIRECT_WRITE_BUFFER);
}
}
else
{
con.getClient().onForcedDisconnection();
closeConnectionImpl(key, con);
}
}
private final boolean prepareWriteBuffer(MMOConnection<T> con)
{
boolean hasPending = false;
DIRECT_WRITE_BUFFER.clear();
// if there is pending content add it
if (con.hasPendingWriteBuffer())
{
con.movePendingWriteBufferTo(DIRECT_WRITE_BUFFER);
hasPending = true;
}
if ((DIRECT_WRITE_BUFFER.remaining() > 1) && !con.hasPendingWriteBuffer())
{
final NioNetStackList<SendablePacket<T>> sendQueue = con.getSendQueue();
final T client = con.getClient();
SendablePacket<T> sp;
for (int i = 0; i < MAX_SEND_PER_PASS; i++)
{
synchronized (con.getSendQueue())
{
if (sendQueue.isEmpty())
{
sp = null;
}
else
{
sp = sendQueue.removeFirst();
}
}
if (sp == null)
{
break;
}
hasPending = true;
// put into WriteBuffer
putPacketIntoWriteBuffer(client, sp);
WRITE_BUFFER.flip();
if (DIRECT_WRITE_BUFFER.remaining() < WRITE_BUFFER.limit())
{
con.createWriteBuffer(WRITE_BUFFER);
break;
}
DIRECT_WRITE_BUFFER.put(WRITE_BUFFER);
}
}
return hasPending;
}
private final void putPacketIntoWriteBuffer(T client, SendablePacket<T> sp)
{
WRITE_BUFFER.clear();
// reserve space for the size
final int headerPos = WRITE_BUFFER.position();
final int dataPos = headerPos + HEADER_SIZE;
WRITE_BUFFER.position(dataPos);
// set the write buffer
sp._buf = WRITE_BUFFER;
// set the client.
sp._client = client;
// write content to buffer
sp.write();
// delete the write buffer
sp._buf = null;
// size (inclusive header)
int dataSize = WRITE_BUFFER.position() - dataPos;
WRITE_BUFFER.position(dataPos);
client.encrypt(WRITE_BUFFER, dataSize);
// recalculate size after encryption
dataSize = WRITE_BUFFER.position() - dataPos;
WRITE_BUFFER.position(headerPos);
// write header
WRITE_BUFFER.putShort((short) (dataSize + HEADER_SIZE));
WRITE_BUFFER.position(dataPos + dataSize);
}
final void closeConnection(MMOConnection<T> con)
{
synchronized (_pendingClose)
{
_pendingClose.addLast(con);
}
}
private final void closeConnectionImpl(SelectionKey key, MMOConnection<T> con)
{
try
{
// notify connection
con.getClient().onDisconnection();
}
finally
{
try
{
// close socket and the SocketChannel
con.close();
}
catch (IOException e)
{
// ignore, we are closing anyway
}
finally
{
con.releaseBuffers();
// clear attachment
key.attach(null);
// cancel key
key.cancel();
}
}
}
public final void shutdown()
{
_shutdown = true;
}
protected void closeSelectorThread()
{
for (SelectionKey key : _selector.keys())
{
try
{
key.channel().close();
}
catch (IOException e)
{
// ignore
}
}
try
{
_selector.close();
}
catch (IOException e)
{
// Ignore
}
}
}

View File

@ -0,0 +1,139 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.commons.mmocore;
/**
* @author KenM
* @param <T>
*/
public abstract class SendablePacket<T extends MMOClient<?>>extends AbstractPacket<T>
{
protected final void putInt(int value)
{
_buf.putInt(value);
}
protected final void putDouble(double value)
{
_buf.putDouble(value);
}
protected final void putFloat(float value)
{
_buf.putFloat(value);
}
/**
* Write <B>byte</B> to the buffer. <BR>
* 8bit integer (00)
* @param data
*/
protected final void writeC(boolean data)
{
_buf.put((byte) (data ? 0x01 : 0x00));
}
/**
* Write <B>byte</B> to the buffer. <BR>
* 8bit integer (00)
* @param data
*/
protected final void writeC(int data)
{
_buf.put((byte) data);
}
/**
* Write <B>double</B> to the buffer. <BR>
* 64bit double precision float (00 00 00 00 00 00 00 00)
* @param value
*/
protected final void writeF(double value)
{
_buf.putDouble(value);
}
/**
* Write <B>short</B> to the buffer. <BR>
* 16bit integer (00 00)
* @param value
*/
protected final void writeH(int value)
{
_buf.putShort((short) value);
}
/**
* Write <B>int</B> to the buffer. <BR>
* 32bit integer (00 00 00 00)
* @param value
*/
protected final void writeD(int value)
{
_buf.putInt(value);
}
/**
* Write <B>int</B> to the buffer. <BR>
* 32bit integer (00 00 00 00)
* @param value
*/
protected final void writeD(boolean value)
{
_buf.putInt(value ? 0x01 : 0x00);
}
/**
* Write <B>long</B> to the buffer. <BR>
* 64bit integer (00 00 00 00 00 00 00 00)
* @param value
*/
protected final void writeQ(long value)
{
_buf.putLong(value);
}
/**
* Write <B>byte[]</B> to the buffer. <BR>
* 8bit integer array (00 ...)
* @param data
*/
protected final void writeB(byte[] data)
{
_buf.put(data);
}
/**
* Write <B>String</B> to the buffer.
* @param text
*/
protected final void writeS(String text)
{
if (text != null)
{
final int len = text.length();
for (int i = 0; i < len; i++)
{
_buf.putChar(text.charAt(i));
}
}
_buf.putChar('\000');
}
protected abstract void write();
}

View File

@ -0,0 +1,111 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.model.L2Clan;
import com.l2jmobius.gameserver.model.entity.Fort;
import com.l2jmobius.gameserver.model.itemcontainer.Inventory;
/**
* Class managing periodical events with castle
* @author Vice - 2008
*/
public class FortUpdater implements Runnable
{
protected static Logger _log = Logger.getLogger(FortUpdater.class.getName());
private final L2Clan _clan;
private final Fort _fort;
private int _runCount;
private final UpdaterType _updaterType;
public enum UpdaterType
{
MAX_OWN_TIME, // gives fort back to NPC clan
PERIODIC_UPDATE // raise blood oath/supply level
}
public FortUpdater(Fort fort, L2Clan clan, int runCount, UpdaterType ut)
{
_fort = fort;
_clan = clan;
_runCount = runCount;
_updaterType = ut;
}
@Override
public void run()
{
try
{
switch (_updaterType)
{
case PERIODIC_UPDATE:
{
_runCount++;
if ((_fort.getOwnerClan() == null) || (_fort.getOwnerClan() != _clan))
{
return;
}
_fort.getOwnerClan().increaseBloodOathCount();
if (_fort.getFortState() == 2)
{
if (_clan.getWarehouse().getAdena() >= Config.FS_FEE_FOR_CASTLE)
{
_clan.getWarehouse().destroyItemByItemId("FS_fee_for_Castle", Inventory.ADENA_ID, Config.FS_FEE_FOR_CASTLE, null, null);
_fort.getContractedCastle().addToTreasuryNoTax(Config.FS_FEE_FOR_CASTLE);
_fort.raiseSupplyLvL();
}
else
{
_fort.setFortState(1, 0);
}
}
_fort.saveFortVariables();
break;
}
case MAX_OWN_TIME:
{
if ((_fort.getOwnerClan() == null) || (_fort.getOwnerClan() != _clan))
{
return;
}
if (_fort.getOwnedTime() > (Config.FS_MAX_OWN_TIME * 3600))
{
_fort.removeOwner(true);
_fort.setFortState(0, 0);
}
break;
}
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "", e);
}
}
public int getRunCount()
{
return _runCount;
}
}

View File

@ -0,0 +1,497 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.awt.Toolkit;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.Server;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.commons.mmocore.SelectorConfig;
import com.l2jmobius.commons.mmocore.SelectorThread;
import com.l2jmobius.gameserver.cache.HtmCache;
import com.l2jmobius.gameserver.data.sql.impl.AnnouncementsTable;
import com.l2jmobius.gameserver.data.sql.impl.CharNameTable;
import com.l2jmobius.gameserver.data.sql.impl.CharSummonTable;
import com.l2jmobius.gameserver.data.sql.impl.ClanTable;
import com.l2jmobius.gameserver.data.sql.impl.CrestTable;
import com.l2jmobius.gameserver.data.sql.impl.NpcBufferTable;
import com.l2jmobius.gameserver.data.sql.impl.OfflineTradersTable;
import com.l2jmobius.gameserver.data.sql.impl.SummonSkillsTable;
import com.l2jmobius.gameserver.data.sql.impl.TeleportLocationTable;
import com.l2jmobius.gameserver.data.xml.impl.AdminData;
import com.l2jmobius.gameserver.data.xml.impl.ArmorSetsData;
import com.l2jmobius.gameserver.data.xml.impl.BuyListData;
import com.l2jmobius.gameserver.data.xml.impl.CategoryData;
import com.l2jmobius.gameserver.data.xml.impl.ClassListData;
import com.l2jmobius.gameserver.data.xml.impl.DoorData;
import com.l2jmobius.gameserver.data.xml.impl.EnchantItemData;
import com.l2jmobius.gameserver.data.xml.impl.EnchantItemGroupsData;
import com.l2jmobius.gameserver.data.xml.impl.EnchantItemHPBonusData;
import com.l2jmobius.gameserver.data.xml.impl.EnchantItemOptionsData;
import com.l2jmobius.gameserver.data.xml.impl.EnchantSkillGroupsData;
import com.l2jmobius.gameserver.data.xml.impl.ExperienceData;
import com.l2jmobius.gameserver.data.xml.impl.FishData;
import com.l2jmobius.gameserver.data.xml.impl.FishingMonstersData;
import com.l2jmobius.gameserver.data.xml.impl.FishingRodsData;
import com.l2jmobius.gameserver.data.xml.impl.HennaData;
import com.l2jmobius.gameserver.data.xml.impl.HitConditionBonusData;
import com.l2jmobius.gameserver.data.xml.impl.InitialEquipmentData;
import com.l2jmobius.gameserver.data.xml.impl.InitialShortcutData;
import com.l2jmobius.gameserver.data.xml.impl.KarmaData;
import com.l2jmobius.gameserver.data.xml.impl.MultisellData;
import com.l2jmobius.gameserver.data.xml.impl.NpcData;
import com.l2jmobius.gameserver.data.xml.impl.OptionData;
import com.l2jmobius.gameserver.data.xml.impl.PetDataTable;
import com.l2jmobius.gameserver.data.xml.impl.PlayerTemplateData;
import com.l2jmobius.gameserver.data.xml.impl.PlayerXpPercentLostData;
import com.l2jmobius.gameserver.data.xml.impl.RecipeData;
import com.l2jmobius.gameserver.data.xml.impl.SecondaryAuthData;
import com.l2jmobius.gameserver.data.xml.impl.SiegeScheduleData;
import com.l2jmobius.gameserver.data.xml.impl.SkillLearnData;
import com.l2jmobius.gameserver.data.xml.impl.SkillTreesData;
import com.l2jmobius.gameserver.data.xml.impl.StaticObjectData;
import com.l2jmobius.gameserver.data.xml.impl.TransformData;
import com.l2jmobius.gameserver.data.xml.impl.UIData;
import com.l2jmobius.gameserver.datatables.AugmentationData;
import com.l2jmobius.gameserver.datatables.BotReportTable;
import com.l2jmobius.gameserver.datatables.EventDroplist;
import com.l2jmobius.gameserver.datatables.ItemTable;
import com.l2jmobius.gameserver.datatables.MerchantPriceConfigTable;
import com.l2jmobius.gameserver.datatables.SkillData;
import com.l2jmobius.gameserver.datatables.SpawnTable;
import com.l2jmobius.gameserver.handler.EffectHandler;
import com.l2jmobius.gameserver.idfactory.IdFactory;
import com.l2jmobius.gameserver.instancemanager.AirShipManager;
import com.l2jmobius.gameserver.instancemanager.AntiFeedManager;
import com.l2jmobius.gameserver.instancemanager.BoatManager;
import com.l2jmobius.gameserver.instancemanager.CHSiegeManager;
import com.l2jmobius.gameserver.instancemanager.CastleManager;
import com.l2jmobius.gameserver.instancemanager.CastleManorManager;
import com.l2jmobius.gameserver.instancemanager.ClanHallAuctionManager;
import com.l2jmobius.gameserver.instancemanager.ClanHallManager;
import com.l2jmobius.gameserver.instancemanager.CoupleManager;
import com.l2jmobius.gameserver.instancemanager.CursedWeaponsManager;
import com.l2jmobius.gameserver.instancemanager.DayNightSpawnManager;
import com.l2jmobius.gameserver.instancemanager.DimensionalRiftManager;
import com.l2jmobius.gameserver.instancemanager.FishingChampionshipManager;
import com.l2jmobius.gameserver.instancemanager.FortManager;
import com.l2jmobius.gameserver.instancemanager.FortSiegeManager;
import com.l2jmobius.gameserver.instancemanager.FourSepulchersManager;
import com.l2jmobius.gameserver.instancemanager.GlobalVariablesManager;
import com.l2jmobius.gameserver.instancemanager.GrandBossManager;
import com.l2jmobius.gameserver.instancemanager.InstanceManager;
import com.l2jmobius.gameserver.instancemanager.ItemAuctionManager;
import com.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2jmobius.gameserver.instancemanager.MailManager;
import com.l2jmobius.gameserver.instancemanager.MapRegionManager;
import com.l2jmobius.gameserver.instancemanager.MercTicketManager;
import com.l2jmobius.gameserver.instancemanager.PetitionManager;
import com.l2jmobius.gameserver.instancemanager.PremiumManager;
import com.l2jmobius.gameserver.instancemanager.PunishmentManager;
import com.l2jmobius.gameserver.instancemanager.QuestManager;
import com.l2jmobius.gameserver.instancemanager.RaidBossPointsManager;
import com.l2jmobius.gameserver.instancemanager.RaidBossSpawnManager;
import com.l2jmobius.gameserver.instancemanager.ServerRestartManager;
import com.l2jmobius.gameserver.instancemanager.SiegeManager;
import com.l2jmobius.gameserver.instancemanager.SoDManager;
import com.l2jmobius.gameserver.instancemanager.SoIManager;
import com.l2jmobius.gameserver.instancemanager.TerritoryWarManager;
import com.l2jmobius.gameserver.instancemanager.WalkingManager;
import com.l2jmobius.gameserver.instancemanager.ZoneManager;
import com.l2jmobius.gameserver.model.AutoSpawnHandler;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.PartyMatchRoomList;
import com.l2jmobius.gameserver.model.PartyMatchWaitingList;
import com.l2jmobius.gameserver.model.entity.Hero;
import com.l2jmobius.gameserver.model.entity.TvTManager;
import com.l2jmobius.gameserver.model.events.EventDispatcher;
import com.l2jmobius.gameserver.model.olympiad.Olympiad;
import com.l2jmobius.gameserver.network.L2GameClient;
import com.l2jmobius.gameserver.network.L2GamePacketHandler;
import com.l2jmobius.gameserver.pathfinding.PathFinding;
import com.l2jmobius.gameserver.scripting.L2ScriptEngineManager;
import com.l2jmobius.gameserver.taskmanager.KnownListUpdateTaskManager;
import com.l2jmobius.gameserver.taskmanager.TaskManager;
import com.l2jmobius.status.Status;
import com.l2jmobius.util.DeadLockDetector;
import com.l2jmobius.util.IPv4Filter;
public final class GameServer
{
private static final Logger _log = Logger.getLogger(GameServer.class.getName());
// Local Constants
private static final String LOG_FOLDER = "log"; // Name of folder for log file
private static final String LOG_NAME = "./log.cfg"; // Name of log file
private final SelectorThread<L2GameClient> _selectorThread;
private final L2GamePacketHandler _gamePacketHandler;
private final DeadLockDetector _deadDetectThread;
public static GameServer gameServer;
public static final Calendar dateTimeServerStarted = Calendar.getInstance();
public GameServer() throws Exception
{
final long serverLoadStart = System.currentTimeMillis();
if (!IdFactory.getInstance().isInitialized())
{
_log.severe(getClass().getSimpleName() + ": Could not read object IDs from database. Please check your configuration.");
throw new Exception("Could not initialize the ID factory!");
}
ThreadPoolManager.getInstance();
EventDispatcher.getInstance();
new File("log/game").mkdirs();
// load script engines
printSection("Engines");
L2ScriptEngineManager.getInstance();
printSection("World");
// start game time control early
GameTimeController.init();
InstanceManager.getInstance();
L2World.getInstance();
MapRegionManager.getInstance();
AnnouncementsTable.getInstance();
GlobalVariablesManager.getInstance();
printSection("Data");
CategoryData.getInstance();
SecondaryAuthData.getInstance();
printSection("Skills");
EffectHandler.getInstance().executeScript();
EnchantSkillGroupsData.getInstance();
SkillTreesData.getInstance();
SkillData.getInstance();
SummonSkillsTable.getInstance();
printSection("Items");
ItemTable.getInstance();
EnchantItemGroupsData.getInstance();
EnchantItemData.getInstance();
EnchantItemOptionsData.getInstance();
OptionData.getInstance();
EnchantItemHPBonusData.getInstance();
MerchantPriceConfigTable.getInstance().loadInstances();
BuyListData.getInstance();
MultisellData.getInstance();
RecipeData.getInstance();
ArmorSetsData.getInstance();
FishData.getInstance();
FishingMonstersData.getInstance();
FishingRodsData.getInstance();
HennaData.getInstance();
printSection("Characters");
ClassListData.getInstance();
InitialEquipmentData.getInstance();
InitialShortcutData.getInstance();
ExperienceData.getInstance();
PlayerXpPercentLostData.getInstance();
KarmaData.getInstance();
HitConditionBonusData.getInstance();
PlayerTemplateData.getInstance();
CharNameTable.getInstance();
AdminData.getInstance();
RaidBossPointsManager.getInstance();
PetDataTable.getInstance();
CharSummonTable.getInstance().init();
if (Config.PREMIUM_SYSTEM_ENABLED)
{
_log.info("PremiumManager: Premium system is enabled.");
PremiumManager.getInstance();
}
printSection("Clans");
ClanTable.getInstance();
CHSiegeManager.getInstance();
ClanHallManager.getInstance();
ClanHallAuctionManager.getInstance();
printSection("Geodata");
long geodataMemory = getUsedMemoryMB();
GeoData.getInstance();
if (Config.PATHFINDING > 0)
{
PathFinding.getInstance();
}
geodataMemory -= getUsedMemoryMB();
if (geodataMemory < 0)
{
geodataMemory = 0;
}
printSection("NPCs");
SkillLearnData.getInstance();
NpcData.getInstance();
WalkingManager.getInstance();
StaticObjectData.getInstance();
ZoneManager.getInstance();
DoorData.getInstance();
ItemAuctionManager.getInstance();
CastleManager.getInstance().loadInstances();
NpcBufferTable.getInstance();
GrandBossManager.getInstance().initZones();
EventDroplist.getInstance();
printSection("Olympiad");
Olympiad.getInstance();
Hero.getInstance();
printSection("Seven Signs");
SevenSigns.getInstance();
// Call to load caches
printSection("Cache");
HtmCache.getInstance();
CrestTable.getInstance();
TeleportLocationTable.getInstance();
UIData.getInstance();
PartyMatchWaitingList.getInstance();
PartyMatchRoomList.getInstance();
PetitionManager.getInstance();
AugmentationData.getInstance();
CursedWeaponsManager.getInstance();
TransformData.getInstance();
BotReportTable.getInstance();
printSection("Scripts");
QuestManager.getInstance();
BoatManager.getInstance();
AirShipManager.getInstance();
SoDManager.getInstance();
SoIManager.getInstance();
try
{
_log.info(getClass().getSimpleName() + ": Loading server scripts:");
if (!Config.ALT_DEV_NO_HANDLERS || !Config.ALT_DEV_NO_QUESTS)
{
L2ScriptEngineManager.getInstance().executeScriptList(new File(Config.DATAPACK_ROOT, "data/scripts.cfg"));
}
}
catch (IOException ioe)
{
_log.severe(getClass().getSimpleName() + ": Failed loading scripts.cfg, scripts are not going to be loaded!");
}
SpawnTable.getInstance().load();
DayNightSpawnManager.getInstance().trim().notifyChangeMode();
FourSepulchersManager.getInstance().init();
DimensionalRiftManager.getInstance();
RaidBossSpawnManager.getInstance();
printSection("Siege");
SiegeManager.getInstance().getSieges();
CastleManager.getInstance().activateInstances();
FortManager.getInstance().loadInstances();
FortManager.getInstance().activateInstances();
FortSiegeManager.getInstance();
SiegeScheduleData.getInstance();
MerchantPriceConfigTable.getInstance().updateReferences();
TerritoryWarManager.getInstance();
CastleManorManager.getInstance();
MercTicketManager.getInstance();
QuestManager.getInstance().report();
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance();
}
if ((Config.AUTODESTROY_ITEM_AFTER > 0) || (Config.HERB_AUTO_DESTROY_TIME > 0))
{
ItemsAutoDestroy.getInstance();
}
MonsterRace.getInstance();
SevenSigns.getInstance().spawnSevenSignsNPC();
SevenSignsFestival.getInstance();
AutoSpawnHandler.getInstance();
_log.info("AutoSpawnHandler: Loaded " + AutoSpawnHandler.getInstance().size() + " handlers in total.");
if (Config.L2JMOD_ALLOW_WEDDING)
{
CoupleManager.getInstance();
}
if (Config.ALT_FISH_CHAMPIONSHIP_ENABLED)
{
FishingChampionshipManager.getInstance();
}
TaskManager.getInstance();
AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID);
if (Config.ALLOW_MAIL)
{
MailManager.getInstance();
}
PunishmentManager.getInstance();
Runtime.getRuntime().addShutdownHook(Shutdown.getInstance());
_log.info("IdFactory: Free ObjectID's remaining: " + IdFactory.getInstance().size());
TvTManager.getInstance();
KnownListUpdateTaskManager.getInstance();
if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS)
{
OfflineTradersTable.getInstance().restoreOfflineTraders();
}
if (Config.DEADLOCK_DETECTOR)
{
_deadDetectThread = new DeadLockDetector();
_deadDetectThread.setDaemon(true);
_deadDetectThread.start();
}
else
{
_deadDetectThread = null;
}
System.gc();
final long totalMem = Runtime.getRuntime().maxMemory() / 1048576;
_log.info(getClass().getSimpleName() + ": Started, using " + getUsedMemoryMB() + " of " + totalMem + " MB total memory.");
_log.info(getClass().getSimpleName() + ": Geodata use " + geodataMemory + " MB of memory.");
_log.info(getClass().getSimpleName() + ": Maximum number of connected players is " + Config.MAXIMUM_ONLINE_USERS + ".");
_log.info(getClass().getSimpleName() + ": Server loaded in " + ((System.currentTimeMillis() - serverLoadStart) / 1000) + " seconds.");
final SelectorConfig sc = new SelectorConfig();
sc.MAX_READ_PER_PASS = Config.MMO_MAX_READ_PER_PASS;
sc.MAX_SEND_PER_PASS = Config.MMO_MAX_SEND_PER_PASS;
sc.SLEEP_TIME = Config.MMO_SELECTOR_SLEEP_TIME;
sc.HELPER_BUFFER_COUNT = Config.MMO_HELPER_BUFFER_COUNT;
sc.TCP_NODELAY = Config.MMO_TCP_NODELAY;
_gamePacketHandler = new L2GamePacketHandler();
_selectorThread = new SelectorThread<>(sc, _gamePacketHandler, _gamePacketHandler, _gamePacketHandler, new IPv4Filter());
InetAddress bindAddress = null;
if (!Config.GAMESERVER_HOSTNAME.equals("*"))
{
try
{
bindAddress = InetAddress.getByName(Config.GAMESERVER_HOSTNAME);
}
catch (UnknownHostException e1)
{
_log.log(Level.SEVERE, getClass().getSimpleName() + ": WARNING: The GameServer bind address is invalid, using all avaliable IPs! Reason: " + e1.getMessage(), e1);
}
}
try
{
_selectorThread.openServerSocket(bindAddress, Config.PORT_GAME);
_selectorThread.start();
_log.log(Level.INFO, getClass().getSimpleName() + ": is now listening on: " + Config.GAMESERVER_HOSTNAME + ":" + Config.PORT_GAME);
}
catch (IOException e)
{
_log.log(Level.SEVERE, getClass().getSimpleName() + ": FATAL: Failed to open server socket. Reason: " + e.getMessage(), e);
System.exit(1);
}
if (Config.SERVER_RESTART_SCHEDULE_ENABLED)
{
ServerRestartManager.getInstance();
}
LoginServerThread.getInstance().start();
Toolkit.getDefaultToolkit().beep();
}
public static void main(String[] args) throws Exception
{
Server.serverMode = Server.MODE_GAMESERVER;
/*** Main ***/
// Create log folder
final File logFolder = new File(Config.DATAPACK_ROOT, LOG_FOLDER);
logFolder.mkdir();
// Create input stream for log file -- or store file data into memory
try (InputStream is = new FileInputStream(new File(LOG_NAME)))
{
LogManager.getLogManager().readConfiguration(is);
}
// Initialize config
Config.load();
printSection("Database");
DatabaseFactory.getInstance();
gameServer = new GameServer();
if (Config.IS_TELNET_ENABLED)
{
new Status(Server.serverMode).start();
}
}
public long getUsedMemoryMB()
{
return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1048576;
}
public SelectorThread<L2GameClient> getSelectorThread()
{
return _selectorThread;
}
public L2GamePacketHandler getL2GamePacketHandler()
{
return _gamePacketHandler;
}
public DeadLockDetector getDeadLockDetectorThread()
{
return _deadDetectThread;
}
public static void printSection(String s)
{
s = "=[ " + s + " ]";
while (s.length() < 61)
{
s = "-" + s;
}
_log.info(s);
}
}

View File

@ -0,0 +1,187 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.util.Calendar;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.gameserver.instancemanager.DayNightSpawnManager;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
* Game Time controller class.
* @author Forsaiken
*/
public final class GameTimeController extends Thread
{
private static final Logger _log = Logger.getLogger(GameTimeController.class.getName());
public static final int TICKS_PER_SECOND = 10; // not able to change this without checking through code
public static final int MILLIS_IN_TICK = 1000 / TICKS_PER_SECOND;
public static final int IG_DAYS_PER_DAY = 6;
public static final int MILLIS_PER_IG_DAY = (3600000 * 24) / IG_DAYS_PER_DAY;
public static final int SECONDS_PER_IG_DAY = MILLIS_PER_IG_DAY / 1000;
public static final int MINUTES_PER_IG_DAY = SECONDS_PER_IG_DAY / 60;
public static final int TICKS_PER_IG_DAY = SECONDS_PER_IG_DAY * TICKS_PER_SECOND;
public static final int TICKS_SUN_STATE_CHANGE = TICKS_PER_IG_DAY / 4;
private static GameTimeController _instance;
private final Set<L2Character> _movingObjects = ConcurrentHashMap.newKeySet();
private final long _referenceTime;
private GameTimeController()
{
super("GameTimeController");
super.setDaemon(true);
super.setPriority(MAX_PRIORITY);
final Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
_referenceTime = c.getTimeInMillis();
super.start();
}
public static void init()
{
_instance = new GameTimeController();
}
public final int getGameTime()
{
return (getGameTicks() % TICKS_PER_IG_DAY) / MILLIS_IN_TICK;
}
public final int getGameHour()
{
return getGameTime() / 60;
}
public final int getGameMinute()
{
return getGameTime() % 60;
}
public final boolean isNight()
{
return getGameHour() < 6;
}
/**
* The true GameTime tick. Directly taken from current time. This represents the tick of the time.
* @return
*/
public final int getGameTicks()
{
return (int) ((System.currentTimeMillis() - _referenceTime) / MILLIS_IN_TICK);
}
/**
* Add a L2Character to movingObjects of GameTimeController.
* @param cha The L2Character to add to movingObjects of GameTimeController
*/
public final void registerMovingObject(L2Character cha)
{
if (cha == null)
{
return;
}
_movingObjects.add(cha);
}
/**
* Move all L2Characters contained in movingObjects of GameTimeController.<BR>
* <B><U> Concept</U> :</B><BR>
* All L2Character in movement are identified in <B>movingObjects</B> of GameTimeController.<BR>
* <B><U> Actions</U> :</B><BR>
* <ul>
* <li>Update the position of each L2Character</li>
* <li>If movement is finished, the L2Character is removed from movingObjects</li>
* <li>Create a task to update the _knownObject and _knowPlayers of each L2Character that finished its movement and of their already known L2Object then notify AI with EVT_ARRIVED</li>
* </ul>
*/
private final void moveObjects()
{
_movingObjects.removeIf(L2Character::updatePosition);
}
public final void stopTimer()
{
super.interrupt();
_log.log(Level.INFO, "Stopping " + getClass().getSimpleName());
}
@Override
public final void run()
{
_log.log(Level.CONFIG, getClass().getSimpleName() + ": Started.");
long nextTickTime, sleepTime;
boolean isNight = isNight();
if (isNight)
{
ThreadPoolManager.getInstance().executeAi(() -> DayNightSpawnManager.getInstance().notifyChangeMode());
}
while (true)
{
nextTickTime = ((System.currentTimeMillis() / MILLIS_IN_TICK) * MILLIS_IN_TICK) + 100;
try
{
moveObjects();
}
catch (Throwable e)
{
_log.log(Level.WARNING, "", e);
}
sleepTime = nextTickTime - System.currentTimeMillis();
if (sleepTime > 0)
{
try
{
Thread.sleep(sleepTime);
}
catch (InterruptedException e)
{
}
}
if (isNight() != isNight)
{
isNight = !isNight;
ThreadPoolManager.getInstance().executeAi(() -> DayNightSpawnManager.getInstance().notifyChangeMode());
}
}
}
public static GameTimeController getInstance()
{
return _instance;
}
}

View File

@ -0,0 +1,578 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.geodriver.Cell;
import com.l2jmobius.commons.geodriver.GeoDriver;
import com.l2jmobius.gameserver.data.xml.impl.DoorData;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.util.GeoUtils;
import com.l2jmobius.gameserver.util.LinePointIterator;
import com.l2jmobius.gameserver.util.LinePointIterator3D;
/**
* Geodata.
* @author -Nemesiss-, HorridoJoho
*/
public class GeoData
{
private static final Logger LOGGER = Logger.getLogger(GeoData.class.getName());
private static final String FILE_NAME_FORMAT = "%d_%d.l2j";
private static final int ELEVATED_SEE_OVER_DISTANCE = 2;
private static final int MAX_SEE_OVER_HEIGHT = 48;
private static final int SPAWN_Z_DELTA_LIMIT = 100;
private final GeoDriver _driver = new GeoDriver();
protected GeoData()
{
int loadedRegions = 0;
try
{
for (int regionX = L2World.TILE_X_MIN; regionX <= L2World.TILE_X_MAX; regionX++)
{
for (int regionY = L2World.TILE_Y_MIN; regionY <= L2World.TILE_Y_MAX; regionY++)
{
final Path geoFilePath = Config.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY));
final Boolean loadFile = Config.GEODATA_REGIONS.get(regionX + "_" + regionY);
if (loadFile != null)
{
if (loadFile)
{
LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "...");
_driver.loadRegion(geoFilePath, regionX, regionY);
loadedRegions++;
}
}
else if (Config.TRY_LOAD_UNSPECIFIED_REGIONS && Files.exists(geoFilePath))
{
try
{
LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "...");
_driver.loadRegion(geoFilePath, regionX, regionY);
loadedRegions++;
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e);
}
}
}
}
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed to load geodata!", e);
System.exit(1);
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions.");
}
public boolean hasGeoPos(int geoX, int geoY)
{
return _driver.hasGeoPos(geoX, geoY);
}
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe)
{
return _driver.checkNearestNswe(geoX, geoY, worldZ, nswe);
}
public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe)
{
boolean can = true;
if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST)
{
// can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH);
can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH);
}
if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST))
{
// can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH);
can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_NORTH);
}
if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST))
{
// can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH);
can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH);
}
if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST))
{
// can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH);
can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH);
}
return can && checkNearestNswe(geoX, geoY, worldZ, nswe);
}
public int getNearestZ(int geoX, int geoY, int worldZ)
{
return _driver.getNearestZ(geoX, geoY, worldZ);
}
public int getNextLowerZ(int geoX, int geoY, int worldZ)
{
return _driver.getNextLowerZ(geoX, geoY, worldZ);
}
public int getNextHigherZ(int geoX, int geoY, int worldZ)
{
return _driver.getNextHigherZ(geoX, geoY, worldZ);
}
public int getGeoX(int worldX)
{
return _driver.getGeoX(worldX);
}
public int getGeoY(int worldY)
{
return _driver.getGeoY(worldY);
}
public int getWorldX(int geoX)
{
return _driver.getWorldX(geoX);
}
public int getWorldY(int geoY)
{
return _driver.getWorldY(geoY);
}
// ///////////////////
// L2J METHODS
/**
* Gets the height.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @return the height
*/
public int getHeight(int x, int y, int z)
{
return getNearestZ(getGeoX(x), getGeoY(y), z);
}
/**
* Gets the spawn height.
* @param x the x coordinate
* @param y the y coordinate
* @param z the the z coordinate
* @return the spawn height
*/
public int getSpawnHeight(int x, int y, int z)
{
final int geoX = getGeoX(x);
final int geoY = getGeoY(y);
if (!hasGeoPos(geoX, geoY))
{
return z;
}
final int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20);
return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z;
}
/**
* Gets the spawn height.
* @param location the location
* @return the spawn height
*/
public int getSpawnHeight(Location location)
{
return getSpawnHeight(location.getX(), location.getY(), location.getZ());
}
/**
* Can see target. Doors as target always return true. Checks doors between.
* @param cha the character
* @param target the target
* @return {@code true} if the character can see the target (LOS), {@code false} otherwise
*/
public boolean canSeeTarget(L2Object cha, L2Object target)
{
return (target != null) && (target.isDoor() || canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId()));
}
/**
* Can see target. Checks doors between.
* @param cha the character
* @param worldPosition the world position
* @return {@code true} if the character can see the target at the given world position, {@code false} otherwise
*/
public boolean canSeeTarget(L2Object cha, ILocational worldPosition)
{
return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ());
}
/**
* Can see target. Checks doors between.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param instanceId
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @param tInstanceId the target's instanceId
* @return
*/
public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId)
{
return (instanceId == tInstanceId) && canSeeTarget(x, y, z, instanceId, tx, ty, tz);
}
/**
* Can see target. Checks doors between.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param instanceId
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise
*/
public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz)
{
return !DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, true);
}
private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe)
{
if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0)))
{
throw new RuntimeException("Multiple directions!");
}
return checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe) ? getNearestZ(curX, curY, prevGeoZ) : getNextHigherZ(curX, curY, prevGeoZ);
}
/**
* Can see target. Does not check doors between.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise
*/
public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz)
{
int geoX = getGeoX(x);
int geoY = getGeoY(y);
int tGeoX = getGeoX(tx);
int tGeoY = getGeoY(ty);
z = getNearestZ(geoX, geoY, z);
tz = getNearestZ(tGeoX, tGeoY, tz);
// fastpath
if ((geoX == tGeoX) && (geoY == tGeoY))
{
return !hasGeoPos(tGeoX, tGeoY) || (z == tz);
}
if (tz > z)
{
int tmp = tx;
tx = x;
x = tmp;
tmp = ty;
ty = y;
y = tmp;
tmp = tz;
tz = z;
z = tmp;
tmp = tGeoX;
tGeoX = geoX;
geoX = tmp;
tmp = tGeoY;
tGeoY = geoY;
geoY = tmp;
}
final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz);
// first point is guaranteed to be available, skip it, we can always see our own position
pointIter.next();
int prevX = pointIter.x();
int prevY = pointIter.y();
final int prevZ = pointIter.z();
int prevGeoZ = prevZ;
int ptIndex = 0;
while (pointIter.next())
{
final int curX = pointIter.x();
final int curY = pointIter.y();
if ((curX == prevX) && (curY == prevY))
{
continue;
}
final int beeCurZ = pointIter.z();
int curGeoZ = prevGeoZ;
// check if the position has geodata
if (hasGeoPos(curX, curY))
{
final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY);
curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe);
final int maxHeight = ptIndex < ELEVATED_SEE_OVER_DISTANCE ? z + MAX_SEE_OVER_HEIGHT : beeCurZ + MAX_SEE_OVER_HEIGHT;
boolean canSeeThrough = false;
if (curGeoZ <= maxHeight)
{
if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST)
{
final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST);
final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH);
canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ));
}
else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)
{
final int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST);
final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH);
canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ));
}
else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)
{
final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST);
final int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH);
canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ));
}
else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)
{
final int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST);
final int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH);
canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ));
}
else
{
canSeeThrough = true;
}
}
if (!canSeeThrough)
{
return false;
}
}
prevX = curX;
prevY = curY;
prevGeoZ = curGeoZ;
++ptIndex;
}
return true;
}
/**
* Verifies if the is a path between origin's location and destination, if not returns the closest location.
* @param origin the origin
* @param destination the destination
* @return the destination if there is a path or the closes location
*/
public Location moveCheck(ILocational origin, ILocational destination)
{
return moveCheck(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), origin.getInstanceId());
}
/**
* Move check.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @param instanceId the instance id
* @return the last Location (x,y,z) where player can walk - just before wall
*/
public Location moveCheck(int x, int y, int z, int tx, int ty, int tz, int instanceId)
{
final int geoX = getGeoX(x);
final int geoY = getGeoY(y);
z = getNearestZ(geoX, geoY, z);
final int tGeoX = getGeoX(tx);
final int tGeoY = getGeoY(ty);
tz = getNearestZ(tGeoX, tGeoY, tz);
if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, false))
{
return new Location(x, y, getHeight(x, y, z));
}
final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY);
// first point is guaranteed to be available
pointIter.next();
int prevX = pointIter.x();
int prevY = pointIter.y();
int prevZ = z;
while (pointIter.next())
{
final int curX = pointIter.x();
final int curY = pointIter.y();
final int curZ = getNearestZ(curX, curY, prevZ);
if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY)))
{
// can't move, return previous location
return new Location(getWorldX(prevX), getWorldY(prevY), prevZ);
}
prevX = curX;
prevY = curY;
prevZ = curZ;
}
return hasGeoPos(prevX, prevY) && (prevZ != tz) ? new Location(x, y, z) : new Location(tx, ty, tz);
}
/**
* Checks if its possible to move from one location to another.
* @param fromX the X coordinate to start checking from
* @param fromY the Y coordinate to start checking from
* @param fromZ the Z coordinate to start checking from
* @param toX the X coordinate to end checking at
* @param toY the Y coordinate to end checking at
* @param toZ the Z coordinate to end checking at
* @param instanceId the instance ID
* @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise
*/
public boolean canMove(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId)
{
final int geoX = getGeoX(fromX);
final int geoY = getGeoY(fromY);
fromZ = getNearestZ(geoX, geoY, fromZ);
final int tGeoX = getGeoX(toX);
final int tGeoY = getGeoY(toY);
toZ = getNearestZ(tGeoX, tGeoY, toZ);
if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instanceId, false))
{
return false;
}
final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY);
// first point is guaranteed to be available
pointIter.next();
int prevX = pointIter.x();
int prevY = pointIter.y();
int prevZ = fromZ;
while (pointIter.next())
{
final int curX = pointIter.x();
final int curY = pointIter.y();
final int curZ = getNearestZ(curX, curY, prevZ);
if (hasGeoPos(prevX, prevY) && !checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, GeoUtils.computeNswe(prevX, prevY, curX, curY)))
{
{
return false;
}
}
prevX = curX;
prevY = curY;
prevZ = curZ;
}
return !hasGeoPos(prevX, prevY) || (prevZ == toZ);
}
public int traceTerrainZ(int x, int y, int z, int tx, int ty)
{
final int geoX = getGeoX(x);
final int geoY = getGeoY(y);
z = getNearestZ(geoX, geoY, z);
final int tGeoX = getGeoX(tx);
final int tGeoY = getGeoY(ty);
final LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY);
// first point is guaranteed to be available
pointIter.next();
int prevZ = z;
while (pointIter.next())
{
prevZ = getNearestZ(pointIter.x(), pointIter.y(), prevZ);
}
return prevZ;
}
/**
* Checks if its possible to move from one location to another.
* @param from the {@code ILocational} to start checking from
* @param toX the X coordinate to end checking at
* @param toY the Y coordinate to end checking at
* @param toZ the Z coordinate to end checking at
* @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise
*/
public boolean canMove(ILocational from, int toX, int toY, int toZ)
{
return canMove(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, from.getInstanceId());
}
/**
* Checks if its possible to move from one location to another.
* @param from the {@code ILocational} to start checking from
* @param to the {@code ILocational} to end checking at
* @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise
*/
public boolean canMove(ILocational from, ILocational to)
{
return canMove(from, to.getX(), to.getY(), to.getZ());
}
/**
* Checks the specified position for available geodata.
* @param x the X coordinate
* @param y the Y coordinate
* @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise
*/
public boolean hasGeo(int x, int y)
{
return hasGeoPos(getGeoX(x), getGeoY(y));
}
public static GeoData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final GeoData _instance = new GeoData();
}
}

View File

@ -0,0 +1,40 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
/**
* Interface for managers of list of instances.
* @author fordfrog
*/
public interface InstanceListManager
{
/**
* Loads instances with their data from persistent format.<br>
* This method has no side effect as calling methods of another instance manager.
*/
void loadInstances();
/**
* For each loaded instance, updates references to related instances.
*/
void updateReferences();
/**
* Activates instances so their setup is performed.
*/
void activateInstances();
}

View File

@ -0,0 +1,102 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.enums.ItemLocation;
import com.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
public final class ItemsAutoDestroy
{
private final Map<Integer, L2ItemInstance> _items = new ConcurrentHashMap<>();
protected ItemsAutoDestroy()
{
ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(this::removeItems, 5000, 5000);
}
public static ItemsAutoDestroy getInstance()
{
return SingletonHolder._instance;
}
public synchronized void addItem(L2ItemInstance item)
{
item.setDropTime(System.currentTimeMillis());
_items.put(item.getObjectId(), item);
}
public synchronized void removeItems()
{
final long curtime = System.currentTimeMillis();
for (L2ItemInstance item : _items.values())
{
if (item == null)
{
continue;
}
if ((item.getDropTime() == 0) || (item.getItemLocation() != ItemLocation.VOID))
{
_items.remove(item.getObjectId());
}
else if (item.getItem().getAutoDestroyTime() > 0)
{
if ((curtime - item.getDropTime()) > item.getItem().getAutoDestroyTime())
{
L2World.getInstance().removeVisibleObject(item, item.getWorldRegion());
L2World.getInstance().removeObject(item);
_items.remove(item.getObjectId());
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
}
else if (item.getItem().hasExImmediateEffect() && ((curtime - item.getDropTime()) > Config.HERB_AUTO_DESTROY_TIME))
{
L2World.getInstance().removeVisibleObject(item, item.getWorldRegion());
L2World.getInstance().removeObject(item);
_items.remove(item.getObjectId());
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
else if ((curtime - item.getDropTime()) > ((Config.AUTODESTROY_ITEM_AFTER == 0) ? 3600000 : Config.AUTODESTROY_ITEM_AFTER * 1000))
{
L2World.getInstance().removeVisibleObject(item, item.getWorldRegion());
L2World.getInstance().removeObject(item);
_items.remove(item.getObjectId());
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
}
}
private static class SingletonHolder
{
protected static final ItemsAutoDestroy _instance = new ItemsAutoDestroy();
}
}

View File

@ -0,0 +1,816 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.L2GameClient;
import com.l2jmobius.gameserver.network.L2GameClient.GameClientState;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.gameserverpackets.AuthRequest;
import com.l2jmobius.gameserver.network.gameserverpackets.BlowFishKey;
import com.l2jmobius.gameserver.network.gameserverpackets.ChangeAccessLevel;
import com.l2jmobius.gameserver.network.gameserverpackets.ChangePassword;
import com.l2jmobius.gameserver.network.gameserverpackets.PlayerAuthRequest;
import com.l2jmobius.gameserver.network.gameserverpackets.PlayerInGame;
import com.l2jmobius.gameserver.network.gameserverpackets.PlayerLogout;
import com.l2jmobius.gameserver.network.gameserverpackets.PlayerTracert;
import com.l2jmobius.gameserver.network.gameserverpackets.ReplyCharacters;
import com.l2jmobius.gameserver.network.gameserverpackets.SendMail;
import com.l2jmobius.gameserver.network.gameserverpackets.ServerStatus;
import com.l2jmobius.gameserver.network.gameserverpackets.TempBan;
import com.l2jmobius.gameserver.network.loginserverpackets.AuthResponse;
import com.l2jmobius.gameserver.network.loginserverpackets.ChangePasswordResponse;
import com.l2jmobius.gameserver.network.loginserverpackets.InitLS;
import com.l2jmobius.gameserver.network.loginserverpackets.KickPlayer;
import com.l2jmobius.gameserver.network.loginserverpackets.LoginServerFail;
import com.l2jmobius.gameserver.network.loginserverpackets.PlayerAuthResponse;
import com.l2jmobius.gameserver.network.loginserverpackets.RequestCharacters;
import com.l2jmobius.gameserver.network.serverpackets.CharSelectionInfo;
import com.l2jmobius.gameserver.network.serverpackets.LoginFail;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.util.Rnd;
import com.l2jmobius.util.Util;
import com.l2jmobius.util.crypt.NewCrypt;
import com.l2jmobius.util.network.BaseSendablePacket;
public class LoginServerThread extends Thread
{
protected static final Logger _log = Logger.getLogger(LoginServerThread.class.getName());
protected static final Logger _logAccounting = Logger.getLogger("accounting");
/** @see com.l2jmobius.loginserver.L2LoginServer#PROTOCOL_REV */
private static final int REVISION = 0x0106;
private final String _hostname;
private final int _port;
private final int _gamePort;
private Socket _loginSocket;
private OutputStream _out;
/**
* The BlowFish engine used to encrypt packets<br>
* It is first initialized with a unified key:<br>
* "_;v.]05-31!|+-%xT!^[$\00"<br>
* <br>
* and then after handshake, with a new key sent by<br>
* login server during the handshake. This new key is stored<br>
* in blowfishKey
*/
private NewCrypt _blowfish;
private byte[] _hexID;
private final boolean _acceptAlternate;
private int _requestID;
private final boolean _reserveHost;
private int _maxPlayer;
private final List<WaitingClient> _waitingClients;
private final Map<String, L2GameClient> _accountsInGameServer = new ConcurrentHashMap<>();
private int _status;
private String _serverName;
private final List<String> _subnets;
private final List<String> _hosts;
/**
* Instantiates a new login server thread.
*/
protected LoginServerThread()
{
super("LoginServerThread");
_port = Config.GAME_SERVER_LOGIN_PORT;
_gamePort = Config.PORT_GAME;
_hostname = Config.GAME_SERVER_LOGIN_HOST;
_hexID = Config.HEX_ID;
if (_hexID == null)
{
_requestID = Config.REQUEST_ID;
_hexID = Util.generateHex(16);
}
else
{
_requestID = Config.SERVER_ID;
}
_acceptAlternate = Config.ACCEPT_ALTERNATE_ID;
_reserveHost = Config.RESERVE_HOST_ON_LOGIN;
_subnets = Config.GAME_SERVER_SUBNETS;
_hosts = Config.GAME_SERVER_HOSTS;
_waitingClients = new CopyOnWriteArrayList<>();
_maxPlayer = Config.MAXIMUM_ONLINE_USERS;
}
@Override
public void run()
{
while (!isInterrupted())
{
int lengthHi = 0;
int lengthLo = 0;
int length = 0;
boolean checksumOk = false;
try
{
// Connection
_log.info("Connecting to login on " + _hostname + ":" + _port);
_loginSocket = new Socket(_hostname, _port);
final InputStream in = _loginSocket.getInputStream();
_out = new BufferedOutputStream(_loginSocket.getOutputStream());
// init Blowfish
final byte[] blowfishKey = Util.generateHex(40);
// Protect the new blowfish key what cannot begin with zero
if (blowfishKey[0] == 0)
{
blowfishKey[0] = (byte) Rnd.get(32, 64);
}
_blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00");
while (!isInterrupted())
{
lengthLo = in.read();
lengthHi = in.read();
length = (lengthHi * 256) + lengthLo;
if (lengthHi < 0)
{
_log.finer("LoginServerThread: Login terminated the connection.");
break;
}
final byte[] incoming = new byte[length - 2];
int receivedBytes = 0;
int newBytes = 0;
int left = length - 2;
while ((newBytes != -1) && (receivedBytes < (length - 2)))
{
newBytes = in.read(incoming, receivedBytes, left);
receivedBytes = receivedBytes + newBytes;
left -= newBytes;
}
if (receivedBytes != (length - 2))
{
_log.warning("Incomplete Packet is sent to the server, closing connection.(LS)");
break;
}
// decrypt if we have a key
_blowfish.decrypt(incoming, 0, incoming.length);
checksumOk = NewCrypt.verifyChecksum(incoming);
if (!checksumOk)
{
_log.warning("Incorrect packet checksum, ignoring packet (LS)");
break;
}
switch (incoming[0] & 0xff)
{
case 0x00:
{
final InitLS init = new InitLS(incoming);
if (init.getRevision() != REVISION)
{
// TODO: revision mismatch
_log.warning("/!\\ Revision mismatch between LS and GS /!\\");
break;
}
RSAPublicKey publicKey;
try
{
publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(new BigInteger(init.getRSAKey()), RSAKeyGenParameterSpec.F4));
}
catch (GeneralSecurityException e)
{
_log.warning("Trouble while init the public key send by login");
break;
}
// send the blowfish key through the rsa encryption
sendPacket(new BlowFishKey(blowfishKey, publicKey));
// now, only accept packet with the new encryption
_blowfish = new NewCrypt(blowfishKey);
sendPacket(new AuthRequest(_requestID, _acceptAlternate, _hexID, _gamePort, _reserveHost, _maxPlayer, _subnets, _hosts));
break;
}
case 0x01:
{
final LoginServerFail lsf = new LoginServerFail(incoming);
_log.info("Damn! Registeration Failed: " + lsf.getReasonString());
// login will close the connection here
break;
}
case 0x02:
{
final AuthResponse aresp = new AuthResponse(incoming);
final int serverID = aresp.getServerId();
_serverName = aresp.getServerName();
Config.saveHexid(serverID, hexToString(_hexID));
_log.info("Registered on login as Server " + serverID + " : " + _serverName);
final ServerStatus st = new ServerStatus();
st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, Config.SERVER_LIST_BRACKET ? ServerStatus.ON : ServerStatus.OFF);
st.addAttribute(ServerStatus.SERVER_TYPE, Config.SERVER_LIST_TYPE);
st.addAttribute(ServerStatus.SERVER_LIST_STATUS, Config.SERVER_GMONLY ? ServerStatus.STATUS_GM_ONLY : ServerStatus.STATUS_AUTO);
st.addAttribute(ServerStatus.SERVER_AGE, Config.SERVER_LIST_AGE == 15 ? ServerStatus.SERVER_AGE_15 : Config.SERVER_LIST_AGE == 18 ? ServerStatus.SERVER_AGE_18 : ServerStatus.SERVER_AGE_ALL);
sendPacket(st);
if (L2World.getInstance().getAllPlayersCount() > 0)
{
final List<String> playerList = new ArrayList<>();
for (L2PcInstance player : L2World.getInstance().getPlayers())
{
playerList.add(player.getAccountName());
}
sendPacket(new PlayerInGame(playerList));
}
break;
}
case 0x03:
{
final PlayerAuthResponse par = new PlayerAuthResponse(incoming);
final String account = par.getAccount();
WaitingClient wcToRemove = null;
synchronized (_waitingClients)
{
for (WaitingClient wc : _waitingClients)
{
if (wc.account.equals(account))
{
wcToRemove = wc;
}
}
}
if (wcToRemove != null)
{
if (par.isAuthed())
{
final PlayerInGame pig = new PlayerInGame(par.getAccount());
sendPacket(pig);
wcToRemove.gameClient.setState(GameClientState.AUTHED);
wcToRemove.gameClient.setSessionId(wcToRemove.session);
final CharSelectionInfo cl = new CharSelectionInfo(wcToRemove.account, wcToRemove.gameClient.getSessionId().playOkID1);
wcToRemove.gameClient.getConnection().sendPacket(cl);
wcToRemove.gameClient.setCharSelection(cl.getCharInfo());
}
else
{
_log.warning("Session key is not correct. Closing connection for account " + wcToRemove.account + ".");
// wcToRemove.gameClient.getConnection().sendPacket(new LoginFail(LoginFail.SYSTEM_ERROR_LOGIN_LATER));
wcToRemove.gameClient.close(new LoginFail(LoginFail.SYSTEM_ERROR_LOGIN_LATER));
_accountsInGameServer.remove(wcToRemove.account);
}
_waitingClients.remove(wcToRemove);
}
break;
}
case 0x04:
{
final KickPlayer kp = new KickPlayer(incoming);
doKickPlayer(kp.getAccount());
break;
}
case 0x05:
{
final RequestCharacters rc = new RequestCharacters(incoming);
getCharsOnServer(rc.getAccount());
break;
}
case 0x06:
{
new ChangePasswordResponse(incoming);
break;
}
}
}
}
catch (UnknownHostException e)
{
_log.log(Level.WARNING, "", e);
}
catch (SocketException e)
{
_log.warning("LoginServer not avaible, trying to reconnect...");
}
catch (IOException e)
{
_log.log(Level.WARNING, "Disconnected from Login, Trying to reconnect: " + e.getMessage(), e);
}
finally
{
try
{
_loginSocket.close();
if (isInterrupted())
{
return;
}
}
catch (Exception e)
{
}
}
try
{
Thread.sleep(5000); // 5 seconds tempo.
}
catch (InterruptedException e)
{
return; // never swallow an interrupt!
}
}
}
/**
* Adds the waiting client and send request.
* @param acc the account
* @param client the game client
* @param key the session key
*/
public void addWaitingClientAndSendRequest(String acc, L2GameClient client, SessionKey key)
{
final WaitingClient wc = new WaitingClient(acc, client, key);
synchronized (_waitingClients)
{
_waitingClients.add(wc);
}
final PlayerAuthRequest par = new PlayerAuthRequest(acc, key);
try
{
sendPacket(par);
}
catch (IOException e)
{
_log.warning("Error while sending player auth request");
}
}
/**
* Removes the waiting client.
* @param client the client
*/
public void removeWaitingClient(L2GameClient client)
{
WaitingClient toRemove = null;
synchronized (_waitingClients)
{
for (WaitingClient c : _waitingClients)
{
if (c.gameClient == client)
{
toRemove = c;
}
}
if (toRemove != null)
{
_waitingClients.remove(toRemove);
}
}
}
/**
* Send logout for the given account.
* @param account the account
*/
public void sendLogout(String account)
{
if (account == null)
{
return;
}
final PlayerLogout pl = new PlayerLogout(account);
try
{
sendPacket(pl);
}
catch (IOException e)
{
_log.warning("Error while sending logout packet to login");
}
finally
{
_accountsInGameServer.remove(account);
}
}
/**
* Adds the game server login.
* @param account the account
* @param client the client
* @return {@code true} if account was not already logged in, {@code false} otherwise
*/
public boolean addGameServerLogin(String account, L2GameClient client)
{
return _accountsInGameServer.putIfAbsent(account, client) == null;
}
/**
* Send access level.
* @param account the account
* @param level the access level
*/
public void sendAccessLevel(String account, int level)
{
final ChangeAccessLevel cal = new ChangeAccessLevel(account, level);
try
{
sendPacket(cal);
}
catch (IOException e)
{
}
}
/**
* Send client tracert.
* @param account the account
* @param address the address
*/
public void sendClientTracert(String account, String[] address)
{
final PlayerTracert ptc = new PlayerTracert(account, address[0], address[1], address[2], address[3], address[4]);
try
{
sendPacket(ptc);
}
catch (IOException e)
{
}
}
/**
* Send mail.
* @param account the account
* @param mailId the mail id
* @param args the args
*/
public void sendMail(String account, String mailId, String... args)
{
final SendMail sem = new SendMail(account, mailId, args);
try
{
sendPacket(sem);
}
catch (IOException e)
{
}
}
/**
* Send temp ban.
* @param account the account
* @param ip the ip
* @param time the time
*/
public void sendTempBan(String account, String ip, long time)
{
final TempBan tbn = new TempBan(account, ip, time);
try
{
sendPacket(tbn);
}
catch (IOException e)
{
}
}
/**
* Hex to string.
* @param hex the hex value
* @return the hex value as string
*/
private String hexToString(byte[] hex)
{
return new BigInteger(hex).toString(16);
}
/**
* Kick player for the given account.
* @param account the account
*/
public void doKickPlayer(String account)
{
final L2GameClient client = _accountsInGameServer.get(account);
if (client == null)
{
return;
}
final LogRecord record = new LogRecord(Level.WARNING, "Kicked by login");
record.setParameters(new Object[]
{
client
});
_logAccounting.log(record);
client.setAditionalClosePacket(SystemMessage.getSystemMessage(SystemMessageId.ANOTHER_PERSON_HAS_LOGGED_IN_WITH_THE_SAME_ACCOUNT));
client.closeNow();
}
/**
* Gets the chars on server.
* @param account the account
*/
private void getCharsOnServer(String account)
{
int chars = 0;
final List<Long> charToDel = new ArrayList<>();
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT deletetime FROM characters WHERE account_name=?"))
{
ps.setString(1, account);
try (ResultSet rs = ps.executeQuery())
{
while (rs.next())
{
chars++;
final long delTime = rs.getLong("deletetime");
if (delTime != 0)
{
charToDel.add(delTime);
}
}
}
}
catch (SQLException e)
{
_log.log(Level.WARNING, "Exception: getCharsOnServer: " + e.getMessage(), e);
}
final ReplyCharacters rec = new ReplyCharacters(account, chars, charToDel);
try
{
sendPacket(rec);
}
catch (IOException e)
{
}
}
/**
* Send packet.
* @param sl the sendable packet
* @throws IOException Signals that an I/O exception has occurred.
*/
private void sendPacket(BaseSendablePacket sl) throws IOException
{
final byte[] data = sl.getContent();
NewCrypt.appendChecksum(data);
_blowfish.crypt(data, 0, data.length);
final int len = data.length + 2;
synchronized (_out) // avoids tow threads writing in the mean time
{
_out.write(len & 0xff);
_out.write((len >> 8) & 0xff);
_out.write(data);
_out.flush();
}
}
/**
* Sets the max player.
* @param maxPlayer The maxPlayer to set.
*/
public void setMaxPlayer(int maxPlayer)
{
sendServerStatus(ServerStatus.MAX_PLAYERS, maxPlayer);
_maxPlayer = maxPlayer;
}
/**
* Gets the max player.
* @return Returns the maxPlayer.
*/
public int getMaxPlayer()
{
return _maxPlayer;
}
/**
* Send server status.
* @param id the id
* @param value the value
*/
public void sendServerStatus(int id, int value)
{
final ServerStatus ss = new ServerStatus();
ss.addAttribute(id, value);
try
{
sendPacket(ss);
}
catch (IOException e)
{
}
}
/**
* Send Server Type Config to LS.
*/
public void sendServerType()
{
final ServerStatus ss = new ServerStatus();
ss.addAttribute(ServerStatus.SERVER_TYPE, Config.SERVER_LIST_TYPE);
try
{
sendPacket(ss);
}
catch (IOException e)
{
}
}
/**
* Send change password.
* @param accountName the account name
* @param charName the char name
* @param oldpass the old pass
* @param newpass the new pass
*/
public void sendChangePassword(String accountName, String charName, String oldpass, String newpass)
{
final ChangePassword cp = new ChangePassword(accountName, charName, oldpass, newpass);
try
{
sendPacket(cp);
}
catch (IOException e)
{
}
}
/**
* Gets the status string.
* @return the status string
*/
public String getStatusString()
{
return ServerStatus.STATUS_STRING[_status];
}
/**
* Gets the server name.
* @return the server name.
*/
public String getServerName()
{
return _serverName;
}
/**
* Sets the server status.
* @param status the new server status
*/
public void setServerStatus(int status)
{
switch (status)
{
case ServerStatus.STATUS_AUTO:
{
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_AUTO);
_status = status;
break;
}
case ServerStatus.STATUS_DOWN:
{
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_DOWN);
_status = status;
break;
}
case ServerStatus.STATUS_FULL:
{
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_FULL);
_status = status;
break;
}
case ServerStatus.STATUS_GM_ONLY:
{
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY);
_status = status;
break;
}
case ServerStatus.STATUS_GOOD:
{
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GOOD);
_status = status;
break;
}
case ServerStatus.STATUS_NORMAL:
{
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_NORMAL);
_status = status;
break;
}
default:
{
throw new IllegalArgumentException("Status does not exists:" + status);
}
}
}
public L2GameClient getClient(String name)
{
return name != null ? _accountsInGameServer.get(name) : null;
}
public static class SessionKey
{
public int playOkID1;
public int playOkID2;
public int loginOkID1;
public int loginOkID2;
/**
* Instantiates a new session key.
* @param loginOK1 the login o k1
* @param loginOK2 the login o k2
* @param playOK1 the play o k1
* @param playOK2 the play o k2
*/
public SessionKey(int loginOK1, int loginOK2, int playOK1, int playOK2)
{
playOkID1 = playOK1;
playOkID2 = playOK2;
loginOkID1 = loginOK1;
loginOkID2 = loginOK2;
}
@Override
public String toString()
{
return "PlayOk: " + playOkID1 + " " + playOkID2 + " LoginOk:" + loginOkID1 + " " + loginOkID2;
}
}
private static class WaitingClient
{
public String account;
public L2GameClient gameClient;
public SessionKey session;
/**
* Instantiates a new waiting client.
* @param acc the acc
* @param client the client
* @param key the key
*/
public WaitingClient(String acc, L2GameClient client, SessionKey key)
{
account = acc;
gameClient = client;
session = key;
}
}
/**
* Gets the single instance of LoginServerThread.
* @return single instance of LoginServerThread
*/
public static LoginServerThread getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final LoginServerThread _instance = new LoginServerThread();
}
}

View File

@ -0,0 +1,140 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.gameserver.data.xml.impl.NpcData;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jmobius.util.Rnd;
public class MonsterRace
{
protected static final Logger _log = Logger.getLogger(MonsterRace.class.getName());
private final L2Npc[] _monsters;
private int[][] _speeds;
private final int[] _first, _second;
protected MonsterRace()
{
_monsters = new L2Npc[8];
_speeds = new int[8][20];
_first = new int[2];
_second = new int[2];
}
public static MonsterRace getInstance()
{
return SingletonHolder._instance;
}
public void newRace()
{
int random = 0;
for (int i = 0; i < 8; i++)
{
final int id = 31003;
random = Rnd.get(24);
while (true)
{
for (int j = i - 1; j >= 0; j--)
{
if (_monsters[j].getTemplate().getId() == (id + random))
{
random = Rnd.get(24);
continue;
}
}
break;
}
try
{
final L2NpcTemplate template = NpcData.getInstance().getTemplate(id + random);
_monsters[i] = (L2Npc) Class.forName("com.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template);
}
catch (Exception e)
{
_log.log(Level.WARNING, "Unable to create monster!", e);
}
}
newSpeeds();
}
public void newSpeeds()
{
_speeds = new int[8][20];
int total = 0;
_first[1] = 0;
_second[1] = 0;
for (int i = 0; i < 8; i++)
{
total = 0;
for (int j = 0; j < 20; j++)
{
_speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65;
total += _speeds[i][j];
}
if (total >= _first[1])
{
_second[0] = _first[0];
_second[1] = _first[1];
_first[0] = 8 - i;
_first[1] = total;
}
else if (total >= _second[1])
{
_second[0] = 8 - i;
_second[1] = total;
}
}
}
/**
* @return Returns the monsters.
*/
public L2Npc[] getMonsters()
{
return _monsters;
}
/**
* @return Returns the speeds.
*/
public int[][] getSpeeds()
{
return _speeds;
}
public int getFirstPlace()
{
return _first[0];
}
public int getSecondPlace()
{
return _second[0];
}
private static class SingletonHolder
{
protected static final MonsterRace _instance = new MonsterRace();
}
}

View File

@ -0,0 +1,763 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.data.xml.impl.RecipeData;
import com.l2jmobius.gameserver.datatables.ItemTable;
import com.l2jmobius.gameserver.enums.StatType;
import com.l2jmobius.gameserver.model.L2ManufactureItem;
import com.l2jmobius.gameserver.model.L2RecipeInstance;
import com.l2jmobius.gameserver.model.L2RecipeList;
import com.l2jmobius.gameserver.model.L2RecipeStatInstance;
import com.l2jmobius.gameserver.model.TempItem;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.itemcontainer.Inventory;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.gameserver.model.skills.CommonSkill;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.stats.Stats;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import com.l2jmobius.gameserver.network.serverpackets.ItemList;
import com.l2jmobius.gameserver.network.serverpackets.MagicSkillUse;
import com.l2jmobius.gameserver.network.serverpackets.RecipeBookItemList;
import com.l2jmobius.gameserver.network.serverpackets.RecipeItemMakeInfo;
import com.l2jmobius.gameserver.network.serverpackets.RecipeShopItemInfo;
import com.l2jmobius.gameserver.network.serverpackets.SetupGauge;
import com.l2jmobius.gameserver.network.serverpackets.StatusUpdate;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.Rnd;
public class RecipeController
{
protected static final Map<Integer, RecipeItemMaker> _activeMakers = new ConcurrentHashMap<>();
protected RecipeController()
{
// Prevent external initialization.
}
public void requestBookOpen(L2PcInstance player, boolean isDwarvenCraft)
{
// Check if player is trying to alter recipe book while engaged in manufacturing.
if (!_activeMakers.containsKey(player.getObjectId()))
{
final RecipeBookItemList response = new RecipeBookItemList(isDwarvenCraft, player.getMaxMp());
response.addRecipes(isDwarvenCraft ? player.getDwarvenRecipeBook() : player.getCommonRecipeBook());
player.sendPacket(response);
return;
}
player.sendPacket(SystemMessageId.YOU_MAY_NOT_ALTER_YOUR_RECIPE_BOOK_WHILE_ENGAGED_IN_MANUFACTURING);
}
public void requestMakeItemAbort(L2PcInstance player)
{
_activeMakers.remove(player.getObjectId()); // TODO: anything else here?
}
public void requestManufactureItem(L2PcInstance manufacturer, int recipeListId, L2PcInstance player)
{
final L2RecipeList recipeList = RecipeData.getInstance().getValidRecipeList(player, recipeListId);
if (recipeList == null)
{
return;
}
final List<L2RecipeList> dwarfRecipes = Arrays.asList(manufacturer.getDwarvenRecipeBook());
final List<L2RecipeList> commonRecipes = Arrays.asList(manufacturer.getCommonRecipeBook());
if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList))
{
Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName() + " sent a false recipe id.", Config.DEFAULT_PUNISH);
return;
}
// Check if manufacturer is under manufacturing store or private store.
if (Config.ALT_GAME_CREATION && _activeMakers.containsKey(manufacturer.getObjectId()))
{
player.sendPacket(SystemMessageId.PLEASE_CLOSE_THE_SETUP_WINDOW_FOR_YOUR_PRIVATE_MANUFACTURING_STORE_OR_PRIVATE_STORE_AND_TRY_AGAIN);
return;
}
final RecipeItemMaker maker = new RecipeItemMaker(manufacturer, recipeList, player);
if (maker._isValid)
{
if (Config.ALT_GAME_CREATION)
{
_activeMakers.put(manufacturer.getObjectId(), maker);
ThreadPoolManager.getInstance().scheduleGeneral(maker, 100);
}
else
{
maker.run();
}
}
}
public void requestMakeItem(L2PcInstance player, int recipeListId)
{
// Check if player is trying to operate a private store or private workshop while engaged in combat.
if (player.isInCombat() || player.isInDuel())
{
player.sendPacket(SystemMessageId.WHILE_YOU_ARE_ENGAGED_IN_COMBAT_YOU_CANNOT_OPERATE_A_PRIVATE_STORE_OR_PRIVATE_WORKSHOP);
return;
}
final L2RecipeList recipeList = RecipeData.getInstance().getValidRecipeList(player, recipeListId);
if (recipeList == null)
{
return;
}
final List<L2RecipeList> dwarfRecipes = Arrays.asList(player.getDwarvenRecipeBook());
final List<L2RecipeList> commonRecipes = Arrays.asList(player.getCommonRecipeBook());
if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList))
{
Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName() + " sent a false recipe id.", Config.DEFAULT_PUNISH);
return;
}
// Check if player is busy (possible if alt game creation is enabled)
if (Config.ALT_GAME_CREATION && _activeMakers.containsKey(player.getObjectId()))
{
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1);
sm.addItemName(recipeList.getItemId());
sm.addString("You are busy creating.");
player.sendPacket(sm);
return;
}
final RecipeItemMaker maker = new RecipeItemMaker(player, recipeList, player);
if (maker._isValid)
{
if (Config.ALT_GAME_CREATION)
{
_activeMakers.put(player.getObjectId(), maker);
ThreadPoolManager.getInstance().scheduleGeneral(maker, 100);
}
else
{
maker.run();
}
}
}
private static class RecipeItemMaker implements Runnable
{
private static final Logger _log = Logger.getLogger(RecipeItemMaker.class.getName());
protected boolean _isValid;
protected List<TempItem> _items = null;
protected final L2RecipeList _recipeList;
protected final L2PcInstance _player; // "crafter"
protected final L2PcInstance _target; // "customer"
protected final Skill _skill;
protected final int _skillId;
protected final int _skillLevel;
protected int _creationPasses = 1;
protected int _itemGrab;
protected int _exp = -1;
protected int _sp = -1;
protected long _price;
protected int _totalItems;
protected int _delay;
public RecipeItemMaker(L2PcInstance pPlayer, L2RecipeList pRecipeList, L2PcInstance pTarget)
{
_player = pPlayer;
_target = pTarget;
_recipeList = pRecipeList;
_isValid = false;
_skillId = _recipeList.isDwarvenRecipe() ? CommonSkill.CREATE_DWARVEN.getId() : CommonSkill.CREATE_COMMON.getId();
_skillLevel = _player.getSkillLevel(_skillId);
_skill = _player.getKnownSkill(_skillId);
_player.isInCraftMode(true);
if (_player.isAlikeDead())
{
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_target.isAlikeDead())
{
_target.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_target.isProcessingTransaction())
{
_target.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_player.isProcessingTransaction())
{
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// validate recipe list
if (_recipeList.getRecipes().length == 0)
{
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// validate skill level
if (_recipeList.getLevel() > _skillLevel)
{
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// check that customer can afford to pay for creation services
if (_player != _target)
{
final L2ManufactureItem item = _player.getManufactureItems().get(_recipeList.getId());
if (item != null)
{
_price = item.getCost();
if (_target.getAdena() < _price) // check price
{
_target.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA);
abort();
return;
}
}
}
// make temporary items
_items = listItems(false);
if (_items == null)
{
abort();
return;
}
for (TempItem i : _items)
{
_totalItems += i.getQuantity();
}
// initial statUse checks
if (!calculateStatUse(false, false))
{
abort();
return;
}
// initial AltStatChange checks
if (Config.ALT_GAME_CREATION)
{
calculateAltStatChange();
}
updateMakeInfo(true);
updateCurMp();
updateCurLoad();
_player.isInCraftMode(false);
_isValid = true;
}
@Override
public void run()
{
if (!Config.IS_CRAFTING_ENABLED)
{
_target.sendMessage("Item creation is currently disabled.");
abort();
return;
}
if ((_player == null) || (_target == null))
{
_log.warning("player or target == null (disconnected?), aborting" + _target + _player);
abort();
return;
}
if (!_player.isOnline() || !_target.isOnline())
{
_log.warning("player or target is not online, aborting " + _target + _player);
abort();
return;
}
if (Config.ALT_GAME_CREATION && !_activeMakers.containsKey(_player.getObjectId()))
{
if (_target != _player)
{
_target.sendMessage("Manufacture aborted");
_player.sendMessage("Manufacture aborted");
}
else
{
_player.sendMessage("Item creation aborted");
}
abort();
return;
}
if (Config.ALT_GAME_CREATION && !_items.isEmpty())
{
if (!calculateStatUse(true, true))
{
return; // check stat use
}
updateCurMp(); // update craft window mp bar
grabSomeItems(); // grab (equip) some more items with a nice msg to player
// if still not empty, schedule another pass
if (!_items.isEmpty())
{
_delay = (int) (Config.ALT_GAME_CREATION_SPEED * _player.getMReuseRate(_skill) * GameTimeController.TICKS_PER_SECOND * GameTimeController.MILLIS_IN_TICK);
// FIXME: please fix this packet to show crafting animation (somebody)
final MagicSkillUse msk = new MagicSkillUse(_player, _skillId, _skillLevel, _delay, 0);
_player.broadcastPacket(msk);
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
}
else
{
// for alt mode, sleep delay msec before finishing
_player.sendPacket(new SetupGauge(0, _delay));
try
{
Thread.sleep(_delay);
}
catch (InterruptedException e)
{
}
finally
{
finishCrafting();
}
}
} // for old craft mode just finish
else
{
finishCrafting();
}
}
private void finishCrafting()
{
if (!Config.ALT_GAME_CREATION)
{
calculateStatUse(false, true);
}
// first take adena for manufacture
if ((_target != _player) && (_price > 0) && (_target.transferItem("PayManufacture", _target.getInventory().getAdenaInstance().getObjectId(), _price, _player.getInventory(), _player) == null)) // customer must pay for services
{
_target.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA);
abort();
return;
}
_items = listItems(true); // this line actually takes materials from inventory
if (_items != null)
{
if (Rnd.get(100) < _recipeList.getSuccessRate())
{
rewardPlayer();
updateMakeInfo(true);
}
else
{
if (_target != _player)
{
SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.YOUR_ATTEMPT_TO_CREATE_S2_FOR_C1_AT_THE_PRICE_OF_S3_ADENA_HAS_FAILED);
msg.addString(_target.getName());
msg.addItemName(_recipeList.getItemId());
msg.addLong(_price);
_player.sendPacket(msg);
msg = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_FAILED_TO_CREATE_S2_AT_THE_PRICE_OF_S3_ADENA);
msg.addString(_player.getName());
msg.addItemName(_recipeList.getItemId());
msg.addLong(_price);
_target.sendPacket(msg);
}
else
{
_target.sendPacket(SystemMessageId.YOU_FAILED_AT_MIXING_THE_ITEM);
}
updateMakeInfo(false);
}
}
// update load and mana bar of craft window
updateCurMp();
updateCurLoad();
_activeMakers.remove(_player.getObjectId());
_player.isInCraftMode(false);
_target.sendPacket(new ItemList(_target, false));
}
private void updateMakeInfo(boolean success)
{
if (_target == _player)
{
_target.sendPacket(new RecipeItemMakeInfo(_recipeList.getId(), _target, success));
}
else
{
_target.sendPacket(new RecipeShopItemInfo(_player, _recipeList.getId()));
}
}
private void updateCurLoad()
{
final StatusUpdate su = new StatusUpdate(_target);
su.addAttribute(StatusUpdate.CUR_LOAD, _target.getCurrentLoad());
_target.sendPacket(su);
}
private void updateCurMp()
{
final StatusUpdate su = new StatusUpdate(_target);
su.addAttribute(StatusUpdate.CUR_MP, (int) _target.getCurrentMp());
_target.sendPacket(su);
}
private void grabSomeItems()
{
int grabItems = _itemGrab;
while ((grabItems > 0) && !_items.isEmpty())
{
final TempItem item = _items.get(0);
final int count = item.getQuantity() >= grabItems ? grabItems : item.getQuantity();
item.setQuantity(item.getQuantity() - count);
if (item.getQuantity() <= 0)
{
_items.remove(0);
}
else
{
_items.set(0, item);
}
grabItems -= count;
if (_target == _player)
{
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.EQUIPPED_S1_S2); // you equipped ...
sm.addLong(count);
sm.addItemName(item.getItemId());
_player.sendPacket(sm);
}
else
{
_target.sendMessage("Manufacturer " + _player.getName() + " used " + count + " " + item.getItemName());
}
}
}
// AltStatChange parameters make their effect here
private void calculateAltStatChange()
{
_itemGrab = _skillLevel;
for (L2RecipeStatInstance altStatChange : _recipeList.getAltStatChange())
{
if (altStatChange.getType() == StatType.XP)
{
_exp = altStatChange.getValue();
}
else if (altStatChange.getType() == StatType.SP)
{
_sp = altStatChange.getValue();
}
else if (altStatChange.getType() == StatType.GIM)
{
_itemGrab *= altStatChange.getValue();
}
}
// determine number of creation passes needed
_creationPasses = (_totalItems / _itemGrab) + ((_totalItems % _itemGrab) != 0 ? 1 : 0);
if (_creationPasses < 1)
{
_creationPasses = 1;
}
}
// StatUse
private boolean calculateStatUse(boolean isWait, boolean isReduce)
{
boolean ret = true;
for (L2RecipeStatInstance statUse : _recipeList.getStatUse())
{
final double modifiedValue = statUse.getValue() / _creationPasses;
if (statUse.getType() == StatType.HP)
{
// we do not want to kill the player, so its CurrentHP must be greater than the reduce value
if (_player.getCurrentHp() <= modifiedValue)
{
// rest (wait for HP)
if (Config.ALT_GAME_CREATION && isWait)
{
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
}
else
{
_target.sendPacket(SystemMessageId.NOT_ENOUGH_HP);
abort();
}
ret = false;
}
else if (isReduce)
{
_player.reduceCurrentHp(modifiedValue, _player, _skill);
}
}
else if (statUse.getType() == StatType.MP)
{
if (_player.getCurrentMp() < modifiedValue)
{
// rest (wait for MP)
if (Config.ALT_GAME_CREATION && isWait)
{
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
}
else
{
_target.sendPacket(SystemMessageId.NOT_ENOUGH_MP);
abort();
}
ret = false;
}
else if (isReduce)
{
_player.reduceCurrentMp(modifiedValue);
}
}
else
{
// there is an unknown StatUse value
_target.sendMessage("Recipe error!!!, please tell this to your GM.");
ret = false;
abort();
}
}
return ret;
}
private List<TempItem> listItems(boolean remove)
{
final L2RecipeInstance[] recipes = _recipeList.getRecipes();
final Inventory inv = _target.getInventory();
final List<TempItem> materials = new ArrayList<>();
SystemMessage sm;
for (L2RecipeInstance recipe : recipes)
{
if (recipe.getQuantity() > 0)
{
final L2ItemInstance item = inv.getItemByItemId(recipe.getItemId());
final long itemQuantityAmount = item == null ? 0 : item.getCount();
// check materials
if (itemQuantityAmount < recipe.getQuantity())
{
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_ARE_MISSING_S2_S1_REQUIRED_TO_CREATE_THAT);
sm.addItemName(recipe.getItemId());
sm.addLong(recipe.getQuantity() - itemQuantityAmount);
_target.sendPacket(sm);
abort();
return null;
}
// make new temporary object, just for counting purposes
materials.add(new TempItem(item, recipe.getQuantity()));
}
}
if (remove)
{
for (TempItem tmp : materials)
{
inv.destroyItemByItemId("Manufacture", tmp.getItemId(), tmp.getQuantity(), _target, _player);
if (tmp.getQuantity() > 1)
{
sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED);
sm.addItemName(tmp.getItemId());
sm.addLong(tmp.getQuantity());
}
else
{
sm = SystemMessage.getSystemMessage(SystemMessageId.S1_HAS_DISAPPEARED);
sm.addItemName(tmp.getItemId());
}
_target.sendPacket(sm);
}
}
return materials;
}
private void abort()
{
updateMakeInfo(false);
_player.isInCraftMode(false);
_activeMakers.remove(_player.getObjectId());
}
private void rewardPlayer()
{
final int rareProdId = _recipeList.getRareItemId();
int itemId = _recipeList.getItemId();
int itemCount = _recipeList.getCount();
final L2Item template = ItemTable.getInstance().getTemplate(itemId);
// check that the current recipe has a rare production or not
if ((rareProdId != -1) && ((rareProdId == itemId) || Config.CRAFT_MASTERWORK) && (Rnd.get(100) < _recipeList.getRarity()))
{
itemId = rareProdId;
itemCount = _recipeList.getRareCount();
}
_target.getInventory().addItem("Manufacture", itemId, itemCount, _target, _player);
// inform customer of earned item
SystemMessage sm = null;
if (_target != _player)
{
// inform manufacturer of earned profit
if (itemCount == 1)
{
sm = SystemMessage.getSystemMessage(SystemMessageId.S2_HAS_BEEN_CREATED_FOR_C1_AFTER_THE_PAYMENT_OF_S3_ADENA_WAS_RECEIVED);
sm.addString(_target.getName());
sm.addItemName(itemId);
sm.addLong(_price);
_player.sendPacket(sm);
sm = SystemMessage.getSystemMessage(SystemMessageId.C1_CREATED_S2_AFTER_RECEIVING_S3_ADENA);
sm.addString(_player.getName());
sm.addItemName(itemId);
sm.addLong(_price);
_target.sendPacket(sm);
}
else
{
sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S3_HAVE_BEEN_CREATED_FOR_C1_AT_THE_PRICE_OF_S4_ADENA);
sm.addString(_target.getName());
sm.addInt(itemCount);
sm.addItemName(itemId);
sm.addLong(_price);
_player.sendPacket(sm);
sm = SystemMessage.getSystemMessage(SystemMessageId.C1_CREATED_S2_S3_AT_THE_PRICE_OF_S4_ADENA);
sm.addString(_player.getName());
sm.addInt(itemCount);
sm.addItemName(itemId);
sm.addLong(_price);
_target.sendPacket(sm);
}
}
if (itemCount > 1)
{
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_EARNED_S2_S1_S);
sm.addItemName(itemId);
sm.addLong(itemCount);
}
else
{
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1);
sm.addItemName(itemId);
}
_target.sendPacket(sm);
if (Config.ALT_GAME_CREATION)
{
final int recipeLevel = _recipeList.getLevel();
if (_exp < 0)
{
_exp = template.getReferencePrice() * itemCount;
_exp /= recipeLevel;
}
if (_sp < 0)
{
_sp = _exp / 10;
}
if (itemId == rareProdId)
{
_exp *= Config.ALT_GAME_CREATION_RARE_XPSP_RATE;
_sp *= Config.ALT_GAME_CREATION_RARE_XPSP_RATE;
}
if (_exp < 0)
{
_exp = 0;
}
if (_sp < 0)
{
_sp = 0;
}
for (int i = _skillLevel; i > recipeLevel; i--)
{
_exp /= 4;
_sp /= 4;
}
// Added multiplication of Creation speed with XP/SP gain slower crafting -> more XP,
// faster crafting -> less XP you can use ALT_GAME_CREATION_XP_RATE/SP to modify XP/SP gained (default = 1)
_player.addExpAndSp((int) _player.calcStat(Stats.EXPSP_RATE, _exp * Config.ALT_GAME_CREATION_XP_RATE * Config.ALT_GAME_CREATION_SPEED, null, null), (int) _player.calcStat(Stats.EXPSP_RATE, _sp * Config.ALT_GAME_CREATION_SP_RATE * Config.ALT_GAME_CREATION_SPEED, null, null));
}
updateMakeInfo(true); // success
}
}
public static RecipeController getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final RecipeController _instance = new RecipeController();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,683 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.data.sql.impl.ClanTable;
import com.l2jmobius.gameserver.data.sql.impl.OfflineTradersTable;
import com.l2jmobius.gameserver.datatables.BotReportTable;
import com.l2jmobius.gameserver.instancemanager.CHSiegeManager;
import com.l2jmobius.gameserver.instancemanager.CastleManorManager;
import com.l2jmobius.gameserver.instancemanager.CursedWeaponsManager;
import com.l2jmobius.gameserver.instancemanager.FishingChampionshipManager;
import com.l2jmobius.gameserver.instancemanager.GlobalVariablesManager;
import com.l2jmobius.gameserver.instancemanager.GrandBossManager;
import com.l2jmobius.gameserver.instancemanager.ItemAuctionManager;
import com.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2jmobius.gameserver.instancemanager.QuestManager;
import com.l2jmobius.gameserver.instancemanager.RaidBossSpawnManager;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.entity.Hero;
import com.l2jmobius.gameserver.model.olympiad.Olympiad;
import com.l2jmobius.gameserver.network.L2GameClient;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.gameserverpackets.ServerStatus;
import com.l2jmobius.gameserver.network.serverpackets.ServerClose;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.gameserver.util.Broadcast;
/**
* This class provides the functions for shutting down and restarting the server.<br>
* It closes all open client connections and saves all data.
* @version $Revision: 1.2.4.5 $ $Date: 2005/03/27 15:29:09 $
*/
public class Shutdown extends Thread
{
private static final Logger _log = Logger.getLogger(Shutdown.class.getName());
private static Shutdown _counterInstance = null;
private int _secondsShut;
private int _shutdownMode;
public static final int SIGTERM = 0;
public static final int GM_SHUTDOWN = 1;
public static final int GM_RESTART = 2;
public static final int ABORT = 3;
private static final String[] MODE_TEXT =
{
"SIGTERM",
"shutting down",
"restarting",
"aborting"
};
/**
* This function starts a shutdown count down from Telnet (Copied from Function startShutdown())
* @param seconds seconds until shutdown
*/
private void SendServerQuit(int seconds)
{
final SystemMessage sysm = SystemMessage.getSystemMessage(SystemMessageId.THE_SERVER_WILL_BE_COMING_DOWN_IN_S1_SECOND_S_PLEASE_FIND_A_SAFE_PLACE_TO_LOG_OUT);
sysm.addInt(seconds);
Broadcast.toAllOnlinePlayers(sysm);
}
public void startTelnetShutdown(String IP, int seconds, boolean restart)
{
_log.warning("IP: " + IP + " issued shutdown command. " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!");
_shutdownMode = restart ? GM_RESTART : GM_SHUTDOWN;
if (_shutdownMode > 0)
{
switch (seconds)
{
case 540:
case 480:
case 420:
case 360:
case 300:
case 240:
case 180:
case 120:
case 60:
case 30:
case 10:
case 5:
case 4:
case 3:
case 2:
case 1:
{
break;
}
default:
{
SendServerQuit(seconds);
}
}
}
if (_counterInstance != null)
{
_counterInstance._abort();
}
_counterInstance = new Shutdown(seconds, restart);
_counterInstance.start();
}
/**
* This function aborts a running countdown
* @param IP IP Which Issued shutdown command
*/
public void telnetAbort(String IP)
{
_log.warning("IP: " + IP + " issued shutdown ABORT. " + MODE_TEXT[_shutdownMode] + " has been stopped!");
if (_counterInstance != null)
{
_counterInstance._abort();
Broadcast.toAllOnlinePlayers("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!", false);
}
}
/**
* Default constructor is only used internal to create the shutdown-hook instance
*/
protected Shutdown()
{
_secondsShut = -1;
_shutdownMode = SIGTERM;
}
/**
* This creates a countdown instance of Shutdown.
* @param seconds how many seconds until shutdown
* @param restart true is the server shall restart after shutdown
*/
public Shutdown(int seconds, boolean restart)
{
if (seconds < 0)
{
seconds = 0;
}
_secondsShut = seconds;
_shutdownMode = restart ? GM_RESTART : GM_SHUTDOWN;
}
/**
* This function is called, when a new thread starts if this thread is the thread of getInstance, then this is the shutdown hook and we save all data and disconnect all clients.<br>
* After this thread ends, the server will completely exit if this is not the thread of getInstance, then this is a countdown thread.<br>
* We start the countdown, and when we finished it, and it was not aborted, we tell the shutdown-hook why we call exit, and then call exit when the exit status of the server is 1, startServer.sh / startServer.bat will restart the server.
*/
@Override
public void run()
{
if (this == getInstance())
{
final TimeCounter tc = new TimeCounter();
final TimeCounter tc1 = new TimeCounter();
try
{
if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS && !Config.STORE_OFFLINE_TRADE_IN_REALTIME)
{
OfflineTradersTable.getInstance().storeOffliners();
_log.info("Offline Traders Table: Offline shops stored(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
}
catch (Throwable t)
{
_log.log(Level.WARNING, "Error saving offline shops.", t);
}
try
{
disconnectAllCharacters();
_log.info("All players disconnected and saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
// ensure all services are stopped
try
{
GameTimeController.getInstance().stopTimer();
_log.info("Game Time Controller: Timer stopped(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
// stop all thread pools
try
{
ThreadPoolManager.getInstance().shutdown();
_log.info("Thread Pool Manager: Manager has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
try
{
LoginServerThread.getInstance().interrupt();
_log.info("Login Server Thread: Thread interruped(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
// last byebye, save all data and quit this server
saveData();
tc.restartCounter();
// saveData sends messages to exit players, so shutdown selector after it
try
{
GameServer.gameServer.getSelectorThread().shutdown();
_log.info("Game Server: Selector thread has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
// commit data, last chance
try
{
DatabaseFactory.getInstance().close();
_log.info("Database Factory: Database connection has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
}
// server will quit, when this function ends.
if (getInstance()._shutdownMode == GM_RESTART)
{
Runtime.getRuntime().halt(2);
}
else
{
Runtime.getRuntime().halt(0);
}
_log.info("The server has been successfully shut down in " + (tc1.getEstimatedTime() / 1000) + "seconds.");
}
else
{
// gm shutdown: send warnings and then call exit to start shutdown sequence
countdown();
// last point where logging is operational :(
_log.warning("GM shutdown countdown is over. " + MODE_TEXT[_shutdownMode] + " NOW!");
switch (_shutdownMode)
{
case GM_SHUTDOWN:
{
getInstance().setMode(GM_SHUTDOWN);
System.exit(0);
break;
}
case GM_RESTART:
{
getInstance().setMode(GM_RESTART);
System.exit(2);
break;
}
case ABORT:
{
LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_AUTO);
break;
}
}
}
}
/**
* This functions starts a shutdown countdown.
* @param activeChar GM who issued the shutdown command
* @param seconds seconds until shutdown
* @param restart true if the server will restart after shutdown
*/
public void startShutdown(L2PcInstance activeChar, int seconds, boolean restart)
{
_shutdownMode = restart ? GM_RESTART : GM_SHUTDOWN;
if (activeChar != null)
{
_log.warning("GM: " + activeChar.getName() + "(" + activeChar.getObjectId() + ") issued shutdown command. " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!");
}
else
{
_log.warning("Server scheduled restart issued shutdown command. Restart in " + seconds + " seconds!");
}
if (_shutdownMode > 0)
{
switch (seconds)
{
case 540:
case 480:
case 420:
case 360:
case 300:
case 240:
case 180:
case 120:
case 60:
case 30:
case 10:
case 5:
case 4:
case 3:
case 2:
case 1:
{
break;
}
default:
{
SendServerQuit(seconds);
}
}
}
if (_counterInstance != null)
{
_counterInstance._abort();
}
// the main instance should only run for shutdown hook, so we start a new instance
_counterInstance = new Shutdown(seconds, restart);
_counterInstance.start();
}
/**
* This function aborts a running countdown.
* @param activeChar GM who issued the abort command
*/
public void abort(L2PcInstance activeChar)
{
_log.warning("GM: " + activeChar.getName() + "(" + activeChar.getObjectId() + ") issued shutdown ABORT. " + MODE_TEXT[_shutdownMode] + " has been stopped!");
if (_counterInstance != null)
{
_counterInstance._abort();
Broadcast.toAllOnlinePlayers("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!", false);
}
}
/**
* Set the shutdown mode.
* @param mode what mode shall be set
*/
private void setMode(int mode)
{
_shutdownMode = mode;
}
/**
* Set shutdown mode to ABORT.
*/
private void _abort()
{
_shutdownMode = ABORT;
}
/**
* This counts the countdown and reports it to all players countdown is aborted if mode changes to ABORT.
*/
private void countdown()
{
try
{
while (_secondsShut > 0)
{
switch (_secondsShut)
{
case 540:
{
SendServerQuit(540);
break;
}
case 480:
{
SendServerQuit(480);
break;
}
case 420:
{
SendServerQuit(420);
break;
}
case 360:
{
SendServerQuit(360);
break;
}
case 300:
{
SendServerQuit(300);
break;
}
case 240:
{
SendServerQuit(240);
break;
}
case 180:
{
SendServerQuit(180);
break;
}
case 120:
{
SendServerQuit(120);
break;
}
case 60:
{
LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_DOWN); // avoids new players from logging in
SendServerQuit(60);
break;
}
case 30:
{
SendServerQuit(30);
break;
}
case 10:
{
SendServerQuit(10);
break;
}
case 5:
{
SendServerQuit(5);
break;
}
case 4:
{
SendServerQuit(4);
break;
}
case 3:
{
SendServerQuit(3);
break;
}
case 2:
{
SendServerQuit(2);
break;
}
case 1:
{
SendServerQuit(1);
break;
}
}
_secondsShut--;
final int delay = 1000; // milliseconds
Thread.sleep(delay);
if (_shutdownMode == ABORT)
{
break;
}
}
}
catch (InterruptedException e)
{
// this will never happen
}
}
/**
* This sends a last byebye, disconnects all players and saves data.
*/
private void saveData()
{
switch (_shutdownMode)
{
case SIGTERM:
{
_log.info("SIGTERM received. Shutting down NOW!");
break;
}
case GM_SHUTDOWN:
{
_log.info("GM shutdown received. Shutting down NOW!");
break;
}
case GM_RESTART:
{
_log.info("GM restart received. Restarting NOW!");
break;
}
}
final TimeCounter tc = new TimeCounter();
// Seven Signs data is now saved along with Festival data.
if (!SevenSigns.getInstance().isSealValidationPeriod())
{
SevenSignsFestival.getInstance().saveFestivalData(false);
_log.info("SevenSignsFestival: Festival data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
// Save Seven Signs data before closing. :)
SevenSigns.getInstance().saveSevenSignsData();
_log.info("SevenSigns: Seven Signs data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
SevenSigns.getInstance().saveSevenSignsStatus();
_log.info("SevenSigns: Seven Signs status saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save all raidboss and GrandBoss status ^_^
RaidBossSpawnManager.getInstance().cleanUp();
_log.info("RaidBossSpawnManager: All raidboss info saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
GrandBossManager.getInstance().cleanUp();
_log.info("GrandBossManager: All Grand Boss info saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
ItemAuctionManager.getInstance().shutdown();
_log.info("Item Auction Manager: All tasks stopped(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
Olympiad.getInstance().saveOlympiadStatus();
_log.info("Olympiad System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
Hero.getInstance().shutdown();
_log.info("Hero System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
ClanTable.getInstance().storeClanScore();
_log.info("Clan System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save Cursed Weapons data before closing.
CursedWeaponsManager.getInstance().saveData();
_log.info("Cursed Weapons Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save all manor data
if (!Config.ALT_MANOR_SAVE_ALL_ACTIONS)
{
CastleManorManager.getInstance().storeMe();
_log.info("Castle Manor Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
CHSiegeManager.getInstance().onServerShutDown();
_log.info("CHSiegeManager: Siegable hall attacker lists saved!");
// Save all global (non-player specific) Quest data that needs to persist after reboot
QuestManager.getInstance().save();
_log.info("Quest Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save all global variables data
GlobalVariablesManager.getInstance().storeMe();
_log.info("Global Variables Manager: Variables saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save Fishing tournament data
if (Config.ALT_FISH_CHAMPIONSHIP_ENABLED)
{
FishingChampionshipManager.getInstance().shutdown();
_log.info("Fishing Championship data has been saved.");
}
// Save items on ground before closing
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().saveInDb();
_log.info("Items On Ground Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
ItemsOnGroundManager.getInstance().cleanUp();
_log.info("Items On Ground Manager: Cleaned up(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
// Save bot reports to database
if (Config.BOTREPORT_ENABLE)
{
BotReportTable.getInstance().saveReportedCharData();
_log.info("Bot Report Table: Successfully saved reports to database!");
}
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
// never happens :p
}
}
/**
* This disconnects all clients from the server.
*/
private void disconnectAllCharacters()
{
for (L2PcInstance player : L2World.getInstance().getPlayers())
{
// Logout Character
try
{
final L2GameClient client = player.getClient();
if ((client != null) && !client.isDetached())
{
client.close(ServerClose.STATIC_PACKET);
client.setActiveChar(null);
player.setClient(null);
}
else if (!player.isInOfflineMode())
// player is probably a bot - force logout
{
player.logout();
}
player.deleteMe();
}
catch (Throwable t)
{
_log.log(Level.WARNING, "Failed logout char " + player, t);
}
}
}
/**
* A simple class used to track down the estimated time of method executions.<br>
* Once this class is created, it saves the start time, and when you want to get the estimated time, use the getEstimatedTime() method.
*/
private static final class TimeCounter
{
private long _startTime;
protected TimeCounter()
{
restartCounter();
}
protected void restartCounter()
{
_startTime = System.currentTimeMillis();
}
protected long getEstimatedTimeAndRestartCounter()
{
final long toReturn = System.currentTimeMillis() - _startTime;
restartCounter();
return toReturn;
}
protected long getEstimatedTime()
{
return System.currentTimeMillis() - _startTime;
}
}
/**
* Get the shutdown-hook instance the shutdown-hook instance is created by the first call of this function, but it has to be registered externally.<br>
* @return instance of Shutdown, to be used as shutdown hook
*/
public static Shutdown getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final Shutdown _instance = new Shutdown();
}
}

View File

@ -0,0 +1,802 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.util.StringUtil;
/**
* <p>
* This class is made to handle all the ThreadPools used in L2J.
* </p>
* <p>
* Scheduled Tasks can either be sent to a {@link #_generalScheduledThreadPool "general"} or {@link #_effectsScheduledThreadPool "effects"} {@link ScheduledThreadPoolExecutor ScheduledThreadPool}: The "effects" one is used for every effects (skills, hp/mp regen ...) while the "general" one is used
* for everything else that needs to be scheduled.<br>
* There also is an {@link #_aiScheduledThreadPool "ai"} {@link ScheduledThreadPoolExecutor ScheduledThreadPool} used for AI Tasks.
* </p>
* <p>
* Tasks can be sent to {@link ScheduledThreadPoolExecutor ScheduledThreadPool} either with:
* <ul>
* <li>{@link #scheduleEffect(Runnable, long, TimeUnit)} and {@link #scheduleEffect(Runnable, long)} : for effects Tasks that needs to be executed only once.</li>
* <li>{@link #scheduleGeneral(Runnable, long, TimeUnit)} and {@link #scheduleGeneral(Runnable, long)} : for scheduled Tasks that needs to be executed once.</li>
* <li>{@link #scheduleAi(Runnable, long, TimeUnit)} and {@link #scheduleAi(Runnable, long)} : for AI Tasks that needs to be executed once</li>
* </ul>
* or
* <ul>
* <li>{@link #scheduleEffectAtFixedRate(Runnable, long, long, TimeUnit)} and {@link #scheduleEffectAtFixedRate(Runnable, long, long)} : for effects Tasks that needs to be executed periodically.</li>
* <li>{@link #scheduleGeneralAtFixedRate(Runnable, long, long, TimeUnit)} and {@link #scheduleGeneralAtFixedRate(Runnable, long, long)} : for scheduled Tasks that needs to be executed periodically.</li>
* <li>{@link #scheduleAiAtFixedRate(Runnable, long, long, TimeUnit)} and {@link #scheduleAiAtFixedRate(Runnable, long, long)} : for AI Tasks that needs to be executed periodically</li>
* </ul>
* </p>
* <p>
* For all Tasks that should be executed with no delay asynchronously in a ThreadPool there also are usual {@link ThreadPoolExecutor ThreadPools} that can grow/shrink according to their load.:
* <ul>
* <li>{@link #_generalPacketsThreadPool GeneralPackets} where most packets handler are executed.</li>
* <li>{@link #_ioPacketsThreadPool I/O Packets} where all the i/o packets are executed.</li>
* <li>There will be an AI ThreadPool where AI events should be executed</li>
* <li>A general ThreadPool where everything else that needs to run asynchronously with no delay should be executed ({@link com.l2jmobius.gameserver.model.actor.knownlist KnownList} updates, SQL updates/inserts...)?</li>
* </ul>
* </p>
* @author -Wooden-
*/
public class ThreadPoolManager
{
protected static final Logger _log = Logger.getLogger(ThreadPoolManager.class.getName());
private static final class RunnableWrapper implements Runnable
{
private final Runnable _r;
public RunnableWrapper(Runnable r)
{
_r = r;
}
@Override
public final void run()
{
try
{
_r.run();
}
catch (Throwable e)
{
final Thread t = Thread.currentThread();
final UncaughtExceptionHandler h = t.getUncaughtExceptionHandler();
if (h != null)
{
h.uncaughtException(t, e);
}
}
}
}
protected ScheduledThreadPoolExecutor _effectsScheduledThreadPool;
protected ScheduledThreadPoolExecutor _generalScheduledThreadPool;
protected ScheduledThreadPoolExecutor _aiScheduledThreadPool;
protected ScheduledThreadPoolExecutor _eventScheduledThreadPool;
private final ThreadPoolExecutor _generalPacketsThreadPool;
private final ThreadPoolExecutor _ioPacketsThreadPool;
private final ThreadPoolExecutor _generalThreadPool;
private final ThreadPoolExecutor _eventThreadPool;
private boolean _shutdown;
public static ThreadPoolManager getInstance()
{
return SingletonHolder._instance;
}
protected ThreadPoolManager()
{
_effectsScheduledThreadPool = new ScheduledThreadPoolExecutor(Config.THREAD_P_EFFECTS, new PriorityThreadFactory("EffectsSTPool", Thread.NORM_PRIORITY));
_generalScheduledThreadPool = new ScheduledThreadPoolExecutor(Config.THREAD_P_GENERAL, new PriorityThreadFactory("GeneralSTPool", Thread.NORM_PRIORITY));
_eventScheduledThreadPool = new ScheduledThreadPoolExecutor(Config.THREAD_E_EVENTS, new PriorityThreadFactory("EventSTPool", Thread.NORM_PRIORITY));
_ioPacketsThreadPool = new ThreadPoolExecutor(Config.IO_PACKET_THREAD_CORE_SIZE, Integer.MAX_VALUE, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("I/O Packet Pool", Thread.NORM_PRIORITY + 1));
_generalPacketsThreadPool = new ThreadPoolExecutor(Config.GENERAL_PACKET_THREAD_CORE_SIZE, Config.GENERAL_PACKET_THREAD_CORE_SIZE + 2, 15L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("Normal Packet Pool", Thread.NORM_PRIORITY + 1));
_generalThreadPool = new ThreadPoolExecutor(Config.GENERAL_THREAD_CORE_SIZE, Config.GENERAL_THREAD_CORE_SIZE + 2, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("General Pool", Thread.NORM_PRIORITY));
_aiScheduledThreadPool = new ScheduledThreadPoolExecutor(Config.AI_MAX_THREAD, new PriorityThreadFactory("AISTPool", Thread.NORM_PRIORITY));
_eventThreadPool = new ThreadPoolExecutor(Config.EVENT_MAX_THREAD, Config.EVENT_MAX_THREAD + 2, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("Event Pool", Thread.NORM_PRIORITY));
scheduleGeneralAtFixedRate(new PurgeTask(), 10, 5, TimeUnit.MINUTES);
}
/**
* Schedules an effect task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffect(Runnable task, long delay, TimeUnit unit)
{
try
{
return _effectsScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
}
catch (RejectedExecutionException e)
{
return null;
}
}
/**
* Schedules an effect task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffect(Runnable task, long delay)
{
return scheduleEffect(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules an effect task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffectAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)
{
try
{
return _effectsScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
}
catch (RejectedExecutionException e)
{
return null; /* shutdown, ignore */
}
}
/**
* Schedules an effect task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in milliseconds
* @param period the period between executions in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffectAtFixedRate(Runnable task, long initialDelay, long period)
{
return scheduleEffectAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS);
}
/**
* Schedules a general task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneral(Runnable task, long delay, TimeUnit unit)
{
try
{
return _generalScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
}
catch (RejectedExecutionException e)
{
return null; /* shutdown, ignore */
}
}
/**
* Schedules a general task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneral(Runnable task, long delay)
{
return scheduleGeneral(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneralAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)
{
try
{
return _generalScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
}
catch (RejectedExecutionException e)
{
return null; /* shutdown, ignore */
}
}
/**
* Schedules a event task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEvent(Runnable task, long delay, TimeUnit unit)
{
try
{
return _eventScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
}
catch (RejectedExecutionException e)
{
return null; /* shutdown, ignore */
}
}
/**
* Schedules a event task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEvent(Runnable task, long delay)
{
return scheduleEvent(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules a event task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEventAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)
{
try
{
return _eventScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
}
catch (RejectedExecutionException e)
{
return null; /* shutdown, ignore */
}
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in milliseconds
* @param period the period between executions in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneralAtFixedRate(Runnable task, long initialDelay, long period)
{
return scheduleGeneralAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS);
}
/**
* Schedules an AI task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAi(Runnable task, long delay, TimeUnit unit)
{
try
{
return _aiScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
}
catch (RejectedExecutionException e)
{
return null; /* shutdown, ignore */
}
}
/**
* Schedules an AI task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAi(Runnable task, long delay)
{
return scheduleAi(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAiAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)
{
try
{
return _aiScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
}
catch (RejectedExecutionException e)
{
return null; /* shutdown, ignore */
}
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in milliseconds
* @param period the period between executions in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAiAtFixedRate(Runnable task, long initialDelay, long period)
{
return scheduleAiAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS);
}
/**
* Executes a packet task sometime in future in another thread.
* @param task the task to execute
*/
public void executePacket(Runnable task)
{
try
{
_generalPacketsThreadPool.execute(task);
}
catch (RejectedExecutionException e)
{
/* shutdown, ignore */
}
}
/**
* Executes an IO packet task sometime in future in another thread.
* @param task the task to execute
*/
public void executeIOPacket(Runnable task)
{
try
{
_ioPacketsThreadPool.execute(task);
}
catch (RejectedExecutionException e)
{
/* shutdown, ignore */
}
}
/**
* Executes a general task sometime in future in another thread.
* @param task the task to execute
*/
public void executeGeneral(Runnable task)
{
try
{
_generalThreadPool.execute(new RunnableWrapper(task));
}
catch (RejectedExecutionException e)
{
/* shutdown, ignore */
}
}
/**
* Executes an AI task sometime in future in another thread.
* @param task the task to execute
*/
public void executeAi(Runnable task)
{
try
{
_aiScheduledThreadPool.execute(new RunnableWrapper(task));
}
catch (RejectedExecutionException e)
{
/* shutdown, ignore */
}
}
/**
* Executes an Event task sometime in future in another thread.
* @param task the task to execute
*/
public void executeEvent(Runnable task)
{
try
{
_eventThreadPool.execute(new RunnableWrapper(task));
}
catch (RejectedExecutionException e)
{
/* shutdown, ignore */
}
}
public StatsSet getStats(String category)
{
final StatsSet StatsSet = new StatsSet();
switch (category)
{
case "EFFECTS":
{
StatsSet.set("ActiveThreads", _effectsScheduledThreadPool.getActiveCount());
StatsSet.set("getCorePoolSize", _effectsScheduledThreadPool.getCorePoolSize());
StatsSet.set("PoolSize", _effectsScheduledThreadPool.getPoolSize());
StatsSet.set("MaximumPoolSize", _effectsScheduledThreadPool.getMaximumPoolSize());
StatsSet.set("CompletedTasks", (int) _effectsScheduledThreadPool.getCompletedTaskCount());
StatsSet.set("ScheduledTasks", _effectsScheduledThreadPool.getQueue().size());
break;
}
case "GENERAL":
{
StatsSet.set("ActiveThreads", _generalScheduledThreadPool.getActiveCount());
StatsSet.set("getCorePoolSizes", _generalScheduledThreadPool.getCorePoolSize());
StatsSet.set("PoolSizes", _generalScheduledThreadPool.getPoolSize());
StatsSet.set("MaximumPoolSizes", _generalScheduledThreadPool.getMaximumPoolSize());
StatsSet.set("CompletedTasks", (int) _generalScheduledThreadPool.getCompletedTaskCount());
StatsSet.set("ScheduledTasks", _generalScheduledThreadPool.getQueue().size());
break;
}
case "AI":
{
StatsSet.set("ActiveThreads", _aiScheduledThreadPool.getActiveCount());
StatsSet.set("getCorePoolSize", _aiScheduledThreadPool.getCorePoolSize());
StatsSet.set("PoolSize", _aiScheduledThreadPool.getPoolSize());
StatsSet.set("MaximumPoolSize", _aiScheduledThreadPool.getMaximumPoolSize());
StatsSet.set("CompletedTasks", (int) _aiScheduledThreadPool.getCompletedTaskCount());
StatsSet.set("ScheduledTasks", _aiScheduledThreadPool.getQueue().size());
break;
}
case "EVENT":
{
StatsSet.set("ActiveThreads", _eventScheduledThreadPool.getActiveCount());
StatsSet.set("getCorePoolSize", _eventScheduledThreadPool.getCorePoolSize());
StatsSet.set("PoolSize", _eventScheduledThreadPool.getPoolSize());
StatsSet.set("MaximumPoolSize", _eventScheduledThreadPool.getMaximumPoolSize());
StatsSet.set("CompletedTasks", (int) _eventScheduledThreadPool.getCompletedTaskCount());
StatsSet.set("ScheduledTasks", _eventScheduledThreadPool.getQueue().size());
break;
}
case "PACKETS":
{
StatsSet.set("ActiveThreads", _generalPacketsThreadPool.getActiveCount());
StatsSet.set("getCorePoolSize", _generalPacketsThreadPool.getCorePoolSize());
StatsSet.set("MaximumPoolSize", _generalPacketsThreadPool.getMaximumPoolSize());
StatsSet.set("LargestPoolSize", _generalPacketsThreadPool.getLargestPoolSize());
StatsSet.set("PoolSize", _generalPacketsThreadPool.getPoolSize());
StatsSet.set("CompletedTasks", (int) _generalPacketsThreadPool.getCompletedTaskCount());
StatsSet.set("QueuedTasks", _generalPacketsThreadPool.getQueue().size());
break;
}
case "IOPACKETS":
{
StatsSet.set("ActiveThreads", _ioPacketsThreadPool.getActiveCount());
StatsSet.set("getCorePoolSize", _ioPacketsThreadPool.getCorePoolSize());
StatsSet.set("MaximumPoolSize", _ioPacketsThreadPool.getMaximumPoolSize());
StatsSet.set("LargestPoolSize", _ioPacketsThreadPool.getLargestPoolSize());
StatsSet.set("PoolSize", _ioPacketsThreadPool.getPoolSize());
StatsSet.set("CompletedTasks", (int) _ioPacketsThreadPool.getCompletedTaskCount());
StatsSet.set("QueuedTasks", _ioPacketsThreadPool.getQueue().size());
break;
}
case "GENERAL_TASKS":
{
StatsSet.set("ActiveThreads", _generalThreadPool.getActiveCount());
StatsSet.set("getCorePoolSize", _generalThreadPool.getCorePoolSize());
StatsSet.set("MaximumPoolSize", _generalThreadPool.getMaximumPoolSize());
StatsSet.set("LargestPoolSize", _generalThreadPool.getLargestPoolSize());
StatsSet.set("PoolSize", _generalThreadPool.getPoolSize());
StatsSet.set("CompletedTasks", (int) _generalThreadPool.getCompletedTaskCount());
StatsSet.set("QueuedTasks", _generalThreadPool.getQueue().size());
break;
}
case "EVENT_TASKS":
{
StatsSet.set("ActiveThreads", _eventThreadPool.getActiveCount());
StatsSet.set("getCorePoolSize", _eventThreadPool.getCorePoolSize());
StatsSet.set("MaximumPoolSize", _eventThreadPool.getMaximumPoolSize());
StatsSet.set("LargestPoolSize", _eventThreadPool.getLargestPoolSize());
StatsSet.set("PoolSize", _eventThreadPool.getPoolSize());
StatsSet.set("CompletedTasks", (int) _eventThreadPool.getCompletedTaskCount());
StatsSet.set("QueuedTasks", _eventThreadPool.getQueue().size());
break;
}
}
return StatsSet;
}
public String[] getStats()
{
return new String[]
{
"STP:",
" + Effects:",
" |- ActiveThreads: " + _effectsScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _effectsScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _effectsScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _effectsScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _effectsScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _effectsScheduledThreadPool.getQueue().size(),
" | -------",
" + General:",
" |- ActiveThreads: " + _generalScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _generalScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _generalScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _generalScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _generalScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _generalScheduledThreadPool.getQueue().size(),
" | -------",
" + AI:",
" |- ActiveThreads: " + _aiScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _aiScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _aiScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _aiScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _aiScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _aiScheduledThreadPool.getQueue().size(),
" | -------",
" + Event:",
" |- ActiveThreads: " + _eventScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _eventScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _eventScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _eventScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _eventScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _eventScheduledThreadPool.getQueue().size(),
"TP:",
" + Packets:",
" |- ActiveThreads: " + _generalPacketsThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _generalPacketsThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _generalPacketsThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _generalPacketsThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _generalPacketsThreadPool.getPoolSize(),
" |- CompletedTasks: " + _generalPacketsThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _generalPacketsThreadPool.getQueue().size(),
" | -------",
" + I/O Packets:",
" |- ActiveThreads: " + _ioPacketsThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _ioPacketsThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _ioPacketsThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _ioPacketsThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _ioPacketsThreadPool.getPoolSize(),
" |- CompletedTasks: " + _ioPacketsThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _ioPacketsThreadPool.getQueue().size(),
" | -------",
" + General Tasks:",
" |- ActiveThreads: " + _generalThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _generalThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _generalThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _generalThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _generalThreadPool.getPoolSize(),
" |- CompletedTasks: " + _generalThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _generalThreadPool.getQueue().size(),
" | -------",
" + Event Tasks:",
" |- ActiveThreads: " + _eventThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _eventThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _eventThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _eventThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _eventThreadPool.getPoolSize(),
" |- CompletedTasks: " + _eventThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _eventThreadPool.getQueue().size(),
" | -------"
};
}
private static class PriorityThreadFactory implements ThreadFactory
{
private final int _prio;
private final String _name;
private final AtomicInteger _threadNumber = new AtomicInteger(1);
private final ThreadGroup _group;
public PriorityThreadFactory(String name, int prio)
{
_prio = prio;
_name = name;
_group = new ThreadGroup(_name);
}
@Override
public Thread newThread(Runnable r)
{
final Thread t = new Thread(_group, r, _name + "-" + _threadNumber.getAndIncrement());
t.setPriority(_prio);
return t;
}
public ThreadGroup getGroup()
{
return _group;
}
}
public void shutdown()
{
_shutdown = true;
try
{
_effectsScheduledThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_generalScheduledThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_generalPacketsThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_ioPacketsThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_generalThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_eventThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_effectsScheduledThreadPool.shutdown();
_generalScheduledThreadPool.shutdown();
_generalPacketsThreadPool.shutdown();
_ioPacketsThreadPool.shutdown();
_generalThreadPool.shutdown();
_eventThreadPool.shutdown();
_log.info("All ThreadPools are now stopped");
}
catch (InterruptedException e)
{
_log.log(Level.WARNING, "There has been a problem shuting down the thread pool manager!", e);
}
}
public boolean isShutdown()
{
return _shutdown;
}
public void purge()
{
_effectsScheduledThreadPool.purge();
_generalScheduledThreadPool.purge();
_aiScheduledThreadPool.purge();
_eventScheduledThreadPool.purge();
_ioPacketsThreadPool.purge();
_generalPacketsThreadPool.purge();
_generalThreadPool.purge();
_eventThreadPool.purge();
}
public String getPacketStats()
{
final StringBuilder sb = new StringBuilder(1000);
final ThreadFactory tf = _generalPacketsThreadPool.getThreadFactory();
if (tf instanceof PriorityThreadFactory)
{
final PriorityThreadFactory ptf = (PriorityThreadFactory) tf;
final int count = ptf.getGroup().activeCount();
final Thread[] threads = new Thread[count + 2];
ptf.getGroup().enumerate(threads);
StringUtil.append(sb, "General Packet Thread Pool:" + Config.EOL + "Tasks in the queue: ", String.valueOf(_generalPacketsThreadPool.getQueue().size()), Config.EOL + "Showing threads stack trace:" + Config.EOL + "There should be ", String.valueOf(count), " Threads" + Config.EOL);
for (Thread t : threads)
{
if (t == null)
{
continue;
}
StringUtil.append(sb, t.getName(), Config.EOL);
for (StackTraceElement ste : t.getStackTrace())
{
StringUtil.append(sb, ste.toString(), Config.EOL);
}
}
}
sb.append("Packet Tp stack traces printed.");
sb.append(Config.EOL);
return sb.toString();
}
public String getIOPacketStats()
{
final StringBuilder sb = new StringBuilder(1000);
final ThreadFactory tf = _ioPacketsThreadPool.getThreadFactory();
if (tf instanceof PriorityThreadFactory)
{
final PriorityThreadFactory ptf = (PriorityThreadFactory) tf;
final int count = ptf.getGroup().activeCount();
final Thread[] threads = new Thread[count + 2];
ptf.getGroup().enumerate(threads);
StringUtil.append(sb, "I/O Packet Thread Pool:" + Config.EOL + "Tasks in the queue: ", String.valueOf(_ioPacketsThreadPool.getQueue().size()), Config.EOL + "Showing threads stack trace:" + Config.EOL + "There should be ", String.valueOf(count), " Threads" + Config.EOL);
for (Thread t : threads)
{
if (t == null)
{
continue;
}
StringUtil.append(sb, t.getName(), Config.EOL);
for (StackTraceElement ste : t.getStackTrace())
{
StringUtil.append(sb, ste.toString(), Config.EOL);
}
}
}
sb.append("Packet Tp stack traces printed." + Config.EOL);
return sb.toString();
}
public String getGeneralStats()
{
final StringBuilder sb = new StringBuilder(1000);
final ThreadFactory tf = _generalThreadPool.getThreadFactory();
if (tf instanceof PriorityThreadFactory)
{
final PriorityThreadFactory ptf = (PriorityThreadFactory) tf;
final int count = ptf.getGroup().activeCount();
final Thread[] threads = new Thread[count + 2];
ptf.getGroup().enumerate(threads);
StringUtil.append(sb, "General Thread Pool:" + Config.EOL + "Tasks in the queue: ", String.valueOf(_generalThreadPool.getQueue().size()), Config.EOL + "Showing threads stack trace:" + Config.EOL + "There should be ", String.valueOf(count), " Threads" + Config.EOL);
for (Thread t : threads)
{
if (t == null)
{
continue;
}
StringUtil.append(sb, t.getName(), Config.EOL);
for (StackTraceElement ste : t.getStackTrace())
{
StringUtil.append(sb, ste.toString(), Config.EOL);
}
}
}
sb.append("Packet Tp stack traces printed." + Config.EOL);
return sb.toString();
}
protected class PurgeTask implements Runnable
{
@Override
public void run()
{
_effectsScheduledThreadPool.purge();
_generalScheduledThreadPool.purge();
_aiScheduledThreadPool.purge();
_eventScheduledThreadPool.purge();
}
}
private static class SingletonHolder
{
protected static final ThreadPoolManager _instance = new ThreadPoolManager();
}
}

View File

@ -0,0 +1,901 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import com.l2jmobius.gameserver.GameTimeController;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import com.l2jmobius.gameserver.network.serverpackets.AutoAttackStart;
import com.l2jmobius.gameserver.network.serverpackets.AutoAttackStop;
import com.l2jmobius.gameserver.network.serverpackets.Die;
import com.l2jmobius.gameserver.network.serverpackets.MoveToLocation;
import com.l2jmobius.gameserver.network.serverpackets.MoveToPawn;
import com.l2jmobius.gameserver.network.serverpackets.StopMove;
import com.l2jmobius.gameserver.network.serverpackets.StopRotation;
import com.l2jmobius.gameserver.taskmanager.AttackStanceTaskManager;
/**
* Mother class of all objects AI in the world.<br>
* AbastractAI :<br>
* <li>L2CharacterAI</li>
*/
public abstract class AbstractAI implements Ctrl
{
protected final Logger _log = Logger.getLogger(getClass().getName());
private NextAction _nextAction;
/**
* @return the _nextAction
*/
public NextAction getNextAction()
{
return _nextAction;
}
/**
* @param nextAction the next action to set.
*/
public void setNextAction(NextAction nextAction)
{
_nextAction = nextAction;
}
private class FollowTask implements Runnable
{
protected int _range = 70;
public FollowTask()
{
}
public FollowTask(int range)
{
_range = range;
}
@Override
public void run()
{
try
{
if (_followTask == null)
{
return;
}
final L2Character followTarget = _followTarget; // copy to prevent NPE
if (followTarget == null)
{
if (_actor instanceof L2Summon)
{
((L2Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
if (!_actor.isInsideRadius(followTarget, _range, true, false))
{
if (!_actor.isInsideRadius(followTarget, 3000, true, false))
{
// if the target is too far (maybe also teleported)
if (_actor instanceof L2Summon)
{
((L2Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
moveToPawn(followTarget, _range);
}
}
catch (Exception e)
{
_log.warning(getClass().getSimpleName() + ": Error: " + e.getMessage());
}
}
}
/** The character that this AI manages */
protected final L2Character _actor;
/** Current long-term intention */
protected CtrlIntention _intention = AI_INTENTION_IDLE;
/** Current long-term intention parameter */
protected Object _intentionArg0 = null;
/** Current long-term intention parameter */
protected Object _intentionArg1 = null;
/** Flags about client's state, in order to know which messages to send */
protected volatile boolean _clientMoving;
/** Flags about client's state, in order to know which messages to send */
protected volatile boolean _clientAutoAttacking;
/** Flags about client's state, in order to know which messages to send */
protected int _clientMovingToPawnOffset;
/** Different targets this AI maintains */
private L2Object _target;
private L2Character _castTarget;
protected L2Character _attackTarget;
protected L2Character _followTarget;
/** The skill we are currently casting by INTENTION_CAST */
Skill _skill;
/** Different internal state flags */
private int _moveToPawnTimeout;
protected Future<?> _followTask = null;
private static final int FOLLOW_INTERVAL = 1000;
private static final int ATTACK_FOLLOW_INTERVAL = 500;
/**
* Constructor of AbstractAI.
* @param creature the creature
*/
protected AbstractAI(L2Character creature)
{
_actor = creature;
}
/**
* @return the L2Character managed by this Accessor AI.
*/
@Override
public L2Character getActor()
{
return _actor;
}
/**
* @return the current Intention.
*/
@Override
public CtrlIntention getIntention()
{
return _intention;
}
protected void setCastTarget(L2Character target)
{
_castTarget = target;
}
/**
* @return the current cast target.
*/
public L2Character getCastTarget()
{
return _castTarget;
}
protected void setAttackTarget(L2Character target)
{
_attackTarget = target;
}
/**
* @return current attack target.
*/
@Override
public L2Character getAttackTarget()
{
return _attackTarget;
}
/**
* Set the Intention of this AbstractAI.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method is USED by AI classes</B></FONT><B><U><br>
* Overridden in </U> : </B><BR>
* <B>L2AttackableAI</B> : Create an AI Task executed every 1s (if necessary)<BR>
* <B>L2PlayerAI</B> : Stores the current AI intention parameters to later restore it if necessary.
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
_intention = intention;
_intentionArg0 = arg0;
_intentionArg1 = arg1;
}
/**
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT>
* @param intention The new Intention to set to the AI
*/
@Override
public final void setIntention(CtrlIntention intention)
{
setIntention(intention, null, null);
}
/**
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention (optional target)
*/
@Override
public final void setIntention(CtrlIntention intention, Object arg0)
{
setIntention(intention, arg0, null);
}
@Override
public final void setIntention(CtrlIntention intention, Object arg0, Object arg1)
{
// Stop the follow mode if necessary
if ((intention != AI_INTENTION_FOLLOW) && (intention != AI_INTENTION_ATTACK))
{
stopFollow();
}
// Launch the onIntention method of the L2CharacterAI corresponding to the new Intention
switch (intention)
{
case AI_INTENTION_IDLE:
{
onIntentionIdle();
break;
}
case AI_INTENTION_ACTIVE:
{
onIntentionActive();
break;
}
case AI_INTENTION_REST:
{
onIntentionRest();
break;
}
case AI_INTENTION_ATTACK:
{
onIntentionAttack((L2Character) arg0);
break;
}
case AI_INTENTION_CAST:
{
onIntentionCast((Skill) arg0, (L2Object) arg1);
break;
}
case AI_INTENTION_MOVE_TO:
{
onIntentionMoveTo((Location) arg0);
break;
}
case AI_INTENTION_FOLLOW:
{
onIntentionFollow((L2Character) arg0);
break;
}
case AI_INTENTION_PICK_UP:
{
onIntentionPickUp((L2Object) arg0);
break;
}
case AI_INTENTION_INTERACT:
{
onIntentionInteract((L2Object) arg0);
break;
}
}
// If do move or follow intention drop next action.
if ((_nextAction != null) && _nextAction.getIntentions().contains(intention))
{
_nextAction = null;
}
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned period)</B></FONT>
* @param evt The event whose the AI must be notified
*/
@Override
public final void notifyEvent(CtrlEvent evt)
{
notifyEvent(evt, null, null);
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event. <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned period)</B></FONT>
* @param evt The event whose the AI must be notified
* @param arg0 The first parameter of the Event (optional target)
*/
@Override
public final void notifyEvent(CtrlEvent evt, Object arg0)
{
notifyEvent(evt, arg0, null);
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event. <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned period)</B></FONT>
* @param evt The event whose the AI must be notified
*/
@Override
public final void notifyEvent(CtrlEvent evt, Object... args)
{
if ((!_actor.isVisible() && !_actor.isTeleporting()) || !_actor.hasAI())
{
return;
}
switch (evt)
{
case EVT_THINK:
{
onEvtThink();
break;
}
case EVT_ATTACKED:
{
onEvtAttacked((L2Character) args[0]);
break;
}
case EVT_AGGRESSION:
{
onEvtAggression((L2Character) args[0], ((Number) args[1]).intValue());
break;
}
case EVT_STUNNED:
{
onEvtStunned((L2Character) args[0]);
break;
}
case EVT_PARALYZED:
{
onEvtParalyzed((L2Character) args[0]);
break;
}
case EVT_SLEEPING:
{
onEvtSleeping((L2Character) args[0]);
break;
}
case EVT_ROOTED:
{
onEvtRooted((L2Character) args[0]);
break;
}
case EVT_CONFUSED:
{
onEvtConfused((L2Character) args[0]);
break;
}
case EVT_MUTED:
{
onEvtMuted((L2Character) args[0]);
break;
}
case EVT_EVADED:
{
onEvtEvaded((L2Character) args[0]);
break;
}
case EVT_READY_TO_ACT:
{
if (!_actor.isCastingNow() && !_actor.isCastingSimultaneouslyNow())
{
onEvtReadyToAct();
}
break;
}
case EVT_USER_CMD:
{
onEvtUserCmd(args[0], args[1]);
break;
}
case EVT_ARRIVED:
{
// happens e.g. from stopmove but we don't process it if we're casting
if (!_actor.isCastingNow() && !_actor.isCastingSimultaneouslyNow())
{
onEvtArrived();
}
break;
}
case EVT_ARRIVED_REVALIDATE:
{
// this is disregarded if the char is not moving any more
if (_actor.isMoving())
{
onEvtArrivedRevalidate();
}
break;
}
case EVT_ARRIVED_BLOCKED:
{
onEvtArrivedBlocked((Location) args[0]);
break;
}
case EVT_FORGET_OBJECT:
{
onEvtForgetObject((L2Object) args[0]);
break;
}
case EVT_CANCEL:
{
onEvtCancel();
break;
}
case EVT_DEAD:
{
onEvtDead();
break;
}
case EVT_FAKE_DEATH:
{
onEvtFakeDeath();
break;
}
case EVT_FINISH_CASTING:
{
onEvtFinishCasting();
break;
}
case EVT_AFRAID:
{
onEvtAfraid((L2Character) args[0], (Boolean) args[1]);
break;
}
}
// Do next action.
if ((_nextAction != null) && _nextAction.getEvents().contains(evt))
{
_nextAction.doAction();
}
}
protected abstract void onIntentionIdle();
protected abstract void onIntentionActive();
protected abstract void onIntentionRest();
protected abstract void onIntentionAttack(L2Character target);
protected abstract void onIntentionCast(Skill skill, L2Object target);
protected abstract void onIntentionMoveTo(Location destination);
protected abstract void onIntentionFollow(L2Character target);
protected abstract void onIntentionPickUp(L2Object item);
protected abstract void onIntentionInteract(L2Object object);
protected abstract void onEvtThink();
protected abstract void onEvtAttacked(L2Character attacker);
protected abstract void onEvtAggression(L2Character target, int aggro);
protected abstract void onEvtStunned(L2Character attacker);
protected abstract void onEvtParalyzed(L2Character attacker);
protected abstract void onEvtSleeping(L2Character attacker);
protected abstract void onEvtRooted(L2Character attacker);
protected abstract void onEvtConfused(L2Character attacker);
protected abstract void onEvtMuted(L2Character attacker);
protected abstract void onEvtEvaded(L2Character attacker);
protected abstract void onEvtReadyToAct();
protected abstract void onEvtUserCmd(Object arg0, Object arg1);
protected abstract void onEvtArrived();
protected abstract void onEvtArrivedRevalidate();
protected abstract void onEvtArrivedBlocked(Location blocked_at_pos);
protected abstract void onEvtForgetObject(L2Object object);
protected abstract void onEvtCancel();
protected abstract void onEvtDead();
protected abstract void onEvtFakeDeath();
protected abstract void onEvtFinishCasting();
protected abstract void onEvtAfraid(L2Character effector, boolean start);
/**
* Cancel action client side by sending Server->Client packet ActionFailed to the L2PcInstance actor. <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
protected void clientActionFailed()
{
if (_actor instanceof L2PcInstance)
{
_actor.sendPacket(ActionFailed.STATIC_PACKET);
}
}
/**
* Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param pawn
* @param offset
*/
protected void moveToPawn(L2Object pawn, int offset)
{
// Check if actor can move
if (!_actor.isMovementDisabled())
{
if (offset < 10)
{
offset = 10;
}
// prevent possible extra calls to this function (there is none?),
// also don't send movetopawn packets too often
boolean sendPacket = true;
if (_clientMoving && (_target == pawn))
{
if (_clientMovingToPawnOffset == offset)
{
if (GameTimeController.getInstance().getGameTicks() < _moveToPawnTimeout)
{
return;
}
sendPacket = false;
}
else if (_actor.isOnGeodataPath() && (GameTimeController.getInstance().getGameTicks() < (_moveToPawnTimeout + 10)))
{
return;
}
}
// Set AI movement data
_clientMoving = true;
_clientMovingToPawnOffset = offset;
_target = pawn;
_moveToPawnTimeout = GameTimeController.getInstance().getGameTicks();
_moveToPawnTimeout += 1000 / GameTimeController.MILLIS_IN_TICK;
if (pawn == null)
{
return;
}
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
_actor.moveToLocation(pawn.getX(), pawn.getY(), pawn.getZ(), offset);
if (!_actor.isMoving())
{
clientActionFailed();
return;
}
// Send a Server->Client packet MoveToPawn/CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
if (pawn instanceof L2Character)
{
if (_actor.isOnGeodataPath())
{
_actor.broadcastPacket(new MoveToLocation(_actor));
_clientMovingToPawnOffset = 0;
}
else if (sendPacket)
{
_actor.broadcastPacket(new MoveToPawn(_actor, (L2Character) pawn, offset));
}
}
else
{
_actor.broadcastPacket(new MoveToLocation(_actor));
}
}
else
{
clientActionFailed();
}
}
/**
* Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param x
* @param y
* @param z
*/
protected void moveTo(int x, int y, int z)
{
// Chek if actor can move
if (!_actor.isMovementDisabled())
{
// Set AI movement data
_clientMoving = true;
_clientMovingToPawnOffset = 0;
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
_actor.moveToLocation(x, y, z, 0);
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new MoveToLocation(_actor));
}
else
{
clientActionFailed();
}
}
/**
* Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param loc
*/
protected void clientStopMoving(Location loc)
{
// Stop movement of the L2Character
if (_actor.isMoving())
{
_actor.stopMove(loc);
}
_clientMovingToPawnOffset = 0;
if (!_clientMoving && (loc == null))
{
return;
}
_clientMoving = false;
// Send a Server->Client packet StopMove to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new StopMove(_actor));
if (loc != null)
{
// Send a Server->Client packet StopRotation to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new StopRotation(_actor.getObjectId(), loc.getHeading(), 0));
}
}
/**
* Client has already arrived to target, no need to force StopMove packet.
*/
protected void clientStoppedMoving()
{
if (_clientMovingToPawnOffset > 0) // movetoPawn needs to be stopped
{
_clientMovingToPawnOffset = 0;
_actor.broadcastPacket(new StopMove(_actor));
}
_clientMoving = false;
}
public boolean isAutoAttacking()
{
return _clientAutoAttacking;
}
public void setAutoAttacking(boolean isAutoAttacking)
{
if (_actor instanceof L2Summon)
{
final L2Summon summon = (L2Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().setAutoAttacking(isAutoAttacking);
}
return;
}
_clientAutoAttacking = isAutoAttacking;
}
/**
* Start the actor Auto Attack client side by sending Server->Client packet AutoAttackStart <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
public void clientStartAutoAttack()
{
if (_actor instanceof L2Summon)
{
final L2Summon summon = (L2Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStartAutoAttack();
}
return;
}
if (!isAutoAttacking())
{
if (_actor.isPlayer() && _actor.hasSummon())
{
_actor.getSummon().broadcastPacket(new AutoAttackStart(_actor.getSummon().getObjectId()));
}
// Send a Server->Client packet AutoAttackStart to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new AutoAttackStart(_actor.getObjectId()));
setAutoAttacking(true);
}
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
/**
* Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
public void clientStopAutoAttack()
{
if (_actor instanceof L2Summon)
{
final L2Summon summon = (L2Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStopAutoAttack();
}
return;
}
if (_actor instanceof L2PcInstance)
{
if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor) && isAutoAttacking())
{
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
}
else if (isAutoAttacking())
{
_actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId()));
setAutoAttacking(false);
}
}
/**
* Kill the actor client side by sending Server->Client packet AutoAttackStop, StopMove/StopRotation, Die <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
protected void clientNotifyDead()
{
// Send a Server->Client packet Die to the actor and all L2PcInstance in its _knownPlayers
final Die msg = new Die(_actor);
_actor.broadcastPacket(msg);
// Init AI
_intention = AI_INTENTION_IDLE;
_target = null;
_castTarget = null;
_attackTarget = null;
// Cancel the follow task if necessary
stopFollow();
}
/**
* Update the state of this actor client side by sending Server->Client packet MoveToPawn/CharMoveToLocation and AutoAttackStart to the L2PcInstance player.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param player The L2PcIstance to notify with state of this L2Character
*/
public void describeStateToPlayer(L2PcInstance player)
{
if (getActor().isVisibleFor(player) && _clientMoving)
{
if ((_clientMovingToPawnOffset != 0) && (_followTarget != null))
{
// Send a Server->Client packet MoveToPawn to the actor and all L2PcInstance in its _knownPlayers
player.sendPacket(new MoveToPawn(_actor, _followTarget, _clientMovingToPawnOffset));
}
else
{
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
player.sendPacket(new MoveToLocation(_actor));
}
}
}
/**
* Create and Launch an AI Follow Task to execute every 1s.
* @param target The L2Character to follow
*/
public synchronized void startFollow(L2Character target)
{
if (_followTask != null)
{
_followTask.cancel(false);
_followTask = null;
}
// Create and Launch an AI Follow Task to execute every 1s
_followTarget = target;
_followTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new FollowTask(), 5, FOLLOW_INTERVAL);
}
/**
* Create and Launch an AI Follow Task to execute every 0.5s, following at specified range.
* @param target The L2Character to follow
* @param range
*/
public synchronized void startFollow(L2Character target, int range)
{
if (_followTask != null)
{
_followTask.cancel(false);
_followTask = null;
}
_followTarget = target;
_followTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new FollowTask(range), 5, ATTACK_FOLLOW_INTERVAL);
}
/**
* Stop an AI Follow Task.
*/
public synchronized void stopFollow()
{
if (_followTask != null)
{
// Stop the Follow Task
_followTask.cancel(false);
_followTask = null;
}
_followTarget = null;
}
protected L2Character getFollowTarget()
{
return _followTarget;
}
protected L2Object getTarget()
{
return _target;
}
protected void setTarget(L2Object target)
{
_target = target;
}
/**
* Stop all Ai tasks and futures.
*/
public void stopAITask()
{
stopFollow();
}
@Override
public String toString()
{
return "Actor: " + _actor;
}
}

View File

@ -0,0 +1,103 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
* Interface of AI and client state.<br>
* To correctly send messages to client we need it's state.<br>
* For example, if we've sent 'StartAutoAttack' message, we need to send 'StopAutoAttack' message before any other action.<br>
* Or if we've sent 'MoveToPawn', we need to send 'StopMove' when the movement of a character is canceled (by Root spell or any other reason).<br>
* Thus, we need to know the state of client, i.e. which messages we've sent and how the client will show the scene.<br>
* Close to this task is the task of AI.<br>
* If a player's character is attacking a mob, his ATTACK may be interrupted by an event, that temporary disable attacking.<br>
* But when the possibility to ATTACK will be enabled, the character must continue the ATTACK.<br>
* For mobs it may be more complex, since we want them to decide when to use magic, or when to follow the player for physical combat, or when to escape, to help another mob, etc.<br>
* This interface is hiding complexity of server<->client interaction and multiple states of a character.<br>
* It allows to set a desired, simple "wish" of a character, and the implementation of this interface will take care about the rest.<br>
* The goal of a character may be like "ATTACK", "random walk" and so on.<br>
* To reach the goal implementation will split it into several small actions, several steps (possibly repeatable).<br>
* Like "run to target" then "hit it", then if target is not dead - repeat.<br>
* This flow of simpler steps may be interrupted by incoming events.<br>
* Like a character's movement was disabled (by Root spell, for instance).<br>
* Depending on character's ability AI may choose to wait, or to use magic ATTACK and so on.<br>
* Additionally incoming events are compared with client's state of the character,<br>
* and required network messages are sent to client's, i.e. if we have incoming event that character's movement was disabled, it causes changing if its behavior,<br>
* and if client's state for the character is "moving" we send messages to clients to stop the avatar/mob.
*/
public interface Ctrl
{
/**
* Gets the actor.
* @return the actor
*/
L2Character getActor();
/**
* Gets the intention.
* @return the intention
*/
CtrlIntention getIntention();
/**
* Gets the attack target.
* @return the attack target
*/
L2Character getAttackTarget();
/**
* Set general state/intention for AI, with optional data.
* @param intention the new intention
*/
void setIntention(CtrlIntention intention);
/**
* Sets the intention.
* @param intention the intention
* @param arg0 the arg0
*/
void setIntention(CtrlIntention intention, Object arg0);
/**
* Sets the intention.
* @param intention the intention
* @param arg0 the arg0
* @param arg1 the arg1
*/
void setIntention(CtrlIntention intention, Object arg0, Object arg1);
/**
* Event, that notifies about previous step result, or user command, that does not change current general intention.
* @param evt the event
*/
void notifyEvent(CtrlEvent evt);
/**
* Notify an event.
* @param evt the event
* @param arg0 the arg0
*/
void notifyEvent(CtrlEvent evt, Object arg0);
/**
* Notify an event.
* @param evt the event
* @param args the args
*/
void notifyEvent(CtrlEvent evt, Object... args);
}

View File

@ -0,0 +1,88 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
/**
* This class contains an enum of each possibles events that can happen on an AI character.
*/
public enum CtrlEvent
{
/**
* Something has changed, usually a previous step has being completed or maybe was completed, the AI must thing on next action.
*/
EVT_THINK,
/**
* The actor was attacked. This event comes each time a physical or magical<br>
* attack was done on the actor. NPC may start attack in response, or ignore<br>
* this event if they already attack someone, or change target and so on.
*/
EVT_ATTACKED,
/** Increase/decrease aggression towards a target, or reduce global aggression if target is null */
EVT_AGGRESSION,
/** Actor is in stun state */
EVT_STUNNED,
/** Actor is paralyzed or petrified */
EVT_PARALYZED,
/** Actor starts/stops sleeping */
EVT_SLEEPING,
/** Actor is in rooted state (cannot move) */
EVT_ROOTED,
/** Actor evaded hit **/
EVT_EVADED,
/**
* An event that previous action was completed. The action may be an attempt to physically/magically hit an enemy, or an action that discarded attack attempt has finished.
*/
EVT_READY_TO_ACT,
/**
* User's command, like using a combat magic or changing weapon, etc. The command is not intended to change final goal
*/
EVT_USER_CMD,
/**
* The actor arrived to assigned location, or it's a time to modify movement destination (follow, interact, random move and others intentions).
*/
EVT_ARRIVED,
/**
* The actor arrived to an intermediate point, and needs to revalidate destination. This is sent when follow/move to pawn if destination is far away.
*/
EVT_ARRIVED_REVALIDATE,
/** The actor cannot move anymore. */
EVT_ARRIVED_BLOCKED,
/** Forgets an object (if it's used as attack target, follow target and so on */
EVT_FORGET_OBJECT,
/**
* Attempt to cancel current step execution, but not change the intention.<br>
* For example, the actor was put into a stun, so it's current attack<br>
* or movement has to be canceled. But after the stun state expired,<br>
* the actor may try to attack again. Another usage for CANCEL is a user's<br>
* attempt to cancel a cast/bow attack and so on.
*/
EVT_CANCEL,
/** The character is dead */
EVT_DEAD,
/** The character looks like dead */
EVT_FAKE_DEATH,
/** The character attack anyone randomly **/
EVT_CONFUSED,
/** The character cannot cast spells anymore **/
EVT_MUTED,
/** The character flee in random directions **/
EVT_AFRAID,
/** The character finish casting **/
EVT_FINISH_CASTING,
/** The character betrayed its master */
EVT_BETRAYED
}

View File

@ -0,0 +1,42 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
/**
* Enumeration of generic intentions of an NPC/PC, an intention may require several steps to be completed.
*/
public enum CtrlIntention
{
/** Do nothing, disconnect AI of NPC if no players around */
AI_INTENTION_IDLE,
/** Alerted state without goal : scan attackable targets, random walk, etc */
AI_INTENTION_ACTIVE,
/** Rest (sit until attacked) */
AI_INTENTION_REST,
/** Attack target (cast combat magic, go to target, combat), may be ignored, if target is locked on another character or a peaceful zone and so on. */
AI_INTENTION_ATTACK,
/** Cast a spell, depending on the spell - may start or stop attacking */
AI_INTENTION_CAST,
/** Just move to another location */
AI_INTENTION_MOVE_TO,
/** Like move, but check target's movement and follow it */
AI_INTENTION_FOLLOW,
/** PickUp and item, (got to item, pickup it, become idle */
AI_INTENTION_PICK_UP,
/** Move to target, then interact */
AI_INTENTION_INTERACT;
}

View File

@ -0,0 +1,75 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.actor.instance.L2AirShipInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.serverpackets.ExMoveToLocationAirShip;
import com.l2jmobius.gameserver.network.serverpackets.ExStopMoveAirShip;
/**
* @author DS
*/
public class L2AirShipAI extends L2VehicleAI
{
public L2AirShipAI(L2AirShipInstance creature)
{
super(creature);
}
@Override
protected void moveTo(int x, int y, int z)
{
if (!_actor.isMovementDisabled())
{
_clientMoving = true;
_actor.moveToLocation(x, y, z, 0);
_actor.broadcastPacket(new ExMoveToLocationAirShip(getActor()));
}
}
@Override
protected void clientStopMoving(Location loc)
{
if (_actor.isMoving())
{
_actor.stopMove(loc);
}
if (_clientMoving || (loc != null))
{
_clientMoving = false;
_actor.broadcastPacket(new ExStopMoveAirShip(getActor()));
}
}
@Override
public void describeStateToPlayer(L2PcInstance player)
{
if (_clientMoving)
{
player.sendPacket(new ExMoveToLocationAirShip(getActor()));
}
}
@Override
public L2AirShipInstance getActor()
{
return (L2AirShipInstance) _actor;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,84 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.actor.instance.L2BoatInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.serverpackets.VehicleDeparture;
import com.l2jmobius.gameserver.network.serverpackets.VehicleInfo;
import com.l2jmobius.gameserver.network.serverpackets.VehicleStarted;
/**
* @author DS
*/
public class L2BoatAI extends L2VehicleAI
{
public L2BoatAI(L2BoatInstance creature)
{
super(creature);
}
@Override
protected void moveTo(int x, int y, int z)
{
if (_actor.isMovementDisabled())
{
return;
}
if (!_clientMoving)
{
_actor.broadcastPacket(new VehicleStarted(getActor(), 1));
}
_clientMoving = true;
_actor.moveToLocation(x, y, z, 0);
_actor.broadcastPacket(new VehicleDeparture(getActor()));
}
@Override
protected void clientStopMoving(Location loc)
{
if (_actor.isMoving())
{
_actor.stopMove(loc);
}
if (_clientMoving || (loc != null))
{
_clientMoving = false;
_actor.broadcastPacket(new VehicleStarted(getActor(), 0));
_actor.broadcastPacket(new VehicleInfo(getActor()));
}
}
@Override
public void describeStateToPlayer(L2PcInstance player)
{
if (_clientMoving)
{
player.sendPacket(new VehicleDeparture(getActor()));
}
}
@Override
public L2BoatInstance getActor()
{
return (L2BoatInstance) _actor;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,507 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import java.util.List;
import java.util.stream.Collectors;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.MobGroup;
import com.l2jmobius.gameserver.model.MobGroupTable;
import com.l2jmobius.gameserver.model.actor.L2Attackable;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.L2Playable;
import com.l2jmobius.gameserver.model.actor.instance.L2ControllableMobInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.Rnd;
/**
* AI for controllable mobs
* @author littlecrow
*/
public final class L2ControllableMobAI extends L2AttackableAI
{
public static final int AI_IDLE = 1;
public static final int AI_NORMAL = 2;
public static final int AI_FORCEATTACK = 3;
public static final int AI_FOLLOW = 4;
public static final int AI_CAST = 5;
public static final int AI_ATTACK_GROUP = 6;
private int _alternateAI;
private boolean _isThinking; // to prevent thinking recursively
private boolean _isNotMoving;
private L2Character _forcedTarget;
private MobGroup _targetGroup;
public L2ControllableMobAI(L2ControllableMobInstance creature)
{
super(creature);
setAlternateAI(AI_IDLE);
}
protected void thinkFollow()
{
if (!Util.checkIfInRange(MobGroupTable.FOLLOW_RANGE, _actor, getForcedTarget(), true))
{
moveTo(getForcedTarget().getX() + (((Rnd.nextInt(2) == 0) ? -1 : 1) * Rnd.nextInt(MobGroupTable.FOLLOW_RANGE)), getForcedTarget().getY() + (((Rnd.nextInt(2) == 0) ? -1 : 1) * Rnd.nextInt(MobGroupTable.FOLLOW_RANGE)), getForcedTarget().getZ());
}
}
@Override
protected void onEvtThink()
{
if (isThinking())
{
return;
}
setThinking(true);
try
{
switch (getAlternateAI())
{
case AI_IDLE:
{
if (getIntention() != CtrlIntention.AI_INTENTION_ACTIVE)
{
setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
}
break;
}
case AI_FOLLOW:
{
thinkFollow();
break;
}
case AI_CAST:
{
thinkCast();
break;
}
case AI_FORCEATTACK:
{
thinkForceAttack();
break;
}
case AI_ATTACK_GROUP:
{
thinkAttackGroup();
break;
}
default:
{
if (getIntention() == AI_INTENTION_ACTIVE)
{
thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
break;
}
}
}
finally
{
setThinking(false);
}
}
@Override
protected void thinkCast()
{
final L2Attackable npc = (L2Attackable) _actor;
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
{
setAttackTarget(findNextRndTarget());
clientStopMoving(null);
}
if (getAttackTarget() == null)
{
return;
}
npc.setTarget(getAttackTarget());
if (_actor.isMuted())
{
return;
}
int max_range = 0;
// check distant skills
for (Skill sk : _actor.getAllSkills())
{
if (Util.checkIfInRange(sk.getCastRange(), _actor, getAttackTarget(), true) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, sk.getCastRange());
}
if (!isNotMoving())
{
moveToPawn(getAttackTarget(), max_range);
}
}
protected void thinkAttackGroup()
{
final L2Character target = getForcedTarget();
if ((target == null) || target.isAlikeDead())
{
// try to get next group target
setForcedTarget(findNextGroupTarget());
clientStopMoving(null);
}
if (target == null)
{
return;
}
_actor.setTarget(target);
// as a response, we put the target in a forced attack mode
final L2ControllableMobInstance theTarget = (L2ControllableMobInstance) target;
final L2ControllableMobAI ctrlAi = (L2ControllableMobAI) theTarget.getAI();
ctrlAi.forceAttack(_actor);
final double dist2 = _actor.calculateDistance(target, false, true);
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius();
int max_range = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
for (Skill sk : _actor.getAllSkills())
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
if (!isNotMoving())
{
moveToPawn(target, range);
}
return;
}
_actor.doAttack(target);
}
protected void thinkForceAttack()
{
if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead())
{
clientStopMoving(null);
setIntention(AI_INTENTION_ACTIVE);
setAlternateAI(AI_IDLE);
}
_actor.setTarget(getForcedTarget());
final double dist2 = _actor.calculateDistance(getForcedTarget(), false, true);
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getForcedTarget().getTemplate().getCollisionRadius();
int max_range = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
for (Skill sk : _actor.getAllSkills())
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
if (!isNotMoving())
{
moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange()/* range */);
}
return;
}
_actor.doAttack(getForcedTarget());
}
@Override
protected void thinkAttack()
{
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
{
if (getAttackTarget() != null)
{
// stop hating
((L2Attackable) _actor).stopHating(getAttackTarget());
}
setIntention(AI_INTENTION_ACTIVE);
}
else
{
// notify aggression
if (((L2Npc) _actor).getTemplate().getClans() != null)
{
for (L2Object obj : _actor.getKnownList().getKnownObjects().values())
{
if (!(obj instanceof L2Npc))
{
continue;
}
final L2Npc npc = (L2Npc) obj;
if (!npc.isInMyClan((L2Npc) _actor))
{
continue;
}
if (_actor.isInsideRadius(npc, npc.getTemplate().getClanHelpRange(), false, true) && (Math.abs(getAttackTarget().getZ() - npc.getZ()) < 200))
{
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
}
}
}
_actor.setTarget(getAttackTarget());
final double dist2 = _actor.calculateDistance(getAttackTarget(), false, true);
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius();
int max_range = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
for (Skill sk : _actor.getAllSkills())
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
moveToPawn(getAttackTarget(), range);
return;
}
// Force mobs to attack anybody if confused.
final L2Character hated = _actor.isConfused() ? findNextRndTarget() : getAttackTarget();
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE);
return;
}
if (hated != getAttackTarget())
{
setAttackTarget(hated);
}
if (!_actor.isMuted() && (Rnd.nextInt(5) == 3))
{
for (Skill sk : _actor.getAllSkills())
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume(sk)))
{
_actor.doCast(sk);
return;
}
}
}
_actor.doAttack(getAttackTarget());
}
}
@Override
protected void thinkActive()
{
setAttackTarget(findNextRndTarget());
final L2Character hated = _actor.isConfused() ? findNextRndTarget() : getAttackTarget();
if (hated == null)
{
return;
}
_actor.setRunning();
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
}
private boolean checkAutoAttackCondition(L2Character target)
{
if ((target == null) || (target instanceof L2NpcInstance) || (target instanceof L2DoorInstance))
{
return false;
}
// TODO(Zoey76)[#112]: This check must change if summon fall in L2Npc hierarchy.
if (target.isNpc())
{
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul() || target.isAlikeDead())
{
return false;
}
// Spawn protection (only against mobs)
if (target.isPlayer() && ((L2PcInstance) target).isSpawnProtected())
{
return false;
}
final L2Attackable me = getActiveChar();
if (!me.isInsideRadius(target, me.getAggroRange(), false, false) || (Math.abs(_actor.getZ() - target.getZ()) > 100))
{
return false;
}
// Check if the target isn't in silent move mode
if (target.isPlayable() && ((L2Playable) target).isSilentMovingAffected())
{
return false;
}
return me.isAggressive();
}
private L2Character findNextRndTarget()
{
final List<L2Character> potentialTarget = _actor.getKnownList().getKnownCharactersInRadius(getActiveChar().getAggroRange()).stream().filter(this::checkAutoAttackCondition).collect(Collectors.toList());
return potentialTarget.isEmpty() ? null : potentialTarget.get(Rnd.nextInt(potentialTarget.size()));
}
private L2ControllableMobInstance findNextGroupTarget()
{
return getGroupTarget().getRandomMob();
}
public int getAlternateAI()
{
return _alternateAI;
}
public void setAlternateAI(int _alternateai)
{
_alternateAI = _alternateai;
}
public void forceAttack(L2Character target)
{
setAlternateAI(AI_FORCEATTACK);
setForcedTarget(target);
}
public void forceAttackGroup(MobGroup group)
{
setForcedTarget(null);
setGroupTarget(group);
setAlternateAI(AI_ATTACK_GROUP);
}
public void stop()
{
setAlternateAI(AI_IDLE);
clientStopMoving(null);
}
public void move(int x, int y, int z)
{
moveTo(x, y, z);
}
public void follow(L2Character target)
{
setAlternateAI(AI_FOLLOW);
setForcedTarget(target);
}
public boolean isThinking()
{
return _isThinking;
}
public boolean isNotMoving()
{
return _isNotMoving;
}
public void setNotMoving(boolean isNotMoving)
{
_isNotMoving = isNotMoving;
}
public void setThinking(boolean isThinking)
{
_isThinking = isThinking;
}
private L2Character getForcedTarget()
{
return _forcedTarget;
}
private MobGroup getGroupTarget()
{
return _targetGroup;
}
private void setForcedTarget(L2Character forcedTarget)
{
_forcedTarget = forcedTarget;
}
private void setGroupTarget(MobGroup targetGroup)
{
_targetGroup = targetGroup;
}
}

View File

@ -0,0 +1,176 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.instance.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.skills.Skill;
/**
* @author mkizub
*/
public class L2DoorAI extends L2CharacterAI
{
public L2DoorAI(L2DoorInstance creature)
{
super(creature);
}
@Override
protected void onIntentionIdle()
{
}
@Override
protected void onIntentionActive()
{
}
@Override
protected void onIntentionRest()
{
}
@Override
protected void onIntentionAttack(L2Character target)
{
}
@Override
protected void onIntentionCast(Skill skill, L2Object target)
{
}
@Override
protected void onIntentionMoveTo(Location destination)
{
}
@Override
protected void onIntentionFollow(L2Character target)
{
}
@Override
protected void onIntentionPickUp(L2Object item)
{
}
@Override
protected void onIntentionInteract(L2Object object)
{
}
@Override
protected void onEvtThink()
{
}
@Override
protected void onEvtAttacked(L2Character attacker)
{
ThreadPoolManager.getInstance().executeGeneral(new onEventAttackedDoorTask((L2DoorInstance) _actor, attacker));
}
@Override
protected void onEvtAggression(L2Character target, int aggro)
{
}
@Override
protected void onEvtStunned(L2Character attacker)
{
}
@Override
protected void onEvtSleeping(L2Character attacker)
{
}
@Override
protected void onEvtRooted(L2Character attacker)
{
}
@Override
protected void onEvtReadyToAct()
{
}
@Override
protected void onEvtUserCmd(Object arg0, Object arg1)
{
}
@Override
protected void onEvtArrived()
{
}
@Override
protected void onEvtArrivedRevalidate()
{
}
@Override
protected void onEvtArrivedBlocked(Location blocked_at_loc)
{
}
@Override
protected void onEvtForgetObject(L2Object object)
{
}
@Override
protected void onEvtCancel()
{
}
@Override
protected void onEvtDead()
{
}
private class onEventAttackedDoorTask implements Runnable
{
private final L2DoorInstance _door;
private final L2Character _attacker;
public onEventAttackedDoorTask(L2DoorInstance door, L2Character attacker)
{
_door = door;
_attacker = attacker;
}
@Override
public void run()
{
for (L2DefenderInstance guard : _door.getKnownDefenders())
{
if (_actor.isInsideRadius(guard, guard.getTemplate().getClanHelpRange(), false, true) && (Math.abs(_attacker.getZ() - guard.getZ()) < 200))
{
guard.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, _attacker, 15);
}
}
}
}
}

View File

@ -0,0 +1,810 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.Collection;
import java.util.concurrent.Future;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.GameTimeController;
import com.l2jmobius.gameserver.GeoData;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.actor.L2Attackable;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.L2Playable;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2FortCommanderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.effects.L2EffectType;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.Rnd;
/**
* This class manages AI of L2Attackable.
*/
public class L2FortSiegeGuardAI extends L2CharacterAI implements Runnable
{
private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds
/** The L2Attackable AI task executed every 1s (call onEvtThink method) */
private Future<?> _aiTask;
/** For attack AI, analysis of mob and its targets */
private final SelfAnalysis _selfAnalysis = new SelfAnalysis();
/** The delay after which the attacked is stopped */
private int _attackTimeout;
/** The L2Attackable aggro counter */
private int _globalAggro;
/** The flag used to indicate that a thinking action is in progress */
private boolean _thinking; // to prevent recursive thinking
private final int _attackRange;
/**
* Constructor of L2AttackableAI.
* @param creature the creature
*/
public L2FortSiegeGuardAI(L2DefenderInstance creature)
{
super(creature);
_selfAnalysis.init();
_attackTimeout = Integer.MAX_VALUE;
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
_attackRange = _actor.getPhysicalAttackRange();
}
@Override
public void run()
{
// Launch actions corresponding to the Event Think
onEvtThink();
}
/**
* <B><U> Actor is a L2GuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* <li>The L2MonsterInstance target is aggressive</li>
* </ul>
* <B><U> Actor is a L2SiegeGuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>A siege is in progress</li>
* <li>The L2PcInstance target isn't a Defender</li>
* </ul>
* <B><U> Actor is a L2FriendlyMobInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* </ul>
* <B><U> Actor is a L2MonsterInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The actor is Aggressive</li>
* </ul>
* @param target The targeted L2Object
* @return True if the target is autoattackable (depends on the actor type).
*/
private boolean autoAttackCondition(L2Character target)
{
// Check if the target isn't another guard, folk or a door
if ((target == null) || (target instanceof L2DefenderInstance) || (target instanceof L2NpcInstance) || (target instanceof L2DoorInstance) || target.isAlikeDead() || (target instanceof L2FortCommanderInstance) || (target instanceof L2Playable))
{
L2PcInstance player = null;
if (target instanceof L2PcInstance)
{
player = (L2PcInstance) target;
}
else if (target instanceof L2Summon)
{
player = ((L2Summon) target).getOwner();
}
if ((player == null) || ((player.getClan() != null) && (player.getClan().getFortId() == ((L2Npc) _actor).getFort().getResidenceId())))
{
return false;
}
}
if ((target != null) && target.isInvul() && ((target.isPlayer() && target.isGM()) || (target.isSummon() && ((L2Summon) target).getOwner().isGM())))
{
return false;
}
// Get the owner if the target is a summon
if (target instanceof L2Summon)
{
final L2PcInstance owner = ((L2Summon) target).getOwner();
if (_actor.isInsideRadius(owner, 1000, true, false))
{
target = owner;
}
}
return (!(target instanceof L2Playable) || !((L2Playable) target).isSilentMovingAffected() || _actor.isInsideRadius(target, 250, false, false)) && _actor.isAutoAttackable(target) && GeoData.getInstance().canSeeTarget(_actor, target);
}
/**
* Set the Intention of this L2CharacterAI and create an AI Task executed every 1s (call onEvtThink method) for this L2Attackable.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</B></FONT>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
if (Config.DEBUG)
{
_log.warning(getClass().getSimpleName() + ": changeIntention(" + intention + ", " + arg0 + ", " + arg1 + ")");
}
if (intention == AI_INTENTION_IDLE /* || intention == AI_INTENTION_ACTIVE */) // active becomes idle if only a summon is present
{
// Check if actor is not dead
if (!_actor.isAlikeDead())
{
intention = ((L2Attackable) _actor).getKnownList().getKnownPlayers().isEmpty() ? AI_INTENTION_IDLE : AI_INTENTION_ACTIVE;
}
if (intention == AI_INTENTION_IDLE)
{
// Set the Intention of this L2AttackableAI to AI_INTENTION_IDLE
super.changeIntention(AI_INTENTION_IDLE, null, null);
// Stop AI task and detach AI from NPC
if (_aiTask != null)
{
_aiTask.cancel(true);
_aiTask = null;
}
// Cancel the AI
_actor.detachAI();
return;
}
}
// Set the Intention of this L2AttackableAI to intention
super.changeIntention(intention, arg0, arg1);
// If not idle - create an AI task (schedule onEvtThink repeatedly)
if (_aiTask == null)
{
_aiTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
}
}
/**
* Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.
* @param target The L2Character to attack
*/
@Override
protected void onIntentionAttack(L2Character target)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
// if (_actor.getTarget() != null)
super.onIntentionAttack(target);
}
/**
* Manage AI standard thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update every 1s the _globalAggro counter to come close to 0</li>
* <li>If the actor is Aggressive and can attack, add all autoAttackable L2Character in its Aggro Range to its _aggroList, chose a target and order to attack it</li>
* <li>If the actor can't attack, order to it to return to its home location</li>
* </ul>
*/
private void thinkActive()
{
final L2Attackable npc = (L2Attackable) _actor;
// Update every 1s the _globalAggro counter to come close to 0
if (_globalAggro != 0)
{
if (_globalAggro < 0)
{
_globalAggro++;
}
else
{
_globalAggro--;
}
}
// Add all autoAttackable L2Character in L2Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
// A L2Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
if (_globalAggro >= 0)
{
for (L2Character target : npc.getKnownList().getKnownCharactersInRadius(_attackRange))
{
if (target == null)
{
continue;
}
if (autoAttackCondition(target) && (npc.getHating(target) == 0)) // check aggression
{
npc.addDamageHate(target, 0, 1);
}
}
// Chose a target from its aggroList
final L2Character hated = _actor.isConfused() ? getAttackTarget() : npc.getMostHated();
// Order to the L2Attackable to attack the target
if (hated != null)
{
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
final int aggro = npc.getHating(hated);
if ((aggro + _globalAggro) > 0)
{
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the AI Intention to AI_INTENTION_ATTACK
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated, null);
}
return;
}
}
// Order to the L2SiegeGuardInstance to return to its home location because there's no target to attack
if (_actor.getWalkSpeed() >= 0)
{
(_actor instanceof L2DefenderInstance ? (L2DefenderInstance) _actor : (L2FortCommanderInstance) _actor).returnHome();
}
}
/**
* Manage AI attack thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update the attack timeout if actor is running</li>
* <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
* <li>Call all L2Object of its Faction inside the Faction Range</li>
* <li>Chose a target and order to attack it with magic skill or physical attack</li>
* </ul>
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
*/
private void thinkAttack()
{
if (Config.DEBUG)
{
_log.warning(getClass().getSimpleName() + ": thinkAttack(); timeout=" + (_attackTimeout - GameTimeController.getInstance().getGameTicks()));
}
if ((_attackTimeout < GameTimeController.getInstance().getGameTicks()) && _actor.isRunning())
{
_actor.setWalking();
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
}
final L2Character attackTarget = getAttackTarget();
// Check if target is dead or if timeout is expired to stop this attack
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getInstance().getGameTicks()))
{
// Stop hating this target after the attack timeout or if target is dead
if (attackTarget != null)
{
((L2Attackable) _actor).stopHating(attackTarget);
}
// Cancel target and timeout
_attackTimeout = Integer.MAX_VALUE;
setAttackTarget(null);
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE, null, null);
_actor.setWalking();
return;
}
factionNotifyAndSupport();
attackPrepare();
}
private final void factionNotifyAndSupport()
{
final L2Character target = getAttackTarget();
// Call all L2Object of its Faction inside the Faction Range
if ((((L2Npc) _actor).getTemplate().getClans() == null) || (target == null) || target.isInvul())
{
return;
}
// Go through all L2Character that belong to its faction
// for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(((L2NpcInstance) _actor).getFactionRange()+_actor.getTemplate().collisionRadius))
for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(1000))
{
if (cha == null)
{
continue;
}
if (!(cha instanceof L2Npc))
{
if (_selfAnalysis.hasHealOrResurrect && (cha instanceof L2PcInstance) && ((L2Npc) _actor).getFort().getSiege().checkIsDefender(((L2PcInstance) cha).getClan())//
&& !_actor.isAttackingDisabled() && (cha.getCurrentHp() < (cha.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && cha.isInCombat())
{
for (Skill sk : _selfAnalysis.healSkills)
{
if ((_actor.getCurrentMp() < sk.getMpConsume()) || _actor.isSkillDisabled(sk) || !Util.checkIfInRange(sk.getCastRange(), _actor, cha, true))
{
continue;
}
final int chance = 5;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, cha))
{
break;
}
final L2Object OldTarget = _actor.getTarget();
_actor.setTarget(cha);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
continue;
}
final L2Npc npc = (L2Npc) cha;
if (!npc.isInMyClan((L2Npc) _actor))
{
continue;
}
if (npc.getAI() != null) // TODO: possibly check not needed
{
if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600) && ((npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE) || (npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE)) && target.isInsideRadius(npc, 1500, true, false) && GeoData.getInstance().canSeeTarget(npc, target))
{
// Notify the L2Object AI with EVT_AGGRESSION
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
return;
}
// heal friends
if (_selfAnalysis.hasHealOrResurrect && !_actor.isAttackingDisabled() && (npc.getCurrentHp() < (npc.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && npc.isInCombat())
{
for (Skill sk : _selfAnalysis.healSkills)
{
if ((_actor.getCurrentMp() < sk.getMpConsume()) || _actor.isSkillDisabled(sk) || !Util.checkIfInRange(sk.getCastRange(), _actor, npc, true))
{
continue;
}
final int chance = 4;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, npc))
{
break;
}
final L2Object OldTarget = _actor.getTarget();
_actor.setTarget(npc);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
}
}
private void attackPrepare()
{
// Get all information needed to choose between physical or magical attack
Collection<Skill> skills = null;
double dist_2 = 0;
int range = 0;
final L2DefenderInstance sGuard = _actor instanceof L2FortCommanderInstance ? (L2FortCommanderInstance) _actor : (L2DefenderInstance) _actor;
L2Character attackTarget = getAttackTarget();
try
{
_actor.setTarget(attackTarget);
skills = _actor.getAllSkills();
dist_2 = _actor.calculateDistance(attackTarget, false, true);
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
if (attackTarget.isMoving())
{
range += 50;
}
}
catch (NullPointerException e)
{
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// never attack defenders
if ((attackTarget instanceof L2PcInstance) && sGuard.getFort().getSiege().checkIsDefender(((L2PcInstance) attackTarget).getClan()))
{
// Cancel the target
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// Check if the actor isn't muted and if it is far from target
if (!_actor.isMuted() && (dist_2 > (range * range)))
{
// check for long ranged skills and heal/buff skills
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if ((dist_2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
{
final L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || sk.hasEffectType(L2EffectType.HEAL))
{
boolean useSkillSelf = true;
if (sk.hasEffectType(L2EffectType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.isContinuous() && !sk.isDebuff() && _actor.isAffectedBySkill(sk.getId()))
{
useSkillSelf = false;
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
// Check if the L2SiegeGuardInstance is attacking, knows the target and can't run
if (!_actor.isAttackingNow() && (_actor.getRunSpeed() == 0) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else
{
final double dx = _actor.getX() - attackTarget.getX();
final double dy = _actor.getY() - attackTarget.getY();
final double dz = _actor.getZ() - attackTarget.getZ();
final double homeX = attackTarget.getX() - sGuard.getSpawn().getX();
final double homeY = attackTarget.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance isn't too far from it's home location
if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
else if ((dz * dz) < (170 * 170))
{
if (_selfAnalysis.isMage)
{
range = _selfAnalysis.maxCastRange - 50;
}
if (_actor.getWalkSpeed() <= 0)
{
return;
}
moveToPawn(attackTarget, attackTarget.isMoving() ? range - 70 : range);
}
}
}
else
{
if (_actor.isMuted() && (dist_2 > (range * range)))
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
final double dz = _actor.getZ() - attackTarget.getZ();
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isMage)
{
range = _selfAnalysis.maxCastRange - 50;
}
if (_actor.getWalkSpeed() <= 0)
{
return;
}
moveToPawn(attackTarget, attackTarget.isMoving() ? range - 70 : range);
}
return;
}
if (dist_2 <= (range * range))
{
final L2Character hated = _actor.isConfused() ? attackTarget : ((L2Attackable) _actor).getMostHated();
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE, null, null);
return;
}
if (hated != attackTarget)
{
attackTarget = hated;
}
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// check for close combat skills && heal/buff skills
if (!_actor.isMuted() && (Rnd.nextInt(100) <= 5))
{
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist_2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk))
{
final L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || sk.hasEffectType(L2EffectType.HEAL))
{
boolean useSkillSelf = true;
if (sk.hasEffectType(L2EffectType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.isContinuous() && !sk.isDebuff() && _actor.isAffectedBySkill(sk.getId()))
{
useSkillSelf = false;
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
// Finally, do the physical attack itself
_actor.doAttack(attackTarget);
}
}
}
/**
* Manage AI thinking actions of a L2Attackable.
*/
@Override
protected void onEvtThink()
{
// if(getIntention() != AI_INTENTION_IDLE && (!_actor.isVisible() || !_actor.hasAI() || !_actor.isKnownPlayers()))
// setIntention(AI_INTENTION_IDLE);
// Check if the actor can't use skills and if a thinking action isn't already in progress
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled())
{
return;
}
// Start thinking action
_thinking = true;
try
{
// Manage AI thinks of a L2Attackable
if (getIntention() == AI_INTENTION_ACTIVE)
{
thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
}
finally
{
// Stop thinking action
_thinking = false;
}
}
/**
* Launch actions corresponding to the Event Attacked.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
* <li>Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance</li>
* <li>Set the Intention to AI_INTENTION_ATTACK</li>
* </ul>
* @param attacker The L2Character that attacks the actor
*/
@Override
protected void onEvtAttacked(L2Character attacker)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Set the _globalAggro to 0 to permit attack even just after spawn
if (_globalAggro < 0)
{
_globalAggro = 0;
}
// Add the attacker to the _aggroList of the actor
((L2Attackable) _actor).addDamageHate(attacker, 0, 1);
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK)
{
setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker, null);
}
super.onEvtAttacked(attacker);
}
/**
* Launch actions corresponding to the Event Aggression.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Add the target to the actor _aggroList or update hate if already present</li>
* <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is L2GuardInstance check if it isn't too far from its home location)</li>
* </ul>
* @param aggro The value of hate to add to the actor against the target
*/
@Override
protected void onEvtAggression(L2Character target, int aggro)
{
if (_actor == null)
{
return;
}
final L2Attackable me = (L2Attackable) _actor;
if (target != null)
{
// Add the target to the actor _aggroList or update hate if already present
me.addDamageHate(target, 0, aggro);
// Get the hate of the actor against the target
aggro = me.getHating(target);
if (aggro <= 0)
{
if (me.getMostHated() == null)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
return;
}
// Set the actor AI Intention to AI_INTENTION_ATTACK
if (getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
{
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
final L2DefenderInstance sGuard = _actor instanceof L2FortCommanderInstance ? (L2FortCommanderInstance) _actor : (L2DefenderInstance) _actor;
final double homeX = target.getX() - sGuard.getSpawn().getX();
final double homeY = target.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance is not too far from its home location
if (((homeX * homeX) + (homeY * homeY)) < 3240000)
{
setIntention(CtrlIntention.AI_INTENTION_ATTACK, target, null);
}
}
}
else
{
// currently only for setting lower general aggro
if (aggro >= 0)
{
return;
}
final L2Character mostHated = me.getMostHated();
if (mostHated == null)
{
_globalAggro = -25;
return;
}
for (L2Character aggroed : me.getAggroList().keySet())
{
me.addDamageHate(aggroed, 0, aggro);
}
aggro = me.getHating(mostHated);
if (aggro <= 0)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
}
}
@Override
public void stopAITask()
{
if (_aiTask != null)
{
_aiTask.cancel(false);
_aiTask = null;
}
_actor.detachAI();
super.stopAITask();
}
}

View File

@ -0,0 +1,119 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Playable;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.zone.ZoneId;
import com.l2jmobius.gameserver.network.SystemMessageId;
/**
* This class manages AI of L2Playable.<br>
* L2PlayableAI :
* <li>L2SummonAI</li>
* <li>L2PlayerAI</li>
* @author JIV
*/
public abstract class L2PlayableAI extends L2CharacterAI
{
/**
* @param creature the creature
*/
public L2PlayableAI(L2Playable creature)
{
super(creature);
}
@Override
protected void onIntentionAttack(L2Character target)
{
if (target instanceof L2Playable)
{
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getKarma() > 0) && !(target.isInsideZone(ZoneId.PVP)))
{
// If attacker have karma and have level >= 10 than his target and target have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
return;
}
if (_actor.getActingPlayer().isProtectionBlessingAffected() && ((target.getActingPlayer().getLevel() - _actor.getActingPlayer().getLevel()) >= 10) && (target.getActingPlayer().getKarma() > 0) && !(target.isInsideZone(ZoneId.PVP)))
{
// If target have karma and have level >= 10 than his target and actor have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
return;
}
if (target.getActingPlayer().isCursedWeaponEquipped() && (_actor.getActingPlayer().getLevel() <= 20))
{
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
return;
}
if (_actor.getActingPlayer().isCursedWeaponEquipped() && (target.getActingPlayer().getLevel() <= 20))
{
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
return;
}
}
super.onIntentionAttack(target);
}
@Override
protected void onIntentionCast(Skill skill, L2Object target)
{
if ((target instanceof L2Playable) && skill.isBad())
{
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getKarma() > 0) && !target.isInsideZone(ZoneId.PVP))
{
// If attacker have karma and have level >= 10 than his target and target have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
_actor.setIsCastingNow(false);
return;
}
if (_actor.getActingPlayer().isProtectionBlessingAffected() && ((target.getActingPlayer().getLevel() - _actor.getActingPlayer().getLevel()) >= 10) && (target.getActingPlayer().getKarma() > 0) && !target.isInsideZone(ZoneId.PVP))
{
// If target have karma and have level >= 10 than his target and actor have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
_actor.setIsCastingNow(false);
return;
}
if (target.getActingPlayer().isCursedWeaponEquipped() && ((_actor.getActingPlayer().getLevel() <= 20) || (target.getActingPlayer().getLevel() <= 20)))
{
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
_actor.setIsCastingNow(false);
return;
}
}
super.onIntentionCast(skill, target);
}
}

View File

@ -0,0 +1,354 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_CAST;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_INTERACT;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_MOVE_TO;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_PICK_UP;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_REST;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.entity.Duel;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.skills.targets.L2TargetType;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
public class L2PlayerAI extends L2PlayableAI
{
private boolean _thinking; // to prevent recursive thinking
private IntentionCommand _nextIntention = null;
public L2PlayerAI(L2PcInstance creature)
{
super(creature);
}
void saveNextIntention(CtrlIntention intention, Object arg0, Object arg1)
{
_nextIntention = new IntentionCommand(intention, arg0, arg1);
}
@Override
public IntentionCommand getNextIntention()
{
return _nextIntention;
}
/**
* Saves the current Intention for this L2PlayerAI if necessary and calls changeIntention in AbstractAI.
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
protected synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
// Forget next if it's not cast or it's cast and skill is toggle.
if ((intention != AI_INTENTION_CAST) || ((arg0 != null) && !((Skill) arg0).isToggle()))
{
_nextIntention = null;
super.changeIntention(intention, arg0, arg1);
return;
}
// do nothing if next intention is same as current one.
if ((intention == _intention) && (arg0 == _intentionArg0) && (arg1 == _intentionArg1))
{
super.changeIntention(intention, arg0, arg1);
return;
}
// save current intention so it can be used after cast
saveNextIntention(_intention, _intentionArg0, _intentionArg1);
super.changeIntention(intention, arg0, arg1);
}
/**
* Launch actions corresponding to the Event ReadyToAct.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Launch actions corresponding to the Event Think</li>
* </ul>
*/
@Override
protected void onEvtReadyToAct()
{
// Launch actions corresponding to the Event Think
if (_nextIntention != null)
{
setIntention(_nextIntention._crtlIntention, _nextIntention._arg0, _nextIntention._arg1);
_nextIntention = null;
}
super.onEvtReadyToAct();
}
/**
* Launch actions corresponding to the Event Cancel.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Stop an AI Follow Task</li>
* <li>Launch actions corresponding to the Event Think</li>
* </ul>
*/
@Override
protected void onEvtCancel()
{
_nextIntention = null;
super.onEvtCancel();
}
/**
* Finalize the casting of a skill. This method overrides L2CharacterAI method.<br>
* <B>What it does:</B><br>
* Check if actual intention is set to CAST and, if so, retrieves latest intention before the actual CAST and set it as the current intention for the player.
*/
@Override
protected void onEvtFinishCasting()
{
if (getIntention() == AI_INTENTION_CAST)
{
// run interrupted or next intention
if (_nextIntention != null)
{
if (_nextIntention._crtlIntention != AI_INTENTION_CAST)
{
setIntention(_nextIntention._crtlIntention, _nextIntention._arg0, _nextIntention._arg1);
}
else
{
setIntention(AI_INTENTION_IDLE);
}
}
else
{
// set intention to idle if skill doesn't change intention.
setIntention(AI_INTENTION_IDLE);
}
}
}
@Override
protected void onIntentionRest()
{
if (getIntention() == AI_INTENTION_REST)
{
return;
}
changeIntention(AI_INTENTION_REST, null, null);
setTarget(null);
if (getAttackTarget() != null)
{
setAttackTarget(null);
}
clientStopMoving(null);
}
@Override
protected void onIntentionActive()
{
setIntention(AI_INTENTION_IDLE);
}
/**
* Manage the Move To Intention : Stop current Attack and Launch a Move to Location Task.<br>
* <B><U> Actions</U> : </B>
* <ul>
* <li>Stop the actor auto-attack server side AND client side by sending Server->Client packet AutoAttackStop (broadcast)</li>
* <li>Set the Intention of this AI to AI_INTENTION_MOVE_TO</li>
* <li>Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)</li>
* </ul>
*/
@Override
protected void onIntentionMoveTo(Location loc)
{
if (getIntention() == AI_INTENTION_REST)
{
// Cancel action client side by sending Server->Client packet ActionFailed to the L2PcInstance actor
clientActionFailed();
return;
}
if (_actor.getActingPlayer().getDuelState() == Duel.DUELSTATE_DEAD)
{
clientActionFailed();
_actor.getActingPlayer().sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_CANNOT_MOVE_WHILE_FROZEN_PLEASE_WAIT));
return;
}
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isAttackingNow())
{
clientActionFailed();
saveNextIntention(AI_INTENTION_MOVE_TO, loc, null);
return;
}
// Set the Intention of this AbstractAI to AI_INTENTION_MOVE_TO
changeIntention(AI_INTENTION_MOVE_TO, loc, null);
// Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
clientStopAutoAttack();
// Abort the attack of the L2Character and send Server->Client ActionFailed packet
_actor.abortAttack();
// Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
moveTo(loc.getX(), loc.getY(), loc.getZ());
}
@Override
protected void clientNotifyDead()
{
_clientMovingToPawnOffset = 0;
_clientMoving = false;
super.clientNotifyDead();
}
private void thinkAttack()
{
final L2Character target = getAttackTarget();
if (target == null)
{
return;
}
if (checkTargetLostOrDead(target))
{
// Notify the target
setAttackTarget(null);
return;
}
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange()))
{
return;
}
clientStopMoving(null);
_actor.doAttack(target);
}
private void thinkCast()
{
final L2Character target = getCastTarget();
if ((_skill.getTargetType() == L2TargetType.GROUND) && (_actor instanceof L2PcInstance))
{
if (maybeMoveToPosition(((L2PcInstance) _actor).getCurrentSkillWorldPosition(), _actor.getMagicalAttackRange(_skill)))
{
_actor.setIsCastingNow(false);
return;
}
}
else
{
if (checkTargetLost(target))
{
if (_skill.isBad() && (getAttackTarget() != null))
{
// Notify the target
setCastTarget(null);
}
_actor.setIsCastingNow(false);
return;
}
if ((target != null) && maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
{
_actor.setIsCastingNow(false);
return;
}
}
if ((_skill.getHitTime() > 50) && !_skill.isSimultaneousCast())
{
clientStopMoving(null);
}
_actor.doCast(_skill);
}
private void thinkPickUp()
{
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow())
{
return;
}
final L2Object target = getTarget();
if (checkTargetLost(target) || maybeMoveToPawn(target, 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
_actor.getActingPlayer().doPickupItem(target);
}
private void thinkInteract()
{
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow())
{
return;
}
final L2Object target = getTarget();
if (checkTargetLost(target) || maybeMoveToPawn(target, 36))
{
return;
}
if (!(target instanceof L2StaticObjectInstance))
{
_actor.getActingPlayer().doInteract((L2Character) target);
}
setIntention(AI_INTENTION_IDLE);
}
@Override
protected void onEvtThink()
{
if (_thinking && (getIntention() != AI_INTENTION_CAST))
{
return;
}
_thinking = true;
try
{
if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
else if (getIntention() == AI_INTENTION_CAST)
{
thinkCast();
}
else if (getIntention() == AI_INTENTION_PICK_UP)
{
thinkPickUp();
}
else if (getIntention() == AI_INTENTION_INTERACT)
{
thinkInteract();
}
}
finally
{
_thinking = false;
}
}
}

View File

@ -0,0 +1,818 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.Collection;
import java.util.concurrent.Future;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.GameTimeController;
import com.l2jmobius.gameserver.GeoData;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.actor.L2Attackable;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.L2Playable;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.effects.L2EffectType;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.Rnd;
/**
* This class manages AI of L2Attackable.
*/
public class L2SiegeGuardAI extends L2CharacterAI implements Runnable
{
private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds
/** The L2Attackable AI task executed every 1s (call onEvtThink method) */
private Future<?> _aiTask;
/** For attack AI, analysis of mob and its targets */
private final SelfAnalysis _selfAnalysis = new SelfAnalysis();
// private TargetAnalysis _mostHatedAnalysis = new TargetAnalysis();
/** The delay after which the attacked is stopped */
private int _attackTimeout;
/** The L2Attackable aggro counter */
private int _globalAggro;
/** The flag used to indicate that a thinking action is in progress */
private boolean _thinking; // to prevent recursive thinking
private final int _attackRange;
/**
* Constructor of L2AttackableAI.
* @param creature the creature
*/
public L2SiegeGuardAI(L2DefenderInstance creature)
{
super(creature);
_selfAnalysis.init();
_attackTimeout = Integer.MAX_VALUE;
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
_attackRange = _actor.getPhysicalAttackRange();
}
@Override
public void run()
{
// Launch actions corresponding to the Event Think
onEvtThink();
}
/**
* <B><U> Actor is a L2GuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* <li>The L2MonsterInstance target is aggressive</li>
* </ul>
* <B><U> Actor is a L2SiegeGuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>A siege is in progress</li>
* <li>The L2PcInstance target isn't a Defender</li>
* </ul>
* <B><U> Actor is a L2FriendlyMobInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* </ul>
* <B><U> Actor is a L2MonsterInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The actor is Aggressive</li>
* </ul>
* @param target The targeted L2Object
* @return True if the target is autoattackable (depends on the actor type).
*/
protected boolean autoAttackCondition(L2Character target)
{
// Check if the target isn't another guard, folk or a door
if ((target == null) || (target instanceof L2DefenderInstance) || (target instanceof L2NpcInstance) || (target instanceof L2DoorInstance) || target.isAlikeDead())
{
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul() && ((target.isPlayer() && target.isGM()) || (target.isSummon() && ((L2Summon) target).getOwner().isGM())))
{
return false;
}
// Get the owner if the target is a summon
if (target instanceof L2Summon)
{
final L2PcInstance owner = ((L2Summon) target).getOwner();
if (_actor.isInsideRadius(owner, 1000, true, false))
{
target = owner;
}
}
// Check if the target isn't in silent move mode AND too far (>100)
if ((target instanceof L2Playable) && ((L2Playable) target).isSilentMovingAffected() && !_actor.isInsideRadius(target, 250, false, false))
{
return false;
}
// Los Check Here
return (_actor.isAutoAttackable(target) && GeoData.getInstance().canSeeTarget(_actor, target));
}
/**
* Set the Intention of this L2CharacterAI and create an AI Task executed every 1s (call onEvtThink method) for this L2Attackable.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</B></FONT>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
if (Config.DEBUG)
{
_log.info(getClass().getSimpleName() + ": changeIntention(" + intention + ", " + arg0 + ", " + arg1 + ")");
}
if (intention == AI_INTENTION_IDLE /* || intention == AI_INTENTION_ACTIVE */) // active becomes idle if only a summon is present
{
// Check if actor is not dead
if (!_actor.isAlikeDead())
{
intention = ((L2Attackable) _actor).getKnownList().getKnownPlayers().isEmpty() ? AI_INTENTION_IDLE : AI_INTENTION_ACTIVE;
}
if (intention == AI_INTENTION_IDLE)
{
// Set the Intention of this L2AttackableAI to AI_INTENTION_IDLE
super.changeIntention(AI_INTENTION_IDLE, null, null);
// Stop AI task and detach AI from NPC
if (_aiTask != null)
{
_aiTask.cancel(true);
_aiTask = null;
}
// Cancel the AI
_actor.detachAI();
return;
}
}
// Set the Intention of this L2AttackableAI to intention
super.changeIntention(intention, arg0, arg1);
// If not idle - create an AI task (schedule onEvtThink repeatedly)
if (_aiTask == null)
{
_aiTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
}
}
/**
* Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.
* @param target The L2Character to attack
*/
@Override
protected void onIntentionAttack(L2Character target)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
// if (_actor.getTarget() != null)
super.onIntentionAttack(target);
}
/**
* Manage AI standard thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update every 1s the _globalAggro counter to come close to 0</li>
* <li>If the actor is Aggressive and can attack, add all autoAttackable L2Character in its Aggro Range to its _aggroList, chose a target and order to attack it</li>
* <li>If the actor can't attack, order to it to return to its home location</li>
* </ul>
*/
private void thinkActive()
{
final L2Attackable npc = (L2Attackable) _actor;
// Update every 1s the _globalAggro counter to come close to 0
if (_globalAggro != 0)
{
if (_globalAggro < 0)
{
_globalAggro++;
}
else
{
_globalAggro--;
}
}
// Add all autoAttackable L2Character in L2Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
// A L2Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
if (_globalAggro >= 0)
{
for (L2Character target : npc.getKnownList().getKnownCharactersInRadius(_attackRange))
{
if (target == null)
{
continue;
}
if (autoAttackCondition(target) && (npc.getHating(target) == 0)) // check aggression
{
npc.addDamageHate(target, 0, 1);
}
}
// Chose a target from its aggroList
final L2Character hated = _actor.isConfused() ? getAttackTarget() : npc.getMostHated();
// Order to the L2Attackable to attack the target
if (hated != null)
{
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
final int aggro = npc.getHating(hated);
if ((aggro + _globalAggro) > 0)
{
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the AI Intention to AI_INTENTION_ATTACK
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated, null);
}
return;
}
}
// Order to the L2DefenderInstance to return to its home location because there's no target to attack
((L2DefenderInstance) _actor).returnHome();
}
/**
* Manage AI attack thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update the attack timeout if actor is running</li>
* <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
* <li>Call all L2Object of its Faction inside the Faction Range</li>
* <li>Chose a target and order to attack it with magic skill or physical attack</li>
* </ul>
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
*/
private void thinkAttack()
{
if (Config.DEBUG)
{
_log.info(getClass().getSimpleName() + ": thinkAttack(); timeout=" + (_attackTimeout - GameTimeController.getInstance().getGameTicks()));
}
if ((_attackTimeout < GameTimeController.getInstance().getGameTicks()) && _actor.isRunning())
{
// Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance
_actor.setWalking();
// Calculate a new attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
}
final L2Character attackTarget = getAttackTarget();
// Check if target is dead or if timeout is expired to stop this attack
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getInstance().getGameTicks()))
{
// Stop hating this target after the attack timeout or if target is dead
if (attackTarget != null)
{
((L2Attackable) _actor).stopHating(attackTarget);
}
// Cancel target and timeout
_attackTimeout = Integer.MAX_VALUE;
setAttackTarget(null);
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE, null, null);
_actor.setWalking();
return;
}
factionNotifyAndSupport();
attackPrepare();
}
private final void factionNotifyAndSupport()
{
final L2Character target = getAttackTarget();
// Call all L2Object of its Faction inside the Faction Range
if ((((L2Npc) _actor).getTemplate().getClans() == null) || (target == null) || target.isInvul())
{
return;
}
// Go through all L2Character that belong to its faction
// for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(((L2NpcInstance) _actor).getFactionRange()+_actor.getTemplate().collisionRadius))
for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(1000))
{
if (cha == null)
{
continue;
}
if (!(cha instanceof L2Npc))
{
if (_selfAnalysis.hasHealOrResurrect && (cha instanceof L2PcInstance) && (((L2Npc) _actor).getCastle().getSiege().checkIsDefender(((L2PcInstance) cha).getClan()))//
&& !_actor.isAttackingDisabled() && (cha.getCurrentHp() < (cha.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && cha.isInCombat())
{
for (Skill sk : _selfAnalysis.healSkills)
{
if ((_actor.getCurrentMp() < sk.getMpConsume()) || _actor.isSkillDisabled(sk) || !Util.checkIfInRange(sk.getCastRange(), _actor, cha, true))
{
continue;
}
final int chance = 5;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, cha))
{
break;
}
final L2Object OldTarget = _actor.getTarget();
_actor.setTarget(cha);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
continue;
}
final L2Npc npc = (L2Npc) cha;
if (!npc.isInMyClan((L2Npc) _actor))
{
continue;
}
if (npc.getAI() != null) // TODO: possibly check not needed
{
if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600) && ((npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE) || (npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE)) && target.isInsideRadius(npc, 1500, true, false) && GeoData.getInstance().canSeeTarget(npc, target))
{
// Notify the L2Object AI with EVT_AGGRESSION
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
return;
}
// heal friends
if (_selfAnalysis.hasHealOrResurrect && !_actor.isAttackingDisabled() && (npc.getCurrentHp() < (npc.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && npc.isInCombat())
{
for (Skill sk : _selfAnalysis.healSkills)
{
if ((_actor.getCurrentMp() < sk.getMpConsume()) || _actor.isSkillDisabled(sk) || !Util.checkIfInRange(sk.getCastRange(), _actor, npc, true))
{
continue;
}
final int chance = 4;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, npc))
{
break;
}
final L2Object OldTarget = _actor.getTarget();
_actor.setTarget(npc);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
}
}
private void attackPrepare()
{
// Get all information needed to choose between physical or magical attack
Collection<Skill> skills = null;
double dist_2 = 0;
int range = 0;
final L2DefenderInstance sGuard = (L2DefenderInstance) _actor;
L2Character attackTarget = getAttackTarget();
try
{
_actor.setTarget(attackTarget);
skills = _actor.getAllSkills();
dist_2 = _actor.calculateDistance(attackTarget, false, true);
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
if (attackTarget.isMoving())
{
range += 50;
}
}
catch (NullPointerException e)
{
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// never attack defenders
if ((attackTarget instanceof L2PcInstance) && (sGuard.getConquerableHall() == null) && sGuard.getCastle().getSiege().checkIsDefender(((L2PcInstance) attackTarget).getClan()))
{
// Cancel the target
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
if (!GeoData.getInstance().canSeeTarget(_actor, attackTarget))
{
// Siege guards differ from normal mobs currently:
// If target cannot seen, don't attack any more
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// Check if the actor isn't muted and if it is far from target
if (!_actor.isMuted() && (dist_2 > (range * range)))
{
// check for long ranged skills and heal/buff skills
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if ((dist_2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
{
final L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || sk.hasEffectType(L2EffectType.HEAL))
{
boolean useSkillSelf = true;
if (sk.hasEffectType(L2EffectType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.isContinuous() && !sk.isDebuff() && _actor.isAffectedBySkill(sk.getId()))
{
useSkillSelf = false;
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
// Check if the L2SiegeGuardInstance is attacking, knows the target and can't run
if (!_actor.isAttackingNow() && (_actor.getRunSpeed() == 0) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else
{
final double dx = _actor.getX() - attackTarget.getX();
final double dy = _actor.getY() - attackTarget.getY();
final double dz = _actor.getZ() - attackTarget.getZ();
final double homeX = attackTarget.getX() - sGuard.getSpawn().getX();
final double homeY = attackTarget.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance isn't too far from it's home location
if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
else if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isHealer)
{
return;
}
if (_selfAnalysis.isMage)
{
range = _selfAnalysis.maxCastRange - 50;
}
moveToPawn(attackTarget, attackTarget.isMoving() ? range - 70 : range);
}
}
}
else
{
if (_actor.isMuted() && (dist_2 > (range * range)) && !_selfAnalysis.isHealer)
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
final double dz = _actor.getZ() - attackTarget.getZ();
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isMage)
{
range = _selfAnalysis.maxCastRange - 50;
}
moveToPawn(attackTarget, attackTarget.isMoving() ? range - 70 : range);
}
return;
}
if (dist_2 <= (range * range))
{
final L2Character hated = _actor.isConfused() ? attackTarget : ((L2Attackable) _actor).getMostHated();
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE, null, null);
return;
}
if (hated != attackTarget)
{
attackTarget = hated;
}
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// check for close combat skills && heal/buff skills
if (!_actor.isMuted() && (Rnd.nextInt(100) <= 5))
{
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist_2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk))
{
final L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || sk.hasEffectType(L2EffectType.HEAL))
{
boolean useSkillSelf = true;
if (sk.hasEffectType(L2EffectType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.isContinuous() && !sk.isDebuff() && _actor.isAffectedBySkill(sk.getId()))
{
useSkillSelf = false;
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
// Finally, do the physical attack itself
if (!_selfAnalysis.isHealer)
{
_actor.doAttack(attackTarget);
}
}
}
}
/**
* Manage AI thinking actions of a L2Attackable.
*/
@Override
protected void onEvtThink()
{
// if(getIntention() != AI_INTENTION_IDLE && (!_actor.isVisible() || !_actor.hasAI() || !_actor.isKnownPlayers()))
// setIntention(AI_INTENTION_IDLE);
// Check if the thinking action is already in progress
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled())
{
return;
}
// Start thinking action
_thinking = true;
try
{
// Manage AI thinks of a L2Attackable
if (getIntention() == AI_INTENTION_ACTIVE)
{
thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
}
finally
{
// Stop thinking action
_thinking = false;
}
}
/**
* Launch actions corresponding to the Event Attacked.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
* <li>Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance</li>
* <li>Set the Intention to AI_INTENTION_ATTACK</li>
* </ul>
* @param attacker The L2Character that attacks the actor
*/
@Override
protected void onEvtAttacked(L2Character attacker)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Set the _globalAggro to 0 to permit attack even just after spawn
if (_globalAggro < 0)
{
_globalAggro = 0;
}
// Add the attacker to the _aggroList of the actor
((L2Attackable) _actor).addDamageHate(attacker, 0, 1);
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK)
{
setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker, null);
}
super.onEvtAttacked(attacker);
}
/**
* Launch actions corresponding to the Event Aggression.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Add the target to the actor _aggroList or update hate if already present</li>
* <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is L2GuardInstance check if it isn't too far from its home location)</li>
* </ul>
* @param aggro The value of hate to add to the actor against the target
*/
@Override
protected void onEvtAggression(L2Character target, int aggro)
{
if (_actor == null)
{
return;
}
final L2Attackable me = (L2Attackable) _actor;
if (target != null)
{
// Add the target to the actor _aggroList or update hate if already present
me.addDamageHate(target, 0, aggro);
// Get the hate of the actor against the target
aggro = me.getHating(target);
if (aggro <= 0)
{
if (me.getMostHated() == null)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
return;
}
// Set the actor AI Intention to AI_INTENTION_ATTACK
if (getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
{
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
final L2DefenderInstance sGuard = (L2DefenderInstance) _actor;
final double homeX = target.getX() - sGuard.getSpawn().getX();
final double homeY = target.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance is not too far from its home location
if (((homeX * homeX) + (homeY * homeY)) < 3240000)
{
setIntention(CtrlIntention.AI_INTENTION_ATTACK, target, null);
}
}
}
else
{
// currently only for setting lower general aggro
if (aggro >= 0)
{
return;
}
final L2Character mostHated = me.getMostHated();
if (mostHated == null)
{
_globalAggro = -25;
return;
}
for (L2Character aggroed : me.getAggroList().keySet())
{
me.addDamageHate(aggroed, 0, aggro);
}
aggro = me.getHating(mostHated);
if (aggro <= 0)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
}
}
@Override
public void stopAITask()
{
if (_aiTask != null)
{
_aiTask.cancel(false);
_aiTask = null;
}
_actor.detachAI();
super.stopAITask();
}
}

View File

@ -0,0 +1,55 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import java.util.ArrayList;
import java.util.List;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.instance.L2DefenderInstance;
/**
* @author BiggBoss
*/
public final class L2SpecialSiegeGuardAI extends L2SiegeGuardAI
{
private final List<Integer> _allied = new ArrayList<>();
/**
* @param creature
*/
public L2SpecialSiegeGuardAI(L2DefenderInstance creature)
{
super(creature);
}
public List<Integer> getAlly()
{
return _allied;
}
@Override
protected boolean autoAttackCondition(L2Character target)
{
if (_allied.contains(target.getObjectId()))
{
return false;
}
return super.autoAttackCondition(target);
}
}

View File

@ -0,0 +1,317 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.GeoData;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.pathfinding.PathFinding;
import com.l2jmobius.util.Rnd;
public class L2SummonAI extends L2PlayableAI implements Runnable
{
private static final int AVOID_RADIUS = 70;
private volatile boolean _thinking; // to prevent recursive thinking
private volatile boolean _startFollow = ((L2Summon) _actor).getFollowStatus();
private L2Character _lastAttack = null;
private volatile boolean _startAvoid = false;
private Future<?> _avoidTask = null;
public L2SummonAI(L2Summon creature)
{
super(creature);
}
@Override
protected void onIntentionAttack(L2Character target)
{
if ((Config.PATHFINDING > 0) && (PathFinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId(), true) == null))
{
return;
}
super.onIntentionAttack(target);
}
@Override
protected void onIntentionIdle()
{
stopFollow();
_startFollow = false;
onIntentionActive();
}
@Override
protected void onIntentionActive()
{
if (_startFollow)
{
setIntention(AI_INTENTION_FOLLOW, ((L2Summon) _actor).getOwner());
}
else
{
super.onIntentionActive();
}
}
@Override
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
switch (intention)
{
case AI_INTENTION_ACTIVE:
case AI_INTENTION_FOLLOW:
{
startAvoidTask();
break;
}
default:
{
stopAvoidTask();
}
}
super.changeIntention(intention, arg0, arg1);
}
private void thinkAttack()
{
if (checkTargetLostOrDead(getAttackTarget()))
{
setAttackTarget(null);
return;
}
if (maybeMoveToPawn(getAttackTarget(), _actor.getPhysicalAttackRange()))
{
return;
}
clientStopMoving(null);
_actor.doAttack(getAttackTarget());
}
private void thinkCast()
{
final L2Summon summon = (L2Summon) _actor;
if (checkTargetLost(getCastTarget()))
{
setCastTarget(null);
return;
}
final boolean val = _startFollow;
if (maybeMoveToPawn(getCastTarget(), _actor.getMagicalAttackRange(_skill)))
{
return;
}
clientStopMoving(null);
summon.setFollowStatus(false);
setIntention(AI_INTENTION_IDLE);
_startFollow = val;
_actor.doCast(_skill);
}
private void thinkPickUp()
{
if (checkTargetLost(getTarget()) || maybeMoveToPawn(getTarget(), 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
((L2Summon) _actor).doPickupItem(getTarget());
}
private void thinkInteract()
{
if (checkTargetLost(getTarget()) || maybeMoveToPawn(getTarget(), 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
}
@Override
protected void onEvtThink()
{
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled())
{
return;
}
_thinking = true;
try
{
switch (getIntention())
{
case AI_INTENTION_ATTACK:
{
thinkAttack();
break;
}
case AI_INTENTION_CAST:
{
thinkCast();
break;
}
case AI_INTENTION_PICK_UP:
{
thinkPickUp();
break;
}
case AI_INTENTION_INTERACT:
{
thinkInteract();
break;
}
}
}
finally
{
_thinking = false;
}
}
@Override
protected void onEvtFinishCasting()
{
if (_lastAttack == null)
{
((L2Summon) _actor).setFollowStatus(_startFollow);
}
else
{
setIntention(CtrlIntention.AI_INTENTION_ATTACK, _lastAttack);
_lastAttack = null;
}
}
@Override
protected void onEvtAttacked(L2Character attacker)
{
super.onEvtAttacked(attacker);
avoidAttack(attacker);
}
@Override
protected void onEvtEvaded(L2Character attacker)
{
super.onEvtEvaded(attacker);
avoidAttack(attacker);
}
private void avoidAttack(L2Character attacker)
{
// trying to avoid if summon near owner
if ((((L2Summon) _actor).getOwner() != null) && (((L2Summon) _actor).getOwner() != attacker) && ((L2Summon) _actor).getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false))
{
_startAvoid = true;
}
}
@Override
public void run()
{
if (!_startAvoid)
{
return;
}
_startAvoid = false;
if (_clientMoving || _actor.isDead() || _actor.isMovementDisabled())
{
return;
}
final int ownerX = ((L2Summon) _actor).getOwner().getX();
final int ownerY = ((L2Summon) _actor).getOwner().getY();
final double angle = Math.toRadians(Rnd.get(-90, 90)) + Math.atan2(ownerY - _actor.getY(), ownerX - _actor.getX());
final int targetX = ownerX + (int) (AVOID_RADIUS * Math.cos(angle));
final int targetY = ownerY + (int) (AVOID_RADIUS * Math.sin(angle));
if (GeoData.getInstance().canMove(_actor.getX(), _actor.getY(), _actor.getZ(), targetX, targetY, _actor.getZ(), _actor.getInstanceId()))
{
moveTo(targetX, targetY, _actor.getZ());
}
}
public void notifyFollowStatusChange()
{
_startFollow = !_startFollow;
switch (getIntention())
{
case AI_INTENTION_ACTIVE:
case AI_INTENTION_FOLLOW:
case AI_INTENTION_IDLE:
case AI_INTENTION_MOVE_TO:
case AI_INTENTION_PICK_UP:
{
((L2Summon) _actor).setFollowStatus(_startFollow);
}
}
}
public void setStartFollowController(boolean val)
{
_startFollow = val;
}
@Override
protected void onIntentionCast(Skill skill, L2Object target)
{
if (getIntention() == AI_INTENTION_ATTACK)
{
_lastAttack = getAttackTarget();
}
else
{
_lastAttack = null;
}
super.onIntentionCast(skill, target);
}
private void startAvoidTask()
{
if (_avoidTask == null)
{
_avoidTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 100, 100);
}
}
private void stopAvoidTask()
{
if (_avoidTask != null)
{
_avoidTask.cancel(false);
_avoidTask = null;
}
}
@Override
public void stopAITask()
{
stopAvoidTask();
super.stopAITask();
}
}

View File

@ -0,0 +1,127 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Vehicle;
import com.l2jmobius.gameserver.model.skills.Skill;
/**
* @author DS
*/
public abstract class L2VehicleAI extends L2CharacterAI
{
/**
* Simple AI for vehicles
* @param creature
*/
public L2VehicleAI(L2Vehicle creature)
{
super(creature);
}
@Override
protected void onIntentionAttack(L2Character target)
{
}
@Override
protected void onIntentionCast(Skill skill, L2Object target)
{
}
@Override
protected void onIntentionFollow(L2Character target)
{
}
@Override
protected void onIntentionPickUp(L2Object item)
{
}
@Override
protected void onIntentionInteract(L2Object object)
{
}
@Override
protected void onEvtAttacked(L2Character attacker)
{
}
@Override
protected void onEvtAggression(L2Character target, int aggro)
{
}
@Override
protected void onEvtStunned(L2Character attacker)
{
}
@Override
protected void onEvtSleeping(L2Character attacker)
{
}
@Override
protected void onEvtRooted(L2Character attacker)
{
}
@Override
protected void onEvtForgetObject(L2Object object)
{
}
@Override
protected void onEvtCancel()
{
}
@Override
protected void onEvtDead()
{
}
@Override
protected void onEvtFakeDeath()
{
}
@Override
protected void onEvtFinishCasting()
{
}
@Override
protected void clientActionFailed()
{
}
@Override
protected void moveToPawn(L2Object pawn, int offset)
{
}
@Override
protected void clientStoppedMoving()
{
}
}

View File

@ -0,0 +1,205 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import java.util.ArrayList;
import java.util.List;
/**
* Class for AI action after some event.<br>
* Has 2 array list for "work" and "break".
* @author Yaroslav
*/
public class NextAction
{
public interface NextActionCallback
{
void doWork();
}
private List<CtrlEvent> _events;
private List<CtrlIntention> _intentions;
private NextActionCallback _callback;
/**
* Main constructor.
* @param events
* @param intentions
* @param callback
*/
public NextAction(List<CtrlEvent> events, List<CtrlIntention> intentions, NextActionCallback callback)
{
_events = events;
_intentions = intentions;
setCallback(callback);
}
/**
* Single constructor.
* @param event
* @param intention
* @param callback
*/
public NextAction(CtrlEvent event, CtrlIntention intention, NextActionCallback callback)
{
if (_events == null)
{
_events = new ArrayList<>();
}
if (_intentions == null)
{
_intentions = new ArrayList<>();
}
if (event != null)
{
_events.add(event);
}
if (intention != null)
{
_intentions.add(intention);
}
setCallback(callback);
}
/**
* Do action.
*/
public void doAction()
{
if (_callback != null)
{
_callback.doWork();
}
}
/**
* @return the _event
*/
public List<CtrlEvent> getEvents()
{
// If null return empty list.
if (_events == null)
{
_events = new ArrayList<>();
}
return _events;
}
/**
* @param event the event to set.
*/
public void setEvents(ArrayList<CtrlEvent> event)
{
_events = event;
}
/**
* @param event
*/
public void addEvent(CtrlEvent event)
{
if (_events == null)
{
_events = new ArrayList<>();
}
if (event != null)
{
_events.add(event);
}
}
/**
* @param event
*/
public void removeEvent(CtrlEvent event)
{
if (_events == null)
{
return;
}
_events.remove(event);
}
/**
* @return the _callback
*/
public NextActionCallback getCallback()
{
return _callback;
}
/**
* @param callback the callback to set.
*/
public void setCallback(NextActionCallback callback)
{
_callback = callback;
}
/**
* @return the _intentions
*/
public List<CtrlIntention> getIntentions()
{
// If null return empty list.
if (_intentions == null)
{
_intentions = new ArrayList<>();
}
return _intentions;
}
/**
* @param intentions the intention to set.
*/
public void setIntentions(ArrayList<CtrlIntention> intentions)
{
_intentions = intentions;
}
/**
* @param intention
*/
public void addIntention(CtrlIntention intention)
{
if (_intentions == null)
{
_intentions = new ArrayList<>();
}
if (intention != null)
{
_intentions.add(intention);
}
}
/**
* @param intention
*/
public void removeIntention(CtrlIntention intention)
{
if (_intentions == null)
{
return;
}
_intentions.remove(intention);
}
}

View File

@ -0,0 +1,208 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.cache;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.file.filter.HTMLFilter;
/**
* @author Layane
*/
public class HtmCache
{
private static final Logger _log = Logger.getLogger(HtmCache.class.getName());
private static final HTMLFilter HTML_FILTER = new HTMLFilter();
private static final Map<String, String> _cache = Config.LAZY_CACHE ? new ConcurrentHashMap<>() : new HashMap<>();
private int _loadedFiles;
private long _bytesBuffLen;
protected HtmCache()
{
reload();
}
public void reload()
{
reload(Config.DATAPACK_ROOT);
}
public void reload(File f)
{
if (!Config.LAZY_CACHE)
{
_log.info("Html cache start...");
parseDir(f);
_log.info("Cache[HTML]: " + String.format("%.3f", getMemoryUsage()) + " megabytes on " + getLoadedFiles() + " files loaded");
}
else
{
_cache.clear();
_loadedFiles = 0;
_bytesBuffLen = 0;
_log.info("Cache[HTML]: Running lazy cache");
}
}
public void reloadPath(File f)
{
parseDir(f);
_log.info("Cache[HTML]: Reloaded specified path.");
}
public double getMemoryUsage()
{
return (float) _bytesBuffLen / 1048576;
}
public int getLoadedFiles()
{
return _loadedFiles;
}
private void parseDir(File dir)
{
final File[] files = dir.listFiles();
if (files != null)
{
for (File file : files)
{
if (!file.isDirectory())
{
loadFile(file);
}
else
{
parseDir(file);
}
}
}
}
public String loadFile(File file)
{
if (!HTML_FILTER.accept(file))
{
return null;
}
final String relpath = Util.getRelativePath(Config.DATAPACK_ROOT, file);
String content = null;
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis))
{
final int bytes = bis.available();
final byte[] raw = new byte[bytes];
bis.read(raw);
content = new String(raw, "UTF-8");
content = content.replaceAll("(?s)<!--.*?-->", ""); // Remove html comments
final String oldContent = _cache.put(relpath, content);
if (oldContent == null)
{
_bytesBuffLen += bytes;
_loadedFiles++;
}
else
{
_bytesBuffLen = (_bytesBuffLen - oldContent.length()) + bytes;
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Problem with htm file " + e.getMessage(), e);
}
return content;
}
public String getHtmForce(String prefix, String path)
{
String content = getHtm(prefix, path);
if (content == null)
{
content = "<html><body>My text is missing:<br>" + path + "</body></html>";
_log.warning("Cache[HTML]: Missing HTML page: " + path);
}
return content;
}
public String getHtm(String prefix, String path)
{
String newPath = null;
String content;
if ((prefix != null) && !prefix.isEmpty())
{
newPath = prefix + path;
content = getHtm(newPath);
if (content != null)
{
return content;
}
}
content = getHtm(path);
if ((content != null) && (newPath != null))
{
_cache.put(newPath, content);
}
return content;
}
private String getHtm(String path)
{
// TODO: Check why some files do not get in cache on server startup.
return (path == null) || path.isEmpty() ? "" : _cache.get(path) == null ? loadFile(new File(Config.DATAPACK_ROOT, path)) : _cache.get(path);
}
public boolean contains(String path)
{
return _cache.containsKey(path);
}
/**
* @param path The path to the HTM
* @return {@code true} if the path targets a HTM or HTML file, {@code false} otherwise.
*/
public boolean isLoadable(String path)
{
return HTML_FILTER.accept(new File(Config.DATAPACK_ROOT, path));
}
public static HtmCache getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final HtmCache _instance = new HtmCache();
}
}

View File

@ -0,0 +1,75 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.cache;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
/**
* @author -Nemesiss-
*/
public class WarehouseCacheManager
{
protected final Map<L2PcInstance, Long> _cachedWh = new ConcurrentHashMap<>();
protected final long _cacheTime = Config.WAREHOUSE_CACHE_TIME * 60000L;
protected WarehouseCacheManager()
{
ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new CacheScheduler(), 120000, 60000);
}
public void addCacheTask(L2PcInstance pc)
{
_cachedWh.put(pc, System.currentTimeMillis());
}
public void remCacheTask(L2PcInstance pc)
{
_cachedWh.remove(pc);
}
public class CacheScheduler implements Runnable
{
@Override
public void run()
{
final long cTime = System.currentTimeMillis();
for (L2PcInstance pc : _cachedWh.keySet())
{
if ((cTime - _cachedWh.get(pc)) > _cacheTime)
{
pc.clearWarehouse();
_cachedWh.remove(pc);
}
}
}
}
public static WarehouseCacheManager getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final WarehouseCacheManager _instance = new WarehouseCacheManager();
}
}

View File

@ -0,0 +1,246 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager;
import com.l2jmobius.gameserver.communitybbs.Manager.TopicBBSManager;
public final class Forum
{
private static final Logger _log = Logger.getLogger(Forum.class.getName());
// type
public static final int ROOT = 0;
public static final int NORMAL = 1;
public static final int CLAN = 2;
public static final int MEMO = 3;
public static final int MAIL = 4;
// perm
public static final int INVISIBLE = 0;
public static final int ALL = 1;
public static final int CLANMEMBERONLY = 2;
public static final int OWNERONLY = 3;
private final List<Forum> _children = new ArrayList<>();
private final Map<Integer, Topic> _topic = new ConcurrentHashMap<>();
private final int _forumId;
private String _forumName;
private int _forumType;
private int _forumPost;
private int _forumPerm;
private final Forum _fParent;
private int _ownerID;
private boolean _loaded = false;
/**
* Creates new instance of Forum. When you create new forum, use {@link com.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager# addForum(com.l2jmobius.gameserver.communitybbs.BB.Forum)} to add forum to the forums manager.
* @param Forumid
* @param FParent
*/
public Forum(int Forumid, Forum FParent)
{
_forumId = Forumid;
_fParent = FParent;
}
/**
* @param name
* @param parent
* @param type
* @param perm
* @param OwnerID
*/
public Forum(String name, Forum parent, int type, int perm, int OwnerID)
{
_forumName = name;
_forumId = ForumsBBSManager.getInstance().getANewID();
_forumType = type;
_forumPost = 0;
_forumPerm = perm;
_fParent = parent;
_ownerID = OwnerID;
parent._children.add(this);
ForumsBBSManager.getInstance().addForum(this);
_loaded = true;
}
private void load()
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM forums WHERE forum_id=?"))
{
ps.setInt(1, _forumId);
try (ResultSet rs = ps.executeQuery())
{
if (rs.next())
{
_forumName = rs.getString("forum_name");
_forumPost = rs.getInt("forum_post");
_forumType = rs.getInt("forum_type");
_forumPerm = rs.getInt("forum_perm");
_ownerID = rs.getInt("forum_owner_id");
}
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Data error on Forum " + _forumId + " : " + e.getMessage(), e);
}
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM topic WHERE topic_forum_id=? ORDER BY topic_id DESC"))
{
ps.setInt(1, _forumId);
try (ResultSet rs = ps.executeQuery())
{
while (rs.next())
{
final Topic t = new Topic(Topic.ConstructorType.RESTORE, rs.getInt("topic_id"), rs.getInt("topic_forum_id"), rs.getString("topic_name"), rs.getLong("topic_date"), rs.getString("topic_ownername"), rs.getInt("topic_ownerid"), rs.getInt("topic_type"), rs.getInt("topic_reply"));
_topic.put(t.getID(), t);
if (t.getID() > TopicBBSManager.getInstance().getMaxID(this))
{
TopicBBSManager.getInstance().setMaxID(t.getID(), this);
}
}
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Data error on Forum " + _forumId + " : " + e.getMessage(), e);
}
}
private void getChildren()
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT forum_id FROM forums WHERE forum_parent=?"))
{
ps.setInt(1, _forumId);
try (ResultSet rs = ps.executeQuery())
{
while (rs.next())
{
final Forum f = new Forum(rs.getInt("forum_id"), this);
_children.add(f);
ForumsBBSManager.getInstance().addForum(f);
}
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Data error on Forum (children): " + e.getMessage(), e);
}
}
public int getTopicSize()
{
vload();
return _topic.size();
}
public Topic getTopic(int j)
{
vload();
return _topic.get(j);
}
public void addTopic(Topic t)
{
vload();
_topic.put(t.getID(), t);
}
/**
* @return the forum Id
*/
public int getID()
{
return _forumId;
}
public String getName()
{
vload();
return _forumName;
}
public int getType()
{
vload();
return _forumType;
}
/**
* @param name the forum name
* @return the forum for the given name
*/
public Forum getChildByName(String name)
{
vload();
return _children.stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
}
/**
* @param id
*/
public void rmTopicByID(int id)
{
_topic.remove(id);
}
public void insertIntoDb()
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("INSERT INTO forums (forum_id,forum_name,forum_parent,forum_post,forum_type,forum_perm,forum_owner_id) VALUES (?,?,?,?,?,?,?)"))
{
ps.setInt(1, _forumId);
ps.setString(2, _forumName);
ps.setInt(3, _fParent.getID());
ps.setInt(4, _forumPost);
ps.setInt(5, _forumType);
ps.setInt(6, _forumPerm);
ps.setInt(7, _ownerID);
ps.execute();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Error while saving new Forum to db " + e.getMessage(), e);
}
}
public void vload()
{
if (_loaded)
{
return;
}
load();
getChildren();
_loaded = true;
}
}

View File

@ -0,0 +1,170 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.communitybbs.Manager.PostBBSManager;
/**
* @author Maktakien
*/
public class Post
{
private static final Logger _log = Logger.getLogger(Post.class.getName());
public static class CPost
{
public int postId;
public String postOwner;
public int postOwnerId;
public long postDate;
public int postTopicId;
public int postForumId;
public String postTxt;
}
private final List<CPost> _post = new ArrayList<>();
/**
* @param _PostOwner
* @param _PostOwnerID
* @param date
* @param tid
* @param _PostForumID
* @param txt
*/
public Post(String _PostOwner, int _PostOwnerID, long date, int tid, int _PostForumID, String txt)
{
final CPost cp = new CPost();
cp.postId = 0;
cp.postOwner = _PostOwner;
cp.postOwnerId = _PostOwnerID;
cp.postDate = date;
cp.postTopicId = tid;
cp.postForumId = _PostForumID;
cp.postTxt = txt;
_post.add(cp);
insertindb(cp);
}
public Post(Topic t)
{
load(t);
}
public void insertindb(CPost cp)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("INSERT INTO posts (post_id,post_owner_name,post_ownerid,post_date,post_topic_id,post_forum_id,post_txt) values (?,?,?,?,?,?,?)"))
{
ps.setInt(1, cp.postId);
ps.setString(2, cp.postOwner);
ps.setInt(3, cp.postOwnerId);
ps.setLong(4, cp.postDate);
ps.setInt(5, cp.postTopicId);
ps.setInt(6, cp.postForumId);
ps.setString(7, cp.postTxt);
ps.execute();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Error while saving new Post to db " + e.getMessage(), e);
}
}
public CPost getCPost(int id)
{
return _post.get(id);
}
public void deleteme(Topic t)
{
PostBBSManager.getInstance().delPostByTopic(t);
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM posts WHERE post_forum_id=? AND post_topic_id=?"))
{
ps.setInt(1, t.getForumID());
ps.setInt(2, t.getID());
ps.execute();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Error while deleting post: " + e.getMessage(), e);
}
}
/**
* @param t
*/
private void load(Topic t)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM posts WHERE post_forum_id=? AND post_topic_id=? ORDER BY post_id ASC"))
{
ps.setInt(1, t.getForumID());
ps.setInt(2, t.getID());
try (ResultSet rs = ps.executeQuery())
{
while (rs.next())
{
final CPost cp = new CPost();
cp.postId = rs.getInt("post_id");
cp.postOwner = rs.getString("post_owner_name");
cp.postOwnerId = rs.getInt("post_ownerid");
cp.postDate = rs.getLong("post_date");
cp.postTopicId = rs.getInt("post_topic_id");
cp.postForumId = rs.getInt("post_forum_id");
cp.postTxt = rs.getString("post_txt");
_post.add(cp);
}
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Data error on Post " + t.getForumID() + "/" + t.getID() + " : " + e.getMessage(), e);
}
}
/**
* @param i
*/
public void updatetxt(int i)
{
final CPost cp = getCPost(i);
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE posts SET post_txt=? WHERE post_id=? AND post_topic_id=? AND post_forum_id=?"))
{
ps.setString(1, cp.postTxt);
ps.setInt(2, cp.postId);
ps.setInt(3, cp.postTopicId);
ps.setInt(4, cp.postForumId);
ps.execute();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Error while saving new Post to db " + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,152 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.communitybbs.Manager.TopicBBSManager;
public class Topic
{
private static final Logger _log = Logger.getLogger(Topic.class.getName());
public static final int MORMAL = 0;
public static final int MEMO = 1;
private final int _id;
private final int _forumId;
private final String _topicName;
private final long _date;
private final String _ownerName;
private final int _ownerId;
private final int _type;
private final int _cReply;
/**
* @param ct
* @param id
* @param fid
* @param name
* @param date
* @param oname
* @param oid
* @param type
* @param Creply
*/
public Topic(ConstructorType ct, int id, int fid, String name, long date, String oname, int oid, int type, int Creply)
{
_id = id;
_forumId = fid;
_topicName = name;
_date = date;
_ownerName = oname;
_ownerId = oid;
_type = type;
_cReply = Creply;
TopicBBSManager.getInstance().addTopic(this);
if (ct == ConstructorType.CREATE)
{
insertindb();
}
}
public void insertindb()
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("INSERT INTO topic (topic_id,topic_forum_id,topic_name,topic_date,topic_ownername,topic_ownerid,topic_type,topic_reply) values (?,?,?,?,?,?,?,?)"))
{
ps.setInt(1, _id);
ps.setInt(2, _forumId);
ps.setString(3, _topicName);
ps.setLong(4, _date);
ps.setString(5, _ownerName);
ps.setInt(6, _ownerId);
ps.setInt(7, _type);
ps.setInt(8, _cReply);
ps.execute();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Error while saving new Topic to db " + e.getMessage(), e);
}
}
public enum ConstructorType
{
RESTORE,
CREATE
}
/**
* @return the topic Id
*/
public int getID()
{
return _id;
}
public int getForumID()
{
return _forumId;
}
/**
* @return the topic name
*/
public String getName()
{
return _topicName;
}
public String getOwnerName()
{
return _ownerName;
}
/**
* @param f
*/
public void deleteme(Forum f)
{
TopicBBSManager.getInstance().delTopic(this);
f.rmTopicByID(getID());
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM topic WHERE topic_id=? AND topic_forum_id=?"))
{
ps.setInt(1, getID());
ps.setInt(2, f.getID());
ps.execute();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Error while deleting topic: " + e.getMessage(), e);
}
}
/**
* @return the topic date
*/
public long getDate()
{
return _date;
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.communitybbs.Manager;
import java.util.ArrayList;
import java.util.List;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.serverpackets.ShowBoard;
public abstract class BaseBBSManager
{
public abstract void parsecmd(String command, L2PcInstance activeChar);
public abstract void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, L2PcInstance activeChar);
/**
* @param html
* @param acha
*/
protected void send1001(String html, L2PcInstance acha)
{
if (html.length() < 8192)
{
acha.sendPacket(new ShowBoard(html, "1001"));
}
}
/**
* @param acha
*/
protected void send1002(L2PcInstance acha)
{
send1002(acha, " ", " ", "0");
}
/**
* @param activeChar
* @param string
* @param string2
* @param string3
*/
protected void send1002(L2PcInstance activeChar, String string, String string2, String string3)
{
final List<String> _arg = new ArrayList<>();
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add(activeChar.getName());
_arg.add(Integer.toString(activeChar.getObjectId()));
_arg.add(activeChar.getAccountName());
_arg.add("9");
_arg.add(string2); // subject?
_arg.add(string2); // subject?
_arg.add(string); // text
_arg.add(string3); // date?
_arg.add(string3); // date?
_arg.add("0");
_arg.add("0");
activeChar.sendPacket(new ShowBoard(_arg));
}
}

View File

@ -0,0 +1,153 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.communitybbs.Manager;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.communitybbs.BB.Forum;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
public class ForumsBBSManager extends BaseBBSManager
{
private static Logger _log = Logger.getLogger(ForumsBBSManager.class.getName());
private final List<Forum> _table = new CopyOnWriteArrayList<>();
private int _lastid = 1;
/**
* Instantiates a new forums bbs manager.
*/
protected ForumsBBSManager()
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT forum_id FROM forums WHERE forum_type = 0"))
{
while (rs.next())
{
addForum(new Forum(rs.getInt("forum_id"), null));
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Data error on Forum (root): " + e.getMessage(), e);
}
}
/**
* Inits the root.
*/
public void initRoot()
{
_table.forEach(f -> f.vload());
_log.info("Loaded " + _table.size() + " forums. Last forum id used: " + _lastid);
}
/**
* Adds the forum.
* @param ff the forum
*/
public void addForum(Forum ff)
{
if (ff == null)
{
return;
}
_table.add(ff);
if (ff.getID() > _lastid)
{
_lastid = ff.getID();
}
}
@Override
public void parsecmd(String command, L2PcInstance activeChar)
{
}
/**
* Gets the forum by name.
* @param name the forum name
* @return the forum by name
*/
public Forum getForumByName(String name)
{
return _table.stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
}
/**
* Creates the new forum.
* @param name the forum name
* @param parent the parent forum
* @param type the forum type
* @param perm the perm
* @param oid the oid
* @return the new forum
*/
public Forum createNewForum(String name, Forum parent, int type, int perm, int oid)
{
final Forum forum = new Forum(name, parent, type, perm, oid);
forum.insertIntoDb();
return forum;
}
/**
* Gets the a new Id.
* @return the a new Id
*/
public int getANewID()
{
return ++_lastid;
}
/**
* Gets the forum by Id.
* @param idf the the forum Id
* @return the forum by Id
*/
public Forum getForumByID(int idf)
{
return _table.stream().filter(f -> f.getID() == idf).findFirst().orElse(null);
}
@Override
public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, L2PcInstance activeChar)
{
}
/**
* Gets the single instance of ForumsBBSManager.
* @return single instance of ForumsBBSManager
*/
public static ForumsBBSManager getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final ForumsBBSManager _instance = new ForumsBBSManager();
}
}

View File

@ -0,0 +1,189 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.communitybbs.Manager;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import com.l2jmobius.gameserver.communitybbs.BB.Forum;
import com.l2jmobius.gameserver.communitybbs.BB.Post;
import com.l2jmobius.gameserver.communitybbs.BB.Topic;
import com.l2jmobius.gameserver.handler.CommunityBoardHandler;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.util.StringUtil;
public class PostBBSManager extends BaseBBSManager
{
private final Map<Topic, Post> _postByTopic = new ConcurrentHashMap<>();
public Post getGPosttByTopic(Topic t)
{
Post post = _postByTopic.get(t);
if (post == null)
{
post = new Post(t);
_postByTopic.put(t, post);
}
return post;
}
public void delPostByTopic(Topic t)
{
_postByTopic.remove(t);
}
public void addPostByTopic(Post p, Topic t)
{
if (_postByTopic.get(t) == null)
{
_postByTopic.put(t, p);
}
}
@Override
public void parsecmd(String command, L2PcInstance activeChar)
{
if (command.startsWith("_bbsposts;read;"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final int idf = Integer.parseInt(st.nextToken());
final int idp = Integer.parseInt(st.nextToken());
final String index = st.hasMoreTokens() ? st.nextToken() : null;
final int ind = index == null ? 1 : Integer.parseInt(index);
showPost(TopicBBSManager.getInstance().getTopicByID(idp), ForumsBBSManager.getInstance().getForumByID(idf), activeChar, ind);
}
else if (command.startsWith("_bbsposts;edit;"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final int idf = Integer.parseInt(st.nextToken());
final int idt = Integer.parseInt(st.nextToken());
final int idp = Integer.parseInt(st.nextToken());
showEditPost(TopicBBSManager.getInstance().getTopicByID(idt), ForumsBBSManager.getInstance().getForumByID(idf), activeChar, idp);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + command + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showEditPost(Topic topic, Forum forum, L2PcInstance activeChar, int idp)
{
if (topic == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error: This topic does not exist!</center></body></html>", activeChar);
}
else
{
final Post p = getGPosttByTopic(topic);
if ((forum == null) || (p == null))
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error: This forum or post does not exist!</center></body></html>", activeChar);
}
else
{
showHtmlEditPost(topic, activeChar, forum, p);
}
}
}
private void showPost(Topic topic, Forum forum, L2PcInstance activeChar, int ind)
{
if ((forum == null) || (topic == null))
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error: This forum is not implemented yet!</center></body></html>", activeChar);
}
else if (forum.getType() == Forum.MEMO)
{
showMemoPost(topic, activeChar, forum);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>The forum: " + forum.getName() + " is not implemented yet!</center></body></html>", activeChar);
}
}
private void showHtmlEditPost(Topic topic, L2PcInstance activeChar, Forum forum, Post p)
{
final String html = StringUtil.concat("<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">", forum.getName(), " Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=0><tr><td width=610><img src=\"sek.cbui355\" width=\"610\" height=\"1\"><br1><img src=\"sek.cbui355\" width=\"610\" height=\"1\"></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=20></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&$413;</td><td FIXWIDTH=540>", topic.getName(), "</td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29 valign=top>&$427;</td><td align=center FIXWIDTH=540><MultiEdit var =\"Content\" width=535 height=313></td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&nbsp;</td><td align=center FIXWIDTH=70><button value=\"&$140;\" action=\"Write Post ", String.valueOf(forum.getID()), ";", String.valueOf(topic.getID()), ";0 _ Content Content Content\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td><td align=center FIXWIDTH=70><button value = \"&$141;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"> </td><td align=center FIXWIDTH=400>&nbsp;</td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table></center></body></html>");
send1001(html, activeChar);
send1002(activeChar, p.getCPost(0).postTxt, topic.getName(), DateFormat.getInstance().format(new Date(topic.getDate())));
}
private void showMemoPost(Topic topic, L2PcInstance activeChar, Forum forum)
{
final Post p = getGPosttByTopic(topic);
CommunityBoardHandler.separateAndSend(StringUtil.concat("<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">Memo Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=0 bgcolor=333333><tr><td height=10></td></tr><tr><td fixWIDTH=55 align=right valign=top>&$413; : &nbsp;</td><td fixWIDTH=380 valign=top>", topic.getName(), "</td><td fixwidth=5></td><td fixwidth=50></td><td fixWIDTH=120></td></tr><tr><td height=10></td></tr><tr><td align=right><font color=\"AAAAAA\" >&$417; : &nbsp;</font></td><td><font color=\"AAAAAA\">", topic.getOwnerName() + "</font></td><td></td><td><font color=\"AAAAAA\">&$418; :</font></td><td><font color=\"AAAAAA\">", DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault()).format(p.getCPost(0).postDate), "</font></td></tr><tr><td height=10></td></tr></table><br><table border=0 cellspacing=0 cellpadding=0><tr><td fixwidth=5></td><td FIXWIDTH=600 align=left>", p.getCPost(0).postTxt.replace(">", "&gt;").replace("<", "&lt;"), "</td><td fixqqwidth=5></td></tr></table><br><img src=\"L2UI.squareblank\" width=\"1\" height=\"5\"><img src=\"L2UI.squaregray\" width=\"610\" height=\"1\"><img src=\"L2UI.squareblank\" width=\"1\" height=\"5\"><table border=0 cellspacing=0 cellpadding=0 FIXWIDTH=610><tr><td width=50><button value=\"&$422;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"></td><td width=560 align=right><table border=0 cellspacing=0><tr><td FIXWIDTH=300></td><td><button value = \"&$424;\" action=\"bypass _bbsposts;edit;", String.valueOf(forum.getID()), ";", String.valueOf(topic.getID()), ";0\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td>&nbsp;<td><button value = \"&$425;\" action=\"bypass _bbstopics;del;", String.valueOf(forum.getID()), ";", String.valueOf(topic.getID()), "\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td>&nbsp;<td><button value = \"&$421;\" action=\"bypass _bbstopics;crea;", String.valueOf(forum.getID()), "\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td>&nbsp;</tr></table></td></tr></table><br><br><br></center></body></html>"), activeChar);
}
@Override
public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, L2PcInstance activeChar)
{
final StringTokenizer st = new StringTokenizer(ar1, ";");
final int idf = Integer.parseInt(st.nextToken());
final int idt = Integer.parseInt(st.nextToken());
final int idp = Integer.parseInt(st.nextToken());
final Forum f = ForumsBBSManager.getInstance().getForumByID(idf);
if (f == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " does not exist !</center><br><br></body></html>", activeChar);
}
else
{
final Topic t = f.getTopic(idt);
if (t == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the topic: " + idt + " does not exist !</center><br><br></body></html>", activeChar);
}
else
{
final Post p = getGPosttByTopic(t);
if (p != null)
{
if (p.getCPost(idp) == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the post: " + idp + " does not exist !</center><br><br></body></html>", activeChar);
}
else
{
p.getCPost(idp).postTxt = ar4;
p.updatetxt(idp);
parsecmd("_bbsposts;read;" + f.getID() + ";" + t.getID(), activeChar);
}
}
}
}
}
public static PostBBSManager getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final PostBBSManager _instance = new PostBBSManager();
}
}

View File

@ -0,0 +1,303 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.communitybbs.Manager;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import com.l2jmobius.gameserver.communitybbs.BB.Forum;
import com.l2jmobius.gameserver.communitybbs.BB.Post;
import com.l2jmobius.gameserver.communitybbs.BB.Topic;
import com.l2jmobius.gameserver.data.sql.impl.ClanTable;
import com.l2jmobius.gameserver.handler.CommunityBoardHandler;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.util.StringUtil;
public class TopicBBSManager extends BaseBBSManager
{
private final List<Topic> _table = new CopyOnWriteArrayList<>();
private final Map<Forum, Integer> _maxId = new HashMap<>();
protected TopicBBSManager()
{
// Prevent external initialization.
}
public void addTopic(Topic tt)
{
_table.add(tt);
}
public void delTopic(Topic topic)
{
_table.remove(topic);
}
public void setMaxID(int id, Forum f)
{
_maxId.put(f, id);
}
public int getMaxID(Forum f)
{
final Integer i = _maxId.get(f);
return i == null ? 0 : i;
}
public Topic getTopicByID(int idf)
{
for (Topic t : _table)
{
if (t.getID() == idf)
{
return t;
}
}
return null;
}
@Override
public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, L2PcInstance activeChar)
{
if (ar1.equals("crea"))
{
final Forum f = ForumsBBSManager.getInstance().getForumByID(Integer.parseInt(ar2));
if (f == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + ar2 + " is not implemented yet</center><br><br></body></html>", activeChar);
}
else
{
f.vload();
final Topic t = new Topic(Topic.ConstructorType.CREATE, TopicBBSManager.getInstance().getMaxID(f) + 1, Integer.parseInt(ar2), ar5, Calendar.getInstance().getTimeInMillis(), activeChar.getName(), activeChar.getObjectId(), Topic.MEMO, 0);
f.addTopic(t);
TopicBBSManager.getInstance().setMaxID(t.getID(), f);
final Post p = new Post(activeChar.getName(), activeChar.getObjectId(), Calendar.getInstance().getTimeInMillis(), t.getID(), f.getID(), ar4);
PostBBSManager.getInstance().addPostByTopic(p, t);
parsecmd("_bbsmemo", activeChar);
}
}
else if (ar1.equals("del"))
{
final Forum f = ForumsBBSManager.getInstance().getForumByID(Integer.parseInt(ar2));
if (f == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + ar2 + " does not exist !</center><br><br></body></html>", activeChar);
}
else
{
final Topic t = f.getTopic(Integer.parseInt(ar3));
if (t == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the topic: " + ar3 + " does not exist !</center><br><br></body></html>", activeChar);
}
else
{
// CPost cp = null;
final Post p = PostBBSManager.getInstance().getGPosttByTopic(t);
if (p != null)
{
p.deleteme(t);
}
t.deleteme(f);
parsecmd("_bbsmemo", activeChar);
}
}
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + ar1 + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
@Override
public void parsecmd(String command, L2PcInstance activeChar)
{
if (command.equals("_bbsmemo"))
{
showTopics(activeChar.getMemo(), activeChar, 1, activeChar.getMemo().getID());
}
else if (command.startsWith("_bbstopics;read"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final int idf = Integer.parseInt(st.nextToken());
final String index = st.hasMoreTokens() ? st.nextToken() : null;
final int ind = index == null ? 1 : Integer.parseInt(index);
showTopics(ForumsBBSManager.getInstance().getForumByID(idf), activeChar, ind, idf);
}
else if (command.startsWith("_bbstopics;crea"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final int idf = Integer.parseInt(st.nextToken());
showNewTopic(ForumsBBSManager.getInstance().getForumByID(idf), activeChar, idf);
}
else if (command.startsWith("_bbstopics;del"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final int idf = Integer.parseInt(st.nextToken());
final int idt = Integer.parseInt(st.nextToken());
final Forum f = ForumsBBSManager.getInstance().getForumByID(idf);
if (f == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " does not exist !</center><br><br></body></html>", activeChar);
}
else
{
final Topic t = f.getTopic(idt);
if (t == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the topic: " + idt + " does not exist !</center><br><br></body></html>", activeChar);
}
else
{
// CPost cp = null;
final Post p = PostBBSManager.getInstance().getGPosttByTopic(t);
if (p != null)
{
p.deleteme(t);
}
t.deleteme(f);
parsecmd("_bbsmemo", activeChar);
}
}
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + command + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showNewTopic(Forum forum, L2PcInstance activeChar, int idf)
{
if (forum == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " is not implemented yet</center><br><br></body></html>", activeChar);
}
else if (forum.getType() == Forum.MEMO)
{
showMemoNewTopics(forum, activeChar);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + forum.getName() + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showMemoNewTopics(Forum forum, L2PcInstance activeChar)
{
final String html = StringUtil.concat("<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">Memo Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=0><tr><td width=610><img src=\"sek.cbui355\" width=\"610\" height=\"1\"><br1><img src=\"sek.cbui355\" width=\"610\" height=\"1\"></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=20></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&$413;</td><td FIXWIDTH=540><edit var = \"Title\" width=540 height=13></td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29 valign=top>&$427;</td><td align=center FIXWIDTH=540><MultiEdit var =\"Content\" width=535 height=313></td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&nbsp;</td><td align=center FIXWIDTH=70><button value=\"&$140;\" action=\"Write Topic crea ", String.valueOf(forum.getID()), " Title Content Title\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td><td align=center FIXWIDTH=70><button value = \"&$141;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"> </td><td align=center FIXWIDTH=400>&nbsp;</td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table></center></body></html>");
send1001(html, activeChar);
send1002(activeChar);
}
private void showTopics(Forum forum, L2PcInstance activeChar, int index, int idf)
{
if (forum == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " is not implemented yet</center><br><br></body></html>", activeChar);
}
else if (forum.getType() == Forum.MEMO)
{
showMemoTopics(forum, activeChar, index);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + forum.getName() + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showMemoTopics(Forum forum, L2PcInstance activeChar, int index)
{
forum.vload();
final StringBuilder html = StringUtil.startAppend(2000, "<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">Memo Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=2 bgcolor=888888 width=610><tr><td FIXWIDTH=5></td><td FIXWIDTH=415 align=center>&$413;</td><td FIXWIDTH=120 align=center></td><td FIXWIDTH=70 align=center>&$418;</td></tr></table>");
final DateFormat dateFormat = DateFormat.getInstance();
for (int i = 0, j = getMaxID(forum) + 1; i < (12 * index); j--)
{
if (j < 0)
{
break;
}
final Topic t = forum.getTopic(j);
if ((t != null) && (i++ >= (12 * (index - 1))))
{
StringUtil.append(html, "<table border=0 cellspacing=0 cellpadding=5 WIDTH=610><tr><td FIXWIDTH=5></td><td FIXWIDTH=415><a action=\"bypass _bbsposts;read;", String.valueOf(forum.getID()), ";", String.valueOf(t.getID()), "\">", t.getName(), "</a></td><td FIXWIDTH=120 align=center></td><td FIXWIDTH=70 align=center>", dateFormat.format(new Date(t.getDate())), "</td></tr></table><img src=\"L2UI.Squaregray\" width=\"610\" height=\"1\">");
}
}
html.append("<br><table width=610 cellspace=0 cellpadding=0><tr><td width=50><button value=\"&$422;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"></td><td width=510 align=center><table border=0><tr>");
if (index == 1)
{
html.append("<td><button action=\"\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16 ></td>");
}
else
{
StringUtil.append(html, "<td><button action=\"bypass _bbstopics;read;", String.valueOf(forum.getID()), ";", String.valueOf(index - 1), "\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16 ></td>");
}
int nbp = forum.getTopicSize() / 8;
if ((nbp * 8) != ClanTable.getInstance().getClanCount())
{
nbp++;
}
for (int i = 1; i <= nbp; i++)
{
if (i == index)
{
StringUtil.append(html, "<td> ", String.valueOf(i), " </td>");
}
else
{
StringUtil.append(html, "<td><a action=\"bypass _bbstopics;read;", String.valueOf(forum.getID()), ";", String.valueOf(i), "\"> ", String.valueOf(i), " </a></td>");
}
}
if (index == nbp)
{
html.append("<td><button action=\"\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
}
else
{
StringUtil.append(html, "<td><button action=\"bypass _bbstopics;read;", String.valueOf(forum.getID()), ";", String.valueOf(index + 1), "\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
}
StringUtil.append(html, "</tr></table> </td> <td align=right><button value = \"&$421;\" action=\"bypass _bbstopics;crea;", String.valueOf(forum.getID()), "\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr> <td></td><td align=center><table border=0><tr><td></td><td><edit var = \"Search\" width=130 height=11></td><td><button value=\"&$420;\" action=\"Write 5 -2 0 Search _ _\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"> </td> </tr></table> </td></tr></table><br><br><br></center></body></html>");
CommunityBoardHandler.separateAndSend(html.toString(), activeChar);
}
public static TopicBBSManager getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final TopicBBSManager _instance = new TopicBBSManager();
}
}

View File

@ -0,0 +1,172 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.enums.ChatType;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.announce.Announcement;
import com.l2jmobius.gameserver.model.announce.AnnouncementType;
import com.l2jmobius.gameserver.model.announce.AutoAnnouncement;
import com.l2jmobius.gameserver.model.announce.IAnnouncement;
import com.l2jmobius.gameserver.network.serverpackets.CreatureSay;
/**
* Loads announcements from database.
* @author UnAfraid
*/
public final class AnnouncementsTable
{
private static Logger LOGGER = Logger.getLogger(AnnouncementsTable.class.getName());
private final Map<Integer, IAnnouncement> _announcements = new ConcurrentSkipListMap<>();
protected AnnouncementsTable()
{
load();
}
private void load()
{
_announcements.clear();
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement st = con.createStatement();
ResultSet rset = st.executeQuery("SELECT * FROM announcements"))
{
while (rset.next())
{
final AnnouncementType type = AnnouncementType.findById(rset.getInt("type"));
final Announcement announce;
switch (type)
{
case NORMAL:
case CRITICAL:
{
announce = new Announcement(rset);
break;
}
case AUTO_NORMAL:
case AUTO_CRITICAL:
{
announce = new AutoAnnouncement(rset);
break;
}
default:
{
continue;
}
}
_announcements.put(announce.getId(), announce);
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed loading announcements:", e);
}
}
/**
* Sending all announcements to the player
* @param player
*/
public void showAnnouncements(L2PcInstance player)
{
sendAnnouncements(player, AnnouncementType.NORMAL);
sendAnnouncements(player, AnnouncementType.CRITICAL);
sendAnnouncements(player, AnnouncementType.EVENT);
}
/**
* Sends all announcements to the player by the specified type
* @param player
* @param type
*/
public void sendAnnouncements(L2PcInstance player, AnnouncementType type)
{
for (IAnnouncement announce : _announcements.values())
{
if (announce.isValid() && (announce.getType() == type))
{
player.sendPacket(new CreatureSay(0, //
type == AnnouncementType.CRITICAL ? ChatType.CRITICAL_ANNOUNCE : ChatType.ANNOUNCEMENT, //
player.getName(), announce.getContent()));
}
}
}
/**
* Adds announcement
* @param announce
*/
public void addAnnouncement(IAnnouncement announce)
{
if (announce.storeMe())
{
_announcements.put(announce.getId(), announce);
}
}
/**
* Removes announcement by id
* @param id
* @return {@code true} if announcement exists and was deleted successfully, {@code false} otherwise.
*/
public boolean deleteAnnouncement(int id)
{
final IAnnouncement announce = _announcements.remove(id);
return (announce != null) && announce.deleteMe();
}
/**
* @param id
* @return {@link IAnnouncement} by id
*/
public IAnnouncement getAnnounce(int id)
{
return _announcements.get(id);
}
/**
* @return {@link Collection} containing all announcements
*/
public Collection<IAnnouncement> getAllAnnouncements()
{
return _announcements.values();
}
/**
* @return Single instance of {@link AnnouncementsTable}
*/
public static AnnouncementsTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final AnnouncementsTable _instance = new AnnouncementsTable();
}
}

View File

@ -0,0 +1,243 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
/**
* Loads name and access level for all players.
* @version 2005/03/27
*/
public class CharNameTable
{
private static Logger _log = Logger.getLogger(CharNameTable.class.getName());
private final Map<Integer, String> _chars = new ConcurrentHashMap<>();
private final Map<Integer, Integer> _accessLevels = new ConcurrentHashMap<>();
protected CharNameTable()
{
if (Config.CACHE_CHAR_NAMES)
{
loadAll();
}
}
public final void addName(L2PcInstance player)
{
if (player == null)
{
return;
}
addName(player.getObjectId(), player.getName());
_accessLevels.put(player.getObjectId(), player.getAccessLevel().getLevel());
}
private final void addName(int objectId, String name)
{
if ((name != null) && !name.equals(_chars.get(objectId)))
{
_chars.put(objectId, name);
}
}
public final void removeName(int objId)
{
_chars.remove(objId);
_accessLevels.remove(objId);
}
public final int getIdByName(String name)
{
if ((name == null) || name.isEmpty())
{
return -1;
}
for (Entry<Integer, String> entry : _chars.entrySet())
{
if (entry.getValue().equalsIgnoreCase(name))
{
return entry.getKey();
}
}
if (Config.CACHE_CHAR_NAMES)
{
return -1;
}
int id = -1;
int accessLevel = 0;
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT charId,accesslevel FROM characters WHERE char_name=?"))
{
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery())
{
while (rs.next())
{
id = rs.getInt(1);
accessLevel = rs.getInt(2);
}
}
}
catch (SQLException e)
{
_log.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char name: " + e.getMessage(), e);
}
if (id <= 0)
{
return -1; // not found
}
_chars.put(id, name);
_accessLevels.put(id, accessLevel);
return id;
}
public final String getNameById(int id)
{
if (id <= 0)
{
return null;
}
String name = _chars.get(id);
if (name != null)
{
return name;
}
if (Config.CACHE_CHAR_NAMES)
{
return null;
}
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT char_name,accesslevel FROM characters WHERE charId=?"))
{
ps.setInt(1, id);
try (ResultSet rset = ps.executeQuery())
{
if (rset.next())
{
name = rset.getString(1);
_chars.put(id, name);
_accessLevels.put(id, rset.getInt(2));
return name;
}
}
}
catch (SQLException e)
{
_log.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char id: " + e.getMessage(), e);
}
return null; // not found
}
public final int getAccessLevelById(int objectId)
{
return getNameById(objectId) != null ? _accessLevels.get(objectId) : 0;
}
public synchronized boolean doesCharNameExist(String name)
{
boolean result = true;
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT account_name FROM characters WHERE char_name=?"))
{
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery())
{
result = rs.next();
}
}
catch (SQLException e)
{
_log.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing charname: " + e.getMessage(), e);
}
return result;
}
public int getAccountCharacterCount(String account)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(char_name) FROM characters WHERE account_name=?"))
{
ps.setString(1, account);
try (ResultSet rset = ps.executeQuery())
{
while (rset.next())
{
return rset.getInt(1);
}
}
}
catch (SQLException e)
{
_log.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char count: " + e.getMessage(), e);
}
return 0;
}
private void loadAll()
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT charId, char_name, accesslevel FROM characters"))
{
while (rs.next())
{
final int id = rs.getInt(1);
_chars.put(id, rs.getString(2));
_accessLevels.put(id, rs.getInt(3));
}
}
catch (SQLException e)
{
_log.log(Level.WARNING, getClass().getSimpleName() + ": Could not load char name: " + e.getMessage(), e);
}
_log.info(getClass().getSimpleName() + ": Loaded " + _chars.size() + " char names.");
}
public static CharNameTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final CharNameTable _instance = new CharNameTable();
}
}

View File

@ -0,0 +1,250 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.data.xml.impl.NpcData;
import com.l2jmobius.gameserver.data.xml.impl.PetDataTable;
import com.l2jmobius.gameserver.datatables.SkillData;
import com.l2jmobius.gameserver.model.L2PetData;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PetInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2ServitorInstance;
import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.network.serverpackets.PetItemList;
/**
* @author Nyaran
*/
public class CharSummonTable
{
private static final Logger LOGGER = Logger.getLogger(CharSummonTable.class.getName());
private static final Map<Integer, Integer> _pets = new ConcurrentHashMap<>();
private static final Map<Integer, Integer> _servitors = new ConcurrentHashMap<>();
// SQL
private static final String INIT_PET = "SELECT ownerId, item_obj_id FROM pets WHERE restore = 'true'";
private static final String INIT_SUMMONS = "SELECT ownerId, summonSkillId FROM character_summons";
private static final String LOAD_SUMMON = "SELECT curHp, curMp, time FROM character_summons WHERE ownerId = ? AND summonSkillId = ?";
private static final String REMOVE_SUMMON = "DELETE FROM character_summons WHERE ownerId = ?";
private static final String SAVE_SUMMON = "REPLACE INTO character_summons (ownerId,summonSkillId,curHp,curMp,time) VALUES (?,?,?,?,?)";
public Map<Integer, Integer> getPets()
{
return _pets;
}
public Map<Integer, Integer> getServitors()
{
return _servitors;
}
public void init()
{
if (Config.RESTORE_SERVITOR_ON_RECONNECT)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery(INIT_SUMMONS))
{
while (rs.next())
{
_servitors.put(rs.getInt("ownerId"), rs.getInt("summonSkillId"));
}
}
catch (Exception e)
{
LOGGER.warning(getClass().getSimpleName() + ": Error while loading saved servitor: " + e);
}
}
if (Config.RESTORE_PET_ON_RECONNECT)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery(INIT_PET))
{
while (rs.next())
{
_pets.put(rs.getInt("ownerId"), rs.getInt("item_obj_id"));
}
}
catch (Exception e)
{
LOGGER.warning(getClass().getSimpleName() + ": Error while loading saved pet: " + e);
}
}
}
public void removeServitor(L2PcInstance activeChar)
{
_servitors.remove(activeChar.getObjectId());
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement(REMOVE_SUMMON))
{
ps.setInt(1, activeChar.getObjectId());
ps.execute();
}
catch (SQLException e)
{
LOGGER.warning(getClass().getSimpleName() + ": Summon cannot be removed: " + e);
}
}
public void restorePet(L2PcInstance activeChar)
{
final L2ItemInstance item = activeChar.getInventory().getItemByObjectId(_pets.get(activeChar.getObjectId()));
if (item == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet summoning item for: " + activeChar);
return;
}
final L2PetData petData = PetDataTable.getInstance().getPetDataByItemId(item.getId());
if (petData == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet data for: " + activeChar + " and summoning item: " + item);
return;
}
final L2NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(petData.getNpcId());
if (npcTemplate == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet NPC template for: " + activeChar + " and pet Id:" + petData.getNpcId());
return;
}
final L2PetInstance pet = L2PetInstance.spawnPet(npcTemplate, activeChar, item);
if (pet == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet instance for: " + activeChar + " and pet NPC template:" + npcTemplate);
return;
}
pet.setShowSummonAnimation(true);
pet.setTitle(activeChar.getName());
if (!pet.isRespawned())
{
pet.setCurrentHp(pet.getMaxHp());
pet.setCurrentMp(pet.getMaxMp());
pet.getStat().setExp(pet.getExpForThisLevel());
pet.setCurrentFed(pet.getMaxFed());
}
pet.setRunning();
if (!pet.isRespawned())
{
pet.storeMe();
}
item.setEnchantLevel(pet.getLevel());
activeChar.setPet(pet);
pet.spawnMe(activeChar.getX() + 50, activeChar.getY() + 100, activeChar.getZ());
pet.startFeed();
pet.setFollowStatus(true);
pet.getOwner().sendPacket(new PetItemList(pet.getInventory().getItems()));
pet.broadcastStatusUpdate();
}
public void restoreServitor(L2PcInstance activeChar)
{
final int skillId = _servitors.get(activeChar.getObjectId());
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement(LOAD_SUMMON))
{
ps.setInt(1, activeChar.getObjectId());
ps.setInt(2, skillId);
try (ResultSet rs = ps.executeQuery())
{
Skill skill;
while (rs.next())
{
final int curHp = rs.getInt("curHp");
final int curMp = rs.getInt("curMp");
final int time = rs.getInt("time");
skill = SkillData.getInstance().getSkill(skillId, activeChar.getSkillLevel(skillId));
if (skill == null)
{
removeServitor(activeChar);
return;
}
skill.applyEffects(activeChar, activeChar);
if (activeChar.hasServitor())
{
final L2ServitorInstance summon = (L2ServitorInstance) activeChar.getSummon();
summon.setCurrentHp(curHp);
summon.setCurrentMp(curMp);
summon.setLifeTimeRemaining(time);
}
}
}
}
catch (SQLException e)
{
LOGGER.warning(getClass().getSimpleName() + ": Servitor cannot be restored: " + e);
}
}
public void saveSummon(L2ServitorInstance summon)
{
if ((summon == null) || (summon.getLifeTimeRemaining() <= 0))
{
return;
}
_servitors.put(summon.getOwner().getObjectId(), summon.getReferenceSkill());
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement(SAVE_SUMMON))
{
ps.setInt(1, summon.getOwner().getObjectId());
ps.setInt(2, summon.getReferenceSkill());
ps.setInt(3, (int) Math.round(summon.getCurrentHp()));
ps.setInt(4, (int) Math.round(summon.getCurrentMp()));
ps.setInt(5, summon.getLifeTimeRemaining());
ps.execute();
}
catch (Exception e)
{
LOGGER.warning(getClass().getSimpleName() + ": Failed to store summon: " + summon + " from " + summon.getOwner() + ", error: " + e);
}
}
public static CharSummonTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final CharSummonTable _instance = new CharSummonTable();
}
}

View File

@ -0,0 +1,543 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager;
import com.l2jmobius.gameserver.idfactory.IdFactory;
import com.l2jmobius.gameserver.instancemanager.CHSiegeManager;
import com.l2jmobius.gameserver.instancemanager.ClanHallAuctionManager;
import com.l2jmobius.gameserver.instancemanager.FortManager;
import com.l2jmobius.gameserver.instancemanager.FortSiegeManager;
import com.l2jmobius.gameserver.instancemanager.SiegeManager;
import com.l2jmobius.gameserver.model.ClanPrivilege;
import com.l2jmobius.gameserver.model.L2Clan;
import com.l2jmobius.gameserver.model.L2ClanMember;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.entity.Auction;
import com.l2jmobius.gameserver.model.entity.Fort;
import com.l2jmobius.gameserver.model.entity.FortSiege;
import com.l2jmobius.gameserver.model.entity.Siege;
import com.l2jmobius.gameserver.model.entity.clanhall.SiegableHall;
import com.l2jmobius.gameserver.model.events.EventDispatcher;
import com.l2jmobius.gameserver.model.events.impl.character.player.clan.OnPlayerClanCreate;
import com.l2jmobius.gameserver.model.events.impl.character.player.clan.OnPlayerClanDestroy;
import com.l2jmobius.gameserver.model.events.impl.clan.OnClanWarFinish;
import com.l2jmobius.gameserver.model.events.impl.clan.OnClanWarStart;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.ExBrExtraUserInfo;
import com.l2jmobius.gameserver.network.serverpackets.PledgeShowInfoUpdate;
import com.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListAll;
import com.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListUpdate;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.gameserver.network.serverpackets.UserInfo;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.EnumIntBitmask;
/**
* This class loads the clan related data.
*/
public class ClanTable
{
private static final Logger _log = Logger.getLogger(ClanTable.class.getName());
private final Map<Integer, L2Clan> _clans = new ConcurrentHashMap<>();
protected ClanTable()
{
// forums has to be loaded before clan data, because of last forum id used should have also memo included
if (Config.ENABLE_COMMUNITY_BOARD)
{
ForumsBBSManager.getInstance().initRoot();
}
L2Clan clan;
// Count the clans
int clanCount = 0;
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT clan_id FROM clan_data"))
{
while (rs.next())
{
final int clanId = rs.getInt("clan_id");
_clans.put(clanId, new L2Clan(clanId));
clan = getClan(clanId);
if (clan.getDissolvingExpiryTime() != 0)
{
scheduleRemoveClan(clan.getId());
}
clanCount++;
}
}
catch (Exception e)
{
_log.log(Level.SEVERE, "Error restoring ClanTable.", e);
}
_log.info(getClass().getSimpleName() + ": Restored " + clanCount + " clans from the database.");
allianceCheck();
restorewars();
}
/**
* Gets the clans.
* @return the clans
*/
public Collection<L2Clan> getClans()
{
return _clans.values();
}
/**
* Gets the clan count.
* @return the clan count
*/
public int getClanCount()
{
return _clans.size();
}
/**
* @param clanId
* @return
*/
public L2Clan getClan(int clanId)
{
return _clans.get(clanId);
}
public L2Clan getClanByName(String clanName)
{
return getClans().stream().filter(c -> c.getName().equalsIgnoreCase(clanName)).findFirst().orElse(null);
}
/**
* Creates a new clan and store clan info to database
* @param player
* @param clanName
* @return NULL if clan with same name already exists
*/
public L2Clan createClan(L2PcInstance player, String clanName)
{
if (null == player)
{
return null;
}
if (Config.DEBUG)
{
_log.info(getClass().getSimpleName() + ": " + player.getObjectId() + "(" + player.getName() + ") requested a clan creation.");
}
if (10 > player.getLevel())
{
player.sendPacket(SystemMessageId.YOU_DO_NOT_MEET_THE_CRITERIA_IN_ORDER_TO_CREATE_A_CLAN);
return null;
}
if (0 != player.getClanId())
{
player.sendPacket(SystemMessageId.YOU_HAVE_FAILED_TO_CREATE_A_CLAN);
return null;
}
if (System.currentTimeMillis() < player.getClanCreateExpiryTime())
{
player.sendPacket(SystemMessageId.YOU_MUST_WAIT_10_DAYS_BEFORE_CREATING_A_NEW_CLAN);
return null;
}
if (!Util.isAlphaNumeric(clanName) || (2 > clanName.length()))
{
player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID);
return null;
}
if (16 < clanName.length())
{
player.sendPacket(SystemMessageId.CLAN_NAME_S_LENGTH_IS_INCORRECT);
return null;
}
if (null != getClanByName(clanName))
{
// clan name is already taken
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_ALREADY_EXISTS);
sm.addString(clanName);
player.sendPacket(sm);
return null;
}
final L2Clan clan = new L2Clan(IdFactory.getInstance().getNextId(), clanName);
final L2ClanMember leader = new L2ClanMember(clan, player);
clan.setLeader(leader);
leader.setPlayerInstance(player);
clan.store();
player.setClan(clan);
player.setPledgeClass(L2ClanMember.calculatePledgeClass(player));
player.setClanPrivileges(new EnumIntBitmask<>(ClanPrivilege.class, true));
_clans.put(Integer.valueOf(clan.getId()), clan);
// should be update packet only
player.sendPacket(new PledgeShowInfoUpdate(clan));
player.sendPacket(new PledgeShowMemberListAll(clan, player));
player.sendPacket(new UserInfo(player));
player.sendPacket(new ExBrExtraUserInfo(player));
player.sendPacket(new PledgeShowMemberListUpdate(player));
player.sendPacket(SystemMessageId.YOUR_CLAN_HAS_BEEN_CREATED);
// Notify to scripts
EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanCreate(player, clan));
return clan;
}
public synchronized void destroyClan(int clanId)
{
final L2Clan clan = getClan(clanId);
if (clan == null)
{
return;
}
clan.broadcastToOnlineMembers(SystemMessage.getSystemMessage(SystemMessageId.CLAN_HAS_DISPERSED));
final int castleId = clan.getCastleId();
if (castleId == 0)
{
for (Siege siege : SiegeManager.getInstance().getSieges())
{
siege.removeSiegeClan(clan);
}
}
final int fortId = clan.getFortId();
if (fortId == 0)
{
for (FortSiege siege : FortSiegeManager.getInstance().getSieges())
{
siege.removeAttacker(clan);
}
}
final int hallId = clan.getHideoutId();
if (hallId == 0)
{
for (SiegableHall hall : CHSiegeManager.getInstance().getConquerableHalls().values())
{
hall.removeAttacker(clan);
}
}
final Auction auction = ClanHallAuctionManager.getInstance().getAuction(clan.getAuctionBiddedAt());
if (auction != null)
{
auction.cancelBid(clan.getId());
}
final L2ClanMember leaderMember = clan.getLeader();
clan.getWarehouse().destroyAllItems("ClanRemove", leaderMember == null ? null : clan.getLeader().getPlayerInstance(), null);
for (L2ClanMember member : clan.getMembers())
{
clan.removeClanMember(member.getObjectId(), 0);
}
_clans.remove(clanId);
IdFactory.getInstance().releaseId(clanId);
try (Connection con = DatabaseFactory.getInstance().getConnection())
{
try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_data WHERE clan_id=?"))
{
ps.setInt(1, clanId);
ps.execute();
}
try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_privs WHERE clan_id=?"))
{
ps.setInt(1, clanId);
ps.execute();
}
try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_skills WHERE clan_id=?"))
{
ps.setInt(1, clanId);
ps.execute();
}
try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_subpledges WHERE clan_id=?"))
{
ps.setInt(1, clanId);
ps.execute();
}
try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_wars WHERE clan1=? OR clan2=?"))
{
ps.setInt(1, clanId);
ps.setInt(2, clanId);
ps.execute();
}
try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_notices WHERE clan_id=?"))
{
ps.setInt(1, clanId);
ps.execute();
}
if (castleId != 0)
{
try (PreparedStatement ps = con.prepareStatement("UPDATE castle SET taxPercent = 0 WHERE id = ?"))
{
ps.setInt(1, castleId);
ps.execute();
}
}
if (fortId != 0)
{
final Fort fort = FortManager.getInstance().getFortById(fortId);
if ((fort != null) && (clan == fort.getOwnerClan()))
{
fort.removeOwner(true);
}
}
if (hallId != 0)
{
final SiegableHall hall = CHSiegeManager.getInstance().getSiegableHall(hallId);
if ((hall != null) && (hall.getOwnerId() == clanId))
{
hall.free();
}
}
}
catch (Exception e)
{
_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error removing clan from DB.", e);
}
// Notify to scripts
EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanDestroy(leaderMember, clan));
}
public void scheduleRemoveClan(int clanId)
{
ThreadPoolManager.getInstance().scheduleGeneral(() ->
{
if (getClan(clanId) == null)
{
return;
}
if (getClan(clanId).getDissolvingExpiryTime() != 0)
{
destroyClan(clanId);
}
}, Math.max(getClan(clanId).getDissolvingExpiryTime() - System.currentTimeMillis(), 300000));
}
public boolean isAllyExists(String allyName)
{
for (L2Clan clan : getClans())
{
if ((clan.getAllyName() != null) && clan.getAllyName().equalsIgnoreCase(allyName))
{
return true;
}
}
return false;
}
public void storeclanswars(int clanId1, int clanId2)
{
final L2Clan clan1 = getClan(clanId1);
final L2Clan clan2 = getClan(clanId2);
EventDispatcher.getInstance().notifyEventAsync(new OnClanWarStart(clan1, clan2));
clan1.setEnemyClan(clan2);
clan2.setAttackerClan(clan1);
clan1.broadcastClanStatus();
clan2.broadcastClanStatus();
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("REPLACE INTO clan_wars (clan1, clan2, wantspeace1, wantspeace2) VALUES(?,?,?,?)"))
{
ps.setInt(1, clanId1);
ps.setInt(2, clanId2);
ps.setInt(3, 0);
ps.setInt(4, 0);
ps.execute();
}
catch (Exception e)
{
_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error storing clan wars data.", e);
}
// SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.WAR_WITH_THE_S1_CLAN_HAS_BEGUN);
//
SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.A_CLAN_WAR_HAS_BEEN_DECLARED_AGAINST_THE_CLAN_S1_IF_YOU_ARE_KILLED_DURING_THE_CLAN_WAR_BY_MEMBERS_OF_THE_OPPOSING_CLAN_YOU_WILL_ONLY_LOSE_A_QUARTER_OF_THE_NORMAL_EXPERIENCE_FROM_DEATH);
msg.addString(clan2.getName());
clan1.broadcastToOnlineMembers(msg);
// msg = SystemMessage.getSystemMessage(SystemMessageId.WAR_WITH_THE_S1_CLAN_HAS_BEGUN);
// msg.addString(clan1.getName());
// clan2.broadcastToOnlineMembers(msg);
// clan1 declared clan war.
msg = SystemMessage.getSystemMessage(SystemMessageId.S1_HAS_DECLARED_A_CLAN_WAR);
msg.addString(clan1.getName());
clan2.broadcastToOnlineMembers(msg);
}
public void deleteclanswars(int clanId1, int clanId2)
{
final L2Clan clan1 = getClan(clanId1);
final L2Clan clan2 = getClan(clanId2);
EventDispatcher.getInstance().notifyEventAsync(new OnClanWarFinish(clan1, clan2));
clan1.deleteEnemyClan(clan2);
clan2.deleteAttackerClan(clan1);
clan1.broadcastClanStatus();
clan2.broadcastClanStatus();
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM clan_wars WHERE clan1=? AND clan2=?"))
{
ps.setInt(1, clanId1);
ps.setInt(2, clanId2);
ps.execute();
}
catch (Exception e)
{
_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error removing clan wars data.", e);
}
// SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.WAR_WITH_THE_S1_CLAN_HAS_ENDED);
SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.THE_WAR_AGAINST_S1_CLAN_HAS_BEEN_STOPPED);
msg.addString(clan2.getName());
clan1.broadcastToOnlineMembers(msg);
msg = SystemMessage.getSystemMessage(SystemMessageId.THE_CLAN_S1_HAS_DECIDED_TO_STOP_THE_WAR);
msg.addString(clan1.getName());
clan2.broadcastToOnlineMembers(msg);
}
public void checkSurrender(L2Clan clan1, L2Clan clan2)
{
int count = 0;
for (L2ClanMember player : clan1.getMembers())
{
if ((player != null) && (player.getPlayerInstance().getWantsPeace() == 1))
{
count++;
}
}
if (count == (clan1.getMembers().length - 1))
{
clan1.deleteEnemyClan(clan2);
clan2.deleteEnemyClan(clan1);
deleteclanswars(clan1.getId(), clan2.getId());
}
}
private void restorewars()
{
L2Clan clan1, clan2;
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement statement = con.createStatement();
ResultSet rset = statement.executeQuery("SELECT clan1, clan2 FROM clan_wars"))
{
while (rset.next())
{
clan1 = getClan(rset.getInt("clan1"));
clan2 = getClan(rset.getInt("clan2"));
if ((clan1 != null) && (clan2 != null))
{
clan1.setEnemyClan(rset.getInt("clan2"));
clan2.setAttackerClan(rset.getInt("clan1"));
}
else
{
_log.log(Level.WARNING, getClass().getSimpleName() + ": restorewars one of clans is null clan1:" + clan1 + " clan2:" + clan2);
}
}
}
catch (Exception e)
{
_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error restoring clan wars data.", e);
}
}
/**
* Check for nonexistent alliances
*/
private void allianceCheck()
{
for (L2Clan clan : _clans.values())
{
final int allyId = clan.getAllyId();
if ((allyId != 0) && (clan.getId() != allyId) && !_clans.containsKey(allyId))
{
clan.setAllyId(0);
clan.setAllyName(null);
clan.changeAllyCrest(0, true);
clan.updateClanInDB();
_log.info(getClass().getSimpleName() + ": Removed alliance from clan: " + clan);
}
}
}
public List<L2Clan> getClanAllies(int allianceId)
{
final List<L2Clan> clanAllies = new ArrayList<>();
if (allianceId != 0)
{
for (L2Clan clan : _clans.values())
{
if ((clan != null) && (clan.getAllyId() == allianceId))
{
clanAllies.add(clan);
}
}
}
return clanAllies;
}
public void storeClanScore()
{
for (L2Clan clan : _clans.values())
{
clan.updateClanScoreInDB();
}
}
public static ClanTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final ClanTable _instance = new ClanTable();
}
}

View File

@ -0,0 +1,219 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.model.L2Clan;
import com.l2jmobius.gameserver.model.L2Crest;
import com.l2jmobius.gameserver.model.L2Crest.CrestType;
/**
* Loads and saves crests from database.
* @author NosBit
*/
public final class CrestTable
{
private static final Logger LOGGER = Logger.getLogger(CrestTable.class.getName());
private final Map<Integer, L2Crest> _crests = new ConcurrentHashMap<>();
private final AtomicInteger _nextId = new AtomicInteger(1);
protected CrestTable()
{
load();
}
public synchronized void load()
{
_crests.clear();
final Set<Integer> crestsInUse = new HashSet<>();
for (L2Clan clan : ClanTable.getInstance().getClans())
{
if (clan.getCrestId() != 0)
{
crestsInUse.add(clan.getCrestId());
}
if (clan.getCrestLargeId() != 0)
{
crestsInUse.add(clan.getCrestLargeId());
}
if (clan.getAllyCrestId() != 0)
{
crestsInUse.add(clan.getAllyCrestId());
}
}
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement statement = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = statement.executeQuery("SELECT `crest_id`, `data`, `type` FROM `crests` ORDER BY `crest_id` DESC"))
{
while (rs.next())
{
final int id = rs.getInt("crest_id");
if (_nextId.get() <= id)
{
_nextId.set(id + 1);
}
// delete all unused crests except the last one we dont want to reuse
// a crest id because client will display wrong crest if its reused
if (!crestsInUse.contains(id) && (id != (_nextId.get() - 1)))
{
rs.deleteRow();
continue;
}
final byte[] data = rs.getBytes("data");
final CrestType crestType = CrestType.getById(rs.getInt("type"));
if (crestType != null)
{
_crests.put(id, new L2Crest(id, data, crestType));
}
else
{
LOGGER.warning("Unknown crest type found in database. Type:" + rs.getInt("type"));
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, "There was an error while loading crests from database:", e);
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crests.size() + " Crests.");
for (L2Clan clan : ClanTable.getInstance().getClans())
{
if ((clan.getCrestId() != 0) && (getCrest(clan.getCrestId()) == null))
{
LOGGER.info("Removing non-existent crest for clan " + clan.getName() + " [" + clan.getId() + "], crestId:" + clan.getCrestId());
clan.setCrestId(0);
clan.changeClanCrest(0);
}
if ((clan.getCrestLargeId() != 0) && (getCrest(clan.getCrestLargeId()) == null))
{
LOGGER.info("Removing non-existent large crest for clan " + clan.getName() + " [" + clan.getId() + "], crestLargeId:" + clan.getCrestLargeId());
clan.setCrestLargeId(0);
clan.changeLargeCrest(0);
}
if ((clan.getAllyCrestId() != 0) && (getCrest(clan.getAllyCrestId()) == null))
{
LOGGER.info("Removing non-existent ally crest for clan " + clan.getName() + " [" + clan.getId() + "], allyCrestId:" + clan.getAllyCrestId());
clan.setAllyCrestId(0);
clan.changeAllyCrest(0, true);
}
}
}
/**
* @param crestId The crest id
* @return {@code L2Crest} if crest is found, {@code null} if crest was not found.
*/
public L2Crest getCrest(int crestId)
{
return _crests.get(crestId);
}
/**
* Creates a {@code L2Crest} object and inserts it in database and cache.
* @param data
* @param crestType
* @return {@code L2Crest} on success, {@code null} on failure.
*/
public L2Crest createCrest(byte[] data, CrestType crestType)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement("INSERT INTO `crests`(`crest_id`, `data`, `type`) VALUES(?, ?, ?)"))
{
final L2Crest crest = new L2Crest(getNextId(), data, crestType);
statement.setInt(1, crest.getId());
statement.setBytes(2, crest.getData());
statement.setInt(3, crest.getType().getId());
statement.executeUpdate();
_crests.put(crest.getId(), crest);
return crest;
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, "There was an error while saving crest in database:", e);
}
return null;
}
/**
* Removes crest from database and cache.
* @param crestId the id of crest to be removed.
*/
public void removeCrest(int crestId)
{
_crests.remove(crestId);
// avoid removing last crest id we dont want to lose index...
// because client will display wrong crest if its reused
if (crestId == (_nextId.get() - 1))
{
return;
}
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement("DELETE FROM `crests` WHERE `crest_id` = ?"))
{
statement.setInt(1, crestId);
statement.executeUpdate();
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, "There was an error while deleting crest from database:", e);
}
}
/**
* @return The next crest id.
*/
public int getNextId()
{
return _nextId.getAndIncrement();
}
public static CrestTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final CrestTable _instance = new CrestTable();
}
}

View File

@ -0,0 +1,199 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.model.holders.ItemHolder;
import com.l2jmobius.gameserver.model.holders.SkillHolder;
public class NpcBufferTable
{
private static final Logger LOGGER = Logger.getLogger(NpcBufferTable.class.getName());
private final Map<Integer, NpcBufferSkills> _buffers = new HashMap<>();
public static class NpcBufferData
{
private final SkillHolder _skill;
private final ItemHolder _fee;
protected NpcBufferData(int skillId, int skillLevel, int feeId, int feeAmount)
{
_skill = new SkillHolder(skillId, skillLevel);
_fee = new ItemHolder(feeId, feeAmount);
}
public SkillHolder getSkill()
{
return _skill;
}
public ItemHolder getFee()
{
return _fee;
}
}
private static class NpcBufferSkills
{
private final int _npcId;
private final Map<Integer, NpcBufferData> _skills = new HashMap<>();
protected NpcBufferSkills(int npcId)
{
_npcId = npcId;
}
public void addSkill(int skillId, int skillLevel, int skillFeeId, int skillFeeAmount, int buffGroup)
{
_skills.put(buffGroup, new NpcBufferData(skillId, skillLevel, skillFeeId, skillFeeAmount));
}
public NpcBufferData getSkillGroupInfo(int buffGroup)
{
return _skills.get(buffGroup);
}
@SuppressWarnings("unused")
public int getNpcId()
{
return _npcId;
}
}
protected NpcBufferTable()
{
int skillCount = 0;
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rset = s.executeQuery("SELECT `npc_id`,`skill_id`,`skill_level`,`skill_fee_id`,`skill_fee_amount`,`buff_group` FROM `npc_buffer` ORDER BY `npc_id` ASC"))
{
int lastNpcId = 0;
NpcBufferSkills skills = null;
while (rset.next())
{
final int npcId = rset.getInt("npc_id");
final int skillId = rset.getInt("skill_id");
final int skillLevel = rset.getInt("skill_level");
final int skillFeeId = rset.getInt("skill_fee_id");
final int skillFeeAmount = rset.getInt("skill_fee_amount");
final int buffGroup = rset.getInt("buff_group");
if (npcId != lastNpcId)
{
if (lastNpcId != 0)
{
_buffers.put(lastNpcId, skills);
}
skills = new NpcBufferSkills(npcId);
skills.addSkill(skillId, skillLevel, skillFeeId, skillFeeAmount, buffGroup);
}
else if (skills != null)
{
skills.addSkill(skillId, skillLevel, skillFeeId, skillFeeAmount, buffGroup);
}
lastNpcId = npcId;
skillCount++;
}
if (lastNpcId != 0)
{
_buffers.put(lastNpcId, skills);
}
}
catch (SQLException e)
{
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error reading npc_buffer table: " + e.getMessage(), e);
}
if (Config.CUSTOM_NPCBUFFER_TABLES)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rset = s.executeQuery("SELECT `npc_id`,`skill_id`,`skill_level`,`skill_fee_id`,`skill_fee_amount`,`buff_group` FROM `custom_npc_buffer` ORDER BY `npc_id` ASC"))
{
int lastNpcId = 0;
NpcBufferSkills skills = null;
while (rset.next())
{
final int npcId = rset.getInt("npc_id");
final int skillId = rset.getInt("skill_id");
final int skillLevel = rset.getInt("skill_level");
final int skillFeeId = rset.getInt("skill_fee_id");
final int skillFeeAmount = rset.getInt("skill_fee_amount");
final int buffGroup = rset.getInt("buff_group");
if (npcId != lastNpcId)
{
if (lastNpcId != 0)
{
_buffers.put(lastNpcId, skills);
}
skills = new NpcBufferSkills(npcId);
skills.addSkill(skillId, skillLevel, skillFeeId, skillFeeAmount, buffGroup);
}
else if (skills != null)
{
skills.addSkill(skillId, skillLevel, skillFeeId, skillFeeAmount, buffGroup);
}
lastNpcId = npcId;
skillCount++;
}
if (lastNpcId != 0)
{
_buffers.put(lastNpcId, skills);
}
}
catch (SQLException e)
{
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error reading custom_npc_buffer table: " + e.getMessage(), e);
}
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _buffers.size() + " buffers and " + skillCount + " skills.");
}
public NpcBufferData getSkillInfo(int npcId, int buffGroup)
{
final NpcBufferSkills skills = _buffers.get(npcId);
return skills != null ? skills.getSkillGroupInfo(buffGroup) : null;
}
public static NpcBufferTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final NpcBufferTable _instance = new NpcBufferTable();
}
}

View File

@ -0,0 +1,407 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.LoginServerThread;
import com.l2jmobius.gameserver.enums.PrivateStoreType;
import com.l2jmobius.gameserver.model.L2ManufactureItem;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.TradeItem;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.L2GameClient;
import com.l2jmobius.gameserver.network.L2GameClient.GameClientState;
public class OfflineTradersTable
{
private static Logger LOGGER = Logger.getLogger(OfflineTradersTable.class.getName());
// SQL DEFINITIONS
private static final String SAVE_OFFLINE_STATUS = "INSERT INTO character_offline_trade (`charId`,`time`,`type`,`title`) VALUES (?,?,?,?)";
private static final String SAVE_ITEMS = "INSERT INTO character_offline_trade_items (`charId`,`item`,`count`,`price`) VALUES (?,?,?,?)";
private static final String CLEAR_OFFLINE_TABLE = "DELETE FROM character_offline_trade";
private static final String CLEAR_OFFLINE_TABLE_PLAYER = "DELETE FROM character_offline_trade WHERE `charId`=?";
private static final String CLEAR_OFFLINE_TABLE_ITEMS = "DELETE FROM character_offline_trade_items";
private static final String CLEAR_OFFLINE_TABLE_ITEMS_PLAYER = "DELETE FROM character_offline_trade_items WHERE `charId`=?";
private static final String LOAD_OFFLINE_STATUS = "SELECT * FROM character_offline_trade";
private static final String LOAD_OFFLINE_ITEMS = "SELECT * FROM character_offline_trade_items WHERE `charId`=?";
public void storeOffliners()
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement stm1 = con.prepareStatement(CLEAR_OFFLINE_TABLE);
PreparedStatement stm2 = con.prepareStatement(CLEAR_OFFLINE_TABLE_ITEMS);
PreparedStatement stm3 = con.prepareStatement(SAVE_OFFLINE_STATUS);
PreparedStatement stm_items = con.prepareStatement(SAVE_ITEMS))
{
stm1.execute();
stm2.execute();
con.setAutoCommit(false); // avoid halfway done
for (L2PcInstance pc : L2World.getInstance().getPlayers())
{
try
{
if ((pc.getPrivateStoreType() != PrivateStoreType.NONE) && pc.isInOfflineMode())
{
stm3.setInt(1, pc.getObjectId()); // Char Id
stm3.setLong(2, pc.getOfflineStartTime());
stm3.setInt(3, pc.getPrivateStoreType().getId()); // store type
String title = null;
switch (pc.getPrivateStoreType())
{
case BUY:
{
if (!Config.OFFLINE_TRADE_ENABLE)
{
continue;
}
title = pc.getBuyList().getTitle();
for (TradeItem i : pc.getBuyList().getItems())
{
stm_items.setInt(1, pc.getObjectId());
stm_items.setInt(2, i.getItem().getId());
stm_items.setLong(3, i.getCount());
stm_items.setLong(4, i.getPrice());
stm_items.executeUpdate();
stm_items.clearParameters();
}
break;
}
case SELL:
case PACKAGE_SELL:
{
if (!Config.OFFLINE_TRADE_ENABLE)
{
continue;
}
title = pc.getSellList().getTitle();
for (TradeItem i : pc.getSellList().getItems())
{
stm_items.setInt(1, pc.getObjectId());
stm_items.setInt(2, i.getObjectId());
stm_items.setLong(3, i.getCount());
stm_items.setLong(4, i.getPrice());
stm_items.executeUpdate();
stm_items.clearParameters();
}
break;
}
case MANUFACTURE:
{
if (!Config.OFFLINE_CRAFT_ENABLE)
{
continue;
}
title = pc.getStoreName();
for (L2ManufactureItem i : pc.getManufactureItems().values())
{
stm_items.setInt(1, pc.getObjectId());
stm_items.setInt(2, i.getRecipeId());
stm_items.setLong(3, 0);
stm_items.setLong(4, i.getCost());
stm_items.executeUpdate();
stm_items.clearParameters();
}
break;
}
}
stm3.setString(4, title);
stm3.executeUpdate();
stm3.clearParameters();
con.commit(); // flush
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline trader: " + pc.getObjectId() + " " + e, e);
}
}
LOGGER.info(getClass().getSimpleName() + ": Offline traders stored.");
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline traders: " + e, e);
}
}
public void restoreOfflineTraders()
{
LOGGER.info(getClass().getSimpleName() + ": Loading offline traders...");
int nTraders = 0;
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement stm = con.createStatement();
ResultSet rs = stm.executeQuery(LOAD_OFFLINE_STATUS))
{
while (rs.next())
{
final long time = rs.getLong("time");
if (Config.OFFLINE_MAX_DAYS > 0)
{
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time);
cal.add(Calendar.DAY_OF_YEAR, Config.OFFLINE_MAX_DAYS);
if (cal.getTimeInMillis() <= System.currentTimeMillis())
{
continue;
}
}
final PrivateStoreType type = PrivateStoreType.findById(rs.getInt("type"));
if (type == null)
{
LOGGER.warning(getClass().getSimpleName() + ": PrivateStoreType with id " + rs.getInt("type") + " could not be found.");
continue;
}
if (type == PrivateStoreType.NONE)
{
continue;
}
L2PcInstance player = null;
try
{
final L2GameClient client = new L2GameClient(null);
client.setDetached(true);
player = L2PcInstance.load(rs.getInt("charId"));
client.setActiveChar(player);
player.setOnlineStatus(true, false);
client.setAccountName(player.getAccountNamePlayer());
L2World.getInstance().addPlayerToWorld(player);
client.setState(GameClientState.IN_GAME);
player.setClient(client);
player.setOfflineStartTime(time);
player.spawnMe(player.getX(), player.getY(), player.getZ());
LoginServerThread.getInstance().addGameServerLogin(player.getAccountName(), client);
try (PreparedStatement stm_items = con.prepareStatement(LOAD_OFFLINE_ITEMS))
{
stm_items.setInt(1, player.getObjectId());
try (ResultSet items = stm_items.executeQuery())
{
switch (type)
{
case BUY:
{
while (items.next())
{
if (player.getBuyList().addItemByItemId(items.getInt(2), items.getLong(3), items.getLong(4)) == null)
{
continue;
// throw new NullPointerException();
}
}
player.getBuyList().setTitle(rs.getString("title"));
break;
}
case SELL:
case PACKAGE_SELL:
{
while (items.next())
{
if (player.getSellList().addItem(items.getInt(2), items.getLong(3), items.getLong(4)) == null)
{
continue;
// throw new NullPointerException();
}
}
player.getSellList().setTitle(rs.getString("title"));
player.getSellList().setPackaged(type == PrivateStoreType.PACKAGE_SELL);
break;
}
case MANUFACTURE:
{
while (items.next())
{
player.getManufactureItems().put(items.getInt(2), new L2ManufactureItem(items.getInt(2), items.getLong(4)));
}
player.setStoreName(rs.getString("title"));
break;
}
}
}
}
player.sitDown();
if (Config.OFFLINE_SET_NAME_COLOR)
{
player.getAppearance().setNameColor(Config.OFFLINE_NAME_COLOR);
}
player.setPrivateStoreType(type);
player.setOnlineStatus(true, true);
player.restoreEffects();
player.broadcastUserInfo();
nTraders++;
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error loading trader: " + player, e);
if (player != null)
{
player.deleteMe();
}
}
}
LOGGER.info(getClass().getSimpleName() + ": Loaded: " + nTraders + " offline trader(s)");
if (!Config.STORE_OFFLINE_TRADE_IN_REALTIME)
{
try (Statement stm1 = con.createStatement())
{
stm1.execute(CLEAR_OFFLINE_TABLE);
stm1.execute(CLEAR_OFFLINE_TABLE_ITEMS);
}
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while loading offline traders: ", e);
}
}
public static synchronized void onTransaction(L2PcInstance trader, boolean finished, boolean firstCall)
{
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement stm1 = con.prepareStatement(CLEAR_OFFLINE_TABLE_ITEMS_PLAYER);
PreparedStatement stm2 = con.prepareStatement(CLEAR_OFFLINE_TABLE_PLAYER);
PreparedStatement stm3 = con.prepareStatement(SAVE_ITEMS);
PreparedStatement stm4 = con.prepareStatement(SAVE_OFFLINE_STATUS))
{
String title = null;
stm1.setInt(1, trader.getObjectId()); // Char Id
stm1.execute();
stm1.close();
// Trade is done - clear info
if (finished)
{
stm2.setInt(1, trader.getObjectId()); // Char Id
stm2.execute();
stm2.close();
}
else
{
try
{
if ((trader.getClient() == null) || trader.getClient().isDetached())
{
switch (trader.getPrivateStoreType())
{
case BUY:
{
if (firstCall)
{
title = trader.getBuyList().getTitle();
}
for (TradeItem i : trader.getBuyList().getItems())
{
stm3.setInt(1, trader.getObjectId());
stm3.setInt(2, i.getItem().getId());
stm3.setLong(3, i.getCount());
stm3.setLong(4, i.getPrice());
stm3.executeUpdate();
stm3.clearParameters();
}
break;
}
case SELL:
case PACKAGE_SELL:
{
if (firstCall)
{
title = trader.getSellList().getTitle();
}
for (TradeItem i : trader.getSellList().getItems())
{
stm3.setInt(1, trader.getObjectId());
stm3.setInt(2, i.getObjectId());
stm3.setLong(3, i.getCount());
stm3.setLong(4, i.getPrice());
stm3.executeUpdate();
stm3.clearParameters();
}
break;
}
case MANUFACTURE:
{
if (firstCall)
{
title = trader.getStoreName();
}
for (L2ManufactureItem i : trader.getManufactureItems().values())
{
stm3.setInt(1, trader.getObjectId());
stm3.setInt(2, i.getRecipeId());
stm3.setLong(3, 0);
stm3.setLong(4, i.getCost());
stm3.executeUpdate();
stm3.clearParameters();
}
break;
}
}
stm3.close();
if (firstCall)
{
stm4.setInt(1, trader.getObjectId()); // Char Id
stm4.setLong(2, trader.getOfflineStartTime());
stm4.setInt(3, trader.getPrivateStoreType().getId()); // store type
stm4.setString(4, title);
stm4.executeUpdate();
stm4.clearParameters();
stm4.close();
}
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "OfflineTradersTable[storeTradeItems()]: Error while saving offline trader: " + trader.getObjectId() + " " + e, e);
}
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "OfflineTradersTable[storeTradeItems()]: Error while saving offline traders: " + e, e);
}
}
/**
* Gets the single instance of OfflineTradersTable.
* @return single instance of OfflineTradersTable
*/
public static OfflineTradersTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final OfflineTradersTable _instance = new OfflineTradersTable();
}
}

View File

@ -0,0 +1,115 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.data.xml.impl.PetDataTable;
public class PetNameTable
{
private static Logger LOGGER = Logger.getLogger(PetNameTable.class.getName());
public static PetNameTable getInstance()
{
return SingletonHolder._instance;
}
public boolean doesPetNameExist(String name, int petNpcId)
{
boolean result = true;
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("SELECT name FROM pets p, items i WHERE p.item_obj_id = i.object_id AND name=? AND i.item_id IN (?)"))
{
ps.setString(1, name);
final StringBuilder cond = new StringBuilder();
if (!cond.toString().isEmpty())
{
cond.append(", ");
}
cond.append(PetDataTable.getInstance().getPetItemsByNpc(petNpcId));
ps.setString(2, cond.toString());
try (ResultSet rs = ps.executeQuery())
{
result = rs.next();
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing petname:" + e.getMessage(), e);
}
return result;
}
public boolean isValidPetName(String name)
{
boolean result = true;
if (!isAlphaNumeric(name))
{
return result;
}
Pattern pattern;
try
{
pattern = Pattern.compile(Config.PET_NAME_TEMPLATE);
}
catch (PatternSyntaxException e) // case of illegal pattern
{
LOGGER.warning(getClass().getSimpleName() + ": Pet name pattern of config is wrong!");
pattern = Pattern.compile(".*");
}
final Matcher regexp = pattern.matcher(name);
if (!regexp.matches())
{
result = false;
}
return result;
}
private boolean isAlphaNumeric(String text)
{
boolean result = true;
final char[] chars = text.toCharArray();
for (int i = 0; i < chars.length; i++)
{
if (!Character.isLetterOrDigit(chars[i]))
{
result = false;
break;
}
}
return result;
}
private static class SingletonHolder
{
protected static final PetNameTable _instance = new PetNameTable();
}
}

View File

@ -0,0 +1,185 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PetInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2ServitorInstance;
import com.l2jmobius.gameserver.model.skills.Skill;
/**
* @author Nyaran
*/
public class SummonEffectsTable
{
/** Servitors **/
// Map tree
// -> key: charObjectId, value: classIndex Map
// --> key: classIndex, value: servitors Map
// ---> key: servitorSkillId, value: Effects list
private final Map<Integer, Map<Integer, Map<Integer, List<SummonEffect>>>> _servitorEffects = new HashMap<>();
private Map<Integer, List<SummonEffect>> getServitorEffects(L2PcInstance owner)
{
final Map<Integer, Map<Integer, List<SummonEffect>>> servitorMap = _servitorEffects.get(owner.getObjectId());
if (servitorMap == null)
{
return null;
}
return servitorMap.get(owner.getClassIndex());
}
private List<SummonEffect> getServitorEffects(L2PcInstance owner, int referenceSkill)
{
return containsOwner(owner) ? getServitorEffects(owner).get(referenceSkill) : null;
}
private boolean containsOwner(L2PcInstance owner)
{
return _servitorEffects.getOrDefault(owner.getObjectId(), Collections.emptyMap()).containsKey(owner.getClassIndex());
}
private void removeEffects(List<SummonEffect> effects, int skillId)
{
if ((effects != null) && !effects.isEmpty())
{
for (SummonEffect effect : effects)
{
final Skill skill = effect.getSkill();
if ((skill != null) && (skill.getId() == skillId))
{
effects.remove(effect);
}
}
}
}
private void applyEffects(L2Summon summon, List<SummonEffect> summonEffects)
{
if (summonEffects == null)
{
return;
}
for (SummonEffect se : summonEffects)
{
if (se != null)
{
se.getSkill().applyEffects(summon, summon, false, se.getEffectCurTime());
}
}
}
public boolean containsSkill(L2PcInstance owner, int referenceSkill)
{
return containsOwner(owner) && getServitorEffects(owner).containsKey(referenceSkill);
}
public void clearServitorEffects(L2PcInstance owner, int referenceSkill)
{
if (containsOwner(owner))
{
getServitorEffects(owner).getOrDefault(referenceSkill, Collections.emptyList()).clear();
}
}
public void addServitorEffect(L2PcInstance owner, int referenceSkill, Skill skill, int effectCurTime)
{
_servitorEffects.putIfAbsent(owner.getObjectId(), new HashMap<Integer, Map<Integer, List<SummonEffect>>>());
_servitorEffects.get(owner.getObjectId()).putIfAbsent(owner.getClassIndex(), new HashMap<Integer, List<SummonEffect>>());
getServitorEffects(owner).putIfAbsent(referenceSkill, new CopyOnWriteArrayList<SummonEffect>());
getServitorEffects(owner).get(referenceSkill).add(new SummonEffect(skill, effectCurTime));
}
public void removeServitorEffects(L2PcInstance owner, int referenceSkill, int skillId)
{
removeEffects(getServitorEffects(owner, referenceSkill), skillId);
}
public void applyServitorEffects(L2ServitorInstance l2ServitorInstance, L2PcInstance owner, int referenceSkill)
{
applyEffects(l2ServitorInstance, getServitorEffects(owner, referenceSkill));
}
/** Pets **/
private final Map<Integer, List<SummonEffect>> _petEffects = new HashMap<>(); // key: petItemObjectId, value: Effects list
public void addPetEffect(int controlObjectId, Skill skill, int effectCurTime)
{
_petEffects.computeIfAbsent(controlObjectId, k -> new ArrayList<>()).add(new SummonEffect(skill, effectCurTime));
}
public boolean containsPetId(int controlObjectId)
{
return _petEffects.containsKey(controlObjectId);
}
public void applyPetEffects(L2PetInstance l2PetInstance, int controlObjectId)
{
applyEffects(l2PetInstance, _petEffects.get(controlObjectId));
}
public void clearPetEffects(int controlObjectId)
{
_petEffects.getOrDefault(controlObjectId, Collections.emptyList()).clear();
}
public void removePetEffects(int controlObjectId, int skillId)
{
removeEffects(_petEffects.get(controlObjectId), skillId);
}
private class SummonEffect
{
Skill _skill;
int _effectCurTime;
public SummonEffect(Skill skill, int effectCurTime)
{
_skill = skill;
_effectCurTime = effectCurTime;
}
public Skill getSkill()
{
return _skill;
}
public int getEffectCurTime()
{
return _effectCurTime;
}
}
public static SummonEffectsTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final SummonEffectsTable _instance = new SummonEffectsTable();
}
}

View File

@ -0,0 +1,179 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.datatables.SkillData;
import com.l2jmobius.gameserver.model.actor.L2Summon;
public class SummonSkillsTable
{
private static Logger LOGGER = Logger.getLogger(SummonSkillsTable.class.getName());
private final Map<Integer, Map<Integer, L2PetSkillLearn>> _skillTrees = new HashMap<>();
protected SummonSkillsTable()
{
load();
}
public void load()
{
_skillTrees.clear();
int count = 0;
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT templateId, minLvl, skillId, skillLvl FROM pets_skills"))
{
while (rs.next())
{
final int npcId = rs.getInt("templateId");
Map<Integer, L2PetSkillLearn> skillTree = _skillTrees.get(npcId);
if (skillTree == null)
{
skillTree = new HashMap<>();
_skillTrees.put(npcId, skillTree);
}
final int id = rs.getInt("skillId");
final int lvl = rs.getInt("skillLvl");
skillTree.put(SkillData.getSkillHashCode(id, lvl + 1), new L2PetSkillLearn(id, lvl, rs.getInt("minLvl")));
count++;
}
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error while loading pet skill tree:", e);
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + count + " skills.");
}
public int getAvailableLevel(L2Summon cha, int skillId)
{
int lvl = 0;
if (!_skillTrees.containsKey(cha.getId()))
{
LOGGER.warning(getClass().getSimpleName() + ": Pet id " + cha.getId() + " does not have any skills assigned.");
return lvl;
}
final Collection<L2PetSkillLearn> skills = _skillTrees.get(cha.getId()).values();
for (L2PetSkillLearn temp : skills)
{
if (temp.getId() != skillId)
{
continue;
}
if (temp.getLevel() == 0)
{
if (cha.getLevel() < 70)
{
lvl = cha.getLevel() / 10;
if (lvl <= 0)
{
lvl = 1;
}
}
else
{
lvl = 7 + ((cha.getLevel() - 70) / 5);
}
// formula usable for skill that have 10 or more skill levels
final int maxLvl = SkillData.getInstance().getMaxLevel(temp.getId());
if (lvl > maxLvl)
{
lvl = maxLvl;
}
break;
}
if ((temp.getMinLevel() <= cha.getLevel()) && (temp.getLevel() > lvl))
{
lvl = temp.getLevel();
}
}
return lvl;
}
public List<Integer> getAvailableSkills(L2Summon cha)
{
final List<Integer> skillIds = new ArrayList<>();
if (!_skillTrees.containsKey(cha.getId()))
{
LOGGER.warning(getClass().getSimpleName() + ": Pet id " + cha.getId() + " does not have any skills assigned.");
return skillIds;
}
final Collection<L2PetSkillLearn> skills = _skillTrees.get(cha.getId()).values();
for (L2PetSkillLearn temp : skills)
{
if (skillIds.contains(temp.getId()))
{
continue;
}
skillIds.add(temp.getId());
}
return skillIds;
}
public static final class L2PetSkillLearn
{
private final int _id;
private final int _level;
private final int _minLevel;
public L2PetSkillLearn(int id, int lvl, int minLvl)
{
_id = id;
_level = lvl;
_minLevel = minLvl;
}
public int getId()
{
return _id;
}
public int getLevel()
{
return _level;
}
public int getMinLevel()
{
return _minLevel;
}
}
public static SummonSkillsTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final SummonSkillsTable _instance = new SummonSkillsTable();
}
}

View File

@ -0,0 +1,125 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.model.L2TeleportLocation;
public class TeleportLocationTable
{
private static Logger LOGGER = Logger.getLogger(TeleportLocationTable.class.getName());
private final Map<Integer, L2TeleportLocation> _teleports = new HashMap<>();
protected TeleportLocationTable()
{
reloadAll();
}
public void reloadAll()
{
_teleports.clear();
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT id, loc_x, loc_y, loc_z, price, fornoble, itemId FROM teleport"))
{
L2TeleportLocation teleport;
while (rs.next())
{
teleport = new L2TeleportLocation();
teleport.setTeleId(rs.getInt("id"));
teleport.setLocX(rs.getInt("loc_x"));
teleport.setLocY(rs.getInt("loc_y"));
teleport.setLocZ(rs.getInt("loc_z"));
teleport.setPrice(rs.getInt("price"));
teleport.setIsForNoble(rs.getInt("fornoble") == 1);
teleport.setItemId(rs.getInt("itemId"));
_teleports.put(teleport.getTeleId(), teleport);
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _teleports.size() + " Teleport Location Templates.");
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error loading Teleport Table.", e);
}
if (!Config.CUSTOM_TELEPORT_TABLE)
{
return;
}
int _cTeleCount = _teleports.size();
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT id, loc_x, loc_y, loc_z, price, fornoble, itemId FROM custom_teleport"))
{
L2TeleportLocation teleport;
while (rs.next())
{
teleport = new L2TeleportLocation();
teleport.setTeleId(rs.getInt("id"));
teleport.setLocX(rs.getInt("loc_x"));
teleport.setLocY(rs.getInt("loc_y"));
teleport.setLocZ(rs.getInt("loc_z"));
teleport.setPrice(rs.getInt("price"));
teleport.setIsForNoble(rs.getInt("fornoble") == 1);
teleport.setItemId(rs.getInt("itemId"));
_teleports.put(teleport.getTeleId(), teleport);
}
_cTeleCount = _teleports.size() - _cTeleCount;
if (_cTeleCount > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _cTeleCount + " Custom Teleport Location Templates.");
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while creating custom teleport table " + e.getMessage(), e);
}
}
/**
* @param id
* @return
*/
public L2TeleportLocation getTemplate(int id)
{
return _teleports.get(id);
}
public static TeleportLocationTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final TeleportLocationTable _instance = new TeleportLocationTable();
}
}

View File

@ -0,0 +1,111 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.sql.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.model.L2Territory;
import com.l2jmobius.gameserver.model.Location;
/**
* @author Balancer, Mr
*/
public class TerritoryTable
{
private static final Logger LOGGER = Logger.getLogger(TerritoryTable.class.getName());
private static final Map<Integer, L2Territory> _territory = new HashMap<>();
/**
* Instantiates a new territory.
*/
protected TerritoryTable()
{
load();
}
/**
* Gets the random point.
* @param terr the territory Id?
* @return the random point
*/
public Location getRandomPoint(int terr)
{
return _territory.get(terr).getRandomPoint();
}
/**
* Gets the proc max.
* @param terr the territory Id?
* @return the proc max
*/
public int getProcMax(int terr)
{
return _territory.get(terr).getProcMax();
}
/**
* Load the data from database.
*/
public void load()
{
_territory.clear();
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement stmt = con.createStatement();
ResultSet rset = stmt.executeQuery("SELECT * FROM locations WHERE loc_id>0"))
{
while (rset.next())
{
final int terrId = rset.getInt("loc_id");
L2Territory terr = _territory.get(terrId);
if (terr == null)
{
terr = new L2Territory(terrId);
_territory.put(terrId, terr);
}
terr.add(rset.getInt("loc_x"), rset.getInt("loc_y"), rset.getInt("loc_zmin"), rset.getInt("loc_zmax"), rset.getInt("proc"));
}
LOGGER.info("TerritoryTable: Loaded " + _territory.size() + " territories from database.");
}
catch (SQLException e)
{
LOGGER.log(Level.SEVERE, "TerritoryTable: Failed to load territories from database!", e);
}
}
/**
* Gets the single instance of Territory.
* @return single instance of Territory
*/
public static TerritoryTable getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final TerritoryTable _instance = new TerritoryTable();
}
}

View File

@ -0,0 +1,352 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.L2AccessLevel;
import com.l2jmobius.gameserver.model.L2AdminCommandAccessRight;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.L2GameServerPacket;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* Loads administrator access levels and commands.
* @author UnAfraid
*/
public final class AdminData implements IXmlReader
{
private final Map<Integer, L2AccessLevel> _accessLevels = new HashMap<>();
private final Map<String, L2AdminCommandAccessRight> _adminCommandAccessRights = new HashMap<>();
private final Map<L2PcInstance, Boolean> _gmList = new ConcurrentHashMap<>();
private int _highestLevel = 0;
protected AdminData()
{
load();
}
@Override
public synchronized void load()
{
_accessLevels.clear();
_adminCommandAccessRights.clear();
parseDatapackFile("config/AccessLevels.xml");
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _accessLevels.size() + " Access Levels.");
parseDatapackFile("config/AdminCommands.xml");
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _adminCommandAccessRights.size() + " Access Commands.");
}
@Override
public void parseDocument(Document doc)
{
NamedNodeMap attrs;
Node attr;
StatsSet set;
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 ("access".equalsIgnoreCase(d.getNodeName()))
{
set = new StatsSet();
attrs = d.getAttributes();
for (int i = 0; i < attrs.getLength(); i++)
{
attr = attrs.item(i);
set.set(attr.getNodeName(), attr.getNodeValue());
}
final L2AccessLevel level = new L2AccessLevel(set);
if (level.getLevel() > _highestLevel)
{
_highestLevel = level.getLevel();
}
_accessLevels.put(level.getLevel(), level);
}
else if ("admin".equalsIgnoreCase(d.getNodeName()))
{
set = new StatsSet();
attrs = d.getAttributes();
for (int i = 0; i < attrs.getLength(); i++)
{
attr = attrs.item(i);
set.set(attr.getNodeName(), attr.getNodeValue());
}
final L2AdminCommandAccessRight command = new L2AdminCommandAccessRight(set);
_adminCommandAccessRights.put(command.getAdminCommand(), command);
}
}
}
}
}
/**
* Returns the access level by characterAccessLevel.
* @param accessLevelNum as int
* @return the access level instance by char access level
*/
public L2AccessLevel getAccessLevel(int accessLevelNum)
{
if (accessLevelNum < 0)
{
return _accessLevels.get(-1);
}
if (!_accessLevels.containsKey(accessLevelNum))
{
_accessLevels.put(accessLevelNum, new L2AccessLevel());
}
return _accessLevels.get(accessLevelNum);
}
/**
* Gets the master access level.
* @return the master access level
*/
public L2AccessLevel getMasterAccessLevel()
{
return _accessLevels.get(_highestLevel);
}
/**
* Checks for access level.
* @param id the id
* @return {@code true}, if successful, {@code false} otherwise
*/
public boolean hasAccessLevel(int id)
{
return _accessLevels.containsKey(id);
}
/**
* Checks for access.
* @param adminCommand the admin command
* @param accessLevel the access level
* @return {@code true}, if successful, {@code false} otherwise
*/
public boolean hasAccess(String adminCommand, L2AccessLevel accessLevel)
{
L2AdminCommandAccessRight acar = _adminCommandAccessRights.get(adminCommand);
if (acar == null)
{
// Trying to avoid the spam for next time when the gm would try to use the same command
if ((accessLevel.getLevel() > 0) && (accessLevel.getLevel() == _highestLevel))
{
acar = new L2AdminCommandAccessRight(adminCommand, true, accessLevel.getLevel());
_adminCommandAccessRights.put(adminCommand, acar);
LOGGER.info(getClass().getSimpleName() + ": No rights defined for admin command " + adminCommand + " auto setting accesslevel: " + accessLevel.getLevel() + " !");
}
else
{
LOGGER.info(getClass().getSimpleName() + ": No rights defined for admin command " + adminCommand + " !");
return false;
}
}
return acar.hasAccess(accessLevel);
}
/**
* Require confirm.
* @param command the command
* @return {@code true}, if the command require confirmation, {@code false} otherwise
*/
public boolean requireConfirm(String command)
{
final L2AdminCommandAccessRight acar = _adminCommandAccessRights.get(command);
if (acar == null)
{
LOGGER.info(getClass().getSimpleName() + ": No rights defined for admin command " + command + ".");
return false;
}
return acar.getRequireConfirm();
}
/**
* Gets the all GMs.
* @param includeHidden the include hidden
* @return the all GMs
*/
public List<L2PcInstance> getAllGms(boolean includeHidden)
{
final List<L2PcInstance> tmpGmList = new ArrayList<>();
for (Entry<L2PcInstance, Boolean> entry : _gmList.entrySet())
{
if (includeHidden || !entry.getValue())
{
tmpGmList.add(entry.getKey());
}
}
return tmpGmList;
}
/**
* Gets the all GM names.
* @param includeHidden the include hidden
* @return the all GM names
*/
public List<String> getAllGmNames(boolean includeHidden)
{
final List<String> tmpGmList = new ArrayList<>();
for (Entry<L2PcInstance, Boolean> entry : _gmList.entrySet())
{
if (!entry.getValue())
{
tmpGmList.add(entry.getKey().getName());
}
else if (includeHidden)
{
tmpGmList.add(entry.getKey().getName() + " (invis)");
}
}
return tmpGmList;
}
/**
* Add a L2PcInstance player to the Set _gmList.
* @param player the player
* @param hidden the hidden
*/
public void addGm(L2PcInstance player, boolean hidden)
{
_gmList.put(player, hidden);
}
/**
* Delete a GM.
* @param player the player
*/
public void deleteGm(L2PcInstance player)
{
_gmList.remove(player);
}
/**
* GM will be displayed on clients GM list.
* @param player the player
*/
public void showGm(L2PcInstance player)
{
if (_gmList.containsKey(player))
{
_gmList.put(player, false);
}
}
/**
* GM will no longer be displayed on clients GM list.
* @param player the player
*/
public void hideGm(L2PcInstance player)
{
if (_gmList.containsKey(player))
{
_gmList.put(player, true);
}
}
/**
* Checks if is GM online.
* @param includeHidden the include hidden
* @return true, if is GM online
*/
public boolean isGmOnline(boolean includeHidden)
{
for (Entry<L2PcInstance, Boolean> entry : _gmList.entrySet())
{
if (includeHidden || !entry.getValue())
{
return true;
}
}
return false;
}
/**
* Send list to player.
* @param player the player
*/
public void sendListToPlayer(L2PcInstance player)
{
if (isGmOnline(player.isGM()))
{
player.sendPacket(SystemMessageId.GM_LIST);
for (String name : getAllGmNames(player.isGM()))
{
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.GM_C1);
sm.addString(name);
player.sendPacket(sm);
}
}
else
{
player.sendPacket(SystemMessageId.THERE_ARE_NO_GMS_CURRENTLY_VISIBLE_IN_THE_PUBLIC_LIST_AS_THEY_MAY_BE_PERFORMING_OTHER_FUNCTIONS_AT_THE_MOMENT);
}
}
/**
* Broadcast to GMs.
* @param packet the packet
*/
public void broadcastToGMs(L2GameServerPacket packet)
{
for (L2PcInstance gm : getAllGms(true))
{
gm.sendPacket(packet);
}
}
/**
* Broadcast message to GMs.
* @param message the message
*/
public void broadcastMessageToGMs(String message)
{
for (L2PcInstance gm : getAllGms(true))
{
gm.sendMessage(message);
}
}
/**
* Gets the single instance of AdminTable.
* @return AccessLevels: the one and only instance of this class<br>
*/
public static AdminData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final AdminData _instance = new AdminData();
}
}

View File

@ -0,0 +1,194 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.L2ArmorSet;
import com.l2jmobius.gameserver.model.holders.SkillHolder;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* Loads armor set bonuses.
* @author godson, Luno, UnAfraid
*/
public final class ArmorSetsData implements IXmlReader
{
private final Map<Integer, L2ArmorSet> _armorSets = new HashMap<>();
/**
* Instantiates a new armor sets data.
*/
protected ArmorSetsData()
{
load();
}
@Override
public void load()
{
_armorSets.clear();
parseDatapackDirectory("data/stats/armorsets", false);
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorSets.size() + " Armor sets.");
}
@Override
public void parseDocument(Document doc)
{
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 ("set".equalsIgnoreCase(d.getNodeName()))
{
final L2ArmorSet set = new L2ArmorSet();
for (Node a = d.getFirstChild(); a != null; a = a.getNextSibling())
{
final NamedNodeMap attrs = a.getAttributes();
switch (a.getNodeName())
{
case "chest":
{
set.addChest(parseInteger(attrs, "id"));
break;
}
case "feet":
{
set.addFeet(parseInteger(attrs, "id"));
break;
}
case "gloves":
{
set.addGloves(parseInteger(attrs, "id"));
break;
}
case "head":
{
set.addHead(parseInteger(attrs, "id"));
break;
}
case "legs":
{
set.addLegs(parseInteger(attrs, "id"));
break;
}
case "shield":
{
set.addShield(parseInteger(attrs, "id"));
break;
}
case "skill":
{
final int skillId = parseInteger(attrs, "id");
final int skillLevel = parseInteger(attrs, "level");
set.addSkill(new SkillHolder(skillId, skillLevel));
break;
}
case "shield_skill":
{
final int skillId = parseInteger(attrs, "id");
final int skillLevel = parseInteger(attrs, "level");
set.addShieldSkill(new SkillHolder(skillId, skillLevel));
break;
}
case "enchant6skill":
{
final int skillId = parseInteger(attrs, "id");
final int skillLevel = parseInteger(attrs, "level");
set.addEnchant6Skill(new SkillHolder(skillId, skillLevel));
break;
}
case "con":
{
set.addCon(parseInteger(attrs, "val"));
break;
}
case "dex":
{
set.addDex(parseInteger(attrs, "val"));
break;
}
case "str":
{
set.addStr(parseInteger(attrs, "val"));
break;
}
case "men":
{
set.addMen(parseInteger(attrs, "val"));
break;
}
case "wit":
{
set.addWit(parseInteger(attrs, "val"));
break;
}
case "int":
{
set.addInt(parseInteger(attrs, "val"));
break;
}
}
}
_armorSets.put(set.getChestId(), set);
}
}
}
}
}
/**
* Checks if is armor set.
* @param chestId the chest Id to verify.
* @return {@code true} if the chest Id belongs to a registered armor set, {@code false} otherwise.
*/
public boolean isArmorSet(int chestId)
{
return _armorSets.containsKey(chestId);
}
/**
* Gets the sets the.
* @param chestId the chest Id identifying the armor set.
* @return the armor set associated to the give chest Id.
*/
public L2ArmorSet getSet(int chestId)
{
return _armorSets.get(chestId);
}
/**
* Gets the single instance of ArmorSetsData.
* @return single instance of ArmorSetsData
*/
public static ArmorSetsData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final ArmorSetsData _instance = new ArmorSetsData();
}
}

View File

@ -0,0 +1,191 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.io.FileFilter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.datatables.ItemTable;
import com.l2jmobius.gameserver.model.buylist.L2BuyList;
import com.l2jmobius.gameserver.model.buylist.Product;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.util.data.xml.IXmlReader;
import com.l2jmobius.util.file.filter.NumericNameFilter;
/**
* Loads buy lists for NPCs.
* @author NosBit
*/
public final class BuyListData implements IXmlReader
{
private final Map<Integer, L2BuyList> _buyLists = new HashMap<>();
private static final FileFilter NUMERIC_FILTER = new NumericNameFilter();
protected BuyListData()
{
load();
}
@Override
public synchronized void load()
{
_buyLists.clear();
parseDatapackDirectory("data/buylists", false);
if (Config.CUSTOM_BUYLIST_LOAD)
{
parseDatapackDirectory("data/buylists/custom", false);
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _buyLists.size() + " BuyLists.");
try (Connection con = DatabaseFactory.getInstance().getConnection();
Statement statement = con.createStatement();
ResultSet rs = statement.executeQuery("SELECT * FROM `buylists`"))
{
while (rs.next())
{
final int buyListId = rs.getInt("buylist_id");
final int itemId = rs.getInt("item_id");
final long count = rs.getLong("count");
final long nextRestockTime = rs.getLong("next_restock_time");
final L2BuyList buyList = getBuyList(buyListId);
if (buyList == null)
{
LOGGER.warning("BuyList found in database but not loaded from xml! BuyListId: " + buyListId);
continue;
}
final Product product = buyList.getProductByItemId(itemId);
if (product == null)
{
LOGGER.warning("ItemId found in database but not loaded from xml! BuyListId: " + buyListId + " ItemId: " + itemId);
continue;
}
if (count < product.getMaxCount())
{
product.setCount(count);
product.restartRestockTask(nextRestockTime);
}
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "Failed to load buyList data from database.", e);
}
}
@Override
public void parseDocument(Document doc, File f)
{
try
{
final int buyListId = Integer.parseInt(f.getName().replaceAll(".xml", ""));
for (Node node = doc.getFirstChild(); node != null; node = node.getNextSibling())
{
if ("list".equalsIgnoreCase(node.getNodeName()))
{
final L2BuyList buyList = new L2BuyList(buyListId);
for (Node list_node = node.getFirstChild(); list_node != null; list_node = list_node.getNextSibling())
{
if ("item".equalsIgnoreCase(list_node.getNodeName()))
{
int itemId = -1;
long price = -1;
long restockDelay = -1;
long count = -1;
final NamedNodeMap attrs = list_node.getAttributes();
Node attr = attrs.getNamedItem("id");
itemId = Integer.parseInt(attr.getNodeValue());
attr = attrs.getNamedItem("price");
if (attr != null)
{
price = Long.parseLong(attr.getNodeValue());
}
attr = attrs.getNamedItem("restock_delay");
if (attr != null)
{
restockDelay = Long.parseLong(attr.getNodeValue());
}
attr = attrs.getNamedItem("count");
if (attr != null)
{
count = Long.parseLong(attr.getNodeValue());
}
final L2Item item = ItemTable.getInstance().getTemplate(itemId);
if (item != null)
{
buyList.addProduct(new Product(buyList.getListId(), item, price, restockDelay, count));
}
else
{
LOGGER.warning("Item not found. BuyList:" + buyList.getListId() + " ItemID:" + itemId + " File:" + f.getName());
}
}
else if ("npcs".equalsIgnoreCase(list_node.getNodeName()))
{
for (Node npcs_node = list_node.getFirstChild(); npcs_node != null; npcs_node = npcs_node.getNextSibling())
{
if ("npc".equalsIgnoreCase(npcs_node.getNodeName()))
{
buyList.addAllowedNpc(Integer.parseInt(npcs_node.getTextContent()));
}
}
}
}
_buyLists.put(buyList.getListId(), buyList);
}
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "Failed to load buyList data from xml File:" + f.getName(), e);
}
}
@Override
public FileFilter getCurrentFileFilter()
{
return NUMERIC_FILTER;
}
public L2BuyList getBuyList(int listId)
{
return _buyLists.get(listId);
}
public static BuyListData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final BuyListData _instance = new BuyListData();
}
}

View File

@ -0,0 +1,126 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.enums.CategoryType;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* Loads the category data with Class or NPC IDs.
* @author NosBit, xban1x
*/
public final class CategoryData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(CategoryData.class.getName());
private final Map<CategoryType, Set<Integer>> _categories = new HashMap<>();
protected CategoryData()
{
load();
}
@Override
public void load()
{
_categories.clear();
parseDatapackFile("data/CategoryData.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _categories.size() + " Categories.");
}
@Override
public void parseDocument(Document doc)
{
for (Node node = doc.getFirstChild(); node != null; node = node.getNextSibling())
{
if ("list".equalsIgnoreCase(node.getNodeName()))
{
for (Node list_node = node.getFirstChild(); list_node != null; list_node = list_node.getNextSibling())
{
if ("category".equalsIgnoreCase(list_node.getNodeName()))
{
final NamedNodeMap attrs = list_node.getAttributes();
final CategoryType categoryType = CategoryType.findByName(attrs.getNamedItem("name").getNodeValue());
if (categoryType == null)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Can't find category by name :" + attrs.getNamedItem("name").getNodeValue());
continue;
}
final Set<Integer> ids = new HashSet<>();
for (Node category_node = list_node.getFirstChild(); category_node != null; category_node = category_node.getNextSibling())
{
if ("id".equalsIgnoreCase(category_node.getNodeName()))
{
ids.add(Integer.parseInt(category_node.getTextContent()));
}
}
_categories.put(categoryType, ids);
}
}
}
}
}
/**
* Checks if ID is in category.
* @param type The category type
* @param id The id to be checked
* @return {@code true} if id is in category, {@code false} if id is not in category or category was not found
*/
public boolean isInCategory(CategoryType type, int id)
{
final Set<Integer> category = getCategoryByType(type);
if (category == null)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Can't find category type :" + type);
return false;
}
return category.contains(id);
}
/**
* Gets the category by category type.
* @param type The category type
* @return A {@code Set} containing all the IDs in category if category is found, {@code null} if category was not found
*/
public Set<Integer> getCategoryByType(CategoryType type)
{
return _categories.get(type);
}
public static CategoryData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final CategoryData _instance = new CategoryData();
}
}

View File

@ -0,0 +1,122 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.base.ClassId;
import com.l2jmobius.gameserver.model.base.ClassInfo;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* Loads the the list of classes and it's info.
* @author Zoey76
*/
public final class ClassListData implements IXmlReader
{
private final Map<ClassId, ClassInfo> _classData = new HashMap<>();
/**
* Instantiates a new class list data.
*/
protected ClassListData()
{
load();
}
@Override
public void load()
{
_classData.clear();
parseDatapackFile("data/stats/chars/classList.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _classData.size() + " Class data.");
}
@Override
public void parseDocument(Document doc)
{
for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
{
if ("list".equals(n.getNodeName()))
{
for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
{
final NamedNodeMap attrs = d.getAttributes();
if ("class".equals(d.getNodeName()))
{
Node attr = attrs.getNamedItem("classId");
final ClassId classId = ClassId.getClassId(parseInteger(attr));
attr = attrs.getNamedItem("name");
final String className = attr.getNodeValue();
attr = attrs.getNamedItem("parentClassId");
final ClassId parentClassId = (attr != null) ? ClassId.getClassId(parseInteger(attr)) : null;
_classData.put(classId, new ClassInfo(classId, className, parentClassId));
}
}
}
}
}
/**
* Gets the class list.
* @return the complete class list
*/
public Map<ClassId, ClassInfo> getClassList()
{
return _classData;
}
/**
* Gets the class info.
* @param classId the class ID
* @return the class info related to the given {@code classId}
*/
public ClassInfo getClass(ClassId classId)
{
return _classData.get(classId);
}
/**
* Gets the class info.
* @param classId the class Id as integer
* @return the class info related to the given {@code classId}
*/
public ClassInfo getClass(int classId)
{
final ClassId id = ClassId.getClassId(classId);
return (id != null) ? _classData.get(id) : null;
}
/**
* Gets the single instance of ClassListData.
* @return single instance of ClassListData
*/
public static ClassListData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final ClassListData _instance = new ClassListData();
}
}

View File

@ -0,0 +1,256 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.instancemanager.InstanceManager;
import com.l2jmobius.gameserver.instancemanager.MapRegionManager;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.actor.templates.L2DoorTemplate;
import com.l2jmobius.gameserver.pathfinding.AbstractNodeLoc;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* Loads doors.
* @author JIV, GodKratos, UnAfraid
*/
public class DoorData implements IXmlReader
{
private static final Map<String, Set<Integer>> _groups = new HashMap<>();
private final Map<Integer, L2DoorInstance> _doors = new HashMap<>();
private final Map<Integer, StatsSet> _templates = new HashMap<>();
private final Map<Integer, List<L2DoorInstance>> _regions = new HashMap<>();
protected DoorData()
{
load();
}
@Override
public void load()
{
_doors.clear();
_groups.clear();
_regions.clear();
parseDatapackFile("data/Doors.xml");
}
@Override
public void parseDocument(Document doc)
{
for (Node a = doc.getFirstChild(); a != null; a = a.getNextSibling())
{
if ("list".equalsIgnoreCase(a.getNodeName()))
{
for (Node b = a.getFirstChild(); b != null; b = b.getNextSibling())
{
if ("door".equalsIgnoreCase(b.getNodeName()))
{
final NamedNodeMap attrs = b.getAttributes();
final StatsSet set = new StatsSet();
set.set("baseHpMax", 1); // Avoid doors without HP value created dead due to default value 0 in L2CharTemplate
for (int i = 0; i < attrs.getLength(); i++)
{
final Node att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
makeDoor(set);
_templates.put(set.getInt("id"), set);
}
}
}
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _doors.size() + " Door Templates for " + _regions.size() + " regions.");
}
public void insertCollisionData(StatsSet set)
{
int posX, posY, nodeX, nodeY, height;
height = set.getInt("height");
String[] pos = set.getString("node1").split(",");
nodeX = Integer.parseInt(pos[0]);
nodeY = Integer.parseInt(pos[1]);
pos = set.getString("node2").split(",");
posX = Integer.parseInt(pos[0]);
posY = Integer.parseInt(pos[1]);
int collisionRadius = Math.min(Math.abs(nodeX - posX), Math.abs(nodeY - posY)); // (max) radius for movement checks
if (collisionRadius < 20)
{
collisionRadius = 20;
}
set.set("collisionRadius", collisionRadius);
set.set("collisionHeight", height);
}
/**
* @param set
*/
private void makeDoor(StatsSet set)
{
insertCollisionData(set);
final L2DoorTemplate template = new L2DoorTemplate(set);
final L2DoorInstance door = new L2DoorInstance(template);
door.setCurrentHp(door.getMaxHp());
door.spawnMe(template.getX(), template.getY(), template.getZ());
putDoor(door, MapRegionManager.getInstance().getMapRegionLocId(door));
}
public StatsSet getDoorTemplate(int doorId)
{
return _templates.get(doorId);
}
public L2DoorInstance getDoor(int doorId)
{
return _doors.get(doorId);
}
public void putDoor(L2DoorInstance door, int region)
{
_doors.put(door.getId(), door);
if (!_regions.containsKey(region))
{
_regions.put(region, new ArrayList<L2DoorInstance>());
}
_regions.get(region).add(door);
}
public static void addDoorGroup(String groupName, int doorId)
{
Set<Integer> set = _groups.get(groupName);
if (set == null)
{
set = new HashSet<>();
_groups.put(groupName, set);
}
set.add(doorId);
}
public static Set<Integer> getDoorsByGroup(String groupName)
{
return _groups.get(groupName);
}
public Collection<L2DoorInstance> getDoors()
{
return _doors.values();
}
public boolean checkIfDoorsBetween(AbstractNodeLoc start, AbstractNodeLoc end, int instanceId)
{
return checkIfDoorsBetween(start.getX(), start.getY(), start.getZ(), end.getX(), end.getY(), end.getZ(), instanceId);
}
public boolean checkIfDoorsBetween(int x, int y, int z, int tx, int ty, int tz, int instanceId)
{
return checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, false);
}
/**
* GodKratos: TODO: remove GeoData checks from door table and convert door nodes to Geo zones
* @param x
* @param y
* @param z
* @param tx
* @param ty
* @param tz
* @param instanceId
* @param doubleFaceCheck
* @return {@code boolean}
*/
public boolean checkIfDoorsBetween(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean doubleFaceCheck)
{
Collection<L2DoorInstance> allDoors;
if ((instanceId > 0) && (InstanceManager.getInstance().getInstance(instanceId) != null))
{
allDoors = InstanceManager.getInstance().getInstance(instanceId).getDoors();
}
else
{
allDoors = _regions.get(MapRegionManager.getInstance().getMapRegionLocId(x, y));
}
if (allDoors == null)
{
return false;
}
for (L2DoorInstance doorInst : allDoors)
{
// check dead and open
if (doorInst.isDead() || doorInst.getOpen() || !doorInst.checkCollision() || (doorInst.getX(0) == 0))
{
continue;
}
boolean intersectFace = false;
for (int i = 0; i < 4; i++)
{
final int j = (i + 1) < 4 ? i + 1 : 0;
// lower part of the multiplier fraction, if it is 0 we avoid an error and also know that the lines are parallel
final int denominator = ((ty - y) * (doorInst.getX(i) - doorInst.getX(j))) - ((tx - x) * (doorInst.getY(i) - doorInst.getY(j)));
if (denominator == 0)
{
continue;
}
// multipliers to the equations of the lines. If they are lower than 0 or bigger than 1, we know that segments don't intersect
final float multiplier1 = (float) (((doorInst.getX(j) - doorInst.getX(i)) * (y - doorInst.getY(i))) - ((doorInst.getY(j) - doorInst.getY(i)) * (x - doorInst.getX(i)))) / denominator;
final float multiplier2 = (float) (((tx - x) * (y - doorInst.getY(i))) - ((ty - y) * (x - doorInst.getX(i)))) / denominator;
if ((multiplier1 >= 0) && (multiplier1 <= 1) && (multiplier2 >= 0) && (multiplier2 <= 1))
{
final int intersectZ = Math.round(z + (multiplier1 * (tz - z)));
// now checking if the resulting point is between door's min and max z
if ((intersectZ > doorInst.getZMin()) && (intersectZ < doorInst.getZMax()))
{
if (!doubleFaceCheck || intersectFace)
{
return true;
}
intersectFace = true;
}
}
}
}
return false;
}
public static DoorData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final DoorData _instance = new DoorData();
}
}

View File

@ -0,0 +1,165 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.items.enchant.EnchantScroll;
import com.l2jmobius.gameserver.model.items.enchant.EnchantSupportItem;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* Loads item enchant data.
* @author UnAfraid
*/
public class EnchantItemData implements IXmlReader
{
private final Map<Integer, EnchantScroll> _scrolls = new HashMap<>();
private final Map<Integer, EnchantSupportItem> _supports = new HashMap<>();
/**
* Instantiates a new enchant item data.
*/
public EnchantItemData()
{
load();
}
@Override
public synchronized void load()
{
_scrolls.clear();
_supports.clear();
parseDatapackFile("data/EnchantItemData.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _scrolls.size() + " Enchant Scrolls.");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _supports.size() + " Support Items.");
}
@Override
public void parseDocument(Document doc)
{
StatsSet set;
Node att;
NamedNodeMap attrs;
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 ("enchant".equalsIgnoreCase(d.getNodeName()))
{
attrs = d.getAttributes();
set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
try
{
final EnchantScroll item = new EnchantScroll(set);
for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if ("item".equalsIgnoreCase(cd.getNodeName()))
{
item.addItem(parseInteger(cd.getAttributes(), "id"));
}
}
_scrolls.put(item.getId(), item);
}
catch (NullPointerException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Unexistent enchant scroll: " + set.getString("id") + " defined in enchant data!");
}
catch (IllegalAccessError e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Wrong enchant scroll item type: " + set.getString("id") + " defined in enchant data!");
}
}
else if ("support".equalsIgnoreCase(d.getNodeName()))
{
attrs = d.getAttributes();
set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
try
{
final EnchantSupportItem item = new EnchantSupportItem(set);
_supports.put(item.getId(), item);
}
catch (NullPointerException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Unexistent enchant support item: " + set.getString("id") + " defined in enchant data!");
}
catch (IllegalAccessError e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Wrong enchant support item type: " + set.getString("id") + " defined in enchant data!");
}
}
}
}
}
}
/**
* Gets the enchant scroll.
* @param scroll the scroll
* @return enchant template for scroll
*/
public final EnchantScroll getEnchantScroll(L2ItemInstance scroll)
{
return _scrolls.get(scroll.getId());
}
/**
* Gets the support item.
* @param item the item
* @return enchant template for support item
*/
public final EnchantSupportItem getSupportItem(L2ItemInstance item)
{
return _supports.get(item.getId());
}
/**
* Gets the single instance of EnchantItemData.
* @return single instance of EnchantItemData
*/
public static final EnchantItemData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final EnchantItemData _instance = new EnchantItemData();
}
}

View File

@ -0,0 +1,166 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.datatables.ItemTable;
import com.l2jmobius.gameserver.model.holders.RangeChanceHolder;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.enchant.EnchantItemGroup;
import com.l2jmobius.gameserver.model.items.enchant.EnchantRateItem;
import com.l2jmobius.gameserver.model.items.enchant.EnchantScrollGroup;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* @author UnAfraid
*/
public final class EnchantItemGroupsData implements IXmlReader
{
private final Map<String, EnchantItemGroup> _itemGroups = new HashMap<>();
private final Map<Integer, EnchantScrollGroup> _scrollGroups = new HashMap<>();
protected EnchantItemGroupsData()
{
load();
}
@Override
public synchronized void load()
{
_itemGroups.clear();
_scrollGroups.clear();
parseDatapackFile("data/EnchantItemGroups.xml");
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _itemGroups.size() + " item group templates.");
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _scrollGroups.size() + " scroll group templates.");
}
@Override
public void parseDocument(Document doc)
{
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 ("enchantRateGroup".equalsIgnoreCase(d.getNodeName()))
{
final String name = parseString(d.getAttributes(), "name");
final EnchantItemGroup group = new EnchantItemGroup(name);
for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if ("current".equalsIgnoreCase(cd.getNodeName()))
{
final String range = parseString(cd.getAttributes(), "enchant");
final double chance = parseDouble(cd.getAttributes(), "chance");
int min = -1;
int max = 0;
if (range.contains("-"))
{
final String[] split = range.split("-");
if ((split.length == 2) && Util.isDigit(split[0]) && Util.isDigit(split[1]))
{
min = Integer.parseInt(split[0]);
max = Integer.parseInt(split[1]);
}
}
else if (Util.isDigit(range))
{
min = Integer.parseInt(range);
max = min;
}
if ((min > -1) && (max > 0))
{
group.addChance(new RangeChanceHolder(min, max, chance));
}
}
}
_itemGroups.put(name, group);
}
else if ("enchantScrollGroup".equals(d.getNodeName()))
{
final int id = parseInteger(d.getAttributes(), "id");
final EnchantScrollGroup group = new EnchantScrollGroup(id);
for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if ("enchantRate".equalsIgnoreCase(cd.getNodeName()))
{
final EnchantRateItem rateGroup = new EnchantRateItem(parseString(cd.getAttributes(), "group"));
for (Node z = cd.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final NamedNodeMap attrs = z.getAttributes();
if (attrs.getNamedItem("slot") != null)
{
rateGroup.addSlot(ItemTable.SLOTS.get(parseString(attrs, "slot")));
}
if (attrs.getNamedItem("magicWeapon") != null)
{
rateGroup.setMagicWeapon(parseBoolean(attrs, "magicWeapon"));
}
if (attrs.getNamedItem("id") != null)
{
rateGroup.setItemId(parseInteger(attrs, "id"));
}
}
}
group.addRateGroup(rateGroup);
}
}
_scrollGroups.put(id, group);
}
}
}
}
}
public EnchantItemGroup getItemGroup(L2Item item, int scrollGroup)
{
final EnchantScrollGroup group = _scrollGroups.get(scrollGroup);
final EnchantRateItem rateGroup = group.getRateGroup(item);
return rateGroup != null ? _itemGroups.get(rateGroup.getName()) : null;
}
public EnchantItemGroup getItemGroup(String name)
{
return _itemGroups.get(name);
}
public EnchantScrollGroup getScrollGroup(int id)
{
return _scrollGroups.get(id);
}
public static EnchantItemGroupsData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final EnchantItemGroupsData _instance = new EnchantItemGroupsData();
}
}

View File

@ -0,0 +1,154 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.datatables.ItemTable;
import com.l2jmobius.gameserver.enums.StatFunction;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.gameserver.model.items.type.CrystalType;
import com.l2jmobius.gameserver.model.stats.Stats;
import com.l2jmobius.gameserver.model.stats.functions.FuncTemplate;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class holds the Enchant HP Bonus Data.
* @author MrPoke, Zoey76
*/
public class EnchantItemHPBonusData implements IXmlReader
{
private final Map<CrystalType, List<Integer>> _armorHPBonuses = new EnumMap<>(CrystalType.class);
private static final float FULL_ARMOR_MODIFIER = 1.5f; // TODO: Move it to config!
/**
* Instantiates a new enchant hp bonus data.
*/
protected EnchantItemHPBonusData()
{
load();
}
@Override
public void parseDocument(Document doc)
{
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 ("enchantHP".equalsIgnoreCase(d.getNodeName()))
{
final List<Integer> bonuses = new ArrayList<>(12);
for (Node e = d.getFirstChild(); e != null; e = e.getNextSibling())
{
if ("bonus".equalsIgnoreCase(e.getNodeName()))
{
bonuses.add(Integer.parseInt(e.getTextContent()));
}
}
_armorHPBonuses.put(parseEnum(d.getAttributes(), CrystalType.class, "grade"), bonuses);
}
}
}
}
if (!_armorHPBonuses.isEmpty())
{
final ItemTable it = ItemTable.getInstance();
// Armors
final Collection<Integer> armorIds = it.getAllArmorsId();
for (Integer itemId : armorIds)
{
final L2Item item = it.getTemplate(itemId);
if ((item != null) && (item.getCrystalType() != CrystalType.NONE))
{
switch (item.getBodyPart())
{
case L2Item.SLOT_CHEST:
case L2Item.SLOT_FEET:
case L2Item.SLOT_GLOVES:
case L2Item.SLOT_HEAD:
case L2Item.SLOT_LEGS:
case L2Item.SLOT_BACK:
case L2Item.SLOT_FULL_ARMOR:
case L2Item.SLOT_UNDERWEAR:
case L2Item.SLOT_L_HAND:
case L2Item.SLOT_BELT:
item.attach(new FuncTemplate(null, null, StatFunction.ENCHANTHP.getName(), -1, Stats.MAX_HP, 0));
break;
default:
break;
}
}
}
}
}
@Override
public void load()
{
_armorHPBonuses.clear();
parseDatapackFile("data/stats/enchantHPBonus.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorHPBonuses.size() + " Enchant HP Bonuses.");
}
/**
* Gets the HP bonus.
* @param item the item
* @return the HP bonus
*/
public final int getHPBonus(L2ItemInstance item)
{
final List<Integer> values = _armorHPBonuses.get(item.getItem().getItemGradeSPlus());
if ((values == null) || values.isEmpty() || (item.getOlyEnchantLevel() <= 0))
{
return 0;
}
final int bonus = values.get(Math.min(item.getOlyEnchantLevel(), values.size()) - 1);
if (item.getItem().getBodyPart() == L2Item.SLOT_FULL_ARMOR)
{
return (int) (bonus * FULL_ARMOR_MODIFIER);
}
return bonus;
}
/**
* Gets the single instance of EnchantHPBonusData.
* @return single instance of EnchantHPBonusData
*/
public static final EnchantItemHPBonusData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final EnchantItemHPBonusData _instance = new EnchantItemHPBonusData();
}
}

View File

@ -0,0 +1,124 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.gameserver.model.options.EnchantOptions;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* @author UnAfraid
*/
public class EnchantItemOptionsData implements IXmlReader
{
private final Map<Integer, Map<Integer, EnchantOptions>> _data = new HashMap<>();
protected EnchantItemOptionsData()
{
load();
}
@Override
public synchronized void load()
{
_data.clear();
parseDatapackFile("data/EnchantItemOptions.xml");
}
@Override
public void parseDocument(Document doc)
{
int counter = 0;
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 ("item".equalsIgnoreCase(d.getNodeName()))
{
final int itemId = parseInteger(d.getAttributes(), "id");
if (!_data.containsKey(itemId))
{
_data.put(itemId, new HashMap<Integer, EnchantOptions>());
}
for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if ("options".equalsIgnoreCase(cd.getNodeName()))
{
final EnchantOptions op = new EnchantOptions(parseInteger(cd.getAttributes(), "level"));
_data.get(itemId).put(op.getLevel(), op);
for (byte i = 0; i < 3; i++)
{
final Node att = cd.getAttributes().getNamedItem("option" + (i + 1));
if ((att != null) && Util.isDigit(att.getNodeValue()))
{
op.setOption(i, parseInteger(att));
}
}
counter++;
}
}
}
}
}
}
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _data.size() + " Items and " + counter + " Options.");
}
/**
* @param itemId
* @param enchantLevel
* @return enchant effects information.
*/
public EnchantOptions getOptions(int itemId, int enchantLevel)
{
return !_data.containsKey(itemId) || !_data.get(itemId).containsKey(enchantLevel) ? null : _data.get(itemId).get(enchantLevel);
}
/**
* @param item
* @return enchant effects information.
*/
public EnchantOptions getOptions(L2ItemInstance item)
{
return item != null ? getOptions(item.getId(), item.getEnchantLevel()) : null;
}
/**
* Gets the single instance of EnchantOptionsData.
* @return single instance of EnchantOptionsData
*/
public static EnchantItemOptionsData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final EnchantItemOptionsData _instance = new EnchantItemOptionsData();
}
}

View File

@ -0,0 +1,245 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.model.L2EnchantSkillGroup;
import com.l2jmobius.gameserver.model.L2EnchantSkillGroup.EnchantSkillHolder;
import com.l2jmobius.gameserver.model.L2EnchantSkillLearn;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class holds the Enchant Groups information.
* @author Micr0
*/
public class EnchantSkillGroupsData implements IXmlReader
{
public static final int NORMAL_ENCHANT_COST_MULTIPLIER = Config.NORMAL_ENCHANT_COST_MULTIPLIER;
public static final int SAFE_ENCHANT_COST_MULTIPLIER = Config.SAFE_ENCHANT_COST_MULTIPLIER;
public static final int NORMAL_ENCHANT_BOOK = 6622;
public static final int SAFE_ENCHANT_BOOK = 9627;
public static final int CHANGE_ENCHANT_BOOK = 9626;
public static final int UNTRAIN_ENCHANT_BOOK = 9625;
private final Map<Integer, L2EnchantSkillGroup> _enchantSkillGroups = new HashMap<>();
private final Map<Integer, L2EnchantSkillLearn> _enchantSkillTrees = new HashMap<>();
/**
* Instantiates a new enchant groups table.
*/
protected EnchantSkillGroupsData()
{
load();
}
@Override
public void load()
{
_enchantSkillGroups.clear();
_enchantSkillTrees.clear();
parseDatapackFile("data/EnchantSkillGroups.xml");
int routes = 0;
for (L2EnchantSkillGroup group : _enchantSkillGroups.values())
{
routes += group.getEnchantGroupDetails().size();
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _enchantSkillGroups.size() + " groups and " + routes + " routes.");
}
@Override
public void parseDocument(Document doc)
{
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 ("group".equalsIgnoreCase(d.getNodeName()))
{
NamedNodeMap attrs = d.getAttributes();
final int id = parseInteger(attrs, "id");
L2EnchantSkillGroup group = _enchantSkillGroups.get(id);
if (group == null)
{
group = new L2EnchantSkillGroup(id);
_enchantSkillGroups.put(id, group);
}
for (Node b = d.getFirstChild(); b != null; b = b.getNextSibling())
{
if ("enchant".equalsIgnoreCase(b.getNodeName()))
{
attrs = b.getAttributes();
final StatsSet set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
final Node att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
group.addEnchantDetail(new EnchantSkillHolder(set));
}
}
}
}
}
}
}
/**
* Adds the new route for skill.
* @param skillId the skill id
* @param maxLvL the max lvl
* @param route the route
* @param group the group
* @return the int
*/
public int addNewRouteForSkill(int skillId, int maxLvL, int route, int group)
{
L2EnchantSkillLearn enchantableSkill = _enchantSkillTrees.get(skillId);
if (enchantableSkill == null)
{
enchantableSkill = new L2EnchantSkillLearn(skillId, maxLvL);
_enchantSkillTrees.put(skillId, enchantableSkill);
}
if (_enchantSkillGroups.containsKey(group))
{
enchantableSkill.addNewEnchantRoute(route, group);
return _enchantSkillGroups.get(group).getEnchantGroupDetails().size();
}
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error while loading generating enchant skill id: " + skillId + "; route: " + route + "; missing group: " + group);
return 0;
}
/**
* Gets the skill enchantment for skill.
* @param skill the skill
* @return the skill enchantment for skill
*/
public L2EnchantSkillLearn getSkillEnchantmentForSkill(Skill skill)
{
// there is enchantment for this skill and we have the required level of it
final L2EnchantSkillLearn esl = getSkillEnchantmentBySkillId(skill.getId());
return (esl != null) && (skill.getLevel() >= esl.getBaseLevel()) ? esl : null;
}
/**
* Gets the skill enchantment by skill id.
* @param skillId the skill id
* @return the skill enchantment by skill id
*/
public L2EnchantSkillLearn getSkillEnchantmentBySkillId(int skillId)
{
return _enchantSkillTrees.get(skillId);
}
/**
* Gets the enchant skill group by id.
* @param id the id
* @return the enchant skill group by id
*/
public L2EnchantSkillGroup getEnchantSkillGroupById(int id)
{
return _enchantSkillGroups.get(id);
}
/**
* Gets the enchant skill sp cost.
* @param skill the skill
* @return the enchant skill sp cost
*/
public int getEnchantSkillSpCost(Skill skill)
{
final L2EnchantSkillLearn enchantSkillLearn = _enchantSkillTrees.get(skill.getId());
if (enchantSkillLearn != null)
{
final EnchantSkillHolder esh = enchantSkillLearn.getEnchantSkillHolder(skill.getLevel());
if (esh != null)
{
return esh.getSpCost();
}
}
return Integer.MAX_VALUE;
}
/**
* Gets the enchant skill Adena cost.
* @param skill the skill
* @return the enchant skill Adena cost
*/
public int getEnchantSkillAdenaCost(Skill skill)
{
final L2EnchantSkillLearn enchantSkillLearn = _enchantSkillTrees.get(skill.getId());
if (enchantSkillLearn != null)
{
final EnchantSkillHolder esh = enchantSkillLearn.getEnchantSkillHolder(skill.getLevel());
if (esh != null)
{
return esh.getAdenaCost();
}
}
return Integer.MAX_VALUE;
}
/**
* Gets the enchant skill rate.
* @param player the player
* @param skill the skill
* @return the enchant skill rate
*/
public byte getEnchantSkillRate(L2PcInstance player, Skill skill)
{
final L2EnchantSkillLearn enchantSkillLearn = _enchantSkillTrees.get(skill.getId());
if (enchantSkillLearn != null)
{
final EnchantSkillHolder esh = enchantSkillLearn.getEnchantSkillHolder(skill.getLevel());
if (esh != null)
{
return esh.getRate(player);
}
}
return 0;
}
/**
* Gets the single instance of EnchantGroupsData.
* @return single instance of EnchantGroupsData
*/
public static EnchantSkillGroupsData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final EnchantSkillGroupsData _instance = new EnchantSkillGroupsData();
}
}

View File

@ -0,0 +1,117 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class holds the Experience points for each level for players and pets.
* @author mrTJO
*/
public final class ExperienceData implements IXmlReader
{
private final Map<Integer, Long> _expTable = new HashMap<>();
private byte MAX_LEVEL;
private byte MAX_PET_LEVEL;
/**
* Instantiates a new experience table.
*/
protected ExperienceData()
{
load();
}
@Override
public void load()
{
_expTable.clear();
parseDatapackFile("data/stats/experience.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _expTable.size() + " levels.");
LOGGER.info(getClass().getSimpleName() + ": Max Player Level is: " + (MAX_LEVEL - 1));
LOGGER.info(getClass().getSimpleName() + ": Max Pet Level is: " + (MAX_PET_LEVEL - 1));
}
@Override
public void parseDocument(Document doc)
{
final Node table = doc.getFirstChild();
final NamedNodeMap tableAttr = table.getAttributes();
MAX_LEVEL = (byte) (Byte.parseByte(tableAttr.getNamedItem("maxLevel").getNodeValue()) + 1);
MAX_PET_LEVEL = (byte) (Byte.parseByte(tableAttr.getNamedItem("maxPetLevel").getNodeValue()) + 1);
for (Node n = table.getFirstChild(); n != null; n = n.getNextSibling())
{
if ("experience".equals(n.getNodeName()))
{
final NamedNodeMap attrs = n.getAttributes();
_expTable.put(parseInteger(attrs, "level"), parseLong(attrs, "tolevel"));
}
}
}
/**
* Gets the exp for level.
* @param level the level required.
* @return the experience points required to reach the given level.
*/
public long getExpForLevel(int level)
{
return _expTable.get(level);
}
/**
* Gets the max level.
* @return the maximum level acquirable by a player.
*/
public byte getMaxLevel()
{
return MAX_LEVEL;
}
/**
* Gets the max pet level.
* @return the maximum level acquirable by a pet.
*/
public byte getMaxPetLevel()
{
return MAX_PET_LEVEL;
}
/**
* Gets the single instance of ExperienceTable.
* @return single instance of ExperienceTable
*/
public static ExperienceData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final ExperienceData _instance = new ExperienceData();
}
}

View File

@ -0,0 +1,169 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.fishing.L2Fish;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class holds the Fish information.
* @author nonom
*/
public final class FishData implements IXmlReader
{
private final Map<Integer, L2Fish> _fishNormal = new HashMap<>();
private final Map<Integer, L2Fish> _fishEasy = new HashMap<>();
private final Map<Integer, L2Fish> _fishHard = new HashMap<>();
/**
* Instantiates a new fish data.
*/
protected FishData()
{
load();
}
@Override
public void load()
{
_fishEasy.clear();
_fishNormal.clear();
_fishHard.clear();
parseDatapackFile("data/stats/fishing/fishes.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_fishEasy.size() + _fishNormal.size() + _fishHard.size()) + " Fishes.");
}
@Override
public void parseDocument(Document doc)
{
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 ("fish".equalsIgnoreCase(d.getNodeName()))
{
final NamedNodeMap attrs = d.getAttributes();
final StatsSet set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
final Node att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
final L2Fish fish = new L2Fish(set);
switch (fish.getFishGrade())
{
case 0:
{
_fishEasy.put(fish.getFishId(), fish);
break;
}
case 1:
{
_fishNormal.put(fish.getFishId(), fish);
break;
}
case 2:
{
_fishHard.put(fish.getFishId(), fish);
break;
}
}
}
}
}
}
}
/**
* Gets the fish.
* @param level the fish Level
* @param group the fish Group
* @param grade the fish Grade
* @return List of Fish that can be fished
*/
public List<L2Fish> getFish(int level, int group, int grade)
{
final ArrayList<L2Fish> result = new ArrayList<>();
Map<Integer, L2Fish> fish = null;
switch (grade)
{
case 0:
{
fish = _fishEasy;
break;
}
case 1:
{
fish = _fishNormal;
break;
}
case 2:
{
fish = _fishHard;
break;
}
default:
{
LOGGER.warning(getClass().getSimpleName() + ": Unmanaged fish grade!");
return result;
}
}
for (L2Fish f : fish.values())
{
if ((f.getFishLevel() != level) || (f.getFishGroup() != group))
{
continue;
}
result.add(f);
}
if (result.isEmpty())
{
LOGGER.warning(getClass().getSimpleName() + ": Cannot find any fish for level: " + level + " group: " + group + " and grade: " + grade + "!");
}
return result;
}
/**
* Gets the single instance of FishData.
* @return single instance of FishData
*/
public static FishData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final FishData _instance = new FishData();
}
}

View File

@ -0,0 +1,121 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.fishing.L2FishingMonster;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class holds the Fishing Monsters information.
* @author nonom
*/
public final class FishingMonstersData implements IXmlReader
{
private final Map<Integer, L2FishingMonster> _fishingMonstersData = new HashMap<>();
/**
* Instantiates a new fishing monsters data.
*/
protected FishingMonstersData()
{
load();
}
@Override
public void load()
{
_fishingMonstersData.clear();
parseDatapackFile("data/stats/fishing/fishingMonsters.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _fishingMonstersData.size() + " Fishing Monsters.");
}
@Override
public void parseDocument(Document doc)
{
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 ("fishingMonster".equalsIgnoreCase(d.getNodeName()))
{
final NamedNodeMap attrs = d.getAttributes();
final StatsSet set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
final Node att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
final L2FishingMonster fishingMonster = new L2FishingMonster(set);
_fishingMonstersData.put(fishingMonster.getFishingMonsterId(), fishingMonster);
}
}
}
}
}
/**
* Gets the fishing monster.
* @param lvl the fisherman level
* @return a fishing monster given the fisherman level
*/
public L2FishingMonster getFishingMonster(int lvl)
{
for (L2FishingMonster fishingMonster : _fishingMonstersData.values())
{
if ((lvl >= fishingMonster.getUserMinLevel()) && (lvl <= fishingMonster.getUserMaxLevel()))
{
return fishingMonster;
}
}
return null;
}
/**
* Gets the fishing monster by Id.
* @param id the fishing monster Id
* @return the fishing monster by Id
*/
public L2FishingMonster getFishingMonsterById(int id)
{
return _fishingMonstersData.containsKey(id) ? _fishingMonstersData.get(id) : null;
}
/**
* Gets the single instance of FishingMonsterData.
* @return single instance of FishingMonsterData
*/
public static FishingMonstersData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final FishingMonstersData _instance = new FishingMonstersData();
}
}

View File

@ -0,0 +1,104 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.fishing.L2FishingRod;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class holds the Fishing Rods information.
* @author nonom
*/
public final class FishingRodsData implements IXmlReader
{
private final Map<Integer, L2FishingRod> _fishingRods = new HashMap<>();
/**
* Instantiates a new fishing rods data.
*/
protected FishingRodsData()
{
load();
}
@Override
public void load()
{
_fishingRods.clear();
parseDatapackFile("data/stats/fishing/fishingRods.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _fishingRods.size() + " Fishing Rods.");
}
@Override
public void parseDocument(Document doc)
{
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 ("fishingRod".equalsIgnoreCase(d.getNodeName()))
{
final NamedNodeMap attrs = d.getAttributes();
final StatsSet set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
final Node att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
final L2FishingRod fishingRod = new L2FishingRod(set);
_fishingRods.put(fishingRod.getFishingRodItemId(), fishingRod);
}
}
}
}
}
/**
* Gets the fishing rod.
* @param itemId the item id
* @return A fishing Rod by Item Id
*/
public L2FishingRod getFishingRod(int itemId)
{
return _fishingRods.get(itemId);
}
/**
* Gets the single instance of FishingRodsData.
* @return single instance of FishingRodsData
*/
public static FishingRodsData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final FishingRodsData _instance = new FishingRodsData();
}
}

View File

@ -0,0 +1,178 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.base.ClassId;
import com.l2jmobius.gameserver.model.items.L2Henna;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class holds the henna related information.<br>
* Cost and required amount to add the henna to the player.<br>
* Cost and retrieved amount for removing the henna from the player.<br>
* Allowed classes to wear each henna.
* @author Zoey76
*/
public final class HennaData implements IXmlReader
{
private final Map<Integer, L2Henna> _hennaList = new HashMap<>();
/**
* Instantiates a new henna data.
*/
protected HennaData()
{
load();
}
@Override
public void load()
{
_hennaList.clear();
parseDatapackFile("data/stats/hennaList.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _hennaList.size() + " Henna data.");
}
@Override
public void parseDocument(Document doc)
{
for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
{
if ("list".equals(n.getNodeName()))
{
for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("henna".equals(d.getNodeName()))
{
parseHenna(d);
}
}
}
}
}
/**
* Parses the henna.
* @param d the d
*/
private void parseHenna(Node d)
{
final StatsSet set = new StatsSet();
final List<ClassId> wearClassIds = new ArrayList<>();
NamedNodeMap attrs = d.getAttributes();
Node attr;
for (int i = 0; i < attrs.getLength(); i++)
{
attr = attrs.item(i);
set.set(attr.getNodeName(), attr.getNodeValue());
}
for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling())
{
final String name = c.getNodeName();
attrs = c.getAttributes();
switch (name)
{
case "stats":
{
for (int i = 0; i < attrs.getLength(); i++)
{
attr = attrs.item(i);
set.set(attr.getNodeName(), attr.getNodeValue());
}
break;
}
case "wear":
{
attr = attrs.getNamedItem("count");
set.set("wear_count", attr.getNodeValue());
attr = attrs.getNamedItem("fee");
set.set("wear_fee", attr.getNodeValue());
break;
}
case "cancel":
{
attr = attrs.getNamedItem("count");
set.set("cancel_count", attr.getNodeValue());
attr = attrs.getNamedItem("fee");
set.set("cancel_fee", attr.getNodeValue());
break;
}
case "classId":
{
wearClassIds.add(ClassId.getClassId(Integer.parseInt(c.getTextContent())));
break;
}
}
}
final L2Henna henna = new L2Henna(set);
henna.setWearClassIds(wearClassIds);
_hennaList.put(henna.getDyeId(), henna);
}
/**
* Gets the henna.
* @param id of the dye.
* @return the dye with that id.
*/
public L2Henna getHenna(int id)
{
return _hennaList.get(id);
}
/**
* Gets the henna list.
* @param classId the player's class Id.
* @return the list with all the allowed dyes.
*/
public List<L2Henna> getHennaList(ClassId classId)
{
final List<L2Henna> list = new ArrayList<>();
for (L2Henna henna : _hennaList.values())
{
if (henna.isAllowedClass(classId))
{
list.add(henna);
}
}
return list;
}
/**
* Gets the single instance of HennaData.
* @return single instance of HennaData
*/
public static HennaData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final HennaData _instance = new HennaData();
}
}

View File

@ -0,0 +1,172 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.data.xml.impl;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.GameTimeController;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.util.data.xml.IXmlReader;
/**
* This class load, holds and calculates the hit condition bonuses.
* @author Nik
*/
public final class HitConditionBonusData implements IXmlReader
{
private int frontBonus = 0;
private int sideBonus = 0;
private int backBonus = 0;
private int highBonus = 0;
private int lowBonus = 0;
private int darkBonus = 0;
private int rainBonus = 0;
/**
* Instantiates a new hit condition bonus.
*/
protected HitConditionBonusData()
{
load();
}
@Override
public void load()
{
parseDatapackFile("data/stats/hitConditionBonus.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded Hit Condition bonuses.");
if (Config.DEBUG)
{
LOGGER.info(getClass().getSimpleName() + ": Front bonus: " + frontBonus);
LOGGER.info(getClass().getSimpleName() + ": Side bonus: " + sideBonus);
LOGGER.info(getClass().getSimpleName() + ": Back bonus: " + backBonus);
LOGGER.info(getClass().getSimpleName() + ": High bonus: " + highBonus);
LOGGER.info(getClass().getSimpleName() + ": Low bonus: " + lowBonus);
LOGGER.info(getClass().getSimpleName() + ": Dark bonus: " + darkBonus);
LOGGER.info(getClass().getSimpleName() + ": Rain bonus: " + rainBonus);
}
}
@Override
public void parseDocument(Document doc)
{
for (Node d = doc.getFirstChild().getFirstChild(); d != null; d = d.getNextSibling())
{
final NamedNodeMap attrs = d.getAttributes();
switch (d.getNodeName())
{
case "front":
{
frontBonus = parseInteger(attrs, "val");
break;
}
case "side":
{
sideBonus = parseInteger(attrs, "val");
break;
}
case "back":
{
backBonus = parseInteger(attrs, "val");
break;
}
case "high":
{
highBonus = parseInteger(attrs, "val");
break;
}
case "low":
{
lowBonus = parseInteger(attrs, "val");
break;
}
case "dark":
{
darkBonus = parseInteger(attrs, "val");
break;
}
case "rain":
{
rainBonus = parseInteger(attrs, "val");
break;
}
}
}
}
/**
* Gets the condition bonus.
* @param attacker the attacking character.
* @param target the attacked character.
* @return the bonus of the attacker against the target.
*/
public double getConditionBonus(L2Character attacker, L2Character target)
{
double mod = 100;
// Get high or low bonus
if ((attacker.getZ() - target.getZ()) > 50)
{
mod += highBonus;
}
else if ((attacker.getZ() - target.getZ()) < -50)
{
mod += lowBonus;
}
// Get weather bonus
if (GameTimeController.getInstance().isNight())
{
mod += darkBonus;
// else if () No rain support yet.
// chance += hitConditionBonus.rainBonus;
}
// Get side bonus
if (attacker.isBehindTarget())
{
mod += backBonus;
}
else if (attacker.isInFrontOfTarget())
{
mod += frontBonus;
}
else
{
mod += sideBonus;
}
// If (mod / 100) is less than 0, return 0, because we can't lower more than 100%.
return Math.max(mod / 100, 0);
}
/**
* Gets the single instance of HitConditionBonus.
* @return single instance of HitConditionBonus
*/
public static HitConditionBonusData getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final HitConditionBonusData _instance = new HitConditionBonusData();
}
}

Some files were not shown because too many files have changed in this diff Show More