View difference between Paste ID: VwdNmCz5 and exNaBVDi
SHOW: | | - or go back to the newest paste.
1
diff --git a/java/net/sf/l2j/Config.java b/java/net/sf/l2j/Config.java
2
index b883c9f..4c6fdf4 100644
3
--- a/java/net/sf/l2j/Config.java
4
+++ b/java/net/sf/l2j/Config.java
5
@@ -41,6 +41,7 @@
6
 	public static final String SCHEMEBUFF = "./config/CustomMods/SchemeBuff.ini";
7
 	public static final String BOSSESETTIGNS = "./config/CustomMods/BossSettings.ini";
8
 	public static final String FLAGZONEMOD = "./config/CustomMods/FlagZone.ini";
9
+	public static final String OFFLINEMOD = "./config/CustomMods/OfflineShop.ini";
10
 	// --------------------------------------------------
11
 	// Clans settings
12
 	// --------------------------------------------------
13
@@ -543,7 +544,16 @@
14
 	public static int PLAYER_RATE_DROP_ITEM;
15
 	public static int PLAYER_RATE_DROP_EQUIP;
16
 	public static int PLAYER_RATE_DROP_EQUIP_WEAPON;
17
-
18
+	/** Offline Shop */
19
+	public static boolean OFFLINE_TRADE_ENABLE;
20
+	public static boolean OFFLINE_CRAFT_ENABLE;
21
+	public static boolean OFFLINE_MODE_IN_PEACE_ZONE;
22
+	public static boolean OFFLINE_MODE_NO_DAMAGE;
23
+	public static boolean RESTORE_OFFLINERS;
24
+	public static int OFFLINE_MAX_DAYS;
25
+	public static boolean OFFLINE_DISCONNECT_FINISHED;
26
+	public static boolean OFFLINE_SET_SLEEP;
27
+ 	
28
 	public static int KARMA_DROP_LIMIT;
29
 	public static int KARMA_RATE_DROP;
30
 	public static int KARMA_RATE_DROP_ITEM;
31
@@ -1081,6 +1091,19 @@
32
 		RANDOM_SPAWN_TIME_ZAKEN = Boss.getProperty("ZakenRandomSpawn", 20);
33
 	}
34
 	
35
+	private static final void loadOff()
36
+	{
37
+		final ExProperties offlineshop = initProperties(Config.OFFLINEMOD);
38
+		OFFLINE_TRADE_ENABLE = offlineshop.getProperty("OfflineTradeEnable", false);
39
+		OFFLINE_CRAFT_ENABLE = offlineshop.getProperty("OfflineCraftEnable", false);
40
+		OFFLINE_MODE_IN_PEACE_ZONE = offlineshop.getProperty("OfflineModeInPeaceZone", false);
41
+		OFFLINE_MODE_NO_DAMAGE = offlineshop.getProperty("OfflineModeNoDamage", false);
42
+		OFFLINE_SET_SLEEP = offlineshop.getProperty("OfflineSetSleepEffect", false);
43
+		RESTORE_OFFLINERS = offlineshop.getProperty("RestoreOffliners", false);
44
+		OFFLINE_MAX_DAYS = offlineshop.getProperty("OfflineMaxDays", 10);
45
+		OFFLINE_DISCONNECT_FINISHED = offlineshop.getProperty("OfflineDisconnectFinished", true);
46
+	
47
+	}
48
 	
49
 	private static final void loadFlag()
50
 	{
51
@@ -1549,6 +1572,9 @@
52
 
53
 		// siege settings
54
 		loadSieges();
55
+		
56
+		// OffShop settings
57
+		loadOff();
58
 
59
 		// server settings
60
 		loadServer();
61
diff --git a/java/net/sf/l2j/gameserver/GameServer.java b/java/net/sf/l2j/gameserver/GameServer.java
62
index 1071569..a829cab 100644
63
--- a/java/net/sf/l2j/gameserver/GameServer.java
64
+++ b/java/net/sf/l2j/gameserver/GameServer.java
65
@@ -75,6 +75,7 @@
66
 import net.sf.l2j.gameserver.data.xml.TeleportData;
67
 import net.sf.l2j.gameserver.data.xml.TeleportLocationData;
68
 import net.sf.l2j.gameserver.data.xml.WalkerRouteData;
69
+import net.sf.l2j.gameserver.enums.actors.OfflineStoresData;
70
 import net.sf.l2j.gameserver.geoengine.GeoEngine;
71
 import net.sf.l2j.gameserver.handler.AdminCommandHandler;
72
 import net.sf.l2j.gameserver.handler.ChatHandler;
73
@@ -244,6 +245,10 @@
74
 		Olympiad.getInstance();
75
 		HeroManager.getInstance();
76
 		
77
+		StringUtil.printSection("OfflineShop Started");
78
+		if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS)
79
+		OfflineStoresData.getInstance().restoreOfflineTraders();
80
+		
81
 		StringUtil.printSection("Four Sepulchers");
82
 		FourSepulchersManager.getInstance();
83
 		
84
diff --git a/java/net/sf/l2j/gameserver/LoginServerThread.java b/java/net/sf/l2j/gameserver/LoginServerThread.java
85
index 5a78bf2..98a34f5 100644
86
--- a/java/net/sf/l2j/gameserver/LoginServerThread.java
87
+++ b/java/net/sf/l2j/gameserver/LoginServerThread.java
88
@@ -295,6 +295,8 @@
89
 	public void addClient(String account, GameClient client)
90
 	{
91
 		final GameClient existingClient = _clients.putIfAbsent(account, client);
92
+		if (client.isDetached())
93
+			return;
94
 		if (existingClient == null)
95
 		{
96
 			try
97
diff --git a/java/net/sf/l2j/gameserver/Shutdown.java b/java/net/sf/l2j/gameserver/Shutdown.java
98
index f0ab468..440d9aa 100644
99
--- a/java/net/sf/l2j/gameserver/Shutdown.java
100
+++ b/java/net/sf/l2j/gameserver/Shutdown.java
101
@@ -18,6 +18,7 @@
102
 import net.sf.l2j.gameserver.data.manager.RaidBossManager;
103
 import net.sf.l2j.gameserver.data.manager.SevenSignsManager;
104
 import net.sf.l2j.gameserver.data.manager.ZoneManager;
105
+import net.sf.l2j.gameserver.enums.actors.OfflineStoresData;
106
 import net.sf.l2j.gameserver.model.World;
107
 import net.sf.l2j.gameserver.model.actor.Player;
108
 import net.sf.l2j.gameserver.model.olympiad.Olympiad;
109
@@ -77,7 +78,18 @@
110
 		if (this == SingletonHolder.INSTANCE)
111
 		{
112
 			StringUtil.printSection("Under " + MODE_TEXT[_shutdownMode] + " process");
113
-			
114
+			try
115
+			{
116
+				if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS)
117
+				{
118
+					OfflineStoresData.getInstance().storeOffliners();
119
+					LOGGER.info("Offline Traders Table: Offline shops stored.");
120
+				}
121
+			}
122
+			catch (Throwable t)
123
+			{
124
+
125
+			}
126
 			// disconnect players
127
 			try
128
 			{
129
diff --git a/java/net/sf/l2j/gameserver/enums/actors/OfflineStoresData.java b/java/net/sf/l2j/gameserver/enums/actors/OfflineStoresData.java
130
new file mode 100644
131
index 0000000..6ac09f9
132
--- /dev/null
133
+++ b/java/net/sf/l2j/gameserver/enums/actors/OfflineStoresData.java
134
@@ -0,0 +1,277 @@
135
+package net.sf.l2j.gameserver.enums.actors;
136
+
137
+import java.sql.Connection;
138
+import java.sql.PreparedStatement;
139
+import java.sql.ResultSet;
140
+import java.sql.Statement;
141
+import java.util.Calendar;
142
+import java.util.logging.Level;
143
+import java.util.logging.Logger;
144
+
145
+import net.sf.l2j.commons.pool.ConnectionPool;
146
+
147
+import net.sf.l2j.Config;
148
+import net.sf.l2j.gameserver.LoginServerThread;
149
+import net.sf.l2j.gameserver.enums.ZoneId;
150
+import net.sf.l2j.gameserver.model.World;
151
+import net.sf.l2j.gameserver.model.actor.Player;
152
+import net.sf.l2j.gameserver.model.craft.ManufactureItem;
153
+import net.sf.l2j.gameserver.model.craft.ManufactureList;
154
+import net.sf.l2j.gameserver.model.trade.TradeItem;
155
+import net.sf.l2j.gameserver.network.GameClient;
156
+import net.sf.l2j.gameserver.network.GameClient.GameClientState;
157
+
158
+public class OfflineStoresData
159
+{
160
+	private static final Logger LOGGER = Logger.getLogger(OfflineStoresData.class.getName());
161
+
162
+	// SQL DEFINITIONS
163
+	private static final String SAVE_OFFLINE_STATUS = "INSERT INTO character_offline_trade (`charId`,`time`,`type`,`title`) VALUES (?,?,?,?)";
164
+	private static final String SAVE_ITEMS = "INSERT INTO character_offline_trade_items (`charId`,`item`,`count`,`price`,`enchant`) VALUES (?,?,?,?,?)";
165
+	private static final String CLEAR_OFFLINE_TABLE = "DELETE FROM character_offline_trade";
166
+	private static final String CLEAR_OFFLINE_TABLE_ITEMS = "DELETE FROM character_offline_trade_items";
167
+	private static final String LOAD_OFFLINE_STATUS = "SELECT * FROM character_offline_trade";
168
+	private static final String LOAD_OFFLINE_ITEMS = "SELECT * FROM character_offline_trade_items WHERE charId = ?";
169
+
170
+	public void storeOffliners()
171
+	{
172
+		try (Connection con = ConnectionPool.getConnection(); PreparedStatement save_offline_status = con.prepareStatement(SAVE_OFFLINE_STATUS); PreparedStatement save_items = con.prepareStatement(SAVE_ITEMS))
173
+		{
174
+			try (Statement stm = con.createStatement())
175
+			{
176
+				stm.execute(CLEAR_OFFLINE_TABLE);
177
+				stm.execute(CLEAR_OFFLINE_TABLE_ITEMS);
178
+			}
179
+			for (Player pc : World.getInstance().getPlayers())
180
+			{
181
+				try
182
+				{
183
+					if (pc.getOperateType() != OperateType.NONE && (pc.getClient() == null || pc.getClient().isDetached()))
184
+					{
185
+						save_offline_status.setInt(1, pc.getObjectId());
186
+						save_offline_status.setLong(2, pc.getOfflineStartTime());
187
+						save_offline_status.setInt(3, pc.getOperateType().getId());
188
+						switch (pc.getOperateType())
189
+						{
190
+							case BUY:
191
+								if (!Config.OFFLINE_TRADE_ENABLE)
192
+									continue;
193
+
194
+								save_offline_status.setString(4, pc.getBuyList().getTitle());
195
+								for (TradeItem i : pc.getBuyList())
196
+								{
197
+									save_items.setInt(1, pc.getObjectId());
198
+									save_items.setInt(2, i.getItem().getItemId());
199
+									save_items.setLong(3, i.getCount());
200
+									save_items.setLong(4, i.getPrice());
201
+									save_items.setLong(5, i.getEnchant());
202
+									save_items.addBatch();
203
+								}
204
+								break;
205
+							case SELL:
206
+							case PACKAGE_SELL:
207
+								if (!Config.OFFLINE_TRADE_ENABLE)
208
+									continue;
209
+
210
+								save_offline_status.setString(4, pc.getSellList().getTitle());
211
+								pc.getSellList().updateItems();
212
+								for (TradeItem i : pc.getSellList())
213
+								{
214
+									save_items.setInt(1, pc.getObjectId());
215
+									save_items.setInt(2, i.getObjectId());
216
+									save_items.setLong(3, i.getCount());
217
+									save_items.setLong(4, i.getPrice());
218
+									save_items.setLong(5, i.getEnchant());
219
+									save_items.addBatch();
220
+								}
221
+								break;
222
+							case MANUFACTURE:
223
+								if (!Config.OFFLINE_CRAFT_ENABLE)
224
+									continue;
225
+
226
+								save_offline_status.setString(4, pc.getManufactureList().getStoreName());
227
+								for (final ManufactureItem i : pc.getManufactureList())
228
+								{
229
+									save_items.setInt(1, pc.getObjectId());
230
+			                        save_items.setInt(2, i.getId());
231
+			                        save_items.setLong(3, 0L);
232
+			                        save_items.setLong(4, i.getValue());
233
+			                        save_items.setLong(5, 0L);
234
+									save_items.addBatch();
235
+								}
236
+								break;
237
+						}
238
+						save_items.executeBatch();
239
+						save_offline_status.execute();
240
+					}
241
+				}
242
+				catch (Exception e)
243
+				{
244
+					LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline trader: " + pc.getObjectId() + " " + e, e);
245
+				}
246
+			}
247
+
248
+			LOGGER.info(getClass().getSimpleName() + ": Offline traders stored.");
249
+		}
250
+		catch (Exception e)
251
+		{
252
+			LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline traders: " + e, e);
253
+		}
254
+	}
255
+
256
+	public void restoreOfflineTraders()
257
+	{
258
+		LOGGER.info(getClass().getSimpleName() + ": Loading offline traders...");
259
+		try (Connection con = ConnectionPool.getConnection(); Statement stm = con.createStatement(); ResultSet rs = stm.executeQuery(LOAD_OFFLINE_STATUS))
260
+		{
261
+			int nTraders = 0;
262
+			while (rs.next())
263
+			{
264
+				final long time = rs.getLong("time");
265
+				if (Config.OFFLINE_MAX_DAYS > 0)
266
+				{
267
+					final Calendar cal = Calendar.getInstance();
268
+					cal.setTimeInMillis(time);
269
+					cal.add(Calendar.DAY_OF_YEAR, Config.OFFLINE_MAX_DAYS);
270
+					if (cal.getTimeInMillis() <= System.currentTimeMillis())
271
+						continue;
272
+				}
273
+
274
+				OperateType type = null;
275
+				for (OperateType t : OperateType.values())
276
+				{
277
+					if (t.getId() == rs.getInt("type"))
278
+					{
279
+						type = t;
280
+						break;
281
+					}
282
+				}
283
+				if (type == null)
284
+				{
285
+					LOGGER.warning(getClass().getSimpleName() + ": PrivateStoreType with id " + rs.getInt("type") + " could not be found.");
286
+					continue;
287
+				}
288
+				if (type == OperateType.NONE)
289
+					continue;
290
+
291
+				final Player player = Player.restore(rs.getInt("charId"));
292
+				if (player == null)
293
+					continue;
294
+
295
+				try (PreparedStatement stm_items = con.prepareStatement(LOAD_OFFLINE_ITEMS))
296
+				{
297
+					player.isRunning();
298
+					player.sitDown();
299
+					player.setOnlineStatus(true, false);
300
+
301
+					World.getInstance().addPlayer(player);
302
+
303
+					final GameClient client = new GameClient(null);
304
+					client.setDetached(true);
305
+					player.setClient(client);
306
+					client.setPlayer(player);
307
+					client.setAccountName(player.getAccountNamePlayer());
308
+					player.setOnlineStatus(true, true);
309
+					client.setState(GameClientState.IN_GAME);
310
+					player.setOfflineStartTime(time);
311
+					player.spawnMe();
312
+
313
+					LoginServerThread.getInstance().addClient(player.getAccountName(), client);
314
+
315
+					stm_items.setInt(1, player.getObjectId());
316
+					try (ResultSet items = stm_items.executeQuery())
317
+					{
318
+						switch (type)
319
+						{
320
+							case BUY:
321
+								while (items.next())
322
+								{
323
+								player.getBuyList().addItemByItemId(items.getInt(2), items.getInt(3), items.getInt(4), items.getInt(5));
324
+								}
325
+								
326
+								player.getBuyList().setTitle(rs.getString("title"));
327
+								break;
328
+							case SELL:
329
+							case PACKAGE_SELL:
330
+								while (items.next())
331
+									if (player.getSellList().addItem(items.getInt(2), items.getInt(3), items.getInt(4)) == null)
332
+										throw new NullPointerException("NPE at SELL of offlineShop " + player.getObjectId() + " " + items.getInt(2) + " " + items.getInt(3) + " " + items.getInt(4));
333
+								
334
+								player.getSellList().setTitle(rs.getString("title"));
335
+								player.getSellList().setPackaged(type == OperateType.PACKAGE_SELL);
336
+								break;
337
+							case MANUFACTURE:
338
+								final ManufactureList createList = new ManufactureList();
339
+								createList.setStoreName(rs.getString("title"));
340
+								while (items.next())
341
+								player.getManufactureList().add(new ManufactureItem(items.getInt(2), items.getInt(4))); 
342
+								player.getManufactureList().setStoreName(rs.getString("title"));
343
+								//player.setCreateList(createList);
344
+								break;
345
+						}
346
+					}
347
+
348
+					if (Config.OFFLINE_SET_SLEEP)
349
+						player.startAbnormalEffect(0x000080);
350
+
351
+					player.setOperateType(type);
352
+					player.restoreEffects();
353
+					player.broadcastUserInfo();
354
+
355
+					nTraders++;
356
+				}
357
+				catch (Exception e)
358
+				{
359
+					LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error loading trader: " + player, e);
360
+
361
+					player.deleteMe();
362
+				}
363
+			}
364
+
365
+			LOGGER.info(getClass().getSimpleName() + ": Loaded: " + nTraders + " offline trader(s)");
366
+
367
+			try (Statement stm1 = con.createStatement())
368
+			{
369
+				stm1.execute(CLEAR_OFFLINE_TABLE);
370
+				stm1.execute(CLEAR_OFFLINE_TABLE_ITEMS);
371
+			}
372
+		}
373
+		catch (Exception e)
374
+		{
375
+			LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while loading offline traders: ", e);
376
+		}
377
+	}
378
+
379
+	public static boolean offlineMode(Player player)
380
+	{
381
+		if (player.isInOlympiadMode() || player.isFestivalParticipant() || player.isInJail() || player.getBoat() != null)
382
+			return false;
383
+
384
+		boolean canSetShop = false;
385
+		switch (player.getOperateType())
386
+		{
387
+			case SELL:
388
+			case PACKAGE_SELL:
389
+			case BUY:
390
+				canSetShop = Config.OFFLINE_TRADE_ENABLE;
391
+				break;
392
+			case MANUFACTURE:
393
+				canSetShop = Config.OFFLINE_CRAFT_ENABLE;
394
+				break;
395
+		}
396
+		if (Config.OFFLINE_MODE_IN_PEACE_ZONE && !player.isInsideZone(ZoneId.PEACE))
397
+			canSetShop = false;
398
+
399
+		return canSetShop;
400
+	}
401
+
402
+	public static OfflineStoresData getInstance()
403
+	{
404
+		return SingletonHolder._instance;
405
+	}
406
+
407
+	private static class SingletonHolder
408
+	{
409
+		protected static final OfflineStoresData _instance = new OfflineStoresData();
410
+	}
411
+}
412
\ No newline at end of file
413
diff --git a/java/net/sf/l2j/gameserver/enums/actors/OperateType.java b/java/net/sf/l2j/gameserver/enums/actors/OperateType.java
414
index a5faa73..562d322 100644
415
--- a/java/net/sf/l2j/gameserver/enums/actors/OperateType.java
416
+++ b/java/net/sf/l2j/gameserver/enums/actors/OperateType.java
417
@@ -23,4 +23,13 @@
418
 	{
419
 		return _id;
420
 	}
421
+	public static OperateType findById(int id)
422
+	{
423
+		for (OperateType privateStoreType : values())
424
+		{
425
+			if (privateStoreType.getId() == id)
426
+				return privateStoreType;
427
+		}
428
+		return null;
429
+	}
430
 }
431
\ No newline at end of file
432
diff --git a/java/net/sf/l2j/gameserver/model/actor/Player.java b/java/net/sf/l2j/gameserver/model/actor/Player.java
433
index 92f7065..698ed21 100644
434
--- a/java/net/sf/l2j/gameserver/model/actor/Player.java
435
+++ b/java/net/sf/l2j/gameserver/model/actor/Player.java
436
@@ -2243,8 +2243,14 @@
437
 	
438
 	public String getAccountName()
439
 	{
440
+		if (getClient() == null)
441
+		return getAccountNamePlayer();
442
 		return _accountName;
443
 	}
444
+	public String getAccountNamePlayer()
445
+	{
446
+	return _accountName;
447
+	}
448
 	
449
 	public Map<Integer, String> getAccountChars()
450
 	{
451
@@ -3292,6 +3298,8 @@
452
 	public void setOperateType(OperateType type)
453
 	{
454
 		_operateType = type;
455
+		if (Config.OFFLINE_DISCONNECT_FINISHED && type == OperateType.NONE && (getClient() == null || getClient().isDetached()))
456
+		deleteMe();
457
 	}
458
 	
459
 	/**
460
@@ -7319,7 +7327,16 @@
461
 		final ItemInstance formal = getInventory().getItemFrom(Paperdoll.CHEST);
462
 		return formal != null && formal.getItem().getBodyPart() == Item.SLOT_ALLDRESS;
463
 	}
464
+	private long _offlineShopStart;
465
+	public long getOfflineStartTime()
466
+	{
467
+		return _offlineShopStart;
468
+	}
469
 	
470
+	public void setOfflineStartTime(long time)
471
+	{
472
+		_offlineShopStart = time;
473
+	}
474
 	public final void startFakeDeath()
475
 	{
476
 		_isFakeDeath = true;
477
diff --git a/java/net/sf/l2j/gameserver/network/GameClient.java b/java/net/sf/l2j/gameserver/network/GameClient.java
478
index c46ef20..860138d 100644
479
--- a/java/net/sf/l2j/gameserver/network/GameClient.java
480
+++ b/java/net/sf/l2j/gameserver/network/GameClient.java
481
@@ -22,9 +22,12 @@
482
 import net.sf.l2j.gameserver.data.sql.ClanTable;
483
 import net.sf.l2j.gameserver.data.sql.PlayerInfoTable;
484
 import net.sf.l2j.gameserver.enums.FloodProtector;
485
+import net.sf.l2j.gameserver.enums.MessageType;
486
+import net.sf.l2j.gameserver.enums.ZoneId;
487
 import net.sf.l2j.gameserver.model.CharSelectSlot;
488
 import net.sf.l2j.gameserver.model.World;
489
 import net.sf.l2j.gameserver.model.actor.Player;
490
+import net.sf.l2j.gameserver.model.olympiad.OlympiadManager;
491
 import net.sf.l2j.gameserver.model.pledge.Clan;
492
 import net.sf.l2j.gameserver.network.serverpackets.ActionFailed;
493
 import net.sf.l2j.gameserver.network.serverpackets.L2GameServerPacket;
494
@@ -209,6 +212,29 @@
495
 				if (getPlayer() != null && !isDetached())
496
 				{
497
 					setDetached(true);
498
+					if (offlineMode(getPlayer()))
499
+					{
500
+						if (getPlayer().getParty() != null)
501
+							getPlayer().getParty().removePartyMember(getPlayer(), MessageType.EXPELLED);
502
+						OlympiadManager.getInstance().unRegisterNoble(getPlayer());
503
+						
504
+						// If the Character has Pet, unsummon it
505
+						if (getPlayer().hasPet())
506
+						{
507
+							getPlayer().getSummon().unSummon(getPlayer());
508
+							// Dead pet wasn't unsummoned, broadcast npcinfo changes (pet will be without owner name - means owner offline)
509
+							if (getPlayer().getSummon() != null)
510
+								getPlayer().getSummon().updateAndBroadcastStatus(0);
511
+						}
512
+						
513
+						if (Config.OFFLINE_SET_SLEEP)
514
+							getPlayer().startAbnormalEffect(0x000080);
515
+						
516
+						if (getPlayer().getOfflineStartTime() == 0)
517
+							getPlayer().setOfflineStartTime(System.currentTimeMillis());
518
+						
519
+						return;
520
+					}
521
 					fast = !getPlayer().isInCombat() && !getPlayer().isLocked();
522
 				}
523
 				cleanMe(fast);
524
@@ -219,6 +245,43 @@
525
 		}
526
 	}
527
 	
528
+	/**
529
+	 * @param player the player to be check.
530
+	 * @return {@code true} if the player is allowed to remain as off-line shop.
531
+	 */
532
+	public static boolean offlineMode(Player player)
533
+	{
534
+		if (player.isInOlympiadMode() || player.isFestivalParticipant() || player.isInJail() || player.getBoat() != null)
535
+			return false;
536
+		
537
+		boolean canSetShop = false;
538
+		switch (player.getOperateType())
539
+		{
540
+			case SELL:
541
+			case PACKAGE_SELL:
542
+			case BUY:
543
+			{
544
+				canSetShop = Config.OFFLINE_TRADE_ENABLE;
545
+				break;
546
+			}
547
+			case MANUFACTURE:
548
+			{
549
+				canSetShop = Config.OFFLINE_TRADE_ENABLE;
550
+				break;
551
+			}
552
+			default:
553
+			{
554
+				canSetShop = Config.OFFLINE_CRAFT_ENABLE && player.isCrafting();
555
+				break;
556
+			}
557
+		}
558
+		
559
+		if (Config.OFFLINE_MODE_IN_PEACE_ZONE && !player.isInsideZone(ZoneId.PEACE))
560
+			canSetShop = false;
561
+		
562
+		return canSetShop;
563
+	}
564
+
565
 	@Override
566
 	protected void onForcedDisconnection()
567
 	{
568
@@ -581,6 +644,8 @@
569
 	
570
 	public void close(L2GameServerPacket gsp)
571
 	{
572
+		if (getConnection() == null)
573
+			return;
574
 		getConnection().close(gsp);
575
 	}
576
 	
577
578
Index: Datapack
579
580
+# =================================================================
581
+#                       Offline trade/craft
582
+# =================================================================
583
+
584
+# Option to enable or disable offline trade feature.
585
+# Enable -> true, Disable -> false
586
+OfflineTradeEnable = True
587
+
588
+# Option to enable or disable offline craft feature.
589
+# Enable -> true, Disable -> false
590
+OfflineCraftEnable = True
591
+
592
+# If set to True, off-line shops will be possible only in peace zones.
593
+# Default: False
594
+OfflineModeInPeaceZone = True
595
+
596
+# If set to True, players in off-line shop mode will not take any damage, thus they cannot be killed.
597
+# Default: False
598
+OfflineModeNoDamage = False
599
+
600
+# Restore offline traders/crafters after restart/shutdown.
601
+# Default: False
602
+RestoreOffliners = True
603
+
604
+# Do not restore offline characters, after OfflineMaxDays days spent from first restore.
605
+# Require server restart to disconnect expired shops.
606
+# 0 = disabled (always restore).
607
+# Default: 7
608
+OfflineMaxDays = 7
609
+
610
+# Disconnect shop after finished selling, buying.
611
+# Default: True
612
+OfflineDisconnectFinished = True
613
+
614
+#Offline Effect Sleep
615
+OfflineSetSleepEffect = True
616
617