Jump to content

Biologist Manager (C++ & LUA)


Recommended Posts

  • Premium

This is the hidden content, please

Metin2 Download

The files contain the server side and quests example.

Video: https://metin2.download/video/tMwmR3u7195gtv0qYh0w0ceE5aIkbJdo/.mp4 (yes, at the end the Biologist doesn't give me the rewards because I forgot to change a condition in the quest and I didn't wanna try to record it, again)

So where is the interface file?

I am sorry but I don't have any way to set up an interface for the original sources of the Client. I can share the logic I used:

import ui
import app
import localeInfo
import item
import colorInfo
import net
import uiCommon
import player
import chat
class uiResearchWindow(ui.ScriptWindow):

	BIOLOGIST_QUEST_DICT = {
		0 : { 'item_wanted': 30006, 'pct' : 90, 'required': 20 },
		1 : { 'item_wanted': 30055, 'pct' : 90, 'required': 20 },
		2 : { 'item_wanted': 30047, 'pct' : 90, 'required': 20 },
		3 : { 'item_wanted': 30324, 'pct' : 90, 'required': 25 },
		4 : { 'item_wanted': 30015, 'pct' : 90, 'required': 20 },
		5 : { 'item_wanted': 30050, 'pct' : 90, 'required': 25 },
		6 : { 'item_wanted': 30198, 'pct' : 44, 'required': 30 },
		7 : { 'item_wanted': 30166, 'pct' : 42, 'required': 35 },
		8 : { 'item_wanted': 30495, 'pct' : 30, 'required': 15 },
		9 : { 'item_wanted': 100029, 'pct' : 38, 'required': 40 },
		10 : { 'item_wanted': 30329, 'pct' : 36, 'required': 45 },
		11 : { 'item_wanted': 100039, 'pct' : 34, 'required': 50 },
	}

	MAX_ITEM_QUANTITY = 5
	PCT_BONUS = 10
	PCT_BONUS_PLUS = 30

	BIOLOGIST_PCT_ADDER_VNUM = 102529
	BIOLOGIST_PCT_ADDER_PLUS_VNUM = 102616
	BIOLOGIST_TIME_DELETER_VNUM = 102517

	def __init__(self):
		ui.ScriptWindow.__init__(self)
		self.remainTime = 0
		self.receivedTime = app.GetTime()
		self.remainTimeText = None
		self.questID = 0
		self.itemRequestedText = None
		self.pctSuccessText = None
		self.lastCountDownRefresh = 0
		self.itemToDeliverCount = 1
		self.itemToDeliverVnum = 0
		self.questionDialog = None
		self.popupDialog = None
		self.deliveredItemsQty = 0
		self.isOpened = False
		self.LoadWindow()

	def __del__(self):
		self.remainTime = 0
		self.questID = 0
		self.itemRequestedText = None
		self.pctSuccessText = None
		self.lastCountDownRefresh = 0
		self.itemToDeliverVnum = 0
		self.questionDialog = None
		self.popupDialog = None
		self.isOpened = False
		self.tooltipItem = 0
		ui.ScriptWindow.__del__(self)

	def LoadWindow(self):
		try:
			PythonScriptLoader = ui.PythonScriptLoader()
			PythonScriptLoader.LoadScriptFile(self, "UIScript/researchwindow.py")

		except:
			import exception
			exception.Abort("ResearchWindow.LoadWindow.LoadUIScriptFiles")

		try:
			self.board = self.GetChild("board")
			self.remainTimeText = self.GetChild("ItemCDText")
			self.itemRequestedText = self.GetChild("ItemNametext")
			self.pctSuccessText = self.GetChild("ItemPctText")
			self.buttonTimeDeleterUP = self.GetChild("UpButton1")
			self.itemTimeDeleterTextqty = self.GetChild("ItemSlotbar1Text")
			self.buttonTimeDeleterDown = self.GetChild("DownButton1")

			self.buttonElixirUP = self.GetChild("UpButton2")
			self.buttonElixirTextQty = self.GetChild("ItemSlotbar2Text")
			self.buttonElixirDown = self.GetChild("DownButton2")

			self.buttonElixirPlusUP = self.GetChild("UpButton3")
			self.buttonElixirPlusTextQty = self.GetChild("ItemSlotbar3Text")
			self.buttonElixirPlusDown = self.GetChild("DownButton3")

			self.itemWanted = self.GetChild("current_item")
			self.itemWantedButtonUP = self.GetChild("UpButton0")
			self.itemWantedButtonText = self.GetChild("ItemSlotbar0Text")
			self.itemWantedButtonDown = self.GetChild("DownButton0")

			self.deliverItemButton = self.GetChild("exam_button")
			self.deliveredItemsText = self.GetChild("textItemDeliverdQty")
			self.notifySlot = self.GetChild("NotifySlot")

			self.elixirSlot = self.GetChild("elisir_10_slot")
			self.elixirPlusSlot = self.GetChild("elisir_30_slot")
			self.timeDeleterSlot = self.GetChild("time_deleter_slot")


		except:
			import exception
			exception.Abort("ResearchWindow.LoadWindow.BindObject")

		self.board.SetCloseEvent(ui.__mem_func__(self.Close))

		self.buttonTimeDeleterUP.SAFE_SetEvent(self.__OnClickButtonTimeDeleterUP)
		self.buttonTimeDeleterDown.SAFE_SetEvent(self.__OnClickButtonTimeDeleterDown)

		self.buttonElixirUP.SAFE_SetEvent(self.__OnClickbuttonElixirUP)
		self.buttonElixirDown.SAFE_SetEvent(self.__OnClickbuttonElixirDown)

		self.buttonElixirPlusUP.SAFE_SetEvent(self.__OnClickbuttonElixirPlusUP)
		self.buttonElixirPlusDown.SAFE_SetEvent(self.__OnClickbuttonElixirPlusDown)

		self.itemWantedButtonUP.SAFE_SetEvent(self.__OnClickButtonItemWantedUP)
		self.itemWantedButtonDown.SAFE_SetEvent(self.__OnClickButtonItemWantedDown)
		self.deliverItemButton.SAFE_SetEvent(self.__OnClickDeliverButton)


		item.SelectItem(self.BIOLOGIST_PCT_ADDER_VNUM)
		itemName = item.GetItemName()				
		self.pctAdderName = itemName
		item.SelectItem(self.BIOLOGIST_PCT_ADDER_PLUS_VNUM)
		itemName = item.GetItemName()		
		self.pctAdderPlusName = itemName
		item.SelectItem(self.BIOLOGIST_TIME_DELETER_VNUM)
		itemName = item.GetItemName()		
		self.timeDeleterName = itemName		

	def SendDeliverPacket(self):
		net.SendBiologistManagerDeliverCommand(self.questID, self.itemToDeliverCount,\
			int(self.buttonElixirTextQty.GetText()), int(self.buttonElixirPlusTextQty.GetText()),\
			int(self.itemTimeDeleterTextqty.GetText()))


	def CanSendRequest(self):
		elixirPlayerCount = player.GetItemCountByVnum(self.BIOLOGIST_PCT_ADDER_VNUM)
		elixirPlusPlayerCount = player.GetItemCountByVnum(self.BIOLOGIST_PCT_ADDER_PLUS_VNUM)
		timeDeleterPlayerCount = player.GetItemCountByVnum(self.BIOLOGIST_TIME_DELETER_VNUM)
		itemToDeliverPlayerCount = player.GetItemCountByVnum(self.itemToDeliverVnum)
		if self.itemToDeliverCount > itemToDeliverPlayerCount or \
		int(self.buttonElixirTextQty.GetText()) > elixirPlayerCount or\
		int(self.buttonElixirPlusTextQty.GetText()) > elixirPlusPlayerCount or\
		int(self.itemTimeDeleterTextqty.GetText()) > timeDeleterPlayerCount:
			self.popupDialog = uiCommon.PopupDialog()
			self.popupDialog.SetText(localeInfo.BIOLOGIST_MANAGER_OVERFLOW_ERROR)
			self.popupDialog.SetAcceptEvent(self.__OnClosePopupDialog)
			self.popupDialog.Open()
			return False
		elif self.GetRemainTime() > 0 and int(self.itemTimeDeleterTextqty.GetText()) <= 0:
			self.popupDialog = uiCommon.PopupDialog()
			self.popupDialog.SetText(localeInfo.BIOLOGIST_MANAGER_TIME_ERROR)
			self.popupDialog.SetAcceptEvent(self.__OnClosePopupDialog)
			self.popupDialog.Open()			
		else:
			return True

	def __OnClickDeliverButton(self):
		if self.pct > 100:
			self.questionDialog = uiCommon.QuestionDialog()
			self.questionDialog.Open()
			self.questionDialog.SetText(localeInfo.BIOLOGIST_MANAGER_CHECK_PCT)
			self.questionDialog.SetAcceptText(localeInfo.UI_ACCEPT)
			self.questionDialog.SetCancelText(localeInfo.UI_DENY)
			self.questionDialog.SetAcceptEvent(lambda arg = True: self.QuestionDialogAnswer(arg))
			self.questionDialog.SetCancelEvent(lambda arg = False: self.QuestionDialogAnswer(arg))
			self.questionDialog.SetTop()
		else:
			if self.CanSendRequest() == True:
				self.SendDeliverPacket()
				

	def __OnClosePopupDialog(self):
		self.popupDialog = None
		
	def QuestionDialogAnswer(self, answer):
		if not self.questionDialog:
			return

		if answer == True:
			if self.CanSendRequest() == True:
				self.SendDeliverPacket()

		self.questionDialog.Close()
		self.questionDialog = None

	def __OnClickButtonItemWantedUP(self):
		if self.itemToDeliverCount >= self.MAX_ITEM_QUANTITY:
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_MANAGER_ITEMS_OVERFLOW % (str(self.MAX_ITEM_QUANTITY), self.itemName))
			return
		if self.itemToDeliverCount + 1 > player.GetItemCountByVnum(self.itemToDeliverVnum):
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_manager_NOT_ENOUGH_ITEMS % (str(self.MAX_ITEM_QUANTITY), self.itemName))
			return				
		self.itemToDeliverCount += 1
		self.itemWantedButtonText.SetText(str(self.itemToDeliverCount))

	def __OnClickButtonItemWantedDown(self):
		if self.itemToDeliverCount <= 1:
			return		
		self.itemToDeliverCount -= 1
		self.itemWantedButtonText.SetText(str(self.itemToDeliverCount))

	def __OnClickButtonTimeDeleterUP(self):
		number = int(self.itemTimeDeleterTextqty.GetText())
		if number >= self.MAX_ITEM_QUANTITY: 
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_MANAGER_ITEMS_OVERFLOW % (str(self.MAX_ITEM_QUANTITY), self.timeDeleterName))
			return
		if number + 1 > player.GetItemCountByVnum(self.BIOLOGIST_TIME_DELETER_VNUM):
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_manager_NOT_ENOUGH_ITEMS % (str(self.MAX_ITEM_QUANTITY), self.timeDeleterName))
			return	
		self.itemTimeDeleterTextqty.SetText(str(number+1))
		self.UpdatePct()

	def __OnClickButtonTimeDeleterDown(self):
		number = int(self.itemTimeDeleterTextqty.GetText())
		if number <= 0:
			return
		self.itemTimeDeleterTextqty.SetText(str(number-1))
		self.UpdatePct()

	def __OnClickbuttonElixirUP(self):		
		number = int(self.buttonElixirTextQty.GetText())
		if number >= self.MAX_ITEM_QUANTITY:
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_MANAGER_ITEMS_OVERFLOW % (str(self.MAX_ITEM_QUANTITY), self.pctAdderName))
			return
		if number + 1 > player.GetItemCountByVnum(self.BIOLOGIST_PCT_ADDER_VNUM):
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_manager_NOT_ENOUGH_ITEMS % (str(self.MAX_ITEM_QUANTITY), self.pctAdderName))
			return				
		self.buttonElixirTextQty.SetText(str(number+1))
		self.UpdatePct()

	def __OnClickbuttonElixirDown(self):
		number = int(self.buttonElixirTextQty.GetText())
		if number <= 0:
			return
		self.buttonElixirTextQty.SetText(str(number-1))
		self.UpdatePct()

	def __OnClickbuttonElixirPlusUP(self):
		number = int(self.buttonElixirPlusTextQty.GetText())
		if number >= self.MAX_ITEM_QUANTITY:
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_MANAGER_ITEMS_OVERFLOW % (str(self.MAX_ITEM_QUANTITY), self.pctAdderPlusName))
			return
		if number + 1 > player.GetItemCountByVnum(self.BIOLOGIST_PCT_ADDER_PLUS_VNUM):
			chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.BIOLOGIST_manager_NOT_ENOUGH_ITEMS % (str(self.MAX_ITEM_QUANTITY), self.pctAdderPlusName))
			return			
		self.buttonElixirPlusTextQty.SetText(str(number+1))
		self.UpdatePct()

	def __OnClickbuttonElixirPlusDown(self):
		number = int(self.buttonElixirPlusTextQty.GetText())
		if number <= 0:
			return
		self.buttonElixirPlusTextQty.SetText(str(number-1))
		self.UpdatePct()
		
	def GetRemainTime(self):
		return self.remainTime - (app.GetTime() - self.receivedTime)

	def ResearchRequestInfo(self, deliversCounter, successDeliveries, failedDeliveries, timeDeleterUses, elixirUses, elixirPlusUses, remainingTime):
		self.remainTime = remainingTime
		self.deliveredItemsQty += successDeliveries
		self.deliveredItemsText.SetText(str(self.deliveredItemsQty) + "/" + str(self.BIOLOGIST_QUEST_DICT[self.questID]['required']))
		self.itemWanted.SetItemSlot(0, self.itemToDeliverVnum, player.GetItemCountByVnum(self.itemToDeliverVnum))
		text = ""
		
		if failedDeliveries == 1:
			text = localeInfo.BIOLOGIST_SYSTEM_MESSAGE_SINGLE_FAIL % (deliversCounter, self.itemName, successDeliveries, failedDeliveries, self.pctAdderName, elixirUses, self.pctAdderPlusName, elixirPlusUses, self.timeDeleterName, timeDeleterUses)
		else:
			text = localeInfo.BIOLOGIST_SYSTEM_MESSAGE_MULTIPLE_FAIL % (deliversCounter, self.itemName, successDeliveries, failedDeliveries, self.pctAdderName, elixirUses, self.pctAdderPlusName, elixirPlusUses, self.timeDeleterName, timeDeleterUses)
		self.multiHolder = ui.GenerateMultiLine(self.notifySlot, text, 300, colorInfo.GOLD)
		for i in self.multiHolder:
			totalH = self.multiHolder[0].GetTextSize()[1] * len(self.multiHolder) + 3 * (len(self.multiHolder) - 1)
			i.SetPosition(3, 80 / 2 - totalH / 2 + 3 * self.multiHolder.index(i) + i.GetTextSize()[1] * self.multiHolder.index(i))
		self.UpdateSlots()	

	def UpdateInfo(self, questID, remain_time, deliveredItems):
		self.remainTime = remain_time
		self.questID = questID
		self.itemToDeliverVnum = self.BIOLOGIST_QUEST_DICT[questID]['item_wanted']
		self.deliveredItemsQty = deliveredItems
		self.deliveredItemsText.SetText(str(self.deliveredItemsQty) + "/" + str(self.BIOLOGIST_QUEST_DICT[self.questID]['required']))
		item.SelectItem(self.itemToDeliverVnum)
		self.itemName = item.GetItemName()
		self.itemRequestedText.SetText(self.itemName)
		self.itemWanted.SetItemSlot(0, self.itemToDeliverVnum, player.GetItemCountByVnum(self.itemToDeliverVnum))
		self.UpdatePct()
		self.UpdateTimer()

	def UpdatePct(self):
		elixir_qty = int(self.buttonElixirTextQty.GetText())
		elixir_plus_qty = int(self.buttonElixirPlusTextQty.GetText())
		if elixir_qty > 0 and elixir_plus_qty > 0:
			self.pct = self.BIOLOGIST_QUEST_DICT[self.questID]['pct'] + self.PCT_BONUS + self.PCT_BONUS_PLUS
		elif elixir_qty > 0:
			self.pct = self.BIOLOGIST_QUEST_DICT[self.questID]['pct'] + self.PCT_BONUS
		elif elixir_plus_qty > 0:
			self.pct = self.BIOLOGIST_QUEST_DICT[self.questID]['pct'] + self.PCT_BONUS_PLUS
		elif elixir_qty <= 0 and elixir_plus_qty <= 0:
			self.pct = self.BIOLOGIST_QUEST_DICT[self.questID]['pct']
		elif elixir_qty <= 0:
			self.pct -= self.PCT_BONUS
		elif elixir_plus_qty <= 0:
			self.pct -= self.PCT_BONUS_PLUS
		self.pctSuccessText.SetText(str(self.pct) + "%")

	def OnUpdate(self):
		if app.GetTime() <= self.lastCountDownRefresh + 1.0:
			return

		self.lastCountDownRefresh = app.GetTime()
		self.UpdateTimer()

	def UpdateTimer(self):
		remain_time = self.GetRemainTime()
		if remain_time <= 0:
			self.remainTimeText.SetPackedFontColor(colorInfo.GREEN)
			self.remainTimeText.SetText(localeInfo.BIOLOGIST_MANAGER_CAN_DELIVER)
		else:
			self.remainTimeText.SetPackedFontColor(colorInfo.RED)
			self.remainTimeText.SetText("%s" % (str(localeInfo.SecondsToClock(remain_time))))

	def SetItemToolTip(self, tooltipItem):
		self.tooltipItem = tooltipItem

	def OverInItem(self, itemVnum):
		if not self.tooltipItem:
			return

		self.tooltipItem.ClearToolTip()
		metinSlot = []
		for i in range(player.METIN_SOCKET_MAX_NUM):
			metinSlot.append(0)
		attrSlot = []
		for i in range(player.ATTRIBUTE_SLOT_MAX_NUM):
			attrSlot.append((0, 0, 0))
		realValue = []
		for i in range(player.REAL_VALUES_MAX_NUM):
			realValue.append(0)
			
		self.tooltipItem.AddItemData(itemVnum, metinSlot, attrSlot, realValue)

	def OverOutItem(self):
		if 0 != self.tooltipItem:
			self.tooltipItem.HideToolTip()

	def UpdateSlots(self):
		self.elixirSlot.SetItemSlot(0, self.BIOLOGIST_PCT_ADDER_VNUM, player.GetItemCountByVnum(self.BIOLOGIST_PCT_ADDER_VNUM))
		self.elixirPlusSlot.SetItemSlot(0, self.BIOLOGIST_PCT_ADDER_PLUS_VNUM, player.GetItemCountByVnum(self.BIOLOGIST_PCT_ADDER_PLUS_VNUM))
		self.timeDeleterSlot.SetItemSlot(0, self.BIOLOGIST_TIME_DELETER_VNUM, player.GetItemCountByVnum(self.BIOLOGIST_TIME_DELETER_VNUM))
		self.itemWanted.SetItemSlot(0, self.itemToDeliverVnum, player.GetItemCountByVnum(self.itemToDeliverVnum))

	def SetSlotsEvents(self):
		self.elixirSlot.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.BIOLOGIST_PCT_ADDER_VNUM)
		self.elixirPlusSlot.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.BIOLOGIST_PCT_ADDER_PLUS_VNUM)
		self.timeDeleterSlot.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.BIOLOGIST_TIME_DELETER_VNUM)

		self.elixirSlot.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)
		self.elixirPlusSlot.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)
		self.timeDeleterSlot.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)

		
		self.itemWanted.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.itemToDeliverVnum)
		self.itemWanted.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)

	def Open(self, questID, remainTime, deliveredItems):
		self.UpdateInfo(questID, remainTime, deliveredItems)
		self.UpdateSlots()
		self.SetSlotsEvents()
		self.Show()
		self.isOpened = True

	def IsOpened(self):
		return self.isOpened

	def Close(self, isOpened = True):
		if self.tooltipItem:
			self.tooltipItem.ClearToolTip()
		self.isOpened = isOpened
		self.Hide()
	
	def Destroy(self):
		self.ClearDictionary()
		if self.tooltipItem:
			self.tooltipItem.ClearToolTip()
		self.remainTime = 0
		self.remainTimeText = None
		self.questID = 0
		self.itemRequestedText = None
		self.pctSuccessText = None
		self.lastCountDownRefresh = 0
		self.itemToDeliverVnum = 0
		self.questionDialog = None
		self.tooltipItem = 0
		self.popupDialog = None
		

	def OnPressEscapeKey(self):
		self.Close()
		return True

Don't just copy and paste it, it won't work because of:

		realValue = []
		for i in range(player.REAL_VALUES_MAX_NUM):
			realValue.append(0)
			
		self.tooltipItem.AddItemData(itemVnum, metinSlot, attrSlot, realValue)

or the whole SetSlotsEvents function:

	def SetSlotsEvents(self):
		self.elixirSlot.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.BIOLOGIST_PCT_ADDER_VNUM)
		self.elixirPlusSlot.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.BIOLOGIST_PCT_ADDER_PLUS_VNUM)
		self.timeDeleterSlot.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.BIOLOGIST_TIME_DELETER_VNUM)

		self.elixirSlot.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)
		self.elixirPlusSlot.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)
		self.timeDeleterSlot.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)

		
		self.itemWanted.SetEvent(ui.SLOT_OVER_IN_ITEM, self.OverInItem, self.itemToDeliverVnum)
		self.itemWanted.SetEvent(ui.SLOT_OVER_OUT_ITEM, self.OverOutItem)

If you make one, please feel free to share, I'll update the topic and give you credits.

 

Why do you require to go back to the biologist instead of doing everything from the interface?

Two reasons:

  • it's based on my server where we want the player to go back to the biologist.
  • there's already people selling that type of interface, if you so prefer

 

 

The system requires c++20 (serverside)

But I have C++17, what do I do then?

Remove the constness from std::array(older version) and std::view_string, therefore even on the loop(older version):

	for(const auto& val : BiologistQuestNames) //here remove const
	{
		if(IsQuestStatusFindItem(ch, val) == true){
			SendBiologistManagerOpenCommand(ch, iterVal);
			return true;
		}
		++iterVal;
	}

But I have C++14/C+11, what do I do then?

Remove std::view_string and use either std::string or const char* and replace:

utils::view_to_string(BiologistQuestStateCollect);

with whatever is the way to convert a const char* to std::string (if you use const char*) https://stackoverflow.com/questions/24127946/converting-a-const-char-to-stdstring

Remove also the namespace on top:

//TO REMOVE
namespace utils{
	std::string string_view_concat(std::string_view a, std::string_view b){

		std::string rv(a.size() + b.size(), '\0');

		std::copy(b.begin(), b.end(),
				std::copy(a.begin(), a.end(), rv.begin()));

		return rv;
	}

	std::string view_to_string(std::string_view a){
		std::string st = {a.begin(), a.end()};
		return st;
	}
}

the concat function can be replaced with the + operator (both have to be strings, not const* char):

std::string new_string = old_string + string_to_concat;

I still use C++98/03

Don't bother downloading these files.

I want to set the cooldown even when some one fails, not only on success, how can I do that?

Replace:

			else{ //set cooldown for success delivery
				pPC->SetFlag(timerFlag, get_global_time() + BiologistQuestCooldowns.at(questID));
				++successDeliveries;
			}
		}
		else{ //fail, but we don't set the cooldown for failing
			itemToDeliver->SetCount(itemToDeliver->GetCount() - 1); //remove the item to deliver
			pPC->SetFlag(timerFlag, 0);
			++failedDeliveries;
		}

with:

			else{ //set cooldown for success delivery
				pPC->SetFlag(timerFlag, get_global_time() + BiologistQuestCooldowns.at(questID));
				++successDeliveries;
			}
		}
		else{ //fail, but we don't set the cooldown for failing
			itemToDeliver->SetCount(itemToDeliver->GetCount() - 1); //remove the item to deliver
			pPC->SetFlag(timerFlag, get_global_time() + BiologistQuestCooldowns.at(questID));
			++failedDeliveries;
		}

 

What if I don't wanna use elixirs, time deleter or something else?

Try yourself first, if you encounter any error during your endeavour, just ask (don't be afraid to ask, people might help you).

Why the double checks in the client AND the server?????

I'll never forget: "what if they craft the packet?" "...fudge. Unlikely, but you are right."

Is it bug free???

I'll never make such claims, if you find some, please report them

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

  • Management
Link to comment
Share on other sites

  • 3 months later...
  • Premium
On 12/4/2022 at 5:49 AM, garofolo said:

basically every system is based on the python part and he posted incomplete


uiscript part?
 

as I said:

Quote

I am sorry but I don't have any way to set up an interface for the original sources of the Client. I can share the logic I used:

uiscript I thought it could be made from the video, and something has to be edited from mine (same reasons, different sources, different stuff):

import uiScriptLocale
import colorInfo

TASKBAR = "d:/ymir work/ui/taskbar/"

window = {
	"name" : "ResearchWindow",

	"x" : 100,
	"y" : 20,

	"style" : ("movable", "float","limit",),

	"width" : 330,
	"height" : 330,

	"children" :
	(
		{
			"name" : "board",
			"type" : "board_with_titlebar",

			"x" : 0,
			"y" : 0,

			"width" : 330,
			"height" : 330,

			"title": uiScriptLocale.RESEARCH_TITLE,

			"children" :
			(
				{
					"name" : "thinboardAllContainer",
					"type" : "thinboard",

					"x" : 7,
					"y" : 27,
					
					"width" : 316,
					"height" : 296,
				
					
					"style" : ["not_pick"],
					
					"children" :
					(
						{
							"name" : "thinboardDeliverContainer",
							"type" : "thinboard",

							"x" : 5,
							"y" : 5,
							
							"width" : 306,
							"height" : 116,
						
							
							"style" : ["not_pick"],
							"children" :
							(
								{
									"name" : "current_item",
									"type" : "grid_table",

									"x" : 31,
									"y" : 34,

									"start_index" : 0,
									"x_count" : 1,
									"y_count" : 1,
									"x_step" : 32,
									"y_step" : 32,

									"image" : "d:/ymir work/ui/public/Slot_Base.sub",
								},
								{
									"name" : "UpButton0",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_UP,
									"x" : 31-4,
									"y" : 34+32+2,
									"default_image" : TASKBAR + "pageup.sub",
									"over_image" : TASKBAR + "pageup_over.sub",
									"down_image" : TASKBAR + "pageup_click.sub",
								},
								{
									"name" : "DownButton0",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_DOWN,

									"x" : 31-4,
									"y" : 34+32+12,

									"default_image" : TASKBAR + "pagedown.sub",
									"over_image" : TASKBAR + "pagedown_over.sub",
									"down_image" : TASKBAR + "pagedown_click.sub",
								},
								{ 
									"name":"ItemSlotbar0", 
									"type":"slotbar", 
									"x":40, 
									"y":34+36, 
									"preset":"parameter_slot_09", 
									"children" :
									(
										{ "name":"ItemSlotbar0Text", "type":"text", "x":1, "y":2, "text":"1", "r":1.0, "g":1.0, "b":1.0, "a":1.0, "text_horizontal_align":"center", "horizontal_align":"center"},
									),
								},
								{
									"name" : "textItemName",
									"type" : "text",

									"x" : 111 - 27,
									"y" : 18,
									
									
									"color" : colorInfo.FONT_COLOR, 
									"fontsize":"middle",
									
									"text" : uiScriptLocale.RESEARCH_OBJECT,
									
									"style" : ["not_pick"],
								},
								{
									"name" : "ItemNametext",
									"type" : "text",

									"x" : 111 - 27 + 84,
									"y" : 18,
									
									
									"color" : colorInfo.GOLD_COLOR, 
									"fontsize":"middle",
									
									"text" : uiScriptLocale.RESEARCH_OBJECT,
									
									"style" : ["not_pick"],
								},
								{
									"name" : "textItemDeliverd",
									"type" : "text",

									"x" : 111 - 27,
									"y" : 18 + 16,
									
									
									"color" : colorInfo.FONT_COLOR, 
									"fontsize":"middle",
									
									"text" : uiScriptLocale.RESEARCH_DELIVERED,
									
									"style" : ["not_pick"],
								},
								{
									"name" : "textItemDeliverdQty",
									"type" : "text",

									"x" : 111 - 27 + 55,
									"y" : 18 + 16,
									
									
									"color" : colorInfo.GOLD_COLOR, 
									"fontsize":"middle",
									
									"text" : "0/0",
									
									"style" : ["not_pick"],
								},								
								{
									"name" : "textItemPct",
									"type" : "text",

									"x" : 111 - 27,
									"y" : 18 + 16 * 2,
									
									
									"color" : colorInfo.FONT_COLOR, 
									"fontsize":"middle",
									
									"text" : uiScriptLocale.RESEARCH_PCT,
									
									"style" : ["not_pick"],
								},
								{
									"name" : "ItemPctText",
									"type" : "text",

									"x" : 111 - 27 + 104,
									"y" : 18 + 16 * 2,
									
									
									"color" : colorInfo.GOLD_COLOR, 
									"fontsize":"middle",
									
									"text" : "",
									
									"style" : ["not_pick"],
								},								
								{
									"name" : "textItemCD",
									"type" : "text",

									"x" : 111 - 27,
									"y" : 18 + 16 * 3,
									
									
									"color" : colorInfo.FONT_COLOR, 
									"fontsize":"middle",
									
									"text" : uiScriptLocale.RESEARCH_TIMER,
									
									"style" : ["not_pick"],
								},
								{
									"name" : "ItemCDText",
									"type" : "text",

									"x" : 111 - 27 + 77,
									"y" : 18 + 16 * 3,
									
									
									"color" : colorInfo.RED, 
									"fontsize":"middle",
									
									"text" : "",
									
									"style" : ["not_pick"],
								},								
								{
									"name" : "exam_button",
									"type" : "dynamic_button",

									"x" : 0,
									"y" : 86,

									"text" : uiScriptLocale.RESEARCH_BUTTON,
									"horizontal_align" : "center",

									"preset" : "not_so_largerer_button",
								},
								{
									"name" : "CD_Image",
									"type" : "image",

									"x" : 246,
									"y" : 46+13,

									"image" : "d:/ymir work/ui/game/mailbox/mailbox_icon_cd.sub",
								},
								{
									"name" : "CD_checkbox",
									"type" : "checkbox_new",


									"x" : 272,
									"y" : 51+14,
								},
							),
						},
						{
							"name" : "thinboardIconContainer",
							"type" : "thinboard",

							"x" : 51-15,
							"y" : 136,
							
							"width" : 214+30,
							"height" : 40,
						
							
							"style" : ["not_pick"],
							
							"children" :
							(
								{
									"name" : "time_deleter_slot",
									"type" : "grid_table",

									"x" : 46,
									"y" : 3,

									"start_index" : 0,
									"x_count" : 1,
									"y_count" : 1,
									"x_step" : 32,
									"y_step" : 32,

									#"image" : "d:/ymir work/ui/public/Slot_Base.sub",
								},
								#{
								#	"name" : "cd_reset_icon",
								#	"type" : "image",
								#	
								#	"x" : 46,
								#	"y" : 3,
								#
								#	"image" : "d:/icon/item/102517.tga",
								#},
								{
									"name" : "UpButton1",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_UP,
									"x" : 9,
									"y" : 9,
									"default_image" : TASKBAR + "pageup.sub",
									"over_image" : TASKBAR + "pageup_over.sub",
									"down_image" : TASKBAR + "pageup_click.sub",
								},
								{
									"name" : "DownButton1",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_DOWN,

									"x" : 9,
									"y" : 19,

									"default_image" : TASKBAR + "pagedown.sub",
									"over_image" : TASKBAR + "pagedown_over.sub",
									"down_image" : TASKBAR + "pagedown_click.sub",
								},
								{ 
									"name":"ItemSlotbar1", 
									"type":"slotbar", 
									"x":22, 
									"y":11, 
									"preset":"parameter_slot_09", 
									"children" :
									(
										{ "name":"ItemSlotbar1Text", "type":"text", "x":1, "y":2, "text":"0", "r":1.0, "g":1.0, "b":1.0, "a":1.0, "text_horizontal_align":"center", "horizontal_align":"center"},
									),
								},
								{
									"name" : "elisir_10_slot",
									"type" : "grid_table",

									"x" : 126,
									"y" : 3,

									"start_index" : 0,
									"x_count" : 1,
									"y_count" : 1,
									"x_step" : 32,
									"y_step" : 32,

									#"image" : "d:/ymir work/ui/public/Slot_Base.sub",
								},
								#{
								#	"name" : "elisir_10_icon",
								#	"type" : "image",
									
								#	"x" : 126,
								#	"y" : 3,

								#	"image" : "d:/icon/item/102529.tga",
								#},
								{
									"name" : "UpButton2",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_UP,
									"x" : 92,
									"y" : 9,
									"default_image" : TASKBAR + "pageup.sub",
									"over_image" : TASKBAR + "pageup_over.sub",
									"down_image" : TASKBAR + "pageup_click.sub",
								},
								{
									"name" : "DownButton2",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_DOWN,

									"x" : 92,
									"y" : 19,

									"default_image" : TASKBAR + "pagedown.sub",
									"over_image" : TASKBAR + "pagedown_over.sub",
									"down_image" : TASKBAR + "pagedown_click.sub",
								},
								{ 
									"name":"ItemSlotbar2", 
									"type":"slotbar", 
									"x":104, 
									"y":11, 
									"preset":"parameter_slot_09", 
									"children" :
									(
										{ "name":"ItemSlotbar2Text", "type":"text", "x":1, "y":2, "text":"0", "r":1.0, "g":1.0, "b":1.0, "a":1.0, "text_horizontal_align":"center", "horizontal_align":"center"},
									),
								},
								{
									"name" : "elisir_30_slot",
									"type" : "grid_table",

									"x" : 206,
									"y" : 3,

									"start_index" : 0,
									"x_count" : 1,
									"y_count" : 1,
									"x_step" : 32,
									"y_step" : 32,

									#"image" : "d:/ymir work/ui/public/Slot_Base.sub",
								},								
								#{
								#	"name" : "elisir_30_icon",
								#	"type" : "image",
								#	
								#	"x" : 206,
								#	"y" : 3,

								#	"image" : "d:/icon/item/102616.tga",
								#},
								{
									"name" : "UpButton3",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_UP,
									"x" : 172,
									"y" : 9,
									"default_image" : TASKBAR + "pageup.sub",
									"over_image" : TASKBAR + "pageup_over.sub",
									"down_image" : TASKBAR + "pageup_click.sub",
								},
								{
									"name" : "DownButton3",
									"type" : "button",
									"tooltip_text" : uiScriptLocale.RESEARCH_DOWN,

									"x" : 172,
									"y" : 19,

									"default_image" : TASKBAR + "pagedown.sub",
									"over_image" : TASKBAR + "pagedown_over.sub",
									"down_image" : TASKBAR + "pagedown_click.sub",
								},
								{ 
									"name":"ItemSlotbar3", 
									"type":"slotbar", 
									"x":184, 
									"y":11, 
									"preset":"parameter_slot_09", 
									"children" :
									(
										{ "name":"ItemSlotbar3Text", "type":"text", "x":1, "y":2, "text":"0", "r":1.0, "g":1.0, "b":1.0, "a":1.0, "text_horizontal_align":"center", "horizontal_align":"center"},
									),
								},
							),
						},
						{
							"name" : "MessagesName",
							"type" : "text",
							"x" : 115,
							"y" : 169+16,
							"text" : uiScriptLocale.SYSTEM_MESSAGES,
							"color" : colorInfo.GOLD, 
							"fontsize":"middle",
							"children" : 
							(

								{
									"name" : "NotifySlot", "type" : "slotbar", "x" : -110, "y" : 18 * 1, "width" : 305, "height" : 16*5,
									"children" :
									(
										{ "name" : "NotifyValue1", "type" : "text", "x" : 0, "y" : 0, "text" : " ", "horizontal_align" : "center", "text_horizontal_align" : "center", },
									),
								},
							),
						},
					),
				},
			),
		},
	),
}

 

  • Metin2 Dev 2
Link to comment
Share on other sites

  • 3 months later...
  • Premium

Updated with check flagitems count in IsQuestStatusFindItem (download file already updated):

---
 Server/game/src/biologist_manager.cpp          | 17 ++++++++++++++---
 Server/game/src/biologist_manager.h            |  2 +-
 Server/game/src/player_achievement_manager.cpp |  8 ++++++--
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/Server/game/src/biologist_manager.cpp b/Server/game/src/biologist_manager.cpp
index 23ed25be..02b17bde 100644
--- a/Server/game/src/biologist_manager.cpp
+++ b/Server/game/src/biologist_manager.cpp
@@ -22,7 +22,7 @@ bool CBiologistManager::IsValidQuestID(LPCHARACTER ch, const uint8_t questID)
 	return true;
 }
 
-bool CBiologistManager::IsQuestStatusFindItem(LPCHARACTER ch, std::string_view quest_name)
+bool CBiologistManager::IsQuestStatusFindItem(LPCHARACTER ch, std::string_view quest_name, uint8_t questID)
 {
 	auto pPC = quest::CQuestManager::instance().GetPCForce(ch->GetPlayerID());
 	std::string questName = utils::view_to_string(quest_name);
@@ -30,8 +30,9 @@ bool CBiologistManager::IsQuestStatusFindItem(LPCHARACTER ch, std::string_view q
 	auto findItemValue = quest::CQuestManager::Instance().GetQuestStateIndex(questName, questState);
 
 	std::string questFlag = utils::string_view_concat(quest_name, ".__status");
+	std::string questFlagItem = utils::string_view_concat(quest_name, utils::view_to_string(BiologistQuestFlagItemsDeliveredCount));
 	int questStateValue = pPC->GetFlag(questFlag);
-
+	const uint32_t questFlagItemCount = pPC->GetFlag(questFlagItem);
 	//if the quest has not started yet, the status can be 0
 	//therefore if 0, the interface cannot be opened
 	if(questStateValue == 0)
@@ -41,6 +42,10 @@ bool CBiologistManager::IsQuestStatusFindItem(LPCHARACTER ch, std::string_view q
 	if(questStateValue != findItemValue)
 		return false;
 	//otherwise it's in a "finditem" state,
+
+	if(questFlagItemCount >= BiologistQuestItemLimits[questID])
+		return false;
+
 	return true;
 }
 
@@ -64,7 +69,7 @@ bool CBiologistManager::CheckOpenInterface(LPCHARACTER ch)
 	uint8_t iterVal = 0;
 	for(const auto& val : BiologistQuestNames)
 	{
-		if(IsQuestStatusFindItem(ch, val) == true){
+		if(IsQuestStatusFindItem(ch, val, iterVal) == true){
 			SendBiologistManagerOpenCommand(ch, iterVal);
 			return true;
 		}
@@ -238,6 +243,12 @@ void CBiologistManager::ExecuteDeliveryRequest(LPCHARACTER ch, uint8_t questID,
 
 void CBiologistManager::ResearchPacket(LPCHARACTER ch, const uint8_t questID, uint8_t subheader, uint8_t deliversCounter, uint8_t successDeliveries, uint8_t failedDeliveries, uint8_t  timeDeleterUses, uint8_t elixirUses, uint8_t elixirPlusUses)
 {
+	if(ch == nullptr)
+		return;
+
+	if(ch->GetDesc() == nullptr)
+		return;
+
 	time_t remainingTime = GetRemainingTime(ch, questID);
 
 	TPacketGCBiologistManagerSendResearch p;
diff --git a/Server/game/src/biologist_manager.h b/Server/game/src/biologist_manager.h
index 2174854f..71b32342 100644
--- a/Server/game/src/biologist_manager.h
+++ b/Server/game/src/biologist_manager.h
@@ -61,7 +61,7 @@ class CBiologistManager : public singleton<CBiologistManager>
 		CBiologistManager() = default;
 		virtual ~CBiologistManager() = default;
 
-		bool	IsQuestStatusFindItem(LPCHARACTER ch, std::string_view quest_name);
+		bool	IsQuestStatusFindItem(LPCHARACTER ch, std::string_view quest_name, uint8_t questID);
 		void	SetQuestStateToBack(DWORD playerID, const uint8_t questID);
 		void	SendBiologistManagerOpenCommand(LPCHARACTER ch, const uint8_t questID);
 		bool	CheckOpenInterface(LPCHARACTER ch);
--

 

Edited by xXIntelXx
  • Good 1
  • Love 2
Link to comment
Share on other sites

  • 2 weeks later...
  • Premium

Updated to set all the values from settings.lua (or w/e file .lua you might load at startup besides settings.lua with similar characteristics)

EX:

--[[
structure:
[QUEST_NAME,QUEST_ITEM,QUEST_CD,QUEST_PCT,QUEST_ITEM_TO_SEND]
]]--
add_biologist_info("ue_collect_item_lv30",30006,0,90,20)
add_biologist_info("ue_collect_item_lv40",30055,0,90,20)
add_biologist_info("ue_collect_item_lv50",30047,0,90,20)
add_biologist_info("ue_collect_item_lv60",30324,0,90,25)
add_biologist_info("ue_collect_item_lv70",30015,0,90,20)
add_biologist_info("ue_collect_item_lv75",30050,3600,90,25)
add_biologist_info("ue_collect_item_lv80",30198,7200,44,30)
add_biologist_info("ue_collect_item_lv90",30166,10800,42,35)
add_biologist_info("ue_collect_item_lv95",30495,10800,30,15)
add_biologist_info("ue_collect_item_lv100",100029,14400,38,40)
add_biologist_info("ue_collect_item_lv105",30329,14400,36,45)
add_biologist_info("ue_collect_item_lv110",100039,18000,34,50)
--[[
structure:
[PCT_ADDER_VNUM,PCT_ADDER_PLUS_VNUM,TIME_DELETER_VNUM,ITEM_MAX_TO_SEND,PCT_ADDER,PCT_ADDER_PLUS]
]]--
add_biologist_globals(102529,102616,102517,5,10,20)

Also found a little mistake:

Qfm6Lub.png

Git patch would be, well..

diff --git a/Locale/italy/settings.lua b/Locale/italy/settings.lua
index ec787cd9..0eaa5e94 100644
--- a/Locale/italy/settings.lua
+++ b/Locale/italy/settings.lua
@@ -322,3 +322,29 @@ add_dungeon_party_size(4, 8)
 add_dungeon_party_size(5, 8)
 add_dungeon_party_size(11, 4)
 add_dungeon_party_size(12, 4)
+
+
+
...........................
@@ -2415,7 +2539,9 @@ namespace quest
 			{	"vid_is_near_to",				_vid_is_near_to	   			},
 			{	"damage_to_vid",				_damage_to_vid	   			},
 			{	"damage_to_vid_pct",			_damage_to_vid_pct	   		},
-			
+
+			{	"add_biologist_info",			_add_biologist_info	   		},
+			{	"add_biologist_globals",		_add_biologist_globals	   	},
 
 			{	NULL,	NULL	}

probably incompatible? 😅

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

  • 4 weeks later...
  • Premium

Updated with biologist pulse (avoiding spamming of requests) and replaced GetPCForce with GetPC (with related checks)

GetPCForce can lead to this crash:

NnnvPle.png

I am not sure if it's because of our questmanager refactor or not, but it's probably 99.99% of the times better this way.

Edited by Intel
Core X - External 2 Internal
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.