· 4 years ago · May 19, 2021, 11:20 PM
1diff --git a/aCis_datapack/sql/character_offline_trade.sql b/aCis_datapack/sql/character_offline_trade.sql
2new file mode 100644
3index 0000000..a69f895
4--- /dev/null
5+++ b/aCis_datapack/sql/character_offline_trade.sql
6@@ -0,0 +1,7 @@
7+CREATE TABLE IF NOT EXISTS `character_offline_trade` (
8+ `charId` int(10) NOT NULL,
9+ `time` bigint(13) NOT NULL DEFAULT 0,
10+ `type` tinyint(4) NOT NULL DEFAULT 0,
11+ `title` varchar(50) NULL DEFAULT NULL,
12+ PRIMARY KEY (`charId`)
13+);
14\ No newline at end of file
15diff --git a/aCis_datapack/sql/character_offline_trade_items.sql b/aCis_datapack/sql/character_offline_trade_items.sql
16new file mode 100644
17index 0000000..e69de29
18--- /dev/null
19+++ b/aCis_datapack/sql/character_offline_trade_items.sql
20@@ -0,0 +1,7 @@
21+CREATE TABLE IF NOT EXISTS `character_offline_trade_items` (
22+ `charId` int(10) NOT NULL,
23+ `item` int(10) NOT NULL DEFAULT 0,
24+ `count` bigint(20) NOT NULL DEFAULT 0,
25+ `price` bigint(20) NOT NULL DEFAULT 0,
26+ `enchant` bigint(20) NULL DEFAULT NULL
27+);
28diff --git a/aCis_gameserver/config/offline.properties b/aCis_gameserver/config/offline.properties
29new file mode 100644
30index 0000000..0fe0a89
31--- /dev/null
32+++ b/aCis_gameserver/config/offline.properties
33@@ -0,0 +1,38 @@
34+# =================================================================
35+# Offline
36+# =================================================================
37+
38+# Enable or disable offline trade feature.
39+# Enable -> true, Disable -> false
40+OfflineTradeEnable = True
41+
42+# Enable or disable offline craft feature.
43+# Enable -> true, Disable -> false
44+OfflineCraftEnable = True
45+
46+# If set to True, players will be allowed to be offline only in peace zones.
47+# Default: False
48+OfflineInPeaceZone = True
49+
50+# If set to True, players offline will be invulnerable.
51+# Default: False
52+OfflineNoDamage = False
53+
54+# Restore offline traders/crafters after restart/shutdown.
55+# Default: False
56+OfflineRestore = True
57+
58+# Require OfflineRestore = true
59+# If a player is offline for more than the set value, won't be restored on server start.
60+# 0 = Offline will be always restored
61+OfflineMaxDays = 30
62+
63+# Disconnect offline player after he sold/bought all items in his list.
64+OfflineDisconnect = True
65+
66+# Offline Effect - Choose one of the following effects:
67+# none, stun, sleep, muted, root -> (none = No effect)
68+OfflineEffect = root
69+
70+# Offline Name Color (set 0 to Disable)
71+OfflineNameColor = 808080
72diff --git a/aCis_gameserver/java/net/sf/l2j/Config.java b/aCis_gameserver/java/net/sf/l2j/Config.java
73index dcbc2f0..10dab9c 100644
74--- a/aCis_gameserver/java/net/sf/l2j/Config.java
75+++ b/aCis_gameserver/java/net/sf/l2j/Config.java
76@@ -35,6 +35,7 @@
77 public static final String PLAYERS_FILE = "./config/players.properties";
78 public static final String SERVER_FILE = "./config/server.properties";
79 public static final String SIEGE_FILE = "./config/siege.properties";
80+ public static final String OFFLINE_FILE = "./config/offline.properties";
81
82 // --------------------------------------------------
83 // Clans settings
84@@ -450,6 +451,20 @@
85 public static int CH_MAX_ATTACKERS_NUMBER;
86
87 // --------------------------------------------------
88+ // Offline
89+ // --------------------------------------------------
90+
91+ public static boolean OFFLINE_TRADE_ENABLE;
92+ public static boolean OFFLINE_CRAFT_ENABLE;
93+ public static boolean OFFLINE_IN_PEACE_ZONE;
94+ public static boolean OFFLINE_NO_DAMAGE;
95+ public static boolean OFFLINE_DISCONNECT;
96+ public static boolean OFFLINE_RESTORE;
97+ public static int OFFLINE_MAX_DAYS;
98+ public static int OFFLINE_NAME_COLOR;
99+ public static String OFFLINE_EFFECT;
100+
101+ // --------------------------------------------------
102 // Server
103 // --------------------------------------------------
104
105@@ -1097,6 +1112,25 @@
106 }
107
108 /**
109+ * Loads offline settings.
110+ */
111+ private static final void loadOffline()
112+ {
113+ final ExProperties offline = initProperties(Config.OFFLINE_FILE);
114+
115+ OFFLINE_TRADE_ENABLE = offline.getProperty("OfflineTradeEnable", false);
116+ OFFLINE_CRAFT_ENABLE = offline.getProperty("OfflineCraftEnable", false);
117+ OFFLINE_IN_PEACE_ZONE = offline.getProperty("OfflineInPeaceZone", false);
118+ OFFLINE_NO_DAMAGE = offline.getProperty("OfflineNoDamage", false);
119+ OFFLINE_EFFECT = offline.getProperty("OfflineEffect", "none");
120+ OFFLINE_RESTORE = offline.getProperty("OfflineRestore", false);
121+ OFFLINE_MAX_DAYS = offline.getProperty("OfflineMaxDays", 10);
122+ OFFLINE_DISCONNECT = offline.getProperty("OfflineDisconnect", true);
123+ OFFLINE_NAME_COLOR = Integer.decode("0x" + offline.getProperty("OfflineNameColor", 808080));
124+
125+ }
126+
127+ /**
128 * Loads gameserver settings.<br>
129 * IP addresses, database, rates, feature enabled/disabled, misc.
130 */
131@@ -1293,6 +1327,9 @@
132 // siege settings
133 loadSieges();
134
135+ // offline settings
136+ loadOffline();
137+
138 // server settings
139 loadServer();
140 }
141diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java b/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
142index 052fd0b..c19e082 100644
143--- a/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
144+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
145@@ -45,6 +45,7 @@
146 import net.sf.l2j.gameserver.data.sql.AutoSpawnTable;
147 import net.sf.l2j.gameserver.data.sql.BookmarkTable;
148 import net.sf.l2j.gameserver.data.sql.ClanTable;
149+import net.sf.l2j.gameserver.data.sql.OfflineTable;
150 import net.sf.l2j.gameserver.data.sql.PlayerInfoTable;
151 import net.sf.l2j.gameserver.data.sql.ServerMemoTable;
152 import net.sf.l2j.gameserver.data.sql.SpawnTable;
153@@ -259,6 +260,10 @@
154 DerbyTrackManager.getInstance();
155 LotteryManager.getInstance();
156
157+ StringUtil.printSection("Offline");
158+ if (Config.OFFLINE_RESTORE && (Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE))
159+ OfflineTable.getInstance().restore();
160+
161 if (Config.ALLOW_WEDDING)
162 CoupleManager.getInstance();
163
164diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/LoginServerThread.java b/aCis_gameserver/java/net/sf/l2j/gameserver/LoginServerThread.java
165index 141b2c7..8f96418 100644
166--- a/aCis_gameserver/java/net/sf/l2j/gameserver/LoginServerThread.java
167+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/LoginServerThread.java
168@@ -295,6 +295,10 @@
169 public void addClient(String account, GameClient client)
170 {
171 final GameClient existingClient = _clients.putIfAbsent(account, client);
172+
173+ if (client.isDetached())
174+ return;
175+
176 if (existingClient == null)
177 {
178 try
179diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/Shutdown.java b/aCis_gameserver/java/net/sf/l2j/gameserver/Shutdown.java
180index 275ae88..11ba89c 100644
181--- a/aCis_gameserver/java/net/sf/l2j/gameserver/Shutdown.java
182+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/Shutdown.java
183@@ -18,6 +18,7 @@
184 import net.sf.l2j.gameserver.data.manager.RaidBossManager;
185 import net.sf.l2j.gameserver.data.manager.SevenSignsManager;
186 import net.sf.l2j.gameserver.data.manager.ZoneManager;
187+import net.sf.l2j.gameserver.data.sql.OfflineTable;
188 import net.sf.l2j.gameserver.model.World;
189 import net.sf.l2j.gameserver.model.actor.Player;
190 import net.sf.l2j.gameserver.model.olympiad.Olympiad;
191@@ -78,6 +79,10 @@
192 {
193 StringUtil.printSection("Under " + MODE_TEXT[_shutdownMode] + " process");
194
195+ // store offline
196+ if (Config.OFFLINE_RESTORE && (Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE))
197+ OfflineTable.getInstance().store();
198+
199 // disconnect players
200 try
201 {
202diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/data/sql/OfflineTable.java b/aCis_gameserver/java/net/sf/l2j/gameserver/data/sql/OfflineTable.java
203new file mode 100644
204index 0000000..41ec827
205--- /dev/null
206+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/data/sql/OfflineTable.java
207@@ -0,0 +1,301 @@
208+package net.sf.l2j.gameserver.data.sql;
209+
210+import java.sql.Connection;
211+import java.sql.PreparedStatement;
212+import java.sql.ResultSet;
213+import java.sql.Statement;
214+import java.util.Arrays;
215+import java.util.Calendar;
216+import java.util.List;
217+
218+import net.sf.l2j.commons.logging.CLogger;
219+import net.sf.l2j.commons.pool.ConnectionPool;
220+
221+import net.sf.l2j.Config;
222+import net.sf.l2j.gameserver.enums.ZoneId;
223+import net.sf.l2j.gameserver.enums.actors.OperateType;
224+import net.sf.l2j.gameserver.enums.skills.AbnormalEffect;
225+import net.sf.l2j.gameserver.model.World;
226+import net.sf.l2j.gameserver.model.actor.Player;
227+import net.sf.l2j.gameserver.model.craft.ManufactureItem;
228+import net.sf.l2j.gameserver.model.trade.TradeItem;
229+import net.sf.l2j.gameserver.network.GameClient;
230+
231+public class OfflineTable
232+{
233+ private static final CLogger LOGGER = new CLogger(OfflineTable.class.getName());
234+
235+ private static final String SAVE_OFFLINE_STATUS = "INSERT INTO character_offline_trade (`charId`,`time`,`type`,`title`) VALUES (?,?,?,?)";
236+ private static final String SAVE_ITEMS = "INSERT INTO character_offline_trade_items (`charId`,`item`,`count`,`price`,`enchant`) VALUES (?,?,?,?,?)";
237+ private static final String CLEAR_OFFLINE_TABLE = "DELETE FROM character_offline_trade";
238+ private static final String CLEAR_OFFLINE_TABLE_ITEMS = "DELETE FROM character_offline_trade_items";
239+ private static final String LOAD_OFFLINE_STATUS = "SELECT * FROM character_offline_trade";
240+ private static final String LOAD_OFFLINE_ITEMS = "SELECT * FROM character_offline_trade_items WHERE charId = ?";
241+
242+ private static final String EFFECT = Config.OFFLINE_EFFECT;
243+ private static final int NAME_COLOR = Config.OFFLINE_NAME_COLOR;
244+
245+ private static final List<String> ALLOWED_EFFECTS = Arrays.asList("sleep", "muted", "root", "stun");
246+
247+ public void store()
248+ {
249+ if (!Config.OFFLINE_RESTORE || (!Config.OFFLINE_TRADE_ENABLE && !Config.OFFLINE_CRAFT_ENABLE))
250+ return;
251+
252+ try (Connection con = ConnectionPool.getConnection();
253+ PreparedStatement offline = con.prepareStatement(SAVE_OFFLINE_STATUS);
254+ PreparedStatement item = con.prepareStatement(SAVE_ITEMS))
255+ {
256+ try (Statement stm = con.createStatement())
257+ {
258+ stm.execute(CLEAR_OFFLINE_TABLE);
259+ stm.execute(CLEAR_OFFLINE_TABLE_ITEMS);
260+ }
261+ for (Player player : World.getInstance().getPlayers())
262+ {
263+ try
264+ {
265+ if (player.getOperateType() != OperateType.NONE && (player.getClient() == null || player.getClient().isDetached()))
266+ {
267+ offline.setInt(1, player.getObjectId());
268+ offline.setLong(2, player.getOfflineStartTime());
269+ offline.setInt(3, player.getOperateType().getId());
270+ switch (player.getOperateType())
271+ {
272+ case BUY:
273+ if (!Config.OFFLINE_TRADE_ENABLE)
274+ continue;
275+
276+ offline.setString(4, player.getBuyList().getTitle());
277+ for (TradeItem i : player.getBuyList())
278+ {
279+ item.setInt(1, player.getObjectId());
280+ item.setInt(2, i.getItem().getItemId());
281+ item.setLong(3, i.getCount());
282+ item.setLong(4, i.getPrice());
283+ item.setLong(5, i.getEnchant());
284+ item.addBatch();
285+ }
286+ break;
287+ case SELL:
288+ case PACKAGE_SELL:
289+ if (!Config.OFFLINE_TRADE_ENABLE)
290+ continue;
291+
292+ offline.setString(4, player.getSellList().getTitle());
293+ player.getSellList().updateItems();
294+ for (TradeItem i : player.getSellList())
295+ {
296+ item.setInt(1, player.getObjectId());
297+ item.setInt(2, i.getObjectId());
298+ item.setLong(3, i.getCount());
299+ item.setLong(4, i.getPrice());
300+ item.setLong(5, i.getEnchant());
301+ item.addBatch();
302+ }
303+ break;
304+ case MANUFACTURE:
305+ if (!Config.OFFLINE_CRAFT_ENABLE)
306+ continue;
307+
308+ offline.setString(4, player.getManufactureList().getStoreName());
309+ for (final ManufactureItem i : player.getManufactureList())
310+ {
311+ item.setInt(1, player.getObjectId());
312+ item.setInt(2, i.getId());
313+ item.setLong(3, 0L);
314+ item.setLong(4, i.getValue());
315+ item.setLong(5, 0L);
316+ item.addBatch();
317+ }
318+ break;
319+ }
320+ item.executeBatch();
321+ offline.execute();
322+ }
323+ }
324+ catch (Exception e)
325+ {
326+ LOGGER.warn("Error while saving offline: " + player.getObjectId() + " " + e, e);
327+ }
328+ }
329+
330+ LOGGER.info("Offline stored.");
331+ }
332+ catch (Exception e)
333+ {
334+ LOGGER.warn("Error while saving offline: " + e, e);
335+ }
336+ }
337+
338+ public void restore()
339+ {
340+
341+ if (!Config.OFFLINE_RESTORE || (!Config.OFFLINE_TRADE_ENABLE && !Config.OFFLINE_CRAFT_ENABLE))
342+ return;
343+
344+ LOGGER.info("Loading offline...");
345+
346+ int count = 0;
347+
348+ try (Connection con = ConnectionPool.getConnection();
349+ Statement stm = con.createStatement();
350+ ResultSet rs = stm.executeQuery(LOAD_OFFLINE_STATUS))
351+ {
352+
353+ while (rs.next())
354+ {
355+ final long time = rs.getLong("time");
356+ if (Config.OFFLINE_MAX_DAYS > 0 && isExpired(time))
357+ continue;
358+
359+ final OperateType type = getType(rs.getInt("type"));
360+ if (type == null || type == OperateType.NONE)
361+ continue;
362+
363+ final Player player = Player.restore(rs.getInt("charId"));
364+ if (player == null)
365+ continue;
366+
367+ final GameClient client = new GameClient(null);
368+ client.spawnOffline(player);
369+ player.setOfflineStartTime(time);
370+ player.sitDown();
371+
372+ final String title = rs.getString("title");
373+
374+ try (PreparedStatement ps = con.prepareStatement(LOAD_OFFLINE_ITEMS))
375+ {
376+ ps.setInt(1, player.getObjectId());
377+ try (ResultSet item = ps.executeQuery())
378+ {
379+ switch (type)
380+ {
381+ case BUY:
382+ while (item.next())
383+ {
384+ if (player.getBuyList().addItemByItemId(item.getInt(2), item.getInt(3), item.getInt(4), item.getInt(5)) == null)
385+ throw new NullPointerException("NPE at BUY of offline" + player.getName() + "(" + player.getObjectId() + ") " + item.getInt(2) + " " + item.getInt(3) + " " + item.getInt(4));
386+ }
387+
388+ player.getBuyList().setTitle(title);
389+ break;
390+ case SELL:
391+ case PACKAGE_SELL:
392+ while (item.next())
393+ if (player.getSellList().addItem(item.getInt(2), item.getInt(3), item.getInt(4)) == null)
394+ throw new NullPointerException("NPE at SELL of offline " + player.getObjectId() + " " + item.getInt(2) + " " + item.getInt(3) + " " + item.getInt(4));
395+
396+ player.getSellList().setTitle(title);
397+ player.getSellList().setPackaged(type == OperateType.PACKAGE_SELL);
398+ break;
399+ case MANUFACTURE:
400+ while (item.next())
401+ player.getManufactureList().add(new ManufactureItem(item.getInt(2), item.getInt(4)));
402+
403+ player.getManufactureList().setStoreName(title);
404+ break;
405+ }
406+ }
407+
408+ applyEffect(player);
409+ player.setOperateType(type);
410+ player.restoreEffects();
411+ player.broadcastUserInfo();
412+ player.broadcastTitleInfo();
413+
414+ count++;
415+ }
416+ catch (Exception e)
417+ {
418+
419+ LOGGER.warn("Error loading offline {}({}).", e, player.getName(), player.getObjectId());
420+ player.logout(true);
421+ }
422+ }
423+
424+ LOGGER.info("Loaded " + count + " offline.");
425+
426+ try (Statement stm2 = con.createStatement())
427+ {
428+ stm2.execute(CLEAR_OFFLINE_TABLE);
429+ stm2.execute(CLEAR_OFFLINE_TABLE_ITEMS);
430+ }
431+ }
432+ catch (Exception e)
433+ {
434+ LOGGER.warn("Error while loading offline: ", e);
435+ }
436+ }
437+
438+ protected OperateType getType(int id)
439+ {
440+ for (final OperateType type : OperateType.values())
441+ if (type.getId() == id)
442+ return type;
443+
444+ LOGGER.warn("Wrong OperateType id '{}' not found.", id);
445+ return null;
446+ }
447+
448+ protected boolean isExpired(long time)
449+ {
450+ final Calendar cal = Calendar.getInstance();
451+ cal.setTimeInMillis(time);
452+ cal.add(Calendar.DAY_OF_YEAR, Config.OFFLINE_MAX_DAYS);
453+ return (cal.getTimeInMillis() <= System.currentTimeMillis());
454+ }
455+
456+ public boolean canBeOffline(Player player)
457+ {
458+ if (player.isInOlympiadMode() || player.isFestivalParticipant() || player.isInJail() || player.getBoat() != null)
459+ return false;
460+
461+ if (Config.OFFLINE_IN_PEACE_ZONE && !player.isInsideZone(ZoneId.PEACE))
462+ return false;
463+
464+ switch (player.getOperateType())
465+ {
466+ case SELL:
467+ case PACKAGE_SELL:
468+ case BUY:
469+ return Config.OFFLINE_TRADE_ENABLE;
470+ case MANUFACTURE:
471+ return Config.OFFLINE_CRAFT_ENABLE;
472+ }
473+
474+ return false;
475+ }
476+
477+ public void applyEffect(Player player)
478+ {
479+ if (EFFECT != null)
480+ {
481+ if (EFFECT.equalsIgnoreCase("none"))
482+ return;
483+
484+ if (!ALLOWED_EFFECTS.contains(EFFECT))
485+ return;
486+
487+ player.startAbnormalEffect(AbnormalEffect.getByName(EFFECT.toLowerCase()).getMask());
488+ }
489+
490+ if (NAME_COLOR > 0)
491+ {
492+ player.getAppearance().setNameColor(NAME_COLOR);
493+ player.broadcastUserInfo();
494+ }
495+
496+ }
497+
498+ public static OfflineTable getInstance()
499+ {
500+ return SingletonHolder._instance;
501+ }
502+
503+ private static class SingletonHolder
504+ {
505+ protected static final OfflineTable _instance = new OfflineTable();
506+ }
507+
508+}
509\ No newline at end of file
510diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/Player.java b/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/Player.java
511index 66ee319..a41d429 100644
512--- a/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/Player.java
513+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/model/actor/Player.java
514@@ -464,6 +464,8 @@
515
516 private Door _requestedGate;
517
518+ private long _offlineStartTime;
519+
520 /**
521 * Constructor of Player (use Creature constructor).
522 * <ul>
523@@ -3278,6 +3280,9 @@
524 public void setOperateType(OperateType type)
525 {
526 _operateType = type;
527+
528+ if (Config.OFFLINE_DISCONNECT && type == OperateType.NONE && (getClient() == null || getClient().isDetached()))
529+ deleteMe();
530 }
531
532 /**
533@@ -7380,4 +7385,14 @@
534
535 return gms;
536 }
537+
538+ public long getOfflineStartTime()
539+ {
540+ return _offlineStartTime;
541+ }
542+
543+ public void setOfflineStartTime(long time)
544+ {
545+ _offlineStartTime = time;
546+ }
547 }
548\ No newline at end of file
549diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/model/trade/TradeList.java b/aCis_gameserver/java/net/sf/l2j/gameserver/model/trade/TradeList.java
550index 019aa39..5542c6f 100644
551--- a/aCis_gameserver/java/net/sf/l2j/gameserver/model/trade/TradeList.java
552+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/model/trade/TradeList.java
553@@ -190,6 +190,9 @@
554 if (!item.isStackable() && count > 1)
555 return null;
556
557+ if (count == 0)
558+ return null;
559+
560 if ((Integer.MAX_VALUE / count) < price)
561 return null;
562diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/network/GameClient.java b/aCis_gameserver/java/net/sf/l2j/gameserver/network/GameClient.java
563index 72499d0..83c8f8f 100644
564--- a/aCis_gameserver/java/net/sf/l2j/gameserver/network/GameClient.java
565+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/network/GameClient.java
566@@ -20,11 +20,15 @@
567 import net.sf.l2j.Config;
568 import net.sf.l2j.gameserver.LoginServerThread;
569 import net.sf.l2j.gameserver.data.sql.ClanTable;
570+import net.sf.l2j.gameserver.data.sql.OfflineTable;
571 import net.sf.l2j.gameserver.data.sql.PlayerInfoTable;
572 import net.sf.l2j.gameserver.enums.FloodProtector;
573+import net.sf.l2j.gameserver.enums.MessageType;
574 import net.sf.l2j.gameserver.model.CharSelectSlot;
575 import net.sf.l2j.gameserver.model.World;
576 import net.sf.l2j.gameserver.model.actor.Player;
577+import net.sf.l2j.gameserver.model.actor.Summon;
578+import net.sf.l2j.gameserver.model.olympiad.OlympiadManager;
579 import net.sf.l2j.gameserver.model.pledge.Clan;
580 import net.sf.l2j.gameserver.network.serverpackets.ActionFailed;
581 import net.sf.l2j.gameserver.network.serverpackets.L2GameServerPacket;
582@@ -206,10 +210,33 @@
583 ThreadPool.execute(() ->
584 {
585 boolean fast = true;
586- if (getPlayer() != null && !isDetached())
587+ final Player player = getPlayer();
588+
589+ if (player != null && !isDetached())
590 {
591 setDetached(true);
592- fast = !getPlayer().isInCombat() && !getPlayer().isLocked();
593+ if (OfflineTable.getInstance().canBeOffline(player))
594+ {
595+ if (player.getParty() != null)
596+ player.getParty().removePartyMember(player, MessageType.EXPELLED);
597+
598+ OlympiadManager.getInstance().unRegisterNoble(player);
599+
600+ final Summon summon = player.getSummon();
601+ if (summon!= null)
602+ {
603+ summon.doRevive();
604+ summon.unSummon(player);
605+ }
606+
607+ OfflineTable.getInstance().applyEffect(player);
608+
609+ if (player.getOfflineStartTime() == 0)
610+ player.setOfflineStartTime(System.currentTimeMillis());
611+
612+ return;
613+ }
614+ fast = !player.isInCombat() && !player.isLocked();
615 }
616 cleanMe(fast);
617 });
618@@ -581,6 +608,9 @@
619
620 public void close(L2GameServerPacket gsp)
621 {
622+ if (getConnection() == null)
623+ return;
624+
625 getConnection().close(gsp);
626 }
627
628@@ -773,4 +803,24 @@
629 return true;
630 }
631 }
632+
633+ public void spawnOffline(Player player)
634+ {
635+ player.isRunning();
636+ player.sitDown();
637+ player.setOnlineStatus(true, false);
638+
639+ World.getInstance().addPlayer(player);
640+
641+ setDetached(true);
642+ player.setClient(this);
643+ setPlayer(player);
644+ setAccountName(player.getAccountName());
645+ player.setOnlineStatus(true, true);
646+ setState(GameClientState.IN_GAME);
647+ player.spawnMe();
648+
649+ LoginServerThread.getInstance().addClient(player.getAccountName(), this);
650+ }
651+
652 }