# Noray

Singleton providing [noray] integration.

*noray* is a backend application that orchestrates connection between players.
To do this, players send a connection request to *noray*, and in turn *noray*
sends the players' external addresses to eachother. It is then up to the
players to conduct a handshake process.

If the handshake fails, players can request a *relay* from *noray*. In these
cases, *noray* will receive data from one player and forward it to the other,
acting as a middle man.

## Identifiers

*noray* identifies players with two different IDs: OpenID and PrivateID.

*OpenID* is public, and can be shared with other players. This ID is used to
identify hosts when connecting to games.

*PrivateID* is only sent to the player it identifies and should **never** be
shared. Acts similar to a password, and is used to authorize commands.

## Relays and NAT Punchthrough

*noray* provides two methods of connecting players.

*NAT Punchthrough* relies on the NAT table. Players must continuously send data
to eachother until either two-way communication is established, or a timeout is
reached. For certain router setups, NAT punchthrough does not work.

See: [NAT Punch-through for Multiplayer Games]

For *relays*, *noray* allocates a specific port to a given player. When *noray*
receives data on this port, it will forward it as-is to the player. As long as
*noray* is accessible over the internet, relays should work reliably no matter
the router setup.

## Registering with noray

To start using *noray*, connect to a *noray* server, request IDs by
registering, and then register the remote address:

```gdscript
var host = "some.noray.host"
var port = 8890
var err = OK

# Connect to noray
err = await Noray.connect_to_host(host, port)
if err != OK:
  return err # Failed to connect

# Register host
Noray.register_host()
await Noray.on_pid

# Register remote address
# This is where noray will direct traffic
err = await Noray.register_remote()
if err != OK:
  return err # Failed to register
```

By calling `Noray.register_host()`, a request is sent to *noray*. Once a
response is received, both the `on_pid` and `on_oid` signals are fired, for
receiving the PrivateID and OpenID respectively.

The remote address must be registered so that *noray* knows where to direct
other players wanting to connect. This process also sets `Noray.local_port`,
which is where traffic can be received through *noray*.

## Starting a host

To host a game, start listening on *noray*'s local port:

```gdscript
var peer = ENetMultiplayerPeer.new()
var err = peer.create_server(Noray.local_port)

if err != OK:
  return false # Failed to listen on port
```

The rest is handled by *noray*.

## Starting a client

To connect to a game, send a request to *noray* with the host's OpenID.

```gdscript
var oid = "abcd1234"

# Connect using NAT punchthrough
Noray.connect_nat(oid)

# Or connect using relay
Noray.connect_relay(oid)
```

Once the request is sent, *noray* will send a message to both the client and
the host players to connect to each other. The actual connection is done by
handling signals.

!!!note
    *noray* provides no functionality to share OpenIDs. For development, you
    can display the OpenID in a textbox, letting players copy it and share over
    their preferred messaging app.

## Handling signals

When a connect message is received, the appropriate signal is fired.

*on_connect_nat* is fired to connect with NAT punchthrough.

*on_connect_relay* is fired to connect to a relay.

In both cases, a public address is passed to the signal handler, in the form of
an address string and a port. Handlers must conduct a handshake ( e.g. with
[PacketHandshake] ) and connect if successful.

Client example:

```gdscript
func _ready():
  Noray.on_connect_nat.connect(_handle_connect)
  Noray.on_connect_relay.connect(_handle_connect)

func _handle_connect(address: String, port: int) -> Error:
  # Do a handshake
  var udp = PacketPeerUDP.new()
  udp.bind(Noray.local_port)
  udp.set_dest_address(address, port)

  var err = await PacketHandshake.over_packet_peer(udp)
  udp.close()

  if err != OK:
    return err

  # Connect to host
  var peer = ENetMultiplayerPeer.new()
  err = peer.create_client(address, port, 0, 0, 0, Noray.local_port)

  if err != OK:
    return err

  return OK
```

!!!note
    Make sure to **always** specifiy the local port for the client - this is
    the only port noray recognizes, and failing to specify it will result in
    broken connectivity.

Host example:

```gdscript
func _ready():
  Noray.on_connect_nat.connect(_handle_connect)
  Noray.on_connect_relay.connect(_handle_connect)

func _handle_connect(address: String, port: int) -> Error:
  var peer = get_tree().get_multiplayer().multiplayer_peer as ENetMultiplayerPeer
  var err = await PacketHandshake.over_enet(peer.host, address, port)

  if err != OK:
    return err

  return OK
```

!!!note
    The host handshake is a bit different, as it can't receive manual packets,
    only send them. So it assumes that the target is always responsive, and
    just blasts them with a bunch of packets. If the target is indeed
    responsive, it can connect. If not, nothing happens, as expected.

[noray]: https://github.com/foxssake/noray
[NAT Punch-through for Multiplayer Games]: https://keithjohnston.wordpress.com/2014/02/17/nat-punch-through-for-multiplayer-games/
[PacketHandshake]: ./packet-handshake.md
