# 3. Implement Custom Command

{% hint style="info" %}
This section assumes you have completed Tutorials 1 and 2.
{% endhint %}

## Introduction

Let's continue to explore the customizability of Diarkis by implementing a custom command.

In this tutorial, we will implement the following command:

* A command to attack enemies in a Room
* Command version: 2 (versions 0 and 1 are used for built-in commands)
* Command ID: 300
* Request: Send a command specifying an attack type (type) (1: Melee, 2: Ranged)
* Response: Return who dealt damage, the damage value, and the total damage value
* Push notifications to all room members

The implementation flow is as follows:

* Write code to generate a payload using puffer (a data serializer provided by Diarkis)
* Implement the custom command on the server
* Implement the command in the test client

## Puffer Module (Data Serializer provided by Diarkis)

Puffer is a tool that generates code to serialize and deserialize data from JSON.

The output supports C++, C#, and Go, simplifying packet data implementation between clients developed in Unreal or Unity and servers developed in Go.

For more details on Puffer, please refer to the API reference.

<https://docs.diarkis.io/docs/server/v1.0.0/diarkis/puffer/index.html>

### Creating a JSON File for Custom Command Data Definition

💾 Add `./puffer/json_definitions/custom/attack.json`.

**attack** is the data used for the custom command's request.

* type: The attack type can be stored as a uint8

**attackResult** is the data used for responses and push notifications to room members.

* type: The attack type can be stored as a uint8
* uid: Who dealt the damage. Stored as a string
* damage: Damage value. Stored as uint16
* totalDamage: Total damage value. Stored as uint16

```json
{
  "attack": {
    "ver": 2,
    "cmd": 300,
    "package": "custom",
    "properties": {
      "type": "u8"
    }
  },
  "attackResult": {
    "ver": 2,
    "cmd": 300,
    "package": "custom",
    "properties": {
      "type": "u8",
      "uid": "string",
      "damage": "u16",
      "totalDamage": "u16"
    }
  }
}
```

### Code Generation

Execute the following command to generate code. The code will be output to the directory of each language under puffer.

```bash
$ make gen
# Code will be output to each language directory under puffer
puffer
├── cpp/custom/Attack.h
├── cpp/custom/AttackResult.h
├── cs/custom/Attack.cs
├── cs/custom/AttackResult.cs
├── go/custom/attack.go
└── go/custom/attackresult.go
```

## Implementing the Attack Command Handler on the Server

Let's implement the attack command on the server.

💾 Add `./cmds/custom/attack.go`.

Comments within the code explain what is being implemented, so please read them as well.

{% code overflow="wrap" fullWidth="true" %}

```go
package customcmds

import (
	"errors"
	// pattack is the package generated from puffer.
	pattack "handson/puffer/go/custom"

	"github.com/Diarkis/diarkis/derror"
	"github.com/Diarkis/diarkis/room"
	"github.com/Diarkis/diarkis/server"
	"github.com/Diarkis/diarkis/user"
	"github.com/Diarkis/diarkis/util"
)

// The attack function attacks enemies within the Room.
// 
// All commands in Diarkis take specific arguments:
// - ver: Command version
// - cmd: Command ID
// - payload: Data passed to the command
// - userData: User data
func attack(ver uint8, cmd uint16, payload []byte, userData *user.User, next func(error)) {

	// Since attack is a command to attack enemies in a Room,
	// an error will occur if the user is not in a Room.
	// Whether the user is in a Room is checked by obtaining the roomID.
	roomID := room.GetRoomID(userData)
	// If not in a room, roomID will be empty, so error handling is performed
	if roomID == "" {
		// Return an error response to the user with userData.ServerRespond().
		err := errors.New("not in the room")
		userData.ServerRespond(derror.ErrData(err.Error(), derror.NotAllowed(0)), ver, cmd, server.Bad, true)
		// After returning the response, execute next(err) and return.
		next(err)
		return
	}

	// After calling pattack.NewAttack(), the payload can be deserialized with req.Unpack(payload).
	req := pattack.NewAttack()
	req.Unpack(payload)

	// Calculate damage based on the Type.
	damage := 0
	switch req.Type {
	case 1: // Melee attack (D20)
		damage = util.RandomInt(1, 20)
	case 2: // Ranged attack (D12 + 3)
		damage = util.RandomInt(1, 12) + 3
	default:
		err := errors.New("invalid attack type")
		userData.ServerRespond(derror.ErrData(err.Error(), derror.InvalidParameter(0)), ver, cmd, server.Bad, true)
		next(err)
		return
	}

	// Add the calculated damage to the enemy's total damage.
	// Information can be saved as a Property in the Room.
	// Here, damage is added to the key "DAMAGE".
	// The add operation returns the new value with room.IncrProperty.
	// There are other functions for handling properties. Details can be found below.
	// https://docs.diarkis.io/docs/server/v1.0.0/diarkis/room/index.html
	updatedDamage, updated := room.IncrProperty(roomID, "DAMAGE", int64(damage))
	if !updated {
		err := errors.New("incr property failed")
		userData.ServerRespond(derror.ErrData(err.Error(), derror.Internal(0)), ver, cmd, server.Err, true)
		next(err)
		return
	}
	logger.Info("Room %s has been attacked by %s using %s attack by %d damage. Total damage: %d", roomID, userData.ID, req.Type, damage, updatedDamage)

	// Return who dealt the damage, the damage value, and the total damage value, and notify room members.
	// Create a new response with res := pattack.NewAttackResult() and set the data to be returned.
	res := pattack.NewAttackResult()
	res.Type = req.Type
	res.Uid = userData.ID
	res.Damage = uint16(damage)
	res.TotalDamage = uint16(updatedDamage)

	// Use userData.ServerRespond() for the user who executed the command,
	// and room.Relay() to notify other room members of the result.
	userData.ServerRespond(res.Pack(), ver, cmd, server.Ok, true)
	room.Relay(roomID, userData, ver, cmd, res.Pack(), true)
	// After returning the response, execute next(nil) and return.
	next(nil)
}
```

{% endcode %}

### Publishing the Attack Command

Once the command is implemented, it needs to be published externally.

Edit `./cmds/custom/main.go` and add the following.

By adding `diarkisexec.SetServerCommandHandler()` to `Expose()`, publish the attack command.

```diff
diff --git a/cmds/custom/main.go b/cmds/custom/main.go
index f2374ed..e98f5ef 100644
--- a/cmds/custom/main.go
+++ b/cmds/custom/main.go
@@ -37,6 +37,7 @@ func Expose() {
        diarkisexec.SetServerCommandHandler(custom.GetFieldInfoVer, custom.GetFieldInfoCmd, getFieldInfo)
        diarkisexec.SetServerCommandHandler(CustomVer, getUserStatusListCmdID, getUserStatusList)
        diarkisexec.SetServerCommandHandler(CustomVer, resonanceCmdID, resonanceCmd)
+       diarkisexec.SetServerCommandHandler(custom.AttackVer, custom.AttackCmd, attack)
 }
```

## Adding the Attack Command to the Test Client

To execute the server's attack command, add the command to the test client.

Add `./testcli/handson/attack.go`.

This includes setup to execute the client, sending the command to the server with `Attack()`, handling the response with `onResponse()`, and handling push notifications with `onPush()`.

```go
package handson

import (
	"fmt"

	pattack "handson/puffer/go/custom"

	"github.com/Diarkis/diarkis/client/go/tcp"
	"github.com/Diarkis/diarkis/client/go/udp"
)

type Handson struct {
	tcp *tcp.Client
	udp *udp.Client
}

func SetupHandsonAsTCP(c *tcp.Client) *Handson {
	h := &Handson{tcp: c}
	h.setup()
	return h
}

func SetupHandsonAsUDP(c *udp.Client) *Handson {
	h := &Handson{udp: c}
	h.setup()
	return h
}

func (h *Handson) setup() {
	if h.tcp != nil {
		h.tcp.OnResponse(h.onResponse)
		h.tcp.OnPush(h.onPush)
		return
	}
	if h.udp != nil {
		h.udp.OnResponse(h.onResponse)
		h.udp.OnPush(h.onPush)
	}
}

func (h *Handson) onResponse(ver uint8, cmd uint16, status uint8, payload []byte) {
	if ver != pattack.AttackVer || cmd != pattack.AttackCmd {
		return
	}
	if status != uint8(1) {
		fmt.Printf("Attack failed: %v\n", string(payload))
		return
	}
	res := pattack.NewAttackResult()
	err := res.Unpack(payload)
	if err != nil {
		fmt.Printf("Failed to unpack attack response: %v\n", err)
		return
	}
	fmt.Printf("You dealt %d damage. Total damage: %d\n", res.Damage, res.TotalDamage)
}

func (h *Handson) onPush(ver uint8, cmd uint16, payload []byte) {
	// No push
	if ver != pattack.AttackVer || cmd != pattack.AttackCmd {
		return
	}
	res := pattack.NewAttackResult()
	err := res.Unpack(payload)
	if err != nil {
		fmt.Printf("Failed to unpack attack response: %v\n", err)
	}
	fmt.Printf("%s dealt %d damage. Total damage: %d\n", res.Uid, res.Damage, res.TotalDamage)
}

func (h *Handson) Attack(attackType uint8) {
	req := pattack.NewAttack()
	req.Type = attackType
	if h.tcp != nil {
		h.tcp.Send(pattack.AttackVer, pattack.AttackCmd, req.Pack())
		return
	}
	if h.udp != nil {
		h.udp.Send(pattack.AttackVer, pattack.AttackCmd, req.Pack())
	}
}
```

Register the `handson attack` command to the test client.

Edit `./testcli/main.go`.

You can register a new command to the test client with cli.RegisterCommands(string, \[]cli.Command).

```diff
diff --git a/testcli/main.go b/testcli/main.go
index bdca4ab..2dbdd2c 100644
--- a/testcli/main.go
+++ b/testcli/main.go
@@ -3,8 +3,11 @@ package main
 import (
        "bufio"
        "fmt"
+       "handson/testcli/handson"
        "handson/testcli/resonance"
        "os"
+       "strconv"
+       "strings"

        "github.com/Diarkis/diarkis/client/go/test/cli"
 )
@@ -12,17 +15,24 @@ import (
 var (
        tcpResonance *resonance.Resonance
        udpResonance *resonance.Resonance
+       tcpHandson   *handson.Handson
+       udpHandson   *handson.Handson
 )

 func main() {
        cli.SetupBuiltInCommands()
        cli.RegisterCommands("test", []cli.Command{{CmdName: "resonate", Desc: "Resonate your message", CmdFunc: resonate}})
+       cli.RegisterCommands("handson", []cli.Command{
+               {CmdName: "attack", Desc: "Attack for hands-on", CmdFunc: attack},
+       })
        cli.Connect()
        if cli.TCPClient != nil {
                tcpResonance = resonance.SetupAsTCP(cli.TCPClient)
+               tcpHandson = handson.SetupHandsonAsTCP(cli.TCPClient)
        }
        if cli.UDPClient != nil {
                udpResonance = resonance.SetupAsUDP(cli.UDPClient)
+               udpHandson = handson.SetupHandsonAsUDP(cli.UDPClient)
        }
        cli.Run()
 }
@@ -45,3 +55,30 @@ func resonate() {
                udpResonance.Resonate(message)
        }
 }
+
+func attack() {
+       reader := bufio.NewReader(os.Stdin)
+       fmt.Println("Which client to join a room? [tcp/udp]")
+       client, _ := reader.ReadString('\n')
+       fmt.Println("Enter the attack type. [1: melee, 2: range]")
+       attackTypeStr, _ := reader.ReadString('\n')
+       attackTypeStr = strings.Trim(attackTypeStr, "\n")
+       attackType, err := strconv.Atoi(attackTypeStr)
+       if err != nil || attackType != 1 && attackType != 2 {
+               fmt.Println("Invalid attack type.")
+               return
+       }
+
+       switch client {
+       case "tcp\n":
+               if tcpHandson == nil {
+                       return
+               }
+               tcpHandson.Attack(uint8(attackType))
+       case "udp\n":
+               if udpHandson == nil {
+                       return
+               }
+               udpHandson.Attack(uint8(attackType))
+       }
+}
```

Generate binary and restart the udp server

Run `make build-local` again to generate the binary.

The server and test client will be updated.

If no errors are found, stop and restart the udp server.

## Verifying the attack command

After `room create` and `room join`, issue the `handson attack` command and confirm that the damage is accumulated.

### Execute `handson attack` with uid: test1

```
> handson attack
Which client to join a room? [tcp/udp]
udp
Enter the attack type. [1: melee, 2: range]
1
You dealt 13 damage. Total damage: 13
```

### Receive push notification of attack result with uid: test2

```
[UID: test2][SID(UDP): zzzz][RoomID: yyyy]
 > test1 dealt 13 damage. Total damage: 13
```
