Die Implementierung ist in python sehr einfach. Wieso python? Weil das der einfachste und gängigste Weg für "Machine Learning" ist. Hier gibt es viele optimierte Bibliotheken, die unter der Haube C++/Rust nutzen und so sehr performant sind.
Die Idee ist nun sehr einfach, wir setzen in python einen lokalen Web-Server (der auf der selben Maschine läuft wie unser Gameserver, aber nicht von extern erreichbar ist) auf.
Dann führen wir von unserem Gameserver (egal ob GTA SA/GTA 5/MTA oder Arc oder sonstiges Game) einfach einen HTTP Request aus.
Gameserver -> ruft per HTTP Anfrage + Frage unseren Server auf -> Der Server nutzt Word2Vec und gleicht das mit der DB ab -> gibt das Ergebnis wieder.
Wenn man wirklich große Infos/Daten hat, dann sollte man hier eine Datenbank, wie Chroma o.ä. verwenden und nicht nur eine Textdatei, da sonst ggfs viel RAM gefressen wird.
Aber für die "normale" Nutzung sollte das erstmal kein Problem sein.
from sentence_transformers import SentenceTransformer, util
from flask import Flask, request
with open("data.txt", "r", encoding="utf-8") as f:
data = [line.strip() for line in f if line]
model = SentenceTransformer("distiluse-base-multilingual-cased-v2", cache_folder="brain") # Gutes Embedding-Modell vor Allem für Deutsch! :)
data_embedding = model.encode(data, convert_to_tensor=True)
def find_top_line(query):
global model, data_embedding, data
query_embedding = model.encode(query, convert_to_tensor=True)
scores = util.cos_sim(query_embedding, data_embedding)[0]
idx = scores.argmax().item()
return "Keine Ergebnisse in der Datenbank gefunden :(" if scores[idx] < 0.15 else data[idx]
app = Flask(__name__)
@app.route('/ai', methods=['GET', 'POST'])
def handle_ai():
question = request.form.get("text") or request.args.get("text") or ""
question = question.strip()
response_text = find_top_line(question) if question else ""
response_bytes = response_text.encode("cp1252", errors="replace")
return response_bytes, 200, {"Content-Type": "text/plain; charset=windows-1252"}
if __name__ == '__main__':
app.run(host="127.0.0.1", port=1337)
Alles anzeigen
Zuerst die Abhängigkeiten installieren:
pip install sentence-transformers flask
Und anschließend kann das Skript einfach mit python main.py.
PS: Bei Linux heißt es pip3 oder python3. Hier natürlich mit nohup o.ä. arbeiten.
Unsere data.txt sieht einfach so z.B. aus:
/switchcolor = Um Ihre Autofarbe anzupassen.
/members = Zeigt die Member an, die online sind.
/duty = Damit können Sie OnDuty gehen.
/open = Damit können jegliche Art von Tor/Schranke geöffnet werden. (Alternativ auch H im Auto drücken)
/r = Damit schreiben Sie Ihren Team-Kollegen.
/d = Damit können Sie allen Staatsfraktionen schreiben.
/gov = Damit können Sie Regierungsnachrichten an den Server senden.
/m = Steht für Megaphone, damit können Sie einen Text aus einem Polizei Auto schreien.
/su = Um einem Spieler Wanteds zu geben.
/cuff = Fesselt einen Spieler.
/uncuff = Entfesselt einen Spieler.
/arrest = Sperrt einen Verbrecher ein.
/frisk = Durchsucht einen Spieler nach illegalen Items.
/take = Damit kannst du einem Spieler illegale Items abnehmen.
/Bluttest = Um Drogen- oder Alkoholtest durchzuführen.
/orten = Damit können Sie einen Spieler suchen.
/hide = Damit entfernen Sie einen Marker.
/rb = Das steht für RoadBlock und erstellt eine Straßensperre.
Um zum Schwarzmarkt zu gelangen "/find blackmarket".
Alles anzeigen
Also unser Algorithmus sucht in jeder Zeile, nach der entsprechenden Antwort von unserer Frage.
Dort müssen nicht nur Befehle drinnen stehen, sondern es können auch Erklärungen/Texte o.ä. reinpassen. Wichtig ist nur, dass es in einer Zeile steht.
Man kann das System beliebig erweitern, wie vllt eine Datei nur für Befehle, die Andere für Erklärungen und dann gibt es mehrere cmds, wie /ai_cmds und /ai_explain...o.ä.
In Pawn (hehe, good old SA:MP) sieht es dann z.B. so aus:
#include <open.mp>
#include <makro>
#include <cmd>
#include <strlib>
CMD:ai(playerid, params[])
{
if(GetPVarType(playerid,"ai") != PLAYER_VARTYPE_NONE) return 0; // um (D)DOS zu unterbinden
SetPVarInt(playerid, "ai", 1);
new out[256];
strurldecode(out, params);
new string[256];
format(string,sizeof(string),"text=%s",out);
HTTP(playerid, HTTP_POST, "http://127.0.0.1:1337/ai", string, "OnHttpResponse");
return 1;
}
forward OnHttpResponse(index, response_code, data[]);
public OnHttpResponse(index, response_code, data[])
{
new playerid = index;
new buffer[128];
static const prefix[] = "* [AI]: ";
new len = strlen(data);
new i = 0;
while (i < len) // so kompliziert, da in SAMP nur 128 Zeichen in eine Zeile passen. Somit loopen wir hier durch den Output, bis alles ausgegeben wurde in jeder Zeile. Würden wir alles in eine Zeile schreiben, würde es leider abgeschnitten werden.
{
new chunkSize = ((i+chunkSize-1) > (len-1)) ? (len - i) : (sizeof(buffer) - 1);
if (i == 0)
{
chunkSize -= strlen(prefix); // leave space for prefix
strmid(buffer, data, i, i + chunkSize - 1);
strins(buffer, prefix, 0, sizeof(buffer));
}
else
{
strmid(buffer, data, i, i + chunkSize - 1);
}
SendClientMessage(playerid, 0xFF8000FF, buffer);
i += chunkSize;
}
DeletePVar(playerid, "ai");
return 1;
}
Alles anzeigen
InGame sieht das dann so aus:
sa-mp-0000003.png