Fallstudie
Hearthheld: En Skyrim-rollspelserver för flera spelare, byggd på någon annans motor
Skala
25+
Alfatestare anmälda
835
Commits på 13 dagar
~65,000
Rader i server, launcher, bot och verktyg
71
Spellägesmoduler
95
Chattkommandon i spelet
47
Testsviter för servern
28
Verktyg för plugin- och databyggen
Problemet
Beständigt rollspel för flera spelare i Skyrim finns redan, men de stora öppna servrarna byter närhet mot skala: hundratals spelare, svårare moderering och en kultur som formas av vem som råkar dyka upp. Hearthheld satsar på motsatsen - en liten server endast för inbjudna med ett tak på 100 spelare, utformad så att de som vanligtvis hamnar i utkanten av spelvärldarna inte behöver förbereda sig mentalt innan de loggar in. Den delen handlar om gemenskapsdesign. Resten är teknik inom de ramar som någon annan har satt upp:
- ·Motorn är tio år gammal. Skyrim Special Edition har varken multiplayer, någon server eller något API som någonsin var avsett för detta.
- ·Multiplayer-delen är ett annat öppen källkodsprojekt. SkyMP består av en C++-klientkoppling och ett TypeScript-server-SDK, under en licens som reglerar hur en fork måste publiceras.
- ·Den publicerade dokumentationen ligger efter den egna källkoden. I konfigurationsguiden utelämnas fem faktiska inställningar, och den innehåller fortfarande en länk till en domän som har varit inaktiv i flera år.
- ·Klienten är ett Windows-spel som Steam uppdaterar i bakgrunden. SkyMP jämför varje plugin byte för byte, vilket innebär att en uppdatering i bakgrunden gör att spelaren kopplas bort från nätet.
- ·Det finns ingen testmiljö. Världen är en server som är igång och där människor spelar rollspel samtidigt som den förändras under deras fötter.
Så nästan inget av detta är helt nytt. Arbetet går ut på att sätta sig in i en okänd kodbas tillräckligt väl för att kunna åtgärda den, hitta svagheterna i en motor som aldrig var avsedd för detta, och utveckla verktyg som gör att en ömtålig, versionslåst klientstack klarar av att fungera tillsammans med vanliga spelares datorer.
Vad som byggdes
Fyra rörliga delar, plus det gemenskapsskikt som bestämde vad de övriga tre skulle göra.
Att arbeta inuti SkyMP
- ·Jag har skapat en fork av skyrim-multiplayer/skymp och markerat den som fast, så att uppströms inte kan göra ändringar mitt i pilotfasen; serverdelprojektet följer AGPLv3, vilket innebär att om man kör en modifierad server åt andra måste man publicera forken - öppen källkod, privat community
- ·Korrigeringar i forken för C++ och TypeScript: att rikta en besvärjelse mot det som besvärjaren faktiskt tittar på, skicka avbrytningar av trollformler för aktörer som saknar animationsvariabler, stoppa rörelse när chatten tar över tangentbordet, samt jämföra listan över spelare man mött mot det ID som servern skrev in den med
- ·Upptäckte att den vitlista som detta projekt skulle bygga redan var implementerad i uppströmsversionen: discordAuth finns på riktigt, fungerar och nämns inte någonstans i den publicerade konfigurationsreferensen. Det tog tjugo minuter att läsa igenom settings.ts, vilket sparade mig en veckas arbete.
- ·Jag läste MasterClient och upptäckte att servern registrerar sig offentligt så fort autentisering är aktiverad, utan möjlighet att välja bort detta, vilket gjorde att det privata nätverkslagret gick från att vara en trevlig extrafunktion till ett avgörande krav
Spelservern
- ·Spelläget består av 71 moduler och cirka 33 000 rader kod som körs på Hetzner Cloud under Linux - enligt den publicerade dokumentationen ska det endast fungera på Windows, men CI har byggt och testat servern på Ubuntu och Arch vid varje push
- ·95 chattkommandon i spelet: rollspel (/me, /whisper, /yell, /ooc), byggande och placering, äganderätt och lås, jakt, gruvdrift, yrken, omdesign av karaktärer samt en separat verktygslåda för spelledare
- ·En värld utan NPC:er - butiker, värdshuset och platserna i rådet är roller som människor spelar. Det eliminerade större delen av kostnaden för Creation Kit och gjorde GM-arbetet till ett fullvärdigt jobb som bemannas separat från moderatoruppgifterna.
- ·47 testuppsättningar samt statiska kontroller som körs före varje driftsättning, däribland en som upptäcker kommandon som saknas i hjälpen i spelet
- ·En spelguide med 53 avsnitt, så att spelreglerna och säkerhetsverktygen går att läsa där spelarna faktiskt spelar, istället för enbart på Discord
Startprogrammet
- ·En Electron-app som installerar och låser versionen på hela klientstacken: Skyrim SE i körningsversion 1.6.1170, den motsvarande SKSE64-versionen, SkyMP-klienten och den delade modlistan
- ·Återställer en installation som uppdaterats via Steam till den fästa versionen genom att använda Steams egen depotkonsol, kontrollera att den nedladdade versionen är den önskade versionen och säkerhetskopiera allt innan något skrivs över
- ·Kontrollerar varje nödvändigt plugin byte för byte mot serverns kopia, eftersom en felaktig version ger samma fel som en saknad version och båda bör upptäckas innan spelet startar
- ·Läser Vortex egna profiler i skrivskyddat läge istället för att flytta filer bakom ryggen på det - en tidigare metod med en ”parkeringsmapp” hade redan lett till att trettio tillägg gått förlorade genom att två system trodde att de ägde samma sökväg
- ·Installerar och konfigurerar NetBird-överlägget samt skapar en regel i Windows-brandväggen som gäller endast för porten och är begränsad till det privata subnätet
- ·Arachnophobia-läge: fyra oberoende omkopplare som byter ut spindlarnas 3D-modeller lokalt, så att karaktären behåller sin kollision, AI, skada och synkronisering samtidigt som den renderas som ingenting på en spelares dator
Identitet och åtkomst
- ·Ett egenhostat master-API som är integrerat i Discord-boten och ersätter beroendet av den uppströmsliggande gatewayen: Discord OAuth, poll-baserad inloggningsstatus, spelsessioner, en identitetsändpunkt som spelservern anropar samt manifest för laddningsordning
- ·Medlemskap i Discord-servern fungerar som en vitlista - när man godkänner någon i Discord får denne tillgång till servern, när man tar bort personen upphävs tillgången, och en konfigurerad avstängningsroll utesluter en spelare mitt i en session
- ·Medlemskapet kontrolleras i webbläsaren under OAuth-processen istället för vid start, så en icke-medlem får ett tydligt besked istället för att nekas tillträde utan någon förklaring till varför
- ·Tar bort registreringen av ett tredjepartskonto från onboarding-processen och löser därmed problemet med den offentliga serverlistan
- ·Samma bot hanterar inbjudningstokens, rekommendationer, tillväxtspärren och modereringsverktygen
Innehållspipeline
- ·28 Python-verktyg för kompilering, cirka 7 500 rader, som skriver .esp-plugin-poster direkt istället för att klicka sig fram i Creation Kit - reproducerbara, jämförbara och möjliga att bygga om från källdata
- ·Genererade data för kartmarkörer, malmådror, interiörer, väder, raser, förmågor, besvärjelser, recept, emotes och svårighetsnivåer, som används av spelläget i JSON-format
- ·Kartmarkörer måste skapas i Tamriels egen permanenta cell med verkliga koordinater: en markörs ikon är en byte i dess TNAM-delpost som inget anrop under körning kan skriva till
- ·Platserna är initialt ”parkerade” och flyttas till sina verkliga koordinater först när de upptäcks, eftersom kompassen bortser från världskartans dolda del och visar alla markörer som befinner sig inom räckvidden
Skärmdumpar
Skrivande och världsbygge
Miljön är eget skrivande, inte en omskrivning av Bethesdas. En lorebibel placerar servern femtio år efter spelet, i en särskilt byggd fristad med stadsbrev från en efterkrigskonfederation: självständig eftersom inget län vill ta ansvar för den, styrd av ett roterande råd, betraktad varmt av Riften och avvaktande av Windhelm. Texten tjänar designen i stället för att dekorera den. Spelet öppnar vid stadens grundande, så en halvtom bosättning med ett dussin personer i är fiktionen som fungerar snarare än en server som väntar på att fyllas, och ingenting om de gamla hjältarna är fastställt, så ingen anländer stående i skuggan av en NPC.
Teknisk arkitektur
Vad jag lärde mig
Det mesta jag lärde mig av det här projektet handlade om att arbeta inom ramar som jag inte själv hade satt upp, och det mesta av det finns med i ändringsloggen eftersom det gick fel till en början.
Lita hellre på källan än på dokumentationen
SkyMP:s publicerade konfigurationsguide listar tolv inställningar och ingen åtkomstkontroll alls. Källkoden innehåller ytterligare fem, inklusive den Discord-baserade vitlistan som detta projekt var på väg att bygga upp från grunden. Dokumentationen var inte felaktig i sig, utan snarare föråldrad, och samma webbplats hänvisar fortfarande besökare till en domän som lades ner för flera år sedan. Tjugo minuter i settings.ts raderade en veckas arbete och förändrade mitt sätt att läsa dokumentation för allt jag inte själv har skrivit.
Ett beroende som du inte kan fastställa är ett beroende som du inte har
Versionsparitet i SkyMP är absolut: varje plugin jämförs byte för byte, och Steam uppdaterar Skyrim när det passar dem. Därför måste startprogrammet kunna återställa spelarens spel till version 1.6.1170 på begäran, och forken måste vara fastlåst istället för att spåras. Detta gäller även äldre spel, eftersom allt som uppdateras automatiskt på användarnas datorer ingår i din kompatibilitetsyta, oavsett om du har planerat för det eller inte.
Saknade uppgifter är inte ett tecken på misslyckande
En natt uppstod tre olika fel som egentligen var ett och samma. Ljusreglaget i modet för karaktärsanpassning fungerade inte, färgerna hade ingen effekt och färgen spreds inte. Startprogrammet hade installerat plugins och arkivet men lämnat kvar motor-DLL:en, eftersom en mod vars filer ligger i arkivets rotkatalog hade fått sina mappar förbisedda. Menyn öppnades ändå, eftersom den finns i arkivet, och kontrollen av laddningsordningen gick fortfarande igenom, eftersom plugins fanns där. Allt som DLL:en stödde saknades och ingenting indikerade detta. Diagnosen tog sedan tre felaktiga vändningar, alla samma misstag i båda riktningarna: ett fungerande plugin förklarades dött utifrån en loggrad som inte alls betyder något sådant, sedan förklarades en installation som fungerande trots att just den fil som spelade roll saknades, och en läsare ställde en fråga vars tomma svar inte går att skilja från inget svar alls. En kontroll som inte kan skilja på ”nej” och ”jag hörde dig inte” kommer att ljuga för dig två gånger.
Motorn kommer att rapportera att åtgärden lyckades och inte göra någonting
De första kartmarkörerna placerades i en ny interiörcell, med den rimliga motiveringen att det är allvarligare att göra fel när man överskriver ett standardvärldsutrymme. Fyra separata anrop rapporterade framgång och kartan förblev tom, eftersom moveTo inte lyfter ut en referens ur ett oladdat interiörutrymme. Genom att skriva in dem i Tamriels permanenta cell löste sig problemet. I en gammal motor utan felrapportering är ett anrop som returnerar true inte ett bevis på att något har hänt; endast mätningar i spelet är det.
Felet som bara uppstår när någon spelar
Loggfilen genererade ett icke-uppfangat undantag var tredje sekund under en hel kvälls spelande. Minimapens interna genomsökning letade efter ett namn som den aldrig hade fått, och den genomsökningen körs bara när någon faktiskt befinner sig på servern, så ingenting under testningen hade någonsin kommit i närheten av den. Allt som är beroende av verklig närvaro, verkliga sessioner eller verklig belastning är osynligt för en testuppsättning som saknar allt detta, och det enda försvaret jag hittade var att läsa live-loggarna som om de vore utdata snarare än avfall.
En vy per användare får inte strida mot serverns modell
Att ta bort spindlar från en spelares skärm genom att radera referenserna fungerar för miljöer men är farligt för en aktiv fiende: servern äger aktören och fortsätter att skicka uppdateringar, vilket leder till att klienten hamnar i konflikt med sin egen visningskod angående något som fortfarande kan träffa dig. Att istället byta ut meshen lämnar kollision, AI, skada och synkronisering oförändrade, och renderar ingenting på en maskin. Tillgänglighetsfunktioner fyller sin funktion genom att vara osynliga för den auktoritativa modellen, inte genom att redigera den.
Licensiering är en arkitektonisk begränsning, inte byråkrati
skymp5-server följer AGPLv3, vilket innebär att om man kör en modifierad server åt andra måste källkoden publiceras. Att reda ut detta innan jag skrev koden gjorde att projektets utformning fastställdes redan från början: forken är offentlig, medan gemenskapen är privat, och det visade sig fungera bra, eftersom den öppna koden inte avslöjar någon medlemslista, inget Discord-konto och ingen adress. Att upptäcka detta i efterhand skulle ha blivit en betydligt mer kostsam diskussion.
Säkerhet
- ·Medlemskap i Discord-servern är åtkomstlistan: att godkänna ger åtkomst till servern, att ta bort godkännandet återkallar åtkomsten, och rollen ”ban” utesluter användaren mitt i en session
- ·Medlemskapet verifieras via OAuth i webbläsaren, vilket innebär att ett avslag förklaras istället för att ske utan förvarning
- ·Ett egenhostat master-API eliminerar beroendet av tredjepartskonton och den offentliga listningen som följde med det
- ·NetBird-överlägget är obligatoriskt och inte valfritt, vilket innebär att en adress som läckt ut eller publicerats inte är nåbar
- ·En regel i Windows-brandväggen är portbaserad och gäller endast det privata subnätet, inte ”vilken som helst/vilken som helst”, och är aldrig kopplad till en binär sökväg
- ·Endast onlineläge; SkyMP:s offlineläge saknar helt autentisering och kan inte användas här
- ·IP-loggning är avstängd som standard - uppströmskanalen för händelselogg publicerar medlemmarnas IP-adresser på Discord vid varje inloggning
- ·Den offentliga forken innehåller endast koden: medlemslistan, Discord-kanalen och serveradressen ingår inte i den