Jump to content

Search the Community

Showing results for tags 'c++'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Metin2 Dev
    • Announcements
  • Community
    • Member Representations
    • Off Topic
  • Miscellaneous
    • Metin2
    • Showcase
    • File Requests
    • Community Support - Questions & Answers
    • Paid Support / Searching / Recruiting
  • Metin2 Development
  • Metin2 Development
    • Basic Tutorials / Beginners
    • Guides & HowTo
    • Binaries
    • Programming & Development
    • Web Development & Scripts / Systems
    • Tools & Programs
    • Maps
    • Quests
    • 3D Models
    • 2D Graphics
    • Operating Systems
    • Miscellaneous
  • Private Servers
    • Private Servers
  • Uncategorized
    • Drafts
    • Trash
    • Archive
    • Temporary
    • Metin2 Download

Product Groups

  • Small Advertisement
  • Large Advertisement
  • Advertising

Categories

  • Third Party - Providers Directory

Categories

  • Overview
  • Pages
    • Overview
    • File Formats
    • Network
    • Extensions

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


Pillory


Marketplace


Game Server


Country


Nationality


Github


Gitlab


Discord


Skype


Website

  1. [Hidden Content] [Hidden Content] The items on the ground that are close enough to be collected appear in different color.
  2. Hi, [Hidden Content] [Hidden Content] This quest is responsible for granting basic costumes to players when they reach certain levels in the game. The quest is triggered when a player logs in or levels up in the game. It checks the player's level and grants various cosmetic items if certain conditions are met. Basic Costume: Players receive a basic costume based on their gender. Basic Hairstyle: Players receive a basic hairstyle and an additional cosmetic item. Basic Pet: Players receive a basic pet at a certain level. Basic Weapon: Players receive a basic weapon based on their class. Basic Mount: Players receive a basic mount at a certain level.
  3. This is basically a system to complement my other system. With this you one can have a custom tag name behind the name/karma of the character As you can see on this picture Behind the character name and karma there's a red tag, this is achieved using this system. I didn't make it show the level by my personal choice but you can easily add it. I remember seeing another post on this forum about this, but i cant find it anymore not sure why, so i'm posting my own version. InstanceBaseEffect.cpp Search for CInstanceBase::UpdateTextTailLevel Below sprintf(szText, "Lv %d", level); And before CPythonTextTail::Instance().AttachLevel(GetVirtualID(), szText, s_kLevelColor); Add this if (IsGameMaster()) { const char* name = GetNameString(); size_t len = strcspn(name, "]"); char *result = (char *)malloc((len + 1) * sizeof(char)); // Not sure why on client side needs to be like this strncpy(result, name, len +1); result[len + 1] = '\0'; const char *tagDictionary[] = { "[GM]", "[SA]", "[GA]", "[DEV]" }; //Here you can also change the color of the tag const char *nameDictionary[] = { "|cffff0000[STAFF]", "|cffff0000[EQUIPA]", "|cffff0000[STAFF2]", "|cffff0000[DEVELOPER]" }; int tagIndex = -1; for (int i = 0; i < sizeof(tagDictionary) / sizeof(tagDictionary[0]); ++i) { if (strcmp(result, tagDictionary[i]) == 0) { tagIndex = i; break; } } if (tagIndex != -1){ sprintf(szText, nameDictionary[tagIndex]); } else{ // This is just for if the code cant find any tag, it will default to this one. // You can also just delete this whole else statement and it will default to the level text sprintf(szText, "|cffff0000[TEST]"); } free(result); CPythonTextTail::Instance().AttachLevel(GetVirtualID(), szText, s_kLevelColor); } And thats all xD
  4. So ashika posted this a few days ago And since i couldnt find a way to show all of them ingame depending on the GM "class" i decided to make my own, its not that hard tbh, and i think it doesnt fck anything but if u find problems let me know. Here's the result First of all, the way the system works is by the GM name, so if it has [SA] or [GM] in name it will apply the effect for it. Server Side service.h #define ENABLE_CUSTOM_TAG_EFFECTS affect.h in enum EAffectBits search for the last enum (in my case is AFF_BITS_MAX) and before it add #ifdef ENABLE_CUSTOM_TAG_EFFECTS AFF_GA, AFF_GM, AFF_SA, #endif on char.cpp Search for m_afAffectFlag.Set(AFF_YMIR); And replace it with this #ifdef ENABLE_CUSTOM_TAG_EFFECTS const char* name = GetName(); // This will only search for the first 4 characters if u want more for tags such as [DEV] you will need to increase this values char result[5]; strncpy(result, name, 4); result[4] = '\0'; // This is where you change the tag const char *tagDictionary[] = { "[GM]", "[SA]", "[GA]" }; // This is where you change the effect name to what you put on affect.h const EAffectBits nameDictionary[] = { AFF_GM, AFF_SA, AFF_GA }; int tagIndex = -1; for (int i = 0; i < sizeof(tagDictionary) / sizeof(tagDictionary[0]); ++i) { if (strcmp(result, tagDictionary[i]) == 0) { tagIndex = i; break; } } if (tagIndex != -1) { m_afAffectFlag.Set(nameDictionary[tagIndex]); } else{ m_afAffectFlag.Set(AFF_YMIR); } #endif On client side locale_inc.h #define ENABLE_CUSTOM_TAG_EFFECTS InstanceBaseEffect.cpp On CInstanceBase::__SetAffect( Search case AFFECT_YMIR: Add below #ifdef ENABLE_CUSTOM_TAG_EFFECTS case AFFECT_GA: case AFFECT_GM: case AFFECT_SA: #endif InstanceBase.h Search for AFFECT_NUM Add before #ifdef ENABLE_CUSTOM_TAG_EFFECTS AFFECT_GA, // 50 AFFECT_GM, // 51 AFFECT_SA, // 52 #endif InstanceBase.cpp On CInstanceBase::IsGameMaster() Add before return false #ifdef ENABLE_CUSTOM_TAG_EFFECTS if (m_kAffectFlagContainer.IsSet(AFFECT_GA)) return true; if (m_kAffectFlagContainer.IsSet(AFFECT_GM)) return true; if (m_kAffectFlagContainer.IsSet(AFFECT_SA)) return true; #endif PythonCharacterModule.cpp Add next to the others #ifdef ENABLE_CUSTOM_TAG_EFFECTS PyModule_AddIntConstant(poModule, "AFFECT_GA", CInstanceBase::AFFECT_GA); PyModule_AddIntConstant(poModule, "AFFECT_GM", CInstanceBase::AFFECT_GM); PyModule_AddIntConstant(poModule, "AFFECT_SA", CInstanceBase::AFFECT_SA); #endif This file is for the Faster Loading System found here on the forum, if u dont have it, you will need to add the effect on client/root/playersettingsmodule.py, i wont provide a code for cuz i dont really have a way to test it, but shouldn't be that hard PythonPlayerSettingsModule.cpp Find {pkBase.EFFECT_REFINED + 1 Before add #ifdef ENABLE_CUSTOM_TAG_EFFECTS {pkBase.EFFECT_AFFECT + CInstanceBase::AFFECT_GA, "Bip01", "d:/ymir work/effect/team_ranks/ga.mse"}, {pkBase.EFFECT_AFFECT + CInstanceBase::AFFECT_GM, "Bip01", "d:/ymir work/effect/team_ranks/gm.mse"}, {pkBase.EFFECT_AFFECT + CInstanceBase::AFFECT_SA, "Bip01", "d:/ymir work/effect/team_ranks/sa.mse"}, #endif Lastly, on client you will need to add ashika files where you want (in my case i added to etc/ymir work/effect/team_ranks/ and duplicate the gm.mse file (in this case 2 times), change the name to ga.mse and sa.mse, then will open them and at the end of the file there is going to be this line List TextureFiles { "gamemaster.tga" } change them acording to the effect you want, on this case, for GA is gameadmin.tga, for GM is gamemaster.tga and for SA is serveradmin.tga. And thats all, hope you all like it!
  5. M2 Download Center Download Here ( Internal ) Download Here ( GitHub ) Prepared src packages: *Granny 2.11.8 *libjpeg-9a *Python-2.7 *Crypto++ 8.4.0 *DevIL-1.6.5 *lzo-2.10 Archive password: black
  6. DISCLAIMER: maybe one or two people will recognize this topic from a different community, saying "hey, you are not the original author". I am not, but I am a co-worker of his and have the permission to do so. This topic wants to be a study case of one of the most basic mechanics in Metin: autoattacks and the in game entities' sync position. The discussion will be divided like this: 1. Analysis on the idea and implementation of the synchronization between clients and management of the attacks 1.1 Design problems and bugs in the code 1.2 Consideration about some derived "mechanics" (fly, rolling dagger skill) 2. Challenges faced when working on an highly customized revision of Metin2 3. My approach on how to fix the missing implementation done in 2009 3.1 Result 3.2 Benefits 4. Possible expansions Chapter 1: Movements and Attacks on Metin2 Metin gives charge to the entire flow logic of attacks and synchronization of the clients, to the client itself. The server is merely there and rarely intervenes to validate and force the position of the players at regular intervals (so the the clients actually know the positions of the players and not at random coords). Every client is notified of basic info, such as "I am 123 and I started attacking in this direction" or "I am 123 and I hit 456" or yet "I am 456, I am standing still, here" and every client executes, locally, with his infos on the state of the game (such as 123 was at coords x,y whereas 456 was at coords x2,y2) the animations and displacements, modifying the local state of the characters (after the hit, 456 will be at the coords x3,y3 whereas 123 moved to coords x+1,y+1). Sometimes, precisely 300ms, every client sends the server his state and the server synchronizes the other clients to have something similar to a stable and solid playable game. So, here a first overview of the logic Metin2 has been using for more than 15 years: 123's client starts wielding the sword and sends to the server the HEADER_CG_CHARACTER_MOVE with argument FUNC_COMBO Server broadcasts the packet to all the nearby clients All the clients receive the packet and show 123 wielding the sword 123's client hits 456 pushing it down on a 10m distance and sends the HEADER_CG_ATTACK packet to the server, saying it hit 456 to then process the dmg During this step, the server will give "ownership" to 123 of 456 (establishing that 123 is attacking 456) It will send all the clients the info that 123 is the only player that can push and edit the position of 456 123's client will gain the coords at which 456 will arrive after standing up and keeps them in memory Meanwhile, in 456's client, 123 eventually will hit 456 and it will push him down on a 10m distance After a maximum of 300ms, 123's client will send the position of all the entities it has "ownership" to the server, with the HEADER_CG_SYNC_POSITION packet, making few controls and broadcast the new position to all the clients, even 456's All the clients will pull the characters involved to the indicated coords, which can be corrected or not This process starts forcing the movement and eventually the KNOCK_BACK of the characters to the coords indicated by 123 Eventually, when 456 ends the STANDUP animation, the client will send the HEADER_CG_CHARACTER_MOVE packet with argument FUNC_WAIT communicating again the definitive position of 456 to all the clients This flow has the advantage that all the clients process locally all the in-between actions, therefore it looks "fast paced", just because the server rarely intervenes and supposedly the clients start from the same conditions and they arrive at the same results. One of the biggest con, though, is that the server has very little info about the real state of the game, therefore it can do very little to do something about malicious declaration by one client. Let's get more into it. Chapter 1.1: Design problems and bugs in the code First critic by the system designed by YMIR is that, as already said above, the server has not much information on the state of the clients. I wanna point out: The server only knows the initial position of the players at the start of the flow: before 123 attacks and 456 gets pushed The server will be notified about the new position of 123 and 456 only at the end of the HEADER_CG_SYNC_POSITION packet, which will immediately move the character to the indicated coords Everything that happens in-between these 2 moments is unknown to the server and can't perform any meaningful control on the attacks or coords where 456 gets pushed For example, the server could see 456 to the initial coords, but on the client's side it already moved 4 meters away from the third sword hit and when the final knock back arrives, the server will see a potential illegal movement (third sword hit + fourth) The server, during this process, can only see two states: initial and final, making it completely stupid. Taking aside the security flaw - that we saw paid very well during these years - an acceptable solution would be if the process would be consistent and functional. Unfortunately, it's not like that, at all. The first problem that it inevitably occurs is that, if for some reason, the initial states of the clients are not 100% the same, they will compute different movements, animations and angles. In this picture we can see how a miniscule difference on the B's initial position can change its final destination, rendering the interaction between A and B, kind of interesting, where you can see getting damaged while thinking to be safe. Despite thinking this is a remote situation, it's actually pretty common. Remember that first, HEADER_CG_CHARACTER_MOVE packet is sent first, with argument FUNC_COMBO to communicate the beginning of a combo and eventually further attacks and the new position. If the characters are very close to one another, the attacker might send HEADER_CG_CHARACTER_MOVE, HEADER_CG_ATTACK and HEADER_CG_SYNC_POSITION before that who gets hit even started the attack animation, forcing a little movement and a KOCK_BACK before the sword hits the character in the victim's client. It's the inevitable that until the attacker doesn't send HEADER_CG_SYNC_POSITION, the states that the players see could differ massively and when it actually arrives to the server to align everything, the players find themselves sucked, pushed or slid in positions where they didn't think to be, giving the PvP that Metin2's touch we all (don't) love. More players interact with each other, the more the imprecision rises and the more drastic the forced movements have to be made to reset the synchrony. To make the clients reactive, the developers decided to force the movements and the KNOCK_BACKs before the packet HEADER_CG_SYNC_POSITION is sent and broadcasted: In the function void CPythonPlayerEventHandler::OnHit(UINT uSkill, CActorInstance& rkActorVictim, BOOL isSendPacket) the synchronization is forced with rkActorVictim.TEMP_Push(kVictim.m_lPixelX, kVictim.m_lPixelY); All good for now, if it wasn't for the fact that the coords synchronized are obtained from rkActorVictim.NEW_GetLastPixelPositionRef a function, which calls GetBlendingPosition that does a simple thing: void CActorInstance::GetBlendingPosition(TPixelPosition * pPosition) { if (m_PhysicsObject.isBlending()) { m_PhysicsObject.GetLastPosition(pPosition); pPosition->x += m_x; pPosition->y += m_y; pPosition->z += m_z; } else { pPosition->x = m_x; pPosition->y = m_y; pPosition->z = m_z; } } Basically, it returns the current coords if the character is standing still, or the current coords + m_PhysicsObject.GetLastPosition(pPosition); if the character is getting pushed. This mystic class called CPhysicsObject is responsible of the movements that occur on Metin. It gets told how much an entity is pushed, in what direction and the time of it, to then move the entity. The bug I found was that using m_PhysicsObject.GetLastPosition(pPosition), which returns the whole movement that the entity has to make, GetBlendingPosition doesn't return the final position of the entity, but the current coords + the movement. Despite this looks correct, it isn't when this function is called after the entity moved a little. In that case the returned value will be a little bit further of the destination initially expected. Chapter: Consideration on some derived mechanics (fly, rolling dagger skill) When eventually the positions between the two clients are synchronized, the function that processes the packet HEADER_CG_SYNC_POSITION void CActorInstance::__Push(int x, int y) { if (IsResistFallen()) return; const D3DXVECTOR3& c_rv3Src=GetPosition(); const D3DXVECTOR3 c_v3Dst=D3DXVECTOR3(x, -y, c_rv3Src.z); const D3DXVECTOR3 c_v3Delta=c_v3Dst-c_rv3Src; const int LoopValue = 100; const D3DXVECTOR3 inc=c_v3Delta / LoopValue; D3DXVECTOR3 v3Movement(0.0f, 0.0f, 0.0f); IPhysicsWorld* pWorld = IPhysicsWorld::GetPhysicsWorld(); if (!pWorld) { return; } for(int i = 0; i < LoopValue; ++i) { if (pWorld->isPhysicalCollision(c_rv3Src + v3Movement)) { ResetBlendingPosition(); return; } v3Movement += inc; } SetBlendingPosition(c_v3Dst); const TPixelPosition& kPPosLast2 = NEW_GetLastPixelPositionRef(); if (!IsUsingSkill()) { int len=sqrt(c_v3Delta.x*c_v3Delta.x+c_v3Delta.y*c_v3Delta.y); if (len>150.0f) { InterceptOnceMotion(CRaceMotionData::NAME_DAMAGE_FLYING); PushOnceMotion(CRaceMotionData::NAME_STAND_UP); } } } uses the function void SetBlendingPosition(const TPixelPosition & c_rPosition, float fBlendingTime = 1.0f) which internally says to the client to make a movement and distribute it on a second timespan. During this time, the character enters a state - __IsSyncing() - in which it cannot move at all if not using an ability. This creates that phenomenon called "Fly", where the character slowly slides and can't either move or attack. Basically the Fly is the attempt, from the client, to synchronize his state with other clients. This forces the character in a slow movement in his position and assures that the state can't be changed, blocking every action. If you want my opinion, it's ok for a sword hit to block a character, to give the possibility to perform a physical combo. This is though a mechanic that should be controlled by the developers who, for example, put half a second of "stun" to the hits instead of being a result of the synchronization process with the other clients. More fascinating is the bug with the function GetBlendingPosition: during the execution of the Rolling Dagger skill, if the player is already in a state of IsPushing (it's getting pushed by the fourth sword hit) during the execution of the function OnHit(UINT uSkill, CActorInstance& rkActorVictim, BOOL isSendPacket) a new TEMP_Push will be performed, with the coordinates that, presumably, represent the point of arrival of the character. If this is true for the first hit of the skill, during the second, the victim will already be moved a little further. At this point, the coords returned by GetBlendingPosition are not the ones of the fourth sword hit but that plus something. The player will be then moved a little bit further and another KNOCK_BACK animation is triggered. I don't know if the developers deliberately introduced this dynamic, or if it was a happy accident, but once again I believe that if an ability like Rolling Dagger can bounce a player, this should be coded with specific parameters (external force? refresh push?), but definitely not with an interaction not so visible and counterintuitive that uses a logic most probably bugged. 2. Challenges found with an highly customized revision of Metin2 If you are people who intend to do significant changes to, maybe the skill system, pvp or want to develop a monitoring system for hacks and cheats server side, the current state makes an hard work even harder. The server cannot contribute much to the management of all the states and this is a problem for all those systems based on the precision of the characters' positions during the interactions of the player with the game. Every attempt to make the client more reactive and precise, clashes inevitably with mechanics born from weird and casual interactions of the entities in the client like the "Fly"'s case or Rolling Dagger's that, while BUGS, are still part of the game for years, therefore they must be preserved or emulated 3. My approach on how to resolve the lacks of the implementation done in 2009 My implementation is strongly linked to UniversalElements files (my server) therefore are not 1:1 applicable to most of the existing servers. I will not post much code (it's not a release) but I will describe how to arrive to a similar result and the logic who brought me to determine my choices. Briefly, short said it will be the following: Expansion of the packet HEADER_CG_ATTACK to better describe the action Broadcast of HEADER_CG_ATTACK of all the clients in real time, to fix immediately the state server-side state implementation emulating the movements of the entities in real time Usage of HEADER_CG_SYNC_POSITION only to rollback the state in case wrong info sent from a client are present sent (to cancel an illegal action) Fix of GetBlendingPosition These simple edits look foregone, so much that the only answer to the question "why was it not designed like this from the beginning" was that, at the time, the servers were not able to maintain such an updated state and the internet connections were not enough for large packets used to the frequency of the HEADER_CG_ATTACK one. Therefore, the new logic will appear like this: 123's client starts wielding and sends the server the HEADER_CG_CHARACTER_MOVE packet with argument FUNC_COMBO Server broadcasts the packet to all the clients nearby All the clients receive the packet and show 123 wielding the sword 123's client hit 456 pushing it on a distance of 10 and send the HEADER_CG_ATTACK packet to the server, saying it hit 456 so that it can process the dmg The packet contains info like 456's initial position (with double precision to be exact to the millimeter), timestamp of the attack, which part of the combo generated the hit, duration of the movement in ms The server validates the packet HEADER_CG_ATTACK, that processes the damage and broadcasts the information to the clients Furthermore the server initializes the emulation of the movement from the initial coord to the 456 final's one. Meanwhile in 456's client, 123 eventually will hit 456 making it fall at 10m of distance When 456's client receives the HEADER_CG_ATTACK packet broadcasted by 123, it corrects the internal state whit the new on-the-fly info For example if the client already processed the movement and the KNOCK_BACK, the trajectory will be corrected with the coords validated by the server So, the synchronization corrects the local execution of the client and overwrites it (whether or not the packet HEADER_CG_ATTACK arrives before or after the sword hit in the observer's clients) The big difference though, is that the server keeps an updated state of every entity at every attack and during the execution of the movement. It becomes then able to perform checks, edits and corrections on the data coming from the attacker with high precision. To give an idea to the edits, in the server I added a state that can be executed in parallel in FCM.cpp // Update void CFSM::Update() { // Check New State if(m_pNewState) { // Execute End State m_pCurrentState->ExecuteEndState(); // Set New State m_pCurrentState = m_pNewState; m_pNewState = 0; // Execute Begin State m_pCurrentState->ExecuteBeginState(); } // Check New State if(m_pNewConcurrentState) { // Execute End State if (m_pConcurrentState) m_pConcurrentState->ExecuteEndState(); // Set New State m_pConcurrentState = m_pNewConcurrentState; m_pNewConcurrentState = 0; // Execute Begin State m_pConcurrentState->ExecuteBeginState(); } if (bStopConcurrent && m_pConcurrentState) { // Execute End State m_pConcurrentState->ExecuteEndState(); m_pConcurrentState = 0; bStopConcurrent = false; } // Execute State m_pCurrentState->ExecuteState(); if (m_pConcurrentState) { m_pConcurrentState->ExecuteState(); } } This enables me to use a fourth state in the CHARACTER class CHARACTER::CHARACTER() { m_stateIdle.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateIdle, &CHARACTER::EndStateEmpty); m_stateMove.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateMove, &CHARACTER::EndStateEmpty); m_stateBattle.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateBattle, &CHARACTER::EndStateEmpty); m_stateSyncing.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateSyncing, &CHARACTER::EndStateEmpty); Initialize(); } void CHARACTER::StateSyncing() { if (IsStone() || IsDoor()) { StopConcurrentState(); return; } DWORD dwElapsedTime = get_dword_time() - m_dwSyncStartTime; float fRate = (float) dwElapsedTime / (float) m_dwSyncDuration; if(fRate > 1.0f) fRate = 1.0f; int x = (int) ((float) (m_posDest.x - m_posStart.x) * fRate + m_posStart.x); int y = (int) ((float) (m_posDest.y - m_posStart.y) * fRate + m_posStart.y); Sync(x, y); if(1.0f == fRate) { StopConcurrentState(); } } /////////////////// ////// To use to gradually "move" the entity on the desired position while it can do whatever it wants (to use when receiving HEADER_CG_ATTACK) bool CHARACTER::BlendSync(long x, long y, unsigned int unDuration) { // TODO distance check required // No need to go the same side as the position (automatic success) if(GetX() == x && GetY() == y) return false; m_posDest.x = m_posStart.x = GetX(); m_posDest.y = m_posStart.y = GetY(); m_posDest.x = x; m_posDest.y = y; m_dwSyncStartTime = get_dword_time(); m_dwSyncDuration = unDuration; m_dwStateDuration = 1; ConcurrentState(m_stateSyncing); return true; } The new TPacketCGAttack will present iself like this typedef struct command_attack { BYTE bHeader; BYTE bType; DWORD dwVID; BOOL bPacket; LONG lSX; LONG lSY; LONG lX; LONG lY; float fSyncDestX; float fSyncDestY; DWORD dwBlendDuration; DWORD dwComboMotion; DWORD dwTime; } TPacketCGAttack; with its counter part TPacketGCAttack typedef struct packet_attack { BYTE bHeader; BYTE bType; DWORD dwAttacakerVID; DWORD dwVID; BOOL bPacket; LONG lSX; LONG lSY; LONG lX; LONG lY; float fSyncDestX; float fSyncDestY; DWORD dwBlendDuration; } TPacketGCAttack; Client side I fixed the inherent logic to GetBlendingPosition and all his variants. To resolve it we must pass to the function that initializes it, not only the displacement, but even the final position. void CPhysicsObject::SetLastPosition(const TPixelPosition& c_rPosition, const TPixelPosition& c_rDeltaPosition, float fBlendingTime) { m_v3FinalPosition.x = float(c_rPosition.x + c_rDeltaPosition.x); m_v3FinalPosition.y = float(c_rPosition.y + c_rDeltaPosition.y); m_v3FinalPosition.z = float(c_rPosition.z + c_rDeltaPosition.z); m_v3DeltaPosition.x = float(c_rDeltaPosition.x); m_v3DeltaPosition.y = float(c_rDeltaPosition.y); m_v3DeltaPosition.z = float(c_rDeltaPosition.z); m_xPushingPosition.Setup(0.0f, c_rDeltaPosition.x, fBlendingTime); m_yPushingPosition.Setup(0.0f, c_rDeltaPosition.y, fBlendingTime); } void CPhysicsObject::GetFinalPosition(TPixelPosition* pPosition) { pPosition->x = (m_v3FinalPosition.x); pPosition->y = (m_v3FinalPosition.y); pPosition->z = (m_v3FinalPosition.z); } void CPhysicsObject::GetDeltaPosition(TPixelPosition* pPosition) { pPosition->x = (m_v3DeltaPosition.x); pPosition->y = (m_v3DeltaPosition.y); pPosition->z = (m_v3DeltaPosition.z); } and fixing the function void CActorInstance::GetBlendingPosition(TPixelPosition * pPosition) { if (m_PhysicsObject.isBlending()) { m_PhysicsObject.GetFinalPosition(pPosition); } else { GetPixelPosition(pPosition); } } The management of the new HEADER_CG_CHARACTER_ATTACK will present itself like this: bool CPythonNetworkStream::RecvCharacterAttackPacket() { TPacketGCAttack kPacket; if (!Recv(sizeof(TPacketGCAttack), &kPacket)) { Tracen("CPythonNetworkStream::RecvCharacterAttackPacket - PACKET READ ERROR"); return false; } if (kPacket.lX && kPacket.lY) { __GlobalPositionToLocalPosition(kPacket.lX, kPacket.lY); } __GlobalPositionToLocalPosition(kPacket.lSX, kPacket.lSY); TPixelPosition tSyncPosition = TPixelPosition{ kPacket.fSyncDestX, kPacket.fSyncDestY, 0 }; m_rokNetActorMgr->AttackActor(kPacket.dwVID, kPacket.dwAttacakerVID, kPacket.lX, kPacket.lY, tSyncPosition, kPacket.dwBlendDuration); return true; } void CNetworkActorManager::AttackActor(DWORD dwVID, DWORD dwAttacakerVID, LONG lDestPosX, LONG lDestPosY, const TPixelPosition& k_pSyncPos, DWORD dwBlendDuration) { std::map<DWORD, SNetworkActorData>::iterator f = m_kNetActorDict.find(dwVID); if (m_kNetActorDict.end() == f) { return; } SNetworkActorData& rkNetActorData = f->second; if (k_pSyncPos.x && k_pSyncPos.y) { CInstanceBase* pkInstFind = __FindActor(rkNetActorData); if (pkInstFind) { const bool bProcessingClientAttack = pkInstFind->ProcessingClientAttack(dwAttacakerVID); pkInstFind->ServerAttack(dwAttacakerVID); // if already blending, update if (bProcessingClientAttack && pkInstFind->IsPushing() && pkInstFind->GetBlendingRemainTime() > 0.15) { pkInstFind->SetBlendingPosition(k_pSyncPos, pkInstFind->GetBlendingRemainTime()); } else { // otherwise sync //pkInstFind->SCRIPT_SetPixelPosition(k_pSyncPos.x, k_pSyncPos.y); pkInstFind->NEW_SyncPixelPosition(k_pSyncPos, dwBlendDuration); } } rkNetActorData.SetPosition(long(k_pSyncPos.x), long(k_pSyncPos.y)); } } ////////////////////// //// Semplified __Push and called only by AttackActor void CActorInstance::__Push(const TPixelPosition& c_rkPPosDst, unsigned int unDuration) { DWORD dwVID = GetVirtualID(); Tracenf("VID %d SyncPixelPosition %f %f", dwVID, c_rkPPosDst.x, c_rkPPosDst.y); if (unDuration == 0) unDuration = 1000; const D3DXVECTOR3& c_rv3Src = GetPosition(); const D3DXVECTOR3 c_v3Delta = c_rkPPosDst - c_rv3Src; SetBlendingPosition(c_rkPPosDst, float(unDuration) / 1000); if (!IsUsingSkill() && !IsResistFallen()) { int len = sqrt(c_v3Delta.x * c_v3Delta.x + c_v3Delta.y * c_v3Delta.y); if (len > 150.0f) { InterceptOnceMotion(CRaceMotionData::NAME_DAMAGE_FLYING); PushOnceMotion(CRaceMotionData::NAME_STAND_UP); } } } ////////////////////// // To understand if the client already started processing the attack or it must be initialized with a new sync_pixelposition: void CInstanceBase::ServerAttack(DWORD dwVID) { m_GraphicThingInstance.ServerAttack(dwVID); } bool CInstanceBase::ProcessingClientAttack(DWORD dwVID) { return m_GraphicThingInstance.ProcessingClientAttack(dwVID); } // client attack decreases the count void CActorInstance::ClientAttack(DWORD dwVID) { if (m_mapAttackSync.find(dwVID) == m_mapAttackSync.end()) { m_mapAttackSync.insert(std::make_pair(dwVID, -1)); } else { if (m_mapAttackSync[dwVID] == 1) { m_mapAttackSync.erase(dwVID); return; } m_mapAttackSync[dwVID]--; } } // server attack increases void CActorInstance::ServerAttack(DWORD dwVID) { if (m_mapAttackSync.find(dwVID) == m_mapAttackSync.end()) { m_mapAttackSync.insert(std::make_pair(dwVID, 1)); } else { if (m_mapAttackSync[dwVID] == -1) { m_mapAttackSync.erase(dwVID); return; } m_mapAttackSync[dwVID]++; } } bool CActorInstance::ProcessingClientAttack(DWORD dwVID) { return m_mapAttackSync.find(dwVID) != m_mapAttackSync.end() && m_mapAttackSync[dwVID] < 0; } // bool CActorInstance::ServerAttackCameFirst(DWORD dwVID) { return m_mapAttackSync.find(dwVID) != m_mapAttackSync.end() && m_mapAttackSync[dwVID] > 0; } New management of the hit which processes locally the movement, collect the infos, and send the packet to the server: struct BlendingPosition { D3DXVECTOR3 source; D3DXVECTOR3 dest; float duration; }; void CActorInstance::__ProcessDataAttackSuccess(const NRaceData::TAttackData & c_rAttackData, CActorInstance & rVictim, const D3DXVECTOR3 & c_rv3Position, UINT uiSkill, BOOL isSendPacket) { if (NRaceData::HIT_TYPE_NONE == c_rAttackData.iHittingType) return; InsertDelay(c_rAttackData.fStiffenTime); BlendingPosition sBlending; memset(&sBlending, 0, sizeof(sBlending)); sBlending.source = rVictim.NEW_GetCurPixelPositionRef(); if (__CanPushDestActor(rVictim) && c_rAttackData.fExternalForce > 0.0f) { const bool bServerAttackAlreadyCame = rVictim.ServerAttackCameFirst(GetVirtualID()); rVictim.ClientAttack(GetVirtualID()); if (!bServerAttackAlreadyCame) { __PushCircle(rVictim); // VICTIM_COLLISION_TEST const D3DXVECTOR3& kVictimPos = rVictim.GetPosition(); rVictim.m_PhysicsObject.IncreaseExternalForce(kVictimPos, c_rAttackData.fExternalForce); rVictim.GetBlendingPosition(&(sBlending.dest)); sBlending.duration = rVictim.m_PhysicsObject.GetRemainingTime(); // VICTIM_COLLISION_TEST_END } } // Invisible Time rVictim.m_fInvisibleTime = CTimer::Instance().GetCurrentSecond() + (c_rAttackData.fInvisibleTime - __GetInvisibleTimeAdjust(uiSkill, c_rAttackData)); // Stiffen Time rVictim.InsertDelay(c_rAttackData.fStiffenTime); // Hit Effect D3DXVECTOR3 vec3Effect(rVictim.m_x, rVictim.m_y, rVictim.m_z); // #0000780: [M2KR] ¼ö·æ Ÿ°Ý±¸ ¹®Á¦ extern bool IS_HUGE_RACE(unsigned int vnum); if (IS_HUGE_RACE(rVictim.GetRace())) { vec3Effect = c_rv3Position; } const D3DXVECTOR3 & v3Pos = GetPosition(); float fHeight = D3DXToDegree(atan2(-vec3Effect.x + v3Pos.x,+vec3Effect.y - v3Pos.y)); // 2004.08.03.myevan.ºôµùÀ̳ª ¹®ÀÇ °æ¿ì Ÿ°Ý È¿°ú°¡ º¸ÀÌÁö ¾Ê´Â´Ù if (rVictim.IsBuilding()||rVictim.IsDoor()) { D3DXVECTOR3 vec3Delta=vec3Effect-v3Pos; D3DXVec3Normalize(&vec3Delta, &vec3Delta); vec3Delta*=30.0f; CEffectManager& rkEftMgr=CEffectManager::Instance(); if (m_dwBattleHitEffectID) rkEftMgr.CreateEffect(m_dwBattleHitEffectID, v3Pos+vec3Delta, D3DXVECTOR3(0.0f, 0.0f, 0.0f)); } else { if(c_rAttackData.isEnemy == 0) { if(rVictim.IsEnemy() || rVictim.IsPC() || rVictim.IsBoss() || rVictim.IsStone()) { return; } } else { CEffectManager& rkEftMgr=CEffectManager::Instance(); if (m_dwBattleHitEffectID) rkEftMgr.CreateEffect(m_dwBattleHitEffectID, vec3Effect, D3DXVECTOR3(0.0f, 0.0f, fHeight)); if (m_dwBattleAttachEffectID) rVictim.AttachEffectByID(0, NULL, m_dwBattleAttachEffectID); } } if (rVictim.IsBuilding()) { // 2004.08.03.ºôµùÀÇ °æ¿ì Èçµé¸®¸é ÀÌ»óÇÏ´Ù } else if (rVictim.IsStone() || rVictim.IsDoor()) { __HitStone(rVictim); } else { /////////// // Motion bool ForceHitGOOD = rVictim.IsPC() && (rVictim.IsKnockDown() || rVictim.__IsStandUpMotion()); if (NRaceData::HIT_TYPE_GOOD == c_rAttackData.iHittingType || (TRUE == rVictim.IsResistFallen())) { __HitGood(rVictim); } else if (NRaceData::HIT_TYPE_GREAT == c_rAttackData.iHittingType) { if(c_rAttackData.isEnemy == 0) { if(rVictim.IsEnemy() || rVictim.IsPC() || rVictim.IsBoss() || rVictim.IsStone()) { return; } else { __HitGreate(rVictim, uiSkill, c_rAttackData.isEnemy); } } else { __HitGreate(rVictim, uiSkill, c_rAttackData.isEnemy); } } else { TraceError("ProcessSucceedingAttacking: Unknown AttackingData.iHittingType %d", c_rAttackData.iHittingType); } } __OnHit(uiSkill, rVictim, isSendPacket, &sBlending); } void CPythonPlayerEventHandler::OnHit(UINT uSkill, CActorInstance& rkActorVictim, BOOL isSendPacket, BlendingPosition* sBlending) { DWORD dwVIDVictim=rkActorVictim.GetVirtualID(); CPythonCharacterManager::Instance().AdjustCollisionWithOtherObjects(&rkActorVictim); BlendingPosition kBlendingPacket; memset(&kBlendingPacket, 0, sizeof(kBlendingPacket)); kBlendingPacket.source = rkActorVictim.NEW_GetCurPixelPositionRef(); if (rkActorVictim.IsPushing()) { kBlendingPacket.dest = rkActorVictim.NEW_GetLastPixelPositionRef(); kBlendingPacket.duration = sBlending->duration; } // Update Target CPythonPlayer::Instance().SetTarget(dwVIDVictim, FALSE); // Update Target //#define ATTACK_TIME_LOG #ifdef ATTACK_TIME_LOG static std::map<DWORD, float> s_prevTimed; float curTime = timeGetTime() / 1000.0f; bool isFirst = false; if (s_prevTimed.end() == s_prevTimed.find(dwVIDVictim)) { s_prevTimed[dwVIDVictim] = curTime; isFirst = true; } float diffTime = curTime-s_prevTimed[dwVIDVictim]; if (diffTime < 0.1f && !isFirst) { TraceError("ATTACK(SPEED_HACK): %.4f(%.4f) %d", curTime, diffTime, dwVIDVictim); } else { TraceError("ATTACK: %.4f(%.4f) %d", curTime, diffTime, dwVIDVictim); } s_prevTimed[dwVIDVictim] = curTime; #endif CPythonNetworkStream& rkStream=CPythonNetworkStream::Instance(); rkStream.SendAttackPacket(uSkill, dwVIDVictim, isSendPacket, kBlendingPacket); } bool CPythonNetworkStream::SendAttackPacket(UINT uMotAttack, DWORD dwVIDVictim, BOOL bPacket, BlendingPosition& sBlending) { NANOBEGIN if (!__CanActMainInstance()) return true; CPythonCharacterManager& rkChrMgr = CPythonCharacterManager::Instance(); CInstanceBase* pkInstMain = rkChrMgr.GetMainInstancePtr(); #ifdef ATTACK_TIME_LOG static DWORD prevTime = timeGetTime(); DWORD curTime = timeGetTime(); TraceError("TIME: %.4f(%.4f) ATTACK_PACKET: %d TARGET: %d", curTime/1000.0f, (curTime-prevTime)/1000.0f, uMotAttack, dwVIDVictim); prevTime = curTime; #endif TPacketCGAttack kPacketAtk; kPacketAtk.header = HEADER_CG_ATTACK; kPacketAtk.bType = uMotAttack; kPacketAtk.dwVictimVID = dwVIDVictim; kPacketAtk.bPacket = bPacket; kPacketAtk.lX = (long)sBlending.dest.x; kPacketAtk.lY = (long)sBlending.dest.y; kPacketAtk.lSX = (long)sBlending.source.x; kPacketAtk.lSY = (long)sBlending.source.y; kPacketAtk.fSyncDestX = sBlending.dest.x; // sources and dest are normalized with both coordinates positive // since fSync are ment to be broadcasted to other clients, the Y has to preserve the negative coord kPacketAtk.fSyncDestY = -sBlending.dest.y; kPacketAtk.dwBlendDuration = (unsigned int) (sBlending.duration *1000); kPacketAtk.dwComboMotion = pkInstMain->GetComboMotion(); kPacketAtk.dwTime = ELTimer_GetServerMSec(); if (kPacketAtk.lX && kPacketAtk.lY) __LocalPositionToGlobalPosition(kPacketAtk.lX, kPacketAtk.lY); __LocalPositionToGlobalPosition(kPacketAtk.lSX, kPacketAtk.lSY); if (!SendSpecial(sizeof(kPacketAtk), &kPacketAtk)) { Tracen("Send Battle Attack Packet Error"); return false; } return SendSequence(); } To preserve the behavior of Rolling Dagger, that was based on a bug, it will be necessary to: //// InstanceBase.cpp /// void CInstanceBase::StateProcess() if (eFunc & FUNC_SKILL) .... // NOTICE HERE THE 1!!!!! NEW_UseSkill(1, eFunc & FUNC_SKILL - 1, uArg&0x0f, (uArg>>4) ? true : false); //Tracen("°¡±õ±â ¶§¹®¿¡ ¿öÇÁ °ø°Ý"); } } break; /// void CInstanceBase::MovementProcess() // TODO get skill vnum // NOTICE HERE THE 1!!!!!!!!!! NEW_UseSkill(1, m_kMovAfterFunc.eFunc & FUNC_SKILL - 1, m_kMovAfterFunc.uArg & 0x0f, (m_kMovAfterFunc.uArg >> 4) ? true : false); .... // ActorInstaceBattle.cpp void CActorInstance::__HitGreate(CActorInstance& rVictim, unsigned int uiSkill, bool isEnemy) { // DISABLE_KNOCKDOWN_ATTACK // !!!! uiSkill if (!uiSkill && rVictim.IsKnockDown()) return; if (rVictim.__IsStandUpMotion()) return; // END_OF_DISABLE_KNOCKDOWN_ATTACK float fRotRad = D3DXToRadian(GetRotation()); float fVictimRotRad = D3DXToRadian(rVictim.GetRotation()); D3DXVECTOR2 v2Normal(sin(fRotRad), cos(fRotRad)); D3DXVECTOR2 v2VictimNormal(sin(fVictimRotRad), cos(fVictimRotRad)); .... and add external force to the msa of the animation to give Rolling Dagger the ability to push therefore making the enemy jump. This way even without the daggers combo it will be possible to do a double or triple rolling. If you wanna make the daggers combo necessary, you can create a new property in the msa like "refresh_displacement" that says to the launcher, in case of a shift happening, the animation must refresh the shifting and knock_back. Chapter 3.1: Result After having applied these structural changes and implementations, you will obtain a revision where the clients will process attacks and movements client side to gain immediate reactivity and the server will perform a validation and synchronization of the states in real-time. The server will act as a referee, while the clients will just execute since they will give the server all the necessary infos to referee. This approach connects the fluidity of the execution of the client with a millimetric real time synchronization of all the clients connected, presenting a more reactive and precise pvp experience. This approach also allows for multiple entities to control the push/movements of another entity at the same time. Therefore it would be possible to do stuff like performing a rolling dagger after the combo of one of your guildmates, or pushing another character already in mid-air. Chapter 3.2: Benefits Other than the precision and reactivity of the synchronization, this approach gives complete control to the server on the actions of the clients say to have done: timestamp, comboArgument manage to detect speed and combohack, starting and final coords are able to detect antifly and rangehacks etc Therefore, in case of malicious intents, the server has the ability to reset to everyone the initial positions. Moreover these changes remove many "obscure" aspects of the combat system. Fly, Rolling Dagger, Falling duration, Flame Strike etc. will all be parameters that the developers and maintainers can manage and orchestrate, not something that happnes because it happens. The Metin2 combat system will, finally, become configurable, not something given for granted and unchangeable. Chapter 4: Possible expansions At this point, changes and integrations are over. Serverside anticheat systems, implementation of a collisions' engine and server side animation to reproduce, on the server, the client's actions, complete removal of the fly.. everything that includes the interaction and management of the synchronization system of the characters between clients and state of the game are finally possible.
  7. Hello guys, again small thing for beginners, if you would like to increase or decrease range of sharing EXP in party you have to find file called "party.h" in your server's source On the top of "party.h" you should find: enum // unit : minute { PARTY_ENOUGH_MINUTE_FOR_EXP_BONUS = 60, // 파티 결성 후 60분 후 부터 추가 경험치 보너스 PARTY_HEAL_COOLTIME_LONG = 60, PARTY_HEAL_COOLTIME_SHORT = 30, PARTY_MAX_MEMBER = 8, PARTY_DEFAULT_RANGE = 5000, }; If you want to increase the range of sharing experience in party you should change "PARTY_DEFAULT_RANGE", I recommend to set this to 10000, it gives us many opportunities of exping in party in any location. If you have any questions, I will try to help you.
  8. Hello, This one is for truly beginners - if you would like to deal the same dmg in every gap between your ninja archer and your target you have to find file: battle.cpp Find: int CalcArrowDamage(LPCHARACTER pkAttacker, LPCHARACTER pkVictim, LPITEM pkBow, LPITEM pkArrow, bool bIgnoreDefense) Under this you have to change "iPercent" int iPercent = 100;
  9. M2 Download Center Download Here ( Internal ) [Hidden Content] [Hidden Content] VT: [Hidden Content]
  10. Download Metin2 Download [Hidden Content] It's working without serverside.
  11. Download Metin2 Download or Github Time of loading each fragment of the game before and after
  12. M2 Download Center Download Here ( Internal ) Download Here ( GitHub ) [Hidden Content] Reversed from 22.2.7.0 Official Binary
  13. This itemshop is an open source project written by CYN3 with the assistance of the sura_head community. Special thanks to: CYN3 Amas KaiaProductions Installation guide: [Hidden Content]
  14. Download Alternative download links → Github Hi, This is all interface about SungmaHee Tower Like official servers and the first floor like Official Servers, In this files we have 2k or 3k LOC, maybe more, I'm tired and I don't have information about all floor's tower, but if you want complete this system, you can contact with some-developer and providing the information about the floors or modifying all floors like your concept for Metin2, you'll be a nice dungeon. I already did the hardest part. You can extract all visual part with the official patchs from this forum. This dungeon is Full C++, Python and SQL. PLEASE: Not more messages for my person if you don't want to pay money, I only sell systems "not personal systems" you need understand about my time, my help, code or resolve some problem, needs time. If you want something (not offlineshop, this world has different options with this system) you can contact me but, you will need understand that I will charge you. I'm a nice person (I think), but this world needs money and if I invest time in one project, I will cancel other projects and is overmoney.
  15. M2 Download Center Download Here ( Internal ) Hi there Devs, I would like to share my "little" system. If you aren't interested in the introduction/preview etc. and you just want to download it and put in to your server, just scroll down until the "[How-To] Set up" subtitle. The story Firstly let me tell this system's story. I've got an idea one year before, that it would be good if the players would be able to put their items into a "global" system where they could see the other player's items, and they could buy it for DC or gold (that time I worked with the latest vanilla core (not with the source)). Then in the following 8 days I made it (it took about 80-90 working hours). Originally the system was created for one of my friend's server. but this server has never started, and nobody used this system. After some mounts I've decided to publish it on the Hungarian forum, because it won't worth to work on it for long hours if nobody uses it and its just collecting dust on my computer. Then I've published it on the 2nd of December, 2014. After some time I've decided to translate it into English and I've got a new idea for a new feature. This feature was: the trade system (I will explain its working later). This idea inspired by one of the players (from a server where this system was able to use). He told me that it would be better if they could set the gold price via an item (what's value is very high). Then with more than 180 working hours (totally) behind my back I'm here. Overview [How-To] Set up Customizing the tradehouse Questions and Answers Notes changelog: 19th of August, 2015: I publicated the tradehouse here. my toDo list: add logging for the system (the released version don't log the actions in the tradehouse) Thanks for reading the topic, if you have any problem/remark feel free to ask it here or write me a PM. Have a good day!
  16. Dear Metin2Dev, I believe that many servers are still using old quests for giving mount bonuses from inventory, which are not bad but not very efficient either. Since I haven't seen anyone share something similar yet, I thought I'd share my solution for getting mount bonuses from the inventory. You need to specify the bonuses in groups, similar to how you specify mob, etc, special drops. After you have set it up and the item is in your inventory, you will receive the bonus you have set. How is a mount's bonus structured? locale/germany/mount_bonus_table.txt: Group MountTest1 { vnum 71224 --Mount item vnum from item_proto (type must be ITEM_QUEST (18)) --You can add maximum 5 bonuses from config (it can be extended) 1 13 5 --13 = APPLY_STUN_PCT 2 14 10 --14 = APPLY_SLOW_PCT 3 15 15 --15 = APPLY_CRITICAL_PCT 4 16 20 --16 = APPLY_PENETRATE_PCT 5 17 25 --17 = APPLY_ATTBONUS_HUMAN } This system works with items whose type is ITEM_QUEST (18). It means that your mount item type must be ITEM_QUEST or it won't give you the bonuses. In the future, i'm planning to extend this system for costume mounts, but you can also do it for yourself. I think this is a good starting point for those are using the old quest solutions to give bonuses from inventory. Tutorial: CommonDefines.h: char.cpp: char.h: char_item.cpp: input_db.cpp: item.cpp: item_manager.cpp: item_manager.h: If you have any ideas on how to make this system better, please leave a comment. I hope this will be useful for you. Best Regards, piktorvik
  17. -1: Very serious common mistake, let's check. Run this query SELECT * FROM `item_proto` WHERE `size` <= '0' If you find something, fix all 'size' or you'll get serious problems with these items. -2: Fix rare crash core with cube (happened and fixxed like this) Go in cube.cpp and find for this log LogManager::instance().CubeLog(ch->GetPlayerID(), ch->GetX(), ch->GetY(), reward_value->vnum, new_item->GetID(), reward_value->count, 1); Replace the log with if (new_item) LogManager::instance().CubeLog(ch->GetPlayerID(), ch->GetX(), ch->GetY(),reward_value->vnum, new_item->GetID(), reward_value->count, 1); else sys_err("Cannot find new_item on CUBE_MAKE!"); -3: config.cpp g_iFullUserCount g_iBusyUserCount These two const will set the STATE_DICT in your serverinfo.py. Edit them as you prefer. BUSY = Warning many users are connected in that channel. FULL = Stop login due to many users in the channel. -4: cmd_gm.cpp Find for: for (int i = 0; i < MAX_PRIV_NUM; ++i) Add the missing braces That's all for now, I'll update this thread if I remember any other useful change. Old threads
  18. M2 Download Center Download Here ( Internal ) Download Here ( GitHub ) This is for those who use the official character select window. There is only the source part. You can check the 2018 root for the pack side.
  19. Download Center Github or M2DL Heyo, this is something i did a while ago, perhaps it will be of use for some. [Hidden Content]
  20. M2 Download Center Download Here ( Internal ) There is a bug about the stealth of the assassin with buffs actived. If the players near the assassin that is using the stealth move the camera zoom , the effects become visible again. here the fix of this problem source : myself Sameone could need to add #include "../UserInterface/Locale_inc.h" at the beginning of the file source/EterBase/StdAfx.h to include the defines in Locale_inc.h ------- EDIT ------ Due to some change on client-source someone may require a small fix to be added to this system: // SEARCH void CEffectInstance::__Initialize() { // ADD #ifdef __ENABLE_STEALTH_FIX__ ReleaseAlwaysHidden(); #endif
  21. Hello! Today I found that in lua the modification of item.set_socket(0, value) does not work. Fixed: int item_set_socket(lua_State* L) { CQuestManager& q = CQuestManager::instance(); if (q.GetCurrentItem() && lua_isnumber(L, 1) && lua_isnumber(L, 2)) { int idx = (int)lua_tonumber(L, 1); int value = (int)lua_tonumber(L, 2); if (idx >= 0 && idx < ITEM_SOCKET_MAX_NUM) q.GetCurrentItem()->SetSocket(idx, value); } return 0; } Original: int item_set_socket(lua_State* L) { CQuestManager& q = CQuestManager::instance(); if (&q == NULL) return 0; LPITEM item = q.GetCurrentItem(); if (item == NULL) return 0; if (item && lua_isnumber(L, 1) && lua_isnumber(L, 2)) { int idx = (int) lua_tonumber(L, 1); int value = (int) lua_tonumber(L, 2); if (idx == NULL) return 0; if (value == NULL) return 0; if (idx >=0 && idx < ITEM_SOCKET_MAX_NUM) item->SetSocket(idx, value); } return 0; } Explanation: If the socket index is 0, it returns and does not run the code... fck logic. if (idx == NULL) return 0;
  22. M2 Download Center Download Here ( Internal ) Download Here ( GitHub ) [Hidden Content]
  23. M2 Download Center Download Here ( Internal )
  24. Hello, I have been hearing about the existence of such an event from time to time. I thought of creating such a solution. I haven't had any problems so far in my tests. To briefly talk about the incident; Some pack files can be opened and edited while the game is open. This may pose a problem in some exceptional cases. Most of the time, changes made due to cache may not be reflected in the game unless the client is reset, but I still thought it wouldn't hurt to prevent this situation. In addition; Nowadays, foxfs etc. methods are used, so you can also consider this as additional security for your game. The codes are completely open to development and customization, you can progress them in any direction you want. I should point out that this method will work more efficiently with autopatcher. So, you also have a job to do here, the codes I share here are the first stage of this system. The necessary adjustments you will make to the autopatcher depending on these codes will be the second stage. To give an example from the working logic; As soon as the pack file named x is edited, the game will close completely, and when it is opened again, autopatcher (if there is a file integrity feature, etc.) will download the original instead of this edited pack file and allow you to log in from the original file. Likewise, if there is any intervention in the pack files again, these operations will be repeated automatically. (This is the idea I suggested, anyone can use it however they want.) Now let's get to the point.. Here's a short video showing it before editing: [Hidden Content] After video: [Hidden Content] [Hidden Content] For those who are curious; The codes run only once when the client is opened and only keep track of the pack files as long as the game (client) is open. In other words, it does not work repeatedly in situations such as teleportation or casting a character.
  25. M2 Download Center Download Here ( Internal ) [Hidden Content] Client side is from 2018 official root. It checks the status of the channel from db and gets the port and addr. It also checks if that map exists in that channel(config). *Info: This is currently disabled on official servers.
×
×
  • 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.