-
Posts
23 -
Joined
-
Last visited
-
Feedback
0%
About Anielle Noir

Recent Profile Visitors
The recent visitors block is disabled and is not being shown to other users.
Anielle Noir's Achievements
-
Anielle Noir started following [C++] Change maximum number of inventory slots and more , Official Extra Equipment Page [22.1] , [C++] Remove FPS Limit Using DirectX StepTimer and 6 others
-
official Official Extra Equipment Page [22.1]
Anielle Noir replied to msnas's topic in Features & Metin2 Systems
thanks for the code, the idea is great. -
[C++] Remove FPS Limit Using DirectX StepTimer
Anielle Noir replied to hasanmacit's topic in Features & Metin2 Systems
I have to point this out: spawn around 100 active players around your character and then test it again. You will quickly see the FPS dropping to 20 or even lower because of skill effects, animations, particles, and everything else being processed around those players. Simply uncapping the FPS is not enough. If you want this to work properly, you also need to rewrite or adapt everything that is frame-dependent: skill effects, .ani images, animations, particles, affects, packet processing, and basically anything whose timing or execution was originally tied to the frame rate. I know you already implemented real-time FPS handling, but the original client has a lot of other logic indirectly tied to frame processing. Testing alone on an empty map will not expose most of these problems. In my case, with over 600 players in Map1, I had to change a lot of the underlying logic for skills, affects, animations, effects, .ani images, packets, and other frame-rendered/frame-processed systems. Cached effects can start generating or processing significantly more work, packet processing becomes much heavier, and sometimes the client can even enter a "Not Responding" state because too much work is being processed during a single frame, especially considering how much of the game still runs on a single core. There is a lot more rewriting involved than simply removing the FPS cap. I honestly don't understand why so much of the original logic was designed around frame-dependent behavior in the first place. I've already spent about a week rewriting parts of it and I still haven't completely finished. There is also another issue with skills and particle effects. Once you unlock the game from the original FPS behavior, some particles can cover a larger area while having a shorter visible lifetime. Their movement, lifetime, emission, and other timing-related behavior need to be converted from frame-based calculations to proper time-based calculations while still preserving the exact appearance they had at the original 60 FPS. A good way to test this is to implement a 60 FPS / 120 FPS toggle using the same rendering pipeline. Switch between them repeatedly and compare everything around the character. The goal should be for the game logic, animations, particles, skills, effects, and networking behavior to remain practically identical regardless of whether the client is running at 60 or 120 FPS. Also run debug builds at both 60 FPS and 120 FPS for around 10 minutes in a populated area. You will start noticing how different the original frame-based logic behaves once the FPS is unlocked. If you only uncap the FPS and test it alone on an empty map, many of these issues remain invisible until actual players start reporting them. -
Great fix, i've done it for shinings a while ago like mse mde for weapons while attacking and falling behind and it fixed it globally. same approach. And yes, one of the oldschool hideos things in metin that was bugging me forever. BTW, same approach is used for the equipment swaps such as sash or horse/mount mounting-dismounting. as you know when you mount, your character is put in the ground under the horse with the horse animation and then it is dragged up to the mount level. you can see that better if you have a sword in your hand, it looks like it's glitching. You have to re-order the update so it shows the character when it's mounted. Same thing with sash, where the models are not as seen in the attached phase, but in the ground half way, on 90digrees, so when it is equipped it goes there first for a frame. re-ordering the updatevafter it is attached skips the visual glitch. another glitch is about motion reset on sash from packet update, same thing. same motion reset on character is seen on stats packets. also on speed attributes packet while running. I've eliminated all this visual glitches. i will edit this post tomorrow with video.
-
Game Window Resize in Real Time
Anielle Noir replied to Froslass's topic in Features & Metin2 Systems
This is a decent experiment, but it is still far from a complete real-time resolution system. Changing the window size is only one part of the problem. You also need proper handling for: Windows display scaling such as 100%, 125%, and 150%. Saving the resulting resolution and scale correctly in config.cfg. Restoring the same resolution and scale when the client is reopened. Synchronizing the client when the Windows resolution or display scale changes. Repositioning every UI window when moving from a higher resolution to a lower one. In the video, the sash side inventory remains stuck around the middle of the screen after the resolution changes. That happens because the UI windows are still using coordinates from the previous resolution. Fixing only the taskbar or inventory is not enough; every saved and anchored window must be recalculated or clamped to the new screen dimensions. There is also the common Windows border problem: your window opens with an offset on the right because the left position is treated as 0. In this mode, it normally needs to begin around -10px on the left so the visible client area aligns correctly with the desktop. I implemented this some time ago using an actual in-game resolution pipeline: selecting a resolution from a dropdown, recreating the DirectX device with the new dimensions, applying an appropriate UI scale, synchronizing it with the real Windows resolution, generating correct defaults when config.cfg does not exist, and writing the correct resolution and scale back into the configuration. That required considerably more C++ work than simply resizing the window, especially to make the entire UI behave correctly. You should also add an independent in-game UI scale. Resolution alone is not enough. Someone playing at 1080p on a 24-inch monitor and someone playing at 1080p on a 50-inch television have the same pixel resolution, but the interface may be far too small or too large depending on viewing distance and screen size. At the moment, this is a partially working window-resize implementation, not a finished real-time resolution system. The device handling, configuration synchronization, DPI scaling, coordinate conversion, and full UI repositioning still need substantial work. -
[Release] Drop Info Window - Pickup tracker
Anielle Noir replied to m2workshop's topic in Features & Metin2 Systems
You could add a Clear/Reset button so players can start tracking drops from any moment they choose. Many players test farming areas by clearing the list, farming for a fixed period, and then calculating average drops or Yang earned. A timer showing the time elapsed since the last reset would make this much more useful for comparing drop rates between different zones. -
Slow clap. Open the entire game client, initialize the window and processes, check and download the update, close the game, launch an external executable to install it, then reopen the game again. What a fucking achievement. It is like starting your car, driving out of the garage to check whether it needs fuel, turning it off, pushing it to the petrol station, and then starting it again. An external helper for replacing loaded files is understandable. Launching the entire client first and advertising this loop as an “in-game autopatcher” is not. I would honestly be ashamed to come up with this workflow, start coding it, and still think: “Yes, this is a good user experience. AAAND, The best part is the initial installation logic. A new player cannot use this “in-game patcher” without first downloading enough of the full client to initialize the game. So the owner must either distribute a multi-gigabyte, potentially outdated client archive and make the player patch it afterward, or distribute a normal external patcher, which already makes this entire launch → check → close → external patch → relaunch circus redundant. A small external bootstrapper could simply download the latest client and launch it once. This "system" is like mailing someone an outdated refrigerator so its built-in screen can tell them to call a delivery company for the current refrigerator.
-
idk what kind of source files you have... with 3 servers online x 4 channels each (3 cores per channel), i have around 9gb ram used which means around 2.8gb per server, and the rest to the linux and free ram. so with 6 core vps and 12gb ram (5euro at contabo.com per month) i have 3 servers with 4channels per server, online, on the same vps.
-
fix [C++] CHARACTER::Sync Renewal (Cannot Find Tree FIX)
Anielle Noir replied to MT2Dev's topic in Bug Fixes
The posted version has the right main idea: mobs/metin stones should not call normal Dead() when sync fails, because that can trigger unwanted rewards or drops. RewardlessDead() is the important fix. However, I would avoid the fallback part: x = GetX(); y = GetY(); NewTree = GetSectree(); If the new position has no valid sectree, the sync should fail immediately. Falling back to the old sectree hides invalid movement/sync issues and makes debugging harder. It also changes the standard behavior for players, because instead of closing the connection on invalid sync, it silently keeps the character in the old tree. The cleaner version keeps the useful part of the fix, but stays stricter: 1. invalid target sectree = return false 2. no SetXYZ() on invalid coordinates 3. mobs/metin stones use RewardlessDead() 4. players keep the standard invalid-sync handling 5. fewer unnecessary null checks 6. less live log spam 7. better debug information with old and new coordinates So the posted version fixes the reward/drop problem, but the stricter version fixes it without masking invalid sync problems. This is my final version for the sync: bool CHARACTER::Sync(long x, long y) { LPSECTREE old_tree = GetSectree(); if (!old_tree) { if (test_server) sys_err("<CHARACTER::Sync> Invalid current sectree. Name: %s Map: %ld X: %ld Y: %ld", GetName(), GetMapIndex(), GetX(), GetY()); return false; } if (IsPC() && IsDead()) { if (test_server) sys_log(0, "<CHARACTER::Sync> Dead player tried to sync. Name: %s Map: %ld X: %ld Y: %ld", GetName(), GetMapIndex(), x, y); return false; } LPSECTREE new_tree = SECTREE_MANAGER::instance().Get(GetMapIndex(), x, y); if (!new_tree) { if (GetDesc()) { sys_err("[SYNC] Cannot find sectree. Name: %s Map: %ld X: %ld Y: %ld OldX: %ld OldY: %ld", GetName(), GetMapIndex(), x, y, GetX(), GetY()); GetDesc()->SetPhase(PHASE_CLOSE); } else { sys_err("[SYNC] Entity has no sectree. Name: %s Map: %ld X: %ld Y: %ld OldX: %ld OldY: %ld", GetName(), GetMapIndex(), x, y, GetX(), GetY()); if (IsMonster() || IsStone()) RewardlessDead(); else if (!IsDead()) Dead(); } return false; } SetRotationToXY(x, y); SetXYZ(x, y, 0); if (GetDungeon()) { const int iLastEventAttr = m_iEventAttr; m_iEventAttr = new_tree->GetEventAttribute(x, y); if (m_iEventAttr != iLastEventAttr) { const DWORD dwPID = GetParty() ? GetParty()->GetLeaderPID() : GetPlayerID(); quest::CQuestManager::instance().AttrOut(dwPID, this, iLastEventAttr); quest::CQuestManager::instance().AttrIn(dwPID, this, m_iEventAttr); } } if (old_tree != new_tree) { if (!IsNPC()) { const SECTREEID id = new_tree->GetID(); const SECTREEID old_id = old_tree->GetID(); const float fDist = DISTANCE_SQRT( id.coord.x - old_id.coord.x, id.coord.y - old_id.coord.y ); if (test_server) { sys_log(0, "SECTREE DIFFER: %s %dx%d was %dx%d dist %.1fm", GetName(), id.coord.x, id.coord.y, old_id.coord.x, old_id.coord.y, fDist); } } new_tree->InsertEntity(this); } return true; } thanks for the heads-up tho -
[C++] Change maximum number of inventory slots and more
Anielle Noir replied to TAUMP's topic in Programming & Scripts
Thanks to you, i made a bonkers inventory and merged with Sanii's special inventory + warehouse: total of Over 1300 slots in inventory where i separated general items, costumes, stones, upgrade items with 6 page of each and with one extra row. and the warehouse, well.. yeah -
i already finished it from the official files, and extended for Dragon Coins for ingame itemshop (buying from shops with dragon coins)
-
After doing the tutorial, modify/add this changes: in constinfo.py after ALREADY_NOTIFY_LIST = [] add: ONLINE_NOTIFICATION_START_TIME = 0 in uimessenger.py in class MessengerWindow(ui.ScriptWindow): find self.pageIdx = 0 add: constInfo.ONLINE_NOTIFICATION_START_TIME = app.GetGlobalTimeStamp() + 2 In uimessenger.py in def OnLogin, replace what you already added: this: if not name in constInfo.ALREADY_NOTIFY_LIST: self.onlinePopup = uiCommon.OnlinePopup() self.onlinePopup.SetUserName(name) self.onlinePopup.SetEvent(ui.__mem_func__(self.OpenWhisper), "MOUSE_LEFT_BUTTON_UP", name) self.onlinePopup.SlideIn() constInfo.ALREADY_NOTIFY_LIST.append(name) with this: currentTime = app.GetGlobalTimeStamp() startTime = constInfo.ONLINE_NOTIFICATION_START_TIME if not name in constInfo.ALREADY_NOTIFY_LIST and currentTime > startTime: self.onlinePopup = uiCommon.OnlinePopup() self.onlinePopup.SetUserName(name) self.onlinePopup.SetEvent(ui.__mem_func__(self.OpenWhisper), "MOUSE_LEFT_BUTTON_UP", name) self.onlinePopup.SlideIn() constInfo.ALREADY_NOTIFY_LIST.append(name) and for the animation to be smoother, in uicommon.py in class OnlinePopup, replace whole OnUpdate definition with this: def OnUpdate(self): x, y = self.GetLocalPosition() # Handle the slide-in animation if self.isActiveSlide: target_x = 10 # Final visible X position (how far it slides in) distance = target_x - x step = max(1, int(abs(distance) * 0.15)) # Smooth easing: fast at first, slower as it reaches target if x < target_x: self.SetPosition(x + step, y) else: # Snap to final position and stop sliding in self.SetPosition(target_x, y) self.isActiveSlide = False # Handle the slide-out animation elif self.isActiveSlideOut: target_x = -self.wndWidth # Fully hidden to the left distance = target_x - x step = max(1, int(abs(distance) * 0.15)) # Same easing as slide-in if x > target_x: self.SetPosition(x - step, y) else: # Snap to hidden position and close the popup self.SetPosition(target_x, y) self.isActiveSlideOut = False self.Close() # Check if the popup should start sliding out (after 5 seconds) elif self.endTime - app.GetGlobalTimeStamp() <= 0: if not self.isActiveSlideOut: self.isActiveSlideOut = True And don't forget to add import constInfo where is required, such as uimessenger.py or in uicommon.py [Hidden Content]
-
Metin2 Environmental Login Background
Anielle Noir replied to TMP4's topic in Features & Metin2 Systems
Thank you so much. I combined your idea with dynamic movement with some math :)), ambient sound by map and shuffling in between coordonates. in the video it was midnight since it was late. hence the dark environment. I call it 'Cinematic Background' hehehe Dankeshooonn Now i have to re-design the classic interface over it aaand, Boom. -
Hello, i dont have any PRIORITY_FIRST 0514 18:00:11215 :: global name 'PRIORITY_FIRST' is not defined only this: nothing else. can you check the repo please?
-
BAN_ACCOUNT /ban_account <character_name> What it does: This command blocks the account associated with the specified character name, whether that character is online or offline. It also disconnects all characters currently online from that account. Important: You cannot ban yourself or your own secondary characters. If you try to do so, you will receive: <Ban Account> You cannot ban yourself. In-Game Message: <Ban Account> character_name's account (ID: x) has been blocked. Example: /ban_account uyvawd This will block the account that owns the character named uyvawd and will kick him. Any other characters logged in from that account will be disconnected (shown in Example 2). Example 2: We target the same name as above, but that character is offline now, and the user went online on another character from the same account. BAN_IP /ban_ip <character_name> What it does: This command blocks all accounts that have used the same IP address as the specified character. It also disconnects any currently online characters from those accounts. Protections in place: You cannot ban yourself, even if one of your alternate characters shares the same IP, but the rest of the accounts will be blocked and kicked. In-Game Messages: <BAN IP> All accounts with IP xx.xx.xx.xx were banned. <BAN IP> Account ID 10001 has been blocked. <BAN IP> Account ID 10002 has been blocked. <BAN IP> You attempted to ban yourself. Action was prevented. <-- Only if: You banned your own name OR You or one of your alternate characters share the banned ip address. Example: /ban_ip uyvawd The command will: Identify the IP of the character uyvawd. Ban all accounts that have logged in using that IP. Disconnect all online characters from those accounts, except your own if you share the IP. CODE In cmd.cpp In cmd_gm.cpp Notes for Developers & Staff Characters do not need to be online for /ban_account or /ban_ip to work. DESC_MANAGER::DestroyDesc(desc) is used to disconnect online characters. What’s coming next (future improvements in the next few days so you should check this topic later as well): Detection and prevention of banning GMs with higher or equal authority. Case-insensitive character name input (UyVaWd, uyvawd, UYVAWD, etc.). IP chain-tracking (ban all accounts across linked IPs used by that player). Character disconnection from all cores (e.g., core99) and channels. SQL query improvements for multi-server environments.
-
- 6
-
-
-
-
For martysama 5.7 & 5.8 i have tested right now and solved the problem. After doing the tutorial that @ Owsap posted, continue with this: in uitooltip.py def AppendTextLine replace the whole function with: def AppendTextLine(self, text, color=FONT_COLOR, centerAlign=True, show=True): textLine = ui.TextLine() textLine.SetParent(self) textLine.SetFontName(self.defFontName) textLine.SetPackedFontColor(color) textLine.SetText(text) textLine.SetOutline() textLine.SetFeather(False) if show: textLine.Show() textWidth, _ = textLine.GetTextSize() textWidth += 20 # padding tooltipWidthChanged = False if self.toolTipWidth < textWidth: self.toolTipWidth = textWidth tooltipWidthChanged = True self.childrenList.append(textLine) if centerAlign: textLine.SetHorizontalAlignCenter() textLine.SetPosition(self.toolTipWidth / 2, self.toolTipHeight) else: textLine.SetPosition(10, self.toolTipHeight) self.toolTipHeight += self.TEXT_LINE_HEIGHT self.ResizeToolTip() if tooltipWidthChanged: self.AlignTextLineHorizonalCenter() return textLine and def __CalculateToolTipWidth replace whole function with: and also for the ShopEX (to center the required item when the text/stone's name is long and gets resized): in def AddSHOPEXItem change: itemImage.SetPosition(75, self.toolTipHeight) with: itemImage.SetPosition(self.toolTipWidth // 2 - itemImage.GetWidth() // 2, self.toolTipHeight)
