Prelude of War branch.

This commit is contained in:
MobiusDevelopment
2019-09-27 13:41:55 +00:00
parent 5a24b99391
commit b07e22d26a
20375 changed files with 4016171 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
/*
* 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 org.l2jmobius.commons.concurrent;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Logger;
/**
* @author NB4L1
*/
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler
{
private static final Logger LOGGER = Logger.getLogger(RejectedExecutionHandlerImpl.class.getName());
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
{
if (executor.isShutdown())
{
return;
}
LOGGER.warning(r + " from " + executor + " " + new RejectedExecutionException());
if (Thread.currentThread().getPriority() > Thread.NORM_PRIORITY)
{
new Thread(r).start();
}
else
{
r.run();
}
}
}

View File

@ -0,0 +1,49 @@
/*
* 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 org.l2jmobius.commons.concurrent;
import java.util.logging.Logger;
import org.l2jmobius.Config;
/**
* @author Mobius
*/
public class RunnableWrapper implements Runnable
{
private static final Logger LOGGER = Logger.getLogger(RunnableWrapper.class.getName());
private final Runnable _runnable;
public RunnableWrapper(Runnable runnable)
{
_runnable = runnable;
}
@Override
public void run()
{
try
{
_runnable.run();
}
catch (Exception e)
{
LOGGER.warning(e.getMessage() + Config.EOL + e.getStackTrace());
}
}
}

View File

@ -0,0 +1,171 @@
/*
* 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 org.l2jmobius.commons.concurrent;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import org.l2jmobius.Config;
/**
* This class handles thread pooling system.<br>
* It relies on two threadpool executors, which pool size is set using config.<br>
* Those arrays hold following pools:<br>
* <ul>
* <li>Scheduled pool keeps a track about incoming, future events.</li>
* <li>Instant pool handles short-life events.</li>
* </ul>
*/
public class ThreadPool
{
private static final Logger LOGGER = Logger.getLogger(ThreadPool.class.getName());
private static final ScheduledThreadPoolExecutor SCHEDULED_POOL = new ScheduledThreadPoolExecutor(Config.SCHEDULED_THREAD_POOL_COUNT);
private static final ThreadPoolExecutor INSTANT_POOL = new ThreadPoolExecutor(Config.INSTANT_THREAD_POOL_COUNT, Config.INSTANT_THREAD_POOL_COUNT, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100000));
public static void init()
{
// Set pool options.
SCHEDULED_POOL.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl());
SCHEDULED_POOL.setRemoveOnCancelPolicy(true);
SCHEDULED_POOL.prestartAllCoreThreads();
INSTANT_POOL.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl());
INSTANT_POOL.prestartAllCoreThreads();
// Launch purge task.
scheduleAtFixedRate(() ->
{
purge();
}, 60000, 60000);
LOGGER.info("ThreadPool: Initialized");
LOGGER.info("...scheduled pool executor with " + Config.SCHEDULED_THREAD_POOL_COUNT + " total threads.");
LOGGER.info("...instant pool executor with " + Config.INSTANT_THREAD_POOL_COUNT + " total threads.");
}
public static void purge()
{
SCHEDULED_POOL.purge();
INSTANT_POOL.purge();
}
/**
* Creates and executes a one-shot action that becomes enabled after the given delay.
* @param runnable : the task to execute.
* @param delay : the time from now to delay execution.
* @return a ScheduledFuture representing pending completion of the task and whose get() method will return null upon completion.
*/
public static ScheduledFuture<?> schedule(Runnable runnable, long delay)
{
try
{
return SCHEDULED_POOL.schedule(new RunnableWrapper(runnable), delay, TimeUnit.MILLISECONDS);
}
catch (Exception e)
{
LOGGER.warning(e.getMessage() + Config.EOL + e.getStackTrace());
return null;
}
}
/**
* Creates and executes a periodic action that becomes enabled first after the given initial delay.
* @param runnable : the task to execute.
* @param initialDelay : the time to delay first execution.
* @param period : the period between successive executions.
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation.
*/
public static ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, long initialDelay, long period)
{
try
{
return SCHEDULED_POOL.scheduleAtFixedRate(new RunnableWrapper(runnable), initialDelay, period, TimeUnit.MILLISECONDS);
}
catch (Exception e)
{
LOGGER.warning(e.getMessage() + Config.EOL + e.getStackTrace());
return null;
}
}
/**
* Executes the given task sometime in the future.
* @param runnable : the task to execute.
*/
public static void execute(Runnable runnable)
{
try
{
INSTANT_POOL.execute(new RunnableWrapper(runnable));
}
catch (Exception e)
{
LOGGER.warning(e.getMessage() + Config.EOL + e.getStackTrace());
}
}
public static String[] getStats()
{
final String[] stats = new String[20];
int pos = 0;
stats[pos++] = "Scheduled pool:";
stats[pos++] = " |- ActiveCount: ...... " + SCHEDULED_POOL.getActiveCount();
stats[pos++] = " |- CorePoolSize: ..... " + SCHEDULED_POOL.getCorePoolSize();
stats[pos++] = " |- PoolSize: ......... " + SCHEDULED_POOL.getPoolSize();
stats[pos++] = " |- LargestPoolSize: .. " + SCHEDULED_POOL.getLargestPoolSize();
stats[pos++] = " |- MaximumPoolSize: .. " + SCHEDULED_POOL.getMaximumPoolSize();
stats[pos++] = " |- CompletedTaskCount: " + SCHEDULED_POOL.getCompletedTaskCount();
stats[pos++] = " |- QueuedTaskCount: .. " + SCHEDULED_POOL.getQueue().size();
stats[pos++] = " |- TaskCount: ........ " + SCHEDULED_POOL.getTaskCount();
stats[pos++] = " | -------";
stats[pos++] = "Instant pool:";
stats[pos++] = " |- ActiveCount: ...... " + INSTANT_POOL.getActiveCount();
stats[pos++] = " |- CorePoolSize: ..... " + INSTANT_POOL.getCorePoolSize();
stats[pos++] = " |- PoolSize: ......... " + INSTANT_POOL.getPoolSize();
stats[pos++] = " |- LargestPoolSize: .. " + INSTANT_POOL.getLargestPoolSize();
stats[pos++] = " |- MaximumPoolSize: .. " + INSTANT_POOL.getMaximumPoolSize();
stats[pos++] = " |- CompletedTaskCount: " + INSTANT_POOL.getCompletedTaskCount();
stats[pos++] = " |- QueuedTaskCount: .. " + INSTANT_POOL.getQueue().size();
stats[pos++] = " |- TaskCount: ........ " + INSTANT_POOL.getTaskCount();
stats[pos++] = " | -------";
return stats;
}
/**
* Shutdown thread pooling system correctly. Send different informations.
*/
public static void shutdown()
{
try
{
LOGGER.info("ThreadPool: Shutting down.");
SCHEDULED_POOL.shutdownNow();
INSTANT_POOL.shutdownNow();
}
catch (Throwable t)
{
t.printStackTrace();
}
}
}

View File

@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.commons.database;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.l2jmobius.Config;
import org.l2jmobius.commons.enums.ServerMode;
/**
* @author Mobius
*/
public class DatabaseBackup
{
public static void performBackup()
{
// Delete old files.
if (Config.BACKUP_DAYS > 0)
{
final long cut = LocalDateTime.now().minusDays(Config.BACKUP_DAYS).toEpochSecond(ZoneOffset.UTC);
final Path path = Paths.get(Config.BACKUP_PATH);
try
{
Files.list(path).filter(n ->
{
try
{
return Files.getLastModifiedTime(n).to(TimeUnit.SECONDS) < cut;
}
catch (Exception ex)
{
return false;
}
}).forEach(n ->
{
try
{
Files.delete(n);
}
catch (Exception ex)
{
}
});
}
catch (Exception e)
{
}
}
// Dump to file.
final String mysqldumpPath = System.getProperty("os.name").toLowerCase().contains("win") ? Config.MYSQL_BIN_PATH : "";
try
{
final Process process = Runtime.getRuntime().exec(mysqldumpPath + "mysqldump -u " + Config.DATABASE_LOGIN + (Config.DATABASE_PASSWORD.trim().isEmpty() ? "" : " -p" + Config.DATABASE_PASSWORD) + " " + Config.DATABASE_URL.replace("jdbc:mariadb://", "").replaceAll(".*\\/|\\?.*", "") + " -r " + Config.BACKUP_PATH + (Config.SERVER_MODE == ServerMode.GAME ? "game" : "login") + new SimpleDateFormat("_yyyy_MM_dd_HH_mm'.sql'").format(new Date()));
process.waitFor();
}
catch (Exception e)
{
}
}
}

View File

@ -0,0 +1,85 @@
/*
* 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 org.l2jmobius.commons.database;
import java.sql.Connection;
import java.util.logging.Logger;
import com.zaxxer.hikari.HikariDataSource;
import org.l2jmobius.Config;
/**
* @author Mobius
*/
public class DatabaseFactory
{
private static final Logger LOGGER = Logger.getLogger(DatabaseFactory.class.getName());
private static final HikariDataSource _hds = new HikariDataSource();
public static void init()
{
_hds.setDriverClassName(Config.DATABASE_DRIVER);
_hds.setJdbcUrl(Config.DATABASE_URL);
_hds.setUsername(Config.DATABASE_LOGIN);
_hds.setPassword(Config.DATABASE_PASSWORD);
_hds.setMaximumPoolSize(Config.DATABASE_MAX_CONNECTIONS);
_hds.setConnectionTimeout(600000);
_hds.setMaxLifetime(1200000);
// Test if connection is valid.
try
{
_hds.getConnection().close();
LOGGER.info("Database: Initialized.");
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static Connection getConnection()
{
Connection con = null;
while (con == null)
{
try
{
con = _hds.getConnection();
}
catch (Exception e)
{
LOGGER.severe("DatabaseFactory: Cound not get a connection. " + e);
}
}
return con;
}
public static void close()
{
try
{
_hds.close();
}
catch (Exception e)
{
LOGGER.severe("DatabaseFactory: There was a problem closing the data source. " + e);
}
}
}

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 org.l2jmobius.commons.enums;
/**
* @author Mobius
*/
public enum IdFactoryType
{
BITSET,
STACK
}

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 org.l2jmobius.commons.enums;
/**
* @author Mobius
*/
public enum ServerMode
{
NONE,
GAME,
LOGIN;
}

View File

@ -0,0 +1,106 @@
/*
* 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 org.l2jmobius.commons.network;
import java.util.logging.Logger;
/**
* @version $Revision: 1.2.4.1 $ $Date: 2005/03/27 15:30:12 $
*/
public abstract class BaseRecievePacket
{
private static final Logger LOGGER = Logger.getLogger(BaseRecievePacket.class.getName());
private final byte[] _decrypt;
private int _off;
public BaseRecievePacket(byte[] decrypt)
{
_decrypt = decrypt;
_off = 1; // skip packet type id
}
public int readD()
{
int result = _decrypt[_off++] & 0xff;
result |= (_decrypt[_off++] << 8) & 0xff00;
result |= (_decrypt[_off++] << 0x10) & 0xff0000;
result |= (_decrypt[_off++] << 0x18) & 0xff000000;
return result;
}
public int readC()
{
return _decrypt[_off++] & 0xff;
}
public int readH()
{
return (_decrypt[_off++] & 0xff) | ((_decrypt[_off++] << 8) & 0xff00);
}
public double readF()
{
long result = _decrypt[_off++] & 0xff;
result |= (_decrypt[_off++] & 0xffL) << 8L;
result |= (_decrypt[_off++] & 0xffL) << 16L;
result |= (_decrypt[_off++] & 0xffL) << 24L;
result |= (_decrypt[_off++] & 0xffL) << 32L;
result |= (_decrypt[_off++] & 0xffL) << 40L;
result |= (_decrypt[_off++] & 0xffL) << 48L;
result |= (_decrypt[_off++] & 0xffL) << 56L;
return Double.longBitsToDouble(result);
}
public String readS()
{
String result = null;
try
{
result = new String(_decrypt, _off, _decrypt.length - _off, "UTF-16LE");
result = result.substring(0, result.indexOf(0x00));
_off += (result.length() * 2) + 2;
}
catch (Exception e)
{
LOGGER.warning(getClass().getSimpleName() + ": " + e.getMessage());
}
return result;
}
public byte[] readB(int length)
{
final byte[] result = new byte[length];
System.arraycopy(_decrypt, _off, result, 0, length);
_off += length;
return result;
}
public long readQ()
{
long result = _decrypt[_off++] & 0xff;
result |= (_decrypt[_off++] & 0xffL) << 8L;
result |= (_decrypt[_off++] & 0xffL) << 16L;
result |= (_decrypt[_off++] & 0xffL) << 24L;
result |= (_decrypt[_off++] & 0xffL) << 32L;
result |= (_decrypt[_off++] & 0xffL) << 40L;
result |= (_decrypt[_off++] & 0xffL) << 48L;
result |= (_decrypt[_off++] & 0xffL) << 56L;
return result;
}
}

View File

@ -0,0 +1,136 @@
/*
* 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 org.l2jmobius.commons.network;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.logging.Logger;
/**
* @version $Revision: 1.2.4.1 $ $Date: 2005/03/27 15:30:11 $
*/
public abstract class BaseSendablePacket
{
private static final Logger LOGGER = Logger.getLogger(BaseSendablePacket.class.getName());
private final ByteArrayOutputStream _bao;
protected BaseSendablePacket()
{
_bao = new ByteArrayOutputStream();
}
protected void writeD(int value)
{
_bao.write(value & 0xff);
_bao.write((value >> 8) & 0xff);
_bao.write((value >> 16) & 0xff);
_bao.write((value >> 24) & 0xff);
}
protected void writeH(int value)
{
_bao.write(value & 0xff);
_bao.write((value >> 8) & 0xff);
}
protected void writeC(int value)
{
_bao.write(value & 0xff);
}
protected void writeF(double org)
{
final long value = Double.doubleToRawLongBits(org);
_bao.write((int) (value & 0xff));
_bao.write((int) ((value >> 8) & 0xff));
_bao.write((int) ((value >> 16) & 0xff));
_bao.write((int) ((value >> 24) & 0xff));
_bao.write((int) ((value >> 32) & 0xff));
_bao.write((int) ((value >> 40) & 0xff));
_bao.write((int) ((value >> 48) & 0xff));
_bao.write((int) ((value >> 56) & 0xff));
}
protected void writeS(String text)
{
try
{
if (text != null)
{
_bao.write(text.getBytes("UTF-16LE"));
}
}
catch (Exception e)
{
LOGGER.warning(getClass().getSimpleName() + ": " + e.getMessage());
}
_bao.write(0);
_bao.write(0);
}
protected void writeB(byte[] array)
{
try
{
_bao.write(array);
}
catch (IOException e)
{
LOGGER.warning(getClass().getSimpleName() + ": " + e.getMessage());
}
}
protected void writeQ(long value)
{
_bao.write((int) (value & 0xff));
_bao.write((int) ((value >> 8) & 0xff));
_bao.write((int) ((value >> 16) & 0xff));
_bao.write((int) ((value >> 24) & 0xff));
_bao.write((int) ((value >> 32) & 0xff));
_bao.write((int) ((value >> 40) & 0xff));
_bao.write((int) ((value >> 48) & 0xff));
_bao.write((int) ((value >> 56) & 0xff));
}
public int getLength()
{
return _bao.size() + 2;
}
public byte[] getBytes()
{
// if (this instanceof Init)
// writeD(0x00); // reserve for XOR initial key
writeD(0x00); // reserve for checksum
final int padding = _bao.size() % 8;
if (padding != 0)
{
for (int i = padding; i < 8; i++)
{
writeC(0x00);
}
}
return _bao.toByteArray();
}
public abstract byte[] getContent() throws IOException;
}

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 org.l2jmobius.commons.network;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @author Nos
* @param <T>
*/
public abstract class ChannelInboundHandler<T extends ChannelInboundHandler<?>>extends SimpleChannelInboundHandler<IIncomingPacket<T>>
{
private Channel _channel;
@Override
public void channelActive(ChannelHandlerContext ctx)
{
_channel = ctx.channel();
}
public void setConnectionState(IConnectionState connectionState)
{
_channel.attr(IConnectionState.ATTRIBUTE_KEY).set(connectionState);
}
public IConnectionState getConnectionState()
{
return _channel != null ? _channel.attr(IConnectionState.ATTRIBUTE_KEY).get() : null;
}
}

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 org.l2jmobius.commons.network;
import io.netty.util.AttributeKey;
/**
* @author Nos
*/
public interface IConnectionState
{
AttributeKey<IConnectionState> ATTRIBUTE_KEY = AttributeKey.valueOf(IConnectionState.class, "");
}

View File

@ -0,0 +1,29 @@
/*
* 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 org.l2jmobius.commons.network;
import io.netty.buffer.ByteBuf;
/**
* @author Nos
*/
public interface ICrypt
{
void encrypt(ByteBuf buf);
void decrypt(ByteBuf buf);
}

View File

@ -0,0 +1,34 @@
/*
* 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 org.l2jmobius.commons.network;
/**
* @author Nos
* @param <T>
*/
public interface IIncomingPacket<T>
{
/**
* Reads a packet.
* @param client the client
* @param packet the packet reader
* @return {@code true} if packet was read successfully, {@code false} otherwise.
*/
boolean read(T client, PacketReader packet);
void run(T client) throws Exception;
}

View File

@ -0,0 +1,32 @@
/*
* 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 org.l2jmobius.commons.network;
import java.util.Set;
/**
* @author Nos
* @param <T>
*/
public interface IIncomingPackets<T>extends IConnectionState
{
int getPacketId();
IIncomingPacket<T> newIncomingPacket();
Set<IConnectionState> getConnectionStates();
}

View File

@ -0,0 +1,29 @@
/*
* 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 org.l2jmobius.commons.network;
/**
* @author Nos
*/
public interface IOutgoingPacket
{
/**
* @param packet the packet writer
* @return {@code true} if packet was writen successfully, {@code false} otherwise.
*/
boolean write(PacketWriter packet);
}

View File

@ -0,0 +1,76 @@
/*
* 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 org.l2jmobius.commons.network;
import java.util.logging.Logger;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author Nos
*/
public class NetworkManager
{
private final Logger LOGGER = Logger.getLogger(getClass().getName());
private final ServerBootstrap _serverBootstrap;
private final String _host;
private final int _port;
private ChannelFuture _channelFuture;
public NetworkManager(EventLoopGroup bossGroup, EventLoopGroup workerGroup, ChannelInitializer<SocketChannel> clientInitializer, String host, int port)
{
// @formatter:off
_serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(clientInitializer)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
// @formatter:on
_host = host;
_port = port;
}
public ChannelFuture getChannelFuture()
{
return _channelFuture;
}
public void start() throws InterruptedException
{
if ((_channelFuture != null) && !_channelFuture.isDone())
{
return;
}
_channelFuture = _serverBootstrap.bind(_host, _port).sync();
LOGGER.info(getClass().getSimpleName() + ": Listening on " + _host + ":" + _port);
}
public void stop() throws InterruptedException
{
_channelFuture.channel().close().sync();
}
}

View File

@ -0,0 +1,163 @@
/*
* 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 org.l2jmobius.commons.network;
import io.netty.buffer.ByteBuf;
/**
* @author Nos
*/
public class PacketReader
{
private final ByteBuf _buf;
public PacketReader(ByteBuf buf)
{
_buf = buf;
}
/**
* Gets the readable bytes.
* @return the readable bytes
*/
public int getReadableBytes()
{
return _buf.readableBytes();
}
/**
* Reads an unsigned byte.
* @return the unsigned byte
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code 1}
*/
public short readC()
{
return _buf.readUnsignedByte();
}
/**
* Reads an unsigned short.
* @return the unsigned short
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code 2}
*/
public int readH()
{
return _buf.readUnsignedShortLE();
}
/**
* Reads an integer.
* @return the integer
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code 4}
*/
public int readD()
{
return _buf.readIntLE();
}
/**
* Reads a long.
* @return the long
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code 8}
*/
public long readQ()
{
return _buf.readLongLE();
}
/**
* Reads a float.
* @return the float
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code 4}
*/
public float readE()
{
return Float.intBitsToFloat(_buf.readIntLE());
}
/**
* Reads a double.
* @return the double
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code 8}
*/
public double readF()
{
return Double.longBitsToDouble(_buf.readLongLE());
}
/**
* Reads a string.
* @return the string
* @throws IndexOutOfBoundsException if string {@code null} terminator is not found within {@code readableBytes}
*/
public String readS()
{
final StringBuilder sb = new StringBuilder();
char chr;
while ((chr = Character.reverseBytes(_buf.readChar())) != 0)
{
sb.append(chr);
}
return sb.toString();
}
/**
* Reads a fixed length string.
* @return the string
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code 2 + String.length * 2}
*/
public String readString()
{
final StringBuilder sb = new StringBuilder();
final int stringLength = _buf.readShortLE();
if ((stringLength * 2) > _buf.readableBytes())
{
throw new IndexOutOfBoundsException("readerIndex(" + _buf.readerIndex() + ") + length(" + (stringLength * 2) + ") exceeds writerIndex(" + _buf.writerIndex() + "): " + _buf);
}
for (int i = 0; i < stringLength; i++)
{
sb.append(Character.reverseBytes(_buf.readChar()));
}
return sb.toString();
}
/**
* Reads a byte array.
* @param length the length
* @return the byte array
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code length}
*/
public byte[] readB(int length)
{
final byte[] result = new byte[length];
_buf.readBytes(result);
return result;
}
/**
* Reads a byte array.
* @param dst the destination
* @param dstIndex the destination index to start writing the bytes from
* @param length the length
* @throws IndexOutOfBoundsException if {@code readableBytes} is less than {@code length}, if the specified dstIndex is less than 0 or if {@code dstIndex + length} is greater than {@code dst.length}
*/
public void readB(byte[] dst, int dstIndex, int length)
{
_buf.readBytes(dst, dstIndex, length);
}
}

View File

@ -0,0 +1,141 @@
/*
* 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 org.l2jmobius.commons.network;
import io.netty.buffer.ByteBuf;
/**
* @author Nos
*/
public class PacketWriter
{
private final ByteBuf _buf;
public PacketWriter(ByteBuf buf)
{
_buf = buf;
}
/**
* Gets the writable bytes.
* @return the writable bytes
*/
public int getWritableBytes()
{
return _buf.writableBytes();
}
/**
* Writes a byte.
* @param value the byte (The 24 high-order bits are ignored)
*/
public void writeC(int value)
{
_buf.writeByte(value);
}
/**
* Writes a short.
* @param value the short (The 16 high-order bits are ignored)
*/
public void writeH(int value)
{
_buf.writeShortLE(value);
}
/**
* Writes an integer.
* @param value the integer
*/
public void writeD(int value)
{
_buf.writeIntLE(value);
}
/**
* Writes a long.
* @param value the long
*/
public void writeQ(long value)
{
_buf.writeLongLE(value);
}
/**
* Writes a float.
* @param value the float
*/
public void writeE(float value)
{
_buf.writeIntLE(Float.floatToIntBits(value));
}
/**
* Writes a double.
* @param value the double
*/
public void writeF(double value)
{
_buf.writeLongLE(Double.doubleToLongBits(value));
}
/**
* Writes a string.
* @param value the string
*/
public void writeS(String value)
{
if (value != null)
{
for (int i = 0; i < value.length(); i++)
{
_buf.writeChar(Character.reverseBytes(value.charAt(i)));
}
}
_buf.writeChar(0);
}
/**
* Writes a string with fixed length specified as [short length, char[length] data].
* @param value the string
*/
public void writeString(String value)
{
if (value != null)
{
_buf.writeShortLE(value.length());
for (int i = 0; i < value.length(); i++)
{
_buf.writeChar(Character.reverseBytes(value.charAt(i)));
}
}
else
{
_buf.writeShort(0);
}
}
/**
* Writes a byte array.
* @param bytes the byte array
*/
public void writeB(byte[] bytes)
{
_buf.writeBytes(bytes);
}
}

View File

@ -0,0 +1,71 @@
/*
* 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 org.l2jmobius.commons.network.codecs;
import java.util.List;
import org.l2jmobius.commons.network.ICrypt;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
/**
* @author Nos
*/
public class CryptCodec extends ByteToMessageCodec<ByteBuf>
{
private final ICrypt _crypt;
public CryptCodec(ICrypt crypt)
{
super();
_crypt = crypt;
}
/*
* (non-Javadoc)
* @see io.netty.handler.codec.ByteToMessageCodec#encode(io.netty.channel.ChannelHandlerContext, java.lang.Object, io.netty.buffer.ByteBuf)
*/
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out)
{
// Check if there are any data to encrypt.
if (!msg.isReadable())
{
return;
}
msg.resetReaderIndex();
_crypt.encrypt(msg);
msg.resetReaderIndex();
out.writeBytes(msg);
}
/*
* (non-Javadoc)
* @see io.netty.handler.codec.ByteToMessageCodec#decode(io.netty.channel.ChannelHandlerContext, io.netty.buffer.ByteBuf, java.util.List)
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
{
in.resetReaderIndex();
_crypt.decrypt(in);
in.readerIndex(in.writerIndex());
out.add(in.copy(0, in.writerIndex()));
}
}

View File

@ -0,0 +1,41 @@
/*
* 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 org.l2jmobius.commons.network.codecs;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
/**
* @author Nos
*/
@Sharable
public class LengthFieldBasedFrameEncoder extends MessageToMessageEncoder<ByteBuf>
{
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out)
{
final ByteBuf buf = ctx.alloc().buffer(2);
final short length = (short) (msg.readableBytes() + 2);
buf.writeShortLE(length);
out.add(buf);
out.add(msg.retain());
}
}

View File

@ -0,0 +1,91 @@
/*
* 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 org.l2jmobius.commons.network.codecs;
import java.util.List;
import java.util.logging.Logger;
import org.l2jmobius.commons.network.IConnectionState;
import org.l2jmobius.commons.network.IIncomingPacket;
import org.l2jmobius.commons.network.IIncomingPackets;
import org.l2jmobius.commons.network.PacketReader;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/**
* @author Nos
* @param <T>
*/
public class PacketDecoder<T>extends ByteToMessageDecoder
{
private static final Logger LOGGER = Logger.getLogger(PacketDecoder.class.getName());
private final IIncomingPackets<T>[] _incomingPackets;
private final T _client;
public PacketDecoder(IIncomingPackets<T>[] incomingPackets, T client)
{
_incomingPackets = incomingPackets;
_client = client;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
{
if ((in == null) || !in.isReadable())
{
return;
}
try
{
final short packetId = in.readUnsignedByte();
if (packetId >= _incomingPackets.length)
{
LOGGER.finer("Unknown packet: " + Integer.toHexString(packetId));
return;
}
final IIncomingPackets<T> incomingPacket = _incomingPackets[packetId];
if (incomingPacket == null)
{
LOGGER.finer("Unknown packet: " + Integer.toHexString(packetId));
return;
}
final IConnectionState connectionState = ctx.channel().attr(IConnectionState.ATTRIBUTE_KEY).get();
if ((connectionState == null) || !incomingPacket.getConnectionStates().contains(connectionState))
{
// LOGGER.warning(incomingPacket + ": Connection at invalid state: " + connectionState + " Required States: " + incomingPacket.getConnectionStates());
return;
}
final IIncomingPacket<T> packet = incomingPacket.newIncomingPacket();
if ((packet != null) && packet.read(_client, new PacketReader(in)))
{
out.add(packet);
}
}
finally
{
// We always consider that we read whole packet.
in.readerIndex(in.writerIndex());
}
}
}

View File

@ -0,0 +1,71 @@
/*
* 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 org.l2jmobius.commons.network.codecs;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.commons.network.IOutgoingPacket;
import org.l2jmobius.commons.network.PacketWriter;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @author Nos
*/
@Sharable
public class PacketEncoder extends MessageToByteEncoder<IOutgoingPacket>
{
private static final Logger LOGGER = Logger.getLogger(PacketEncoder.class.getName());
private final int _maxPacketSize;
public PacketEncoder(int maxPacketSize)
{
super();
_maxPacketSize = maxPacketSize;
}
@Override
protected void encode(ChannelHandlerContext ctx, IOutgoingPacket packet, ByteBuf out)
{
try
{
if (packet.write(new PacketWriter(out)))
{
if (out.writerIndex() > _maxPacketSize)
{
throw new IllegalStateException("Packet (" + packet + ") size (" + out.writerIndex() + ") is bigger than the limit (" + _maxPacketSize + ")");
}
}
else
{
// Avoid sending the packet
out.clear();
}
}
catch (Throwable e)
{
LOGGER.log(Level.WARNING, "Failed sending Packet(" + packet + ")", e);
// Avoid sending the packet if some exception happened
out.clear();
}
}
}

View File

@ -0,0 +1,590 @@
/*
* 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 org.l2jmobius.commons.util;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.StringJoiner;
import java.util.StringTokenizer;
import org.l2jmobius.Config;
public class CommonUtil
{
private static final char[] ILLEGAL_CHARACTERS =
{
'/',
'\n',
'\r',
'\t',
'\0',
'\f',
'`',
'?',
'*',
'\\',
'<',
'>',
'|',
'\"',
':'
};
/**
* Method to generate the hexadecimal representation of a byte array.<br>
* 16 bytes per row, while ascii chars or "." is shown at the end of the line.
* @param data the byte array to be represented in hexadecimal representation
* @param len the number of bytes to represent in hexadecimal representation
* @return byte array represented in hexadecimal format
*/
public static String printData(byte[] data, int len)
{
return new String(HexUtils.bArr2HexEdChars(data, len));
}
/**
* This call is equivalent to Util.printData(data, data.length)
* @see CommonUtil#printData(byte[],int)
* @param data data to represent in hexadecimal
* @return byte array represented in hexadecimal format
*/
public static String printData(byte[] data)
{
return printData(data, data.length);
}
/**
* Method to represent the remaining bytes of a ByteBuffer as hexadecimal
* @param buf ByteBuffer to represent the remaining bytes of as hexadecimal
* @return hexadecimal representation of remaining bytes of the ByteBuffer
*/
public static String printData(ByteBuffer buf)
{
final byte[] data = new byte[buf.remaining()];
buf.get(data);
final String hex = printData(data, data.length);
buf.position(buf.position() - data.length);
return hex;
}
/**
* Method to generate a random sequence of bytes returned as byte array
* @param size number of random bytes to generate
* @return byte array with sequence of random bytes
*/
public static byte[] generateHex(int size)
{
final byte[] array = new byte[size];
Rnd.nextBytes(array);
// Don't allow 0s inside the array!
for (int i = 0; i < array.length; i++)
{
while (array[i] == 0)
{
array[i] = (byte) Rnd.get(Byte.MAX_VALUE);
}
}
return array;
}
/**
* Replaces most invalid characters for the given string with an underscore.
* @param str the string that may contain invalid characters
* @return the string with invalid character replaced by underscores
*/
public static String replaceIllegalCharacters(String str)
{
String valid = str;
for (char c : ILLEGAL_CHARACTERS)
{
valid = valid.replace(c, '_');
}
return valid;
}
/**
* Verify if a file name is valid.
* @param name the name of the file
* @return {@code true} if the file name is valid, {@code false} otherwise
*/
public static boolean isValidFileName(String name)
{
final File f = new File(name);
try
{
f.getCanonicalPath();
return true;
}
catch (IOException e)
{
return false;
}
}
/**
* Split words with a space.
* @param input the string to split
* @return the split string
*/
public static String splitWords(String input)
{
return input.replaceAll("(\\p{Ll})(\\p{Lu})", "$1 $2");
}
/**
* Gets the next or same closest date from the specified days in {@code daysOfWeek Array} at specified {@code hour} and {@code min}.
* @param daysOfWeek the days of week
* @param hour the hour
* @param min the min
* @return the next or same date from the days of week at specified time
* @throws IllegalArgumentException if the {@code daysOfWeek Array} is empty.
*/
public static LocalDateTime getNextClosestDateTime(DayOfWeek[] daysOfWeek, int hour, int min) throws IllegalArgumentException
{
return getNextClosestDateTime(Arrays.asList(daysOfWeek), hour, min);
}
/**
* Gets the next or same closest date from the specified days in {@code daysOfWeek List} at specified {@code hour} and {@code min}.
* @param daysOfWeek the days of week
* @param hour the hour
* @param min the min
* @return the next or same date from the days of week at specified time
* @throws IllegalArgumentException if the {@code daysOfWeek List} is empty.
*/
public static LocalDateTime getNextClosestDateTime(List<DayOfWeek> daysOfWeek, int hour, int min) throws IllegalArgumentException
{
if (daysOfWeek.isEmpty())
{
throw new IllegalArgumentException("daysOfWeek should not be empty.");
}
final LocalDateTime dateNow = LocalDateTime.now();
final LocalDateTime dateNowWithDifferentTime = dateNow.withHour(hour).withMinute(min).withSecond(0);
// @formatter:off
return daysOfWeek.stream()
.map(d -> dateNowWithDifferentTime.with(TemporalAdjusters.nextOrSame(d)))
.filter(d -> d.isAfter(dateNow))
.min(Comparator.naturalOrder())
.orElse(dateNowWithDifferentTime.with(TemporalAdjusters.next(daysOfWeek.get(0))));
// @formatter:on
}
/**
* Method to get the stack trace of a Throwable into a String
* @param t Throwable to get the stacktrace from
* @return stack trace from Throwable as String
*/
public static String getStackTrace(Throwable t)
{
final StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
public static String getTraceString(StackTraceElement[] stackTraceElements)
{
final StringJoiner sj = new StringJoiner(Config.EOL);
for (StackTraceElement stackTraceElement : stackTraceElements)
{
sj.add(stackTraceElement.toString());
}
return sj.toString();
}
public static int min(int value1, int value2, int... values)
{
int min = Math.min(value1, value2);
for (int value : values)
{
if (min > value)
{
min = value;
}
}
return min;
}
public static int max(int value1, int value2, int... values)
{
int max = Math.max(value1, value2);
for (int value : values)
{
if (max < value)
{
max = value;
}
}
return max;
}
public static long min(long value1, long value2, long... values)
{
long min = Math.min(value1, value2);
for (long value : values)
{
if (min > value)
{
min = value;
}
}
return min;
}
public static long max(long value1, long value2, long... values)
{
long max = Math.max(value1, value2);
for (long value : values)
{
if (max < value)
{
max = value;
}
}
return max;
}
public static float min(float value1, float value2, float... values)
{
float min = Math.min(value1, value2);
for (float value : values)
{
if (min > value)
{
min = value;
}
}
return min;
}
public static float max(float value1, float value2, float... values)
{
float max = Math.max(value1, value2);
for (float value : values)
{
if (max < value)
{
max = value;
}
}
return max;
}
public static double min(double value1, double value2, double... values)
{
double min = Math.min(value1, value2);
for (double value : values)
{
if (min > value)
{
min = value;
}
}
return min;
}
public static double max(double value1, double value2, double... values)
{
double max = Math.max(value1, value2);
for (double value : values)
{
if (max < value)
{
max = value;
}
}
return max;
}
public static int getIndexOfMaxValue(int... array)
{
int index = 0;
for (int i = 1; i < array.length; i++)
{
if (array[i] > array[index])
{
index = i;
}
}
return index;
}
public static int getIndexOfMinValue(int... array)
{
int index = 0;
for (int i = 1; i < array.length; i++)
{
if (array[i] < array[index])
{
index = i;
}
}
return index;
}
/**
* Re-Maps a value from one range to another.
* @param input
* @param inputMin
* @param inputMax
* @param outputMin
* @param outputMax
* @return The mapped value
*/
public static int map(int input, int inputMin, int inputMax, int outputMin, int outputMax)
{
input = constrain(input, inputMin, inputMax);
return (((input - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)) + outputMin;
}
/**
* Re-Maps a value from one range to another.
* @param input
* @param inputMin
* @param inputMax
* @param outputMin
* @param outputMax
* @return The mapped value
*/
public static long map(long input, long inputMin, long inputMax, long outputMin, long outputMax)
{
input = constrain(input, inputMin, inputMax);
return (((input - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)) + outputMin;
}
/**
* Re-Maps a value from one range to another.
* @param input
* @param inputMin
* @param inputMax
* @param outputMin
* @param outputMax
* @return The mapped value
*/
public static double map(double input, double inputMin, double inputMax, double outputMin, double outputMax)
{
input = constrain(input, inputMin, inputMax);
return (((input - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)) + outputMin;
}
/**
* Constrains a number to be within a range.
* @param input the number to constrain, all data types
* @param min the lower end of the range, all data types
* @param max the upper end of the range, all data types
* @return input: if input is between min and max, min: if input is less than min, max: if input is greater than max
*/
public static int constrain(int input, int min, int max)
{
return (input < min) ? min : (input > max) ? max : input;
}
/**
* Constrains a number to be within a range.
* @param input the number to constrain, all data types
* @param min the lower end of the range, all data types
* @param max the upper end of the range, all data types
* @return input: if input is between min and max, min: if input is less than min, max: if input is greater than max
*/
public static long constrain(long input, long min, long max)
{
return (input < min) ? min : (input > max) ? max : input;
}
/**
* Constrains a number to be within a range.
* @param input the number to constrain, all data types
* @param min the lower end of the range, all data types
* @param max the upper end of the range, all data types
* @return input: if input is between min and max, min: if input is less than min, max: if input is greater than max
*/
public static double constrain(double input, double min, double max)
{
return (input < min) ? min : (input > max) ? max : input;
}
/**
* @param array - the array to look into
* @param obj - the object to search for
* @return {@code true} if the {@code array} contains the {@code obj}, {@code false} otherwise.
*/
public static boolean startsWith(String[] array, String obj)
{
for (String element : array)
{
if (element.startsWith(obj))
{
return true;
}
}
return false;
}
/**
* @param <T>
* @param array - the array to look into
* @param obj - the object to search for
* @return {@code true} if the {@code array} contains the {@code obj}, {@code false} otherwise.
*/
public static <T> boolean contains(T[] array, T obj)
{
for (T element : array)
{
if (element.equals(obj))
{
return true;
}
}
return false;
}
/**
* @param array - the array to look into
* @param obj - the integer to search for
* @return {@code true} if the {@code array} contains the {@code obj}, {@code false} otherwise
*/
public static boolean contains(int[] array, int obj)
{
for (int element : array)
{
if (element == obj)
{
return true;
}
}
return false;
}
/**
* @param array - the array to look into
* @param obj - the object to search for
* @param ignoreCase
* @return {@code true} if the {@code array} contains the {@code obj}, {@code false} otherwise.
*/
public static boolean contains(String[] array, String obj, boolean ignoreCase)
{
for (String element : array)
{
if (element.equals(obj) || (ignoreCase && element.equalsIgnoreCase(obj)))
{
return true;
}
}
return false;
}
public static int parseNextInt(StringTokenizer st, int defaultVal)
{
try
{
final String value = st.nextToken().trim();
return Integer.parseInt(value);
}
catch (Exception e)
{
return defaultVal;
}
}
public static int parseInt(String value, int defaultValue)
{
try
{
return Integer.parseInt(value);
}
catch (Exception e)
{
return defaultValue;
}
}
/**
* @param str - the string whose first letter to capitalize
* @return a string with the first letter of the {@code str} capitalized
*/
public static String capitalizeFirst(String str)
{
if ((str == null) || str.isEmpty())
{
return str;
}
final char[] arr = str.toCharArray();
final char c = arr[0];
if (Character.isLetter(c))
{
arr[0] = Character.toUpperCase(c);
}
return new String(arr);
}
/**
* Based on implode() in PHP
* @param <T>
* @param iteratable
* @param delim
* @return a delimited string for a given array of string elements.
*/
public static <T> String implode(Iterable<T> iteratable, String delim)
{
final StringJoiner sj = new StringJoiner(delim);
iteratable.forEach(o -> sj.add(o.toString()));
return sj.toString();
}
/**
* Based on implode() in PHP
* @param <T>
* @param array
* @param delim
* @return a delimited string for a given array of string elements.
*/
public static <T> String implode(T[] array, String delim)
{
final StringJoiner sj = new StringJoiner(delim);
for (T o : array)
{
sj.add(o.toString());
}
return sj.toString();
}
/**
* @param val
* @param format
* @return
*/
public static String formatDouble(double val, String format)
{
final DecimalFormat formatter = new DecimalFormat(format, new DecimalFormatSymbols(Locale.ENGLISH));
return formatter.format(val);
}
}

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 org.l2jmobius.commons.util;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.time.Duration;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.Config;
/**
* Thread to check for deadlocked threads.
* @author -Nemesiss- L2M
*/
public class DeadLockDetector extends Thread
{
private static Logger LOGGER = Logger.getLogger(DeadLockDetector.class.getName());
private final Duration _checkInterval;
private final Runnable _deadLockCallback;
private final ThreadMXBean tmx;
public DeadLockDetector(Duration checkInterval, Runnable deadLockCallback)
{
super("DeadLockDetector");
_checkInterval = checkInterval;
_deadLockCallback = deadLockCallback;
tmx = ManagementFactory.getThreadMXBean();
}
@Override
public void run()
{
boolean deadlock = false;
while (!deadlock)
{
try
{
final long[] ids = tmx.findDeadlockedThreads();
if (ids != null)
{
deadlock = true;
final ThreadInfo[] tis = tmx.getThreadInfo(ids, true, true);
final StringBuilder info = new StringBuilder();
info.append("DeadLock Found!");
info.append(Config.EOL);
for (ThreadInfo ti : tis)
{
info.append(ti.toString());
}
for (ThreadInfo ti : tis)
{
final LockInfo[] locks = ti.getLockedSynchronizers();
final MonitorInfo[] monitors = ti.getLockedMonitors();
if ((locks.length == 0) && (monitors.length == 0))
{
continue;
}
ThreadInfo dl = ti;
info.append("Java-level deadlock:");
info.append(Config.EOL);
info.append('\t');
info.append(dl.getThreadName());
info.append(" is waiting to lock ");
info.append(dl.getLockInfo().toString());
info.append(" which is held by ");
info.append(dl.getLockOwnerName());
info.append(Config.EOL);
while ((dl = tmx.getThreadInfo(new long[]
{
dl.getLockOwnerId()
}, true, true)[0]).getThreadId() != ti.getThreadId())
{
info.append('\t');
info.append(dl.getThreadName());
info.append(" is waiting to lock ");
info.append(dl.getLockInfo().toString());
info.append(" which is held by ");
info.append(dl.getLockOwnerName());
info.append(Config.EOL);
}
}
LOGGER.warning(info.toString());
if (_deadLockCallback != null)
{
_deadLockCallback.run();
}
}
Thread.sleep(_checkInterval.toMillis());
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "DeadLockDetector: ", e);
}
}
}
}

View File

@ -0,0 +1,145 @@
/*
* 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 org.l2jmobius.commons.util;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Queue;
/**
* @author UnAfraid
* @param <E>
*/
public class EmptyQueue<E> implements Queue<E>
{
private static final Queue<Object> EMPTY_QUEUE = new EmptyQueue<>();
@SuppressWarnings("unchecked")
public static <E> Queue<E> emptyQueue()
{
return (Queue<E>) EMPTY_QUEUE;
}
@Override
public int size()
{
return 0;
}
@Override
public boolean isEmpty()
{
return true;
}
@Override
public boolean contains(Object o)
{
return false;
}
@Override
public Iterator<E> iterator()
{
return Collections.<E> emptyIterator();
}
@Override
public Object[] toArray()
{
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a)
{
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o)
{
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(Collection<?> c)
{
return false;
}
@Override
public boolean addAll(Collection<? extends E> c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c)
{
throw new UnsupportedOperationException();
}
@Override
public void clear()
{
throw new UnsupportedOperationException();
}
@Override
public boolean add(E e)
{
throw new UnsupportedOperationException();
}
@Override
public boolean offer(E e)
{
throw new UnsupportedOperationException();
}
@Override
public E remove()
{
throw new UnsupportedOperationException();
}
@Override
public E poll()
{
throw new UnsupportedOperationException();
}
@Override
public E element()
{
throw new UnsupportedOperationException();
}
@Override
public E peek()
{
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,260 @@
/*
* 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 org.l2jmobius.commons.util;
import java.util.Arrays;
/**
* @author HorridoJoho
*/
public class HexUtils
{
// lookup table for hex characters
private static final char[] _NIBBLE_CHAR_LOOKUP =
{
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F'
};
private static final char[] _NEW_LINE_CHARS = System.getProperty("line.separator").toCharArray();
/**
* Method to generate the hexadecimal character presentation of a byte<br>
* This call is equivalent to {@link HexUtils#b2HexChars(byte, char[], int)} with parameters (data, null, 0)
* @param data byte to generate the hexadecimal character presentation from
* @return a new char array with exactly 2 elements
*/
public static char[] b2HexChars(byte data)
{
return b2HexChars(data, null, 0);
}
/**
* Method to generate the hexadecimal character presentation of a byte
* @param data byte to generate the hexadecimal character presentation from
* @param dstHexChars the char array the hexadecimal character presentation should be copied to, if this is null, dstOffset is ignored and a new char array with 2 elements is created
* @param dstOffset offset at which the hexadecimal character presentation is copied to dstHexChars
* @return the char array the hexadecimal character presentation was copied to
*/
public static char[] b2HexChars(byte data, char[] dstHexChars, int dstOffset)
{
if (dstHexChars == null)
{
dstHexChars = new char[2];
dstOffset = 0;
}
// /////////////////////////////
// NIBBLE LOOKUP
dstHexChars[dstOffset] = _NIBBLE_CHAR_LOOKUP[(data & 0xF0) >> 4];
dstHexChars[dstOffset + 1] = _NIBBLE_CHAR_LOOKUP[data & 0x0F];
return dstHexChars;
}
/**
* Method to generate the hexadecimal character presentation of an integer This call is equivalent to {@link HexUtils#int2HexChars(int, char[], int)} with parameters (data, null, 0)
* @param data integer to generate the hexadecimal character presentation from
* @return new char array with 8 elements
*/
public static char[] int2HexChars(int data)
{
return int2HexChars(data, new char[8], 0);
}
/**
* Method to generate the hexadecimal character presentation of an integer
* @param data integer to generate the hexadecimal character presentation from
* @param dstHexChars the char array the hexadecimal character presentation should be copied to, if this is null, dstOffset is ignored and a new char array with 8 elements is created
* @param dstOffset offset at which the hexadecimal character presentation is copied to dstHexChars
* @return the char array the hexadecimal character presentation was copied to
*/
public static char[] int2HexChars(int data, char[] dstHexChars, int dstOffset)
{
if (dstHexChars == null)
{
dstHexChars = new char[8];
dstOffset = 0;
}
b2HexChars((byte) ((data & 0xFF000000) >> 24), dstHexChars, dstOffset);
b2HexChars((byte) ((data & 0x00FF0000) >> 16), dstHexChars, dstOffset + 2);
b2HexChars((byte) ((data & 0x0000FF00) >> 8), dstHexChars, dstOffset + 4);
b2HexChars((byte) (data & 0x000000FF), dstHexChars, dstOffset + 6);
return dstHexChars;
}
/**
* Method to generate the hexadecimal character presentation of a byte array<br>
* This call is equivalent to {@link HexUtils#bArr2HexChars(byte[], int, int, char[], int)} with parameters (data, offset, len, null, 0)
* @param data byte array to generate the hexadecimal character presentation from
* @param offset offset where to start in data array
* @param len number of bytes to generate the hexadecimal character presentation from
* @return a new char array with len*2 elements
*/
public static char[] bArr2HexChars(byte[] data, int offset, int len)
{
return bArr2HexChars(data, offset, len, null, 0);
}
/**
* Method to generate the hexadecimal character presentation of a byte array
* @param data byte array to generate the hexadecimal character presentation from
* @param offset offset where to start in data array
* @param len number of bytes to generate the hexadecimal character presentation from
* @param dstHexChars the char array the hexadecimal character presentation should be copied to, if this is null, dstOffset is ignored and a new char array with len*2 elements is created
* @param dstOffset offset at which the hexadecimal character presentation is copied to dstHexChars
* @return the char array the hexadecimal character presentation was copied to
*/
public static char[] bArr2HexChars(byte[] data, int offset, int len, char[] dstHexChars, int dstOffset)
{
if (dstHexChars == null)
{
dstHexChars = new char[len * 2];
dstOffset = 0;
}
for (int dataIdx = offset, charsIdx = dstOffset; dataIdx < (len + offset); ++dataIdx, ++charsIdx)
{
// /////////////////////////////
// NIBBLE LOOKUP, we duplicate the code from b2HexChars here, we want to save a few cycles(for charsIdx increment)
dstHexChars[charsIdx] = _NIBBLE_CHAR_LOOKUP[(data[dataIdx] & 0xF0) >> 4];
dstHexChars[++charsIdx] = _NIBBLE_CHAR_LOOKUP[data[dataIdx] & 0x0F];
}
return dstHexChars;
}
public static char[] bArr2AsciiChars(byte[] data, int offset, int len)
{
return bArr2AsciiChars(data, offset, len, new char[len], 0);
}
public static char[] bArr2AsciiChars(byte[] data, int offset, int len, char[] dstAsciiChars, int dstOffset)
{
if (dstAsciiChars == null)
{
dstAsciiChars = new char[len];
dstOffset = 0;
}
for (int dataIdx = offset, charsIdx = dstOffset; dataIdx < (len + offset); ++dataIdx, ++charsIdx)
{
if ((data[dataIdx] > 0x1f) && (data[dataIdx] < 0x80))
{
dstAsciiChars[charsIdx] = (char) data[dataIdx];
}
else
{
dstAsciiChars[charsIdx] = '.';
}
}
return dstAsciiChars;
}
private static final int _HEX_ED_BPL = 16;
private static final int _HEX_ED_CPB = 2;
/**
* Method to generate the hexadecimal character representation of a byte array like in a hex editor<br>
* Line Format: {OFFSET} {HEXADECIMAL} {ASCII}({NEWLINE})<br>
* {OFFSET} = offset of the first byte in line(8 chars)<br>
* {HEXADECIMAL} = hexadecimal character representation({@link #_HEX_ED_BPL}*2 chars)<br>
* {ASCII} = ascii character presentation({@link #_HEX_ED_BPL} chars)
* @param data byte array to generate the hexadecimal character representation
* @param len the number of bytes to generate the hexadecimal character representation from
* @return byte array which contains the hexadecimal character representation of the given byte array
*/
public static char[] bArr2HexEdChars(byte[] data, int len)
{
// {OFFSET} {HEXADECIMAL} {ASCII}{NEWLINE}
final int lineLength = 9 + (_HEX_ED_BPL * _HEX_ED_CPB) + 1 + _HEX_ED_BPL + _NEW_LINE_CHARS.length;
final int lenBplMod = len % _HEX_ED_BPL;
// create text buffer
// 1. don't allocate a full last line if not _HEX_ED_BPL bytes are shown in last line
// 2. no new line at end of buffer
// BUG: when the length is multiple of _HEX_ED_BPL we erase the whole ascii space with this
// char[] textData = new char[lineLength * numLines - (_HEX_ED_BPL - (len % _HEX_ED_BPL)) - _NEW_LINE_CHARS.length];
// FIXED HERE
int numLines;
char[] textData;
if (lenBplMod == 0)
{
numLines = len / _HEX_ED_BPL;
textData = new char[(lineLength * numLines) - _NEW_LINE_CHARS.length];
}
else
{
numLines = (len / _HEX_ED_BPL) + 1;
textData = new char[(lineLength * numLines) - (_HEX_ED_BPL - (lenBplMod)) - _NEW_LINE_CHARS.length];
}
// performance penalty, only doing space filling in the loop is faster
// Arrays.fill(textData, ' ');
int dataOffset;
int dataLen;
int lineStart;
int lineHexDataStart;
int lineAsciiDataStart;
for (int i = 0; i < numLines; ++i)
{
dataOffset = i * _HEX_ED_BPL;
dataLen = Math.min(len - dataOffset, _HEX_ED_BPL);
lineStart = i * lineLength;
lineHexDataStart = lineStart + 9;
lineAsciiDataStart = lineHexDataStart + (_HEX_ED_BPL * _HEX_ED_CPB) + 1;
int2HexChars(dataOffset, textData, lineStart); // the offset of this line
textData[lineHexDataStart - 1] = ' '; // separate
bArr2HexChars(data, dataOffset, dataLen, textData, lineHexDataStart); // the data in hex
bArr2AsciiChars(data, dataOffset, dataLen, textData, lineAsciiDataStart); // the data in ascii
if (i < (numLines - 1))
{
textData[lineAsciiDataStart - 1] = ' '; // separate
System.arraycopy(_NEW_LINE_CHARS, 0, textData, lineAsciiDataStart + _HEX_ED_BPL, _NEW_LINE_CHARS.length); // the new line
}
else if (dataLen < _HEX_ED_BPL)
{
// last line which shows less than _HEX_ED_BPL bytes
final int lineHexDataEnd = lineHexDataStart + (dataLen * _HEX_ED_CPB);
Arrays.fill(textData, lineHexDataEnd, lineHexDataEnd + ((_HEX_ED_BPL - dataLen) * _HEX_ED_CPB) + 1, ' '); // spaces, for the last line if there are not _HEX_ED_BPL bytes
}
else
{
// last line which shows _HEX_ED_BPL bytes
textData[lineAsciiDataStart - 1] = ' '; // separate
}
}
return textData;
}
}

View File

@ -0,0 +1,156 @@
/*
* 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 org.l2jmobius.commons.util;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IPSubnet
{
private final byte[] _addr;
private final byte[] _mask;
private final boolean _isIPv4;
public IPSubnet(String input) throws UnknownHostException, NumberFormatException, ArrayIndexOutOfBoundsException
{
final int idx = input.indexOf("/");
if (idx > 0)
{
_addr = InetAddress.getByName(input.substring(0, idx)).getAddress();
_mask = getMask(Integer.parseInt(input.substring(idx + 1)), _addr.length);
_isIPv4 = _addr.length == 4;
if (!applyMask(_addr))
{
throw new UnknownHostException(input);
}
}
else
{
_addr = InetAddress.getByName(input).getAddress();
_mask = getMask(_addr.length * 8, _addr.length); // host, no need to check mask
_isIPv4 = _addr.length == 4;
}
}
public byte[] getAddress()
{
return _addr;
}
private boolean applyMask(byte[] addr)
{
// V4 vs V4 or V6 vs V6 checks
if (_isIPv4 == (addr.length == 4))
{
for (int i = 0; i < _addr.length; i++)
{
if ((addr[i] & _mask[i]) != _addr[i])
{
return false;
}
}
}
else
{
// check for embedded v4 in v6 addr (not done !)
if (_isIPv4)
{
// my V4 vs V6
for (int i = 0; i < _addr.length; i++)
{
if ((addr[i + 12] & _mask[i]) != _addr[i])
{
return false;
}
}
}
else
{
// my V6 vs V4
for (int i = 0; i < _addr.length; i++)
{
if ((addr[i] & _mask[i + 12]) != _addr[i + 12])
{
return false;
}
}
}
}
return true;
}
@Override
public String toString()
{
int size = 0;
for (byte element : _mask)
{
size += Integer.bitCount((element & 0xFF));
}
try
{
return InetAddress.getByAddress(_addr) + "/" + size;
}
catch (UnknownHostException e)
{
return "Invalid";
}
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o instanceof IPSubnet)
{
return applyMask(((IPSubnet) o).getAddress());
}
else if (o instanceof InetAddress)
{
return applyMask(((InetAddress) o).getAddress());
}
return false;
}
private static byte[] getMask(int n, int maxLength) throws UnknownHostException
{
if ((n > (maxLength << 3)) || (n < 0))
{
throw new UnknownHostException("Invalid netmask: " + n);
}
final byte[] result = new byte[maxLength];
for (int i = 0; i < maxLength; i++)
{
result[i] = (byte) 0xFF;
}
for (int i = (maxLength << 3) - 1; i >= n; i--)
{
result[i >> 3] = (byte) (result[i >> 3] << 1);
}
return result;
}
}

View File

@ -0,0 +1,830 @@
/*
* 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 org.l2jmobius.commons.util;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.util.file.filter.XMLFilter;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.holders.MinionHolder;
import org.l2jmobius.gameserver.model.holders.SkillHolder;
/**
* Interface for XML parsers.
* @author Zoey76
*/
public interface IXmlReader
{
Logger LOGGER = Logger.getLogger(IXmlReader.class.getName());
String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
/** The default file filter, ".xml" files only. */
XMLFilter XML_FILTER = new XMLFilter();
/**
* This method can be used to load/reload the data.<br>
* It's highly recommended to clear the data storage, either the list or map.
*/
void load();
/**
* Wrapper for {@link #parseFile(File)} method.
* @param path the relative path to the datapack root of the XML file to parse.
*/
default void parseDatapackFile(String path)
{
parseFile(new File(Config.DATAPACK_ROOT, path));
}
/**
* Parses a single XML file.<br>
* If the file was successfully parsed, call {@link #parseDocument(Document, File)} for the parsed document.<br>
* <b>Validation is enforced.</b>
* @param f the XML file to parse.
*/
default void parseFile(File f)
{
if (!getCurrentFileFilter().accept(f))
{
LOGGER.warning("Could not parse " + f.getName() + " is not a file or it doesn't exist!");
return;
}
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(isValidating());
dbf.setIgnoringComments(isIgnoringComments());
try
{
dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
final DocumentBuilder db = dbf.newDocumentBuilder();
db.setErrorHandler(new XMLErrorHandler());
parseDocument(db.parse(f), f);
}
catch (SAXParseException e)
{
LOGGER.log(Level.WARNING, "Could not parse file: " + f.getName() + " at line: " + e.getLineNumber() + ", column: " + e.getColumnNumber() + " :", e);
return;
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "Could not parse file: " + f.getName(), e);
return;
}
}
/**
* Checks if XML validation is enabled.
* @return {@code true} if its enabled, {@code false} otherwise
*/
default boolean isValidating()
{
return true;
}
/**
* Checks if XML comments are ignored.
* @return {@code true} if its comments are ignored, {@code false} otherwise
*/
default boolean isIgnoringComments()
{
return true;
}
/**
* Wrapper for {@link #parseDirectory(File, boolean)}.
* @param file the path to the directory where the XML files are.
* @return {@code false} if it fails to find the directory, {@code true} otherwise.
*/
default boolean parseDirectory(File file)
{
return parseDirectory(file, false);
}
/**
* Wrapper for {@link #parseDirectory(File, boolean)}.
* @param path the path to the directory where the XML files are
* @param recursive parses all sub folders if there is
* @return {@code false} if it fails to find the directory, {@code true} otherwise
*/
default boolean parseDatapackDirectory(String path, boolean recursive)
{
return parseDirectory(new File(Config.DATAPACK_ROOT, path), recursive);
}
/**
* Loads all XML files from {@code path} and calls {@link #parseFile(File)} for each one of them.
* @param dir the directory object to scan.
* @param recursive parses all sub folders if there is.
* @return {@code false} if it fails to find the directory, {@code true} otherwise.
*/
default boolean parseDirectory(File dir, boolean recursive)
{
if (!dir.exists())
{
LOGGER.warning("Folder " + dir.getAbsolutePath() + " doesn't exist!");
return false;
}
if (Config.THREADS_FOR_LOADING)
{
final Collection<ScheduledFuture<?>> jobs = ConcurrentHashMap.newKeySet();
final File[] listOfFiles = dir.listFiles();
for (File file : listOfFiles)
{
if (recursive && file.isDirectory())
{
parseDirectory(file, recursive);
}
else if (getCurrentFileFilter().accept(file))
{
jobs.add(ThreadPool.schedule(() ->
{
parseFile(file);
}, 0));
}
}
while (!jobs.isEmpty())
{
for (ScheduledFuture<?> job : jobs)
{
if ((job == null) || job.isDone() || job.isCancelled())
{
jobs.remove(job);
}
}
}
}
else
{
final File[] listOfFiles = dir.listFiles();
for (File file : listOfFiles)
{
if (recursive && file.isDirectory())
{
parseDirectory(file, recursive);
}
else if (getCurrentFileFilter().accept(file))
{
parseFile(file);
}
}
}
return true;
}
/**
* Abstract method that when implemented will parse the current document.<br>
* Is expected to be call from {@link #parseFile(File)}.
* @param doc the current document to parse
* @param f the current file
*/
void parseDocument(Document doc, File f);
/**
* Parses a boolean value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Boolean parseBoolean(Node node, Boolean defaultValue)
{
return node != null ? Boolean.valueOf(node.getNodeValue()) : defaultValue;
}
/**
* Parses a boolean value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Boolean parseBoolean(Node node)
{
return parseBoolean(node, null);
}
/**
* Parses a boolean value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Boolean parseBoolean(NamedNodeMap attrs, String name)
{
return parseBoolean(attrs.getNamedItem(name));
}
/**
* Parses a boolean value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Boolean parseBoolean(NamedNodeMap attrs, String name, Boolean defaultValue)
{
return parseBoolean(attrs.getNamedItem(name), defaultValue);
}
/**
* Parses a byte value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Byte parseByte(Node node, Byte defaultValue)
{
return node != null ? Byte.decode(node.getNodeValue()) : defaultValue;
}
/**
* Parses a byte value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Byte parseByte(Node node)
{
return parseByte(node, null);
}
/**
* Parses a byte value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Byte parseByte(NamedNodeMap attrs, String name)
{
return parseByte(attrs.getNamedItem(name));
}
/**
* Parses a byte value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Byte parseByte(NamedNodeMap attrs, String name, Byte defaultValue)
{
return parseByte(attrs.getNamedItem(name), defaultValue);
}
/**
* Parses a short value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Short parseShort(Node node, Short defaultValue)
{
return node != null ? Short.decode(node.getNodeValue()) : defaultValue;
}
/**
* Parses a short value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Short parseShort(Node node)
{
return parseShort(node, null);
}
/**
* Parses a short value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Short parseShort(NamedNodeMap attrs, String name)
{
return parseShort(attrs.getNamedItem(name));
}
/**
* Parses a short value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Short parseShort(NamedNodeMap attrs, String name, Short defaultValue)
{
return parseShort(attrs.getNamedItem(name), defaultValue);
}
/**
* Parses an int value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default int parseInt(Node node, Integer defaultValue)
{
return node != null ? Integer.decode(node.getNodeValue()) : defaultValue;
}
/**
* Parses an int value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default int parseInt(Node node)
{
return parseInt(node, -1);
}
/**
* Parses an integer value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Integer parseInteger(Node node, Integer defaultValue)
{
return node != null ? Integer.decode(node.getNodeValue()) : defaultValue;
}
/**
* Parses an integer value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Integer parseInteger(Node node)
{
return parseInteger(node, null);
}
/**
* Parses an integer value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Integer parseInteger(NamedNodeMap attrs, String name)
{
return parseInteger(attrs.getNamedItem(name));
}
/**
* Parses an integer value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Integer parseInteger(NamedNodeMap attrs, String name, Integer defaultValue)
{
return parseInteger(attrs.getNamedItem(name), defaultValue);
}
/**
* Parses a long value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Long parseLong(Node node, Long defaultValue)
{
return node != null ? Long.decode(node.getNodeValue()) : defaultValue;
}
/**
* Parses a long value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Long parseLong(Node node)
{
return parseLong(node, null);
}
/**
* Parses a long value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Long parseLong(NamedNodeMap attrs, String name)
{
return parseLong(attrs.getNamedItem(name));
}
/**
* Parses a long value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Long parseLong(NamedNodeMap attrs, String name, Long defaultValue)
{
return parseLong(attrs.getNamedItem(name), defaultValue);
}
/**
* Parses a float value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Float parseFloat(Node node, Float defaultValue)
{
return node != null ? Float.valueOf(node.getNodeValue()) : defaultValue;
}
/**
* Parses a float value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Float parseFloat(Node node)
{
return parseFloat(node, null);
}
/**
* Parses a float value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Float parseFloat(NamedNodeMap attrs, String name)
{
return parseFloat(attrs.getNamedItem(name));
}
/**
* Parses a float value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Float parseFloat(NamedNodeMap attrs, String name, Float defaultValue)
{
return parseFloat(attrs.getNamedItem(name), defaultValue);
}
/**
* Parses a double value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Double parseDouble(Node node, Double defaultValue)
{
return node != null ? Double.valueOf(node.getNodeValue()) : defaultValue;
}
/**
* Parses a double value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Double parseDouble(Node node)
{
return parseDouble(node, null);
}
/**
* Parses a double value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default Double parseDouble(NamedNodeMap attrs, String name)
{
return parseDouble(attrs.getNamedItem(name));
}
/**
* Parses a double value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default Double parseDouble(NamedNodeMap attrs, String name, Double defaultValue)
{
return parseDouble(attrs.getNamedItem(name), defaultValue);
}
/**
* Parses a string value.
* @param node the node to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default String parseString(Node node, String defaultValue)
{
return node != null ? node.getNodeValue() : defaultValue;
}
/**
* Parses a string value.
* @param node the node to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default String parseString(Node node)
{
return parseString(node, null);
}
/**
* Parses a string value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @return if the node is not null, the value of the parsed node, otherwise null
*/
default String parseString(NamedNodeMap attrs, String name)
{
return parseString(attrs.getNamedItem(name));
}
/**
* Parses a string value.
* @param attrs the attributes
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null, the value of the parsed node, otherwise the default value
*/
default String parseString(NamedNodeMap attrs, String name, String defaultValue)
{
return parseString(attrs.getNamedItem(name), defaultValue);
}
default Location parseLocation(Node n)
{
final NamedNodeMap attrs = n.getAttributes();
final int x = parseInteger(attrs, "x");
final int y = parseInteger(attrs, "y");
final int z = parseInteger(attrs, "z");
final int heading = parseInteger(attrs, "heading", 0);
return new Location(x, y, z, heading);
}
/**
* Parses an enumerated value.
* @param <T> the enumerated type
* @param node the node to parse
* @param clazz the class of the enumerated
* @param defaultValue the default value
* @return if the node is not null and the node value is valid the parsed value, otherwise the default value
*/
default <T extends Enum<T>> T parseEnum(Node node, Class<T> clazz, T defaultValue)
{
if (node == null)
{
return defaultValue;
}
try
{
return Enum.valueOf(clazz, node.getNodeValue());
}
catch (IllegalArgumentException e)
{
LOGGER.warning("Invalid value specified for node: " + node.getNodeName() + " specified value: " + node.getNodeValue() + " should be enum value of \"" + clazz.getSimpleName() + "\" using default value: " + defaultValue);
return defaultValue;
}
}
/**
* Parses an enumerated value.
* @param <T> the enumerated type
* @param node the node to parse
* @param clazz the class of the enumerated
* @return if the node is not null and the node value is valid the parsed value, otherwise null
*/
default <T extends Enum<T>> T parseEnum(Node node, Class<T> clazz)
{
return parseEnum(node, clazz, null);
}
/**
* Parses an enumerated value.
* @param <T> the enumerated type
* @param attrs the attributes
* @param clazz the class of the enumerated
* @param name the name of the attribute to parse
* @return if the node is not null and the node value is valid the parsed value, otherwise null
*/
default <T extends Enum<T>> T parseEnum(NamedNodeMap attrs, Class<T> clazz, String name)
{
return parseEnum(attrs.getNamedItem(name), clazz);
}
/**
* Parses an enumerated value.
* @param <T> the enumerated type
* @param attrs the attributes
* @param clazz the class of the enumerated
* @param name the name of the attribute to parse
* @param defaultValue the default value
* @return if the node is not null and the node value is valid the parsed value, otherwise the default value
*/
default <T extends Enum<T>> T parseEnum(NamedNodeMap attrs, Class<T> clazz, String name, T defaultValue)
{
return parseEnum(attrs.getNamedItem(name), clazz, defaultValue);
}
/**
* @param node
* @return parses all attributes to a Map
*/
default Map<String, Object> parseAttributes(Node node)
{
final NamedNodeMap attrs = node.getAttributes();
final Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < attrs.getLength(); i++)
{
final Node att = attrs.item(i);
map.put(att.getNodeName(), att.getNodeValue());
}
return map;
}
/**
* @param n
* @return a map of parameters
*/
default Map<String, Object> parseParameters(Node n)
{
final Map<String, Object> parameters = new HashMap<>();
for (Node parameters_node = n.getFirstChild(); parameters_node != null; parameters_node = parameters_node.getNextSibling())
{
NamedNodeMap attrs = parameters_node.getAttributes();
switch (parameters_node.getNodeName().toLowerCase())
{
case "param":
{
parameters.put(parseString(attrs, "name"), parseString(attrs, "value"));
break;
}
case "skill":
{
parameters.put(parseString(attrs, "name"), new SkillHolder(parseInteger(attrs, "id"), parseInteger(attrs, "level")));
break;
}
case "location":
{
parameters.put(parseString(attrs, "name"), new Location(parseInteger(attrs, "x"), parseInteger(attrs, "y"), parseInteger(attrs, "z"), parseInteger(attrs, "heading", 0)));
break;
}
case "minions":
{
final List<MinionHolder> minions = new ArrayList<>(1);
for (Node minions_node = parameters_node.getFirstChild(); minions_node != null; minions_node = minions_node.getNextSibling())
{
if (minions_node.getNodeName().equalsIgnoreCase("npc"))
{
attrs = minions_node.getAttributes();
minions.add(new MinionHolder(parseInteger(attrs, "id"), parseInteger(attrs, "count"), parseInteger(attrs, "respawnTime"), parseInteger(attrs, "weightPoint")));
}
}
if (!minions.isEmpty())
{
parameters.put(parseString(parameters_node.getAttributes(), "name"), minions);
}
break;
}
}
}
return parameters;
}
/**
* Executes action for each child of node
* @param node
* @param action
*/
default void forEach(Node node, Consumer<Node> action)
{
forEach(node, a -> true, action);
}
/**
* Executes action for each child that matches nodeName
* @param node
* @param nodeName
* @param action
*/
default void forEach(Node node, String nodeName, Consumer<Node> action)
{
forEach(node, innerNode -> nodeName.equalsIgnoreCase(innerNode.getNodeName()), action);
}
/**
* Executes action for each child of node if matches the filter specified
* @param node
* @param filter
* @param action
*/
default void forEach(Node node, Predicate<Node> filter, Consumer<Node> action)
{
final NodeList list = node.getChildNodes();
for (int i = 0; i < list.getLength(); i++)
{
final Node targetNode = list.item(i);
if (filter.test(targetNode))
{
action.accept(targetNode);
}
}
}
/**
* @param node
* @return {@code true} if the node is an element type, {@code false} otherwise
*/
static boolean isNode(Node node)
{
return node.getNodeType() == Node.ELEMENT_NODE;
}
/**
* @param node
* @return {@code true} if the node is an element type, {@code false} otherwise
*/
static boolean isText(Node node)
{
return node.getNodeType() == Node.TEXT_NODE;
}
/**
* Gets the current file filter.
* @return the current file filter
*/
default FileFilter getCurrentFileFilter()
{
return XML_FILTER;
}
/**
* Simple XML error handler.
* @author Zoey76
*/
class XMLErrorHandler implements ErrorHandler
{
@Override
public void warning(SAXParseException e) throws SAXParseException
{
throw e;
}
@Override
public void error(SAXParseException e) throws SAXParseException
{
throw e;
}
@Override
public void fatalError(SAXParseException e) throws SAXParseException
{
throw e;
}
}
}

View File

@ -0,0 +1,161 @@
/*
* 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 org.l2jmobius.commons.util;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
/*
* A class to control the maximum number of lines to be stored in a Document
*
* Excess lines can be removed from the start or end of the Document
* depending on your requirement.
*
* a) if you append text to the Document, then you would want to remove lines
* from the start.
* b) if you insert text at the beginning of the Document, then you would
* want to remove lines from the end.
*/
public class LimitLinesDocumentListener implements DocumentListener
{
private int _maximumLines;
private final boolean _isRemoveFromStart;
/*
* Specify the number of lines to be stored in the Document. Extra lines will be removed from the start of the Document.
*/
public LimitLinesDocumentListener(int maximumLines)
{
this(maximumLines, true);
}
/*
* Specify the number of lines to be stored in the Document. Extra lines will be removed from the start or end of the Document, depending on the boolean value specified.
*/
public LimitLinesDocumentListener(int maximumLines, boolean isRemoveFromStart)
{
setLimitLines(maximumLines);
_isRemoveFromStart = isRemoveFromStart;
}
/*
* Return the maximum number of lines to be stored in the Document.
*/
public int getLimitLines()
{
return _maximumLines;
}
/*
* Set the maximum number of lines to be stored in the Document.
*/
public void setLimitLines(int maximumLines)
{
if (maximumLines < 1)
{
String message = "Maximum lines must be greater than 0";
throw new IllegalArgumentException(message);
}
_maximumLines = maximumLines;
}
/*
* Handle insertion of new text into the Document.
*/
@Override
public void insertUpdate(DocumentEvent e)
{
// Changes to the Document can not be done within the listener so we need to add the processing to the end of the EDT.
SwingUtilities.invokeLater(() -> removeLines(e));
}
@Override
public void removeUpdate(DocumentEvent e)
{
}
@Override
public void changedUpdate(DocumentEvent e)
{
}
/*
* Remove lines from the Document when necessary.
*/
private void removeLines(DocumentEvent e)
{
// The root Element of the Document will tell us the total number of line in the Document.
Document document = e.getDocument();
Element root = document.getDefaultRootElement();
while (root.getElementCount() > _maximumLines)
{
if (_isRemoveFromStart)
{
removeFromStart(document, root);
}
else
{
removeFromEnd(document, root);
}
}
}
/*
* Remove lines from the start of the Document
*/
private void removeFromStart(Document document, Element root)
{
Element line = root.getElement(0);
int end = line.getEndOffset();
try
{
document.remove(0, end);
}
catch (BadLocationException ble)
{
System.out.println(ble);
}
}
/*
* Remove lines from the end of the Document
*/
private void removeFromEnd(Document document, Element root)
{
// We use start minus 1 to make sure we remove the newline character of the previous line.
Element line = root.getElement(root.getElementCount() - 1);
int start = line.getStartOffset();
int end = line.getEndOffset();
try
{
document.remove(start - 1, end - start);
}
catch (BadLocationException ble)
{
System.out.println(ble);
}
}
}

View File

@ -0,0 +1,381 @@
/*
* 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 org.l2jmobius.commons.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;
/**
* Simplifies loading of property files and adds logging if a non existing property is requested.
* @author NosBit
*/
public class PropertiesParser
{
private static final Logger LOGGER = Logger.getLogger(PropertiesParser.class.getName());
private final Properties _properties = new Properties();
private final File _file;
public PropertiesParser(String name)
{
this(new File(name));
}
public PropertiesParser(File file)
{
_file = file;
try (FileInputStream fileInputStream = new FileInputStream(file))
{
try (InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, Charset.defaultCharset()))
{
_properties.load(inputStreamReader);
}
}
catch (Exception e)
{
LOGGER.warning("[" + _file.getName() + "] There was an error loading config reason: " + e.getMessage());
}
}
public boolean containskey(String key)
{
return _properties.containsKey(key);
}
private String getValue(String key)
{
final String value = _properties.getProperty(key);
return value != null ? value.trim() : null;
}
public boolean getBoolean(String key, boolean defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
if (value.equalsIgnoreCase("true"))
{
return true;
}
else if (value.equalsIgnoreCase("false"))
{
return false;
}
else
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be \"boolean\" using default value: " + defaultValue);
return defaultValue;
}
}
public byte getByte(String key, byte defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
try
{
return Byte.parseByte(value);
}
catch (NumberFormatException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be \"byte\" using default value: " + defaultValue);
return defaultValue;
}
}
public short getShort(String key, short defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
try
{
return Short.parseShort(value);
}
catch (NumberFormatException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be \"short\" using default value: " + defaultValue);
return defaultValue;
}
}
public int getInt(String key, int defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
try
{
return Integer.parseInt(value);
}
catch (NumberFormatException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be \"int\" using default value: " + defaultValue);
return defaultValue;
}
}
public long getLong(String key, long defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
try
{
return Long.parseLong(value);
}
catch (NumberFormatException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be \"long\" using default value: " + defaultValue);
return defaultValue;
}
}
public float getFloat(String key, float defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
try
{
return Float.parseFloat(value);
}
catch (NumberFormatException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be \"float\" using default value: " + defaultValue);
return defaultValue;
}
}
public double getDouble(String key, double defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
try
{
return Double.parseDouble(value);
}
catch (NumberFormatException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be \"double\" using default value: " + defaultValue);
return defaultValue;
}
}
public String getString(String key, String defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
return value;
}
public <T extends Enum<T>> T getEnum(String key, Class<T> clazz, T defaultValue)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValue);
return defaultValue;
}
try
{
return Enum.valueOf(clazz, value);
}
catch (IllegalArgumentException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be enum value of \"" + clazz.getSimpleName() + "\" using default value: " + defaultValue);
return defaultValue;
}
}
/**
* @param durationPattern
* @param defaultValue
* @return {@link Duration} object by the durationPattern specified, {@code null} in case of malformed pattern.
*/
public Duration getDuration(String durationPattern, String defaultValue)
{
return getDuration(durationPattern, defaultValue, null);
}
/**
* @param durationPattern
* @param defaultValue
* @param defaultDuration
* @return {@link Duration} object by the durationPattern specified, the defaultDuration in case of malformed pattern.
*/
public Duration getDuration(String durationPattern, String defaultValue, Duration defaultDuration)
{
final String value = getString(durationPattern, defaultValue);
try
{
return TimeUtil.parseDuration(value);
}
catch (IllegalStateException e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + durationPattern + " specified value: " + value + " should be time patttern using default value: " + defaultValue);
}
return defaultDuration;
}
/**
* @param key
* @param separator
* @param defaultValues
* @return int array
*/
public int[] getIntArray(String key, String separator, int... defaultValues)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValues);
return defaultValues;
}
try
{
final String[] data = value.trim().split(separator);
final int[] result = new int[data.length];
for (int i = 0; i < data.length; i++)
{
result[i] = Integer.decode(data[i].trim());
}
return result;
}
catch (Exception e)
{
LOGGER.warning("[+_file.getName()+] Invalid value specified for key: " + key + " specified value: " + value + " should be array using default value: " + defaultValues);
return defaultValues;
}
}
/**
* @param <T>
* @param key
* @param separator
* @param clazz
* @param defaultValues
* @return enum array
*/
@SafeVarargs
public final <T extends Enum<T>> T[] getEnumArray(String key, String separator, Class<T> clazz, T... defaultValues)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValues);
return defaultValues;
}
try
{
final String[] data = value.trim().split(separator);
@SuppressWarnings("unchecked")
final T[] result = (T[]) Array.newInstance(clazz, data.length);
for (int i = 0; i < data.length; i++)
{
result[i] = Enum.valueOf(clazz, data[i]);
}
return result;
}
catch (Exception e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be array using default value: " + defaultValues);
return defaultValues;
}
}
/**
* @param <T>
* @param key
* @param separator
* @param clazz
* @param defaultValues
* @return list
*/
@SafeVarargs
public final <T extends Enum<T>> List<T> getEnumList(String key, String separator, Class<T> clazz, T... defaultValues)
{
final String value = getValue(key);
if (value == null)
{
LOGGER.warning("[" + _file.getName() + "] missing property for key: " + key + " using default value: " + defaultValues);
return Arrays.asList(defaultValues);
}
try
{
final String[] data = value.trim().split(separator);
final List<T> result = new ArrayList<>(data.length);
for (String element : data)
{
result.add(Enum.valueOf(clazz, element));
}
return result;
}
catch (Exception e)
{
LOGGER.warning("[" + _file.getName() + "] Invalid value specified for key: " + key + " specified value: " + value + " should be array using default value: " + defaultValues);
return Arrays.asList(defaultValues);
}
}
}

View File

@ -0,0 +1,156 @@
/*
* 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 org.l2jmobius.commons.util;
import java.util.Random;
/**
* @author Mobius
*/
public class Rnd
{
/**
* Thread-specific random number generator.<br>
* Each is seeded with the thread ID, so the sequence of random numbers are unique between threads.
*/
private static ThreadLocal<Random> RANDOM = new ThreadLocal<>()
{
@Override
protected Random initialValue()
{
return new Random(System.nanoTime() + Thread.currentThread().getId());
}
};
/**
* @return a random boolean value.
*/
public static boolean nextBoolean()
{
return RANDOM.get().nextBoolean();
}
/**
* Generates random bytes and places them into a user-supplied byte array. The number of random bytes produced is equal to the length of the byte array.
* @param bytes the byte array to fill with random bytes.
*/
public static void nextBytes(byte[] bytes)
{
RANDOM.get().nextBytes(bytes);
}
/**
* @param bound (int)
* @return a random int value between zero (inclusive) and the specified bound (exclusive).
*/
public static int get(int bound)
{
return (int) (RANDOM.get().nextDouble() * bound);
}
/**
* @param origin (int)
* @param bound (int)
* @return a random int value between the specified origin (inclusive) and the specified bound (inclusive).
*/
public static int get(int origin, int bound)
{
if (origin == bound)
{
return origin;
}
return origin + (int) (((bound - origin) + 1) * RANDOM.get().nextDouble());
}
/**
* @return a random int value.
*/
public static int nextInt()
{
return RANDOM.get().nextInt();
}
/**
* @param bound (long)
* @return a random long value between zero (inclusive) and the specified bound (exclusive).
*/
public static long get(long bound)
{
return (long) (RANDOM.get().nextDouble() * bound);
}
/**
* @param origin (long)
* @param bound (long)
* @return a random long value between the specified origin (inclusive) and the specified bound (inclusive).
*/
public static long get(long origin, long bound)
{
if (origin == bound)
{
return origin;
}
return origin + (long) (((bound - origin) + 1) * RANDOM.get().nextDouble());
}
/**
* @return a random long value.
*/
public static long nextLong()
{
return RANDOM.get().nextLong();
}
/**
* @param bound (double)
* @return a random double value between zero (inclusive) and the specified bound (exclusive).
*/
public static double get(double bound)
{
return RANDOM.get().nextDouble() * bound;
}
/**
* @param origin (double)
* @param bound (double)
* @return a random double value between the specified origin (inclusive) and the specified bound (inclusive).
*/
public static double get(double origin, double bound)
{
if (origin == bound)
{
return origin;
}
return origin + (((bound - origin) + 1) * RANDOM.get().nextDouble());
}
/**
* @return a random double value between zero (inclusive) and one (exclusive).
*/
public static double nextDouble()
{
return RANDOM.get().nextDouble();
}
/**
* @return the next random, Gaussian ("normally") distributed double value with mean 0.0 and standard deviation 1.0 from this random number generator's sequence.
*/
public static double nextGaussian()
{
return RANDOM.get().nextGaussian();
}
}

View File

@ -0,0 +1,78 @@
/*
* 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 org.l2jmobius.commons.util;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JWindow;
/**
* @author Mobius
*/
public class SplashScreen extends JWindow
{
Image image;
/**
* @param path of image file
* @param time in milliseconds
* @param parent frame to set visible after time ends
*/
public SplashScreen(String path, long time, JFrame parent)
{
setBackground(new Color(0, 255, 0, 0)); // Transparency.
image = Toolkit.getDefaultToolkit().getImage(path);
ImageIcon imageIcon = new ImageIcon(image);
setSize(imageIcon.getIconWidth(), imageIcon.getIconHeight());
setLocationRelativeTo(null);
setAlwaysOnTop(true);
setVisible(true);
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
setVisible(false);
if (parent != null)
{
// Make parent visible.
parent.setVisible(true);
// Focus parent window.
parent.toFront();
parent.setState(Frame.ICONIFIED);
parent.setState(Frame.NORMAL);
}
dispose();
}
}, imageIcon.getIconWidth() > 0 ? time : 100);
}
@Override
public void paint(Graphics g)
{
g.drawImage(image, 0, 0, null);
}
}

View File

@ -0,0 +1,276 @@
/*
* 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 org.l2jmobius.commons.util;
import org.l2jmobius.Config;
/**
* String utilities optimized for the best performance.<br>
* <h1>How to Use It</h1>
* <h2>concat() or append()</h2> If concatenating strings<br>
* in single call, use StringUtil.concat(), otherwise use StringUtil.append()<br>
* and its variants.<br>
* <br>
* <h2>Minimum Calls</h2><br>
* Bad:
*
* <pre>
* final StringBuilder sbString = new StringBuilder();
* StringUtil.append(sbString, &quot;text 1&quot;, String.valueOf(npcId));
* StringUtil.append(&quot;text 2&quot;);
* </pre>
*
* Good:
*
* <pre>
* final StringBuilder sbString = new StringBuilder();
* StringUtil.append(sbString, &quot;text 1&quot;, String.valueOf(npcId), &quot;text 2&quot;);
* </pre>
*
* Why?<br/>
* Because the less calls you do, the less memory re-allocations have to be done<br>
* so the whole text fits into the memory and less array copy tasks has to be<br>
* performed. So if using less calls, less memory is used and string concatenation is faster.<br>
* <br>
* <h2>Size Hints for Loops</h2><br>
* Bad:
*
* <pre>
* final StringBuilder sbString = new StringBuilder();
* StringUtil.append(sbString, &quot;header start&quot;, someText, &quot;header end&quot;);
* for (int i = 0; i &lt; 50; i++)
* {
* StringUtil.append(sbString, &quot;text 1&quot;, stringArray[i], &quot;text 2&quot;);
* }
* </pre>
*
* Good:
*
* <pre>
* final StringBuilder sbString = StringUtil.startAppend(1300, &quot;header start&quot;, someText, &quot;header end&quot;);
* for (int i = 0; i &lt; 50; i++)
* {
* StringUtil.append(sbString, &quot;text 1&quot;, stringArray[i], &quot;text 2&quot;);
* }
* </pre>
*
* Why?<br/>
* When using StringUtil.append(), memory is only allocated to fit in the strings in method argument. So on each loop new memory for the string has to be allocated and old string has to be copied to the new string. With size hint, even if the size hint is above the needed memory, memory is saved
* because new memory has not to be allocated on each cycle. Also it is much faster if no string copy tasks has to be performed. So if concatenating strings in a loop, count approximately the size and set it as the hint for the string builder size. It's better to make the size hint little bit larger
* rather than smaller.<br/>
* In case there is no text appended before the cycle, just use <code>new
* StringBuilder(1300)</code>.<br>
* <br>
* <h2>Concatenation and Constants</h2><br>
* Bad:
*
* <pre>
* StringUtil.concat(&quot;text 1 &quot;, &quot;text 2&quot;, String.valueOf(npcId));
* </pre>
*
* Good:
*
* <pre>
* StringUtil.concat(&quot;text 1 &quot; + &quot;text 2&quot;, String.valueOf(npcId));
* </pre>
*
* or
*
* <pre>
* StringUtil.concat(&quot;text 1 text 2&quot;, String.valueOf(npcId));
* </pre>
*
* Why?<br/>
* It saves some cycles when determining size of memory that needs to be allocated because less strings are passed to concat() method. But do not use + for concatenation of non-constant strings, that degrades performance and makes extra memory allocations needed.<br>
* <h2>Concatenation and Constant Variables</h2> Bad:
*
* <pre>
* String glue = &quot;some glue&quot;;
* StringUtil.concat(&quot;text 1&quot;, glue, &quot;text 2&quot;, glue, String.valueOf(npcId));
* </pre>
*
* Good:
*
* <pre>
* final String glue = &quot;some glue&quot;;
* StringUtil.concat(&quot;text 1&quot; + glue + &quot;text2&quot; + glue, String.valueOf(npcId));
* </pre>
*
* Why? Because when using <code>final</code> keyword, the <code>glue</code> is marked as constant string and compiler treats it as a constant string so it is able to create string "text1some gluetext2some glue" during the compilation. But this only works in case the value is known at compilation
* time, so this cannot be used for cases like <code>final String objectIdString =
* String.valueOf(getObjectId)</code>.<br>
* <br>
* <h2>StringBuilder Reuse</h2><br>
* Bad:
*
* <pre>
* final StringBuilder sbString1 = new StringBuilder();
* StringUtil.append(sbString1, &quot;text 1&quot;, String.valueOf(npcId), &quot;text 2&quot;);
* ... // output of sbString1, it is no more needed
* final StringBuilder sbString2 = new StringBuilder();
* StringUtil.append(sbString2, &quot;text 3&quot;, String.valueOf(npcId), &quot;text 4&quot;);
* </pre>
*
* Good:
*
* <pre>
* final StringBuilder sbString = new StringBuilder();
* StringUtil.append(sbString, &quot;text 1&quot;, String.valueOf(npcId), &quot;text 2&quot;);
* ... // output of sbString, it is no more needed
* sbString.setLength(0);
* StringUtil.append(sbString, &quot;text 3&quot;, String.valueOf(npcId), &quot;text 4&quot;);
* </pre>
*
* Why?</br>
* In first case, new memory has to be allocated for the second string. In second case already allocated memory is reused, but only in case the new string is not longer than the previously allocated string. Anyway, the second way is better because the string either fits in the memory and some memory
* is saved, or it does not fit in the memory, and in that case it works as in the first case.
* <h2>Primitives to Strings</h2> To convert primitives to string, use String.valueOf().<br>
* <br>
* <h2>How much faster is it?</h2><br>
* Here are some results of my tests. Count is number of strings concatenated. Don't take the numbers as 100% true as the numbers are affected by other programs running on my computer at the same time. Anyway, from the results it is obvious that using StringBuilder with predefined size is the
* fastest (and also most memory efficient) solution. It is about 5 times faster when concatenating 7 strings, compared to TextBuilder. Also, with more strings concatenated, the difference between StringBuilder and TextBuilder gets larger. In code, there are many cases, where there are concatenated
* 50+ strings so the time saving is even greater.<br>
*
* <pre>
* Count: 2
* TextBuilder: 1893
* TextBuilder with size: 1703
* String: 1033
* StringBuilder: 993
* StringBuilder with size: 1024
* Count: 3
* TextBuilder: 1973
* TextBuilder with size: 1872
* String: 2583
* StringBuilder: 1633
* StringBuilder with size: 1156
* Count: 4
* TextBuilder: 2188
* TextBuilder with size: 2229
* String: 4207
* StringBuilder: 1816
* StringBuilder with size: 1444
* Count: 5
* TextBuilder: 9185
* TextBuilder with size: 9464
* String: 6937
* StringBuilder: 2745
* StringBuilder with size: 1882
* Count: 6
* TextBuilder: 9785
* TextBuilder with size: 10082
* String: 9471
* StringBuilder: 2889
* StringBuilder with size: 1857
* Count: 7
* TextBuilder: 10169
* TextBuilder with size: 10528
* String: 12746
* StringBuilder: 3081
* StringBuilder with size: 2139
* </pre>
*
* @author fordfrog
*/
public class StringUtil
{
private StringUtil()
{
}
/**
* Concatenates strings.
* @param strings strings to be concatenated
* @return concatenated string
*/
public static String concat(String... strings)
{
final StringBuilder sbString = new StringBuilder();
for (String string : strings)
{
sbString.append(string);
}
return sbString.toString();
}
/**
* Creates new string builder with size initializated to <code>sizeHint</code>, unless total length of strings is greater than <code>sizeHint</code>.
* @param sizeHint hint for string builder size allocation
* @param strings strings to be appended
* @return created string builder
*/
public static StringBuilder startAppend(int sizeHint, String... strings)
{
final int length = getLength(strings);
final StringBuilder sbString = new StringBuilder(sizeHint > length ? sizeHint : length);
for (String string : strings)
{
sbString.append(string);
}
return sbString;
}
/**
* Appends strings to existing string builder.
* @param sbString string builder
* @param strings strings to be appended
*/
public static void append(StringBuilder sbString, String... strings)
{
sbString.ensureCapacity(sbString.length() + getLength(strings));
for (String string : strings)
{
sbString.append(string);
}
}
public static int getLength(Iterable<String> strings)
{
int length = 0;
for (String string : strings)
{
length += (string == null) ? 4 : string.length();
}
return length;
}
/**
* Counts total length of all the strings.
* @param strings array of strings
* @return total length of all the strings
*/
public static int getLength(String[] strings)
{
int length = 0;
for (String string : strings)
{
length += (string == null) ? 4 : string.length();
}
return length;
}
public static String getTraceString(StackTraceElement[] trace)
{
final StringBuilder sbString = new StringBuilder();
for (StackTraceElement element : trace)
{
sbString.append(element.toString()).append(Config.EOL);
}
return sbString.toString();
}
}

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 org.l2jmobius.commons.util;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
/**
* @author UnAfraid
*/
public class TimeUtil
{
private static int findIndexOfNonDigit(CharSequence text)
{
for (int i = 0; i < text.length(); i++)
{
if (Character.isDigit(text.charAt(i)))
{
continue;
}
return i;
}
return -1;
}
/**
* Parses patterns like:
* <ul>
* <li>1min or 10mins</li>
* <li>1day or 10days</li>
* <li>1week or 4weeks</li>
* <li>1month or 12months</li>
* <li>1year or 5years</li>
* </ul>
* @param datePattern
* @return {@link Duration} object converted by the date pattern specified.
* @throws IllegalStateException when malformed pattern specified.
*/
public static Duration parseDuration(String datePattern)
{
final int index = findIndexOfNonDigit(datePattern);
if (index == -1)
{
throw new IllegalStateException("Incorrect time format given: " + datePattern);
}
try
{
final int val = Integer.parseInt(datePattern.substring(0, index));
final String type = datePattern.substring(index);
final ChronoUnit unit;
switch (type.toLowerCase())
{
case "sec":
case "secs":
{
unit = ChronoUnit.SECONDS;
break;
}
case "min":
case "mins":
{
unit = ChronoUnit.MINUTES;
break;
}
case "hour":
case "hours":
{
unit = ChronoUnit.HOURS;
break;
}
case "day":
case "days":
{
unit = ChronoUnit.DAYS;
break;
}
case "week":
case "weeks":
{
unit = ChronoUnit.WEEKS;
break;
}
case "month":
case "months":
{
unit = ChronoUnit.MONTHS;
break;
}
case "year":
case "years":
{
unit = ChronoUnit.YEARS;
break;
}
default:
{
unit = ChronoUnit.valueOf(type);
if (unit == null)
{
throw new IllegalStateException("Incorrect format: " + type + " !!");
}
}
}
return Duration.of(val, unit);
}
catch (Exception e)
{
throw new IllegalStateException("Incorrect time format given: " + datePattern + " val: " + datePattern.substring(0, index));
}
}
}

View File

@ -0,0 +1,149 @@
/*
* 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 org.l2jmobius.commons.util.crypt;
import javax.crypto.SecretKey;
import org.l2jmobius.commons.network.ICrypt;
import org.l2jmobius.commons.util.Rnd;
import io.netty.buffer.ByteBuf;
/**
* @author NosBit
*/
public class LoginCrypt implements ICrypt
{
private static final byte[] STATIC_BLOWFISH_KEY =
{
(byte) 0x6b,
(byte) 0x60,
(byte) 0xcb,
(byte) 0x5b,
(byte) 0x82,
(byte) 0xce,
(byte) 0x90,
(byte) 0xb1,
(byte) 0xcc,
(byte) 0x2b,
(byte) 0x6c,
(byte) 0x55,
(byte) 0x6c,
(byte) 0x6c,
(byte) 0x6c,
(byte) 0x6c
};
private static final BlowfishEngine STATIC_BLOWFISH_ENGINE = new BlowfishEngine();
static
{
STATIC_BLOWFISH_ENGINE.init(STATIC_BLOWFISH_KEY);
}
private final BlowfishEngine _blowfishEngine = new BlowfishEngine();
private boolean _static = true;
public LoginCrypt(SecretKey blowfishKey)
{
_blowfishEngine.init(blowfishKey.getEncoded());
}
/*
* (non-Javadoc)
* @see com.l2jserver.commons.network.ICrypt#encrypt(io.netty.buffer.ByteBuf)
*/
@Override
public void encrypt(ByteBuf buf)
{
// Checksum & XOR Key or Checksum only
buf.writeZero(_static ? 16 : 12);
// Padding
buf.writeZero(8 - (buf.readableBytes() % 8));
if (_static)
{
_static = false;
int key = Rnd.nextInt();
buf.skipBytes(4); // The first 4 bytes are ignored
while (buf.readerIndex() < (buf.writerIndex() - 8))
{
int data = buf.readIntLE();
key += data;
data ^= key;
buf.setIntLE(buf.readerIndex() - 4, data);
}
buf.setIntLE(buf.readerIndex(), key);
buf.resetReaderIndex();
final byte[] block = new byte[8];
while (buf.isReadable(8))
{
buf.readBytes(block);
STATIC_BLOWFISH_ENGINE.encryptBlock(block, 0);
buf.setBytes(buf.readerIndex() - block.length, block);
}
}
else
{
int checksum = 0;
while (buf.isReadable(8))
{
checksum ^= buf.readIntLE();
}
buf.setIntLE(buf.readerIndex(), checksum);
buf.resetReaderIndex();
final byte[] block = new byte[8];
while (buf.isReadable(8))
{
buf.readBytes(block);
_blowfishEngine.encryptBlock(block, 0);
buf.setBytes(buf.readerIndex() - block.length, block);
}
}
}
/*
* (non-Javadoc)
* @see com.l2jserver.commons.network.ICrypt#decrypt(io.netty.buffer.ByteBuf)
*/
@Override
public void decrypt(ByteBuf buf)
{
// Packet size must be multiple of 8
if ((buf.readableBytes() % 8) != 0)
{
buf.clear();
return;
}
final byte[] block = new byte[8];
while (buf.isReadable(8))
{
buf.readBytes(block);
_blowfishEngine.decryptBlock(block, 0);
buf.setBytes(buf.readerIndex() - block.length, block);
}
// TODO: verify checksum also dont forget!
}
}

View File

@ -0,0 +1,218 @@
/*
* 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 org.l2jmobius.commons.util.crypt;
/**
* Class to use a blowfish cipher with ECB processing.<br>
* Static methods are present to append/check the checksum of<br>
* packets exchanged between the following partners:<br>
* Login Server <-> Game Client<br>
* Login Server <-> Game Server<br>
* Also a static method is provided for the initial xor encryption between Login Server <-> Game Client.
*/
public class NewCrypt
{
private final BlowfishEngine _cipher;
/**
* @param blowfishKey
*/
public NewCrypt(byte[] blowfishKey)
{
_cipher = new BlowfishEngine();
_cipher.init(blowfishKey);
}
public NewCrypt(String key)
{
this(key.getBytes());
}
/**
* Equivalent to calling {@link #verifyChecksum(byte[], int, int)} with parameters (raw, 0, raw.length)
* @param raw data array to be verified
* @return true when the checksum of the data is valid, false otherwise
*/
public static boolean verifyChecksum(byte[] raw)
{
return verifyChecksum(raw, 0, raw.length);
}
/**
* Method to verify the checksum of a packet received by login server from game client.<br>
* This is also used for game server <-> login server communication.
* @param raw data array to be verified
* @param offset at which offset to start verifying
* @param size number of bytes to verify
* @return true if the checksum of the data is valid, false otherwise
*/
public static boolean verifyChecksum(byte[] raw, int offset, int size)
{
// check if size is multiple of 4 and if there is more then only the checksum
if (((size & 3) != 0) || (size <= 4))
{
return false;
}
long chksum = 0;
final int count = size - 4;
long check = -1;
int i;
for (i = offset; i < count; i += 4)
{
check = raw[i] & 0xff;
check |= (raw[i + 1] << 8) & 0xff00;
check |= (raw[i + 2] << 0x10) & 0xff0000;
check |= (raw[i + 3] << 0x18) & 0xff000000;
chksum ^= check;
}
check = raw[i] & 0xff;
check |= (raw[i + 1] << 8) & 0xff00;
check |= (raw[i + 2] << 0x10) & 0xff0000;
check |= (raw[i + 3] << 0x18) & 0xff000000;
return check == chksum;
}
/**
* Equivalent to calling {@link #appendChecksum(byte[], int, int)} with parameters (raw, 0, raw.length)
* @param raw data array to compute the checksum from
*/
public static void appendChecksum(byte[] raw)
{
appendChecksum(raw, 0, raw.length);
}
/**
* Method to append packet checksum at the end of the packet.
* @param raw data array to compute the checksum from
* @param offset offset where to start in the data array
* @param size number of bytes to compute the checksum from
*/
public static void appendChecksum(byte[] raw, int offset, int size)
{
long chksum = 0;
final int count = size - 4;
long ecx;
int i;
for (i = offset; i < count; i += 4)
{
ecx = raw[i] & 0xff;
ecx |= (raw[i + 1] << 8) & 0xff00;
ecx |= (raw[i + 2] << 0x10) & 0xff0000;
ecx |= (raw[i + 3] << 0x18) & 0xff000000;
chksum ^= ecx;
}
ecx = raw[i] & 0xff;
ecx |= (raw[i + 1] << 8) & 0xff00;
ecx |= (raw[i + 2] << 0x10) & 0xff0000;
ecx |= (raw[i + 3] << 0x18) & 0xff000000;
raw[i] = (byte) (chksum & 0xff);
raw[i + 1] = (byte) ((chksum >> 0x08) & 0xff);
raw[i + 2] = (byte) ((chksum >> 0x10) & 0xff);
raw[i + 3] = (byte) ((chksum >> 0x18) & 0xff);
}
/**
* Packet is first XOR encoded with <code>key</code> then, the last 4 bytes are overwritten with the the XOR "key".<br>
* Thus this assume that there is enough room for the key to fit without overwriting data.
* @param raw The raw bytes to be encrypted
* @param key The 4 bytes (int) XOR key
*/
public static void encXORPass(byte[] raw, int key)
{
encXORPass(raw, 0, raw.length, key);
}
/**
* Packet is first XOR encoded with <code>key</code> then, the last 4 bytes are overwritten with the the XOR "key".<br>
* Thus this assume that there is enough room for the key to fit without overwriting data.
* @param raw The raw bytes to be encrypted
* @param offset The beginning of the data to be encrypted
* @param size Length of the data to be encrypted
* @param key The 4 bytes (int) XOR key
*/
static void encXORPass(byte[] raw, int offset, int size, int key)
{
final int stop = size - 8;
int pos = 4 + offset;
int edx;
int ecx = key; // Initial xor key
while (pos < stop)
{
edx = raw[pos] & 0xFF;
edx |= (raw[pos + 1] & 0xFF) << 8;
edx |= (raw[pos + 2] & 0xFF) << 16;
edx |= (raw[pos + 3] & 0xFF) << 24;
ecx += edx;
edx ^= ecx;
raw[pos++] = (byte) (edx & 0xFF);
raw[pos++] = (byte) ((edx >> 8) & 0xFF);
raw[pos++] = (byte) ((edx >> 16) & 0xFF);
raw[pos++] = (byte) ((edx >> 24) & 0xFF);
}
raw[pos++] = (byte) (ecx & 0xFF);
raw[pos++] = (byte) ((ecx >> 8) & 0xFF);
raw[pos++] = (byte) ((ecx >> 16) & 0xFF);
raw[pos++] = (byte) ((ecx >> 24) & 0xFF);
}
/**
* Method to decrypt using Blowfish-Blockcipher in ECB mode.<br>
* The results will be directly placed inside {@code raw} array.<br>
* This method does not do any error checking, since the calling code<br>
* should ensure sizes.
* @param raw the data array to be decrypted
* @param offset the offset at which to start decrypting
* @param size the number of bytes to be decrypted
*/
public void decrypt(byte[] raw, int offset, int size)
{
for (int i = offset; i < (offset + size); i += 8)
{
_cipher.decryptBlock(raw, i);
}
}
/**
* Method to encrypt using Blowfish-Blockcipher in ECB mode.<br>
* The results will be directly placed inside {@code raw} array.<br>
* This method does not do any error checking, since the calling code should ensure sizes.
* @param raw the data array to be decrypted
* @param offset the offset at which to start decrypting
* @param size the number of bytes to be decrypted
*/
public void crypt(byte[] raw, int offset, int size)
{
for (int i = offset; i < (offset + size); i += 8)
{
_cipher.encryptBlock(raw, i);
}
}
}

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 org.l2jmobius.commons.util.crypt;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.logging.Logger;
public class ScrambledKeyPair
{
private static Logger LOGGER = Logger.getLogger(ScrambledKeyPair.class.getName());
private final KeyPair _pair;
private final byte[] _scrambledModulus;
public ScrambledKeyPair(KeyPair pPair)
{
_pair = pPair;
_scrambledModulus = scrambleModulus(((RSAPublicKey) _pair.getPublic()).getModulus());
}
private byte[] scrambleModulus(BigInteger modulus)
{
byte[] scrambledMod = modulus.toByteArray();
if ((scrambledMod.length == 0x81) && (scrambledMod[0] == 0x00))
{
final byte[] temp = new byte[0x80];
System.arraycopy(scrambledMod, 1, temp, 0, 0x80);
scrambledMod = temp;
}
// step 1 : 0x4d-0x50 <-> 0x00-0x04
for (int i = 0; i < 4; i++)
{
final byte temp = scrambledMod[0x00 + i];
scrambledMod[0x00 + i] = scrambledMod[0x4d + i];
scrambledMod[0x4d + i] = temp;
}
// step 2 : xor first 0x40 bytes with last 0x40 bytes
for (int i = 0; i < 0x40; i++)
{
scrambledMod[i] = (byte) (scrambledMod[i] ^ scrambledMod[0x40 + i]);
}
// step 3 : xor bytes 0x0d-0x10 with bytes 0x34-0x38
for (int i = 0; i < 4; i++)
{
scrambledMod[0x0d + i] = (byte) (scrambledMod[0x0d + i] ^ scrambledMod[0x34 + i]);
}
// step 4 : xor last 0x40 bytes with first 0x40 bytes
for (int i = 0; i < 0x40; i++)
{
scrambledMod[0x40 + i] = (byte) (scrambledMod[0x40 + i] ^ scrambledMod[i]);
}
LOGGER.finer("Modulus was scrambled");
return scrambledMod;
}
public byte[] getScrambledModulus()
{
return _scrambledModulus;
}
public Key getPrivateKey()
{
return _pair.getPrivate();
}
public Key getPublicKey()
{
return _pair.getPublic();
}
}

View File

@ -0,0 +1,43 @@
/*
* 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 org.l2jmobius.commons.util.file.filter;
import java.io.File;
import java.io.FileFilter;
/**
* @author lasarus
*/
public class ExtFilter implements FileFilter
{
private final String _ext;
public ExtFilter(String ext)
{
_ext = ext;
}
@Override
public boolean accept(File f)
{
if ((f == null) || !f.isFile())
{
return false;
}
return f.getName().toLowerCase().endsWith(_ext);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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 org.l2jmobius.commons.util.file.filter;
import java.io.File;
import java.io.FileFilter;
/**
* Specialized {@link FileFilter} class.<br>
* Accepts <b>files</b> ending with ".htm" and ".html" only.
* @author Zoey76
*/
public class HTMLFilter implements FileFilter
{
@Override
public boolean accept(File f)
{
if ((f == null) || !f.isFile())
{
return false;
}
final String name = f.getName().toLowerCase();
return name.endsWith(".htm") || name.endsWith(".html");
}
}

View File

@ -0,0 +1,33 @@
/*
* 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 org.l2jmobius.commons.util.file.filter;
import java.io.File;
/**
* Specialized {@link XMLFilter} class.<br>
* Accepts <b>files</b> matching "numbers".xml only.
* @author UnAfraid
*/
public class NumericNameFilter extends XMLFilter
{
@Override
public boolean accept(File f)
{
return super.accept(f) && f.getName().matches("\\d+\\.xml");
}
}

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 org.l2jmobius.commons.util.file.filter;
/**
* Specialized {@link ExtFilter} class.<br>
* Accepts <b>files</b> ending with ".sql" only.
* @author Zoey76
*/
public class SQLFilter extends ExtFilter
{
public SQLFilter()
{
super(".sql");
}
}

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 org.l2jmobius.commons.util.file.filter;
/**
* Specialized {@link ExtFilter} class.<br>
* Accepts files ending with ".xml" only.
* @author mrTJO
*/
public class XMLFilter extends ExtFilter
{
public XMLFilter()
{
super(".xml");
}
}

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 org.l2jmobius.gameserver;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.gameserver.model.clan.Clan;
import org.l2jmobius.gameserver.model.entity.Fort;
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
/**
* Class managing periodical events with castle
* @author Vice - 2008
*/
public class FortUpdater implements Runnable
{
private static Logger LOGGER = Logger.getLogger(FortUpdater.class.getName());
private final Clan _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, Clan 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)
{
LOGGER.log(Level.WARNING, "", e);
}
}
public int getRunCount()
{
return _runCount;
}
}

View File

@ -0,0 +1,520 @@
/*
* 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 org.l2jmobius.gameserver;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.time.Duration;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.commons.enums.ServerMode;
import org.l2jmobius.commons.util.DeadLockDetector;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.data.sql.impl.AnnouncementsTable;
import org.l2jmobius.gameserver.data.sql.impl.CharNameTable;
import org.l2jmobius.gameserver.data.sql.impl.CharSummonTable;
import org.l2jmobius.gameserver.data.sql.impl.ClanTable;
import org.l2jmobius.gameserver.data.sql.impl.CrestTable;
import org.l2jmobius.gameserver.data.sql.impl.OfflineTradersTable;
import org.l2jmobius.gameserver.data.xml.impl.ActionData;
import org.l2jmobius.gameserver.data.xml.impl.AdminData;
import org.l2jmobius.gameserver.data.xml.impl.AlchemyData;
import org.l2jmobius.gameserver.data.xml.impl.AppearanceItemData;
import org.l2jmobius.gameserver.data.xml.impl.ArmorSetsData;
import org.l2jmobius.gameserver.data.xml.impl.AttendanceRewardData;
import org.l2jmobius.gameserver.data.xml.impl.BeautyShopData;
import org.l2jmobius.gameserver.data.xml.impl.BuyListData;
import org.l2jmobius.gameserver.data.xml.impl.CategoryData;
import org.l2jmobius.gameserver.data.xml.impl.ClanHallData;
import org.l2jmobius.gameserver.data.xml.impl.ClanMasteryData;
import org.l2jmobius.gameserver.data.xml.impl.ClanRewardData;
import org.l2jmobius.gameserver.data.xml.impl.ClanShopData;
import org.l2jmobius.gameserver.data.xml.impl.ClassListData;
import org.l2jmobius.gameserver.data.xml.impl.CombinationItemsData;
import org.l2jmobius.gameserver.data.xml.impl.CubicData;
import org.l2jmobius.gameserver.data.xml.impl.DailyMissionData;
import org.l2jmobius.gameserver.data.xml.impl.DoorData;
import org.l2jmobius.gameserver.data.xml.impl.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.impl.EnchantItemGroupsData;
import org.l2jmobius.gameserver.data.xml.impl.EnchantItemHPBonusData;
import org.l2jmobius.gameserver.data.xml.impl.EnchantItemOptionsData;
import org.l2jmobius.gameserver.data.xml.impl.EnchantSkillGroupsData;
import org.l2jmobius.gameserver.data.xml.impl.EnsoulData;
import org.l2jmobius.gameserver.data.xml.impl.EquipmentUpgradeData;
import org.l2jmobius.gameserver.data.xml.impl.EventEngineData;
import org.l2jmobius.gameserver.data.xml.impl.ExperienceData;
import org.l2jmobius.gameserver.data.xml.impl.ExtendDropData;
import org.l2jmobius.gameserver.data.xml.impl.FakePlayerData;
import org.l2jmobius.gameserver.data.xml.impl.FenceData;
import org.l2jmobius.gameserver.data.xml.impl.FishingData;
import org.l2jmobius.gameserver.data.xml.impl.HennaData;
import org.l2jmobius.gameserver.data.xml.impl.HitConditionBonusData;
import org.l2jmobius.gameserver.data.xml.impl.InitialEquipmentData;
import org.l2jmobius.gameserver.data.xml.impl.InitialShortcutData;
import org.l2jmobius.gameserver.data.xml.impl.ItemCrystallizationData;
import org.l2jmobius.gameserver.data.xml.impl.KarmaData;
import org.l2jmobius.gameserver.data.xml.impl.LuckyGameData;
import org.l2jmobius.gameserver.data.xml.impl.MonsterBookData;
import org.l2jmobius.gameserver.data.xml.impl.MultisellData;
import org.l2jmobius.gameserver.data.xml.impl.NpcData;
import org.l2jmobius.gameserver.data.xml.impl.NpcNameLocalisationData;
import org.l2jmobius.gameserver.data.xml.impl.OptionData;
import org.l2jmobius.gameserver.data.xml.impl.PetDataTable;
import org.l2jmobius.gameserver.data.xml.impl.PetSkillData;
import org.l2jmobius.gameserver.data.xml.impl.PlayerTemplateData;
import org.l2jmobius.gameserver.data.xml.impl.PlayerXpPercentLostData;
import org.l2jmobius.gameserver.data.xml.impl.PrimeShopData;
import org.l2jmobius.gameserver.data.xml.impl.RecipeData;
import org.l2jmobius.gameserver.data.xml.impl.ResidenceFunctionsData;
import org.l2jmobius.gameserver.data.xml.impl.SayuneData;
import org.l2jmobius.gameserver.data.xml.impl.SecondaryAuthData;
import org.l2jmobius.gameserver.data.xml.impl.SendMessageLocalisationData;
import org.l2jmobius.gameserver.data.xml.impl.ShuttleData;
import org.l2jmobius.gameserver.data.xml.impl.SiegeScheduleData;
import org.l2jmobius.gameserver.data.xml.impl.SkillData;
import org.l2jmobius.gameserver.data.xml.impl.SkillTreesData;
import org.l2jmobius.gameserver.data.xml.impl.SpawnsData;
import org.l2jmobius.gameserver.data.xml.impl.StaticObjectData;
import org.l2jmobius.gameserver.data.xml.impl.TeleportersData;
import org.l2jmobius.gameserver.data.xml.impl.TransformData;
import org.l2jmobius.gameserver.data.xml.impl.VariationData;
import org.l2jmobius.gameserver.datatables.BotReportTable;
import org.l2jmobius.gameserver.datatables.EventDroplist;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.geoengine.GeoEngine;
import org.l2jmobius.gameserver.handler.ConditionHandler;
import org.l2jmobius.gameserver.handler.DailyMissionHandler;
import org.l2jmobius.gameserver.handler.EffectHandler;
import org.l2jmobius.gameserver.handler.SkillConditionHandler;
import org.l2jmobius.gameserver.idfactory.IdFactory;
import org.l2jmobius.gameserver.instancemanager.AirShipManager;
import org.l2jmobius.gameserver.instancemanager.AntiFeedManager;
import org.l2jmobius.gameserver.instancemanager.BoatManager;
import org.l2jmobius.gameserver.instancemanager.CastleManager;
import org.l2jmobius.gameserver.instancemanager.CastleManorManager;
import org.l2jmobius.gameserver.instancemanager.ClanEntryManager;
import org.l2jmobius.gameserver.instancemanager.ClanHallAuctionManager;
import org.l2jmobius.gameserver.instancemanager.CommissionManager;
import org.l2jmobius.gameserver.instancemanager.CursedWeaponsManager;
import org.l2jmobius.gameserver.instancemanager.CustomMailManager;
import org.l2jmobius.gameserver.instancemanager.DBSpawnManager;
import org.l2jmobius.gameserver.instancemanager.FactionManager;
import org.l2jmobius.gameserver.instancemanager.FakePlayerChatManager;
import org.l2jmobius.gameserver.instancemanager.FortManager;
import org.l2jmobius.gameserver.instancemanager.FortSiegeManager;
import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager;
import org.l2jmobius.gameserver.instancemanager.GraciaSeedsManager;
import org.l2jmobius.gameserver.instancemanager.GrandBossManager;
import org.l2jmobius.gameserver.instancemanager.InstanceManager;
import org.l2jmobius.gameserver.instancemanager.ItemAuctionManager;
import org.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import org.l2jmobius.gameserver.instancemanager.MailManager;
import org.l2jmobius.gameserver.instancemanager.MapRegionManager;
import org.l2jmobius.gameserver.instancemanager.MatchingRoomManager;
import org.l2jmobius.gameserver.instancemanager.MentorManager;
import org.l2jmobius.gameserver.instancemanager.PcCafePointsManager;
import org.l2jmobius.gameserver.instancemanager.PetitionManager;
import org.l2jmobius.gameserver.instancemanager.PremiumManager;
import org.l2jmobius.gameserver.instancemanager.PunishmentManager;
import org.l2jmobius.gameserver.instancemanager.QuestManager;
import org.l2jmobius.gameserver.instancemanager.SellBuffsManager;
import org.l2jmobius.gameserver.instancemanager.ServerRestartManager;
import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager;
import org.l2jmobius.gameserver.instancemanager.SiegeManager;
import org.l2jmobius.gameserver.instancemanager.WalkingManager;
import org.l2jmobius.gameserver.instancemanager.ZoneManager;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.entity.Hero;
import org.l2jmobius.gameserver.model.events.EventDispatcher;
import org.l2jmobius.gameserver.model.olympiad.Olympiad;
import org.l2jmobius.gameserver.model.votereward.VoteSystem;
import org.l2jmobius.gameserver.network.ClientNetworkManager;
import org.l2jmobius.gameserver.network.NpcStringId;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.loginserver.LoginServerNetworkManager;
import org.l2jmobius.gameserver.network.telnet.TelnetServer;
import org.l2jmobius.gameserver.scripting.ScriptEngineManager;
import org.l2jmobius.gameserver.taskmanager.TaskManager;
import org.l2jmobius.gameserver.ui.Gui;
import org.l2jmobius.gameserver.util.Broadcast;
public class GameServer
{
private static final Logger LOGGER = Logger.getLogger(GameServer.class.getName());
private final DeadLockDetector _deadDetectThread;
private static GameServer INSTANCE;
public static final Calendar dateTimeServerStarted = Calendar.getInstance();
public long getUsedMemoryMB()
{
return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1048576;
}
public DeadLockDetector getDeadLockDetectorThread()
{
return _deadDetectThread;
}
public GameServer() throws Exception
{
final long serverLoadStart = System.currentTimeMillis();
// GUI
if (!GraphicsEnvironment.isHeadless())
{
System.out.println("GameServer: Running in GUI mode.");
new Gui();
}
// Create log folder
final File logFolder = new File(".", "log");
logFolder.mkdir();
// Create input stream for log file -- or store file data into memory
try (InputStream is = new FileInputStream(new File("./log.cfg")))
{
LogManager.getLogManager().readConfiguration(is);
}
// Initialize config
Config.load(ServerMode.GAME);
printSection("Database");
DatabaseFactory.init();
printSection("ThreadPool");
ThreadPool.init();
printSection("IdFactory");
if (!IdFactory.getInstance().isInitialized())
{
LOGGER.severe(getClass().getSimpleName() + ": Could not read object IDs from database. Please check your configuration.");
throw new Exception("Could not initialize the ID factory!");
}
// load script engines
printSection("Scripting Engine");
EventDispatcher.getInstance();
ScriptEngineManager.getInstance();
printSection("Telnet");
TelnetServer.getInstance();
printSection("World");
// start game time control early
GameTimeController.init();
World.getInstance();
MapRegionManager.getInstance();
ZoneManager.getInstance();
DoorData.getInstance();
FenceData.getInstance();
AnnouncementsTable.getInstance();
GlobalVariablesManager.getInstance();
printSection("Data");
ActionData.getInstance();
CategoryData.getInstance();
SecondaryAuthData.getInstance();
CombinationItemsData.getInstance();
SayuneData.getInstance();
ClanRewardData.getInstance();
DailyMissionHandler.getInstance().executeScript();
DailyMissionData.getInstance();
printSection("Skills");
SkillConditionHandler.getInstance().executeScript();
EffectHandler.getInstance().executeScript();
EnchantSkillGroupsData.getInstance();
SkillTreesData.getInstance();
SkillData.getInstance();
PetSkillData.getInstance();
printSection("Items");
ConditionHandler.getInstance().executeScript();
ItemTable.getInstance();
EnchantItemGroupsData.getInstance();
EnchantItemData.getInstance();
EnchantItemOptionsData.getInstance();
ItemCrystallizationData.getInstance();
OptionData.getInstance();
VariationData.getInstance();
EnsoulData.getInstance();
EnchantItemHPBonusData.getInstance();
BuyListData.getInstance();
MultisellData.getInstance();
EquipmentUpgradeData.getInstance();
RecipeData.getInstance();
ArmorSetsData.getInstance();
FishingData.getInstance();
HennaData.getInstance();
PrimeShopData.getInstance();
PcCafePointsManager.getInstance();
AppearanceItemData.getInstance();
AlchemyData.getInstance();
CommissionManager.getInstance();
LuckyGameData.getInstance();
AttendanceRewardData.getInstance();
printSection("Characters");
ClassListData.getInstance();
InitialEquipmentData.getInstance();
InitialShortcutData.getInstance();
ExperienceData.getInstance();
PlayerXpPercentLostData.getInstance();
KarmaData.getInstance();
HitConditionBonusData.getInstance();
PlayerTemplateData.getInstance();
CharNameTable.getInstance();
AdminData.getInstance();
PetDataTable.getInstance();
CubicData.getInstance();
CharSummonTable.getInstance().init();
BeautyShopData.getInstance();
MentorManager.getInstance();
if (Config.FACTION_SYSTEM_ENABLED)
{
FactionManager.getInstance();
}
if (Config.PREMIUM_SYSTEM_ENABLED)
{
LOGGER.info("PremiumManager: Premium system is enabled.");
PremiumManager.getInstance();
}
printSection("Clans");
ClanTable.getInstance();
ResidenceFunctionsData.getInstance();
ClanHallData.getInstance();
ClanHallAuctionManager.getInstance();
ClanEntryManager.getInstance();
ClanMasteryData.getInstance();
ClanShopData.getInstance();
printSection("Geodata");
GeoEngine.getInstance();
printSection("NPCs");
NpcData.getInstance();
FakePlayerData.getInstance();
FakePlayerChatManager.getInstance();
ExtendDropData.getInstance();
SpawnsData.getInstance();
MonsterBookData.getInstance();
WalkingManager.getInstance();
StaticObjectData.getInstance();
ItemAuctionManager.getInstance();
CastleManager.getInstance().loadInstances();
GrandBossManager.getInstance();
EventDroplist.getInstance();
printSection("Instance");
InstanceManager.getInstance();
printSection("Olympiad");
Olympiad.getInstance();
Hero.getInstance();
// Call to load caches
printSection("Cache");
HtmCache.getInstance();
CrestTable.getInstance();
TeleportersData.getInstance();
MatchingRoomManager.getInstance();
PetitionManager.getInstance();
CursedWeaponsManager.getInstance();
TransformData.getInstance();
BotReportTable.getInstance();
if (Config.SELLBUFF_ENABLED)
{
SellBuffsManager.getInstance();
}
if (Config.MULTILANG_ENABLE)
{
SystemMessageId.loadLocalisations();
NpcStringId.loadLocalisations();
SendMessageLocalisationData.getInstance();
NpcNameLocalisationData.getInstance();
}
printSection("Scripts");
QuestManager.getInstance();
BoatManager.getInstance();
AirShipManager.getInstance();
ShuttleData.getInstance();
GraciaSeedsManager.getInstance();
try
{
LOGGER.info(getClass().getSimpleName() + ": Loading server scripts:");
ScriptEngineManager.getInstance().executeScript(ScriptEngineManager.MASTER_HANDLER_FILE);
ScriptEngineManager.getInstance().executeScriptList();
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to execute script list!", e);
}
SpawnsData.getInstance().init();
DBSpawnManager.getInstance();
printSection("Event Engine");
EventEngineData.getInstance();
VoteSystem.initialize();
printSection("Siege");
SiegeManager.getInstance().getSieges();
CastleManager.getInstance().activateInstances();
FortManager.getInstance().loadInstances();
FortManager.getInstance().activateInstances();
FortSiegeManager.getInstance();
SiegeScheduleData.getInstance();
CastleManorManager.getInstance();
SiegeGuardManager.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();
}
if (Config.ALLOW_RACE)
{
MonsterRace.getInstance();
}
TaskManager.getInstance();
AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID);
if (Config.ALLOW_MAIL)
{
MailManager.getInstance();
}
if (Config.CUSTOM_MAIL_MANAGER_ENABLED)
{
CustomMailManager.getInstance();
}
PunishmentManager.getInstance();
Runtime.getRuntime().addShutdownHook(Shutdown.getInstance());
LOGGER.info("IdFactory: Free ObjectID's remaining: " + IdFactory.getInstance().size());
if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS)
{
OfflineTradersTable.getInstance().restoreOfflineTraders();
}
if (Config.SERVER_RESTART_SCHEDULE_ENABLED)
{
ServerRestartManager.getInstance();
}
if (Config.DEADLOCK_DETECTOR)
{
_deadDetectThread = new DeadLockDetector(Duration.ofSeconds(Config.DEADLOCK_CHECK_INTERVAL), () ->
{
if (Config.RESTART_ON_DEADLOCK)
{
Broadcast.toAllOnlinePlayers("Server has stability issues - restarting now.");
Shutdown.getInstance().startShutdown(null, 60, true);
}
});
_deadDetectThread.setDaemon(true);
_deadDetectThread.start();
}
else
{
_deadDetectThread = null;
}
System.gc();
final long totalMem = Runtime.getRuntime().maxMemory() / 1048576;
LOGGER.info(getClass().getSimpleName() + ": Started, using " + getUsedMemoryMB() + " of " + totalMem + " MB total memory.");
LOGGER.info(getClass().getSimpleName() + ": Maximum number of connected players is " + Config.MAXIMUM_ONLINE_USERS + ".");
LOGGER.info(getClass().getSimpleName() + ": Server loaded in " + ((System.currentTimeMillis() - serverLoadStart) / 1000) + " seconds.");
ClientNetworkManager.getInstance().start();
if (Boolean.getBoolean("newLoginServer"))
{
LoginServerNetworkManager.getInstance().connect();
}
else
{
LoginServerThread.getInstance().start();
}
Toolkit.getDefaultToolkit().beep();
}
public long getStartedTime()
{
return ManagementFactory.getRuntimeMXBean().getStartTime();
}
public String getUptime()
{
final long uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000;
final long hours = uptime / 3600;
final long mins = (uptime - (hours * 3600)) / 60;
final long secs = ((uptime - (hours * 3600)) - (mins * 60));
if (hours > 0)
{
return hours + "hrs " + mins + "mins " + secs + "secs";
}
return mins + "mins " + secs + "secs";
}
public static void main(String[] args) throws Exception
{
INSTANCE = new GameServer();
}
private void printSection(String s)
{
s = "=[ " + s + " ]";
while (s.length() < 61)
{
s = "-" + s;
}
LOGGER.info(s);
}
public static GameServer getInstance()
{
return INSTANCE;
}
}

View File

@ -0,0 +1,221 @@
/*
* 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 org.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 org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.events.EventDispatcher;
import org.l2jmobius.gameserver.model.events.impl.OnDayNightChange;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
/**
* Game Time controller class.
* @author Forsaiken
*/
public class GameTimeController extends Thread
{
private static final Logger LOGGER = 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 TICKS_PER_IG_DAY = SECONDS_PER_IG_DAY * TICKS_PER_SECOND;
private final static int SHADOW_SENSE_ID = 294;
private static GameTimeController _instance;
private final Set<Creature> _movingObjects = ConcurrentHashMap.newKeySet();
private final Set<Creature> _shadowSenseCharacters = 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 int getGameTime()
{
return (getGameTicks() % TICKS_PER_IG_DAY) / MILLIS_IN_TICK;
}
public int getGameHour()
{
return getGameTime() / 60;
}
public int getGameMinute()
{
return getGameTime() % 60;
}
public boolean isNight()
{
return getGameHour() < 6;
}
/**
* The true GameTime tick. Directly taken from current time. This represents the tick of the time.
* @return
*/
public int getGameTicks()
{
return (int) ((System.currentTimeMillis() - _referenceTime) / MILLIS_IN_TICK);
}
/**
* Add a Creature to movingObjects of GameTimeController.
* @param creature The Creature to add to movingObjects of GameTimeController
*/
public void registerMovingObject(Creature creature)
{
if (creature == null)
{
return;
}
if (!_movingObjects.contains(creature))
{
_movingObjects.add(creature);
}
}
/**
* Move all Creatures contained in movingObjects of GameTimeController.<BR>
* <B><U> Concept</U> :</B><BR>
* All Creature in movement are identified in <B>movingObjects</B> of GameTimeController.<BR>
* <B><U> Actions</U> :</B><BR>
* <ul>
* <li>Update the position of each Creature</li>
* <li>If movement is finished, the Creature is removed from movingObjects</li>
* <li>Create a task to update the _knownObject and _knowPlayers of each Creature that finished its movement and of their already known WorldObject then notify AI with EVT_ARRIVED</li>
* </ul>
*/
private void moveObjects()
{
_movingObjects.removeIf(Creature::updatePosition);
}
public void stopTimer()
{
super.interrupt();
LOGGER.info(getClass().getSimpleName() + ": Stopped.");
}
@Override
public void run()
{
LOGGER.info(getClass().getSimpleName() + ": Started.");
long nextTickTime;
long sleepTime;
boolean isNight = isNight();
EventDispatcher.getInstance().notifyEventAsync(new OnDayNightChange(isNight));
while (true)
{
nextTickTime = ((System.currentTimeMillis() / MILLIS_IN_TICK) * MILLIS_IN_TICK) + 100;
try
{
moveObjects();
}
catch (Throwable e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName(), e);
}
sleepTime = nextTickTime - System.currentTimeMillis();
if (sleepTime > 0)
{
try
{
Thread.sleep(sleepTime);
}
catch (InterruptedException e)
{
}
}
if (isNight() != isNight)
{
isNight = !isNight;
EventDispatcher.getInstance().notifyEventAsync(new OnDayNightChange(isNight));
notifyShadowSense();
}
}
}
public synchronized void addShadowSenseCharacter(Creature creature)
{
if (!_shadowSenseCharacters.contains(creature))
{
_shadowSenseCharacters.add(creature);
if (isNight())
{
final SystemMessage msg = new SystemMessage(SystemMessageId.IT_IS_NOW_MIDNIGHT_AND_THE_EFFECT_OF_S1_CAN_BE_FELT);
msg.addSkillName(SHADOW_SENSE_ID);
creature.sendPacket(msg);
}
}
}
public void removeShadowSenseCharacter(Creature creature)
{
_shadowSenseCharacters.remove(creature);
}
private void notifyShadowSense()
{
final SystemMessage msg = new SystemMessage(isNight() ? SystemMessageId.IT_IS_NOW_MIDNIGHT_AND_THE_EFFECT_OF_S1_CAN_BE_FELT : SystemMessageId.IT_IS_DAWN_AND_THE_EFFECT_OF_S1_WILL_NOW_DISAPPEAR);
msg.addSkillName(SHADOW_SENSE_ID);
for (Creature creature : _shadowSenseCharacters)
{
creature.getStat().recalculateStats(true);
creature.sendPacket(msg);
}
}
public static GameTimeController getInstance()
{
return _instance;
}
}

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 org.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,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 org.l2jmobius.gameserver;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.gameserver.enums.ItemLocation;
import org.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
public class ItemsAutoDestroy
{
private final List<ItemInstance> _items = new LinkedList<>();
protected ItemsAutoDestroy()
{
ThreadPool.scheduleAtFixedRate(this::removeItems, 5000, 5000);
}
public static ItemsAutoDestroy getInstance()
{
return SingletonHolder.INSTANCE;
}
public synchronized void addItem(ItemInstance item)
{
item.setDropTime(System.currentTimeMillis());
_items.add(item);
}
private synchronized void removeItems()
{
if (_items.isEmpty())
{
return;
}
final long curtime = System.currentTimeMillis();
final Iterator<ItemInstance> itemIterator = _items.iterator();
while (itemIterator.hasNext())
{
final ItemInstance item = itemIterator.next();
if ((item.getDropTime() == 0) || (item.getItemLocation() != ItemLocation.VOID))
{
itemIterator.remove();
}
else
{
final long autoDestroyTime;
if (item.getItem().getAutoDestroyTime() > 0)
{
autoDestroyTime = item.getItem().getAutoDestroyTime();
}
else if (item.getItem().hasExImmediateEffect())
{
autoDestroyTime = Config.HERB_AUTO_DESTROY_TIME;
}
else
{
autoDestroyTime = ((Config.AUTODESTROY_ITEM_AFTER == 0) ? 3600000 : Config.AUTODESTROY_ITEM_AFTER * 1000);
}
if ((curtime - item.getDropTime()) > autoDestroyTime)
{
item.decayMe();
itemIterator.remove();
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,842 @@
/*
* 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 org.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.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.commons.network.BaseSendablePacket;
import org.l2jmobius.commons.util.CommonUtil;
import org.l2jmobius.commons.util.crypt.NewCrypt;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.ConnectionState;
import org.l2jmobius.gameserver.network.GameClient;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.loginserverpackets.game.AuthRequest;
import org.l2jmobius.gameserver.network.loginserverpackets.game.BlowFishKey;
import org.l2jmobius.gameserver.network.loginserverpackets.game.ChangeAccessLevel;
import org.l2jmobius.gameserver.network.loginserverpackets.game.ChangePassword;
import org.l2jmobius.gameserver.network.loginserverpackets.game.PlayerAuthRequest;
import org.l2jmobius.gameserver.network.loginserverpackets.game.PlayerInGame;
import org.l2jmobius.gameserver.network.loginserverpackets.game.PlayerLogout;
import org.l2jmobius.gameserver.network.loginserverpackets.game.PlayerTracert;
import org.l2jmobius.gameserver.network.loginserverpackets.game.ReplyCharacters;
import org.l2jmobius.gameserver.network.loginserverpackets.game.SendMail;
import org.l2jmobius.gameserver.network.loginserverpackets.game.ServerStatus;
import org.l2jmobius.gameserver.network.loginserverpackets.game.TempBan;
import org.l2jmobius.gameserver.network.loginserverpackets.login.AuthResponse;
import org.l2jmobius.gameserver.network.loginserverpackets.login.ChangePasswordResponse;
import org.l2jmobius.gameserver.network.loginserverpackets.login.InitLS;
import org.l2jmobius.gameserver.network.loginserverpackets.login.KickPlayer;
import org.l2jmobius.gameserver.network.loginserverpackets.login.LoginServerFail;
import org.l2jmobius.gameserver.network.loginserverpackets.login.PlayerAuthResponse;
import org.l2jmobius.gameserver.network.loginserverpackets.login.RequestCharacters;
import org.l2jmobius.gameserver.network.serverpackets.CharSelectionInfo;
import org.l2jmobius.gameserver.network.serverpackets.LoginFail;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
public class LoginServerThread extends Thread
{
protected static final Logger LOGGER = Logger.getLogger(LoginServerThread.class.getName());
protected static final Logger ACCOUNTING_LOGGER = Logger.getLogger("accounting");
/**
* @see org.l2jmobius.loginserver.LoginServer#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 Set<WaitingClient> _waitingClients = ConcurrentHashMap.newKeySet();
private final Map<String, GameClient> _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 = CommonUtil.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;
_maxPlayer = Config.MAXIMUM_ONLINE_USERS;
}
/**
* Gets the single instance of LoginServerThread.
* @return single instance of LoginServerThread
*/
public static LoginServerThread getInstance()
{
return SingletonHolder.INSTANCE;
}
@Override
public void run()
{
while (!isInterrupted())
{
int lengthHi = 0;
int lengthLo = 0;
int length = 0;
boolean checksumOk = false;
try
{
// Connection
LOGGER.info(getClass().getSimpleName() + ": 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 = CommonUtil.generateHex(40);
_blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00");
while (!isInterrupted())
{
lengthLo = in.read();
lengthHi = in.read();
length = (lengthHi * 256) + lengthLo;
if (lengthHi < 0)
{
LOGGER.finer(getClass().getSimpleName() + ": 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 += newBytes;
left -= newBytes;
}
if (receivedBytes != (length - 2))
{
LOGGER.warning(getClass().getSimpleName() + ": 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)
{
LOGGER.warning(getClass().getSimpleName() + ": Incorrect packet checksum, ignoring packet (LS)");
break;
}
final int packetType = incoming[0] & 0xff;
switch (packetType)
{
case 0x00:
{
final InitLS init = new InitLS(incoming);
if (init.getRevision() != REVISION)
{
// TODO: revision mismatch
LOGGER.warning("/!\\ Revision mismatch between LS and GS /!\\");
break;
}
RSAPublicKey publicKey;
try
{
final KeyFactory kfac = KeyFactory.getInstance("RSA");
final BigInteger modulus = new BigInteger(init.getRSAKey());
final RSAPublicKeySpec kspec1 = new RSAPublicKeySpec(modulus, RSAKeyGenParameterSpec.F4);
publicKey = (RSAPublicKey) kfac.generatePublic(kspec1);
}
catch (GeneralSecurityException e)
{
LOGGER.warning(getClass().getSimpleName() + ": 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);
LOGGER.info(getClass().getSimpleName() + ": 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));
LOGGER.info(getClass().getSimpleName() + ": Registered on login as Server " + serverID + ": " + _serverName);
final ServerStatus st = new ServerStatus();
if (Config.SERVER_LIST_BRACKET)
{
st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.ON);
}
else
{
st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.OFF);
}
st.addAttribute(ServerStatus.SERVER_TYPE, Config.SERVER_LIST_TYPE);
if (Config.SERVER_GMONLY)
{
st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY);
}
else
{
st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_AUTO);
}
if (Config.SERVER_LIST_AGE == 15)
{
st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_15);
}
else if (Config.SERVER_LIST_AGE == 18)
{
st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_18);
}
else
{
st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_ALL);
}
sendPacket(st);
final List<String> playerList = World.getInstance().getPlayers().stream().filter(player -> !player.isInOfflineMode()).map(PlayerInstance::getAccountName).collect(Collectors.toList());
if (!playerList.isEmpty())
{
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.setConnectionState(ConnectionState.AUTHENTICATED);
wcToRemove.gameClient.setSessionId(wcToRemove.session);
wcToRemove.gameClient.sendPacket(LoginFail.LOGIN_SUCCESS);
final CharSelectionInfo cl = new CharSelectionInfo(wcToRemove.account, wcToRemove.gameClient.getSessionId().playOkID1);
wcToRemove.gameClient.sendPacket(cl);
wcToRemove.gameClient.setCharSelection(cl.getCharInfo());
}
else
{
LOGGER.warning(getClass().getSimpleName() + ": 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)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ", e);
}
catch (SocketException e)
{
LOGGER.warning(getClass().getSimpleName() + ": LoginServer not avaible, trying to reconnect...");
}
catch (IOException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Disconnected from Login, Trying to reconnect: ", 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, GameClient 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)
{
LOGGER.warning(getClass().getSimpleName() + ": Error while sending player auth request");
}
}
/**
* Removes the waiting client.
* @param client the client
*/
public void removeWaitingClient(GameClient 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)
{
LOGGER.warning(getClass().getSimpleName() + ": 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, GameClient 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
*/
private void doKickPlayer(String account)
{
final GameClient client = _accountsInGameServer.get(account);
if (client != null)
{
if (client.isDetached())
{
if (client.getPlayer() != null)
{
client.getPlayer().deleteMe();
}
}
else
{
ACCOUNTING_LOGGER.info("Kicked by login, " + client);
}
client.close(new SystemMessage(SystemMessageId.YOU_ARE_LOGGED_IN_TO_TWO_PLACES_IF_YOU_SUSPECT_ACCOUNT_THEFT_WE_RECOMMEND_CHANGING_YOUR_PASSWORD_SCANNING_YOUR_COMPUTER_FOR_VIRUSES_AND_USING_AN_ANTI_VIRUS_SOFTWARE));
}
sendLogout(account);
}
/**
* 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.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)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": 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 GameClient 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 GameClient 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, GameClient client, SessionKey key)
{
account = acc;
gameClient = client;
session = key;
}
}
private static class SingletonHolder
{
protected static final LoginServerThread INSTANCE = new LoginServerThread();
}
}

View File

@ -0,0 +1,141 @@
/*
* 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 org.l2jmobius.gameserver;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.impl.NpcData;
import org.l2jmobius.gameserver.model.actor.Npc;
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
public class MonsterRace
{
protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName());
private final Npc[] _monsters;
private int[][] _speeds;
private final int[] _first;
private final int[] _second;
protected MonsterRace()
{
_monsters = new Npc[8];
_speeds = new int[8][20];
_first = new int[2];
_second = new int[2];
}
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 NpcTemplate template = NpcData.getInstance().getTemplate(id + random);
_monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template);
}
catch (Exception e)
{
LOGGER.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 Npc[] getMonsters()
{
return _monsters;
}
/**
* @return Returns the speeds.
*/
public int[][] getSpeeds()
{
return _speeds;
}
public int getFirstPlace()
{
return _first[0];
}
public int getSecondPlace()
{
return _second[0];
}
public static MonsterRace getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final MonsterRace INSTANCE = new MonsterRace();
}
}

View File

@ -0,0 +1,605 @@
/*
* 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 org.l2jmobius.gameserver;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.database.DatabaseBackup;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.data.sql.impl.ClanTable;
import org.l2jmobius.gameserver.data.sql.impl.OfflineTradersTable;
import org.l2jmobius.gameserver.datatables.BotReportTable;
import org.l2jmobius.gameserver.instancemanager.CastleManorManager;
import org.l2jmobius.gameserver.instancemanager.CeremonyOfChaosManager;
import org.l2jmobius.gameserver.instancemanager.CursedWeaponsManager;
import org.l2jmobius.gameserver.instancemanager.DBSpawnManager;
import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager;
import org.l2jmobius.gameserver.instancemanager.GrandBossManager;
import org.l2jmobius.gameserver.instancemanager.ItemAuctionManager;
import org.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import org.l2jmobius.gameserver.instancemanager.QuestManager;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.entity.Hero;
import org.l2jmobius.gameserver.model.olympiad.Olympiad;
import org.l2jmobius.gameserver.network.ClientNetworkManager;
import org.l2jmobius.gameserver.network.Disconnection;
import org.l2jmobius.gameserver.network.EventLoopGroupManager;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.loginserverpackets.game.ServerStatus;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import org.l2jmobius.gameserver.network.telnet.TelnetServer;
import org.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 LOGGER = Logger.getLogger(Shutdown.class.getName());
private static Shutdown _counterInstance = null;
private int _secondsShut;
private int _shutdownMode;
private static final int SIGTERM = 0;
private static final int GM_SHUTDOWN = 1;
private static final int GM_RESTART = 2;
private 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 = new SystemMessage(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);
}
/**
* 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();
LOGGER.info("Offline Traders Table: Offline shops stored(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
}
catch (Throwable t)
{
LOGGER.log(Level.WARNING, "Error saving offline shops.", t);
}
try
{
disconnectAllCharacters();
LOGGER.info("All players disconnected and saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
// ensure all services are stopped
try
{
GameTimeController.getInstance().stopTimer();
LOGGER.info("Game Time Controller: Timer stopped(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
// stop all thread pools
try
{
ThreadPool.shutdown();
LOGGER.info("Thread Pool Manager: Manager has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
try
{
LoginServerThread.getInstance().interrupt();
LOGGER.info("Login Server Thread: Thread interruped(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
try
{
TelnetServer.getInstance().shutdown();
LOGGER.info("Telnet 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
{
ClientNetworkManager.getInstance().stop();
EventLoopGroupManager.getInstance().shutdown();
LOGGER.info("Game Server: Selector thread has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
// ignore
}
// commit data, last chance
try
{
DatabaseFactory.close();
LOGGER.info("Database Factory: Database connection has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
catch (Throwable t)
{
}
// Backup database.
if (Config.BACKUP_DATABASE)
{
DatabaseBackup.performBackup();
}
// server will quit, when this function ends.
if (getInstance()._shutdownMode == GM_RESTART)
{
Runtime.getRuntime().halt(2);
}
else
{
Runtime.getRuntime().halt(0);
}
LOGGER.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 :(
LOGGER.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 player GM who issued the shutdown command
* @param seconds seconds until shutdown
* @param restart true if the server will restart after shutdown
*/
public void startShutdown(PlayerInstance player, int seconds, boolean restart)
{
_shutdownMode = restart ? GM_RESTART : GM_SHUTDOWN;
if (player != null)
{
LOGGER.warning("GM: " + player.getName() + "(" + player.getObjectId() + ") issued shutdown command. " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!");
}
else
{
LOGGER.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 player GM who issued the abort command
*/
public void abort(PlayerInstance player)
{
LOGGER.warning("GM: " + (player != null ? player.getName() + "(" + player.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:
{
LOGGER.info("SIGTERM received. Shutting down NOW!");
break;
}
case GM_SHUTDOWN:
{
LOGGER.info("GM shutdown received. Shutting down NOW!");
break;
}
case GM_RESTART:
{
LOGGER.info("GM restart received. Restarting NOW!");
break;
}
}
final TimeCounter tc = new TimeCounter();
// Save all raidboss and GrandBoss status ^_^
DBSpawnManager.getInstance().cleanUp();
LOGGER.info("RaidBossSpawnManager: All raidboss info saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
GrandBossManager.getInstance().cleanUp();
LOGGER.info("GrandBossManager: All Grand Boss info saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
ItemAuctionManager.getInstance().shutdown();
LOGGER.info("Item Auction Manager: All tasks stopped(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
Olympiad.getInstance().saveOlympiadStatus();
LOGGER.info("Olympiad System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
CeremonyOfChaosManager.getInstance().stopScheduler();
LOGGER.info("CeremonyOfChaosManager: Scheduler stopped(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
Hero.getInstance().shutdown();
LOGGER.info("Hero System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
ClanTable.getInstance().shutdown();
LOGGER.info("Clan System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save Cursed Weapons data before closing.
CursedWeaponsManager.getInstance().saveData();
LOGGER.info("Cursed Weapons Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save all manor data
if (!Config.ALT_MANOR_SAVE_ALL_ACTIONS)
{
CastleManorManager.getInstance().storeMe();
LOGGER.info("Castle Manor Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
// Save all global (non-player specific) Quest data that needs to persist after reboot
QuestManager.getInstance().save();
LOGGER.info("Quest Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save all global variables data
GlobalVariablesManager.getInstance().storeMe();
LOGGER.info("Global Variables Manager: Variables saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
// Save items on ground before closing
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().saveInDb();
LOGGER.info("Items On Ground Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
ItemsOnGroundManager.getInstance().cleanUp();
LOGGER.info("Items On Ground Manager: Cleaned up(" + tc.getEstimatedTimeAndRestartCounter() + "ms).");
}
// Save bot reports to database
if (Config.BOTREPORT_ENABLE)
{
BotReportTable.getInstance().saveReportedCharData();
LOGGER.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 (PlayerInstance player : World.getInstance().getPlayers())
{
Disconnection.of(player).defaultSequence(true);
}
}
/**
* 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,819 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.GameTimeController;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.Summon;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.interfaces.ILocational;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
import org.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import org.l2jmobius.gameserver.network.serverpackets.AutoAttackStart;
import org.l2jmobius.gameserver.network.serverpackets.AutoAttackStop;
import org.l2jmobius.gameserver.network.serverpackets.Die;
import org.l2jmobius.gameserver.network.serverpackets.MoveToLocation;
import org.l2jmobius.gameserver.network.serverpackets.MoveToPawn;
import org.l2jmobius.gameserver.network.serverpackets.StopMove;
import org.l2jmobius.gameserver.taskmanager.AttackStanceTaskManager;
/**
* Mother class of all objects AI in the world.<br>
* AbastractAI :<br>
* <li>CreatureAI</li>
*/
public abstract class AbstractAI implements Ctrl
{
private static final Logger LOGGER = Logger.getLogger(AbstractAI.class.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;
}
/** The creature that this AI manages */
protected final Creature _actor;
/** Current long-term intention */
protected CtrlIntention _intention = AI_INTENTION_IDLE;
/** Current long-term intention parameter */
protected Object[] _intentionArgs = 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 */
private 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 WorldObject _target;
/** The skill we are currently casting by INTENTION_CAST */
Skill _skill;
ItemInstance _item;
boolean _forceUse;
boolean _dontMove;
/** Different internal state flags */
protected int _moveToPawnTimeout;
private Future<?> _followTask = null;
private static final int FOLLOW_INTERVAL = 1000;
private static final int ATTACK_FOLLOW_INTERVAL = 500;
protected AbstractAI(Creature creature)
{
_actor = creature;
}
/**
* @return the Creature managed by this Accessor AI.
*/
@Override
public Creature getActor()
{
return _actor;
}
/**
* @return the current Intention.
*/
@Override
public CtrlIntention getIntention()
{
return _intention;
}
/**
* 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>AttackableAI</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 args The first parameter of the Intention
*/
synchronized void changeIntention(CtrlIntention intention, Object... args)
{
_intention = intention;
_intentionArgs = args;
}
/**
* Launch the CreatureAI 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 void setIntention(CtrlIntention intention)
{
setIntention(intention, null, null);
}
/**
* Launch the CreatureAI 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 args The first parameters of the Intention (optional target)
*/
@Override
@SafeVarargs
public final void setIntention(CtrlIntention intention, Object... args)
{
// Stop the follow mode if necessary
if ((intention != AI_INTENTION_FOLLOW) && (intention != AI_INTENTION_ATTACK))
{
stopFollow();
}
// Launch the onIntention method of the CreatureAI 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((Creature) args[0]);
break;
}
case AI_INTENTION_CAST:
{
onIntentionCast((Skill) args[0], (WorldObject) args[1], args.length > 2 ? (ItemInstance) args[2] : null, args.length > 3 ? (boolean) args[3] : false, args.length > 4 ? (boolean) args[4] : false);
break;
}
case AI_INTENTION_MOVE_TO:
{
onIntentionMoveTo((ILocational) args[0]);
break;
}
case AI_INTENTION_FOLLOW:
{
onIntentionFollow((Creature) args[0]);
break;
}
case AI_INTENTION_PICK_UP:
{
onIntentionPickUp((WorldObject) args[0]);
break;
}
case AI_INTENTION_INTERACT:
{
onIntentionInteract((WorldObject) args[0]);
break;
}
}
// If do move or follow intention drop next action.
if ((_nextAction != null) && _nextAction.getIntentions().contains(intention))
{
_nextAction = null;
}
}
/**
* Launch the CreatureAI 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 void notifyEvent(CtrlEvent evt)
{
notifyEvent(evt, null, null);
}
/**
* Launch the CreatureAI 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 void notifyEvent(CtrlEvent evt, Object arg0)
{
notifyEvent(evt, arg0, null);
}
/**
* Launch the CreatureAI 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)
* @param arg1 The second parameter of the Event (optional target)
*/
@Override
public void notifyEvent(CtrlEvent evt, Object arg0, Object arg1)
{
if ((!_actor.isSpawned() && !_actor.isTeleporting()) || !_actor.hasAI())
{
return;
}
switch (evt)
{
case EVT_THINK:
{
onEvtThink();
break;
}
case EVT_ATTACKED:
{
onEvtAttacked((Creature) arg0);
break;
}
case EVT_AGGRESSION:
{
onEvtAggression((Creature) arg0, ((Number) arg1).intValue());
break;
}
case EVT_ACTION_BLOCKED:
{
onEvtActionBlocked((Creature) arg0);
break;
}
case EVT_ROOTED:
{
onEvtRooted((Creature) arg0);
break;
}
case EVT_CONFUSED:
{
onEvtConfused((Creature) arg0);
break;
}
case EVT_MUTED:
{
onEvtMuted((Creature) arg0);
break;
}
case EVT_EVADED:
{
onEvtEvaded((Creature) arg0);
break;
}
case EVT_READY_TO_ACT:
{
if (!_actor.isCastingNow())
{
onEvtReadyToAct();
}
break;
}
case EVT_ARRIVED:
{
// happens e.g. from stopmove but we don't process it if we're casting
if (!_actor.isCastingNow())
{
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) arg0);
break;
}
case EVT_FORGET_OBJECT:
{
onEvtForgetObject((WorldObject) arg0);
break;
}
case EVT_CANCEL:
{
onEvtCancel();
break;
}
case EVT_DEAD:
{
onEvtDead();
break;
}
case EVT_FAKE_DEATH:
{
onEvtFakeDeath();
break;
}
case EVT_FINISH_CASTING:
{
onEvtFinishCasting();
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(Creature target);
protected abstract void onIntentionCast(Skill skill, WorldObject target, ItemInstance item, boolean forceUse, boolean dontMove);
protected abstract void onIntentionMoveTo(ILocational destination);
protected abstract void onIntentionFollow(Creature target);
protected abstract void onIntentionPickUp(WorldObject item);
protected abstract void onIntentionInteract(WorldObject object);
protected abstract void onEvtThink();
protected abstract void onEvtAttacked(Creature attacker);
protected abstract void onEvtAggression(Creature target, int aggro);
protected abstract void onEvtActionBlocked(Creature attacker);
protected abstract void onEvtRooted(Creature attacker);
protected abstract void onEvtConfused(Creature attacker);
protected abstract void onEvtMuted(Creature attacker);
protected abstract void onEvtEvaded(Creature attacker);
protected abstract void onEvtReadyToAct();
protected abstract void onEvtArrived();
protected abstract void onEvtArrivedRevalidate();
protected abstract void onEvtArrivedBlocked(Location blocked_at_pos);
protected abstract void onEvtForgetObject(WorldObject object);
protected abstract void onEvtCancel();
protected abstract void onEvtDead();
protected abstract void onEvtFakeDeath();
protected abstract void onEvtFinishCasting();
/**
* Cancel action client side by sending Server->Client packet ActionFailed to the PlayerInstance actor. <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
protected void clientActionFailed()
{
if (_actor.isPlayer())
{
_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(WorldObject pawn, int offset)
{
// Check if actor can move
if (!_actor.isMovementDisabled() && !_actor.isAttackingNow() && !_actor.isCastingNow())
{
if (offset < 10)
{
offset = 10;
}
// prevent possible extra calls to this function (there is none?),
// also don't send movetopawn packets too often
if (_clientMoving && (_target == pawn))
{
if (_clientMovingToPawnOffset == offset)
{
if (GameTimeController.getInstance().getGameTicks() < _moveToPawnTimeout)
{
return;
}
}
else if (_actor.isOnGeodataPath())
{
// minimum time to calculate new route is 2 seconds
if (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 PlayerInstance in its _knownPlayers
if (pawn.isCreature())
{
if (_actor.isOnGeodataPath())
{
_actor.broadcastPacket(new MoveToLocation(_actor));
_clientMovingToPawnOffset = 0;
}
else
{
_actor.broadcastPacket(new MoveToPawn(_actor, pawn, offset));
}
}
else
{
_actor.broadcastPacket(new MoveToLocation(_actor));
}
}
else
{
clientActionFailed();
}
}
public void moveTo(ILocational loc)
{
moveTo(loc.getX(), loc.getY(), loc.getZ());
}
/**
* 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)
{
// Check 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 PlayerInstance 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
*/
public void clientStopMoving(Location loc)
{
// Stop movement of the Creature
if (_actor.isMoving())
{
_actor.stopMove(loc);
}
_clientMovingToPawnOffset = 0;
_clientMoving = false;
}
/**
* 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.isSummon())
{
final Summon summon = (Summon) _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()
{
// Non attackable NPCs should not get in combat.
if (_actor.isNpc() && !_actor.isAttackable())
{
return;
}
if (_actor.isSummon())
{
final Summon summon = (Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStartAutoAttack();
}
return;
}
if (!_clientAutoAttacking)
{
if (_actor.isPlayer() && _actor.hasSummon())
{
final Summon pet = _actor.getPet();
if (pet != null)
{
pet.broadcastPacket(new AutoAttackStart(pet.getObjectId()));
}
_actor.getServitors().values().forEach(s -> s.broadcastPacket(new AutoAttackStart(s.getObjectId())));
}
// Send a Server->Client packet AutoAttackStart to the actor and all PlayerInstance 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>
*/
void clientStopAutoAttack()
{
if (_actor.isSummon())
{
final Summon summon = (Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStopAutoAttack();
}
return;
}
if (_actor.isPlayer())
{
if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor) && isAutoAttacking())
{
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
}
else if (_clientAutoAttacking)
{
_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 PlayerInstance in its _knownPlayers
final Die msg = new Die(_actor);
_actor.broadcastPacket(msg);
// Init AI
_intention = AI_INTENTION_IDLE;
_target = 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 PlayerInstance player.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param player The PlayerIstance to notify with state of this Creature
*/
public void describeStateToPlayer(PlayerInstance player)
{
if (_actor.isVisibleFor(player))
{
if (_clientMoving)
{
if ((_clientMovingToPawnOffset != 0) && isFollowing())
{
// Send a Server->Client packet MoveToPawn to the actor and all PlayerInstance in its _knownPlayers
player.sendPacket(new MoveToPawn(_actor, _target, _clientMovingToPawnOffset));
}
else
{
// Send a Server->Client packet CharMoveToLocation to the actor and all PlayerInstance in its _knownPlayers
player.sendPacket(new MoveToLocation(_actor));
}
}
}
}
public boolean isFollowing()
{
return (_target != null) && _target.isCreature() && (_intention == AI_INTENTION_FOLLOW);
}
/**
* Create and Launch an AI Follow Task to execute every 1s.
* @param target The Creature to follow
*/
public synchronized void startFollow(Creature target)
{
startFollow(target, -1);
}
/**
* Create and Launch an AI Follow Task to execute every 0.5s, following at specified range.
* @param target The Creature to follow
* @param range
*/
public synchronized void startFollow(Creature target, int range)
{
if (_followTask != null)
{
_followTask.cancel(false);
_followTask = null;
}
setTarget(target);
final int followRange = range == -1 ? Rnd.get(50, 100) : range;
_followTask = ThreadPool.scheduleAtFixedRate(() ->
{
try
{
if (_followTask == null)
{
return;
}
final WorldObject followTarget = getTarget(); // copy to prevent NPE
if (followTarget == null)
{
if (_actor.isSummon())
{
((Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
if (!_actor.isInsideRadius3D(followTarget, followRange))
{
if (!_actor.isInsideRadius3D(followTarget, 3000))
{
// if the target is too far (maybe also teleported)
if (_actor.isSummon())
{
((Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
moveToPawn(followTarget, followRange);
}
}
catch (Exception e)
{
LOGGER.warning("Error: " + e.getMessage());
}
}, 5, range == -1 ? FOLLOW_INTERVAL : ATTACK_FOLLOW_INTERVAL);
}
/**
* Stop an AI Follow Task.
*/
public synchronized void stopFollow()
{
if (_followTask != null)
{
// Stop the Follow Task
_followTask.cancel(false);
_followTask = null;
}
}
public void setTarget(WorldObject target)
{
_target = target;
}
public WorldObject getTarget()
{
return _target;
}
/**
* Stop all Ai tasks and futures.
*/
public void stopAITask()
{
stopFollow();
}
@Override
public String toString()
{
return "Actor: " + _actor;
}
}

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 org.l2jmobius.gameserver.ai;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.actor.instance.AirShipInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.serverpackets.ExMoveToLocationAirShip;
import org.l2jmobius.gameserver.network.serverpackets.ExStopMoveAirShip;
/**
* @author DS
*/
public class AirShipAI extends VehicleAI
{
public AirShipAI(AirShipInstance airShip)
{
super(airShip);
}
@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
public 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(PlayerInstance player)
{
if (_clientMoving)
{
player.sendPacket(new ExMoveToLocationAirShip(getActor()));
}
}
@Override
public AirShipInstance getActor()
{
return (AirShipInstance) _actor;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.actor.instance.BoatInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.serverpackets.VehicleDeparture;
import org.l2jmobius.gameserver.network.serverpackets.VehicleInfo;
import org.l2jmobius.gameserver.network.serverpackets.VehicleStarted;
/**
* @author DS
*/
public class BoatAI extends VehicleAI
{
public BoatAI(BoatInstance boat)
{
super(boat);
}
@Override
protected void moveTo(int x, int y, int z)
{
if (!_actor.isMovementDisabled())
{
if (!_clientMoving)
{
_actor.broadcastPacket(new VehicleStarted(getActor(), 1));
}
_clientMoving = true;
_actor.moveToLocation(x, y, z, 0);
_actor.broadcastPacket(new VehicleDeparture(getActor()));
}
}
@Override
public 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(PlayerInstance player)
{
if (_clientMoving)
{
player.sendPacket(new VehicleDeparture(getActor()));
}
}
@Override
public BoatInstance getActor()
{
return (BoatInstance) _actor;
}
}

View File

@ -0,0 +1,545 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import java.util.ArrayList;
import java.util.List;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.model.MobGroup;
import org.l2jmobius.gameserver.model.MobGroupTable;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Attackable;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.Npc;
import org.l2jmobius.gameserver.model.actor.Playable;
import org.l2jmobius.gameserver.model.actor.instance.ControllableMobInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
import org.l2jmobius.gameserver.util.Util;
/**
* AI for controllable mobs
* @author littlecrow
*/
public class ControllableMobAI extends AttackableAI
{
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 Creature _forcedTarget;
private MobGroup _targetGroup;
protected void thinkFollow()
{
final Attackable me = (Attackable) _actor;
if (!Util.checkIfInRange(MobGroupTable.FOLLOW_RANGE, me, getForcedTarget(), true))
{
final int signX = Rnd.nextBoolean() ? -1 : 1;
final int signY = Rnd.nextBoolean() ? -1 : 1;
final int randX = Rnd.get(MobGroupTable.FOLLOW_RANGE);
final int randY = Rnd.get(MobGroupTable.FOLLOW_RANGE);
moveTo(getForcedTarget().getX() + (signX * randX), getForcedTarget().getY() + (signY * randY), getForcedTarget().getZ());
}
}
@Override
protected void onEvtThink()
{
if (_isThinking)
{
return;
}
setThinking(true);
try
{
switch (_alternateAI)
{
case AI_IDLE:
{
if (getIntention() != AI_INTENTION_ACTIVE)
{
setIntention(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()
{
WorldObject target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
if ((target == null) || !target.isCreature() || ((Creature) target).isAlikeDead())
{
target = _skill.getTarget(_actor, findNextRndTarget(), _forceUse, _dontMove, false);
}
if (target == null)
{
return;
}
setTarget(target);
if (!_actor.isMuted())
{
int max_range = 0;
// check distant skills
for (Skill sk : _actor.getAllSkills())
{
if (Util.checkIfInRange(sk.getCastRange(), _actor, target, 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(target, max_range);
}
return;
}
}
protected void thinkAttackGroup()
{
final Creature target = getForcedTarget();
if ((target == null) || target.isAlikeDead())
{
// try to get next group target
setForcedTarget(findNextGroupTarget());
clientStopMoving(null);
}
if (target == null)
{
return;
}
setTarget(target);
// as a response, we put the target in a forcedattack mode
final ControllableMobInstance theTarget = (ControllableMobInstance) target;
final ControllableMobAI ctrlAi = (ControllableMobAI) theTarget.getAI();
ctrlAi.forceAttack(_actor);
final double dist2 = _actor.calculateDistanceSq2D(target);
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.doAutoAttack(target);
}
protected void thinkForceAttack()
{
if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead())
{
clientStopMoving(null);
setIntention(AI_INTENTION_ACTIVE);
setAlternateAI(AI_IDLE);
}
setTarget(getForcedTarget());
final double dist2 = _actor.calculateDistanceSq2D(getForcedTarget());
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.doAutoAttack(getForcedTarget());
}
@Override
protected void thinkAttack()
{
Creature target = getForcedTarget();
if ((target == null) || target.isAlikeDead())
{
if (target != null)
{
// stop hating
final Attackable npc = (Attackable) _actor;
npc.stopHating(target);
}
setIntention(AI_INTENTION_ACTIVE);
}
else
{
// notify aggression
final Creature finalTarget = target;
if (((Npc) _actor).getTemplate().getClans() != null)
{
World.getInstance().forEachVisibleObject(_actor, Npc.class, npc ->
{
if (!npc.isInMyClan((Npc) _actor))
{
return;
}
if (_actor.isInsideRadius3D(npc, npc.getTemplate().getClanHelpRange()))
{
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, finalTarget, 1);
}
});
}
setTarget(target);
final double dist2 = _actor.calculateDistanceSq2D(target);
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);
}
moveToPawn(target, range);
return;
}
// Force mobs to attack anybody if confused.
Creature hated;
if (_actor.isConfused())
{
hated = findNextRndTarget();
}
else
{
hated = target;
}
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE);
return;
}
if (hated != target)
{
target = hated;
}
if (!_actor.isMuted() && (Rnd.get(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.doAutoAttack(target);
}
}
@Override
protected void thinkActive()
{
Creature hated;
if (_actor.isConfused())
{
hated = findNextRndTarget();
}
else
{
final WorldObject target = _actor.getTarget();
hated = (target != null) && target.isCreature() ? (Creature) target : null;
}
if (hated != null)
{
_actor.setRunning();
setIntention(AI_INTENTION_ATTACK, hated);
}
}
private boolean checkAutoAttackCondition(Creature target)
{
if ((target == null) || !_actor.isAttackable())
{
return false;
}
final Attackable me = (Attackable) _actor;
if (target.isNpc() || target.isDoor())
{
return false;
}
if (target.isAlikeDead() || !me.isInsideRadius2D(target, me.getAggroRange()) || (Math.abs(_actor.getZ() - target.getZ()) > 100))
{
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul())
{
return false;
}
// Spawn protection (only against mobs)
if (target.isPlayer() && ((PlayerInstance) target).isSpawnProtected())
{
return false;
}
// Check if the target is a Playable
if (target.isPlayable())
{
// Check if the target isn't in silent move mode
if (((Playable) target).isSilentMovingAffected())
{
return false;
}
}
if (target.isNpc())
{
return false;
}
return me.isAggressive();
}
private Creature findNextRndTarget()
{
final List<Creature> potentialTarget = new ArrayList<>();
World.getInstance().forEachVisibleObject(_actor, Creature.class, target ->
{
if (Util.checkIfInShortRange(((Attackable) _actor).getAggroRange(), _actor, target, true) && checkAutoAttackCondition(target))
{
potentialTarget.add(target);
}
});
return !potentialTarget.isEmpty() ? potentialTarget.get(Rnd.get(potentialTarget.size())) : null;
}
private ControllableMobInstance findNextGroupTarget()
{
return getGroupTarget().getRandomMob();
}
public ControllableMobAI(ControllableMobInstance controllableMob)
{
super(controllableMob);
setAlternateAI(AI_IDLE);
}
public int getAlternateAI()
{
return _alternateAI;
}
public void setAlternateAI(int _alternateai)
{
_alternateAI = _alternateai;
}
public void forceAttack(Creature 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(Creature 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 Creature getForcedTarget()
{
return _forcedTarget;
}
private MobGroup getGroupTarget()
{
return _targetGroup;
}
private void setForcedTarget(Creature forcedTarget)
{
_forcedTarget = forcedTarget;
}
private void setGroupTarget(MobGroup targetGroup)
{
_targetGroup = targetGroup;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import org.l2jmobius.gameserver.model.actor.Creature;
/**
* 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
*/
Creature getActor();
/**
* Gets the intention.
* @return the intention
*/
CtrlIntention getIntention();
/**
* 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 args
*/
void setIntention(CtrlIntention intention, Object... args);
/**
* 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 arg0 the arg0
* @param arg1 the arg1
*/
void notifyEvent(CtrlEvent evt, Object arg0, Object arg1);
}

View File

@ -0,0 +1,80 @@
/*
* 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 org.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_ACTION_BLOCKED,
/** 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,
/**
* 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 creature is dead */
EVT_DEAD,
/** The creature looks like dead */
EVT_FAKE_DEATH,
/** The creature attack anyone randomly **/
EVT_CONFUSED,
/** The creature cannot cast spells anymore **/
EVT_MUTED,
/** The creature flee in random directions **/
EVT_AFRAID,
/** The creature finish casting **/
EVT_FINISH_CASTING,
/** The creature 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 org.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,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 org.l2jmobius.gameserver.ai;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.instance.DefenderInstance;
import org.l2jmobius.gameserver.model.actor.instance.DoorInstance;
import org.l2jmobius.gameserver.model.interfaces.ILocational;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
/**
* @author mkizub
*/
public class DoorAI extends CreatureAI
{
public DoorAI(DoorInstance door)
{
super(door);
}
@Override
protected void onIntentionIdle()
{
}
@Override
protected void onIntentionActive()
{
}
@Override
protected void onIntentionRest()
{
}
@Override
protected void onIntentionAttack(Creature target)
{
}
@Override
protected void onIntentionCast(Skill skill, WorldObject target, ItemInstance item, boolean forceUse, boolean dontMove)
{
}
@Override
protected void onIntentionMoveTo(ILocational destination)
{
}
@Override
protected void onIntentionFollow(Creature target)
{
}
@Override
protected void onIntentionPickUp(WorldObject item)
{
}
@Override
protected void onIntentionInteract(WorldObject object)
{
}
@Override
protected void onEvtThink()
{
}
@Override
protected void onEvtAttacked(Creature attacker)
{
ThreadPool.execute(new onEventAttackedDoorTask((DoorInstance) _actor, attacker));
}
@Override
protected void onEvtAggression(Creature target, int aggro)
{
}
@Override
protected void onEvtActionBlocked(Creature attacker)
{
}
@Override
protected void onEvtRooted(Creature attacker)
{
}
@Override
protected void onEvtReadyToAct()
{
}
@Override
protected void onEvtArrived()
{
}
@Override
protected void onEvtArrivedRevalidate()
{
}
@Override
protected void onEvtArrivedBlocked(Location blocked_at_loc)
{
}
@Override
protected void onEvtForgetObject(WorldObject object)
{
}
@Override
protected void onEvtCancel()
{
}
@Override
protected void onEvtDead()
{
}
private class onEventAttackedDoorTask implements Runnable
{
private final DoorInstance _door;
private final Creature _attacker;
public onEventAttackedDoorTask(DoorInstance door, Creature attacker)
{
_door = door;
_attacker = attacker;
}
@Override
public void run()
{
World.getInstance().forEachVisibleObject(_door, DefenderInstance.class, guard ->
{
if (_actor.isInsideRadius3D(guard, guard.getTemplate().getClanHelpRange()))
{
guard.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, _attacker, 15);
}
});
}
}
}

View File

@ -0,0 +1,281 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.GameTimeController;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.instance.DoppelgangerInstance;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
import org.l2jmobius.gameserver.model.skills.SkillCaster;
import org.l2jmobius.gameserver.network.serverpackets.MoveToLocation;
public class DoppelgangerAI extends CreatureAI
{
private volatile boolean _thinking; // to prevent recursive thinking
private volatile boolean _startFollow;
private Creature _lastAttack = null;
public DoppelgangerAI(DoppelgangerInstance clone)
{
super(clone);
}
@Override
protected void onIntentionIdle()
{
stopFollow();
_startFollow = false;
onIntentionActive();
}
@Override
protected void onIntentionActive()
{
if (_startFollow)
{
setIntention(AI_INTENTION_FOLLOW, getActor().getSummoner());
}
else
{
super.onIntentionActive();
}
}
private void thinkAttack()
{
final WorldObject target = getTarget();
final Creature attackTarget = (target != null) && target.isCreature() ? (Creature) target : null;
if (checkTargetLostOrDead(attackTarget))
{
setTarget(null);
return;
}
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange()))
{
return;
}
clientStopMoving(null);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()
{
if (_actor.isCastingNow(SkillCaster::isAnyNormalType))
{
return;
}
final WorldObject target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
if (checkTargetLost(target))
{
setTarget(null);
return;
}
final boolean val = _startFollow;
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
{
return;
}
getActor().followSummoner(false);
setIntention(AI_INTENTION_IDLE);
_startFollow = val;
_actor.doCast(_skill, _item, _forceUse, _dontMove);
}
private void thinkInteract()
{
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 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_INTERACT:
{
thinkInteract();
break;
}
}
}
finally
{
_thinking = false;
}
}
@Override
protected void onEvtFinishCasting()
{
if (_lastAttack == null)
{
getActor().followSummoner(_startFollow);
}
else
{
setIntention(AI_INTENTION_ATTACK, _lastAttack);
_lastAttack = null;
}
}
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:
{
getActor().followSummoner(_startFollow);
}
}
}
public void setStartFollowController(boolean val)
{
_startFollow = val;
}
@Override
protected void onIntentionCast(Skill skill, WorldObject target, ItemInstance item, boolean forceUse, boolean dontMove)
{
if (getIntention() == AI_INTENTION_ATTACK)
{
_lastAttack = (getTarget() != null) && getTarget().isCreature() ? (Creature) getTarget() : null;
}
else
{
_lastAttack = null;
}
super.onIntentionCast(skill, target, item, forceUse, dontMove);
}
@Override
protected void moveToPawn(WorldObject pawn, int offset)
{
// Check if actor can move
if (!_actor.isMovementDisabled() && (_actor.getMoveSpeed() > 0))
{
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 && (getTarget() == pawn))
{
if (_clientMovingToPawnOffset == offset)
{
if (GameTimeController.getInstance().getGameTicks() < _moveToPawnTimeout)
{
return;
}
sendPacket = false;
}
else if (_actor.isOnGeodataPath())
{
// minimum time to calculate new route is 2 seconds
if (GameTimeController.getInstance().getGameTicks() < (_moveToPawnTimeout + 10))
{
return;
}
}
}
// Set AI movement data
_clientMoving = true;
_clientMovingToPawnOffset = offset;
setTarget(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);
final Location loc = new Location(pawn.getX() + Rnd.get(-offset, offset), pawn.getY() + Rnd.get(-offset, offset), pawn.getZ());
_actor.moveToLocation(loc.getX(), loc.getY(), loc.getZ(), 0);
if (!_actor.isMoving())
{
clientActionFailed();
return;
}
// Doppelgangers always send MoveToLocation packet.
if (sendPacket)
{
_actor.broadcastPacket(new MoveToLocation(_actor));
}
}
else
{
clientActionFailed();
}
}
@Override
public DoppelgangerInstance getActor()
{
return (DoppelgangerInstance) super.getActor();
}
}

View File

@ -0,0 +1,239 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_REST;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.geoengine.GeoEngine;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Attackable;
import org.l2jmobius.gameserver.model.actor.Creature;
/**
* @author Sdw
*/
public class FriendlyNpcAI extends AttackableAI
{
public FriendlyNpcAI(Attackable attackable)
{
super(attackable);
}
@Override
protected void onEvtAttacked(Creature attacker)
{
}
@Override
protected void onEvtAggression(Creature target, int aggro)
{
}
@Override
protected void onIntentionAttack(Creature target)
{
if (target == null)
{
clientActionFailed();
return;
}
if (getIntention() == AI_INTENTION_REST)
{
clientActionFailed();
return;
}
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isControlBlocked())
{
clientActionFailed();
return;
}
// Set the Intention of this AbstractAI to AI_INTENTION_ATTACK
changeIntention(AI_INTENTION_ATTACK, target);
// Set the AI attack target
setTarget(target);
stopFollow();
// Launch the Think Event
notifyEvent(CtrlEvent.EVT_THINK, null);
}
@Override
protected void thinkAttack()
{
final Attackable npc = getActiveChar();
if (npc.isCastingNow() || npc.isCoreAIDisabled())
{
return;
}
final WorldObject target = getTarget();
final Creature originalAttackTarget = (target != null) && target.isCreature() ? (Creature) target : null;
// Check if target is dead or if timeout is expired to stop this attack
if ((originalAttackTarget == null) || originalAttackTarget.isAlikeDead())
{
// Stop hating this target after the attack timeout or if target is dead
if (originalAttackTarget != null)
{
npc.stopHating(originalAttackTarget);
}
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE);
npc.setWalking();
return;
}
final int collision = npc.getTemplate().getCollisionRadius();
setTarget(originalAttackTarget);
final int combinedCollision = collision + originalAttackTarget.getTemplate().getCollisionRadius();
if (!npc.isMovementDisabled() && (Rnd.get(100) <= 3))
{
for (Attackable nearby : World.getInstance().getVisibleObjects(npc, Attackable.class))
{
if (npc.isInsideRadius2D(nearby, collision) && (nearby != originalAttackTarget))
{
int newX = combinedCollision + Rnd.get(40);
if (Rnd.nextBoolean())
{
newX += originalAttackTarget.getX();
}
else
{
newX = originalAttackTarget.getX() - newX;
}
int newY = combinedCollision + Rnd.get(40);
if (Rnd.nextBoolean())
{
newY += originalAttackTarget.getY();
}
else
{
newY = originalAttackTarget.getY() - newY;
}
if (!npc.isInsideRadius2D(newX, newY, 0, collision))
{
final int newZ = npc.getZ() + 30;
if (GeoEngine.getInstance().canMoveToTarget(npc.getX(), npc.getY(), npc.getZ(), newX, newY, newZ, npc.getInstanceWorld()))
{
moveTo(newX, newY, newZ);
}
}
return;
}
}
}
// Dodge if its needed
if (!npc.isMovementDisabled() && (npc.getTemplate().getDodge() > 0))
{
if (Rnd.get(100) <= npc.getTemplate().getDodge())
{
final double distance2 = npc.calculateDistanceSq2D(originalAttackTarget);
if (Math.sqrt(distance2) <= (60 + combinedCollision))
{
int posX = npc.getX();
int posY = npc.getY();
final int posZ = npc.getZ() + 30;
if (originalAttackTarget.getX() < posX)
{
posX += 300;
}
else
{
posX -= 300;
}
if (originalAttackTarget.getY() < posY)
{
posY += 300;
}
else
{
posY -= 300;
}
if (GeoEngine.getInstance().canMoveToTarget(npc.getX(), npc.getY(), npc.getZ(), posX, posY, posZ, npc.getInstanceWorld()))
{
setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new Location(posX, posY, posZ, 0));
}
return;
}
}
}
final double dist = npc.calculateDistance2D(originalAttackTarget);
final int dist2 = (int) dist - collision;
int range = npc.getPhysicalAttackRange() + combinedCollision;
if (originalAttackTarget.isMoving())
{
range += 50;
if (npc.isMoving())
{
range += 50;
}
}
if ((dist2 > range) || !GeoEngine.getInstance().canSeeTarget(npc, originalAttackTarget))
{
if (originalAttackTarget.isMoving())
{
range -= 100;
}
if (range < 5)
{
range = 5;
}
moveToPawn(originalAttackTarget, range);
return;
}
_actor.doAutoAttack(originalAttackTarget);
}
@Override
protected void thinkCast()
{
final WorldObject target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
if (checkTargetLost(target))
{
setTarget(null);
return;
}
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
{
return;
}
_actor.doCast(_skill, _item, _forceUse, _dontMove);
}
}

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 org.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,113 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.Playable;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
import org.l2jmobius.gameserver.model.zone.ZoneId;
import org.l2jmobius.gameserver.network.SystemMessageId;
/**
* This class manages AI of Playable.<br>
* PlayableAI :
* <li>SummonAI</li>
* <li>PlayerAI</li>
* @author JIV
*/
public abstract class PlayableAI extends CreatureAI
{
public PlayableAI(Playable playable)
{
super(playable);
}
@Override
protected void onIntentionAttack(Creature target)
{
if ((target != null) && target.isPlayable())
{
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getReputation() < 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().getReputation() < 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, WorldObject target, ItemInstance item, boolean forceUse, boolean dontMove)
{
if ((target != null) && (target.isPlayable()) && skill.isBad())
{
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getReputation() < 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().getReputation() < 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) || (target.getActingPlayer().getLevel() <= 20)))
{
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
clientActionFailed();
return;
}
}
super.onIntentionCast(skill, target, item, forceUse, dontMove);
}
}

View File

@ -0,0 +1,379 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_CAST;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_INTERACT;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_MOVE_TO;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_PICK_UP;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_REST;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.instance.StaticObjectInstance;
import org.l2jmobius.gameserver.model.interfaces.ILocational;
import org.l2jmobius.gameserver.model.skills.Skill;
import org.l2jmobius.gameserver.model.skills.targets.TargetType;
public class PlayerAI extends PlayableAI
{
private boolean _thinking; // to prevent recursive thinking
private IntentionCommand _nextIntention = null;
public PlayerAI(PlayerInstance player)
{
super(player);
}
private 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 PlayerAI if necessary and calls changeIntention in AbstractAI.
* @param intention The new Intention to set to the AI
* @param args The first parameter of the Intention
*/
@Override
protected synchronized void changeIntention(CtrlIntention intention, Object... args)
{
final Object localArg0 = args.length > 0 ? args[0] : null;
final Object localArg1 = args.length > 1 ? args[1] : null;
final Object globalArg0 = (_intentionArgs != null) && (_intentionArgs.length > 0) ? _intentionArgs[0] : null;
final Object globalArg1 = (_intentionArgs != null) && (_intentionArgs.length > 1) ? _intentionArgs[1] : null;
// do nothing unless CAST intention
// however, forget interrupted actions when starting to use an offensive skill
if ((intention != AI_INTENTION_CAST) || ((Skill) args[0]).isBad())
{
_nextIntention = null;
super.changeIntention(intention, args);
return;
}
// do nothing if next intention is same as current one.
if ((intention == _intention) && (globalArg0 == localArg0) && (globalArg1 == localArg1))
{
super.changeIntention(intention, args);
return;
}
// save current intention so it can be used after cast
saveNextIntention(_intention, globalArg0, globalArg1);
super.changeIntention(intention, args);
}
/**
* 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 CreatureAI 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
final IntentionCommand nextIntention = _nextIntention;
if (nextIntention != null)
{
if (nextIntention._crtlIntention != AI_INTENTION_CAST) // previous state shouldn't be casting
{
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 onEvtAttacked(Creature attacker)
{
super.onEvtAttacked(attacker);
// Summons in defending mode defend its master when attacked.
if (_actor.getActingPlayer().hasServitors())
{
_actor.getActingPlayer().getServitors().values().stream().filter(summon -> ((SummonAI) summon.getAI()).isDefending()).forEach(summon -> ((SummonAI) summon.getAI()).defendAttack(attacker));
}
}
@Override
protected void onEvtEvaded(Creature attacker)
{
super.onEvtEvaded(attacker);
// Summons in defending mode defend its master when attacked.
if (_actor.getActingPlayer().hasServitors())
{
_actor.getActingPlayer().getServitors().values().stream().filter(summon -> ((SummonAI) summon.getAI()).isDefending()).forEach(summon -> ((SummonAI) summon.getAI()).defendAttack(attacker));
}
}
@Override
protected void onIntentionRest()
{
if (getIntention() != AI_INTENTION_REST)
{
changeIntention(AI_INTENTION_REST);
setTarget(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(ILocational loc)
{
if (getIntention() == AI_INTENTION_REST)
{
// Cancel action client side by sending Server->Client packet ActionFailed to the PlayerInstance actor
clientActionFailed();
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);
// Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
clientStopAutoAttack();
// Abort the attack of the Creature 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 WorldObject target = getTarget();
if ((target == null) || !target.isCreature())
{
return;
}
if (checkTargetLostOrDead((Creature) target))
{
// Notify the target
setTarget(null);
return;
}
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange()))
{
return;
}
clientStopMoving(null);
_actor.doAutoAttack((Creature) target);
}
private void thinkCast()
{
final WorldObject target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
if ((_skill.getTargetType() == TargetType.GROUND) && _actor.isPlayer())
{
if (maybeMoveToPosition(((PlayerInstance) _actor).getCurrentSkillWorldPosition(), _actor.getMagicalAttackRange(_skill)))
{
return;
}
}
else
{
if (checkTargetLost(target))
{
if (_skill.isBad() && (target != null))
{
// Notify the target
setTarget(null);
}
return;
}
if ((target != null) && maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
{
return;
}
}
_actor.doCast(_skill, _item, _forceUse, _dontMove);
}
private void thinkPickUp()
{
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow())
{
return;
}
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
getActor().doPickupItem(target);
}
private void thinkInteract()
{
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow())
{
return;
}
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
if (!(target instanceof StaticObjectInstance))
{
getActor().doInteract((Creature) 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;
}
}
@Override
public PlayerInstance getActor()
{
return (PlayerInstance) super.getActor();
}
}

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 org.l2jmobius.gameserver.ai;
import org.l2jmobius.gameserver.model.actor.instance.ShuttleInstance;
import org.l2jmobius.gameserver.network.serverpackets.shuttle.ExShuttleMove;
/**
* @author UnAfraid
*/
public class ShuttleAI extends VehicleAI
{
public ShuttleAI(ShuttleInstance shuttle)
{
super(shuttle);
}
@Override
public void moveTo(int x, int y, int z)
{
if (!_actor.isMovementDisabled())
{
_clientMoving = true;
_actor.moveToLocation(x, y, z, 0);
_actor.broadcastPacket(new ExShuttleMove(getActor(), x, y, z));
}
}
@Override
public ShuttleInstance getActor()
{
return (ShuttleInstance) _actor;
}
}

View File

@ -0,0 +1,386 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.geoengine.GeoEngine;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.Summon;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
import org.l2jmobius.gameserver.model.skills.SkillCaster;
public class SummonAI extends PlayableAI implements Runnable
{
private static final int AVOID_RADIUS = 70;
private volatile boolean _thinking; // to prevent recursive thinking
private volatile boolean _startFollow = ((Summon) _actor).getFollowStatus();
private Creature _lastAttack = null;
private volatile boolean _startAvoid;
private volatile boolean _isDefending;
private Future<?> _avoidTask = null;
public SummonAI(Summon summon)
{
super(summon);
}
@Override
protected void onIntentionIdle()
{
stopFollow();
_startFollow = false;
onIntentionActive();
}
@Override
protected void onIntentionActive()
{
final Summon summon = (Summon) _actor;
if (_startFollow)
{
setIntention(AI_INTENTION_FOLLOW, summon.getOwner());
}
else
{
super.onIntentionActive();
}
}
@Override
synchronized void changeIntention(CtrlIntention intention, Object... args)
{
switch (intention)
{
case AI_INTENTION_ACTIVE:
case AI_INTENTION_FOLLOW:
{
startAvoidTask();
break;
}
default:
{
stopAvoidTask();
}
}
super.changeIntention(intention, args);
}
private void thinkAttack()
{
final WorldObject target = getTarget();
final Creature attackTarget = (target != null) && target.isCreature() ? (Creature) target : null;
if (checkTargetLostOrDead(attackTarget))
{
setTarget(null);
((Summon) _actor).setFollowStatus(true);
return;
}
if (maybeMoveToPawn(attackTarget, _actor.getPhysicalAttackRange()))
{
return;
}
clientStopMoving(null);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()
{
final Summon summon = (Summon) _actor;
if (summon.isCastingNow(SkillCaster::isAnyNormalType))
{
return;
}
final WorldObject target = _skill.getTarget(_actor, _skill.isBad(), _dontMove, false);
if (checkTargetLost(target))
{
setTarget(null);
summon.setFollowStatus(true);
return;
}
final boolean val = _startFollow;
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
{
return;
}
summon.setFollowStatus(false);
setIntention(AI_INTENTION_IDLE);
_startFollow = val;
_actor.doCast(_skill, _item, _skill.isBad(), _dontMove);
}
private void thinkPickUp()
{
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
getActor().doPickupItem(target);
}
private void thinkInteract()
{
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 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)
{
((Summon) _actor).setFollowStatus(_startFollow);
}
else
{
setIntention(AI_INTENTION_ATTACK, _lastAttack);
_lastAttack = null;
}
}
@Override
protected void onEvtAttacked(Creature attacker)
{
super.onEvtAttacked(attacker);
if (_isDefending)
{
defendAttack(attacker);
}
else
{
avoidAttack(attacker);
}
}
@Override
protected void onEvtEvaded(Creature attacker)
{
super.onEvtEvaded(attacker);
if (_isDefending)
{
defendAttack(attacker);
}
else
{
avoidAttack(attacker);
}
}
private void avoidAttack(Creature attacker)
{
// Don't move while casting. It breaks casting animation, but still casts the skill... looks so bugged.
if (_actor.isCastingNow())
{
return;
}
final Creature owner = getActor().getOwner();
// trying to avoid if summon near owner
if ((owner != null) && (owner != attacker) && owner.isInsideRadius3D(_actor, 2 * AVOID_RADIUS))
{
_startAvoid = true;
}
}
public void defendAttack(Creature attacker)
{
// Cannot defend while attacking or casting.
if (_actor.isAttackingNow() || _actor.isCastingNow())
{
return;
}
final Summon summon = getActor();
if ((summon.getOwner() != null) && (summon.getOwner() != attacker) && !summon.isMoving() && summon.canAttack(attacker, false) && summon.getOwner().isInsideRadius3D(_actor, 2 * AVOID_RADIUS))
{
summon.doAutoAttack(attacker);
}
}
@Override
public void run()
{
if (_startAvoid)
{
_startAvoid = false;
if (!_clientMoving && !_actor.isDead() && !_actor.isMovementDisabled() && (_actor.getMoveSpeed() > 0))
{
final int ownerX = ((Summon) _actor).getOwner().getX();
final int ownerY = ((Summon) _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 (GeoEngine.getInstance().canMoveToTarget(_actor.getX(), _actor.getY(), _actor.getZ(), targetX, targetY, _actor.getZ(), _actor.getInstanceWorld()))
{
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:
{
((Summon) _actor).setFollowStatus(_startFollow);
}
}
}
public void setStartFollowController(boolean val)
{
_startFollow = val;
}
@Override
protected void onIntentionCast(Skill skill, WorldObject target, ItemInstance item, boolean forceUse, boolean dontMove)
{
if (getIntention() == AI_INTENTION_ATTACK)
{
_lastAttack = (getTarget() != null) && getTarget().isCreature() ? (Creature) getTarget() : null;
}
else
{
_lastAttack = null;
}
super.onIntentionCast(skill, target, item, forceUse, dontMove);
}
private void startAvoidTask()
{
if (_avoidTask == null)
{
_avoidTask = ThreadPool.scheduleAtFixedRate(this, 100, 100);
}
}
private void stopAvoidTask()
{
if (_avoidTask != null)
{
_avoidTask.cancel(false);
_avoidTask = null;
}
}
@Override
public void stopAITask()
{
stopAvoidTask();
super.stopAITask();
}
@Override
public Summon getActor()
{
return (Summon) super.getActor();
}
/**
* @return if the summon is defending itself or master.
*/
public boolean isDefending()
{
return _isDefending;
}
/**
* @param isDefending set the summon to defend itself and master, or be passive and avoid while being attacked.
*/
public void setDefending(boolean isDefending)
{
_isDefending = isDefending;
}
}

View File

@ -0,0 +1,123 @@
/*
* 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 org.l2jmobius.gameserver.ai;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.Vehicle;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
/**
* @author DS
*/
public abstract class VehicleAI extends CreatureAI
{
/**
* Simple AI for vehicles
* @param vehicle
*/
public VehicleAI(Vehicle vehicle)
{
super(vehicle);
}
@Override
protected void onIntentionAttack(Creature target)
{
}
@Override
protected void onIntentionCast(Skill skill, WorldObject target, ItemInstance item, boolean forceUse, boolean dontMove)
{
}
@Override
protected void onIntentionFollow(Creature target)
{
}
@Override
protected void onIntentionPickUp(WorldObject item)
{
}
@Override
protected void onIntentionInteract(WorldObject object)
{
}
@Override
protected void onEvtAttacked(Creature attacker)
{
}
@Override
protected void onEvtAggression(Creature target, int aggro)
{
}
@Override
protected void onEvtActionBlocked(Creature attacker)
{
}
@Override
protected void onEvtRooted(Creature attacker)
{
}
@Override
protected void onEvtForgetObject(WorldObject 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(WorldObject pawn, int offset)
{
}
@Override
protected void clientStoppedMoving()
{
}
}

View File

@ -0,0 +1,198 @@
/*
* 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 org.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 org.l2jmobius.Config;
import org.l2jmobius.commons.util.file.filter.HTMLFilter;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.util.BuilderUtil;
/**
* @author Layane
* @author Zoey76
*/
public class HtmCache
{
private static final Logger LOGGER = Logger.getLogger(HtmCache.class.getName());
private static final HTMLFilter HTML_FILTER = new HTMLFilter();
private static final Map<String, String> HTML_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)
{
LOGGER.info("Html cache start...");
parseDir(f);
LOGGER.info("Cache[HTML]: " + String.format("%.3f", getMemoryUsage()) + " megabytes on " + _loadedFiles + " files loaded.");
}
else
{
HTML_CACHE.clear();
_loadedFiles = 0;
_bytesBuffLen = 0;
LOGGER.info("Cache[HTML]: Running lazy cache.");
}
}
public void reloadPath(File f)
{
parseDir(f);
LOGGER.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;
}
String content = null;
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis))
{
final int bytes = bis.available();
byte[] raw = new byte[bytes];
bis.read(raw);
content = new String(raw, "UTF-8");
content = content.replaceAll("(?s)<!--.*?-->", ""); // Remove html comments
final String oldContent = HTML_CACHE.put(file.toURI().getPath().substring(Config.DATAPACK_ROOT.toURI().getPath().length()), content);
if (oldContent == null)
{
_bytesBuffLen += bytes;
_loadedFiles++;
}
else
{
_bytesBuffLen = (_bytesBuffLen - oldContent.length()) + bytes;
}
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "Problem with htm file:", e);
}
return content;
}
public String getHtm(PlayerInstance player, String path)
{
final String prefix = player != null ? player.getHtmlPrefix() : "";
String newPath = prefix + path;
String content = HTML_CACHE.get(newPath);
if (Config.LAZY_CACHE && (content == null))
{
content = loadFile(new File(Config.DATAPACK_ROOT, newPath));
if (content == null)
{
content = loadFile(new File(Config.SCRIPT_ROOT, newPath));
}
}
// In case localisation does not exist try the default path.
if ((content == null) && !prefix.contentEquals(""))
{
content = HTML_CACHE.get(path);
newPath = path;
}
if ((player != null) && player.isGM() && Config.GM_DEBUG_HTML_PATHS)
{
BuilderUtil.sendHtmlMessage(player, newPath.substring(5));
}
return content;
}
public boolean contains(String path)
{
return HTML_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,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 org.l2jmobius.gameserver.cache;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
/**
* @author -Nemesiss-
*/
public class WarehouseCacheManager
{
final Map<PlayerInstance, Long> _cachedWh = new ConcurrentHashMap<>();
final long _cacheTime = Config.WAREHOUSE_CACHE_TIME * 60000;
protected WarehouseCacheManager()
{
ThreadPool.scheduleAtFixedRate(new CacheScheduler(), 120000, 60000);
}
public void addCacheTask(PlayerInstance pc)
{
_cachedWh.put(pc, System.currentTimeMillis());
}
public void remCacheTask(PlayerInstance pc)
{
_cachedWh.remove(pc);
}
private class CacheScheduler implements Runnable
{
public CacheScheduler()
{
}
@Override
public void run()
{
final long cTime = System.currentTimeMillis();
for (PlayerInstance 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 org.l2jmobius.gameserver.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.TopicBBSManager;
public class Forum
{
private static final Logger LOGGER = 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 Collection<Forum> _children;
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 org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager# addForum(org.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;
_children = ConcurrentHashMap.newKeySet();
}
/**
* @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;
_children = ConcurrentHashMap.newKeySet();
parent._children.add(this);
ForumsBBSManager.getInstance().addForum(this);
_loaded = true;
}
private void load()
{
try (Connection con = DatabaseFactory.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)
{
LOGGER.log(Level.WARNING, "Data error on Forum " + _forumId + " : " + e.getMessage(), e);
}
try (Connection con = DatabaseFactory.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)
{
LOGGER.log(Level.WARNING, "Data error on Forum " + _forumId + " : " + e.getMessage(), e);
}
}
private void getChildren()
{
try (Connection con = DatabaseFactory.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)
{
LOGGER.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.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)
{
LOGGER.log(Level.WARNING, "Error while saving new Forum to db " + e.getMessage(), e);
}
}
public void vload()
{
if (!_loaded)
{
load();
getChildren();
_loaded = true;
}
}
}

View File

@ -0,0 +1,180 @@
/*
* 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 org.l2jmobius.gameserver.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.communitybbs.Manager.PostBBSManager;
/**
* @author Maktakien
*/
public class Post
{
private static Logger LOGGER = 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 Collection<CPost> _post;
/**
* @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)
{
_post = ConcurrentHashMap.newKeySet();
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);
}
private void insertindb(CPost cp)
{
try (Connection con = DatabaseFactory.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)
{
LOGGER.log(Level.WARNING, "Error while saving new Post to db " + e.getMessage(), e);
}
}
public Post(Topic t)
{
_post = ConcurrentHashMap.newKeySet();
load(t);
}
public CPost getCPost(int id)
{
int i = 0;
for (CPost cp : _post)
{
if (i++ == id)
{
return cp;
}
}
return null;
}
public void deleteme(Topic t)
{
PostBBSManager.getInstance().delPostByTopic(t);
try (Connection con = DatabaseFactory.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)
{
LOGGER.log(Level.WARNING, "Error while deleting post: " + e.getMessage(), e);
}
}
/**
* @param t
*/
private void load(Topic t)
{
try (Connection con = DatabaseFactory.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)
{
LOGGER.log(Level.WARNING, "Data error on Post " + t.getForumID() + "/" + t.getID() + " : " + e.getMessage(), e);
}
}
/**
* @param i
*/
public void updatetxt(int i)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE posts SET post_txt=? WHERE post_id=? AND post_topic_id=? AND post_forum_id=?"))
{
final CPost cp = getCPost(i);
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)
{
LOGGER.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 org.l2jmobius.gameserver.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.communitybbs.Manager.TopicBBSManager;
public class Topic
{
private static final Logger LOGGER = 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();
}
}
private void insertindb()
{
try (Connection con = DatabaseFactory.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)
{
LOGGER.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(_id);
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM topic WHERE topic_id=? AND topic_forum_id=?"))
{
ps.setInt(1, _id);
ps.setInt(2, f.getID());
ps.execute();
}
catch (Exception e)
{
LOGGER.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 org.l2jmobius.gameserver.communitybbs.Manager;
import java.util.ArrayList;
import java.util.List;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.serverpackets.ShowBoard;
public abstract class BaseBBSManager
{
public abstract void parsecmd(String command, PlayerInstance player);
public abstract void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, PlayerInstance player);
/**
* @param html
* @param acha
*/
protected void send1001(String html, PlayerInstance acha)
{
if (html.length() < 8192)
{
acha.sendPacket(new ShowBoard(html, "1001"));
}
}
/**
* @param acha
*/
protected void send1002(PlayerInstance acha)
{
send1002(acha, " ", " ", "0");
}
/**
* @param player
* @param string
* @param string2
* @param string3
*/
protected void send1002(PlayerInstance player, String string, String string2, String string3)
{
final List<String> _arg = new ArrayList<>(20);
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add("0");
_arg.add(player.getName());
_arg.add(Integer.toString(player.getObjectId()));
_arg.add(player.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");
player.sendPacket(new ShowBoard(_arg));
}
}

View File

@ -0,0 +1,168 @@
/*
* 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 org.l2jmobius.gameserver.communitybbs.Manager;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.communitybbs.BB.Forum;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
public class ForumsBBSManager extends BaseBBSManager
{
private static Logger LOGGER = Logger.getLogger(ForumsBBSManager.class.getName());
private final Collection<Forum> _table;
private int _lastid = 1;
/**
* Instantiates a new forums bbs manager.
*/
protected ForumsBBSManager()
{
_table = ConcurrentHashMap.newKeySet();
try (Connection con = DatabaseFactory.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)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Data error on Forum (root): " + e.getMessage(), e);
}
}
/**
* Inits the root.
*/
public void initRoot()
{
_table.forEach(f -> f.vload());
LOGGER.info(getClass().getSimpleName() + ": 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, PlayerInstance player)
{
}
/**
* Gets the forum by name.
* @param name the forum name
* @return the forum by name
*/
public Forum getForumByName(String name)
{
for (Forum f : _table)
{
if (f.getName().equals(name))
{
return f;
}
}
return 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)
{
for (Forum f : _table)
{
if (f.getID() == idf)
{
return f;
}
}
return null;
}
@Override
public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, PlayerInstance player)
{
}
/**
* 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,188 @@
/*
* 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 org.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 org.l2jmobius.gameserver.communitybbs.BB.Forum;
import org.l2jmobius.gameserver.communitybbs.BB.Post;
import org.l2jmobius.gameserver.communitybbs.BB.Topic;
import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
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, PlayerInstance player)
{
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), player, 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), player, idp);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + command + " is not implemented yet</center><br><br></body></html>", player);
}
}
private void showEditPost(Topic topic, Forum forum, PlayerInstance player, int idp)
{
final Post p = getGPosttByTopic(topic);
if ((forum == null) || (topic == null) || (p == null))
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error, this forum, topic or post does not exist!</center><br><br></body></html>", player);
}
else
{
showHtmlEditPost(topic, player, forum, p);
}
}
private void showPost(Topic topic, Forum forum, PlayerInstance player, int ind)
{
if ((forum == null) || (topic == null))
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error: This forum is not implemented yet!</center></body></html>", player);
}
else if (forum.getType() == Forum.MEMO)
{
showMemoPost(topic, player, forum);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>The forum: " + forum.getName() + " is not implemented yet!</center></body></html>", player);
}
}
private void showHtmlEditPost(Topic topic, PlayerInstance player, Forum forum, Post p)
{
final String html = "<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 " + forum.getID() + ";" + 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, player);
send1002(player, p.getCPost(0).postTxt, topic.getName(), DateFormat.getInstance().format(new Date(topic.getDate())));
}
private void showMemoPost(Topic topic, PlayerInstance player, Forum forum)
{
final Post p = getGPosttByTopic(topic);
final Locale locale = Locale.getDefault();
final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
String mes = p.getCPost(0).postTxt.replace(">", "&gt;");
mes = mes.replace("<", "&lt;");
final String html = "<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.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>" + mes + "</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;" + forum.getID() + ";" + 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;" + forum.getID() + ";" + 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;" + 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>";
CommunityBoardHandler.separateAndSend(html, player);
}
@Override
public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, PlayerInstance player)
{
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>", player);
}
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>", player);
}
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>", player);
}
else
{
p.getCPost(idp).postTxt = ar4;
p.updatetxt(idp);
parsecmd("_bbsposts;read;" + f.getID() + ";" + t.getID(), player);
}
}
}
}
}
public static PostBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final PostBBSManager INSTANCE = new PostBBSManager();
}
}

View File

@ -0,0 +1,305 @@
/*
* 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 org.l2jmobius.gameserver.communitybbs.Manager;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import org.l2jmobius.gameserver.communitybbs.BB.Forum;
import org.l2jmobius.gameserver.communitybbs.BB.Post;
import org.l2jmobius.gameserver.communitybbs.BB.Topic;
import org.l2jmobius.gameserver.data.sql.impl.ClanTable;
import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
public class TopicBBSManager extends BaseBBSManager
{
private final Collection<Topic> _table = ConcurrentHashMap.newKeySet();
private final Map<Forum, Integer> _maxId = new ConcurrentHashMap<>();
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, PlayerInstance player)
{
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>", player);
}
else
{
f.vload();
final Topic t = new Topic(Topic.ConstructorType.CREATE, getInstance().getMaxID(f) + 1, Integer.parseInt(ar2), ar5, Calendar.getInstance().getTimeInMillis(), player.getName(), player.getObjectId(), Topic.MEMO, 0);
f.addTopic(t);
getInstance().setMaxID(t.getID(), f);
final Post p = new Post(player.getName(), player.getObjectId(), Calendar.getInstance().getTimeInMillis(), t.getID(), f.getID(), ar4);
PostBBSManager.getInstance().addPostByTopic(p, t);
parsecmd("_bbsmemo", player);
}
}
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>", player);
}
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>", player);
}
else
{
// CPost cp = null;
final Post p = PostBBSManager.getInstance().getGPosttByTopic(t);
if (p != null)
{
p.deleteme(t);
}
t.deleteme(f);
parsecmd("_bbsmemo", player);
}
}
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + ar1 + " is not implemented yet</center><br><br></body></html>", player);
}
}
@Override
public void parsecmd(String command, PlayerInstance player)
{
if (command.equals("_bbsmemo"))
{
showTopics(player.getMemo(), player, 1, player.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), player, 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), player, 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>", player);
}
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>", player);
}
else
{
// CPost cp = null;
final Post p = PostBBSManager.getInstance().getGPosttByTopic(t);
if (p != null)
{
p.deleteme(t);
}
t.deleteme(f);
parsecmd("_bbsmemo", player);
}
}
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + command + " is not implemented yet</center><br><br></body></html>", player);
}
}
private void showNewTopic(Forum forum, PlayerInstance player, int idf)
{
if (forum == null)
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " is not implemented yet</center><br><br></body></html>", player);
}
else if (forum.getType() == Forum.MEMO)
{
showMemoNewTopics(forum, player);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + forum.getName() + " is not implemented yet</center><br><br></body></html>", player);
}
}
private void showMemoNewTopics(Forum forum, PlayerInstance player)
{
final String html = "<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 " + 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, player);
send1002(player);
}
private void showTopics(Forum forum, PlayerInstance player, 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>", player);
}
else if (forum.getType() == Forum.MEMO)
{
showMemoTopics(forum, player, index);
}
else
{
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + forum.getName() + " is not implemented yet</center><br><br></body></html>", player);
}
}
private void showMemoTopics(Forum forum, PlayerInstance player, int index)
{
forum.vload();
final StringBuilder html = new StringBuilder(2000);
html.append("<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)
{
if (i++ >= (12 * (index - 1)))
{
html.append("<table border=0 cellspacing=0 cellpadding=5 WIDTH=610><tr><td FIXWIDTH=5></td><td FIXWIDTH=415><a action=\"bypass _bbsposts;read;" + forum.getID() + ";" + 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
{
html.append("<td><button action=\"bypass _bbstopics;read;" + forum.getID() + ";" + (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)
{
html.append("<td> " + i + " </td>");
}
else
{
html.append("<td><a action=\"bypass _bbstopics;read;" + forum.getID() + ";" + i + "\"> " + 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
{
html.append("<td><button action=\"bypass _bbstopics;read;" + forum.getID() + ";" + (index + 1) + "\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
}
html.append("</tr></table> </td> <td align=right><button value = \"&$421;\" action=\"bypass _bbstopics;crea;" + 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(), player);
}
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 org.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 org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.enums.ChatType;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.announce.Announcement;
import org.l2jmobius.gameserver.model.announce.AnnouncementType;
import org.l2jmobius.gameserver.model.announce.AutoAnnouncement;
import org.l2jmobius.gameserver.model.announce.IAnnouncement;
import org.l2jmobius.gameserver.network.serverpackets.CreatureSay;
/**
* Loads announcements from database.
* @author UnAfraid
*/
public 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.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(PlayerInstance 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
*/
private void sendAnnouncements(PlayerInstance 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,311 @@
/*
* 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 org.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 org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
/**
* Loads name and access level for all players.
* @version 2005/03/27
*/
public class CharNameTable
{
private static Logger LOGGER = 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 void addName(PlayerInstance player)
{
if (player != null)
{
addName(player.getObjectId(), player.getName());
_accessLevels.put(player.getObjectId(), player.getAccessLevel().getLevel());
}
}
private final void addName(int objectId, String name)
{
if (name != null)
{
if (!name.equals(_chars.get(objectId)))
{
_chars.put(objectId, name);
}
}
}
public void removeName(int objId)
{
_chars.remove(objId);
_accessLevels.remove(objId);
}
public 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.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("charId");
accessLevel = rs.getInt("accesslevel");
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char name: " + e.getMessage(), e);
}
if (id > 0)
{
_chars.put(id, name);
_accessLevels.put(id, accessLevel);
return id;
}
return -1; // not found
}
public 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.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("char_name");
_chars.put(id, name);
_accessLevels.put(id, rset.getInt("accesslevel"));
return name;
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char id: " + e.getMessage(), e);
}
return null; // not found
}
public int getAccessLevelById(int objectId)
{
return getNameById(objectId) != null ? _accessLevels.get(objectId) : 0;
}
public synchronized boolean doesCharNameExist(String name)
{
boolean result = false;
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) as count FROM characters WHERE char_name=?"))
{
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery())
{
if (rs.next())
{
result = rs.getInt("count") > 0;
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing charname: " + e.getMessage(), e);
}
return result;
}
public int getAccountCharacterCount(String account)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(char_name) as count FROM characters WHERE account_name=?"))
{
ps.setString(1, account);
try (ResultSet rset = ps.executeQuery())
{
if (rset.next())
{
return rset.getInt("count");
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, "Couldn't retrieve account for id: " + e.getMessage(), e);
}
return 0;
}
public int getLevelById(int objectId)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT level FROM characters WHERE charId = ?"))
{
ps.setInt(1, objectId);
try (ResultSet rset = ps.executeQuery())
{
if (rset.next())
{
return rset.getInt("level");
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char count: " + e.getMessage(), e);
}
return 0;
}
public int getClassIdById(int objectId)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT classid FROM characters WHERE charId = ?"))
{
ps.setInt(1, objectId);
try (ResultSet rset = ps.executeQuery())
{
if (rset.next())
{
return rset.getInt("classid");
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't retrieve class for id: " + e.getMessage(), e);
}
return 0;
}
public int getClanIdById(int objectId)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT clanId FROM characters WHERE charId = ?"))
{
ps.setInt(1, objectId);
try (ResultSet rset = ps.executeQuery())
{
while (rset.next())
{
return rset.getInt("clanId");
}
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char count: " + e.getMessage(), e);
}
return 0;
}
private void loadAll()
{
try (Connection con = DatabaseFactory.getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT charId, char_name, accesslevel FROM characters"))
{
while (rs.next())
{
final int id = rs.getInt("charId");
_chars.put(id, rs.getString("char_name"));
_accessLevels.put(id, rs.getInt("accesslevel"));
}
}
catch (SQLException e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't retrieve all char id/name/access: " + e.getMessage(), e);
}
LOGGER.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,259 @@
/*
* 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 org.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.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.data.xml.impl.NpcData;
import org.l2jmobius.gameserver.data.xml.impl.PetDataTable;
import org.l2jmobius.gameserver.data.xml.impl.SkillData;
import org.l2jmobius.gameserver.model.PetData;
import org.l2jmobius.gameserver.model.actor.instance.PetInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.instance.ServitorInstance;
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Skill;
import org.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, Set<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, summonId FROM character_summons";
private static final String LOAD_SUMMON = "SELECT summonSkillId, summonId, curHp, curMp, time FROM character_summons WHERE ownerId = ?";
private static final String REMOVE_SUMMON = "DELETE FROM character_summons WHERE ownerId = ? and summonId = ?";
private static final String SAVE_SUMMON = "REPLACE INTO character_summons (ownerId,summonId,summonSkillId,curHp,curMp,time) VALUES (?,?,?,?,?,?)";
public Map<Integer, Integer> getPets()
{
return _pets;
}
public Map<Integer, Set<Integer>> getServitors()
{
return _servitors;
}
public void init()
{
if (Config.RESTORE_SERVITOR_ON_RECONNECT)
{
try (Connection con = DatabaseFactory.getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery(INIT_SUMMONS))
{
while (rs.next())
{
_servitors.computeIfAbsent(rs.getInt("ownerId"), k -> ConcurrentHashMap.newKeySet()).add(rs.getInt("summonId"));
}
}
catch (Exception e)
{
LOGGER.warning(getClass().getSimpleName() + ": Error while loading saved servitor: " + e);
}
}
if (Config.RESTORE_PET_ON_RECONNECT)
{
try (Connection con = DatabaseFactory.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(PlayerInstance player, int summonObjectId)
{
_servitors.computeIfPresent(player.getObjectId(), (k, v) ->
{
v.remove(summonObjectId);
return !v.isEmpty() ? v : null;
});
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement(REMOVE_SUMMON))
{
ps.setInt(1, player.getObjectId());
ps.setInt(2, summonObjectId);
ps.execute();
}
catch (SQLException e)
{
LOGGER.warning(getClass().getSimpleName() + ": Summon cannot be removed: " + e);
}
}
public void restorePet(PlayerInstance player)
{
final ItemInstance item = player.getInventory().getItemByObjectId(_pets.get(player.getObjectId()));
if (item == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet summoning item for: " + player);
return;
}
final PetData petData = PetDataTable.getInstance().getPetDataByItemId(item.getId());
if (petData == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet data for: " + player + " and summoning item: " + item);
return;
}
final NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(petData.getNpcId());
if (npcTemplate == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet NPC template for: " + player + " and pet Id:" + petData.getNpcId());
return;
}
final PetInstance pet = PetInstance.spawnPet(npcTemplate, player, item);
if (pet == null)
{
LOGGER.warning(getClass().getSimpleName() + ": Null pet instance for: " + player + " and pet NPC template:" + npcTemplate);
return;
}
pet.setShowSummonAnimation(true);
pet.setTitle(player.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());
player.setPet(pet);
pet.spawnMe(player.getX() + 50, player.getY() + 100, player.getZ());
pet.startFeed();
pet.setFollowStatus(true);
pet.getOwner().sendPacket(new PetItemList(pet.getInventory().getItems()));
pet.broadcastStatusUpdate();
}
public void restoreServitor(PlayerInstance player)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement(LOAD_SUMMON))
{
ps.setInt(1, player.getObjectId());
try (ResultSet rs = ps.executeQuery())
{
Skill skill;
while (rs.next())
{
final int summonObjId = rs.getInt("summonId");
final int skillId = rs.getInt("summonSkillId");
final int curHp = rs.getInt("curHp");
final int curMp = rs.getInt("curMp");
final int time = rs.getInt("time");
skill = SkillData.getInstance().getSkill(skillId, player.getSkillLevel(skillId));
if (skill == null)
{
removeServitor(player, summonObjId);
return;
}
skill.applyEffects(player, player);
if (player.hasServitors())
{
final ServitorInstance summon = player.getServitors().values().stream().map(s -> ((ServitorInstance) s)).filter(s -> s.getReferenceSkill() == skillId).findAny().orElse(null);
summon.setCurrentHp(curHp);
summon.setCurrentMp(curMp);
summon.setLifeTimeRemaining(time);
}
}
}
}
catch (SQLException e)
{
LOGGER.warning(getClass().getSimpleName() + ": Servitor cannot be restored: " + e);
}
}
public void saveSummon(ServitorInstance summon)
{
if ((summon == null) || (summon.getLifeTimeRemaining() <= 0))
{
return;
}
_servitors.computeIfAbsent(summon.getOwner().getObjectId(), k -> ConcurrentHashMap.newKeySet()).add(summon.getObjectId());
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement(SAVE_SUMMON))
{
ps.setInt(1, summon.getOwner().getObjectId());
ps.setInt(2, summon.getObjectId());
ps.setInt(3, summon.getReferenceSkill());
ps.setInt(4, (int) Math.round(summon.getCurrentHp()));
ps.setInt(5, (int) Math.round(summon.getCurrentMp()));
ps.setInt(6, 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,489 @@
/*
* 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 org.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 org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager;
import org.l2jmobius.gameserver.data.xml.impl.ClanHallData;
import org.l2jmobius.gameserver.enums.ClanWarState;
import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.idfactory.IdFactory;
import org.l2jmobius.gameserver.instancemanager.ClanEntryManager;
import org.l2jmobius.gameserver.instancemanager.FortManager;
import org.l2jmobius.gameserver.instancemanager.FortSiegeManager;
import org.l2jmobius.gameserver.instancemanager.SiegeManager;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.clan.Clan;
import org.l2jmobius.gameserver.model.clan.ClanMember;
import org.l2jmobius.gameserver.model.clan.ClanPrivilege;
import org.l2jmobius.gameserver.model.clan.ClanWar;
import org.l2jmobius.gameserver.model.entity.ClanHall;
import org.l2jmobius.gameserver.model.entity.Fort;
import org.l2jmobius.gameserver.model.entity.FortSiege;
import org.l2jmobius.gameserver.model.entity.Siege;
import org.l2jmobius.gameserver.model.events.EventDispatcher;
import org.l2jmobius.gameserver.model.events.impl.clan.OnClanWarFinish;
import org.l2jmobius.gameserver.model.events.impl.creature.player.OnPlayerClanCreate;
import org.l2jmobius.gameserver.model.events.impl.creature.player.OnPlayerClanDestroy;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.PledgeShowInfoUpdate;
import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListAll;
import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListUpdate;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import org.l2jmobius.gameserver.util.EnumIntBitmask;
import org.l2jmobius.gameserver.util.Util;
/**
* This class loads the clan related data.
*/
public class ClanTable
{
private static final Logger LOGGER = Logger.getLogger(ClanTable.class.getName());
private final Map<Integer, Clan> _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();
}
// Get all clan ids.
final List<Integer> cids = new ArrayList<>();
try (Connection con = DatabaseFactory.getConnection();
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT clan_id FROM clan_data"))
{
while (rs.next())
{
cids.add(rs.getInt("clan_id"));
}
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, "Error restoring ClanTable.", e);
}
// Initialize clans.
for (int cid : cids)
{
final Clan clan = new Clan(cid);
_clans.put(cid, clan);
if (clan.getDissolvingExpiryTime() != 0)
{
scheduleRemoveClan(clan.getId());
}
}
LOGGER.info(getClass().getSimpleName() + ": Restored " + cids.size() + " clans from the database.");
allianceCheck();
restoreClanWars();
}
/**
* Gets the clans.
* @return the clans
*/
public Collection<Clan> getClans()
{
return _clans.values();
}
/**
* Gets the clan count.
* @return the clan count
*/
public int getClanCount()
{
return _clans.size();
}
/**
* @param clanId
* @return
*/
public Clan getClan(int clanId)
{
return _clans.get(clanId);
}
public Clan getClanByName(String clanName)
{
return _clans.values().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 Clan createClan(PlayerInstance player, String clanName)
{
if (player == null)
{
return null;
}
if (player.getLevel() < 10)
{
player.sendPacket(SystemMessageId.YOU_DO_NOT_MEET_THE_CRITERIA_IN_ORDER_TO_CREATE_A_CLAN);
return null;
}
if (player.getClanId() != 0)
{
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) || (clanName.length() < 2))
{
player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID);
return null;
}
if (clanName.length() > 16)
{
player.sendPacket(SystemMessageId.CLAN_NAME_S_LENGTH_IS_INCORRECT);
return null;
}
if (getClanByName(clanName) != null)
{
// clan name is already taken
final SystemMessage sm = new SystemMessage(SystemMessageId.S1_ALREADY_EXISTS);
sm.addString(clanName);
player.sendPacket(sm);
return null;
}
final Clan clan = new Clan(IdFactory.getInstance().getNextId(), clanName);
final ClanMember leader = new ClanMember(clan, player);
clan.setLeader(leader);
leader.setPlayerInstance(player);
clan.store();
player.setClan(clan);
player.setPledgeClass(ClanMember.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));
PledgeShowMemberListAll.sendAllTo(player);
player.sendPacket(new PledgeShowMemberListUpdate(player));
player.sendPacket(SystemMessageId.YOUR_CLAN_HAS_BEEN_CREATED);
player.broadcastUserInfo(UserInfoType.RELATION, UserInfoType.CLAN);
// Notify to scripts
EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanCreate(player, clan));
return clan;
}
public synchronized void destroyClan(int clanId)
{
final Clan clan = getClan(clanId);
if (clan == null)
{
return;
}
clan.broadcastToOnlineMembers(new SystemMessage(SystemMessageId.CLAN_HAS_DISPERSED));
ClanEntryManager.getInstance().removeFromClanList(clan.getId());
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 ClanHall hall = ClanHallData.getInstance().getClanHallByClan(clan);
if (hall != null)
{
hall.setOwner(null);
}
final ClanMember leaderMember = clan.getLeader();
if (leaderMember == null)
{
clan.getWarehouse().destroyAllItems("ClanRemove", null, null);
}
else
{
clan.getWarehouse().destroyAllItems("ClanRemove", clan.getLeader().getPlayerInstance(), null);
}
for (ClanMember member : clan.getMembers())
{
clan.removeClanMember(member.getObjectId(), 0);
}
_clans.remove(clanId);
IdFactory.getInstance().releaseId(clanId);
try (Connection con = DatabaseFactory.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 (fortId != 0)
{
final Fort fort = FortManager.getInstance().getFortById(fortId);
if (fort != null)
{
final Clan owner = fort.getOwnerClan();
if (clan == owner)
{
fort.removeOwner(true);
}
}
}
}
catch (Exception e)
{
LOGGER.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)
{
ThreadPool.schedule(() ->
{
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 (Clan clan : _clans.values())
{
if ((clan.getAllyName() != null) && clan.getAllyName().equalsIgnoreCase(allyName))
{
return true;
}
}
return false;
}
public void storeClanWars(ClanWar war)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement("REPLACE INTO clan_wars (clan1, clan2, clan1Kill, clan2Kill, winnerClan, startTime, endTime, state) VALUES(?,?,?,?,?,?,?,?)"))
{
ps.setInt(1, war.getAttackerClanId());
ps.setInt(2, war.getAttackedClanId());
ps.setInt(3, war.getAttackerKillCount());
ps.setInt(4, war.getAttackedKillCount());
ps.setInt(5, war.getWinnerClanId());
ps.setLong(6, war.getStartTime());
ps.setLong(7, war.getEndTime());
ps.setInt(8, war.getState().ordinal());
ps.execute();
}
catch (Exception e)
{
LOGGER.severe("Error storing clan wars data: " + e);
}
}
public void deleteClanWars(int clanId1, int clanId2)
{
final Clan clan1 = getInstance().getClan(clanId1);
final Clan clan2 = getInstance().getClan(clanId2);
EventDispatcher.getInstance().notifyEventAsync(new OnClanWarFinish(clan1, clan2));
clan1.deleteWar(clan2.getId());
clan2.deleteWar(clan1.getId());
clan1.broadcastClanStatus();
clan2.broadcastClanStatus();
try (Connection con = DatabaseFactory.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)
{
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error removing clan wars data.", e);
}
}
private void restoreClanWars()
{
try (Connection con = DatabaseFactory.getConnection();
Statement statement = con.createStatement();
ResultSet rset = statement.executeQuery("SELECT clan1, clan2, clan1Kill, clan2Kill, winnerClan, startTime, endTime, state FROM clan_wars"))
{
while (rset.next())
{
final Clan attacker = getClan(rset.getInt("clan1"));
final Clan attacked = getClan(rset.getInt("clan2"));
if ((attacker != null) && (attacked != null))
{
final ClanWarState state = ClanWarState.values()[rset.getInt("state")];
final ClanWar clanWar = new ClanWar(attacker, attacked, rset.getInt("clan1Kill"), rset.getInt("clan2Kill"), rset.getInt("winnerClan"), rset.getLong("startTime"), rset.getLong("endTime"), state);
attacker.addWar(attacked.getId(), clanWar);
attacked.addWar(attacker.getId(), clanWar);
}
else
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Restorewars one of clans is null attacker:" + attacker + " attacked:" + attacked);
}
}
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error restoring clan wars data.", e);
}
}
/**
* Check for nonexistent alliances
*/
private void allianceCheck()
{
for (Clan 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();
LOGGER.info(getClass().getSimpleName() + ": Removed alliance from clan: " + clan);
}
}
}
public List<Clan> getClanAllies(int allianceId)
{
final List<Clan> clanAllies = new ArrayList<>();
if (allianceId != 0)
{
for (Clan clan : _clans.values())
{
if ((clan != null) && (clan.getAllyId() == allianceId))
{
clanAllies.add(clan);
}
}
}
return clanAllies;
}
public void shutdown()
{
for (Clan clan : _clans.values())
{
clan.updateInDB();
for (ClanWar war : clan.getWarList().values())
{
storeClanWars(war);
}
}
}
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 org.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 org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.Crest;
import org.l2jmobius.gameserver.model.Crest.CrestType;
import org.l2jmobius.gameserver.model.clan.Clan;
/**
* Loads and saves crests from database.
* @author NosBit
*/
public class CrestTable
{
private static final Logger LOGGER = Logger.getLogger(CrestTable.class.getName());
private final Map<Integer, Crest> _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 (Clan 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.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 Crest(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 (Clan 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 Crest} if crest is found, {@code null} if crest was not found.
*/
public Crest getCrest(int crestId)
{
return _crests.get(crestId);
}
/**
* Creates a {@code Crest} object and inserts it in database and cache.
* @param data
* @param crestType
* @return {@code Crest} on success, {@code null} on failure.
*/
public Crest createCrest(byte[] data, CrestType crestType)
{
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement statement = con.prepareStatement("INSERT INTO `crests`(`crest_id`, `data`, `type`) VALUES(?, ?, ?)"))
{
final Crest crest = new Crest(_nextId.getAndIncrement(), 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.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,491 @@
/*
* 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 org.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.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.enums.PrivateStoreType;
import org.l2jmobius.gameserver.instancemanager.PlayerCountManager;
import org.l2jmobius.gameserver.model.TradeItem;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.holders.SellBuffHolder;
import org.l2jmobius.gameserver.network.Disconnection;
import org.l2jmobius.gameserver.network.GameClient;
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`=?";
protected OfflineTradersTable()
{
}
public void storeOffliners()
{
try (Connection con = DatabaseFactory.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 (PlayerInstance pc : World.getInstance().getPlayers())
{
try
{
if ((pc.getPrivateStoreType() != PrivateStoreType.NONE) && ((pc.getClient() == null) || pc.getClient().isDetached()))
{
stm3.setInt(1, pc.getObjectId()); // Char Id
stm3.setLong(2, pc.getOfflineStartTime());
stm3.setInt(3, pc.isSellingBuffs() ? PrivateStoreType.SELL_BUFFS.getId() : 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();
if (pc.isSellingBuffs())
{
for (SellBuffHolder holder : pc.getSellingBuffs())
{
stm_items.setInt(1, pc.getObjectId());
stm_items.setInt(2, holder.getSkillId());
stm_items.setLong(3, 0);
stm_items.setLong(4, holder.getPrice());
stm_items.executeUpdate();
stm_items.clearParameters();
}
}
else
{
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 (Entry<Integer, Long> entry : pc.getManufactureItems().entrySet())
{
stm_items.setInt(1, pc.getObjectId());
stm_items.setInt(2, entry.getKey());
stm_items.setLong(3, 0);
stm_items.setLong(4, entry.getValue());
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.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 int typeId = rs.getInt("type");
boolean isSellBuff = false;
if (typeId == PrivateStoreType.SELL_BUFFS.getId())
{
isSellBuff = true;
}
final PrivateStoreType type = isSellBuff ? PrivateStoreType.PACKAGE_SELL : PrivateStoreType.findById(typeId);
if (type == null)
{
LOGGER.warning(getClass().getSimpleName() + ": PrivateStoreType with id " + rs.getInt("type") + " could not be found.");
continue;
}
if (type == PrivateStoreType.NONE)
{
continue;
}
PlayerInstance player = null;
try
{
final GameClient client = new GameClient();
client.setDetached(true);
player = PlayerInstance.load(rs.getInt("charId"));
client.setPlayer(player);
player.setOnlineStatus(true, false);
client.setAccountName(player.getAccountNamePlayer());
player.setClient(client);
player.setOfflineStartTime(time);
if (isSellBuff)
{
player.setIsSellingBuffs(true);
}
player.spawnMe(player.getX(), player.getY(), player.getZ());
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:
{
if (player.isSellingBuffs())
{
while (items.next())
{
player.getSellingBuffs().add(new SellBuffHolder(items.getInt("item"), items.getLong("price")));
}
}
else
{
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:
{
final Map<Integer, Long> manufactureItems = new HashMap<>();
while (items.next())
{
manufactureItems.put(items.getInt(2), items.getLong(4));
}
player.setManufactureItems(manufactureItems);
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();
PlayerCountManager.getInstance().incOfflineTradeCount();
nTraders++;
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error loading trader: " + player, e);
if (player != null)
{
Disconnection.of(player).defaultSequence(false);
}
}
}
LOGGER.info(getClass().getSimpleName() + ": Loaded " + nTraders + " offline traders.");
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(PlayerInstance trader, boolean finished, boolean firstCall)
{
try (Connection con = DatabaseFactory.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();
}
if (trader.isSellingBuffs())
{
for (SellBuffHolder holder : trader.getSellingBuffs())
{
stm3.setInt(1, trader.getObjectId());
stm3.setInt(2, holder.getSkillId());
stm3.setLong(3, 0);
stm3.setLong(4, holder.getPrice());
stm3.executeUpdate();
stm3.clearParameters();
}
}
else
{
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 (Entry<Integer, Long> entry : trader.getManufactureItems().entrySet())
{
stm3.setInt(1, trader.getObjectId());
stm3.setInt(2, entry.getKey());
stm3.setLong(3, 0);
stm3.setLong(4, entry.getValue());
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.isSellingBuffs() ? PrivateStoreType.SELL_BUFFS.getId() : 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);
}
}
public static synchronized void removeTrader(int traderObjId)
{
PlayerCountManager.getInstance().decOfflineTradeCount();
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement stm1 = con.prepareStatement(CLEAR_OFFLINE_TABLE_ITEMS_PLAYER);
PreparedStatement stm2 = con.prepareStatement(CLEAR_OFFLINE_TABLE_PLAYER))
{
stm1.setInt(1, traderObjId);
stm1.execute();
stm1.close();
stm2.setInt(1, traderObjId);
stm2.execute();
stm2.close();
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "OfflineTradersTable[removeTrader()]: Error while removing offline trader: " + traderObjId + " " + 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 org.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 org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.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.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 (char aChar : chars)
{
if (!Character.isLetterOrDigit(aChar))
{
result = false;
break;
}
}
return result;
}
private static class SingletonHolder
{
protected static final PetNameTable INSTANCE = new PetNameTable();
}
}

View File

@ -0,0 +1,92 @@
/*
* 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 org.l2jmobius.gameserver.data.sql.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.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<>();
public Map<Integer, Map<Integer, Map<Integer, List<SummonEffect>>>> getServitorEffectsOwner()
{
return _servitorEffects;
}
public Map<Integer, List<SummonEffect>> getServitorEffects(PlayerInstance owner)
{
final Map<Integer, Map<Integer, List<SummonEffect>>> servitorMap = _servitorEffects.get(owner.getObjectId());
if (servitorMap == null)
{
return null;
}
return servitorMap.get(owner.getClassIndex());
}
/** Pets **/
private final Map<Integer, List<SummonEffect>> _petEffects = new HashMap<>(); // key: petItemObjectId, value: Effects list
public Map<Integer, List<SummonEffect>> getPetEffects()
{
return _petEffects;
}
public static class SummonEffect
{
private final Skill _skill;
private final 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,96 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.model.ActionDataHolder;
import org.l2jmobius.gameserver.model.StatsSet;
/**
* @author UnAfraid
*/
public class ActionData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(ActionData.class.getName());
private final Map<Integer, ActionDataHolder> _actionData = new HashMap<>();
private final Map<Integer, Integer> _actionSkillsData = new HashMap<>(); // skillId, actionId
protected ActionData()
{
load();
}
@Override
public void load()
{
_actionData.clear();
_actionSkillsData.clear();
parseDatapackFile("data/ActionData.xml");
_actionData.values().stream().filter(h -> h.getHandler().equals("PetSkillUse") || h.getHandler().equals("ServitorSkillUse")).forEach(h -> _actionSkillsData.put(h.getOptionId(), h.getId()));
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _actionData.size() + " player actions.");
}
@Override
public void parseDocument(Document doc, File f)
{
forEach(doc, "list", listNode -> forEach(listNode, "action", actionNode ->
{
final ActionDataHolder holder = new ActionDataHolder(new StatsSet(parseAttributes(actionNode)));
_actionData.put(holder.getId(), holder);
}));
}
/**
* @param id
* @return the ActionDataHolder for specified id
*/
public ActionDataHolder getActionData(int id)
{
return _actionData.get(id);
}
/**
* @param skillId
* @return the actionId corresponding to the skillId or -1 if no actionId is found for the specified skill.
*/
public int getSkillActionId(int skillId)
{
return _actionSkillsData.getOrDefault(skillId, -1);
}
/**
* Gets the single instance of ActionData.
* @return single instance of ActionData
*/
public static ActionData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ActionData INSTANCE = new ActionData();
}
}

View File

@ -0,0 +1,355 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
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.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.model.AccessLevel;
import org.l2jmobius.gameserver.model.AdminCommandAccessRight;
import org.l2jmobius.gameserver.model.StatsSet;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
/**
* Loads administrator access levels and commands.
* @author UnAfraid
*/
public class AdminData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(AdminData.class.getName());
private final Map<Integer, AccessLevel> _accessLevels = new HashMap<>();
private final Map<String, AdminCommandAccessRight> _adminCommandAccessRights = new HashMap<>();
private final Map<PlayerInstance, 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.info(getClass().getSimpleName() + ": Loaded " + _accessLevels.size() + " access levels.");
parseDatapackFile("config/AdminCommands.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _adminCommandAccessRights.size() + " access commands.");
}
@Override
public void parseDocument(Document doc, File f)
{
NamedNodeMap attrs;
Node attr;
StatsSet set;
AccessLevel level;
AdminCommandAccessRight command;
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());
}
level = new AccessLevel(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());
}
command = new AdminCommandAccessRight(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 AccessLevel getAccessLevel(int accessLevelNum)
{
if (accessLevelNum < 0)
{
return _accessLevels.get(-1);
}
return _accessLevels.get(accessLevelNum);
}
/**
* Gets the master access level.
* @return the master access level
*/
public AccessLevel 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, AccessLevel accessLevel)
{
AdminCommandAccessRight 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 AdminCommandAccessRight(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 AdminCommandAccessRight 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<PlayerInstance> getAllGms(boolean includeHidden)
{
final List<PlayerInstance> tmpGmList = new ArrayList<>();
for (Entry<PlayerInstance, 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<PlayerInstance, 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 PlayerInstance player to the Set _gmList.
* @param player the player
* @param hidden the hidden
*/
public void addGm(PlayerInstance player, boolean hidden)
{
_gmList.put(player, hidden);
}
/**
* Delete a GM.
* @param player the player
*/
public void deleteGm(PlayerInstance player)
{
_gmList.remove(player);
}
/**
* GM will be displayed on clients GM list.
* @param player the player
*/
public void showGm(PlayerInstance 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(PlayerInstance 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<PlayerInstance, Boolean> entry : _gmList.entrySet())
{
if (includeHidden || !entry.getValue())
{
return true;
}
}
return false;
}
/**
* Send list to player.
* @param player the player
*/
public void sendListToPlayer(PlayerInstance player)
{
if (isGmOnline(player.isGM()))
{
player.sendPacket(SystemMessageId.GM_LIST);
for (String name : getAllGmNames(player.isGM()))
{
final SystemMessage sm = new SystemMessage(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(IClientOutgoingPacket packet)
{
for (PlayerInstance gm : getAllGms(true))
{
gm.sendPacket(packet);
}
}
/**
* Broadcast message to GMs.
* @param message the message
* @return the message that was broadcasted
*/
public String broadcastMessageToGMs(String message)
{
for (PlayerInstance gm : getAllGms(true))
{
gm.sendMessage(message);
}
return 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,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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.model.StatsSet;
import org.l2jmobius.gameserver.model.alchemy.AlchemyCraftData;
import org.l2jmobius.gameserver.model.holders.ItemHolder;
/**
* @author Sdw
*/
public class AlchemyData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(AlchemyData.class.getName());
private final Map<Long, AlchemyCraftData> _alchemy = new HashMap<>();
protected AlchemyData()
{
load();
}
@Override
public void load()
{
_alchemy.clear();
parseDatapackFile("data/AlchemyData.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _alchemy.size() + " alchemy craft skills.");
}
@Override
public void parseDocument(Document doc, File f)
{
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 ("skill".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());
}
final AlchemyCraftData alchemyCraft = new AlchemyCraftData(set);
for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling())
{
if ("ingredients".equalsIgnoreCase(c.getNodeName()))
{
for (Node b = c.getFirstChild(); b != null; b = b.getNextSibling())
{
if ("item".equalsIgnoreCase(b.getNodeName()))
{
final int ingId = Integer.parseInt(b.getAttributes().getNamedItem("id").getNodeValue());
final int ingCount = Integer.parseInt(b.getAttributes().getNamedItem("count").getNodeValue());
alchemyCraft.addIngredient(new ItemHolder(ingId, ingCount));
}
}
}
else if ("production".equalsIgnoreCase(c.getNodeName()))
{
for (Node b = c.getFirstChild(); b != null; b = b.getNextSibling())
{
if ("item".equalsIgnoreCase(b.getNodeName()))
{
final String type = b.getAttributes().getNamedItem("type").getNodeValue();
final int prodId = Integer.parseInt(b.getAttributes().getNamedItem("id").getNodeValue());
final int prodCount = Integer.parseInt(b.getAttributes().getNamedItem("count").getNodeValue());
if (type.equalsIgnoreCase("ON_SUCCESS"))
{
alchemyCraft.setProductionSuccess(new ItemHolder(prodId, prodCount));
}
else if (type.equalsIgnoreCase("ON_FAILURE"))
{
alchemyCraft.setProductionFailure(new ItemHolder(prodId, prodCount));
}
}
}
}
}
_alchemy.put(SkillData.getSkillHashCode(set.getInt("id"), set.getInt("level")), alchemyCraft);
}
}
}
}
}
public AlchemyCraftData getCraftData(int skillId, int skillLevel)
{
return _alchemy.get(SkillData.getSkillHashCode(skillId, skillLevel));
}
/**
* Gets the single instance of AlchemyData.
* @return single instance of AlchemyData
*/
public static AlchemyData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final AlchemyData INSTANCE = new AlchemyData();
}
}

View File

@ -0,0 +1,164 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.enums.Race;
import org.l2jmobius.gameserver.model.StatsSet;
import org.l2jmobius.gameserver.model.holders.AppearanceHolder;
import org.l2jmobius.gameserver.model.items.appearance.AppearanceStone;
import org.l2jmobius.gameserver.model.items.appearance.AppearanceTargetType;
import org.l2jmobius.gameserver.model.items.type.CrystalType;
/**
* @author UnAfraid
*/
public class AppearanceItemData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(AppearanceItemData.class.getName());
private final Map<Integer, AppearanceStone> _stones = new HashMap<>();
protected AppearanceItemData()
{
load();
}
@Override
public void load()
{
parseDatapackFile("data/AppearanceStones.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _stones.size() + " stones.");
//@formatter:off
/*
for (Item item : ItemTable.getInstance().getAllItems())
{
if ((item == null) || !item.getName().contains("Appearance Stone"))
{
continue;
}
if (item.getName().contains("Pack") || _stones.containsKey(item.getId()))
{
continue;
}
System.out.println("Unhandled appearance stone: " + item);
}
*/
//@formatter:on
}
@Override
public void parseDocument(Document doc, File f)
{
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 ("appearance_stone".equalsIgnoreCase(d.getNodeName()))
{
final AppearanceStone stone = new AppearanceStone(new StatsSet(parseAttributes(d)));
for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling())
{
switch (c.getNodeName())
{
case "grade":
{
final CrystalType type = CrystalType.valueOf(c.getTextContent());
stone.addCrystalType(type);
break;
}
case "targetType":
{
final AppearanceTargetType type = AppearanceTargetType.valueOf(c.getTextContent());
stone.addTargetType(type);
break;
}
case "bodyPart":
{
final long part = ItemTable.SLOTS.get(c.getTextContent());
stone.addBodyPart(part);
break;
}
case "race":
{
final Race race = Race.valueOf(c.getTextContent());
stone.addRace(race);
break;
}
case "raceNot":
{
final Race raceNot = Race.valueOf(c.getTextContent());
stone.addRaceNot(raceNot);
break;
}
case "visual":
{
stone.addVisualId(new AppearanceHolder(new StatsSet(parseAttributes(c))));
}
}
}
if (ItemTable.getInstance().getTemplate(stone.getId()) != null)
{
_stones.put(stone.getId(), stone);
}
else
{
LOGGER.info(getClass().getSimpleName() + ": Could not find appearance stone item " + stone.getId());
}
}
}
}
}
}
public int getLoadedElementsCount()
{
return _stones.size();
}
public AppearanceStone getStone(int stone)
{
return _stones.get(stone);
}
/**
* Gets the single instance of AppearanceItemData.
* @return single instance of AppearanceItemData
*/
public static AppearanceItemData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final AppearanceItemData INSTANCE = new AppearanceItemData();
}
}

View File

@ -0,0 +1,188 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import java.util.stream.Stream;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.model.ArmorSet;
import org.l2jmobius.gameserver.model.holders.ArmorsetSkillHolder;
import org.l2jmobius.gameserver.model.items.Item;
import org.l2jmobius.gameserver.model.stats.BaseStats;
/**
* Loads armor set bonuses.
* @author godson, Luno, UnAfraid
*/
public class ArmorSetsData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(ArmorSetsData.class.getName());
private final Map<Integer, ArmorSet> _armorSets = new ConcurrentHashMap<>();
private final Map<Integer, List<ArmorSet>> _armorSetItems = new ConcurrentHashMap<>();
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, File f)
{
for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
{
if ("list".equalsIgnoreCase(n.getNodeName()))
{
for (Node setNode = n.getFirstChild(); setNode != null; setNode = setNode.getNextSibling())
{
if ("set".equalsIgnoreCase(setNode.getNodeName()))
{
final int id = parseInteger(setNode.getAttributes(), "id");
final int minimumPieces = parseInteger(setNode.getAttributes(), "minimumPieces", 0);
final boolean isVisual = parseBoolean(setNode.getAttributes(), "visual", false);
final ArmorSet set = new ArmorSet(id, minimumPieces, isVisual);
if (_armorSets.putIfAbsent(id, set) != null)
{
LOGGER.warning("Duplicate set entry with id: " + id + " in file: " + f.getName());
}
for (Node innerSetNode = setNode.getFirstChild(); innerSetNode != null; innerSetNode = innerSetNode.getNextSibling())
{
switch (innerSetNode.getNodeName())
{
case "requiredItems":
{
forEach(innerSetNode, b -> "item".equals(b.getNodeName()), node ->
{
final NamedNodeMap attrs = node.getAttributes();
final int itemId = parseInteger(attrs, "id");
final Item item = ItemTable.getInstance().getTemplate(itemId);
if (item == null)
{
LOGGER.warning("Attempting to register non existing required item: " + itemId + " to a set: " + f.getName());
}
else if (!set.addRequiredItem(itemId))
{
LOGGER.warning("Attempting to register duplicate required item " + item + " to a set: " + f.getName());
}
});
break;
}
case "optionalItems":
{
forEach(innerSetNode, b -> "item".equals(b.getNodeName()), node ->
{
final NamedNodeMap attrs = node.getAttributes();
final int itemId = parseInteger(attrs, "id");
final Item item = ItemTable.getInstance().getTemplate(itemId);
if (item == null)
{
LOGGER.warning("Attempting to register non existing optional item: " + itemId + " to a set: " + f.getName());
}
else if (!set.addOptionalItem(itemId))
{
LOGGER.warning("Attempting to register duplicate optional item " + item + " to a set: " + f.getName());
}
});
break;
}
case "skills":
{
forEach(innerSetNode, b -> "skill".equals(b.getNodeName()), node ->
{
final NamedNodeMap attrs = node.getAttributes();
final int skillId = parseInteger(attrs, "id");
final int skillLevel = parseInteger(attrs, "level");
final int minPieces = parseInteger(attrs, "minimumPieces", set.getMinimumPieces());
final int minEnchant = parseInteger(attrs, "minimumEnchant", 0);
final boolean isOptional = parseBoolean(attrs, "optional", false);
final int artifactSlotMask = parseInteger(attrs, "slotMask", 0);
final int artifactBookSlot = parseInteger(attrs, "bookSlot", 0);
set.addSkill(new ArmorsetSkillHolder(skillId, skillLevel, minPieces, minEnchant, isOptional, artifactSlotMask, artifactBookSlot));
});
break;
}
case "stats":
{
forEach(innerSetNode, b -> "stat".equals(b.getNodeName()), node ->
{
final NamedNodeMap attrs = node.getAttributes();
set.addStatsBonus(parseEnum(attrs, BaseStats.class, "type"), parseInteger(attrs, "val"));
});
break;
}
}
}
Stream.concat(set.getRequiredItems().stream(), set.getOptionalItems().stream()).forEach(itemHolder -> _armorSetItems.computeIfAbsent(itemHolder, key -> new ArrayList<>()).add(set));
}
}
}
}
}
/**
* @param setId the set id that is attached to a set
* @return the armor set associated to the given item id
*/
public ArmorSet getSet(int setId)
{
return _armorSets.get(setId);
}
/**
* @param itemId the item id that is attached to a set
* @return the armor set associated to the given item id
*/
public List<ArmorSet> getSets(int itemId)
{
return _armorSetItems.getOrDefault(itemId, Collections.emptyList());
}
/**
* 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,100 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.l2jmobius.Config;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.model.StatsSet;
import org.l2jmobius.gameserver.model.holders.ItemHolder;
/**
* @author Mobius
*/
public class AttendanceRewardData implements IXmlReader
{
private static Logger LOGGER = Logger.getLogger(AttendanceRewardData.class.getName());
private final List<ItemHolder> _rewards = new ArrayList<>();
private int _rewardsCount = 0;
protected AttendanceRewardData()
{
load();
}
@Override
public void load()
{
if (Config.ENABLE_ATTENDANCE_REWARDS)
{
_rewards.clear();
parseDatapackFile("data/AttendanceRewards.xml");
_rewardsCount = _rewards.size();
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _rewardsCount + " rewards.");
}
else
{
LOGGER.info(getClass().getSimpleName() + ": Disabled.");
}
}
@Override
public void parseDocument(Document doc, File f)
{
forEach(doc, "list", listNode -> forEach(listNode, "item", rewardNode ->
{
final StatsSet set = new StatsSet(parseAttributes(rewardNode));
final int itemId = set.getInt("id");
final int itemCount = set.getInt("count");
if (ItemTable.getInstance().getTemplate(itemId) == null)
{
LOGGER.info(getClass().getSimpleName() + ": Item with id " + itemId + " does not exist.");
}
else
{
_rewards.add(new ItemHolder(itemId, itemCount));
}
}));
}
public List<ItemHolder> getRewards()
{
return _rewards;
}
public int getRewardsCount()
{
return _rewardsCount;
}
public static AttendanceRewardData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final AttendanceRewardData INSTANCE = new AttendanceRewardData();
}
}

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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.enums.Race;
import org.l2jmobius.gameserver.enums.Sex;
import org.l2jmobius.gameserver.model.StatsSet;
import org.l2jmobius.gameserver.model.beautyshop.BeautyData;
import org.l2jmobius.gameserver.model.beautyshop.BeautyItem;
/**
* @author Sdw
*/
public class BeautyShopData implements IXmlReader
{
private final Map<Race, Map<Sex, BeautyData>> _beautyList = new HashMap<>();
private final Map<Sex, BeautyData> _beautyData = new HashMap<>();
protected BeautyShopData()
{
load();
}
@Override
public synchronized void load()
{
_beautyList.clear();
_beautyData.clear();
parseDatapackFile("data/BeautyShop.xml");
}
@Override
public void parseDocument(Document doc, File f)
{
NamedNodeMap attrs;
StatsSet set;
Node att;
Race race = null;
Sex sex = null;
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 ("race".equalsIgnoreCase(d.getNodeName()))
{
att = d.getAttributes().getNamedItem("type");
if (att != null)
{
race = parseEnum(att, Race.class);
}
for (Node b = d.getFirstChild(); b != null; b = b.getNextSibling())
{
if ("sex".equalsIgnoreCase(b.getNodeName()))
{
att = b.getAttributes().getNamedItem("type");
if (att != null)
{
sex = parseEnum(att, Sex.class);
}
final BeautyData beautyData = new BeautyData();
for (Node a = b.getFirstChild(); a != null; a = a.getNextSibling())
{
if ("hair".equalsIgnoreCase(a.getNodeName()))
{
attrs = a.getAttributes();
set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
final BeautyItem hair = new BeautyItem(set);
for (Node g = a.getFirstChild(); g != null; g = g.getNextSibling())
{
if ("color".equalsIgnoreCase(g.getNodeName()))
{
attrs = g.getAttributes();
set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
hair.addColor(set);
}
}
beautyData.addHair(hair);
}
else if ("face".equalsIgnoreCase(a.getNodeName()))
{
attrs = a.getAttributes();
set = new StatsSet();
for (int i = 0; i < attrs.getLength(); i++)
{
att = attrs.item(i);
set.set(att.getNodeName(), att.getNodeValue());
}
final BeautyItem face = new BeautyItem(set);
beautyData.addFace(face);
}
}
_beautyData.put(sex, beautyData);
}
}
_beautyList.put(race, _beautyData);
}
}
}
}
}
public boolean hasBeautyData(Race race, Sex sex)
{
return _beautyList.containsKey(race) && _beautyList.get(race).containsKey(sex);
}
public BeautyData getBeautyData(Race race, Sex sex)
{
if (_beautyList.containsKey(race))
{
return _beautyList.get(race).get(sex);
}
return null;
}
public static BeautyShopData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final BeautyShopData INSTANCE = new BeautyShopData();
}
}

View File

@ -0,0 +1,175 @@
/*
* 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 org.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.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.commons.util.file.filter.NumericNameFilter;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.model.buylist.Product;
import org.l2jmobius.gameserver.model.buylist.ProductList;
import org.l2jmobius.gameserver.model.items.Item;
/**
* Loads buy lists for NPCs.
* @author NosBit
*/
public class BuyListData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(BuyListData.class.getName());
private final Map<Integer, ProductList> _buyLists = new ConcurrentHashMap<>();
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.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 ProductList 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", ""));
forEach(doc, "list", (list) ->
{
final int defaultBaseTax = parseInteger(list.getAttributes(), "baseTax", 0);
final ProductList buyList = new ProductList(buyListId);
forEach(list, (node) ->
{
switch (node.getNodeName())
{
case "item":
{
final NamedNodeMap attrs = node.getAttributes();
final int itemId = parseInteger(attrs, "id");
final Item item = ItemTable.getInstance().getTemplate(itemId);
if (item != null)
{
final long price = parseLong(attrs, "price", -1L);
final long restockDelay = parseLong(attrs, "restock_delay", -1L);
final long count = parseLong(attrs, "count", -1L);
final int baseTax = parseInteger(attrs, "baseTax", defaultBaseTax);
buyList.addProduct(new Product(buyListId, item, price, restockDelay, count, baseTax));
}
else
{
LOGGER.warning("Item not found. BuyList:" + buyListId + " ItemID:" + itemId + " File:" + f);
}
break;
}
case "npcs":
{
forEach(node, "npc", (npcNode) -> buyList.addAllowedNpc(Integer.parseInt(npcNode.getTextContent())));
break;
}
}
});
_buyLists.put(buyListId, 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 ProductList 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,148 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.enums.CastleSide;
import org.l2jmobius.gameserver.enums.SiegeGuardType;
import org.l2jmobius.gameserver.model.holders.CastleSpawnHolder;
import org.l2jmobius.gameserver.model.holders.SiegeGuardHolder;
/**
* @author St3eT
*/
public class CastleData implements IXmlReader
{
private final Map<Integer, List<CastleSpawnHolder>> _spawns = new ConcurrentHashMap<>();
private static final Map<Integer, List<SiegeGuardHolder>> _siegeGuards = new ConcurrentHashMap<>();
protected CastleData()
{
load();
}
@Override
public void load()
{
_spawns.clear();
_siegeGuards.clear();
parseDatapackDirectory("data/residences/castles", true);
}
@Override
public void parseDocument(Document doc, File f)
{
for (Node listNode = doc.getFirstChild(); listNode != null; listNode = listNode.getNextSibling())
{
if ("list".equals(listNode.getNodeName()))
{
for (Node castleNode = listNode.getFirstChild(); castleNode != null; castleNode = castleNode.getNextSibling())
{
if ("castle".equals(castleNode.getNodeName()))
{
final int castleId = parseInteger(castleNode.getAttributes(), "id");
for (Node tpNode = castleNode.getFirstChild(); tpNode != null; tpNode = tpNode.getNextSibling())
{
final List<CastleSpawnHolder> spawns = new ArrayList<>();
if ("spawns".equals(tpNode.getNodeName()))
{
for (Node npcNode = tpNode.getFirstChild(); npcNode != null; npcNode = npcNode.getNextSibling())
{
if ("npc".equals(npcNode.getNodeName()))
{
final NamedNodeMap np = npcNode.getAttributes();
final int npcId = parseInteger(np, "id");
final CastleSide side = parseEnum(np, CastleSide.class, "castleSide", CastleSide.NEUTRAL);
final int x = parseInteger(np, "x");
final int y = parseInteger(np, "y");
final int z = parseInteger(np, "z");
final int heading = parseInteger(np, "heading");
spawns.add(new CastleSpawnHolder(npcId, side, x, y, z, heading));
}
}
_spawns.put(castleId, spawns);
}
else if ("siegeGuards".equals(tpNode.getNodeName()))
{
final List<SiegeGuardHolder> guards = new ArrayList<>();
for (Node npcNode = tpNode.getFirstChild(); npcNode != null; npcNode = npcNode.getNextSibling())
{
if ("guard".equals(npcNode.getNodeName()))
{
final NamedNodeMap np = npcNode.getAttributes();
final int itemId = parseInteger(np, "itemId");
final SiegeGuardType type = parseEnum(tpNode.getAttributes(), SiegeGuardType.class, "type");
final boolean stationary = parseBoolean(np, "stationary", false);
final int npcId = parseInteger(np, "npcId");
final int npcMaxAmount = parseInteger(np, "npcMaxAmount");
guards.add(new SiegeGuardHolder(castleId, itemId, type, stationary, npcId, npcMaxAmount));
}
}
_siegeGuards.put(castleId, guards);
}
}
}
}
}
}
}
public List<CastleSpawnHolder> getSpawnsForSide(int castleId, CastleSide side)
{
return _spawns.getOrDefault(castleId, Collections.emptyList()).stream().filter(s -> s.getSide() == side).collect(Collectors.toList());
}
public List<SiegeGuardHolder> getSiegeGuardsForCastle(int castleId)
{
return _siegeGuards.getOrDefault(castleId, Collections.emptyList());
}
public Map<Integer, List<SiegeGuardHolder>> getSiegeGuards()
{
return _siegeGuards;
}
/**
* Gets the single instance of CastleData.
* @return single instance of CastleData
*/
public static CastleData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final CastleData INSTANCE = new CastleData();
}
}

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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.enums.CategoryType;
/**
* Loads the category data with Class or NPC IDs.
* @author NosBit, xban1x
*/
public 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, File f)
{
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.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.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,212 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.enums.ClanHallGrade;
import org.l2jmobius.gameserver.enums.ClanHallType;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.StatsSet;
import org.l2jmobius.gameserver.model.actor.instance.DoorInstance;
import org.l2jmobius.gameserver.model.clan.Clan;
import org.l2jmobius.gameserver.model.entity.ClanHall;
import org.l2jmobius.gameserver.model.holders.ClanHallTeleportHolder;
/**
* @author St3eT
*/
public class ClanHallData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(ClanHallData.class.getName());
private static final Map<Integer, ClanHall> _clanHalls = new ConcurrentHashMap<>();
protected ClanHallData()
{
load();
}
@Override
public void load()
{
parseDatapackDirectory("data/residences/clanHalls", true);
LOGGER.info(getClass().getSimpleName() + ": Succesfully loaded " + _clanHalls.size() + " clan halls.");
}
@Override
public void parseDocument(Document doc, File f)
{
final List<DoorInstance> doors = new ArrayList<>();
final List<Integer> npcs = new ArrayList<>();
final List<ClanHallTeleportHolder> teleports = new ArrayList<>();
final StatsSet params = new StatsSet();
for (Node listNode = doc.getFirstChild(); listNode != null; listNode = listNode.getNextSibling())
{
if ("list".equals(listNode.getNodeName()))
{
for (Node clanHallNode = listNode.getFirstChild(); clanHallNode != null; clanHallNode = clanHallNode.getNextSibling())
{
if ("clanHall".equals(clanHallNode.getNodeName()))
{
params.set("id", parseInteger(clanHallNode.getAttributes(), "id"));
params.set("name", parseString(clanHallNode.getAttributes(), "name", "None"));
params.set("grade", parseEnum(clanHallNode.getAttributes(), ClanHallGrade.class, "grade", ClanHallGrade.GRADE_NONE));
params.set("type", parseEnum(clanHallNode.getAttributes(), ClanHallType.class, "type", ClanHallType.OTHER));
for (Node tpNode = clanHallNode.getFirstChild(); tpNode != null; tpNode = tpNode.getNextSibling())
{
switch (tpNode.getNodeName())
{
case "auction":
{
final NamedNodeMap at = tpNode.getAttributes();
params.set("minBid", parseInteger(at, "minBid"));
params.set("lease", parseInteger(at, "lease"));
params.set("deposit", parseInteger(at, "deposit"));
break;
}
case "npcs":
{
for (Node npcNode = tpNode.getFirstChild(); npcNode != null; npcNode = npcNode.getNextSibling())
{
if ("npc".equals(npcNode.getNodeName()))
{
final NamedNodeMap np = npcNode.getAttributes();
final int npcId = parseInteger(np, "id");
npcs.add(npcId);
}
}
params.set("npcList", npcs);
break;
}
case "doorlist":
{
for (Node npcNode = tpNode.getFirstChild(); npcNode != null; npcNode = npcNode.getNextSibling())
{
if ("door".equals(npcNode.getNodeName()))
{
final NamedNodeMap np = npcNode.getAttributes();
final int doorId = parseInteger(np, "id");
final DoorInstance door = DoorData.getInstance().getDoor(doorId);
if (door != null)
{
doors.add(door);
}
}
}
params.set("doorList", doors);
break;
}
case "teleportList":
{
for (Node npcNode = tpNode.getFirstChild(); npcNode != null; npcNode = npcNode.getNextSibling())
{
if ("teleport".equals(npcNode.getNodeName()))
{
final NamedNodeMap np = npcNode.getAttributes();
final int npcStringId = parseInteger(np, "npcStringId");
final int x = parseInteger(np, "x");
final int y = parseInteger(np, "y");
final int z = parseInteger(np, "z");
final int minFunctionLevel = parseInteger(np, "minFunctionLevel");
final int cost = parseInteger(np, "cost");
teleports.add(new ClanHallTeleportHolder(npcStringId, x, y, z, minFunctionLevel, cost));
}
}
params.set("teleportList", teleports);
break;
}
case "ownerRestartPoint":
{
final NamedNodeMap ol = tpNode.getAttributes();
params.set("owner_loc", new Location(parseInteger(ol, "x"), parseInteger(ol, "y"), parseInteger(ol, "z")));
break;
}
case "banishPoint":
{
final NamedNodeMap bl = tpNode.getAttributes();
params.set("banish_loc", new Location(parseInteger(bl, "x"), parseInteger(bl, "y"), parseInteger(bl, "z")));
break;
}
}
}
}
}
}
}
_clanHalls.put(params.getInt("id"), new ClanHall(params));
}
public ClanHall getClanHallById(int clanHallId)
{
return _clanHalls.get(clanHallId);
}
public Collection<ClanHall> getClanHalls()
{
return _clanHalls.values();
}
public ClanHall getClanHallByNpcId(int npcId)
{
return _clanHalls.values().stream().filter(ch -> ch.getNpcs().contains(npcId)).findFirst().orElse(null);
}
public ClanHall getClanHallByClan(Clan clan)
{
return _clanHalls.values().stream().filter(ch -> ch.getOwner() == clan).findFirst().orElse(null);
}
public ClanHall getClanHallByDoorId(int doorId)
{
final DoorInstance door = DoorData.getInstance().getDoor(doorId);
return _clanHalls.values().stream().filter(ch -> ch.getDoors().contains(door)).findFirst().orElse(null);
}
public List<ClanHall> getFreeAuctionableHall()
{
return _clanHalls.values().stream().filter(ch -> (ch.getType() == ClanHallType.AUCTIONABLE) && (ch.getOwner() == null)).sorted(Comparator.comparingInt(ClanHall::getResidenceId)).collect(Collectors.toList());
}
/**
* Gets the single instance of ClanHallData.
* @return single instance of ClanHallData
*/
public static ClanHallData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ClanHallData INSTANCE = new ClanHallData();
}
}

View File

@ -0,0 +1,97 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
/**
* @author Mobius
*/
public class ClanLevelData
{
// TODO: Move to XML.
private final static int[] CLAN_LEVEL_REQUIREMENTS =
{
35000,
80000,
140000,
315000,
560000,
965000,
2690000,
4050000,
5930000,
7560000,
11830000,
19110000,
27300000,
36400000,
46410000,
0
};
private final static int[] COMMON_CLAN_MEMBER_LIMIT =
{
10,
15,
20,
30,
40,
42,
68,
85,
94,
102,
111,
120,
128,
137,
145,
171
};
private final static int[] ELITE_CLAN_MEMBER_LIMIT =
{
0,
0,
0,
0,
0,
8,
12,
15,
16,
18,
19,
20,
22,
23,
25,
29
};
public static int getLevelRequirement(int clanLevel)
{
return CLAN_LEVEL_REQUIREMENTS[clanLevel];
}
public static int getCommonMemberLimit(int clanLevel)
{
return COMMON_CLAN_MEMBER_LIMIT[clanLevel];
}
public static int getEliteMemberLimit(int clanLevel)
{
return ELITE_CLAN_MEMBER_LIMIT[clanLevel];
}
}

View File

@ -0,0 +1,134 @@
/*
* 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 org.l2jmobius.gameserver.data.xml.impl;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.model.StatsSet;
import org.l2jmobius.gameserver.model.holders.ClanMasteryHolder;
import org.l2jmobius.gameserver.model.skills.Skill;
/**
* @author Mobius
*/
public class ClanMasteryData implements IXmlReader
{
private static Logger LOGGER = Logger.getLogger(ClanMasteryData.class.getName());
private final Map<Integer, ClanMasteryHolder> _clanMasteryData = new HashMap<>();
protected ClanMasteryData()
{
load();
}
@Override
public void load()
{
_clanMasteryData.clear();
parseDatapackFile("data/ClanMasteryData.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _clanMasteryData.size() + " clan masteries.");
}
@Override
public void parseDocument(Document doc, File f)
{
forEach(doc, "list", listNode -> forEach(listNode, "clan", clanNode ->
{
final StatsSet set = new StatsSet(parseAttributes(clanNode));
final int id = set.getInt("mastery");
final int skill1Id = set.getInt("skill1Id");
final int skill1Level = set.getInt("skill1Level");
final Skill skill1 = SkillData.getInstance().getSkill(skill1Id, skill1Level);
if (skill1 == null)
{
LOGGER.info(getClass().getSimpleName() + ": Could not create clan mastery, skill id " + skill1Id + " with level " + skill1Level + " does not exist.");
return;
}
final int skill2Id = set.getInt("skill2Id", 0);
final int skill2Level = set.getInt("skill2Level", 0);
Skill skill2 = null;
if (skill2Id > 0)
{
skill2 = SkillData.getInstance().getSkill(skill2Id, skill2Level);
if (skill2 == null)
{
LOGGER.info(getClass().getSimpleName() + ": Could not create clan mastery, skill id " + skill2Id + " with level " + skill2Level + " does not exist.");
return;
}
}
final int skill3Id = set.getInt("skill3Id", 0);
final int skill3Level = set.getInt("skill3Level", 0);
Skill skill3 = null;
if (skill3Id > 0)
{
skill3 = SkillData.getInstance().getSkill(skill3Id, skill3Level);
if (skill3 == null)
{
LOGGER.info(getClass().getSimpleName() + ": Could not create clan mastery, skill id " + skill3Id + " with level " + skill3Level + " does not exist.");
return;
}
}
final int skill4Id = set.getInt("skill4Id", 0);
final int skill4Level = set.getInt("skill4Level", 0);
Skill skill4 = null;
if (skill4Id > 0)
{
skill4 = SkillData.getInstance().getSkill(skill4Id, skill4Level);
if (skill4 == null)
{
LOGGER.info(getClass().getSimpleName() + ": Could not create clan mastery, skill id " + skill4Id + " with level " + skill4Level + " does not exist.");
return;
}
}
final int clanLevel = set.getInt("clanLevel");
final int clanReputation = set.getInt("clanReputation");
final int previousMastery = set.getInt("previousMastery", 0);
final int previousMasteryAlt = set.getInt("previousMasteryAlt", 0);
_clanMasteryData.put(id, new ClanMasteryHolder(id, skill1, skill2, skill3, skill4, clanLevel, clanReputation, previousMastery, previousMasteryAlt));
}));
}
public Collection<ClanMasteryHolder> getMasteries()
{
return _clanMasteryData.values();
}
public ClanMasteryHolder getClanMastery(int id)
{
return _clanMasteryData.get(id);
}
public static ClanMasteryData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ClanMasteryData INSTANCE = new ClanMasteryData();
}
}

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