Jump to content

Recommended Posts

Posted

Hello Mates,

Here we back again with another Lineage Development Tutorial, this time we will  learn how to work and create a Java NPC, Well what is this ?

NPC is stands for Non-Playable Character,  which is a Character in server that provide some help or services like Buffer, GateKeeper, Agumenter ….. etc, y’all know what NPC is i guess  , so let’s get to the point.

javanpc.jpg

Table of Contents :

  • Tools and Requirements
  • Work With NPC XML Files
  • Register your new NPC Type
  • Create your Java NPC Type Class
  • Build and Test

Tools and Requirements :

- Your favorite XML Editor (I Prefer Notepad++ / Download) or you can use Eclipse for that

- Eclipse or Netbeans for Java Edits and Builds ( Eclipse Download – Netbeans Download )

- A Server Pack (for this tutorial i use Private Pack Based on L2JServer – Download)

- Lineage 2 Client (I will use High Five Client, but you can Adapt it to any Chronicle Except for XML Part i guess)

Work with NPC XML Files :

Okay Before we go any further we need to let the server know that we have a new NPC and tell it’s type, id, stats …. etc, to do this you need to go to this path of game/data/stats/npcs inside your server files, there you’ll find some XML files, you can create your own XML file here or go to the folder custom/ and there you’ll find an XML file named custom.xml , i’ll put my new NPC there for the sake of simplicity .

Open this File with your favorite Editor (Notepad++ in my case) and go to the end of the file, you’ll see something like </list> , Add this code just before it :

<npc id="750002" displayId="32140" name="MxC Guide" usingServerSideName="true" title="Java NPC" usingServerSideTitle="true" type="L2MxCNPC">
	<collision>
		<radius normal="11" />
		<height normal="22.25" />
	</collision>
</npc>

We will end up with something like this :
1-3.png

Let’s Break it down and focus on few parts of this code :

- (id=”750002″) : this is the ID of your new NPC that you will use it to spawn, so make sure it’s unique and try to find your unique numeric schema to avoid conflicts

- (displayId=”32140″) : this indicated how your NPC will look like, it can be considered as Template Id, and of course you can change it to the template your want

- (name=”MxC Guide”) : it’s the NPC name that will appear

- (title=”Java NPC”) : is the NPC Title

- (type=”L2MxCNPC”) : it’s the most important parameter since it’s declaring what type this NPC will be and will look for that Type in your java files, so that’s they type we will be Creating L2MxCNPC

So you can manipulate this values according to your needs, but be away of displayId since the collision parameters are very vary based on the NPC Template you use.

Register Your new NPC Type :

Now we need to register our new Type as we said before our type for this tutorial will be L2MxCNPC, so we will register L2MxCNPCInstance as a type, and to do this you need to go to your server java files and find the package called com.l2jserver.gameserver.enums or could be com.PACKNAME.gameserver.enums , it doesn’t really matter since it can be vary from Pack to Pack but i use Official Structure. Inside this package you will find a Java File called InstanceType.java .

2-2.png

Open this Java File and find the Section named Custom, and add this code at the beginning of this section :

L2MxCNPCInstance(L2Npc),

So it will look like this :

3-2.png

Now we have our new NPC Instance Type Registered, so lets move to the next step.

Create Your Java NPC Type Class : 

Here we got to the fun part, to create your NPC Class find a package with name com.l2jserver.gameserver.model.actor.instance or com.PACKNAME.gameserver.model.actor.instance , name can be vary from pack to pack but almost same structure.

Step .1 :

Right Click on this Package then choose >New > Class

4-2.png

You’ll get a “New Java Class” window, we need to set some options there, First of all set name to L2MxCNPCInstance , second Click on Browse in front of Superclass input, you’ll get another window , search for L2Npc, and when you find it just click on it and hit Ok

5-2.png

Step .2 :

Now we need to add what’s called Constructor which is a block of code that Execute at the beginning of initialization of the Type, so just add this code inside your new class (between open and close curly braces of your class { } )

public L2MxCNPCInstance(L2NpcTemplate template)
{
	super(template);
	setInstanceType(InstanceType.L2MxCNPCInstance);
}

So it will look like :

6-1.png

as you can see here we set instance type to L2MxCNPCInstance which we registered in InstanceType.java file.

Step .3 :

Now what we need to do is to override 1 or 2 methods depends on NPC functionality, by overriding we mean that we are replacing the main functionality of L2Npc (since our type extends L2Npc) with a new functionality or implementation, the first method we need to override is showChatWindow which responsible to show the chat window of the NPC to the player when interact with it.

To override this method just go to below L2MxCNPCInstance method (Constructor) and add this code :

@Override
public void showChatWindow(L2PcInstance player, int val) 
{
		
}

Well, we need to add some java code inside this method to show our HTML Document, but guess what , we didn’t created our HTML Document yet, so we don’t we go and create it now ….

Step .4 :

Go to your game html folder (usually at game/data/html ) and create an HTML document there or in any sub folder, for me i’ll create mxcnpc.html in custom subfolder, and initially i’ll put that html code in :

<html><title>L2JSamDev MxC NPC</title>
<body>
<center>
	<br><br>
	<img src="L2UI_CH3.herotower_deco" width=256 height=32><br>
	<font name="hs9" color="00aff0">Hello From MxC Java NPC</font><br>
	<button action="bypass -h npc_%objectId%_MyCommand" value="Click Me (ByPass)" width=130 height=30 back="L2UI_ct1.button_df" fore="L2UI_ct1.button_df">
	<br>
	<img src="L2UI_CH3.herotower_deco" width=256 height=32><br>
</center>
</body>
</html>

Of course you can do the HTML code / design you want, but this is just for demonstration, but in real life, you’re free to do any HTML design you want.

Step .5 :

Go back to your Java file and add this code to the showChatWindow method, that we overrided before :

//Try to load the HTML document
String content = HtmCache.getInstance().getHtm(player.getHtmlPrefix(), "data/html/custom/mxcnpc.html");
//If not found ?
if(content == null)
{
	//Set a Fallback Content
	content = "Sorry we cannot find your document for MxC NPC";
}

//Create a new NPC Message and set Object ID
NpcHtmlMessage message = new NpcHtmlMessage(getObjectId());
//Set Message Content
message.setHtml(content);
//Replace %objecId% in HTMl with Real Object ID
message.replace("%objectId%", String.valueOf(getObjectId()));
//Send HTML to Player
player.sendPacket(message);

Step .6 :

Well I Guess we are ready to go, but our new NPC actually do nothing, just showing an HTML, so what we can do to give this npc some power ? ….

What makes NPC much powerful and full of features is what called ByPass which means it’s the ability to let players able to click on some Buttons or link and with that click they will send a command to the server, and in Java side we implement the feature or the action that will be taken when the player bypass any command.

If you look closely to the HTML Code we’ve created you will notice this line :

<button action="bypass -h npc_%objectId%_MyCommand" value="Click Me (ByPass)" width=130 height=30 back="L2UI_ct1.button_df" fore="L2UI_ct1.button_df">

Focus on action parameter here it’s equal to ( bypass -h npc_%objectId%_MyCommand ) this means by clicking on this button player will pass a command of MyCommand to the Java NPC handler which has the valid Object ID .

but how we can handle commands bypassed by player using our new NPC ?

It’s simple, all we need is to override another method which is onBypassFeedback .. and to do so add this code below showChatWindow method :

@Override
public void onBypassFeedback(L2PcInstance player, String command) {
		
}

Here i’ll add some little simple code to implement the feature of trading 50KK Adena for 1K Clan Reputation if you are the clan leader, so add this code to on onBypassFeedback method :

//Check for Player and Last NPC if Same
if(player == null || player.getLastFolkNPC() == null || player.getLastFolkNPC().getObjectId() != getObjectId())
{ return; }
if(command.startsWith("MyCommand"))
{
	//Check if Player is the clan Leader
	if(player.isClanLeader())
	{
		//Is he has 50kk Adena ?
		if(player.getAdena() >= 50000000)
		{
			L2Clan clan = player.getClan();
			//Give Reputation
			clan.addReputationScore(1000, true);
			//Take Adena
			player.getInventory().reduceAdena("Clan Requtation", 50000000, player, this);
			player.sendMessage("Your Clan has Earned 1000 Rep for 50 Millions Adena");
		}
		else {player.sendMessage("You don't have Enough Adena");}
	}
	else {player.sendMessage("You are not a Clan Leader");}
}

of course you can maintain and make the code much smarter, but we seek simplicity to be easy to any one to start creating amazing Java NPCs .

Step .7 :

Now our NPC Java class will look like :

package com.l2jserver.gameserver.model.actor.instance;

import com.l2jserver.gameserver.cache.HtmCache;
import com.l2jserver.gameserver.enums.InstanceType;
import com.l2jserver.gameserver.model.L2Clan;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jserver.gameserver.network.serverpackets.NpcHtmlMessage;

public class L2MxCNPCInstance extends L2Npc {

	public L2MxCNPCInstance(L2NpcTemplate template)
	{
		super(template);
		setInstanceType(InstanceType.L2MxCNPCInstance);
	}
	
	@Override
	public void showChatWindow(L2PcInstance player, int val) 
	{
		
		//Try to load the HTML document
		String content = HtmCache.getInstance().getHtm(player.getHtmlPrefix(), "data/html/custom/mxcnpc.html");
		//If not found ?
		if(content == null)
		{
			//Set a Fallback Content
			content = "Sorry we cannot find your document for MxC NPC";
		}
		
		//Create a new NPC Message and set Object ID
		NpcHtmlMessage message = new NpcHtmlMessage(getObjectId());
		//Set Message Content
		message.setHtml(content);
		//Replace %objecId% in HTMl with Real Object ID
		message.replace("%objectId%", String.valueOf(getObjectId()));
		//Send HTML to Player
		player.sendPacket(message);
	}
	
	@Override
	public void onBypassFeedback(L2PcInstance player, String command) {
		//Check for Player and Last NPC if Same
		if(player == null || player.getLastFolkNPC() == null || player.getLastFolkNPC().getObjectId() != getObjectId())
		{ return; }
		if(command.startsWith("MyCommand"))
		{
			//Check if Player is the clan Leader
			if(player.isClanLeader())
			{
				//Is he has 50kk Adena ?
				if(player.getAdena() >= 50000000)
				{
					L2Clan clan = player.getClan();
					//Give Reputation
					clan.addReputationScore(1000, true);
					//Take Adena
					player.getInventory().reduceAdena("Clan Requtation", 50000000, player, this);
					player.sendMessage("Your Clan has Earned 1000 Rep for 50 Millions Adena");
				}
				else {player.sendMessage("You don't have Enough Adena");}
			}
			else {player.sendMessage("You are not a Clan Leader");}
		}
	}
}

So let’s Build our Project and Login into our server to check what we got …

– Lets Spawn our NPC

7.jpg

– and Try to interact with ….

8.jpg

– Finally let’s Check if our command work

9.jpg

Yeah, Finally it's working ... :)

Sorry if any mistakes and if you got any question or need a specific Guide just let me know

Thanks Everyone.

 

  • Upvote 2
Posted

nice guide dude.. i really like it and it helped me even though i tried on interlude ... good for newbies a specified interlude guide would even help me :P if u can provide some help i would appreciate it

Posted
1 hour ago, VanGon said:

nice guide dude.. i really like it and it helped me even though i tried on interlude ... good for newbies a specified interlude guide would even help me :P if u can provide some help i would appreciate it

I'm Glad that you like it, and can you tell me what guide you would want for interlude ? and could you suggest a pack to demonstrate guide for interludes on ? i see many people go for aCis and say it's different structure, some go with another so let me know which pack community go for more , and Thanks :)

Posted
12 minutes ago, SamDev-Coder said:

I'm Glad that you like it, and can you tell me what guide you would want for interlude ? and could you suggest a pack to demonstrate guide for interludes on ? i see many people go for aCis and say it's different structure, some go with another so let me know which pack community go for more , and Thanks :)

most ppl nowadays use acis since its the best u will find out there for free.. what i mean is i try to create a fcking npc in acis some time now and it is just getting errors everywhere :P well i would mostly like a guide on how to create a gmshop in acis.. (not the htm/xml parts but the whole process)

Posted
7 minutes ago, VanGon said:

most ppl nowadays use acis since its the best u will find out there for free.. what i mean is i try to create a fcking npc in acis some time now and it is just getting errors everywhere :P well i would mostly like a guide on how to create a gmshop in acis.. (not the htm/xml parts but the whole process)

No worries i'll try to find aCis and create a tutorial for a GM Shop :) ... Thanks for info

Posted
1 minute ago, Reborn12 said:

Nice guide thanks for share..            gm shop is only htm/multisell :D

well y but both the width and height gives me some trouble.. as well as the fcking button lines... christs sake

Posted
39 minutes ago, Reborn12 said:

Nice guide thanks for share..            gm shop is only htm/multisell :D

Thank you, hope that it's useful for the community :)

Posted

For aCis you only need to copy  an existing template, paste it on 50000-50999.xml (common file for all custom NPCs, no forced reason to use it but it's simply common logic), add following tags if you want custom name/title being displayed (otherwise client names are used)

        <set name="usingServerSideName" val="true"/>
        <set name="usingServerSideTitle" val="true"/>

Then you edit following tags : "name"/"title", rename "id" for "idTemplate" and add a new, unused "id"  (you can see existing exemples on 50000-50999.xml) and edit "type" for the name of the server-side instance type (Folk being the common invul one, Monster for basic monster). That's all for the DP.

-----

For the server side part, you only need to create your instance on model.actor.instance. The name of instance is the name of "type" you entered on XML, there is no "L2" and "Instance" anymore on latest aCis.

showChatWindow is also the most common thing to override, but you can basically override everything. For all overriding possibilities, open and read the class than your instance currently extends. Choices would be different depending about it (Monster offer different choices than Npc, etc)

Posted
2 hours ago, Tryskell said:

For aCis you only need to copy  an existing template, paste it on 50000-50999.xml (common file for all custom NPCs, no forced reason to use it but it's simply common logic), add following tags if you want custom name/title being displayed (otherwise client names are used)


        <set name="usingServerSideName" val="true"/>
        <set name="usingServerSideTitle" val="true"/>

Then you edit following tags : "name"/"title", rename "id" for "idTemplate" and add a new, unused "id"  (you can see existing exemples on 50000-50999.xml) and edit "type" for the name of the server-side instance type (Folk being the common invul one, Monster for basic monster). That's all for the DP.

-----

For the server side part, you only need to create your instance on model.actor.instance. The name of instance is the name of "type" you entered on XML, there is no "L2" and "Instance" anymore on latest aCis.

showChatWindow is also the most common thing to override, but you can basically override everything. For all overriding possibilities, open and read the class than your instance currently extends. Choices would be different depending about it (Monster offer different choices than Npc, etc)

oftopic:ty even though i already fixed it .. :P the only thing im missing is the fcking buffer which needs java so im gonna make one ... also i have rev 360 so the l2 and instance are still there for me in the npc management :P

ontopic: any update my dear friend? i saw u updated ur first post and made some significant improvements

Posted
20 minutes ago, SickJacken said:

can be used example to click npc and open community board?

Well i think it's simply doable

instead of calculating adena, give reputation for the class ... etc

just load the HTML of the Community board which located in data/html/CommunityBoard/xxxxx.html (maybe u use custom community board)

String content = HtmCache.getInstance().getHtm("data/html/CommunityBoard/home.html");

and Send it but instead of sending the packet as NPCMessage, Send it via BaseBBSManager , for example :

BaseBBSManager.separateAndSend(content, player);

You may get errors like Cannot Resolve BaseBBSManager .... blablabla

then u need to import it like 

import com.l2jserver.gameserver.communitybbs.Manager.BaseBBSManager;

and Good luck :) lemme know if u need any help

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



  • Posts

    • [Release] Solo PvP Zone System 🔹 Compatible with: aCis 401+ 📜 Features: ✅ Automatic Exit on Restart: Players are removed from the zone if a restart occurs or logout. ✅ Custom Exit Command: Players can exit the Solo Zone with the voice command .exit. ✅ Teleport NPC Command: new bypass solopvp for gatekeeper. ✅ Random Name Generator: Generates random names. ✅ PvP Flag: The players are flagged within this zone.   xml preview & java code backup code -> https://pastebin.com/974V2p2p   SoloZone.xml <?xml version="1.0" encoding="UTF-8"?> <list> <zone shape="NPoly" minZ="-5200" maxZ="-4680"><!-- Frintezza Solo Zone --> <stat name="name" val="Solo PvP Zone" /> <stat name="locs" val="174244,-89089,-5112;174260,-86881,-5112;173184,-88090,-5112;175309,-88018,-5112;174231,-88019,-5112;175136,-88828,-5104;174962,-87025,-5104;173149,-87142,-5104;173470,-88908,-5112" /> <stat name="restrictedClasses" val="15,16,97" /> <node x="172031" y="-90127"/> <node x="176428" y="-90089"/> <node x="176428" y="-74051"/> <node x="172057" y="-74108"/> </zone> </list> SoloZone Code: diff --git a/java/net/sf/l2j/gameserver/taskmanager/SoloZoneTaskManager.java b/java/net/sf/l2j/gameserver/taskmanager/SoloZoneTaskManager.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/taskmanager/SoloZoneTaskManager.java @@ -0,0 +1,98 @@ +package net.sf.l2j.gameserver.taskmanager; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.logging.Logger; + +import net.sf.l2j.commons.random.Rnd; + +import net.sf.l2j.gameserver.data.manager.ZoneManager; +import net.sf.l2j.gameserver.enums.ZoneId; +import net.sf.l2j.gameserver.handler.voicecommandhandlers.VoiceExitSoloZone; +import net.sf.l2j.gameserver.model.World; +import net.sf.l2j.gameserver.model.actor.Player; +import net.sf.l2j.gameserver.model.location.Location; +import net.sf.l2j.gameserver.model.zone.type.SoloZone; + + +/** + * @author MarGaZeaS + */ +public class SoloZoneTaskManager implements Runnable { + + private static final Location EXIT_LOCATION = VoiceExitSoloZone.getExitLocation(); // Λαμβάνουμε την έξοδο από το VoiceExitSoloZone + + @Override + public void run() + { + // Διασχίζουμε όλους τους παίκτες του κόσμου + for (Player player : World.getInstance().getPlayers()) + { + // Ελέγχουμε αν ο παίκτης είναι στο SoloZone + if (player.isInsideZone(ZoneId.SOLO)) + { + // Μεταφέρουμε τον παίκτη στην έξοδο + player.teleportTo(EXIT_LOCATION.getX(), EXIT_LOCATION.getY(), EXIT_LOCATION.getZ(), 0); + player.sendMessage("The server is restarting, you have been moved out of the Solo Zone."); + } + } + } + + private int _id; + + private static final Logger _log = Logger.getLogger(SoloZoneTaskManager.class.getName()); + private static final ArrayList<String> _rndNames = new ArrayList<>(); + private static final int RANDOM_NAMES = 500; + private static final String CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private int _playersInSoloZone = 0; + + public int getPlayersInside() { + return _playersInSoloZone; + } + + public void setPlayersInside(int val) { + _playersInSoloZone = val; + } + + public SoloZoneTaskManager() { + _log.info("Solo Zone System: Loading..."); + for (int i = 0; i < RANDOM_NAMES; i++) { + String name = generateName(); + _rndNames.add(name); + _log.info("Generated name: " + name); + } + _log.info("Solo Zone System: Loaded " + _rndNames.size() + " names."); + } + + public String getAName() { + if (_rndNames.isEmpty()) { + _log.warning("SoloZoneManager: No random names available."); + return "Unknown"; + } + return _rndNames.get(Rnd.get(5, RANDOM_NAMES - 5)); + } + + private static String generateName() { + SecureRandom rnd = new SecureRandom(); + StringBuilder sb = new StringBuilder(15); + for (int i = 0; i < 15; i++) { + sb.append(CHARS.charAt(rnd.nextInt(CHARS.length()))); + } + return sb.toString(); + } + + public int getZoneId() + { + return _id; + } + + public final static SoloZone getCurrentZone() { + return ZoneManager.getInstance().getAllZones(SoloZone.class) + .stream() + .findFirst() // Επιστρέφει την πρώτη SoloZone (αν υπάρχει μόνο μία) + .orElse(null); + } + + public static SoloZoneTaskManager getInstance() { + return SingletonHolder._instance; + } + + private static class SingletonHolder { + private static final SoloZoneTaskManager _instance = new SoloZoneTaskManager(); + } +} diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/taskmanager/PvpFlagTaskManager.java b/aCis_gameserver/java/net/sf/l2j/gameserver/taskmanager/PvpFlagTaskManager.java index a707ce5..d247e2e 100644 --- a/aCis_gameserver/java/net/sf/l2j/gameserver/taskmanager/PvpFlagTaskManager.java final Player player = entry.getKey(); final long timeLeft = entry.getValue(); + if(player.isInsideZone(ZoneId.SOLO)) + continue; if(player.isInsideZone(ZoneId.BOSS)) continue; // Time is running out, clear PvP flag and remove from list. if (currentTime > timeLeft) diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/RequestCharacterCreate.java b/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/RequestCharacterCreate.java index a707ce5..d247e2e 100644 +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/RequestCharacterCreate.java if (Config.ALLOW_FISH_CHAMPIONSHIP) FishingChampionshipManager.getInstance(); + if (Config.ENABLE_STARTUP) + StartupManager.getInstance(); diff --git a/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminMaintenance.java b/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminMaintenance.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminMaintenance.java if (!st.hasMoreTokens()) { sendHtmlForm(player); return; } try { switch (st.nextToken()) { case "shutdown": + SoloZoneTaskManager exitTask = new SoloZoneTaskManager(); + ThreadPool.schedule(exitTask, 0); Shutdown.getInstance().startShutdown(player, null, Integer.parseInt(st.nextToken()), false); break; case "restart": + exitTask = new SoloZoneTaskManager(); + ThreadPool.schedule(exitTask, 0); Shutdown.getInstance().startShutdown(player, null, Integer.parseInt(st.nextToken()), true); break; case "abort": Shutdown.getInstance().abort(player); break; diff --git a/java/net/sf/l2j/gameserver/handler/voicecommandhandlers/VoiceExitSoloZone.java b/java/net/sf/l2j/gameserver/handler/voicecommandhandlers/VoiceExitSoloZone.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/handler/voicecommandhandlers/VoiceExitSoloZone.java +package net.sf.l2j.gameserver.handler.voicecommandhandlers; + +import net.sf.l2j.commons.pool.ThreadPool; + +import net.sf.l2j.gameserver.enums.ZoneId; +import net.sf.l2j.gameserver.handler.IVoiceCommandHandler; +import net.sf.l2j.gameserver.model.actor.Player; +import net.sf.l2j.gameserver.model.location.Location; +import net.sf.l2j.gameserver.network.serverpackets.MagicSkillUse; + +/** + * Handles the voice command for exiting the Solo Zone with delay and effects. + * + * @author MarGaZeaS + */ +public class VoiceExitSoloZone implements IVoiceCommandHandler +{ + private static final String[] VOICE_COMMANDS = + { + "exit" + }; + + // Default location to teleport players when exiting the Solo Zone + private static final Location EXIT_LOCATION = new Location(81318, 148064, -3464); // Replace with your desired coordinates + + // Προσθήκη της μεθόδου για να πάρουμε την τοποθεσία εξόδου + public static Location getExitLocation() { + return EXIT_LOCATION; + } + + @Override + public void useVoiceCommand(Player player, String command) + { + if (command.equalsIgnoreCase("exit")) + { + if (!player.isInsideZone(ZoneId.SOLO)) + { + player.sendMessage("You are not inside the Solo Zone."); + return; + } + + // Notify the player about the delay + player.sendMessage("You will be teleported out of the Solo Zone in 2 seconds."); + + // Cast skill effect (Skill ID: 2100, Level: 1) + player.broadcastPacket(new MagicSkillUse(player, player, 2100, 1, 2000, 0)); + + // Schedule the teleportation after a 2-second delay + ThreadPool.schedule(() -> { + // Teleport the player to the designated exit location + player.teleportTo(EXIT_LOCATION.getX(), EXIT_LOCATION.getY(), EXIT_LOCATION.getZ(), 0); + + // Inform the player + player.sendMessage("You have exited the Solo Zone."); + }, 2000); // Delay in milliseconds (2000ms = 2 seconds) + } + } + + @Override + public String[] getVoiceCommandList() + { + return VOICE_COMMANDS; + } +} diff --git a/java/net/sf/l2j/gameserver/handler/VoiceCommandHandler.java b/java/net/sf/l2j/gameserver/handler/VoiceCommandHandler.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/handler/VoiceCommandHandler.java public class VoiceCommandHandler { private final Map<String, IVoiceCommandHandler> _entries = new HashMap<>(); protected VoiceCommandHandler() { ............ ............ + registerHandler(new VoiceExitSoloZone()); } public void registerHandler(IVoiceCommandHandler handler) { for (String command : handler.getVoiceCommandList()) _entries.put(command, handler); } diff --git a/java/net/sf/l2j/gameserver/model/actor/Npc.java b/java/net/sf/l2j/gameserver/model/actor/Npc.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/model/actor/Npc.java else if (command.startsWith("Chat")) { int val = 0; try { val = Integer.parseInt(command.substring(5)); } catch (final IndexOutOfBoundsException ioobe) { } catch (final NumberFormatException nfe) { } showChatWindow(player, val); + ) + else if (command.startsWith("solopvp")) + { + SoloZoneTaskManager.getInstance(); + player.teleportTo(SoloZoneTaskManager.getCurrentZone().getLoc(), 25); + } else if (command.startsWith("Link")) { final String path = command.substring(5).trim(); if (path.indexOf("..") != -1) return; final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); html.setFile("data/html/" + path); html.replace("%objectId%", getObjectId()); player.sendPacket(html); } diff --git a/java/net/sf/l2j/gameserver/network/clientpackets/RequestRestartPoint.java b/java/net/sf/l2j/gameserver/network/clientpackets/RequestRestartPoint.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/network/clientpackets/RequestRestartPoint.java // Fixed. - else if (_requestType == 4) - { - if (!player.isGM() && !player.isFestivalParticipant()) - return; - - loc = player.getPosition(); - } + if (_requestType == 4) + { + // Έλεγχος αν ο παίκτης δεν είναι GM, δεν είναι μέρος του φεστιβάλ και δεν είναι στην Solo Zone + if (!player.isGM() && !player.isFestivalParticipant() && !player.isInsideZone(ZoneId.SOLO)) + { + return; + } + + SoloZoneTaskManager.getInstance(); + SoloZone currentZone = SoloZoneTaskManager.getCurrentZone(); + if (currentZone != null && currentZone.getLoc() != null) + { + // Αν υπάρχει ζώνη και οι τοποθεσίες δεν είναι κενές, χρησιμοποιούμε τυχαία τοποθεσία από την ζώνη + loc = currentZone.getLoc(); + } else + { + // Διαφορετικά, κάνουμε respawn στην τρέχουσα θέση του παίκτη + loc = player.getPosition(); + } + } diff --git a/java/net/sf/l2j/gameserver/network/clientpackets/RequestRestart.java b/java/net/sf/l2j/gameserver/network/clientpackets/RequestRestart.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/network/clientpackets/RequestRestart.java if (player.isFestivalParticipant() && FestivalOfDarknessManager.getInstance().isFestivalInitialized()) { player.sendPacket(SystemMessageId.NO_RESTART_HERE); sendPacket(RestartResponse.valueOf(false)); return; } + if (player.isInsideZone(ZoneId.SOLO)) + { + player.sendMessage("You cannot restart your character while in Solo Zone. Use .exit to leave"); + player.setFakeName(null); + sendPacket(RestartResponse.valueOf(false)); + return; + } player.removeFromBossZone(); diff --git a/java/net/sf/l2j/gameserver/network/clientpackets/Logout.java b/java/net/sf/l2j/gameserver/network/clientpackets/Logout.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/network/clientpackets/Logout.java player.removeFromBossZone(); player.logout(true); } } + + if (player.isInsideZone(ZoneId.SOLO)) + { + player.sendMessage("You cannot logout or restart your character while in Solo Zone. Use .exit to leave"); + player.setFakeName(null); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + player.removeFromBossZone(); player.logout(true); } } diff --git a/java/net/sf/l2j/gameserver/model/zone/type/SoloZone.java b/java/net/sf/l2j/gameserver/model/zone/type/SoloZone.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/model/zone/type/SoloZone.java +package net.sf.l2j.gameserver.model.zone.type; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import net.sf.l2j.commons.random.Rnd; + +import net.sf.l2j.Config; +import net.sf.l2j.gameserver.enums.MessageType; +import net.sf.l2j.gameserver.enums.ZoneId; +import net.sf.l2j.gameserver.handler.voicecommandhandlers.VoiceExitSoloZone; +import net.sf.l2j.gameserver.model.World; +import net.sf.l2j.gameserver.model.actor.Creature; +import net.sf.l2j.gameserver.model.actor.Player; +import net.sf.l2j.gameserver.model.location.Location; +import net.sf.l2j.gameserver.model.zone.type.subtype.ZoneType; +import net.sf.l2j.gameserver.network.SystemMessageId; +import net.sf.l2j.gameserver.network.serverpackets.EtcStatusUpdate; +import net.sf.l2j.gameserver.taskmanager.PvpFlagTaskManager; +import net.sf.l2j.gameserver.taskmanager.SoloZoneTaskManager; + +/** + * @author MarGaZeaS + * + */ +public class SoloZone extends ZoneType +{ + private String _name; + private List<Location> _locations = new ArrayList<>(); + + public SoloZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("name")) + _name = value; + else if (name.equals("locs")) + { + for (String locs : value.split(";")) + { + String[] coordinates = locs.split(","); + if (coordinates.length == 3) + { + int x = Integer.parseInt(coordinates[0]); + int y = Integer.parseInt(coordinates[1]); + int z = Integer.parseInt(coordinates[2]); + _locations.add(new Location(x, y, z)); + } + else + { + LOGGER.warn("Invalid location format: " + locs); + } + } + } + } + + + @Override + protected void onEnter(Creature character) + { + if (character instanceof Player) + { + final Player player = (Player) character; + + if ((player.getClassId().getId() == 15 || player.getClassId().getId() == 16 || player.getClassId().getId() == 97)) + { + Location respawnLocation = VoiceExitSoloZone.getExitLocation(); + player.instantTeleportTo(respawnLocation, 20); + player.sendMessage("Your class is not allowed in this zone."); + return; + } + + String randomName = SoloZoneTaskManager.getInstance().getAName(); + if (randomName == null || randomName.isEmpty() || !isValidName(randomName)) + { + randomName = generateRandomName(); + } + if (isNameAlreadyTaken(randomName)) + { + randomName = generateRandomName(); + } + player.setFakeName(randomName); + player.sendMessage("Welcome to the Solo Zone, your random name is: " + randomName); + player.sendPacket(SystemMessageId.ENTERED_COMBAT_ZONE); + character.setInsideZone(ZoneId.SOLO, true); + character.setInsideZone(ZoneId.NO_STORE, true); + character.setInsideZone(ZoneId.NO_SUMMON_FRIEND, true); + + if (player.getParty() != null) + { + player.getParty().removePartyMember(player, MessageType.DISCONNECTED); + } + + if (player.getPvpFlag() > 0) + PvpFlagTaskManager.getInstance().remove(player, true); + + player.updatePvPStatus(); + player.broadcastUserInfo(); + } + } + + private static boolean isValidName(String name) { + return name.matches("[a-zA-Z0-9_]+"); + } + + private static String generateRandomName() { + Random rand = new Random(); + int nameLength = rand.nextInt(12) + 4; + StringBuilder nameBuilder = new StringBuilder(); + + for (int i = 0; i < nameLength; i++) { + char randomChar = (char) (rand.nextInt(26) + 'a'); + nameBuilder.append(randomChar); + } + + return nameBuilder.toString(); + } + + private static boolean isNameAlreadyTaken(String name) { + return World.getInstance().getPlayers().stream().anyMatch(player -> player.getFakeName().equals(name)); + } + + @Override + protected void onExit(Creature character) + { + character.setInsideZone(ZoneId.SOLO, false); // Solo zone + character.setInsideZone(ZoneId.NO_STORE, false); // Allow making a store + character.setInsideZone(ZoneId.NO_SUMMON_FRIEND, false); // Allow summon + + if (character instanceof Player) + { + final Player player = (Player) character; + + if (player.getFakeName() != null) + { + player.setFakeName(null); + } + + player.sendPacket(SystemMessageId.LEFT_COMBAT_ZONE); + { + if(!player.isInObserverMode() && player.getPvpFlag() > 0) + PvpFlagTaskManager.getInstance().add(player, Config.PVP_NORMAL_TIME); + + player.sendPacket(new EtcStatusUpdate(player)); + player.broadcastUserInfo(); + } + } + } + + public String getName() + { + return _name; + } + + public Location getLoc() + { + if (_locations.isEmpty()) + { + return null; // Αν η λίστα είναι κενή, επιστρέφουμε null + } + return _locations.get(Rnd.get(0, _locations.size() - 1)); // Επιλέγουμε τυχαία τοποθεσία + } +} diff --git a/java/net/sf/l2j/gameserver/GameServer.java b/java/net/sf/l2j/gameserver/GameServer.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/GameServer.java if (Config.ALLOW_FISH_CHAMPIONSHIP) FishingChampionshipManager.getInstance(); + StringUtil.printSection("Custom Features"); + SoloZoneTaskManager.getInstance(); StringUtil.printSection("Handlers"); LOGGER.info("Loaded {} admin command handlers.", AdminCommandHandler.getInstance().size()); diff --git a/java/net/sf/l2j/gameserver/GameServer.java b/java/net/sf/l2j/gameserver/Shutdown.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/Shutdown.java // disconnect players try { disconnectAllPlayers(); LOGGER.info("All players have been disconnected."); } catch (Exception e) { // Silent catch. } + // Restore real names for players in SoloZone + restoreRealNamesInSoloZone(); // stop all threadpolls ThreadPool.shutdown(); try { LoginServerThread.getInstance().interrupt(); } catch (Exception e) { // Silent catch. } // avoids new players from logging in if (_secondsShut <= 60 && LoginServerThread.getInstance().getServerType() != ServerType.DOWN) LoginServerThread.getInstance().setServerType(ServerType.DOWN); _secondsShut--; Thread.sleep(1000); } } catch (InterruptedException e) { } } + // This method restores the real names of players in SoloZone + private static void restoreRealNamesInSoloZone() + { + for (Player player : World.getInstance().getPlayers()) + { + // Check if player is inside the SoloZone + if (player.isInsideZone(ZoneId.SOLO)) + { + // Restore the real name by removing the fake name + if (player.getFakeName() != null) + { + player.setFakeName(null); // Restore the real name + LOGGER.info("Player {}'s fake name has been removed and real name restored.", player.getName()); + } + } + } + } private static void sendServerQuit(int seconds) { World.toAllOnlinePlayers(SystemMessage.getSystemMessage(SystemMessageId.THE_SERVER_WILL_BE_COMING_DOWN_IN_S1_SECONDS).addNumber(seconds)); } diff --git a/java/net/sf/l2j/gameserver/enums/ZoneId.java b/java/net/sf/l2j/gameserver/enums/ZoneId.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/enums/ZoneId.java public enum ZoneId { PVP(0), PEACE(1), SIEGE(2), MOTHER_TREE(3), CLAN_HALL(4), NO_LANDING(5), WATER(6), JAIL(7), MONSTER_TRACK(8), CASTLE(9), SWAMP(10), NO_SUMMON_FRIEND(11), NO_STORE(12), TOWN(13), HQ(14), DANGER_AREA(15), CAST_ON_ARTIFACT(16), NO_RESTART(17), SCRIPT(18), - BOSS(19), + BOSS(19), + SOLO(20); private final int _id; private ZoneId(int id) { _id = id; } diff --git a/java/net/sf/l2j/gameserver/network/serverpackets/Die.java b/java/net/sf/l2j/gameserver/network/serverpackets/Die.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/network/serverpackets/Die.java if (creature instanceof Player) { Player player = (Player) creature; - _allowFixedRes = player.getAccessLevel().allowFixedRes(); + _allowFixedRes = player.getAccessLevel().allowFixedRes() || player.isInsideZone(ZoneId.SOLO); _clan = player.getClan(); } diff --git a/java/net/sf/l2j/gameserver/model/actor/Player.java b/java/net/sf/l2j/gameserver//model/actor/Player.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java // Attacker or spectator logging into a siege zone will be ported at town. if (player.isInsideZone(ZoneId.SIEGE) && player.getSiegeState() < 2) player.teleportTo(TeleportType.TOWN); + if (player.isInsideZone(ZoneId.SOLO)) + { + ThreadPool.schedule(() -> { + Location exitLocation = VoiceExitSoloZone.getExitLocation(); + + if (exitLocation != null) + { + player.teleportTo(exitLocation.getX(), exitLocation.getY(), exitLocation.getZ(), 0); + player.sendMessage("You have been moved to the exit of the SoloZone."); + } + }, 5000); // 5000 milliseconds (5sec) + } diff --git a/java/net/sf/l2j/gameserver/model/actor/Player.java b/java/net/sf/l2j/gameserver/model/actor/Player.java new file mode 100644 index 0000000..6b7ef6f --- /dev/null +++ a/java/net/sf/l2j/gameserver/model/actor/Player.java @Override public void doRevive() { super.doRevive(); stopEffects(EffectType.CHARM_OF_COURAGE); sendPacket(new EtcStatusUpdate(this)); getStatus().setCpHpMp(getStatus().getMaxCp(), getStatus().getMaxHp(), getStatus().getMaxMp()); _reviveRequested = 0; _revivePower = 0; if (isMounted()) startFeed(_mountNpcId); + if (isInsideZone(ZoneId.SOLO)) + { + // Give Nobless (1323 ID) + L2Skill no = SkillTable.getInstance().getInfo(1323, 1); + no.getEffects(this, this); + sendMessage("You have received the Nobless status in the Solo Zone."); + } + }   If anyone thinks the code is wrong, please make an update and upload it here so I can update the post. A part was edited with chatgpt
    • Always remember, when you buy files, just compare with my files that I publish for free. and you will know that you are being ripped off. Greetings to all community!!! 🙂
    • Thank you for sharing. You are a capable and skilled person. Thank you again for your selfless dedication, Guytis🫡
    • he kept his promise! i think it's a good idea to unban his old account. he shares files with the community and could help both new and veteran l2off users! good job, Guytis!
  • Topics

×
×
  • Create New...