Jump to content

Search the Community

Showing results for tags 'tutorial'.

  • 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. Hello guys, Recently I have been updating my whole workflow and I discovered Ninja. To be concise, Ninja is basically a more modern and lightweight equivalent to "Make" that we (almost) all use. Its motto is simple, avoid being too complex to allow for a faster build & rebuild time, while allowing a human-readable syntax. I will just paraphrase them : "When you are in a edit-compile cycle you want it to be as fast as possible — you want the build system to do the minimum work necessary to figure out what needs to be built immediately. [...] Ninja contains the barest functionality necessary to describe arbitrary dependency graphs. Its lack of syntax makes it impossible to express complex decisions." Long story short, using it, you will basically build slightly faster & rebuild your project even faster. Let's get started! Then you are good! As a rule of thumb, you can use the following commands the same way you use your previous Make commands: Clean your solution: ninja -t clean Build your solution using a number of workers: ninja -j<number_of_workers> Have a nice day!
  2. I can show u today HOW TO update DevIL library "STATIC" if you wanna SHARED library change BUILD_SHARED_LIBS:BOOL=OFF Change: BUILD_SHARED_LIBS:BOOL=ON and src/ILUT, ILU in CMakeList.txt inside have For STATIC library: add_library(ILUT ${ILUT_SRCS} ${ILUT_INC} ${ILUT_RSRC}) For SHARED library: add_library(ILUT SHARED ${ILUT_SRCS} ${ILUT_INC} ${ILUT_RSRC}) How to make video
  3. Hi guys! use a mysql editor (HeidSQL or NavCat) and select your ACCOUNT.ACCOUNT table to crate a trigger script. Run this query: CREATE TRIGGER `account_before_update` BEFORE UPDATE ON `account` FOR EACH ROW BEGIN /* TRIGGER WRITED BY [ADM]Arkatuz (Meleb.Net) */ if (LEFT(NEW.password,1)<>'*') then SET NEW.password = PASSWORD(NEW.password); END if; END Now, your can update PASSWORD column directly of the Mysql Editor. Eg.: your put 'mypasswd123' and the trigger crated go crypt to '*DD33176027C486CECB9B6F770756A1C115A1F659'. This is good to help administer your server without external applications to generate passwords. good lock! ^^
  4. Hello everybody! Today I was with a friend and he needed to download some files directly from Mega on his server and I happened to know a way, I even use it whenever it is necessary to transfer large files to the server, such as backups in tar.gz. The big advantage is that the file transfer speed is very high so it goes faster than doing it via ftp from your computer. I believe it can be useful for many. I'm posting because I didn't find anything in the community teaching how to do it. Basically the method used is the installation of a package called MEGA TOOLS I leave the tutorial: #1 Install the Megatools package ([Hidden Content]) with the command: Then go to the mega file you want to download and get the link. But attention, the link has to be direct to the file that you want to download, it cannot be a folder, because no file will be downloaded, even if it is inside the folder. and type the command: and your file will be downloaded directly to the current directory, you can choose a directory to which you want to download the file, for example: then I hope that was clear and that it is useful for some of you. Sorry if my english is not the best
  5. So I was checking out Aeldra's Discord and apparently their patcher was slow as a snail, which reminded me of the opening of WoM3 back in the day. The typical OVH dedicated server has a bandwidth of 100 Mbps upstream if I remember well, although you can buy more bandwidth (which costs as much as the server itself) and some come with 1 Gbps. Anyway that's a gigaBIT per second which makes around 150 megabytes per second. Pretty good but not enough for a big opening thing where you have hundreds of users expecting to download at 4 Mbps each. NGINX (the one you pay for) includes some load balancing mechanism but we can emulate it with the free one. We are in fact sorta randomly distributing people among a number of servers. So here is how you can just throw money at the problem (if you have this problem you're probably gonna get rich anyway) and just rent more servers or just use a bunch of VPS you have lying around. I'm not going to make lengthy explanations here if u need help you know where to find me and my paypal. So we have our url let's say patch.wom2.org pointing to our main webserver, Here's where the magic happens (http context) split_clients "${remote_addr}" $destination { 50% alpha.patch.wom2.org:8080; 30% bravo.patch.wom2.org:8080; 20% charlie.patch.wom2.org:8080; } Here we have three subdomains pointing at three different servers (any place where u can install nginx will do, you can measure speed at the endpoint with nethogs for example to see which is slower and reduce the percent of requests that are sent there). The OG web server can serve files too (here it's alpha). Do not use Linux if you can avoid it. And do NOT use Apache. FreeBSD is the king when it comes to streaming sry Linux fanboys. The three servers must of course have identical copies of your files (use rsync when updating patches) and the same nginx configuration. Here's the config for the OG server which redirects the user to the previously chosen subdomain when downloading from the pack directory (server context obviously) server { listen 51.84.214.58:80; server_name patch.wom2.org; root /home/www/patch.wom2.org; location /1.1.1.1/ { log_not_found on; return 302 [Hidden Content]; } } Finally here's the config of one of our load balancing server, which in its root folder contains the contents of 1.1.1.1, in fact alpha.patch and patch are in the same folder. server { listen 51.84.214.58:8080 sndbuf=32k; server_name alpha.patch.wom2.org; root /home/www/patch.wom2.org; location / { limit_rate 4096k; if_modified_since off; expires epoch; } } As you can see I limited speed to 4 Mbps to avoid people with a big pipe taking all the bandwidth. Remember no Cloudflare here, CF is not for file serving.
  6. Hey Dev! Welcome to my first small tutorial here. When i implemented mount renewal and mount follow system i thought that mounts are really coming too close to character when u walk and then stop. so i was looking for a solution: 1. Open ServerSource src/game/MountSystem.cpp 2. Search for: int APPROACH = 200; 3. Just play around with 200, enjoy
  7. There was a time, when Metin2 was still a recent game, when it was fairly easy to perform Layer 7 attacks on FreeBSD servers, or even hack into them. Much software was shipped with insecure defaults, and it was expected from the user to properly secure it. This has changed, and now MySQL is only listening to localhost by default, Apache is for the most part an unnecessary relic from the past, root user cannot login to ssh, and so on. But there is a part of the structure that has always been extremely vulnerable: the website, particularly the cheap webhosts many people opt for when they need to use certain poorly written CMS or Forum software that doesn't play well with Nginx. Since the needs of a game server (payment, voting and so on) can hardly be covered by any off-the-shelf solution, there will often be a need for some php script directly pulling data from the database to show a player ranking, or similar functions. This script can be repeatedly hit by one or multiple IP addresses and eventually overload the MySQL database which your game happens to use as well; eventually, both your game server and website go down. And no, Cloudflare will not help you unless you pay money and/or configure it extensively and properly, a process I may explain some other time. Today we are going to introduce two extremely easy solutions to mitigate this sort of attack I described with the help of nginx and a bit of mysql. Two stage rate limiting The first technique is rate limiting. It involves throttling repeated request from the same IP, particularly to php files which are the ones that consume by far more resources in the server. Hitting anything else is unlikely to cause any harm. In order to enable rate limiting, first we must add in the http context a "zone" where IPs are saved: limit_req_zone $binary_remote_addr zone=www:10m rate=5r/s; This will create a 10 mb memory zone to store a log of connections; if any of them exceeds 5 requests per second, they will be refused with a 503 error. But for this to actually work we must add this extra line into the php part of the server context - just mind the first two line heres and ignore the others that are there for context: location ~ \.php$ { limit_req zone=one burst=20 delay=10; limit_req_status 444; try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm.sock; fastcgi_index index.php; include fastcgi_params; } Besides specifying where to perform this rate limiting (.php files), the settings enabled here make the experience a bit smoother by allowing the client to send a burst of up to 20 requests/second before refusing subsequent requests. Finally, the delay parameter indicates that when the connection speed exceeds 10r/second, the subsequent requests will be served with a delay. The second limit_req_status line instructs to give an empty response (444) instead of the default 503 error to excess connections, slightly reducing the server resources needed to deal with the presumed attack. FastCGI cache: serving stale content Now this is all fine and well, but what happens if we are attacked from multiple IPs? The feared DDoS! Well, it depends on what our hypotetical php script is exactly doing. If it's simply pulling data from the Database, we can use the proxy cache to force NGINX to serve such pages from a cache and avoid making repeated connections to the database. Let's define our cache in the http context: fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=mycache:10m inactive=10m; fastcgi_cache_key "$request_method$host$request_uri"; fastcgi_cache_use_stale updating; The first line creates a cache zone in memory of 10 mb, and specifies that if there are no requests for 10 minutes, the cache will be refreshed anyway. The second line specifies the arguments to use for creating a key (a sort of hash) in the cache for this request. In practice this means that if, for example, the query string is different in two requests they will still be considered to be the same request and for caching purposes, as the query string is not part of this "key". And the third line and most relevant means that while the cache is updating, the client will not wait for said update to finish before serving the requested content;instead it will serve the outdated ("stale") version of the page. Since every request normally triggers a cache update, this technique reduces the number of times the php script is actually executed during a flood enormously. (On a side note, this setting also allows us to show stale content when the backend is not responding. This is exactly what Cloudflare does with its "offline mode". We can enable this behavior by adding further triggers:) fastcgi cache use stale updating error timeout invalid_header http_500 http_503; Finally and to use the cache we defined in a location (in this case it must be the php location since its a fastcgi cache) we add this. Second line specifies for how long a 200 OK response is valid; other response codes will not be cached: fastcgi_cache mycache; fastcgi_cache_valid 200 5m; The icing in the cake: limit MySQL connections by user What about scripts that UPDATE the database? Things can get nasty here, since there's no cache to speak of all we can do is limit the total amount of requests that can be made to the backend and the database. In this case, nginx is not going to help; instead, create a specific user for your website different from the game user in MySQL and set a strict limit of connections. This means such attack will not take down the database. create user 'website'@'someip' identified by 'somepassword'; grant usage on account.* to 'website'@'someip' with max_user_connections 10; As an extra measure you can set more strict rate limits for the most vulnerable POSTing scripts in nginx (register, login...). Be aware you need to place specific locations (such as login.php) above the wildcard *.php location in the nginx config: location /login.php { limit_req zone=one burst=5 delay=2; limit_req_status 444; fastcgi_pass unix:/var/run/php-fpm.sock; include fastcgi_params; } And that is all you need to prevent your php scripts from being flooded. Of course, that's only one of the vector attacks, but also the most overlooked, yet easiest to fix. PHP programmers may also add extra checks in their code for repeated connections - memcached is your friend. But that's outside of our scope here.
  8. Download Metin2 Download Description: Some serverfiles have wrong base position coordinates in Setting.txt of the Orks valley (map_a2 or in some cases map_n_threeway on client side) and it collides with deviltower Here is an example from wom2 client from 2016 [Hidden Content] Problems caused in most of cases by wrong coordinates or bad server_attr file: - When you choose coordinates for BasePosition in Setting.txt, make sure they are multiples of 25600 if you want to avoid unknown behavior such as: "Mobs can't walk on specific parts of the map" or "Players getting kicked because sectree manager cannot find tree at position x y" - Players getting kicked & etc Things you need to watch out: - Make sure the map size is the same in setting.txt from client side & server side too - Open the map in WorldEditor.exe and save again the server_attr, then upload it to server side inside the map folder/map_name_xx (when you save it close WorldEditor & save the map and open it again, I heard if you make some changes in the attributes of the map it won't load them from cache so you have to open it again) How to trace broken maps ? Using a special software made for metin2 (there are several, ones paid with license, etc) I offer you a free one that I found on the forums, it's simple and easy DOWNLOAD - AtlasInfoShower.exe - no virus I guarantee Once you got the little program, put it in a folder Put your atlasinfo.txt inside the folder (make sure you put the correct one, some people have 2 separate atlasinfo.txt, one in locale pack and one in root pack, find out which is your correct one that your binary is using) Edit your atlasinfo.txt using notepad++ software (the majority have it with syntax errors) Toggle the "Show White Space and TAB" in notepad++ menu (View tab -> Show Symbol -> Show White Space and TAB) Correct ALL the wrong lines, the ones that have more than one TAB and the ones that have TABS mixed with spaces Now you can open AtlasInfoShower.exe Here is an example of map coordinates collission, make sure they have a little space between them so you are 100% sure it's okay When you choose coordinates for BasePosition in Setting.txt, make sure they are multiples of 25600 if you want to avoid unknown behavior such as: "Mobs can't walk on specific parts of the map" or "Players getting kicked because sectree manager cannot find tree at position x y" How to find multiples of 25600 ? Search on google "multiples of 25600" or use this link [Hidden Content]
  9. Hello community, I have seen many people selling "wiki equal to the official" for prices ranging from 300€ to 500€ or more, what is wrong with this? These "web developers" are selling only open source code. In a didactic way these "professionals" are selling something open source that is free. How do I know this is not speculation? For those who don't know there are several free tools to detect which engine a site is using. For example, open whatcms.org and put the link to the official wiki. And voila, we have discovered the secret of many super web developers The secret goes by the name of MediaWiki. Requirements Hardware Minimum 256MB RAM Minimum 85MB disk space Dependencies PHP 7.3.19+ or 7.4.3+ and respective dependencies MySQL 5.5.8+ or MariaDB 5.1+ If you want to see the full requirements see the documentation: [Hidden Content] You don't know how to configure a webhost? Don't worry, consult my topic about configuring a web server using CyberPanel + OpenLiteSpeed: MediaWiki Setup [Hidden Content] Questions? Comment, as soon as possible I will answer. Best Regards, Papix
  10. Hello community, As I already have some years of experience installing webservers and developing websites, I decided to share one of the methods I usually use for small and big websites. If you don't know anything about web server configuration and don't want to learn, you don't need to follow an old tutorial on google in which you may not even be able to start your website. In this topic I will explain how to install CyberPanel + OpenLiteSpeed. What is CyberPanel? CyberPanel is a control panel open source where you will manage your websites. What is OpenLiteSpeed? OpenLiteSpeed is the best, fastest and most secure alternative open source to apache and nginx. My site uses htaccess how do I convert it? You dont need convert anything, OpenLiteSpeed can load your apache rules. Note: The openlitespeed rules are loaded at startup and not in real time, you after editing your rules must restart the controller. (Don't worry, you can restart the openlitespeed easily with 3 clicks through the cyberpanel) Do you really advise the use of CyberPanel + OpenLiteSpeed? Yes, in my opinion CyberPanel with OpenLiteSpeed besides its great performance and the safest choice. In my opinion with years of experience testing web panels, CyberPanel is superior to any other panel, be it paid or open source too. I don't understand any of this, is it difficult to learn how to use? No, you don't even need to run commands on your machine other than the one installation command. All the configuration of your websites will be done through the browser using CyberPanel. Requirements Centos 7.x, Centos 8.x, Ubuntu 18.04, Ubuntu 20.04 Python 3.x 1GB or more RAM. 10GB Disk Space. Setup CyberPanel + OpenLiteSpeed [Hidden Content] Questions? Comment, as soon as possible I will answer. Best Regards, Papix
  11. Hello community, About Firewall This firewall is ideal for those who have a national server and not an international server. It is based on GEO block, adding new countries or removing them is extremely easy. Currently I can easily say that it is the best pf that is published for free at the level of efficiency. I attacked the firewall several times, it was tested on online servers with 700/800 simultaneous players and it worked efficiently without problems. Warning The best protection that can be applied is at the network level. there is no point in having 1 firewall running on an operating system if the network does not support a large amount of traffic for the operating system's firewall to be able to act. This firewall in combination with a good VPS/dedicated VPS and good network protection on the company side can easily handle small to medium attacks. All the magic happens because of GEO BLOCK, without GEO BLOCK it is equivalent to the pf's shared here in the community. The code is not optimally organized and can be improved, remembering that it was my first version. Requirements -pftop (PF) -wget How To Setup [Hidden Content] Questions? Comment, as soon as possible I will answer. Best Regards, Papix
  12. The language of the video is Turkish, but Im adding in case you may need visuals. Skip to 19th minute, rain files start from there... Short summary for avoiding the video: Extract files from InstantServer.exe Create virtual machine using .vhd file that extracted from InstanServer SFX archive. Be generous when adjusting the RAM amount. Otherwise, swap space will not be enough. Change the local IP of the virtual machine sysinstall -> Configure -> Networking -> Interfaces -> em0 Do you want to try IPv6 blabla.. -> No (In Turkey we don't use IPv6 yet.) Do you want to try DHCP blabla.. -> Yes (With this, you can automatically assign the first available IP in the modem.) Reboot or restart netif service with; /etc/rc.d/netif restart Type the following command in root directory for start the server: ./start Extract client files from metin2.7z ( I used WinXP because, defender deletes mc.exe for containing virus. Also bunch of compability issues in win10. I strongly suggest use virtual machine. XP in virtual machine safe and sound...) Open mc.txt with text editor. Put your local IP address of FreeBSD Start metin2.exe and click the button in the image below... Use ID: rain Pass: 12345 and Booomm... For English client use the mc_eng.7z Extract everything inside to client folder and replace. Special thanks to ShinsooLord for reuploading and rain for releasing this beauty. Download links You can get InstantServer from this topic All in one => Metin2 Download Recompressed InstantServer SFX archive contents -> Mc倚天2完整版.7z VT SHA-256: 436BCFB3963B02506EEEB07713F18396CA49DA6BA084D7ACE8CC361885622062 Pass: rain Client -> metin2.7z VT SHA-256 : 27E2FCFB486E3682A898FB32F7F30F064729D7BD7323FEB4D9B202A76486FB2B Pass: rain Client Eng patch -> mc_eng.7z VT SHA-256 : 974F8402524BC10F5EF82D87F11C64721525AD00D89AD9E02171D8CD75612672 Pass: rain Rain server files extracted in the video from vhd, recompressed with lzma2 -> rain.7z VT SHA-256 : 1833A13707ABBCE334A030CD5A2E5F8D6F2C336FF9EB9D2CDD3417519758A5FA Sql file that contains all databases extracted with mysqldump from vhd -> all_rain_db.sql VT SHA-256 : E42637DA2B712A829D43186F29532A643DB02363366DDAE082D08EC2235373EF That's all folks...
  13. Hello community! This guide is for those who want to edit the default FreeBSD text editor. I will use the nano editor in the explanation but it is optional. Guide [Hidden Content]
  14. 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.
  15. Hello guys! Being in the Metin2 P-Scene for quite some time, I've seen one issue that most of the "Metin2 Entrepreneurs" have - advertising. It's either cringe, either spammy, either non-existent and it's understandable - opening a Metin2 server is not an easy peasy task. Based on my experience as Promotion Manager for almost 2 years on a well known P-Server (World of Metin2) and also from my marketing and public relations experience, I've decided to write this guide - some small tips and tricks to start a marketing campaign as close to professional as possible. 1. Don't be kitsch! Your design is one of the first things a player will see. Try to avoid kitsch. Use simple and clean designs. Pick only two or three colors and use them as your brand colors in all of your assets: website, presentation, forum, banners, logo, social media images. Try as much as possible to hire professional designers for your Website and Presentation. Banners, logo and Social Media posts can be easily done by yourself. If you don't have enough money to hire a full time designer for your server - use Canva. I won't enter in details about Canva, but it is one of the easiest and most used design apps by companies around the world. You can create with it simple designs and animations. 2. Be active online! In my day to day job I can't stop telling clients about how important it is to make use of Social Media. Create three acounts (Facebook, Instagram, YouTube) and be active. Additionally, you can also create a TikTok account. Add your logo and covers, and start posting about your server (Metin2 related memes do work as well). Keep your social media images clean Write small descriptions (100 words maximum) Make use of emotes to increase text's readability Add a link of the full post when it comes to Changelogs or important announcements Try to post at least once a week. 3. Start Facebook and Instagram Campaigns! Even if you've heard that "Hire Youtubers, that's the only promotion you want" line, it is not entirely true. Facebook and Instagram Ads are some huge tools that might help you increasing your brand awareness and players. Speaking of which, there are two types of campaigns I recommend for a Metin2 P-Server: Brand Awareness Conversion A Brand Awareness campaign is highly recommended a good time before server's launch, maybe a month or two. Use a low Daily Budget and keep your campaign running for a longer period of time (maybe even a month). It'll help players to hear about your server. When you'll launch your server, it's not gonna be just a random server out there, but an already known one. A Conversion campaign should come prior the launch (a week before) and everytime you have new content that is worth being promoted. A conversion campaign is quite simple - it tries to get real players registrations for money. A well built campaign can go as low as 0,08€ per player (and that's good). Ask your Developer to build a Facebook Pixel inside your Website. It is not a hard job and it'll help you contorize and analyze more the players that come from your Facebook/Instagram Ad. Before you say "Yeah dude, but is hard and I have no idea how to create a campaign", it is not as hard as it sounds. Facebook Business platform is easier than ever to use and you can find thousands of guides on Google on how to do that. I won't enter in details here, I think you can found the basics on your own, but I'll suggest a few things: Pick your demographic well Age: 16-40 Countries: Germany, Poland and Hungary - *Focus on them if you have a Middle or New-School server Romania, Turkey and Italy - *Focus on them if you have an Old with Middle-School influences server Spain, Portugal and Greece - *Good for most of the server types *when I say to focus on them, I don't mean to ignore the other communities 4. Youtubers - be aware! Sadly, from my experience, a lot of promoters are boosting their views, comments or likes and it makes your job a lot more difficult when you want to hire or pay someone. Having this in mind, I personally recommend to start with a bigger number of promoters and cut them one by one in time - remain only with a core of Youtubers that will give you most of the results on long term. Try to avoid: Short term promoters They're usually jumping from server to server and their subscribers will follow them - don't lose your money for nothing Small amount of subscribers but a really high number of views Check their "numbers" in the first 60-90 minutes after posting a video. Normally, a big part of those views should come in the first hour. Low interaction in the comment section "Giveawayers" Try to avoid promoters that built their communities from giveaways only Toxic characters If your image is important, don't break it by associating yourself with anyone Reward your "own" small Youtubers from your community. Give them a couple of DC's per video or just reward them from time to time, it'll make a difference in time and you can build a nice network. 5. Discord Server You know how important it is, your friend knows how important it is, your players know how important it is - basically everyone. Promote it, engage with your players there, give them some sneak peaks of the new content that is about to come - in a word - communicate! I might have missed something and I'll update the thread if I remember. It's maybe not a 1-1 guide step by step on "how to have Aeldra's players", but it is a more professional approach than what most of the servers are doing right now. Thank you and I hope it'll help some of you!
  16. Master Guide English version of the French Master Guide Summary Once upon a time Introduction Prerequisites Creation & Management of a Metin2 Private Server Introduction Create your Metin2 Private Server Configure & Manage your Metin2 Server Server Client Quests Maps Items Entities Extra Sources of Metin2 Introduction Compilation Conclusion Once upon a time Introduction If you are reading this topic, it's because you too are looking for to create your own private server for our favorite MMORPG! Metin2! With this guide, you will be able to create and modify your Metin2 private server. You can customize it as much as you want! First of all, a little theory! Some computer fluency is recommended to successfully complete the quest you are leading. Metin2 uses several programming languages, such as LUA, Python, C++, and SQL. We have, since 2014, the Sources of Metin2, that is to say that with the sources and some knowledge in programming, you will be able to create your own systems, add character classes, add kingdoms and other things! With the sources, you will have complete control over the game! This part is reserved for people with knowledge in programming. The C++ language requires analysis and understanding about the errors and the information that is transmitted by the compiler regardless of the tutorials and shares offered... Before 2014, we had to use pre-made server files and we had to make classic modifications through the use of diffs... For the most part, the servers all looked the same, a bygone era! You have the possibility to self-host and install it on your computer if it's efficient. Local use for tests will be recommended, followed by the acquisition of a dedicated server from a host such as OVH, SoYouStart or even Kimsufi for public use with players, for a few euros per month. This guide will mainly tell you what are the important topics to see first, then some extras to improve your server! Prerequisites Lots of coffee Patience Motivation A lot of time Money Creation & Management of a Metin2 Private Server Introduction I know you are looking forward to this! Today, you will see on the forum server files with a year of release, keep in mind that the 2014++ server files have the sources of Metin2. I want to reassure you on one point, for a beginner, it's not necessary to work on the sources of Metin2, the sources should be used only if you want to add and create new systems or fix bugs. The creation of a Private Metin2 server begins with the emulation of an operating system in addition to yours, FreeBSD. FreeBSD is a UNIX-like operating system, on which most Metin2 servers run. It's the emulation of a second operating system that consumes resources in processor, RAM and storage on the host system (Your computer), it's also possible to run a server natively on Windows. If you are using a dedicated server, you will install FreeBSD as your main operating system without going through Windows! Metin2 is composed of two parts, a server part and a client part, the player uses a client to connect to the server part. This is what makes it possible to play online. The server part is also made up of two parts, the game and the database. The files or server files are the set of files which allow Metin2 to function. [Hidden Content] Create your Metin2 Private Server (Recommended) [40250] Reference Serverfile + Client + Src [15 Available Languages] (Alternate) Metin2 Serverfiles Windows + Client + Source Configure & Manage your Metin2 Server Server Metin2 Sources - 2014 Kraizy MySQL Password Generator for your Metin2 Account How to create a shop or multi shop and modify it with an NPC GM Commands Navicat Premium 15 TXT Item_Proto Flags / Antiflags / Bonusflags Forum: Binaries Client (Recommended) EterPack Archiver - PackMakerLite (Alternate) Eter Nexus File Archiver Client Console Commands How to Metin2 & Cytho Forum: Binaries Quests How To Questing for Beginners List of common quest functions Event Flags Forum: Quests Maps World Editor Remix How to Install Map Manage NPC / Monster spawn on your Metin2 maps How to install World Editor? Windows Regen Creator Forum: Maps Items (Recommended) Metin2 Icon Database Implement a Weapon / Armor / Costume / Item... Configure your Weapons / Armors / Items Import / Export Model with Noesis and 3DS Max How to import GR2 animation into 3DS Max Forum: 3D Models or 2D Graphics Entities Nothing here... Websites (Recommanded) Metin2 CMS Forum: Web Development & Scripts / Systems Extra (Recommended) Official Unpacked Updates Metin2 No Spam Which programs do I need for what? A security checklist for your Metin2 server Guide for better security Useful Tools to Get you Started Forum: Binaries Forum: Programming & Scripts / Systems Forum: Tools & Programs Forum: 3D Models or 2D Graphics Forum: Guides & HowTo Forum: Operating Systems Forum: Others Sources of Metin2 Introduction Compiling requires programming knowledge! Compilation, in computer science, is a work carried out by a compiler which consists in transforming a source code readable by a human into a binary file executable by a computer. For example, the sources of the Metin2 client, compiled with Microsoft Visual Studio, we will give an executable file named: metin2client.exe. The sources of Metin2, consist of several files whose extensions are respectively .cpp and .h, these files form what is called: the sources. The compilation of these files allows to obtain files: db and the game in a UNIX environment with FreeBSD, a client file: metin2client.exe and tools: worldeditor.exe, dump_proto.exe in a WIN32 environment with Windows... Compiling is translating human language (Python, C++, Java, etc...) into machine language (Binary), with a tool called a compiler. To compile, we use for example Microsoft Visual Studio (Windows) or Gmake / CLang (FreeBSD). [Hidden Content] Compilation (Recommended) Get started - C++ on W3Schools The programming languages you should learn! How to compile the source code on FreeBSD How to compile Metin2 server source on FreeBSD? How to compile client binary on Windows UI programming guideline Introduction to C++ Forum: Binaries Forum: Programming & Scripts / Systems Forum: Guides & HowTo Conclusion With Metin2 Dev, the members who make up this forum, the shares, releases and the tutorials, you are now able to create a Metin2 Private Server. Don't hesitate to share your findings and your ideas to help Metin2 evolve. Have you opened your Metin2 Private Server? Now show us what you can do! Good luck to you ! If you need help, we'll be there to help you in Questions & Answers. Sincerly, ASIKOO
  17. Hello hello! I search for it many times, but i didnt find anything about this. So basically your weapons (except lvl30-75 weapons), you cannot switch average and skill dmg bonuses by default. If you are using .txt version, you just need to go to end of the line (for example poison: id: 180-189) and check the last column. Its "-1", which is enabling the switchable avg and skill dmg. Example line: Sorry for my horrible english... Sorry if its already posted somewhere on this forum!
  18. Hey guys i now write how to can install svn to debian server. ( what is svn? ) SVN is an open source version control system build by Apache Foundation Team. It helps you keep track of a collection of files and folders. Any time you change, add or delete a file or folder that you manage with Subversion, you commit these changes to your Subversion repository, which creates a new revision in your repository reflecting these changes. You can always go back, look at and get the contents of previous revisions. This article will help you for step by step setup of Subversion (svn) server on Debian 10, Debian 9 and Debian 8 operating systems. source Step 1 – Install Apache We are using Apache web server to host SVN server. You need to install the Apache web server to access the svn server using HTTP URLs. Skip this step if you already have Apache web server on your system. sudo apt-get update sudo apt-get install apache2 Step 2 – Install SVN Server Use the following command to install subversion packages and their dependencies. Also, install svn module for Apache libapache2-mod-svn packages on your system. sudo apt-get install subversion libapache2-mod-svn libapache2-svn libsvn-dev After installation, enable required Apache modules and restart Apache service. sudo a2enmod dav sudo a2enmod dav_svn sudo service apache2 restart Step 3 – Configure Apache with Subversion Subversion Apache module package creates an configuration file /etc/apache2/mods-enabled/dav_svn.conf. You just need to make necessary changes to it. Alias /svn /var/lib/svn <Location /svn> DAV svn SVNParentPath /var/lib/svn AuthType Basic AuthName "Subversion Repository" AuthUserFile /etc/apache2/dav_svn.passwd Require valid-user </Location> Step 4 – Create SVN Repository Use following commands to create your first svn repository with name myrepo. Also, set the required permissions on newly created directories. sudo mkdir -p /var/lib/svn/ sudo svnadmin create /var/lib/svn/myrepo sudo chown -R www-data:www-data /var/lib/svn sudo chmod -R 775 /var/lib/svn Step 5 – Create Users for Subversion Now create first svn user in /etc/apache2/dav_svn.passwd file. These users will use for authentication of svn repositories for checkout, commit processes. sudo htpasswd -cm /etc/apache2/dav_svn.passwd admin To create additional users, use following commands. sudo htpasswd -m /etc/apache2/dav_svn.passwd user1 sudo htpasswd -m /etc/apache2/dav_svn.passwd user2 Step 6 – Access Repository in tortoisesvn download link right click -> tortoisesvn -> repobrowser -> write the url when you make your repo. (example: [Hidden Content]) right click -> svn commit (to upload files)
  19. 1. Faster and cleaner boot: 2. Alias for shortcuts such as paths and executions. UPDATED! 12.10.2021 3. Disable Send Mail - Credits to: @ martysama0134 (Not waiting after Send Mail Service on boot, it will reduce your boot time with 10 more seconds) Hope it's usefull. It was for me Hope you have a great day! Shahin
  20. Download Metin2 Download How to Update Client src c++20 Thanks lot of @ Mali First, we open the client source with visual studio 2022. Select all projects and right click on them -> properties -> General -> c++ language standard -> Here we choose c++20. [Hidden Content] need to make some step from this topic HowToc++20 If you don't understand something, you can ask your questions here.
  21. How to Update Client src c++17 Step 1: First, we open the client source with visual studio 2019/2022. Select all projects and right click on them -> properties -> General -> c++ language standard -> Here we choose c++17. [Hidden Content] Step 2: In each file (ctrl + shift +f) , we search for the following & delete them: using namespace std; Step 3: Now the following details need to be modified (every file): string pair make_pair vector to std::string std::pair std::make_pair std::vector Step 4: Open Eterbase project stl.h file and delete this code: namespace std { template <class _Ty> class void_mem_fun_t : public unary_function<_Ty *, void> { public: explicit void_mem_fun_t(void (_Ty::*_Pm)()) : _Ptr(_Pm) {} void operator()(_Ty *_P) const {((_P->*_Ptr)()); } private: void (_Ty::*_Ptr)(); }; template<class _Ty> inline void_mem_fun_t<_Ty> void_mem_fun(void (_Ty::*_Pm)()) { return (void_mem_fun_t<_Ty>(_Pm)); } template<class _Ty> class void_mem_fun_ref_t : public unary_function<_Ty, void> { public: explicit void_mem_fun_ref_t(void (_Ty::*_Pm)()) : _Ptr(_Pm) {} void operator()(_Ty& _X) const { return ((_X.*_Ptr)()); } private: void (_Ty::*_Ptr)(); }; template<class _Ty> inline void_mem_fun_ref_t<_Ty> void_mem_fun_ref(void (_Ty::*_Pm)()) { return (void_mem_fun_ref_t< _Ty>(_Pm)); } // TEMPLATE CLASS mem_fun1_t template<class _R, class _Ty, class _A> class void_mem_fun1_t : public binary_function<_Ty *, _A, _R> { public: explicit void_mem_fun1_t(_R (_Ty::*_Pm)(_A)) : _Ptr(_Pm) {} _R operator()(_Ty *_P, _A _Arg) const { return ((_P->*_Ptr)(_Arg)); } private: _R (_Ty::*_Ptr)(_A); }; // TEMPLATE FUNCTION mem_fun1 template<class _R, class _Ty, class _A> inline void_mem_fun1_t<_R, _Ty, _A> void_mem_fun1(_R (_Ty::*_Pm)(_A)) { return (void_mem_fun1_t<_R, _Ty, _A>(_Pm)); } } Step 5: In each file (ctrl + shift +f) , we search for the following & modify them: std::void_mem_fun to std::mem_fn this std::auto_ptr to: std::unique_ptr Step 6: Open userinterface/userinterface.cpp Search this: extern "C" { extern int _fltused; volatile int _AVOID_FLOATING_POINT_LIBRARY_BUG = _fltused; }; add under this: extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; } Step 7: Open Speherelib/spherepack.h modify this: inline void LostChild(SpherePack *pack); to: void LostChild(SpherePack *pack); Step 8: open etherBase/cipher.h search this: #include <cryptopp/cryptlib.h> #pragma warning(pop) add under this: using CryptoPP::byte; Step 9 Open etherpack/Eterpack.h #include <boost/unordered_map.hpp> #include <boost/shared_array.hpp> modify to: #include <unordered_map> Replace all file this: boost:: to: std:: Open etherpack/EterpackManager.h & EterPackPolicy_CSHybridCrypt.h modify this: #include <boost/unordered_map.hpp> to #include <unordered_map> Open GameLib/Area.cpp remove this: #include <boost/algorithm/string.hpp> and this: boost::algorithm::to_lower(attrFileName); modify to: std::transform(attrFileName.begin(), attrFileName.end(), attrFileName.begin(), [](unsigned char c) { return std::tolower(c); }); You can also delete the boost in the include folder. If you don't understand something, you can ask your questions here. thanks for @ Mali and @xXIntelXx some detail.
  22. Hello Friends You know that so many files are shared in the Forum that some of them are compiled with GCC8 - GCC9 or GCC10. There is also MySQL, of course, if it is on MySQL, MySQL 5.6 or MariaDB 10.3 is used. That's why I will share 2 different Freebsd VDIs on a single topic. 1 - Mysql 5.6 Version 2 - MariaDB 10.3 Version You can download and use these 2 VDIs according to your needs. ISO File Used - 2021-Apr-09 06:58 ; Freebsd Login ; User : root Password : dev Package Installation List for MySQL 5.6 ; gcc10: 10.3.0 gcc8: 8.5.0_1 gcc9: 9.4.0 gdb: 10.2_1 gmake: 4.3_2 makedepend: 1.0.6,1 mysql56-client: 5.6.51 mysql56-server: 5.6.51 python27-2.7.18_1 Package Installation List for MariaDB 10.3 ; gcc10: 10.3.0 gcc8: 8.5.0_1 gcc9: 9.4.0 gdb: 10.2_1 gmake: 4.3_2 makedepend: 1.0.6,1 mariadb103-client: 10.3.30 mariadb103-server: 10.3.30 python27-2.7.18_1 Download Link ; Google.Drive (Download) Dosya.Upload (Download) Virüs Scan ; Thank you for using it and for your nice comments. Enix Yazılım.
  23. M2 Download Center Internal Hi Everyone! Hello Community, I make this little tutorial, for all the people who wants to try modelling or modifying a Metin2 models This video only intoduce gr2 -> fbx with Noesis, after that import fbx files to 3ds max , and exporting model to gr2. I make 2 video about the process easy way, but keep eye on parameters, switchers in every program. For helping I upload to my mega folders some files 3ds Max 2018 granny export plugin whole noesis 4.44 with export/import plugin 2 video file about the process export settings to granny export in 3ds MAX I. Converting gr2 files to FBX exporting option with animation Exporting option without animation In the video you will see it I paired a model with animation, because sometime the model doesnt have it all the bones. I think its important. II. Importing FBX files With animation importing @.plechito' makes a really usefull tutorial in here Only few thing In my video I show you howto set 3ds MAX 2018 eg. UNITS setup Set all things into centimeter In exporting set all the things what i'm make if you DONT have animation in a fbx files, if you have it In animation import you haveto choose Noesis frames After all that click on OK , and your model finally imported to 3ds MAX 2018 III. Exporting from 3ds Max After you make your pretty modification in the model you have to exporting In my way. I select all things what i wanna export, In the export menu tick out animation if you didnt want to ( It usefull if you wanna make model with animation) Thick out Move Origins in Animation and Model submenu In Meshes submenu, deselect all the bones At the texture submenu thick out include, because it build it the texture into .gr2 I think thats all. If you have question send me a letter or find in discord, or leave a message somewhere Have a nice day. UPDATE I added some exporting setting to mega folder, you can use it exporting setup exporting model exporting animation exporting move animation exporting dead animation exporting static model Cheers. Update 25.03.2022 - added 64 bit support for Granny2 plugin, you should place granny2_x64.dll in noesis root folder. -following @.plechito' logic i've added path for looking for textures eg. If warrior_m.gr2 textures is missing is looking for d:\ymir work\pc\warrior . Update 24.03.2022 I'm updating Noesis Granny plugin now adding texture to the fbx material, so in your 3d application (Noesis, Blender, 3dsmax) you can see texture correctly. if you wanna download just a plugin you can find it here [Hidden Content] you have to copying the file to Noesis/plugin/python folder
  24. Hi everyone, Small internal tool to generate passwords for your Metin2 private server. Click here... PS: Of course, passwords are not saved Sincerly, ASIKOO
  25. I was wondering lately how to change the color of the Experience Orbs and I find out it's easier than I even thought. You don't need any special software for this simple change, a text editor is enough for this. For this you will first have to unpack ( you can use Eternexus for this step ) your Effect.eix from Pack directory and by default you have to find the file called ga_piece_yellow_small2.msf which you can find in effects\etc\gathering. If the path is different you will find the correct path in playersettingmodule.py from root by line effect.RegisterIndexedFlyData(effect.FLY_EXP... Now, opening ga_piece_yellow_small2.msf with Notepad you have to look for AttachedFile in the block called Group AttachData. There is the path to the actual file where you will have to edit some numbers to change the color. By default the file you are looking for is called ga_piece_yellow2.mse. Now open the new file and look for the following code. List TimeEventColorRed { 0.828205 1.000000 } List TimeEventColorGreen { 0.828205 0.100000 } List TimeEventColorBlue { 0.828205 1.000000 } You will have to edit the second number for each color representing each color with values from 0 to 1 The example above is for a pink experience orb. You can change the numbers to create any color from RGB. To calculate any color you can find out on internet some website with the RGB Color with numbers and change it for the color you wish. Now you have just to take the numbers associated with each color and divide it by 255. (For example, if the red is 100 you divide it by 255 and you will get the result of 0.392156 to replace inside TimeEventColorRed, do the same for all 3 colors ) In the end you have to pack back the effect directory using Eternexus(or the soft you are using to unpack the Pack files)* and make sure the result of the packing is placed correctly in the Pack folder of the client.
×
×
  • 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.