Skip to content

Instantly share code, notes, and snippets.

@Steffo99
Last active April 12, 2023 23:56
Show Gist options
  • Save Steffo99/983a36cecbb3e1edcb2d97c72e729678 to your computer and use it in GitHub Desktop.
Save Steffo99/983a36cecbb3e1edcb2d97c72e729678 to your computer and use it in GitHub Desktop.
Tentative spec for a Matrix extension for roleplaying games

Un modulo di estensione per Matrix per giocare di ruolo via chat.

Concetti base di Matrix

Questa è una grossa semplificazione di come funziona effettivamente un server Matrix perchè in realtà internamente è molto più complesso. Per più dettagli, andatevi a leggere la specifica completa...

Tutte le cose che avvengono su Matrix sono rappresentate attraverso events, che i client possono inviare in vari canali di comunicazione detti rooms.

Events

Ogni event ha un type, una stringa che determina come viene gestito dal server.
I type che iniziano con m., come m.room.name o m.room.message, sono riservati, e vengono definiti nella specifica di Matrix.
È possibile definire eventi personalizzati dandogli un namespace come i package Java, come one.ryg.matrix.dnd.mycustomevent.

Ogni event ha un content, un payload JSON arbitrario, il cui significato dipende dal type dell'event.

Inoltre, gli event possono avere una state key, una stringa che li distingue in una di queste due categorie:

  • se non ha una state key, un evento è un message, e rappresenta qualcosa di transitorio e immediato come appunto un messaggio
  • se ha una state key, un evento è uno state, e rappresenta una modifica permanente a un metadato di una stanza, come il suo titolo.

Una stringa vuota come state key rende comunque l'evento uno state; se non è specificata la state key, significa che è una stringa vuota ""!

Uno state più recente sovrascrive lo state più vecchio.

Il server provvede a salvare tutti gli state della room in un oggetto con la seguente forma:

{
	"tipoevento": {
		"statekey": "payload",
		"statekey2": {"altro payload": "ma più bello"}
	},
	"m.room.name": {
		"": {
			"name": "Garasauto"
		}
	},
	"m.room.topic": {
		"": {
			"topic": "Luogo di culto del Garasautismo."
		}
	},
	"m.room.member": {
		"@steffo:ryg.one": {
			"avatar_url": "...",
			"displayname": "Steffo",
			"membership": "join"
		},
		"@nemesis:ryg.one": {
			"displayname": "Balulo",
			"membership": "invite"
		},
		"@gimbaro:ryg.one": {
			"displayname": "Gimbardo",
			"membership": "ban",
			"reason": "È un clown."
		}
	},
	"tk.garasfunny.car": {
		"@steffo:ryg.one": {
			"type": "Subalu Baracca"
		},
		"@pesca:ryg.one": {
			"type": "Macchina gialla"
		}
	}
}

Rooms

Ogni room è identificata da un id namespaced casuale, nel formato !frSboFqpSCmLMieeVJ:ryg.one, il cui namespace è il server Matrix che l'ha creata.

Anche le room hanno un type, che al momento viene solo utilizzato dai client per cambiare come vengono visualizzate le room.

La specifica definisce solo due possibili type per le room:

  • null, per le chat
  • "m.space", contenitori di altre room simili alle categorie di Telegram, ma che possono essere innestati

Funzionamento del modulo

Il modulo RPG che vogliamo creare aggiunge i seguenti concetti a Matrix.

Campagna

Una campagna è l'insieme di tutte le partite di un gioco di ruolo ambientate nello stesso universo narrativo.

La campagna è realizzata come una room di tipo m.space.

Marker

Tutte le room della campagna dovranno avere uno state di tipo one.ryg.matrix.dnd.campaign.v1, contenente un oggetto vuoto.

{}

Esso verrà usato dai client per il play-by-chat per filtrare le room: non si vuole che essi visualizzano tutti gli space in cui un utente si trova, ma solo quelli con questo state.

Titolo

Il titolo della campagna sarà determinato dal contenuto dello state m.room.name della room che la rappresenta.

{
	"name": "Sarcofago dell'Annullamento"
}

Journal

La campagna offre un campo di testo "fisso" in cui possono essere inserite informazioni di ogni tipo, detto il journal, il cui testo è determinato dal contenuto dello state m.room.topic.

{
	"topic": "Campagna nella giungla.\n\nGiorno 1."
}

Membri della campagna

Nella room della campagna, potranno entrare diversi utenti, nei seguenti "ruoli":

  • organizzatore
  • game master
  • giocatore
  • spettatore
Power levels

Alla creazione, nella room deve essere impostato lo state m.room.power_levels per gestire le autorizzazioni degli utenti:

  • alla chiave users, si associa a ciascun utente il suo ruolo nella campagna

    • organizzatore: 100
    • game master: 50
    • giocatore: 10
  • alla chiave users_default, si definisce il livello di privilegi per gli utenti non specificati in precedenza, ovvero gli spettatori:

    • spettatore: 0
  • alla chiave events, si specifica il livello di privilegi necessari per effettuare determinate azioni:

    • cambiare il titolo della campagna, m.room.name: 100 (organizzatore)
    • modificare il journal della campagna, m.room.topic: 50 (game master)
    • aggiungere nuove partizioni alla campagna, m.space.child: 50 (game master)
  • alla chiave events_default, si definisce il livello di privilegi necessario per inviare messaggi i cui tipi non sono tra quelli specificati in precedenza; essendo questa una stanza m.space, in essa non devono essere inviati messaggi di alcun tipo, quindi:

    • 100 (organizzatore)
  • allo stesso modo, si imposta lo stesso valore alla chiave state_default, il livello di privilegi necessario per impostare stati i cui tipi non sono tra quelli specificati in precedenza:

    • 100 (organizzatore)
  • infine, si impostano i permessi di base relativi alla stanza:

    • chiedere a utenti di unirsi alla campagna, invite: 50 (game master)
    • eliminare messaggi e stati, redact: 50 (game master)
    • rimuovere membri esistenti, kick: 50 (game master) e ban: 100 (organizzatore)
{
	"users": {
		"@steffo:ryg.one": 100,
		"@mallllco:ryg.one": 50,
		"@dicebot:ryg.one": 50,
		"@gimbaro:matrix.org": 50,
		"@nemesis:ryg.one": 10,
		"@cookie:ryg.one": 10,
		"@tizio:example.org": 10
	},
	"users_default": 0,
	"events": {
		"m.room.name": 100,
		"m.room.topic": 50,
		"m.space.child": 50,
	},
	"events_default": 100,
	"state_default": 50,
	"ban": 50,
	"kick": 50,
	"redact": 50,
	"invite": 0
}
Invitare utenti

Per invitare nuovi utenti alla campagna, un utente autorizzato dovrà chiamare il metodo POST /_matrix/client/v3/rooms/{roomId}/invite sul server.

Perchè questi utenti si uniscano effettivamente alla stanza, dovranno inviare loro stessi uno state m.room.member che cambi la loro stessa membership a join.

Partizione

Una partizione è una chat relativa a una specifica campagna, i cui contenuti sono visibili solo a un sottoinsieme di utenti.

Le partizioni potrebbero essere usate per rappresentare gruppi di giocatori nella campagna, luoghi nell'universo di gioco, oppure scene narrative.

Ciascuna partizione è implementata come una room con tipo null.

Titolo

Ogni partizione ha un titolo, determinato dal contenuto dello state m.room.name della room:

{
	"name": "Chat segreta con Paltri e Balu"
}

Journal

Ogni partizione ha anch'essa un journal, il cui testo è determinato dal contenuto dello state m.room.topic.

{
	"topic": "Inventario di questo party:\n\n- un sasso\n- un altro sasso"
}

Campagna di appartenenza

Perchè una room venga considerata parte di uno space, devono essere impostati due stati:

  • all'interno della room della campagna, uno stato m.space.child con il room id della partizione in questione come chiave:

     {
     	"via": [
     		"ryg.one",
     	],
     	"canonical": true
     }
  • all'interno della room della partizione, uno stato m.space.parent con il room id della campagna in questione come chiave:

     {
     	"via": [
     		"ryg.one",
     	],
     	"canonical": true
     }

Sì, questo significa che una partizione potrebbe appartenere a più campagne contemporaneamente, ma sinceramente non penso dovrebbe cambiare niente a livello di frontend...

Messaggi

Il modulo per il gioco di ruolo prevede messaggi aventi uno di quattro formati diversi:

  • narrazione
  • dialogo
  • sistema
  • offtopic
Narrazione

I messaggi di narrazione descrivono una scena, e non vengono pronunciati da nessun personaggio.

Si rappresentano con un messaggio del tipo m.room.message, e un contenuto nella seguente forma:

{
	"msgtype": "one.ryg.matrix.dnd.narration.v1",
	"body": "C'è uno gnomo dietro al bancone."
}
Dialogo

I messaggi di dialogo rappresentano qualcosa di pronunciato da un personaggio nel mondo di gioco.

Si rappresentano con un messaggio del tipo m.room.message, e un contenuto nella seguente forma:

{
	"msgtype": "one.ryg.matrix.dnd.dialogue.v1",
	"body": "Dural: \"Dove sono?!\"",
	"dialogue": {
		"character": "Dural",
		"text": "Dove sono?!"
	}
}

I client useranno il contenuto dell'oggetto dialogue per visualizzare il messaggio; la stringa body viene usata per il rendering delle notifiche e per compatibilità con client Matrix "standard".

Sistema

I messaggi di sistema rappresentano qualcosa relativo alle meccaniche di gioco, come il risultato di un lancio di un dado.

Si rappresentano con un messaggio del tipo m.room.message, e un contenuto nella seguente forma:

{
	"msgtype": "m.notice",
	"body": "1d20+1 = 20+1 = 21"
}
Offtopic

I messaggi offtopic permettono la conversazione tra giocatori al di fuori del mondo di gioco.

Si rappresentano con un messaggio del tipo m.room.message, e un contenuto nella seguente forma:

{
	"msgtype": "m.text",
	"body": "POSSO GIOCARE ZARIL ONECRIT MORTE ISTANTANEA ZIC ZAC"
}

Personaggi

Per rendere conversare come un dato personaggio più comodo, i client devono poter tenere traccia dei personaggi all'interno di una scena.

Questo viene fatto con uno stato del tipo one.ryg.matrix.dnd.character.v1, avente il nome del personaggio stesso come chiave, e un oggetto vuoto come payload.

{}

Membri della partizione

I membri della partizione funzionano esattamente come per le campagne.

Power levels

Come per le campagne, alla creazione, nella room deve essere impostato lo state m.room.power_levels per gestire le autorizzazioni degli utenti:

  • alla chiave users, si associa a ciascun utente il suo ruolo nella campagna

    • organizzatore: 100
    • game master: 50
    • giocatore: 10
  • alla chiave users_default, si definisce il livello di privilegi per gli utenti non specificati in precedenza, ovvero gli spettatori:

    • spettatore (o giocatore senza permesso di parlare nella partizione): 0
  • alla chiave events, si specifica il livello di privilegi necessari per effettuare determinate azioni:

    • cambiare il titolo della partizione, m.room.name: 100 (organizzatore)
    • modificare il journal della partizione, m.room.topic: 50 (game master)
    • associare la partizione a una campagna, m.space.parent: 50 (game master)
    • aggiungere nuovi personaggi alla partizione, one.ryg.matrix.dnd.character.v1: 10 (giocatore)
  • alla chiave events_default, si definisce il livello di privilegi necessario per inviare messaggi i cui tipi non sono tra quelli specificati in precedenza:

    • 10 (giocatore)
  • allo stesso modo, si imposta lo stesso valore alla chiave state_default, il livello di privilegi necessario per impostare stati i cui tipi non sono tra quelli specificati in precedenza:

    • 100 (organizzatore)
  • infine, si impostano i permessi di base relativi alla stanza:

    • chiedere a utenti di unirsi alla partizione, invite: 50 (game master)
    • eliminare messaggi e stati, redact: 50 (game master)
    • rimuovere membri esistenti, kick: 50 (game master) e ban: 100 (organizzatore)
{
	"users": {
		"@steffo:ryg.one": 100,
		"@mallllco:ryg.one": 50,
		"@dicebot:ryg.one": 50,
		"@gimbaro:matrix.org": 50,
		"@nemesis:ryg.one": 10,
		"@cookie:ryg.one": 10,
		"@tizio:example.org": 10
	},
	"users_default": 0,
	"events": {
		"m.room.name": 100,
		"m.room.topic": 50,
		"m.space.parent": 50,
		"one.ryg.matrix.dnd.character.v1": 10,
		"m.room.message": 10
	},
	"events_default": 100,
	"state_default": 50,
	"ban": 50,
	"kick": 50,
	"redact": 50,
	"invite": 0
}

Una cosa più fancy sarebbe permettere anche agli spettatori (0) di inviare messaggi, ma aggiungendo un modo nel client per nascondere i messaggi di utenti con power level inferiore a giocatore (10)... Eviterei, per questo prototipo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment