Beiträge von Lead0b110010100

    I think there is an issue in your logic, atleast under some conditions.

    The method will select the last npc found on the map.


    So if I place 10 NPC's of the same vnum on the map, it will probably select only one (the first npc matching the vnum).

    Maybe it would be better to return a struct with npc's that were found instead of the first one?

    Es gibt kein vollständiges How-To, das ist leider die bittere Wahrheit. Wenn du wirklich alle Stellen erreichen willst, dann wirst du wohl oder übel selbst Hand anlegen müssen oder einen Entwickler dafür bezahlen. Jedes Tutorial was ich bisher gefunden habe, ist nur semi-vollständig und unter gewissen Voraussetzungen exploitbar. Die meisten Spieler kennen diese Features überhaupt nicht, aber das Tanaka Event zum Beispiel. Oder die Drachenhöhle, das "Monarch-System" oder auch Yang in eine Gilde einzahlen!


    Du musst neben dem eigentlichen YMIR Source Code auch unbedingt alle deine Systeme checken (Offline-Shop, Bio-System, Itemshop usw usw.).

    Hab jetzt initial das Movement hinbekommen, auch wenn da noch einige Dinge geschliffen werden müssen.

    Zur Erklärung (Danke Steap noch Mal für das Gespräch gestern, deine erotische Stimme ist immer wieder schön zu hören - Pro Homo):


    Wenn ein Spieler sich in Metin auf der Overworld bewegt, dann rotieren wir den Spieler auf der Z-Achse (Daher oben das 'D3DXMatrixRotationZ'). Man kann sich das vorstellen wie wenn man mit einer Kamera über dem Spieler guckt, die Blickrichtung in die der Spieler schaut wird bestimmt, je nachdem um wieviel Grad man die Z-Achse rotiert.


    ~Lead

    _______________________________________________________________________________________________________


    Für Interessierte:

    Und zum Vorgehen in Metin2 (ist mehrteilig):


    1) Initialer Tastendruck (W, A, S, D und Pfeiltasten):

    - Beim Tastendruck kommen wir zunächst in die CPythonPlayer::SetSingleDirKeyState Methode, dort wird eine Variable gesetzt falls der Spieler eine Positionstaste gedrückt hat: Diese heißt "m_isDirKey", die wird später noch wichtig!

    - Anschließend wird initial CPythonPlayer::SetMultiDirKeyState aufgerufen


    2) Loop für Taste aus 1) gedrückt halten

    - Es gibt eine CPythonPlayer::Update Methode, welche im Main-Loop der Anwendung zur Laufzeit (aufgerufen durch den Client -> app.UpdateGame) drin ist. Diese ruft als aller Erstes folgende Methode auf: RefreshMouseWalkingDirection und diese wiederum prüft:

    Code
    1. if (m_isDirKey)
    2. SetMultiDirKeyState(m_isLeft, m_isRight, m_isUp, m_isDown);

    Der Name der Methode "RefreshMouseWalkingDirection" ist etwas irreführend, weil diese Prüfung immer ausgeführt wird. Aber saubere Funktionsnamen waren vermutlich bei den hunderttausenden Zeilen Code keine Priorität für die Entwickler.

    - Was nun passiert ist, dass währenddessen man die Lauftasten (W, A, S, D und Pfeiltasten) gedrückt hält, man immer wieder in diese CPythonPlayer::SetMultiDirKeyState aus 1) reinkommt, man schickt immer wieder ein Move-Paket an den Server bis man irgendwann stoppt. Dann wird m_isDirKey auf false gesetzt und der Loop wird verlassen.


    3) Und nun?

    - Jetzt, wo wir wissen, wie die normale Bewegung in Metin2 abläuft, können wir weiteruntersuchen und schauen, wie wir den Code ergänzen.

    - Es gibt vermutlich verschiedene Möglichkeiten zur Umsetzung, ich habe die CActorInstance::AddMovement Methode von M2 genutzt. Diese wird aufgerufen, dann wird einem Vektor m_v3Movment.z der zu updatende Wert zugewiesen.


    4) Loop fürs laufen

    - Man kommt in die CPythonCharacterManager::Update Methode (wieder durch app.UpdateGame - CPythonApplication::UpdateGame)

    - Es wird für jede lebende Instanz ein Update ausgeführt in CInstanceBase::Update

    - Über eine Abfrage hier (IsMovement) wird gefragt, ob eines der Werte in m_v3Movement > 0 sind, falls ja, führt man SetPixelPosition aus und aktualisiert die aktuelle Position des Spielers. (Das ist m_x, m_y und m_z für die Interessierten)

    - Weiter im Update Prozess kommt man in die CInstanceBase::MovementProcess und hier wird geprüft

    Code
    1. TPixelPosition kPPosNext;
    2. {
    3. const D3DXVECTOR3 &c_rkV3Mov = m_GraphicThingInstance.GetMovementVectorRef();
    4. kPPosNext.x = kPPosCur.x + (+c_rkV3Mov.x);
    5. kPPosNext.y = kPPosCur.y + (-c_rkV3Mov.y);
    6. kPPosNext.z = kPPosCur.z + (+c_rkV3Mov.z);
    7. }

    - Man berechnet also auf die Current Pixel Position (im Schritt zuvor gesetzt) die Werte aus m_v3Movement (GetMovementVectorRef) drauf.

    - Jetzt finden nach meinem Verständnis Prüfungen statt (1x für mich selbst und 1x für alle anderen Instanzen in meiner Nähe), die berechnen, wie die Instanzen rotieren sollen. Ergo wo der Kopf der Instanzen auf der Overworld hinschaut. Das ist weniger spannend.

    - Wieder zurück in der Update-Methode von CPythonCharacterManager prüfen wir ob der Spieler durch das Laufen in eine Blockzone laufen würde oder mit etwas anderem kollidiert (CheckAdvancing), anschließend rufen wir CInstanceBase::Transform auf (wieder für alle lebenden Instanzen).

    - Hier wird initial ein Paket gesendet (CPacketCGMove):

    damit der Server bescheid weiß, dass der Spieler nun anfängt zu laufen.

    - m_GraphicThingInstance.INSTANCEBASE_Transform() -> CActorInstance::TransformProcess -> SetPosition setzt die neuen m_x, m_y und m_z Werte des Spielers (errinert euch von oben, das ist die Current Position) und setzt m_v3Movement (x, y und z) wieder auf 0.

    - Diese wird zuletzt bei der CGraphicObjectInstance::Transform Methode genutzt, um die WorldMatrix zu aktualisieren

    - Und der Rest der Renderprozesses (sowie der Animationen) ist von Metin bereits implementiert. Erläutere ich hier mal nicht, jeder der aber mal ein Object / Model Scaling System eingebaut hat, ist an all den relevanten Stellen vorbeigekommen.

    Code
    1. m_worldMatrix = m_matScaleWorld * m_mRotation;
    2. m_worldMatrix._41 += m_v3Position.x;
    3. m_worldMatrix._42 += m_v3Position.y;
    4. m_worldMatrix._43 += m_v3Position.z;


    Ich hab fertig, ich bin fertig und ich brauche eine Pause. Gute Nacht Leute. ;D

    Hallo liebe Community,


    da ich aktuell mit meinem Latein am Ende bin, dachte ich, ich poste ein Stück Code, welches sich meiner Verständnis entzieht.

    Konkret versuche ich zu verstehen, wie Metin2 die Bewegungen des Spielers errechnet. Hier was ich dazu hab:


    Warum sorgt die UpdateTransform Methode dafür, dass die Z-Achse (s_matRotationZ._43) sich nie aktualisiert?

    Intern ruft diese Methode irgendwann

    Code
    1. GrannyUpdateModelMatrix(m_pgrnModelInstance, fSecondsElapsed, (const float *)pMatrix, (float *)pMatrix, false);


    auf. Leider geht es dann nicht mehr weiter und ich hab keine Doku zu Methoden von Granny2 gefunden, kommentiert ist das auch nicht.

    Das Ergebnis ist jedenfalls, dass die X und Y Achse aktualisiert wird (Spieler läuft ja rum, also logisch), die Z-Achse aber unberührt bleibt.


    Wie würde man es hinbekommen, dass der Spieler sich "nach oben" bewegt?

    Ich kann meine Hintergründe gerne auch verraten, ich versuche mich aktuell an einem Mount Fly System. Habe mir die vorhandenen Lösungen angesehen und diese erweitern das Vorhandene von YMIR nicht, sondern sie führen einfach eine Variable "m_fZExtra" ein und addieren sie in der "AddMovement..." Zeile zur Z-Achse.


    Mein Ziel wäre aber ein Verständnis für diesen Prozess aufzubauen und mich da ranzutasten.


    Danke für den Wissensaustausch im Voraus.


    ~Lead

    Keine Animation um die Truhe zu öffnen? :(
    Oder so eine coole Animation wie bei Kratos, der einfach die Truhe kaputtschlägt!


    Beim Metin2 Ninja wärs dann eher so hier:


    Bitte melden Sie sich an, um diesen Link zu sehen.


    P.S: Der Sound ist ein geniales Detail :)

    Ich suche irgendwie verzweifelt nach dem P2P Paket, welches den Kick ausführt. Hast du dich vielleicht verschrieben?

    "Kick läuft über p2p ist die Standart Funktion von Metin"


    Das Ergebnis der aktuellen Logik ist:

    1) Spieler ist auf Map 1

    2) GM bannt Spieler über GUI

    3) Spieler teleportiert vorher in einen Dungeon

    4) Spieler darf weiterspielen, handeln, chatten, seine Items handeln usw.


    Das ist schon nicht minder interessant für Leute, die das "System" verwenden möchten.

    Wenn ich was übersehe, dann sags mir ruhig. Kann ja sein.


    Nachtrag: In etwa so müsste das eigentlich aussehen..


    Ist das etwas, was Metin bei der Charaktererstellung verbietet?

    Dann wäre der Punkt natürlich nichtig.


    Wobei dann erst Recht das 'LIMIT 1' weg kann, wenn es sowieso keine Dopplungen gibt.

    Hey mein Lieber,


    dachte ich nehme mir mal die Zeit und schaue über den Code, was mir dabei aufgefallen ist:


    1) Prüfungen auf Ungleichheit mittels == false

    Code
    1. if (ch->IsGM() == false || ch->GetGMLevel() < GM_HIGH_WIZARD) {


    Das ist redundant, schöner und kürzer wäre:

    Code
    1. if (!ch->IsGM() || ch->GetGMLevel() < GM_HIGH_WIZARD) {


    2) Magic Numbers

    Im Code gibt es häufig irgendwelche rumfliegenden Zahlen, wo man Variablen (Enums, Enum Classes) nutzen sollte.

    Code
    1. char escapePlayer[20 * 2 + 1];


    sollte vermutlich sein:

    Code
    1. char escapePlayer[CHARACTER_NAME_MAX_LEN + 1];


    3) Spieler Namen Query

    Diese Query wäre nur dann erfolgreich, wenn es genau einen Spieler gibt, der sich (case insensitiv) gleich nennt.


    Beispiel:

    - Spieler A nennt sich 'Lead'

    - Spieler B nennt sich 'lEad'

    - Spieler C nennt sich 'lead'


    Frage: Welchen Spieler selected die folgende Query? Bannt man Ausersehen den Falschen?

    SQL
    1. SELECT player.account_id FROM player WHERE LOWER(name) like LOWER('%s') LIMIT 1


    Es gibt hier nun verschiedene Lösungsansätze, z.B: über die Datenbank ünmöglich zu machen (über einen Constraint), dass die selben Namen genutzt werden (inkl. Groß und Kleinschreibungs-Check). Alternativ würde ich die Query auf Gleichheit prüfen und ohne LIMIT 1, von einem Usernamen wird es nur genau einen geben. Der GM muss dann auf Groß und Kleinschreibung achten.

    SQL
    1. SELECT player.account_id FROM player WHERE name = '%s'


    4) Guards für Anweisungen


    könnte man für lesbareren Code auch so schreiben (der Else-Pfad würde sowieso nie erreicht werden oder nur fälschlicherweise wenn tch = NULL wäre):


    Ein paar finale Hinweise:

    - Denk daran, dass das Bannen aktuell nicht Core-übergreifend funktioniert. Sollte der Spieler sich also rechtzeitig wegteleportieren oder ausloggen, hast du womöglich ein Problem.

    - In der CInputMain::Analyze befindet sich direkt bei Eintritt folgende Prüfung:

    Code
    1. int32_t CInputMain::Analyze(LPDESC d, uint8_t bHeader, const char *c_pData)
    2. {
    3. LPCHARACTER ch;
    4. if (!(ch = d->GetCharacter()))
    5. {
    6. sys_err("no character on desc");
    7. d->SetPhase(PHASE_CLOSE);
    8. return (0);
    9. }


    Du kannst also so Prüfungen wie "!ch" oder "!d->GetCharacter()" weglassen, außer du erwartest aus irgendwelchen Gründen, dass der User genau in dieser Milisekunde destroyed wird, in der dieser Code ausgeführt wird. Unmöglich ist nichts, aber dann haben wir größere Probleme als das :D

    - Nach einem 'strncpy' emphielt es sich, explizit die letzte Stelle des Arrays auf '\0' zu setzen, siehe hier:

    Code
    1. strncpy(szName, c_szName, GUILD_GRADE_NAME_MAX_LEN);
    2. szName[GUILD_GRADE_NAME_MAX_LEN] = '\0';

    Weil diese Methode so funktioniert, dass wenn dein Buffer nicht groß genug ist, sie nur "so viele Bytes von src kopiert, wie möglich". Ergo, wenn dein Buffer nur 10 Stellen groß ist, du willst aber 11 kopieren, hört er bei 10 auf. Die 11. Stelle wäre aber das benötigte '\0' gewesen. Mit strlcpy hat man das Problem btw. nicht.

    - Dir fehlt die Logik, wenn ein Spieler in einem Dungeon ist und der Gruppenleiter. Das kann dazu führen, dass die Gruppe den Dungeon nicht mehr beenden kann, je nachdem wie die Quest geschrieben ist.


    Zum Abschluss:

    Ich hoffe, dass es mehr Beiträge wie dieses gibt und das wir weiterhin gute Entwickler in der Szene haben, die so freundlich sind, uns ihre Arbeiten zur Verfügung zu stellen.


    ~Lead

    Entweder die meisten sind vom Verständigkeitsgott geküsst worden oder ich verstehe dein genaues Problem / Ansatzpunkt nicht anhand deiner Erklärung / Skizzierung. Du hast ein Fehler bemerkt, aufgrund von?

    Einer falschen Konfiguration auf meiner Seite.

    Das Finding hat mich viel Zeit gekostet, das Fixen war nicht so wild.


    Konkret hab ich in meiner serverinfo.py den Main-Port sagen wir A eingetragen, ein Core zu diesem Port existierte aber nicht. Also hat man eine "Fehler beim Verbinden mit dem Server" Meldung bekommen, nach dem initialen "Anmeldevorgang läuft". Der Output von Metin2 in diesem Case (auf dem Auth Core) ist:



    Weiter passierte nichts. Und auf der Suche nach dem Fehler hab ich gemerkt, dass ich den gesamten Prozess zunächst einmal überblicken können muss, bevor ich weiß warum es an dieser Stelle nicht weiter geht. Ich wüsste sonst überhaupt nicht, wo ich ansetzen soll und blind suchen, wollte ich nicht - Das ist meist deprimierend und nicht von Erfolg gekrönt.


    Wie so häufig ist aber eine falsche Konfiguration die häufigste Fehlerursache. Und jetzt kommts:

    Wenn ein Socket nicht erreichbar ist, hat die WSAGetLastError-Methode der winsock Library folgende Fehler-ID ausgegeben:

    Code
    1. // A non-blocking socket operation could not be completed immediately.
    2. //
    3. #define WSAEWOULDBLOCK 10035L


    und nicht wie erwartet


    Code
    1. /*
    2. * This is used instead of -1, since the
    3. * SOCKET type is unsigned.
    4. */
    5. #define INVALID_SOCKET (SOCKET)(~0)
    6. #define SOCKET_ERROR (-1)


    Und was macht Metin bei dieser Fehler ID? Genau, sie ignorieren und nichts loggen!


    Code
    1. if (connect(m_sock, reinterpret_cast<PSOCKADDR>(&m_addr), m_addr.GetSize()) == SOCKET_ERROR)
    2. {
    3. int error = WSAGetLastError();
    4. if (error != WSAEWOULDBLOCK)
    5. {
    6. ...
    7. }
    8. }


    Hoffe das genügt dir als Abriss, was meine Intention war bei dem Vorgehen.

    Hallo liebe Community,


    ich habe gerade mal wieder Lust bekommen, mich auf meinem Server einzuloggen (lokal) und bin in ein Problem beim Loginprozess gestoßen.

    Leider gab es keine hilfreiche sys_err, nur irgendwann das Resultat "closing socket..." vom Auth Core.


    Also hab ich mich entschieden mal den Login-Prozess zu skizzieren und konnte meinen Fehler dadurch finden und fixen.

    Vielleicht hilft es ja dem Einen oder Anderen von euch auch. Es gab leider keinen Bereich "Architektur" oder sowas Ähnliches für solche Bildchen.


    Bitte melden Sie sich an, um diesen Anhang zu sehen.


    Was in dem Bild fehlt:

    - Abläufe im DB-Core (QID_LOGIN_BY_KEY -> QID_LOGIN im Erfolgsfall usw.)

    - Master / Slave Konzept für den Auth-Core

    - Vorgänge im Client (speichert intern einen State usw.)


    ~Lead

    So wie du das hier machst, lädst du die aber nur auf der Core neu, auf der du gerade bist.

    Besser wäre per P2P Packet alle Cores darüber zu verständigen, sodass du z.B: auf Map 1 Blau die selben "Ergebnisse" bekommst wie auf Map 1 Gelb*.


    * vorausgesetzt sie befinden sich auf verschiedenen Cores

    Überprüf sonst noch mal was von GetGold zurück kommt, ggf. hab ich da irgendwo was vergessen.

    Wollte gerade kommentieren, dass im Code eine Prüfung für das Gold des Spielers fehlt, weil man sonst auch ohne genug Gold pullen kann, hab aber dann das gesehen. Good catch! <3


    Erklärung warum deine vorherige Lösung "nicht geklappt hat":

    SetGold setzt zwar das Gold auf Serverseite, sendet dem Client aber kein Paket zur Aktualisierung. Mit PointChange(POINT_GOLD, -100) wird dieses Paket gesendet :)

    Würde nur die Verteilung von ToxiC nochmal etwas anpassen. Ich behaupte Metin2 ist:


    40% Python

    30% Lua

    20% C / C++

    10% SQL


    Also rein von der zeitlichen Verteilung wirst du viel eher am Python Code sitzen, durch das Ganze designen und austesten oder eine Quest schreiben / verbessern,

    als jetzt ein neues System in C / C++ zu implementieren oder speziellen Items eine Funktion zu geben. Besonders wenn du ein Oldschool Server bist, fummelst du kaum am Backend herum, oder baust nur vorhandene Systeme ein die durch die ganzen Server gut getestet sind.

    Marty wrote a good explanation on another board about this issue. Just make sure, that your coordinates are an even divisible of 1024. Soo:

    Good example: 307200 76800 (307200 % 1024 is 0 and 76800 % 1024 is 0)

    Bad example: 987600 1234000


    This issue was not related to the server_attr generation.

    I've already had a very lengthy conversation with Marty explaining this to me. Nevertheless I still have problems with the Block-Zones.

    You shouldn't if you did it right.

    That's the fix. Maybe you show us the Settings.txt of the map that still has the problems?


    Or better: Check your atlasinfo.txt and your serverside maps.

    martysama0134 Could you please fix the server_attr generation?

    Blockzones are just not loaded in game correctly and there is no real alternative at the moment.

    Anyone still having trouble with this?

    Marty wrote a good explanation on another board about this issue. Just make sure, that your coordinates are an even divisible of 1024. Soo:

    Good example: 307200 76800 (307200 % 1024 is 0 and 76800 % 1024 is 0)

    Bad example: 987600 1234000


    This issue was not related to the server_attr generation.

    Bugfreie Systeme? Niemand.

    Und das ist auch gut so! Ich erkläre dir auch gerne warum:


    Ich und alle anderen Softwareentwickler der Welt wären damit abgelöst und arbeitslos, da unsere Arbeit komplett frei von Bugs wäre. Demnach müsste man eine Software ja nur ein Mal entwickeln und danach nie wieder warten oder ändern. Neue Anforderungen und Features würden immer direkt funktionieren und auf jedem der mittlerweile gefühlt 680 Millionen Typen von Medien, Browsern und Endgeräten problemlos funktionieren.


    Daher: Ich habe noch nie ein System entwickelt, was komplett frei von Bugs ist, nur Software, bei denen die Bugs noch nicht aufgefallen sind.

    Wenn das so einfach wäre: Warum braucht Google tausende Entwickler, wahrscheinlich die Besten der Besten, immernoch?


    Die Anzahl an Bugs, Sicherheitslücken etc pp. steigt exponential, da die Komplexität von Anwendungen über die Jahre stark zugenommen hat.

    Damals war ein simples Bildbearbeitungsprogramm zum Anzeigen völlig genügend auf den alten 'Apple 1' und 'Apple 2' PC's.


    Heute wird Photoshop immernoch jährlich erweitert mit hochmodernen Algorithmen, die für dich einen Gegenstand aus einem Bild rauskopieren

    und die freigewordenen Pixel errechnen und erraten, damit es natürlich aussieht.

    Arbeit reinstecken, oder nen alten Rubi client entpacken, da findest Du einige Sprachen vorgaben

    Arbeit reinstecken ist ja kein Ding, wollt nur wissen ob sowas schneller geht.

    Wenn das nur per Hand alles geht, dann ist das ok. :)

    Du kannst dir auch ein Script schreiben, 3-5€ in die DeepL API bezahlen und dann jede Sprache über das Script übersetzen lassen.

    Also zumindestens jede außer Deutsch, Türkisch und Englisch.

    Für die drei findest du sicher noch händische Übersetzer. Bei Arabisch wirds zum Beispiel meist schwieriger.