Jump to content

Recommended Posts

Posted (edited)
package l2s.gameserver.model.entity;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import l2s.commons.util.Rnd;
import l2s.gameserver.data.xml.holder.CouponHolder;
import l2s.gameserver.database.DatabaseFactory;
import l2s.gameserver.model.Player;
import l2s.gameserver.network.l2.s2c.SocialActionPacket;
import l2s.gameserver.templates.item.data.ItemData;
import l2s.gameserver.utils.ItemFunctions;

public class CouponManager
{
	private static final Logger LOG = LoggerFactory.getLogger(CouponManager.class);
	private static final CouponManager INSTANCE = new CouponManager();
	private static final String DEFAULT_OWNER = "NO_OWNER";
	private static final Map<String, String> COUPONS = new HashMap<>();

	public static CouponManager getInstance() {
		return INSTANCE;
	}

	public void load() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
		PreparedStatement statement = con.prepareStatement("SELECT * FROM coupons")) {
			try (ResultSet rset = statement.executeQuery()) {
				while (rset.next())
					COUPONS.put(rset.getString("coupon_id").replace("-", "").toUpperCase(), rset.getString("coupon_owner"));
			}
		} catch (SQLException e) {
			LOG.error("Coupon issues: ", e);
		} finally {
			LOG.info("Coupons: loaded size: " + COUPONS.size());
		}
	}

	public synchronized final boolean tryUseCoupon(Player player, String id) {
		final String owner = COUPONS.get(id.toUpperCase());
		if (owner == null) {
			player.sendMessage("Invalid coupon code.");
			return false;
		}
		if (!owner.equals(DEFAULT_OWNER)) {
			player.sendMessage("This coupon code has expired or was already used.");
			return false;
		}
		final String acc = player.getAccountName().toLowerCase();
		final Collection<String> accounts = COUPONS.values();
		for (String account : accounts) {
			if (account.equals(acc)) {
				player.sendMessage("You may only claim 1 coupon per account.");
				return false;
			}
		}
		COUPONS.put(id, player.getAccountName());
		try (Connection con = DatabaseFactory.getInstance().getConnection();
		PreparedStatement statement = con.prepareStatement("UPDATE coupons SET coupon_owner = ? WHERE coupon_id like ?")) {
			statement.setString(1, acc);
			statement.setString(2, id);
			statement.execute();
		} catch (SQLException e) {
			LOG.warn("Coupons: update failure: " + e);
			return false;
		}
		player.sendGfxMessage("Coupon accepted! Enjoy!");
		player.broadcastPacket(new SocialActionPacket(player.getObjectId(), SocialActionPacket.BOASTING));
		for (ItemData it : CouponHolder.getInstance().getRewards()) {
			if (it.getMinEnchant() <= 0)
				ItemFunctions.addItem(player, it.getId(), it.getCount(), true, "Coupon Reward");
			else
				ItemFunctions.addItem(player, it.getId(), it.getCount(), Rnd.get(it.getMinEnchant(), it.getMaxEnchant()), true, "Coupon Reward");
		}
		return true;
	}
}

 

package l2s.gameserver.data.xml.parser;

import java.io.File;
import org.dom4j.Element;

import l2s.commons.data.xml.AbstractParser;
import l2s.gameserver.Config;
import l2s.gameserver.ConfigSelector;
import l2s.gameserver.data.xml.holder.CouponHolder;
import l2s.gameserver.model.entity.CouponManager;
import l2s.gameserver.templates.item.data.ItemData;

public final class CouponParser extends AbstractParser<CouponHolder>
{
	private static final CouponParser INSTANCE = new CouponParser();

	protected CouponParser() {
		super(CouponHolder.getInstance());
	}

	public static CouponParser getInstance() {
		return INSTANCE;
	}

	public void reload() {
		info("reload start...");
		getHolder().clear();
		load();
	}

	@Override
	protected void onParsed() {
		CouponManager.getInstance().load();
	}
	
	@Override
	public File getXMLPath() {
		return new File(Config.DATAPACK_ROOT, ConfigSelector.getDataFolder() + "/Coupons.xml");
	}

	@Override
	public String getDTDFileName() {
		return "Coupons.dtd";
	}

	@Override
	protected void readData(Element rootElement) {
		for (Element listSet : rootElement.elements("config"))
			getHolder().setLength(parseInt(listSet, "code_length"));
		for (Element listSet : rootElement.elements("rewards")) {
			for (Element paramsSet : listSet.elements("item")) {
				final String p = paramsSet.toString();
				if (p.contains("minEnchant") && !p.contains("maxEnchant"))
					getHolder().addItem(new ItemData(parseInt(paramsSet, "id"), parseInt(paramsSet, "count"), parseInt(paramsSet, "minEnchant")));
				else if (p.contains("minEnchant") && p.contains("maxEnchant"))
					getHolder().addItem(new ItemData(parseInt(paramsSet, "id"), parseInt(paramsSet, "count"), parseInt(paramsSet, "minEnchant", 0), parseInt(paramsSet, "maxEnchant")));
				else
					getHolder().addItem(new ItemData(parseInt(paramsSet, "id"), parseInt(paramsSet, "count")));
			}
		}
	}
}

 

package l2s.gameserver.data.xml.holder;

import java.util.ArrayList;
import java.util.List;
import l2s.commons.data.xml.AbstractHolder;
import l2s.gameserver.templates.item.data.ItemData;

public final class CouponHolder extends AbstractHolder
{
	private static final CouponHolder INSTANCE = new CouponHolder();
	private static int COUPON_LENGTH;
	private static final List<ItemData> REWARDS = new ArrayList<>();

	public static CouponHolder getInstance() {
		return INSTANCE;
	}

	public void addItem(ItemData item) {
		REWARDS.add(item);
	}

	public final List<ItemData> getRewards(){
		return REWARDS;
	}
	
	public void setLength(int i) {
		COUPON_LENGTH = i;
	}
	
	public final int getLength() {
		return COUPON_LENGTH;
	}

	@Override
	public int size() {
		return REWARDS.size();
	}

	@Override
	public void clear() {
		REWARDS.clear();
	}
}

 

Load on Gameserver or Parsers

CouponParser.getInstance().load();

 

Reload Parser (like with //reload)

CouponParser.getInstance().reload();

 

Coupons.dtd (for xml)

<!ELEMENT list (config|rewards)*>
	<!ELEMENT config (config)*>
		<!ATTLIST config
			code_length CDATA #REQUIRED>
	<!ELEMENT rewards (item)*>
		<!ELEMENT item (#PCDATA)>
		<!ATTLIST item
			id CDATA #REQUIRED
			count CDATA #REQUIRED
			minEnchant CDATA #IMPLIED
			maxEnchant CDATA #IMPLIED>

 

Coupons.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE list SYSTEM "Coupons.dtd">
<!-- minEnchant for min enchant value, maxEnchant for max enchant value -->
<list>
	<config code_length="20"/>
	<rewards>
		<item id="3031" count="2000" /> <!--2000 Spirit Ore -->
		<item id="90907" count="500" /> <!--500 Soulshot Ticket -->
		<item id="91641" count="100" /> <!--100 Sayha's Blessing -->
		<item id="91767" count="2" /> <!--2 x Enchant Kit: Talisman of Aden -->
		<item id="91864" count="2" /> <!--2 x Dragon Belt Pack -->
		<item id="91912" count="5000" /> <!--5000 Hp Potion (Exchangeable) -->
		<item id="92387" count="1" /> <!--+4 Black Ore Set -->
		<item id="93499" count="1" /> <!--Enchanted B-grade Weapon Coupon -->
		<item id="93500" count="5" /> <!--5 x Enchanted B-Grade Armor Coupon -->
		<item id="94269" count="50" /> <!--50 x Scroll: Boost Attack -->
		<item id="94271" count="50" /> <!--50 x Scroll: Boost Defense -->
	</rewards>
</list>

 

ItemData (if you dont have it)

package l2s.gameserver.templates.item.data;

/**
 * @author Bonux
 */
public class ItemData {
	private final int _id;
	private final long _count;
	private final int _minEnchant;
	private int _maxEnchant;
	private long _price;

	public ItemData(int id, long count) {
		_id = id;
		_count = count;
		_minEnchant = -1;
		_maxEnchant = 0;
		_price = 0;
	}

	public ItemData(int id, long count, int enchant) {
		_id = id;
		_count = count;
		_minEnchant = enchant;
		_maxEnchant = enchant;
		_price = 0;
	}
	
	public ItemData(int id, long count, int enchant, int enchantMax) {
		_id = id;
		_count = count;
		_minEnchant = enchant;
		_maxEnchant = enchantMax;
		_price = 0;
	}
	
	public ItemData(int id, long count, int enchant, int enchantMax, long price) {
		_id = id;
		_count = count;
		_minEnchant = enchant;
		_maxEnchant = enchantMax;
		_price = price;
	}
	
	public ItemData(int id, long count, long price) {
		_id = id;
		_count = count;
		_minEnchant = 0;
		_maxEnchant = 0;
		_price = price;
	}

	public int getId() {
		return _id;
	}

	public long getCount() {
		return _count;
	}

	public int getMinEnchant() {
		return _minEnchant;
	}
	
	public int getMaxEnchant() {
		return _maxEnchant;
	}
	
	public long getPrice() {
		return _price;
	}
}

 

ClientPacket (RequestPCCafeCouponUse)

package l2s.gameserver.network.l2.c2s;

import l2s.gameserver.data.xml.holder.CouponHolder;
import l2s.gameserver.model.Player;
import l2s.gameserver.model.entity.CouponManager;

/**
 * format: chS
 */
public class RequestPCCafeCouponUse extends L2GameClientPacket
{
	// format: (ch)S
	private String _code;

	@Override
	protected boolean readImpl() {
		_code = readS();
		return true;
	}

	@Override
	protected void runImpl() {
		final Player activeChar = getClient().getActiveChar();
		if (activeChar == null)
			return;
		if (_code == null || _code.length() != CouponHolder.getInstance().getLength()) {
			activeChar.sendGfxMessage("Your code cannot be empty or less than " + CouponHolder.getInstance().getLength() + " letters/digits");
			activeChar.sendActionFailed();
			return;
		}
		if (CouponManager.getInstance().tryUseCoupon(activeChar, _code))
			activeChar.sendGfxMessage("Your coupon was activated, thank you for playing!");
	}
}

 

ServerPacket (ShowPCCafeCouponShowUI)

package l2s.gameserver.network.l2.s2c;

import l2s.gameserver.network.l2.ServerPacketOpcodes;

/**
 * Даный пакет показывает менюшку для ввода серийника. Можно что-то придумать :)
 * Format: (ch)
 */
public class ShowPCCafeCouponShowUI extends L2GameServerPacket {
	public static final ShowPCCafeCouponShowUI STATIC = new ShowPCCafeCouponShowUI();

	@Override
	protected final void writeImpl() {
		writeOpcode(ServerPacketOpcodes.ShowPCCafeCouponShowUI);
		//
	}
}

 

Open Coupon Window (from bypass)

player.sendPacket(ShowPCCafeCouponShowUI.STATIC);

 

This system is old and no longer useful (at least for me), maybe someone else can make use of it 🙂

Done on l2scripts and tested with protocol 311 (should work starting from h5 but i'm not sure)

 

SQL: https://pastebin.com/FTYqSAWx

 

Generator: https://www.voucherify.io/generator

Character Set: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ

86DvcqL.png

 

QnCSzqb.pngjOPE86X.png

 

Edited by eMommy
  • Like 3
  • Upvote 1
Posted
9 hours ago, splicho said:

Not for interlude tho, I guess? Since coupon window doesn't exist etc..

you can do it with like a html window too instead of the packet

  • 7 months later...
  • 7 months later...
Posted
On 7/21/2022 at 12:49 AM, eMommy said:
package l2s.gameserver.model.entity;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import l2s.commons.util.Rnd;
import l2s.gameserver.data.xml.holder.CouponHolder;
import l2s.gameserver.database.DatabaseFactory;
import l2s.gameserver.model.Player;
import l2s.gameserver.network.l2.s2c.SocialActionPacket;
import l2s.gameserver.templates.item.data.ItemData;
import l2s.gameserver.utils.ItemFunctions;

public class CouponManager
{
	private static final Logger LOG = LoggerFactory.getLogger(CouponManager.class);
	private static final CouponManager INSTANCE = new CouponManager();
	private static final String DEFAULT_OWNER = "NO_OWNER";
	private static final Map<String, String> COUPONS = new HashMap<>();

	public static CouponManager getInstance() {
		return INSTANCE;
	}

	public void load() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
		PreparedStatement statement = con.prepareStatement("SELECT * FROM coupons")) {
			try (ResultSet rset = statement.executeQuery()) {
				while (rset.next())
					COUPONS.put(rset.getString("coupon_id").replace("-", "").toUpperCase(), rset.getString("coupon_owner"));
			}
		} catch (SQLException e) {
			LOG.error("Coupon issues: ", e);
		} finally {
			LOG.info("Coupons: loaded size: " + COUPONS.size());
		}
	}

	public synchronized final boolean tryUseCoupon(Player player, String id) {
		final String owner = COUPONS.get(id.toUpperCase());
		if (owner == null) {
			player.sendMessage("Invalid coupon code.");
			return false;
		}
		if (!owner.equals(DEFAULT_OWNER)) {
			player.sendMessage("This coupon code has expired or was already used.");
			return false;
		}
		final String acc = player.getAccountName().toLowerCase();
		final Collection<String> accounts = COUPONS.values();
		for (String account : accounts) {
			if (account.equals(acc)) {
				player.sendMessage("You may only claim 1 coupon per account.");
				return false;
			}
		}
		COUPONS.put(id, player.getAccountName());
		try (Connection con = DatabaseFactory.getInstance().getConnection();
		PreparedStatement statement = con.prepareStatement("UPDATE coupons SET coupon_owner = ? WHERE coupon_id like ?")) {
			statement.setString(1, acc);
			statement.setString(2, id);
			statement.execute();
		} catch (SQLException e) {
			LOG.warn("Coupons: update failure: " + e);
			return false;
		}
		player.sendGfxMessage("Coupon accepted! Enjoy!");
		player.broadcastPacket(new SocialActionPacket(player.getObjectId(), SocialActionPacket.BOASTING));
		for (ItemData it : CouponHolder.getInstance().getRewards()) {
			if (it.getMinEnchant() <= 0)
				ItemFunctions.addItem(player, it.getId(), it.getCount(), true, "Coupon Reward");
			else
				ItemFunctions.addItem(player, it.getId(), it.getCount(), Rnd.get(it.getMinEnchant(), it.getMaxEnchant()), true, "Coupon Reward");
		}
		return true;
	}
}

 

package l2s.gameserver.data.xml.parser;

import java.io.File;
import org.dom4j.Element;

import l2s.commons.data.xml.AbstractParser;
import l2s.gameserver.Config;
import l2s.gameserver.ConfigSelector;
import l2s.gameserver.data.xml.holder.CouponHolder;
import l2s.gameserver.model.entity.CouponManager;
import l2s.gameserver.templates.item.data.ItemData;

public final class CouponParser extends AbstractParser<CouponHolder>
{
	private static final CouponParser INSTANCE = new CouponParser();

	protected CouponParser() {
		super(CouponHolder.getInstance());
	}

	public static CouponParser getInstance() {
		return INSTANCE;
	}

	public void reload() {
		info("reload start...");
		getHolder().clear();
		load();
	}

	@Override
	protected void onParsed() {
		CouponManager.getInstance().load();
	}
	
	@Override
	public File getXMLPath() {
		return new File(Config.DATAPACK_ROOT, ConfigSelector.getDataFolder() + "/Coupons.xml");
	}

	@Override
	public String getDTDFileName() {
		return "Coupons.dtd";
	}

	@Override
	protected void readData(Element rootElement) {
		for (Element listSet : rootElement.elements("config"))
			getHolder().setLength(parseInt(listSet, "code_length"));
		for (Element listSet : rootElement.elements("rewards")) {
			for (Element paramsSet : listSet.elements("item")) {
				final String p = paramsSet.toString();
				if (p.contains("minEnchant") && !p.contains("maxEnchant"))
					getHolder().addItem(new ItemData(parseInt(paramsSet, "id"), parseInt(paramsSet, "count"), parseInt(paramsSet, "minEnchant")));
				else if (p.contains("minEnchant") && p.contains("maxEnchant"))
					getHolder().addItem(new ItemData(parseInt(paramsSet, "id"), parseInt(paramsSet, "count"), parseInt(paramsSet, "minEnchant", 0), parseInt(paramsSet, "maxEnchant")));
				else
					getHolder().addItem(new ItemData(parseInt(paramsSet, "id"), parseInt(paramsSet, "count")));
			}
		}
	}
}

 

package l2s.gameserver.data.xml.holder;

import java.util.ArrayList;
import java.util.List;
import l2s.commons.data.xml.AbstractHolder;
import l2s.gameserver.templates.item.data.ItemData;

public final class CouponHolder extends AbstractHolder
{
	private static final CouponHolder INSTANCE = new CouponHolder();
	private static int COUPON_LENGTH;
	private static final List<ItemData> REWARDS = new ArrayList<>();

	public static CouponHolder getInstance() {
		return INSTANCE;
	}

	public void addItem(ItemData item) {
		REWARDS.add(item);
	}

	public final List<ItemData> getRewards(){
		return REWARDS;
	}
	
	public void setLength(int i) {
		COUPON_LENGTH = i;
	}
	
	public final int getLength() {
		return COUPON_LENGTH;
	}

	@Override
	public int size() {
		return REWARDS.size();
	}

	@Override
	public void clear() {
		REWARDS.clear();
	}
}

 

Load on Gameserver or Parsers

CouponParser.getInstance().load();

 

Reload Parser (like with //reload)

CouponParser.getInstance().reload();

 

Coupons.dtd (for xml)

<!ELEMENT list (config|rewards)*>
	<!ELEMENT config (config)*>
		<!ATTLIST config
			code_length CDATA #REQUIRED>
	<!ELEMENT rewards (item)*>
		<!ELEMENT item (#PCDATA)>
		<!ATTLIST item
			id CDATA #REQUIRED
			count CDATA #REQUIRED
			minEnchant CDATA #IMPLIED
			maxEnchant CDATA #IMPLIED>

 

Coupons.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE list SYSTEM "Coupons.dtd">
<!-- minEnchant for min enchant value, maxEnchant for max enchant value -->
<list>
	<config code_length="20"/>
	<rewards>
		<item id="3031" count="2000" /> <!--2000 Spirit Ore -->
		<item id="90907" count="500" /> <!--500 Soulshot Ticket -->
		<item id="91641" count="100" /> <!--100 Sayha's Blessing -->
		<item id="91767" count="2" /> <!--2 x Enchant Kit: Talisman of Aden -->
		<item id="91864" count="2" /> <!--2 x Dragon Belt Pack -->
		<item id="91912" count="5000" /> <!--5000 Hp Potion (Exchangeable) -->
		<item id="92387" count="1" /> <!--+4 Black Ore Set -->
		<item id="93499" count="1" /> <!--Enchanted B-grade Weapon Coupon -->
		<item id="93500" count="5" /> <!--5 x Enchanted B-Grade Armor Coupon -->
		<item id="94269" count="50" /> <!--50 x Scroll: Boost Attack -->
		<item id="94271" count="50" /> <!--50 x Scroll: Boost Defense -->
	</rewards>
</list>

 

ItemData (if you dont have it)

package l2s.gameserver.templates.item.data;

/**
 * @author Bonux
 */
public class ItemData {
	private final int _id;
	private final long _count;
	private final int _minEnchant;
	private int _maxEnchant;
	private long _price;

	public ItemData(int id, long count) {
		_id = id;
		_count = count;
		_minEnchant = -1;
		_maxEnchant = 0;
		_price = 0;
	}

	public ItemData(int id, long count, int enchant) {
		_id = id;
		_count = count;
		_minEnchant = enchant;
		_maxEnchant = enchant;
		_price = 0;
	}
	
	public ItemData(int id, long count, int enchant, int enchantMax) {
		_id = id;
		_count = count;
		_minEnchant = enchant;
		_maxEnchant = enchantMax;
		_price = 0;
	}
	
	public ItemData(int id, long count, int enchant, int enchantMax, long price) {
		_id = id;
		_count = count;
		_minEnchant = enchant;
		_maxEnchant = enchantMax;
		_price = price;
	}
	
	public ItemData(int id, long count, long price) {
		_id = id;
		_count = count;
		_minEnchant = 0;
		_maxEnchant = 0;
		_price = price;
	}

	public int getId() {
		return _id;
	}

	public long getCount() {
		return _count;
	}

	public int getMinEnchant() {
		return _minEnchant;
	}
	
	public int getMaxEnchant() {
		return _maxEnchant;
	}
	
	public long getPrice() {
		return _price;
	}
}

 

ClientPacket (RequestPCCafeCouponUse)

package l2s.gameserver.network.l2.c2s;

import l2s.gameserver.data.xml.holder.CouponHolder;
import l2s.gameserver.model.Player;
import l2s.gameserver.model.entity.CouponManager;

/**
 * format: chS
 */
public class RequestPCCafeCouponUse extends L2GameClientPacket
{
	// format: (ch)S
	private String _code;

	@Override
	protected boolean readImpl() {
		_code = readS();
		return true;
	}

	@Override
	protected void runImpl() {
		final Player activeChar = getClient().getActiveChar();
		if (activeChar == null)
			return;
		if (_code == null || _code.length() != CouponHolder.getInstance().getLength()) {
			activeChar.sendGfxMessage("Your code cannot be empty or less than " + CouponHolder.getInstance().getLength() + " letters/digits");
			activeChar.sendActionFailed();
			return;
		}
		if (CouponManager.getInstance().tryUseCoupon(activeChar, _code))
			activeChar.sendGfxMessage("Your coupon was activated, thank you for playing!");
	}
}

 

ServerPacket (ShowPCCafeCouponShowUI)

package l2s.gameserver.network.l2.s2c;

import l2s.gameserver.network.l2.ServerPacketOpcodes;

/**
 * Даный пакет показывает менюшку для ввода серийника. Можно что-то придумать :)
 * Format: (ch)
 */
public class ShowPCCafeCouponShowUI extends L2GameServerPacket {
	public static final ShowPCCafeCouponShowUI STATIC = new ShowPCCafeCouponShowUI();

	@Override
	protected final void writeImpl() {
		writeOpcode(ServerPacketOpcodes.ShowPCCafeCouponShowUI);
		//
	}
}

 

Open Coupon Window (from bypass)

player.sendPacket(ShowPCCafeCouponShowUI.STATIC);

 

This system is old and no longer useful (at least for me), maybe someone else can make use of it 🙂

Done on l2scripts and tested with protocol 311 (should work starting from h5 but i'm not sure)

 

SQL: https://pastebin.com/FTYqSAWx

 

Generator: https://www.voucherify.io/generator

Character Set: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ

86DvcqL.png

 

QnCSzqb.pngjOPE86X.png

 

can u share the implement of this gfx chat packet?

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Posts

    • Good afternoon, gentlemen. I’d like to ask a question to those who know their stuff — and just get some advice. Let me start with the main point. I want to try learning Java. Just for myself — I simply like programming, and it’s a small hobby of mine. Though, perhaps it’s too early to say I really like Java, since I don’t actually know it well yet. I’ll get straight to the point. Back in my youth, like many others, I played Lineage 2. Later, I became interested in creating servers and even made my own — we had around 70 players, all schoolkids like me. I learned how to install builds, add NPCs and various add-ons “from the internet” back then. But my attempts to create my own Lineage 2 additions in Java almost always failed. At best, I could slightly modify ready-made code, but not write anything from scratch. My interest in Lineage 2 and Java has stayed with me to this day. For me, it’s a creative process — a favorite game where you can build your own “sandbox.” I’d really like to hear from the community about where you started. In your opinion, what’s the best way to learn Java using Lineage 2 as an example? I don’t have a clear learning plan — and that’s probably the problem. I’ve tried reading Herbert Schildt book, and I’ve also tried studying Java source codes of various server builds by poking around to understand how the server works and how programs are written. I can’t say I understand nothing — I do get some things, and I believe I have a knack for it. But it’s not systematic. Maybe I really do need advice from more experienced people, to find a strategy that’s both effective for learning Java and interesting because it’s connected to Lineage 2 — not just writing abstract programs “for nothing.” In Lineage 2, you can see the result right away — and I get genuinely happy even from small successes. So here’s the main point: I’d like to know where you started. Is it even worth combining Java and Lineage 2? Recently, ChatGPT has been a huge help — thanks to it, I’ve made some progress and started to understand the server structure at least a little. But there’s one issue: if I just copy and paste, the result works — but without ChatGPT, I can’t reproduce it on my own. I’ve been working with PwSoft source files — I think many people know them. Why exactly them? I can’t really say — probably because there are lots of ready-made add-ons for this build online, and I originally focused more on modifying existing code than writing my own. I worked with PwSoft for a long time. Then, on ChatGPT’s advice, I switched to aCis — it’s cleaner, more minimalistic, without unnecessary “clutter.” It’s easier to understand where everything is located. I installed the aCis sources, everything compiles fine, but… if in PwSoft I could at least navigate the code and write simple methods, in aCis I couldn’t even make a basic NPC that gives noblesse for adena. That’s why I decided to write this post — just to talk about learning Java through Lineage 2, to hear your opinions, your stories, your approaches. Maybe someone will say: “Don’t bother, it’s a waste of time.” I’ll accept those answers too. But I’d still like to hear different perspectives. If you can, please recommend good video courses on Java that would help me learn faster and in a more structured way. To repeat: this is something from my childhood that I never finished. My job is stable now, and I have time — at least sometimes. I can work on this calmly, as long as my learning goes in the right direction. I think you get it: this is something “for the soul,” and I really want to grow in it. Thanks to everyone who read to the end. I really appreciate any advice. And if someone is open to chatting privately or by voice — I’d gladly talk, and I’m happy to “buy a beer” for a constructive conversation. Peace to everyone!😀
    • 亲爱的朋友们,我们很高兴地宣布一个好消息—— 我们正式推出我们的新机器人,用于购买和租用虚拟号码以接收来自任何服务的短信!您是否已经厌倦了其他平台上重复使用或转售的号码? 试试我们的解决方案! 我们的服务允许您接收来自任何主流热门服务的短信。 服务列表包含超过 200 个项目! 短信接收国家:美国 (+1)。我们使用真实的美国实体号码——从未在其他平台上使用过! 目前仅提供短期租用。每个电话号码的租用时长会在购买时显示。 每个号码的价格也会在购买前显示在对应的服务旁。 您还可以为所选服务的号码接收额外短信(接收额外短信无需额外费用)。 若要快速找到所需服务,可使用便捷的搜索功能——只需输入您所需服务的名称即可获得激活号码。 可用支付方式:加密货币、银行卡,以及从我们其他机器人转移余额。 感谢您的信任! 有效链接: 虚拟号码服务:前往 数字商品商店(网站):前往 商店 Telegram 机器人:前往 – 通过 Telegram 信使方便地访问商店。 Telegram 星星购买机器人:前往 – 在 Telegram 中快速且优惠地购买星星。 SMM 面板:前往 – 推广您的社交媒体账户。 我们向您展示当前的促销与特别优惠,用于购买我们平台的商品和服务: 1. 使用促销码 OCTOBER2025(8% 折扣)在十月份于我们的商店(网站、机器人)购物!您也可以使用首次购买促销码:SOCNET(15% 折扣) 2. 获得 $1 商店余额或 10–20% 折扣 —— 注册后在我们网站按以下格式发布用户名:"SEND ME BONUS, MY USERNAME IS..." —— 并将其发布在我们的论坛主题中! 3. 获取 SMM 面板首个试用奖励 $1 —— 只需在我们网站(支持)提交标题为 “Get Trial Bonus” 的工单。 4. 每周在我们的 Telegram 频道和星星购买机器人中赠送 Telegram 星星! 新闻: ➡ Telegram 频道:https://t.me/accsforyou_shop ➡ WhatsApp 频道:https://chat.whatsapp.com/K8rBy500nA73z27PxgaJUw?mode=ems_copy_t ➡ Discord 服务器:https://discord.gg/y9AStFFsrh 联系方式与支持: ➡ Telegram:https://t.me/socnet_support ➡ WhatsApp:https://wa.me/79051904467 ➡ Discord:socnet_support ➡ ✉ 邮箱:solomonbog@socnet.store
    • 亲爱的朋友们,我们很高兴地宣布一个好消息—— 我们正式推出我们的新机器人,用于购买和租用虚拟号码以接收来自任何服务的短信!您是否已经厌倦了其他平台上重复使用或转售的号码? 试试我们的解决方案! 我们的服务允许您接收来自任何主流热门服务的短信。 服务列表包含超过 200 个项目! 短信接收国家:美国 (+1)。我们使用真实的美国实体号码——从未在其他平台上使用过! 目前仅提供短期租用。每个电话号码的租用时长会在购买时显示。 每个号码的价格也会在购买前显示在对应的服务旁。 您还可以为所选服务的号码接收额外短信(接收额外短信无需额外费用)。 若要快速找到所需服务,可使用便捷的搜索功能——只需输入您所需服务的名称即可获得激活号码。 可用支付方式:加密货币、银行卡,以及从我们其他机器人转移余额。 感谢您的信任! 有效链接: 虚拟号码服务:前往 数字商品商店(网站):前往 商店 Telegram 机器人:前往 – 通过 Telegram 信使方便地访问商店。 Telegram 星星购买机器人:前往 – 在 Telegram 中快速且优惠地购买星星。 SMM 面板:前往 – 推广您的社交媒体账户。 我们向您展示当前的促销与特别优惠,用于购买我们平台的商品和服务: 1. 使用促销码 OCTOBER2025(8% 折扣)在十月份于我们的商店(网站、机器人)购物!您也可以使用首次购买促销码:SOCNET(15% 折扣) 2. 获得 $1 商店余额或 10–20% 折扣 —— 注册后在我们网站按以下格式发布用户名:"SEND ME BONUS, MY USERNAME IS..." —— 并将其发布在我们的论坛主题中! 3. 获取 SMM 面板首个试用奖励 $1 —— 只需在我们网站(支持)提交标题为 “Get Trial Bonus” 的工单。 4. 每周在我们的 Telegram 频道和星星购买机器人中赠送 Telegram 星星! 新闻: ➡ Telegram 频道:https://t.me/accsforyou_shop ➡ WhatsApp 频道:https://chat.whatsapp.com/K8rBy500nA73z27PxgaJUw?mode=ems_copy_t ➡ Discord 服务器:https://discord.gg/y9AStFFsrh 联系方式与支持: ➡ Telegram:https://t.me/socnet_support ➡ WhatsApp:https://wa.me/79051904467 ➡ Discord:socnet_support ➡ ✉ 邮箱:solomonbog@socnet.store
    • 亲爱的朋友们,我们很高兴地宣布一个好消息—— 我们正式推出我们的新机器人,用于购买和租用虚拟号码以接收来自任何服务的短信!您是否已经厌倦了其他平台上重复使用或转售的号码? 试试我们的解决方案! 我们的服务允许您接收来自任何主流热门服务的短信。 服务列表包含超过 200 个项目! 短信接收国家:美国 (+1)。我们使用真实的美国实体号码——从未在其他平台上使用过! 目前仅提供短期租用。每个电话号码的租用时长会在购买时显示。 每个号码的价格也会在购买前显示在对应的服务旁。 您还可以为所选服务的号码接收额外短信(接收额外短信无需额外费用)。 若要快速找到所需服务,可使用便捷的搜索功能——只需输入您所需服务的名称即可获得激活号码。 可用支付方式:加密货币、银行卡,以及从我们其他机器人转移余额。 感谢您的信任! 有效链接: 虚拟号码服务:前往 数字商品商店(网站):前往 商店 Telegram 机器人:前往 – 通过 Telegram 信使方便地访问商店。 Telegram 星星购买机器人:前往 – 在 Telegram 中快速且优惠地购买星星。 SMM 面板:前往 – 推广您的社交媒体账户。 我们向您展示当前的促销与特别优惠,用于购买我们平台的商品和服务: 1. 使用促销码 OCTOBER2025(8% 折扣)在十月份于我们的商店(网站、机器人)购物!您也可以使用首次购买促销码:SOCNET(15% 折扣) 2. 获得 $1 商店余额或 10–20% 折扣 —— 注册后在我们网站按以下格式发布用户名:"SEND ME BONUS, MY USERNAME IS..." —— 并将其发布在我们的论坛主题中! 3. 获取 SMM 面板首个试用奖励 $1 —— 只需在我们网站(支持)提交标题为 “Get Trial Bonus” 的工单。 4. 每周在我们的 Telegram 频道和星星购买机器人中赠送 Telegram 星星! 新闻: ➡ Telegram 频道:https://t.me/accsforyou_shop ➡ WhatsApp 频道:https://chat.whatsapp.com/K8rBy500nA73z27PxgaJUw?mode=ems_copy_t ➡ Discord 服务器:https://discord.gg/y9AStFFsrh 联系方式与支持: ➡ Telegram:https://t.me/socnet_support ➡ WhatsApp:https://wa.me/79051904467 ➡ Discord:socnet_support ➡ ✉ 邮箱:solomonbog@socnet.store
    • ANOTHER EPIC SERVER AND ANOTHER EPIC PROJECT! Thank You for using my support!   FAFURION UPDATER for upcoming server! Thank You for using my support! 
  • Topics

×
×
  • Create New...

AdBlock Extension Detected!

Our website is made possible by displaying online advertisements to our members.

Please disable AdBlock browser extension first, to be able to use our community.

I've Disabled AdBlock