Posts by GearByte

    Update: Crafting

    Das Prinzip „Jeder kann alles“ (mit Einschränkungen)
    Grundstufe (1–50): Theoretisch kann ein Spieler alle Berufe (Schmied, Schneider, Lederverarbeiter, Kräuterkunde) gleichzeitig erlernen und hoch leveln. Es gibt keine Beschränkung hinsichtlich der Anzahl der Berufe.


    Meisterschaftsgrenze (Fertigkeit 50): Sobald ein Spieler in einem Beruf Fertigkeit 50 erreicht, greift die Meisterschaftslogik des Servers.


    Die Entscheidung: Beim Versuch, einen Beruf von 50 auf 51 zu steigern, prüft der Server: „Hat dieser Spieler bereits einen anderen Beruf über Stufe 50?“
    Nein: Die Fertigkeit steigt auf 51. Dieser Beruf ist nun dein Meisterberuf. Die Obergrenze für diesen Beruf wird auf 100 erhöht.
    Ja: Die Fertigkeit bleibt bei 50. Du erhältst die Fehlermeldung: „Du kannst nur einen Beruf über Stufe 50 meistern!“

    Beispiel: Du kannst Schmiedekunst, Schneiderei und Lederverarbeitung auf Stufe 50 haben. Sobald du die Schmiedekunst auf Stufe 51 bringst, bleiben Schneiderei und Lederverarbeitung dauerhaft auf Stufe 50.

    Die Rezepte sind farblich gekennzeichnet, um dem Spieler die Wahrscheinlichkeit einer Fertigkeitssteigerung anzuzeigen.
    Orange: 100% Chance auf Skill Steigerung.
    Gelb: 50% Chance auf Skill Steigerung.
    Grün: 25% Chance auf Skill Steigerung.
    Grau: 0% Chance auf Skill Steigerung.

    $gameMap.events().forEach(function(ev) {
    // Prüfen ob Event existiert und den richtigen Namen hat
    if (!ev || !ev.event() || !ev.event().name.includes("Schwarze Ordenswache")) return;

    // === INIT ===
    if (!ev._moveCounter) ev._moveCounter = 0;
    if (ev._state == null) ev._state = "patrol";
    if (ev._chaseTarget == null) ev._chaseTarget = null;
    if (!ev._lostSightTimer) ev._lostSightTimer = 0;
    // NEU: Initialisierung der letzten bekannten Position
    if (ev._lastSeenX == null) ev._lastSeenX = ev.x;
    if (ev._lastSeenY == null) ev._lastSeenY = ev.y;

    ev._moveCounter++;
    if (ev._moveCounter < 2) return; // Drosselung (optional, aber gut für Performance)
    ev._moveCounter = 0;

    const player = $gamePlayer;
    const dx = player.x - ev.x;
    const dy = player.y - ev.y;
    const dist = Math.sqrt(dx * dx + dy * dy);

    // === SICHTKONTROLLE (Raycasting) ===
    const maxDist = 10;
    const fov = 60;
    const dirAngles = { 2: 90, 4: 180, 6: 0, 8: 270 };
    const guardAngle = dirAngles[ev.direction()];
    // Berechnung des Winkels zum Spieler
    const angleToPlayer = (Math.atan2(-dy, dx) * 180 / Math.PI + 360) % 360;
    let diff = Math.abs(guardAngle - angleToPlayer);
    if (diff > 180) diff = 360 - diff;

    let seesPlayer = false;
       
    // Einfache Distanz und Winkelprüfung
    if (dist <= maxDist && diff <= fov / 2) {
    seesPlayer = true; // Erstmal annehmen, wir sehen ihn
    const steps = Math.ceil(dist * 2);
    // Raycasting: Prüfen ob Wände im Weg sind
    for (let i = 1; i < steps; i++) {
    const stepX = Math.round(ev.x + (dx * (i / steps)));
    const stepY = Math.round(ev.y + (dy * (i / steps)));
    // Prüft Passability in alle 4 Richtungen an diesem Punkt
    if (
    !$gameMap.isPassable(stepX, stepY, 2) &&
    !$gameMap.isPassable(stepX, stepY, 8) &&
    !$gameMap.isPassable(stepX, stepY, 4) &&
    !$gameMap.isPassable(stepX, stepY, 6)
    ) {
    seesPlayer = false;
    break;
    }
    }
    }

    // === STATUSWECHSEL & MERKEN ===
    if (seesPlayer) {
    ev._state = "chase";
    ev._chaseTarget = player;
    ev._lostSightTimer = 0;
           
    // NEU: Position merken, solange wir ihn sehen!
    ev._lastSeenX = player.x;
    ev._lastSeenY = player.y;

    } else if (ev._state === "chase") {
    ev._lostSightTimer++;
           
    // GEÄNDERT: Wenn er an der letzten bekannten Position angekommen ist
    // UND den Spieler immer noch nicht sieht -> sofort aufgeben oder suchen.
    if (ev.x === ev._lastSeenX && ev.y === ev._lastSeenY) {
    ev._state = "alert";
    ev._lostSightTimer = 0;
    }
           
    // Oder wenn der Timer abläuft (Timeout)
    if (ev._lostSightTimer > 180) {
    ev._state = "alert";
    ev._chaseTarget = null;
    ev._lostSightTimer = 0;
    }
    }

    // === BEWEGUNGSVERHALTEN ===
    if (ev._state === "chase" && !ev.isMoving()) {
    ev.setMoveSpeed(4);
           
    // GEÄNDERT: Zielwahl
    // Wenn wir ihn sehen -> Lauf zum Spieler.
    // Wenn nicht -> Lauf zur letzten bekannten Position.
    let targetX, targetY;
           
    if (seesPlayer) {
    targetX = player.x;
    targetY = player.y;
    } else {
    targetX = ev._lastSeenX;
    targetY = ev._lastSeenY;
    }

    const dx2 = targetX - ev.x;
    const dy2 = targetY - ev.y;
    const dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);

    if (dist2 > 0) { // Größer 0, sonst zittert er auf der Stelle
    const dir =
    Math.abs(dx2) > Math.abs(dy2)
    ? (dx2 > 0 ? 6 : 4)
    : (dy2 > 0 ? 2 : 8);

    // Simples pathfinding (Try & Error)
    if (ev.canPass(ev.x, ev.y, dir)) {
    ev.moveStraight(dir);
    } else {
    // NEU: Kleiner Tweak - wenn geradeaus blockiert ist, versuche die andere Achse
    // Das verhindert Hängenbleiben an Ecken
    const altDir = (dy2 > 0 ? 2 : 8); // Vertikale Alternative, wenn Horizontal blockiert
    if (Math.abs(dx2) > Math.abs(dy2) && ev.canPass(ev.x, ev.y, altDir)) {
    ev.moveStraight(altDir);
    } else if (ev.canPass(ev.x, ev.y, (dx2 > 0 ? 6 : 4))) {
    ev.moveStraight((dx2 > 0 ? 6 : 4));
    }
    }
    } else {
    // Ziel erreicht (sei es Spieler oder letzte Position)
    ev.setDirection(ev.direction());
    }
    return;
    }

    if (ev._state === "alert" && !ev.isMoving()) {
    ev.setMoveSpeed(3);
    // Zufälliges Umherschauen / kleine Schritte
    if (Math.random() < 0.1) { // Nicht jeden Frame bewegen, wirkt natürlicher
    const dirs = [2, 4, 6, 8];
    const rand = dirs[Math.floor(Math.random() * 4)];
    if (ev.canPass(ev.x, ev.y, rand)) ev.moveStraight(rand);
    }

    ev._lostSightTimer++;
    if (ev._lostSightTimer > 300) {
    ev._state = "patrol";
    ev._lostSightTimer = 0;
    }
    return;
    }

    if (ev._state === "patrol" && !ev.isMoving()) {
    ev.setMoveSpeed(3);
    // Behält die Editor-Route bei
    }
    });

    Das Problem in deinem Quell ode:

    1. Der Spieler rennt Hinter die Wand.
    2. seesPlayer wird false.
    3. Der Code springt in den else if (ev._state === "chase") Block.
    4. Der _lostSightTimer beginnt zu zählen (deine 1,5–2 Sekunden / 180 Frames).
    5. Aber: Während dieser Zeit ist der Status immer noch "chase". Dein Bewegungscode nimmt sich als Ziel (target) immer noch den aktuellen Standort des Spielers (player).

    Der Gegner "weiß" also durch die Wand hindurch exakt, wo du bist, und versucht, die Luftlinie zu dir zu laufen. Da die Wand dazwischen ist, rennt er dagegen.


    meine Idee:

    Der Gegner darf nicht den Spieler jagen, wenn er ihn nicht sieht, sondern mit dei Stelle, an der er den Spieler zuletzt gesehen hat.

    Wir müssen zwei kleine Dinge ändern:

    1. Speichern der Koordinaten, solange Sichtkontakt besteht (_lastSeenX, _lastSeenY).
    2. Wenn der Sichtkontakt abbricht, läuft der Gegner zu diesen Koordinaten anstatt zum Live-Spieler.

    Update: Skillsystem (in Progress)

    Positionen der Talente, Abhängigkeiten, Ränge, Effekte, Icon, Auswirkung, Beschreibung etc. alles wird über die Datenbank gesteuert. Bei Änderungen muss der Client nicht updaten, da beim Login alle Daten per TCP synchronisiert werden.

    1. Dynamische & Unendliche Talentbäume

    • Unbegrenzter Platz: Egal wie groß ein Talentbaum wird, das UI passt sich automatisch an und lässt den Spieler scrollen.
    • Data-Driven: Das Layout (Positionen, Verbindungen, Pfeile) wird zu 100% aus der Datenbank geladen. Wir können Bäume umbauen, ohne den Client zu patchen.
    • Visualisierung: Voraussetzungen (Pfeile) werden automatisch gezeichnet und färben sich ein, sobald ein Talent verfügbar ist.

    2. Server-Authoritative Logik

    Der Client ist "dumm" und zeigt nur an, was der Server erlaubt.

    • Punkte-Berechnung: Der Server berechnet basierend auf dem Level, wie viele Punkte verfügbar sind. Cheaten ist unmöglich.
    • Echtzeit-Updates: Egal ob durch Quest-XP, Monster-Kills oder GM-Befehle – sobald das Level steigt, sind die neuen Talentpunkte sofort verfügbar, ohne Relog.
    • Respec-Funktion: Ein vollständiges Reset-System (z.B. auch durch GM-Befehl) entfernt sauber alle gelernten Zauber, löscht passive Buffs und erstattet die Punkte zurück.

    3. Hybride Effekte: Aktiv & Passiv

    Das System unterscheidet intelligent zwischen zwei Arten von Talenten:

    1. Aktive Fähigkeiten: Lernt der Spieler z.B. "Shadow Strike", wird der Zauber direkt ins Zauberbuch geschrieben und kann in die Leiste gezogen werden.
    2. Passive Stats: Talente wie "+5% Krit" werden als permanente, unsichtbare Aura (Buff) auf den Spieler gewirkt.



    Das ganze wird natürlich noch verschöndert.

    Update: Serverseitiges Anticheat gegen Speedhacks und Teleportation

    Wie funktioniert es? (Die "Time-Bank" Logik)

    Stellt euch vor, jeder Spieler hat ein unsichtbares "Zeit-Konto" auf dem Server:

    1. Für jede Sekunde, die in der echten Welt vergeht, bekommt ihr 1 Sekunde Bewegungsguthaben.
    2. Wenn ihr lauft, "bezahlt" ihr dieses Guthaben basierend auf eurer erlaubten Geschwindigkeit.
    3. Normales Laufen: Ihr gebt genau so viel aus, wie ihr bekommt. Das Konto bleibt stabil.
    4. Lag/Ruckler: Wenn ihr kurz laggt, spart ihr Guthaben an. Wenn das Internet wieder da ist, dürft ihr kurz "schneller" sein, um aufzuholen (Lag-Kompensation).
    5. Speedhack: Ihr bewegt euch schneller als erlaubt. Ihr verbraucht mehr Guthaben, als ihr bekommt. Sobald das Konto leer ist (nach ca. 0,5 Sekunden bei Speedhacks), greift das System ein.

    Was passiert bei einem Verstoß?

    1. Rubberbanding (Gummiband-Effekt): Der Server lehnt eure falsche Position ab und zwingt euren Client hart auf die letzte gültige Position zurück. Ihr werdet also immer wieder zurückgesetzt, wenn ihr versucht zu hacken.
    2. GM-Alarm: Die GameMaster erhalten sofort eine Meldung mit dem Namen des Spielers und der Abweichung (z.B. "Speedhack detected: PlayerX is 0.8s too fast").
    3. Auto-Ban: Wenn ein Spieler das System dauerhaft triggert (ca. 25 Verstöße in Folge ohne GM-Eingriff), wird der Account automatisch für 24 Stunden gesperrt.


    Update: Einsammelbare Game Objekte.

    Ab sofort können Ressourcen oder Questitems eingesammelt werden.

    Jedes GameObjekt kann ganz einfach in der Datenbank angelegt werden. Hierzu gibt es folgende Einstellungen die man wählen kann:

    Typ-Unterscheidung:

    • Shared: Das Objekt verschwindet für alle nach Benutzung (perfekt für Wirtschaft/Ressourcen).
    • Personal: Das Objekt verschwindet nur für den Benutzer (perfekt für Quests).

    Loot-Logik:

    Jedes Objekt ist mit einer Loot-Table verknüpft. Es kann feste Quest-Items droppen oder (bei Erzen) zufällige Edelsteine mit Wahrscheinlichkeiten.

    Respawn-Timer:
    Es gibt eine Min- und Max-Zeit. Ein Erzvorkommen kommt also nicht stur nach 5 Minuten wieder, sondern zufällig zwischen z.B. 10 und 20 Minuten, um Campen zu erschweren.

    Cast-Zeit & Animation:
    Wie lange dauert das Abbauen? Welche Animation spielt der Charakter dabei ab (mining, gathering, opening, etc.)?

    Sichtbarkeit und Bedingungen:

    • Benötigt der Spieler ein bestimmtes Item im Inventar? (z.B. Spitzhake für Erze)
    • Benötigt der Spieler eine aktive Quest?

      Performance:
      Die Objekte werden – genau wie NPCs und Spieler – über das Grid-System verwaltet. Der Client lädt sie erst, wenn er in die Nähe kommt, was die Performance hoch hält.

    Es wurde außerdem die Webseite geupdated: http://www.fantasyofeldoria.com
    und eine komplett neue Beta Seite erstellt. http://www.fantasyofeldoria.com/beta

    Hi,

    das Handelssystem ist in 4 Sicherheitsebenen unterteilt:

    1. Atomare Transaktionen (Schutz gegen Item-Duping durch Server-Crashes oder Lags).
    Beispiel: In schlechten Systemen wird erst dem Spieler A das Item weggenommen (Datenbank Update) und dann Spieler B das Item gegeben (Datenbank Insert). Wenn der Server dazwischen abstürzt, ist das Item weg (oder bei umgekehrter Reihenfolge verdoppelt).
    Lösung in meinem Code: Der Handel wird im RAM in einem einzigen "lock" Block ausgeführt, damit der Tausch in einem einzigen CPU-Takt passiert. Außerdem lösche ich die alte Iteminstanz aus der Datenbank und erstelle direkt eine neue. Selbst wenn das Speichern bei Spieler B fehlschlägt, hat Spieler A das Item im RAM nicht mehr.

    2. Globales Locking
    Das Spielerinventar wird gesperrt, wenn der Spieler handelt. Er kann also nichts an NPCs verkaufen oder aus dem Inventar löschen.

    3. Validierung "Last Second" (Anti-Quick-Switch)
    Beispiel: Spieler A legt ein Item rein. Spieler B klickt "Trade". Im letzten Millisekunden-Moment tauscht Spieler A das Item gegen Müll aus oder entfernt es komplett und klickt auch "Trade".
    Lösung: Jede Änderung am Handel (Item rein/raus, Geld ändern) setzt IsAccepted bei beiden sofort auf false.

    4. Umwelt-Faktoren (Anti-Lag-Switch & Distance)
    Distanz-Check: Der Server prüft bei jedem Schritt (Update-Loop im TradeManager) und bei jeder Aktion (PlayerAccept), ob die Spieler weiter als 6 Meter entfernt sind. Falls ja wird der Handel automatisch abgebrochen.
    Disconnect-Handler: Wer offline geht (auch durch Crash/Alt+F4/Connection lost etc...), bricht den Handel ab. Items bleiben beim Besitzer.


    Falls ihr noch mehr Fragen habt oder testen möchtet, könnt ihr mir gerne schreiben! Ein Testsystem ist 24/7 online.

    Das nimmt alles immer mehr Gestalt an, der Look gefällt mir bisher sehr :thumbup:

    Hi schadowdong, danke für deine Antwort ^^


    Aktuell arbeite ich ebenfalls an einer Webseite:


    Fantasy of Eldoria - Join the Adventure
    Enter a world torn apart by ancient magic. Choose your class and fight for the soul of Eldoria in this open-world MMORPG.
    www.fantasyofeldoria.com


    Außerdem kann der Webserver die Anzahl der aktuellen Spieler aufrufen und auf der Webseite anzeigen:

    Server Status - Eldoria

    Später ist auch noch ein Control Panel geplant, indem die Spieler sich anmelden können.

    Hier schon ein Konzeptscreen: