...
 
Commits (22)
......@@ -14,17 +14,43 @@ channel #l1j on Freenode IRC.
### Recent History
This repo started as a direct copy of the l1j-en project on Google Code as of
their revision 726. We pulled upstream updates from l1j-jp2 but since have
diverged significantly.
As of March 2014, everial's downstream fork was merged back into master. As of
June 2014, this and the Google Code repo are synced, with the GitHub version
typically getting fixes first.
As of 2018, the Google Code repository (and any related repos there) is to be
considered abandoned as Google has long since shut down that service. We're
also now off of GitHub and hosted here at l1j.org.
This history is being pieced together based on memories and what links can still
be verified.
Pre l1j-jp the history of l1j is somewhat difficult to verify. Several people
claim they are the original creators. I've never been able to find proof. Plus
so many years, revisions and changes have gone by I'm not sure how relevant that
even is. I've heard mention of a project called LoSe (or something like that)
that is possibly the parent of all l1j projects. Or maybe it was just one of
the first and none of its original code still exists, or ever existed within l1j.
There are also several different languages/forks of l1j. The code is so
significantly different within some of them that I'm unsure if they're all forks
or l1j is just often used as a name prefix for new/original code bases.
This code base was originally a fork of
[l1j-jp](https://code.google.com/archive/p/l1j-jp/) named
[lindc](https://code.google.com/archive/p/lindc/)
translated to english with stability and security improvements. An important goal
was to leave the code as similar/compatibile with l1j-jp as possible so we could
take advantage of their content updates. They had a larger team and progressed
further/faster on that than we did.
Around Oct, 2008 the project was renamed to l1j-en to detach it from any
particular server. Sometime in l1j-en's early life we began pulling in updates
from l1j-jp2 as well. jp2 was actually located on l1j.org at that time.
In 2014 the project slowly transitioned to git (on github), away from svn and
googlecode. Github has since removed the repo that was located there.
From 2014 to 2018 the code base has entered many periods of dormancy. Work began
in earnest again to bring up the zelgo server. Several old and new
members of the project have randomly come and gone over that time period.
In 2018 we moved to l1j.org and are working on cleaning things up, optimizing
things and sticking to the original open source principles of the project.
Code compatibility with other l1j projects is no longer considered a priority,
especially where it is/was in the way of improvements.
### Contributing
......
-- IMPORTANT: THIS UPDATE IS NOT YET FINISHED, DONT USE!
-- Queries that are pending an update should be placed here. This allows them
-- to be verified as working together. Once complete, the -pending suffix will
-- be removed.
INSERT INTO commands (name, access_level, class_name, help_text, run_on_login) VALUES('createpet', 200, 'L1CreatePet', 'Lets to create a pet with a level of your choice', 0);
-- add back the lancemaster polymorph!
......@@ -53,7 +48,7 @@ INSERT INTO `commands` (`name`,`access_level`,`class_name`,`help_text`,`run_on_l
-- Update range on fantasm and set probability value and dice to 0 since the probability is now hard-coded to 30%
-- http://lineage.power.plaync.com/wiki/%ED%8C%90%ED%83%80%EC%A6%98
UPDATE `skills` SET `ranged` = 4, `probability_value` = 0, `probability_dice` = 0 WHERE `name` = 'Phantasm';
UPDATE `skills` SET `ranged` = 4, `probability_value` = 0, `probability_dice` = 0 WHERE `name` = 'Phantasm';
-- lowered the leverage of high raccoon and raccoon so slow won't land all the time. The leverage was 3x that of a mage.
UPDATE `mobskill` SET `Leverage` = 5 WHERE `Skill_Description` LIKE '%Slow%' AND `mobname` IN('Raccoon', 'High Raccoon');
......@@ -68,7 +63,7 @@ UPDATE weapon SET trade = 1 WHERE item_id = 124;
-- aos
UPDATE skills SET probability_value = 50, probability_dice = 10 WHERE skill_id = 161;
-- shackle
UPDATE skills SET probability_dice = 20 WHERE skill_id = 167;
UPDATE skills SET probability_dice = 20 WHERE skill_id = 167;
-- remove adena drops from TOI, as discussed in gm room
delete from droplist where mobId in (select npc_templateid from spawnlist where mapid in (select mapid from mapids where locationname like "%insolence%f%")) and itemId = 40308;
......
......@@ -935,8 +935,8 @@ INSERT INTO `droplist` VALUES(90503,'Ecu Azte',40308,'Adena',350,500,1000000),
(90516,'Jad Teo',40308,'Adena',350,500,1000000),
(90517,'Jad Tow',40308,'Adena',350,500,1000000);
-- update thebes ring
-- update thebes ring
UPDATE `armor` SET `regist_sleep` = 0, `regist_stun` = 5 WHERE `item_id` = 21094;
-- update phantasm probability to match stun
UPDATE `skills` SET `probability_value` = 50 WHERE `skill_id` = 212;
\ No newline at end of file
UPDATE `skills` SET `probability_value` = 50 WHERE `skill_id` = 212;
-- IMPORTANT: THIS UPDATE IS NOT YET FINISHED, DONT USE!
-- Queries that are pending an update should be placed here. This allows them
-- to be verified as working together. Once complete, the -pending suffix will
-- be removed.
update mapids set endx = 32831, endy = 32959 where mapid = 3;
update mapids set endX = 32831, startY = 32704 where mapid = 34;
update mapids set startX = 32576, startY = 32768, endY = 32959 where mapid = 106;
......@@ -41,4 +47,32 @@ update mapids set endX = 32959, startY = 32704 where mapid = 2003;
update mapids set endY = 32895 where mapid = 2004;
update mapids set startX = 32512, endX = 33023 where mapid = 1001;
update mapids set startX = 32448 where mapid = 4;
INSERT INTO skills VALUES('14012', 'Jeb Requie (Right) Sandstorm', '0', '0', '5', '0', '0', '0', '0', '0', 'none', '3', '120', '5', '3', '0', '0', '0', '64', '0', '15', '17', '0', '0', '', '19', '7293', '0', '0', '0', '0', NULL, '1'),
('14013', 'Jeb Requie (Right) - Pole Mine', '0', '0', '5', '0', '0', '0', '0', '0', 'attack', '3', '90', '5', '3', '0', '0', '0', '64', '0', '15', '17', '0', '0', '', '30', '10', '0', '0', '0', '0', NULL, '1');
INSERT INTO skills VALUES ('15005', 'Jeb Requie (Right) - Poison Spray', '0', '0', '5', '0', '0', '0', '0', '0', 'none', '3', '200', '5', '3', '0', '0', '4', '64', '0', '6', '6', '0', '0', '', '11', '7272', '0', '0', '0', '0', NULL, '1');
INSERT INTO skills VALUES ('15000', 'Jeb Requie (Left) - Posion Spray', '0', '0', '5', '0', '0', '0', '0', '0', 'none', '3', '200', '5', '3', '0', '0', '4', '64', '0', '6', '6', '0', '0', '', '11', '7272', '0', '0', '0', '0', NULL, '1');
-- Fix names of various teleport scrolls.
update etcitem set name = 'Teleport - Giran Dungeon 2F' where item_id = 40827;
update etcitem set name = 'Teleport - Giran Dungeon 3F' where item_id = 40828;
update etcitem set name = 'Teleport - Giran Dungeon 4F' where item_id = 40829;
update etcitem set name = 'Teleport - Kent Dungeon 1F' where item_id = 42006;
update etcitem set name = 'Teleport - Kent Dungeon 2F' where item_id = 42008;
update etcitem set name = 'Teleport - Kent Dungeon 3F' where item_id = 42009;
update etcitem set name = 'Teleport - Kent Dungeon 4F' where item_id = 42010;
update etcitem set name = 'Teleport - Oum Dungeon' where item_id = 42048;
-- Fix zombie group aggro.
update npc set agrofamily = 0 where npcid in (45065, 45105, 45757);
-- Remove Unknown Spear from Mandra (Oren weapon shop).
delete from shop where npc_id = 70061 and item_id = 87;
-- Enable .burf command for monitors.
update commands set access_level = 100 where name = 'burf';
-- Remove SKT map from Berry (Werldern pot shop).
delete from shop where npc_id = 70045 and item_id = 40380;
-- Remove SKT map from Bius (Oren pot shop).
delete from shop where npc_id = 70063 and item_id = 40380;
......@@ -17,10 +17,6 @@
* http://www.gnu.org/copyleft/gpl.html
*/
/*
* (Tricid) This is the first class in the pipeline that handles new connections. Right now I'm only
* using it to send the first packet that the client expects.
*/
package l1j.server.server.network;
import java.io.IOException;
......@@ -32,12 +28,17 @@ import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import l1j.server.Config;
import l1j.server.server.datatables.IpTable;
import l1j.server.server.encryptions.ClientIdExistsException;
import l1j.server.server.encryptions.L1JEncryption;
/*
* (Tricid) This is the first class in the pipeline that handles new connections. Right now I'm only
* using it to send the first packet that the client expects.
*/
public class ChannelInit extends ChannelInitializer<Channel> {
private static Logger _log = LoggerFactory.getLogger(ChannelInit.class);
......@@ -46,34 +47,57 @@ public class ChannelInit extends ChannelInitializer<Channel> {
(byte) 0x41, (byte) 0x5A, (byte) 0x9B, (byte) 0x01, (byte) 0xB6, (byte) 0x81, (byte) 0x01, (byte) 0x09,
(byte) 0xBD, (byte) 0xCC, (byte) 0xC0 };
@Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
// its likely already closed at this point
// but lets make sure
try {
ctx.close();
} catch (Exception e) {
}
// I think this will silence those annoying exceptions on disconnects
if (cause instanceof java.io.IOException) {
} else {
_log.error("", cause);
}
}
@Override
protected void initChannel(Channel channel) throws Exception {
String ip = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress().toString();
//check if banned, close and return if yes
// check if banned, close and return if yes
if (IpTable.getInstance().isBannedIp(ip)) {
channel.close();
return;
}
//check if connections from this IP exceed limits
// check if connections from this IP exceed limits
int ipcount = Collections.frequency(NetworkServer.getInstance().getIps(), ip);
if (ipcount >= Config.CONNECTIONS_PER_IP) {
channel.close();
return;
} else {
NetworkServer.getInstance().getIps().add(ip);
}
// prepare first packet buffer
final ByteBuf first = channel.alloc().buffer(FIRST_PACKET.length + 7);
// Prepare a new Client, replaces ClientThread
Client client = null;
try {
client = new Client(channel);
} catch (IOException e) {
// TODO Auto-generated catch block
_log.error("",e);
_log.error("", e);
}
// collections for connections in case we need to find it later
NetworkServer.getInstance().getClients().put(channel.id(), client);
long seed = 0x7C98BDFA; // 3.0 English Packet Seed
......@@ -92,9 +116,9 @@ public class ChannelInit extends ChannelInitializer<Channel> {
try {
client.set_clkey(L1JEncryption.initKeys(channel.id(), seed));
} catch (ClientIdExistsException e) {
_log.error("",e);
_log.error("", e);
}
channel.pipeline().addLast(new PacketDecoder(),new PacketDecrypter());
channel.pipeline().addLast(new PacketDecoder(), new PacketDecrypter());
}
......
......@@ -105,9 +105,11 @@ public class Client implements Runnable, PacketOutput {
// TODO Auto-generated method stub
quitGame(_activeChar, client.getLastActiveCharName());
synchronized (_activeChar) {
_activeChar.logout();
setActiveChar(null);
if (_activeChar != null) {
synchronized (_activeChar) {
_activeChar.logout();
setActiveChar(null);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
......
......@@ -17,11 +17,6 @@
* http://www.gnu.org/copyleft/gpl.html
*/
/*
* (Tricid) New network server using netty. Rather than needing one thread per client we will now use a proper
* nio library and a producer/consumer model. On start up this results in a few more threads but in exchange requires no
* thread per client. So in theory 1 player of 1000 players the thread count stays the same and performance is better for it.
*/
package l1j.server.server.network;
import java.util.ArrayList;
......@@ -44,12 +39,18 @@ import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import l1j.server.Config;
/*
* (Tricid) New network server using netty. Rather than needing one thread per client we will now use a proper
* nio library and a producer/consumer model. On start up this results in a few more threads but in exchange requires no
* thread per client. So in theory 1 player or 1000 players the thread count stays the same and performance is better for it.
*/
public class NetworkServer implements Runnable {
Logger _log = LoggerFactory.getLogger(NetworkServer.class);
private static final NetworkServer instance = new NetworkServer();
private ArrayBlockingQueue<Client> clientQueue;
private ArrayList<String> ips = new ArrayList<String>();
private NetworkServer() {
}
......@@ -77,9 +78,8 @@ public class NetworkServer implements Runnable {
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// Takes input data and once a complete packet is formed
// passes it along
// takes complete packets from above and decrypts them
// Only add the pipeline that sends the first packet
// It'll add the other pipelines when it's ready
ch.pipeline().addLast(new ChannelInit());
}
......
......@@ -17,6 +17,8 @@
* http://www.gnu.org/copyleft/gpl.html
*/
package l1j.server.server.network;
/*
* (Tricid) These are the packet consumers for the new network model. They try to pull from a queue that contains
* clients (not packets), blocking on the queue when it's empty. Once they get a client it runs a client method that
......@@ -27,11 +29,8 @@
* I might tweak how that works.
*
* Additionally, if I ever get motivated in the future to implement my speed hack fix I've done on other servers, this is
* the model it relies on.
* the model it relies on. It'll make sense when/if I implement it for l1j
*/
package l1j.server.server.network;
public class PacketConsumer implements Runnable {
String name;
......
......@@ -17,11 +17,6 @@
* http://www.gnu.org/copyleft/gpl.html
*/
/*
* (Tricid) This is the second class in the pipeline that handles packets (after ChannelInit).
* All this is doing is continually reading bytes until a proper packet is formed. Afterwards, it passes it
* to the next in the pipeline (the decrypter).
*/
package l1j.server.server.network;
import java.util.List;
......@@ -33,16 +28,15 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/*
* (Tricid) This is the second class in the pipeline that handles packets (after ChannelInit).
* All this is doing is continually reading bytes until a proper packet is formed. Afterwards, it passes it
* to the next in the pipeline (the decrypter).
*/
public class PacketDecoder extends ByteToMessageDecoder {
Logger _log = LoggerFactory.getLogger(PacketDecoder.class);
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
// TODO Auto-generated method stub
}
@Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
// its likely already closed at this point
......@@ -71,16 +65,11 @@ public class PacketDecoder extends ByteToMessageDecoder {
_log.error("", e);
}
}
NetworkServer.getInstance().getIps().remove(client.getIp());
NetworkServer.getInstance().getClients().remove(ctx.channel().id());
}
@Override
public void channelActive(final ChannelHandlerContext ctx) {
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
int dataLength = 0;
if (in.readableBytes() >= 2) {
......
......@@ -17,10 +17,6 @@
* http://www.gnu.org/copyleft/gpl.html
*/
/*
* (Tricid) The last step in the pipeline. This class should receive only complete packets/frames to decrypt then pass along
* to the queue to be processed.
*/
package l1j.server.server.network;
import org.slf4j.Logger;
......@@ -31,6 +27,10 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import l1j.server.server.encryptions.L1JEncryption;
import l1j.server.server.encryptions.NoEncryptionKeysSelectedException;
/*
* (Tricid) The last step in the pipeline. This class should receive only complete packets/frames to decrypt then pass along
* to the queue to be processed.
*/
public class PacketDecrypter extends ChannelInboundHandlerAdapter {
private static Logger _log = LoggerFactory.getLogger(PacketDecrypter.class);
......@@ -49,7 +49,24 @@ public class PacketDecrypter extends ChannelInboundHandlerAdapter {
NetworkServer.getInstance().getClientQueue().offer(client);
} catch (NoEncryptionKeysSelectedException e) {
// TODO Auto-generated catch block
_log.error("",e);
_log.error("", e);
}
}
@Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
// its likely already closed at this point
// but lets make sure
try {
ctx.close();
} catch (Exception e) {
}
// I think this will silence those annoying exceptions on disconnects
if (cause instanceof java.io.IOException) {
} else {
_log.error("", cause);
}
}
......
......@@ -205,35 +205,35 @@ import l1j.server.server.serverpackets.S_SystemMessage;
// Referenced classes of package l1j.server.server:
// Opcodes, LoginController, Client, Logins
public class PacketHandler {
private static Logger _log =LoggerFactory .getLogger(PacketHandler.class.getName());
private static Logger _log = LoggerFactory.getLogger(PacketHandler.class.getName());
private final Client _client;
public PacketHandler(Client client) {
_client = client;
}
public void handlePacket(byte abyte0[], L1PcInstance object)
throws Exception {
public void handlePacket(byte abyte0[], L1PcInstance object) throws Exception {
int i = abyte0[0] & 0xff;
if (Config.LOGGING_INCOMING_PACKETS) {
_log.info("Packet sent from client: " + i);
if (_client.getActiveChar() != null) {
if (_client.getActiveChar().isGm()) {
_client.getActiveChar().sendPackets(
new S_SystemMessage("Sent from client: " + i));
_client.getActiveChar().sendPackets(new S_SystemMessage("Sent from client: " + i));
}
}
}
_client.setLastClientPacket(i);
// String packet = IntArrayUtil.toCsv(ByteArrayUtil.convertToInt(abyte0));
if(object != null && object.isLoggingPackets()) {
//LogPacketsTable.storeLogPacket(object.getId(), object.getName(), object.getTempCharGfx(), i, packet, "report", System.currentTimeMillis());
if (object != null && object.isLoggingPackets()) {
// LogPacketsTable.storeLogPacket(object.getId(), object.getName(),
// object.getTempCharGfx(), i, packet, "report", System.currentTimeMillis());
} else {
//_client.addToClientPacketLog(i, packet); // only add to the historical log if we aren't logging to the database
// _client.addToClientPacketLog(i, packet); // only add to the historical log if
// we aren't logging to the database
}
switch (i) {
case C_OPCODE_EXCLUDE:
new C_Exclude(abyte0, _client);
......