This wiki is not yet updated for 4.1. Please review our questions channel's pins on Discord for more info.
For the complete documentation index, see llms.txt. This page is also available as Markdown.

Creating Fika-Compatible Mods

Updated for 4.0

Fika Events

Fika has a lot of events that you can subscribe to, which makes it easier to run code at certain key moments of the raid. To subscribe to an event, use:

/// <summary>
/// Subscribes a callback to a specific type of Fika event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to subscribe to.</typeparam>
/// <param name="callback">The callback to invoke when the event is dispatched.</param>
public static void SubscribeEvent<TEvent>(Action<TEvent> callback) where TEvent : FikaEvent

To unsubscribe, use:

/// <summary>
/// Unsubscribes a callback from a specific type of Fika event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to unsubscribe from.</typeparam>
/// <param name="callback">The callback to remove from the event subscription.</param>
public static void UnsubscribeEvent<TEvent>(Action<TEvent> callback) where TEvent : FikaEvent

The event triggered will usually pass an important object related to the event, e.g. FikaNetworkManagerCreatedEvent passes a IFikaNetworkManager (named Manager in the object). This object can then be accessed if needed.

You can read the source code here to find all events.

Registering Packets

To register packets, subscribe to the FikaNetworkManagerCreatedEvent and access the IFikaNetworkManager. In the manager you can call either of these methods:

The INetSerializable needs to be a packet that you have created, and these methods are invoked when that packet is received. The second method also passes the NetPeer, which is useful on the FikaServer. You handle the logic however you want when receiving the packet with these methods.

Creating a Packet

To create a packet, implement the INetSerializable interface into a new class. For packets that are sent often, I highly recommend using a struct. Add the data that you need in the form of Field and make all of them Public. Use the Serialize() and Deserialize() methods to write/read data. You can find an example here, which also includes how to write an enum. There are also a lot of extensions to write EFT/Unity specific data (e.g. Vector3) that you can find here.

Do not instantiate and send new collections in packets that are sent often, e.g. List<T> or T[]. The allocations will become expensive. Fika has an interface IReusable that you can use to reuse a single instance of a packet, and only mutate the collections that exist in it.

An example of these packets can be found here. Create the class somewhere, keep track of it and reuse it. You can find an example of that in the FikaClientWorld.

Sending a Packet

To send a packet, you need a IFikaNetworkManager. This is either a FikaServer or FikaClient that you can access with the Comfort.Common namespace using Singleton<IFikaNetworkManager>.Instance. To determine whether you are a server or client, use FikaBackendUtils.IsServer.

Use this method to send packets:

The broadcast argument determines whether it will be sent to all other clients. As the server, this is always true.

If you want to send to just one specific NetPeer, e.g. after receiving a packet and you want to respond to that peer:

spinner

Some specific functions are class specific, and cannot be called from the interface singleton. You can access the specific FikaServer or FikaClient using e.g. Singleton<FikaServer>.Instance.

The specific methods are:

Client

Server


Inventory Replication

Fika will automatically replicate all inventory actions, as long as you pass the operation through the InventoryController.vmethod_1. Every player has an inventory controller property (EFT.Player.InventoryController), use it! Even without Fika, do not try to cirumvent this method and always pass them through the controller. Each operation has a constructor you can find in the assembly. You can look at these to see how to properly create and execute and inventory operation. Do not call the result directly! Use it to create the operation (I've seen this mistake a lot in the past from mod developers), and run it on the controller. Some examples you can look for the subtypes of are: IExecute, IRollback, IRaiseEvents.


Snapshotting

This guide explains the implementation and usage of the Snapshotter<T> system made for Fika. This module provides a zero-allocation, high-fidelity interpolation and extrapolation engine designed for high-tick-rate synchronization. It is highly recommended to use the ThrottledMono to send the data at a tickrate of 20, i.e. 20 times per second on average. Why would I use this and the snapshotter? To save bandwidth and CPU usage. See General Information About Data and Interpolation for more information.

Key Features

  • Zero-Allocation: Utilizes generic value-type specialization and ref semantics to ensure no Garbage Collection (GC) overhead during the hot path.

  • Adaptive Jitter Buffer: Dynamically adjusts interpolation delay based on real-time network variance (Jitter) using an asymmetric EMA.

  • Clock Synchronization: Automatically aligns local client time with server time using a smoothed offset.

  • Validation: Built-in protection against out-of-order and duplicate UDP packets.

Implementation

  1. Define Your Data

  2. Initialize the Snapshotter

Usage

Adding Data

When a packet arrives from the network, pass it directly to the snapshotter. The system will internally handle clock synchronization and jitter estimation.

Sampling for Rendering

In your Update loop, sample the buffer to find the correct interpolation indices and the t (0.0 to 1.0) progress value.

Performance Tip: Always use ref readonly var when retrieving snapshots. This prevents the CPU from copying the snapshot data (which can be 60+ bytes) and instead points directly to the data inside the buffer.

Technical Performance

The system is optimized at the IL (Intermediate Language) level. By using where T : struct, the JIT compiler devirtualizes interface calls and inlines property getters.

Metric
Result

Memory Allocation

0 Bytes

Insertion Speed

~5.8ns

Sampling Speed

~4.2ns

Algorithm

O(log N) Binary Search

General Information About Data

Keep in mind that performance and bandwidth is not free! Do not send redundant data every Update() unless you have to.

Fika has a class that you can inherit called ThrottledMono, where you can set an UpdateRate which is how many times it should update per second. This can dramatically increase performance and reduce bandwidth used.

Calculating Packet Size (UDP with Headers)

When sending data over a network using UDP, each packet consists of:

  1. Your payload (the actual data, e.g., floats)

  2. Packet-specific overhead (1–4 bytes depending on the type, e.g. Unreliable or ReliableOrdered)

  3. UDP header (8 bytes)

  4. IP header (20–60 bytes, depending on IPv4 options)

It’s important to account for all headers, not just the payload, because small payloads can become inefficient due to header overhead.

Formula

Let:

  • N = number of elements being sent

  • Selement = size of one element in bytes (e.g., 4 bytes for a float)

  • Hpacket​ = packet-specific overhead (1–4 bytes)

  • HUDPH​ = UDP header size (8 bytes)

  • HIPH​ = IP header size (20–60 bytes)

Then, the total packet size in bytes is:

Packet Size (bytes)=N×Selement+Hpacket+Hudp+Hip\text{Packet Size (bytes)} = N \times S_{\text{element}} + H_{\text{packet}} + H_{\text{udp}} + H_{\text{ip}}

To convert to bits:

Packet Size (bits)=8×(NSelement+Hpacket+Hudp+Hip)\text{Packet Size (bits)} = 8 \times \Big( N \cdot S_{\text{element}} + H_{\text{packet}} + H_{\text{udp}} + H_{\text{ip}} \Big)

Example

Suppose you are sending a Vector3, which is 3 floats (4 bytes each), with 2 bytes of packet-specific overhead, 8 bytes UDP header, and 20 bytes IP header:

Packet Size=3×4+2+8+20=42 bytesPacket Size=3×4+2+8+20=42 bytes
Packet Size in bits=42×8=336 bitsPacket Size in bits=42×8=336 bits

Even this small payload, if sent frequently (e.g., every frame in a game), can consume significant bandwidth. Notice that even though the payload is only 12 bytes, headers increase the total packet size almost . Now imagine this being unthrottled, and the client is running at 120 FPS:

We already have:

Packet Size=42 bytesPacket Size=42 bytes

If we send 120 packets per second:

Data per second (bytes)=42×120Data per second (bytes)=42×120

Step-by-step

  • Packet size is 42 bytes.

  • Sending 120 packets per second.

  • Multiply packet size by number of packets: 42 × 120.

Break it down:

42×120=42×(12×10)=(42×12)×1042×120=42×(12×10)=(42×12)×10
42×12=50442×12=504
504×10=5,040504×10=5,040

Convert to bits:

5,040×8=40,320 bits per second (bps)5,040×8=40,320 bits per second (bps)

This is a lot of wasted bandwidth and CPU usage. Unless it's critical, do not send data every tick. Rather, interpolate values on the receiving end if needed. Breaking it down further:

Data transferred=5KB/s×60s=300KBData transferred=5KB/s×60s=300KB

That is 300KB per minute for one, single Vector3. That is almost ¼ of the bandwidth that Fika sends for all bots states every minute.

The size of one entire player state (52 bytes), 20/s:

52×20=1040bytes/sec per entity52×20=1040bytes/sec per entity

Assuming we have 20 bots:

1040×20=20,800bytes/sec total1040×20=20,800bytes/sec total

Now per minute:

20,800×60=1,248,000bytes/minute1,248MB/minute(1.19MiB)20,800×60=1,248,000bytes/minute ≈ 1,248MB/minute (1.19 MiB)

Interpolation

As you can see from the breakdown, this is a lot of wasted data that could be throttled and sent less frequently, and potentially interpolated instead by lerping the values and sending the time when sending and comparing with the time when received.

Interpolation factor t:

t=currentTimesentTimereceivedTimesentTimet = \frac{\text{currentTime} - \text{sentTime}}{\text{receivedTime} - \text{sentTime}}

Clamp t between 0 and 1:

t=max(0,min(1,t))t = \max(0, \min(1, t))

Linear interpolation formula:

lerpedValue=oldValue+(newValueoldValue)t\text{lerpedValue} = \text{oldValue} + (\text{newValue} - \text{oldValue}) \cdot t

You can then send the current time (NetworkTimeSync.NetworkTime) and compare it with current time when received, and smooth out differences using the equations above.

Now comparing the different methods of sending

20 messages/sec at 16 bytes:

16×20=320 bytes/sec16 \times 20 = 320\ \text{bytes/sec}

120 messages/sec at 12 bytes:

12×120=1,440 bytes/sec12 \times 120 = 1{,}440\ \text{bytes/sec}

That is ~1080bytes saved per second:

1,440320=1,120 bytes/sec1{,}440 - 320 = 1{,}120\ \text{bytes/sec}

Summary

By combining time synchronization with lerp-based smoothing, we can create fluid, latency-tolerant motion without spamming the network. Instead of sending full position updates every frame, we send fewer messages that include a timestamp and interpolate locally:

lerpedValue=(1t)oldValue+tnewValue\text{lerpedValue} = (1 - t) \cdot \text{oldValue} + t \cdot \text{newValue}

This allows clients to smoothly reconstruct movement based on timing differences rather than raw frequency.

For example, reducing from 120 messages/sec at 12 bytes to 20 messages/sec at 16 bytes saves:

1,440320=1,120 bytes/sec1{,}440 - 320 = 1{,}120\ \text{bytes/sec}

While 1,12 KB/sec per stream might seem small, it scales quickly — each actor can send multiple data streams (position, rotation, animation state, etc.), and with many actors, the savings multiply dramatically.

Optimizing send frequency and payload size is one of the most effective ways to achieve smooth, efficient, and scalable networked movement.

This approach doesn’t just apply to movement — it’s equally useful for synchronizing any time-based value, such as animations, UI transitions, physics states, or even audio parameters.

Tips and useful classes

  • FikaBackendUtils has tons of useful methods/properties/fields that can be used.

  • FikaGlobals has some helper methods that can be useful during development.

  • CoopHandler has useful properties and methods, mainly to track players (especially human). It can be accessed on the Singleton<IFikaNetworkManager>.Instance or CoopHandler.TryGetCoopHandler() depending on your code style preference.

  • FikaSerializationExtensions have tons of good extension methods to handle data, e.g. packing a float. If precision is not important to the last decimal, it's recommended to pack your primitives.

Mod Examples

You can find an example sync-mod from the creator of Fika for the mod MergeConsumables here.

Last updated