Beiträge von Wodka

    Pokemon Violet - Alpha 2.1 steht vor der Tür!


    Hallo zusammen, es ist wieder soweit, nach unendlich langer Übertragungsarbeit ist das Spiel wieder auf dem Stand, auf dem es vor gut 1 1/2 Jahren war (yeah...).


    Ergo habe ich vor, eine neue Version zu veröffentlichen, welche vom Spielinhalt her rein storytechnisch nicht mehr zu bieten hat, als die Version 2.0. Allerdings existieren einige Features (Geheimpower-Dungeons, Botogel-Geschenke, neue Musik etc.), welche nun erhältlich sind. Storytexte wurden überarbeitet und kürzer, weniger hochtrabend und konsistenter formuliert. Charaktere wurden angepasst, sodass sie weniger klischeehaft wirken. Generell wird man hier und da kleine Änderungen finden können, welche hoffentlich zu einem besseren Spielerlebnis beitragen.


    Bevor ich diese Version allerdings veröffentlichen kann, muss sie natürlich wieder ausgiebig getestet werden. Und da hoffe ich - wie immer - auf die Mithilfe der Fans von Pokémon Violet! Meldet euch einfach hier in diesem Thread und ich werde ich in naher Zukunft (heute, morgen, übermorgen etc.) einen Patch zukommen lassen, welcher die neue Version beinhaltet. Außerdem erhalten alle Tester ein exklusives kleines Geschenk (in Form eines Geschenkcodes, den man bei Botogel einlösen kann).


    Generell benötige ich zwei Arten von Tests:

    A)


    Das Spiel muss von Anfang bis Ende gespielt werden. Möglichst alle Events und Sidequests sollten ausgelöst werden (sprecht mit allen Personen, kehrt zu bereits besuchten Routen zurück, wenn ihr VMs, die Wolke, o.Ä. habt).

    B)

    Übertragt einen Spielstand der Version 2.0 (Achtung, ihr müsst das Spiel auf 2.0 durchgespielt haben, das heißt in der Relaxo-Höhle auf Route 2 gespeichert haben) auf die jetzige Version. Dann muss geprüft werden, ob, wenn man auf vorherige Routen und andere Maps (Städte, etc.) zurückkehrt, irgendwelche Bugs entstehen. Achtet darauf, ob eure Pokémon (Team, Boxen) ihre Werte (Wesen, Angriff, Shiny-Status, etc.) beibehalten. Dieser Teil ist notwendig, damit ich sicherstellen kann, dass auch in Zukunft Transfers eures Spielstandes auf höhere Versionen ohne Probleme von statten gehen können.


    Ist die Version 2.1 dann veröffentlicht, werde ich (endlich!) wieder an neuem Storycontent für Pokémon Violet arbeiten! Ein Danke an alle, die immer noch nicht das Interesse an dem Spiel verloren haben!

    Also wenn ich ehrlich bin: Gedulde dich noch ein paar Wochen, dann release ich (*hust* hoffentlich) den Overhaul zur aktuellen Version (2.1). Wenn du willst, kannst du auch wieder testen ;) Da sind dann die ganzen Features auch enthalten.

    Naja, je größer die LUT, desto weniger machbar wird das Verfahren, einfach die Tabelle abzusuchen. Ich weiß nicht, ob dir Komplexitätstheorie etwas sagt, aber in dem Fall hätte die Sinusfunktion lineare Laufzeit (O(n)), d.h. der Rechenaufwand steigt linear mit der Größe der LUT. Wenn du dagegen den Index in der LUT "berechnest", geht es viel schneller (konstante Laufzeit, unabhängig von der LUT-Größe -> O(1)).


    Jeden Wert in eine Tabelle packen ist auch sehr schlecht machbar, nachdem es unendlich viele rationale / reelle Zahlen zwischen [0;2pi) gibt.

    Den Index eines x-Werts in der LUT kannst du ja auch sehr leicht ausrechnen. x * #Elemente in der LUT / | LUT Intervall |. Und stückweise lineare Interpolation liefert dabei schon sehr gute Ergebnisse. Wenn du sehr genau sein möchtest, nimmst du halt z.B. 2048 Stützstellen her.


    Für die Sinusfunktion gibt es außerdem noch einen Trick: Selbst im Intervall [0;2pi) braucht man nicht alle Stützen, da man die Sinusfunktion durch Spiegelung erhalten kann, wenn man nur das Interval [0;pi/2) interpoliert. Das habe beispielsweise ich so implementiert. Ich interpoliere sin(x) im Interval [0;pi/2) und benutze dann horizontale/vertikale Spiegelung, um auch das Interval [pi/2;2pi) abzudecken.

    Den Kosinus kannst du dann auch über die Identität cos(x) = sin(x - pi/2) implementieren. Den Tangens über tan(x) = sin(x)/cos(x) = sin(x) / (sin x - pi/2).

    Eine lookup-table hält die Werte einer Funktion für bestimmte Eingaben. Eine einfache LUT für Sinus wäre:

    Code
    1. float sin_lut[4] = {0.0, 1.0, 0.0, -1.0}; // Hält Werte für Eingaben 0, pi/2, pi, 3pi/2

    Wenn ich jetzt die Sinusfunktion implementieren will, kann ich einfach linear zwischen zwei Funktionswerten interpolieren.

    Code
    1. float sin(float x) {
    2.     // Es wird angenommen, dass 0 <= x < 2pi
    3.     int lut_idx = (int) (4 * x / (2 * pi)); // Finde den letzten Index, der <= x ist
    4.     float y0 = sin_lut[lut_idx]; // Wert der Sinusfunktion am letzten Index <= x
    5.     float y1 = sin_lut[(lut_idx + 1) % 4]; // Wert der Sinusfunktion am ersten Index > x (Nutze modulo 4, weil der Sinus zyklisch ist)
    6.     float dy = y1 - y0;
    7.     float dx = 2 * pi / 4;
    8.     float a = dy / dx;
    9.     return y0 + a * (x - x0);
    10. }    

    Bildlich passiert folgendes (Achtung, schreckliche Grafik folgt):




    Blau der Graph der Sinusfunktion. Vier sogenannte Stützstellen werden verwendet (bei 0, pi/2, pi und 3pi/2). Zwischen den Stützstellen legen wir Geraden, um die Sinusfunktion zu approximieren. Wenn wir jetzt sin(x) ausrechen wollen, müssen wir:

    a) Die richtige gerade finden (die Stützstellen, zwischen denen x liegt (x0 und x1).

    b) Dann rechnen wir die Geradensteigung aus (y1 - y0) / (x1 - x0) = a

    c) Wir benutzen die Geradengleichung y = mx + t, um sin(x) abzuschätzen (wichtig ist, das x jetzt relativ zu x0 liegt, also verschoben werden muss)
    -> sin(x) ~= y0 + (x - x0) * a


    Wichtige Punkte, die zu beachten sind:

    1. Wie finden wir allgemein heraus, was x0 und x1 sind (im Hinblick auf indizes in der LUT)?

    Idx_x0 = #Stützstellen * x / Größe_des_Intervals
    idx_x1 = idx_x0 + 1


    2. dx = x1-x0 = Größe_des_Intervals / #Stützstellen ist konstant


    3. Je mehr Stützstellen man verwendet, desto genauer wird die Approximation. Ich verwende z.B. 512 Stützstellen. Wie man im Bild sieht, ist die Gerade nicht unbedingt die genauste Approximation.


    4. Es gibt weitaus bessere Verfahren zur Interpolation (so heißt das ganze Verfahren), z.B. kubische Splines. Da wird zwischen die Stützstellen keine Gerade, sondern ein kubisches Polynom dritten Grades gelegt und zwar so, dass an den Stützstellen selbst, zwei aneinandergrenzende Teilfunktionen auch die gleiche Ableitung haben (der Graph also nicht solche Knicke wie im linearen Beispiel macht). Man sollte sich allerdings immer fragen, inwieweit so etwas im Kontext eines PKMN-Games wirklich sinnvoll und notwendig ist (die kubischen Splines sind deutlich aufwändiger zu implementieren).

    Hi, also zuerst, tut die Funktion von Sbird schon, was sie soll:

    Wenn du aber Polynome implementieren willst, wirst du sehr schnell auf Probleme stoßen. Terme von höherem Grad wachsen sehr sehr schnell (also alles über Grad 3 wird dir durch die Decke schießen und damit für 32-bit Integer nicht mehr greifbar). In dem Fall solltest du auf floats zurückgreifen. Für Polynome gibt es zur effizienten Berechnung außerdem das Horner-Schema (siehe Wikipedia).


    Für exp, sin, etc. gibt es im GBA-BIOS nur bedingt support: Das heißt, nur der Arcustangens ist implementiert (als Funktion, die auf fixed point 32-bit Werten mit 16-bit Integer und 16-bit fractional arbeitet. Für die fehlenden Funktionen würde ich auch von den meisten Reihendarstellungen abraten, da sie langsam konverigeren und v.a. Taylor-Reihen auch Terme mit hohem Grad einführen (die das gleiche Problem haben, wie bereits zuvor beschrieben).


    Ich kann dir sagen, wie ich es gelöst habe, und bin damit eigentlich recht zufrieden:

    Für trigonometrische Funktionen (sin, cos, tan) habe ich sogenannte Lookup-Tables (kurz LUT) verwendet, wo für bestimmte Eingaben die Ausgabe einfach hinterlegt ist. Für Eingaben, die nicht in der Tabelle vorhanden sind, kann man einfach zwischen den beiden Werten interpolieren (also einfach eine Gerade durchlegen und das ausrechnen). So verhindert man integer-overflows bei der Taylor-Darstellung der trig-Funktionen.


    Logarithmus arbeitet mit Potenzgesetzten, sodass die Eingabe zuerst ins Interval [1;2] transformiert wird. Danach wird in diesem Fall die logarithmus-Funktion mit einem Polynom fünften Grades approximiert. Auch hier sind die Funktionswerte sehr gut. Meine Implementierung findest du hier. Wenn du bessere Lösungen findest, scheu dich nicht, sie vorzuschlagen.

    Ja, die Eingänge sind etwas klobig. Aber ich hatte in der Vergangenheit schon Probleme mit solchen "versteckten" Eingängen - deswegen habe ich es diesmal sehr sehr signifikant gemacht...

    Schon über ein halbes Jahr lang habe ich wohl kein Lebenszeichen mehr von mir gegeben - aber nein, Violet ist nicht gestorben, ich arbeite im Hintergrund immer noch weiter, allerdings während des Semesters immer langsamer...


    Dafür darf ich jetzt (wohl auch ein bisschen stolz) mein neustes "Feature" präsentieren. Genauergesagt handelt es sich dabei um eine Überholung des alten Systems, zufällige Dungeons zu generieren. Die Areale sehen nun (hoffentlich) viel natürlicher aus, sind aber immer noch völlig zufällig. Man kann nun mittels Geheimpower (in der Laz. Corp erhältlich) in Wäldern, Felswänden oder auf Meeresrouten im 24h Takt neue Areale finden und betreten. In jedem Areal werden auch die wilden Pokemon randomisiert erscheinen. Zusätlich gibt es in jedem Areal einen "Mini-Boss", welcher ein seltenes Pokemon darstellt, welches mit erhöhter Wahrscheinlichkeit Erbattacken, gute DVs, etc. mitbringt.




    Feedback ist gerne gesehen!

    Wird das Levelscript auch nach einem kampf wieder neu überprüft?

    Das kommt auf den Levelscript Typ an. Es gibt solche, die bei jedem Schritt überprüft werden. Normalerweise assoziiert man deshalb Levelscripts (bestimmter Typen) mit einer Variable, sodass die Scripts nur einmalig ausgeführt werden.


    Wenn du möchtest, dass dein Script nach einem (Trainerkampf) startet, kannst du diesen z.B. komplett scripten. Anstatt eines Trainers benutzt du Scriptfelder.

    Hm, sprich du siehst das Celebi Bild nicht? Hast du denn zwischendruch irgendwann Version gewechselt, oder direkt von Anfang an durchgespielt? Hast du das Rom mit irgendwelchen Tools (AM, etc.) geöffnet / bearbeitet?


    Und bezüglich des Zerschneiders: Den bekommt man nun automatisch direkt nach dem Event, das bei dir abbricht...

    Das war auch pures Glück den zweiten zu finden^^ Man kämpft dann weiter gegen das Pkmn gegen welches man verloren hat (Wenn es angeschwächt war geht der Kampf auch abgeschwächt weiter [auch mit falscher Musik natürlich]). Lässt sich gegen den 1. Bug was machen, damit ich weiterspielen kann?


    Es wird tatsächlich schwer, das zu fixen, weil das Spiel, wie ich es gerade habe, sich stark von der jetzigen öffentlichen Version unterscheidet. Sprich ich kann das leider nicht einfach so fixen und einen neuen Patch veröffentlichen.


    Vielleicht können wir das aber so auch irgendwie hinkriegen: Magst du mir mal sagen, vor welchem "Teil" aus dem Event es genau stecken bleibt (vergleiche

    , bei ca. 14 Minuten ist das Celebi Event)

    Die Bugs sind beide nicht bekannt, vor allem der Zweite nicht! Danke dir!


    Edit:

    Bezüglich des zweiten Bugs (Trainer Pkmn): Meinst du das in etwa so? Der verlierst gegen Trainer mit den Pokemon A, B und startest dann einen Pokeradar kampf. Besiegt man das wilde Pokemon, geht der Kampf aber weiter, mit Pokemon B vom Trainer zuvor?

    Ja die Werte steigen mit. Nur konnte ich mit einem Lv. 100 rayquaza und einmal Wutanfall ganze 3 Lv. 170+ erlegen ohne Probleme. Deswegen wollte ich damit mal experimentieren, ob es so besser ist wenn das Endgame stärker Level abhängig ist. Damit man mit 20-30 Level unterschied schon hart knabbern muss um den Gegner zu besiegen. Lv. 20 gg Lv. 50 ist echt hart, aber Lv. 100 gg Lv. 130 ist eigentlich recht ausgeglichen.

    Hm, probier es doch mal mit "normalen" Pokemon. Wie schlägt sich ein durchschnittliches Lv. 100 Pkmn gegen ein durchschnittliches Lv. 170 Pokemon? Rayquaza ist natürlich auch eine Nummer für sich. Du musst dir halt überlegen, 70 Level sind bei Pokemon im Schnitt vielleicht zwischen 140 und 210 mehr auf einem Statuswert. So eine Diskrepanz hast du auch zwischen einem Uber wie Rayquaza auf Lv. 100 und einem eher schwächeren Pokemon auf Lv. 100. Oder anders - wie sieht Rayquqza gegen Rayquaza (100 vs 170) aus?

    Erhalten die Pokémon denn regulär Status-Upgrades (also +XY auf den Stats beim Level-Up)? Falls ja, sollte die Schadensberechnung eigentlich funktionieren. Falls nein, würde ich da ansetzten.


    Die Schadensberechnungsroutine (ich kenne sie nur in Feuerrot) ist prinzipiell zwar komplex, es ist aber kein Problem ans Ende einen Multiplikator zu setzten. Allerdings würde ich dir, Zwecks Balancing, erst einmal empfehlen, zu schauen, ob denn die normalen Werte auch weiter ansteigen bei > Level 100.

    Ich persönlich liebe den GBA Soundfont, weshalb ich auch hauptsächlich mit dem arbeite (und dem DPP Soundfont an manchen Stellen). Allerdings musst du bei z.B. dem Demake halt ein paar Dinge noch anpassen, gerade gegen Ende sind manche Instrumentgruppen zu laut und nicht gut integriert.

    Wie SL schon gesagt hat, sind die letzten paar Tiles im Tileset2 (genauer sollte man 8 Stück freilassen), für Tür Animationen reserviert. Das kannst du auch gut beobachten, wenn du im VBA den TileViewer öffnest und (während Automatic Update läuft) beobachtest, wie genau an der Stelle im Tileset die Tür platziert wird.


    Lösung: Die letzten 8 (oder, wenn du nur eine 1-Tile Tür benutzt, 4) Tiles frei lassen.

    Hat die entsprechende Person denn auch als Flag ID die Flag 0x200? Wann immer du einen Schritt machst, sucht die Spielengine nach NPCs, die in der Nähe sind und überprüft, ob ihre Flag gesetzt ist. Falls dies nicht der Fall ist, wird die Person in einen kleinen Speicher aktiver NPCs geladen, welche dann auch angezeigt werden. -> Die entsprechende Personen Flag auf 0x200 festlegen.


    Außerdem bin ich mir nicht absolut sicher, ob Trainerbattle wirklich verlässlich die Person-Id des Trainers in LASTTALKED lädt, du könntest, falls das oben nicht hilft, also auch versuchen, dem hidesprite explizit die Id der Person zu übergeben.