Möglichkeiten zum Verwalten sich ändernder Designdaten neben sich ändernden Spielerdaten


14

Ich habe ein Online-Spiel, in dem die Spieler die Welt auf irgendeine Weise gestalten können - z. Das Gehäuse von Ultima Online, in dem Sie Ihre Häuser direkt auf bestimmten Teilen der Weltkarte bauen können. Dies sind Veränderungen, die im Laufe der Zeit als Teil der beständigen Welt fortbestehen sollten.

Gleichzeitig fügt das Designteam neue Inhalte hinzu und ändert alte Inhalte, um das Spiel für neue Spieler zu verbessern und zu erweitern. Sie tun dies zuerst während des Testens auf einem Entwicklungsserver und müssen dann später ihre Arbeit mit der "Arbeit" der Spieler auf dem Live-Server zusammenführen.

Angenommen, wir beheben die Probleme mit dem Spieldesign - z. Spieler können nur in bestimmten Bereichen bauen, sodass sie niemals geografisch mit Designer-Bearbeitungen in Konflikt geraten. Was sind gute Möglichkeiten, um mit den Daten umzugehen oder die Datenstrukturen so anzuordnen, dass Konflikte vermieden werden, wenn neue Designer-Daten mit neuen Player-Daten zusammengeführt werden?

Beispiel 1: Ein Spieler stellt einen neuen Gegenstandstyp her und das Spiel weist ihm die ID 123456 zu. Alle Exemplare dieses Gegenstands beziehen sich auf 123456. Stellen Sie sich nun vor, die Spieleentwickler haben ein ähnliches System, und ein Entwickler erstellt einen neuen Gegenstand mit der Nummer 123456. Wie kann dies vermieden werden?

Beispiel 2: Jemand macht einen beliebten Mod, der all Ihren Drachen einen französischen Akzent verleiht. Es enthält ein Skript mit einem neuen Objekt namens, mit assignFrenchAccentdem die neuen Sprachelemente jedem Drachenobjekt zugewiesen werden. Aber Sie sind dabei, Ihren "Napoleon vs Smaug" -DLC bereitzustellen, der ein Objekt mit dem gleichen Namen hat - wie können Sie dies ohne viele Kundendienstprobleme tun?

Ich habe mir folgende Strategien überlegt:

  • Sie können 2 separate Dateien / Verzeichnisse / Datenbanken verwenden, aber dann sind Ihre Lesevorgänge erheblich kompliziert. "Show All Items" muss einen Lesevorgang in der Designer-Datenbank und einen Lesevorgang in der Player-Datenbank ausführen (und muss trotzdem irgendwie zwischen den beiden unterscheiden.)
  • Sie können 2 verschiedene Namespaces in einem Geschäft verwenden, z. Das Verwenden von Zeichenfolgen als Primärschlüssel und das Präfixieren mit "DESIGN:" oder "PLAYER:", das Erstellen dieser Namespaces ist jedoch möglicherweise nicht trivial und die Abhängigkeiten sind nicht klar. (In einem RDBMS können Sie möglicherweise Zeichenfolgen nicht effizient als Primärschlüssel verwenden. Sie können Ganzzahlen verwenden und alle Primärschlüssel unter einer bestimmten Zahl, z. B. 1 Million, als Designdaten und alle darüber liegenden Werte zuweisen Spielerdaten. Diese Informationen sind jedoch für das RDBMS nicht sichtbar, und Fremdschlüsselverknüpfungen überschreiten die „Kluft“.
  • Sie können immer in Echtzeit an derselben gemeinsam genutzten Datenbank arbeiten, die Leistung ist jedoch möglicherweise schlecht und das Risiko einer Beschädigung der Spielerdaten ist möglicherweise erhöht. Es gilt auch nicht für Spiele, die auf mehr als einem Server mit unterschiedlichen Weltdaten ausgeführt werden.
  • ... noch andere Ideen?

Mir fällt ein, dass dies zwar in erster Linie ein Problem für Online-Spiele ist, die Konzepte jedoch möglicherweise auch für Modding gelten, bei dem die Community Mods zur gleichen Zeit erstellt, in der die Entwickler ihr Spiel patchen. Werden hier Strategien angewendet, um die Wahrscheinlichkeit des Abbrechens von Mods zu verringern, wenn neue Patches veröffentlicht werden?

Ich habe dies auch als "Versionskontrolle" getaggt, da dies auf einer Ebene genau das ist - zwei Zweige der Datenentwicklung, die zusammengeführt werden müssen. Vielleicht könnten einige Erkenntnisse aus dieser Richtung kommen.

BEARBEITEN - Einige Beispiele wurden hinzugefügt, um das Problem zu klären. Ich beginne zu glauben, dass es sich wirklich um einen Namensraum handelt, der über zusammengesetzte Schlüssel in einem Geschäft implementiert werden könnte. Das vereinfacht zumindest die Merge-Strategie. Aber es kann Alternativen geben, die ich nicht sehe.


1
Ich stelle mir vor, die Antwort darauf könnte zumindest teilweise davon abhängen, welche Art von Daten die Designer hinzufügen und welche die Spieler hinzufügen.
Lathomas64

Es könnte sein, aber ich bin mehr an generischen Lösungen für zwei Parteien interessiert, die beide zu einem einzigen Datenspeicher beitragen.
Kylotan,

1
Vielen Menschen entgeht der Sinn dieser Frage - es sind nicht die Änderungen der Spieler, die sich widersprechen: Stattdessen brechen Inhaltsaktualisierungen usw. bestehende Layouts. @Kylotan habe ich recht?
Jonathan Dickinson

Hier stoßen Entwickler-Content-Updates möglicherweise auf Player-Content-Updates. Ich bin nicht wirklich an Problemumgehungen für das Spieldesign interessiert (z. B. lassen Sie Spieler nur an bestimmten Stellen bauen), sondern an Problemumgehungen für die Datenstruktur (z. B. lassen Sie Spieler nur Dinge mit IDs erstellen, die größer als 1 Million sind).
Kylotan,

2
Sie haben nichts angegeben. Erwarten Sie, Echtzeit-Updates für eine Live-Welt durchzuführen? Eine Randnotiz: 2 Parteien, die zu einem einzigen Datenspeicher beitragen, SIND eine Datenbank, das ist, was Datenbanken tun, man kann diese Tatsache nicht umgehen, und es wäre töricht, jahrzehntelanges Wissen darüber zu ignorieren, wie man Probleme mit gemeinsam genutzten Daten vermeidet.
Patrick Hughes

Antworten:


2

Ich denke, die Antworten, die DB-Lösungen vorschlagen, springen zu einer bestimmten Implementierung, ohne das Problem zu verstehen. Datenbanken machen das Zusammenführen nicht einfach, sondern bieten lediglich einen Rahmen zum Speichern Ihrer Daten. Ein Konflikt ist immer noch ein Konflikt, auch wenn er sich in einer Datenbank befindet. Und das Auschecken ist die Lösung des Problems für einen armen Mann - es wird funktionieren, aber zu einem lähmenden Preis für Ihre Benutzerfreundlichkeit.

Was Sie hier sagen, fällt in das verteilte Entwicklungsmodell des Problems. Der erste Schritt besteht meiner Meinung nach darin, Player und Designer nicht als separate Arten von Inhaltserstellern zu betrachten. Dadurch wird eine künstliche Dimension Ihres Problems entfernt, die die Lösung nicht beeinträchtigt.

Tatsächlich haben Sie Ihre Hauptlinie - die kanonische, vom Entwickler genehmigte Version. Möglicherweise haben Sie (wahrscheinlich) auch andere Zweige - Live-Server, auf denen Leute Mods entwickeln und gemeinsam nutzen. Inhalte können in jedem Zweig hinzugefügt werden. Entscheidend ist, dass Ihre Designer hier nichts Besonderes sind - sie sind nur zufällige Inhaltsschöpfer, die im eigenen Haus leben (und Sie können sie finden und treffen, wenn sie versauen).

Das Akzeptieren von benutzerdefinierten Inhalten ist dann ein Standardproblem beim Zusammenführen. Sie müssen entweder ihre Änderungen zurück auf die Hauptlinie ziehen, zusammenführen und dann erneut verschieben oder die Hauptlinienänderungen auf ihren Zweig ziehen und zusammenführen (sodass die Hauptlinie von benutzergenerierten Inhalten 'sauber' bleibt). Wie üblich ist es freundlicher, zu Ihrem Zweig zu ziehen und dort zu reparieren, als andere Leute zu bitten, Ihre Änderungen zu übernehmen und dann zu versuchen, sie an ihrem Ende aus der Ferne zu reparieren.

Sobald Sie mit einem solchen Modell arbeiten, gelten alle normalen Prozesse zur Vermeidung von Zusammenführungskonflikten. Einige der offensichtlicheren:

  • Ermutigen Sie die großzügige Verwendung von Namensräumen, um Inhalte eines bestimmten Autors / Mods / Teams zu schützen.
  • Wenn Inhalte interagieren müssen, legen Sie klare Aufruf- / Verwendungskonventionen, Namenskonventionen und andere lose „Regeln“ fest, die die Entwicklung leiten, damit das Zusammenführen einfacher wird. Stellen Sie Tools bereit, mit denen Inhaltsersteller erkennen können, ob sie diese Regeln einhalten. Diese sind idealerweise in die Inhaltserstellung selbst integriert.
  • Bereitstellung von Berichts- / Analysetools, um wahrscheinliche Zusammenführungsfehler zu erkennen, bevor sie auftreten. Das Reparieren nach dem Zusammenführen ist wahrscheinlich sehr schmerzhaft. Stellen Sie sicher, dass ein bestimmtes Stück Inhalt überprüft und freigegeben wird, damit die Zusammenführung reibungslos vonstatten geht
  • Machen Sie Ihre Zusammenführung / Integration robust. Ermöglichen Sie einfache Rollbacks. Führen Sie strenge Tests von zusammengeführten Inhalten durch: Wenn die Tests fehlschlagen, führen Sie sie nicht zusammen! Iterieren Sie entweder ihren oder Ihren Inhalt, bis die Zusammenführung reibungslos verläuft.
  • Vermeiden Sie die Verwendung inkrementeller Integer-IDs für alles (Sie haben keine zuverlässige Möglichkeit, sie an Ersteller weiterzugeben). Dies funktioniert nur in einer Datenbank, da die Datenbank selbst ein kanonischer Anbieter von IDs ist, sodass Sie niemals Duplikate erhalten. Es wird jedoch auch ein einzelner Fehler- / Lastpunkt in Ihrem System eingeführt.
  • Verwenden Sie stattdessen GUIDs. Die Speicherung kostet mehr, ist jedoch maschinenspezifisch und verursacht keine Kollisionen. Alternativ können Sie auch Zeichenfolgen-IDs verwenden. Dies ist viel einfacher zu debuggen / beheben, aber für die Speicherung und den Vergleich teurer.

Leider ist ein Teil davon für mein Problem nicht hilfreich (z. B. dass Spieler bestimmte Regeln befolgen, da dies alles automatisch serverseitig erfolgen muss), und ich denke nicht, dass es praktisch ist, den Grad der Zusammenführungsverwaltung und der Transaktionsverwaltung zu unterstützen Die Semantik, die Sie erwähnen, aber der allgemeine Ansatz, garantierte eindeutige IDs, möglicherweise GUIDs, zuzuweisen, ist wahrscheinlich der Vorgehensweise am nächsten.
Kylotan

Ah ich sehe. Nun, da Sie die Erstellungstools kontrollieren, können Sie zumindest diese zusammenführungsfreundlichen Ansätze (Namespaces usw.) durchsetzen, ohne dass die Spieler zustimmen müssen.
MrCranky

Was machst du, wenn zwei Spieler doppelte Inhalte erstellen? Oder werden einzelne Instanzen Ihrer Spielwelt als einzigartig behandelt? In diesem Fall ist es möglicherweise eine hilfreiche Methode, jede Ihnen bekannte eindeutige Instanz automatisch anhand Ihres Kern- / Hauptzweigs auf Konflikte zu überprüfen, die auftreten, wenn Sie diese Änderungen an den Instanzen vornehmen. Wenn Sie die Spieler nicht kontrollieren können, können Sie Ihr internes Team zumindest warnen, dass die von ihnen geleistete Arbeit zu einem frühen Zeitpunkt in Konflikt mit Instanz X der Welt steht.
MrCranky

Das Konzept der Namespaces ist nicht so sehr das Problem - es ist das Auswählen geeigneter Namespaces aus dem Namespace aller möglichen Namespaces! :) Und doppelte Inhalte sind für mich kein Problem - es sind nur 2 Instanzen von etwas, was äquivalent ist. Wichtig ist, dass keine schädlichen Zusammenführungen oder Überschreibungen auftreten. Bei der automatischen Kollisionsprüfung wird der beim Schreiben verursachte Schaden zwar gestoppt, das ursprüngliche Benennungsproblem jedoch nicht gelöst. (Das Umbenennen von
Objekten

Ah ja, ich verstehe jetzt, es sind nicht die Namespaces selbst, sondern die Wahl des Namens. In diesem Fall sind GUIDs wahrscheinlich wieder die Antwort - haben Inhalte effektiv in einem eigenen kleinen Bereich aufbewahrt. Es kann ein dekorativer Name angegeben werden, das Spiel verwendet jedoch die GUID.
MrCranky

1

Speichern Sie alles als Attribut (oder Dekorator) - mit Einhängepunkten. Nehmen wir ein Haus, das der Spieler als Beispiel entworfen hat:

o House: { Type = 105 } // Simple square cottage.
 o Mount point: South Wall:
  o Doodad: Chair { Displacement = 10cm }
   o Mount point: Seat:
    o Doodad: Pot Plant { Displacement = 0cm, Flower = Posies } // Work with me here :)
 o Mount point: North Wall:
  o Doodad: Table { Displacement = 1m }
    o Mount point: Left Edge:
     o Doodad: Food Bowl { Displacement = 20cm, Food = Meatballs}

So kann jede Entität einen oder mehrere Einhängepunkte haben - jeder Einhängepunkt kann keine oder mehrere andere Komponenten aufnehmen. Diese Daten würden mit der Version gespeichert, in der sie gespeichert wurden, zusammen mit den relevanten Eigenschaften (wie Verschiebung usw. in meinem Beispiel). - NoSQL würde hier wahrscheinlich eine gute Anpassung finden (Schlüssel = Entitäts-ID, Wert = Serialisierte Binärdatei) Daten).

Jede Komponente müsste dann in der Lage sein, alte Daten aus einer früheren Version zu aktualisieren (niemals Felder aus serialisierten Daten entfernen - nur 'null') - diese Aktualisierung erfolgt in dem Moment, in dem sie geladen wird (sie würde dann sofort wieder in gespeichert werden) die neueste verfügbare Version). Nehmen wir an, unser Haus hat seine Maße geändert. Der Upgrade-Code würde den Abstand zwischen der Nord- und Südwand relativ berechnen und die Verschiebungen aller enthaltenen Entitäten proportional ändern. Als weiteres Beispiel könnte in unserer Fleischschüssel das Feld "Essen" entfernt werden und stattdessen "Vielfalt" (Fleisch) und "Rezept" (Bällchen) angezeigt werden. Das Upgrade-Skript verwandelt "Fleischbällchen" in "Fleisch", "Bällchen". Jede Komponente sollte auch wissen, wie sie mit Änderungen an Einhängepunkten umgeht - z

Damit bleibt genau ein Problem offen: Was passiert, wenn zwei Objekte miteinander kollidieren (nicht ihre Container - Mount Points schützen Sie davor)? Nach einem Upgrade sollten Sie nach Kollisionen suchen und versuchen, diese zu beheben (durch Auseinanderbewegen, ein bisschen wie bei SAT). Wenn Sie nicht herausfinden können, wie die Kollision behoben werden kann, entfernen Sie eines der Objekte und verstauen Sie es. Dort können sie diese entfernten Objekte kaufen (kostenlos) oder verkaufen (zum vollen Preis). und informieren Sie den Player offensichtlich darüber, dass beim Upgrade ein Teil des Layouts beschädigt wurde - möglicherweise mit der Funktion "Vergrößern", damit er das Problem erkennen kann.

Letztendlich sollten Sie komplexe Änderungen in den Händen der Spieler belassen (Fehler schnell), da kein Algorithmus die Ästhetik erklären kann. Sie sollten lediglich in der Lage sein, dem Spieler den Kontext anzugeben, in dem sich der Gegenstand befand (damit sie sich nicht nur erinnern können) lande mit all diesen Gegenständen in ihrem Versteck und weiß nicht, wo sie waren).


Dies konzentriert sich ein wenig zu eng auf die Objektpositionierung, was eigentlich nicht das Hauptproblem ist, das ich zu lösen versuche. Es geht mehr darum, eindeutige Bezeichner in gleichzeitigen Datensätzen zu haben und diese zusammenführen zu können, ohne dass das Risiko eines Konflikts besteht. Ich habe neulich 2 Beispiele zu meinem Beitrag hinzugefügt, um ein bisschen mehr zu erklären.
Kylotan

1

Ich versuche, dies mit etwas in Verbindung zu bringen, das ich verstehe, also denke ich gerade in Bezug auf Minecraft. Ich stelle mir einen Live-Server vor, auf dem Spieler Änderungen in Echtzeit vornehmen, während die Entwickler einen Testserver ausführen, der neue Inhalte repariert / erstellt.

Ihre Frage scheint fast wie 2 eindeutige Fragen:

  1. So stellen Sie sicher, dass Objekt-IDs eindeutig sind
  2. So stellen Sie sicher, dass Skript-Namespaces nicht kollidieren

Ich würde versuchen, # 1 durch ein temporäres Referenzierungssystem zu lösen. Wenn beispielsweise ein neues Objekt von jemandem erstellt wird, kann es als flüchtig oder temporär markiert werden. Ich würde mir vorstellen, dass alle neuen Inhalte, die auf dem Testserver erstellt werden, als flüchtig gekennzeichnet sind (obwohl sie auch auf nicht flüchtige Inhalte verweisen können).

Wenn Sie bereit sind, neuen Inhalt auf den Live-Server zu übertragen, werden die flüchtigen Objekte beim Importvorgang gefunden und den Live-Server-Objekt-IDs zugewiesen, die in Stein gemeißelt sind. Dies unterscheidet sich von einem direkten Import / Zusammenführen, da Sie in der Lage sein müssen, auf vorhandene nichtflüchtige Objekte zu verweisen, wenn Sie diese reparieren oder aktualisieren müssen.

Für # 2 scheint es wirklich notwendig zu sein, dass Sie eine Stufe der Zwischenskripttransmutation haben, die den Funktionsnamen in einen eindeutigen Namespace hasht. dh

assignFrenchAccent

Wird

_Z11assignFrenchAccent

0

Wenn es sich bei den Dateien für die Daten nicht um Binärdateien, sondern um Textdateien handelt und die Designer und Player verschiedene Bereiche ändern, können Sie eine SVN-Zusammenführung versuchen.


0

Ich denke, dass ein Datenbank- / Dateisystem, das über Umgebungen hinweg mit einem Auscheck-Verfahren repliziert wird, am besten ist.

Wenn ein Designer also Änderungen an der Welt vornehmen möchte, checkt er alle Assets aus / sperrt sie, die er für alle Kopien der Datenbank (Entwicklung und Produktion) erstellen / ändern möchte, damit kein anderer Player oder Designer sie ändern kann . Anschließend arbeitete er an der Entwicklungsdatenbank, bis das neue Design fertig war. Zu diesem Zeitpunkt wurden die Änderungen mit der Produktionsdatenbank zusammengeführt und diese Assets in allen Umgebungen eingecheckt / entsperrt.

Player-Bearbeitungen würden auf die gleiche Weise funktionieren, mit der Ausnahme, dass die Datenbank- / Dateisystemrollen umgekehrt würden - sie funktionieren in der Produktionsdatenbank und alle Aktualisierungen werden nach Abschluss auf dev hochgeladen.

Die Asset-Sperre kann auf die Eigenschaften beschränkt werden, für die Sie keine Konflikte garantieren möchten: In Beispiel 1 würden Sie die Sperre aktivieren, ID 123456sobald der Spieler mit der Erstellung beginnt, sodass den Entwicklern diese ID nicht zugewiesen wird. In Beispiel 2 hätten Ihre Entwickler den Skriptnamen assignFrenchAccentwährend der Entwicklung gesperrt , sodass der Spieler bei der Entwicklung seiner Modifikation einen anderen Namen wählen müsste (dieses kleine Ärgernis kann durch Namespacing verringert werden, aber das allein vermeidet keine Konflikte, wenn Sie nicht jeden Namen angeben Benutzer / Entwickler einen bestimmten Namespace, und dann haben Sie das gleiche Problem mit der Verwaltung der Namespaces). Dies bedeutet, dass die gesamte Entwicklung aus einer einzelnen Online-Datenbank gelesen werden muss. In diesen Beispielen sind jedoch nur die Objektnamen aus dieser Datenbank erforderlich, sodass die Leistung kein Problem darstellen sollte.

In Bezug auf die Implementierung sollte es ausreichen, eine einzige Tabelle mit allen Schlüsseln und dem Asset-Status (verfügbar, von dev gesperrt, von prod gesperrt) zu haben, die in Echtzeit über alle Umgebungen hinweg synchronisiert / zugänglich sind. Eine komplexere Lösung würde ein vollständiges Versionskontrollsystem implementieren - Sie können ein vorhandenes System wie CVS oder SVN verwenden, wenn sich Ihre Assets alle in einem Dateisystem befinden.


Normalerweise ist es nicht praktisch, Daten global zu sperren - Spieler bearbeiten die Welt möglicherweise nur im normalen Spiel, und Sie möchten nicht, dass das Spiel die Designer daran hindert, arbeiten zu können. Wenn Sie globale Sperren zulassen, ist eine Zusammenführungsoperation im Grunde eine Überschreiboperation, die einfach ist - aber wenn Sie keine globalen Sperren haben, welche dann?
Kylotan,

Wie lathomas64 bereits sagte, hängt die Antwort davon ab, um welche Art von Daten es sich handelt. Ohne globale Sperren müssten Sie vermutlich ein Versionsverwaltungssystem und eine Reihe von Regeln haben, um Konflikte zu lösen. Diese Regeln hängen von den Daten- und Spielanforderungen ab. Sobald Sie diese haben, reduziert sich vermutlich jede Zusammenführung auf einen einfachen Überschreibvorgang.
SkimFlux

0

Ich denke, es geht hier darum, Ihre Verantwortung sauber zu übernehmen. 1) Der Server gibt an, was derzeit akzeptabel ist und mit welcher API der Zugriff erfolgen soll. Die Datenbank wird nach bestimmten Regeln geändert. 2) Ersteller dürfen Inhalte erstellen, diese müssen jedoch nach Aktualisierungen wiedergegeben werden können. Dies liegt ausschließlich in Ihrer Verantwortung: Jedes Update muss in der Lage sein, alte Datenstrukturen möglichst sauber und einfach zu analysieren.

Die Mount Point-Idee hat Vorteile, wenn Sie den Überblick über einzigartige Gegenstände und Positionen innerhalb einer formbaren Struktur behalten möchten, insbesondere wenn wir akzeptieren, dass sich die gesamte Heimstruktur eines Spielers dramatisch ändern wird und Sie dies beibehalten möchten kleine Deko-Sachen in ihren jeweiligen Schließfächern.

Es ist eine sehr komplizierte Angelegenheit, viel Glück! Es gibt wahrscheinlich keine Antwort.


0

Ich denke nicht, dass das ein großes Problem ist, wie Sie es machen.

Ich würde nur vom Benutzer erstellte Mods überschreiben, mit der Warnung, dass "Mod X mit dieser Version möglicherweise nicht richtig funktioniert". Überlasse es den Mod-Erstellern, ihre Arbeit zu ändern. Ich denke nicht, dass dies eine unrealistische Erwartung ist, dass Updates bestimmte Mods deaktivieren könnten.

Das Gleiche gilt für vom Benutzer erstellte Inhalte. Erstellen Sie einfach eine Sicherungskopie und überschreiben Sie sie.

Ich habe keine wirkliche Erfahrung damit, nur Vorschläge zu machen.


Ich denke, wenn es nur für vom Benutzer bereitgestellte Mods wäre, dann hättest du recht. Bei einigen Spielen geht es jedoch explizit um vom Benutzer erstellte Inhalte, sodass Sie diese nicht einfach zerstören können.
Kylotan

Lassen Sie dann im System Platz für Inhalte, die Sie später hinzufügen können. Wenn Sie ID-Nummern verwenden, reservieren Sie 1-1000. Oder wenn Benutzer ihre Assets benennen können, lassen Sie Benutzer den Namen nicht mit "FINAL-" oder etwas anderem beginnen (reservieren Sie ihn für Ihre eigenen Assets). BEARBEITEN: oder noch besser, machen Sie es umgekehrt, indem Sie Benutzerinhalte in einen Bereich oder ein Präfix zwingen
Woody Zantzinger
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.