Jump to content

Syreldar

Premium
  • Posts

    1298
  • Joined

  • Last visited

  • Days Won

    38
  • Feedback

    100%

Posts posted by Syreldar

  1. The table_shuffle function from my functions file does what you want.

    --[[
        Returns:
            The shuffled version of a table.
    
        Example:
            local table_ex = {
                [1] = "cow",
                [2] = "bird",
                [3] = "dog",
                [4] = "cat",
                [5] = "monkey"
            };
    
            table_shuffle(table_ex) -> returns {
                [1] = "monkey",
                [2] = "cow",
                [3] = "bird",
                [4] = "cat",
                [5] = "dog"
            };
    
            (Since it's random, some values may be the same as before.)
    ]]
    table_shuffle = function(table_ex)
        local table_ex_copy = table_ex;
        local rand = 0;
        for i = table.getn(table_ex_copy), 2, -1 do
            rand = math.random(i);
            table_ex_copy[i], table_ex_copy[rand] = table_ex_copy[rand], table_ex_copy[i]
        end -- for
    
        return table_ex_copy;
    end -- function

     

    • Love 1
  2. void CActorInstance::__ProcessDataAttackSuccess(const NRaceData::TAttackData& c_rAttackData, CActorInstance& rVictim, const D3DXVECTOR3& c_rv3Position, UINT uiSkill, BOOL isSendPacket)
    	else if (rVictim.IsStone() || rVictim.IsDoor())
    	{
    		__HitStone(rVictim);
    	}
    void CActorInstance::__HitStone(CActorInstance& rVictim)
    {
    	if (rVictim.IsStun())
    	{
    		rVictim.Die();
    	}
    	else
    	{
    		rVictim.__Shake(100); // This is what triggers the shake, make sure it's there.
    	}
    }

     

    • Metin2 Dev 1
    • Smile Tear 1
  3. This is not a quest issue.

    There's a conflict of coordinates between DevilTower and Valley of Seungryong. This bug also exists in official servers, but it's irrelevant cause they don't have a Rejoin system within the DevilTower dungeon, thus never accessing that set of coordinates globally.

    The conflict for the DevilTower becomes relevant at 7th, 8th and 9th floor cause they're placed in the utmost right section of the map, which, as you can see from the screenshot below, is partly inside the Valley's map sector.

    Discord_UPIiwq9wr2.png

     

    The server simply doesn't know where to place you., because that global set of coordinates belongs to two different maps.

    To fix the issue, you gotta move either the DevilTower or the Valley in different coordinates in the plane, be careful to choose a free and valid set of coordinates.

    • Love 1
  4. 8 hours ago, VegaS™ said:

    Guys, why are you always using quest flags? It's been 8 years since the source first appeared; we should avoid them because they're highly toxic; let's not use the database for everything when we already have everything in C++, it just needs to be properly accessed.

     

    This is the hidden content, please

     

    Hidden Content

     

    	int party_get_all_member_pids(lua_State* L)
    	{
    		auto ch = CQuestManager::instance().GetCurrentCharacterPtr();
    		if (!ch)
    			return 0;
    
    		auto pParty = ch->GetParty();
    		if (!pParty)
    			return 0;
    
    		const auto& memberMap = pParty->GetPartyMembers();
    		for (const auto& it : memberMap)
    			lua_pushnumber(L, it.first);
    
    		return static_cast<int>(memberMap.size());
    	}
    
    	{ "party_get_all_member_pids",		party_get_all_member_pids	},

     

      • questlua_party.cpp

     

    • party.h

     

    Hidden Content

     

    	public:
    		const TMemberMap& GetPartyMembers() const
    		{
    			return m_memberMap;
    		}

     

     

    How-To-Use:

    if party.is_party() then
    	local party_member_table = {party.party_get_all_member_pids()}
    	local party_member_count = table.getn(party_member_table)
    	
    	-- Debug
    	local party_member_string = table.concat(party_member_table, ", ")
    	say(string.format("Party members: count(%d), pids(%s)", party_member_count, party_member_string))
    end

     

    Good. That fixes the first part of the issue: The party member pids not being taken into account for characters in different cores/channels.

    However, that still won't do, it requires more work: Your solution assumes that the party stays valid through channels, but as I previously stated, when a group member logins in a different one they will be without a party in that channel, parties are channel-exclusive.

    Screenshots for reference: t8xbjmL.jpeg

    KzTZ25p.jpeg

     

    This means that they can just change channel, do whatever they want while eluding the party checks, then log back in the old one. With my solution they can't, because a flag stays valid between different channels.

    Also, I have already stated that my solution was suboptimal, I simply shared my own approach while being informative about it.

    That said, I believe your take towards questflags is a bit of a stretch: the game is built to handle hundreds of thousands of them active and get info on them at the same time with negligible impact on the server.

    • Good 1
  5. SELECT `locale_name`, `max_hp` FROM `player`.`mob_proto` WHERE `max_hp` = 0 AND (`type` = 0 OR `type` = 2);

    This makes you see all the monsters and metins that have a bugged max_hp value in your proto, so you can fix them manually.

    but, If you want to temporarily fix them all automatically, you can run this:
     

    UPDATE `player`.`mob_proto` SET `max_hp` = 100 WHERE `max_hp` = 0 AND (`type` = 0 OR `type` = 2);

     

  6. 1. Clientside:

    Srcs/Client/InstanceBase.cpp

    bool CInstanceBase::IsAttackableInstance(CInstanceBase& rkInstVictim)
    {
    	//
    	else if (IsPC())
    	{
    		if (rkInstVictim.IsStone())
    			return true;
    
    		if (rkInstVictim.IsPC())
    		{
    			// ADD This here:
    			if (IsAffect(AFFECT_INVISIBILITY))
    				return false;
    	//
    //

     

    2. Serverside:

    Srcs/game/src/pvp.cpp:

    bool CPVPManager::CanAttack(LPCHARACTER pkChr, LPCHARACTER pkVictim)
    {
    	//
    	if (pkChr == pkVictim) // ³»°¡ ³¯ Ä¥¶ó°í ÇÏ³× -_-
    		return false;
    
    	if (pkVictim->IsNPC() && pkChr->IsNPC() && !pkChr->IsGuardNPC())
    		return false;
    
    	// Add this here:
    	if (pkChr->IsAffectFlag(AFF_INVISIBILITY) && pkVictim->IsPC())
    		return false;
    	//
    //
  7. 36 minutes ago, WeedHex said:

    Are you sure in official side it's like that?

    Why would you care how is in official, if that's what he wants regardless?

    53 minutes ago, ReFresh said:

    #ref

    char_skill.cpp, search:

    		// 2. Shoot!
    		if (f.GetVictim())
    		{
    			ch->CreateFly(FLY_SKILL_MUYEONG, f.GetVictim());
    			ch->ComputeSkill(SKILL_MUYEONG, f.GetVictim());
    		}

    Turn into:

    		// 2. Shoot!
    		LPCHARACTER victim = f.GetVictim();
    		if (victim && !victim->IsAffectFlag(AFF_EUNHYUNG) && !victim->IsAffectFlag(AFF_INVISIBILITY) && !victim->IsAffectFlag(AFF_REVIVE_INVISIBLE)))
    		{
    			ch->CreateFly(FLY_SKILL_MUYEONG, victim);
    			ch->ComputeSkill(SKILL_MUYEONG, victim);
    		}

     

    • Good 1
    • Love 1
  8. 3 hours ago, Ezequiel G. said:

    I tried to change the .dds textures of the armors from one client to another but they still look the same.

    Because your first screen shows what it looks like on metin2 by default.

    The screenshot with the Greatsword shows a modified version of the Hwang where only the main details are not set to opaque in the alpha channel.

    This is the modified Hwang texture that you want: 

    This is the hidden content, please

    • Metin2 Dev 3
    • Love 2
  9. g	385	387	1	1	0	0	15400s	100	1	2019
    

    `g` means I'm spawning the vnum of a group, not a monster. So in this case I'm calling the vnum of the group associated with the Spider Queen.

    `385` `387` are the local spawn coordinates of the group.

    `1` `1` are the random values for the local spawn coordinates.

    `0` is the Z section, you don't care about that.

    the other `0` is the direction, since this is not an NPC, you don't care about this either.

    `15400` is the number of seconds in between respawns since server start time. Change it as you like.

    `100` is the spawn chance per tick.

    `1` is the number of groups to spawn per tick.

    and finally, `2019` is the vnum of the monster, or in this case the group, to spawn.

    • Love 1
  10. 6 hours ago, PetePeter said:

    Why not using Timestamp instead of increase a dungeon quest flag every time ?

    d.setf("dungeon_time_out_end", get_global_time() + (TIME_OUT_STEP*60)) -- Should be `get_time() + TIME_OUT`. [note that get_global_time() and get_time() are the same.]
    if (d.getf("dungeon_time_out_end") < get_global_time()) -- should be `<=`, if not, it'll run for an additional TIME_OUT_STEP tick if math.mod(TIME_OUT, TIME_OUT_STEP) == 0.

     

    Yes, that's perfect. What you wrote also happens to be the method I use for my dungeons.

    I applied two corrections to it, maybe it'll help you out.

    Regarding your question, I wrote it with the incremental flag because while this way of writing it is more efficient, with the incremental flag it becomes easier to understand, I chose to do it because this is an example and as such it is meant to instruct people.

  11. 44 minutes ago, Tekanse said:
    d.setf("seconds_passed", d.getf("seconds_passed") + TIME_OUT_STEP);
    if (d.getf("seconds_passed") == TIME_OUT) then

    Hmm.
     

    define TIME_OUT 4000
    define TIME_OUT_STEP 55

    Hmmmm.

    This is a development forum, not a baby school, I have wrote an example, you obviously adapt the code to your needs.

    use the >= operator if you need it.

    45 minutes ago, Exygo said:

    or just be badass and send the timestamp cmdchat(string.format("commandName %d",get_time() + 3600))

    and make an UI for it in client and use app.GetGlobalTimeStamp() for calculation. And also in that UI you can display tips E.G: "Defeat all the monsters in the floor to advance to level 3", "Unlock all the 6 seals in order to advance to level 4"

    If by "BEING BADASS" you mean "copy literally what illiterates did for Zodiac", then sure. But I don't see how that's relevant for the topic.

    • kekw 1
  12. Hello.

    100% of the people I've seen coding dungeons or making time-based events of any sort handled `Time remaining` notices like this:
     

    Spoiler
    		when devilcatacomb_45m_left_timer.server_timer begin
    			if d.select(get_server_timer_arg()) then
    				d.notice(string.format(gameforge.devilcatacomb_zone._20_say, 45))
    				server_timer('devilcatacomb_30m_left_timer', 60 * 15, get_server_timer_arg())
    			end
    		end
    
    		when devilcatacomb_30m_left_timer.server_timer begin
    			if d.select(get_server_timer_arg()) then
    				d.notice(string.format(gameforge.devilcatacomb_zone._20_say, 30))
    				server_timer('devilcatacomb_15m_left_timer', 60 * 15, get_server_timer_arg())
    			end
    		end
    	
    		when devilcatacomb_15m_left_timer.server_timer begin
    			if d.select(get_server_timer_arg()) then
    				d.notice(string.format(gameforge.devilcatacomb_zone._20_say, 15))
    				server_timer('devilcatacomb_5m_left_timer', 60 * 10, get_server_timer_arg())
    			end
    		end
    
    		when devilcatacomb_5m_left_timer.server_timer begin
    			if d.select(get_server_timer_arg()) then
    				d.notice(string.format(gameforge.devilcatacomb_zone._20_say, 5))
    				server_timer('devilcatacomb_1m_left_timer', 60 * 4, get_server_timer_arg())
    			end
    		end
    		
    		when devilcatacomb_1m_left_timer.server_timer begin
    			if d.select(get_server_timer_arg()) then
    				d.notice(string.format(gameforge.devilcatacomb_zone._20_say, 1))
    				server_timer ("devilcatacomb_0m_left_timer", 60 * 1, get_server_timer_arg())
    			end
    		end
    		
    		when devilcatacomb_0m_left_timer.server_timer begin
    			if d.select(get_server_timer_arg()) then
    				d.notice (gameforge.devilcatacomb_zone._210_dNotice)
    				d.set_warp_location (65, 5914, 992)
    				server_timer('devilcatacomb_exit_timer', 7, get_server_timer_arg())
    			end
    		end

     

    This is not entirely their fault, sadly Webzen isn't exactly capable of setting a good example of how to do things right in Python, C++ and -of course- Lua.

    This way of doing things is very limited, initializing a new timer for every notice they wanna make is not a very efficient way of dealing with the matter.

    Naturally, you could implement the Official Zodiac's way of handling Time remaining, by overriding the Minimaps' interface with a new interface that has a built-in timer which works via python-quest communication, but shouldn't that be available for you, then you can do this:

     

    First things first, we'll need a single server timer, which will be of the loop type, and it will handle both the `Time remaining` notices and the actions for when the time expires.

    For simplicity purposes, I'll make an example for the dungeons.

     

    Now, we can either set up two full-scope variables, or defines:

    define TIME_OUT 3600 -- 1 hour
    define TIME_OUT_STEP 60 -- 1 minute

    Or, we can make an array that stores the information within a function, like this:

    function GetDungeonData()
    	return {
    		["time_out"] = 3600,
    		["time_out_step"] = 60
    	};
    end -- function

    Note that functions initialized inside quests are not global: they are only valid within their scope - the state they get initialized in.

     

    For this example, I'll make use of the first of the 2 methods I just showed you.

    Now, we got 2 elements:

    1. time_out, which is our time limit.
    2. time_out_step, which will be the interval in seconds in-between triggers of our server loop timer.

    We'll use them like this:

    if (TIME_OUT and TIME_OUT_STEP) then -- No reason to initialize anything if you don't want a time limit.
    	server_loop_timer("dungeon_time_out_check", TIME_OUT_STEP, d.get_map_index());
    	notice(string.format("<Dungeon> Initialized. Time limit: %s!", get_time_format(TIME_OUT))) -- get_time_format - https://metin2.dev/topic/15905-syreldars-quest-functions/
    end -- if

    We have bound a timer to our dungeon instance and set it to trigger once every `time_out_step` seconds;

    This allows us to control the interval between `Time remaining` notices by changing the `time_out_step` element as we like.

    Now all that remains is to set our timer's trigger and make use of `time_out` and `time_out_step`, the full example code would look like this:

    define TIME_OUT 3600 -- 1 hour
    define TIME_OUT_STEP 60 -- 1 minute
    define DUNGEON_MAP_INDEX XXX
    
    quest dungeon begin
    	state start begin
    		when --[[]] with InDungeon(DUNGEON_MAP_INDEX) begin -- InDungeon - https://metin2.dev/topic/15905-syreldars-quest-functions/
    			--
    
    			if (TIME_OUT and TIME_OUT_STEP) then -- No reason to initialize anything if you don't want a time limit.
    				server_loop_timer("dungeon_time_out_check", TIME_OUT_STEP, d.get_map_index());
    				d.notice(string.format("<Dungeon> Initialized! Time limit: %s!", get_time_format(TIME_OUT))) -- get_time_format - https://metin2.dev/topic/15905-syreldars-quest-functions/
    			end -- if
    		end -- when
    
    		when dungeon_time_out_check.server_timer begin
    			-- Only refer to our personal dungeon instance's server timer.
    			if (d.select(get_server_timer_arg())) then
    				-- increase by TIME_OUT_STEP each trigger
    				d.setf("seconds_passed", d.getf("seconds_passed") + TIME_OUT_STEP);
    				-- If the flag equals or surpasses TIME_OUT after being increased, it means the time has expired.
    				if (d.getf("seconds_passed") >= TIME_OUT) then
    					-- Clear the timer as there's no more need for it to loop.
    					clear_server_timer("dungeon_time_out_check", get_server_timer_arg());
    					d.notice("<Dungeon> Time expired.")
    					d.exit_all();
    				else
    					-- As long as the time hasn't expired, we just notify the dungeon about the Time remaining.
    					d.notice(string.format("<Dungeon> Time remaining: %s.", get_time_format(TIME_OUT - d.getf("seconds_passed")))) -- get_time_format - https://metin2.dev/topic/15905-syreldars-quest-functions/
    				end -- if/else
    			end -- if
    		end -- when
    	end -- state
    end -- quest

    Alternatively, instead of using the `seconds_passed` flag like I'm doing (the way I showed you is more understandable and thus more proper since this is an example), you can initialize a single flag to register the starting time and make use of get_time() in between triggers.

     

    And there you have it, full control in a single timer for your time remaining notices.

    • Metin2 Dev 3
    • Good 2
    • Love 5
  13. 1 hour ago, iderx said:

    Hey when i dropped the quest.lua file and pasted the text from questlib file to my questlib (i pasted it at the end of the file)
    i get this in my server\Machine after typing ./qc quest.lua
    FUNCTION GetYangReward(lv_next)
    FUNCITON GetExpReward(current.mission,lv_next)

    i dont know what to do 

    ..that's not an error.

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