Jump to content

Braxy

Member
  • Posts

    182
  • Joined

  • Last visited

  • Days Won

    7
  • Feedback

    0%

Posts posted by Braxy

  1. 9 hours ago, Thrall said:

    For who uses gold directly in chests, i added this:

      Hide contents
    			case CSpecialItemGroup::GOLD:
    			{
    				// Prevents yang overfloww
    				if (llItemCount + GetGold() >= GOLD_MAX)
    				{
    					ChatPacket(CHAT_TYPE_TALKING, LC_TEXT("»ç¿ë Á¦ÇÑ·®À» ÃÊ°úÇÏ¿´½À´Ï´Ù."));
    					if (item)
    						item->Lock(false);
    					return false;
    				}

     

     

    PointChange already checks for the maximum gold limit. What you did will eventually stop the loop and you will lose the other chest items. (you can get a crash as well by calling the deleted item) If you want to atleast give the gold they can receive upon their gold limit you can do it like this:

    			case CSpecialItemGroup::GOLD:
    			{
    				if (llItemCount + GetGold() > GOLD_MAX)
    					llItemCount = MIN(llItemCount, GOLD_MAX - GetGold());
    				
    				PointChange(POINT_GOLD, llItemCount);
    				ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You have received %d Yang."), llItemCount);
    			}

     

    • Metin2 Dev 1
  2. (This topic addresses to the most common crashes that occur using server_timers in dungeons)


    In this short tutorial i'm gonna explain why are the crashes happening and how we can deal with them along with a use case of this.

     The difference between the Normal Timers and Server Timers

    Normal Timers:

    - Directly tied to a character pointer.

    - Timer execution halts if the associated character logs out.

    - Implicitly dependent on the character's in-game presence.

    Server Timers:

    - Operate independently of character pointers.

    - Continue to execute even if the initiating character exits the game.

    - Offers persistent timing functionalities.


    You might ponder on the need for server timers, especially when normal timers are present. Here's why:
    1. Party Dynamics: In a multiplayer setting, if a party member possessing the timer logs out, gameplay can be disrupted, leading to a halted dungeon instance. Server timers mitigate this risk.
    2. Dungeon Persistence: Players often exit and re-enter dungeons. With a normal timer, exiting erases the timer, jeopardizing the dungeon's progression. Server timers ensure continuity
    .

     Why do crashes occur?

    Server timers, by virtue of their independence, lack character pointers upon invocation. This absence becomes problematic when:

    Distributing or dropping items.

    Executing specific chats or commands.

    Running functions reliant on character pointers.

    The game struggles to reference a non-existent character, which can either lead to functional anomalies or outright crashes.


     How can we solve the problem?
    My way of solving this issue is simple.
    I've created a global function that when called is selecting the character pointer of the specified PID and returns a boolean of whether it selected it or not.

    // questlua_global.cpp
    	int _select_pid(lua_State* L)
    	{
    		DWORD dwPID = static_cast<DWORD>(lua_tonumber(L, 1));
    		quest::PC* pPC = CQuestManager::instance().GetPC(dwPID);
    		if(pPC)
    		{
    			LPCHARACTER lpSelectedChar = CQuestManager::instance().GetCurrentCharacterPtr();
    			lua_pushboolean(L, (lpSelectedChar ? 1 : 0));
    			return 1;
    		}
    		
    		lua_pushboolean(L, false);
    		return 1;
    	}
    
    	{	"select_pid", 					_select_pid						},
     
     
    Now we set the leaderPID as a dungeon flag upon entering the instance for the first time. (if it's not a group, it will set the pid of the player that entered)
     
    when login with <PC_IN_DUNGEON> begin
    	if (((party.is_party() and party.is_leader()) or not party.is_party()) and d.getf("leaderPID") < 1) then
    		d.setf("leaderPID", pc.get_player_id())
        end
    end

    Now when we call the server_timer, we first select the leader and check if we succeeded or not.
     
    when give_item.server_timer begin
    	if (d.select(get_server_timer_arg())) then
    		if (select_pid(d.getf("leaderPID"))) then
    			pc.give_item2(19, 1)
    		else
    			-- handle what happends if the selection was unsuccessful
    		end
    	end
    end

    That's basically it. You can now call any function in server_timer.

     How can this get very useful?

    Let's say we want to create an update timer that constantly updates different information on players client.
    I've made a function that is pushing each dungeon's member PID. (only if it's in dungeon)

    	#include <functional>
    
    	struct FDungeonPIDCollector
    	{
    		std::vector<DWORD> vecPIDs;
    		void operator () (LPCHARACTER ch)
    		{
    			if (ch)
    				vecPIDs.push_back(ch->GetPlayerID());
    		}
    	};
    	
    	int dungeon_get_member_pids(lua_State* L)
    	{
    		LPDUNGEON pDungeon = CQuestManager::instance().GetCurrentDungeon();
    		if (!pDungeon)
    			return 0;
    		
    		FDungeonPIDCollector collector;
    		pDungeon->ForEachMember(std::ref(collector));
    		
    		for (const auto& pid : collector.vecPIDs)
    		{
    			lua_pushnumber(L, pid);
    		}
    		
    		return collector.vecPIDs.size();
    	}
    
    	{ "get_member_pids",			dungeon_get_member_pids		},

    Using this, we can just update each dungeon's member informations:

    when dungeon_update_info.server_timer begin
    	if (d.select(get_server_timer_arg())) then
    		local dungeonMemberIds = {d.get_member_pids()};
    		for index, value in ipairs(dungeonMemberIds) do
    			if (select_pid(value)) then
    				cmdchat(string.format("UpdateDungeonInformation %d %d", 1, 2))
          				-- pc.update_dungeon_info()
    			else
    				-- handle the negative outcome
    			end
    		end
    	end
    end

     

     
    • Metin2 Dev 1
    • Good 5
    • Love 1
  3. 8 hours ago, SlayerPro said:

     

     

     

     

    i like the coding and i install it but when i login ingame i see nothing . only blacksmith has .chat 

    https://metin2.download/picture/ZfdmIbf33NPqs5WfNFlrYi6i47kGW5Vy/.jpg

     

    i just changed the reward and required mob

    here is  a sample of my quest 

     

    quest lvl_110 begin
    	state start begin
    		function returnData()
    			local data = {
    				["missionData"] = {["missionName"] = "Player Mission I", ["missionFlag"] = "playerMissionKill"},
    				
    				["timeToComplete"] = {true, time_min_to_sec(60)},
    				["requireData"] = {{6856, 5}, {6895, 5}},
    				
    				["playerRewards"] = {["itemVnum"] = {83006, 30962, 30334}, ["itemCount"] = {1, 1, 1}}
    			};
    			
    			return data;
    		end
    		
    		function initializeData()
    			local data = playerMission.returnData();
    			
    			for index in data["requireData"] do
    				local strFlag = string.format("%s_%d", data["missionData"]["missionFlag"], data["requireData"][index][1]);
    				pc.setqf(strFlag, data["requireData"][index][2]);
    			end
    			
    			if (data["timeToComplete"][1]) then
    				return pc.setqf("playerMissionTime", get_time() + data["timeToComplete"][2]);
    			end return true;
    		end
    		
    		function initializeInfo()
    			local data = playerMission.returnData();
    			for index in data["requireData"] do
    				local strFlag = string.format("%s_%d", data["missionData"]["missionFlag"], data["requireData"][index][1]);
    				q.set_counter(string.format("Remaining %s:", mob_name(data["requireData"][index][2])), pc.getqf(strFlag));
    			end
    			
    			if (data["timeToComplete"][1]) then
    				if (pc.getqf("playerMissionTime") < get_time()) then
    					return false;
    				end
    				q.set_clock("Remaining Time:", pc.getqf("playerMissionTime") - get_time());
    			end
    			
    			return true;
    		end
    		
    		function isKillableMonster()
    			local data = playerMission.returnData();
    			local npcRace = npc.get_race();
    			
    			for index in data["requireData"] do
    				if (data["requireData"][index][1] == npcRace) then
    					return true;
    				end
    			end return false;
    		end
    		
    		when login or enter or levelup begin
    			local data = playerMission.returnData();
    			send_letter(data["missionData"]["missionName"]);
    		end
    		
    		when button or info begin
    			local data = playerMission.returnData();
    			
    			say_title(string.format("%s:[ENTER]", data["missionData"]["missionName"]))
    			say("Talk to Blacksmith if you want to accept the mission.")
    		end
    		
    		when 20016.chat."The Player Mission" begin
    			say_title(string.format("%s:[ENTER]", mob_name(npc.get_race())))
    			say("Do you wish to accept the mission?")
    			if (select("Yes, i do", "No, i don't") == 1) then
    				playerMission.initializeData();
    				set_state("run");
    				say("Your mission has been updated.")
    			end
    		end
    	end
    	
    	state run begin
    		when login or enter begin
    			local data = playerMission.returnData();
    			local initializeMissionInfo = playerMission.initializeInfo();
    			
    			if (not initializeMissionInfo) then
    				syschat(string.format("You failed mission: %s.", data["missionData"]["missionName"]))
    				set_state("done");
    				return;
    			end
    			
    			send_letter(data["missionData"]["missionName"]);
    		end
    		
    		when button or info begin
    			local data = playerMission.returnData();
    			
    			say_title(data["missionData"]["missionName"])
    			if (pc.getqf("playerMissionTime") < get_time()) then
    				syschat(string.format("You failed mission: %s.", data["missionData"]["missionName"]))
    				set_state("done");
    				return;
    			end
    			
    			say("You still must kill:[ENTER]")
    			for index in data["requireData"] do
    				local strFlag = string.format("%s_%d", data["missionData"]["missionFlag"], data["requireData"][index][1]);
    				if (pc.getqf(strFlag) > 0) then
    					say_reward(string.format("- %s - x%d", mob_name(data["requireData"][index][1]), pc.getqf(strFlag)))
    				end
    			end
    		end
    		
    		when kill with playerMission.isKillableMonster() begin
    			local data = playerMission.returnData();
    			local npcRace = npc.get_race();
    			local isMissionOver = true;
    			
    			if (pc.getqf("playerMissionTime") < get_time()) then
    				syschat(string.format("You failed mission: %s.", data["missionData"]["missionName"]))
    				set_state("done");
    				return;
    			end
    			
    			local npcFlag = string.format("%s_%d", data["missionData"]["missionFlag"], npcRace);
    			if (pc.getqf(npcFlag) < 1) then
    				return;
    			end
    			
    			pc.setqf(npcFlag, pc.getqf(npcFlag) - 1);
    			for index in data["requireData"] do
    				local strFlag = string.format("%s_%d", data["missionData"]["missionFlag"], data["requireData"][index][1]);
    				
    				if (pc.getqf(strFlag) > 0) then
    					isMissionOver = false;
    				end
    			end
    			
    			if (isMissionOver) then
    				set_state("reward");
    			end
    		end
    	end
    	
    	state reward begin
    		when login or enter begin
    			send_letter("*Player Mission Reward");
    		end
    		
    		when button or info begin
    			local data = playerMission.returnData();
    			
    			say_title("*Player Mission Reward")
    			say("Go back to Blacksmith to take your reward.")
    		end
    		
    		when 20016.chat."Your reward!" begin
    			local data = playerMission.returnData();
    			
    			say_title(string.format("%s:[ENTER]", mob_name(npc.get_race())))
    			say("You have succesfully completed the mission.[ENTER]Your reward is:")
    			
    			for index in data["playerRewards"]["itemVnum"] do
    				pc.give_item2(data["playerRewards"]["itemVnum"][index], data["playerRewards"]["itemCount"][index]);
    				say_reward(string.format("- %s - %d", item_name(data["playerRewards"]["itemVnum"][index]), data["playerRewards"]["itemCount"][index]))
    			end
    			
    			set_state("done");
    		end
    	end
    	
    	state done begin
    		when login or enter begin
    			clear_letter();
    			q.done();
    		end
    	end
    end

     

    Hi, just add me over discord and i'll happly help you ^^

  4. 13 hours ago, Syreldar said:

    What are you even talking about, drop%s are not adding up to 100. 100% is 400.

     

    Group	Metin_of_Combat
    {
    	Mob	8002
    	Type	drop
    	1	5000	1	20 --Copper Bell+0 -- 5%
    	2	11210	1	20 --Iron Plate Armour+0 -- 5%
    	3	11410	1	20 --Ivory Suit+0 -- 5%
    	4	11610	1	20 --Storm Plate Armour+0 -- 5%
    	5	11810	1	20 --Turquoise Clothing+0 -- 5%
    	6	27002	50	100 --Red Potion (M) -- 25%
    	7	27003	30	50 --Red Potion (L) -- 12.5%
    	8	27005	30	50 --Blue Potion (M) -- 12.5%
    	9	27006	20	50 --Blue Potion (L) -- 12.5%
    	10	50300	1	400 --Skill Book --100%
    }

    Ignore the comments, they're not fully accurate, but this is a perfectly valid drop from a metinstone.

     

    matching monster level (best chances [200]) a bit off monster level (184)
    iDeltaPercent 200, iRandRange 2000000      iDeltaPercent 184, iRandRange 2000000
    iPercent 20000000, iRandRange 2000000      iPercent 18400000, iRandRange 2000000 -- 1000 (mob_drop_chance)
    iPercent 20000000, iRandRange 2000000      iPercent 18400000, iRandRange 2000000 -- 1000 (mob_drop_chance)
    iPercent 20000000, iRandRange 2000000      iPercent 18400000, iRandRange 2000000 -- 1000 (mob_drop_chance)
    iPercent 400000, iRandRange 2000000        iPercent 368000, iRandRange 2000000 -- 20 (mob_drop_chance)
    iPercent 400000, iRandRange 2000000        iPercent 368000, iRandRange 2000000 -- 20 (mob_drop_chance)
    iPercent 140000, iRandRange 2000000        iPercent 128800, iRandRange 2000000 -- 7 (mob_drop_chance)

    I'm pretty sure 100% means "1000" in mob_drop_item not 400 ^^

    • Not Good 1
  5. 59 minutes ago, mustlight said:

    I dont get it, why do you even ask if you think releasing it. People obviously like free product and i am pretty sure that no one will say do not if they are not trolling. Such a stupid pool.

    Yea im not sure what's up with y'all but i closed the poll and you can take a look there, maybe as you said they all troll 🙂

    • Cry 1
  6. I found this exploit a long time ago, but i never searched why is this happening. My take on it, was to use the pc.remove_item and check if the item is still there.
    Basically i "avoided" using item.remove and returned if item was not in the inventory. (trash method now but whatever)

    Anyway, the way you did it is the right way to solve it, thanks marty🙂

    • Metin2 Dev 1
  7. 2 hours ago, dotMatryx said:

    I get this error on the stage of compiling the quests.This function works pretty well on other quests with a differend syntax.

    Here is a line that works pretty well:

      Reveal hidden contents

                    say_title(gameforge[pc.get_language()].arena_manager._20_sayTitle)
     

    I think it's a qc bug or something like this.

    Hi, it's a QC syntax issue, it wasn't made to work like this. Try this one instead it should be ok: https://we.tl/t-3RLL7vcrbF

    • Smile Tear 1
  8. 19 hours ago, Owsap said:

    Nice release, as a contribution, I added support for scrollbar in case anyone prefers it.

    This is the hidden content, please


    mOZztPF.gif    xQbunuL.gif

     

    I like the idea but i would go for grid resize and pagination based on item count and size.

    • Metin2 Dev 10
    • Dislove 1
    • Think 1
    • Good 2
    • Love 1
    • Love 4
  9. 1 hour ago, Karbust said:

    You just proved my point...

    The granny_track_group structure for each model in an animation contains the extracted data in either the PeriodicLoop member or the LoopTranslation member. The former is for animations that have rotational accumulation, and the latter is for those that do not.

     

    The model is being loaded BY GRANNY with the LoopTranslation's elements all 0. So that code will do just keep value 0...

    There's no need to make any kind of calculations to get the Accumulation values, they are the LoopTranslation elements... And if there are calculations involved, they probably are on granny's side.

    If you back-trace just using granny.h you will notice the structs, and you will also find out where the values should come from.

    Wdym? xDspacer.png

  10. 4 hours ago, blaxis said:

     

    local affected_rows, goldd = mysql_direct_query("SELECT gold FROM player.player WHERE account_id = '2';")
    say_reward(tostring(goldd))

    Not work.

     

    Quest screen error:

    table: 00xc66e bla bla bla

    Since you are selecting, you are inserting information into two local variables. Information such as affected_rows and the values of those rows (affected_rows, goldd).
    You can access the values from index 1 to (affected_rows) and the specific name.

    Ex:

    local affected_rows, goldd = mysql_direct_query("SELECT gold FROM player.player WHERE account_id = '2';")
    say_reward(tostring(goldd[affected_rows]["gold"]))

    In this example we are accessing table index by affected_rows because in our case you can only select the gold from account_id 2.

    • Good 1
  11. local inputPrice = tonumber(string.gsub(input(), "[^0-9]", ""))
    local realPrice = math.floor(inputPrice)

    There is no logic explanation for the math.floor since you only let people write numbers but you can let that be there just in case.

  12. 58 minutes ago, TMP4 said:

    If somebody starts a refund, both the seller and buyer will be placed in a conversation with Paypal where the buyer needs to write his problem. Paypal and the seller will see this and if the buyer states that he did not received the goods, the seller can insert a screenshot from the itemshop_logs table as proof of the delivery. Then paypal will decide to who they believes. As long the seller proves the delivery i don't think Paypal decides against him.

    The problem lies in where the seller does not respond to the dispute and the time frame passes, then the buyer will auto-won the case.

    What I wanted to say that "instant block" is not instant, the seller have to ignore (or loose) 50-60 refund request before that happens.


    Well, this is not quite true. The buyer is opening a dispute with the seller, if the dispute is not solved by the both seller and buyer then Paypal intervenes. The instant block is as i said, made automatically by the system when you have over 50-60 cases opened in a short time (two days or so) (doesn't matter if you reply to them or not)

    Now you might ask, how in the world am i gonna get this ammount of refunds. Well if you have a big server this is  not a big deal what so ever. Everything i said, is from a personal experience so trust me, i've been there. Imagine people donating and playing with hacks, when the admins are banning them they all refund their money. Paypal is one of the worst payment's method out here.

  13. 13 hours ago, TMP4 said:

    The key word is pay taxes lol. Also you can't accept lots of payments to a personal paypal account, you have to register a business paypal account so paypal don't freeze your balance after a while. (TOS)

    If you do your business legally then you don't have to worry about anything.

    Everything you said is right, until one point. If the players are refunding the money (romanian's are doing this alot) the account will be blocked for 180 days accepting every refund instantly without any chance to prove it's wrong. The problem is that this happends automatically by the system, so for example if people are refunding like 50-60 payments your account will instantly be blocked leaving you without any way to contact them and even if you try to, they will send you the same message over and over again.

  14. 4 hours ago, Macromango said:

    Thanks for your prompt reply @mast210!

    After a good night of sleep I managed to undestand what was the issue: as I said, it was something stupid!

    I've completely forgot the fact that I've tested the quests one by one when I did acquired it, some weeks ago.
    So the quest flags were still active on my testing character and when I did upload all the package, obviously, this triggered all those unfinished quests.

    Checking on the DB under the table "quest" allowed me to delete all those flags and after resetting the service, everything worked like a charm!


    Thank you very mutch for your support!

    This issue is happily #SOLVED : kindly requesting to close it


    Hi, i just want to give you a tip for a better usage of the states. You can use the "enter" event for handlings such as initializing missions or sending letters, that way it won't require relog for players in order to receive their letters or to continue the missions.

    • Love 1
  15. Uhmm, i think the sysser you get is pretty much self explanatory. QUEST NOT END RUNNING on Login/Logout Resume: wrong QUEST_WAIT request!
    I guess it's intended this way, i mean both login and logout events are not created to have a input at this point, specially the logout.

    My advice is to either make it as a timer (tho i won't ever do that myself, just letting you know about an option if you are really into doing it) or write the message with less words.

     

    Anyway, you can also remove the interogation, but that can lead into other problems, why else it would be there :)

  16. 1 minute ago, xKnox said:

    I didn't stole anything, I paid for this quest. This one was sold by another guy, I couldn't know if he was the original author, I'm sorry.

    I'm really sorry about this situation, I really didn't know, there was another guy that sold me this quest, not Braxy, and he's not responding to anything, the irony :). The quest didn't come with any lib files, he told me this is all I need so I can get to the Razador directly without doing the run and that's what I needed. The timer is completely fucked up basically, I can enter the dungeon, kill Razador but I can do it for how many times I want because the timer is not working, so if I killed the Razador now after I'm being teleported outside I can do it again at the same time..

    This is what I receive in sysser:

     

    SYSERR: Dec 6 22:59:29 :: RunState: LUA_ERROR: [string "BlazingPurgatory_Zone"]:3: attempt to call global `party_get_member_pids' (a nil value) SYSERR: Dec 6 22:59:29 :: WriteRunningStateToSyserr: LUA_ERROR: quest BlazingPurgatory_Zone.start click

     

    I have party_get_member_pids in quest_function and questlua_party.cpp

    I'm really stuck here, I don't know what should I do..

    As i said, these quests were spread everywhere. The problem comes because you don't have a func that returns the player pids from party.

    party_get_member_pids = function()
        local pids = {party.get_member_pids()};
        return pids;
    end

    add this in questlib

    • Good 1
  17. 7 hours ago, Syreldar said:

    Braxy did and sold quests to many, this guy is one of them. 

    This is one of my "creations" from 2018, a shit quest tbh. I was still learning back then and it was not sold for too many people, i sold it to like two people and it got spread everywhere. I don't really care about these quests anymore that's why i don't said anything.

×
×
  • 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.