MXTRIX

Streamer. Developer. Rainbow-Six-Veteran.
Live auf Twitch
Halte die Maus gedrückt, um Kugeln zu stoppen.

Protokoll

Operator
Wer bist du?
Mxtrix
Mxtrix. Ich streame auf Twitch und schneide das Beste daraus für TikTok und YouTube.
Operator
Was spielst du?
Mxtrix
Gerade Wardogs. Rainbow Six Siege seit Jahren, da bin ich Veteran. Sonst alles, worauf der Chat Bock hat.
Operator
Und wenn der Stream aus ist?
Mxtrix
Dann schreibe ich Code. Bots, Tools, komplette Spielsysteme.
Operator
Wie tief willst du gehen?
Mxtrix
Zeig mir, wie tief das Kaninchenloch geht.

Programme

Geladen und einsatzbereit.

Wardogs
Läuft gerade
Hier gehen gerade die meisten Stunden rein. Taktik, Teamplay, und laut werden, wenn es brennt.
Rainbow Six Siege
Veteran
Jahre voller Ranked, Clutches und Lineups. Operator-Wissen abrufbar, jederzeit.
Alles andere
Auf Abruf
Shooter, Story, Chaos mit Freunden. Der Chat darf mitbestimmen.

Code

Zwischen den Streams baue ich Dinge, die laufen: Spielsysteme für Multiplayer-Server, Discord-Bots, Web-Apps. Drei echte Ausschnitte aus meinen Projekten.

Gebiete zahlen täglich aus
FiveM · Lua · MySQL

Gangsystem für einen Rollenspiel-Server. Wer ein Gebiet hält, bekommt einmal am Tag die hinterlegten Buffs: Geld auf das Gangkonto, Items an alle Mitglieder online oder ein Serverbefehl. Jede Auszahlung landet im Log.

local function pay(organization, territory, buff)
    if buff.type == 'bank' or buff.type == 'money' then
        MySQL.update.await('UPDATE cc_crime_organizations SET balance=balance+? WHERE id=?',
            { tonumber(buff.amount) or 0, organization.id })
    elseif buff.type == 'item' and QBCore.Shared.Items[buff.item] then
        for _, id in ipairs(GetPlayers()) do
            local member = CCCrime.GetOrganizationByPlayer(tonumber(id))
            if member and member.id == organization.id then
                local player = QBCore.Functions.GetPlayer(tonumber(id))
                if player then player.Functions.AddItem(buff.item, tonumber(buff.amount) or 1) end
            end
        end
    elseif buff.type == 'command' and buff.command then
        ExecuteCommand(buff.command:gsub('{organization}', organization.id))
    end
    MySQL.insert.await('INSERT INTO cc_crime_reward_log (territory_id, organization_id, buff_json) VALUES (?,?,?)',
        { territory.id, organization.id, json.encode(buff) })
end

CreateThread(function()
    while true do
        Wait(60000)
        local utc = os.date('!*t')
        if utc.hour == Config.DailyRewardHourUtc and utc.min == 0 then
            for _, territory in ipairs(ownedTerritories()) do
                for _, buff in ipairs(json.decode(territory.buffs_json or '[]')) do
                    pay(territory.owner, territory, buff)
                end
            end
            Wait(61000)
        end
    end
end)
Slash-Commands laden sich selbst
Node.js · discord.js · MySQL

Der Bot für meine Community. Jeder Befehl ist eine eigene Datei im Ordner commands. Beim Start werden alle eingesammelt, bei Discord registriert und die Module für Voice, Status und Verifizierung erst geladen, wenn der Client wirklich bereit ist.

client.commands = new Collection();
const commandsArray = [];

const commandsPath = path.join(__dirname, 'commands');
for (const file of fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'))) {
    const command = require(path.join(commandsPath, file));
    if ('data' in command && 'execute' in command) {
        client.commands.set(command.data.name, command);
        commandsArray.push(command.data.toJSON());
    }
}

client.once('clientReady', async () => {
    console.log(`Eingeloggt als ${client.user.tag}!`);
    await rest.put(Routes.applicationCommands(client.user.id), { body: commandsArray });

    // Features erst laden, wenn der Client bereit ist
    require('./features/voiceChannel')(client, pool);
    require('./features/serverStatus')(client);
    require('./features/voiceSupport')(client, pool);
    require('./features/welcome')(client, pool);
});

client.on('interactionCreate', async (interaction) => {
    if (!interaction.isChatInputCommand()) return;
    const command = client.commands.get(interaction.commandName);
    if (!command) return;
    try {
        await command.execute(interaction);
    } catch (err) {
        console.error(err);
        await interaction.reply({ content: 'Da ist etwas schiefgelaufen.', ephemeral: true });
    }
});
Login über Discord, ohne offene Weiterleitung
Next.js · TypeScript · Supabase r6cards.com öffnen

R6CARDS, ein Sammelkartenspiel rund um Rainbow Six. Der Rückkanal des Discord-Logins tauscht den Code gegen eine Session und schickt den Nutzer dorthin zurück, wo er war. Nur seiteninterne Ziele sind erlaubt.

import { NextResponse, type NextRequest } from "next/server";
import { createClient } from "@/lib/supabase/server";

export async function GET(request: NextRequest) {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get("code");
  const next = searchParams.get("next");

  // Nur seiteninterne Ziele zulassen, sonst wäre das eine offene Weiterleitung.
  const target = next && next.startsWith("/") && !next.startsWith("//") ? next : "/collection";

  if (!code) {
    return NextResponse.redirect(`${origin}/?error=auth`);
  }

  const supabase = await createClient();
  const { error } = await supabase.auth.exchangeCodeForSession(code);

  if (error) {
    return NextResponse.redirect(`${origin}/?error=auth`);
  }

  return NextResponse.redirect(`${origin}${target}`);
}

Kontakt

Der Operator verbindet dich.

Discord beitreten