Jump to content

Recommended Posts

  • Premium

M2 Download Center

This is the hidden content, please
( Internal )

Hi there Devs,

I would like to share my "little" system. If you aren't interested in the introduction/preview etc. and you just want to download it and put in to your server, just scroll down until the "[How-To] Set up" subtitle.

The story

Firstly let me tell this system's story. I've got an idea one year before, that it would be good if the players would be able to put their items into a "global" system where they could see the other player's items, and they could buy it for DC or gold (that time I worked with the latest vanilla core (not with the source)). Then in the following 8 days I made it (it took about 80-90 working hours). Originally the system was created for one of my friend's server. but this server has never started, and nobody used this system.
After some mounts I've decided to publish it on the Hungarian forum, because it won't worth to work on it for long hours if nobody uses it and its just collecting dust on my computer. Then I've published it on the 2nd of December, 2014.
After some time I've decided to translate it into English and I've got a new idea for a new feature. This feature was: the trade system (I will explain its working later). This idea inspired by one of the players (from a server where this system was able to use). He told me that it would be better if they could set the gold price via an item (what's value is very high).
Then with more than 180 working hours (totally) behind my back I'm here.

Overview

Spoiler

 

Since I'm not a designer and my "graphical skills" are under zero, the system doesn't contains bonus pictures, so it uses the default graphic. Also the layout here and there is not the best. But lets start it.

https://metin2.download/picture/r3F9504x09DVmF8K988YXgTtGTP6KMUj/.gif

What do we see here?

  • On the left side we can find the available menus. The items sorted into groups. After the items there is 3 groups where we can manage our stuffs. Then you can find 2 more functions, the search and the sell functions.
  • On the middle we can find the items. If we hold our mouse on the item's picture, we would be able to see its bonuses/stones. The thee button (and the gold/DC texts) on the right side only displayed if that price is defined.
  • The shown gold is stored in the system, and its value is not equal to the value from the player's inventory.
  • If your purchase is successful, the bought item placed into the "purchases" group, and you can claim your item there. The item's price will be removed from your tradehouse account's bill, and will be credited to the owner's bill.

 https://metin2.download/picture/r3F9504x09DVmF8K988YXgTtGTP6KMUj/.gif

What do we see here?

  • If you click on the "View" button (if its shown) you will see the owner's demand.
  • If you want to buy the selected item for other item(s) you have to fulfil the owner's "dream". You are only able to pull items to the right grid that is on the "owner's list".
  • If you fulfilled the owner's demand, the "Buy it" button will raise. (Its only clickable if all conditions are accomplished.)
  • Its not matter where you pull your item.
  • If your item's bonus value higher or the same as the same item on the owner's list, your item will be accepted.
  • If your item has more bonuses than the same item on the owner's list, but the other bonuses are fit, your item will be accepted.
  • If your item's count doesn't fit with any of the owner's item's count, your item won't be accepted.
  • If you want to pull item to the right grid that is already on the list, it won't be accepted again.
  • If you move your affected items in your inventory or change they quantities during the trade, the trade will fail.
  • If the trade finished successfully, the bought item will be placed into the "purchases" group, and the original owner will be able to claim his/her item(s) in the "Get items from trade" group.

 https://metin2.download/picture/r3F9504x09DVmF8K988YXgTtGTP6KMUj/.gif

What do we see here?

  • Here you can put out for sale your item. You must define at least one price.
  • The item's bonuses are displayed on the middle of the window.
  • If you sell stackable item, the players could select how much they want to buy. If you define the "trade price" they won't be able to select how much they want to buy (they are only able to buy the whole stack).

 https://metin2.download/picture/r3F9504x09DVmF8K988YXgTtGTP6KMUj/.gif

What do we see here?

  • If you click to the "Make trade" button, you can set up the trade.
  • First you must enter the whole name of the item. (no case sensitive)
  • If the item's name not exist or more than one match with it in the server's item_proto table, it will push back an error message.
  • If the item is stackable, an input box appears.
  • If the item is a "wearable item" (e.g. weapon, armour, etc...) the "Set bonuses" buttons will appear. Also, if you can set sockets to the item, the socket buttons will appear.
  • If you hit the "Apply" button, your items will be saved temporary. If you reopen the trade window again, you will see the saved state of it. If you delete items but you don't hit the apply button, the changes won't take effect.

https://metin2.download/picture/r3F9504x09DVmF8K988YXgTtGTP6KMUj/.gif 

What do we see here?

  • Here you can search for other player's items. As the text says, you must fill at least one field.
  • You must enter the owner's name properly. (case sensitive)
  • You don't have to enter the entire item name, but you must enter at least 2 characters. (not case sensitive)

https://metin2.download/picture/r3F9504x09DVmF8K988YXgTtGTP6KMUj/.gif

What do we see here?

  • Here you can see the available languages. Now only 2 languages exists, but if you can (and want to) translate it to your language, find me and I will help you to prepare it for a new lang. (and of course I will share it here with your name)
  • If you change the language the whole window (only the tradehouse's)  will close, and reopen with the new lang.
  • This language changes won't take effect on the item names and the bonus names! (It depends on your client's language (as you can see mine is Hungarian))

Also I've made a video, so if you have some time maybe you should watch it:

 

 

 

 

 

[How-To] Set up

 

Spoiler

 

Firstly download:

This is the hidden content, please

Then unpack it somewhere. Then open your client's source/pack.h

Find this and edit:



QUEST_INPUT_STRING_MAX_NUM = 120,

Then we done with the client.

Open the server source/game/packet.h and edit this:



typedef struct command_quest_input_string
{
	BYTE header;
	char msg[120+1];
} TPacketCGQuestInputString;

Then edit input_main.cpp: (replace the whole function)



void CInputMain::QuestInputString(LPCHARACTER ch, const void* c_pData)
{
	TPacketCGQuestInputString * p = (TPacketCGQuestInputString*) c_pData;

	char msg[121];
	strlcpy(msg, p->msg, sizeof(msg));
	sys_log(0, "QUEST InputString pid %u msg %s", ch->GetPlayerID(), msg);

	quest::CQuestManager::Instance().Input(ch->GetPlayerID(), msg);
}

Then open questlua_game.cpp (if you have the game.mysql_query function from the vanilla source you don't have to do this) and after the includes on the top of the file add this:



#include "db.h"

Then add this function above this: void RegisterGameFunctionTable()



	int game_mysql_query(lua_State* L)
	{
		//MYSQL_FIELD *field;
		SQLMsg* run = DBManager::instance().DirectQuery(lua_tostring(L,1));
		MYSQL_RES* res=run->Get()->pSQLResult;
		if (!res){
			lua_pushnumber(L, 0);
			return 0;
		}
		MYSQL_ROW row;
		lua_newtable(L);			
		int rowcount = 1;
		while((row = mysql_fetch_row(res))){
			lua_newtable(L);
			lua_pushnumber(L, rowcount);
			lua_pushvalue(L, -2);
			lua_settable(L, -4);
			unsigned int fields = mysql_num_fields(res);
			for(unsigned int i = 0; i < fields; i++){
				lua_pushnumber(L, i + 1);
				lua_pushstring(L, row[i]);
				lua_settable(L, -3);
			}
			lua_pop(L, 1);
			rowcount++;
		}
		return 1;
	}

Then add this to the void RegisterGameFunctionTable() under this: { "open_web_mall",                game_web_mall                    },



{"mysql_query", game_mysql_query},

Then open questlua_item.cpp and add this functions before this: void RegisterITEMFunctionTable()



	int item_get_attr_value(lua_State* L)
	{
		CQuestManager& q = CQuestManager::instance();
		LPITEM item = q.GetCurrentItem();

		if (!item)
		{
			sys_err("cannot get current item");
			lua_pushnumber(L, 0);
			return 1;
		}

		if (false == lua_isnumber(L, 1))
		{
			sys_err("index is not a number");
			lua_pushnumber(L, 0);
			return 1;
		}

		int index = lua_tonumber(L, 1);
		const TPlayerItemAttribute& attrItem = item->GetAttribute(index);

		lua_pushnumber(L, attrItem.sValue);

		return 1;
	}

	int item_get_attr_type(lua_State* L)
	{
		CQuestManager& q = CQuestManager::instance();
		LPITEM item = q.GetCurrentItem();

		if (!item)
		{
			sys_err("cannot get current item");
			lua_pushnumber(L, 0);
			return 1;
		}

		if (false == lua_isnumber(L, 1))
		{
			sys_err("index is not a number");
			lua_pushnumber(L, 0);
			return 1;
		}

		int index = lua_tonumber(L, 1);
		const TPlayerItemAttribute& attrItem = item->GetAttribute(index);

		lua_pushnumber(L, attrItem.bType);

		return 1;
	}

Then add this after this: { "copy_and_give_before_remove",    item_copy_and_give_before_remove},



			{ "get_attr_type", item_get_attr_type },
			{ "get_attr_value", item_get_attr_value },

(Originally this functions made by P3NG3R, but I modified them.)

Then open questlua_pc.cpp and add this new function above this: void RegisterPCFunctionTable()



	int pc_give_item_with(lua_State* L)
	{
		if (lua_gettop(L) != 19)
		{
			lua_pushboolean(L, false);
			return 1;
		}

		LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();

		if (!ch)
		{
			sys_err("Char not found (give item with)");
			lua_pushboolean(L, false);
			return 1;
		}

		if (!lua_isstring(L, 1) && !lua_isnumber(L, 1))
		{
			sys_err("QUEST Make item call error : wrong argument");
			lua_pushboolean(L, false);
			return 1;
		}

		DWORD dwVnum;

		if (lua_isnumber(L, 1)) // ąřČŁŔΰćżě ąřČŁ·Î ÁŘ´Ů.
		{
			dwVnum = (int)lua_tonumber(L, 1);
		}
		else if (!ITEM_MANAGER::instance().GetVnum(lua_tostring(L, 1), dwVnum))
		{
			sys_err("QUEST Make item call error : wrong item name : %s", lua_tostring(L, 1));
			lua_pushboolean(L, false);
			return 1;
		}

		int icount = 1;
		if (lua_isnumber(L, 2) && lua_tonumber(L, 2)>0)
		{
			icount = (int)rint(lua_tonumber(L, 2));
			if (icount <= 0)
			{
				sys_err("QUEST Make item call error : wrong item count : %g", lua_tonumber(L, 2));
				lua_pushboolean(L, false);
				return 1;
			}
		}

		LPITEM item = ch->AutoGiveItem(dwVnum, icount);

		if (NULL != item)
		{
			int attr1;
			int value1;
			int attr2;
			int value2;
			int attr3;
			int value3;
			int attr4;
			int value4;
			int attr5;
			int value5;
			int attr6;
			int value6;
			int attr7;
			int value7;
			int socket1;
			int socket2;
			int socket3;

			if (lua_isnumber(L, 3) && lua_isnumber(L, 4) && lua_isnumber(L, 5) && lua_isnumber(L, 6) && lua_isnumber(L, 7) && lua_isnumber(L, 8) && lua_isnumber(L, 9) && lua_isnumber(L, 10) && lua_isnumber(L, 11) && lua_isnumber(L, 12) && lua_isnumber(L, 13) && lua_isnumber(L, 14) && lua_isnumber(L, 15) && lua_isnumber(L, 16) && lua_isnumber(L, 17) && lua_isnumber(L, 18) && lua_isnumber(L, 19))
			{
				attr1 = (int)lua_tonumber(L, 3);
				value1 = (int)lua_tonumber(L, 4);

				attr2 = (int)lua_tonumber(L, 5);
				value2 = (int)lua_tonumber(L, 6);

				attr3 = (int)lua_tonumber(L, 7);
				value3 = (int)lua_tonumber(L, 8);

				attr4 = (int)lua_tonumber(L, 9);
				value4 = (int)lua_tonumber(L, 10);

				attr5 = (int)lua_tonumber(L, 11);
				value5 = (int)lua_tonumber(L, 12);

				attr6 = (int)lua_tonumber(L, 13);
				value6 = (int)lua_tonumber(L, 14);

				attr7 = (int)lua_tonumber(L, 15);
				value7 = (int)lua_tonumber(L, 16);

				socket1 = (int)lua_tonumber(L, 17);
				socket2 = (int)lua_tonumber(L, 18);
				socket3 = (int)lua_tonumber(L, 19);


				item->SetForceAttribute(0, attr1, value1);
				item->SetForceAttribute(1, attr2, value2);
				item->SetForceAttribute(2, attr3, value3);
				item->SetForceAttribute(3, attr4, value4);
				item->SetForceAttribute(4, attr5, value5);
				item->SetForceAttribute(5, attr6, value6);
				item->SetForceAttribute(6, attr7, value7);

				item->SetSocket(0, socket1);
				item->SetSocket(1, socket2);
				item->SetSocket(2, socket3);

				lua_pushboolean(L, true);
				return 1;
			}

			lua_pushboolean(L, false);
			return 1;
		}
		lua_pushboolean(L, false);
		return 1;
	}

And then add this before this: { NULL,            NULL            }



{ "give_item_with", pc_give_item_with },

Now we done with the source. Open your questlib.lua, and write this to somewhere:



os.setlocale("fr_FR.ISO8859-1","ctype")
dofile(get_locale_base_path().."/quest/igshop_settings.lua")
dofile(get_locale_base_path().."/quest/igshop.lua")

Edit the "dofile"s if necessary. The os.setlocale is necessary, because if we don't set it, the string.lower() LUA function won't work with some characters (for e.g ŐÚÓŰÉÁÍ (from the Hungarian lang)) If you are "scared" from the HU language or from this os.setlocale and you are sure that you don't need this just delete them (you can find an other one in the mb_igshop_source.quest).

After this copy all files (included the igshop directory) from the serverside dir into your quest folder.

Then open the igshop_settings.lua and edit the igshop_path variable (it should point to the igshop directory!)
After this try to ./qc the mb_igshop_source.quest, and if you run into missing functions then copy and paste them into your quest_functions file.

Okay, we now done with the clientside. Unpack our root files, then copy the igshop.py and the igshop_lang.py files from the clientside directory, and add it to our root files.

Then open the game.py, and write 



import igshop

to the top of the file

After this find



class GameWindow(ui.ScriptWindow):
	def __init__(self, stream):

then add this to the __init__ function:



        ## Ingame Shop
        self.InGameShop = None
        ## Ingame Shop END

Then search for this:



	def Close(self):

and add this:



		if self.InGameShop != None:
			self.InGameShop.Close()

		## Ingame Shop
		self.InGameShop = None
		## Ingame Shop END

Then search for this:



	def __ServerCommand_Build(self):
		serverCommandList={

and add this to the list:



			##Ingame Shop
			"IGShopqID"					: self._IGShopqID,
			"IGSHOP"					: self._IGShopCMD,
			"IGShop_ITEMS"				: self._IGShopItems,
			"IGShop_BOUGHTITEMS"		: self._IGShopBoughtItems,
			"IGShop_MYSALES"			: self._IGShopMyItems,
			"IGSHOP_ADDTOTEMPSLOT"		: self._AddItemSlot,
			"trade.ReturnSearchItem"	: self._IGShopReturnSearchItem,
			"trade.ApplySocket"			: self._IGShopApplySocket,
			"trade.SendData" 			: self._IGShopAppendTradeDiagItem,
			"trade.GetTradeItems"		: self._IGShopGetTradeItem,
			"trade.AppendGetItem"		: self._IGShopAppendGetItem,
			##End of Ingame Shop
			#InputBlock
			"inputblock"			: self.__InputBlock,
			"inputblockend"			: self.__InputBlockEnd,
			##End of InputBlock

Then search this function:



	def OpenQuestWindow(self, skin, idx):

and add this directly under the function's definition!!!:



		if constInfo.INPUT_IGNORE == 1:
			return

Then scroll down to the button of the file and add this functions:



	#InputBlock
	
	def __InputBlock(self):
		constInfo.INPUT_IGNORE = 1 
		
	def __InputBlockEnd(self):
		constInfo.INPUT_IGNORE = 0
	
	##End of InputBlock
	
	## Ingame Shop
	def _IGShopqID(self, qID):
		constInfo.IGShop["qID"] = int(qID)
		
	def _IngameShopQuestCMD(self):
		net.SendQuestInputStringPacket(str(constInfo.IGShop["questCMD"]))
		constInfo.IGShop["questCMD"] = "NULL#"
		
	def _IGShopCMD(self, command):
		cmd = command.split("/")
		
		if cmd[0] == "QUESTCMD":
			self._IngameShopQuestCMD()

		elif cmd[0] == "OPEN":
			if self.InGameShop == None:
				enableTradeSys = False
				if int(cmd[2]) == 1:
					enableTradeSys = True
				self.InGameShop = igshop.IngameShop(enableTradeSys, int(cmd[3])-1)
			tradeSys = 0
			if self.InGameShop.enableTradeSys:
				tradeSys = 1
			if tradeSys != int(cmd[2]) or int(cmd[3])-1 != self.InGameShop.lang:
				del self.InGameShop
				enableTradeSys = False
				if int(cmd[2]) == 1:
					enableTradeSys = True
				self.InGameShop = igshop.IngameShop(enableTradeSys, int(cmd[3])-1)
			if not self.InGameShop.IsShow():
				self.InGameShop.SetSE(int(cmd[1]))
				self.InGameShop.Show()
			
		elif cmd[0] == "MANAGEPAGES":
			self.InGameShop.ManagePageButtons(int(cmd[1]), int(cmd[2]))
			
		elif cmd[0] == "SETCASH":
			self.InGameShop.SetSE(cmd[1])
			
		elif cmd[0] == "SETGOLD":
			self.InGameShop.SetGold(cmd[1])
		
		elif cmd[0] == "REFRESHAFTERBUY":
			self.InGameShop.Refresh_func()
			
		elif cmd[0] == "EMPTYINPUTGOLD":
			self.InGameShop.GetMoney.SetText("0")

		elif cmd[0] == "trade.ApplyWantsItem":
			self.InGameShop.sellTradeDiag.RecvAppendItemCommand()

		elif cmd[0] == "recvSuccessSaleItem":
			self.InGameShop.RecvFinisSale()
			if self.InGameShop.enableTradeSys:
				self.InGameShop.sellTradeDiag.Close(2)

		elif cmd[0] == "OpenTradeDiag":
			if self.InGameShop.enableTradeSys:
				self.InGameShop.tradeDiag.Open()

		elif cmd[0] == "RecvTradeError":
			if self.InGameShop.enableTradeSys:
				self.InGameShop.tradeDiag.representedSlots.remove(int(cmd[2]))
				self.InGameShop.tradeDiag.applyedSlots.pop(int(cmd[1]))

		elif cmd[0] == "SecondFail":
			if self.InGameShop.enableTradeSys:
				self.InGameShop.tradeDiag.representedSlots.remove(int(cmd[1]))

				for i in self.InGameShop.tradeDiag.applyedSlots:
					if i[0] == int(cmd[2]):
						self.InGameShop.tradeDiag.applyedSlots.remove(i)
						break

				for i in self.InGameShop.tradeDiag.applyedSlots:
					if i[0] == int(cmd[1]):
						i[0] = int(cmd[2])
						break

		elif cmd[0] == "SuccessAppend":
			if self.InGameShop.enableTradeSys:
				self.InGameShop.tradeDiag.SuccessAppend(int(cmd[1]))
		elif cmd[0] == "OpenGetDialog":
			if self.InGameShop.enableTradeSys:
				isPreview = False
				if int(cmd[4]) == 1:
					isPreview = True
				self.InGameShop.getTradeDiag.Open(int(cmd[1]), int(cmd[2]), int(cmd[3]), isPreview)
			
	def _IGShopItems(self, slot, mID, vnum, count, owner_name, price_se, price_yang, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, istate, isTradeItem):
		item.SelectItem(int(vnum))
		constInfo.IGShop["slot"+slot]["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
		constInfo.IGShop["slot"+slot]["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
		constInfo.IGShop["vnum"+slot] = int(vnum)
		self.InGameShop.AddItem(int(slot), long(mID), int(vnum), item.GetItemName(), count, owner_name, price_se, price_yang, int(isTradeItem))
		
	def _IGShopBoughtItems(self, slot, mID, vnum, count, owner_name, price_se, price_yang, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, istate, isTradeItem):
		item.SelectItem(int(vnum))
		constInfo.IGShop["slot"+slot]["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
		constInfo.IGShop["slot"+slot]["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
		constInfo.IGShop["vnum"+slot] = int(vnum)
		self.InGameShop.AddItemToBoughtItems(int(slot), long(mID), int(vnum), item.GetItemName(), count, owner_name, int(isTradeItem))
		
	def _IGShopMyItems(self, slot, mID, vnum, count, owner_name, price_se, price_yang, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, istate, isTradeItem):
		item.SelectItem(int(vnum))
		constInfo.IGShop["slot"+slot]["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
		constInfo.IGShop["slot"+slot]["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
		constInfo.IGShop["vnum"+slot] = int(vnum)
		self.InGameShop.AddItemToMyItems(int(slot), long(mID), int(vnum), item.GetItemName(), count, long(istate), int(isTradeItem))
		
	def _AddItemSlot(self, slot ,itemVnum, count, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6):
		self.InGameShop.itembs["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
		self.InGameShop.itembs["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
		self.InGameShop.vnumb = int(itemVnum)
		self.InGameShop.countb = int(count)
		self.InGameShop.Slots.ClearSlot(1)
		self.InGameShop.Slots.ClearSlot(2)
		self.InGameShop.Slots.ClearSlot(3)
		if int(count) == 1:
			count = 0
		self.InGameShop.Slots.SetItemSlot(int(slot), int(itemVnum), int(count))
		self.InGameShop.Slots.RefreshSlot()
		self.InGameShop.ShowForSell()
		self.InGameShop.SetBuypageTexts()

	def _IGShopReturnSearchItem(self, itemVnum, slot, isStackable, addOnType, maxSocket):
		if int(isStackable) > 0:
			isStackable = True
		else:
			isStackable = False
		self.InGameShop.sellTradeDiag.SetSearchSlots(int(itemVnum), int(slot), isStackable, int(addOnType), int(maxSocket))

	def _IGShopApplySocket(self, pos, vnum):
		self.InGameShop.sellTradeDiag.ApplySocket(int(pos), int(vnum))

	def _IGShopAppendTradeDiagItem(self, pos, vnum, count, socket1, socket2, socket3, attrtype1, attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, attrtype7, attrvalue7):
		self.InGameShop.tradeDiag.AppendWantsItem(int(pos), int(vnum), int(count), [int(socket1), int(socket2), int(socket3)], [[int(attrtype1), int(attrvalue1)], [int(attrtype2), int(attrvalue2)], [int(attrtype3), int(attrvalue3)], [int(attrtype4), int(attrvalue4)], [int(attrtype5), int(attrvalue5)], [int(attrtype6), int(attrvalue6)], [int(attrtype7), int(attrvalue7)]])

	def _IGShopGetTradeItem(self, slot, itemID, vnum, count, buyer_name, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6):
		item.SelectItem(int(vnum))
		constInfo.IGShop["slot"+slot]["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
		constInfo.IGShop["slot"+slot]["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
		constInfo.IGShop["vnum"+slot] = int(vnum)
		self.InGameShop.AddItemToTradeItem(int(slot), int(vnum), int(count), buyer_name, long(itemID))

	def _IGShopAppendGetItem(self, pos, vnum, count, socket1, socket2, socket3, attrtype1, attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, attrtype7, attrvalue7):
		self.InGameShop.getTradeDiag.AppendItem(int(pos), int(vnum), int(count), [int(socket1), int(socket2), int(socket3)], [[int(attrtype1), int(attrvalue1)], [int(attrtype2), int(attrvalue2)], [int(attrtype3), int(attrvalue3)], [int(attrtype4), int(attrvalue4)], [int(attrtype5), int(attrvalue5)], [int(attrtype6), int(attrvalue6)], [int(attrtype7), int(attrvalue7)]])
	## Ingame Shop END

After this open the ui.py and add this "new" class to the file:



class IgShopListBox(Window):

	TEMPORARY_PLACE = 3

	def __init__(self, layer = "UI", isIgShop = False):
		Window.__init__(self, layer)
		self.isIgShop = isIgShop
		self.overLine = -1
		self.selectedLine = -1
		self.width = 0
		self.height = 0
		self.stepSize = 17
		self.basePos = 0
		self.showLineCount = 0
		self.itemCenterAlign = True
		self.itemList = []
		self.keyDict = {}
		self.textDict = {}
		self.event = lambda *arg: None
	def __del__(self):
		Window.__del__(self)

	def GetSelectedItemText(self):
		return self.textDict.get(self.selectedLine, "")

	def SetWidth(self, width):
		self.SetSize(width, self.height)

	def SetSize(self, width, height):
		Window.SetSize(self, width, height)
		self.width = width
		self.height = height

	def SetTextCenterAlign(self, flag):
		self.itemCenterAlign = flag

	def SetBasePos(self, pos):
		self.basePos = pos
		self._LocateItem()

	def ClearItem(self):
		self.keyDict = {}
		self.textDict = {}
		self.itemList = []
		self.overLine = -1
		self.selectedLine = -1

	def InsertItem(self, number, text):
		self.keyDict[len(self.itemList)] = number
		self.textDict[len(self.itemList)] = text

		textLine = TextLine()
		textLine.SetParent(self)
		textLine.SetText(text)
		textLine.Show()

		if self.itemCenterAlign:
			textLine.SetWindowHorizontalAlignCenter()
			textLine.SetHorizontalAlignCenter()

		self.itemList.append(textLine)

		self._LocateItem()

	def ChangeItem(self, number, text):
		for key, value in self.keyDict.items():
			if value == number:
				self.textDict[key] = text

				if number < len(self.itemList):
					self.itemList[key].SetText(text)

				return

	def LocateItem(self):
		self._LocateItem()

	def _LocateItem(self):

		skipCount = self.basePos
		yPos = 0
		self.showLineCount = 0

		for textLine in self.itemList:
			textLine.Hide()

			if skipCount > 0:
				skipCount -= 1
				continue

			if localeInfo.IsARABIC():
				w, h = textLine.GetTextSize()
				textLine.SetPosition(w+10, yPos + 3)
			else:
				textLine.SetPosition(0, yPos + 3)

			yPos += self.stepSize

			if yPos <= self.GetHeight():
				self.showLineCount += 1
				textLine.Show()

	def ArrangeItem(self):
		self.SetSize(self.width, len(self.itemList) * self.stepSize)
		self._LocateItem()

	def GetViewItemCount(self):
		return int(self.GetHeight() / self.stepSize)

	def GetItemCount(self):
		return len(self.itemList)

	def SetEvent(self, event):
		self.event = event

	def SelectItem(self, line):

		if not self.keyDict.has_key(line):
			return

		if line == self.selectedLine:
			return

		self.selectedLine = line
		self.event(self.keyDict.get(line, 0), self.textDict.get(line, "None"))

	def GetSelectedItem(self):
		return self.keyDict.get(self.selectedLine, 0)

	def OnMouseLeftButtonDown(self):
		if self.overLine < 0:
			return

	def OnMouseLeftButtonUp(self):
		if self.overLine >= 0:
			if self.isIgShop and self.keyDict.has_key(self.overLine+self.basePos):
				if self.textDict.get(self.overLine+self.basePos, "") == "":
					return
			self.SelectItem(self.overLine+self.basePos)

	def OnUpdate(self):

		self.overLine = -1

		if self.IsIn():
			x, y = self.GetGlobalPosition()
			height = self.GetHeight()
			xMouse, yMouse = wndMgr.GetMousePosition()

			if yMouse - y < height - 1:
				self.overLine = (yMouse - y) / self.stepSize

				if self.overLine < 0:
					self.overLine = -1
				if self.overLine >= len(self.itemList):
					self.overLine = -1
				if self.overLine != -1 and self.isIgShop and self.keyDict.has_key(self.overLine):
					if self.textDict.get(self.overLine, "") == "":
						self.overLine = -1


	def OnRender(self):
		xRender, yRender = self.GetGlobalPosition()
		yRender -= self.TEMPORARY_PLACE
		widthRender = self.width
		heightRender = self.height + self.TEMPORARY_PLACE*2

		if localeInfo.IsCIBN10:
			if -1 != self.overLine and self.keyDict[self.overLine] != -1:
				grp.SetColor(HALF_WHITE_COLOR)
				grp.RenderBar(xRender + 2, yRender + self.overLine*self.stepSize + 4, self.width - 3, self.stepSize)				

			if -1 != self.selectedLine and self.keyDict[self.selectedLine] != -1:
				if self.selectedLine >= self.basePos:
					if self.selectedLine - self.basePos < self.showLineCount:
						grp.SetColor(SELECT_COLOR)
						grp.RenderBar(xRender + 2, yRender + (self.selectedLine-self.basePos)*self.stepSize + 4, self.width - 3, self.stepSize)

		else:		
			if -1 != self.overLine:
				grp.SetColor(HALF_WHITE_COLOR)
				grp.RenderBar(xRender + 2, yRender + self.overLine*self.stepSize + 4, self.width - 3, self.stepSize)				

			if -1 != self.selectedLine:
				if self.selectedLine >= self.basePos:
					if self.selectedLine - self.basePos < self.showLineCount:
						grp.SetColor(SELECT_COLOR)
						grp.RenderBar(xRender + 2, yRender + (self.selectedLine-self.basePos)*self.stepSize + 4, self.width - 3, self.stepSize)

Then open the constinfo.py and add this:



INPUT_IGNORE = 0

## Ingame Shop
IGShop = {
	"qID"	: 0,
	"questCMD" : "",
	"slot1" : {"socket" : {}, "attr" : {}},
	"slot2" : {"socket" : {}, "attr" : {}},
	"slot3" : {"socket" : {}, "attr" : {}},
	"slot4" : {"socket" : {}, "attr" : {}},
	"slot5" : {"socket" : {}, "attr" : {}},
	"vnum1" : 0,
	"vnum2" : 0,
	"vnum3" : 0,
	"vnum4" : 0,
	"vnum5" : 0,
}
##End of Ingame Shop

Then open the mousemodule.py and edit this under this:def AttachObject(self, Owner, Type, SlotNumber, ItemIndex, count = 0):



if Type == player.SLOT_TYPE_INVENTORY or\
				Type == player.SLOT_TYPE_PRIVATE_SHOP or\
				Type == player.SLOT_TYPE_SHOP or\
				Type == player.SLOT_TYPE_SAFEBOX or\
				Type == player.SLOT_TYPE_MALL or\
				Type == player.SLOT_TYPE_DRAGON_SOUL_INVENTORY:

to this:



if Type == player.SLOT_TYPE_INVENTORY or\
				Type == player.SLOT_TYPE_PRIVATE_SHOP or\
				Type == player.SLOT_TYPE_SHOP or\
				Type == player.SLOT_TYPE_SAFEBOX or\
				Type == player.SLOT_TYPE_MALL or\
				Type == player.SLOT_TYPE_DRAGON_SOUL_INVENTORY or\
				Type == 777:

And we are done. You can open the tradehouse via the storekeeper (9005). If you run into a problem during the set up, check this video and you maybe find whats wrong:

 

 

 

 

Customizing the tradehouse

 

Spoiler

 

If everything is working well, you can start to customize the tradehouse. I made some config options/filters that maybe will be help you to set for e.g. the maximum number of item's that a player could put out for sale, the minimum level for selling items, and so on. Open the igshop_settings.lua on the serverside, and read the comments. If something is not clear, ask me here or via PM.

As you can see, there is a variable with this name: igshopEnableTrade
With this variable, you can disable the "trade system", if you don't need it. All of the buttons and text that is connected to this feature won't be displayed for the players.

You can open the tradehouse from other quest, just type



mb_igshop.OpenIgShop()


Important, that you should modifiy the "name changer quest", because if a player has items in the tradehouse and he/she changes his/her name, his/her items will be lost. Add this check somewhere to the name changer quest:



if not mb_igshop.can_change_name(pc.get_name()) then say("You can't change your name because") say("you have items in the tradehouse") return end

 

 

 

 

Questions and Answers

Spoiler

 

  • Q.: Did you test the system?
  • A.: Of course I did. This don't mean that there can't be bugs, but I did everything I can to make an unbugged and safe system. I know one server with more than 300 online player what is using this system's previous version (without the trade system), and they using this since May, 2015.

 

  • Q.: How much resources does the system use?
  • A.: I'm very proud that the system uses quite little resources (RAM, CPU). Once I've tried to fill the itemlists with more than 10 000 items (editting the txts) and the CPU usage remained average. On my computer the highest usage was 9%. (That time I've tried to flood the system with a refresh command). I have an intel dual core 3GHZ.

 

  • Q.: If someone hack my client can he/she cheat with this system?
  • A.: Well I've made as much checks as I can on the server-side to avoid security problems, so I hope that no one will find a loophole.

 

  • Q.: If someone find a bug will you repair it and publicate the fix?
  • A.: Yes, I will start the repair ASAP.

 

  • Q.: Why don't you sell the system instead of publicating it?
  • A.: There is three reasons. One, because this is my "hi there I'm here" system, its a good reference work. The second one is because I used a lot of functions in lua and python, and made a lot of solutions for a lot of different problems, and maybe it will usefull for someone. The third one is a simple reason: I think the system's layout a bit "ugly" and maybe no one gonna buy it ;)

 

  • Q.: I would like to help you to improve this system. How can I?
  • A.: If you find a bug, report it to me with as much information as possible (how the bug appeared, and so on). Also if your graphics skill and imagination are better than mine, and you like to make a layout for the system, make some picture and send it to me, and if I find it cool and I have time I'm gonna program it. (ofc I will publicate it with your name)
    OR if you can translate the system to an other language find me and I will help you to prepare the new slot for the new lang. (ofc I will publicate it with your name)

 

  • Q.: One more question. How long is this system?
  • A.: It has more than 5 000 lines and 200 000 characters.

 

 

 

Notes

changelog:

19th of August, 2015: I publicated the tradehouse here.

my toDo list:

add logging for the system (the released version don't log the actions in the tradehouse)

Thanks for reading the topic, if you have any problem/remark feel free to ask it here or write me a PM. Have a good day!

Edited by Metin2 Dev
Core X - External 2 Internal
  • Metin2 Dev 27
  • Eyes 1
  • Good 20
  • Love 4
  • Love 67

The one and only UI programming guideline

Link to comment
Share on other sites

  • Premium

Thank you guys for the positive feedbacks.

I use to use Sublime Text 3 since I saw Chuck's signature (when there was signature). I started to use this because this:

https://metin2.download/picture/r3F9504x09DVmF8K988YXgTtGTP6KMUj/.gif

But its more better than notepad++ (for programming). And with Chuck's quest plugin its pretty useful for editing quests too :D

Edited by Metin2 Dev
Core X - External 2 Internal
  • Love 1

The one and only UI programming guideline

Link to comment
Share on other sites

  • Premium

Well in the

void CInputMain::QuestInputString(LPCHARACTER ch, const void* c_pData)

function, I've only edited the maximum input, because originally its about 64 (if I remember well) and its not enough in some cases. So the

strlcpy

originally from ymir.

Anyway you are right, and the get_locale_base_path() is a good idea. I'm gonna edit the topic, thanks :)

The one and only UI programming guideline

Link to comment
Share on other sites

  • Bronze

Well in the

void CInputMain::QuestInputString(LPCHARACTER ch, const void* c_pData)

function, I've only edited the maximum input, because originally its about 64 (if I remember well) and its not enough in some cases. So the

strlcpy

originally from ymir.

Anyway you are right, and the get_locale_base_path() is a good idea. I'm gonna edit the topic, thanks :)

i see , Good Job

Link to comment
Share on other sites

  • Premium
dofile(get_locale_base_path().."/quest/igshop_settings.lua")
dofile(get_locale_base_path().."/quest/igshop.lua")

this looks better + portable

and why the :

strlcpy

i mean yes it does do the job (if you want to make sure that

msg

always end with a \0 ) but i think that you should at least make it like this for ex.:

#ifdef __FreeBSD__ // or any BSD system
    strlcpy(msg, p->msg, sizeof(msg));
#else
    strncpy(msg, p->msg, sizeof(msg));
#endif

btw , strlcpy is not a standard of c++

btw , this :

char msg[120+1];

and this :

char msg[121];

is ugly xd .

one last thing : thx for the release , it is a great idea +1 .

Since I'm not so proficient in c++ I have a question about the difference between the

strlcpy and strncpy

So we always need to terminate the strings with the null character (if no, it could lead to strange results). Then what is the problem with the strlcpy? As you said it automatically set the last character to null. Then why strncpy better? Maybe the packet from the client (in this case) is always terminated by the null character and this is why strncpy enough? :unsure:And one more question, why is this problem if its not a standard? Maybe on different os it could lead to different operation?

Edited by masodikbela

The one and only UI programming guideline

Link to comment
Share on other sites

  • Bronze
dofile(get_locale_base_path().."/quest/igshop_settings.lua")
dofile(get_locale_base_path().."/quest/igshop.lua")

this looks better + portable

and why the :

strlcpy

i mean yes it does do the job (if you want to make sure that

msg

always end with a \0 ) but i think that you should at least make it like this for ex.:

#ifdef __FreeBSD__ // or any BSD system
    strlcpy(msg, p->msg, sizeof(msg));
#else
    strncpy(msg, p->msg, sizeof(msg));
#endif

btw , strlcpy is not a standard of c++

btw , this :

char msg[120+1];

and this :

char msg[121];

is ugly xd .

one last thing : thx for the release , it is a great idea +1 .

Since I'm not so proficient in c++ I have a question about the difference between the

strlcpy and strncpy

So we always need to terminate the strings with the null character (if no, it could lead to strange results). Then what is the problem with the strlcpy? As you said it automatically set the last character to null. Then why strncpy better? Maybe the packet from the client (in this case) is always terminated by the null character and this is why strncpy enough? :unsure:And one more question, why is this problem if its not a standard? Maybe on different os it could lead to different operation?

strncpy_s might be a better option as well if you are using windows. but still not protable as usuall same goes for strlcpy.

strncpy should work more safely (compared to strcpy) by taking a third argument to indicate the maximum allowed size of the target array,however it is hated duo to some problems, strlcpy is just marginally then strncpy , most ppl would prefer using a c++ standard function rather then a BSD fix .

btw the main diffirence is that strlcpy guarantee to NUL-terminate(as long as size is larger than 0). so if you are confident enough just use strncpy if not and as long as you are in BSD system then you are welcome to use strlcpy . (here is a small help) . for me i prefer strncpy maybe in the far future someone will fix it. or just get ride of it.

>basicly i hate how ymir coders think ^^. (ask someone who is recoding some part to tell you about the books drop function)

and one last thing there is that small problem with the syntax highlighting in most of the forums i recommend you to use the spoiler.

btw , if you use strncpy and there is a problem then there might be a chance to see something funny (such things happen all the time).

Edited by Night
  • Love 1
Link to comment
Share on other sites

  • Premium

where i should to put

 

os.setlocale("fr_FR.ISO8859-1","ctype")
dofile(get_locale_base_path().."/quest/igshop_settings.lua")
dofile(get_locale_base_path().."/quest/igshop.lua")

in questlua.cpp, i tryed but always i have this error

 

428gtRt.png

The dofiles and the os.setlocale goes to the questlib.lua, not to the questlua.cpp. (Sorry I was wrong with the tutorial, I'm going to edit the topic)

 

@Night: Since the forum was updated I can't find the spoiler :( Also thank you for the explanation, its clear and it will definitely helps me in the future. Now I don't like to edit the affected function because my goal was to increase the maximum input and I don't like to experiment with the guys whose just want to set up the system.

Edited by Metin2 Dev
Core X - External 2 Internal

The one and only UI programming guideline

Link to comment
Share on other sites

where i should to put

 

os.setlocale("fr_FR.ISO8859-1","ctype")
dofile(get_locale_base_path().."/quest/igshop_settings.lua")
dofile(get_locale_base_path().."/quest/igshop.lua")

in questlua.cpp, i tryed but always i have this error

 

428gtRt.png

The dofiles and the os.setlocale goes to the questlib.lua, not to the questlua.cpp. (Sorry I was wrong with the tutorial, I'm going to edit the topic)

 

@Night: Since the forum was updated I can't find the spoiler :( Also thank you for the explanation, its clear and it will definitely helps me in the future. Now I don't like to edit the affected function because my goal was to increase the maximum input and I don't like to experiment with the guys whose just want to set up the system.

this is not funny :)) i whisted all night trying

Edited by Metin2 Dev
Core X - External 2 Internal
Link to comment
Share on other sites

where i should to put

 

os.setlocale("fr_FR.ISO8859-1","ctype")
dofile(get_locale_base_path().."/quest/igshop_settings.lua")
dofile(get_locale_base_path().."/quest/igshop.lua")

in questlua.cpp, i tryed but always i have this error

 

428gtRt.png

The dofiles and the os.setlocale goes to the questlib.lua, not to the questlua.cpp. (Sorry I was wrong with the tutorial, I'm going to edit the topic)

 

@Night: Since the forum was updated I can't find the spoiler :( Also thank you for the explanation, its clear and it will definitely helps me in the future. Now I don't like to edit the affected function because my goal was to increase the maximum input and I don't like to experiment with the guys whose just want to set up the system.

this is not funny :)) i whisted all night trying

Now we done with the source. Open your questlib.lua, and write this to somewhere:

os.setlocale("fr_FR.ISO8859-1","ctype")
dofile(get_locale_base_path().."/quest/igshop_settings.lua")
dofile(get_locale_base_path().."/quest/igshop.lua")

 questlib.lua no questlua.cpp

/usr/home/game/share/locale/xxx/quest/questlib.lua

 

How i can put yang in tradehouse? 

https://metin2.download/picture/WJ9ZG803H9BnnGsSgC7RwvtRK4vOywUd/.jpg?1

Edited by Metin2 Dev
Core X - External 2 Internal
  • Love 1
Link to comment
Share on other sites

  • Premium

You have to set the "gold items" in igshop_settings.lua. (igshop_gold_items) By default its the gold/silver ingots (and gold nugget). Then you have to pull the gold items to the sell grids.

Edited by masodikbela

The one and only UI programming guideline

Link to comment
Share on other sites

  • Premium

According to this:

local igshop = io . open ( igshop_path .. "accids/" .. pc . get_account_id ( ) .. "/gold.txt" , "w+" ) 

The problem is with the igshop_path. You can find it in the igshop_settings.lua. Maybe it doesn't point into the igshop folder in the quest directory.

The one and only UI programming guideline

Link to comment
Share on other sites

According to this:

local igshop = io . open ( igshop_path .. "accids/" .. pc . get_account_id ( ) .. "/gold.txt" , "w+" ) 

The problem is with the igshop_path. You can find it in the igshop_settings.lua. Maybe it don't point into the igshop folder in the quest directory.

I forgot to change the path for the patch so it couldn't find the file. My mistake but thank you for the advice.

Link to comment
Share on other sites

Announcements



×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.