Addition of C4 branch.

This commit is contained in:
MobiusDevelopment
2020-06-14 05:43:34 +00:00
parent bf7c04f166
commit 141764df73
16864 changed files with 934319 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,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.lang.Thread.UncaughtExceptionHandler;
/**
* @author Mobius
*/
public class RunnableWrapper implements Runnable
{
private final Runnable _runnable;
public RunnableWrapper(Runnable runnable)
{
_runnable = runnable;
}
@Override
public void run()
{
try
{
_runnable.run();
}
catch (Throwable e)
{
final Thread t = Thread.currentThread();
final UncaughtExceptionHandler h = t.getUncaughtExceptionHandler();
if (h != null)
{
h.uncaughtException(t, e);
}
}
}
}

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.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(ThreadPool::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)
{
LOGGER.info("ThreadPool: Problem at Shutting down. " + t.getMessage());
}
}
}

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.crypt;
import java.io.IOException;
import org.l2jmobius.commons.util.Rnd;
/**
* @author KenM
*/
public class LoginCrypt
{
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 final NewCrypt _staticCrypt = new NewCrypt(STATIC_BLOWFISH_KEY);
private NewCrypt _crypt;
private boolean _static = true;
public void setKey(byte[] key)
{
_crypt = new NewCrypt(key);
}
public boolean decrypt(byte[] raw, int offset, int size) throws IOException
{
_crypt.decrypt(raw, offset, size);
return NewCrypt.verifyChecksum(raw, offset, size);
}
public int encrypt(byte[] raw, int offset, int size) throws IOException
{
int newSize = size;
// reserve checksum
newSize += 4;
if (_static)
{
// reserve for XOR "key"
newSize += 4;
// padding
newSize += 8 - (newSize % 8);
NewCrypt.encXORPass(raw, offset, newSize, Rnd.nextInt());
_staticCrypt.crypt(raw, offset, newSize);
_static = false;
}
else
{
// padding
newSize += 8 - (newSize % 8);
NewCrypt.appendChecksum(raw, offset, newSize);
_crypt.crypt(raw, offset, newSize);
}
return newSize;
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.crypt;
import java.io.IOException;
import java.util.logging.Logger;
/**
* @version $Revision: 1.3.4.1 $ $Date: 2005/03/27 15:30:09 $
*/
public class NewCrypt
{
protected static final Logger LOGGER = Logger.getLogger(NewCrypt.class.getName());
BlowfishEngine _crypt;
BlowfishEngine _decrypt;
/**
* @param blowfishKey
*/
public NewCrypt(byte[] blowfishKey)
{
_crypt = new BlowfishEngine();
_crypt.init(true, blowfishKey);
_decrypt = new BlowfishEngine();
_decrypt.init(false, blowfishKey);
}
public NewCrypt(String key)
{
this(key.getBytes());
}
public static boolean verifyChecksum(byte[] raw)
{
return verifyChecksum(raw, 0, raw.length);
}
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;
}
public static void appendChecksum(byte[] raw)
{
appendChecksum(raw, 0, raw.length);
}
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". 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". 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 begining of the data to be encrypted
* @param size Length of the data to be encrypted
* @param key The 4 bytes (int) XOR key
*/
public 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);
}
public byte[] decrypt(byte[] raw) throws IOException
{
final byte[] result = new byte[raw.length];
final int count = raw.length / 8;
for (int i = 0; i < count; i++)
{
_decrypt.processBlock(raw, i * 8, result, i * 8);
}
return result;
}
public void decrypt(byte[] raw, int offset, int size) throws IOException
{
final byte[] result = new byte[size];
final int count = size / 8;
for (int i = 0; i < count; i++)
{
_decrypt.processBlock(raw, offset + (i * 8), result, i * 8);
}
// TODO can the crypt and decrypt go direct to the array
System.arraycopy(result, 0, raw, offset, size);
}
public byte[] crypt(byte[] raw) throws IOException
{
final int count = raw.length / 8;
final byte[] result = new byte[raw.length];
for (int i = 0; i < count; i++)
{
_crypt.processBlock(raw, i * 8, result, i * 8);
}
return result;
}
public void crypt(byte[] raw, int offset, int size) throws IOException
{
final int count = size / 8;
final byte[] result = new byte[size];
for (int i = 0; i < count; i++)
{
_crypt.processBlock(raw, offset + (i * 8), result, i * 8);
}
// TODO can the crypt and decrypt go direct to the array
System.arraycopy(result, 0, raw, offset, size);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.crypt;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
public class ScrambledKeyPair
{
public KeyPair _pair;
public 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]);
}
return scrambledMod;
}
}

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.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)
{
// Ignore.
}
});
}
catch (Exception e)
{
// Ignore.
}
}
// 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)
{
// Ignore.
}
}
}

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)
{
LOGGER.info("Database: Problem on initialize. " + e);
}
}
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,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,35 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public abstract class AbstractPacket<T extends MMOClient<?>>
{
protected ByteBuffer _buf;
T _client;
public T getClient()
{
return _client;
}
}

View File

@@ -0,0 +1,27 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
import java.nio.channels.SocketChannel;
/**
* @author KenM
*/
public interface IAcceptFilter
{
boolean accept(SocketChannel sc);
}

View File

@@ -0,0 +1,26 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
/**
* @author KenM
* @param <T>
*/
public interface IClientFactory<T extends MMOClient<?>>
{
T create(MMOConnection<T> con);
}

View File

@@ -0,0 +1,26 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
/**
* @author KenM
* @param <T>
*/
public interface IMMOExecutor<T extends MMOClient<?>>
{
void execute(ReceivablePacket<T> packet);
}

View File

@@ -0,0 +1,28 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public interface IPacketHandler<T extends MMOClient<?>>
{
ReceivablePacket<T> handlePacket(ByteBuffer buf, T client);
}

View File

@@ -0,0 +1,46 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public abstract class MMOClient<T extends MMOConnection<?>>
{
private final T _con;
public MMOClient(T con)
{
_con = con;
}
public T getConnection()
{
return _con;
}
public abstract boolean decrypt(ByteBuffer buf, int size);
public abstract boolean encrypt(ByteBuffer buf, int size);
protected abstract void onDisconnection();
protected abstract void onForcedDisconnection();
}

View File

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

View File

@@ -0,0 +1,101 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
/**
* @author Forsaiken
* @param <E>
*/
public class NioNetStackList<E>
{
private final NioNetStackNode _start = new NioNetStackNode();
private final NioNetStackNodeBuf _buf = new NioNetStackNodeBuf();
private NioNetStackNode _end = new NioNetStackNode();
public NioNetStackList()
{
clear();
}
public void addLast(E elem)
{
final NioNetStackNode newEndNode = _buf.removeFirst();
_end._value = elem;
_end._next = newEndNode;
_end = newEndNode;
}
public E removeFirst()
{
final NioNetStackNode old = _start._next;
final E value = old._value;
_start._next = old._next;
_buf.addLast(old);
return value;
}
public boolean isEmpty()
{
return _start._next == _end;
}
public void clear()
{
_start._next = _end;
}
protected final class NioNetStackNode
{
protected NioNetStackNode _next;
protected E _value;
}
private final class NioNetStackNodeBuf
{
private final NioNetStackNode _start = new NioNetStackNode();
private NioNetStackNode _end = new NioNetStackNode();
NioNetStackNodeBuf()
{
_start._next = _end;
}
final void addLast(NioNetStackNode node)
{
node._next = null;
node._value = null;
_end._next = node;
_end = node;
}
final NioNetStackNode removeFirst()
{
if (_start._next == _end)
{
return new NioNetStackNode();
}
final NioNetStackNode old = _start._next;
_start._next = old._next;
return old;
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
import java.nio.BufferOverflowException;
/**
* @author Forsaiken
*/
public class NioNetStringBuffer
{
private final char[] _buf;
private final int _size;
private int _len;
public NioNetStringBuffer(int size)
{
_buf = new char[size];
_size = size;
_len = 0;
}
public void clear()
{
_len = 0;
}
public void append(char c)
{
if (_len < _size)
{
_buf[_len++] = c;
}
else
{
throw new BufferOverflowException();
}
}
@Override
public String toString()
{
return new String(_buf, 0, _len);
}
}

View File

@@ -0,0 +1,139 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
import java.nio.ByteBuffer;
/**
* @author KenM
* @param <T>
*/
public abstract class ReceivablePacket<T extends MMOClient<?>>extends AbstractPacket<T> implements Runnable
{
NioNetStringBuffer _sbuf;
protected ReceivablePacket()
{
}
protected abstract boolean read();
@Override
public abstract void run();
/**
* Reads <b>byte[]</b> from the buffer.<br>
* Reads as many bytes as the length of the array.
* @param dst : the byte array which will be filled with the data.
*/
protected final void readB(byte[] dst)
{
_buf.get(dst);
}
/**
* Reads <b>byte[]</b> from the buffer.<br>
* Reads as many bytes as the given length (len). Starts to fill the byte array from the given offset to <b>offset</b> + <b>len</b>.
* @param dst : the byte array which will be filled with the data.
* @param offset : starts to fill the byte array from the given offset.
* @param len : the given length of bytes to be read.
*/
protected final void readB(byte[] dst, int offset, int len)
{
_buf.get(dst, offset, len);
}
/**
* Reads <b>byte</b> from the buffer.<br>
* 8bit integer (00)
* @return
*/
protected final int readC()
{
return _buf.get() & 0xFF;
}
/**
* Reads <b>short</b> from the buffer.<br>
* 16bit integer (00 00)
* @return
*/
protected final int readH()
{
return _buf.getShort() & 0xFFFF;
}
/**
* Reads <b>int</b> from the buffer.<br>
* 32bit integer (00 00 00 00)
* @return
*/
protected final int readD()
{
return _buf.getInt();
}
/**
* Reads <b>long</b> from the buffer.<br>
* 64bit integer (00 00 00 00 00 00 00 00)
* @return
*/
protected final long readQ()
{
return _buf.getLong();
}
/**
* Reads <b>double</b> from the buffer.<br>
* 64bit double precision float (00 00 00 00 00 00 00 00)
* @return
*/
protected final double readF()
{
return _buf.getDouble();
}
/**
* Reads <b>String</b> from the buffer.
* @return
*/
protected final String readS()
{
_sbuf.clear();
char ch;
while ((ch = _buf.getChar()) != 0)
{
_sbuf.append(ch);
}
return _sbuf.toString();
}
/**
* packet forge purpose
* @param data
* @param client
* @param sBuffer
*/
public void setBuffers(ByteBuffer data, T client, NioNetStringBuffer sBuffer)
{
_buf = data;
_client = client;
_sbuf = sBuffer;
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.mmocore;
/**
* @author KenM
*/
public class SelectorConfig
{
public int READ_BUFFER_SIZE = 64 * 1024;
public int WRITE_BUFFER_SIZE = 64 * 1024;
public int HELPER_BUFFER_COUNT = 20;
public int HELPER_BUFFER_SIZE = 64 * 1024;
/**
* Server will try to send MAX_SEND_PER_PASS packets per socket write call<br>
* however it may send less if the write buffer was filled before achieving this value.
*/
public int MAX_SEND_PER_PASS = 10;
/**
* Server will try to read MAX_READ_PER_PASS packets per socket read call<br>
* however it may read less if the read buffer was empty before achieving this value.
*/
public int MAX_READ_PER_PASS = 10;
/**
* Defines how much time (in milis) should the selector sleep, an higher value increases throughput but also increases latency(to a max of the sleep value itself).<br>
* Also an extremely high value(usually > 100) will decrease throughput due to the server not doing enough sends per second (depends on max sends per pass).<br>
* Recommended values:<br>
* 1 for minimal latency.<br>
* 10-30 for an latency/troughput trade-off based on your needs.
*/
public int SLEEP_TIME = 10;
}

View File

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

View File

@@ -0,0 +1,119 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.mmocore;
/**
* @author KenM
* @param <T>
*/
public abstract class SendablePacket<T extends MMOClient<?>>extends AbstractPacket<T>
{
protected final void putInt(int value)
{
_buf.putInt(value);
}
protected final void putDouble(double value)
{
_buf.putDouble(value);
}
protected final void putFloat(float value)
{
_buf.putFloat(value);
}
/**
* Write <b>byte</b> to the buffer.<br>
* 8bit integer (00)
* @param data
*/
protected final void writeC(int data)
{
_buf.put((byte) data);
}
/**
* Write <b>double</b> to the buffer.<br>
* 64bit double precision float (00 00 00 00 00 00 00 00)
* @param value
*/
protected final void writeF(double value)
{
_buf.putDouble(value);
}
/**
* Write <b>short</b> to the buffer.<br>
* 16bit integer (00 00)
* @param value
*/
protected final void writeH(int value)
{
_buf.putShort((short) value);
}
/**
* Write <b>int</b> to the buffer.<br>
* 32bit integer (00 00 00 00)
* @param value
*/
protected final void writeD(int value)
{
_buf.putInt(value);
}
/**
* Write <b>long</b> to the buffer.<br>
* 64bit integer (00 00 00 00 00 00 00 00)
* @param value
*/
protected final void writeQ(long value)
{
_buf.putLong(value);
}
/**
* Write <b>byte[]</b> to the buffer.<br>
* 8bit integer array (00 ...)
* @param data
*/
protected final void writeB(byte[] data)
{
_buf.put(data);
}
/**
* Write <b>String</b> to the buffer.
* @param text
*/
protected final void writeS(String text)
{
if (text != null)
{
final int len = text.length();
for (int i = 0; i < len; i++)
{
_buf.putChar(text.charAt(i));
}
}
_buf.putChar('\000');
}
protected abstract void write();
}

View File

@@ -0,0 +1,108 @@
/*
* 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.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
public class ClassMasterSettings
{
private final Map<Integer, Map<Integer, Integer>> _claimItems;
private final Map<Integer, Map<Integer, Integer>> _rewardItems;
private final Map<Integer, Boolean> _allowedClassChange;
public ClassMasterSettings(String configLine)
{
_claimItems = new HashMap<>();
_rewardItems = new HashMap<>();
_allowedClassChange = new HashMap<>();
if (configLine != null)
{
parseConfigLine(configLine.trim());
}
}
private void parseConfigLine(String configLine)
{
final StringTokenizer st = new StringTokenizer(configLine, ";");
while (st.hasMoreTokens())
{
final int job = Integer.parseInt(st.nextToken());
_allowedClassChange.put(job, true);
Map<Integer, Integer> items = new HashMap<>();
if (st.hasMoreTokens())
{
final StringTokenizer st2 = new StringTokenizer(st.nextToken(), "[],");
while (st2.hasMoreTokens())
{
final StringTokenizer st3 = new StringTokenizer(st2.nextToken(), "()");
final int itemId = Integer.parseInt(st3.nextToken());
final int quantity = Integer.parseInt(st3.nextToken());
items.put(itemId, quantity);
}
}
_claimItems.put(job, items);
items = new HashMap<>();
if (st.hasMoreTokens())
{
final StringTokenizer st2 = new StringTokenizer(st.nextToken(), "[],");
while (st2.hasMoreTokens())
{
final StringTokenizer st3 = new StringTokenizer(st2.nextToken(), "()");
final int itemId = Integer.parseInt(st3.nextToken());
final int quantity = Integer.parseInt(st3.nextToken());
items.put(itemId, quantity);
}
}
_rewardItems.put(job, items);
}
}
public boolean isAllowed(int job)
{
if (_allowedClassChange == null)
{
return false;
}
if (_allowedClassChange.containsKey(job))
{
return _allowedClassChange.get(job);
}
return false;
}
public Map<Integer, Integer> getRewardItems(int job)
{
if (_rewardItems.containsKey(job))
{
return _rewardItems.get(job);
}
return null;
}
public Map<Integer, Integer> getRequireItems(int job)
{
if (_claimItems.containsKey(job))
{
return _claimItems.get(job);
}
return null;
}
}

View File

@@ -0,0 +1,102 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.commons.util;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* Thread to check for deadlocked threads.
* @author -Nemesiss- L2M
*/
public class DeadlockDetector implements Runnable
{
protected static final Logger LOGGER = Logger.getLogger(DeadlockDetector.class.getName());
private final Set<Long> _logged = new HashSet<>();
private static DeadlockDetector _instance;
public static DeadlockDetector getInstance()
{
if (_instance == null)
{
_instance = new DeadlockDetector();
}
return _instance;
}
private DeadlockDetector()
{
LOGGER.info("DeadlockDetector daemon started.");
}
@Override
public void run()
{
final long[] ids = findDeadlockedThreadIDs();
if (ids == null)
{
return;
}
final List<Thread> deadlocked = new ArrayList<>();
for (long id : ids)
{
if (_logged.add(id))
{
deadlocked.add(findThreadById(id));
}
}
if (!deadlocked.isEmpty())
{
Util.printSection("Deadlocked Thread(s)");
for (Thread thread : deadlocked)
{
thread.getName();
}
Util.printSection("End");
}
}
private long[] findDeadlockedThreadIDs()
{
if (ManagementFactory.getThreadMXBean().isSynchronizerUsageSupported())
{
return ManagementFactory.getThreadMXBean().findDeadlockedThreads();
}
return ManagementFactory.getThreadMXBean().findMonitorDeadlockedThreads();
}
private Thread findThreadById(long id)
{
for (Thread thread : Thread.getAllStackTraces().keySet())
{
if (thread.getId() == id)
{
return thread;
}
}
throw new IllegalStateException("Deadlocked Thread not found!");
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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
{
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;
}
}
}
// check for embedded v4 in v6 addr (not done !)
else 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());
}
return (o instanceof InetAddress) && applyMask(((InetAddress) o).getAddress());
}
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,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.util;
import java.net.InetAddress;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import org.l2jmobius.commons.mmocore.IAcceptFilter;
/**
* Formatted Forsaiken's IPv4 filter [DrHouse]
* @author Forsaiken
*/
public class IPv4Filter implements IAcceptFilter, Runnable
{
private final HashMap<Integer, Flood> _ipFloodMap;
private static final long SLEEP_TIME = 5000;
public IPv4Filter()
{
_ipFloodMap = new HashMap<>();
final Thread t = new Thread(this);
t.setName(getClass().getSimpleName());
t.setDaemon(true);
t.start();
}
/**
* @param ip
* @return
*/
private static final int hash(byte[] ip)
{
return (ip[0] & 0xFF) | ((ip[1] << 8) & 0xFF00) | ((ip[2] << 16) & 0xFF0000) | ((ip[3] << 24) & 0xFF000000);
}
protected static final class Flood
{
long lastAccess;
int trys;
Flood()
{
lastAccess = System.currentTimeMillis();
trys = 0;
}
}
@Override
public boolean accept(SocketChannel sc)
{
final InetAddress addr = sc.socket().getInetAddress();
final int h = hash(addr.getAddress());
final long current = System.currentTimeMillis();
Flood f;
synchronized (_ipFloodMap)
{
f = _ipFloodMap.get(h);
}
if (f != null)
{
if (f.trys == -1)
{
f.lastAccess = current;
return false;
}
if ((f.lastAccess + 1000) > current)
{
f.lastAccess = current;
if (f.trys >= 3)
{
f.trys = -1;
return false;
}
f.trys++;
}
else
{
f.lastAccess = current;
}
}
else
{
synchronized (_ipFloodMap)
{
_ipFloodMap.put(h, new Flood());
}
}
return true;
}
@Override
public void run()
{
while (true)
{
final long reference = System.currentTimeMillis() - (1000 * 300);
synchronized (_ipFloodMap)
{
final Iterator<Entry<Integer, Flood>> it = _ipFloodMap.entrySet().iterator();
while (it.hasNext())
{
final Flood f = it.next().getValue();
if (f.lastAccess < reference)
{
it.remove();
}
}
}
try
{
Thread.sleep(SLEEP_TIME);
}
catch (InterruptedException e)
{
return;
}
}
}
}

View File

@@ -0,0 +1,737 @@
/*
* 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.LinkedHashMap;
import java.util.Map;
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.util.file.filter.XMLFilter;
import org.l2jmobius.gameserver.model.Location;
/**
* 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);
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "Could not parse file: " + f.getName(), e);
}
}
/**
* 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;
}
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;
}
/**
* 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,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.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)
{
final 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)
{
// Ignore.
}
@Override
public void changedUpdate(DocumentEvent e)
{
// Ignore.
}
/*
* 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.
final Document document = e.getDocument();
final 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)
{
final Element line = root.getElement(0);
final 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.
final Element line = root.getElement(root.getElementCount() - 1);
final int start = line.getStartOffset();
final int end = line.getEndOffset();
try
{
document.remove(start - 1, end - start);
}
catch (BadLocationException ble)
{
System.out.println(ble);
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;
public class OlympiadLogger
{
private static final Logger LOGGER = Logger.getLogger(OlympiadLogger.class.getName());
public static void add(String text, String cat)
{
final String date = new SimpleDateFormat("yy.MM.dd H:mm:ss").format(new Date());
new File("log/game").mkdirs();
final File file = new File("log/game/" + (cat != null ? cat : "_all") + ".txt");
FileWriter save = null;
try
{
save = new FileWriter(file, true);
final String out = "[" + date + "] '---': " + text + "\n"; // "+char_name()+"
save.write(out);
save.flush();
}
catch (IOException e)
{
LOGGER.warning("saving chat LOGGER failed: " + e);
e.printStackTrace();
}
finally
{
if (save != null)
{
try
{
save.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
if (cat != null)
{
add(text, null);
}
}
}

View File

@@ -0,0 +1,345 @@
/*
* 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;
/*
* Modified for Trove to use the java.util.Arrays sort/search
* algorithms instead of those provided with colt.
*/
/**
* Used to keep hash table capacities prime numbers. Not of interest for users; only for implementors of hashtables.
* <p>
* Choosing prime numbers as hash table capacities is a good idea to keep them working fast, particularly under hash table expansions.
* <p>
* However, JDK 1.2, JGL 3.1 and many other toolkits do nothing to keep capacities prime. This class provides efficient means to choose prime capacities.
* <p>
* Choosing a prime is <tt>O(LOGGER 300)</tt> (binary search in a list of 300 ints). Memory requirements: 1 KB static memory.
* @author wolfgang.hoschek@cern.ch
* @version 1.0, 09/24/99
*/
public class PrimeFinder
{
/**
* The largest prime this class can generate; currently equal to <tt>Integer.MAX_VALUE</tt>
*/
public static final int LARGEST_PRIME = Integer.MAX_VALUE; // yes, it is prime.
/**
* The prime number list consists of 11 chunks. Each chunk contains prime numbers. A chunk starts with a prime P1. The next element is a prime P2. P2 is the smallest prime for which holds: P2 >= 2*P1. The next element is P3, for which the same holds with respect to P2, and so on. Chunks are
* chosen such that for any desired capacity >= 1000 the list includes a prime number <= desired capacity * 1.11. Therefore, primes can be retrieved which are quite close to any desired capacity, which in turn avoids wasting memory. For example, the list includes
* 1039,1117,1201,1277,1361,1439,1523,1597,1759,1907,2081. So if you need a prime >= 1040, you will find a prime <= 1040*1.11=1154. Chunks are chosen such that they are optimized for a hashtable growthfactor of 2.0; If your hashtable has such a growthfactor then, after initially "rounding to a
* prime" upon hashtable construction, it will later expand to prime capacities such that there exist no better primes. In total these are about 32*10=320 numbers -> 1 KB of static memory needed. If you are stingy, then delete every second or fourth chunk.
*/
private static final int[] PRIME_CAPACITIES =
{
// chunk #0
LARGEST_PRIME,
// chunk #1
5,
11,
23,
47,
97,
197,
397,
797,
1597,
3203,
6421,
12853,
25717,
51437,
102877,
205759,
411527,
823117,
1646237,
3292489,
6584983,
13169977,
26339969,
52679969,
105359939,
210719881,
421439783,
842879579,
1685759167,
// chunk #2
433,
877,
1759,
3527,
7057,
14143,
28289,
56591,
113189,
226379,
452759,
905551,
1811107,
3622219,
7244441,
14488931,
28977863,
57955739,
115911563,
231823147,
463646329,
927292699,
1854585413,
// chunk #3
953,
1907,
3821,
7643,
15287,
30577,
61169,
122347,
244703,
489407,
978821,
1957651,
3915341,
7830701,
15661423,
31322867,
62645741,
125291483,
250582987,
501165979,
1002331963,
2004663929,
// chunk #4
1039,
2081,
4177,
8363,
16729,
33461,
66923,
133853,
267713,
535481,
1070981,
2141977,
4283963,
8567929,
17135863,
34271747,
68543509,
137087021,
274174111,
548348231,
1096696463,
// chunk #5
31,
67,
137,
277,
557,
1117,
2237,
4481,
8963,
17929,
35863,
71741,
143483,
286973,
573953,
1147921,
2295859,
4591721,
9183457,
18366923,
36733847,
73467739,
146935499,
293871013,
587742049,
1175484103,
// chunk #6
599,
1201,
2411,
4831,
9677,
19373,
38747,
77509,
155027,
310081,
620171,
1240361,
2480729,
4961459,
9922933,
19845871,
39691759,
79383533,
158767069,
317534141,
635068283,
1270136683,
// chunk #7
311,
631,
1277,
2557,
5119,
10243,
20507,
41017,
82037,
164089,
328213,
656429,
1312867,
2625761,
5251529,
10503061,
21006137,
42012281,
84024581,
168049163,
336098327,
672196673,
1344393353,
// chunk #8
3,
7,
17,
37,
79,
163,
331,
673,
1361,
2729,
5471,
10949,
21911,
43853,
87719,
175447,
350899,
701819,
1403641,
2807303,
5614657,
11229331,
22458671,
44917381,
89834777,
179669557,
359339171,
718678369,
1437356741,
// chunk #9
43,
89,
179,
359,
719,
1439,
2879,
5779,
11579,
23159,
46327,
92657,
185323,
370661,
741337,
1482707,
2965421,
5930887,
11861791,
23723597,
47447201,
94894427,
189788857,
379577741,
759155483,
1518310967,
// chunk #10
379,
761,
1523,
3049,
6101,
12203,
24407,
48817,
97649,
195311,
390647,
781301,
1562611,
3125257,
6250537,
12501169,
25002389,
50004791,
100009607,
200019221,
400038451,
800076929,
1600153859
};
static
{
// initializer
// The above prime numbers are formatted for human readability.
// To find numbers fast, we sort them once and for all.
Arrays.sort(PRIME_CAPACITIES);
}
/**
* Returns a prime number which is <code>&gt;= desiredCapacity</code> and very close to <code>desiredCapacity</code> (within 11% if <code>desiredCapacity &gt;= 1000</code>).
* @param desiredCapacity the capacity desired by the user.
* @return the capacity which should be used for a hashtable.
*/
public static final int nextPrime(int desiredCapacity)
{
int i = Arrays.binarySearch(PRIME_CAPACITIES, desiredCapacity);
if (i < 0)
{
// desired capacity not found, choose next prime greater
// than desired capacity
i = -i - 1; // remember the semantics of binarySearch...
}
return PRIME_CAPACITIES[i];
}
}

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);
final 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,307 @@
/*
* 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.text.NumberFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.l2jmobius.Config;
/**
* String utilities optimized for the best performance.
* <h1>How to Use It</h1>
* <h2>concat() or append()</h2> If concatenating strings in single call, use StringUtil.concat(), otherwise use StringUtil.append() and its variants.
* <h2>Minimum Calls</h2> 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 so the whole text fits into the memory and less array copy tasks has to be performed. So if using less calls, less memory is used and string concatenation is faster.
* <h2>Size Hints for Loops</h2> 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>.
* <h2>Concatenation and Constants</h2> 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.
* <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>.
* <h2>StringBuilder Reuse</h2> 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().
* <h2>How much faster is it?</h2> 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 StringBuilder. Also, with more strings concatenated, the difference between StringBuilder and StringBuilder gets larger. In code, there are many
* cases, where there are concatenated 50+ strings so the time saving is even greater.
*
* <pre>
* Count: 2
* StringBuilder: 1893
* StringBuilder with size: 1703
* String: 1033
* StringBuilder: 993
* StringBuilder with size: 1024
* Count: 3
* StringBuilder: 1973
* StringBuilder with size: 1872
* String: 2583
* StringBuilder: 1633
* StringBuilder with size: 1156
* Count: 4
* StringBuilder: 2188
* StringBuilder with size: 2229
* String: 4207
* StringBuilder: 1816
* StringBuilder with size: 1444
* Count: 5
* StringBuilder: 9185
* StringBuilder with size: 9464
* String: 6937
* StringBuilder: 2745
* StringBuilder with size: 1882
* Count: 6
* StringBuilder: 9785
* StringBuilder with size: 10082
* String: 9471
* StringBuilder: 2889
* StringBuilder with size: 1857
* Count: 7
* StringBuilder: 10169
* StringBuilder 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
* @see StringUtil
*/
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
* @see StringUtil
*/
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
* @see StringUtil
*/
public static void append(StringBuilder sbString, String... strings)
{
sbString.ensureCapacity(sbString.length() + getLength(strings));
for (String string : strings)
{
sbString.append(string);
}
}
/**
* Appends objects to an existing StringBuilder.
* @param sb : the StringBuilder to edit.
* @param content : parameters to append.
*/
public static void append(StringBuilder sb, Object... content)
{
for (Object obj : content)
{
sb.append((obj == null) ? null : obj.toString());
}
}
/**
* Counts total length of all the strings.
* @param strings array of strings
* @return total length of all the strings
*/
private static int getLength(String[] strings)
{
int length = 0;
for (String string : strings)
{
if (string == null)
{
length += 4;
}
else
{
length += 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();
}
/**
* @param value : the number to format.
* @return a number formatted with "," delimiter.
*/
public static String formatNumber(long value)
{
return NumberFormat.getInstance(Locale.ENGLISH).format(value);
}
/**
* @param string : the initial word to scramble.
* @return an anagram of the given string.
*/
public static String scrambleString(String string)
{
final List<String> letters = Arrays.asList(string.split(""));
Collections.shuffle(letters);
final StringBuilder sb = new StringBuilder(string.length());
for (String c : letters)
{
sb.append(c);
}
return sb.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,223 @@
/*
* 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.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
/**
* @version $Revision: 1.2 $ $Date: 2004/06/27 08:12:59 $
* @author luisantonioa
*/
public class Util
{
protected static final Logger LOGGER = Logger.getLogger(Util.class.getName());
public static boolean isInternalIP(String ipAddress)
{
return ipAddress.startsWith("192.168.") || ipAddress.startsWith("10.") || ipAddress.startsWith("127.0.0.1");
}
public static String printData(byte[] data, int len)
{
final StringBuilder result = new StringBuilder();
int counter = 0;
for (int i = 0; i < len; i++)
{
if ((counter % 16) == 0)
{
result.append(fillHex(i, 4) + ": ");
}
result.append(fillHex(data[i] & 0xff, 2) + " ");
counter++;
if (counter == 16)
{
result.append(" ");
int charpoint = i - 15;
for (int a = 0; a < 16; a++)
{
final int t1 = data[charpoint++];
if ((t1 > 0x1f) && (t1 < 0x80))
{
result.append((char) t1);
}
else
{
result.append('.');
}
}
result.append('\n');
counter = 0;
}
}
final int rest = data.length % 16;
if (rest > 0)
{
for (int i = 0; i < (17 - rest); i++)
{
result.append(" ");
}
int charpoint = data.length - rest;
for (int a = 0; a < rest; a++)
{
final int t1 = data[charpoint++];
if ((t1 > 0x1f) && (t1 < 0x80))
{
result.append((char) t1);
}
else
{
result.append('.');
}
}
result.append('\n');
}
return result.toString();
}
public static String fillHex(int data, int digits)
{
String number = Integer.toHexString(data);
for (int i = number.length(); i < digits; i++)
{
number = "0" + number;
}
return number;
}
public static void printSection(String section)
{
String s = "=[ " + section + " ]";
while (s.length() < 61)
{
s = "-" + s;
}
LOGGER.info(s);
}
/**
* @param raw
* @return
*/
public static String printData(byte[] raw)
{
return printData(raw, raw.length);
}
/**
* converts a given time from minutes -> miliseconds
* @param minutesToConvert
* @return
*/
public static int convertMinutesToMiliseconds(int minutesToConvert)
{
return minutesToConvert * 60000;
}
/**
* 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();
}
/**
* 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;
}
/**
* @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;
}
}

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.util.file.filter;
import java.io.File;
import java.io.FileFilter;
/**
* Specialized {@link FileFilter} class.<br>
* Accepts <b>files</b> ending with ".sql" only.
* @author Zoey76
*/
public class SQLFilter implements FileFilter
{
@Override
public boolean accept(File f)
{
return (f != null) && f.isFile() && f.getName().toLowerCase().endsWith(".sql");
}
}

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.util.file.filter;
import java.io.File;
import java.io.FileFilter;
/**
* Specialized {@link FileFilter} class.<br>
* Accepts files ending with ".xml" only.
* @author mrTJO
*/
public class XMLFilter implements FileFilter
{
@Override
public boolean accept(File f)
{
return (f != null) && f.isFile() && f.getName().toLowerCase().endsWith(".xml");
}
}

View File

@@ -0,0 +1,549 @@
/*
* 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.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.logging.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.mmocore.SelectorConfig;
import org.l2jmobius.commons.mmocore.SelectorThread;
import org.l2jmobius.commons.util.DeadlockDetector;
import org.l2jmobius.commons.util.IPv4Filter;
import org.l2jmobius.commons.util.Util;
import org.l2jmobius.gameserver.cache.CrestCache;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager;
import org.l2jmobius.gameserver.datatables.HeroSkillTable;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.datatables.NobleSkillTable;
import org.l2jmobius.gameserver.datatables.OfflineTradeTable;
import org.l2jmobius.gameserver.datatables.SchemeBufferTable;
import org.l2jmobius.gameserver.datatables.SkillTable;
import org.l2jmobius.gameserver.datatables.sql.CharNameTable;
import org.l2jmobius.gameserver.datatables.sql.ClanTable;
import org.l2jmobius.gameserver.datatables.sql.HelperBuffTable;
import org.l2jmobius.gameserver.datatables.sql.NpcTable;
import org.l2jmobius.gameserver.datatables.sql.PetDataTable;
import org.l2jmobius.gameserver.datatables.sql.SkillSpellbookTable;
import org.l2jmobius.gameserver.datatables.sql.SkillTreeTable;
import org.l2jmobius.gameserver.datatables.sql.SpawnTable;
import org.l2jmobius.gameserver.datatables.sql.TeleportLocationTable;
import org.l2jmobius.gameserver.datatables.xml.AdminData;
import org.l2jmobius.gameserver.datatables.xml.ArmorSetData;
import org.l2jmobius.gameserver.datatables.xml.AugmentationData;
import org.l2jmobius.gameserver.datatables.xml.BoatData;
import org.l2jmobius.gameserver.datatables.xml.DoorData;
import org.l2jmobius.gameserver.datatables.xml.ExperienceData;
import org.l2jmobius.gameserver.datatables.xml.ExtractableItemData;
import org.l2jmobius.gameserver.datatables.xml.FenceData;
import org.l2jmobius.gameserver.datatables.xml.FishData;
import org.l2jmobius.gameserver.datatables.xml.HennaData;
import org.l2jmobius.gameserver.datatables.xml.ManorSeedData;
import org.l2jmobius.gameserver.datatables.xml.MapRegionData;
import org.l2jmobius.gameserver.datatables.xml.MultisellData;
import org.l2jmobius.gameserver.datatables.xml.PlayerTemplateData;
import org.l2jmobius.gameserver.datatables.xml.RecipeData;
import org.l2jmobius.gameserver.datatables.xml.StaticObjectData;
import org.l2jmobius.gameserver.datatables.xml.SummonItemData;
import org.l2jmobius.gameserver.datatables.xml.WalkerRouteData;
import org.l2jmobius.gameserver.datatables.xml.ZoneData;
import org.l2jmobius.gameserver.geoengine.GeoEngine;
import org.l2jmobius.gameserver.handler.AdminCommandHandler;
import org.l2jmobius.gameserver.handler.AutoAnnouncementHandler;
import org.l2jmobius.gameserver.handler.AutoChatHandler;
import org.l2jmobius.gameserver.handler.ItemHandler;
import org.l2jmobius.gameserver.handler.SkillHandler;
import org.l2jmobius.gameserver.handler.UserCommandHandler;
import org.l2jmobius.gameserver.handler.VoicedCommandHandler;
import org.l2jmobius.gameserver.idfactory.IdFactory;
import org.l2jmobius.gameserver.instancemanager.AuctionManager;
import org.l2jmobius.gameserver.instancemanager.CastleManager;
import org.l2jmobius.gameserver.instancemanager.CastleManorManager;
import org.l2jmobius.gameserver.instancemanager.ClanHallManager;
import org.l2jmobius.gameserver.instancemanager.ClassDamageManager;
import org.l2jmobius.gameserver.instancemanager.CoupleManager;
import org.l2jmobius.gameserver.instancemanager.CrownManager;
import org.l2jmobius.gameserver.instancemanager.CursedWeaponsManager;
import org.l2jmobius.gameserver.instancemanager.DayNightSpawnManager;
import org.l2jmobius.gameserver.instancemanager.DimensionalRiftManager;
import org.l2jmobius.gameserver.instancemanager.DuelManager;
import org.l2jmobius.gameserver.instancemanager.FishingChampionshipManager;
import org.l2jmobius.gameserver.instancemanager.FortManager;
import org.l2jmobius.gameserver.instancemanager.FortSiegeManager;
import org.l2jmobius.gameserver.instancemanager.FourSepulchersManager;
import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager;
import org.l2jmobius.gameserver.instancemanager.GrandBossManager;
import org.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import org.l2jmobius.gameserver.instancemanager.MercTicketManager;
import org.l2jmobius.gameserver.instancemanager.PetitionManager;
import org.l2jmobius.gameserver.instancemanager.QuestManager;
import org.l2jmobius.gameserver.instancemanager.RaidBossPointsManager;
import org.l2jmobius.gameserver.instancemanager.RaidBossSpawnManager;
import org.l2jmobius.gameserver.instancemanager.SiegeManager;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.entity.Announcements;
import org.l2jmobius.gameserver.model.entity.Hero;
import org.l2jmobius.gameserver.model.entity.event.Lottery;
import org.l2jmobius.gameserver.model.entity.event.MonsterRace;
import org.l2jmobius.gameserver.model.entity.event.PcPoint;
import org.l2jmobius.gameserver.model.entity.event.manager.EventManager;
import org.l2jmobius.gameserver.model.entity.olympiad.Olympiad;
import org.l2jmobius.gameserver.model.entity.sevensigns.SevenSigns;
import org.l2jmobius.gameserver.model.entity.sevensigns.SevenSignsFestival;
import org.l2jmobius.gameserver.model.entity.siege.clanhalls.BanditStrongholdSiege;
import org.l2jmobius.gameserver.model.entity.siege.clanhalls.DevastatedCastle;
import org.l2jmobius.gameserver.model.entity.siege.clanhalls.FortressOfResistance;
import org.l2jmobius.gameserver.model.partymatching.PartyMatchRoomList;
import org.l2jmobius.gameserver.model.partymatching.PartyMatchWaitingList;
import org.l2jmobius.gameserver.model.spawn.AutoSpawn;
import org.l2jmobius.gameserver.network.GameClient;
import org.l2jmobius.gameserver.network.GamePacketHandler;
import org.l2jmobius.gameserver.script.EventDroplist;
import org.l2jmobius.gameserver.script.faenor.FaenorScriptEngine;
import org.l2jmobius.gameserver.scripting.ScriptEngineManager;
import org.l2jmobius.gameserver.taskmanager.TaskManager;
import org.l2jmobius.gameserver.ui.Gui;
import org.l2jmobius.telnet.TelnetStatusThread;
public class GameServer
{
private static final Logger LOGGER = Logger.getLogger(GameServer.class.getName());
private static SelectorThread<GameClient> _selectorThread;
private static LoginServerThread _loginThread;
private static GamePacketHandler _gamePacketHandler;
private static TelnetStatusThread _statusServer;
private static GameServer INSTANCE;
public static final Calendar dateTimeServerStarted = Calendar.getInstance();
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);
Util.printSection("Database");
DatabaseFactory.init();
Util.printSection("ThreadPool");
ThreadPool.init();
if (Config.DEADLOCKCHECK_INTIAL_TIME > 0)
{
ThreadPool.scheduleAtFixedRate(DeadlockDetector.getInstance(), Config.DEADLOCKCHECK_INTIAL_TIME, Config.DEADLOCKCHECK_DELAY_TIME);
}
Util.printSection("IdFactory");
IdFactory.init();
if (!IdFactory.hasInitialized())
{
LOGGER.severe("IdFactory: Could not read object IDs from database. Please check your configuration.");
throw new Exception("Could not initialize the ID factory!");
}
new File(Config.DATAPACK_ROOT, "data/clans").mkdirs();
new File(Config.DATAPACK_ROOT, "data/crests").mkdirs();
new File(Config.DATAPACK_ROOT, "data/geodata").mkdirs();
HtmCache.getInstance();
CrestCache.getInstance();
ScriptEngineManager.getInstance();
Util.printSection("World");
World.getInstance();
MapRegionData.getInstance();
Announcements.getInstance();
AutoAnnouncementHandler.getInstance();
GlobalVariablesManager.getInstance();
StaticObjectData.getInstance();
TeleportLocationTable.getInstance();
PartyMatchWaitingList.getInstance();
PartyMatchRoomList.getInstance();
GameTimeController.getInstance();
CharNameTable.getInstance();
ExperienceData.getInstance();
DuelManager.getInstance();
Util.printSection("Players");
PlayerTemplateData.getInstance();
if (Config.ENABLE_CLASS_DAMAGE_SETTINGS)
{
ClassDamageManager.loadConfig();
}
ClanTable.getInstance();
if (Config.ENABLE_COMMUNITY_BOARD)
{
ForumsBBSManager.getInstance().initRoot();
}
Util.printSection("Skills");
if (!SkillTable.getInstance().isInitialized())
{
LOGGER.info("Could not find the extraced files. Please Check Your Data.");
throw new Exception("Could not initialize the skill table");
}
SkillTreeTable.getInstance();
SkillSpellbookTable.getInstance();
NobleSkillTable.getInstance();
HeroSkillTable.getInstance();
if (!HelperBuffTable.getInstance().isInitialized())
{
throw new Exception("Could not initialize the Helper Buff Table.");
}
LOGGER.info("Skills: All skills loaded.");
Util.printSection("Items");
ItemTable.getInstance();
ArmorSetData.getInstance();
ExtractableItemData.getInstance();
SummonItemData.getInstance();
HennaData.getInstance();
if (Config.ALLOWFISHING)
{
FishData.getInstance();
}
Util.printSection("Npc");
SchemeBufferTable.getInstance();
WalkerRouteData.getInstance();
if (!NpcTable.getInstance().isInitialized())
{
LOGGER.info("Could not find the extracted files. Please Check Your Data.");
throw new Exception("Could not initialize the npc table");
}
Util.printSection("Geodata");
GeoEngine.getInstance();
Util.printSection("Economy");
TradeController.getInstance();
MultisellData.getInstance();
Util.printSection("Clan Halls");
ClanHallManager.getInstance();
FortressOfResistance.getInstance();
DevastatedCastle.getInstance();
BanditStrongholdSiege.getInstance();
AuctionManager.getInstance();
Util.printSection("Zone");
ZoneData.getInstance();
Util.printSection("Spawnlist");
if (!Config.ALT_DEV_NO_SPAWNS)
{
SpawnTable.getInstance();
}
else
{
LOGGER.info("Spawn: disable load.");
}
if (!Config.ALT_DEV_NO_RB)
{
RaidBossSpawnManager.getInstance();
GrandBossManager.getInstance();
RaidBossPointsManager.init();
}
else
{
LOGGER.info("RaidBoss: disable load.");
}
DayNightSpawnManager.getInstance().notifyChangeMode();
Util.printSection("Dimensional Rift");
DimensionalRiftManager.getInstance();
Util.printSection("Misc");
RecipeData.getInstance();
RecipeController.getInstance();
EventDroplist.getInstance();
AugmentationData.getInstance();
MonsterRace.getInstance();
Lottery.getInstance();
MercTicketManager.getInstance();
PetitionManager.getInstance();
CursedWeaponsManager.getInstance();
TaskManager.getInstance();
PetDataTable.getInstance();
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance();
}
if ((Config.AUTODESTROY_ITEM_AFTER > 0) || (Config.HERB_AUTO_DESTROY_TIME > 0))
{
ItemsAutoDestroy.getInstance();
}
Util.printSection("Manor");
ManorSeedData.getInstance();
CastleManorManager.getInstance();
Util.printSection("Castles");
CastleManager.getInstance();
SiegeManager.getInstance();
FortManager.getInstance();
FortSiegeManager.getInstance();
CrownManager.getInstance();
Util.printSection("Boat");
BoatData.getInstance();
Util.printSection("Doors");
DoorData.getInstance().load();
FenceData.getInstance();
Util.printSection("Four Sepulchers");
FourSepulchersManager.getInstance();
Util.printSection("Seven Signs");
SevenSigns.getInstance();
SevenSignsFestival.getInstance();
AutoSpawn.getInstance();
AutoChatHandler.getInstance();
Util.printSection("Olympiad System");
Olympiad.getInstance();
Hero.getInstance();
Util.printSection("Access Levels");
AdminData.getInstance();
Util.printSection("Handlers");
ItemHandler.getInstance();
SkillHandler.getInstance();
AdminCommandHandler.getInstance();
UserCommandHandler.getInstance();
VoicedCommandHandler.getInstance();
LOGGER.info("AutoChatHandler: Loaded " + AutoChatHandler.getInstance().size() + " handlers in total.");
LOGGER.info("AutoSpawnHandler: Loaded " + AutoSpawn.getInstance().size() + " handlers in total.");
Runtime.getRuntime().addShutdownHook(Shutdown.getInstance());
// Schedule auto opening/closing doors.
DoorData.getInstance().checkAutoOpen();
Util.printSection("Scripts");
if (!Config.ALT_DEV_NO_SCRIPT)
{
LOGGER.info("ScriptEngineManager: Loading server scripts:");
ScriptEngineManager.getInstance().executeScriptList();
FaenorScriptEngine.getInstance();
}
else
{
LOGGER.info("Script: disable load.");
}
if (Config.ALT_FISH_CHAMPIONSHIP_ENABLED)
{
FishingChampionshipManager.getInstance();
}
/* QUESTS */
Util.printSection("Quests");
if (!Config.ALT_DEV_NO_QUESTS)
{
if (QuestManager.getInstance().getQuests().size() == 0)
{
QuestManager.getInstance().reloadAllQuests();
}
else
{
QuestManager.getInstance().report();
}
}
else
{
QuestManager.getInstance().unloadAllQuests();
}
Util.printSection("Game Server");
LOGGER.info("IdFactory: Free ObjectID's remaining: " + IdFactory.size());
if (Config.ALLOW_WEDDING)
{
CoupleManager.getInstance();
}
if (Config.PCB_ENABLE)
{
ThreadPool.scheduleAtFixedRate(PcPoint.getInstance(), Config.PCB_INTERVAL * 1000, Config.PCB_INTERVAL * 1000);
}
Util.printSection("EventManager");
EventManager.getInstance().startEventRegistration();
if (EventManager.TVT_EVENT_ENABLED || EventManager.CTF_EVENT_ENABLED || EventManager.DM_EVENT_ENABLED)
{
if (EventManager.TVT_EVENT_ENABLED)
{
LOGGER.info("TVT Event is Enabled.");
}
if (EventManager.CTF_EVENT_ENABLED)
{
LOGGER.info("CTF Event is Enabled.");
}
if (EventManager.DM_EVENT_ENABLED)
{
LOGGER.info("DM Event is Enabled.");
}
}
else
{
LOGGER.info("All events are Disabled.");
}
if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS)
{
OfflineTradeTable.restoreOfflineTraders();
}
Util.printSection("Protection");
if (Config.CHECK_SKILLS_ON_ENTER)
{
LOGGER.info("Check skills on enter actived.");
}
if (Config.CHECK_NAME_ON_LOGIN)
{
LOGGER.info("Check bad name on enter actived.");
}
if (Config.PROTECTED_ENCHANT)
{
LOGGER.info("Check OverEnchant items on enter actived.");
}
if (Config.BYPASS_VALIDATION)
{
LOGGER.info("Bypass Validation actived.");
}
if (Config.L2WALKER_PROTECTION)
{
LOGGER.info("L2Walker protection actived.");
}
System.gc();
Util.printSection("Info");
LOGGER.info("Maximum Numbers of Connected Players: " + Config.MAXIMUM_ONLINE_USERS);
LOGGER.info("GameServer Started, free memory " + (((Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory()) + Runtime.getRuntime().freeMemory()) / 1048576) + " Mb of " + (Runtime.getRuntime().maxMemory() / 1048576) + " Mb");
LOGGER.info("Used memory: " + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1048576) + " MB");
Util.printSection("Status");
LOGGER.info("Server Loaded in " + ((System.currentTimeMillis() - serverLoadStart) / 1000) + " seconds.");
// Load telnet status
Util.printSection("Telnet");
if (Config.IS_TELNET_ENABLED)
{
_statusServer = new TelnetStatusThread();
_statusServer.start();
}
else
{
LOGGER.info("Telnet server is disabled.");
}
Util.printSection("Login");
_loginThread = LoginServerThread.getInstance();
_loginThread.start();
final SelectorConfig sc = new SelectorConfig();
sc.MAX_READ_PER_PASS = Config.MMO_MAX_READ_PER_PASS;
sc.MAX_SEND_PER_PASS = Config.MMO_MAX_SEND_PER_PASS;
sc.SLEEP_TIME = Config.MMO_SELECTOR_SLEEP_TIME;
sc.HELPER_BUFFER_COUNT = Config.MMO_HELPER_BUFFER_COUNT;
_gamePacketHandler = new GamePacketHandler();
_selectorThread = new SelectorThread<>(sc, _gamePacketHandler, _gamePacketHandler, _gamePacketHandler, new IPv4Filter());
InetAddress bindAddress = null;
if (!Config.GAMESERVER_HOSTNAME.equals("*"))
{
try
{
bindAddress = InetAddress.getByName(Config.GAMESERVER_HOSTNAME);
}
catch (UnknownHostException e1)
{
LOGGER.warning("The GameServer bind address is invalid, using all avaliable IPs. Reason: " + e1);
}
}
try
{
_selectorThread.openServerSocket(bindAddress, Config.PORT_GAME);
}
catch (IOException e)
{
LOGGER.severe("Failed to open server socket. Reason: " + e);
System.exit(1);
}
_selectorThread.start();
}
public static SelectorThread<GameClient> getSelectorThread()
{
return _selectorThread;
}
public static void main(String[] args) throws Exception
{
INSTANCE = new GameServer();
}
public static GameServer getInstance()
{
return INSTANCE;
}
}

View File

@@ -0,0 +1,278 @@
/*
* 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.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Logger;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.gameserver.ai.CtrlEvent;
import org.l2jmobius.gameserver.instancemanager.DayNightSpawnManager;
import org.l2jmobius.gameserver.model.actor.Creature;
/**
* @version $Revision: 1.1.4.8 $ $Date: 2005/04/06 16:13:24 $
*/
public class GameTimeController
{
static final Logger LOGGER = Logger.getLogger(GameTimeController.class.getName());
public static final int TICKS_PER_SECOND = 10;
public static final int MILLIS_IN_TICK = 1000 / TICKS_PER_SECOND;
private static GameTimeController INSTANCE = new GameTimeController();
protected static int _gameTicks;
protected static long _gameStartTime;
protected static boolean _isNight = false;
private static List<Creature> _movingObjects = new ArrayList<>();
protected static TimerThread _timer;
private final ScheduledFuture<?> _timerWatcher;
/**
* one ingame day is 240 real minutes
* @return
*/
public static GameTimeController getInstance()
{
return INSTANCE;
}
private GameTimeController()
{
_gameStartTime = System.currentTimeMillis() - 3600000; // offset so that the server starts a day begin
_gameTicks = 3600000 / MILLIS_IN_TICK; // offset so that the server starts a day begin
_timer = new TimerThread();
_timer.start();
_timerWatcher = ThreadPool.scheduleAtFixedRate(new TimerWatcher(), 0, 1000);
ThreadPool.scheduleAtFixedRate(new BroadcastSunState(), 0, 600000);
}
public boolean isNowNight()
{
return _isNight;
}
public int getGameTime()
{
return _gameTicks / (TICKS_PER_SECOND * 10);
}
public static int getGameTicks()
{
return _gameTicks;
}
/**
* Add a Creature to movingObjects of GameTimeController.<br>
* <br>
* <b><u>Concept</u>:</b><br>
* <br>
* All Creature in movement are identified in <b>movingObjects</b> of GameTimeController.
* @param creature The Creature to add to movingObjects of GameTimeController
*/
public synchronized void registerMovingObject(Creature creature)
{
if (creature == null)
{
return;
}
if (!_movingObjects.contains(creature))
{
_movingObjects.add(creature);
}
}
/**
* Move all Creatures contained in movingObjects of GameTimeController.<br>
* <br>
* <b><u>Concept</u>:</b><br>
* <br>
* All Creature in movement are identified in <b>movingObjects</b> of GameTimeController.<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <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>
*/
protected synchronized void moveObjects()
{
// Get all Creature from the ArrayList movingObjects and put them into a table
final Creature[] chars = _movingObjects.toArray(new Creature[_movingObjects.size()]);
// Create an ArrayList to contain all Creature that are arrived to destination
List<Creature> ended = null;
// Go throw the table containing Creature in movement
for (Creature creature : chars)
{
// Update the position of the Creature and return True if the movement is finished
final boolean end = creature.updatePosition(_gameTicks);
// If movement is finished, the Creature is removed from movingObjects and added to the ArrayList ended
if (end)
{
_movingObjects.remove(creature);
if (ended == null)
{
ended = new ArrayList<>();
}
ended.add(creature);
}
}
// 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
// TODO: maybe a general TP is needed for that kinda stuff (all knownlist updates should be done in a TP anyway).
if (ended != null)
{
ThreadPool.execute(new MovingObjectArrived(ended));
}
}
public void stopTimer()
{
_timerWatcher.cancel(true);
_timer.interrupt();
}
class TimerThread extends Thread
{
protected Exception _error;
public TimerThread()
{
super("GameTimeController");
setDaemon(true);
setPriority(MAX_PRIORITY);
}
@Override
public void run()
{
for (;;)
{
final int _oldTicks = _gameTicks; // save old ticks value to avoid moving objects 2x in same tick
long runtime = System.currentTimeMillis() - _gameStartTime; // from server boot to now
_gameTicks = (int) (runtime / MILLIS_IN_TICK); // new ticks value (ticks now)
if (_oldTicks != _gameTicks)
{
moveObjects(); // XXX: if this makes objects go slower, remove it
// but I think it can't make that effect. is it better to call moveObjects() twice in same
// tick to make-up for missed tick ? or is it better to ignore missed tick ?
// (will happen very rarely but it will happen ... on garbage collection definitely)
}
runtime = System.currentTimeMillis() - _gameStartTime - runtime;
// calculate sleep time... time needed to next tick minus time it takes to call moveObjects()
final int sleepTime = (1 + MILLIS_IN_TICK) - ((int) runtime % MILLIS_IN_TICK);
// LOGGER.finest("TICK: "+_gameTicks);
try
{
sleep(sleepTime); // hope other threads will have much more cpu time available now
}
catch (InterruptedException e)
{
// nothing
}
// SelectorThread most of all
}
}
}
class TimerWatcher implements Runnable
{
@Override
public void run()
{
if (!_timer.isAlive())
{
final String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
LOGGER.warning(time + " TimerThread stop with following error. restart it.");
if (_timer._error != null)
{
_timer._error.printStackTrace();
}
_timer = new TimerThread();
_timer.start();
}
}
}
/**
* Update the _knownObject and _knowPlayers of each Creature that finished its movement and of their already known WorldObject then notify AI with EVT_ARRIVED.
*/
class MovingObjectArrived implements Runnable
{
private final List<Creature> _ended;
MovingObjectArrived(List<Creature> ended)
{
_ended = ended;
}
@Override
public void run()
{
for (Creature creature : _ended)
{
try
{
creature.getKnownList().updateKnownObjects();
creature.getAI().notifyEvent(CtrlEvent.EVT_ARRIVED);
}
catch (NullPointerException e)
{
}
}
}
}
class BroadcastSunState implements Runnable
{
@Override
public void run()
{
final int h = (getGameTime() / 60) % 24; // Time in hour
final boolean tempIsNight = h < 6;
// If diff day/night state
if (tempIsNight != _isNight)
{
// Set current day/night varible to value of temp varible
_isNight = tempIsNight;
DayNightSpawnManager.getInstance().notifyChangeMode();
}
}
}
}

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.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.items.type.EtcItemType;
public class ItemsAutoDestroy
{
protected static final Logger LOGGER = Logger.getLogger(ItemsAutoDestroy.class.getName());
private final Collection<ItemInstance> _items = ConcurrentHashMap.newKeySet();
protected ItemsAutoDestroy()
{
ThreadPool.scheduleAtFixedRate(this::removeItems, 5000, 5000);
}
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();
for (ItemInstance item : _items)
{
if ((item == null) || (item.getDropTime() == 0) || (item.getItemLocation() != ItemInstance.ItemLocation.VOID))
{
_items.remove(item);
}
else if (item.getItemType() == EtcItemType.HERB)
{
if ((curtime - item.getDropTime()) > Config.HERB_AUTO_DESTROY_TIME)
{
World.getInstance().removeVisibleObject(item, item.getWorldRegion());
World.getInstance().removeObject(item);
_items.remove(item);
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
}
else if ((curtime - item.getDropTime()) > (Config.AUTODESTROY_ITEM_AFTER * 1000))
{
World.getInstance().removeVisibleObject(item, item.getWorldRegion());
World.getInstance().removeObject(item);
_items.remove(item);
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
}
}
public static ItemsAutoDestroy getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ItemsAutoDestroy INSTANCE = new ItemsAutoDestroy();
}
}

View File

@@ -0,0 +1,696 @@
/*
* 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.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.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.crypt.NewCrypt;
import org.l2jmobius.commons.util.Rnd;
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.gameserverpackets.AuthRequest;
import org.l2jmobius.gameserver.network.gameserverpackets.BlowFishKey;
import org.l2jmobius.gameserver.network.gameserverpackets.ChangeAccessLevel;
import org.l2jmobius.gameserver.network.gameserverpackets.GameServerBasePacket;
import org.l2jmobius.gameserver.network.gameserverpackets.PlayerAuthRequest;
import org.l2jmobius.gameserver.network.gameserverpackets.PlayerInGame;
import org.l2jmobius.gameserver.network.gameserverpackets.PlayerLogout;
import org.l2jmobius.gameserver.network.gameserverpackets.ServerStatus;
import org.l2jmobius.gameserver.network.loginserverpackets.AuthResponse;
import org.l2jmobius.gameserver.network.loginserverpackets.InitLS;
import org.l2jmobius.gameserver.network.loginserverpackets.KickPlayer;
import org.l2jmobius.gameserver.network.loginserverpackets.LoginServerFail;
import org.l2jmobius.gameserver.network.loginserverpackets.PlayerAuthResponse;
import org.l2jmobius.gameserver.network.serverpackets.AuthLoginFail;
import org.l2jmobius.gameserver.network.serverpackets.CharSelectInfo;
public class LoginServerThread extends Thread
{
protected static final Logger LOGGER = Logger.getLogger(LoginServerThread.class.getName());
/** The LoginServerThread singleton */
private static final int REVISION = 0x0102;
private RSAPublicKey _publicKey;
private final String _hostname;
private final int _port;
private final int _gamePort;
private Socket _loginSocket;
private InputStream _in;
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>
* and then after handshake, with a new key sent by<br>
* loginserver during the handshake. This new key is stored<br>
* in {@link #_blowfishKey}
*/
private NewCrypt _blowfish;
private byte[] _blowfishKey;
private byte[] _hexID;
private final boolean _acceptAlternate;
private int _requestID;
private int _serverID;
private final boolean _reserveHost;
private int _maxPlayer;
private final List<WaitingClient> _waitingClients;
private final Map<String, GameClient> _accountsInGameServer;
private int _status;
private String _serverName;
private final String _gameExternalHost;
private final String _gameInternalHost;
public 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 = generateHex(16);
}
else
{
_requestID = Config.SERVER_ID;
}
_acceptAlternate = Config.ACCEPT_ALTERNATE_ID;
_reserveHost = Config.RESERVE_HOST_ON_LOGIN;
_gameExternalHost = Config.EXTERNAL_HOSTNAME;
_gameInternalHost = Config.INTERNAL_HOSTNAME;
_waitingClients = new ArrayList<>();
_accountsInGameServer = new ConcurrentHashMap<>();
_maxPlayer = Config.MAXIMUM_ONLINE_USERS;
}
@Override
public void run()
{
while (!_interrupted)
{
int lengthHi = 0;
int lengthLo = 0;
int length = 0;
boolean checksumOk = false;
try
{
// Connection
LOGGER.info("Connecting to login on " + _hostname + ":" + _port);
_loginSocket = new Socket(_hostname, _port);
_in = _loginSocket.getInputStream();
if (_out != null)
{
synchronized (_out) // avoids tow threads writing in the mean time
{
_out = new BufferedOutputStream(_loginSocket.getOutputStream());
}
}
else
{
_out = new BufferedOutputStream(_loginSocket.getOutputStream());
}
// init Blowfish
_blowfishKey = generateHex(40);
_blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00");
while (!_interrupted)
{
lengthLo = _in.read();
lengthHi = _in.read();
length = (lengthHi * 256) + lengthLo;
if (lengthHi < 0)
{
LOGGER.info("LoginServerThread: Login terminated the connection.");
break;
}
final byte[] incoming = new byte[length];
incoming[0] = (byte) lengthLo;
incoming[1] = (byte) lengthHi;
int receivedBytes = 0;
int newBytes = 0;
while ((newBytes != -1) && (receivedBytes < (length - 2)))
{
newBytes = _in.read(incoming, 2, length - 2);
receivedBytes = receivedBytes + newBytes;
}
if (receivedBytes != (length - 2))
{
LOGGER.warning("Incomplete Packet is sent to the server, closing connection.(LS)");
break;
}
byte[] decrypt = new byte[length - 2];
System.arraycopy(incoming, 2, decrypt, 0, decrypt.length);
// decrypt if we have a key
decrypt = _blowfish.decrypt(decrypt);
checksumOk = NewCrypt.verifyChecksum(decrypt);
if (!checksumOk)
{
LOGGER.warning("Incorrect packet checksum, ignoring packet (LS)");
break;
}
final int packetType = decrypt[0] & 0xff;
switch (packetType)
{
case 0x00:
{
final InitLS init = new InitLS(decrypt);
if (init.getRevision() != REVISION)
{
// TODO: revision mismatch
LOGGER.warning("/!\\ Revision mismatch between LS and GS /!\\");
break;
}
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("Trouble while init the public key send by login");
break;
}
// send the blowfish key through the rsa encryption
final BlowFishKey bfk = new BlowFishKey(_blowfishKey, _publicKey);
sendPacket(bfk);
// now, only accept paket with the new encryption
_blowfish = new NewCrypt(_blowfishKey);
final AuthRequest ar = new AuthRequest(_requestID, _acceptAlternate, _hexID, _gameExternalHost, _gameInternalHost, _gamePort, _reserveHost, _maxPlayer);
sendPacket(ar);
break;
}
case 0x01:
{
final LoginServerFail lsf = new LoginServerFail(decrypt);
LOGGER.info("Damn! Registeration Failed: " + lsf.getReasonString());
// login will close the connection here
break;
}
case 0x02:
{
final AuthResponse aresp = new AuthResponse(decrypt);
_serverID = aresp.getServerId();
_serverName = aresp.getServerName();
Config.saveHexid(_serverID, hexToString(_hexID));
LOGGER.info("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);
}
if (Config.SERVER_LIST_CLOCK)
{
st.addAttribute(ServerStatus.SERVER_LIST_CLOCK, ServerStatus.ON);
}
else
{
st.addAttribute(ServerStatus.SERVER_LIST_CLOCK, ServerStatus.OFF);
}
if (Config.SERVER_LIST_TESTSERVER)
{
st.addAttribute(ServerStatus.TEST_SERVER, ServerStatus.ON);
}
else
{
st.addAttribute(ServerStatus.TEST_SERVER, ServerStatus.OFF);
}
if (Config.SERVER_GMONLY)
{
st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY);
}
else
{
st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_AUTO);
}
sendPacket(st);
if (World.getAllPlayersCount() > 0)
{
final List<String> playerList = new ArrayList<>();
for (PlayerInstance player : World.getInstance().getAllPlayers())
{
playerList.add(player.getAccountName());
}
final PlayerInGame pig = new PlayerInGame(playerList);
sendPacket(pig);
}
break;
}
case 0x03:
{
final PlayerAuthResponse par = new PlayerAuthResponse(decrypt);
final String account = par.getAccount();
WaitingClient wcToRemove = null;
synchronized (_waitingClients)
{
for (WaitingClient wc : _waitingClients)
{
if (wc.account.equals(account))
{
wcToRemove = wc;
}
}
}
if (wcToRemove != null)
{
if (par.isAuthed())
{
final PlayerInGame pig = new PlayerInGame(par.getAccount());
sendPacket(pig);
wcToRemove.gameClient.setState(ConnectionState.AUTHENTICATED);
wcToRemove.gameClient.setSessionId(wcToRemove.session);
final CharSelectInfo cl = new CharSelectInfo(wcToRemove.account, wcToRemove.gameClient.getSessionId().playOkID1);
wcToRemove.gameClient.getConnection().sendPacket(cl);
wcToRemove.gameClient.setCharSelection(cl.getCharInfo());
}
else
{
LOGGER.warning("Session key is not correct. Closing connection for account " + wcToRemove.account + ".");
wcToRemove.gameClient.getConnection().sendPacket(new AuthLoginFail(1));
wcToRemove.gameClient.closeNow();
}
_waitingClients.remove(wcToRemove);
}
break;
}
case 0x04:
{
final KickPlayer kp = new KickPlayer(decrypt);
doKickPlayer(kp.getAccount());
break;
}
}
}
}
catch (UnknownHostException e)
{
LOGGER.info("Deconnected from Login, Trying to reconnect:");
LOGGER.info(e.toString());
}
catch (IOException e)
{
LOGGER.info("Deconnected from Login, Trying to reconnect..");
}
finally
{
if (_out != null)
{
synchronized (_out) // avoids tow threads writing in the mean time
{
try
{
_loginSocket.close();
}
catch (Exception e)
{
}
}
}
else
{
try
{
_loginSocket.close();
}
catch (Exception e)
{
}
}
}
try
{
Thread.sleep(5000); // 5 seconds
}
catch (InterruptedException e)
{
}
}
}
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("Error while sending player auth request");
}
}
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);
}
}
}
public void sendLogout(String account)
{
if (account == null)
{
return;
}
final PlayerLogout pl = new PlayerLogout(account);
try
{
sendPacket(pl);
}
catch (IOException e)
{
LOGGER.warning("Error while sending logout packet to login: " + e.getMessage());
}
}
public boolean addGameServerLogin(String account, GameClient client)
{
final GameClient savedClient = _accountsInGameServer.get(account);
if (savedClient != null)
{
if (savedClient.isDetached())
{
_accountsInGameServer.put(account, client);
return true;
}
savedClient.closeNow();
_accountsInGameServer.remove(account);
return false;
}
_accountsInGameServer.put(account, client);
return true;
}
public void sendAccessLevel(String account, int level)
{
final ChangeAccessLevel cal = new ChangeAccessLevel(account, level);
try
{
sendPacket(cal);
}
catch (IOException e)
{
}
}
private String hexToString(byte[] hex)
{
return new BigInteger(hex).toString(16);
}
public void doKickPlayer(String account)
{
if (_accountsInGameServer.get(account) != null)
{
_accountsInGameServer.get(account).closeNow();
getInstance().sendLogout(account);
}
}
public static byte[] generateHex(int size)
{
final byte[] array = new byte[size];
Rnd.nextBytes(array);
return array;
}
/**
* @param sl
* @throws IOException
*/
private void sendPacket(GameServerBasePacket sl) throws IOException
{
if (_interrupted)
{
return;
}
byte[] data = sl.getContent();
NewCrypt.appendChecksum(data);
data = _blowfish.crypt(data);
final int len = data.length + 2;
if ((_out != null) && !_loginSocket.isClosed() && _loginSocket.isConnected())
{
synchronized (_out) // avoids tow threads writing in the mean time
{
_out.write(len & 0xff);
_out.write((len >> 8) & 0xff);
_out.write(data);
_out.flush();
}
}
}
/**
* @param maxPlayer The maxPlayer to set.
*/
public void setMaxPlayer(int maxPlayer)
{
sendServerStatus(ServerStatus.MAX_PLAYERS, maxPlayer);
_maxPlayer = maxPlayer;
}
/**
* @return Returns the maxPlayer.
*/
public int getMaxPlayer()
{
return _maxPlayer;
}
/**
* @param id
* @param value
*/
public void sendServerStatus(int id, int value)
{
final ServerStatus ss = new ServerStatus();
ss.addAttribute(id, value);
try
{
sendPacket(ss);
}
catch (IOException e)
{
}
}
/**
* @return
*/
public String getStatusString()
{
return ServerStatus.STATUS_STRING[_status];
}
/**
* @return
*/
public boolean isClockShown()
{
return Config.SERVER_LIST_CLOCK;
}
/**
* @return
*/
public boolean isBracketShown()
{
return Config.SERVER_LIST_BRACKET;
}
/**
* @return Returns the serverName.
*/
public String getServerName()
{
return _serverName;
}
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 static class SessionKey
{
public int playOkID1;
public int playOkID2;
public int loginOkID1;
public int loginOkID2;
public int clientKey = -1;
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 class WaitingClient
{
public String account;
public GameClient gameClient;
public SessionKey session;
public WaitingClient(String acc, GameClient client, SessionKey key)
{
account = acc;
gameClient = client;
session = key;
}
}
private boolean _interrupted = false;
@Override
public void interrupt()
{
_interrupted = true;
super.interrupt();
}
@Override
public boolean isInterrupted()
{
return _interrupted;
}
/**
* Gets the single instance of LoginServerThread.
* @return single instance of LoginServerThread
*/
public static LoginServerThread getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final LoginServerThread INSTANCE = new LoginServerThread();
}
}

View File

@@ -0,0 +1,710 @@
/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.datatables.xml.RecipeData;
import org.l2jmobius.gameserver.model.ManufactureItem;
import org.l2jmobius.gameserver.model.RecipeList;
import org.l2jmobius.gameserver.model.Skill;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.instance.RecipeInstance;
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.skills.Stat;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import org.l2jmobius.gameserver.network.serverpackets.ItemList;
import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse;
import org.l2jmobius.gameserver.network.serverpackets.RecipeBookItemList;
import org.l2jmobius.gameserver.network.serverpackets.RecipeItemMakeInfo;
import org.l2jmobius.gameserver.network.serverpackets.RecipeShopItemInfo;
import org.l2jmobius.gameserver.network.serverpackets.SetupGauge;
import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import org.l2jmobius.gameserver.util.Util;
public class RecipeController
{
protected static final Logger LOGGER = Logger.getLogger(RecipeController.class.getName());
private static RecipeController INSTANCE;
protected static final Map<PlayerInstance, RecipeItemMaker> _activeMakers = Collections.synchronizedMap(new WeakHashMap<PlayerInstance, RecipeItemMaker>());
public static RecipeController getInstance()
{
return INSTANCE == null ? INSTANCE = new RecipeController() : INSTANCE;
}
public synchronized void requestBookOpen(PlayerInstance player, boolean isDwarvenCraft)
{
RecipeItemMaker maker = null;
if (Config.ALT_GAME_CREATION)
{
maker = _activeMakers.get(player);
}
if (maker == null)
{
final RecipeBookItemList response = new RecipeBookItemList(isDwarvenCraft, player.getMaxMp());
response.addRecipes(isDwarvenCraft ? player.getDwarvenRecipeBook() : player.getCommonRecipeBook());
player.sendPacket(response);
return;
}
player.sendPacket(new SystemMessage(SystemMessageId.YOU_MAY_NOT_ALTER_YOUR_RECIPE_BOOK_WHILE_ENGAGED_IN_MANUFACTURING));
}
public synchronized void requestMakeItemAbort(PlayerInstance player)
{
_activeMakers.remove(player); // TODO: anything else here?
}
public synchronized void requestManufactureItem(PlayerInstance manufacturer, int recipeListId, PlayerInstance player)
{
final RecipeList recipeList = getValidRecipeList(player, recipeListId);
if (recipeList == null)
{
return;
}
final List<RecipeList> dwarfRecipes = Arrays.asList(manufacturer.getDwarvenRecipeBook());
final List<RecipeList> commonRecipes = Arrays.asList(manufacturer.getCommonRecipeBook());
if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList))
{
Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName() + " sent a false recipe id.", Config.DEFAULT_PUNISH);
return;
}
RecipeItemMaker maker;
if (Config.ALT_GAME_CREATION && ((maker = _activeMakers.get(manufacturer)) != null)) // check if busy
{
player.sendMessage("Manufacturer is busy, please try later.");
return;
}
maker = new RecipeItemMaker(manufacturer, recipeList, player);
if (maker._isValid)
{
if (Config.ALT_GAME_CREATION)
{
_activeMakers.put(manufacturer, maker);
ThreadPool.schedule(maker, 100);
}
else
{
maker.run();
}
}
}
public synchronized void requestMakeItem(PlayerInstance player, int recipeListId)
{
if (player.isInDuel())
{
player.sendPacket(SystemMessageId.WHILE_YOU_ARE_ENGAGED_IN_COMBAT_YOU_CANNOT_OPERATE_A_PRIVATE_STORE_OR_PRIVATE_WORKSHOP);
return;
}
final RecipeList recipeList = getValidRecipeList(player, recipeListId);
if (recipeList == null)
{
return;
}
final List<RecipeList> dwarfRecipes = Arrays.asList(player.getDwarvenRecipeBook());
final List<RecipeList> commonRecipes = Arrays.asList(player.getCommonRecipeBook());
if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList))
{
Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName() + " sent a false recipe id.", Config.DEFAULT_PUNISH);
return;
}
RecipeItemMaker maker;
// check if already busy (possible in alt mode only)
if (Config.ALT_GAME_CREATION && ((maker = _activeMakers.get(player)) != null))
{
player.sendMessage("You are busy creating " + ItemTable.getInstance().getTemplate(recipeList.getItemId()).getName());
return;
}
maker = new RecipeItemMaker(player, recipeList, player);
if (maker._isValid)
{
if (Config.ALT_GAME_CREATION)
{
_activeMakers.put(player, maker);
ThreadPool.schedule(maker, 100);
}
else
{
maker.run();
}
}
}
private class RecipeItemMaker implements Runnable
{
protected boolean _isValid;
protected List<TempItem> _items = null;
protected final RecipeList _recipeList;
protected final PlayerInstance _player; // "crafter"
protected final PlayerInstance _target; // "customer"
protected final Skill _skill;
protected final int _skillId;
protected final int _skillLevel;
protected double _creationPasses;
protected double _manaRequired;
protected int _price;
protected int _totalItems;
protected int _delay;
public RecipeItemMaker(PlayerInstance pPlayer, RecipeList pRecipeList, PlayerInstance pTarget)
{
_player = pPlayer;
_target = pTarget;
_recipeList = pRecipeList;
_isValid = false;
_skillId = _recipeList.isDwarvenRecipe() ? Skill.SKILL_CREATE_DWARVEN : Skill.SKILL_CREATE_COMMON;
_skillLevel = _player.getSkillLevel(_skillId);
_skill = _player.getKnownSkill(_skillId);
_player.setCrafting(true);
if (_player.isAlikeDead())
{
_player.sendMessage("Dead people don't craft.");
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_target.isAlikeDead())
{
_target.sendMessage("Dead customers can't use manufacture.");
_target.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_target.isProcessingTransaction())
{
_target.sendMessage("You are busy.");
_target.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_player.isProcessingTransaction())
{
if (_player != _target)
{
_target.sendMessage("Manufacturer " + _player.getName() + " is busy.");
}
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// validate recipe list
if ((_recipeList == null) || (_recipeList.getRecipes().length == 0))
{
_player.sendMessage("No such recipe");
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
_manaRequired = _recipeList.getMpCost();
// validate skill level
if (_recipeList.getLevel() > _skillLevel)
{
_player.sendMessage("Need skill level " + _recipeList.getLevel());
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// check that customer can afford to pay for creation services
if (_player != _target)
{
for (ManufactureItem temp : _player.getCreateList().getList())
{
if (temp.getRecipeId() == _recipeList.getId()) // find recipe for item we want manufactured
{
_price = temp.getCost();
if (_target.getAdena() < _price) // check price
{
_target.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA);
abort();
return;
}
break;
}
}
}
_items = listItems(false);
// make temporary items
if (_items == null)
{
abort();
return;
}
// calculate reference price
for (TempItem i : _items)
{
_totalItems += i.getQuantity();
}
// initial mana check requires MP as written on recipe
if (_player.getCurrentMp() < _manaRequired)
{
_target.sendPacket(SystemMessageId.NOT_ENOUGH_MP);
abort();
return;
}
// determine number of creation passes needed
// can "equip" skillLevel items each pass
_creationPasses = (_totalItems / _skillLevel) + ((_totalItems % _skillLevel) != 0 ? 1 : 0);
if (Config.ALT_GAME_CREATION && (_creationPasses != 0))
{
_manaRequired /= _creationPasses; // checks to validateMp() will only need portion of mp for one pass
}
updateMakeInfo(true);
updateCurMp();
updateCurLoad();
_player.setCrafting(false);
_isValid = true;
}
@Override
public void run()
{
if (!Config.IS_CRAFTING_ENABLED)
{
_target.sendMessage("Item creation is currently disabled.");
abort();
return;
}
if ((_player == null) || (_target == null))
{
LOGGER.warning("Player or target == null (disconnected?), aborting" + _target + _player);
abort();
return;
}
if (!_player.isOnline() || !_target.isOnline())
{
LOGGER.warning("Player or target is not online, aborting " + _target + _player);
abort();
return;
}
if (Config.ALT_GAME_CREATION && (_activeMakers.get(_player) == null))
{
if (_target != _player)
{
_target.sendMessage("Manufacture aborted.");
_player.sendMessage("Manufacture aborted.");
}
else
{
_player.sendMessage("Item creation aborted.");
}
abort();
return;
}
if (Config.ALT_GAME_CREATION && !_items.isEmpty())
{
// check mana
if (!validateMp())
{
return;
}
// use some mp
_player.reduceCurrentMp(_manaRequired);
// update craft window mp bar
updateCurMp();
// grab (equip) some more items with a nice msg to player
grabSomeItems();
// if still not empty, schedule another pass
if (!_items.isEmpty())
{
// divided by RATE_CONSUMABLES_COST to remove craft time increase on higher consumables rates
_delay = (int) ((Config.ALT_GAME_CREATION_SPEED * _player.getMReuseRate(_skill) * GameTimeController.TICKS_PER_SECOND) / Config.RATE_CONSUMABLE_COST) * GameTimeController.MILLIS_IN_TICK;
// Show crafting animation.
_player.broadcastPacket(new MagicSkillUse(_player, _player, _skillId, _skillLevel, _delay, 0));
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPool.schedule(this, 100 + _delay);
}
else
{
// for alt mode, sleep delay msec before finishing
_player.sendPacket(new SetupGauge(0, _delay));
try
{
Thread.sleep(_delay);
}
catch (Exception e)
{
// Ignore.
}
finally
{
finishCrafting();
}
}
}
// for old craft mode just finish
else
{
finishCrafting();
}
}
private void finishCrafting()
{
if (!Config.ALT_GAME_CREATION)
{
_player.reduceCurrentMp(_manaRequired);
}
// first take adena for manufacture
if ((_target != _player) && (_price > 0)) // customer must pay for services
{
// attempt to pay for item
final ItemInstance adenatransfer = _target.transferItem("PayManufacture", _target.getInventory().getAdenaInstance().getObjectId(), _price, _player.getInventory(), _player);
if (adenatransfer == null)
{
_target.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA);
abort();
return;
}
}
_items = listItems(true);
// this line actually takes materials from inventory
if (_items == null)
{
// handle possible cheaters here
// (they click craft then try to get rid of items in order to get free craft)
}
else if (Rnd.get(100) < _recipeList.getSuccessRate())
{
rewardPlayer(); // and immediately puts created item in its place
updateMakeInfo(true);
}
else
{
_player.sendMessage("Item(s) failed to create");
if (_target != _player)
{
_target.sendMessage("Item(s) failed to create");
}
updateMakeInfo(false);
}
// update load and mana bar of craft window
updateCurMp();
updateCurLoad();
_activeMakers.remove(_player);
_player.setCrafting(false);
_target.sendPacket(new ItemList(_target, false));
}
private void updateMakeInfo(boolean success)
{
if (_target == _player)
{
_target.sendPacket(new RecipeItemMakeInfo(_recipeList.getId(), _target, success));
}
else
{
_target.sendPacket(new RecipeShopItemInfo(_player, _recipeList.getId()));
}
}
private void updateCurLoad()
{
final StatusUpdate su = new StatusUpdate(_target.getObjectId());
su.addAttribute(StatusUpdate.CUR_LOAD, _target.getCurrentLoad());
_target.sendPacket(su);
}
private void updateCurMp()
{
final StatusUpdate su = new StatusUpdate(_target.getObjectId());
su.addAttribute(StatusUpdate.CUR_MP, (int) _target.getCurrentMp());
_target.sendPacket(su);
}
private void grabSomeItems()
{
int numItems = _skillLevel;
while ((numItems > 0) && !_items.isEmpty())
{
final TempItem item = _items.get(0);
int count = item.getQuantity();
if (count >= numItems)
{
count = numItems;
}
item.setQuantity(item.getQuantity() - count);
if (item.getQuantity() <= 0)
{
_items.remove(0);
}
else
{
_items.set(0, item);
}
numItems -= count;
if (_target == _player)
{
// you equipped ...
final SystemMessage sm = new SystemMessage(SystemMessageId.EQUIPPED_S1_S2);
sm.addNumber(count);
sm.addItemName(item.getItemId());
_player.sendPacket(sm);
}
else
{
_target.sendMessage("Manufacturer " + _player.getName() + " used " + count + " " + item.getItemName());
}
}
}
private boolean validateMp()
{
if (_player.getCurrentMp() < _manaRequired)
{
// rest (wait for MP)
if (Config.ALT_GAME_CREATION)
{
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPool.schedule(this, 100 + _delay);
}
// no rest - report no mana
else
{
_target.sendPacket(SystemMessageId.NOT_ENOUGH_MP);
abort();
}
return false;
}
return true;
}
private List<TempItem> listItems(boolean remove)
{
final RecipeInstance[] recipes = _recipeList.getRecipes();
final Inventory inv = _target.getInventory();
final List<TempItem> materials = new ArrayList<>();
for (RecipeInstance recipe : recipes)
{
final int quantity = _recipeList.isConsumable() ? (int) (recipe.getQuantity() * Config.RATE_CONSUMABLE_COST) : recipe.getQuantity();
if (quantity > 0)
{
final ItemInstance item = inv.getItemByItemId(recipe.getItemId());
// check materials
if ((item == null) || (item.getCount() < quantity))
{
_target.sendMessage("You dont have the right elements for making this item" + (_recipeList.isConsumable() && (Config.RATE_CONSUMABLE_COST != 1) ? ".\nDue to server rates you need " + Config.RATE_CONSUMABLE_COST + "x more material than listed in recipe" : ""));
abort();
return null;
}
// make new temporary object, just for counting puroses
final TempItem temp = new TempItem(item, quantity);
materials.add(temp);
}
}
if (remove)
{
for (TempItem tmp : materials)
{
inv.destroyItemByItemId("Manufacture", tmp.getItemId(), tmp.getQuantity(), _target, _player);
}
}
return materials;
}
private void abort()
{
updateMakeInfo(false);
_player.setCrafting(false);
_activeMakers.remove(_player);
}
/**
* FIXME: This class should be in some other file, but I don't know where Class explanation: For item counting or checking purposes. When you don't want to modify inventory class contains itemId, quantity, ownerId, referencePrice, but not objectId
*/
private class TempItem
{
// no object id stored, this will be only "list" of items with it's owner
private final int _itemId;
private int _quantity;
private final String _itemName;
/**
* @param item
* @param quantity of that item
*/
public TempItem(ItemInstance item, int quantity)
{
super();
_itemId = item.getItemId();
_quantity = quantity;
_itemName = item.getItem().getName();
}
/**
* @return Returns the quantity.
*/
public int getQuantity()
{
return _quantity;
}
/**
* @param quantity The quantity to set.
*/
public void setQuantity(int quantity)
{
_quantity = quantity;
}
/**
* @return Returns the itemId.
*/
public int getItemId()
{
return _itemId;
}
/**
* @return Returns the itemName.
*/
public String getItemName()
{
return _itemName;
}
}
private void rewardPlayer()
{
final int itemId = _recipeList.getItemId();
final int itemCount = _recipeList.getCount();
final ItemInstance createdItem = _target.getInventory().addItem("Manufacture", itemId, itemCount, _target, _player);
// inform customer of earned item
SystemMessage sm = null;
if (itemCount > 1)
{
sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S2_S1_S);
sm.addItemName(itemId);
sm.addNumber(itemCount);
_target.sendPacket(sm);
}
else
{
sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1);
sm.addItemName(itemId);
_target.sendPacket(sm);
}
if (_target != _player)
{
// inform manufacturer of earned profit
sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1_ADENA);
sm.addNumber(_price);
_player.sendPacket(sm);
}
if (Config.ALT_GAME_CREATION)
{
final int recipeLevel = _recipeList.getLevel();
int exp = createdItem.getReferencePrice() * itemCount;
// one variation
// exp -= materialsRefPrice;
// mat. ref. price is not accurate so other method is better
if (exp < 0)
{
exp = 0;
}
// another variation
exp /= recipeLevel;
for (int i = _skillLevel; i > recipeLevel; i--)
{
exp /= 4;
}
final int sp = exp / 10;
// Added multiplication of Creation speed with XP/SP gain
// slower crafting -> more XP, faster crafting -> less XP
// you can use ALT_GAME_CREATION_XP_RATE/SP to
// modify XP/SP gained (default = 1)
_player.addExpAndSp((int) _player.calcStat(Stat.EXPSP_RATE, exp * Config.ALT_GAME_CREATION_XP_RATE * Config.ALT_GAME_CREATION_SPEED, null, null), (int) _player.calcStat(Stat.EXPSP_RATE, sp * Config.ALT_GAME_CREATION_SP_RATE * Config.ALT_GAME_CREATION_SPEED, null, null));
}
updateMakeInfo(true); // success
}
}
private RecipeList getValidRecipeList(PlayerInstance player, int id)
{
final RecipeList recipeList = RecipeData.getInstance().getRecipe(id);
if ((recipeList == null) || (recipeList.getRecipes().length == 0))
{
player.sendMessage("No recipe for: " + id);
player.setCrafting(false);
return null;
}
return recipeList;
}
}

View File

@@ -0,0 +1,555 @@
/*
* 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.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.datatables.OfflineTradeTable;
import org.l2jmobius.gameserver.datatables.SchemeBufferTable;
import org.l2jmobius.gameserver.instancemanager.CastleManorManager;
import org.l2jmobius.gameserver.instancemanager.CursedWeaponsManager;
import org.l2jmobius.gameserver.instancemanager.FishingChampionshipManager;
import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager;
import org.l2jmobius.gameserver.instancemanager.GrandBossManager;
import org.l2jmobius.gameserver.instancemanager.ItemsOnGroundManager;
import org.l2jmobius.gameserver.instancemanager.QuestManager;
import org.l2jmobius.gameserver.instancemanager.RaidBossSpawnManager;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.entity.Announcements;
import org.l2jmobius.gameserver.model.entity.olympiad.Olympiad;
import org.l2jmobius.gameserver.model.entity.sevensigns.SevenSigns;
import org.l2jmobius.gameserver.model.entity.sevensigns.SevenSignsFestival;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.gameserverpackets.ServerStatus;
import org.l2jmobius.gameserver.network.serverpackets.ServerClose;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
/**
* 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.6 $ $Date: 2009/05/12 19:45:09 $
*/
public class Shutdown extends Thread
{
public enum ShutdownModeType1
{
SIGTERM("Terminating"),
SHUTDOWN("Shutting down"),
RESTART("Restarting"),
ABORT("Aborting");
private final String _modeText;
ShutdownModeType1(String modeText)
{
_modeText = modeText;
}
public String getText()
{
return _modeText;
}
}
protected static final Logger LOGGER = Logger.getLogger(Shutdown.class.getName());
private static Shutdown _counterInstance = null;
private int _secondsShut;
private int _shutdownMode;
private boolean _shutdownStarted;
public static final int SIGTERM = 0;
public static final int GM_SHUTDOWN = 1;
public static final int GM_RESTART = 2;
public static final int ABORT = 3;
private static final String[] MODE_TEXT =
{
"SIGTERM",
"shutting down",
"restarting",
"aborting"
};
/**
* Default constructor is only used internal to create the shutdown-hook instance
*/
public Shutdown()
{
_secondsShut = -1;
_shutdownMode = SIGTERM;
_shutdownStarted = false;
}
/**
* This creates a count down instance of Shutdown.
* @param seconds how many seconds until shutdown
* @param restart true is the server shall restart after shutdown
*/
private Shutdown(int seconds, boolean restart)
{
_secondsShut = Math.max(0, seconds);
if (restart)
{
_shutdownMode = GM_RESTART;
}
else
{
_shutdownMode = GM_SHUTDOWN;
}
_shutdownStarted = false;
}
public boolean isShutdownStarted()
{
boolean output = _shutdownStarted;
// if a counter is started, the value of shutdownstarted is of counterinstance
if (_counterInstance != null)
{
output = _counterInstance._shutdownStarted;
}
return output;
}
/**
* 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. after this thread ends, the server will completely exit if this is not the thread of getInstance, then this is a
* countdown thread. 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())
{
closeServer();
}
else
{
// GM shutdown: send warnings and then call exit to start shutdown sequence
countdown();
if (_shutdownMode != ABORT)
{
// last point where logging is operational :(
LOGGER.warning("GM shutdown countdown is over. " + MODE_TEXT[_shutdownMode] + " NOW!");
closeServer();
}
}
}
/**
* 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)
{
final Announcements announcements = Announcements.getInstance();
LOGGER.warning((player != null ? "GM: " + player.getName() + "(" + player.getObjectId() + ")" : "Server") + " issued shutdown command. " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!");
if (restart)
{
_shutdownMode = GM_RESTART;
}
else
{
_shutdownMode = GM_SHUTDOWN;
}
if (_shutdownMode > 0)
{
announcements.announceToAll("Server is " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!");
announcements.announceToAll("Please exit game now!!");
}
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();
}
public int getCountdown()
{
return _secondsShut;
}
/**
* This function aborts a running countdown
* @param player GM who issued the abort command
*/
public void abort(PlayerInstance player)
{
final Announcements announcements = Announcements.getInstance();
LOGGER.warning((player != null ? "GM: " + player.getName() + "(" + player.getObjectId() + ")" : "Server") + " issued shutdown ABORT. " + MODE_TEXT[_shutdownMode] + " has been stopped!");
announcements.announceToAll("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!");
if (_counterInstance != null)
{
_counterInstance._abort();
}
}
/**
* 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
*/
/**
* 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)
{
int seconds;
int minutes;
int hours;
seconds = _secondsShut;
minutes = seconds / 60;
hours = seconds / 3600;
// announce only every minute after 10 minutes left and every second after 20 seconds
if (((seconds <= 20) || (seconds == (minutes * 10))) && (seconds <= 600) && (hours <= 1))
{
final SystemMessage sm = new SystemMessage(SystemMessageId.THE_SERVER_WILL_BE_COMING_DOWN_IN_S1_SECOND_S_PLEASE_FIND_A_SAFE_PLACE_TO_LOG_OUT);
sm.addString(Integer.toString(seconds));
Announcements.getInstance().announceToAll(sm);
}
try
{
if (seconds <= 60)
{
LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_DOWN);
}
}
catch (Exception e)
{
// do nothing, we maybe are not connected to LS anymore
}
_secondsShut--;
final int delay = 1000; // milliseconds
Thread.sleep(delay);
if (_shutdownMode == ABORT)
{
break;
}
}
}
catch (InterruptedException e)
{
}
}
private void closeServer()
{
// Save all data and quit this server.
_shutdownStarted = true;
try
{
LoginServerThread.getInstance().interrupt();
}
catch (Throwable t)
{
}
// saveData sends messages to exit players, so shutdown selector after it
saveData();
try
{
GameTimeController.getInstance().stopTimer();
}
catch (Throwable t)
{
}
try
{
// GameServer.getSelectorThread().setDaemon(true);
GameServer.getSelectorThread().shutdown();
}
catch (Throwable t)
{
}
// stop all threadpolls
try
{
ThreadPool.shutdown();
}
catch (Throwable t)
{
}
LOGGER.info("Committing all data, last chance...");
// commit data, last chance
try
{
DatabaseFactory.close();
}
catch (Throwable t)
{
}
LOGGER.info("All database data committed.");
// Backup database.
if (Config.BACKUP_DATABASE)
{
DatabaseBackup.performBackup();
}
LOGGER.info("[STATUS] Server shutdown successfully.");
if (getInstance()._shutdownMode == GM_RESTART)
{
Runtime.getRuntime().halt(2);
}
else
{
Runtime.getRuntime().halt(0);
}
}
/**
* this sends a last byebye, disconnects all players and saves data
*/
private synchronized void saveData()
{
final Announcements _an = Announcements.getInstance();
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;
}
}
try
{
_an.announceToAll("Server is " + MODE_TEXT[_shutdownMode] + " NOW!");
}
catch (Throwable t)
{
}
try
{
if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS)
{
OfflineTradeTable.storeOffliners();
}
}
catch (Throwable t)
{
LOGGER.warning("Error saving offline shops. " + t);
}
// Disconnect all the players from the server
disconnectAllCharacters();
// Save players data!
saveAllPlayers();
// Seven Signs data is now saved along with Festival data.
if (!SevenSigns.getInstance().isSealValidationPeriod())
{
SevenSignsFestival.getInstance().saveFestivalData(false);
}
// Save Seven Signs data before closing. :)
SevenSigns.getInstance().saveSevenSignsData(null, true);
LOGGER.info("SevenSigns: All info saved!!");
// Save all raidboss status
RaidBossSpawnManager.getInstance().cleanUp();
LOGGER.info("RaidBossSpawnManager: All raidboss info saved!!");
// Save all Grandboss status
GrandBossManager.getInstance().cleanUp();
LOGGER.info("GrandBossManager: All Grand Boss info saved!!");
// Save data CountStore
TradeController.getInstance().dataCountStore();
LOGGER.info("TradeController: All count Item Saved!!");
// Save Olympiad status
try
{
Olympiad.getInstance().saveOlympiadStatus();
}
catch (Exception e)
{
LOGGER.warning("Problem while saving Olympiad: " + e.getMessage());
}
LOGGER.info("Olympiad System: Data saved!!");
// Save Cursed Weapons data before closing.
CursedWeaponsManager.getInstance().saveData();
// Save all manor data
CastleManorManager.getInstance().save();
// Save Fishing tournament data
FishingChampionshipManager.getInstance().shutdown();
LOGGER.info("Fishing Championship data have been saved!!");
// Schemes save.
SchemeBufferTable.getInstance().saveSchemes();
LOGGER.info("BufferTable data has been saved!!");
// Save all global (non-player specific) Quest data that needs to persist after reboot
if (!Config.ALT_DEV_NO_QUESTS)
{
QuestManager.getInstance().save();
}
// Save all global variables data
GlobalVariablesManager.getInstance().storeMe();
LOGGER.info("Global Variables Manager: Variables have been saved!!");
// Save items on ground before closing
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().saveInDb();
ItemsOnGroundManager.getInstance().cleanUp();
LOGGER.info("ItemsOnGroundManager: All items on ground saved!!");
}
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
// never happens :p
}
}
private void saveAllPlayers()
{
LOGGER.info("Saving all players data...");
for (PlayerInstance player : World.getInstance().getAllPlayers())
{
if (player == null)
{
continue;
}
// Logout Character
try
{
// Save player status
player.store();
}
catch (Throwable t)
{
}
}
}
/**
* this disconnects all clients from the server
*/
private void disconnectAllCharacters()
{
LOGGER.info("Disconnecting all players from the Server...");
for (PlayerInstance player : World.getInstance().getAllPlayers())
{
if (player == null)
{
continue;
}
try
{
// Player Disconnect
if (player.getClient() != null)
{
player.getClient().sendPacket(ServerClose.STATIC_PACKET);
player.getClient().close(0);
player.getClient().setPlayer(null);
player.setClient(null);
}
}
catch (Throwable t)
{
}
}
}
/**
* 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.
* @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,522 @@
/*
* 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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.datatables.ItemTable;
import org.l2jmobius.gameserver.model.StoreTradeList;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
/**
* @version $Revision: 1.5.4.13 $ $Date: 2005/04/06 16:13:38 $
*/
public class TradeController
{
private static final Logger LOGGER = Logger.getLogger(TradeController.class.getName());
private int _nextListId;
private final Map<Integer, StoreTradeList> _lists;
private final Map<Integer, StoreTradeList> _listsTaskItem;
/** Task launching the function for restore count of Item (Clan Hall) */
public class RestoreCount implements Runnable
{
private final int _timer;
public RestoreCount(int time)
{
_timer = time;
}
@Override
public void run()
{
try
{
restoreCount(_timer);
dataTimerSave(_timer);
ThreadPool.schedule(new RestoreCount(_timer), _timer * 60 * 60 * 1000);
}
catch (Throwable t)
{
}
}
}
private TradeController()
{
boolean limitedItem = false;
_lists = new HashMap<>();
_listsTaskItem = new HashMap<>();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement1 = con.prepareStatement("SELECT * FROM merchant_shopids");
final ResultSet rset1 = statement1.executeQuery();
while (rset1.next())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM merchant_buylists WHERE shop_id=? ORDER BY `order` ASC");
statement.setString(1, String.valueOf(rset1.getInt("shop_id")));
final ResultSet rset = statement.executeQuery();
if (rset.next())
{
limitedItem = false;
final StoreTradeList buy1 = new StoreTradeList(rset1.getInt("shop_id"));
int itemId = rset.getInt("item_id");
int price = rset.getInt("price");
int count = rset.getInt("count");
int currentCount = rset.getInt("currentCount");
int time = rset.getInt("time");
final ItemInstance item = ItemTable.getInstance().createDummyItem(itemId);
if (item == null)
{
rset.close();
statement.close();
continue;
}
if (count > -1)
{
item.setCountDecrease(true);
limitedItem = true;
}
if (!rset1.getString("npc_id").equals("gm") && (price < (item.getReferencePrice() / 2)))
{
LOGGER.warning("TradeList " + buy1.getListId() + " itemId " + itemId + " has an ADENA sell price lower then reference price.. Automatically Updating it..");
price = item.getReferencePrice();
}
item.setPriceToSell(price);
item.setTime(time);
item.setInitCount(count);
if (currentCount > -1)
{
item.setCount(currentCount);
}
else
{
item.setCount(count);
}
buy1.addItem(item);
buy1.setNpcId(rset1.getString("npc_id"));
try
{
while (rset.next())
{
itemId = rset.getInt("item_id");
price = rset.getInt("price");
count = rset.getInt("count");
time = rset.getInt("time");
currentCount = rset.getInt("currentCount");
final ItemInstance item2 = ItemTable.getInstance().createDummyItem(itemId);
if (item2 == null)
{
continue;
}
if (count > -1)
{
item2.setCountDecrease(true);
limitedItem = true;
}
if (!rset1.getString("npc_id").equals("gm") && (price < (item2.getReferencePrice() / 2)))
{
LOGGER.warning("TradeList " + buy1.getListId() + " itemId " + itemId + " has an ADENA sell price lower then reference price.. Automatically Updating it..");
price = item2.getReferencePrice();
}
item2.setPriceToSell(price);
item2.setTime(time);
item2.setInitCount(count);
if (currentCount > -1)
{
item2.setCount(currentCount);
}
else
{
item2.setCount(count);
}
buy1.addItem(item2);
}
}
catch (Exception e)
{
LOGGER.warning("TradeController: Problem with buylist " + buy1.getListId() + " item " + itemId);
}
if (limitedItem)
{
_listsTaskItem.put(buy1.getListId(), buy1);
}
else
{
_lists.put(buy1.getListId(), buy1);
}
_nextListId = Math.max(_nextListId, buy1.getListId() + 1);
}
rset.close();
statement.close();
}
rset1.close();
statement1.close();
LOGGER.info("TradeController: Loaded " + _lists.size() + " Buylists.");
LOGGER.info("TradeController: Loaded " + _listsTaskItem.size() + " Limited Buylists.");
/*
* Restore Task for reinitialize count of buy item
*/
try
{
int time = 0;
long savetimer = 0;
final long currentMillis = System.currentTimeMillis();
final PreparedStatement statement2 = con.prepareStatement("SELECT DISTINCT time, savetimer FROM merchant_buylists WHERE time <> 0 ORDER BY time");
final ResultSet rset2 = statement2.executeQuery();
while (rset2.next())
{
time = rset2.getInt("time");
savetimer = rset2.getLong("savetimer");
if ((savetimer - currentMillis) > 0)
{
ThreadPool.schedule(new RestoreCount(time), savetimer - System.currentTimeMillis());
}
else
{
ThreadPool.schedule(new RestoreCount(time), 0);
}
}
rset2.close();
statement2.close();
}
catch (Exception e)
{
LOGGER.warning("TradeController: Could not restore Timer for Item count. " + e.getMessage());
}
}
catch (Exception e)
{
LOGGER.warning("TradeController: Buylists could not be initialized." + e.getMessage());
}
/*
* If enabled, initialize the custom buylist.
*/
if (Config.CUSTOM_MERCHANT_TABLES) // Custom merchant tables.
{
try (Connection con = DatabaseFactory.getConnection())
{
final int initialSize = _lists.size();
final PreparedStatement statement1 = con.prepareStatement("SELECT * FROM custom_merchant_shopids");
final ResultSet rset1 = statement1.executeQuery();
while (rset1.next())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM custom_merchant_buylists WHERE shop_id=? ORDER BY `order` ASC");
statement.setString(1, String.valueOf(rset1.getInt("shop_id")));
final ResultSet rset = statement.executeQuery();
if (rset.next())
{
limitedItem = false;
final StoreTradeList buy1 = new StoreTradeList(rset1.getInt("shop_id"));
int itemId = rset.getInt("item_id");
int price = rset.getInt("price");
int count = rset.getInt("count");
int currentCount = rset.getInt("currentCount");
int time = rset.getInt("time");
final ItemInstance item = ItemTable.getInstance().createDummyItem(itemId);
if (item == null)
{
rset.close();
statement.close();
continue;
}
if (count > -1)
{
item.setCountDecrease(true);
limitedItem = true;
}
if (!rset1.getString("npc_id").equals("gm") && (price < (item.getReferencePrice() / 2)))
{
LOGGER.warning("TradeList " + buy1.getListId() + " itemId " + itemId + " has an ADENA sell price lower then reference price.. Automatically Updating it..");
price = item.getReferencePrice();
}
item.setPriceToSell(price);
item.setTime(time);
item.setInitCount(count);
if (currentCount > -1)
{
item.setCount(currentCount);
}
else
{
item.setCount(count);
}
buy1.addItem(item);
buy1.setNpcId(rset1.getString("npc_id"));
try
{
while (rset.next())
{
itemId = rset.getInt("item_id");
price = rset.getInt("price");
count = rset.getInt("count");
time = rset.getInt("time");
currentCount = rset.getInt("currentCount");
final ItemInstance item2 = ItemTable.getInstance().createDummyItem(itemId);
if (item2 == null)
{
continue;
}
if (count > -1)
{
item2.setCountDecrease(true);
limitedItem = true;
}
if (!rset1.getString("npc_id").equals("gm") && (price < (item2.getReferencePrice() / 2)))
{
LOGGER.warning("TradeList " + buy1.getListId() + " itemId " + itemId + " has an ADENA sell price lower then reference price.. Automatically Updating it..");
price = item2.getReferencePrice();
}
item2.setPriceToSell(price);
item2.setTime(time);
item2.setInitCount(count);
if (currentCount > -1)
{
item2.setCount(currentCount);
}
else
{
item2.setCount(count);
}
buy1.addItem(item2);
}
}
catch (Exception e)
{
LOGGER.warning("TradeController: Problem with buylist " + buy1.getListId() + " item " + itemId);
}
if (limitedItem)
{
_listsTaskItem.put(buy1.getListId(), buy1);
}
else
{
_lists.put(buy1.getListId(), buy1);
}
_nextListId = Math.max(_nextListId, buy1.getListId() + 1);
}
rset.close();
statement.close();
}
rset1.close();
statement1.close();
LOGGER.info("TradeController: Loaded " + (_lists.size() - initialSize) + " Custom Buylists.");
/**
* Restore Task for reinitialize count of buy item
*/
try
{
int time = 0;
long savetimer = 0;
final long currentMillis = System.currentTimeMillis();
final PreparedStatement statement2 = con.prepareStatement("SELECT DISTINCT time, savetimer FROM custom_merchant_buylists WHERE time <> 0 ORDER BY time");
final ResultSet rset2 = statement2.executeQuery();
while (rset2.next())
{
time = rset2.getInt("time");
savetimer = rset2.getLong("savetimer");
if ((savetimer - currentMillis) > 0)
{
ThreadPool.schedule(new RestoreCount(time), savetimer - System.currentTimeMillis());
}
else
{
ThreadPool.schedule(new RestoreCount(time), 0);
}
}
rset2.close();
statement2.close();
}
catch (Exception e)
{
LOGGER.warning("TradeController: Could not restore Timer for Item count. " + e.getMessage());
}
}
catch (Exception e)
{
LOGGER.warning("TradeController: Buylists could not be initialized. " + e.getMessage());
}
}
}
public StoreTradeList getBuyList(int listId)
{
if (_lists.get(listId) != null)
{
return _lists.get(listId);
}
return _listsTaskItem.get(listId);
}
public List<StoreTradeList> getBuyListByNpcId(int npcId)
{
final List<StoreTradeList> lists = new ArrayList<>();
for (StoreTradeList list : _lists.values())
{
if (list.getNpcId().startsWith("gm"))
{
continue;
}
if (npcId == Integer.parseInt(list.getNpcId()))
{
lists.add(list);
}
}
for (StoreTradeList list : _listsTaskItem.values())
{
if (list.getNpcId().startsWith("gm"))
{
continue;
}
if (npcId == Integer.parseInt(list.getNpcId()))
{
lists.add(list);
}
}
return lists;
}
protected void restoreCount(int time)
{
if (_listsTaskItem == null)
{
return;
}
for (StoreTradeList list : _listsTaskItem.values())
{
list.restoreCount(time);
}
}
protected void dataTimerSave(int time)
{
final long timerSave = System.currentTimeMillis() + (time * 60 * 60 * 1000);
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("UPDATE merchant_buylists SET savetimer =? WHERE time =?");
statement.setLong(1, timerSave);
statement.setInt(2, time);
statement.executeUpdate();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("TradeController: Could not update Timer save in Buylist");
}
}
public void dataCountStore()
{
int listId;
if (_listsTaskItem == null)
{
return;
}
PreparedStatement statement;
try (Connection con = DatabaseFactory.getConnection())
{
for (StoreTradeList list : _listsTaskItem.values())
{
if (list == null)
{
continue;
}
listId = list.getListId();
for (ItemInstance Item : list.getItems())
{
if (Item.getCount() < Item.getInitCount()) // needed?
{
statement = con.prepareStatement("UPDATE merchant_buylists SET currentCount=? WHERE item_id=? AND shop_id=?");
statement.setInt(1, Item.getCount());
statement.setInt(2, Item.getItemId());
statement.setInt(3, listId);
statement.executeUpdate();
statement.close();
}
}
}
}
catch (Exception e)
{
LOGGER.warning("TradeController: Could not store Count Item");
}
}
public synchronized int getNextId()
{
return _nextListId++;
}
public static TradeController getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final TradeController INSTANCE = new TradeController();
}
}

View File

@@ -0,0 +1,840 @@
/*
* 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.gameserver.GameTimeController;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.Skill;
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.Playable;
import org.l2jmobius.gameserver.model.actor.Summon;
import org.l2jmobius.gameserver.model.actor.instance.NpcInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
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.CharMoveToLocation;
import org.l2jmobius.gameserver.network.serverpackets.Die;
import org.l2jmobius.gameserver.network.serverpackets.MoveToLocationInVehicle;
import org.l2jmobius.gameserver.network.serverpackets.MoveToPawn;
import org.l2jmobius.gameserver.network.serverpackets.StopMove;
import org.l2jmobius.gameserver.network.serverpackets.StopRotation;
import org.l2jmobius.gameserver.taskmanager.AttackStanceTaskManager;
import org.l2jmobius.gameserver.taskmanager.CreatureFollowTaskManager;
/**
* Mother class of all objects AI in the world.<br>
* <br>
* AbastractAI:<br>
* <li>CreatureAI</li>
*/
abstract class AbstractAI implements Ctrl
{
/** The creature that this AI manages */
protected final Creature _actor;
/** An accessor for private methods of the actor */
protected final Creature.AIAccessor _accessor;
/** Current long-term intention */
private CtrlIntention _intention = AI_INTENTION_IDLE;
/** Current long-term intention parameter */
private Object _intentionArg0 = null;
/** Current long-term intention parameter */
private Object _intentionArg1 = null;
/** Flags about client's state, in order to know which messages to send */
protected boolean _clientMoving;
/** Flags about client's state, in order to know which messages to send */
protected 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;
private Creature _castTarget;
private Creature _attackTarget;
private Creature _followTarget;
/** Diferent internal state flags */
private int _moveToPawnTimeout;
/**
* Constructor of AbstractAI.
* @param accessor The AI accessor of the Creature
*/
protected AbstractAI(Creature.AIAccessor accessor)
{
_accessor = accessor;
// Get the Creature managed by this Accessor AI
_actor = accessor.getActor();
}
/**
* Return the Creature managed by this Accessor AI.
*/
@Override
public Creature getActor()
{
return _actor;
}
/**
* Set the Intention of this AbstractAI.<br>
* <font color=#FF0000><b><u>Caution</u>: This method is USED by AI classes</b></font><br>
* <br>
* <b><u>Overriden in</u>:</b><br>
* <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 arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
public synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
_intention = intention;
_intentionArg0 = arg0;
_intentionArg1 = arg1;
}
/**
* 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 arg0 The first parameter of the Intention (optional target)
*/
@Override
public void setIntention(CtrlIntention intention, Object arg0)
{
setIntention(intention, arg0, 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 arg0 The first parameter of the Intention (optional target)
* @param arg1 The second parameter of the Intention (optional target)
*/
@Override
public void setIntention(CtrlIntention intention, Object arg0, Object arg1)
{
if (!_actor.isVisible() || !_actor.hasAI())
{
return;
}
// 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) arg0);
break;
}
case AI_INTENTION_CAST:
{
onIntentionCast((Skill) arg0, (WorldObject) arg1);
break;
}
case AI_INTENTION_MOVE_TO:
{
onIntentionMoveTo((Location) arg0);
break;
}
case AI_INTENTION_MOVE_TO_IN_A_BOAT:
{
onIntentionMoveToInABoat((Location) arg0, (Location) arg1);
break;
}
case AI_INTENTION_FOLLOW:
{
onIntentionFollow((Creature) arg0);
break;
}
case AI_INTENTION_PICK_UP:
{
onIntentionPickUp((WorldObject) arg0);
break;
}
case AI_INTENTION_INTERACT:
{
onIntentionInteract((WorldObject) arg0);
break;
}
}
}
/**
* 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 periode)</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.<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 periode)</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.<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 periode)</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.isVisible() || !_actor.hasAI() || ((_actor instanceof PlayerInstance) && !((PlayerInstance) _actor).isOnline()) || ((_actor instanceof PlayerInstance) && ((PlayerInstance) _actor).isInOfflineMode()))
{
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_STUNNED:
{
onEvtStunned((Creature) arg0);
break;
}
case EVT_SLEEPING:
{
onEvtSleeping((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_READY_TO_ACT:
{
onEvtReadyToAct();
break;
}
case EVT_USER_CMD:
{
onEvtUserCmd(arg0, arg1);
break;
}
case EVT_ARRIVED:
{
onEvtArrived();
break;
}
case EVT_ARRIVED_REVALIDATE:
{
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;
}
}
}
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);
protected abstract void onIntentionMoveTo(Location destination);
protected abstract void onIntentionMoveToInABoat(Location destination, Location origin);
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 onEvtStunned(Creature attacker);
protected abstract void onEvtSleeping(Creature attacker);
protected abstract void onEvtRooted(Creature attacker);
protected abstract void onEvtConfused(Creature attacker);
protected abstract void onEvtMuted(Creature attacker);
protected abstract void onEvtReadyToAct();
protected abstract void onEvtUserCmd(Object arg0, Object arg1);
protected abstract void onEvtArrived();
protected abstract void onEvtArrivedRevalidate();
protected abstract void onEvtArrivedBlocked(Location location);
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.<br>
* <font color=#FF0000><b><u>Caution</u>: Low level function, used by AI subclasses</b></font>
*/
protected void clientActionFailed()
{
if (_actor instanceof PlayerInstance)
{
_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 offsetValue
*/
public void moveToPawn(WorldObject pawn, int offsetValue)
{
// Check if actor can move
if (!_actor.isMovementDisabled())
{
int offset = offsetValue;
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.getGameTicks() < _moveToPawnTimeout)
{
return;
}
sendPacket = false;
}
else if (_actor.isOnGeodataPath())
{
// minimum time to calculate new route is 2 seconds
if (GameTimeController.getGameTicks() < (_moveToPawnTimeout + 10))
{
return;
}
}
}
// Set AI movement data
_clientMoving = true;
_clientMovingToPawnOffset = offset;
setTarget(pawn);
_moveToPawnTimeout = GameTimeController.getGameTicks();
_moveToPawnTimeout += /* 1000 */ 200 / GameTimeController.MILLIS_IN_TICK;
if ((pawn == null) || (_accessor == null))
{
return;
}
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
_accessor.moveTo(pawn.getX(), pawn.getY(), pawn.getZ(), offset);
// Mobius: Solves moving to wrong Z when not using geodata,
// but probably is not accurate and you should use geodata.
// _accessor.moveTo(pawn.getX(), pawn.getY(), _actor.getZ(), offset);
if (!_actor.isMoving())
{
_actor.sendPacket(ActionFailed.STATIC_PACKET);
return;
}
// Send a Server->Client packet MoveToPawn/CharMoveToLocation to the actor and all PlayerInstance in its _knownPlayers
if (pawn instanceof Creature)
{
if (_actor.isOnGeodataPath())
{
_actor.broadcastPacket(new CharMoveToLocation(_actor));
_clientMovingToPawnOffset = 0;
}
else if (sendPacket)
{
_actor.broadcastPacket(new MoveToPawn(_actor, (Creature) pawn, offset));
}
}
else
{
_actor.broadcastPacket(new CharMoveToLocation(_actor));
}
}
else
{
_actor.sendPacket(ActionFailed.STATIC_PACKET);
}
}
/**
* 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
*/
public 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
_accessor.moveTo(x, y, z);
// Send a Server->Client packet CharMoveToLocation to the actor and all PlayerInstance in its _knownPlayers
_actor.broadcastPacket(new CharMoveToLocation(_actor));
}
else
{
_actor.sendPacket(ActionFailed.STATIC_PACKET);
}
}
protected void moveToInABoat(Location destination, Location origin)
{
// Chek if actor can move
if (!_actor.isMovementDisabled())
{
// Send a Server->Client packet CharMoveToLocation to the actor and all PlayerInstance in its _knownPlayers
// CharMoveToLocation msg = new CharMoveToLocation(_actor);
if (((PlayerInstance) _actor).getBoat() != null)
{
_actor.broadcastPacket(new MoveToLocationInVehicle(_actor, destination, origin));
}
}
else
{
_actor.sendPacket(ActionFailed.STATIC_PACKET);
}
}
/**
* 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 pos
*/
protected void clientStopMoving(Location pos)
{
// Stop movement of the Creature
if (_actor.isMoving())
{
_accessor.stopMove(pos);
}
_clientMovingToPawnOffset = 0;
if (_clientMoving || (pos != null))
{
_clientMoving = false;
// Send a Server->Client packet StopMove to the actor and all PlayerInstance in its _knownPlayers
_actor.broadcastPacket(new StopMove(_actor));
if (pos != null)
{
// Send a Server->Client packet StopRotation to the actor and all PlayerInstance in its _knownPlayers
final StopRotation sr = new StopRotation(_actor, pos.getHeading(), 0);
_actor.sendPacket(sr);
_actor.broadcastPacket(sr);
}
}
}
// 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;
}
/**
* Start the actor Auto Attack client side by sending Server->Client packet AutoAttackStart <i>(broadcast)</i>.<br>
* <font color=#FF0000><b><u>Caution</u>: Low level function, used by AI subclasses</b></font>
*/
public void clientStartAutoAttack()
{
if ((((_actor instanceof NpcInstance) && !(_actor instanceof Attackable)) && !(_actor instanceof Playable)))
{
return;
}
if (_actor instanceof Summon)
{
final Summon summon = (Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStartAutoAttack();
}
return;
}
if (!_clientAutoAttacking)
{
if ((_actor instanceof PlayerInstance) && (((PlayerInstance) _actor).getPet() != null))
{
((PlayerInstance) _actor).getPet().broadcastPacket(new AutoAttackStart(((PlayerInstance) _actor).getPet().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>
*/
public void clientStopAutoAttack()
{
if (_actor instanceof Summon)
{
final Summon summon = (Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStopAutoAttack();
}
return;
}
final boolean isAutoAttacking = _clientAutoAttacking;
if (_actor instanceof PlayerInstance)
{
if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor) && isAutoAttacking)
{
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
}
else if (isAutoAttacking)
{
_actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId()));
setAutoAttacking(false);
}
}
/**
* Kill the actor client side by sending Server->Client packet AutoAttackStop, StopMove/StopRotation, Die <i>(broadcast)</i>.<br>
* <font color=#FF0000><b><u>Caution</u>: Low level function, used by AI subclasses</b></font>
*/
protected void clientNotifyDead()
{
// Send a Server->Client packet Die to the actor and all PlayerInstance in its _knownPlayers
_actor.broadcastPacket(new Die(_actor));
// Init AI
setIntention(AI_INTENTION_IDLE);
setTarget(null);
setAttackTarget(null);
setCastTarget(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 (_clientMoving)
{
final Creature follow = getFollowTarget();
if ((_clientMovingToPawnOffset != 0) && (follow != null))
{
// Send a Server->Client packet MoveToPawn to the actor and all PlayerInstance in its _knownPlayers
player.sendPacket(new MoveToPawn(_actor, follow, _clientMovingToPawnOffset));
}
else
{
// Send a Server->Client packet CharMoveToLocation to the actor and all PlayerInstance in its _knownPlayers
player.sendPacket(new CharMoveToLocation(_actor));
}
}
}
/**
* 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)
{
stopFollow();
_followTarget = target;
if (range == -1)
{
CreatureFollowTaskManager.getInstance().addNormalFollow(_actor, range);
}
else
{
CreatureFollowTaskManager.getInstance().addAttackFollow(_actor, range);
}
}
/**
* Stop an AI Follow Task.
*/
public synchronized void stopFollow()
{
CreatureFollowTaskManager.getInstance().remove(_actor);
_followTarget = null;
}
public synchronized Creature getFollowTarget()
{
return _followTarget;
}
protected synchronized WorldObject getTarget()
{
return _target;
}
protected synchronized void setTarget(WorldObject target)
{
_target = target;
}
protected synchronized void setCastTarget(Creature target)
{
_castTarget = target;
}
/**
* @return the current cast target.
*/
public synchronized Creature getCastTarget()
{
return _castTarget;
}
protected synchronized void setAttackTarget(Creature target)
{
_attackTarget = target;
}
/**
* Return current attack target.
*/
@Override
public synchronized Creature getAttackTarget()
{
return _attackTarget;
}
public synchronized boolean isAutoAttacking()
{
return _clientAutoAttacking;
}
public synchronized void setAutoAttacking(boolean isAutoAttacking)
{
_clientAutoAttacking = isAutoAttacking;
}
/**
* @return the _intentionArg0
*/
public synchronized Object getIntentionArg0()
{
return _intentionArg0;
}
/**
* @param intentionArg0 the _intentionArg0 to set
*/
public synchronized void setIntentionArg0(Object intentionArg0)
{
_intentionArg0 = intentionArg0;
}
/**
* @return the _intentionArg1
*/
public synchronized Object getIntentionArg1()
{
return _intentionArg1;
}
/**
* @param intentionArg1 the _intentionArg1 to set
*/
public synchronized void setIntentionArg1(Object intentionArg1)
{
_intentionArg1 = intentionArg1;
}
/**
* Return the current Intention.
*/
@Override
public synchronized CtrlIntention getIntention()
{
return _intention;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,549 @@
/*
* 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.datatables.MobGroupTable;
import org.l2jmobius.gameserver.model.MobGroup;
import org.l2jmobius.gameserver.model.Skill;
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.Creature.AIAccessor;
import org.l2jmobius.gameserver.model.actor.instance.ControllableMobInstance;
import org.l2jmobius.gameserver.model.actor.instance.DoorInstance;
import org.l2jmobius.gameserver.model.actor.instance.FolkInstance;
import org.l2jmobius.gameserver.model.actor.instance.NpcInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.util.Util;
/**
* @author littlecrow AI for controllable mobs
*/
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
public void onEvtThink()
{
if (_isThinking || _actor.isAllSkillsDisabled())
{
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);
}
}
protected void thinkCast()
{
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
{
setAttackTarget(findNextRndTarget());
clientStopMoving(null);
}
if (getAttackTarget() == null)
{
return;
}
((Attackable) _actor).setTarget(getAttackTarget());
if (!_actor.isMuted())
{
// check distant skills
int maxRange = 0;
for (Skill sk : _actor.getAllSkills())
{
if (Util.checkIfInRange(sk.getCastRange(), _actor, getAttackTarget(), true) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
maxRange = Math.max(maxRange, sk.getCastRange());
}
if (!_isNotMoving)
{
moveToPawn(getAttackTarget(), maxRange);
}
}
}
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;
}
_actor.setTarget(target);
// as a response, we put the target in a forced attack mode
final ControllableMobInstance theTarget = (ControllableMobInstance) target;
final ControllableMobAI ctrlAi = (ControllableMobAI) theTarget.getAI();
ctrlAi.forceAttack(_actor);
final Skill[] skills = _actor.getAllSkills();
final double dist2 = _actor.getPlanDistanceSq(target.getX(), target.getY());
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius();
int maxRange = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
maxRange = Math.max(maxRange, castRange);
}
if (!_isNotMoving)
{
moveToPawn(target, range);
}
return;
}
_accessor.doAttack(target);
}
protected void thinkForceAttack()
{
if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead())
{
clientStopMoving(null);
setIntention(AI_INTENTION_ACTIVE);
setAlternateAI(AI_IDLE);
}
_actor.setTarget(getForcedTarget());
final Skill[] skills = _actor.getAllSkills();
final double dist2 = _actor.getPlanDistanceSq(getForcedTarget().getX(), getForcedTarget().getY());
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getForcedTarget().getTemplate().getCollisionRadius();
int maxRange = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
maxRange = Math.max(maxRange, castRange);
}
if (!_isNotMoving)
{
moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange()/* range */);
}
return;
}
_accessor.doAttack(getForcedTarget());
}
protected void thinkAttack()
{
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
{
if (getAttackTarget() != null)
{
// stop hating
final Attackable npc = (Attackable) _actor;
npc.stopHating(getAttackTarget());
}
setIntention(AI_INTENTION_ACTIVE);
}
else
{
// notify aggression
if (((NpcInstance) _actor).getFactionId() != null)
{
for (WorldObject obj : _actor.getKnownList().getKnownObjects().values())
{
if (!(obj instanceof NpcInstance))
{
continue;
}
final NpcInstance npc = (NpcInstance) obj;
final String factionId = ((NpcInstance) _actor).getFactionId();
if (!factionId.equalsIgnoreCase(npc.getFactionId()))
{
continue;
}
if (_actor.isInsideRadius(npc, npc.getFactionRange(), false, true) && (Math.abs(getAttackTarget().getZ() - npc.getZ()) < 200))
{
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
}
}
}
_actor.setTarget(getAttackTarget());
final Skill[] skills = _actor.getAllSkills();
final double dist2 = _actor.getPlanDistanceSq(getAttackTarget().getX(), getAttackTarget().getY());
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius();
int maxRange = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
maxRange = Math.max(maxRange, castRange);
}
moveToPawn(getAttackTarget(), range);
return;
}
// Force mobs to attack anybody if confused.
Creature hated;
if (_actor.isConfused())
{
hated = findNextRndTarget();
}
else
{
hated = getAttackTarget();
}
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE);
return;
}
if (hated != getAttackTarget())
{
setAttackTarget(hated);
}
if (!_actor.isMuted() && (skills.length > 0) && (Rnd.get(5) == 3))
{
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
}
}
_accessor.doAttack(getAttackTarget());
}
}
private void thinkActive()
{
setAttackTarget(findNextRndTarget());
Creature hated;
if (_actor.isConfused())
{
hated = findNextRndTarget();
}
else
{
hated = getAttackTarget();
}
if (hated != null)
{
_actor.setRunning();
setIntention(AI_INTENTION_ATTACK, hated);
}
}
private boolean autoAttackCondition(Creature target)
{
if ((target == null) || !(_actor instanceof Attackable))
{
return false;
}
final Attackable me = (Attackable) _actor;
if ((target instanceof FolkInstance) || (target instanceof DoorInstance))
{
return false;
}
if (target.isAlikeDead() || !me.isInsideRadius(target, me.getAggroRange(), false, false) || (Math.abs(_actor.getZ() - target.getZ()) > 100))
{
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul())
{
return false;
}
// Check if the target is a PlayerInstance and if the target isn't in silent move mode
if ((target instanceof PlayerInstance) && ((PlayerInstance) target).isSilentMoving())
{
return false;
}
if (target instanceof NpcInstance)
{
return false;
}
return me.isAggressive();
}
private Creature findNextRndTarget()
{
final int aggroRange = ((Attackable) _actor).getAggroRange();
final Attackable npc = (Attackable) _actor;
int npcX;
int npcY;
int targetX;
int targetY;
double dy;
double dx;
final double dblAggroRange = aggroRange * aggroRange;
final List<Creature> potentialTarget = new ArrayList<>();
for (WorldObject obj : npc.getKnownList().getKnownObjects().values())
{
if (!(obj instanceof Creature))
{
continue;
}
npcX = npc.getX();
npcY = npc.getY();
targetX = obj.getX();
targetY = obj.getY();
dx = npcX - targetX;
dy = npcY - targetY;
if (((dx * dx) + (dy * dy)) > dblAggroRange)
{
continue;
}
final Creature target = (Creature) obj;
if (autoAttackCondition(target))
{
potentialTarget.add(target);
}
}
if (potentialTarget.isEmpty())
{
return null;
}
// we choose a random target
final int choice = Rnd.get(potentialTarget.size());
return potentialTarget.get(choice);
}
private ControllableMobInstance findNextGroupTarget()
{
return getGroupTarget().getRandomMob();
}
public ControllableMobAI(AIAccessor accessor)
{
super(accessor);
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 synchronized Creature getForcedTarget()
{
return _forcedTarget;
}
private synchronized MobGroup getGroupTarget()
{
return _targetGroup;
}
private synchronized void setForcedTarget(Creature forcedTarget)
{
_forcedTarget = forcedTarget;
}
private synchronized void setGroupTarget(MobGroup targetGroup)
{
_targetGroup = targetGroup;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
/*
* 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. To correctly send messages to client we need it's state. For example, if we've sent 'StartAutoAttack' message, we need to send 'StopAutoAttack' message before any other action. 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). Thus, we need to know the state of client, i.e. which messages we've sent and how the client will show the scene. Close to this task is the task of AI. If a player's character is attacking a mob, his ATTACK may be interrupted by an event,
* that temporary disable attacking. But when the possibility to ATTACK will be enabled, the character must continue the ATTACK. 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. This interface is hiding complexity of server<->client interaction and multiple states of a character. It allows to set a desired, simple "wish" of a character, and the implementation of this interface will take care about the rest. The goal of a character may be like "ATTACK", "random walk"
* and so on. To reach the goal implementation will split it into several small actions, several steps (possibly repeatable). Like "run to target" then "hit it", then if target is not dead - repeat. This flow of simpler steps may be interrupted by incoming events. Like a character's movement was
* disabled (by Root spell, for instance). Depending on character's ability AI may choose to wait, or to use magic ATTACK and so on. Additionally incoming events are compared with client's state of the character, 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, and if client's state for the character is "moving" we send messages to clients to stop the avatar/mob.
*/
public interface Ctrl
{
/**
* @return the character this AI serves
*/
Creature getActor();
/**
* @return the current intention.
*/
CtrlIntention getIntention();
/**
* @return the current attack target.
*/
Creature getAttackTarget();
/**
* Set general state/intention for AI, with optional data
* @param intention
*/
void setIntention(CtrlIntention intention);
void setIntention(CtrlIntention intention, Object arg0);
void setIntention(CtrlIntention intention, Object arg0, Object arg1);
/**
* Event, that notifies about previous step result, or user command, that does not change current general intention
* @param evt
*/
void notifyEvent(CtrlEvent evt);
void notifyEvent(CtrlEvent evt, Object arg0);
void notifyEvent(CtrlEvent evt, Object arg0, Object arg1);
}

View File

@@ -0,0 +1,99 @@
/*
* 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 evenements 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 attack was done on the actor. NPC may start attack in responce, or ignore this event if they already attack someone, or change target and so on.
*/
EVT_ATTACKED,
/** Increase/decrease aggression towards a target, or reduce global aggression if target is null */
EVT_AGGRESSION,
/** Actor is in stun state */
EVT_STUNNED,
/** Actor starts/stops sleeping */
EVT_SLEEPING,
/** Actor is in rooted state (cannot move) */
EVT_ROOTED,
/**
* An event that previous action was completed. The action may be an attempt to physically/magically hit an enemy, or an action that discarded attack attempt has finished.
*/
EVT_READY_TO_ACT,
/**
* User's command, like using a combat magic or changing weapon, etc. The command is not intended to change final goal
*/
EVT_USER_CMD,
/**
* The actor arrived to assigned location, or it's a time to modify movement destination (follow, interact, random move and others intentions).
*/
EVT_ARRIVED,
/**
* The actor arrived to an intermidiate point, and needs 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. For example, the actor was putted into a stun, so it's current attack or movement has to be canceled. But after the stun state expired, the actor may try to attack again. Another usage for CANCEL is a user's 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 randoms directions **/
EVT_AFFRAID,
/** The creature finish casting **/
EVT_FINISH_CASTING,
/** The creature betrayed its master */
EVT_BETRAYED
}

View File

@@ -0,0 +1,56 @@
/*
* 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;
/**
* Enumaration 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 peacefull 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,
/** Move to another location in a boat */
AI_INTENTION_MOVE_TO_IN_A_BOAT
}

View File

@@ -0,0 +1,210 @@
/*
* 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.Skill;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.instance.DoorInstance;
import org.l2jmobius.gameserver.model.actor.instance.FortSiegeGuardInstance;
import org.l2jmobius.gameserver.model.actor.instance.SiegeGuardInstance;
/**
* @author mkizub
*/
public class DoorAI extends CreatureAI
{
public DoorAI(DoorInstance.AIAccessor accessor)
{
super(accessor);
}
// rather stupid AI... well, it's for doors :D
@Override
protected void onIntentionIdle()
{
// null;
}
@Override
protected void onIntentionActive()
{
// null;
}
@Override
protected void onIntentionRest()
{
// null;
}
@Override
protected void onIntentionAttack(Creature target)
{
// null;
}
@Override
protected void onIntentionCast(Skill skill, WorldObject target)
{
// null;
}
@Override
protected void onIntentionMoveTo(Location destination)
{
// null;
}
@Override
protected void onIntentionFollow(Creature target)
{
// null;
}
@Override
protected void onIntentionPickUp(WorldObject item)
{
// null;
}
@Override
protected void onIntentionInteract(WorldObject object)
{
// null;
}
@Override
public void onEvtThink()
{
// null;
}
@Override
protected void onEvtAttacked(Creature attacker)
{
final DoorInstance me = (DoorInstance) _actor;
ThreadPool.execute(new onEventAttackedDoorTask(me, attacker));
}
@Override
protected void onEvtAggression(Creature target, int aggro)
{
// null;
}
@Override
protected void onEvtStunned(Creature attacker)
{
// null;
}
@Override
protected void onEvtSleeping(Creature attacker)
{
// null;
}
@Override
protected void onEvtRooted(Creature attacker)
{
// null;
}
@Override
protected void onEvtReadyToAct()
{
// null;
}
@Override
protected void onEvtUserCmd(Object arg0, Object arg1)
{
// null;
}
@Override
protected void onEvtArrived()
{
// null;
}
@Override
protected void onEvtArrivedRevalidate()
{
// null;
}
@Override
protected void onEvtArrivedBlocked(Location blocked_at_pos)
{
// null;
}
@Override
protected void onEvtForgetObject(WorldObject object)
{
// null;
}
@Override
protected void onEvtCancel()
{
// null;
}
@Override
protected void onEvtDead()
{
// null;
}
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()
{
_door.getKnownList().updateKnownObjects();
for (SiegeGuardInstance guard : _door.getKnownSiegeGuards())
{
if ((guard != null) && (guard.getAI() != null) && _actor.isInsideRadius(guard, guard.getFactionRange(), false, true) && (Math.abs(_attacker.getZ() - guard.getZ()) < 200))
{
guard.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, _attacker, 15);
}
}
for (FortSiegeGuardInstance guard : _door.getKnownFortSiegeGuards())
{
if ((guard != null) && (guard.getAI() != null) && _actor.isInsideRadius(guard, guard.getFactionRange(), false, true) && (Math.abs(_attacker.getZ() - guard.getZ()) < 200))
{
guard.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, _attacker, 15);
}
}
}
}
}

View File

@@ -0,0 +1,997 @@
/*
* 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_IDLE;
import java.util.ArrayList;
import java.util.List;
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.geoengine.GeoEngine;
import org.l2jmobius.gameserver.model.Effect;
import org.l2jmobius.gameserver.model.Skill;
import org.l2jmobius.gameserver.model.Skill.SkillType;
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.Playable;
import org.l2jmobius.gameserver.model.actor.Summon;
import org.l2jmobius.gameserver.model.actor.instance.CommanderInstance;
import org.l2jmobius.gameserver.model.actor.instance.DoorInstance;
import org.l2jmobius.gameserver.model.actor.instance.FolkInstance;
import org.l2jmobius.gameserver.model.actor.instance.FortSiegeGuardInstance;
import org.l2jmobius.gameserver.model.actor.instance.NpcInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.util.Util;
/**
* This class manages AI of Attackable.
*/
public class FortSiegeGuardAI extends CreatureAI implements Runnable
{
protected static final Logger _log1 = Logger.getLogger(FortSiegeGuardAI.class.getName());
// SelfAnalisis ))
public List<Skill> pdamSkills = new ArrayList<>();
public List<Skill> mdamSkills = new ArrayList<>();
public List<Skill> healSkills = new ArrayList<>();
public List<Skill> rootSkills = new ArrayList<>();
public boolean hasPDam = false;
public boolean hasMDam = false;
public boolean hasHeal = false;
public boolean hasRoot = false;
private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds
/** The Attackable AI task executed every 1s (call onEvtThink method) */
private Future<?> _aiTask;
/** For attack AI, analysis of mob and its targets */
// private SelfAnalysis _selfAnalysis = new SelfAnalysis();
/** The delay after which the attacked is stopped */
private int _attackTimeout;
/** The Attackable aggro counter */
private int _globalAggro;
/** The flag used to indicate that a thinking action is in progress */
private boolean _thinking; // to prevent recursive thinking
private final int _attackRange;
/**
* Constructor of AttackableAI.
* @param accessor The AI accessor of the Creature
*/
public FortSiegeGuardAI(Creature.AIAccessor accessor)
{
super(accessor);
// _selfAnalysis.init();
_attackTimeout = Integer.MAX_VALUE;
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
_attackRange = ((Attackable) _actor).getPhysicalAttackRange();
}
@Override
public void run()
{
// Launch actions corresponding to the Event Think
onEvtThink();
}
/**
* Return True if the target is autoattackable (depends on the actor type).<br>
* <br>
* <b><u>Actor is a GuardInstance</u>:</b><br>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The PlayerInstance target has karma (=PK)</li>
* <li>The MonsterInstance target is aggressive</li><br>
* <br>
* <b><u>Actor is a SiegeGuardInstance</u>:</b><br>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>A siege is in progress</li>
* <li>The PlayerInstance target isn't a Defender</li><br>
* <br>
* <b><u>Actor is a FriendlyMobInstance</u>:</b><br>
* <li>The target isn't a Folk, a Door or another NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The PlayerInstance target has karma (=PK)</li><br>
* <br>
* <b><u>Actor is a MonsterInstance</u>:</b><br>
* <li>The target isn't a Folk, a Door or another NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The actor is Aggressive</li><br>
* @param target The targeted WorldObject
* @return
*/
private boolean autoAttackCondition(Creature target)
{
// Check if the target isn't another guard, folk or a door
if ((target == null) || (target instanceof FortSiegeGuardInstance) || (target instanceof FolkInstance) || (target instanceof DoorInstance) || target.isAlikeDead() || (target instanceof CommanderInstance) || (target instanceof Playable))
{
PlayerInstance player = null;
if (target instanceof PlayerInstance)
{
player = (PlayerInstance) target;
}
else if (target instanceof Summon)
{
player = ((Summon) target).getOwner();
}
if ((player == null) || ((player.getClan() != null) && (player.getClan().getHasFort() == ((NpcInstance) _actor).getFort().getFortId())))
{
return false;
}
}
// Check if the target isn't invulnerable
if ((target != null) && target.isInvul())
{
// However EffectInvincible requires to check GMs specially
if ((target instanceof PlayerInstance) && ((PlayerInstance) target).isGM())
{
return false;
}
if ((target instanceof Summon) && ((Summon) target).getOwner().isGM())
{
return false;
}
}
// Get the owner if the target is a summon
Creature currentTarget = target;
if (currentTarget instanceof Summon)
{
final PlayerInstance owner = ((Summon) currentTarget).getOwner();
if (_actor.isInsideRadius(owner, 1000, true, false))
{
currentTarget = owner;
}
}
// Check if the target is a PlayerInstance
if (currentTarget instanceof PlayerInstance)
{
// Check if the target isn't in silent move mode AND too far (>100)
if (((PlayerInstance) currentTarget).isSilentMoving() && !_actor.isInsideRadius(currentTarget, 250, false, false))
{
return false;
}
}
// Los Check Here
return _actor.isAutoAttackable(currentTarget) && GeoEngine.getInstance().canSeeTarget(_actor, currentTarget);
}
/**
* Set the Intention of this CreatureAI and create an AI Task executed every 1s (call onEvtThink method) for this Attackable.<br>
* <font color=#FF0000><b><u>Caution</u>: If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</b></font>
* @param newIntention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
public synchronized void changeIntention(CtrlIntention newIntention, Object arg0, Object arg1)
{
CtrlIntention intention = newIntention;
if (intention == AI_INTENTION_IDLE /* || intention == AI_INTENTION_ACTIVE */) // active becomes idle if only a summon is present
{
// Check if actor is not dead
if (!_actor.isAlikeDead())
{
final Attackable npc = (Attackable) _actor;
// If its _knownPlayer isn't empty set the Intention to AI_INTENTION_ACTIVE
if (npc.getKnownList().getKnownPlayers().size() > 0)
{
intention = AI_INTENTION_ACTIVE;
}
else
{
intention = AI_INTENTION_IDLE;
}
}
if (intention == AI_INTENTION_IDLE)
{
// Set the Intention of this AttackableAI to AI_INTENTION_IDLE
super.changeIntention(AI_INTENTION_IDLE, null, null);
// Stop AI task and detach AI from NPC
if (_aiTask != null)
{
_aiTask.cancel(true);
_aiTask = null;
}
// Cancel the AI
_accessor.detachAI();
return;
}
}
// Set the Intention of this AttackableAI to intention
super.changeIntention(intention, arg0, arg1);
// If not idle - create an AI task (schedule onEvtThink repeatedly)
if (_aiTask == null)
{
_aiTask = ThreadPool.scheduleAtFixedRate(this, 1000, 1000);
}
}
/**
* Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.
* @param target The Creature to attack
*/
@Override
protected void onIntentionAttack(Creature target)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
// if (_actor.getTarget() != null)
super.onIntentionAttack(target);
}
/**
* Manage AI standard thinks of a Attackable (called by onEvtThink).<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Update every 1s the _globalAggro counter to come close to 0</li>
* <li>If the actor is Aggressive and can attack, add all autoAttackable Creature in its Aggro Range to its _aggroList, chose a target and order to attack it</li>
* <li>If the actor can't attack, order to it to return to its home location</li>
*/
private void thinkActive()
{
final Attackable npc = (Attackable) _actor;
// Update every 1s the _globalAggro counter to come close to 0
if (_globalAggro != 0)
{
if (_globalAggro < 0)
{
_globalAggro++;
}
else
{
_globalAggro--;
}
}
// Add all autoAttackable Creature in Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
// A Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
if (_globalAggro >= 0)
{
for (Creature target : npc.getKnownList().getKnownCharactersInRadius(_attackRange))
{
if (target == null)
{
continue;
}
if (autoAttackCondition(target)) // check aggression
{
// Get the hate level of the Attackable against this Creature target contained in _aggroList
final int hating = npc.getHating(target);
// Add the attacker to the Attackable _aggroList with 0 damage and 1 hate
if (hating == 0)
{
npc.addDamageHate(target, 0, 1);
}
}
}
// Chose a target from its aggroList
Creature hated;
if (_actor.isConfused())
{
hated = getAttackTarget(); // Force mobs to attack anybody if confused
}
else
{
hated = npc.getMostHated();
// _mostHatedAnalysis.Update(hated);
}
// Order to the Attackable to attack the target
if (hated != null)
{
// Get the hate level of the Attackable against this Creature target contained in _aggroList
final int aggro = npc.getHating(hated);
if ((aggro + _globalAggro) > 0)
{
// Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the AI Intention to AI_INTENTION_ATTACK
setIntention(AI_INTENTION_ATTACK, hated, null);
}
return;
}
}
// Order to the SiegeGuardInstance to return to its home location because there's no target to attack
if (_actor.getWalkSpeed() >= 0)
{
if (_actor instanceof FortSiegeGuardInstance)
{
((FortSiegeGuardInstance) _actor).returnHome();
}
else
{
((CommanderInstance) _actor).returnHome();
}
}
}
/**
* Manage AI attack thinks of a Attackable (called by onEvtThink).<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Update the attack timeout if actor is running</li>
* <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
* <li>Call all WorldObject of its Faction inside the Faction Range</li>
* <li>Chose a target and order to attack it with magic skill or physical attack</li><br>
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
*/
private void thinkAttack()
{
// Check if the actor is running
if ((_attackTimeout < GameTimeController.getGameTicks()) && _actor.isRunning())
{
// Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others PlayerInstance
_actor.setWalking();
// Calculate a new attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
}
final Creature attackTarget = getAttackTarget();
// Check if target is dead or if timeout is expired to stop this attack
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getGameTicks()))
{
// Stop hating this target after the attack timeout or if target is dead
if (attackTarget != null)
{
final Attackable npc = (Attackable) _actor;
npc.stopHating(attackTarget);
}
// Cancel target and timeout
_attackTimeout = Integer.MAX_VALUE;
setAttackTarget(null);
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE, null, null);
_actor.setWalking();
return;
}
factionNotifyAndSupport();
attackPrepare();
}
private final void factionNotifyAndSupport()
{
final Creature target = getAttackTarget();
// Call all WorldObject of its Faction inside the Faction Range
if ((((NpcInstance) _actor).getFactionId() == null) || (target == null))
{
return;
}
if (target.isInvul())
{
return; // speeding it up for siege guards
}
if (Rnd.get(10) > 4)
{
return; // test for reducing CPU load
}
final String faction_id = ((NpcInstance) _actor).getFactionId();
// SalfAnalisis ))
for (Skill sk : _actor.getAllSkills())
{
if (sk.isPassive())
{
continue;
}
switch (sk.getSkillType())
{
case PDAM:
{
rootSkills.add(sk);
hasPDam = true;
break;
}
case MDAM:
{
rootSkills.add(sk);
hasMDam = true;
break;
}
case HEAL:
{
healSkills.add(sk);
hasHeal = true;
break;
}
case ROOT:
{
rootSkills.add(sk);
hasRoot = true;
break;
}
default:
{
// Haven`t anything useful for us.
break;
}
}
}
// Go through all Creature that belong to its faction
// for (Creature creature : _actor.getKnownList().getKnownCharactersInRadius(((NpcInstance) _actor).getFactionRange()+_actor.getTemplate().collisionRadius))
for (Creature creature : _actor.getKnownList().getKnownCharactersInRadius(1000))
{
if (creature == null)
{
continue;
}
if (!(creature instanceof NpcInstance))
{
if (/* _selfAnalysis.hasHealOrResurrect && */(creature instanceof PlayerInstance) && ((NpcInstance) _actor).getFort().getSiege().checkIsDefender(((PlayerInstance) creature).getClan()))
{
// heal friends
if (!_actor.isAttackingDisabled() && (creature.getCurrentHp() < (creature.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && creature.isInCombat())
{
for (Skill sk : /* _selfAnalysis.healSkills */healSkills)
{
if (_actor.getCurrentMp() < sk.getMpConsume())
{
continue;
}
if (_actor.isSkillDisabled(sk))
{
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, creature, true))
{
continue;
}
final int chance = 5;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoEngine.getInstance().canSeeTarget(_actor, creature))
{
break;
}
final WorldObject oldTarget = _actor.getTarget();
_actor.setTarget(creature);
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(oldTarget);
return;
}
}
}
continue;
}
final NpcInstance npc = (NpcInstance) creature;
if (!faction_id.equalsIgnoreCase(npc.getFactionId()))
{
continue;
}
if (npc.getAI() != null) // TODO: possibly check not needed
{
if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600)
// && _actor.getAttackByList().contains(getAttackTarget())
&& ((npc.getAI().getIntention() == AI_INTENTION_IDLE) || (npc.getAI().getIntention() == AI_INTENTION_ACTIVE))
// limiting aggro for siege guards
&& target.isInsideRadius(npc, 1500, true, false) && GeoEngine.getInstance().canSeeTarget(npc, target))
{
// Notify the WorldObject AI with EVT_AGGRESSION
final CreatureAI ai = npc.getAI();
if (ai != null)
{
ai.notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
}
}
// heal friends
if (/* _selfAnalysis.hasHealOrResurrect && */ !_actor.isAttackingDisabled() && (npc.getCurrentHp() < (npc.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && npc.isInCombat())
{
for (Skill sk : /* _selfAnalysis.healSkills */ healSkills)
{
if (_actor.getCurrentMp() < sk.getMpConsume())
{
continue;
}
if (_actor.isSkillDisabled(sk))
{
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, npc, true))
{
continue;
}
final int chance = 4;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoEngine.getInstance().canSeeTarget(_actor, npc))
{
break;
}
final WorldObject oldTarget = _actor.getTarget();
_actor.setTarget(npc);
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(oldTarget);
return;
}
}
}
}
}
private void attackPrepare()
{
// Get all information needed to choose between physical or magical attack
Skill[] skills = null;
double dist2 = 0;
int range = 0;
FortSiegeGuardInstance sGuard;
sGuard = (FortSiegeGuardInstance) _actor;
Creature attackTarget = getAttackTarget();
try
{
_actor.setTarget(attackTarget);
skills = _actor.getAllSkills();
dist2 = _actor.getPlanDistanceSq(attackTarget.getX(), attackTarget.getY());
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
if (attackTarget.isMoving())
{
range += 50;
}
}
catch (NullPointerException e)
{
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// never attack defenders
if ((attackTarget instanceof PlayerInstance) && sGuard.getFort().getSiege().checkIsDefender(((PlayerInstance) attackTarget).getClan()))
{
// Cancel the target
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
if (!GeoEngine.getInstance().canSeeTarget(_actor, attackTarget))
{
// Siege guards differ from normal mobs currently:
// If target cannot seen, don't attack any more
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// Check if the actor isn't muted and if it is far from target
if (!_actor.isMuted() && (dist2 > (range * range)))
{
// check for long ranged skills and heal/buff skills
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if ((dist2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
{
final WorldObject oldTarget = _actor.getTarget();
if ((sk.getSkillType() == SkillType.BUFF) || (sk.getSkillType() == SkillType.HEAL))
{
boolean useSkillSelf = true;
if ((sk.getSkillType() == SkillType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.getSkillType() == SkillType.BUFF)
{
final Effect[] effects = _actor.getAllEffects();
for (int i = 0; (effects != null) && (i < effects.length); i++)
{
final Effect effect = effects[i];
if (effect.getSkill() == sk)
{
useSkillSelf = false;
break;
}
}
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(oldTarget);
return;
}
}
// Check if the SiegeGuardInstance is attacking, knows the target and can't run
if (!_actor.isAttackingNow() && (_actor.getRunSpeed() == 0) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else
{
final double dx = _actor.getX() - attackTarget.getX();
final double dy = _actor.getY() - attackTarget.getY();
final double dz = _actor.getZ() - attackTarget.getZ();
final double homeX = attackTarget.getX() - sGuard.getSpawn().getX();
final double homeY = attackTarget.getY() - sGuard.getSpawn().getY();
// Check if the SiegeGuardInstance isn't too far from it's home location
if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else // Temporary hack for preventing guards jumping off towers,
// before replacing this with effective GeoClient checks and AI modification
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
// if (_selfAnalysis.isMage)
// range = _selfAnalysis.maxCastRange - 50;
if (_actor.getWalkSpeed() <= 0)
{
return;
}
if (attackTarget.isMoving())
{
moveToPawn(attackTarget, range - 70);
}
else
{
moveToPawn(attackTarget, range);
}
}
}
}
// Else, if the actor is muted and far from target, just "move to pawn"
else if (_actor.isMuted() && (dist2 > (range * range)))
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective GeoClient checks and AI modification
final double dz = _actor.getZ() - attackTarget.getZ();
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
// if (_selfAnalysis.isMage)
// range = _selfAnalysis.maxCastRange - 50;
if (_actor.getWalkSpeed() <= 0)
{
return;
}
if (attackTarget.isMoving())
{
moveToPawn(attackTarget, range - 70);
}
else
{
moveToPawn(attackTarget, range);
}
}
}
// Else, if this is close enough to attack
else if (dist2 <= (range * range))
{
// Force mobs to attack anybody if confused
Creature hated = null;
if (_actor.isConfused())
{
hated = attackTarget;
}
else
{
hated = ((Attackable) _actor).getMostHated();
}
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE, null, null);
return;
}
if (hated != attackTarget)
{
attackTarget = hated;
}
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// check for close combat skills && heal/buff skills
if (!_actor.isMuted() && (Rnd.get(100) <= 5))
{
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk))
{
final WorldObject oldTarget = _actor.getTarget();
if ((sk.getSkillType() == SkillType.BUFF) || (sk.getSkillType() == SkillType.HEAL))
{
boolean useSkillSelf = true;
if ((sk.getSkillType() == SkillType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.getSkillType() == SkillType.BUFF)
{
final Effect[] effects = _actor.getAllEffects();
for (int i = 0; (effects != null) && (i < effects.length); i++)
{
final Effect effect = effects[i];
if (effect.getSkill() == sk)
{
useSkillSelf = false;
break;
}
}
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(oldTarget);
return;
}
}
}
// Finally, do the physical attack itself
_accessor.doAttack(attackTarget);
}
}
/**
* Manage AI thinking actions of a Attackable.
*/
@Override
public void onEvtThink()
{
// if(getIntention() != AI_INTENTION_IDLE && (!_actor.isVisible() || !_actor.hasAI() || !_actor.isKnownPlayers()))
// setIntention(AI_INTENTION_IDLE);
// Check if the actor can't use skills and if a thinking action isn't already in progress
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled())
{
return;
}
// Start thinking action
_thinking = true;
try
{
// Manage AI thinks of a Attackable
if (getIntention() == AI_INTENTION_ACTIVE)
{
thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
}
finally
{
// Stop thinking action
_thinking = false;
}
}
/**
* Launch actions corresponding to the Event Attacked.<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
* <li>Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance</li>
* <li>Set the Intention to AI_INTENTION_ATTACK</li>
* @param attacker The Creature that attacks the actor
*/
@Override
protected void onEvtAttacked(Creature attacker)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// Set the _globalAggro to 0 to permit attack even just after spawn
if (_globalAggro < 0)
{
_globalAggro = 0;
}
// Add the attacker to the _aggroList of the actor
((Attackable) _actor).addDamageHate(attacker, 0, 1);
// Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK)
{
setIntention(AI_INTENTION_ATTACK, attacker, null);
}
super.onEvtAttacked(attacker);
}
/**
* Launch actions corresponding to the Event Aggression.<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Add the target to the actor _aggroList or update hate if already present</li>
* <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is GuardInstance check if it isn't too far from its home location)</li><br>
* @param target The Creature that attacks
* @param aggro The value of hate to add to the actor against the target
*/
@Override
protected void onEvtAggression(Creature target, int aggro)
{
if (_actor == null)
{
return;
}
final Attackable me = (Attackable) _actor;
if (target != null)
{
// Add the target to the actor _aggroList or update hate if already present
me.addDamageHate(target, 0, aggro);
// Get the hate of the actor against the target
if (me.getHating(target) <= 0)
{
if (me.getMostHated() == null)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
return;
}
// Set the actor AI Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK)
{
// Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
FortSiegeGuardInstance sGuard;
sGuard = (FortSiegeGuardInstance) _actor;
final double homeX = target.getX() - sGuard.getSpawn().getX();
final double homeY = target.getY() - sGuard.getSpawn().getY();
// Check if the SiegeGuardInstance is not too far from its home location
if (((homeX * homeX) + (homeY * homeY)) < 3240000)
{
setIntention(AI_INTENTION_ATTACK, target, null);
}
}
}
else
{
// currently only for setting lower general aggro
if (aggro >= 0)
{
return;
}
final Creature mostHated = me.getMostHated();
if (mostHated == null)
{
_globalAggro = -25;
return;
}
for (Creature aggroed : me.getAggroList().keySet())
{
me.addDamageHate(aggroed, 0, aggro);
}
if (me.getHating(mostHated) <= 0)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
}
}
@Override
protected void onEvtDead()
{
stopAITask();
super.onEvtDead();
}
public void stopAITask()
{
if (_aiTask != null)
{
_aiTask.cancel(false);
_aiTask = null;
}
_accessor.detachAI();
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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.List;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.gameserver.datatables.xml.WalkerRouteData;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.NpcWalkerNode;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.instance.NpcWalkerInstance;
public class NpcWalkerAI extends CreatureAI implements Runnable
{
private static final int DEFAULT_MOVE_DELAY = 0;
private long _nextMoveTime;
private boolean _walkingToNextPoint = false;
/**
* home points for xyz
*/
int _homeX;
/**
* home points for xyz
*/
int _homeY;
/**
* home points for xyz
*/
int _homeZ;
/**
* route of the current npc
*/
private final List<NpcWalkerNode> _route = WalkerRouteData.getInstance().getRouteForNpc(getActor().getNpcId());
/**
* current node
*/
private int _currentPos;
/**
* Constructor of CreatureAI.
* @param accessor The AI accessor of the Creature
*/
public NpcWalkerAI(Creature.AIAccessor accessor)
{
super(accessor);
// Do we really need 2 minutes delay before start?
// no we dont... :)
ThreadPool.scheduleAtFixedRate(this, 0, 1000);
}
@Override
public void run()
{
onEvtThink();
}
@Override
public void onEvtThink()
{
if (!Config.ALLOW_NPC_WALKERS)
{
return;
}
if (_walkingToNextPoint)
{
checkArrived();
return;
}
if (_nextMoveTime < System.currentTimeMillis())
{
walkToLocation();
}
}
/**
* If npc can't walk to it's target then just teleport to next point
* @param location ignoring it
*/
@Override
protected void onEvtArrivedBlocked(Location location)
{
// LOGGER.warning("NpcWalker ID: " + getActor().getNpcId() + ": Blocked at rote position [" + _currentPos + "], coords: " + location.getX() + ", " + location.getY() + ", " + location.getZ() + ". Teleporting to next point");
if (_route.size() <= _currentPos)
{
return;
}
final int destinationX = _route.get(_currentPos).getMoveX();
final int destinationY = _route.get(_currentPos).getMoveY();
final int destinationZ = _route.get(_currentPos).getMoveZ();
getActor().teleToLocation(destinationX, destinationY, destinationZ, false);
super.onEvtArrivedBlocked(location);
}
private void checkArrived()
{
if (_route.size() <= _currentPos)
{
return;
}
final int destinationX = _route.get(_currentPos).getMoveX();
final int destinationY = _route.get(_currentPos).getMoveY();
final int destinationZ = _route.get(_currentPos).getMoveZ();
if ((getActor().getX() == destinationX) && (getActor().getY() == destinationY) && (getActor().getZ() == destinationZ))
{
final String chat = _route.get(_currentPos).getChatText();
if ((chat != null) && !chat.isEmpty())
{
try
{
getActor().broadcastChat(chat);
}
catch (ArrayIndexOutOfBoundsException e)
{
// LOGGER.info("NpcWalkerInstance: Error, " + e);
}
}
// time in millis
long delay = _route.get(_currentPos).getDelay() * 1000;
// sleeps between each move
if (delay < 0)
{
delay = DEFAULT_MOVE_DELAY;
}
_nextMoveTime = System.currentTimeMillis() + delay;
setWalkingToNextPoint(false);
}
}
private void walkToLocation()
{
if (_currentPos < (_route.size() - 1))
{
_currentPos++;
}
else
{
_currentPos = 0;
}
if (_route.size() <= _currentPos)
{
return;
}
final boolean moveType = _route.get(_currentPos).getRunning();
/**
* false - walking true - Running
*/
if (moveType)
{
getActor().setRunning();
}
else
{
getActor().setWalking();
}
// now we define destination
final int destinationX = _route.get(_currentPos).getMoveX();
final int destinationY = _route.get(_currentPos).getMoveY();
final int destinationZ = _route.get(_currentPos).getMoveZ();
// notify AI of MOVE_TO
setWalkingToNextPoint(true);
setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new Location(destinationX, destinationY, destinationZ, 0));
}
@Override
public NpcWalkerInstance getActor()
{
return (NpcWalkerInstance) super.getActor();
}
public int getHomeX()
{
return _homeX;
}
public int getHomeY()
{
return _homeY;
}
public int getHomeZ()
{
return _homeZ;
}
public void setHomeX(int homeX)
{
_homeX = homeX;
}
public void setHomeY(int homeY)
{
_homeY = homeY;
}
public void setHomeZ(int homeZ)
{
_homeZ = homeZ;
}
public boolean isWalkingToNextPoint()
{
return _walkingToNextPoint;
}
public void setWalkingToNextPoint(boolean value)
{
_walkingToNextPoint = value;
}
}

View File

@@ -0,0 +1,337 @@
/*
* 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_PICK_UP;
import static org.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_REST;
import java.util.EmptyStackException;
import java.util.Stack;
import org.l2jmobius.gameserver.model.Skill;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.Creature.AIAccessor;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.instance.StaticObjectInstance;
public class PlayerAI extends CreatureAI
{
private boolean _thinking; // to prevent recursive thinking
class IntentionCommand
{
protected CtrlIntention _crtlIntention;
protected Object _arg0;
protected Object _arg1;
protected IntentionCommand(CtrlIntention pIntention, Object pArg0, Object pArg1)
{
_crtlIntention = pIntention;
_arg0 = pArg0;
_arg1 = pArg1;
}
}
private final Stack<IntentionCommand> _interuptedIntentions = new Stack<>();
private synchronized Stack<IntentionCommand> getInterruptedIntentions()
{
return _interuptedIntentions;
}
public PlayerAI(AIAccessor accessor)
{
super(accessor);
}
/**
* 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 arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
public void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
// nothing to do if it does not CAST intention
if (intention != AI_INTENTION_CAST)
{
super.changeIntention(intention, arg0, arg1);
return;
}
final CtrlIntention oldIntention = getIntention();
final Object oldArg0 = getIntentionArg0();
final Object oldArg1 = getIntentionArg1();
// do nothing if next intention is same as current one.
if ((intention == oldIntention) && (arg0 == oldArg0) && (arg1 == oldArg1))
{
super.changeIntention(intention, arg0, arg1);
return;
}
// push current intention to stack
getInterruptedIntentions().push(new IntentionCommand(oldIntention, oldArg0, oldArg1));
super.changeIntention(intention, arg0, arg1);
}
/**
* Finalize the casting of a skill. This method overrides CreatureAI method.<br>
* <b>What it does:</b> 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()
{
// forget interupted actions after offensive skill
final Skill skill = getSkill();
if ((skill != null) && skill.isOffensive())
{
getInterruptedIntentions().clear();
}
if (getIntention() == AI_INTENTION_CAST)
{
// run interupted intention if it remain.
if (!getInterruptedIntentions().isEmpty())
{
IntentionCommand cmd = null;
try
{
cmd = getInterruptedIntentions().pop();
}
catch (EmptyStackException ese)
{
}
if ((cmd != null) && (cmd._crtlIntention != AI_INTENTION_CAST)) // previous state shouldn't be casting
{
setIntention(cmd._crtlIntention, cmd._arg0, cmd._arg1);
}
else
{
setIntention(AI_INTENTION_IDLE);
}
}
else
{
// set intention to idle if skill doesn't change intention.
setIntention(AI_INTENTION_IDLE);
}
}
}
@Override
protected void onIntentionRest()
{
if (getIntention() != AI_INTENTION_REST)
{
changeIntention(AI_INTENTION_REST, null, null);
setTarget(null);
if (getAttackTarget() != null)
{
setAttackTarget(null);
}
clientStopMoving(null);
}
}
@Override
protected void onIntentionActive()
{
setIntention(AI_INTENTION_IDLE);
}
@Override
protected void clientNotifyDead()
{
_clientMovingToPawnOffset = 0;
_clientMoving = false;
super.clientNotifyDead();
}
private void thinkAttack()
{
final Creature target = getAttackTarget();
if (target == null)
{
return;
}
if (checkTargetLostOrDead(target))
{
// Notify the target
setAttackTarget(null);
return;
}
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange()))
{
return;
}
_accessor.doAttack(target);
}
private void thinkCast()
{
final Creature target = getCastTarget();
final Skill skill = getSkill();
// if (Config.DEBUG) LOGGER.warning("PlayerAI: thinkCast -> Start");
if (checkTargetLost(target))
{
if (skill.isOffensive() && (getAttackTarget() != null))
{
// Notify the target
setCastTarget(null);
}
return;
}
if ((target != null) && maybeMoveToPawn(target, _actor.getMagicalAttackRange(skill)))
{
return;
}
if (skill.getHitTime() > 50)
{
clientStopMoving(null);
}
final WorldObject oldTarget = _actor.getTarget();
if (oldTarget != null)
{
// Replace the current target by the cast target
if ((target != null) && (oldTarget != target))
{
_actor.setTarget(getCastTarget());
}
// Launch the Cast of the skill
_accessor.doCast(getSkill());
// Restore the initial target
if ((target != null) && (oldTarget != target))
{
_actor.setTarget(oldTarget);
}
}
else
{
_accessor.doCast(skill);
}
}
private void thinkPickUp()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
((PlayerInstance.AIAccessor) _accessor).doPickupItem(target);
}
private void thinkInteract()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
if (!(target instanceof StaticObjectInstance))
{
((PlayerInstance.AIAccessor) _accessor).doInteract((Creature) target);
}
setIntention(AI_INTENTION_IDLE);
}
@Override
public void onEvtThink()
{
if (_thinking || _actor.isAllSkillsDisabled())
{
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
protected void onEvtArrivedRevalidate()
{
if (_actor != null)
{
_actor.getKnownList().updateKnownObjects();
}
super.onEvtArrivedRevalidate();
}
}

View File

@@ -0,0 +1,803 @@
/*
* 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_IDLE;
import java.util.concurrent.Future;
import org.l2jmobius.Config;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.GameTimeController;
import org.l2jmobius.gameserver.geoengine.GeoEngine;
import org.l2jmobius.gameserver.model.Effect;
import org.l2jmobius.gameserver.model.Skill;
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.Summon;
import org.l2jmobius.gameserver.model.actor.instance.DoorInstance;
import org.l2jmobius.gameserver.model.actor.instance.FolkInstance;
import org.l2jmobius.gameserver.model.actor.instance.MonsterInstance;
import org.l2jmobius.gameserver.model.actor.instance.NpcInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.instance.SiegeGuardInstance;
/**
* This class manages AI of Attackable.
*/
public class SiegeGuardAI extends CreatureAI implements Runnable
{
// protected static final Logger LOGGER = Logger.getLogger(SiegeGuardAI.class);
private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds
/** The Attackable AI task executed every 1s (call onEvtThink method) */
private Future<?> _aiTask;
/** The delay after wich the attacked is stopped */
private int _attackTimeout;
/** The Attackable aggro counter */
private int _globalAggro;
/** The flag used to indicate that a thinking action is in progress */
private boolean _thinking; // to prevent recursive thinking
private final int _attackRange;
/**
* Constructor of AttackableAI.
* @param accessor The AI accessor of the Creature
*/
public SiegeGuardAI(Creature.AIAccessor accessor)
{
super(accessor);
_attackTimeout = Integer.MAX_VALUE;
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
_attackRange = ((Attackable) _actor).getPhysicalAttackRange();
}
@Override
public void run()
{
// Launch actions corresponding to the Event Think
onEvtThink();
}
/**
* Return True if the target is autoattackable (depends on the actor type).<br>
* <br>
* <b><u>Actor is a GuardInstance</u>:</b><br>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The PlayerInstance target has karma (=PK)</li>
* <li>The MonsterInstance target is aggressive</li><br>
* <br>
* <b><u>Actor is a SiegeGuardInstance</u>:</b><br>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>A siege is in progress</li>
* <li>The PlayerInstance target isn't a Defender</li><br>
* <br>
* <b><u>Actor is a FriendlyMobInstance</u>:</b><br>
* <li>The target isn't a Folk, a Door or another NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The PlayerInstance target has karma (=PK)</li><br>
* <br>
* <b><u>Actor is a MonsterInstance</u>:</b><br>
* <li>The target isn't a Folk, a Door or another NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The actor is Aggressive</li><br>
* @param target The targeted WorldObject
* @return
*/
private boolean autoAttackCondition(Creature target)
{
// Check if the target isn't another guard, folk or a door
if ((target == null) || (target instanceof SiegeGuardInstance) || (target instanceof FolkInstance) || (target instanceof DoorInstance) || target.isAlikeDead() || target.isInvul())
{
return false;
}
// Get the owner if the target is a summon
Creature currentTarget = target;
if (currentTarget instanceof Summon)
{
final PlayerInstance owner = ((Summon) currentTarget).getOwner();
if (_actor.isInsideRadius(owner, 1000, true, false))
{
currentTarget = owner;
}
}
// Check if the target is a PlayerInstance and if the target isn't in silent move mode AND too far (>100)
if ((currentTarget instanceof PlayerInstance) && ((PlayerInstance) currentTarget).isSilentMoving() && !_actor.isInsideRadius(currentTarget, 250, false, false))
{
return false;
}
// Los Check Here
return _actor.isAutoAttackable(currentTarget) && GeoEngine.getInstance().canSeeTarget(_actor, currentTarget);
}
/**
* Set the Intention of this CreatureAI and create an AI Task executed every 1s (call onEvtThink method) for this Attackable.<br>
* <font color=#FF0000><b><u>Caution</u>: If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</b></font>
* @param newIntention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
public void changeIntention(CtrlIntention newIntention, Object arg0, Object arg1)
{
CtrlIntention intention = newIntention;
((Attackable) _actor).setReturningToSpawnPoint(false);
if (intention == AI_INTENTION_IDLE /* || intention == AI_INTENTION_ACTIVE */) // active becomes idle if only a summon is present
{
// Check if actor is not dead
if (!_actor.isAlikeDead())
{
final Attackable npc = (Attackable) _actor;
// If its _knownPlayer isn't empty set the Intention to AI_INTENTION_ACTIVE
if (npc.getKnownList().getKnownPlayers().size() > 0)
{
intention = AI_INTENTION_ACTIVE;
}
else
{
intention = AI_INTENTION_IDLE;
}
}
if (intention == AI_INTENTION_IDLE)
{
// Set the Intention of this AttackableAI to AI_INTENTION_IDLE
super.changeIntention(AI_INTENTION_IDLE, null, null);
// Stop AI task and detach AI from NPC
if (_aiTask != null)
{
_aiTask.cancel(true);
_aiTask = null;
}
// Cancel the AI
_accessor.detachAI();
return;
}
}
// Set the Intention of this AttackableAI to intention
super.changeIntention(intention, arg0, arg1);
// If not idle - create an AI task (schedule onEvtThink repeatedly)
if (_aiTask == null)
{
_aiTask = ThreadPool.scheduleAtFixedRate(this, 1000, 1000);
}
}
/**
* Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.
* @param target The Creature to attack
*/
@Override
protected void onIntentionAttack(Creature target)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
// if (_actor.getTarget() != null)
super.onIntentionAttack(target);
}
/**
* Manage AI standard thinks of a Attackable (called by onEvtThink).<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Update every 1s the _globalAggro counter to come close to 0</li>
* <li>If the actor is Aggressive and can attack, add all autoAttackable Creature in its Aggro Range to its _aggroList, chose a target and order to attack it</li>
* <li>If the actor can't attack, order to it to return to its home location</li>
*/
private void thinkActive()
{
final Attackable npc = (Attackable) _actor;
// Update every 1s the _globalAggro counter to come close to 0
if (_globalAggro != 0)
{
if (_globalAggro < 0)
{
_globalAggro++;
}
else
{
_globalAggro--;
}
}
// Add all autoAttackable Creature in Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
// A Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
if (_globalAggro >= 0)
{
for (Creature target : npc.getKnownList().getKnownCharactersInRadius(_attackRange))
{
if (target == null)
{
continue;
}
if (autoAttackCondition(target)) // check aggression
{
// Get the hate level of the Attackable against this Creature target contained in _aggroList
final int hating = npc.getHating(target);
// Add the attacker to the Attackable _aggroList with 0 damage and 1 hate
if (hating == 0)
{
npc.addDamageHate(target, 0, 1);
}
}
}
// Chose a target from its aggroList
Creature hated;
// Force mobs to attak anybody if confused
if (_actor.isConfused())
{
hated = getAttackTarget();
}
else
{
hated = npc.getMostHated();
}
// Order to the Attackable to attack the target
if (hated != null)
{
// Get the hate level of the Attackable against this Creature target contained in _aggroList
final int aggro = npc.getHating(hated);
if ((aggro + _globalAggro) > 0)
{
// Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the AI Intention to AI_INTENTION_ATTACK
setIntention(AI_INTENTION_ATTACK, hated, null);
}
return;
}
}
// Order to the SiegeGuardInstance to return to its home location because there's no target to attack
((SiegeGuardInstance) _actor).returnHome();
}
private void attackPrepare()
{
// Get all information needed to chose between physical or magical attack
Skill[] skills = null;
double dist2 = 0;
int range = 0;
final SiegeGuardInstance sGuard = (SiegeGuardInstance) _actor;
final Creature attackTarget = getAttackTarget();
try
{
_actor.setTarget(attackTarget);
skills = _actor.getAllSkills();
dist2 = _actor.getPlanDistanceSq(attackTarget.getX(), attackTarget.getY());
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
}
catch (NullPointerException e)
{
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// never attack defenders
if ((attackTarget instanceof PlayerInstance) && sGuard.getCastle().getSiege().checkIsDefender(((PlayerInstance) attackTarget).getClan()))
{
// Cancel the target
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
if (!GeoEngine.getInstance().canSeeTarget(_actor, attackTarget))
{
// Siege guards differ from normal mobs currently:
// If target cannot seen, don't attack any more
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// Check if the actor isn't muted and if it is far from target
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check for long ranged skills and heal/buff skills
if (!Config.ALT_GAME_MOB_ATTACK_AI || ((_actor instanceof MonsterInstance) && (Rnd.get(100) <= 5)))
{
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((sk.getSkillType() == Skill.SkillType.BUFF) || (sk.getSkillType() == Skill.SkillType.HEAL) || ((dist2 >= ((castRange * castRange) / 9)) && (dist2 <= (castRange * castRange)) && (castRange > 70))) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
{
if ((sk.getSkillType() == Skill.SkillType.BUFF) || (sk.getSkillType() == Skill.SkillType.HEAL))
{
boolean useSkillSelf = true;
if (((sk.getSkillType() == Skill.SkillType.BUFF) || (sk.getSkillType() == Skill.SkillType.HEAL) || ((dist2 >= ((castRange * castRange) / 9)) && (dist2 <= (castRange * castRange)) && (castRange > 70))) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
{
useSkillSelf = false;
break;
}
if (sk.getSkillType() == Skill.SkillType.BUFF)
{
final Effect[] effects = _actor.getAllEffects();
for (int i = 0; (effects != null) && (i < effects.length); i++)
{
final Effect effect = effects[i];
if (effect.getSkill() == sk)
{
useSkillSelf = false;
break;
}
}
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
final WorldObject oldTarget = _actor.getTarget();
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(oldTarget);
return;
}
}
}
// Check if the SiegeGuardInstance is attacking, knows the target and can't run
if (!_actor.isAttackingNow() && (_actor.getRunSpeed() == 0) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else
{
final double dx = _actor.getX() - attackTarget.getX();
final double dy = _actor.getY() - attackTarget.getY();
final double dz = _actor.getZ() - attackTarget.getZ();
final double homeX = attackTarget.getX() - sGuard.getHomeX();
final double homeY = attackTarget.getY() - sGuard.getHomeY();
// Check if the SiegeGuardInstance isn't too far from it's home location
if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) && _actor.getKnownList().knowsObject(attackTarget))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else // Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
if ((dz * dz) < (170 * 170))
{
moveToPawn(attackTarget, range);
}
}
}
// Else, if the actor is muted and far from target, just "move to pawn"
else if (_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
final double dz = _actor.getZ() - attackTarget.getZ();
// normally 130 if guard z coordinates correct
if ((dz * dz) < (170 * 170))
{
moveToPawn(attackTarget, range);
}
}
// Else, if this is close enough to attack
else if (dist2 <= ((range + 20) * (range + 20)))
{
// Force mobs to attak anybody if confused
Creature hated = null;
if (_actor.isConfused())
{
hated = attackTarget;
}
else
{
hated = ((Attackable) _actor).getMostHated();
}
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE, null, null);
return;
}
if (hated != attackTarget)
{
setAttackTarget(hated);
}
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// check for close combat skills && heal/buff skills
if (!_actor.isMuted() && (Rnd.get(100) <= 5))
{
for (Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && (castRange <= 70) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk))
{
if ((sk.getSkillType() == Skill.SkillType.BUFF) || (sk.getSkillType() == Skill.SkillType.HEAL))
{
boolean useSkillSelf = true;
if ((sk.getSkillType() == Skill.SkillType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.getSkillType() == Skill.SkillType.BUFF)
{
final Effect[] effects = _actor.getAllEffects();
for (int i = 0; (effects != null) && (i < effects.length); i++)
{
final Effect effect = effects[i];
if (effect.getSkill() == sk)
{
useSkillSelf = false;
break;
}
}
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
final WorldObject oldTarget = _actor.getTarget();
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(oldTarget);
return;
}
}
}
// Finally, do the physical attack itself
_accessor.doAttack(getAttackTarget());
}
}
/**
* Manage AI attack thinks of a Attackable (called by onEvtThink).<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Update the attack timeout if actor is running</li>
* <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
* <li>Call all WorldObject of its Faction inside the Faction Range</li>
* <li>Chose a target and order to attack it with magic skill or physical attack</li><br>
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
*/
private void thinkAttack()
{
// Check if the actor is running
if ((_attackTimeout < GameTimeController.getGameTicks()) && _actor.isRunning())
{
// Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others PlayerInstance
_actor.setWalking();
// Calculate a new attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
}
final Creature attackTarget = getAttackTarget();
// Check if target is dead or if timeout is expired to stop this attack
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getGameTicks()))
{
// Stop hating this target after the attack timeout or if target is dead
if (attackTarget != null)
{
final Attackable npc = (Attackable) _actor;
npc.stopHating(attackTarget);
}
// Cancel target and timeout
_attackTimeout = Integer.MAX_VALUE;
setAttackTarget(null);
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE, null, null);
_actor.setWalking();
return;
}
attackPrepare();
factionNotify();
}
private final void factionNotify()
{
final Creature actor = getActor();
final Creature target = getAttackTarget();
// Call all WorldObject of its Faction inside the Faction Range
if ((actor == null) || (target == null) || (((NpcInstance) actor).getFactionId() == null))
{
return;
}
if (target.isInvul())
{
return;
}
// Go through all WorldObject that belong to its faction
for (Creature creature : actor.getKnownList().getKnownCharactersInRadius(1000))
{
if (creature == null)
{
continue;
}
if (!(creature instanceof NpcInstance))
{
continue;
}
final NpcInstance npc = (NpcInstance) creature;
final String factionId = ((NpcInstance) actor).getFactionId();
if (!factionId.equalsIgnoreCase(npc.getFactionId()))
{
continue;
}
// Check if the WorldObject is inside the Faction Range of the actor
if ((npc.getAI() != null) && ((npc.getAI().getIntention() == AI_INTENTION_IDLE) || (npc.getAI().getIntention() == AI_INTENTION_ACTIVE)) && actor.isInsideRadius(npc, npc.getFactionRange(), false, true) && target.isInsideRadius(npc, npc.getFactionRange(), false, true))
{
if (Config.PATHFINDING)
{
if (GeoEngine.getInstance().canSeeTarget(npc, target))
{
// Notify the WorldObject AI with EVT_AGGRESSION
final CreatureAI ai = npc.getAI();
if (ai != null)
{
ai.notifyEvent(CtrlEvent.EVT_AGGRESSION, target, 1);
}
}
}
else if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600))
{
// Notify the WorldObject AI with EVT_AGGRESSION
final CreatureAI ai = npc.getAI();
if (ai != null)
{
ai.notifyEvent(CtrlEvent.EVT_AGGRESSION, target, 1);
}
}
}
}
}
/**
* Manage AI thinking actions of a Attackable.
*/
@Override
public void onEvtThink()
{
// Check if the actor can't use skills and if a thinking action isn't already in progress
if (_thinking || _actor.isAllSkillsDisabled())
{
return;
}
// Start thinking action
_thinking = true;
try
{
// Manage AI thinks of a Attackable
if (getIntention() == AI_INTENTION_ACTIVE)
{
thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
}
finally
{
// Stop thinking action
_thinking = false;
}
}
/**
* Launch actions corresponding to the Event Attacked.<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
* <li>Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance</li>
* <li>Set the Intention to AI_INTENTION_ATTACK</li>
* @param attacker The Creature that attacks the actor
*/
@Override
protected void onEvtAttacked(Creature attacker)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// Set the _globalAggro to 0 to permit attack even just after spawn
if (_globalAggro < 0)
{
_globalAggro = 0;
}
// Add the attacker to the _aggroList of the actor
((Attackable) _actor).addDamageHate(attacker, 0, 1);
// Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK)
{
setIntention(AI_INTENTION_ATTACK, attacker, null);
}
super.onEvtAttacked(attacker);
}
/**
* Launch actions corresponding to the Event Aggression.<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Add the target to the actor _aggroList or update hate if already present</li>
* <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is GuardInstance check if it isn't too far from its home location)</li><br>
* @param target The Creature that attacks
* @param aggro The value of hate to add to the actor against the target
*/
@Override
protected void onEvtAggression(Creature target, int aggro)
{
if (_actor == null)
{
return;
}
final Attackable me = (Attackable) _actor;
if (target != null)
{
// Add the target to the actor _aggroList or update hate if already present
me.addDamageHate(target, 0, aggro);
// Get the hate of the actor against the target
if (me.getHating(target) <= 0)
{
if (me.getMostHated() == null)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
return;
}
// Set the actor AI Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK)
{
// Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others PlayerInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
final SiegeGuardInstance sGuard = (SiegeGuardInstance) _actor;
final double homeX = target.getX() - sGuard.getHomeX();
final double homeY = target.getY() - sGuard.getHomeY();
// Check if the SiegeGuardInstance is not too far from its home location
if (((homeX * homeX) + (homeY * homeY)) < 3240000)
{
setIntention(AI_INTENTION_ATTACK, target, null);
}
}
}
else
{
// currently only for setting lower general aggro
if (aggro >= 0)
{
return;
}
final Creature mostHated = me.getMostHated();
if (mostHated == null)
{
_globalAggro = -25;
return;
}
for (Creature aggroed : me.getAggroList().keySet())
{
me.addDamageHate(aggroed, 0, aggro);
}
if (me.getHating(mostHated) <= 0)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
}
}
@Override
protected void onEvtDead()
{
stopAITask();
super.onEvtDead();
}
public void stopAITask()
{
if (_aiTask != null)
{
_aiTask.cancel(false);
_aiTask = null;
}
_accessor.detachAI();
}
}

View File

@@ -0,0 +1,210 @@
/*
* 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_FOLLOW;
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_PICK_UP;
import org.l2jmobius.gameserver.model.Skill;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.Creature.AIAccessor;
import org.l2jmobius.gameserver.model.actor.Summon;
public class SummonAI extends CreatureAI
{
private boolean _thinking; // to prevent recursive thinking
public SummonAI(AIAccessor accessor)
{
super(accessor);
}
@Override
protected void onIntentionIdle()
{
stopFollow();
onIntentionActive();
}
@Override
protected void onIntentionActive()
{
final Summon summon = (Summon) _actor;
if (summon.getFollowStatus())
{
setIntention(AI_INTENTION_FOLLOW, summon.getOwner());
}
else
{
super.onIntentionActive();
}
}
private void thinkAttack()
{
final Summon summon = (Summon) _actor;
WorldObject target = null;
target = summon.getTarget();
// Like L2OFF if the target is dead the summon must go back to his owner
if ((target != null) && ((Creature) target).isDead())
{
summon.setFollowStatus(true);
}
if (checkTargetLostOrDead(getAttackTarget()))
{
setAttackTarget(null);
return;
}
if (maybeMoveToPawn(getAttackTarget(), _actor.getPhysicalAttackRange()))
{
return;
}
clientStopMoving(null);
_accessor.doAttack(getAttackTarget());
}
private void thinkCast()
{
final Summon summon = (Summon) _actor;
final Creature target = getCastTarget();
if (checkTargetLost(target))
{
setCastTarget(null);
}
final Skill skill = getSkill();
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(skill)))
{
return;
}
clientStopMoving(null);
summon.setFollowStatus(false);
setIntention(AI_INTENTION_IDLE);
_accessor.doCast(skill);
}
private void thinkPickUp()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
((Summon.AIAccessor) _accessor).doPickupItem(target);
}
private void thinkInteract()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
final WorldObject target = getTarget();
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
}
@Override
public void onEvtThink()
{
if (_thinking || _actor.isAllSkillsDisabled())
{
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
protected void onEvtFinishCasting()
{
super.onEvtFinishCasting();
final Summon summon = (Summon) _actor;
WorldObject target = null;
target = summon.getTarget();
if (target == null)
{
return;
}
if (summon.getAI().getIntention() != AI_INTENTION_ATTACK)
{
summon.setFollowStatus(true);
}
else if (((Creature) target).isDead())
{
summon.setFollowStatus(true);
}
}
}

View File

@@ -0,0 +1,380 @@
/*
* 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.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.datatables.sql.ClanTable;
import org.l2jmobius.gameserver.idfactory.IdFactory;
import org.l2jmobius.gameserver.model.clan.Clan;
/**
* @author Layane
*/
public class CrestCache
{
private static final Logger LOGGER = Logger.getLogger(CrestCache.class.getName());
private final Map<Integer, byte[]> _cachePledge = new HashMap<>();
private final Map<Integer, byte[]> _cachePledgeLarge = new HashMap<>();
private final Map<Integer, byte[]> _cacheAlly = new HashMap<>();
private int _loadedFiles;
private long _bytesBuffLen;
private CrestCache()
{
convertOldPedgeFiles();
reload();
}
public void reload()
{
final FileFilter filter = new BmpFilter();
final File dir = new File(Config.DATAPACK_ROOT, "data/crests/");
final File[] files = dir.listFiles(filter);
byte[] content;
synchronized (this)
{
_loadedFiles = 0;
_bytesBuffLen = 0;
_cachePledge.clear();
_cachePledgeLarge.clear();
_cacheAlly.clear();
}
for (File file : files)
{
RandomAccessFile f = null;
synchronized (this)
{
try
{
f = new RandomAccessFile(file, "r");
content = new byte[(int) f.length()];
f.readFully(content);
if (file.getName().startsWith("Crest_Large_"))
{
_cachePledgeLarge.put(Integer.parseInt(file.getName().substring(12, file.getName().length() - 4)), content);
}
else if (file.getName().startsWith("Crest_"))
{
_cachePledge.put(Integer.parseInt(file.getName().substring(6, file.getName().length() - 4)), content);
}
else if (file.getName().startsWith("AllyCrest_"))
{
_cacheAlly.put(Integer.parseInt(file.getName().substring(10, file.getName().length() - 4)), content);
}
_loadedFiles++;
_bytesBuffLen += content.length;
}
catch (Exception e)
{
LOGGER.warning("problem with crest bmp file " + e);
}
finally
{
if (f != null)
{
try
{
f.close();
}
catch (Exception e1)
{
LOGGER.warning("Problem with CrestCache: " + e1.getMessage());
}
}
}
}
}
LOGGER.info("Cache[Crest]: " + String.format("%.3f", getMemoryUsage()) + "MB on " + _loadedFiles + " files loaded.");
}
public void convertOldPedgeFiles()
{
final File dir = new File(Config.DATAPACK_ROOT, "data/crests/");
final File[] files = dir.listFiles(new OldPledgeFilter());
if (files == null)
{
LOGGER.info("No old crest files found in \"data/crests/\"!!! May be you deleted them?");
return;
}
for (File file : files)
{
final int clanId = Integer.parseInt(file.getName().substring(7, file.getName().length() - 4));
LOGGER.info("Found old crest file \"" + file.getName() + "\" for clanId " + clanId);
final int newId = IdFactory.getNextId();
final Clan clan = ClanTable.getInstance().getClan(clanId);
if (clan != null)
{
removeOldPledgeCrest(clan.getCrestId());
file.renameTo(new File(Config.DATAPACK_ROOT, "data/crests/Crest_" + newId + ".bmp"));
LOGGER.info("Renamed Clan crest to new format: Crest_" + newId + ".bmp");
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("UPDATE clan_data SET crest_id = ? WHERE clan_id = ?");
statement.setInt(1, newId);
statement.setInt(2, clan.getClanId());
statement.executeUpdate();
statement.close();
}
catch (SQLException e)
{
LOGGER.warning("could not update the crest id:" + e.getMessage());
}
clan.setCrestId(newId);
clan.setHasCrest(true);
}
else
{
LOGGER.info("Clan Id: " + clanId + " does not exist in table.. deleting.");
file.delete();
}
}
}
public float getMemoryUsage()
{
return (float) _bytesBuffLen / 1048576;
}
public int getLoadedFiles()
{
return _loadedFiles;
}
public byte[] getPledgeCrest(int id)
{
return _cachePledge.get(id);
}
public byte[] getPledgeCrestLarge(int id)
{
return _cachePledgeLarge.get(id);
}
public byte[] getAllyCrest(int id)
{
return _cacheAlly.get(id);
}
public void removePledgeCrest(int id)
{
final File crestFile = new File(Config.DATAPACK_ROOT, "data/crests/Crest_" + id + ".bmp");
_cachePledge.remove(id);
try
{
crestFile.delete();
}
catch (Exception e)
{
}
}
public void removePledgeCrestLarge(int id)
{
final File crestFile = new File(Config.DATAPACK_ROOT, "data/crests/Crest_Large_" + id + ".bmp");
_cachePledgeLarge.remove(id);
try
{
crestFile.delete();
}
catch (Exception e)
{
}
}
public void removeOldPledgeCrest(int id)
{
final File crestFile = new File(Config.DATAPACK_ROOT, "data/crests/Pledge_" + id + ".bmp");
try
{
crestFile.delete();
}
catch (Exception e)
{
}
}
public void removeAllyCrest(int id)
{
final File crestFile = new File(Config.DATAPACK_ROOT, "data/crests/AllyCrest_" + id + ".bmp");
_cacheAlly.remove(id);
try
{
crestFile.delete();
}
catch (Exception e)
{
}
}
public boolean savePledgeCrest(int newId, byte[] data)
{
boolean output = false;
final File crestFile = new File(Config.DATAPACK_ROOT, "data/crests/Crest_" + newId + ".bmp");
FileOutputStream out = null;
try
{
out = new FileOutputStream(crestFile);
out.write(data);
_cachePledge.put(newId, data);
output = true;
}
catch (IOException e)
{
LOGGER.warning("Error saving pledge crest" + crestFile + " " + e);
}
finally
{
if (out != null)
{
try
{
out.close();
}
catch (IOException e)
{
LOGGER.warning("Problem with CrestCache: " + e.getMessage());
}
}
}
return output;
}
public boolean savePledgeCrestLarge(int newId, byte[] data)
{
boolean output = false;
final File crestFile = new File(Config.DATAPACK_ROOT, "data/crests/Crest_Large_" + newId + ".bmp");
FileOutputStream out = null;
try
{
out = new FileOutputStream(crestFile);
out.write(data);
_cachePledgeLarge.put(newId, data);
output = true;
}
catch (IOException e)
{
LOGGER.warning("Error saving Large pledge crest" + crestFile + " " + e);
}
finally
{
if (out != null)
{
try
{
out.close();
}
catch (IOException e)
{
LOGGER.warning("Problem with CrestCache: " + e.getMessage());
}
}
}
return output;
}
public boolean saveAllyCrest(int newId, byte[] data)
{
boolean output = false;
final File crestFile = new File(Config.DATAPACK_ROOT, "data/crests/AllyCrest_" + newId + ".bmp");
FileOutputStream out = null;
try
{
out = new FileOutputStream(crestFile);
out.write(data);
_cacheAlly.put(newId, data);
output = true;
}
catch (IOException e)
{
LOGGER.warning("Error saving ally crest" + crestFile + " " + e);
}
finally
{
if (out != null)
{
try
{
out.close();
}
catch (IOException e)
{
LOGGER.warning("Problem with CrestCache: " + e.getMessage());
}
}
}
return output;
}
class BmpFilter implements FileFilter
{
@Override
public boolean accept(File file)
{
return file.getName().endsWith(".bmp");
}
}
class OldPledgeFilter implements FileFilter
{
@Override
public boolean accept(File file)
{
return file.getName().startsWith("Pledge_");
}
}
public static CrestCache getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final CrestCache INSTANCE = new CrestCache();
}
}

View File

@@ -0,0 +1,238 @@
/*
* 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.FileFilter;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.gameserver.util.Util;
/**
* @author Layane
*/
public class HtmCache
{
private static final Logger LOGGER = Logger.getLogger(HtmCache.class.getName());
private final Map<Integer, String> _cache;
private int _loadedFiles;
private long _bytesBuffLen;
private HtmCache()
{
_cache = new HashMap<>();
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
{
_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;
}
class HtmFilter implements FileFilter
{
@Override
public boolean accept(File file)
{
if (!file.isDirectory())
{
return file.getName().endsWith(".htm") || file.getName().endsWith(".html");
}
return true;
}
}
private void parseDir(File dir)
{
final FileFilter filter = new HtmFilter();
final File[] files = dir.listFiles(filter);
for (File file : files)
{
if (!file.isDirectory())
{
loadFile(file);
}
else
{
parseDir(file);
}
}
}
public String loadFile(File file)
{
final HtmFilter filter = new HtmFilter();
String content = null;
if (file.exists() && filter.accept(file) && !file.isDirectory())
{
FileInputStream fis = null;
BufferedInputStream bis = null;
try
{
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
final int bytes = bis.available();
final byte[] raw = new byte[bytes];
bis.read(raw);
content = new String(raw, StandardCharsets.UTF_8);
content = content.replaceAll("\r\n", "\n");
content = content.replaceAll("(?s)<!--.*?-->", ""); // Remove html comments
final String relpath = Util.getRelativePath(Config.DATAPACK_ROOT, file);
final int hashcode = relpath.hashCode();
if (Config.CHECK_HTML_ENCODING && !StandardCharsets.US_ASCII.newEncoder().canEncode(content))
{
LOGGER.warning("HTML encoding check: File " + relpath + " contains non ASCII content.");
}
final String oldContent = _cache.get(hashcode);
if (oldContent == null)
{
_bytesBuffLen += bytes;
_loadedFiles++;
}
else
{
_bytesBuffLen = (_bytesBuffLen - oldContent.length()) + bytes;
}
_cache.put(hashcode, content);
}
catch (Exception e)
{
LOGGER.warning("Problem with htm file " + e);
}
finally
{
if (bis != null)
{
try
{
bis.close();
}
catch (Exception e1)
{
LOGGER.warning("Problem with HtmCache: " + e1.getMessage());
}
}
if (fis != null)
{
try
{
fis.close();
}
catch (Exception e1)
{
LOGGER.warning("Problem with HtmCache: " + e1.getMessage());
}
}
}
}
return content;
}
public String getHtmForce(String path)
{
String content = getHtm(path);
if (content == null)
{
content = "<html><body>My text is missing:<br>" + path + "</body></html>";
LOGGER.warning("Cache[HTML]: Missing HTML page: " + path);
}
return content;
}
public String getHtm(String path)
{
String content = _cache.get(path.hashCode());
if (Config.LAZY_CACHE && (content == null))
{
content = loadFile(new File(Config.DATAPACK_ROOT, path));
}
return content;
}
public boolean contains(String path)
{
return _cache.containsKey(path.hashCode());
}
/**
* Check if an HTM exists and can be loaded
* @param path The path to the HTM
* @return
*/
public boolean isLoadable(String path)
{
final File file = new File(path);
final HtmFilter filter = new HtmFilter();
return file.exists() && filter.accept(file) && !file.isDirectory();
}
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.Map.Entry;
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
{
protected final Map<PlayerInstance, Long> _cachedWh;
protected final long _cacheTime;
private WarehouseCacheManager()
{
_cacheTime = Config.WAREHOUSE_CACHE_TIME * 60000; // 60*1000 = 60000
_cachedWh = new ConcurrentHashMap<>();
ThreadPool.scheduleAtFixedRate(new CacheScheduler(), 120000, 60000);
}
public void addCacheTask(PlayerInstance pc)
{
_cachedWh.put(pc, System.currentTimeMillis());
}
public void remCacheTask(PlayerInstance pc)
{
_cachedWh.remove(pc);
}
public class CacheScheduler implements Runnable
{
@Override
public void run()
{
final long cTime = System.currentTimeMillis();
for (Entry<PlayerInstance, Long> entry : _cachedWh.entrySet())
{
if ((cTime - entry.getValue()) > _cacheTime)
{
final PlayerInstance player = entry.getKey();
player.clearWarehouse();
_cachedWh.remove(player);
}
}
}
}
public static WarehouseCacheManager getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final WarehouseCacheManager INSTANCE = new WarehouseCacheManager();
}
}

View File

@@ -0,0 +1,252 @@
/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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
{
// Types
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;
// Permissions
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 static final Logger LOGGER = Logger.getLogger(Forum.class.getName());
private final List<Forum> _children;
private final Map<Integer, Topic> _topic;
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;
/**
* @param forumId
* @param fParent
*/
public Forum(int forumId, Forum fParent)
{
_forumId = forumId;
_fParent = fParent;
_children = new ArrayList<>();
_topic = new HashMap<>();
}
/**
* @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 = new ArrayList<>();
_topic = new HashMap<>();
parent._children.add(this);
ForumsBBSManager.getInstance().addForum(this);
_loaded = true;
}
private void load()
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM forums WHERE forum_id=?");
statement.setInt(1, _forumId);
final ResultSet result = statement.executeQuery();
if (result.next())
{
_forumName = result.getString("forum_name");
_forumPost = result.getInt("forum_post");
_forumType = result.getInt("forum_type");
_forumPerm = result.getInt("forum_perm");
_ownerID = result.getInt("forum_owner_id");
}
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Data error on Forum " + _forumId + " : " + e);
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM topic WHERE topic_forum_id=? ORDER BY topic_id DESC");
statement.setInt(1, _forumId);
final ResultSet result = statement.executeQuery();
while (result.next())
{
final Topic t = new Topic(Topic.ConstructorType.RESTORE, result.getInt("topic_id"), result.getInt("topic_forum_id"), result.getString("topic_name"), result.getLong("topic_date"), result.getString("topic_ownername"), result.getInt("topic_ownerid"), result.getInt("topic_type"), result.getInt("topic_reply"));
_topic.put(t.getID(), t);
if (t.getID() > TopicBBSManager.getInstance().getMaxID(this))
{
TopicBBSManager.getInstance().setMaxID(t.getID(), this);
}
}
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Data error on Forum " + _forumId + " : " + e);
}
}
private void getChildren()
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT forum_id FROM forums WHERE forum_parent=?");
statement.setInt(1, _forumId);
final ResultSet result = statement.executeQuery();
while (result.next())
{
final Forum f = new Forum(result.getInt("forum_id"), this);
_children.add(f);
ForumsBBSManager.getInstance().addForum(f);
}
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Data error on Forum (children): " + 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);
}
public int getID()
{
return _forumId;
}
public String getName()
{
vload();
return _forumName;
}
public int getType()
{
vload();
return _forumType;
}
/**
* @param name
* @return
*/
public Forum getChildByName(String name)
{
vload();
for (Forum f : _children)
{
if (f.getName().equals(name))
{
return f;
}
}
return null;
}
public void rmTopicByID(int id)
{
_topic.remove(id);
}
public void insertIntoDb()
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("INSERT INTO forums (forum_id,forum_name,forum_parent,forum_post,forum_type,forum_perm,forum_owner_id) values (?,?,?,?,?,?,?)");
statement.setInt(1, _forumId);
statement.setString(2, _forumName);
statement.setInt(3, _fParent.getID());
statement.setInt(4, _forumPost);
statement.setInt(5, _forumType);
statement.setInt(6, _forumPerm);
statement.setInt(7, _ownerID);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while saving new Forum to db " + e);
}
}
public void vload()
{
if (!_loaded)
{
load();
getChildren();
_loaded = true;
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.communitybbs.Manager.PostBBSManager;
/**
* @author Maktakien
*/
public class Post
{
private static final Logger LOGGER = Logger.getLogger(Post.class.getName());
public class CPost
{
public int postId;
public String postOwner;
public int postOwnerId;
public long postDate;
public int postTopicId;
public int postForumId;
public String postTxt;
}
private final List<CPost> _post;
/**
* @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 = new ArrayList<>();
final CPost cp = new CPost();
cp.postId = 0;
cp.postOwner = postOwner;
cp.postOwnerId = postOwnerId;
cp.postDate = date;
cp.postTopicId = tid;
cp.postForumId = postForumId;
cp.postTxt = txt;
_post.add(cp);
insertindb(cp);
}
public void insertindb(CPost cp)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("INSERT INTO posts (post_id,post_owner_name,post_ownerid,post_date,post_topic_id,post_forum_id,post_txt) values (?,?,?,?,?,?,?)");
statement.setInt(1, cp.postId);
statement.setString(2, cp.postOwner);
statement.setInt(3, cp.postOwnerId);
statement.setLong(4, cp.postDate);
statement.setInt(5, cp.postTopicId);
statement.setInt(6, cp.postForumId);
statement.setString(7, cp.postTxt);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while saving new Post to db " + e);
}
}
public Post(Topic t)
{
_post = new ArrayList<>();
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())
{
final PreparedStatement statement = con.prepareStatement("DELETE FROM posts WHERE post_forum_id=? AND post_topic_id=?");
statement.setInt(1, t.getForumID());
statement.setInt(2, t.getID());
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while deleting post: " + e.getMessage());
}
}
private void load(Topic t)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM posts WHERE post_forum_id=? AND post_topic_id=? ORDER BY post_id ASC");
statement.setInt(1, t.getForumID());
statement.setInt(2, t.getID());
final ResultSet result = statement.executeQuery();
while (result.next())
{
final CPost cp = new CPost();
cp.postId = result.getInt("post_id");
cp.postOwner = result.getString("post_owner_name");
cp.postOwnerId = result.getInt("post_ownerid");
cp.postDate = result.getLong("post_date");
cp.postTopicId = result.getInt("post_topic_id");
cp.postForumId = result.getInt("post_forum_id");
cp.postTxt = result.getString("post_txt");
_post.add(cp);
}
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Data error on Post " + t.getForumID() + "/" + t.getID() + " : " + e);
}
}
public void updateText(int i)
{
try (Connection con = DatabaseFactory.getConnection())
{
final CPost cp = getCPost(i);
final PreparedStatement statement = con.prepareStatement("UPDATE posts SET post_txt=? WHERE post_id=? AND post_topic_id=? AND post_forum_id=?");
statement.setString(1, cp.postTxt);
statement.setInt(2, cp.postId);
statement.setInt(3, cp.postTopicId);
statement.setInt(4, cp.postForumId);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while saving new Post to db " + e);
}
}
}

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.communitybbs.BB;
import java.sql.Connection;
import java.sql.PreparedStatement;
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)
{
insertIntoDb();
}
}
private void insertIntoDb()
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("INSERT INTO topic (topic_id,topic_forum_id,topic_name,topic_date,topic_ownername,topic_ownerid,topic_type,topic_reply) values (?,?,?,?,?,?,?,?)");
statement.setInt(1, _id);
statement.setInt(2, _forumId);
statement.setString(3, _topicName);
statement.setLong(4, _date);
statement.setString(5, _ownerName);
statement.setInt(6, _ownerId);
statement.setInt(7, _type);
statement.setInt(8, _cReply);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while saving new Topic to db " + e);
}
}
public enum ConstructorType
{
RESTORE,
CREATE
}
public int getID()
{
return _id;
}
public int getForumID()
{
return _forumId;
}
public String getName()
{
return _topicName;
}
public String getOwnerName()
{
return _ownerName;
}
public long getDate()
{
return _date;
}
public void deleteMe(Forum f)
{
TopicBBSManager.getInstance().delTopic(this);
f.rmTopicByID(_id);
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("DELETE FROM topic WHERE topic_id=? AND topic_forum_id=?");
statement.setInt(1, _id);
statement.setInt(2, f.getID());
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while deleting topic: " + e.getMessage());
}
}
}

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.communitybbs;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.l2jmobius.Config;
import org.l2jmobius.gameserver.communitybbs.Manager.BaseBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.ClanBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.FavoriteBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.FriendsBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.MailBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.PostBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.RegionBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.TopBBSManager;
import org.l2jmobius.gameserver.communitybbs.Manager.TopicBBSManager;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.GameClient;
import org.l2jmobius.gameserver.network.SystemMessageId;
public class CommunityBoard
{
/** The bypasses used by the players. */
private final Map<Integer, String> _bypasses = new ConcurrentHashMap<>();
protected CommunityBoard()
{
}
public static CommunityBoard getInstance()
{
return SingletonHolder.INSTANCE;
}
public void handleCommands(GameClient client, String command)
{
final PlayerInstance player = client.getPlayer();
if (player == null)
{
return;
}
if (!Config.ENABLE_COMMUNITY_BOARD)
{
player.sendPacket(SystemMessageId.THE_COMMUNITY_SERVER_IS_CURRENTLY_OFFLINE);
return;
}
if (command.startsWith("_bbshome"))
{
TopBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_bbsloc"))
{
RegionBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_bbsclan"))
{
ClanBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_bbsmemo"))
{
TopicBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_bbsmail") || command.equals("_maillist_0_1_0_"))
{
MailBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_friend") || command.startsWith("_block"))
{
FriendsBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_bbstopics"))
{
TopicBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_bbsposts"))
{
PostBBSManager.getInstance().parseCmd(command, player);
}
else if (command.startsWith("_bbsgetfav") || command.startsWith("bbs_add_fav") || command.startsWith("_bbsdelfav_"))
{
FavoriteBBSManager.getInstance().parseCmd(command, player);
}
else
{
BaseBBSManager.separateAndSend("<html><body><br><br><center>The command: " + command + " isn't implemented.</center></body></html>", player);
}
}
public void handleWriteCommands(GameClient client, String url, String arg1, String arg2, String arg3, String arg4, String arg5)
{
final PlayerInstance player = client.getPlayer();
if (player == null)
{
return;
}
if (!Config.ENABLE_COMMUNITY_BOARD)
{
player.sendPacket(SystemMessageId.THE_COMMUNITY_SERVER_IS_CURRENTLY_OFFLINE);
return;
}
if (url.equals("Topic"))
{
TopicBBSManager.getInstance().parseWrite(arg1, arg2, arg3, arg4, arg5, player);
}
else if (url.equals("Post"))
{
PostBBSManager.getInstance().parseWrite(arg1, arg2, arg3, arg4, arg5, player);
}
else if (url.equals("_bbsloc"))
{
RegionBBSManager.getInstance().parseWrite(arg1, arg2, arg3, arg4, arg5, player);
}
else if (url.equals("_bbsclan"))
{
ClanBBSManager.getInstance().parseWrite(arg1, arg2, arg3, arg4, arg5, player);
}
else if (url.equals("Mail"))
{
MailBBSManager.getInstance().parseWrite(arg1, arg2, arg3, arg4, arg5, player);
}
else if (url.equals("_friend"))
{
FriendsBBSManager.getInstance().parseWrite(arg1, arg2, arg3, arg4, arg5, player);
}
else
{
BaseBBSManager.separateAndSend("<html><body><br><br><center>The command: " + url + " isn't implemented.</center></body></html>", player);
}
}
/**
* Sets the last bypass used by the player.
* @param player the player
* @param title the title
* @param bypass the bypass
*/
public void addBypass(PlayerInstance player, String title, String bypass)
{
_bypasses.put(player.getObjectId(), title + "&" + bypass);
}
/**
* Removes the last bypass used by the player.
* @param player the player
* @return the last bypass used
*/
public String removeBypass(PlayerInstance player)
{
return _bypasses.remove(player.getObjectId());
}
private static class SingletonHolder
{
protected static final CommunityBoard INSTANCE = new CommunityBoard();
}
}

View File

@@ -0,0 +1,124 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.communitybbs.Manager;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.serverpackets.ShowBoard;
public abstract class BaseBBSManager
{
protected static final Logger LOGGER = Logger.getLogger(BaseBBSManager.class.getName());
protected static final String CB_PATH = "data/html/CommunityBoard/";
public void parseCmd(String command, PlayerInstance player)
{
separateAndSend("<html><body><br><br><center>The command: " + command + " isn't implemented.</center></body></html>", player);
}
public void parseWrite(String ar1, String ar2, String ar3, String ar4, String ar5, PlayerInstance player)
{
separateAndSend("<html><body><br><br><center>The command: " + ar1 + " isn't implemented.</center></body></html>", player);
}
public static void separateAndSend(String html, PlayerInstance acha)
{
if ((html == null) || (acha == null))
{
return;
}
if (html.length() < 4090)
{
acha.sendPacket(new ShowBoard(html, "101"));
acha.sendPacket(ShowBoard.STATIC_SHOWBOARD_102);
acha.sendPacket(ShowBoard.STATIC_SHOWBOARD_103);
}
else if (html.length() < 8180)
{
acha.sendPacket(new ShowBoard(html.substring(0, 4090), "101"));
acha.sendPacket(new ShowBoard(html.substring(4090, html.length()), "102"));
acha.sendPacket(ShowBoard.STATIC_SHOWBOARD_103);
}
else if (html.length() < 12270)
{
acha.sendPacket(new ShowBoard(html.substring(0, 4090), "101"));
acha.sendPacket(new ShowBoard(html.substring(4090, 8180), "102"));
acha.sendPacket(new ShowBoard(html.substring(8180, html.length()), "103"));
}
}
protected static void send1001(String html, PlayerInstance acha)
{
if (html.length() < 8180)
{
acha.sendPacket(new ShowBoard(html, "1001"));
}
}
protected static void send1002(PlayerInstance acha)
{
send1002(acha, " ", " ", "0");
}
protected static void send1002(PlayerInstance player, String string, String string2, String string3)
{
final List<String> arg = new ArrayList<>();
arg.add("0");
arg.add("0");
arg.add("0");
arg.add("0");
arg.add("0");
arg.add("0");
arg.add(player.getName());
arg.add(Integer.toString(player.getObjectId()));
arg.add(player.getAccountName());
arg.add("9");
arg.add(string2);
arg.add(string2);
arg.add(string);
arg.add(string3);
arg.add(string3);
arg.add("0");
arg.add("0");
player.sendPacket(new ShowBoard(arg));
}
/**
* Loads an HTM located in the default CB path.
* @param file : the file to load.
* @param player : the requester.
*/
protected void loadStaticHtm(String file, PlayerInstance player)
{
separateAndSend(HtmCache.getInstance().getHtm(CB_PATH + getFolder() + file), player);
}
/**
* That method is overidden in every board type. It allows to switch of folders following the board.
* @return the folder.
*/
protected String getFolder()
{
return "";
}
}

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.communitybbs.Manager;
import java.util.StringTokenizer;
import org.l2jmobius.commons.util.StringUtil;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.communitybbs.CommunityBoard;
import org.l2jmobius.gameserver.datatables.sql.ClanTable;
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.network.SystemMessageId;
public class ClanBBSManager extends BaseBBSManager
{
protected ClanBBSManager()
{
}
public static ClanBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
@Override
public void parseCmd(String command, PlayerInstance activeChar)
{
if (command.equalsIgnoreCase("_bbsclan"))
{
if (activeChar.getClan() == null)
{
sendClanList(activeChar, 1);
}
else
{
sendClanDetails(activeChar, activeChar.getClan().getClanId());
}
}
else if (command.startsWith("_bbsclan"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
final String clanCommand = st.nextToken();
if (clanCommand.equalsIgnoreCase("clan"))
{
CommunityBoard.getInstance().addBypass(activeChar, "Clan", command);
sendClanList(activeChar, Integer.parseInt(st.nextToken()));
}
else if (clanCommand.equalsIgnoreCase("home"))
{
CommunityBoard.getInstance().addBypass(activeChar, "Clan Home", command);
sendClanDetails(activeChar, Integer.parseInt(st.nextToken()));
}
else if (clanCommand.equalsIgnoreCase("mail"))
{
CommunityBoard.getInstance().addBypass(activeChar, "Clan Mail", command);
sendClanMail(activeChar, Integer.parseInt(st.nextToken()));
}
else if (clanCommand.equalsIgnoreCase("management"))
{
CommunityBoard.getInstance().addBypass(activeChar, "Clan Management", command);
sendClanManagement(activeChar, Integer.parseInt(st.nextToken()));
}
else if (clanCommand.equalsIgnoreCase("notice"))
{
CommunityBoard.getInstance().addBypass(activeChar, "Clan Notice", command);
if (st.hasMoreTokens())
{
final String noticeCommand = st.nextToken();
if (!noticeCommand.isEmpty() && (activeChar.getClan() != null))
{
activeChar.getClan().setNoticeEnabledAndStore(Boolean.parseBoolean(noticeCommand));
}
}
sendClanNotice(activeChar, activeChar.getClanId());
}
}
else
{
super.parseCmd(command, activeChar);
}
}
@Override
public void parseWrite(String ar1, String ar2, String ar3, String ar4, String ar5, PlayerInstance activeChar)
{
if (ar1.equalsIgnoreCase("intro"))
{
if (Integer.parseInt(ar2) != activeChar.getClanId())
{
return;
}
final Clan clan = ClanTable.getInstance().getClan(activeChar.getClanId());
if (clan == null)
{
return;
}
clan.setIntroduction(ar3, true);
sendClanManagement(activeChar, Integer.parseInt(ar2));
}
else if (ar1.equals("notice"))
{
activeChar.getClan().setNoticeAndStore(ar4);
sendClanNotice(activeChar, activeChar.getClanId());
}
else if (ar1.equalsIgnoreCase("mail"))
{
if (Integer.parseInt(ar2) != activeChar.getClanId())
{
return;
}
final Clan clan = ClanTable.getInstance().getClan(activeChar.getClanId());
if (clan == null)
{
return;
}
// Retrieve clans members, and store them under a String.
final StringBuilder membersList = new StringBuilder();
for (ClanMember player : clan.getMembers())
{
if (player != null)
{
if (membersList.length() > 0)
{
membersList.append(";");
}
membersList.append(player.getName());
}
}
MailBBSManager.getInstance().sendLetter(membersList.toString(), ar4, ar5, activeChar);
sendClanDetails(activeChar, activeChar.getClanId());
}
else
{
super.parseWrite(ar1, ar2, ar3, ar4, ar5, activeChar);
}
}
@Override
protected String getFolder()
{
return "clan/";
}
private void sendClanMail(PlayerInstance activeChar, int clanId)
{
final Clan clan = ClanTable.getInstance().getClan(clanId);
if (clan == null)
{
return;
}
if ((activeChar.getClanId() != clanId) || !activeChar.isClanLeader())
{
activeChar.sendPacket(SystemMessageId.ONLY_THE_CLAN_LEADER_IS_ENABLED);
sendClanList(activeChar, 1);
return;
}
String content = HtmCache.getInstance().getHtm(CB_PATH + "clan/clanhome-mail.htm");
content = content.replace("%clanid%", Integer.toString(clanId));
content = content.replace("%clanName%", clan.getName());
separateAndSend(content, activeChar);
}
private void sendClanManagement(PlayerInstance activeChar, int clanId)
{
final Clan clan = ClanTable.getInstance().getClan(clanId);
if (clan == null)
{
return;
}
if ((activeChar.getClanId() != clanId) || !activeChar.isClanLeader())
{
activeChar.sendPacket(SystemMessageId.ONLY_THE_CLAN_LEADER_IS_ENABLED);
sendClanList(activeChar, 1);
return;
}
String content = HtmCache.getInstance().getHtm(CB_PATH + "clan/clanhome-management.htm");
content = content.replace("%clanid%", Integer.toString(clan.getClanId()));
send1001(content, activeChar);
send1002(activeChar, clan.getIntroduction(), "", "");
}
private void sendClanNotice(PlayerInstance activeChar, int clanId)
{
final Clan clan = ClanTable.getInstance().getClan(clanId);
if ((clan == null) || (activeChar.getClanId() != clanId))
{
return;
}
if (clan.getLevel() < 2)
{
activeChar.sendPacket(SystemMessageId.THERE_ARE_NO_COMMUNITIES_IN_MY_CLAN_CLAN_COMMUNITIES_ARE_ALLOWED_FOR_CLANS_WITH_SKILL_LEVELS_OF_2_AND_HIGHER);
sendClanList(activeChar, 1);
return;
}
String content = HtmCache.getInstance().getHtm(CB_PATH + "clan/clanhome-notice.htm");
content = content.replace("%clanid%", Integer.toString(clan.getClanId()));
content = content.replace("%enabled%", "[" + String.valueOf(clan.isNoticeEnabled()) + "]");
content = content.replace("%flag%", String.valueOf(!clan.isNoticeEnabled()));
send1001(content, activeChar);
send1002(activeChar, clan.getNotice(), "", "");
}
private void sendClanList(PlayerInstance activeChar, int indexValue)
{
String content = HtmCache.getInstance().getHtm(CB_PATH + "clan/clanlist.htm");
// Player got a clan, show the associated header.
final StringBuilder sb = new StringBuilder();
final Clan playerClan = activeChar.getClan();
if (playerClan != null)
{
StringUtil.append(sb, "<table width=610 bgcolor=A7A19A><tr><td width=5></td><td width=605><a action=\"bypass _bbsclan;home;", playerClan.getClanId(), "\">[GO TO MY CLAN]</a></td></tr></table>");
}
content = content.replace("%homebar%", sb.toString());
int index = indexValue;
if (index < 1)
{
index = 1;
}
// Cleanup sb.
sb.setLength(0);
// List of clans.
int i = 0;
for (Clan cl : ClanTable.getInstance().getClans())
{
if (i > ((index + 1) * 7))
{
break;
}
if (i++ >= ((index - 1) * 7))
{
StringUtil.append(sb, "<table width=610><tr><td width=5></td><td width=150 align=center><a action=\"bypass _bbsclan;home;", cl.getClanId(), "\">", cl.getName(), "</a></td><td width=150 align=center>", cl.getLeaderName(), "</td><td width=100 align=center>", cl.getLevel(), "</td><td width=200 align=center>", cl.getMembersCount(), "</td><td width=5></td></tr></table><br1><img src=\"L2UI.Squaregray\" width=605 height=1><br1>");
}
}
sb.append("<table><tr>");
if (index == 1)
{
sb.append("<td><button action=\"\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16></td>");
}
else
{
StringUtil.append(sb, "<td><button action=\"_bbsclan;clan;", index - 1, "\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16 ></td>");
}
int nbp = ClanTable.getInstance().getClans().length / 8;
if ((nbp * 8) != ClanTable.getInstance().getClans().length)
{
nbp++;
}
for (i = 1; i <= nbp; i++)
{
if (i == index)
{
StringUtil.append(sb, "<td> ", i, " </td>");
}
else
{
StringUtil.append(sb, "<td><a action=\"bypass _bbsclan;clan;", i, "\"> ", i, " </a></td>");
}
}
if (index == nbp)
{
sb.append("<td><button action=\"\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16></td>");
}
else
{
StringUtil.append(sb, "<td><button action=\"bypass _bbsclan;clan;", index + 1, "\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
}
sb.append("</tr></table>");
content = content.replace("%clanlist%", sb.toString());
separateAndSend(content, activeChar);
}
private void sendClanDetails(PlayerInstance activeChar, int clanId)
{
final Clan clan = ClanTable.getInstance().getClan(clanId);
if (clan == null)
{
return;
}
if (clan.getLevel() < 2)
{
activeChar.sendPacket(SystemMessageId.THERE_ARE_NO_COMMUNITIES_IN_MY_CLAN_CLAN_COMMUNITIES_ARE_ALLOWED_FOR_CLANS_WITH_SKILL_LEVELS_OF_2_AND_HIGHER);
sendClanList(activeChar, 1);
return;
}
// Load different HTM following player case, 3 possibilities : randomer, member, clan leader.
String content;
if (activeChar.getClanId() != clanId)
{
content = HtmCache.getInstance().getHtm(CB_PATH + "clan/clanhome.htm");
}
else if (activeChar.isClanLeader())
{
content = HtmCache.getInstance().getHtm(CB_PATH + "clan/clanhome-leader.htm");
}
else
{
content = HtmCache.getInstance().getHtm(CB_PATH + "clan/clanhome-member.htm");
}
content = content.replace("%clanid%", Integer.toString(clan.getClanId()));
content = content.replace("%clanIntro%", clan.getIntroduction());
content = content.replace("%clanName%", clan.getName());
content = content.replace("%clanLvL%", Integer.toString(clan.getLevel()));
content = content.replace("%clanMembers%", Integer.toString(clan.getMembersCount()));
content = content.replace("%clanLeader%", clan.getLeaderName());
content = content.replace("%allyName%", (clan.getAllyId() > 0) ? clan.getAllyName() : "");
separateAndSend(content, activeChar);
}
private static class SingletonHolder
{
protected static final ClanBBSManager INSTANCE = new ClanBBSManager();
}
}

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.communitybbs.Manager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.communitybbs.CommunityBoard;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.util.Util;
/**
* Favorite board.
* @author Zoey76
*/
public class FavoriteBBSManager extends BaseBBSManager
{
// SQL Queries
private static final String SELECT_FAVORITES = "SELECT * FROM `bbs_favorites` WHERE `playerId`=? ORDER BY `favAddDate` DESC";
private static final String DELETE_FAVORITE = "DELETE FROM `bbs_favorites` WHERE `playerId`=? AND `favId`=?";
private static final String ADD_FAVORITE = "REPLACE INTO `bbs_favorites`(`playerId`, `favTitle`, `favBypass`) VALUES(?, ?, ?)";
protected FavoriteBBSManager()
{
}
public static FavoriteBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
@Override
public void parseCmd(String command, PlayerInstance player)
{
// None of this commands can be added to favorites.
if (command.startsWith("_bbsgetfav"))
{
// Load Favorite links
final String list = HtmCache.getInstance().getHtm(CB_PATH + "favorite_list.html");
final StringBuilder sb = new StringBuilder();
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement(SELECT_FAVORITES))
{
ps.setInt(1, player.getObjectId());
try (ResultSet rs = ps.executeQuery())
{
while (rs.next())
{
String link = list.replace("%fav_bypass%", rs.getString("favBypass"));
link = link.replace("%fav_title%", rs.getString("favTitle"));
final SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
link = link.replace("%fav_add_date%", date.format(rs.getTimestamp("favAddDate")));
link = link.replace("%fav_id%", String.valueOf(rs.getInt("favId")));
sb.append(link);
}
}
String html = HtmCache.getInstance().getHtm(CB_PATH + "favorite.html");
html = html.replace("%fav_list%", sb.toString());
separateAndSend(html, player);
}
catch (Exception e)
{
LOGGER.warning(FavoriteBBSManager.class.getSimpleName() + ": Couldn't load favorite links for player " + player.getName());
}
}
else if (command.startsWith("bbs_add_fav"))
{
final String bypass = CommunityBoard.getInstance().removeBypass(player);
if (bypass != null)
{
final String[] parts = bypass.split("&", 2);
if (parts.length != 2)
{
LOGGER.warning(FavoriteBBSManager.class.getSimpleName() + ": Couldn't add favorite link, " + bypass + " it's not a valid bypass!");
return;
}
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement(ADD_FAVORITE))
{
ps.setInt(1, player.getObjectId());
ps.setString(2, parts[0].trim());
ps.setString(3, parts[1].trim());
ps.execute();
// Callback
parseCmd("_bbsgetfav", player);
}
catch (Exception e)
{
LOGGER.warning(FavoriteBBSManager.class.getSimpleName() + ": Couldn't add favorite link " + command + " for player " + player.getName());
}
}
}
else if (command.startsWith("_bbsdelfav_"))
{
final String favId = command.replace("_bbsdelfav_", "");
if (!Util.isDigit(favId))
{
LOGGER.warning(FavoriteBBSManager.class.getSimpleName() + ": Couldn't delete favorite link, " + favId + " it's not a valid ID!");
return;
}
try (Connection con = DatabaseFactory.getConnection();
PreparedStatement ps = con.prepareStatement(DELETE_FAVORITE))
{
ps.setInt(1, player.getObjectId());
ps.setInt(2, Integer.parseInt(favId));
ps.execute();
// Callback
parseCmd("_bbsgetfav", player);
}
catch (Exception e)
{
LOGGER.warning(FavoriteBBSManager.class.getSimpleName() + ": Couldn't delete favorite link ID " + favId + " for player " + player.getName());
}
}
}
private static class SingletonHolder
{
protected static final FavoriteBBSManager INSTANCE = new FavoriteBBSManager();
}
}

View File

@@ -0,0 +1,127 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.communitybbs.Manager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.communitybbs.BB.Forum;
public class ForumsBBSManager extends BaseBBSManager
{
private final Collection<Forum> _table;
private int _lastid = 1;
public static ForumsBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
protected ForumsBBSManager()
{
_table = ConcurrentHashMap.newKeySet();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT forum_id FROM forums WHERE forum_type=0");
final ResultSet result = statement.executeQuery();
while (result.next())
{
addForum(new Forum(result.getInt("forum_id"), null));
}
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Data error on Forum (root): " + e.getMessage());
}
}
public void initRoot()
{
for (Forum f : _table)
{
f.vload();
}
LOGGER.info("Loaded " + _table.size() + " forums. Last forum id used: " + _lastid);
}
public void addForum(Forum ff)
{
if (ff == null)
{
return;
}
_table.add(ff);
if (ff.getID() > _lastid)
{
_lastid = ff.getID();
}
}
public Forum getForumByName(String name)
{
for (Forum f : _table)
{
if (f.getName().equals(name))
{
return f;
}
}
return null;
}
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;
}
public int getANewID()
{
return ++_lastid;
}
public Forum getForumByID(int id)
{
for (Forum f : _table)
{
if (f.getID() == id)
{
return f;
}
}
return null;
}
private static class SingletonHolder
{
protected static final ForumsBBSManager INSTANCE = new ForumsBBSManager();
}
}

View File

@@ -0,0 +1,371 @@
/*
* 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.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.commons.util.StringUtil;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.communitybbs.CommunityBoard;
import org.l2jmobius.gameserver.datatables.sql.CharNameTable;
import org.l2jmobius.gameserver.model.BlockList;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.FriendList;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
public class FriendsBBSManager extends BaseBBSManager
{
private static final String FRIENDLIST_DELETE_BUTTON = "<br>\n<table><tr><td width=10></td><td>Are you sure you want to delete all friends from your Friends List?</td><td width=20></td><td><button value=\"OK\" action=\"bypass _friend;delall\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"></td></tr></table>";
private static final String BLOCKLIST_DELETE_BUTTON = "<br>\n<table><tr><td width=10></td><td>Are you sure you want to delete all players from your Block List?</td><td width=20></td><td><button value=\"OK\" action=\"bypass _block;delall\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"></td></tr></table>";
protected FriendsBBSManager()
{
}
public static FriendsBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
@Override
public void parseCmd(String command, PlayerInstance activeChar)
{
if (command.startsWith("_friendlist"))
{
CommunityBoard.getInstance().addBypass(activeChar, "Friends List", command);
showFriendsList(activeChar, false);
}
else if (command.startsWith("_blocklist"))
{
CommunityBoard.getInstance().addBypass(activeChar, "Ignore List", command);
showBlockList(activeChar, false);
}
else if (command.startsWith("_friend"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
final String action = st.nextToken();
if (action.equals("select"))
{
activeChar.selectFriend((st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : 0);
showFriendsList(activeChar, false);
}
else if (action.equals("deselect"))
{
activeChar.deselectFriend((st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : 0);
showFriendsList(activeChar, false);
}
else if (action.equals("delall"))
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("DELETE FROM character_friends WHERE char_id = ? OR friend_id = ?");
statement.setInt(1, activeChar.getObjectId());
statement.setInt(2, activeChar.getObjectId());
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("could not delete friends objectid: " + e);
}
for (int friendId : activeChar.getFriendList())
{
final PlayerInstance player = World.getInstance().getPlayer(friendId);
if (player != null)
{
player.getFriendList().remove(Integer.valueOf(activeChar.getObjectId()));
player.getSelectedFriendList().remove(Integer.valueOf(activeChar.getObjectId()));
player.sendPacket(new FriendList(player)); // update friendList *heavy method*
}
}
activeChar.getFriendList().clear();
activeChar.getSelectedFriendList().clear();
showFriendsList(activeChar, false);
activeChar.sendMessage("You have cleared your friend list.");
activeChar.sendPacket(new FriendList(activeChar));
}
else if (action.equals("delconfirm"))
{
showFriendsList(activeChar, true);
}
else if (action.equals("del"))
{
try (Connection con = DatabaseFactory.getConnection())
{
for (int friendId : activeChar.getSelectedFriendList())
{
final PreparedStatement statement = con.prepareStatement("DELETE FROM character_friends WHERE (char_id = ? AND friend_id = ?) OR (char_id = ? AND friend_id = ?)");
statement.setInt(1, activeChar.getObjectId());
statement.setInt(2, friendId);
statement.setInt(3, friendId);
statement.setInt(4, activeChar.getObjectId());
statement.execute();
statement.close();
final String name = CharNameTable.getInstance().getPlayerName(friendId);
final PlayerInstance player = World.getInstance().getPlayer(friendId);
if (player != null)
{
player.getFriendList().remove(Integer.valueOf(activeChar.getObjectId()));
player.sendPacket(new FriendList(player)); // update friendList *heavy method*
}
// Player deleted from your friendlist
activeChar.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_HAS_BEEN_DELETED_FROM_YOUR_FRIENDS_LIST).addString(name));
activeChar.getFriendList().remove(Integer.valueOf(friendId));
}
}
catch (Exception e)
{
LOGGER.warning("could not delete friend objectid: " + e);
}
activeChar.getSelectedFriendList().clear();
showFriendsList(activeChar, false);
activeChar.sendPacket(new FriendList(activeChar)); // update friendList *heavy method*
}
else if (action.equals("mail"))
{
if (!activeChar.getSelectedFriendList().isEmpty())
{
showMailWrite(activeChar);
}
}
}
else if (command.startsWith("_block"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
final String action = st.nextToken();
if (action.equals("select"))
{
activeChar.selectBlock((st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : 0);
showBlockList(activeChar, false);
}
else if (action.equals("deselect"))
{
activeChar.deselectBlock((st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : 0);
showBlockList(activeChar, false);
}
else if (action.equals("delall"))
{
final List<Integer> list = new ArrayList<>();
list.addAll(activeChar.getBlockList().getBlockList());
for (Integer blockId : list)
{
BlockList.removeFromBlockList(activeChar, blockId);
}
activeChar.getSelectedBlocksList().clear();
showBlockList(activeChar, false);
}
else if (action.equals("delconfirm"))
{
showBlockList(activeChar, true);
}
else if (action.equals("del"))
{
for (Integer blockId : activeChar.getSelectedBlocksList())
{
BlockList.removeFromBlockList(activeChar, blockId);
}
activeChar.getSelectedBlocksList().clear();
showBlockList(activeChar, false);
}
}
else
{
super.parseCmd(command, activeChar);
}
}
@Override
public void parseWrite(String ar1, String ar2, String ar3, String ar4, String ar5, PlayerInstance activeChar)
{
if (ar1.equalsIgnoreCase("mail"))
{
MailBBSManager.getInstance().sendLetter(ar2, ar4, ar5, activeChar);
showFriendsList(activeChar, false);
}
else
{
super.parseWrite(ar1, ar2, ar3, ar4, ar5, activeChar);
}
}
private void showFriendsList(PlayerInstance activeChar, boolean delMsg)
{
String content = HtmCache.getInstance().getHtm(CB_PATH + "friend/friend-list.htm");
if (content == null)
{
return;
}
// Retrieve activeChar's friendlist and selected
final List<Integer> list = activeChar.getFriendList();
final List<Integer> slist = activeChar.getSelectedFriendList();
final StringBuilder sb = new StringBuilder();
// Friendlist
for (Integer id : list)
{
if (slist.contains(id))
{
continue;
}
final String friendName = CharNameTable.getInstance().getPlayerName(id);
if (friendName == null)
{
continue;
}
final PlayerInstance friend = World.getInstance().getPlayer(id);
StringUtil.append(sb, "<a action=\"bypass _friend;select;", id, "\">[Select]</a>&nbsp;", friendName, " ", (((friend != null) && friend.isOnline()) ? "(on)" : "(off)"), "<br1>");
}
content = content.replace("%friendslist%", sb.toString());
// Cleanup sb.
sb.setLength(0);
// Selected friendlist
for (Integer id : slist)
{
final String friendName = CharNameTable.getInstance().getPlayerName(id);
if (friendName == null)
{
continue;
}
final PlayerInstance friend = World.getInstance().getPlayer(id);
StringUtil.append(sb, "<a action=\"bypass _friend;deselect;", id, "\">[Deselect]</a>&nbsp;", friendName, " ", (((friend != null) && friend.isOnline()) ? "(on)" : "(off)"), "<br1>");
}
content = content.replace("%selectedFriendsList%", sb.toString());
// Delete button.
content = content.replace("%deleteMSG%", (delMsg) ? FRIENDLIST_DELETE_BUTTON : "");
separateAndSend(content, activeChar);
}
private void showBlockList(PlayerInstance activeChar, boolean delMsg)
{
String content = HtmCache.getInstance().getHtm(CB_PATH + "friend/friend-blocklist.htm");
if (content == null)
{
return;
}
// Retrieve activeChar's blocklist and selected
final List<Integer> list = activeChar.getBlockList().getBlockList();
final List<Integer> slist = activeChar.getSelectedBlocksList();
final StringBuilder sb = new StringBuilder();
// Blocklist
for (Integer id : list)
{
if (slist.contains(id))
{
continue;
}
final String blockName = CharNameTable.getInstance().getPlayerName(id);
if (blockName == null)
{
continue;
}
final PlayerInstance block = World.getInstance().getPlayer(id);
StringUtil.append(sb, "<a action=\"bypass _block;select;", id, "\">[Select]</a>&nbsp;", blockName, " ", (((block != null) && block.isOnline()) ? "(on)" : "(off)"), "<br1>");
}
content = content.replace("%blocklist%", sb.toString());
// Cleanup sb.
sb.setLength(0);
// Selected Blocklist
for (Integer id : slist)
{
final String blockName = CharNameTable.getInstance().getPlayerName(id);
if (blockName == null)
{
continue;
}
final PlayerInstance block = World.getInstance().getPlayer(id);
StringUtil.append(sb, "<a action=\"bypass _block;deselect;", id, "\">[Deselect]</a>&nbsp;", blockName, " ", (((block != null) && block.isOnline()) ? "(on)" : "(off)"), "<br1>");
}
content = content.replace("%selectedBlocksList%", sb.toString());
// Delete button.
content = content.replace("%deleteMSG%", (delMsg) ? BLOCKLIST_DELETE_BUTTON : "");
separateAndSend(content, activeChar);
}
public static void showMailWrite(PlayerInstance activeChar)
{
String content = HtmCache.getInstance().getHtm(CB_PATH + "friend/friend-mail.htm");
if (content == null)
{
return;
}
final StringBuilder sb = new StringBuilder();
for (int id : activeChar.getSelectedFriendList())
{
final String friendName = CharNameTable.getInstance().getPlayerName(id);
if (friendName == null)
{
continue;
}
if (sb.length() > 0)
{
sb.append(";");
}
sb.append(friendName);
}
content = content.replace("%list%", sb.toString());
separateAndSend(content, activeChar);
}
@Override
protected String getFolder()
{
return "friend/";
}
private static class SingletonHolder
{
protected static final FriendsBBSManager INSTANCE = new FriendsBBSManager();
}
}

View File

@@ -0,0 +1,841 @@
/*
* 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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.commons.util.StringUtil;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.communitybbs.CommunityBoard;
import org.l2jmobius.gameserver.datatables.sql.CharNameTable;
import org.l2jmobius.gameserver.model.BlockList;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.ExMailArrived;
import org.l2jmobius.gameserver.network.serverpackets.PlaySound;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
/**
* @author JIV, Johan, Vital
*/
public class MailBBSManager extends BaseBBSManager
{
private enum MailType
{
INBOX("Inbox", "<a action=\"bypass _bbsmail\">Inbox</a>"),
SENTBOX("Sent Box", "<a action=\"bypass _bbsmail;sentbox\">Sent Box</a>"),
ARCHIVE("Mail Archive", "<a action=\"bypass _bbsmail;archive\">Mail Archive</a>"),
TEMPARCHIVE("Temporary Mail Archive", "<a action=\"bypass _bbsmail;temp_archive\">Temporary Mail Archive</a>");
private final String _description;
private final String _bypass;
private MailType(String description, String bypass)
{
_description = description;
_bypass = bypass;
}
public String getDescription()
{
return _description;
}
public String getBypass()
{
return _bypass;
}
public static final MailType[] VALUES = values();
}
private final Map<Integer, List<Mail>> _mails = new HashMap<>();
private int _lastid = 0;
private static final String SELECT_CHAR_MAILS = "SELECT * FROM character_mail WHERE charId = ? ORDER BY letterId ASC";
private static final String INSERT_NEW_MAIL = "INSERT INTO character_mail (charId, letterId, senderId, location, recipientNames, subject, message, sentDate, unread) VALUES (?,?,?,?,?,?,?,?,?)";
private static final String DELETE_MAIL = "DELETE FROM character_mail WHERE letterId = ?";
private static final String MARK_MAIL_READ = "UPDATE character_mail SET unread = ? WHERE letterId = ?";
private static final String SET_LETTER_LOC = "UPDATE character_mail SET location = ? WHERE letterId = ?";
private static final String SELECT_LAST_ID = "SELECT letterId FROM character_mail ORDER BY letterId DESC LIMIT 1";
public class Mail
{
int charId;
int letterId;
int senderId;
MailType location;
String recipientNames;
String subject;
String message;
Timestamp sentDate;
String sentDateString;
boolean unread;
}
public static MailBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
protected MailBBSManager()
{
initId();
}
@Override
public void parseCmd(String command, PlayerInstance activeChar)
{
CommunityBoard.getInstance().addBypass(activeChar, "Mail Command", command);
if (command.equals("_bbsmail") || command.equals("_maillist_0_1_0_"))
{
showMailList(activeChar, 1, MailType.INBOX);
}
else if (command.startsWith("_bbsmail"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
final String action = st.nextToken();
if (action.equals("inbox") || action.equals("sentbox") || action.equals("archive") || action.equals("temparchive"))
{
final int page = (st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : 1;
final String sType = (st.hasMoreTokens()) ? st.nextToken() : "";
final String search = (st.hasMoreTokens()) ? st.nextToken() : "";
showMailList(activeChar, page, Enum.valueOf(MailType.class, action.toUpperCase()), sType, search);
}
else if (action.equals("crea"))
{
showWriteView(activeChar);
}
else if (action.equals("view"))
{
final int letterId = (st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : -1;
final Mail letter = getLetter(activeChar, letterId);
if (letter == null)
{
showLastForum(activeChar);
}
else
{
showLetterView(activeChar, letter);
if (letter.unread)
{
setLetterToRead(activeChar, letter.letterId);
}
}
}
else if (action.equals("reply"))
{
final int letterId = (st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : -1;
final Mail letter = getLetter(activeChar, letterId);
if (letter == null)
{
showLastForum(activeChar);
}
else
{
showWriteView(activeChar, getCharName(letter.senderId), letter);
}
}
else if (action.equals("del"))
{
final int letterId = (st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : -1;
final Mail letter = getLetter(activeChar, letterId);
if (letter != null)
{
deleteLetter(activeChar, letter.letterId);
}
showLastForum(activeChar);
}
else if (action.equals("store"))
{
final int letterId = (st.hasMoreTokens()) ? Integer.parseInt(st.nextToken()) : -1;
final Mail letter = getLetter(activeChar, letterId);
if (letter != null)
{
setLetterLocation(activeChar, letter.letterId, MailType.ARCHIVE);
}
showMailList(activeChar, 1, MailType.ARCHIVE);
}
}
else
{
super.parseCmd(command, activeChar);
}
}
@Override
public void parseWrite(String ar1, String ar2, String ar3, String ar4, String ar5, PlayerInstance activeChar)
{
if (ar1.equals("Send"))
{
sendLetter(ar3, ar4, ar5, activeChar);
showMailList(activeChar, 1, MailType.SENTBOX);
}
else if (ar1.startsWith("Search"))
{
final StringTokenizer st = new StringTokenizer(ar1, ";");
st.nextToken();
showMailList(activeChar, 1, Enum.valueOf(MailType.class, st.nextToken().toUpperCase()), ar4, ar5);
}
else
{
super.parseWrite(ar1, ar2, ar3, ar4, ar5, activeChar);
}
}
private void initId()
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement(SELECT_LAST_ID);
final ResultSet result = statement.executeQuery();
while (result.next())
{
if (result.getInt(1) > _lastid)
{
_lastid = result.getInt(1);
}
}
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning(getClass().getSimpleName() + ": data error on MailBBS (initId): " + e);
e.printStackTrace();
}
}
private synchronized int getNewMailId()
{
return ++_lastid;
}
private List<Mail> getPlayerMails(int objId)
{
List<Mail> letters = _mails.get(objId);
if (letters == null)
{
letters = new ArrayList<>();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement(SELECT_CHAR_MAILS);
statement.setInt(1, objId);
final ResultSet result = statement.executeQuery();
while (result.next())
{
final Mail letter = new Mail();
letter.charId = result.getInt("charId");
letter.letterId = result.getInt("letterId");
letter.senderId = result.getInt("senderId");
letter.location = Enum.valueOf(MailType.class, result.getString("location").toUpperCase());
letter.recipientNames = result.getString("recipientNames");
letter.subject = result.getString("subject");
letter.message = result.getString("message");
letter.sentDate = result.getTimestamp("sentDate");
letter.sentDateString = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(letter.sentDate);
letter.unread = result.getInt("unread") != 0;
letters.add(0, letter);
}
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("couldnt load mail for ID:" + objId + " " + e.getMessage());
}
_mails.put(objId, letters);
}
return letters;
}
private Mail getLetter(PlayerInstance activeChar, int letterId)
{
for (Mail letter : getPlayerMails(activeChar.getObjectId()))
{
if (letter.letterId == letterId)
{
return letter;
}
}
return null;
}
private static String abbreviate(String s, int maxWidth)
{
return s.length() > maxWidth ? s.substring(0, maxWidth) : s;
}
public int checkUnreadMail(PlayerInstance activeChar)
{
int count = 0;
for (Mail letter : getPlayerMails(activeChar.getObjectId()))
{
if (letter.unread)
{
count++;
}
}
return count;
}
private void showMailList(PlayerInstance activeChar, int page, MailType type)
{
showMailList(activeChar, page, type, "", "");
}
private void showMailList(PlayerInstance activeChar, int pageValue, MailType type, String sType, String search)
{
List<Mail> letters;
if (!sType.equals("") && !search.equals(""))
{
letters = new ArrayList<>();
final boolean byTitle = sType.equalsIgnoreCase("title");
for (Mail letter : getPlayerMails(activeChar.getObjectId()))
{
if (byTitle && letter.subject.toLowerCase().contains(search.toLowerCase()))
{
letters.add(letter);
}
else if (!byTitle)
{
final String writer = getCharName(letter.senderId);
if (writer.toLowerCase().contains(search.toLowerCase()))
{
letters.add(letter);
}
}
}
}
else
{
letters = getPlayerMails(activeChar.getObjectId());
}
final int countMails = getCountLetters(activeChar.getObjectId(), type, sType, search);
final int maxpage = getMaxPageId(countMails);
int page = pageValue;
if (page > maxpage)
{
page = maxpage;
}
if (page < 1)
{
page = 1;
}
activeChar.setMailPosition(page);
int index = 0;
int minIndex = 0;
int maxIndex = 0;
maxIndex = (page == 1 ? page * 9 : (page * 10) - 1);
minIndex = maxIndex - 9;
String content = HtmCache.getInstance().getHtm(CB_PATH + "mail/mail.htm");
content = content.replace("%inbox%", Integer.toString(getCountLetters(activeChar.getObjectId(), MailType.INBOX, "", "")));
content = content.replace("%sentbox%", Integer.toString(getCountLetters(activeChar.getObjectId(), MailType.SENTBOX, "", "")));
content = content.replace("%archive%", Integer.toString(getCountLetters(activeChar.getObjectId(), MailType.ARCHIVE, "", "")));
content = content.replace("%temparchive%", Integer.toString(getCountLetters(activeChar.getObjectId(), MailType.TEMPARCHIVE, "", "")));
content = content.replace("%type%", type.getDescription());
content = content.replace("%htype%", type.toString().toLowerCase());
final StringBuilder sb = new StringBuilder();
for (Mail letter : letters)
{
if (letter.location.equals(type))
{
if (index < minIndex)
{
index++;
continue;
}
if (index > maxIndex)
{
break;
}
StringUtil.append(sb, "<table width=610><tr><td width=5></td><td width=150>", getCharName(letter.senderId), "</td><td width=300><a action=\"bypass _bbsmail;view;", letter.letterId, "\">");
if (letter.unread)
{
sb.append("<font color=\"LEVEL\">");
}
sb.append(abbreviate(letter.subject, 51));
if (letter.unread)
{
sb.append("</font>");
}
StringUtil.append(sb, "</a></td><td width=150>", letter.sentDateString, "</td><td width=5></td></tr></table><img src=\"L2UI.Squaregray\" width=610 height=1>");
index++;
}
}
content = content.replace("%maillist%", sb.toString());
// CLeanup sb.
sb.setLength(0);
final String fullSearch = (!sType.equals("") && !search.equals("")) ? ";" + sType + ";" + search : "";
StringUtil.append(sb, "<td><table><tr><td></td></tr><tr><td><button action=\"bypass _bbsmail;", type, ";", (page == 1 ? page : page - 1), fullSearch, "\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16></td></tr></table></td>");
int i = 0;
if (maxpage > 21)
{
if (page <= 11)
{
for (i = 1; i <= (10 + page); i++)
{
if (i == page)
{
StringUtil.append(sb, "<td> ", i, " </td>");
}
else
{
StringUtil.append(sb, "<td><a action=\"bypass _bbsmail;", type, ";", i, fullSearch, "\"> ", i, " </a></td>");
}
}
}
else if ((page > 11) && ((maxpage - page) > 10))
{
for (i = (page - 10); i <= (page - 1); i++)
{
if (i == page)
{
continue;
}
StringUtil.append(sb, "<td><a action=\"bypass _bbsmail;", type, ";", i, fullSearch, "\"> ", i, " </a></td>");
}
for (i = page; i <= (page + 10); i++)
{
if (i == page)
{
StringUtil.append(sb, "<td> ", i, " </td>");
}
else
{
StringUtil.append(sb, "<td><a action=\"bypass _bbsmail;", type, ";", i, fullSearch, "\"> ", i, " </a></td>");
}
}
}
else if ((maxpage - page) <= 10)
{
for (i = (page - 10); i <= maxpage; i++)
{
if (i == page)
{
StringUtil.append(sb, "<td> ", i, " </td>");
}
else
{
StringUtil.append(sb, "<td><a action=\"bypass _bbsmail;", type, ";", i, fullSearch, "\"> ", i, " </a></td>");
}
}
}
}
else
{
for (i = 1; i <= maxpage; i++)
{
if (i == page)
{
StringUtil.append(sb, "<td> ", i, " </td>");
}
else
{
StringUtil.append(sb, "<td><a action=\"bypass _bbsmail;", type, ";", i, fullSearch, "\"> ", i, " </a></td>");
}
}
}
StringUtil.append(sb, "<td><table><tr><td></td></tr><tr><td><button action=\"bypass _bbsmail;", type, ";", (page == maxpage ? page : page + 1), fullSearch, "\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td></tr></table></td>");
content = content.replace("%maillistlength%", sb.toString());
separateAndSend(content, activeChar);
}
private void showLetterView(PlayerInstance activeChar, Mail letter)
{
if (letter == null)
{
showMailList(activeChar, 1, MailType.INBOX);
return;
}
String content = HtmCache.getInstance().getHtm(CB_PATH + "mail/mail-show.htm");
final String link = letter.location.getBypass() + "&nbsp;&gt;&nbsp;" + letter.subject;
content = content.replace("%maillink%", link);
content = content.replace("%writer%", getCharName(letter.senderId));
content = content.replace("%sentDate%", letter.sentDateString);
content = content.replace("%receiver%", letter.recipientNames);
content = content.replace("%delDate%", "Unknown");
content = content.replace("%title%", letter.subject.replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;"));
content = content.replace("%mes%", letter.message.replace("\r\n", "<br>").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;"));
content = content.replace("%letterId%", letter.letterId + "");
separateAndSend(content, activeChar);
}
private void showWriteView(PlayerInstance activeChar)
{
final String content = HtmCache.getInstance().getHtm(CB_PATH + "mail/mail-write.htm");
separateAndSend(content, activeChar);
}
private void showWriteView(PlayerInstance activeChar, String parcipientName, Mail letter)
{
String content = HtmCache.getInstance().getHtm(CB_PATH + "mail/mail-reply.htm");
final String link = letter.location.getBypass() + "&nbsp;&gt;&nbsp;<a action=\"bypass _bbsmail;view;" + letter.letterId + "\">" + letter.subject + "</a>&nbsp;&gt;&nbsp;";
content = content.replace("%maillink%", link);
content = content.replace("%recipients%", letter.senderId == activeChar.getObjectId() ? letter.recipientNames : getCharName(letter.senderId));
content = content.replace("%letterId%", letter.letterId + "");
send1001(content, activeChar);
send1002(activeChar, " ", "Re: " + letter.subject, "0");
}
public void sendLetter(String recipients, String subjectValue, String messageValue, PlayerInstance activeChar)
{
// Current time.
final long currentDate = Calendar.getInstance().getTimeInMillis();
// Get the current time - 1 day under timestamp format.
final Timestamp ts = new Timestamp(currentDate - 86400000);
// Check sender mails based on previous timestamp. If more than 10 mails have been found for today, then cancel the use.
if (getPlayerMails(activeChar.getObjectId()).stream().filter(l -> l.sentDate.after(ts) && (l.location == MailType.SENTBOX)).count() >= 10)
{
activeChar.sendPacket(SystemMessageId.NO_MORE_MESSAGES_MAY_BE_SENT_AT_THIS_TIME_EACH_ACCOUNT_IS_ALLOWED_10_MESSAGES_PER_DAY);
return;
}
// Format recipient names. If more than 5 are found, cancel the mail.
final String[] recipientNames = recipients.trim().split(";");
if ((recipientNames.length > 5) && !activeChar.isGM())
{
activeChar.sendPacket(SystemMessageId.YOU_ARE_LIMITED_TO_FIVE_RECIPIENTS_AT_A_TIME);
return;
}
// Edit subject, if none.
String subject = subjectValue;
if ((subject == null) || subject.isEmpty())
{
subject = "(no subject)";
}
// Edit message.
String message = messageValue.replace("\n", "<br1>");
try (Connection con = DatabaseFactory.getConnection())
{
// Get the current time under timestamp format.
final Timestamp time = new Timestamp(currentDate);
PreparedStatement statement = null;
for (String recipientName : recipientNames)
{
// Recipient is an invalid player, or is the sender.
final int recipientId = CharNameTable.getInstance().getPlayerObjectId(recipientName);
if ((recipientId <= 0) || (recipientId == activeChar.getObjectId()))
{
activeChar.sendPacket(SystemMessageId.INVALID_TARGET);
continue;
}
final PlayerInstance recipientPlayer = World.getInstance().getPlayer(recipientId);
if (!activeChar.isGM())
{
// Sender is a regular player, while recipient is a GM.
if (isGM(recipientId))
{
activeChar.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOUR_MESSAGE_TO_S1_DID_NOT_REACH_IT_S_RECIPIENT_YOU_CANNOT_SEND_MAIL_TO_THE_GM_STAFF).addString(recipientName));
continue;
}
// The recipient is on block mode.
if (isBlocked(activeChar, recipientId))
{
activeChar.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_HAS_BLOCKED_YOU_YOU_CANNOT_SEND_MAIL_TO_S1).addString(recipientName));
continue;
}
// The recipient box is already full.
if (isRecipInboxFull(recipientId))
{
activeChar.sendPacket(SystemMessageId.THE_MESSAGE_WAS_NOT_SENT);
if (recipientPlayer != null)
{
recipientPlayer.sendPacket(SystemMessageId.YOUR_MAILBOX_IS_FULL_THERE_IS_A_100_MESSAGE_LIMIT);
}
continue;
}
}
final int id = getNewMailId();
if (statement == null)
{
statement = con.prepareStatement(INSERT_NEW_MAIL);
statement.setInt(3, activeChar.getObjectId());
statement.setString(4, "inbox");
statement.setString(5, recipients);
statement.setString(6, abbreviate(subject, 128));
statement.setString(7, message);
statement.setTimestamp(8, time);
statement.setInt(9, 1);
}
statement.setInt(1, recipientId);
statement.setInt(2, id);
statement.execute();
final Mail letter = new Mail();
letter.charId = recipientId;
letter.letterId = id;
letter.senderId = activeChar.getObjectId();
letter.location = MailType.INBOX;
letter.recipientNames = recipients;
letter.subject = abbreviate(subject, 128);
letter.message = message;
letter.sentDate = time;
letter.sentDateString = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(letter.sentDate);
letter.unread = true;
getPlayerMails(recipientId).add(0, letter);
if (recipientPlayer != null)
{
recipientPlayer.sendPacket(SystemMessageId.YOU_VE_GOT_MAIL);
recipientPlayer.sendPacket(new PlaySound("systemmsg_e.1233"));
recipientPlayer.sendPacket(ExMailArrived.STATIC_PACKET);
}
}
// Create a copy into activeChar's sent box, if at least one recipient has been reached.
if (statement != null)
{
final int id = getNewMailId();
statement.setInt(1, activeChar.getObjectId());
statement.setInt(2, id);
statement.setString(4, "sentbox");
statement.setInt(9, 0);
statement.execute();
statement.close();
final Mail letter = new Mail();
letter.charId = activeChar.getObjectId();
letter.letterId = id;
letter.senderId = activeChar.getObjectId();
letter.location = MailType.SENTBOX;
letter.recipientNames = recipients;
letter.subject = abbreviate(subject, 128);
letter.message = message;
letter.sentDate = time;
letter.sentDateString = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(letter.sentDate);
letter.unread = false;
getPlayerMails(activeChar.getObjectId()).add(0, letter);
activeChar.sendPacket(SystemMessageId.YOU_VE_SENT_MAIL);
}
}
catch (Exception e)
{
LOGGER.warning("couldnt send letter for " + activeChar.getName() + " " + e.getMessage());
}
}
private int getCountLetters(int objId, MailType location, String sType, String search)
{
int count = 0;
if (!sType.equals("") && !search.equals(""))
{
final boolean byTitle = sType.equalsIgnoreCase("title");
for (Mail letter : getPlayerMails(objId))
{
if (!letter.location.equals(location))
{
continue;
}
if (byTitle && letter.subject.toLowerCase().contains(search.toLowerCase()))
{
count++;
}
else if (!byTitle)
{
final String writer = getCharName(letter.senderId);
if (writer.toLowerCase().contains(search.toLowerCase()))
{
count++;
}
}
}
}
else
{
for (Mail letter : getPlayerMails(objId))
{
if (letter.location.equals(location))
{
count++;
}
}
}
return count;
}
private static boolean isBlocked(PlayerInstance activeChar, int recipId)
{
for (PlayerInstance player : World.getInstance().getAllPlayers())
{
if (player.getObjectId() == recipId)
{
return BlockList.isInBlockList(player, activeChar);
}
}
return false;
}
private void deleteLetter(PlayerInstance activeChar, int letterId)
{
for (Mail letter : getPlayerMails(activeChar.getObjectId()))
{
if (letter.letterId == letterId)
{
getPlayerMails(activeChar.getObjectId()).remove(letter);
break;
}
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement(DELETE_MAIL);
statement.setInt(1, letterId);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("couldnt delete letter " + letterId + " " + e);
}
}
private void setLetterToRead(PlayerInstance activeChar, int letterId)
{
getLetter(activeChar, letterId).unread = false;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement(MARK_MAIL_READ);
statement.setInt(1, 0);
statement.setInt(2, letterId);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("couldnt set unread to false for " + letterId + " " + e);
}
}
private void setLetterLocation(PlayerInstance activeChar, int letterId, MailType location)
{
getLetter(activeChar, letterId).location = location;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement(SET_LETTER_LOC);
statement.setString(1, location.toString().toLowerCase());
statement.setInt(2, letterId);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("couldnt set location to false for " + letterId + " " + e);
}
}
private static String getCharName(int charId)
{
final String name = CharNameTable.getInstance().getPlayerName(charId);
return name == null ? "Unknown" : name;
}
private static boolean isGM(int charId)
{
boolean isGM = false;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT accesslevel FROM characters WHERE obj_Id = ?");
statement.setInt(1, charId);
final ResultSet result = statement.executeQuery();
result.next();
isGM = result.getInt(1) > 0;
result.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning(e.getMessage());
}
return isGM;
}
private boolean isRecipInboxFull(int charId)
{
return getCountLetters(charId, MailType.INBOX, "", "") >= 100;
}
private void showLastForum(PlayerInstance activeChar)
{
final int page = activeChar.getMailPosition() % 1000;
final int type = activeChar.getMailPosition() / 1000;
showMailList(activeChar, page, MailType.VALUES[type]);
}
private static int getMaxPageId(int letterCount)
{
if (letterCount < 1)
{
return 1;
}
if ((letterCount % 10) == 0)
{
return letterCount / 10;
}
return (letterCount / 10) + 1;
}
private static class SingletonHolder
{
protected static final MailBBSManager INSTANCE = new MailBBSManager();
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import org.l2jmobius.gameserver.communitybbs.CommunityBoard;
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.model.actor.instance.PlayerInstance;
public class PostBBSManager extends BaseBBSManager
{
private final Map<Topic, Post> _postByTopic;
public static PostBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
protected PostBBSManager()
{
_postByTopic = new HashMap<>();
}
@Override
public void parseCmd(String command, PlayerInstance player)
{
if (command.startsWith("_bbsposts;read;"))
{
CommunityBoard.getInstance().addBypass(player, "Posts Command", command);
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final int idf = Integer.parseInt(st.nextToken());
final int idp = Integer.parseInt(st.nextToken());
String index = null;
if (st.hasMoreTokens())
{
index = st.nextToken();
}
int ind = 0;
if (index == null)
{
ind = 1;
}
else
{
ind = 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
{
super.parseCmd(command, 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 forum = ForumsBBSManager.getInstance().getForumByID(idf);
if (forum == null)
{
separateAndSend("<html><body><br><br><center>The forum named '" + idf + "' doesn't exist.</center></body></html>", player);
return;
}
final Topic topic = forum.getTopic(idt);
if (topic == null)
{
separateAndSend("<html><body><br><br><center>The topic named '" + idt + "' doesn't exist.</center></body></html>", player);
return;
}
final Post post = getPostByTopic(topic);
if (post.getCPost(idp) == null)
{
separateAndSend("<html><body><br><br><center>The post named '" + idp + "' doesn't exist.</center></body></html>", player);
return;
}
post.getCPost(idp).postTxt = ar4;
post.updateText(idp);
parseCmd("_bbsposts;read;" + forum.getID() + ";" + topic.getID(), player);
}
public Post getPostByTopic(Topic t)
{
Post post = _postByTopic.get(t);
if (post == null)
{
post = load(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);
}
}
private static Post load(Topic t)
{
return new Post(t);
}
private void showEditPost(Topic topic, Forum forum, PlayerInstance player, int idp)
{
if ((forum == null) || (topic == null))
{
separateAndSend("<html><body><br><br><center>This forum and/or topic don't exit.</center></body></html>", player);
return;
}
final Post p = getPostByTopic(topic);
if (p == null)
{
separateAndSend("<html><body><br><br><center>This post doesn't exist.</center></body></html>", player);
return;
}
showHtmlEditPost(topic, player, forum, p);
}
private void showPost(Topic topic, Forum forum, PlayerInstance player, int ind)
{
if ((forum == null) || (topic == null))
{
separateAndSend("<html><body><br><br><center>This forum and/or topic don't exist.</center></body></html>", player);
}
else if (forum.getType() == Forum.MEMO)
{
showMemoPost(topic, player, forum);
}
else
{
separateAndSend("<html><body><br><br><center>The forum named '" + forum.getName() + "' isn't implemented.</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 = getPostByTopic(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;");
mes = mes.replace("\n", "<br1>");
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>";
separateAndSend(html, player);
}
private static class SingletonHolder
{
protected static final PostBBSManager INSTANCE = new PostBBSManager();
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.SimpleDateFormat;
import java.util.List;
import java.util.StringTokenizer;
import org.l2jmobius.commons.util.StringUtil;
import org.l2jmobius.gameserver.cache.HtmCache;
import org.l2jmobius.gameserver.communitybbs.CommunityBoard;
import org.l2jmobius.gameserver.datatables.sql.ClanTable;
import org.l2jmobius.gameserver.instancemanager.CastleManager;
import org.l2jmobius.gameserver.instancemanager.ClanHallManager;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.clan.Clan;
import org.l2jmobius.gameserver.model.entity.ClanHall;
import org.l2jmobius.gameserver.model.entity.siege.Castle;
public class RegionBBSManager extends BaseBBSManager
{
protected RegionBBSManager()
{
}
public static RegionBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
@Override
public void parseCmd(String command, PlayerInstance player)
{
if (command.equals("_bbsloc"))
{
CommunityBoard.getInstance().addBypass(player, "Region>", command);
showRegionsList(player);
}
else if (command.startsWith("_bbsloc"))
{
CommunityBoard.getInstance().addBypass(player, "Region>", command);
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
showRegion(player, Integer.parseInt(st.nextToken()));
}
else
{
super.parseCmd(command, player);
}
}
@Override
protected String getFolder()
{
return "region/";
}
private void showRegionsList(PlayerInstance player)
{
final String content = HtmCache.getInstance().getHtm(CB_PATH + "region/castlelist.htm");
final StringBuilder sb = new StringBuilder(500);
for (Castle castle : CastleManager.getInstance().getCastles())
{
final Clan owner = ClanTable.getInstance().getClan(castle.getOwnerId());
StringUtil.append(sb, "<table><tr><td width=5></td><td width=160><a action=\"bypass _bbsloc;", castle.getCastleId(), "\">", castle.getName(), "</a></td><td width=160>", ((owner != null) ? "<a action=\"bypass _bbsclan;home;" + owner.getClanId() + "\">" + owner.getName() + "</a>" : "None"), "</td><td width=160>", (((owner != null) && (owner.getAllyId() > 0)) ? owner.getAllyName() : "None"), "</td><td width=120>", ((owner != null) ? castle.getTaxPercent() : "0"), "</td><td width=5></td></tr></table><br1><img src=\"L2UI.Squaregray\" width=605 height=1><br1>");
}
separateAndSend(content.replace("%castleList%", sb.toString()), player);
}
private void showRegion(PlayerInstance player, int castleId)
{
final Castle castle = CastleManager.getInstance().getCastleById(castleId);
final Clan owner = ClanTable.getInstance().getClan(castle.getOwnerId());
String content = HtmCache.getInstance().getHtm(CB_PATH + "region/castle.htm");
content = content.replace("%castleName%", castle.getName());
content = content.replace("%tax%", Integer.toString(castle.getTaxPercent()));
content = content.replace("%lord%", ((owner != null) ? owner.getLeaderName() : "None"));
content = content.replace("%clanName%", ((owner != null) ? "<a action=\"bypass _bbsclan;home;" + owner.getClanId() + "\">" + owner.getName() + "</a>" : "None"));
content = content.replace("%allyName%", (((owner != null) && (owner.getAllyId() > 0)) ? owner.getAllyName() : "None"));
content = content.replace("%siegeDate%", new SimpleDateFormat("yyyy-MM-dd HH:mm").format(castle.getSiegeDate().getTimeInMillis()));
final StringBuilder sb = new StringBuilder(200);
final List<ClanHall> clanHalls = ClanHallManager.getInstance().getClanHallsByLocation(castle.getName());
if ((clanHalls != null) && !clanHalls.isEmpty())
{
sb.append("<br><br><table width=610 bgcolor=A7A19A><tr><td width=5></td><td width=200>Clan Hall Name</td><td width=200>Owning Clan</td><td width=200>Clan Leader Name</td><td width=5></td></tr></table><br1>");
for (ClanHall ch : clanHalls)
{
final Clan chOwner = ClanTable.getInstance().getClan(ch.getOwnerId());
StringUtil.append(sb, "<table><tr><td width=5></td><td width=200>", ch.getName(), "</td><td width=200>", ((chOwner != null) ? "<a action=\"bypass _bbsclan;home;" + chOwner.getClanId() + "\">" + chOwner.getName() + "</a>" : "None"), "</td><td width=200>", ((chOwner != null) ? chOwner.getLeaderName() : "None"), "</td><td width=5></td></tr></table><br1><img src=\"L2UI.Squaregray\" width=605 height=1><br1>");
}
}
separateAndSend(content.replace("%hallsList%", sb.toString()), player);
}
private static class SingletonHolder
{
protected static final RegionBBSManager INSTANCE = new RegionBBSManager();
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.StringTokenizer;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
public class TopBBSManager extends BaseBBSManager
{
protected TopBBSManager()
{
}
public static TopBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
@Override
public void parseCmd(String command, PlayerInstance player)
{
if (command.equals("_bbshome"))
{
loadStaticHtm("index.htm", player);
}
else if (command.startsWith("_bbshome;"))
{
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
loadStaticHtm(st.nextToken(), player);
}
else
{
super.parseCmd(command, player);
}
}
@Override
protected String getFolder()
{
return "top/";
}
private static class SingletonHolder
{
protected static final TopBBSManager INSTANCE = new TopBBSManager();
}
}

View File

@@ -0,0 +1,330 @@
/*
* 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.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import org.l2jmobius.commons.util.StringUtil;
import org.l2jmobius.gameserver.communitybbs.CommunityBoard;
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.datatables.sql.ClanTable;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
public class TopicBBSManager extends BaseBBSManager
{
private final List<Topic> _table;
private final Map<Forum, Integer> _maxId;
public static TopicBBSManager getInstance()
{
return SingletonHolder.INSTANCE;
}
protected TopicBBSManager()
{
_table = new ArrayList<>();
_maxId = new ConcurrentHashMap<>();
}
@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)
{
separateAndSend("<html><body><br><br><center>The forum named '" + ar2 + "' doesn't exist.</center></body></html>", player);
return;
}
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)
{
separateAndSend("<html><body><br><br><center>The forum named '" + ar2 + "' doesn't exist.</center></body></html>", player);
return;
}
final Topic t = f.getTopic(Integer.parseInt(ar3));
if (t == null)
{
separateAndSend("<html><body><br><br><center>The topic named '" + ar3 + "' doesn't exist.</center></body></html>", player);
return;
}
final Post p = PostBBSManager.getInstance().getPostByTopic(t);
if (p != null)
{
p.deleteMe(t);
}
t.deleteMe(f);
parseCmd("_bbsmemo", player);
}
else
{
super.parseWrite(ar1, ar2, ar3, ar4, ar5, player);
}
}
@Override
public void parseCmd(String command, PlayerInstance player)
{
if (command.equals("_bbsmemo"))
{
CommunityBoard.getInstance().addBypass(player, "Memo Command", command);
showTopics(player.getMemo(), player, 1, player.getMemo().getID());
}
else if (command.startsWith("_bbstopics;read"))
{
CommunityBoard.getInstance().addBypass(player, "Topics Command", command);
final StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final int idf = Integer.parseInt(st.nextToken());
String index = null;
if (st.hasMoreTokens())
{
index = st.nextToken();
}
int ind = 0;
if (index == null)
{
ind = 1;
}
else
{
ind = 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)
{
separateAndSend("<html><body><br><br><center>The forum named '" + idf + "' doesn't exist.</center></body></html>", player);
return;
}
final Topic t = f.getTopic(idt);
if (t == null)
{
separateAndSend("<html><body><br><br><center>The topic named '" + idt + "' doesn't exist.</center></body></html>", player);
return;
}
final Post p = PostBBSManager.getInstance().getPostByTopic(t);
if (p != null)
{
p.deleteMe(t);
}
t.deleteMe(f);
parseCmd("_bbsmemo", player);
}
else
{
super.parseCmd(command, player);
}
}
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);
if (i == null)
{
return 0;
}
return i;
}
public Topic getTopicByID(int idf)
{
for (Topic t : _table)
{
if (t.getID() == idf)
{
return t;
}
}
return null;
}
private void showNewTopic(Forum forum, PlayerInstance player, int idf)
{
if (forum == null)
{
separateAndSend("<html><body><br><br><center>The forum named '" + idf + "' doesn't exist.</center></body></html>", player);
return;
}
if (forum.getType() == Forum.MEMO)
{
showMemoNewTopics(forum, player);
}
else
{
separateAndSend("<html><body><br><br><center>The forum named '" + forum.getName() + "' doesn't exist.</center></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)
{
separateAndSend("<html><body><br><br><center>The forum named '" + idf + "' doesn't exist.</center></body></html>", player);
return;
}
if (forum.getType() == Forum.MEMO)
{
showMemoTopics(forum, player, index);
}
else
{
separateAndSend("<html><body><br><br><center>The forum named '" + forum.getName() + "' doesn't exist.</center></body></html>", player);
}
}
private void showMemoTopics(Forum forum, PlayerInstance player, int index)
{
forum.vload();
final StringBuilder sb = new StringBuilder("<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">Memo Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=2 bgcolor=888888 width=610><tr><td FIXWIDTH=5></td><td FIXWIDTH=415 align=center>&$413;</td><td FIXWIDTH=120 align=center></td><td FIXWIDTH=70 align=center>&$418;</td></tr></table>");
final DateFormat dateFormat = DateFormat.getInstance();
for (int i = 0, j = getMaxID(forum) + 1; i < (12 * index); j--)
{
if (j < 0)
{
break;
}
final Topic t = forum.getTopic(j);
if ((t != null) && (i++ >= (12 * (index - 1))))
{
StringUtil.append(sb, "<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\">");
}
}
sb.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)
{
sb.append("<td><button action=\"\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16 ></td>");
}
else
{
StringUtil.append(sb, "<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;
nbp = forum.getTopicSize() / 8;
if ((nbp * 8) != ClanTable.getInstance().getClans().length)
{
nbp++;
}
for (int i = 1; i <= nbp; i++)
{
if (i == index)
{
StringUtil.append(sb, "<td> ", i, " </td>");
}
else
{
StringUtil.append(sb, "<td><a action=\"bypass _bbstopics;read;", forum.getID(), ";", i, "\"> ", i, " </a></td>");
}
}
if (index == nbp)
{
sb.append("<td><button action=\"\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
}
else
{
StringUtil.append(sb, "<td><button action=\"bypass _bbstopics;read;", forum.getID(), ";", index + 1, "\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
}
StringUtil.append(sb, "</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>");
separateAndSend(sb.toString(), player);
}
private static class SingletonHolder
{
protected static final TopicBBSManager INSTANCE = new TopicBBSManager();
}
}

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.datatables;
import java.util.ArrayList;
import java.util.List;
/**
* This class has just one simple function to return the item id of a crown regarding to castleid
* @author evill33t
*/
public class CrownTable
{
private static List<Integer> _crownList = new ArrayList<>();
public static List<Integer> getCrownList()
{
if (_crownList.isEmpty())
{
_crownList.add(6841); // Crown of the lord
_crownList.add(6834); // Innadril
_crownList.add(6835); // Dion
_crownList.add(6836); // Goddard
_crownList.add(6837); // Oren
_crownList.add(6838); // Gludio
_crownList.add(6839); // Giran
_crownList.add(6840); // Aden
_crownList.add(8182); // Rune
_crownList.add(8183); // Schuttgart
}
return _crownList;
}
public static int getCrownId(int castleId)
{
int crownId = 0;
switch (castleId)
{
// Gludio
case 1:
{
crownId = 6838;
break;
}
// Dion
case 2:
{
crownId = 6835;
break;
}
// Giran
case 3:
{
crownId = 6839;
break;
}
// Oren
case 4:
{
crownId = 6837;
break;
}
// Aden
case 5:
{
crownId = 6840;
break;
}
// Innadril
case 6:
{
crownId = 6834;
break;
}
// Goddard
case 7:
{
crownId = 6836;
break;
}
// Rune
case 8:
{
crownId = 8182;
break;
}
// Schuttgart
case 9:
{
crownId = 8183;
break;
}
default:
{
crownId = 0;
break;
}
}
return crownId;
}
}

View File

@@ -0,0 +1,193 @@
/*
* 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.datatables;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.WorldObject;
/**
* @version $Revision$ $Date$
*/
public class DesireTable
{
public static final DesireType[] DEFAULT_DESIRES =
{
DesireType.FEAR,
DesireType.DISLIKE,
DesireType.HATE,
DesireType.DAMAGE
};
public enum DesireType
{
FEAR,
DISLIKE,
HATE,
DAMAGE
}
class DesireValue
{
private float _value;
DesireValue()
{
this(0f);
}
DesireValue(Float pValue)
{
_value = pValue;
}
public void addValue(float pValue)
{
_value += pValue;
}
public float getValue()
{
return _value;
}
}
class Desires
{
private final Map<DesireType, DesireValue> _desireTable;
public Desires(DesireType... desireList)
{
_desireTable = new EnumMap<>(DesireType.class);
for (DesireType desire : desireList)
{
_desireTable.put(desire, new DesireValue());
}
}
public DesireValue getDesireValue(DesireType type)
{
return _desireTable.get(type);
}
public void addValue(DesireType type, float value)
{
final DesireValue temp = getDesireValue(type);
if (temp != null)
{
temp.addValue(value);
}
}
public void createDesire(DesireType type)
{
_desireTable.put(type, new DesireValue());
}
public void deleteDesire(DesireType type)
{
_desireTable.remove(type);
}
}
private final Map<WorldObject, Desires> _objectDesireTable;
private final Desires _generalDesires;
private final DesireType[] _desireTypes;
public DesireTable(DesireType... desireList)
{
_desireTypes = desireList;
_objectDesireTable = new HashMap<>();
_generalDesires = new Desires(_desireTypes);
}
public float getDesireValue(DesireType type)
{
return _generalDesires.getDesireValue(type).getValue();
}
public float getDesireValue(WorldObject object, DesireType type)
{
final Desires desireList = _objectDesireTable.get(object);
if (desireList == null)
{
return 0f;
}
return desireList.getDesireValue(type).getValue();
}
public void addDesireValue(DesireType type, float value)
{
_generalDesires.addValue(type, value);
}
public void addDesireValue(WorldObject object, DesireType type, float value)
{
final Desires desireList = _objectDesireTable.get(object);
if (desireList != null)
{
desireList.addValue(type, value);
}
}
public void createDesire(DesireType type)
{
_generalDesires.createDesire(type);
}
public void deleteDesire(DesireType type)
{
_generalDesires.deleteDesire(type);
}
public void createDesire(WorldObject object, DesireType type)
{
final Desires desireList = _objectDesireTable.get(object);
if (desireList != null)
{
desireList.createDesire(type);
}
}
public void deleteDesire(WorldObject object, DesireType type)
{
final Desires desireList = _objectDesireTable.get(object);
if (desireList != null)
{
desireList.deleteDesire(type);
}
}
public void addKnownObject(WorldObject object)
{
if (object != null)
{
addKnownObject(object, DesireType.DISLIKE, DesireType.FEAR, DesireType.DAMAGE, DesireType.HATE);
}
}
public void addKnownObject(WorldObject object, DesireType... desireList)
{
if (object != null)
{
_objectDesireTable.put(object, new Desires(desireList));
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.datatables;
import org.l2jmobius.gameserver.model.Skill;
/**
* @author BiTi
*/
public class HeroSkillTable
{
private static final Integer[] HERO_SKILL_IDS = new Integer[]
{
395,
396,
1374,
1375,
1376
};
private static Skill[] HERO_SKILLS;
private HeroSkillTable()
{
HERO_SKILLS = new Skill[5];
HERO_SKILLS[0] = SkillTable.getInstance().getInfo(395, 1);
HERO_SKILLS[1] = SkillTable.getInstance().getInfo(396, 1);
HERO_SKILLS[2] = SkillTable.getInstance().getInfo(1374, 1);
HERO_SKILLS[3] = SkillTable.getInstance().getInfo(1375, 1);
HERO_SKILLS[4] = SkillTable.getInstance().getInfo(1376, 1);
}
public static Skill[] getHeroSkills()
{
return HERO_SKILLS;
}
public static boolean isHeroSkill(int skillid)
{
for (int id : HERO_SKILL_IDS)
{
if (id == skillid)
{
return true;
}
}
return false;
}
public static HeroSkillTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final HeroSkillTable INSTANCE = new HeroSkillTable();
}
}

View File

@@ -0,0 +1,366 @@
/*
* 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.datatables;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Level;
import java.util.logging.LogRecord;
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.datatables.sql.PetDataTable;
import org.l2jmobius.gameserver.engines.DocumentEngine;
import org.l2jmobius.gameserver.engines.ItemDataHolder;
import org.l2jmobius.gameserver.idfactory.IdFactory;
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.instance.GrandBossInstance;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.instance.RaidBossInstance;
import org.l2jmobius.gameserver.model.items.Armor;
import org.l2jmobius.gameserver.model.items.EtcItem;
import org.l2jmobius.gameserver.model.items.Item;
import org.l2jmobius.gameserver.model.items.Weapon;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance.ItemLocation;
/**
* @version $Revision: 1.9.2.6.2.9 $ $Date: 2005/04/02 15:57:34 $
*/
public class ItemTable
{
private static final Logger LOGGER = Logger.getLogger(ItemTable.class.getName());
private static final Logger _logItems = Logger.getLogger("item");
private Item[] _allTemplates;
private final Map<Integer, EtcItem> _etcItems;
private final Map<Integer, Armor> _armors;
private final Map<Integer, Weapon> _weapons;
private static final Map<String, Integer> _crystalTypes = new HashMap<>();
static
{
_crystalTypes.put("s", Item.CRYSTAL_S);
_crystalTypes.put("a", Item.CRYSTAL_A);
_crystalTypes.put("b", Item.CRYSTAL_B);
_crystalTypes.put("c", Item.CRYSTAL_C);
_crystalTypes.put("d", Item.CRYSTAL_D);
_crystalTypes.put("none", Item.CRYSTAL_NONE);
}
/**
* Returns a new object Item
* @return
*/
public ItemDataHolder newItem()
{
return new ItemDataHolder();
}
/**
* Constructor.
*/
private ItemTable()
{
_etcItems = new HashMap<>();
_armors = new HashMap<>();
_weapons = new HashMap<>();
load();
}
private void load()
{
int highest = 0;
_armors.clear();
_etcItems.clear();
_weapons.clear();
for (Item item : DocumentEngine.getInstance().loadItems())
{
if (highest < item.getItemId())
{
highest = item.getItemId();
}
if (item instanceof EtcItem)
{
_etcItems.put(item.getItemId(), (EtcItem) item);
}
else if (item instanceof Armor)
{
_armors.put(item.getItemId(), (Armor) item);
}
else
{
_weapons.put(item.getItemId(), (Weapon) item);
}
}
buildFastLookupTable(highest);
}
/**
* Builds a variable in which all items are putting in in function of their ID.
* @param size
*/
private void buildFastLookupTable(int size)
{
// Create a FastLookUp Table called _allTemplates of size : value of the highest item ID
LOGGER.info("Highest item id used: " + size);
_allTemplates = new Item[size + 1];
// Insert armor item in Fast Look Up Table
for (Armor item : _armors.values())
{
_allTemplates[item.getItemId()] = item;
}
// Insert weapon item in Fast Look Up Table
for (Weapon item : _weapons.values())
{
_allTemplates[item.getItemId()] = item;
}
// Insert etcItem item in Fast Look Up Table
for (EtcItem item : _etcItems.values())
{
_allTemplates[item.getItemId()] = item;
}
}
/**
* Returns the item corresponding to the item ID
* @param id : int designating the item
* @return Item
*/
public Item getTemplate(int id)
{
if (id >= _allTemplates.length)
{
return null;
}
return _allTemplates[id];
}
/**
* Create the ItemInstance corresponding to the Item Identifier and quantitiy add logs the activity.<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Create and Init the ItemInstance corresponding to the Item Identifier and quantity</li>
* <li>Add the ItemInstance object to _allObjects of L2world</li>
* <li>Logs Item creation according to LOGGER settings</li><br>
* @param process : String Identifier of process triggering this action
* @param itemId : int Item Identifier of the item to be created
* @param count : int Quantity of items to be created for stackable items
* @param actor : PlayerInstance Player requesting the item creation
* @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation
* @return ItemInstance corresponding to the new item
*/
public ItemInstance createItem(String process, int itemId, int count, PlayerInstance actor, WorldObject reference)
{
// Create and Init the ItemInstance corresponding to the Item Identifier
final ItemInstance item = new ItemInstance(IdFactory.getNextId(), itemId);
// create loot schedule also if autoloot is enabled
if (process.equalsIgnoreCase("loot")/* && !Config.AUTO_LOOT */)
{
ScheduledFuture<?> itemLootShedule;
long delay = 0;
// if in CommandChannel and was killing a World/RaidBoss
if ((reference instanceof GrandBossInstance) || (reference instanceof RaidBossInstance))
{
if ((((Attackable) reference).getFirstCommandChannelAttacked() != null) && ((Attackable) reference).getFirstCommandChannelAttacked().meetRaidWarCondition(reference))
{
item.setOwnerId(((Attackable) reference).getFirstCommandChannelAttacked().getChannelLeader().getObjectId());
delay = 300000;
}
else
{
delay = 15000;
item.setOwnerId(actor.getObjectId());
}
}
else
{
item.setOwnerId(actor.getObjectId());
delay = 15000;
}
itemLootShedule = ThreadPool.schedule(new resetOwner(item), delay);
item.setItemLootShedule(itemLootShedule);
}
// Add the ItemInstance object to _allObjects of L2world
World.getInstance().storeObject(item);
// Set Item parameters
if (item.isStackable() && (count > 1))
{
item.setCount(count);
}
if (Config.LOG_ITEMS)
{
final LogRecord record = new LogRecord(Level.INFO, "CREATE:" + process);
record.setLoggerName("item");
record.setParameters(new Object[]
{
item,
actor,
reference
});
_logItems.log(record);
}
return item;
}
public ItemInstance createItem(String process, int itemId, int count, PlayerInstance actor)
{
return createItem(process, itemId, count, actor, null);
}
/**
* Returns a dummy (fr = factice) item.<br>
* <u><i>Concept :</i></u><br>
* Dummy item is created by setting the ID of the object in the world at null value
* @param itemId : int designating the item
* @return ItemInstance designating the dummy item created
*/
public ItemInstance createDummyItem(int itemId)
{
final Item item = getTemplate(itemId);
if (item == null)
{
return null;
}
ItemInstance temp = new ItemInstance(0, item);
try
{
temp = new ItemInstance(0, itemId);
}
catch (ArrayIndexOutOfBoundsException e)
{
// this can happen if the item templates were not initialized
}
if (temp.getItem() == null)
{
LOGGER.warning("ItemTable: Item Template missing for Id: " + itemId);
}
return temp;
}
/**
* Destroys the ItemInstance.<br>
* <br>
* <b><u>Actions</u>:</b><br>
* <li>Sets ItemInstance parameters to be unusable</li>
* <li>Removes the ItemInstance object to _allObjects of L2world</li>
* <li>Logs Item delettion according to LOGGER settings</li><br>
* @param process : String Identifier of process triggering this action
* @param item
* @param actor : PlayerInstance Player requesting the item destroy
* @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation
*/
public void destroyItem(String process, ItemInstance item, PlayerInstance actor, WorldObject reference)
{
synchronized (item)
{
item.setCount(0);
item.setOwnerId(0);
item.setLocation(ItemLocation.VOID);
item.setLastChange(ItemInstance.REMOVED);
World.getInstance().removeObject(item);
IdFactory.releaseId(item.getObjectId());
// if it's a pet control item, delete the pet as well
if (PetDataTable.isPetItem(item.getItemId()))
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("DELETE FROM pets WHERE item_obj_id=?");
statement.setInt(1, item.getObjectId());
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Could not delete pet objectid " + e);
}
}
}
}
public void reload()
{
load();
}
protected class resetOwner implements Runnable
{
ItemInstance _item;
public resetOwner(ItemInstance item)
{
_item = item;
}
@Override
public void run()
{
_item.setOwnerId(0);
_item.setItemLootShedule(null);
}
}
public Set<Integer> getAllArmorsId()
{
return _armors.keySet();
}
public Set<Integer> getAllWeaponsId()
{
return _weapons.keySet();
}
public int getArraySize()
{
return _allTemplates.length;
}
/**
* Returns instance of ItemTable
* @return ItemTable
*/
public static ItemTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ItemTable INSTANCE = new ItemTable();
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.datatables;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.MobGroup;
import org.l2jmobius.gameserver.model.actor.instance.ControllableMobInstance;
/**
* @author littlecrow
*/
public class MobGroupTable
{
private final Map<Integer, MobGroup> _groupMap;
public static final int FOLLOW_RANGE = 300;
public static final int RANDOM_RANGE = 300;
private MobGroupTable()
{
_groupMap = new HashMap<>();
}
public void addGroup(int groupKey, MobGroup group)
{
_groupMap.put(groupKey, group);
}
public MobGroup getGroup(int groupKey)
{
return _groupMap.get(groupKey);
}
public int getGroupCount()
{
return _groupMap.size();
}
public MobGroup getGroupForMob(ControllableMobInstance mobInst)
{
for (MobGroup mobGroup : _groupMap.values())
{
if (mobGroup.isGroupMember(mobInst))
{
return mobGroup;
}
}
return null;
}
public MobGroup[] getGroups()
{
return _groupMap.values().toArray(new MobGroup[_groupMap.size()]);
}
public boolean removeGroup(int groupKey)
{
return _groupMap.remove(groupKey) != null;
}
public static MobGroupTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final MobGroupTable INSTANCE = new MobGroupTable();
}
}

View File

@@ -0,0 +1,55 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.datatables;
import org.l2jmobius.gameserver.model.Skill;
/**
* @author -Nemesiss-
*/
public class NobleSkillTable
{
private static Skill[] _nobleSkills;
private NobleSkillTable()
{
_nobleSkills = new Skill[8];
_nobleSkills[0] = SkillTable.getInstance().getInfo(1323, 1);
_nobleSkills[1] = SkillTable.getInstance().getInfo(325, 1);
_nobleSkills[2] = SkillTable.getInstance().getInfo(326, 1);
_nobleSkills[3] = SkillTable.getInstance().getInfo(327, 1);
_nobleSkills[4] = SkillTable.getInstance().getInfo(1324, 1);
_nobleSkills[5] = SkillTable.getInstance().getInfo(1325, 1);
_nobleSkills[6] = SkillTable.getInstance().getInfo(1326, 1);
_nobleSkills[7] = SkillTable.getInstance().getInfo(1327, 1);
}
public Skill[] GetNobleSkills()
{
return _nobleSkills;
}
public static NobleSkillTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final NobleSkillTable INSTANCE = new NobleSkillTable();
}
}

View File

@@ -0,0 +1,421 @@
/*
* 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.datatables;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Calendar;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.LoginServerThread;
import org.l2jmobius.gameserver.model.ManufactureItem;
import org.l2jmobius.gameserver.model.ManufactureList;
import org.l2jmobius.gameserver.model.TradeList.TradeItem;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Creature;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.ConnectionState;
import org.l2jmobius.gameserver.network.GameClient;
/**
* @author Shyla
*/
public class OfflineTradeTable
{
private static final Logger LOGGER = Logger.getLogger(OfflineTradeTable.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`,`enchant`) VALUES (?,?,?,?,?)";
private static final String DELETE_OFFLINE_TABLE_ALL_ITEMS = "delete from character_offline_trade_items where charId=?";
private static final String DELETE_OFFLINE_TRADER = "DELETE FROM character_offline_trade where charId=?";
private static final String CLEAR_OFFLINE_TABLE = "DELETE FROM character_offline_trade";
private static final String CLEAR_OFFLINE_TABLE_ITEMS = "DELETE FROM character_offline_trade_items";
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 = ?";
// called when server will go off, different from storeOffliner because
// of store of normal sellers/buyers also if not in offline mode
public static void storeOffliners()
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement stm = con.prepareStatement(CLEAR_OFFLINE_TABLE);
stm.execute();
stm.close();
stm = con.prepareStatement(CLEAR_OFFLINE_TABLE_ITEMS);
stm.execute();
stm.close();
con.setAutoCommit(false); // avoid halfway done
stm = con.prepareStatement(SAVE_OFFLINE_STATUS);
final PreparedStatement stmItems = con.prepareStatement(SAVE_ITEMS);
for (PlayerInstance pc : World.getInstance().getAllPlayers())
{
try
{
// without second check, server will store all guys that are in shop mode
if ((pc.getPrivateStoreType() != PlayerInstance.STORE_PRIVATE_NONE)/* && (pc.isOffline()) */)
{
stm.setInt(1, pc.getObjectId()); // Char Id
stm.setLong(2, pc.getOfflineStartTime());
stm.setInt(3, pc.getPrivateStoreType()); // store type
String title = null;
switch (pc.getPrivateStoreType())
{
case PlayerInstance.STORE_PRIVATE_BUY:
{
if (!Config.OFFLINE_TRADE_ENABLE)
{
continue;
}
title = pc.getBuyList().getTitle();
for (TradeItem i : pc.getBuyList().getItems())
{
stmItems.setInt(1, pc.getObjectId());
stmItems.setInt(2, i.getItem().getItemId());
stmItems.setLong(3, i.getCount());
stmItems.setLong(4, i.getPrice());
stmItems.setLong(5, i.getEnchant());
stmItems.executeUpdate();
stmItems.clearParameters();
}
break;
}
case PlayerInstance.STORE_PRIVATE_SELL:
case PlayerInstance.STORE_PRIVATE_PACKAGE_SELL:
{
if (!Config.OFFLINE_TRADE_ENABLE)
{
continue;
}
title = pc.getSellList().getTitle();
pc.getSellList().updateItems();
for (TradeItem i : pc.getSellList().getItems())
{
stmItems.setInt(1, pc.getObjectId());
stmItems.setInt(2, i.getObjectId());
stmItems.setLong(3, i.getCount());
stmItems.setLong(4, i.getPrice());
stmItems.setLong(5, i.getEnchant());
stmItems.executeUpdate();
stmItems.clearParameters();
}
break;
}
case PlayerInstance.STORE_PRIVATE_MANUFACTURE:
{
if (!Config.OFFLINE_CRAFT_ENABLE)
{
continue;
}
title = pc.getCreateList().getStoreName();
for (ManufactureItem i : pc.getCreateList().getList())
{
stmItems.setInt(1, pc.getObjectId());
stmItems.setInt(2, i.getRecipeId());
stmItems.setLong(3, 0);
stmItems.setLong(4, i.getCost());
stmItems.setLong(5, 0);
stmItems.executeUpdate();
stmItems.clearParameters();
}
break;
}
default:
{
// LOGGER.info( "OfflineTradersTable[storeTradeItems()]: Error while saving offline trader: " + pc.getObjectId() + ", store type: "+pc.getPrivateStoreType());
// no save for this kind of shop
continue;
}
}
stm.setString(4, title);
stm.executeUpdate();
stm.clearParameters();
con.commit(); // flush
}
}
catch (Exception e)
{
LOGGER.warning("OfflineTradersTable[storeTradeItems()]: Error while saving offline trader: " + pc.getObjectId() + " " + e);
}
}
stm.close();
stmItems.close();
LOGGER.info("Offline traders stored.");
}
catch (Exception e)
{
LOGGER.warning("OfflineTradersTable[storeTradeItems()]: Error while saving offline traders: " + e);
}
}
public static void restoreOfflineTraders()
{
LOGGER.info("Loading offline traders...");
int nTraders = 0;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement stm = con.prepareStatement(LOAD_OFFLINE_STATUS);
final ResultSet rs = stm.executeQuery();
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())
{
LOGGER.info("Offline trader with id " + rs.getInt("charId") + " reached OfflineMaxDays, kicked.");
continue;
}
}
final int type = rs.getInt("type");
if (type == PlayerInstance.STORE_PRIVATE_NONE)
{
continue;
}
PlayerInstance player = null;
try
{
final GameClient client = new GameClient(null);
player = PlayerInstance.load(rs.getInt("charId"));
client.setPlayer(player);
client.setAccountName(player.getAccountName());
client.setState(ConnectionState.IN_GAME);
player.setClient(client);
player.setOfflineMode(true);
player.setOnlineStatus(false);
player.setOfflineStartTime(time);
if (Config.OFFLINE_SLEEP_EFFECT)
{
player.startAbnormalEffect(Creature.ABNORMAL_EFFECT_SLEEP);
}
player.spawnMe(player.getX(), player.getY(), player.getZ());
LoginServerThread.getInstance().addGameServerLogin(player.getAccountName(), client);
final PreparedStatement stmItems = con.prepareStatement(LOAD_OFFLINE_ITEMS);
stmItems.setInt(1, player.getObjectId());
final ResultSet items = stmItems.executeQuery();
switch (type)
{
case PlayerInstance.STORE_PRIVATE_BUY:
{
while (items.next())
{
player.getBuyList().addItemByItemId(items.getInt(2), items.getInt(3), items.getInt(4), items.getInt(5));
}
player.getBuyList().setTitle(rs.getString("title"));
break;
}
case PlayerInstance.STORE_PRIVATE_SELL:
case PlayerInstance.STORE_PRIVATE_PACKAGE_SELL:
{
while (items.next())
{
player.getSellList().addItem(items.getInt(2), items.getInt(3), items.getInt(4));
}
player.getSellList().setTitle(rs.getString("title"));
player.getSellList().setPackaged(type == PlayerInstance.STORE_PRIVATE_PACKAGE_SELL);
break;
}
case PlayerInstance.STORE_PRIVATE_MANUFACTURE:
{
final ManufactureList createList = new ManufactureList();
while (items.next())
{
createList.add(new ManufactureItem(items.getInt(2), items.getInt(4)));
}
player.setCreateList(createList);
player.getCreateList().setStoreName(rs.getString("title"));
break;
}
default:
{
LOGGER.info("Offline trader " + player.getName() + " finished to sell his items");
}
}
items.close();
stmItems.close();
player.sitDown();
if (Config.OFFLINE_MODE_SET_INVULNERABLE)
{
player.setInvul(true);
}
if (Config.OFFLINE_SET_NAME_COLOR)
{
player._originalNameColorOffline = player.getAppearance().getNameColor();
player.getAppearance().setNameColor(Config.OFFLINE_NAME_COLOR);
}
player.setPrivateStoreType(type);
player.setOnlineStatus(true);
player.restoreEffects();
player.broadcastUserInfo();
nTraders++;
}
catch (Exception e)
{
LOGGER.warning("OfflineTradersTable[loadOffliners()]: Error loading trader: " + e);
if (player != null)
{
player.logout();
}
}
}
rs.close();
stm.close();
World.OFFLINE_TRADE_COUNT = nTraders;
LOGGER.info("Loaded " + nTraders + " offline traders.");
}
catch (Exception e)
{
LOGGER.warning("OfflineTradersTable[loadOffliners()]: Error while loading offline traders: " + e);
}
}
public static void storeOffliner(PlayerInstance pc)
{
if ((pc.getPrivateStoreType() == PlayerInstance.STORE_PRIVATE_NONE) || (!pc.isInOfflineMode()))
{
return;
}
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement stm = con.prepareStatement(DELETE_OFFLINE_TABLE_ALL_ITEMS);
stm.setInt(1, pc.getObjectId());
stm.execute();
stm.clearParameters();
stm.close();
stm = con.prepareStatement(DELETE_OFFLINE_TRADER);
stm.setInt(1, pc.getObjectId());
stm.execute();
stm.clearParameters();
stm.close();
con.setAutoCommit(false); // avoid halfway done
stm = con.prepareStatement(SAVE_OFFLINE_STATUS);
final PreparedStatement stmItems = con.prepareStatement(SAVE_ITEMS);
boolean save = true;
try
{
stm.setInt(1, pc.getObjectId()); // Char Id
stm.setLong(2, pc.getOfflineStartTime());
stm.setInt(3, pc.getPrivateStoreType()); // store type
String title = null;
switch (pc.getPrivateStoreType())
{
case PlayerInstance.STORE_PRIVATE_BUY:
{
if (!Config.OFFLINE_TRADE_ENABLE)
{
break;
}
title = pc.getBuyList().getTitle();
for (TradeItem i : pc.getBuyList().getItems())
{
stmItems.setInt(1, pc.getObjectId());
stmItems.setInt(2, i.getItem().getItemId());
stmItems.setLong(3, i.getCount());
stmItems.setLong(4, i.getPrice());
stmItems.setLong(5, i.getEnchant());
stmItems.executeUpdate();
stmItems.clearParameters();
}
break;
}
case PlayerInstance.STORE_PRIVATE_SELL:
case PlayerInstance.STORE_PRIVATE_PACKAGE_SELL:
{
if (!Config.OFFLINE_TRADE_ENABLE)
{
break;
}
title = pc.getSellList().getTitle();
pc.getSellList().updateItems();
for (TradeItem i : pc.getSellList().getItems())
{
stmItems.setInt(1, pc.getObjectId());
stmItems.setInt(2, i.getObjectId());
stmItems.setLong(3, i.getCount());
stmItems.setLong(4, i.getPrice());
stmItems.setLong(5, i.getEnchant());
stmItems.executeUpdate();
stmItems.clearParameters();
}
break;
}
case PlayerInstance.STORE_PRIVATE_MANUFACTURE:
{
if (!Config.OFFLINE_CRAFT_ENABLE)
{
break;
}
title = pc.getCreateList().getStoreName();
for (ManufactureItem i : pc.getCreateList().getList())
{
stmItems.setInt(1, pc.getObjectId());
stmItems.setInt(2, i.getRecipeId());
stmItems.setLong(3, 0);
stmItems.setLong(4, i.getCost());
stmItems.setLong(5, 0);
stmItems.executeUpdate();
stmItems.clearParameters();
}
break;
}
default:
{
// LOGGER.info( "OfflineTradersTable[storeOffliner()]: Error while saving offline trader: " + pc.getObjectId() + ", store type: "+pc.getPrivateStoreType());
// no save for this kind of shop
save = false;
}
}
if (save)
{
stm.setString(4, title);
stm.executeUpdate();
stm.clearParameters();
con.commit(); // flush
}
}
catch (Exception e)
{
LOGGER.warning("OfflineTradersTable[storeOffliner()]: Error while saving offline trader: " + pc.getObjectId() + " " + e);
}
stm.close();
stmItems.close();
}
catch (Exception e)
{
LOGGER.warning("OfflineTradersTable[storeOffliner()]: Error while saving offline traders: " + e);
}
}
}

View File

@@ -0,0 +1,282 @@
/*
* 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.datatables;
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
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.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.holders.BuffSkillHolder;
/**
* This class loads available skills and stores players' buff schemes into _schemesTable.
*/
public class SchemeBufferTable
{
private static final Logger LOGGER = Logger.getLogger(SchemeBufferTable.class.getName());
private static final String LOAD_SCHEMES = "SELECT * FROM buffer_schemes";
private static final String DELETE_SCHEMES = "TRUNCATE TABLE buffer_schemes";
private static final String INSERT_SCHEME = "INSERT INTO buffer_schemes (object_id, scheme_name, skills) VALUES (?,?,?)";
private final Map<Integer, Map<String, List<Integer>>> _schemesTable = new ConcurrentHashMap<>();
private final Map<Integer, BuffSkillHolder> _availableBuffs = new LinkedHashMap<>();
public SchemeBufferTable()
{
int count = 0;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement st = con.prepareStatement(LOAD_SCHEMES);
final ResultSet rs = st.executeQuery();
while (rs.next())
{
final int objectId = rs.getInt("object_id");
final String schemeName = rs.getString("scheme_name");
final String[] skills = rs.getString("skills").split(",");
final ArrayList<Integer> schemeList = new ArrayList<>();
for (String skill : skills)
{
// Don't feed the skills list if the list is empty.
if (skill.isEmpty())
{
break;
}
schemeList.add(Integer.parseInt(skill));
}
setScheme(objectId, schemeName, schemeList);
count++;
}
rs.close();
st.close();
}
catch (Exception e)
{
LOGGER.warning("SchemeBufferTable: Failed to load buff schemes : " + e);
}
try
{
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
final Document doc = db.parse(new File("./data/SchemeBufferSkills.xml"));
final Node n = doc.getFirstChild();
for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
{
if (!d.getNodeName().equalsIgnoreCase("category"))
{
continue;
}
final String category = d.getAttributes().getNamedItem("type").getNodeValue();
for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling())
{
if (!c.getNodeName().equalsIgnoreCase("buff"))
{
continue;
}
final NamedNodeMap attrs = c.getAttributes();
final int skillId = Integer.parseInt(attrs.getNamedItem("id").getNodeValue());
_availableBuffs.put(skillId, new BuffSkillHolder(skillId, Integer.parseInt(attrs.getNamedItem("price").getNodeValue()), category, attrs.getNamedItem("desc").getNodeValue()));
}
}
}
catch (Exception e)
{
LOGGER.warning("SchemeBufferTable: Failed to load buff info : " + e);
}
LOGGER.info("SchemeBufferTable: Loaded " + count + " players schemes and " + _availableBuffs.size() + " available buffs.");
}
public void saveSchemes()
{
try (Connection con = DatabaseFactory.getConnection())
{
// Delete all entries from database.
try (PreparedStatement st = con.prepareStatement(DELETE_SCHEMES))
{
st.execute();
}
// Save _schemesTable content.
try (PreparedStatement st = con.prepareStatement(INSERT_SCHEME))
{
for (Map.Entry<Integer, Map<String, List<Integer>>> player : _schemesTable.entrySet())
{
for (Map.Entry<String, List<Integer>> scheme : player.getValue().entrySet())
{
// Build a String composed of skill ids seperated by a ",".
final StringBuilder sb = new StringBuilder();
for (int skillId : scheme.getValue())
{
sb.append(skillId + ",");
}
// Delete the last "," : must be called only if there is something to delete !
if (sb.length() > 0)
{
sb.setLength(sb.length() - 1);
}
st.setInt(1, player.getKey());
st.setString(2, scheme.getKey());
st.setString(3, sb.toString());
st.addBatch();
}
}
st.executeBatch();
}
}
catch (Exception e)
{
LOGGER.warning("BufferTableScheme: Error while saving schemes : " + e);
}
}
public void setScheme(int playerId, String schemeName, List<Integer> list)
{
if (!_schemesTable.containsKey(playerId))
{
_schemesTable.put(playerId, new TreeMap<>(String.CASE_INSENSITIVE_ORDER));
}
else if (_schemesTable.get(playerId).size() >= Config.BUFFER_MAX_SCHEMES)
{
return;
}
_schemesTable.get(playerId).put(schemeName, list);
}
/**
* @param playerId : The player objectId to check.
* @return the list of schemes for a given player.
*/
public Map<String, List<Integer>> getPlayerSchemes(int playerId)
{
return _schemesTable.get(playerId);
}
/**
* @param playerId : The player objectId to check.
* @param schemeName : The scheme name to check.
* @return the List holding skills for the given scheme name and player, or null (if scheme or player isn't registered).
*/
public List<Integer> getScheme(int playerId, String schemeName)
{
if ((_schemesTable.get(playerId) == null) || (_schemesTable.get(playerId).get(schemeName) == null))
{
return Collections.emptyList();
}
return _schemesTable.get(playerId).get(schemeName);
}
/**
* @param playerId : The player objectId to check.
* @param schemeName : The scheme name to check.
* @param skillId : The skill id to check.
* @return true if the skill is already registered on the scheme, or false otherwise.
*/
public boolean getSchemeContainsSkill(int playerId, String schemeName, int skillId)
{
final List<Integer> skills = getScheme(playerId, schemeName);
if (skills.isEmpty())
{
return false;
}
for (int id : skills)
{
if (id == skillId)
{
return true;
}
}
return false;
}
/**
* @param groupType : The type of skills to return.
* @return a list of skills ids based on the given groupType.
*/
public List<Integer> getSkillsIdsByType(String groupType)
{
final List<Integer> skills = new ArrayList<>();
for (BuffSkillHolder skill : _availableBuffs.values())
{
if (skill.getType().equalsIgnoreCase(groupType))
{
skills.add(skill.getId());
}
}
return skills;
}
/**
* @return a list of all buff types available.
*/
public List<String> getSkillTypes()
{
final List<String> skillTypes = new ArrayList<>();
for (BuffSkillHolder skill : _availableBuffs.values())
{
if (!skillTypes.contains(skill.getType()))
{
skillTypes.add(skill.getType());
}
}
return skillTypes;
}
public BuffSkillHolder getAvailableBuff(int skillId)
{
return _availableBuffs.get(skillId);
}
public static SchemeBufferTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final SchemeBufferTable INSTANCE = new SchemeBufferTable();
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.datatables;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.engines.DocumentEngine;
import org.l2jmobius.gameserver.model.Skill;
import org.l2jmobius.gameserver.model.items.type.WeaponType;
public class SkillTable
{
private final Map<Integer, Skill> _skills = new HashMap<>();
private final boolean _initialized = true;
private SkillTable()
{
reload();
}
public void reload()
{
_skills.clear();
DocumentEngine.getInstance().loadAllSkills(_skills);
}
public boolean isInitialized()
{
return _initialized;
}
/**
* Provides the skill hash
* @param skill The Skill to be hashed
* @return SkillTable.getSkillHashCode(skill.getId(), skill.getLevel())
*/
public static int getSkillHashCode(Skill skill)
{
return getSkillHashCode(skill.getId(), skill.getLevel());
}
/**
* Centralized method for easier change of the hashing sys
* @param skillId The Skill Id
* @param skillLevel The Skill Level
* @return The Skill hash number
*/
public static int getSkillHashCode(int skillId, int skillLevel)
{
return (skillId * 256) + skillLevel;
}
public Skill getInfo(int skillId, int level)
{
return _skills.get(getSkillHashCode(skillId, level));
}
public int getMaxLevel(int magicId, int level)
{
Skill temp;
int result = level;
while (result < 100)
{
result++;
temp = _skills.get(getSkillHashCode(magicId, result));
if (temp == null)
{
return result - 1;
}
}
return result;
}
private static final WeaponType[] weaponDbMasks =
{
WeaponType.ETC,
WeaponType.BOW,
WeaponType.POLE,
WeaponType.DUALFIST,
WeaponType.DUAL,
WeaponType.BLUNT,
WeaponType.SWORD,
WeaponType.DAGGER,
WeaponType.BIGSWORD,
WeaponType.ROD,
WeaponType.BIGBLUNT
};
public int calcWeaponsAllowed(int mask)
{
if (mask == 0)
{
return 0;
}
int weaponsAllowed = 0;
for (int i = 0; i < weaponDbMasks.length; i++)
{
if ((mask & (1 << i)) != 0)
{
weaponsAllowed |= weaponDbMasks[i].mask();
}
}
return weaponsAllowed;
}
public static SkillTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final SkillTable INSTANCE = new SkillTable();
}
}

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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
public class CharNameTable
{
private static final Logger LOGGER = Logger.getLogger(CharNameTable.class.getName());
public synchronized boolean doesCharNameExist(String name)
{
boolean result = true;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT account_name FROM characters WHERE char_name=?");
statement.setString(1, name);
final ResultSet rset = statement.executeQuery();
result = rset.next();
statement.close();
rset.close();
}
catch (SQLException e)
{
LOGGER.warning("Could not check existing charname " + e);
}
return result;
}
public String getPlayerName(int objId)
{
String name = "";
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT char_name FROM characters WHERE obj_Id=?");
statement.setInt(1, objId);
final ResultSet rset = statement.executeQuery();
while (rset.next())
{
name = rset.getString(1);
}
statement.close();
rset.close();
}
catch (SQLException e)
{
LOGGER.warning("Could not check existing player name " + e);
}
return name;
}
public int getPlayerObjectId(String name)
{
int id = 0;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT obj_Id FROM characters WHERE char_name=?");
statement.setString(1, name);
final ResultSet rset = statement.executeQuery();
while (rset.next())
{
id = rset.getInt(1);
}
statement.close();
rset.close();
}
catch (SQLException e)
{
LOGGER.warning("Could not check existing player id " + e);
}
return id;
}
public int getPlayerAccessLevel(int objId)
{
int accessLevel = 0;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT accesslevel FROM characters WHERE obj_Id=?");
statement.setInt(1, objId);
final ResultSet rset = statement.executeQuery();
while (rset.next())
{
accessLevel = rset.getInt(1);
}
statement.close();
rset.close();
}
catch (SQLException e)
{
LOGGER.warning("Could not check existing player id " + e);
}
return accessLevel;
}
public int accountCharNumber(String account)
{
int number = 0;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT COUNT(char_name) FROM characters WHERE account_name=?");
statement.setString(1, account);
final ResultSet rset = statement.executeQuery();
while (rset.next())
{
number = rset.getInt(1);
}
statement.close();
rset.close();
}
catch (SQLException e)
{
LOGGER.warning("Could not check existing char number " + e);
}
return number;
}
public static CharNameTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final CharNameTable INSTANCE = new CharNameTable();
}
}

View File

@@ -0,0 +1,538 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
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.concurrent.ThreadPool;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.idfactory.IdFactory;
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.entity.siege.Fort;
import org.l2jmobius.gameserver.model.entity.siege.FortSiege;
import org.l2jmobius.gameserver.model.entity.siege.Siege;
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.network.serverpackets.UserInfo;
import org.l2jmobius.gameserver.util.Util;
/**
* @version $Revision: 1.11.2.5.2.5 $ $Date: 2005/03/27 15:29:18 $
*/
public class ClanTable
{
private static final Logger LOGGER = Logger.getLogger(ClanTable.class.getName());
private final Map<Integer, Clan> _clans = new HashMap<>();
private ClanTable()
{
load();
}
public void load()
{
_clans.clear();
Clan clan;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM clan_data");
final ResultSet result = statement.executeQuery();
// Count the clans
int clanCount = 0;
while (result.next())
{
_clans.put(Integer.parseInt(result.getString("clan_id")), new Clan(Integer.parseInt(result.getString("clan_id"))));
clan = getClan(Integer.parseInt(result.getString("clan_id")));
if (clan.getDissolvingExpiryTime() != 0)
{
if (clan.getDissolvingExpiryTime() < System.currentTimeMillis())
{
destroyClan(clan.getClanId());
}
else
{
scheduleRemoveClan(clan.getClanId());
}
}
clan.setNoticeEnabled(result.getBoolean("enabled"));
clan.setNotice(result.getString("notice"));
clan.setIntroduction(result.getString("introduction"), false);
clanCount++;
}
result.close();
statement.close();
LOGGER.info("Restored " + clanCount + " clans from the database.");
}
catch (Exception e)
{
LOGGER.warning("Data error on ClanTable " + e);
}
restoreClanWars();
}
public Clan[] getClans()
{
return _clans.values().toArray(new Clan[_clans.size()]);
}
public int getTopRate(int clanId)
{
final Clan clan = getClan(clanId);
if (clan.getLevel() < 3)
{
return 0;
}
int i = 1;
for (Clan c : getClans())
{
if ((clan != c) && ((clan.getLevel() < c.getLevel()) || ((clan.getLevel() == c.getLevel()) && (clan.getReputationScore() <= c.getReputationScore()))))
{
i++;
}
}
return i;
}
/**
* @param clanId
* @return
*/
public Clan getClan(int clanId)
{
return _clans.get(clanId);
}
public Clan getClanByName(String clanName)
{
for (Clan clan : getClans())
{
if (clan.getName().equalsIgnoreCase(clanName))
{
return clan;
}
}
return 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 (null == player)
{
return null;
}
LOGGER.info("{" + player.getObjectId() + "}({" + player.getName() + "}) requested a clan creation.");
if (10 > player.getLevel())
{
player.sendPacket(SystemMessageId.YOU_DO_NOT_MEET_THE_CRITERIA_IR_ORDER_TO_CREATE_A_CLAN);
return null;
}
if (0 != player.getClanId())
{
player.sendPacket(SystemMessageId.YOU_HAVE_FAILED_TO_CREATE_A_CLAN);
return null;
}
if (System.currentTimeMillis() < player.getClanCreateExpiryTime())
{
player.sendPacket(SystemMessageId.YOU_MUST_WAIT_10_DAYS_BEFORE_CREATING_A_NEW_CLAN);
return null;
}
if (!isValidCalnName(player, clanName))
{
return null;
}
final Clan clan = new Clan(IdFactory.getNextId(), clanName);
final ClanMember leader = new ClanMember(clan, player.getName(), player.getLevel(), player.getClassId().getId(), player.getObjectId(), player.getPledgeType(), player.getPowerGrade(), player.getTitle());
clan.setLeader(leader);
leader.setPlayerInstance(player);
clan.store();
player.setClan(clan);
player.setPledgeClass(leader.calculatePledgeClass(player));
player.setClanPrivileges(Clan.CP_ALL);
LOGGER.info("New clan created: {" + clan.getClanId() + "} {" + clan.getName() + "}");
_clans.put(clan.getClanId(), clan);
// should be update packet only
player.sendPacket(new PledgeShowInfoUpdate(clan));
player.sendPacket(new PledgeShowMemberListAll(clan, player));
player.sendPacket(new UserInfo(player));
player.sendPacket(new PledgeShowMemberListUpdate(player));
player.sendPacket(SystemMessageId.YOUR_CLAN_HAS_BEEN_CREATED);
return clan;
}
public boolean isValidCalnName(PlayerInstance player, String clanName)
{
if (!Util.isAlphaNumeric(clanName) || (clanName.length() < 2))
{
player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID);
return false;
}
if (clanName.length() > 16)
{
player.sendPacket(SystemMessageId.CLAN_NAME_S_LENGTH_IS_INCORRECT);
return false;
}
if (getClanByName(clanName) != null)
{
final SystemMessage sm = new SystemMessage(SystemMessageId.S1_ALREADY_EXISTS);
sm.addString(clanName);
player.sendPacket(sm);
return false;
}
Pattern pattern;
try
{
pattern = Pattern.compile(Config.CLAN_NAME_TEMPLATE);
}
catch (PatternSyntaxException e) // case of illegal pattern
{
LOGGER.warning("ERROR: Clan name pattern of config is wrong!");
pattern = Pattern.compile(".*");
}
final Matcher match = pattern.matcher(clanName);
if (!match.matches())
{
player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID);
return false;
}
return true;
}
public synchronized void destroyClan(int clanId)
{
final Clan clan = getClan(clanId);
if (clan == null)
{
return;
}
PlayerInstance leader = null;
if ((clan.getLeader() != null) && ((leader = clan.getLeader().getPlayerInstance()) != null))
{
if (Config.CLAN_LEADER_COLOR_ENABLED && (clan.getLevel() >= Config.CLAN_LEADER_COLOR_CLAN_LEVEL))
{
if (Config.CLAN_LEADER_COLORED == 1)
{
leader.getAppearance().setNameColor(0x000000);
}
else
{
leader.getAppearance().setTitleColor(0xFFFF77);
}
}
// remove clan leader skills
leader.addClanLeaderSkills(false);
}
clan.broadcastToOnlineMembers(new SystemMessage(SystemMessageId.CLAN_HAS_DISPERSED));
final int castleId = clan.getHasCastle();
if (castleId == 0)
{
for (Siege siege : SiegeManager.getInstance().getSieges())
{
siege.removeSiegeClan(clanId);
}
}
final int fortId = clan.getHasFort();
if (fortId == 0)
{
for (FortSiege siege : FortSiegeManager.getInstance().getSieges())
{
siege.removeSiegeClan(clanId);
}
}
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.getName(), 0);
}
final int leaderId = clan.getLeaderId();
final int clanLvl = clan.getLevel();
_clans.remove(clanId);
IdFactory.releaseId(clanId);
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = con.prepareStatement("DELETE FROM clan_data WHERE clan_id=?");
statement.setInt(1, clanId);
statement.execute();
statement = con.prepareStatement("DELETE FROM clan_privs WHERE clan_id=?");
statement.setInt(1, clanId);
statement.execute();
statement = con.prepareStatement("DELETE FROM clan_skills WHERE clan_id=?");
statement.setInt(1, clanId);
statement.execute();
statement = con.prepareStatement("DELETE FROM clan_subpledges WHERE clan_id=?");
statement.setInt(1, clanId);
statement.execute();
statement = con.prepareStatement("DELETE FROM clan_wars WHERE clan1=? OR clan2=?");
statement.setInt(1, clanId);
statement.setInt(2, clanId);
statement.execute();
if ((leader == null) && (leaderId != 0) && Config.CLAN_LEADER_COLOR_ENABLED && (clanLvl >= Config.CLAN_LEADER_COLOR_CLAN_LEVEL))
{
String query;
if (Config.CLAN_LEADER_COLORED == 1)
{
query = "UPDATE characters SET name_color = '000000' WHERE obj_Id = ?";
}
else
{
query = "UPDATE characters SET title_color = 'FFFF77' WHERE obj_Id = ?";
}
statement = con.prepareStatement(query);
statement.setInt(1, leaderId);
statement.execute();
}
if (castleId != 0)
{
statement = con.prepareStatement("UPDATE castle SET taxPercent = 0 WHERE id = ?");
statement.setInt(1, castleId);
statement.execute();
}
if (fortId != 0)
{
final Fort fort = FortManager.getInstance().getFortById(fortId);
if (fort != null)
{
final Clan owner = fort.getOwnerClan();
if (clan == owner)
{
fort.removeOwner(clan);
}
}
}
LOGGER.info("Clan removed in db: " + clanId);
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while removing clan in db " + e);
}
}
public void scheduleRemoveClan(int clanId)
{
ThreadPool.schedule(() ->
{
if (getClan(clanId) == null)
{
return;
}
if (getClan(clanId).getDissolvingExpiryTime() != 0)
{
destroyClan(clanId);
}
}, getClan(clanId).getDissolvingExpiryTime() - System.currentTimeMillis());
}
public boolean isAllyExists(String allyName)
{
for (Clan clan : getClans())
{
if ((clan.getAllyName() != null) && clan.getAllyName().equalsIgnoreCase(allyName))
{
return true;
}
}
return false;
}
public void storeClanWars(int clanId1, int clanId2)
{
final Clan clan1 = getInstance().getClan(clanId1);
final Clan clan2 = getInstance().getClan(clanId2);
clan1.setEnemyClan(clan2);
clan2.setAttackerClan(clan1);
clan1.broadcastClanStatus();
clan2.broadcastClanStatus();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("REPLACE INTO clan_wars (clan1, clan2, wantspeace1, wantspeace2) VALUES(?,?,?,?)");
statement.setInt(1, clanId1);
statement.setInt(2, clanId2);
statement.setInt(3, 0);
statement.setInt(4, 0);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Could not store clans wars data " + e);
}
SystemMessage msg = new SystemMessage(SystemMessageId.A_CLAN_WAR_HAS_BEEN_DECLARED_AGAINST_THE_CLAN_S1_IF_YOU_ARE_KILLED_DURING_THE_CLAN_WAR_BY_MEMBERS_OF_THE_OPPOSING_CLAN_YOU_WILL_ONLY_LOSE_A_QUARTER_OF_THE_NORMAL_EXPERIENCE_FROM_DEATH);
msg.addString(clan2.getName());
clan1.broadcastToOnlineMembers(msg);
msg = new SystemMessage(SystemMessageId.THE_CLAN_S1_HAS_DECLARED_A_CLAN_WAR);
msg.addString(clan1.getName());
clan2.broadcastToOnlineMembers(msg);
}
public void deleteClanWars(int clanId1, int clanId2)
{
final Clan clan1 = getInstance().getClan(clanId1);
final Clan clan2 = getInstance().getClan(clanId2);
clan1.deleteEnemyClan(clan2);
clan2.deleteAttackerClan(clan1);
clan1.broadcastClanStatus();
clan2.broadcastClanStatus();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("DELETE FROM clan_wars WHERE clan1=? AND clan2=?");
statement.setInt(1, clanId1);
statement.setInt(2, clanId2);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Could not restore clans wars data " + e);
}
SystemMessage msg = new SystemMessage(SystemMessageId.THE_WAR_AGAINST_S1_CLAN_HAS_BEEN_STOPPED);
msg.addString(clan2.getName());
clan1.broadcastToOnlineMembers(msg);
msg = new SystemMessage(SystemMessageId.THE_CLAN_S1_HAS_DECIDED_TO_STOP_THE_WAR);
msg.addString(clan1.getName());
clan2.broadcastToOnlineMembers(msg);
}
public void checkSurrender(Clan clan1, Clan clan2)
{
int count = 0;
for (ClanMember player : clan1.getMembers())
{
if ((player != null) && (player.getPlayerInstance().getWantsPeace() == 1))
{
count++;
}
}
if (count == (clan1.getMembers().length - 1))
{
clan1.deleteEnemyClan(clan2);
clan2.deleteEnemyClan(clan1);
deleteClanWars(clan1.getClanId(), clan2.getClanId());
}
}
private void restoreClanWars()
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT clan1, clan2, wantspeace1, wantspeace2 FROM clan_wars");
final ResultSet rset = statement.executeQuery();
while (rset.next())
{
getClan(rset.getInt("clan1")).setEnemyClan(rset.getInt("clan2"));
getClan(rset.getInt("clan2")).setAttackerClan(rset.getInt("clan1"));
}
rset.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Could not restore clan wars data: " + e.getMessage());
}
}
public static ClanTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ClanTable INSTANCE = new ClanTable();
}
}

View File

@@ -0,0 +1,220 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.holders.HelperBuffHolder;
/**
* This class represents the Newbie Helper Buff list. Author: Ayor
*/
public class HelperBuffTable
{
private static final Logger LOGGER = Logger.getLogger(HelperBuffTable.class.getName());
public List<HelperBuffHolder> helperBuff = new ArrayList<>();
private final boolean _initialized = true;
private int _magicClassLowestLevel = 100;
private int _physicClassLowestLevel = 100;
private int _magicClassHighestLevel = 1;
private int _physicClassHighestLevel = 1;
public void load()
{
helperBuff.clear();
restoreHelperBuffData();
}
/**
* Create and Load the Newbie Helper Buff list from SQL Table helper_buff_list
*/
private HelperBuffTable()
{
load();
}
/**
* Read and Load the Newbie Helper Buff list from SQL Table helper_buff_list
*/
private void restoreHelperBuffData()
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM helper_buff_list");
final ResultSet helperbuffdata = statement.executeQuery();
fillHelperBuffTable(helperbuffdata);
helperbuffdata.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Table helper_buff_list not found: Update your database " + e);
}
}
/**
* Load the Newbie Helper Buff list from SQL Table helper_buff_list
* @param helperBuffData
* @throws Exception
*/
private void fillHelperBuffTable(ResultSet helperBuffData) throws Exception
{
while (helperBuffData.next())
{
final StatSet helperBuffDat = new StatSet();
final int id = helperBuffData.getInt("id");
helperBuffDat.set("id", id);
helperBuffDat.set("skillID", helperBuffData.getInt("skill_id"));
helperBuffDat.set("skillLevel", helperBuffData.getInt("skill_level"));
helperBuffDat.set("lowerLevel", helperBuffData.getInt("lower_level"));
helperBuffDat.set("upperLevel", helperBuffData.getInt("upper_level"));
helperBuffDat.set("isMagicClass", helperBuffData.getString("is_magic_class"));
// Calulate the range level in wich player must be to obtain buff from Newbie Helper
if ("false".equals(helperBuffData.getString("is_magic_class")))
{
if (helperBuffData.getInt("lower_level") < _physicClassLowestLevel)
{
_physicClassLowestLevel = helperBuffData.getInt("lower_level");
}
if (helperBuffData.getInt("upper_level") > _physicClassHighestLevel)
{
_physicClassHighestLevel = helperBuffData.getInt("upper_level");
}
}
else
{
if (helperBuffData.getInt("lower_level") < _magicClassLowestLevel)
{
_magicClassLowestLevel = helperBuffData.getInt("lower_level");
}
if (helperBuffData.getInt("upper_level") > _magicClassHighestLevel)
{
_magicClassHighestLevel = helperBuffData.getInt("upper_level");
}
}
// Add this Helper Buff to the Helper Buff List
final HelperBuffHolder template = new HelperBuffHolder(helperBuffDat);
helperBuff.add(template);
}
LOGGER.info("HelperBuffTable: Loaded " + helperBuff.size() + " templates.");
}
public boolean isInitialized()
{
return _initialized;
}
public HelperBuffHolder getHelperBuffTableItem(int id)
{
return helperBuff.get(id);
}
/**
* @return the Helper Buff List
*/
public List<HelperBuffHolder> getHelperBuffTable()
{
return helperBuff;
}
/**
* @return Returns the magicClassHighestLevel.
*/
public int getMagicClassHighestLevel()
{
return _magicClassHighestLevel;
}
/**
* @param magicClassHighestLevel The magicClassHighestLevel to set.
*/
public void setMagicClassHighestLevel(int magicClassHighestLevel)
{
_magicClassHighestLevel = magicClassHighestLevel;
}
/**
* @return Returns the magicClassLowestLevel.
*/
public int getMagicClassLowestLevel()
{
return _magicClassLowestLevel;
}
/**
* @param magicClassLowestLevel The magicClassLowestLevel to set.
*/
public void setMagicClassLowestLevel(int magicClassLowestLevel)
{
_magicClassLowestLevel = magicClassLowestLevel;
}
/**
* @return Returns the physicClassHighestLevel.
*/
public int getPhysicClassHighestLevel()
{
return _physicClassHighestLevel;
}
/**
* @param physicClassHighestLevel The physicClassHighestLevel to set.
*/
public void setPhysicClassHighestLevel(int physicClassHighestLevel)
{
_physicClassHighestLevel = physicClassHighestLevel;
}
/**
* @return Returns the physicClassLowestLevel.
*/
public int getPhysicClassLowestLevel()
{
return _physicClassLowestLevel;
}
/**
* @param physicClassLowestLevel The physicClassLowestLevel to set.
*/
public void setPhysicClassLowestLevel(int physicClassLowestLevel)
{
_physicClassLowestLevel = physicClassLowestLevel;
}
public static HelperBuffTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final HelperBuffTable INSTANCE = new HelperBuffTable();
}
}

View File

@@ -0,0 +1,729 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.datatables.SkillTable;
import org.l2jmobius.gameserver.model.DropCategory;
import org.l2jmobius.gameserver.model.DropData;
import org.l2jmobius.gameserver.model.MinionData;
import org.l2jmobius.gameserver.model.Skill;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
import org.l2jmobius.gameserver.model.base.ClassId;
import org.l2jmobius.gameserver.model.skills.BaseStat;
import org.l2jmobius.gameserver.model.skills.Stat;
/**
* @version $Revision: 1.8.2.6.2.9 $ $Date: 2005/04/06 16:13:25 $
*/
public class NpcTable
{
private static final Logger LOGGER = Logger.getLogger(NpcTable.class.getName());
private final Map<Integer, NpcTemplate> _npcs = new HashMap<>();
private boolean _initialized = false;
private NpcTable()
{
load();
}
private void load()
{
_npcs.clear();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM npc");
final ResultSet npcdata = statement.executeQuery();
fillNpcTable(npcdata, false);
npcdata.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Error creating NPC table. " + e);
}
if (Config.CUSTOM_NPC_TABLE)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM custom_npc");
final ResultSet npcdata = statement.executeQuery();
fillNpcTable(npcdata, true);
npcdata.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Error creating custom NPC table." + e);
}
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT npcid, skillid, level FROM npcskills");
final ResultSet npcskills = statement.executeQuery();
NpcTemplate npcDat = null;
Skill npcSkill = null;
while (npcskills.next())
{
final int mobId = npcskills.getInt("npcid");
npcDat = _npcs.get(mobId);
if (npcDat == null)
{
continue;
}
final int skillId = npcskills.getInt("skillid");
final int level = npcskills.getInt("level");
if ((npcDat.getRace() == null) && (skillId == 4416))
{
npcDat.setRace(level);
continue;
}
npcSkill = SkillTable.getInstance().getInfo(skillId, level);
if (npcSkill == null)
{
continue;
}
npcDat.addSkill(npcSkill);
}
npcskills.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Error reading NPC skills table." + e);
}
if (Config.CUSTOM_DROPLIST_TABLE)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM custom_droplist ORDER BY mobId, chance DESC");
final ResultSet dropData = statement.executeQuery();
int cCount = 0;
while (dropData.next())
{
final int mobId = dropData.getInt("mobId");
final NpcTemplate npcDat = _npcs.get(mobId);
if (npcDat == null)
{
LOGGER.warning("NPCTable: CUSTOM DROPLIST No npc correlating with id: " + mobId);
continue;
}
final DropData dropDat = new DropData();
dropDat.setItemId(dropData.getInt("itemId"));
dropDat.setMinDrop(dropData.getInt("min"));
dropDat.setMaxDrop(dropData.getInt("max"));
dropDat.setChance(dropData.getInt("chance"));
final int category = dropData.getInt("category");
npcDat.addDropData(dropDat, category);
cCount++;
}
dropData.close();
statement.close();
LOGGER.info("CustomDropList : Added " + cCount + " custom droplist");
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Error reading NPC CUSTOM drop data." + e);
}
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM droplist ORDER BY mobId, chance DESC");
final ResultSet dropData = statement.executeQuery();
DropData dropDat = null;
NpcTemplate npcDat = null;
while (dropData.next())
{
final int mobId = dropData.getInt("mobId");
npcDat = _npcs.get(mobId);
if (npcDat == null)
{
LOGGER.info("NPCTable: No npc correlating with id: " + mobId);
continue;
}
dropDat = new DropData();
dropDat.setItemId(dropData.getInt("itemId"));
dropDat.setMinDrop(dropData.getInt("min"));
dropDat.setMaxDrop(dropData.getInt("max"));
dropDat.setChance(dropData.getInt("chance"));
final int category = dropData.getInt("category");
npcDat.addDropData(dropDat, category);
}
dropData.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Error reading NPC drop data." + e);
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM skill_learn");
final ResultSet learndata = statement.executeQuery();
while (learndata.next())
{
final int npcId = learndata.getInt("npc_id");
final int cId = learndata.getInt("class_id");
final NpcTemplate npc = getTemplate(npcId);
if (npc == null)
{
LOGGER.warning("NPCTable: Error getting NPC template ID " + npcId + " while trying to load skill trainer data.");
continue;
}
final ClassId classId = ClassId.getClassId(cId);
if (classId == null)
{
LOGGER.warning("NPCTable: Error defining learning data for NPC " + npcId + ": specified classId " + classId + " is not specified into ClassID Enum --> check your Database to be complient with it.");
continue;
}
npc.addTeachInfo(classId);
}
learndata.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Error reading NPC trainer data." + e);
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM minions");
final ResultSet minionData = statement.executeQuery();
MinionData minionDat = null;
NpcTemplate npcDat = null;
int cnt = 0;
while (minionData.next())
{
final int raidId = minionData.getInt("boss_id");
npcDat = _npcs.get(raidId);
minionDat = new MinionData();
minionDat.setMinionId(minionData.getInt("minion_id"));
minionDat.setAmountMin(minionData.getInt("amount_min"));
minionDat.setAmountMax(minionData.getInt("amount_max"));
npcDat.addRaidData(minionDat);
cnt++;
}
minionData.close();
statement.close();
LOGGER.info("NpcTable: Loaded " + cnt + " Minions.");
}
catch (Exception e)
{
LOGGER.info("Error loading minion data: " + e.getMessage());
}
_initialized = true;
}
private void fillNpcTable(ResultSet npcData, boolean custom) throws Exception
{
while (npcData.next())
{
final StatSet npcDat = new StatSet();
final int id = npcData.getInt("id");
npcDat.set("npcId", id);
npcDat.set("idTemplate", npcData.getInt("idTemplate"));
// Level: for special bosses could be different
int level = 0;
float diff = 0; // difference between setted value and retail one
boolean minion = false;
switch (id)
{
case 29002: // and minions
case 29003:
case 29004:
case 29005:
{
minion = true;
// fallthrough
}
case 29001: // queenAnt
{
if (Config.QA_LEVEL > 0)
{
diff = Config.QA_LEVEL - npcData.getInt("level");
level = Config.QA_LEVEL;
}
else
{
level = npcData.getInt("level");
}
break;
}
case 29022: // zaken
{
if (Config.ZAKEN_LEVEL > 0)
{
diff = Config.ZAKEN_LEVEL - npcData.getInt("level");
level = Config.ZAKEN_LEVEL;
}
else
{
level = npcData.getInt("level");
}
break;
}
case 29015: // and minions
case 29016:
case 29017:
case 29018:
{
minion = true;
// fallthrough
}
case 29014: // orfen
{
if (Config.ORFEN_LEVEL > 0)
{
diff = Config.ORFEN_LEVEL - npcData.getInt("level");
level = Config.ORFEN_LEVEL;
}
else
{
level = npcData.getInt("level");
}
break;
}
case 29007: // and minions
case 29008:
case 290011:
{
minion = true;
// fallthrough
}
case 29006: // core
{
if (Config.CORE_LEVEL > 0)
{
diff = Config.CORE_LEVEL - npcData.getInt("level");
level = Config.CORE_LEVEL;
}
else
{
level = npcData.getInt("level");
}
break;
}
default:
{
level = npcData.getInt("level");
}
}
npcDat.set("level", level);
npcDat.set("jClass", npcData.getString("class"));
npcDat.set("baseShldDef", 0);
npcDat.set("baseShldRate", 0);
npcDat.set("baseCritRate", 4);
npcDat.set("name", npcData.getString("name"));
npcDat.set("serverSideName", npcData.getBoolean("serverSideName"));
npcDat.set("title", npcData.getString("title"));
npcDat.set("serverSideTitle", npcData.getBoolean("serverSideTitle"));
npcDat.set("collision_radius", npcData.getDouble("collision_radius"));
npcDat.set("collision_height", npcData.getDouble("collision_height"));
npcDat.set("sex", npcData.getString("sex"));
npcDat.set("type", npcData.getString("type"));
npcDat.set("baseAtkRange", npcData.getInt("attackrange"));
// BOSS POWER CHANGES
double multiValue = 1;
if (diff >= 15) // means that there is level customization
{
multiValue = multiValue * (diff / 10);
}
else if ((diff > 0) && (diff < 15))
{
multiValue = multiValue + (diff / 10);
}
if (minion)
{
multiValue = multiValue * Config.LEVEL_DIFF_MULTIPLIER_MINION;
}
else
{
switch (id)
{
case 29001: // queenAnt
{
if (Config.QA_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.QA_POWER_MULTIPLIER;
}
break;
}
case 29022: // zaken
{
if (Config.ZAKEN_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.ZAKEN_POWER_MULTIPLIER;
}
break;
}
case 29014: // orfen
{
if (Config.ORFEN_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.ORFEN_POWER_MULTIPLIER;
}
break;
}
case 29006: // core
{
if (Config.CORE_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.CORE_POWER_MULTIPLIER;
}
break;
}
case 29019: // antharas
{
if (Config.ANTHARAS_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.ANTHARAS_POWER_MULTIPLIER;
}
break;
}
case 29028: // valakas
{
if (Config.VALAKAS_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.VALAKAS_POWER_MULTIPLIER;
}
break;
}
case 29020: // baium
{
if (Config.BAIUM_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.BAIUM_POWER_MULTIPLIER;
}
break;
}
case 29045: // frintezza
{
if (Config.FRINTEZZA_POWER_MULTIPLIER > 0)
{
multiValue = multiValue * Config.FRINTEZZA_POWER_MULTIPLIER;
}
break;
}
}
}
npcDat.set("rewardExp", npcData.getInt("exp") * multiValue);
npcDat.set("rewardSp", npcData.getInt("sp") * multiValue);
npcDat.set("basePAtkSpd", npcData.getInt("atkspd") * multiValue);
npcDat.set("baseMAtkSpd", npcData.getInt("matkspd") * multiValue);
npcDat.set("baseHpMax", npcData.getInt("hp") * multiValue);
npcDat.set("baseMpMax", npcData.getInt("mp") * multiValue);
npcDat.set("baseHpReg", ((int) npcData.getFloat("hpreg") * multiValue) > 0 ? npcData.getFloat("hpreg") : 1.5 + ((level - 1) / 10.0));
npcDat.set("baseMpReg", ((int) npcData.getFloat("mpreg") * multiValue) > 0 ? npcData.getFloat("mpreg") : 0.9 + ((0.3 * (level - 1)) / 10.0));
npcDat.set("basePAtk", npcData.getInt("patk") * multiValue);
npcDat.set("basePDef", npcData.getInt("pdef") * multiValue);
npcDat.set("baseMAtk", npcData.getInt("matk") * multiValue);
npcDat.set("baseMDef", npcData.getInt("mdef") * multiValue);
npcDat.set("aggroRange", npcData.getInt("aggro"));
npcDat.set("rhand", npcData.getInt("rhand"));
npcDat.set("lhand", npcData.getInt("lhand"));
npcDat.set("armor", npcData.getInt("armor"));
npcDat.set("baseWalkSpd", npcData.getInt("walkspd"));
npcDat.set("baseRunSpd", npcData.getInt("runspd"));
// constants, until we have stats in DB
npcDat.safeSet("baseSTR", npcData.getInt("str"), 0, BaseStat.MAX_STAT_VALUE, "Loading npc template id: " + npcData.getInt("idTemplate"));
npcDat.safeSet("baseCON", npcData.getInt("con"), 0, BaseStat.MAX_STAT_VALUE, "Loading npc template id: " + npcData.getInt("idTemplate"));
npcDat.safeSet("baseDEX", npcData.getInt("dex"), 0, BaseStat.MAX_STAT_VALUE, "Loading npc template id: " + npcData.getInt("idTemplate"));
npcDat.safeSet("baseINT", npcData.getInt("int"), 0, BaseStat.MAX_STAT_VALUE, "Loading npc template id: " + npcData.getInt("idTemplate"));
npcDat.safeSet("baseWIT", npcData.getInt("wit"), 0, BaseStat.MAX_STAT_VALUE, "Loading npc template id: " + npcData.getInt("idTemplate"));
npcDat.safeSet("baseMEN", npcData.getInt("men"), 0, BaseStat.MAX_STAT_VALUE, "Loading npc template id: " + npcData.getInt("idTemplate"));
npcDat.set("baseCpMax", 0);
npcDat.set("factionId", npcData.getString("faction_id"));
npcDat.set("factionRange", npcData.getInt("faction_range"));
npcDat.set("isUndead", npcData.getString("isUndead"));
npcDat.set("absorb_level", npcData.getString("absorb_level"));
npcDat.set("absorb_type", npcData.getString("absorb_type"));
final NpcTemplate template = new NpcTemplate(npcDat, custom);
template.addVulnerability(Stat.BOW_WPN_VULN, 1);
template.addVulnerability(Stat.BLUNT_WPN_VULN, 1);
template.addVulnerability(Stat.DAGGER_WPN_VULN, 1);
_npcs.put(id, template);
}
LOGGER.info("NpcTable: Loaded " + _npcs.size() + " Npc Templates.");
}
public void reloadNpc(int id)
{
try (Connection con = DatabaseFactory.getConnection())
{
// save a copy of the old data
final NpcTemplate old = getTemplate(id);
final Map<Integer, Skill> skills = new HashMap<>();
skills.putAll(old.getSkills());
final List<DropCategory> categories = new ArrayList<>();
if (old.getDropData() != null)
{
categories.addAll(old.getDropData());
}
final List<ClassId> classIds = old.getTeachInfo();
final List<MinionData> minions = new ArrayList<>();
if (old.getMinionData() != null)
{
minions.addAll(old.getMinionData());
}
if (old.isCustom())
{
final PreparedStatement st = con.prepareStatement("SELECT * FROM custom_npc WHERE id=?");
st.setInt(1, id);
final ResultSet rs = st.executeQuery();
fillNpcTable(rs, true);
rs.close();
st.close();
}
else
{
final PreparedStatement st = con.prepareStatement("SELECT * FROM npc WHERE id=?");
st.setInt(1, id);
final ResultSet rs = st.executeQuery();
fillNpcTable(rs, false);
rs.close();
st.close();
}
// restore additional data from saved copy
final NpcTemplate created = getTemplate(id);
for (Skill skill : skills.values())
{
created.addSkill(skill);
}
for (ClassId classId : classIds)
{
created.addTeachInfo(classId);
}
for (MinionData minion : minions)
{
created.addRaidData(minion);
}
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Could not reload data for NPC " + id + " " + e);
}
}
public void reloadAllNpc()
{
load();
}
public void saveNpc(StatSet npc)
{
try (Connection con = DatabaseFactory.getConnection())
{
final Map<String, Object> set = npc.getSet();
String name = "";
String values = "";
final NpcTemplate old = getTemplate(npc.getInt("npcId"));
for (Object obj : set.keySet())
{
name = (String) obj;
if (!name.equalsIgnoreCase("npcId"))
{
if (values != "")
{
values += ", ";
}
values += name + " = '" + set.get(name) + "'";
}
}
PreparedStatement statement = null;
if (old.isCustom())
{
statement = con.prepareStatement("UPDATE custom_npc SET " + values + " WHERE id = ?");
}
else
{
statement = con.prepareStatement("UPDATE npc SET " + values + " WHERE id = ?");
}
statement.setInt(1, npc.getInt("npcId"));
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("NPCTable: Could not store new NPC data in database. " + e);
}
}
public boolean isInitialized()
{
return _initialized;
}
public void replaceTemplate(NpcTemplate npc)
{
_npcs.put(npc.getNpcId(), npc);
}
public NpcTemplate getTemplate(int id)
{
return _npcs.get(id);
}
public NpcTemplate getTemplateByName(String name)
{
for (NpcTemplate npcTemplate : _npcs.values())
{
if (npcTemplate.getName().equalsIgnoreCase(name))
{
return npcTemplate;
}
}
return null;
}
public NpcTemplate[] getAllOfLevel(int lvl)
{
final List<NpcTemplate> list = new ArrayList<>();
for (NpcTemplate t : _npcs.values())
{
if (t.getLevel() == lvl)
{
list.add(t);
}
}
return list.toArray(new NpcTemplate[list.size()]);
}
public NpcTemplate[] getAllMonstersOfLevel(int lvl)
{
final List<NpcTemplate> list = new ArrayList<>();
for (NpcTemplate t : _npcs.values())
{
if ((t.getLevel() == lvl) && "Monster".equals(t.getType()))
{
list.add(t);
}
}
return list.toArray(new NpcTemplate[list.size()]);
}
public NpcTemplate[] getAllNpcStartingWith(String letter)
{
final List<NpcTemplate> list = new ArrayList<>();
for (NpcTemplate t : _npcs.values())
{
if (t.getName().startsWith(letter) && "Npc".equals(t.getType()))
{
list.add(t);
}
}
return list.toArray(new NpcTemplate[list.size()]);
}
/**
* @param classType
* @return
*/
public Set<Integer> getAllNpcOfClassType(String classType)
{
return null;
}
/**
* @param clazz
* @return
*/
public Set<Integer> getAllNpcOfL2jClass(Class<?> clazz)
{
return null;
}
/**
* @param aiType
* @return
*/
public Set<Integer> getAllNpcOfAiType(String aiType)
{
return null;
}
public Map<Integer, NpcTemplate> getAllTemplates()
{
return _npcs;
}
public static NpcTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final NpcTable INSTANCE = new NpcTable();
}
}

View File

@@ -0,0 +1,448 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.PetData;
public class PetDataTable
{
private static final Logger LOGGER = Logger.getLogger(PetDataTable.class.getName());
private static Map<Integer, Map<Integer, PetData>> _petTable = new HashMap<>();
private PetDataTable()
{
load();
}
private void load()
{
_petTable.clear();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT typeID, level, expMax, hpMax, mpMax, patk, pdef, matk, mdef, acc, evasion, crit, speed, atk_speed, cast_speed, feedMax, feedbattle, feednormal, loadMax, hpregen, mpregen, owner_exp_taken FROM pets_stats");
final ResultSet rset = statement.executeQuery();
int petId;
int petLevel;
while (rset.next())
{
petId = rset.getInt("typeID");
petLevel = rset.getInt("level");
// build the petdata for this level
final PetData petData = new PetData();
petData.setPetID(petId);
petData.setPetLevel(petLevel);
petData.setPetMaxExp(rset.getInt("expMax"));
petData.setPetMaxHP(rset.getInt("hpMax"));
petData.setPetMaxMP(rset.getInt("mpMax"));
petData.setPetPAtk(rset.getInt("patk"));
petData.setPetPDef(rset.getInt("pdef"));
petData.setPetMAtk(rset.getInt("matk"));
petData.setPetMDef(rset.getInt("mdef"));
petData.setPetAccuracy(rset.getInt("acc"));
petData.setPetEvasion(rset.getInt("evasion"));
petData.setPetCritical(rset.getInt("crit"));
petData.setPetSpeed(rset.getInt("speed"));
petData.setPetAtkSpeed(rset.getInt("atk_speed"));
petData.setPetCastSpeed(rset.getInt("cast_speed"));
petData.setPetMaxFeed(rset.getInt("feedMax"));
petData.setPetFeedNormal(rset.getInt("feednormal"));
petData.setPetFeedBattle(rset.getInt("feedbattle"));
petData.setPetMaxLoad(rset.getInt("loadMax"));
petData.setPetRegenHP(rset.getInt("hpregen"));
petData.setPetRegenMP(rset.getInt("mpregen"));
petData.setPetRegenMP(rset.getInt("mpregen"));
petData.setOwnerExpTaken(rset.getFloat("owner_exp_taken"));
// if its the first data for this petid, we initialize its level Map
if (!_petTable.containsKey(petId))
{
_petTable.put(petId, new HashMap<Integer, PetData>());
}
_petTable.get(petId).put(petLevel, petData);
}
rset.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Could not load pets stats " + e);
}
}
public void addPetData(PetData petData)
{
final Map<Integer, PetData> h = _petTable.get(petData.getPetID());
if (h == null)
{
final Map<Integer, PetData> statTable = new HashMap<>();
statTable.put(petData.getPetLevel(), petData);
_petTable.put(petData.getPetID(), statTable);
return;
}
h.put(petData.getPetLevel(), petData);
}
public void addPetData(PetData[] petLevelsList)
{
for (PetData element : petLevelsList)
{
addPetData(element);
}
}
public PetData getPetData(int petID, int petLevel)
{
return _petTable.get(petID).get(petLevel);
}
/**
* Pets stuffs
* @param npcId
* @return
*/
public static boolean isWolf(int npcId)
{
return npcId == 12077;
}
public static boolean isSinEater(int npcId)
{
return npcId == 12564;
}
public static boolean isHatchling(int npcId)
{
return (npcId > 12310) && (npcId < 12314);
}
public static boolean isStrider(int npcId)
{
return (npcId > 12525) && (npcId < 12529);
}
public static boolean isWyvern(int npcId)
{
return npcId == 12621;
}
public static boolean isBaby(int npcId)
{
return (npcId > 12779) && (npcId < 12783);
}
public static boolean isPetFood(int itemId)
{
return (itemId == 2515) || (itemId == 4038) || (itemId == 5168) || (itemId == 5169) || (itemId == 6316) || (itemId == 7582);
}
public static boolean isWolfFood(int itemId)
{
return itemId == 2515;
}
public static boolean isSinEaterFood(int itemId)
{
return itemId == 2515;
}
public static boolean isHatchlingFood(int itemId)
{
return itemId == 4038;
}
public static boolean isStriderFood(int itemId)
{
return (itemId == 5168) || (itemId == 5169);
}
public static boolean isWyvernFood(int itemId)
{
return itemId == 6316;
}
public static boolean isBabyFood(int itemId)
{
return itemId == 7582;
}
public static int getFoodItemId(int npcId)
{
if (isWolf(npcId))
{
return 2515;
}
else if (isSinEater(npcId))
{
return 2515;
}
else if (isHatchling(npcId))
{
return 4038;
}
else if (isStrider(npcId))
{
return 5168;
}
else if (isBaby(npcId))
{
return 7582;
}
else
{
return 0;
}
}
public static int getPetIdByItemId(int itemId)
{
switch (itemId)
{
// wolf pet a
case 2375:
{
return 12077;
}
// Sin Eater
case 4425:
{
return 12564;
}
// hatchling of wind
case 3500:
{
return 12311;
}
// hatchling of star
case 3501:
{
return 12312;
}
// hatchling of twilight
case 3502:
{
return 12313;
}
// wind strider
case 4422:
{
return 12526;
}
// Star strider
case 4423:
{
return 12527;
}
// Twilight strider
case 4424:
{
return 12528;
}
// Wyvern
case 8663:
{
return 12621;
}
// Baby Buffalo
case 6648:
{
return 12780;
}
// Baby Cougar
case 6649:
{
return 12782;
}
// Baby Kookaburra
case 6650:
{
return 12781;
}
// unknown item id.. should never happen
default:
{
return 0;
}
}
}
public static int getHatchlingWindId()
{
return 12311;
}
public static int getHatchlingStarId()
{
return 12312;
}
public static int getHatchlingTwilightId()
{
return 12313;
}
public static int getStriderWindId()
{
return 12526;
}
public static int getStriderStarId()
{
return 12527;
}
public static int getStriderTwilightId()
{
return 12528;
}
public static int getWyvernItemId()
{
return 8663;
}
public static int getStriderWindItemId()
{
return 4422;
}
public static int getStriderStarItemId()
{
return 4423;
}
public static int getStriderTwilightItemId()
{
return 4424;
}
public static int getSinEaterItemId()
{
return 4425;
}
public static boolean isPetItem(int itemId)
{
return (itemId == 2375 // wolf
) || (itemId == 4425 // Sin Eater
) || (itemId == 3500) || (itemId == 3501) || (itemId == 3502 // hatchlings
) || (itemId == 4422) || (itemId == 4423) || (itemId == 4424 // striders
) || (itemId == 8663 // Wyvern
) || (itemId == 6648) || (itemId == 6649) || (itemId == 6650); // Babies
}
public static int[] getPetItemsAsNpc(int npcId)
{
switch (npcId)
{
case 12077: // wolf pet a
{
return new int[]
{
2375
};
}
case 12564: // Sin Eater
{
return new int[]
{
4425
};
}
case 12311: // hatchling of wind
case 12312: // hatchling of star
case 12313: // hatchling of twilight
{
return new int[]
{
3500,
3501,
3502
};
}
case 12526: // wind strider
case 12527: // Star strider
case 12528: // Twilight strider
{
return new int[]
{
4422,
4423,
4424
};
}
case 12621: // Wyvern
{
return new int[]
{
8663
};
}
case 12780: // Baby Buffalo
case 12782: // Baby Cougar
case 12781: // Baby Kookaburra
{
return new int[]
{
6648,
6649,
6650
};
}
// unknown item id.. should never happen
default:
{
return new int[]
{
0
};
}
}
}
public static boolean isMountable(int npcId)
{
return (npcId == 12526) // wind strider
|| (npcId == 12527) // star strider
|| (npcId == 12528) // twilight strider
|| (npcId == 12621); // wyvern
}
public static PetDataTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final PetDataTable INSTANCE = new PetDataTable();
}
}

View File

@@ -0,0 +1,117 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
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;
public class PetNameTable
{
private static final Logger LOGGER = Logger.getLogger(PetNameTable.class.getName());
public boolean doesPetNameExist(String name, int petNpcId)
{
boolean result = true;
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT name FROM pets p, items i WHERE p.item_obj_id = i.object_id AND name=? AND i.item_id IN (?)");
statement.setString(1, name);
String cond = "";
for (int it : PetDataTable.getPetItemsAsNpc(petNpcId))
{
if (cond != "")
{
cond += ", ";
}
cond += it;
}
statement.setString(2, cond);
final ResultSet rset = statement.executeQuery();
result = rset.next();
rset.close();
statement.close();
}
catch (SQLException e)
{
LOGGER.warning("Could not check existing petname " + 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("ERROR : 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;
}
public static PetNameTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final PetNameTable INSTANCE = new PetNameTable();
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.Skill;
/**
* @author l2jserver
*/
public class SkillSpellbookTable
{
private static final Logger LOGGER = Logger.getLogger(SkillSpellbookTable.class.getName());
private static Map<Integer, Integer> skillSpellbooks;
private SkillSpellbookTable()
{
skillSpellbooks = new HashMap<>();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT skill_id, item_id FROM skill_spellbooks");
final ResultSet spbooks = statement.executeQuery();
while (spbooks.next())
{
skillSpellbooks.put(spbooks.getInt("skill_id"), spbooks.getInt("item_id"));
}
spbooks.close();
statement.close();
LOGGER.info("SkillSpellbookTable: Loaded " + skillSpellbooks.size() + " spellbooks");
}
catch (Exception e)
{
LOGGER.warning("Error while loading spellbook data " + e);
}
}
public int getBookForSkill(int skillId, int level)
{
if ((skillId == Skill.SKILL_DIVINE_INSPIRATION) && (level != -1))
{
switch (level)
{
case 1:
{
return 8618; // Ancient Book - Divine Inspiration (Modern Language Version)
}
case 2:
{
return 8619; // Ancient Book - Divine Inspiration (Original Language Version)
}
case 3:
{
return 8620; // Ancient Book - Divine Inspiration (Manuscript)
}
case 4:
{
return 8621; // Ancient Book - Divine Inspiration (Original Version)
}
default:
{
return -1;
}
}
}
if (!skillSpellbooks.containsKey(skillId))
{
return -1;
}
return skillSpellbooks.get(skillId);
}
public int getBookForSkill(Skill skill)
{
return getBookForSkill(skill.getId(), -1);
}
public int getBookForSkill(Skill skill, int level)
{
return getBookForSkill(skill.getId(), level);
}
public static SkillSpellbookTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final SkillSpellbookTable INSTANCE = new SkillSpellbookTable();
}
}

View File

@@ -0,0 +1,647 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.datatables.SkillTable;
import org.l2jmobius.gameserver.datatables.xml.PlayerTemplateData;
import org.l2jmobius.gameserver.model.EnchantSkillLearn;
import org.l2jmobius.gameserver.model.PledgeSkillLearn;
import org.l2jmobius.gameserver.model.Skill;
import org.l2jmobius.gameserver.model.SkillLearn;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.templates.PlayerTemplate;
import org.l2jmobius.gameserver.model.base.ClassId;
import org.l2jmobius.gameserver.model.skills.holders.ISkillsHolder;
import org.l2jmobius.gameserver.model.skills.holders.PlayerSkillHolder;
/**
* @version $Revision: 1.13.2.2.2.8 $ $Date: 2005/04/06 16:13:25 $
*/
public class SkillTreeTable
{
private static final Logger LOGGER = Logger.getLogger(SkillTreeTable.class.getName());
private final Map<ClassId, Map<Integer, SkillLearn>> _skillTrees = new EnumMap<>(ClassId.class);
private final List<SkillLearn> _fishingSkillTrees = new ArrayList<>();
private final List<SkillLearn> _expandDwarfCraftSkillTrees = new ArrayList<>();
private final List<PledgeSkillLearn> _pledgeSkillTrees = new ArrayList<>();
private final List<EnchantSkillLearn> _enchantSkillTrees = new ArrayList<>();
private SkillTreeTable()
{
load();
}
public void load()
{
_skillTrees.clear();
_fishingSkillTrees.clear();
_expandDwarfCraftSkillTrees.clear();
_pledgeSkillTrees.clear();
_enchantSkillTrees.clear();
ClassId classId = null;
ClassId parentClassId;
int count = 0;
try (Connection con = DatabaseFactory.getConnection())
{
for (PlayerTemplate playerTemplate : PlayerTemplateData.getInstance().getAllTemplates())
{
final Map<Integer, SkillLearn> map = new HashMap<>();
classId = playerTemplate.getClassId();
parentClassId = classId.getParent();
final PreparedStatement statement = con.prepareStatement("SELECT * FROM skill_trees where class_id=? ORDER BY skill_id, level");
statement.setInt(1, classId.getId());
final ResultSet skilltree = statement.executeQuery();
if (parentClassId != null)
{
map.putAll(_skillTrees.get(parentClassId));
count -= map.size();
}
int prevSkillId = -1;
while (skilltree.next())
{
final int id = skilltree.getInt("skill_id");
final int lvl = skilltree.getInt("level");
final String name = skilltree.getString("name");
final int minLvl = skilltree.getInt("min_level");
final int cost = skilltree.getInt("sp");
if (prevSkillId != id)
{
prevSkillId = id;
}
map.put(SkillTable.getSkillHashCode(id, lvl), new SkillLearn(id, lvl, minLvl, name, cost, 0, 0));
}
_skillTrees.put(classId, map);
count += map.size();
skilltree.close();
statement.close();
}
}
catch (Exception e)
{
if (classId != null)
{
LOGGER.warning("Error while creating skill tree (Class ID " + classId.getId() + "): " + e);
}
}
LOGGER.info("SkillTreeTable: Loaded " + count + " skills.");
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM fishing_skill_trees ORDER BY skill_id, level");
final ResultSet skilltree = statement.executeQuery();
int prevSkillId = -1;
while (skilltree.next())
{
final int id = skilltree.getInt("skill_id");
final int lvl = skilltree.getInt("level");
final String name = skilltree.getString("name");
final int minLvl = skilltree.getInt("min_level");
final int cost = skilltree.getInt("sp");
final int costId = skilltree.getInt("costid");
final int costCount = skilltree.getInt("cost");
final int isDwarven = skilltree.getInt("isfordwarf");
if (prevSkillId != id)
{
prevSkillId = id;
}
final SkillLearn skill = new SkillLearn(id, lvl, minLvl, name, cost, costId, costCount);
if (isDwarven == 0)
{
_fishingSkillTrees.add(skill);
}
else
{
_expandDwarfCraftSkillTrees.add(skill);
}
}
skilltree.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while creating fishing skill table " + e);
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM enchant_skill_trees ORDER BY skill_id, level");
final ResultSet skilltree = statement.executeQuery();
int prevSkillId = -1;
while (skilltree.next())
{
final int id = skilltree.getInt("skill_id");
final int lvl = skilltree.getInt("level");
final String name = skilltree.getString("name");
final int baseLvl = skilltree.getInt("base_lvl");
final int minSkillLvl = skilltree.getInt("min_skill_lvl");
final int sp = skilltree.getInt("sp");
final int exp = skilltree.getInt("exp");
final byte rate76 = skilltree.getByte("success_rate76");
final byte rate77 = skilltree.getByte("success_rate77");
final byte rate78 = skilltree.getByte("success_rate78");
final byte rate79 = skilltree.getByte("success_rate79");
final byte rate80 = skilltree.getByte("success_rate80");
if (prevSkillId != id)
{
prevSkillId = id;
}
_enchantSkillTrees.add(new EnchantSkillLearn(id, lvl, minSkillLvl, baseLvl, name, sp, exp, rate76, rate77, rate78, rate79, rate80));
}
skilltree.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while creating enchant skill table " + e);
}
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT * FROM pledge_skill_trees ORDER BY skill_id, level");
final ResultSet skilltree = statement.executeQuery();
int prevSkillId = -1;
while (skilltree.next())
{
final int id = skilltree.getInt("skill_id");
final int lvl = skilltree.getInt("level");
final String name = skilltree.getString("name");
final int baseLvl = skilltree.getInt("clan_lvl");
final int sp = skilltree.getInt("repCost");
final int itemId = skilltree.getInt("itemId");
if (prevSkillId != id)
{
prevSkillId = id;
}
_pledgeSkillTrees.add(new PledgeSkillLearn(id, lvl, baseLvl, name, sp, itemId));
}
skilltree.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("Error while creating fishing skill table " + e);
}
LOGGER.info("FishingSkillTreeTable: Loaded " + _fishingSkillTrees.size() + " general skills.");
LOGGER.info("FishingSkillTreeTable: Loaded " + _expandDwarfCraftSkillTrees.size() + " dwarven skills.");
LOGGER.info("EnchantSkillTreeTable: Loaded " + _enchantSkillTrees.size() + " enchant skills.");
LOGGER.info("PledgeSkillTreeTable: Loaded " + _pledgeSkillTrees.size() + " pledge skills.");
}
/**
* Return the minimum level needed to have this Expertise.
* @param grade The grade level searched
* @return
*/
public int getExpertiseLevel(int grade)
{
if (grade <= 0)
{
return 0;
}
// since expertise comes at same level for all classes we use paladin for now
final Map<Integer, SkillLearn> learnMap = _skillTrees.get(ClassId.PALADIN);
final int skillHashCode = SkillTable.getSkillHashCode(239, grade);
if (learnMap.containsKey(skillHashCode))
{
return learnMap.get(skillHashCode).getMinLevel();
}
return 0;
}
/**
* Each class receives new skill on certain levels, this methods allow the retrieval of the minimum character level of given class required to learn a given skill
* @param skillId The iD of the skill
* @param classId The classId of the character
* @param skillLvl The SkillLvl
* @return The min level
*/
public int getMinSkillLevel(int skillId, ClassId classId, int skillLvl)
{
final Map<Integer, SkillLearn> map = _skillTrees.get(classId);
final int skillHashCode = SkillTable.getSkillHashCode(skillId, skillLvl);
if (map.containsKey(skillHashCode))
{
return map.get(skillHashCode).getMinLevel();
}
return 0;
}
public int getMinSkillLevel(int skillId, int skillLvl)
{
final int skillHashCode = SkillTable.getSkillHashCode(skillId, skillLvl);
// Look on all classes for this skill (takes the first one found)
for (Map<Integer, SkillLearn> map : _skillTrees.values())
{
// checks if the current class has this skill
if (map.containsKey(skillHashCode))
{
return map.get(skillHashCode).getMinLevel();
}
}
return 0;
}
public SkillLearn[] getAvailableSkills(PlayerInstance player, ClassId classId)
{
final List<SkillLearn> result = getAvailableSkills(player, classId, player);
return result.toArray(new SkillLearn[result.size()]);
}
/**
* Gets the available skills.
* @param player the learning skill player.
* @param classId the learning skill class ID.
* @param holder
* @return all available skills for a given {@code player}, {@code classId}, {@code includeByFs} and {@code includeAutoGet}.
*/
private List<SkillLearn> getAvailableSkills(PlayerInstance player, ClassId classId, ISkillsHolder holder)
{
final List<SkillLearn> result = new ArrayList<>();
final Collection<SkillLearn> skills = _skillTrees.get(classId).values();
if (skills.isEmpty())
{
LOGGER.warning(getClass().getSimpleName() + ": Skilltree for class " + classId + " is not defined!");
return result;
}
for (SkillLearn skill : skills)
{
if (skill.getMinLevel() <= player.getLevel())
{
final Skill oldSkill = holder.getKnownSkill(skill.getId());
if (oldSkill != null)
{
if (oldSkill.getLevel() == (skill.getLevel() - 1))
{
result.add(skill);
}
}
else if (skill.getLevel() == 1)
{
result.add(skill);
}
}
}
return result;
}
public SkillLearn[] getAvailableSkills(PlayerInstance player)
{
final List<SkillLearn> result = new ArrayList<>();
final List<SkillLearn> skills = new ArrayList<>();
skills.addAll(_fishingSkillTrees);
if (player.hasDwarvenCraft() && (_expandDwarfCraftSkillTrees != null))
{
skills.addAll(_expandDwarfCraftSkillTrees);
}
final Skill[] oldSkills = player.getAllSkills();
for (SkillLearn temp : skills)
{
if (temp.getMinLevel() <= player.getLevel())
{
boolean knownSkill = false;
for (int j = 0; (j < oldSkills.length) && !knownSkill; j++)
{
if (oldSkills[j].getId() == temp.getId())
{
knownSkill = true;
if (oldSkills[j].getLevel() == (temp.getLevel() - 1))
{
// this is the next level of a skill that we know
result.add(temp);
}
}
}
if (!knownSkill && (temp.getLevel() == 1))
{
// this is a new skill
result.add(temp);
}
}
}
return result.toArray(new SkillLearn[result.size()]);
}
public EnchantSkillLearn[] getAvailableEnchantSkills(PlayerInstance player)
{
final List<EnchantSkillLearn> result = new ArrayList<>();
final List<EnchantSkillLearn> skills = new ArrayList<>();
skills.addAll(_enchantSkillTrees);
final Skill[] oldSkills = player.getAllSkills();
if (player.getLevel() < 76)
{
return result.toArray(new EnchantSkillLearn[result.size()]);
}
for (EnchantSkillLearn skillLearn : skills)
{
boolean isKnownSkill = false;
for (Skill skill : oldSkills)
{
if (isKnownSkill)
{
continue;
}
if (skill.getId() == skillLearn.getId())
{
isKnownSkill = true;
if (skill.getLevel() == skillLearn.getMinSkillLevel())
{
// this is the next level of a skill that we know
result.add(skillLearn);
}
}
}
}
return result.toArray(new EnchantSkillLearn[result.size()]);
}
public PledgeSkillLearn[] getAvailablePledgeSkills(PlayerInstance player)
{
final List<PledgeSkillLearn> result = new ArrayList<>();
final List<PledgeSkillLearn> skills = _pledgeSkillTrees;
if (skills == null)
{
LOGGER.warning("No clan skills defined!");
return new PledgeSkillLearn[0];
}
final Skill[] oldSkills = player.getClan().getAllSkills();
for (PledgeSkillLearn temp : skills)
{
if (temp.getBaseLevel() <= player.getClan().getLevel())
{
boolean knownSkill = false;
for (int j = 0; (j < oldSkills.length) && !knownSkill; j++)
{
if (oldSkills[j].getId() == temp.getId())
{
knownSkill = true;
if (oldSkills[j].getLevel() == (temp.getLevel() - 1))
{
// this is the next level of a skill that we know
result.add(temp);
}
}
}
if (!knownSkill && (temp.getLevel() == 1))
{
// this is a new skill
result.add(temp);
}
}
}
return result.toArray(new PledgeSkillLearn[result.size()]);
}
/**
* Returns all allowed skills for a given class.
* @param classId
* @return all allowed skills for a given class.
*/
public Collection<SkillLearn> getAllowedSkills(ClassId classId)
{
return _skillTrees.get(classId).values();
}
public int getMinLevelForNewSkill(PlayerInstance player, ClassId classId)
{
int minLevel = 0;
final Collection<SkillLearn> skills = _skillTrees.get(classId).values();
for (SkillLearn temp : skills)
{
if ((temp.getMinLevel() > player.getLevel()) && (temp.getSpCost() != 0) && ((minLevel == 0) || (temp.getMinLevel() < minLevel)))
{
minLevel = temp.getMinLevel();
}
}
return minLevel;
}
public int getMinLevelForNewSkill(PlayerInstance player)
{
int minLevel = 0;
final List<SkillLearn> skills = new ArrayList<>();
skills.addAll(_fishingSkillTrees);
if (player.hasDwarvenCraft() && (_expandDwarfCraftSkillTrees != null))
{
skills.addAll(_expandDwarfCraftSkillTrees);
}
for (SkillLearn s : skills)
{
if ((s.getMinLevel() > player.getLevel()) && ((minLevel == 0) || (s.getMinLevel() < minLevel)))
{
minLevel = s.getMinLevel();
}
}
return minLevel;
}
public int getSkillCost(PlayerInstance player, Skill skill)
{
int skillCost = 100000000;
final ClassId classId = player.getSkillLearningClassId();
final int skillHashCode = SkillTable.getSkillHashCode(skill);
if (_skillTrees.get(classId).containsKey(skillHashCode))
{
final SkillLearn skillLearn = _skillTrees.get(classId).get(skillHashCode);
if (skillLearn.getMinLevel() <= player.getLevel())
{
skillCost = skillLearn.getSpCost();
if (!player.getClassId().equalsOrChildOf(classId))
{
if (skill.getCrossLearnAdd() < 0)
{
return skillCost;
}
skillCost += skill.getCrossLearnAdd();
skillCost *= skill.getCrossLearnMul();
}
if ((classId.getRace() != player.getRace()) && !player.isSubClassActive())
{
skillCost *= skill.getCrossLearnRace();
}
if (classId.isMage() != player.getClassId().isMage())
{
skillCost *= skill.getCrossLearnProf();
}
}
}
return skillCost;
}
public int getSkillSpCost(PlayerInstance player, Skill skill)
{
int skillCost = 100000000;
final EnchantSkillLearn[] enchantSkillLearnList = getAvailableEnchantSkills(player);
for (EnchantSkillLearn enchantSkillLearn : enchantSkillLearnList)
{
if (enchantSkillLearn.getId() != skill.getId())
{
continue;
}
if (enchantSkillLearn.getLevel() != skill.getLevel())
{
continue;
}
if (76 > player.getLevel())
{
continue;
}
skillCost = enchantSkillLearn.getSpCost();
}
return skillCost;
}
public int getSkillExpCost(PlayerInstance player, Skill skill)
{
int skillCost = 100000000;
final EnchantSkillLearn[] enchantSkillLearnList = getAvailableEnchantSkills(player);
for (EnchantSkillLearn enchantSkillLearn : enchantSkillLearnList)
{
if (enchantSkillLearn.getId() != skill.getId())
{
continue;
}
if (enchantSkillLearn.getLevel() != skill.getLevel())
{
continue;
}
if (76 > player.getLevel())
{
continue;
}
skillCost = enchantSkillLearn.getExp();
}
return skillCost;
}
public byte getSkillRate(PlayerInstance player, Skill skill)
{
final EnchantSkillLearn[] enchantSkillLearnList = getAvailableEnchantSkills(player);
for (EnchantSkillLearn enchantSkillLearn : enchantSkillLearnList)
{
if (enchantSkillLearn.getId() != skill.getId())
{
continue;
}
if (enchantSkillLearn.getLevel() != skill.getLevel())
{
continue;
}
return enchantSkillLearn.getRate(player);
}
return 0;
}
/**
* @param player
* @param classId
* @return
*/
public Collection<Skill> getAllAvailableSkills(PlayerInstance player, ClassId classId)
{
// Get available skills
int unLearnable = 0;
final PlayerSkillHolder holder = new PlayerSkillHolder(player.getSkills());
List<SkillLearn> learnable = getAvailableSkills(player, classId, holder);
while (learnable.size() > unLearnable)
{
for (SkillLearn s : learnable)
{
final Skill sk = SkillTable.getInstance().getInfo(s.getId(), s.getLevel());
if ((sk == null) || ((sk.getId() == Skill.SKILL_DIVINE_INSPIRATION) && !Config.AUTO_LEARN_DIVINE_INSPIRATION && !player.isGM()))
{
unLearnable++;
continue;
}
holder.addSkill(sk);
}
// Get new available skills, some skills depend of previous skills to be available.
learnable = getAvailableSkills(player, classId, holder);
}
return holder.getSkills().values();
}
public static SkillTreeTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final SkillTreeTable INSTANCE = new SkillTreeTable();
}
}

View File

@@ -0,0 +1,389 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.instancemanager.DayNightSpawnManager;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
import org.l2jmobius.gameserver.model.entity.olympiad.Olympiad;
import org.l2jmobius.gameserver.model.spawn.Spawn;
/**
* @author Nightmare
* @version $Revision: 1.5.2.6.2.7 $ $Date: 2005/03/27 15:29:18 $
*/
public class SpawnTable
{
private static final Logger LOGGER = Logger.getLogger(SpawnTable.class.getName());
private final Map<Integer, Spawn> _spawntable = new ConcurrentHashMap<>();
private int _npcSpawnCount;
private int _customSpawnCount;
private int _highestId;
private SpawnTable()
{
if (!Config.ALT_DEV_NO_SPAWNS)
{
fillSpawnTable();
}
}
public Map<Integer, Spawn> getSpawnTable()
{
return _spawntable;
}
private void fillSpawnTable()
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement;
if (Config.DELETE_GMSPAWN_ON_CUSTOM)
{
statement = con.prepareStatement("SELECT id, count, npc_templateid, locx, locy, locz, heading, respawn_delay, loc_id, periodOfDay FROM spawnlist where id NOT in ( select id from custom_notspawned where isCustom = false ) ORDER BY id");
}
else
{
statement = con.prepareStatement("SELECT id, count, npc_templateid, locx, locy, locz, heading, respawn_delay, loc_id, periodOfDay FROM spawnlist ORDER BY id");
}
final ResultSet rset = statement.executeQuery();
Spawn spawnDat;
NpcTemplate template1;
while (rset.next())
{
template1 = NpcTable.getInstance().getTemplate(rset.getInt("npc_templateid"));
if (template1 != null)
{
if (template1.getType().equalsIgnoreCase("SiegeGuard"))
{
// Don't spawn
}
else if (template1.getType().equalsIgnoreCase("RaidBoss"))
{
// Don't spawn raidboss
}
else if (template1.getType().equalsIgnoreCase("GrandBoss"))
{
// Don't spawn grandboss
}
else if (!Config.ALLOW_CLASS_MASTERS && template1.getType().equals("ClassMaster"))
{
// Dont' spawn class masters
}
else
{
spawnDat = new Spawn(template1);
spawnDat.setId(rset.getInt("id"));
spawnDat.setAmount(rset.getInt("count"));
spawnDat.setX(rset.getInt("locx"));
spawnDat.setY(rset.getInt("locy"));
spawnDat.setZ(rset.getInt("locz"));
spawnDat.setHeading(rset.getInt("heading"));
spawnDat.setRespawnDelay(rset.getInt("respawn_delay"));
final int loc_id = rset.getInt("loc_id");
spawnDat.setLocation(loc_id);
switch (rset.getInt("periodOfDay"))
{
case 0: // default
{
_npcSpawnCount += spawnDat.init();
break;
}
case 1: // Day
{
DayNightSpawnManager.getInstance().addDayCreature(spawnDat);
_npcSpawnCount++;
break;
}
case 2: // Night
{
DayNightSpawnManager.getInstance().addNightCreature(spawnDat);
_npcSpawnCount++;
break;
}
}
_spawntable.put(spawnDat.getId(), spawnDat);
if (spawnDat.getId() > _highestId)
{
_highestId = spawnDat.getId();
}
if (spawnDat.getTemplate().getNpcId() == Olympiad.OLY_MANAGER)
{
Olympiad.OLY_MANAGERS.add(spawnDat);
}
}
}
else
{
LOGGER.warning("SpawnTable: Data missing in NPC table for ID: " + rset.getInt("npc_templateid") + ".");
}
}
rset.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("SpawnTable: Spawn could not be initialized. " + e);
}
LOGGER.info("SpawnTable: Loaded " + _spawntable.size() + " Npc Spawn Locations. ");
LOGGER.info("SpawnTable: Total number of NPCs in the world: " + _npcSpawnCount);
// -------------------------------Custom Spawnlist----------------------------//
if (Config.CUSTOM_SPAWNLIST_TABLE)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement;
if (Config.DELETE_GMSPAWN_ON_CUSTOM)
{
statement = con.prepareStatement("SELECT id, count, npc_templateid, locx, locy, locz, heading, respawn_delay, loc_id, periodOfDay FROM custom_spawnlist where id NOT in ( select id from custom_notspawned where isCustom = false ) ORDER BY id");
}
else
{
statement = con.prepareStatement("SELECT id, count, npc_templateid, locx, locy, locz, heading, respawn_delay, loc_id, periodOfDay FROM custom_spawnlist ORDER BY id");
}
final ResultSet rset = statement.executeQuery();
Spawn spawnDat;
NpcTemplate template1;
while (rset.next())
{
template1 = NpcTable.getInstance().getTemplate(rset.getInt("npc_templateid"));
if (template1 != null)
{
if (template1.getType().equalsIgnoreCase("SiegeGuard"))
{
// Don't spawn
}
else if (template1.getType().equalsIgnoreCase("RaidBoss"))
{
// Don't spawn raidboss
}
else if (!Config.ALLOW_CLASS_MASTERS && template1.getType().equals("ClassMaster"))
{
// Dont' spawn class masters
}
else
{
spawnDat = new Spawn(template1);
spawnDat.setId(rset.getInt("id"));
spawnDat.setAmount(rset.getInt("count"));
spawnDat.setX(rset.getInt("locx"));
spawnDat.setY(rset.getInt("locy"));
spawnDat.setZ(rset.getInt("locz"));
spawnDat.setHeading(rset.getInt("heading"));
spawnDat.setRespawnDelay(rset.getInt("respawn_delay"));
final int loc_id = rset.getInt("loc_id");
spawnDat.setLocation(loc_id);
switch (rset.getInt("periodOfDay"))
{
case 0: // default
{
_customSpawnCount += spawnDat.init();
break;
}
case 1: // Day
{
DayNightSpawnManager.getInstance().addDayCreature(spawnDat);
_customSpawnCount++;
break;
}
case 2: // Night
{
DayNightSpawnManager.getInstance().addNightCreature(spawnDat);
_customSpawnCount++;
break;
}
}
_spawntable.put(spawnDat.getId(), spawnDat);
if (spawnDat.getId() > _highestId)
{
_highestId = spawnDat.getId();
}
}
}
else
{
LOGGER.warning("CustomSpawnTable: Data missing in NPC table for ID: " + rset.getInt("npc_templateid") + ".");
}
}
rset.close();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("CustomSpawnTable: Spawn could not be initialized. " + e);
}
LOGGER.info("CustomSpawnTable: Loaded " + _customSpawnCount + " Npc Spawn Locations. ");
LOGGER.info("CustomSpawnTable: Total number of NPCs in the world: " + _customSpawnCount);
}
}
public Spawn getTemplate(int id)
{
return _spawntable.get(id);
}
public void addNewSpawn(Spawn spawn, boolean storeInDb)
{
_highestId++;
spawn.setId(_highestId);
_spawntable.put(_highestId, spawn);
if (storeInDb)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("INSERT INTO " + (Config.SAVE_GMSPAWN_ON_CUSTOM ? "custom_spawnlist" : "spawnlist") + "(id,count,npc_templateid,locx,locy,locz,heading,respawn_delay,loc_id) values(?,?,?,?,?,?,?,?,?)");
statement.setInt(1, spawn.getId());
statement.setInt(2, spawn.getAmount());
statement.setInt(3, spawn.getNpcId());
statement.setInt(4, spawn.getX());
statement.setInt(5, spawn.getY());
statement.setInt(6, spawn.getZ());
statement.setInt(7, spawn.getHeading());
statement.setInt(8, spawn.getRespawnDelay() / 1000);
statement.setInt(9, spawn.getLocation());
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("SpawnTable: Could not store spawn in the DB. " + e);
}
}
}
public void deleteSpawn(Spawn spawn, boolean updateDb)
{
if (_spawntable.remove(spawn.getId()) == null)
{
return;
}
if (updateDb)
{
if (Config.DELETE_GMSPAWN_ON_CUSTOM)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("Replace into custom_notspawned VALUES (?,?)");
statement.setInt(1, spawn.getId());
statement.setBoolean(2, false);
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("SpawnTable: Spawn " + spawn.getId() + " could not be insert into DB. " + e);
}
}
else
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement;
statement = con.prepareStatement("DELETE FROM spawnlist WHERE id=?");
statement.setInt(1, spawn.getId());
statement.execute();
statement.close();
statement = con.prepareStatement("DELETE FROM custom_spawnlist WHERE id=?");
statement.setInt(1, spawn.getId());
statement.execute();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("SpawnTable: Spawn " + spawn.getId() + " could not be removed from DB. " + e);
}
}
}
}
// just wrapper
public void reloadAll()
{
fillSpawnTable();
}
/**
* Get all the spawn of a NPC
* @param player
* @param npcId : ID of the NPC to find.
* @param teleportIndex
*/
public void findNPCInstances(PlayerInstance player, int npcId, int teleportIndex)
{
int index = 0;
for (Spawn spawn : _spawntable.values())
{
if (npcId == spawn.getNpcId())
{
index++;
if (teleportIndex > -1)
{
if (teleportIndex == index)
{
player.teleToLocation(spawn.getX(), spawn.getY(), spawn.getZ(), true);
}
}
else
{
player.sendMessage(index + " - " + spawn.getTemplate().getName() + " (" + spawn.getId() + "): " + spawn.getX() + " " + spawn.getY() + " " + spawn.getZ());
}
}
}
if (index == 0)
{
player.sendMessage("No current spawns found.");
}
}
public static SpawnTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final SpawnTable INSTANCE = new SpawnTable();
}
}

View File

@@ -0,0 +1,127 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.Config;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.TeleportLocation;
/**
* @version $Revision: 1.3.2.2.2.3 $ $Date: 2005/03/27 15:29:18 $
*/
public class TeleportLocationTable
{
private static final Logger LOGGER = Logger.getLogger(TeleportLocationTable.class.getName());
private final Map<Integer, TeleportLocation> _teleports = new HashMap<>();
private TeleportLocationTable()
{
load();
}
public void load()
{
_teleports.clear();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT Description, id, loc_x, loc_y, loc_z, price, fornoble FROM teleport");
final ResultSet rset = statement.executeQuery();
TeleportLocation teleport;
while (rset.next())
{
teleport = new TeleportLocation();
teleport.setTeleId(rset.getInt("id"));
teleport.setX(rset.getInt("loc_x"));
teleport.setY(rset.getInt("loc_y"));
teleport.setZ(rset.getInt("loc_z"));
teleport.setPrice(rset.getInt("price"));
teleport.setForNoble(rset.getInt("fornoble") == 1);
_teleports.put(teleport.getTeleId(), teleport);
}
statement.close();
rset.close();
LOGGER.info("TeleportLocationTable: Loaded " + _teleports.size() + " Teleport Location Templates");
}
catch (Exception e)
{
LOGGER.warning("Error while creating teleport table " + e);
}
if (Config.CUSTOM_TELEPORT_TABLE)
{
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT Description, id, loc_x, loc_y, loc_z, price, fornoble FROM custom_teleport");
final ResultSet rset = statement.executeQuery();
TeleportLocation teleport;
int cTeleCount = _teleports.size();
while (rset.next())
{
teleport = new TeleportLocation();
teleport.setTeleId(rset.getInt("id"));
teleport.setX(rset.getInt("loc_x"));
teleport.setY(rset.getInt("loc_y"));
teleport.setZ(rset.getInt("loc_z"));
teleport.setPrice(rset.getInt("price"));
teleport.setForNoble(rset.getInt("fornoble") == 1);
_teleports.put(teleport.getTeleId(), teleport);
}
statement.close();
rset.close();
cTeleCount = _teleports.size() - cTeleCount;
if (cTeleCount > 0)
{
LOGGER.info("TeleportLocationTable: Loaded " + cTeleCount + " Custom Teleport Location Templates.");
}
}
catch (Exception e)
{
LOGGER.warning("Error while creating custom teleport table " + e);
}
}
}
public TeleportLocation getTemplate(int id)
{
return _teleports.get(id);
}
public static TeleportLocationTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final TeleportLocationTable INSTANCE = new TeleportLocationTable();
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.model.Territory;
public class TerritoryTable
{
private static final Logger LOGGER = Logger.getLogger(TerritoryTable.class.getName());
private static Map<Integer, Territory> _territory = new HashMap<>();
public TerritoryTable()
{
_territory.clear();
reload_data();
}
public int[] getRandomPoint(Integer terr)
{
return _territory.get(terr).getRandomPoint();
}
public int getProcMax(Integer terr)
{
return _territory.get(terr).getProcMax();
}
public void reload_data()
{
_territory.clear();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("SELECT loc_id, loc_x, loc_y, loc_zmin, loc_zmax, proc FROM `locations`");
final ResultSet rset = statement.executeQuery();
while (rset.next())
{
final int terr = rset.getInt("loc_id");
if (_territory.get(terr) == null)
{
final Territory t = new Territory();
_territory.put(terr, t);
}
_territory.get(terr).add(rset.getInt("loc_x"), rset.getInt("loc_y"), rset.getInt("loc_zmin"), rset.getInt("loc_zmax"), rset.getInt("proc"));
}
rset.close();
statement.close();
}
catch (Exception e1)
{
LOGGER.warning("Locations couldn't be initialized " + e1);
}
LOGGER.info("TerritoryTable: Loaded " + _territory.size() + " locations.");
}
public static TerritoryTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final TerritoryTable INSTANCE = new TerritoryTable();
}
}

View File

@@ -0,0 +1,284 @@
/*
* 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.datatables.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.l2jmobius.commons.concurrent.ThreadPool;
import org.l2jmobius.commons.database.DatabaseFactory;
import org.l2jmobius.gameserver.datatables.ItemTable;
import org.l2jmobius.gameserver.model.StoreTradeList;
import org.l2jmobius.gameserver.model.items.instance.ItemInstance;
/**
* This class manages buylists from database
* @version $Revision: 1.5.4.13 $ $Date: 2005/04/06 16:13:38 $
*/
public class TradeListTable
{
private static final Logger LOGGER = Logger.getLogger(TradeListTable.class.getName());
private int _nextListId;
private final Map<Integer, StoreTradeList> _lists = new HashMap<>();
/** Task launching the function for restore count of Item (Clan Hall) */
private class RestoreCount implements Runnable
{
private final int _timer;
public RestoreCount(int time)
{
_timer = time;
}
@Override
public void run()
{
restoreCount(_timer);
dataTimerSave(_timer);
ThreadPool.schedule(new RestoreCount(_timer), _timer * 60 * 60 * 1000);
}
}
private TradeListTable()
{
load();
}
private void load(boolean custom)
{
_lists.clear();
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement1 = con.prepareStatement("SELECT shop_id,npc_id FROM " + (custom ? "custom_merchant_shopids" : "merchant_shopids"));
final ResultSet rset1 = statement1.executeQuery();
while (rset1.next())
{
final PreparedStatement statement = con.prepareStatement("SELECT item_id, price, shop_id, order, count, time, currentCount FROM " + (custom ? "custom_merchant_buylists" : "merchant_buylists") + " WHERE shop_id=? ORDER BY order ASC");
statement.setString(1, String.valueOf(rset1.getInt("shop_id")));
final ResultSet rset = statement.executeQuery();
final StoreTradeList buylist = new StoreTradeList(rset1.getInt("shop_id"));
buylist.setNpcId(rset1.getString("npc_id"));
int itemId = 0;
int itemCount = 0;
int price = 0;
if (!buylist.isGm() && (NpcTable.getInstance().getTemplate(rset1.getInt("npc_id")) == null))
{
LOGGER.warning("TradeListTable: Merchant id " + rset1.getString("npc_id") + " with buylist " + buylist.getListId() + " does not exist.");
}
try
{
while (rset.next())
{
itemId = rset.getInt("item_id");
price = rset.getInt("price");
final int count = rset.getInt("count");
final int currentCount = rset.getInt("currentCount");
final int time = rset.getInt("time");
final ItemInstance buyItem = ItemTable.getInstance().createDummyItem(itemId);
if (buyItem == null)
{
continue;
}
itemCount++;
if (count > -1)
{
buyItem.setCountDecrease(true);
}
buyItem.setPriceToSell(price);
buyItem.setTime(time);
buyItem.setInitCount(count);
if (currentCount > -1)
{
buyItem.setCount(currentCount);
}
else
{
buyItem.setCount(count);
}
buylist.addItem(buyItem);
if (!buylist.isGm() && (buyItem.getReferencePrice() > price))
{
LOGGER.warning("TradeListTable: Reference price of item " + itemId + " in buylist " + buylist.getListId() + " higher then sell price.");
}
}
}
catch (Exception e)
{
LOGGER.warning("TradeListTable: Problem with buylist " + buylist.getListId() + ". " + e);
}
if (itemCount > 0)
{
_lists.put(buylist.getListId(), buylist);
_nextListId = Math.max(_nextListId, buylist.getListId() + 1);
}
else
{
LOGGER.warning("TradeListTable: Empty buylist " + buylist.getListId() + ".");
}
statement.close();
rset.close();
}
rset1.close();
statement1.close();
LOGGER.info("TradeListTable: Loaded " + _lists.size() + " Buylists.");
try
{
int time = 0;
long savetimer = 0;
final long currentMillis = System.currentTimeMillis();
final PreparedStatement statement2 = con.prepareStatement("SELECT DISTINCT time, savetimer FROM " + (custom ? "custom_merchant_buylists" : "merchant_buylists") + " WHERE time <> 0 ORDER BY time");
final ResultSet rset2 = statement2.executeQuery();
while (rset2.next())
{
time = rset2.getInt("time");
savetimer = rset2.getLong("savetimer");
if ((savetimer - currentMillis) > 0)
{
ThreadPool.schedule(new RestoreCount(time), savetimer - System.currentTimeMillis());
}
else
{
ThreadPool.schedule(new RestoreCount(time), 0);
}
}
rset2.close();
statement2.close();
}
catch (Exception e)
{
LOGGER.warning("TradeController: Could not restore Timer for Item count. " + e);
}
}
catch (Exception e)
{
// problem with initializing buylists, go to next one
LOGGER.warning("TradeListTable: Buylists could not be initialized. " + e);
}
}
public void load()
{
load(false); // not custom
load(true); // custom
}
public void reloadAll()
{
_lists.clear();
load();
}
public StoreTradeList getBuyList(int listId)
{
if (_lists.containsKey(listId))
{
return _lists.get(listId);
}
return null;
}
protected void restoreCount(int time)
{
for (StoreTradeList list : _lists.values())
{
list.restoreCount(time);
}
}
protected void dataTimerSave(int time)
{
final long timerSave = System.currentTimeMillis() + (time * 3600000); // 60*60*1000
try (Connection con = DatabaseFactory.getConnection())
{
final PreparedStatement statement = con.prepareStatement("UPDATE merchant_buylists SET savetimer =? WHERE time =?");
statement.setLong(1, timerSave);
statement.setInt(2, time);
statement.executeUpdate();
statement.close();
}
catch (Exception e)
{
LOGGER.warning("TradeController: Could not update Timer save in Buylist. " + e);
}
}
public void dataCountStore()
{
int listId;
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement;
for (StoreTradeList list : _lists.values())
{
if (list == null)
{
continue;
}
listId = list.getListId();
for (ItemInstance Item : list.getItems())
{
if (Item.getCount() < Item.getInitCount()) // needed?
{
statement = con.prepareStatement("UPDATE merchant_buylists SET currentCount=? WHERE item_id=? AND shop_id=?");
statement.setInt(1, Item.getCount());
statement.setInt(2, Item.getItemId());
statement.setInt(3, listId);
statement.executeUpdate();
statement.close();
}
}
}
}
catch (Exception e)
{
LOGGER.warning("TradeController: Could not store Count Item. " + e);
}
}
public static TradeListTable getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final TradeListTable INSTANCE = new TradeListTable();
}
}

View File

@@ -0,0 +1,333 @@
/*
* 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.datatables.xml;
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.Node;
import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.model.AccessLevel;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.GameServerPacket;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
/**
* Loads administrator access levels and commands.
* @author Mobius
*/
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, Integer> _adminCommandAccessRights = new HashMap<>();
private final Map<PlayerInstance, Boolean> _gmList = new ConcurrentHashMap<>();
protected AdminData()
{
load();
}
@Override
public void load()
{
_accessLevels.clear();
parseDatapackFile("config/AccessLevels.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _accessLevels.size() + " access levels.");
_adminCommandAccessRights.clear();
parseDatapackFile("config/AdminCommands.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _adminCommandAccessRights.size() + " access commands.");
}
@Override
public void parseDocument(Document doc, File f)
{
StatSet set = null;
String command = null;
int accessLevel = 0;
String name = null;
int nameColor = 0;
int titleColor = 0;
boolean isGm = false;
boolean allowPeaceAttack = false;
boolean allowFixedRes = false;
boolean allowTransaction = false;
boolean allowAltG = false;
boolean giveDamage = false;
boolean takeAggro = false;
boolean gainExp = false;
boolean useNameColor = true;
boolean useTitleColor = false;
boolean canDisableGmStatus = true;
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".equals(d.getNodeName()))
{
set = new StatSet(parseAttributes(d));
accessLevel = set.getInt("level");
name = set.getString("name");
if (accessLevel < 0)
{
LOGGER.info(getClass().getSimpleName() + ": Access level with name " + name + " is using banned access level state(below 0). Ignoring it...");
continue;
}
try
{
nameColor = Integer.decode("0x" + set.getString("nameColor"));
}
catch (NumberFormatException nfe)
{
LOGGER.warning(nfe.getMessage());
try
{
nameColor = Integer.decode("0xFFFFFF");
}
catch (NumberFormatException nfe2)
{
LOGGER.warning(nfe.getMessage());
}
}
try
{
titleColor = Integer.decode("0x" + set.getString("titleColor"));
}
catch (NumberFormatException nfe)
{
LOGGER.warning(nfe.getMessage());
try
{
titleColor = Integer.decode("0x77FFFF");
}
catch (NumberFormatException nfe2)
{
LOGGER.warning(nfe.getMessage());
}
}
isGm = set.getBoolean("isGm");
allowPeaceAttack = set.getBoolean("allowPeaceAttack");
allowFixedRes = set.getBoolean("allowFixedRes");
allowTransaction = set.getBoolean("allowTransaction");
allowAltG = set.getBoolean("allowAltg");
giveDamage = set.getBoolean("giveDamage");
takeAggro = set.getBoolean("takeAggro");
gainExp = set.getBoolean("gainExp");
useNameColor = set.getBoolean("useNameColor");
useTitleColor = set.getBoolean("useTitleColor");
canDisableGmStatus = set.getBoolean("canDisableGmStatus");
_accessLevels.put(accessLevel, new AccessLevel(accessLevel, name, nameColor, titleColor, isGm, allowPeaceAttack, allowFixedRes, allowTransaction, allowAltG, giveDamage, takeAggro, gainExp, useNameColor, useTitleColor, canDisableGmStatus));
}
else if ("admin".equals(d.getNodeName()))
{
set = new StatSet(parseAttributes(d));
command = set.getString("command");
accessLevel = set.getInt("accessLevel");
_adminCommandAccessRights.put(command, accessLevel);
}
}
}
}
}
/**
* Returns the access level by characterAccessLevel
* @param accessLevelNum as int<br>
* @return AccessLevel: AccessLevel instance by char access level
*/
public AccessLevel getAccessLevel(int accessLevelNum)
{
AccessLevel accessLevel = null;
synchronized (_accessLevels)
{
accessLevel = _accessLevels.get(accessLevelNum);
}
return accessLevel;
}
public void addBanAccessLevel(int accessLevel)
{
synchronized (_accessLevels)
{
if (accessLevel > -1)
{
return;
}
_accessLevels.put(accessLevel, new AccessLevel(accessLevel, "Banned", Integer.decode("0x000000"), Integer.decode("0x000000"), false, false, false, false, false, false, false, false, false, false, false));
}
}
public int accessRightForCommand(String command)
{
int out = -1;
if (_adminCommandAccessRights.containsKey(command))
{
out = _adminCommandAccessRights.get(command);
}
return out;
}
public boolean hasAccess(String adminCommand, AccessLevel accessLevel)
{
if (accessLevel.getLevel() <= 0)
{
return false;
}
if (!accessLevel.isGm())
{
return false;
}
String command = adminCommand;
if (adminCommand.indexOf(' ') != -1)
{
command = adminCommand.substring(0, adminCommand.indexOf(' '));
}
int acar = 0;
if (_adminCommandAccessRights.get(command) != null)
{
acar = _adminCommandAccessRights.get(command);
}
if (acar == 0)
{
LOGGER.warning("Admin Access Rights: No rights defined for admin command " + command + ".");
return false;
}
return accessLevel.getLevel() >= acar;
}
public List<PlayerInstance> getAllGms(boolean includeHidden)
{
final List<PlayerInstance> tmpGmList = new ArrayList<>();
for (Entry<PlayerInstance, Boolean> n : _gmList.entrySet())
{
if (includeHidden || !n.getValue())
{
tmpGmList.add(n.getKey());
}
}
return tmpGmList;
}
public List<String> getAllGmNames(boolean includeHidden)
{
final List<String> tmpGmList = new ArrayList<>();
for (Entry<PlayerInstance, Boolean> n : _gmList.entrySet())
{
if (!n.getValue())
{
tmpGmList.add(n.getKey().getName());
}
else if (includeHidden)
{
tmpGmList.add(n.getKey().getName() + " (invis)");
}
}
return tmpGmList;
}
public void addGm(PlayerInstance player, boolean hidden)
{
_gmList.put(player, hidden);
}
public void deleteGm(PlayerInstance player)
{
_gmList.remove(player);
}
public boolean isGmOnline(boolean includeHidden)
{
for (boolean value : _gmList.values())
{
if (includeHidden || !value)
{
return true;
}
}
return false;
}
public void sendListToPlayer(PlayerInstance player)
{
if (isGmOnline(player.isGM()))
{
player.sendPacket(new SystemMessage(SystemMessageId.GM_LIST));
for (String name : getAllGmNames(player.isGM()))
{
final SystemMessage sm = new SystemMessage(SystemMessageId.GM_S1);
sm.addString(name);
player.sendPacket(sm);
}
}
else
{
player.sendPacket(new SystemMessage(SystemMessageId.THERE_ARE_NO_GMS_CURRENTLY_VISIBLE_IN_THE_PUBLIC_LIST_AS_THEY_MAY_BE_PERFORMING_OTHER_FUNCTIONS_AT_THE_MOMENT));
}
}
public static void broadcastToGMs(GameServerPacket packet)
{
for (PlayerInstance gm : getInstance().getAllGms(true))
{
gm.sendPacket(packet);
}
}
public static void broadcastMessageToGMs(String message)
{
for (PlayerInstance gm : getInstance().getAllGms(true))
{
if (gm != null)
{
gm.sendPacket(SystemMessage.sendString(message));
}
}
}
public static AdminData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final AdminData INSTANCE = new AdminData();
}
}

View File

@@ -0,0 +1,101 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.datatables.xml;
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.ArmorSet;
import org.l2jmobius.gameserver.model.StatSet;
/**
* This class loads and stores {@link ArmorSet}s, the key being the chest item id.
*/
public class ArmorSetData implements IXmlReader
{
private static final Logger LOGGER = Logger.getLogger(ArmorSetData.class.getName());
public Map<Integer, ArmorSet> _armorSets = new HashMap<>();
private ArmorSetData()
{
load();
}
@Override
public void load()
{
parseDatapackFile("data/ArmorSets.xml");
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorSets.size() + " armor sets.");
}
@Override
public void parseDocument(Document doc, File f)
{
// StatsSet used to feed informations. Cleaned on every entry.
final StatSet set = new StatSet();
// First element is never read.
final Node n = doc.getFirstChild();
for (Node node = n.getFirstChild(); node != null; node = node.getNextSibling())
{
if (!"armorset".equalsIgnoreCase(node.getNodeName()))
{
continue;
}
// Parse and feed content.
final NamedNodeMap attrs = node.getAttributes();
for (int i = 0; i < attrs.getLength(); i++)
{
final Node attr = attrs.item(i);
set.set(attr.getNodeName(), attr.getNodeValue());
}
// Feed the map with new data.
final int chestId = set.getInt("chest");
_armorSets.put(chestId, new ArmorSet(chestId, set.getInt("legs"), set.getInt("head"), set.getInt("gloves"), set.getInt("feet"), set.getInt("skillId"), set.getInt("shield"), set.getInt("shieldSkillId"), set.getInt("enchant6Skill")));
}
}
public boolean setExists(int chestId)
{
return _armorSets.containsKey(chestId);
}
public ArmorSet getSet(int chestId)
{
return _armorSets.get(chestId);
}
public static ArmorSetData getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ArmorSetData INSTANCE = new ArmorSetData();
}
}

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