Pacchetti e handler
Consulta il riferimento interattivo dei pacchetti per ogni pacchetto registrato, la sua struttura sul protocollo, direzione, dimensione, riepilogo dell’handler e file sorgente.
Moongate.Network.Packets definisce i formati del protocollo indipendentemente da TCP e dal server
di gioco. PacketRegistry descrive i frame e decodifica i pacchetti in ingresso;
IPacketHandler<TPacket> fornisce il comportamento di gioco sincrono, mentre
IAsyncPacketHandler<TPacket> gestisce i pacchetti che richiedono I/O. Sono registrazioni
separate. I formati integrati iniziali sono destinati a ClassicUO 7.x.
Copertura dei pacchetti integrati
Section titled “Copertura dei pacchetti integrati”Le lunghezze includono l’opcode e, per i pacchetti variabili, l’intestazione della lunghezza. Le direzioni sono relative al server. Questa è la tabella predefinita, non l’intero protocollo UO:
| Opcode | Classe | Direzione | Lunghezza | Handler predefinito dell’host |
|---|---|---|---|---|
0x53 |
PopupMessagePacket |
In uscita | Fissa 2 | Game: inviato prima della disconnessione quando la creazione del personaggio viene rifiutata o fallisce |
0x55 |
LoginCompletePacket |
In uscita | Fissa 1 | — |
0x73 |
PingPacket |
Entrambe | Fissa 2 | Game: PingPacketHandler; Login: LoginRolePingPacketHandler |
0x80 |
AccountLoginPacket |
In ingresso | Fissa 62 | Login: verifica asincrona dell’account, poi elenco 0xA8 oppure rifiuto 0x82 |
0x82 |
LoginDeniedPacket |
In uscita | Fissa 2 | — |
0x8C |
ServerRedirectPacket |
In uscita | Fissa 11 | Login: inviato dopo un 0xA0 valido, prima di chiudere la connessione di accesso |
0x91 |
GameLoginPacket |
In ingresso | Fissa 65 | Game: valida e consuma il ticket monouso di passaggio, poi invia 0xB9 e 0xA9 |
0xA0 |
ServerSelectPacket |
In ingresso | Fissa 3 | Login: verifica l’idoneità al realm, emette il ticket e reindirizza |
0xA6 |
UpdatesWindowPacket |
In uscita | Variabile, minimo 10 | Game: pergamena nativa Updates per il MOTD, testo Windows-1252 |
0xA8 |
ServerListPacket |
In uscita | Variabile, minimo 6 | — |
0xB9 |
SupportFeaturesPacket |
In uscita | Fissa 5 | Game: inviato dopo un 0x91 valido |
0xBD |
ClientVersionPacket |
In ingresso | Variabile, minimo 4 | ClientVersionPacketHandler: registra la versione del client nella sessione |
0xBD |
ClientVersionRequestPacket |
In uscita | Fissa 3 | — |
0xEF |
LoginSeedPacket |
In ingresso | Fissa 21 | LoginSeedPacketHandler; sui listener di accesso registra anche la versione del client, che il ticket di passaggio porta alla sessione di gioco |
Il plugin Ultima aggiunge questi pacchetti nelle modalità game e standalone, con
RegisterIncomingPacket per quelli in ingresso (vedi
Integrazione con l’host). 0xD9 è l’eccezione: è registrato in ogni
modalità, perché l’Enhanced Client lo invia anche al server di accesso:
| Opcode | Classe | Direzione | Lunghezza | Handler |
|---|---|---|---|---|
0x8D |
CreateCharacterEnhancedPacket |
In ingresso | Variabile | CreateCharacterEnhancedPacketHandler: crea e salva il personaggio e gli oggetti iniziali, poi lo porta nel mondo |
0xA9 |
CharacterListPacket |
In uscita | Variabile, minimo 6 | — |
0xD9 |
ClientHardwareInfoPacket |
In ingresso | Fissa 268 | Game: IgnoredPacketHandler<T>; Login: LoginRoleIgnoredPacketHandler<T>. Riconosciuto e ignorato per ora (log Debug) |
0xF8 |
CreateCharacterPacket |
In ingresso | Fissa 106 | CreateCharacterPacketHandler: crea e salva il personaggio e gli oggetti iniziali, poi lo porta nel mondo |
0x5D |
PlayCharacterPacket |
In ingresso | Fissa 73 | PlayCharacterPacketHandler: porta il personaggio scelto nel mondo |
0x83 |
DeleteCharacterPacket |
In ingresso | Fissa 39 | DeleteCharacterPacketHandler: contrassegna il personaggio per l’eliminazione |
0x02 |
MoveRequestPacket |
In ingresso | Fissa 7 | MoveRequestPacketHandler: gira o fa compiere un passo al personaggio, con risposta 0x22 o 0x21 |
0x22 |
MovementAckPacket |
In uscita | Fissa 3 | — |
0x21 |
MovementRejectPacket |
In uscita | Fissa 8 | — |
0x77 |
MobileMovingPacket |
In uscita | Fissa 17 | — |
0x06 |
UseRequestPacket |
In ingresso | Fissa 5 | UseRequestPacketHandler: apre un contenitore trasportato dal personaggio oppure un paperdoll; fa salire su una creatura che è una cavalcatura, e fa scendere quando il personaggio fa doppio clic su sé stesso |
0x03 |
AsciiSpeechRequestPacket |
In ingresso | Variabile, minimo 9 | SpeechRequestPacketHandler: parlato locale o comando con punto in gioco |
0xAD |
UnicodeSpeechRequestPacket |
In ingresso | Variabile, minimo 14 | SpeechRequestPacketHandler: parlato Unicode e con parole chiave codificate oppure comando con punto; i tipi gilda (0x0D) e alleanza (0x0E) vanno alla chat di gilda |
0xAE |
UnicodeSpeechMessagePacket |
In uscita | Variabile, minimo 50 | Parlato del giocatore e output privato dei comandi |
0x24 |
DisplayContainerPacket |
In uscita | 7, oppure 9 dal client 7.0.9.0 (senza intestazione di lunghezza) | — |
0x3C |
ContainerContentPacket |
In uscita | Variabile, minimo 5 | — |
0x09 |
LookRequestPacket |
In ingresso | Fissa 5 | LookRequestPacketHandler: mostra il nome sopra l’oggetto (0xC1) |
0x34 |
MobileQueryPacket |
In ingresso | Fissa 10 | MobileQueryPacketHandler: risponde con lo stato del personaggio o di un mobile visibile (0x11) e le abilità del personaggio (0x3A) |
0x75 |
RenameRequestPacket |
In ingresso | Fissa 35 | RenameRequestPacketHandler: il padrone rinomina il suo animale dalla barra di stato (da 1 a 16 lettere, nessuna parola vietata), lo staff qualsiasi creatura che raggiunge |
0x6F |
SecureTradeRequestPacket |
In ingresso | Variabile, minimo 8 | SecureTradeRequestPacketHandler: il giocatore annulla lo scambio del suo contenitore o spunta la sua parte; il comando gold (3) e i comandi sconosciuti vengono letti e ignorati |
0x6F |
SecureTradePacket |
In uscita | 47 (apertura), 17 (chiusura, aggiornamento) | — |
0x72 |
WarModeRequestPacket |
In ingresso | Fissa 5 | WarModeRequestPacketHandler: mette il personaggio in modalità guerra o pace, risponde con 0x72 e mostra la postura ai giocatori vicini (0x77) |
0xC8 |
UpdateRangePacket |
In ingresso | Fissa 2 | UpdateRangePacketHandler: risponde con il raggio visivo del server |
0xC8 |
ViewRangePacket |
In uscita | Fissa 2 | — |
0x88 |
DisplayPaperdollPacket |
In uscita | Fissa 66 | — |
0x07 |
LiftRequestPacket |
In ingresso | Fissa 7 | LiftRequestPacketHandler: raccoglie un oggetto trasportato o indossato dal personaggio, oppure uno a terra entro 2 caselle |
0x08 |
DropRequestPacket |
In ingresso | Fissa 15 | DropRequestPacketHandler: rilascia l’oggetto tenuto in un contenitore trasportato oppure a terra |
0x25 |
ContainerItemUpdatePacket |
In uscita | 20, oppure 21 dal client 6.0.1.7 (senza intestazione di lunghezza) | — |
0x27 |
LiftRejectPacket |
In uscita | Fissa 2 | — |
0x1D |
RemoveEntityPacket |
In uscita | Fissa 5 | — |
0x1A |
WorldItemPacket |
In uscita | Variabile, minimo 16 | — |
0xF3 |
WorldItemSaPacket |
In uscita | 24, oppure 26 dal client 7.0.9.0 | — |
0x13 |
EquipRequestPacket |
In ingresso | Fissa 10 | EquipRequestPacketHandler: mette l’oggetto tenuto sul personaggio oppure lo respinge |
0x2E |
WornItemPacket |
In uscita | Fissa 15 | — |
0x6E |
MobileAnimationPacket |
In uscita | Fissa 14 | — |
0xAF |
DeathAnimationPacket |
In uscita | Fissa 13 | — |
0x2C |
DeathStatusPacket |
In uscita | Fissa 2 | — |
0x89 |
CorpseEquipmentPacket |
In uscita | Variabile | — |
0x74 |
VendorBuyListPacket |
In uscita | Variabile | I prezzi e i nomi della finestra del negozio di un venditore |
0x3B |
VendorEndPacket |
In uscita | Fissa 8 | Chiude la finestra del negozio |
0x3B |
VendorBuyReplyPacket |
In entrata | Variabile, minimo 8 | VendorBuyReplyPacketHandler: l’acquisto della finestra del negozio |
0x9E |
VendorSellListPacket |
In uscita | Variabile | Cosa offre di comprare un venditore dal giocatore |
0x9F |
VendorSellReplyPacket |
In entrata | Variabile, minimo 9 | VendorSellReplyPacketHandler: la vendita dell’elenco |
0x6C |
TargetCursorPacket |
In uscita | Fissa 19 | — |
0x6C |
TargetResponsePacket |
In ingresso | Fissa 19 | TargetResponsePacketHandler: completa il bersaglio in attesa del giocatore |
0x95 |
HuePickerPacket |
In uscita | Fissa 9 | — |
0x95 |
HuePickerResponsePacket |
In ingresso | Fissa 9 | HuePickerResponsePacketHandler: fornisce la tinta scelta al selettore aperto del giocatore |
0x05 |
AttackRequestPacket |
In ingresso | Fissa 5 | AttackRequestPacketHandler: il personaggio combatte il mobile; una richiesta rifiutata riceve 0xAA e zero |
0x22, 0xFB |
ResynchronizeRequestPacket, PublicHouseContentPacket |
In ingresso | Fissa 3, 2 | IgnoredPacketHandler<T>: riconosciuto e ignorato per ora (log Debug) |
0xB5 |
OpenChatWindowPacket |
In ingresso | Fissa 64 | OpenChatWindowPacketHandler: il pulsante della chat riceve in risposta le chat che funzionano; la finestra di chat classica non è costruita |
0x9B |
HelpRequestPacket |
In ingresso | Fissa 258 | HelpRequestPacketHandler: apre il menu di aiuto del personaggio |
0xAA |
CombatantPacket |
In uscita | Fissa 5 | — |
0x2F |
SwingPacket |
In uscita | Fissa 10 | — |
0x0B |
DamagePacket |
In uscita | Fissa 7 | — |
0x12 |
TextCommandPacket |
In ingresso | Variabile | TextCommandPacketHandler: il tipo 0x24 usa l’abilità il cui numero inizia il testo; gli altri tipi vengono ignorati per ora (log Debug) |
0xB8 |
ProfileRequestPacket |
In ingresso | Variabile | ProfileRequestPacketHandler: la modalità 0 risponde con il profilo di un personaggio quando può essere letto, la modalità 1 modifica il profilo di chi l’ha inviato |
0xB8 |
ProfilePacket |
In uscita | Variabile | — |
0xE1 |
ClientTypePacket |
In ingresso | Variabile | IgnoredPacketHandler<T>: riconosciuto e ignorato per ora (log Debug) |
0xF0 |
ProtocolExtensionPacket |
In ingresso | Variabile, minimo 4 | ProtocolExtensionPacketHandler: il comando 0 risponde con le posizioni dei membri del gruppo fuori vista; il comando della gilda viene ignorato |
0xBF |
ExtendedCommandPacket |
In ingresso | Variabile | ExtendedCommandPacketHandler: il sottocomando 0x06 esegue un comando del gruppo, 0x10 risponde a un tooltip, 0x1A imposta il blocco di una statistica, 0x13 chiede il menu contestuale di un mobile o di un oggetto, 0x15 sceglie una delle sue voci e 0x1C lancia un incantesimo da un libro o da una pergamena; gli altri vengono ignorati per ora |
0xD7 |
AbilityRequestPacket |
In ingresso | Variabile, minimo 9 | AbilityRequestPacketHandler: il sottocomando 0x19 sceglie una mossa speciale dell’arma (0 la annulla), il sottocomando 0x32 è il pulsante Quest del paperdoll e apre il giornale delle quest; ogni altro sottocomando viene ignorato, letto in modo tollerante |
0xD6 |
QueryPropertiesPacket |
In ingresso | Variabile, al massimo 500 seriali | QueryPropertiesPacketHandler: un 0xD6 per oggetto visto dal personaggio |
0xD6 |
PropertyListPacket |
In uscita | Variabile | — |
0xDC |
PropertyListInfoPacket |
In uscita | Fissa 9 | — |
0xC1 |
LocalizedMessagePacket |
In uscita | Variabile | — |
0xC2 |
TextPromptPacket |
In uscita | Variabile (21) | — |
0xC2 |
TextPromptResponsePacket |
In ingresso | Variabile | TextPromptResponsePacketHandler: completa la richiesta di testo in attesa del giocatore |
0x54 |
PlaySoundPacket |
In uscita | Fissa 12 | — |
0xC0 |
HuedEffectPacket |
In uscita | Fissa 36 | — |
0xC7 |
ParticleEffectPacket |
In uscita | Fissa 49 | — |
0x1B |
LoginConfirmPacket |
In uscita | Fissa 37 | — |
0xBF |
MapChangePacket |
In uscita | Variabile, 6 (sottocomando 0x08) |
— |
0xBF |
StatLockInfoPacket |
In uscita | Variabile, 12 (sottocomando 0x19) |
— |
0xBF |
ClearAbilityPacket |
In uscita | Variabile, 5 (sottocomando 0x21) |
— |
0xBF |
DisplayContextMenuPacket |
In uscita | Variabile, minimo 12 (sottocomando 0x14) |
— |
0xBF |
PartyMemberListPacket |
In uscita | Variabile, minimo 7 (sottocomando 0x06, comando 0x01) |
— |
0xBF |
PartyRemoveMemberPacket |
In uscita | Variabile, minimo 11 (sottocomando 0x06, comando 0x02) |
— |
0xBF |
PartyMessagePacket |
In uscita | Variabile, minimo 12 (sottocomando 0x06, comandi 0x03 e 0x04) |
— |
0xBF |
PartyInvitePacket |
In uscita | Variabile, 10 (sottocomando 0x06, comando 0x07) |
— |
0xF0 |
PartyLocationsPacket |
In uscita | Variabile, minimo 8 (comando 0x01) |
— |
0xD4 |
BookHeaderPacket |
In uscita | Variabile, minimo 17 | — |
0x66 |
BookPagesPacket |
In uscita | Variabile, minimo 9 | — |
0x66 |
BookPagesRequestPacket |
In ingresso | Variabile, minimo 7 | BookEditPacketHandler: una richiesta di pagine o una modifica di un libro scrivibile |
0xD4 |
BookHeaderChangePacket |
In ingresso | Variabile, minimo 7 | BookEditPacketHandler: cambia titolo e autore di un libro scrivibile |
0x93 |
OldBookHeaderChangePacket |
In ingresso | Fissa 99 | BookEditPacketHandler: la stessa modifica dai client che usano il vecchio pacchetto |
0x71 |
BulletinBoardDisplayPacket |
In uscita | Variabile, minimo 38 | — |
0x71 |
BulletinBoardSummaryPacket |
In uscita | Variabile, minimo 22 | — |
0x71 |
BulletinBoardMessagePacket |
In uscita | Variabile, minimo 24 | — |
0x71 |
BulletinBoardRequestPacket |
In ingresso | Variabile, minimo 12 | BulletinBoardRequestPacketHandler: vedi Bacheche |
0xCC |
LocalizedMessageAffixPacket |
In uscita | Variabile, minimo 52 | — |
0x38 |
PathfindPacket |
In uscita | Fissa 7 | — |
0xBC |
SeasonChangePacket |
In uscita | Fissa 3 | — |
0xBA |
QuestArrowPacket |
In uscita | 6 byte, 10 dal client 7.0.9.0 (nessun campo di lunghezza; marcato Variabile per le due dimensioni) | ArrowModule: arrow.show e arrow.clear; l’abilità Tracking; non provato con un client reale |
0x4F |
GlobalLightLevelPacket |
In uscita | Fissa 2 | — |
0x4E |
PersonalLightLevelPacket |
In uscita | Fissa 6 | — |
0x20 |
MobileUpdatePacket |
In uscita | Fissa 19 | — |
0x78 |
MobileIncomingPacket |
In uscita | Variabile, minimo 23 | — |
0x11 |
MobileStatusPacket |
In uscita | Variabile, 121 (versione 6, un client dalla 7.0.30 con le resistenze accese: le resistenze massime e i bonus dell’era AOS), 91 (versione 5), oppure 43 (versione 0) per un altro mobile | — |
0xA1, 0xA2, 0xA3 |
MobileHitsPacket, MobileManaPacket, MobileStaminaPacket |
In uscita | Fissa 9 | Inviato da MobileStateService; i punti vita arrivano ai giocatori vicini come proporzione su 100 |
0x3A |
SkillsPacket |
In uscita | Variabile, minimo 6 | L’intero elenco delle abilità oppure un’abilità cambiata |
0x3A |
SkillLockPacket |
In ingresso | Variabile, minimo 6 | SkillLockPacketHandler: imposta il blocco di un’abilità del personaggio (aumenta, diminuisce o bloccata); un’abilità o un blocco inesistenti vengono ignorati |
0x72 |
WarModePacket |
In uscita | Fissa 5 | — |
0x5B |
CurrentTimePacket |
In uscita | Fissa 4 | — |
0x65 |
WeatherPacket |
In uscita | Fissa 4 | — |
0x6D |
PlayMusicPacket |
In uscita | Fissa 3 | — |
0x86 |
CharacterListUpdatePacket |
In uscita | Variabile, minimo 4 | — |
0x85 |
CharacterDeleteResultPacket |
In uscita | Fissa 2 | — |
0xB0 |
GumpPacket |
In uscita | Variabile, minimo 23 | — |
0xDD |
CompressedGumpPacket |
In uscita | Variabile, minimo 35 | — |
0xB1 |
GumpResponsePacket |
In ingresso | Variabile, minimo 23 | GumpResponsePacketHandler: consegna la risposta verificata al gump inviato al giocatore |
0xBF |
CloseGumpPacket |
In uscita | Variabile, 13 (sottocomando 0x04) |
— |
Il parlato raggiunge chi parla e gli altri personaggi giocanti sulla stessa mappa entro
la sua portata, come in ModernUO: 15 caselle a voce normale o come emote, 1 casella per un
sussurro, 18 per un urlo. Il client sceglie il modo da ciò che il giocatore scrive (; sussurra,
! urla, : fa un emote) e il testo esce invariato, con il suo tipo. La chat globale e la
finestra di chat separata non sono ancora supportate. Un . iniziale richiama il sistema dei comandi esistente
in privato; .. permette di scrivere un punto iniziale. Il parlato vuoto o superiore a 128 caratteri viene ignorato.
0xAD viene letto in modo tollerante, come negli altri emulatori, perché un pacchetto rifiutato disconnette il
client: un terminatore mancante viene accettato, un carattere codificato male diventa U+FFFD e un
codice lingua che non consiste di tre lettere ASCII viene letto come inglese. Viene rifiutato solo un elenco troncato di
parole chiave.
Un gump viene inviato come 0xDD (compresso) ai client da 5.0.0a e come 0xB0 ai precedenti;
la risposta torna come 0xB1 e il server chiude un gump con 0xBF sottocomando 0x04. Vedi
Gump.
Un effetto grafico viene inviato come 0xC0 (36 byte): tipo (0 in movimento, 1 fulmine, 2 fisso in un
punto, 3 fisso su un oggetto), i due seriali, la grafica, entrambi i punti, velocità, durata, i
due flag, la tinta e la modalità di rendering. Il semplice 0x70 non viene mai inviato, come in ModernUO. Un
effetto con un id di particella viene inviato all’Enhanced Client come 0xC7 (49 byte), lo stesso corpo
seguito dai campi della particella; ogni altro client riceve 0xC0, oppure nulla quando l’effetto
non ha grafica. EffectService li invia ai giocatori della mappa entro il raggio visivo
dell’effetto e, per un effetto in movimento, anche entro il raggio della destinazione, una volta ciascuno.
Quando un personaggio entra nel mondo il server invia, in quest’ordine (quello di ModernUO, verificato con
ServUO, UOX3, POL e Source-X): 0x1B conferma di accesso, 0xBF sottocomando 0x08 mappa, 0xBC
stagione, 0x4F e 0x4E luce, 0x20 il giocatore, 0x78 il giocatore con oggetti indossati, capelli e
barba (seriali virtuali da IMobileService), 0x11 stato (versione 5, o 6 per un client dalla 7.0.30 con le resistenze accese), 0x72 pace, 0x55
accesso completato e 0x5B ora; poi viene pubblicato CharacterEnteredWorldEvent. Le classi in uscita
risiedono in Moongate.Server.Ultima.Packets.World. Il personaggio è quindi una MobileEntity attiva
in IMobileService, rivolta nella direzione con cui era stata salvata.
Un passo (0x02) contiene la direzione, un bit di corsa (0x80) e una sequenza. La sequenza è 0
dopo l’accesso o un passo rifiutato, poi da 1 a 255 e ricomincia da 1; ogni altro valore viene rifiutato.
Una direzione verso cui il personaggio non è rivolto lo gira soltanto e non consuma tempo. Un passo nella direzione
verso cui è rivolto prenota il successivo 400 ms dopo camminando o 200 ms dopo correndo, e può arrivare fino a 200 ms
in anticipo; un passo precedente, oppure uno bloccato da IMovementService, viene rifiutato. 0x22 accetta il passo con
la notorietà del personaggio; 0x21 lo rifiuta e riporta il client alla posizione reale.
I giocatori si vedono entro il raggio visivo (18 caselle per impostazione predefinita) su entrambi gli assi. ISectorService mantiene i mobile attivi in
settori 16×16 per mappa e IWorldViewService ricalcola ciò che è cambiato dalla vecchia e dalla nuova
posizione di ogni passo, come fanno ModernUO e POL. Dopo la sequenza di ingresso nel mondo il giocatore riceve
0x78 di tutti quelli nel raggio e loro ricevono il suo 0x78. Un passo o una rotazione accettati inviano 0x77 (il bit
0x80 della direzione indica una corsa) ai giocatori che vedevano già chi si muove, 0x78 in entrambe le direzioni
ai giocatori appena entrati nel raggio e 0x1D ai giocatori che non lo vedono più; il client di chi si muove
elimina da sé ciò da cui si allontana. Quando un personaggio lascia il mondo i giocatori nel
raggio ricevono il suo 0x1D. Il raggio è ultima.world.view_range (predefinito 18, da 5 a 24); alla richiesta
0xC8 del client si risponde con esso, qualunque fosse la richiesta, così entrambe le parti usano lo stesso raggio.
ISectorService.Query restituisce i giocatori, gli NPC e gli oggetti a terra attorno a un punto, nel
raggio visivo salvo che ne venga indicato un altro. Un settore è attivo mentre un giocatore si trova entro due settori da esso (i settori
5×5 attorno a ogni giocatore, come in ModernUO); gli NPC non attivano i settori. ISectorService.IsActive lo indica
per la futura IA degli NPC.
Quando la sessione si chiude, CharacterLeaveWorldService (un ISessionClosedListener) copia il
personaggio, lo rimuove dal mondo, salva la copia e pubblica CharacterLeftWorldEvent. Il
salvataggio del mondo scrive anche i personaggi attivi, quindi un arresto anomalo perde al massimo i passi dall’ultimo salvataggio.
Gli oggetti indossati da un personaggio e tutto ciò che contengono a qualsiasi profondità vengono caricati a 0x5D e
risiedono in IItemService mentre gioca; lasciano il mondo e vengono salvati dopo il personaggio
quando la sua sessione si chiude, e anche il salvataggio del mondo li scrive. Un doppio clic (0x06) su un oggetto
attivo lo apre quando la sua grafica ha il flag tiledata Container e il personaggio della sessione
lo trasporta, indossato oppure dentro qualcosa di indossato: 0x24 con il gump da containers.toml (la
voce predefinita quando la grafica non ne ha uno), poi 0x3C con il suo contenuto diretto, anche se vuoto.
I client precedenti a 7.0.9.0 ricevono il 0x24 da 7 byte e quelli precedenti a 6.0.1.7 un 0x3C senza il byte della griglia;
una versione sconosciuta riceve i formati moderni.
Il byte della griglia di 0x3C e 0x25 è lo slot dell’oggetto (da 0 a 124) nella griglia con cui l’Enhanced Client
mostra un contenitore; il client classico lo ignora e usa la posizione del gump. Ogni oggetto riceve uno
slot libero quando entra in un contenitore e lo conserva nel database (grid_index). Un rilascio (0x08)
contiene lo slot richiesto dall’Enhanced Client: l’oggetto lo prende quando è libero, altrimenti prende il
successivo libero. Un contenitore con più di 125 oggetti condivide gli slot.
Un doppio clic su un mobile apre il suo paperdoll (0x88) quando il suo corpo è umano in
data/bodies.toml (un mostro non ne ha) e si trova sulla mappa del personaggio entro
ultima.world.view_range lungo X e Y; il pulsante del paperdoll del personaggio stesso invia il suo seriale
con il bit alto 0x80000000 impostato e si apre senza il controllo del raggio (il corpo deve comunque essere
umano). Un corpo assente da bodies.toml non ha paperdoll. Il titolo viene costruito come in ModernUO: il prefisso di fama
e karma di titles.toml, le cui righe da 10.000 di fama indicano Lord
o Lady, il nome, poi , <title> quando il mobile ne ha uno (i template NPC danno titoli come “il
mago”), come “Il Glorioso Lord Aria, il mago”; sia per i giocatori sia per gli NPC umani. I titoli delle abilità non vengono
ancora aggiunti. I flag
indicano se il mobile è in modalità guerra e se chi guarda può togliere oggetti, impostato solo sul
paperdoll del personaggio stesso. Gli oggetti indossati sono già noti al client da 0x78. Gli altri
doppi clic non sono ancora gestiti.
Raccogliere un oggetto (0x07) lo registra come tenuto nella sessione; rimane nel suo contenitore fino
al rilascio. Un oggetto intero dentro un contenitore trasportato dal personaggio, oppure uno indossato dal personaggio
(dal paperdoll; non lo zaino), può essere raccolto: avere già un altro oggetto tenuto
(AreHolding), parte di un oggetto non impilabile o di una pila indossata, lo zaino o un oggetto non
trasportato vengono rifiutati con 0x27 (CannotLift); un oggetto del personaggio dentro un contenitore viene mostrato nuovamente con 0x25 e uno
indossato viene rimesso sul paperdoll con 0x2E, come in ModernUO; un oggetto di un altro giocatore non viene mai
mostrato. Un oggetto indossato raccolto rimane sul personaggio fino al rilascio e gli altri giocatori nel
raggio lo vedono rimosso (0x1D). Rilasciarlo (0x08)
lo inserisce in un contenitore trasportato alla posizione di rilascio, riportata entro i limiti del gump, oppure in un
punto casuale quando viene rilasciato sull’icona del contenitore; rilasciato su un oggetto trasportato che non è un
contenitore, va nel contenitore di quell’oggetto alla posizione di quell’oggetto. I mobile, gli oggetti non
trasportati e un contenitore dentro sé stesso o qualcosa al suo interno respingono l’oggetto. Ogni rilascio libera
la mano e invia 0x25 con la posizione reale dell’oggetto, oppure mostra un oggetto a terra dove si trova.
Sollevare parte di un oggetto impilabile (tiledata Generic) lo divide: la parte tenuta conserva il seriale,
perché il client la trascina, e il resto prende un seriale da IItemSerialPool (64 seriali riservati
dalla sequenza degli oggetti, riforniti sotto 16), rimane dove si trovava la pila e viene mostrato con 0x25.
Se non rimangono seriali riservati il sollevamento viene rifiutato con Inspecific. Rilasciare l’oggetto tenuto su una
pila trasportata dello stesso tipo (grafica, tinta, template, nome e rarità, entrambe senza proprietà), fino
a 60000, le unisce: la pila cresce (0x25),
l’oggetto tenuto viene rimosso dal client (0x1D) e dalla memoria e la sua riga viene eliminata dal successivo
salvataggio del mondo nella stessa transazione della pila cresciuta, oppure dal salvataggio all’uscita, che prende in carico le
eliminazioni in sospeso del personaggio e le abbandona se fallisce (il database contiene quindi ancora entrambe
le pile com’erano prima dell’unione). Rilasciare su un contenitore non unisce mai. 0x5D attende i salvataggi all’uscita
dell’ultima sessione dell’account prima di caricare il personaggio, quindi non legge mai righe più vecchie
di ciò che quella sessione ha salvato. Un oggetto tenuto rilasciato su un paperdoll (0x13) viene indossato quando il paperdoll è quello del personaggio e
IEquipmentService lo consente: il layer proprio dell’oggetto (quello del template, altrimenti quello dei tiledata per una
grafica indossabile; il layer suggerito dal client viene ignorato, come in ModernUO) deve essere indossabile dal
paperdoll (non zaino, capelli, barba, cavalcatura, negozio o banca), libero e compatibile con entrambe le mani come in
ModernUO e POL: un template con two_handed_weapon = true richiede entrambe le mani libere e nient’altro
va in mano mentre uno è indossato; scudi e torce si accompagnano a un’arma a una mano. L’oggetto
lascia il suo contenitore o il terreno e tutti nel raggio, incluso il personaggio, ricevono 0x2E.
Tutto il resto viene respinto dove l’oggetto si trova ancora: il suo contenitore (0x25), il terreno oppure
il personaggio da cui è stato tolto (0x2E a tutti nel raggio). Una pila di più di un oggetto non viene
indossata. La mano viene sempre liberata e un rilascio o un equipaggiamento che indica un oggetto diverso da quello tenuto
rimette a posto quello tenuto. Per mantenere soddisfatto l’indice univoco (mobile_id, layer) dopo un cambio di
abiti, il salvataggio del mondo e quello all’uscita eliminano prima le righe rimosse e scrivono gli oggetti non indossati
prima di quelli indossati, e un salvataggio all’uscita salta un oggetto lasciato dal personaggio che ora qualcun altro
trasporta o indossa (lo scrive il salvataggio di quel proprietario).
Gli oggetti a terra risiedono nella griglia dei settori insieme ai mobile. Rilasciare l’oggetto tenuto sul
terreno (0x08 con destinazione 0xFFFFFFFF) funziona entro 2 caselle dal personaggio, in linea di
vista dai suoi occhi: la Z del client viene ignorata e l’oggetto atterra sulla superficie più alta del
terreno o di uno statico fino a 16 unità sopra i piedi del personaggio, come in ModernUO; gli altri oggetti a terra non vengono
sovrapposti in altezza, quindi due oggetti sulla stessa casella si sovrappongono. Viene mostrato a tutti nel raggio, incluso chi
lo rilascia: 0x1A per i client precedenti a 7.0.0.0, 0xF3 dopo, due byte più lungo da 7.0.9.0.
Chiunque può raccogliere (0x07) un oggetto a terra entro 2 caselle in linea di vista; scompare da ogni schermo
(0x1D) mentre è tenuto e un sollevamento parziale lascia il resto a terra con un nuovo seriale. Troppo lontano
o fuori dalla linea di vista viene rifiutato con OutOfRange o OutOfSight e l’oggetto viene mostrato di nuovo solo a quel
giocatore; un oggetto tenuto da qualcun altro non viene mai mostrato. Un oggetto a terra
tenuto che viene respinto torna dov’era, e anche uno ancora tenuto quando la sessione si chiude
viene rimesso a posto. Rilasciare su una pila a terra raggiungibile le unisce come nello zaino. I giocatori
che camminano entro il raggio di un oggetto a terra, o entrano nel mondo vicino ad esso, lo ricevono con lo stesso controllo di vecchia e
nuova posizione dei mobile. Gli oggetti a terra e tutto ciò che contengono vengono caricati
all’avvio e salvati dal salvataggio del mondo. Un oggetto a terra decade dopo il tempo del suo template, 60 minuti
salvo diversa impostazione di decay_minutes, contati da quando è stato posato a terra e riavviati
ogni volta che viene posato di nuovo; un contenitore decaduto porta con sé il contenuto (vedi
Template). Un contenitore a terra, o al suo interno, si apre quando è raggiungibile dal
personaggio (0x24 e 0x3C), e 0x25 mostra gli oggetti che entrano ed escono a tutti quelli vicini.
IPromptService chiede a un giocatore una riga di testo con il prompt Unicode (0xC2) e consegna ciò che ha
digitato a un callback sul game loop. Un giocatore ha un solo prompt alla volta: uno nuovo conclude il precedente senza
testo, così come Cancel e la chiusura di una sessione. Il client non ha un pacchetto che chiude il proprio prompt, quindi
una risposta tardiva viene ignorata: gli id aumentano per sessione e una risposta con un id diverso non fa nulla. Come in
ModernUO, il tipo 0 è l’annullamento del giocatore e un testo superiore a 128 caratteri viene ignorato; il testo viene letto fino
al terminatore, senza caratteri di controllo, e ripulito dagli spazi ai bordi.
ITargetService mostra a un giocatore il cursore del bersaglio (0x6C) e consegna la scelta a un callback sul
game loop, oppure a un comando in attesa di RequestAsync. Un giocatore ha un solo bersaglio alla volta: uno nuovo
conclude il precedente come sostituito, Cancel invia 0x6C con flag 3 e id 0, e una sessione che si chiude lo conclude
come disconnesso. Gli id dei cursori aumentano per sessione e una risposta con un id diverso viene ignorata.
La risposta viene risolta come in ModernUO: x e y -1 senza seriale sono l’annullamento del giocatore; un
seriale deve essere un oggetto o mobile attivo; il terreno prende l’altezza media della mappa, non quella del client;
uno statico deve esistere davvero e fornisce la propria sommità (metà dell’altezza di un ponte). Raggio e linea di
vista sono lasciati al chiamante. I comandi in gioco vengono eseguiti separatamente dal pacchetto del parlato, quindi un comando
in attesa di un bersaglio non trattiene i pacchetti della sessione; il suo output arriva quando termina.
Un giocatore esegue un comando alla volta: un altro nel frattempo viene rifiutato con “Un comando è già
in esecuzione.”
.where (game master) stampa ciò che seleziona un bersaglio.
IHuePickerService mostra a un giocatore il selettore di tinta del client (0x95) con una grafica e consegna
la tinta scelta a un callback sul game loop. Un giocatore ha un solo selettore alla volta: uno nuovo conclude il
precedente senza tinta, così come una sessione che si chiude. Gli id dei selettori aumentano per sessione e non sono il seriale di un
oggetto, come in ModernUO: una risposta con un altro id, o senza un selettore aperto, viene ignorata, quindi un
client non può ricolorare nulla inviando 0x95 autonomamente (UOX3 ricolora qualsiasi seriale indicato dal
pacchetto). La tinta viene mascherata con 0x3FFF e mantenuta tra 2 e 1001, come il
ClipDyedHue di ModernUO. Un client che chiude il selettore non invia nulla, quindi il callback potrebbe non essere mai eseguito: chi
apre un selettore controlla di nuovo, nel callback, ciò che era vero all’apertura.
IDeathService uccide un NPC (Morte e resurrezione). Ai giocatori che lo vedono vengono inviati, in
quest’ordine, il cadavere (0xF3, oppure 0x1A a un client vecchio), la morte (0xAF: il mobile, il suo
cadavere, quattro byte zero, come in ModernUO) e, quando l’NPC viene rimosso, 0x1D. Il client riproduce
l’animazione di morte del corpo autonomamente; il server non ne indica una. Un cadavere è la grafica 0x2006 e
comunica al client il proprio corpo al posto della quantità e il modo in cui giace nel byte della luce, come
fa ServUO: l’oggetto stesso mantiene quantità 1 ed entrambi provengono dalle proprietà corpse.body e
corpse.direction. Dopo il cadavere di un corpo umano, elfo o gargoyle arrivano 0x3C, con gli oggetti
indossati ancora al suo interno più capelli e barba con seriali virtuali, e 0x89, che indica il
layer di ciascuno come layer più uno e termina con un byte zero: il client disegna il cadavere vestito. Un corpo del genere
non riceve 0xAF: gli viene detto di riprodurre la caduta (0x6E, azione 21) e il suo cadavere arriva 1,5 secondi
dopo, perché ClassicUO elimina gli oggetti indossati di un mobile che muore tramite 0xAF e lo mostra cadere
nudo.
Lo stesso opcode può avere definizioni diverse in ciascuna direzione, come 0xBD.
L’elenco dei realm viene filtrato in base al livello minimo del realm dell’account autenticato.
Contiene l’indirizzo IPv4 di ogni realm ma nessuna porta. 0xA0 seleziona un realm attivo
idoneo e 0x8C fornisce il suo indirizzo IPv4, la porta e una chiave monouso. Il mittente di accesso
svuota il buffer di 0x8C prima di chiudere la connessione di accesso. Sulla nuova connessione di gioco
il client invia quella chiave come seed grezzo di quattro byte, seguito da 0x91 con la
stessa chiave, nome utente e password. Il gioco controlla il seed e consuma atomicamente
il ticket Redis. Il ticket scade 30 secondi dopo l’emissione, quindi la
riconnessione al gioco deve completarsi entro quel tempo. Un seed diretto di versione client 0xEF
funziona ancora sui listener di gioco.
Dopo un 0x91 valido il server di gioco copia la versione del client dal ticket alla
sessione, attiva la compressione Huffman per tutto ciò che invia da quel momento
e invia 0xB9 seguito da 0xA9: i personaggi salvati dell’account nel
numero configurato di slot (ultima.characters.max_per_account, predefinito 7), più le
città iniziali da data/starting_cities.toml. Gli handler di creazione salvano un nuovo
personaggio e i suoi oggetti iniziali in un’unica transazione. 0x5D porta il personaggio scelto
nel mondo (vedi sotto).
TryGetDescriptor(opCode, out descriptor) preferisce l’ingresso, poi l’uscita;
descriptor.PacketType.Name restituisce il nome della classe. L’overload che accetta
PacketDirection seleziona esplicitamente una direzione quando necessario.
registry.TryDecode(bytes, out packet, out opCode) imposta sempre opCode al
primo byte, anche in caso di errore. Un input vuoto restituisce false con opcode zero; ispeziona
la lunghezza dell’input per distinguere quel caso. La decodifica richiede un frame completo in
ingresso, compresa l’intestazione. Non memorizza in un buffer un flusso TCP. I pacchetti solo in uscita
hanno metadati ma nessun parser in ingresso.
Tooltip
Section titled “Tooltip”I flag dell’elenco personaggi (0xA9) includono AOS (0x20), quindi il client usa i tooltip AOS
(elenchi delle proprietà degli oggetti) e li richiede con 0xD6 (un elenco di seriali, al massimo 500; una
lunghezza che non consiste di seriali interi, oppure più seriali, non può essere letta e chiude la connessione, come
qualsiasi pacchetto illeggibile) oppure 0xBF sottocomando 0x10 (un seriale). Il
server risponde con un 0xD6 per oggetto che il personaggio può vedere: un oggetto che trasporta o indossa, un
oggetto indossato da un mobile o a terra entro ultima.world.view_range, oppure un mobile in
quel raggio sulla sua mappa; tutto il resto non riceve nulla. ITooltipService costruisce le righe, come
ModernUO e UOX3:
- il nome di un oggetto: il cliloc del client per la sua grafica (1020000 + grafica, 1078872 + grafica
da
0x4000), che il client mostra nella propria lingua, oppure il nome dell’oggetto o del template come testo; un oggetto senza nome proprio che ha la proprietàlabel_numbermostra invece quel cliloc (un’insegna); una pila usa 1050039 con la quantità; - le righe di combattimento, cliloc che il client traduce, lette dal template dell’oggetto, dopo il nome e le
righe eccezionale, creato da e usi rimasti: per un’arma (
damage_maxsopra 0) 1061168, “weapon damage”, con il danno minimo e massimo (min\tmax), e 1061167, “weapon speed”, con laspeedgrezza del template (il numero classico,35per una spada lunga, non secondi; un’arma senza velocità mostra 30, la velocità dei pugni, che è quella usata da un colpo); per un pezzo d’armatura 1060448, “physical resist”, con la suaarmor_rating(il numero che la finestra di stato mostra per l’armatura prima di AOS, con la qualità del pezzo: 8 in più se eccezionale, 8 in meno se bassa, e nessuna riga per uno scudo, che il combattimento per ora non conta); e per ogni oggetto con unastrength_requiredsopra 0 1061170, “strength requirement” (mostrata, non ancora imposta). Le righe sono i numeri base del template, non il danno di un colpo con le abilità di chi lo impugna. Durabilità, portata e mani non sono mostrate: gli oggetti non hanno ancora punti ferita propri; - nella lingua del server, dai file dei messaggi (
ILocalizationService, come UOX3): blessed o newbie (9055 “[Benedetto]”) oppure cursed (30005), il tipo di bottino dell’oggetto altrimenti quello del template; il peso dell’intera pila (30006 / 30007); la rarità (30000–30004), colorata (comune bianco). Un testo assente dai file usa l’inglese come fallback; - un mobile: 1050045 con nome e titolo.
I nomi (cliloc degli oggetti, quantità, nomi e titoli dei mobile) rimangono quelli del client finché il server non ha nomi tradotti; ogni altra riga è del server, quindi i tooltip seguono la lingua del server.
Il testo libero passa attraverso i cliloc il cui testo completo è ~1_NOTHING~ (1042971, 1070722, …),
uno per riga; un argomento viene tagliato a 504 caratteri, limite che i client più vecchi non possono superare. La
revisione del tooltip è un hash di 26 bit delle sue righe, come in ModernUO: 0xD6 lo contiene e 0xDC
lo contiene con il bit 30 impostato. Come in ModernUO, ciò che viene mostrato è seguito dal suo 0xDC: un
mobile che entra in vista (0x78) con ciascuno dei suoi oggetti indossati, un oggetto a terra (0x1A/0xF3), un
oggetto indossato (0x2E), ogni oggetto di un contenitore aperto (0x3C) e un oggetto di contenitore aggiornato
(0x25) dopo una divisione, un’unione, un posizionamento o un rifiuto; il client richiede di nuovo quando una revisione
cambia. Il 0x78 del personaggio stesso all’ingresso nel mondo non è ancora seguito: il client richiede
i tooltip che non possiede quando il cursore vi passa sopra. Un tooltip dipende solo da pochi campi del suo
oggetto (per un oggetto: template, grafica, quantità, nome, rarità, tipo di bottino, spostabile, label_number, valore, qualità,
creatore e usi rimasti; per un mobile: nome e titolo), quindi viene memorizzato in cache in base ad essi: oggetti uguali condividono un tooltip e una modifica dà
un’altra chiave, quindi nulla viene mai invalidato. La cache mantiene fino a 10000 tooltip e ricomincia
da capo quando è piena. Un singolo clic (0x09) mostra la prima riga, il nome, sopra
l’oggetto con 0xC1.
Definire un pacchetto e verificarne i byte
Section titled “Definire un pacchetto e verificarne i byte”Questo esempio usa un opcode privato illustrativo. Non è un’aggiunta al
protocollo ClassicUO; usalo solo con un client di test corrispondente. Aggiungi un riferimento a
Moongate.Network.Packets e inserisci questo in ExamplePacket.cs:
using System.Diagnostics.CodeAnalysis;using Moongate.Network.Packets.Attributes;using Moongate.Network.Packets.Base;using Moongate.Network.Packets.Interfaces;using Moongate.Network.Packets.Spans;using Moongate.Network.Packets.Types.Packets;
[PacketHandler(0xFE, PacketSizing.Fixed, Length = 3)]public sealed class ExamplePacket : BaseFixedPacket<ExamplePacket>, IIncomingPacket<ExamplePacket>, IOutgoingPacket{ public ushort Value { get; }
public ExamplePacket(ushort value) { Value = value; }
public static bool TryParse(ReadOnlySpan<byte> data, [NotNullWhen(true)] out ExamplePacket? packet) { packet = null; if (!HasValidHeader(data)) { return false; } var reader = new PacketReader(data[1..]); if (!reader.TryReadUInt16BigEndian(out var value)) { return false; } packet = new ExamplePacket(value); return true; }
public void Write(ref PacketWriter writer) { writer.EnsureCapacity(Length); writer.WriteByte(OpCode); writer.WriteUInt16BigEndian(Value); }}L’attributo dei metadati si chiama PacketHandler, ma descrive il pacchetto sul protocollo;
non registra la classe dell’handler di gioco. Chiama RegisterIncoming<T>() per un
pacchetto che implementa IIncomingPacket<T> (un pacchetto bidirezionale copre entrambe
le direzioni in una chiamata), oppure RegisterOutgoing<T>() per uno che implementa solo
IOutgoingPacket. Tipi duplicati o coppie opcode/direzione in conflitto fanno fallire
la registrazione. Blocca la tabella solo dopo averla composta.
Esegui questo Program.cs come smoke test a livello di byte senza server né client:
using Moongate.Network.Packets.Registry;using Moongate.Network.Packets.Serialization;
var registry = new PacketRegistry();PacketTable.Register(registry); // Optional: include the built-in formats.registry.RegisterIncoming<ExamplePacket>();registry.Freeze();
var bytes = PacketCodec.Encode(new ExamplePacket(0x1234));if (!bytes.AsSpan().SequenceEqual(new byte[] { 0xFE, 0x12, 0x34 }) || !registry.TryDecode(bytes, out var packet, out var opCode) || packet is not ExamplePacket { Value: 0x1234 } || opCode != 0xFE){ throw new InvalidOperationException("Packet round trip failed");}if (registry.TryDecode(new byte[] { 0xFE }, out _, out var failedOpCode) || failedOpCode != 0xFE){ throw new InvalidOperationException("Malformed-frame opcode was lost");}Console.WriteLine("Packet bytes and failure opcode verified");Per i pacchetti variabili, usa BasePacket<T> con
[PacketHandler(opcode, PacketSizing.Variable, MinimumLength = ...)], valida
l’intero frame dichiarato e scrivi la sua intestazione di lunghezza completa. Vedi i pacchetti integrati
ClientVersionPacket e ServerListPacket per esempi di parsing e scrittura.
Registrare un handler di gioco
Section titled “Registrare un handler di gioco”Aggiungi un riferimento a Moongate.Server.Core. ExamplePacketHandler.cs può reinviare il
pacchetto tramite il mittente con capacità limitata senza attendere l’I/O del socket:
using Moongate.Server.Core.Data.Sessions;using Moongate.Server.Core.Interfaces.Packets;using Moongate.Server.Core.Interfaces.Services;
public sealed class ExamplePacketHandler : IPacketHandler<ExamplePacket>{ private readonly IPacketSendService _sender;
public ExamplePacketHandler(IPacketSendService sender) { _sender = sender; }
public void Handle(GameSession session, ExamplePacket packet) { if (!_sender.TrySend(session.SessionId, new ExamplePacket(packet.Value))) { // The sender owns and observes connection cleanup. _ = _sender.DisconnectAsync(session.SessionId); } }}Nella composizione dei servizi di Program.cs, oppure nel callback Register di un plugin, importa
Moongate.Server.Core.Extensions e chiama:
container.RegisterPacketHandler<ExamplePacket, ExamplePacketHandler>();Gli handler sono singleton. Mantieni lo stato per giocatore nella sessione/nel mondo, non in campi mutabili
dell’handler. Handle viene eseguito sul game loop; mantienilo breve e sincrono.
TrySend acquisisce una copia dei byte codificati e restituisce lo stato di ammissione, non una conferma
di consegna. Decidi cosa fare quando restituisce false; l’esempio disconnette.
Per un handler che attende I/O di database o rete, implementa
IAsyncPacketHandler<TPacket> e registralo con
RegisterAsyncPacketHandler<TPacket, THandler>(). Il suo HandleAsync viene eseguito fuori
dal game loop. L’handler riceve un PacketContext invece di una GameSession
mutabile. Questo esempio presuppone un servizio di ricerca specifico dell’applicazione:
// Define this service in your plugin; keep its interface in a separate file.public interface IExampleLookup{ Task<ushort?> LoadAsync(ushort value, CancellationToken cancellationToken);}
public sealed class ExampleAsyncPacketHandler : IAsyncPacketHandler<ExamplePacket>{ private readonly IExampleLookup _lookup;
public ExampleAsyncPacketHandler(IExampleLookup lookup) { _lookup = lookup; }
public async ValueTask HandleAsync( PacketContext context, ExamplePacket packet, CancellationToken cancellationToken ) { var value = await _lookup.LoadAsync(packet.Value, cancellationToken);
if (value is not null) { context.TrySend(new ExamplePacket(value.Value)); } }}
container.RegisterAsyncPacketHandler<ExamplePacket, ExampleAsyncPacketHandler>();Quando il risultato deve cambiare lo stato di gioco, torna sul loop con
await context.RunOnGameLoopAsync(session => { /* update session/world */ }, cancellationToken).
Restituisce false se la sessione originale si è disconnessa prima dell’esecuzione dell’azione.
Un handler asincrono non deve modificare direttamente una sessione dopo un await.
Per una sessione può essere in corso un solo pacchetto asincrono. I pacchetti inviati dalla sessione
nel frattempo attendono, fino a 1024 (PacketDispatchService.MaxPendingPerSession), e vengono
inoltrati in ordine di arrivo quando termina; uno in più viene rifiutato e il client
disconnesso. L’esecutore accetta al massimo 64 operazioni alla volta ed esegue al massimo
quattro handler contemporaneamente.
L’ammissione rimane non bloccante. Disconnessione e arresto del server annullano il
token dell’handler; osservalo in ogni chiamata I/O attesa. Le eccezioni vengono registrate
senza contenuti dei pacchetti e non fermano il game loop.
Integrazione con l’host
Section titled “Integrazione con l’host”Un nuovo pacchetto in ingresso richiede due registrazioni. La registrazione dell’handler non aggiunge l’opcode alla tabella del protocollo. Aggiungi anche il tipo del pacchetto:
container.RegisterIncomingPacket<ExamplePacket>();container.RegisterPacketHandler<ExamplePacket, ExamplePacketHandler>();All’avvio l’host costruisce un registro da PacketTable più ogni chiamata
RegisterIncomingPacket, lo blocca e lo fornisce ai framer, ai listener di accesso
e gioco e al dispatcher. I pacchetti in uscita non richiedono registrazione per essere
inviati. Un pacchetto registrato senza handler viene decodificato ma rifiutato e il server di
gioco chiude la connessione, come fa per un opcode sconosciuto. Registra
IgnoredPacketHandler<T> per un pacchetto inviato dal client su cui il server non
agisce ancora.
L’host registra anche LoginSeed e un handler AccountLogin asincrono. Quest’ultimo
verifica le credenziali con IAccountService, invia 0x82 per un accesso rifiutato o
un elenco vuoto di realm idonei e invia un elenco 0xA8 filtrato dopo il successo.
L’host solo di accesso usa una pipeline asincrona ordinata di connessione dedicata; standalone
esegue listener separati per accesso e gioco. Selezione e passaggio usano lease
basati su Redis e ticket monouso (Stato dell’implementazione). Vedi
Trasporto e proprietà dello stato di gioco per il ciclo di vita della connessione,
i limiti delle code e la politica di sovraccarico, e Game loop e timer
per la proprietà dei thread e il completamento.
Creato con ❤️ da Tom
