Jump to content

msnas

Premium
  • Posts

    44
  • Joined

  • Days Won

    1
  • Feedback

    0%

Posts posted by msnas

  1. 9 hours ago, envy said:

    In my opinion, dungeons should remain on quest, the way the official thought of them. Instead of selecting the rewards with a directquery and sending a packet just for the rewards, you could've done a dictionary on clientside.
    Also, what is this?

    spacer.png

    That's definitely going to crash the server.
    @ Rakancito, can you explain how you thought process of this code?

  2. I would also love to add Azure Dev Ops to the mix - It has an amazing board, every US (card) has a number, unlimited storage for you repo but the best part is to be fully connected through Visual Studio.
    It's the one I currently use for local systems and testing:

    SyOkvH3.png

    xLjhvul.pnguCTGSlx.png

     

    Ah, and it's completely free (máx 5 users, then it's 5€/m per extra user).

     

    • Love 1
  3. 4 hours ago, Alessio said:

    ch error resolved, now i have this left

    char_battle.cppcompile polymorph.cpp
    :1264:27: error: no matching member function for call to 'ItemLog'
                            LogManager::instance().ItemLog(this, item, "DROP_EVENT", item->GetID());
                            ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
    ./log.h:29:9: note: candidate function not viable: no known conversion from 'DWORD' (aka 'unsigned int') to 'const char *' for 4th argument
                    void            ItemLog(LPCHARACTER ch, LPITEM item, const char * c_pszText, const char * c_pszHint);
                                    ^
    ./log.h:30:9: note: candidate function not viable: requires 5 arguments, but 4 were provided
                    void            ItemLog(LPCHARACTER ch, int itemID, int itemVnum, const char * c_pszText, const char * c_pszHint);
                                    ^
    ./log.h:28:9: note: candidate function not viable: requires 8 arguments, but 4 were provided
                    void            ItemLog(DWORD dwPID, DWORD x, DWORD y, DWORD dwItemID, const char * c_pszText, const char * c_pszHint, const char * c_pszIP, DWORD dwVnum);
                                    ^

     

    			PIXEL_POSITION pxPos = GetXYZ();
    			LPITEM item;
    			for (int i = 0; i < WEAR_MAX_NUM; ++i)
    			{
    				if ((item = GetWear(i)))
    				{
    					item->RemoveFromCharacter();
    					item->AddToGround(GetMapIndex(), pxPos);
    					item->StartDestroyEvent();
    
    					LogManager::instance().ItemLog(this, item, "DROP_EVENT", item->GetName());
    				}
    			}

     

    • Love 1
  4. 33 minutes ago, Alessio said:

    hey i understood my mistake, i wasn't doing gamecompile after modifing the files, i tried the code that you have writed for Mefarious and it work super good, then i tried to modify it like you showed me and i get this error while i do gamecompile

    char_battle.cpp:1258:15: error: use of undeclared identifier 'ch'
                    if ((item = ch->GetWear(i)))
                                       ^
    char_battle.cpp:1264:27: error: no matching member function for call to 'ItemLog'
                            LogManager::instance().ItemLog(this, item, "DROP_EVENT", item->GetID());
                            ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
    ./log.h:29:9: note: candidate function not viable: no known conversion from 'DWORD' (aka 'unsigned int') to 'const                                                                            char *' for 4th argument
                    void            ItemLog(LPCHARACTER ch, LPITEM item, const char * c_pszText, const char * c_pszHin                                                                           t);
                                    ^
    ./log.h:30:9: note: candidate function not viable: requires 5 arguments, but 4 were provided
                    void            ItemLog(LPCHARACTER ch, int itemID, int itemVnum, const char * c_pszText, const ch                                                                           ar * c_pszHint);
                                    ^
    ./log.h:28:9: note: candidate function not viable: requires 8 arguments, but 4 were provided
                    void            ItemLog(DWORD dwPID, DWORD x, DWORD y, DWORD dwItemID, const char * c_pszText, con                                                                           st char * c_pszHint, const char * c_pszIP, DWORD dwVnum);
                                    ^
    compile horsename_manager.cpp
    compile input.cpp
    2 errors generated.
    gmake: *** [Makefile:119: OBJDIR/char_battle.o] Error 1
    gmake: *** Waiting for unfinished jobs....
    root@metin2:/usr/metin2/src/game/src #
         

    could you help me, please?                         '
     

    this is what i did
     

    CGuildManager::instance().Kill(pkKiller, this);
    		}
    #ifdef DEATH_EVENT
    
    			PIXEL_POSITION pxPos = GetXYZ();
    	LPITEM item;
    	for (int i = 0; i < WEAR_MAX_NUM; ++i)
    	{
    		if ((item = ch->GetWear(i)))
    		{
    			item->RemoveFromCharacter();
    			item->AddToGround(GetMapIndex(), pxPos);
    			item->StartDestroyEvent();
    			
    			LogManager::instance().ItemLog(this, item, "DROP_EVENT", item->GetID());
    		}
    	}
    #endif

     

    	PIXEL_POSITION pxPos = GetXYZ();
    	LPITEM item;
    	for (int i = 0; i < WEAR_MAX_NUM; ++i)
    	{
    		if ((item = GetWear(i)))
    		{
    			item->RemoveFromCharacter();
    			item->AddToGround(GetMapIndex(), pxPos);
    			item->StartDestroyEvent();
    			
    			LogManager::instance().ItemLog(this, item, "DROP_EVENT", item->GetID());
    		}
    	}

     

  5. This is the hidden content, please

    This is the hidden content, please

    Change Your Equipment!

    This update allows you to unlock a second equipment page and switch quickly and easily between two loadouts!
    No longer will you have to change each piece of equipment individually when you want to don a different set. Instead you can enjoy a super-speedy equipment change at the touch of a button.
    For example, you can clad yourself in PvP equipment on one page and PvE equipment on the other, so you’re perfectly prepared for anything!

     

    How It Works

    To unlock the second equipment page you’ll need the new Extra Equipment Page item.
    Use the item and the second equipment page will appear. You can kit this out just as you did the first one.

    As soon as you’ve activated the additional equipment page, the associated Change Equipment Page skill will automatically be unlocked so you can change equipment quickly and easily at any time.
    You’ll find this skill in your secondary skills.

     

    • Metin2 Dev 118
    • Lmao 1
    • Good 18
    • Love 2
    • Love 54
  6. 11 hours ago, Alessio said:

    I tried to modify the code for my needs but it doesn't work i think i'm doing something wrong, i need that only the equipment that the character is wearing is gonna fall on the ground, don't need level drop and not even all the inventory could you help me with that?

    	PIXEL_POSITION pxPos = GetXYZ();
    	LPITEM item;
    	for (int i = 0; i < WEAR_MAX_NUM; ++i)
    	{
    		if ((item = ch->GetWear(i)))
    		{
    			item->RemoveFromCharacter();
    			item->AddToGround(GetMapIndex(), pxPos);
    			item->StartDestroyEvent();
    			
    			LogManager::instance().ItemLog(this, item, "DROP_EVENT", item->GetID());
    		}
    	}

     

    11 hours ago, Alessio said:

    also do you think that there is a way to make spawn an interactive item like a tombstone, and by clicking on it a window containing all the items will open up? instead of make them drop on the ground

    It's possible but it would require time.

  7. This is the hidden content, please

    Alternative download links →

    This is the hidden content, please

     

    This is the hidden content, please

    A couple of days ago, I was searching something on metin2 wiki and found out there's a new called Shoulder Sash Transfer 70070.png.
    After talking to some people who play the official version, I realised it wasn't being used. 
    Some didn't even know of the item's existence.

    There's @ Mali's version but I didn't know he had done it + doesn't have the sash/acce transfer.
    Nonetheless, I decided to create the code for the full Item Combination (this means Costume Bonus Transfer Icona_Transfer_Bonus_Costume.png is also included).

     

    Icona_Transfer_Bonus_Costume.png Costume Bonus Transfer is straightforward, you transfer bonus from a costume to another.
    Here are the requirements in order to get the bonus transfered:

    • Costume's source need to have bonus
    • If costume's target has bonus, it will be removed
    • Both costumes need to have the same type and subtyp (eg: You can't transfer bonus from a costume head to a costume body)

     

    70070.png Shoulder Sash Transfer is more tricky, has 3 levels:

    • Total success: Your sash will absorb all bonus without any penalization
    • Partial success: Your sash will absorb all bonus BUT the absorption rate will decrease 1% (25% -> 24%). That means all bonus will be recalculated according to the new absorption rate.
    • Failure: Your sash WON'T absorb bonus BUT you won't lose the sash you're using to transfer all bonus.

    Here are the requirements in order to get the bonus transfered:

    • You cannot use a sash with a bonus for the target
    • You cannot use a sash without a bonus as the source
    • You cannot transfer the bonus as the sashes have different grades
    • You cannot transfer the bonus as the sashes have different absorption rates

     

    I tried to be as official-like as possible, there are one or other func missing but everything is working just fine.
    Thanks to @ Gurgarath for helping me understand the partial success stuff! 

    • Metin2 Dev 42
    • Good 5
    • Love 26
  8. 4 hours ago, Deso said:

    We should check for nullptr in this place:

     

    //Find in void CWindowManager::SetTopUIWindow() (PythonWindowManager.cpp)
    #if defined(__BL_MOUSE_WHEEL_TOP_WINDOW__)
    		m_pTopWindow = pTopWindow;
    #endif
    
    ///Replace
    #if defined(__BL_MOUSE_WHEEL_TOP_WINDOW__)
    		if (m_pTopWindow)
    		{
    			m_pTopWindow = pTopWindow;
    		}
    #endif

     

     

    I understand the objective of checking but m_pTopWindow is considered NULL by default.
    Meaning the code wouldn't work without wndMgr.SetWheelTopWindow
    There's no reason to use that piece of code.

  9. I found something interesting.

    The tool is only checking if Pi_InitModule starts as a PyObject's variable.
    However, there are some files that aren't being initialized in that way - 14 to be exact:

    6596a8b2cc806aa54c2d3f836a350ba0.png7095949cac34b57eaf0a2e696921ab5f.png733bcc18bf420eec814c35eb6d90fecb.png

     

    The solution would be to remove/comment this line:
    ffdfdc2f4aae619d3d080972d984543a.png

     

    And voilá, files like PythonDebugModule.cpp are being called correctly:
    11cdb0243d339d7e263616d4f74befe8.png
    8242fe080a591f7cf72a0d5246b5fac1.png

    • Good 1
    • Love 1
  10. 1 hour ago, LMAOAtWannabeDevs said:

    Maybe I will look at the source files myself at some point but right now I'd rather just play on official servers 🙂

    Since I have personally experienced the "double drop" since using a bracelet with 20% DD on DE server, I am quite convinced it has to be implemented somewhere.

    It is especially noticeable when farming dragon stone shards, because the 2 dropped shards will in those instances be stackable, which is normally (unfortunately) not the case.

     

    I stumbled upon this thread by googling for "metin2 random number generator" and don't want some baseless accusations of faulty coding stand uncontested in case other people come here the same way.

    // cmd_general.cpp
    case POINT_ITEM_DROP_BONUS:	return LC_TEXT("%d%% 확률로 적퇴치시 아이템 2배 드롭"); 
    
    // constants.cpp
    { POINT_ITEM_DROP_BONUS,	        },   // APPLY_ITEM_DROP_BONUS,	45 
    
    // char.cpp
    case POINT_ITEM_DROP_BONUS:	// 73  
    if (GetPoint(type) + amount > 100)
    {
      sys_err("BONUS exceeded over 100!! point type: %d name: %s amount %d", type, GetName(), amount);
      amount = 100 - GetPoint(type);
    }
    SetPoint(type, GetPoint(type) + amount);
    val = GetPoint(type);
    break;
    
    // char.h
    POINT_ITEM_DROP_BONUS,		// 85 

    Taken from the original source, this are all the referenced points.

     

    void CHARACTER::Reward(bool bItemDrop)
    {
    	if (GetRaceNum() == 5001) // їЦ±ёґВ µ·А» №«Б¶°З µе·У
    	{
    		PIXEL_POSITION pos;
    
    		if (!SECTREE_MANAGER::instance().GetMovablePosition(GetMapIndex(), GetX(), GetY(), pos))
    			return;
    
    		LPITEM item;
    		int iGold = number(GetMobTable().dwGoldMin, GetMobTable().dwGoldMax);
    		iGold = iGold * CHARACTER_MANAGER::instance().GetMobGoldAmountRate(NULL) / 100;
    		iGold *= GetGoldMultipler();
    		int iSplitCount = number(25, 35);
    
    		sys_log(0, "WAEGU Dead gold %d split %d", iGold, iSplitCount);
    
    		for (int i = 1; i <= iSplitCount; ++i)
    		{
    			if ((item = ITEM_MANAGER::instance().CreateItem(1, iGold / iSplitCount)))
    			{
    				if (i != 0)
    				{
    					pos.x = number(-7, 7) * 20;
    					pos.y = number(-7, 7) * 20;
    
    					pos.x += GetX();
    					pos.y += GetY();
    				}
    
    				item->AddToGround(GetMapIndex(), pos);
    				item->StartDestroyEvent();
    			}
    		}
    		return;
    	}
    
    	//PROF_UNIT puReward("Reward");
       	LPCHARACTER pkAttacker = DistributeExp();
    
    	if (!pkAttacker)
    		return;
    
    	//PROF_UNIT pu1("r1");
    	if (pkAttacker->IsPC())
    	{
    		if (GetLevel() - pkAttacker->GetLevel() >= -10)
    			if (pkAttacker->GetRealAlignment() < 0)
    			{
    				if (pkAttacker->IsEquipUniqueItem(UNIQUE_ITEM_FASTER_ALIGNMENT_UP_BY_KILL))
    					pkAttacker->UpdateAlignment(14);
    				else
    					pkAttacker->UpdateAlignment(7);
    			}
    			else
    				pkAttacker->UpdateAlignment(2);
    
    		pkAttacker->SetQuestNPCID(GetVID());
    		quest::CQuestManager::instance().Kill(pkAttacker->GetPlayerID(), GetRaceNum());
    		CHARACTER_MANAGER::instance().KillLog(GetRaceNum());
    
    		if (!number(0, 9))
    		{
    			if (pkAttacker->GetPoint(POINT_KILL_HP_RECOVERY))
    			{
    				int iHP = pkAttacker->GetMaxHP() * pkAttacker->GetPoint(POINT_KILL_HP_RECOVERY) / 100;
    				pkAttacker->PointChange(POINT_HP, iHP);
    				CreateFly(FLY_HP_SMALL, pkAttacker);
    			}
    
    			if (pkAttacker->GetPoint(POINT_KILL_SP_RECOVER))
    			{
    				int iSP = pkAttacker->GetMaxSP() * pkAttacker->GetPoint(POINT_KILL_SP_RECOVER) / 100;
    				pkAttacker->PointChange(POINT_SP, iSP);
    				CreateFly(FLY_SP_SMALL, pkAttacker);
    			}
    		}
    	}
    	//pu1.Pop();
    
    	if (!bItemDrop)
    		return;
    
    	PIXEL_POSITION pos = GetXYZ();
    
    	if (!SECTREE_MANAGER::instance().GetMovablePosition(GetMapIndex(), pos.x, pos.y, pos))
    		return;
    
    	//
    	// µ· µе·У
    	//
    	//PROF_UNIT pu2("r2");
    	if (test_server)
    		sys_log(0, "Drop money : Attacker %s", pkAttacker->GetName());
    	RewardGold(pkAttacker);
    	//pu2.Pop();
    
    	//
    	// ѕЖАМЕЫ µе·У
    	//
    	//PROF_UNIT pu3("r3");
    	LPITEM item;
    
    	static std::vector<LPITEM> s_vec_item;
    	s_vec_item.clear();
    
    	if (ITEM_MANAGER::instance().CreateDropItem(this, pkAttacker, s_vec_item))
    	{
    		if (s_vec_item.size() == 0);
    		else if (s_vec_item.size() == 1)
    		{
    			item = s_vec_item[0];
    			item->AddToGround(GetMapIndex(), pos);
    
    			if (CBattleArena::instance().IsBattleArenaMap(pkAttacker->GetMapIndex()) == false)
    			{
    				item->SetOwnership(pkAttacker);
    			}
    
    			item->StartDestroyEvent();
    
    			pos.x = number(-7, 7) * 20;
    			pos.y = number(-7, 7) * 20;
    			pos.x += GetX();
    			pos.y += GetY();
    
    			sys_log(0, "DROP_ITEM: %s %d %d from %s", item->GetName(), pos.x, pos.y, GetName());
    		}
    		else
    		{
    			int iItemIdx = s_vec_item.size() - 1;
    
    			std::priority_queue<std::pair<int, LPCHARACTER> > pq;
    
    			int total_dam = 0;
    
    			for (TDamageMap::iterator it = m_map_kDamage.begin(); it != m_map_kDamage.end(); ++it)
    			{
    				int iDamage = it->second.iTotalDamage;
    				if (iDamage > 0)
    				{
    					LPCHARACTER ch = CHARACTER_MANAGER::instance().Find(it->first);
    
    					if (ch)
    					{
    						pq.push(std::make_pair(iDamage, ch));
    						total_dam += iDamage;
    					}
    				}
    			}
    
    			std::vector<LPCHARACTER> v;
    
    			while (!pq.empty() && pq.top().first * 10 >= total_dam)
    			{
    				v.push_back(pq.top().second);
    				pq.pop();
    			}
    
    			if (v.empty())
    			{
    				// µҐ№МБцё¦ ЖЇє°Ич ё№АМ БШ »з¶чАМ ѕшАёґП јТАЇ±З ѕшАЅ
    				while (iItemIdx >= 0)
    				{
    					item = s_vec_item[iItemIdx--];
    
    					if (!item)
    					{
    						sys_err("item null in vector idx %d", iItemIdx + 1);
    						continue;
    					}
    
    					item->AddToGround(GetMapIndex(), pos);
    					// 10% АМЗП µҐ№МБц БШ »з¶чіўё®ґВ јТАЇ±ЗѕшАЅ
    					//item->SetOwnership(pkAttacker);
    					item->StartDestroyEvent();
    
    					pos.x = number(-7, 7) * 20;
    					pos.y = number(-7, 7) * 20;
    					pos.x += GetX();
    					pos.y += GetY();
    
    					sys_log(0, "DROP_ITEM: %s %d %d by %s", item->GetName(), pos.x, pos.y, GetName());
    				}
    			}
    			else
    			{
    				// µҐ№МБц ё№АМ БШ »з¶чµй іўё®ёё јТАЇ±З іЄґІ°ЎБь
    				std::vector<LPCHARACTER>::iterator it = v.begin();
    
    				while (iItemIdx >= 0)
    				{
    					item = s_vec_item[iItemIdx--];
    
    					if (!item)
    					{
    						sys_err("item null in vector idx %d", iItemIdx + 1);
    						continue;
    					}
    
    					item->AddToGround(GetMapIndex(), pos);
    
    					LPCHARACTER ch = *it;
    
    					if (ch->GetParty())
    						ch = ch->GetParty()->GetNextOwnership(ch, GetX(), GetY());
    
    					++it;
    
    					if (it == v.end())
    						it = v.begin();
    
    					if (CBattleArena::instance().IsBattleArenaMap(ch->GetMapIndex()) == false)
    					{
    						item->SetOwnership(ch);
    					}
    
    					item->StartDestroyEvent();
    
    					pos.x = number(-7, 7) * 20;
    					pos.y = number(-7, 7) * 20;
    					pos.x += GetX();
    					pos.y += GetY();
    
    					sys_log(0, "DROP_ITEM: %s %d %d by %s", item->GetName(), pos.x, pos.y, GetName());
    				}
    			}
    		}
    	}
    
    	m_map_kDamage.clear();
    }

    Default Reward function.

     

    The only thing that actually adds some kind of calculation is ITEM_MANAGER::GetDropPct:

    bool ITEM_MANAGER::GetDropPct(LPCHARACTER pkChr, LPCHARACTER pkKiller, OUT int& iDeltaPercent, OUT int& iRandRange)
    {
     	if (NULL == pkChr || NULL == pkKiller)
    		return false;
    
    	.....
    
    	iRandRange = 4000000;
    	iRandRange = iRandRange * 100 / 
    		(100 + 
    		 CPrivManager::instance().GetPriv(pkKiller, PRIV_ITEM_DROP) + 
    		 pkKiller->IsEquipUniqueItem(UNIQUE_ITEM_DOUBLE_ITEM)?100:0);
    
    	if (distribution_test_server) iRandRange /= 3;
    
    	return true;
    }
    
    // And the UNIQUE_ITEM_DOUBLE_ITEM is Thief's Gloves.

    Please tell me where is the calculation for the Chance to drop double the Items/POINT_ITEM_DROP_BONUS.

     

    Don't use your arguments by saying "it works on the official ones, so you're wrong", prove us wrong by using facts.

  11. 16 hours ago, ReFresh said:
      Reveal hidden contents

    Client/PythonNetworkStreamPhaseLoading.cpp:

      Reveal hidden contents
    bool CPythonNetworkStream::RecvMainCharacter()
    {
    	TPacketGCMainCharacter MainChrPacket;
    	if (!Recv(sizeof(TPacketGCMainCharacter), &MainChrPacket))
    		return false;
    
    	m_dwMainActorVID = MainChrPacket.dwVID;
    	m_dwMainActorRace = MainChrPacket.wRaceNum;
    	m_dwMainActorEmpire = MainChrPacket.empire; //Here is the change
    	m_dwMainActorSkillGroup = MainChrPacket.bySkillGroup;
    
    	m_rokNetActorMgr->SetMainActorVID(m_dwMainActorVID);
    
    	CPythonPlayer& rkPlayer=CPythonPlayer::Instance();
    	rkPlayer.SetName(MainChrPacket.szName);
    	rkPlayer.SetMainCharacterIndex(GetMainActorVID());
    
    	PyCallClassMemberFunc(m_apoPhaseWnd[PHASE_WINDOW_LOAD], "LoadData", Py_BuildValue("(ii)", MainChrPacket.lX, MainChrPacket.lY));
    
    	//Tracef(" >> RecvMainCharacter\n");
    
    	SendClientVersionPacket();
    	return true;
    }

     

    Client/Packet.h:

      Reveal hidden contents
    typedef struct packet_main_character
    {
    	BYTE		header;
    	DWORD		dwVID;
    	WORD		wRaceNum;
    	char		szName[CHARACTER_NAME_MAX_LEN + 1];
    	long		lX, lY, lZ;
      	BYTE		empire; //Here is the change
    	BYTE		bySkillGroup;
    } TPacketGCMainCharacter;

     

    Server/Packet.h:

      Hide contents
    typedef struct packet_main_character
    {
    	BYTE header;
    	DWORD dwVID;
    	WORD wRaceNum;
    	char szName[CHARACTER_NAME_MAX_LEN + 1];
    	long lx, ly, lz;
    	BYTE empire; //Here is no change
    	BYTE skill_group;
    } TPacketGCMainCharacter;

     

    Yes, you can have it that way, I was explaining why you shouldn't need to worry about that since the function won't be called in a default way.

    • Metin2 Dev 1
    • Love 1
  12. Yes, the problem is there and your logical is correct.

    However, it shouldn't be an issue.

     

    Shal we trace a little bit?

    // PythonNetworkPhaseLoading.cpp
    HEADER_GC_MAIN_CHARACTER // Header calling the function
    bool CPythonNetworkStream::RecvMainCharacter() // Function
    
    
    // input_db.cpp
    void CInputDB::PlayerLoad(LPDESC d, const char * data)
    {
    	....
    	ch->MainCharacterPacket(); // Calling the function
    }
    
    // char.cpp
    void CHARACTER::MainCharacterPacket()
    {
        const unsigned mapIndex = GetMapIndex();
        const BGMInfo& bgmInfo = CHARACTER_GetBGMInfo(mapIndex);
    
        // SUPPORT_BGM
        if (!bgmInfo.name.empty())
        {
            if (CHARACTER_IsBGMVolumeEnable())
            {
                sys_log(1, "bgm_info.play_bgm_vol(%d, name='%s', vol=%f)", mapIndex, bgmInfo.name.c_str(), bgmInfo.vol);
                TPacketGCMainCharacter4_BGM_VOL mainChrPacket;
                mainChrPacket.header = HEADER_GC_MAIN_CHARACTER4_BGM_VOL;
                mainChrPacket.dwVID = m_vid;
                mainChrPacket.wRaceNum = GetRaceNum();
                mainChrPacket.lx = GetX();
                mainChrPacket.ly = GetY();
                mainChrPacket.lz = GetZ();
                mainChrPacket.empire = GetDesc()->GetEmpire();
                mainChrPacket.skill_group = GetSkillGroup();
                strlcpy(mainChrPacket.szChrName, GetName(), sizeof(mainChrPacket.szChrName));
    
                mainChrPacket.fBGMVol = bgmInfo.vol;
                strlcpy(mainChrPacket.szBGMName, bgmInfo.name.c_str(), sizeof(mainChrPacket.szBGMName));
                GetDesc()->Packet(&mainChrPacket, sizeof(TPacketGCMainCharacter4_BGM_VOL));
            }
            else
            {
                sys_log(1, "bgm_info.play(%d, '%s')", mapIndex, bgmInfo.name.c_str());
                TPacketGCMainCharacter3_BGM mainChrPacket;
                mainChrPacket.header = HEADER_GC_MAIN_CHARACTER3_BGM;
                mainChrPacket.dwVID = m_vid;
                mainChrPacket.wRaceNum = GetRaceNum();
                mainChrPacket.lx = GetX();
                mainChrPacket.ly = GetY();
                mainChrPacket.lz = GetZ();
                mainChrPacket.empire = GetDesc()->GetEmpire();
                mainChrPacket.skill_group = GetSkillGroup();
                strlcpy(mainChrPacket.szChrName, GetName(), sizeof(mainChrPacket.szChrName));
                strlcpy(mainChrPacket.szBGMName, bgmInfo.name.c_str(), sizeof(mainChrPacket.szBGMName));
                GetDesc()->Packet(&mainChrPacket, sizeof(TPacketGCMainCharacter3_BGM));
            }
            //if (m_stMobile.length())
            //		ChatPacket(CHAT_TYPE_COMMAND, "sms");
        }
        // END_OF_SUPPORT_BGM
    
     	.....
    }

     

    Let's understand first the functions:

    // Getting the current map index where the player is and find out if there is any music associated to the map
    const unsigned mapIndex = GetMapIndex();
    const BGMInfo& bgmInfo = CHARACTER_GetBGMInfo(mapIndex);
    
    ....
      if (CHARACTER_IsBGMVolumeEnable()) // Gets if the bgm volume is enable (we'll go through this later=
      ....
    }
    
    
    // A std::map that assigns a map to a music.
    typedef std::map<unsigned, BGMInfo> BGMInfoMap;
    
    // Sets gs_bgmVolEnable to true
    void CHARACTER_SetBGMVolumeEnable()
    {
        gs_bgmVolEnable = true;
        sys_log(0, "bgm_info.set_bgm_volume_enable");
    }
    
    // Adds info to the std::map
    void CHARACTER_AddBGMInfo(unsigned mapIndex, const char* name, float vol)
    {
        BGMInfo newInfo;
        newInfo.name = name;
        newInfo.vol = vol;
    
        gs_bgmInfoMap[mapIndex] = newInfo;
    
        sys_log(0, "bgm_info.add_info(%d, '%s', %f)", mapIndex, name, vol);
    }
    
    // Gets the info in the std::map
    const BGMInfo& CHARACTER_GetBGMInfo(unsigned mapIndex)
    {
        BGMInfoMap::iterator f = gs_bgmInfoMap.find(mapIndex);
        if (gs_bgmInfoMap.end() == f)
        {
            static BGMInfo s_empty = {"", 0.0f};
            return s_empty;
        }
        return f->second;
    }

     

    So far, we don't quite understand what's the problem, but let's try to trace even futher.

    void CHARACTER::MainCharacterPacket()
    {
    	const unsigned mapIndex = GetMapIndex();
    	const BGMInfo& bgmInfo = CHARACTER_GetBGMInfo(mapIndex);
    
    	if (!bgmInfo.name.empty()) // If there is any music associated to the map
    	{
    		if (CHARACTER_IsBGMVolumeEnable()) // If the variable gs_bgmVolEnable is true
    			// something happens
    		else // If the gs_bgmVolEnable variable is false
    			// other stuff happens
    	}
    	else // If there isn't any music associated to the map
    	{
    		sys_log(0, "bgm_info.play(%d, DEFAULT_BGM_NAME)", mapIndex);
    
    		TPacketGCMainCharacter pack;
    		pack.header = HEADER_GC_MAIN_CHARACTER;
    		pack.dwVID = m_vid;
    		pack.wRaceNum = GetRaceNum();
    		pack.lx = GetX();
    		pack.ly = GetY();
    		pack.lz = GetZ();
    		pack.empire = GetDesc()->GetEmpire();
    		pack.skill_group = GetSkillGroup();
    		strlcpy(pack.szName, GetName(), sizeof(pack.szName));
    		GetDesc()->Packet(&pack, sizeof(TPacketGCMainCharacter));
    	}
    }

     

    But why it shouldn't happen?

    Because, by default all the info is being called in settings.lua in your share folder.

    add_bgm_info( 1, "enter_the_east.mp3", 0.5);
    add_bgm_info(21, "enter_the_east.mp3", 0.5);
    add_bgm_info(41, "enter_the_east.mp3", 0.5);
    add_bgm_info( 3, "back_to_back.mp3", 0.5);
    add_bgm_info(23, "back_to_back.mp3", 0.5);
    add_bgm_info(43, "back_to_back.mp3", 0.5);
    add_bgm_info(63, "open_the_gate.mp3", 0.5);
    add_bgm_info(69, "open_the_gate.mp3", 0.5);
    add_bgm_info(70, "open_the_gate.mp3", 0.5);
    add_bgm_info(67, "a_rhapsody_of_war.mp3", 0.5);
    add_bgm_info(68, "lost_my_name.mp3", 0.5);
    add_bgm_info(65, "wonderland.mp3", 0.5);
    add_bgm_info(61, "mountain_of_death.mp3", 0.5);
    add_bgm_info(64, "save_me.mp3", 0.5);
    add_bgm_info(74, "mountain_of_death.mp3", 0.5);
    add_bgm_info(75, "follow_war_god.mp3", 0.5);
    add_bgm_info(76, "mountain_of_death.mp3", 0.5);
    add_bgm_info(77, "save_me.mp3", 0.5);
    add_bgm_info(78, "wonderland.mp3", 0.5);
    add_bgm_info(104, "Only_my_battle.mp3", 0.5);
    add_bgm_info(62, "follow_war_god.mp3", 0.5);
    add_bgm_info(66, "death_of_landmark.mp3", 0.5);
    add_bgm_info(107, "monkey_temple.mp3", 0.5);
    add_bgm_info(108, "monkey_temple.mp3", 0.5);
    add_bgm_info(109, "monkey_temple.mp3", 0.5);
    add_bgm_info(114, "last-war2.mp3", 0.5);
    add_bgm_info(115, "last-war2.mp3", 0.5);
    add_bgm_info(116, "last-war2.mp3", 0.5);
    add_bgm_info(117, "last-war2.mp3", 0.5);
    add_bgm_info(118, "last-war2.mp3", 0.5);
    add_bgm_info(119, "last-war2.mp3", 0.5);
    add_bgm_info(120, "last-war2.mp3", 0.5);
    add_bgm_info(121, "last-war2.mp3", 0.5);
    add_bgm_info(122, "last-war2.mp3", 0.5);
    add_bgm_info(123, "last-war2.mp3", 0.5);
    add_bgm_info(124, "last-war2.mp3", 0.5);
    add_bgm_info(125, "last-war2.mp3", 0.5);
    add_bgm_info(126, "last-war2.mp3", 0.5);
    add_bgm_info(127, "last-war2.mp3", 0.5);
    add_bgm_info(128, "last-war2.mp3", 0.5);
    add_bgm_info(181, "last-war2.mp3", 0.5);
    add_bgm_info(182, "last-war2.mp3", 0.5);
    add_bgm_info(183, "last-war2.mp3", 0.5);
    add_bgm_info(216, "catacomb_of_devil.mp3", 0.5);
    add_bgm_info(71, "Only_my_battle.mp3", 0.5);
    add_bgm_info(217, "Only_my_battle.mp3", 0.5);
    add_bgm_info(301, "another_way.mp3", 0.5);
    add_bgm_info(302, "misty_forest.mp3", 0.5);
    add_bgm_info(303, "blacksea.mp3", 0.5);
    add_bgm_info(304, "mt.mp3", 0.5);
    set_bgm_volume_enable();
    	int _set_bgm_volume_enable(lua_State* L)
    	{
    		CHARACTER_SetBGMVolumeEnable();
    
    		return 0;
    	}
    
    	int _add_bgm_info(lua_State* L)
    	{
    		if (!lua_isnumber(L, 1) || !lua_isstring(L, 2))
    			return 0;
    
    		int mapIndex		= (int)lua_tonumber(L, 1);
    
    		const char*	bgmName	= lua_tostring(L, 2);
    		if (!bgmName)
    			return 0;
    
    		float bgmVol = lua_isnumber(L, 3) ? lua_tonumber(L, 3) : (1.0f/5.0f)*0.1f;
    
    		CHARACTER_AddBGMInfo(mapIndex, bgmName, bgmVol);
    
    		return 0;
    	}

    This is also being called everytime you enter the game.

    So, in theory you shouldn't worry about that, but it's a nice catch and you can change it.

    OBS: If there isn't any music associated to a map, it will use the default: M2BG.mp3 (inside BGM folder)

     

    TLDR: Yes, but no.

    • Love 1
  13. Before I tell you the solution, you need to understand exactly what the problem is:

    Open() takes exactly 3 arguments (2 given)

    This means that a function is expecting to recieve 3 informations:

    def new_func(arg1, arg2, arg3):
      print("this func works!")
    
    # new_func(1, 2) # This won't work because we're only sending 2 infos
    
    new_func(1, 2, 3) # This will work since the func is expecting 3 arguments

     

    Now, with that in mind, let's indentify the problem:

    # So, we know that the open is getting 3 arguments
    def Open(self, title,days):
    
    # And we're calling the function with this:
    self.privateShopBuilder.Open(self.inputDialog.GetText())
    
    

    We now know that it's wrong since the function is expecting 3 infos and we are sending it once

    This means that we probably want to do this... right?

    self.privateShopBuilder.Open(1, 2, 3)

    Well, yes (the logical is correct) but in this case, it's wrong.

    In the beginning of the function, notice the word self. I can go to a deep explanation but it's not relevant to the topic. Let's just say you can ignore since it's used to represent the class where the function is.

     

    But what does that mean?

    Remember the initial error?

    Open() takes exactly 3 arguments (2 given)

    The function is recieving 2 arguments: self and title.

     

    We need to send 2 infos, instead of 1:

    def Open(self, title,days): # title and days
    self.privateShopBuilder.Open(self.inputDialog.GetText()) # we're ONLY sending title, where's the "days"?

    You probably didn't follow the tutorial at interfarcemodule.py, check the files again.

     

    Quick solution:

    self.privateShopBuilder.Open(self.inputDialog.GetText(), 1) # The 1 represents the day, probably how many days to open an offline shop (?)

     

    • Love 1
    • Love 2
  14. It depends.

    If your server is sucessfully running with a considered number of players online, expect them to put you on their list.
    In a normal case, first GF sends you a letter saying that you have X days to close the server or expect a lawsuit.

    If that day comes, just comply with what they are saying and close it, or else they will go after you in real life.

    • Scream 1
  15. Go to char_battle.cpp and search for (in ::Dead function):

    CGuildManager::instance().Kill(pkKiller, this);

     

    Add this after:

    #ifdef DEATH_EVENT
    #define DEATH_INITIAL_LEVEL	1 // What's the level the player should start?
    
    			ResetPoint(MINMAX(1, DEATH_INITIAL_LEVEL, PLAYER_MAX_LEVEL_CONST));
    			ClearSkill();
    			ClearSubSkill();
    
    
    			LPITEM	item;
    			for (int i = 0; i < INVENTORY_AND_EQUIP_SLOT_MAX; ++i)
    			{
    				if ((item = GetInventoryItem(i)))
    				{
    					ITEM_MANAGER::instance().RemoveItem(item, "DEATH_EVENT");
    					SyncQuickslot(QUICKSLOT_TYPE_ITEM, i, 255);
    				}
    			}
    
    			ChatPacket(CHAT_TYPE_INFO, "<Death Event> You died and as such, your level is now %d and you lost all your equipment", GetLevel());
    #endif

     

    In service.h, add:

    #define DEATH_EVENT

     

  16. After trying it out on atom and pycharm, I decided to use it on VS Code.

    However, there's a problem with the sintax from the pylance (built-in with the python extension):

    spacer.png

    This is because the functions are set as NoReturn and pylance doesn't like that, that's why the code seems unreachable.

     

    In order to fix this, you need to change the type NoReturn to None:

    spacer.png

    You can do it by changing all the files after the manipulation or change the 

    This is the hidden content, please
    .

     

    Thank you for the release, everything is working as expected!

     

    • Metin2 Dev 3
    • Love 2
  17. 16 hours ago, JinxTheLux said:

    Yes, I guess you are right, reload commands have always been a problem in active game, so I wanted to get your opinion. It shows +1 total players I can't understand about the system, do you know why?

    mlGc5jM.png

     

    • Total [1] 0 / 0 / 1 (this server 1) -> /w command 
    • Testing Total 1 -> /u command
    • Requesting total users. PLAYERS: 3 -> /reload y command, this is what the system is all about.
  18. 14 minutes ago, JinxTheLux said:

    Don't you think it's okay when the number of players is high?

    The system isn't requesting anything new, the GetUserCount() already exists and only returns the size from m_map_kLogonAccount, so I think it will be fine even if you have 30.000 players online.

  19. This is a simple (and probably unwanted) tutorial that will give you all the online players for each core, including the auth
    We might argue about whether being in the select character phase is really in-game, but we will include it anyway

     

    Why?

    The /u and /w commands only provide data from the channel where the player is
    Since I needed absolute values - as soon as someone logs in - I went searching in the source and came across the GetUserCount() function

    mlGc5jM.png

    I had 3 players in separated channels (auth, 1 and 99 respectively)
    I used the commands above and lastly the new one we are going to create

     

    Explanation?

    The function returns (in type DWORD) the size of m_map_kLogonAccount
    It is a map that stores the login values with class CLoginData
    It is also responsible for storing the values of the players online every 1h in usage.txt
    It is given an insert in the map every login a player makes through the boolean function InsertLogonAccount()

    What we're going to do is requesting the data from GetUserCount() and show it.

     

    Installing

     

    DB

     

    ClientManager.cpp:

    Search for:

    				//RELOAD_ADMIN
    			case HEADER_GD_RELOAD_ADMIN:
    				ReloadAdmin(peer, (TPacketReloadAdmin*)data);
    				break;
    				//END_RELOAD_ADMIN

    Add below:

    			case HEADER_GD_REQ_USER_COUNT:
    				RequestUserCount(dwHandle);
    				break;

    Search for:

    void CClientManager::ReloadAdmin(CPeer*, TPacketReloadAdmin* p)
    {
      ....
    }

    Add below:

    void CClientManager::RequestUserCount(DWORD dwHandle)
    {
    	for (TPeerList::const_iterator it = m_peerList.begin(); it != m_peerList.end(); ++it)
    	{
    		CPeer* peer = *it;
    
    		if (!peer->GetChannel())
    			continue;
    
    		TPacketDGUserCount Info;
    		peer->EncodeHeader(HEADER_DG_RET_USER_COUNT, dwHandle, sizeof(TPacketDGUserCount));
    		Info.user = GetUserCount();
    		peer->Encode(&Info, sizeof(TPacketDGUserCount));
    	}
    }

     

    ClientManager.h:

    Search for:

    	//RELOAD_ADMIN
    	void ReloadAdmin(CPeer * peer, TPacketReloadAdmin * p);
    	//END_RELOAD_ADMIN

    Add below:

    	void RequestUserCount(DWORD dwHandle);

     

    Common

    tables.h:

    Search for:

    	HEADER_DG_RESPOND_CHANNELSTATUS		= 181,

    Add below:

    	HEADER_GD_REQ_USER_COUNT = 141,
    	HEADER_DG_RET_USER_COUNT = 182,

    Search for:

    typedef struct SPacketDGChangeEmpirePriv
    {
    	BYTE type;
    	int value;
    	BYTE empire;
    	BYTE bLog;
    	time_t end_time_sec;
    } TPacketDGChangeEmpirePriv;

    Add below:

    typedef struct SPacketDGUserCount
    {
    	DWORD user;
    } TPacketDGUserCount;

     

    Game

    cmd_gm.cpp:

    Search for:

    				//RELOAD_ADMIN
    			case 'a':
    				ch->ChatPacket(CHAT_TYPE_INFO, "Reloading Admin infomation.");
    				db_clientdesc->DBPacket(HEADER_GD_RELOAD_ADMIN, 0, NULL, 0);
    				sys_log(0, "Reloading admin infomation.");
    				break;
    				//END_RELOAD_ADMIN

    Add below:

    			case 'y':
    				ch->ChatPacket(CHAT_TYPE_INFO, "Requesting total users.");
    				db_clientdesc->DBPacket(HEADER_GD_REQ_USER_COUNT, ch->GetDesc()->GetHandle(), NULL, 0);
    				break;

     

    input.cpp:

    Search for:

    							case 'a':
    								db_clientdesc->DBPacket(HEADER_GD_RELOAD_ADMIN, 0, NULL, 0);
    								sys_log(0, "Reloading admin infomation.");
    								break;

    Add below:

    							case 'y':
    								db_clientdesc->DBPacket(HEADER_GD_REQ_USER_COUNT, d->GetHandle(), NULL, 0);
    								sys_log(0, "Reloading user count.");
    								break;

     

    input.h:

    Search for:

    	//RELOAD_ADMIN
    	void ReloadAdmin( const char * c_pData );
    	//END_RELOAD_ADMIN

    Add below:

    	void		UserCount(LPDESC d, const TPacketDGUserCount* p);

     

    input_db.cpp:

    Search for:

    // MYSHOP_PRICE_LIST
    void CInputDB::MyshopPricelistRes(LPDESC d, const TPacketMyshopPricelistHeader* p )
    {
    	LPCHARACTER ch;
    
    	if (!d || !(ch = d->GetCharacter()) )
    		return;
    
    	sys_log(0, "RecvMyshopPricelistRes name[%s]", ch->GetName());
    	ch->UseSilkBotaryReal(p );
    
    }
    // END_OF_MYSHOP_PRICE_LIST

    Add below:

    void CInputDB::UserCount(LPDESC d, const TPacketDGUserCount* p)
    {
    	sys_err("ADMIN WHISPER: USERCOUNT");
    
    	LPCHARACTER ch;
    
    	if (!d || !(ch = d->GetCharacter()))
    		return;
    
    	ch->ChatPacket(CHAT_TYPE_PARTY, "PLAYERS: %d", p->user);
    }

    Search for:

    	// RELOAD_ADMIN
    	case HEADER_DG_RELOAD_ADMIN:
    		ReloadAdmin(c_pData );		
    		break;
    	//END_RELOAD_ADMIN

    Add below:

    	case HEADER_DG_RET_USER_COUNT:
    		UserCount(DESC_MANAGER::instance().FindByHandle(m_dwHandle), (TPacketDGUserCount*)c_pData);
    		break;

     

    Usage
    The usage is /reload y

     

    • Metin2 Dev 1
    • Think 1
    • Love 6
  20. This tutorial is going to teach you how to compilerun and configure a server on Windows.

    I needed something like this a few days ago and since it doesn't exist, I decided to make it
    There is no addition or modification in the source or client (except for small bonuses).

     

    0. Beginning

    At the end of the topic there will be two links where you will need to download:

    Client + Server + Source 

    22140444EtEk9.png

     

    MySQL

    221404ylHz4se.png

     

    The client is based on the

    This is the hidden content, please
    , I just edited it to have the classic format.

    Regardless the Server and Client source.

     

    1. The files

    We will need to download the following files:

     

    2. Installing

    The installation is easy enough for me to consider that I don't need to spend much time on this, however I hope this two pictures will facilitate (more) on what you need to do:

    Visual Studio Community

    2214222m7Ypro.png

     

    252310GreenCheck.pngNote: You actually just need the MSVC v142, C++ CMake, C++ ATL, C++ MFC and C++/CLI for this to work

     

    MySQL:

    221443oxTfHOm.png

    252730RedInfo.pngWarning: In this tutorial we're going to use Mysql Server 5.7.33 X64 but you can (must) upgrade it to 8.0

     

     2.1 Installing Server / Client / Database

    Here you need to pay attention because there's a limitation:

    252730RedInfo.pngWarningYou must unzip the file "dev" on C:\

    If you don't want, follow the Mali61's topic and you need to create manually the symlinks for each core on the server.

    221443WyA2gIx.png

    This is how it should be.

     

    Client:

    221458bFzGfWH.png

    There isn't much to say, in pack/ you already have root and locale_de unpacked but since this is going to be localhost only, you don't need to change nothing on the serverinfo.py

    252310GreenCheck.pngBonus: I translated the client to English, just because

     

    Database:

    1) Windows Key + R and write services.msc

    221532KUJa9tj.png

     

    2) Search for MySQL57 (or the version you installed) and click on Stop

    221532yrBV5Uz.png

    Since Im portuguese, yeah

     

    3) Go to directory C:\ProgramData\MySQL

    221603C8f3FRA.png

     

    4) In the folder MySQL Server 5.7 (or the version you installed) and in the folder Data, paste the files you previously downloaded and unziped from mysql_dev.rar

    221603Fq0bWUj.png

     

    5) On services.msc, start the MySQL process

    221635PSkiGvf.png

    Back it again with the portuguese

     

    Server:

     221635CSbHUNr.png

    2216357DEjN2J.png

    These images are referenced in each core's CONFIG and conf.txt, respectively where the location is on directory C:\dev\2. Server.

     

    252730RedInfo.pngWarning: Don't forget to change the MySQL's user password! You need to put the same password you had when installing the MySQL.

      3. Compile Server / Client Source

    There is nothing introductory since it is something very simple that you will be able to.

    Server:

    It's quite simple, to build the server source, we just need to open the file dev_server.sln which is located in C:\dev\1. Svn\Server\build

    221706uaqgrnO.png

    You can build all at once or separately.

     

    252310GreenCheck.pngBonus: I linked the files to go to the directory C:\dev\2. Server\share\ so you don't need to c&p multiple times.

     

    Client:

    Same as before, open the dev_solution.sln which is located in C:\dev\1. Svn\Client

    221717hPUXBq3.png

     

    252709OrangeWarning.png Since I have a good computer, I enabled the multi-processor compilation option. If your computer is very slow while you are compiling, I suggest you deactivate by going to Properties in all the builds.

    221728rnATdeX.png

     

     

    4. Starting the Server

    On the main directory of the server (C:\dev\2. Server) you'll have 2 bat files:

    • start.bat* - As the name says, it will start the server
    • clear.bat - It will clear all the server's logs

    * I forgot the make it dynamic so if you don't want to have on the C:\dev, you'll need to change the directory.

     

    Execute start.bat and it will show up first the db.exe, then auth's game.exe and last channel1's game.exe

    221759uFL6g7O.png

    221759VxfIg9D.png

    And there you have it, your server is now online!

     

    5. Debug

    You can debug by going to Debug -> Start New Instance

     

    221814yttutrS.png

     

    252310GreenCheck.pngBonus: I linked everything so you don't have to worry about anything

       

    5. Credits

    I like to say that I don't know anything about anything and as such, everything here has its credits.

    @Mali61 - Client/Server compilable with VS2019 (Server & Client)

    @Karbust - If it wasn't for him, I couldn't have done this

    ThatGuyPT - The base was from his Windows Serverfiles

     

     

    6. FAQ

    Q: Why didn't you use xampp instead of MySQL?

    A: At the moment I use MySQL a lot even outside of Metin2, so it makes more sense to me that it be this way.
    However, it is exactly the same, especially on localhost.

     

    Q: Can I migrate the source to FreeBSD?

    A: Yes, you can! As long as you have cmake configured, you can distribute to FreeBSD and use it there.

     

    Q: What is the id and password to enter the game?

    (I put this question because I know there will be someone asking this)

    A: You can create an account in the database, but you can use id: admin pw: 123

     

     

     

    7. Links

    This is the hidden content, please
     - 
    This is the hidden content, please

    This is the hidden content, please

    If you have any questions that I can answer, feel free to write a post here.

     

    • Metin2 Dev 376
    • kekw 12
    • Eyes 26
    • Flame 1
    • Dislove 5
    • Angry 4
    • Not Good 3
    • Sad 2
    • Smile Tear 1
    • Think 11
    • Confused 7
    • Scream 4
    • Lmao 8
    • Good 223
    • muscle 2
    • Love 52
    • Love 425
  21. il y a 1 minute, Kori a dit :

    Can you share with all users here? 

     

    It's not that hard.

    	if (!PERF_CHECKER_RENDER_GAME)
    	{
    		m_kRenderTargetManager.RenderBackgrounds();
    		float fAspect=m_kWndMgr.GetAspect();
    		float fFarClip=m_pyBackground.GetFarClip();
    
    		m_pyGraphic.SetPerspective(30.0f, fAspect, 100.0, fFarClip);
    
    		CCullingManager::Instance().Process();
    
    		m_kChrMgr.Deform();
    		m_kEftMgr.Update();
    		m_kRenderTargetManager.DeformModels();

     

  22. 5 hours ago, iBeast said:
    
    quest special map
    	state start begin
    		when xyz.chat."Take me to the map" begin
    			say("Do you want to enter?")
    			local s = select ("Yes", "No")
    			if s == 2 then return end
    			if s == 1 then pc.warp(x, y) timer("leave_timer", secs) end --Warping and setting timer
    		end --when
    		
    		when "leave_timer".timer begin --When runs out - returns to basic position timer
    			pc.warp(x, y)
    		end --when
    	end --state
    end --quest

    Try it this way... Code is not tested, only "sample".

     

    iBeast

    You can't put pc functions inside of global functions, it will crash the server.

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