Loading...
Loading...
BepInEx multiplayer mod for Subnautica 2 with synchronized co-op gameplay, adaptive scaling, and shared world state
npx skill4agent add aradotso/devtools-skills subnautica-ii-deep-synergy-coop-modSkill by ara.so — Devtools Skills collection.
# 1. Install BepInEx to Subnautica 2 directory
# Download from https://github.com/BepInEx/BepInEx/releases
# Extract to game root (where Subnautica2.exe is located)
# 2. Download Deep Synergy mod
# Extract to BepInEx/plugins/ directory
BepInEx/
plugins/
DeepSynergy/
DeepSynergy.dll
WebRTC.Native.dll
SyncEngine.dll
# 3. Create configuration directory
mkdir -p BepInEx/config/DeepSynergy
# 4. Launch game once to generate default configs
# Configs will appear in BepInEx/config/DeepSynergy/# Check BepInEx console output on game launch
# Should see:
# [Info : BepInEx] Loading [DeepSynergy 1.0.0]
# [Info : DeepSynergy] Session Manager initialized
# [Info : DeepSynergy] WebRTC channel readyBepInEx/config/DeepSynergy/synergy_profile.json{
"session_name": "My Co-op Session",
"max_players": 4,
"difficulty_scale": "adaptive",
"resource_multiplier": 1.0,
"oxygen_consumption": 1.0,
"creature_spawn_divider": 1,
"enable_pvp": false,
"friendly_fire": false,
"shared_blueprints": true,
"ping_locations_shared": true,
"time_of_day_sync": "all",
"voice_chat_integration": "none",
"api_integration": {
"openai": {
"enabled": false,
"role": "narrator"
},
"claude": {
"enabled": false,
"role": "lore_engine"
}
}
}BepInEx/config/DeepSynergy/network.json{
"nat_traversal": "auto",
"stun_servers": [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302"
],
"max_latency_ms": 150,
"sync_rate_hz": 20,
"compression": "zstd",
"encryption": true
}.envOPENAI_API_KEY=your_openai_key_here
CLAUDE_API_KEY=your_claude_key_heresynergy_profile.json{
"api_integration": {
"openai": {
"enabled": true,
"role": "narrator",
"model": "gpt-4",
"max_tokens": 150,
"temperature": 0.7
},
"claude": {
"enabled": true,
"role": "lore_engine",
"model": "claude-3-opus-20240229",
"max_tokens": 300
}
}
}F12# Start hosting a session
/start_server
# Join existing session with code
/join_session 9B2A-4C7D-E8F1
# Leave current session
/leave_session
# Display session status
/synergy_status# Adjust difficulty multiplier (temporary)
/synergy_scale 1.5
# Reset to profile defaults
/synergy_scale reset
# Show current scaling values
/synergy_info# Check synchronization state
/sync_check
# Force inventory resync
/force_sync inventory
# Show peer connection details
/peer_info
# Override world seed
/seed_override 12345# Trigger custom narration
/api_narrate "discovering ancient alien ruins"
# Generate creature lore
/api_lore_gen "ghost leviathan"
# Get contextual hint
/api_hint current_biomeusing DeepSynergy.API;
using System.Collections.Generic;
public class CustomSessionManager
{
public static SessionProfile CreateHardcoreProfile()
{
return new SessionProfile
{
SessionName = "Hardcore Duo",
MaxPlayers = 2,
DifficultyScale = ScaleMode.Static,
ResourceMultiplier = 0.7f,
OxygenConsumption = 1.3f,
CreatureSpawnDivider = 0.8f,
EnablePvP = false,
FriendlyFire = true,
SharedBlueprints = false,
TimeOfDaySync = SyncMode.Host
};
}
public static void ApplyProfile(SessionProfile profile)
{
var manager = SynergyCore.GetSessionManager();
manager.LoadProfile(profile);
manager.BroadcastConfiguration();
}
}using DeepSynergy.Events;
using BepInEx;
using BepInEx.IL2CPP;
[BepInPlugin("com.mymod.synergyextension", "Synergy Extension", "1.0.0")]
public class SynergyExtension : BasePlugin
{
public override void Load()
{
// Subscribe to inventory sync events
SyncEvents.OnInventorySync += HandleInventorySync;
// Subscribe to player join/leave
SyncEvents.OnPlayerJoined += HandlePlayerJoined;
SyncEvents.OnPlayerLeft += HandlePlayerLeft;
// Subscribe to base building events
SyncEvents.OnStructurePlaced += HandleStructurePlaced;
}
private void HandleInventorySync(InventorySyncData data)
{
Log.LogInfo($"Inventory synced: {data.ItemCount} items, hash {data.MerkleRoot}");
}
private void HandlePlayerJoined(PlayerJoinData data)
{
Log.LogInfo($"Player joined: {data.PlayerName} (ID: {data.PlayerId})");
}
private void HandlePlayerLeft(PlayerLeaveData data)
{
Log.LogInfo($"Player left: {data.PlayerId}");
}
private void HandleStructurePlaced(StructureData data)
{
Log.LogInfo($"Structure placed: {data.Type} at {data.Position}");
}
}using DeepSynergy.Scaling;
public class CustomScaler : IAdaptiveScaler
{
public float CalculateResourceMultiplier(int playerCount)
{
// Linear scaling: each player adds 25% resources
return 1.0f + (playerCount - 1) * 0.25f;
}
public float CalculateCreatureSpawnRate(int playerCount)
{
// Logarithmic scaling to avoid overwhelming players
return 1.0f + Mathf.Log10(playerCount);
}
public float CalculateOxygenConsumption(int playerCount)
{
// Reduce oxygen drain slightly in co-op
return Mathf.Max(0.5f, 1.0f - (playerCount - 1) * 0.1f);
}
}
// Register custom scaler
SynergyCore.GetSessionManager().RegisterScaler(new CustomScaler());using DeepSynergy.State;
public class BaseMonitor
{
public void CheckSharedBaseStatus()
{
var stateManager = SynergyCore.GetStateManager();
// Get all synchronized base structures
var bases = stateManager.GetSyncedStructures();
foreach (var structure in bases)
{
Debug.Log($"Structure: {structure.Id}");
Debug.Log($" Type: {structure.Type}");
Debug.Log($" Owner: {structure.PlacedByPlayerId}");
Debug.Log($" Position: {structure.Position}");
Debug.Log($" Integrity: {structure.Integrity}%");
}
// Get shared inventory state
var inventory = stateManager.GetSharedInventory();
Debug.Log($"Total items in network: {inventory.TotalItemCount}");
Debug.Log($"Merkle root: {inventory.MerkleRoot}");
}
}using DeepSynergy.Networking;
public class ConnectionManager
{
public async Task<string> CreateSession()
{
var network = SynergyCore.GetNetworkManager();
// Initialize WebRTC host
var sessionCode = await network.CreateHostSession();
Debug.Log($"Session created: {sessionCode}");
// Set up connection callbacks
network.OnPeerConnected += (peerId) => {
Debug.Log($"Peer connected: {peerId}");
};
network.OnPeerDisconnected += (peerId) => {
Debug.Log($"Peer disconnected: {peerId}");
};
return sessionCode;
}
public async Task<bool> JoinSession(string sessionCode)
{
var network = SynergyCore.GetNetworkManager();
try
{
await network.JoinSession(sessionCode);
Debug.Log("Successfully joined session");
return true;
}
catch (NetworkException ex)
{
Debug.LogError($"Failed to join: {ex.Message}");
return false;
}
}
}using DeepSynergy.Migration;
SyncEvents.OnHostMigration += (newHostId) => {
if (newHostId == SynergyCore.GetLocalPlayerId())
{
// This client is now the host
Debug.Log("Became session host");
// Broadcast new session state
var state = SynergyCore.GetStateManager();
state.BroadcastFullState();
}
else
{
Debug.Log($"New host: {newHostId}");
}
};using DeepSynergy.AI;
public class NarrativeController
{
private AIIntegration aiIntegration;
public void Initialize()
{
aiIntegration = SynergyCore.GetAIIntegration();
// Only enable if configured
if (!aiIntegration.IsEnabled())
{
Debug.Log("AI integration disabled in profile");
return;
}
}
public async Task NarrateDiscovery(string biome, string discovery)
{
if (!aiIntegration.IsEnabled()) return;
var prompt = $"The team discovered {discovery} in the {biome}. " +
$"Write a brief journal entry (50 words max).";
var narration = await aiIntegration.GenerateNarration(prompt);
// Broadcast to all players
SynergyCore.GetChatManager().SendSystemMessage(narration);
}
}using DeepSynergy.Validation;
public static class ProfileValidator
{
public static bool ValidateProfile(SessionProfile profile, out string error)
{
error = null;
if (profile.MaxPlayers < 2 || profile.MaxPlayers > 8)
{
error = "MaxPlayers must be between 2 and 8";
return false;
}
if (profile.ResourceMultiplier < 0.1f || profile.ResourceMultiplier > 5.0f)
{
error = "ResourceMultiplier must be between 0.1 and 5.0";
return false;
}
if (profile.OxygenConsumption < 0.1f)
{
error = "OxygenConsumption cannot be less than 0.1";
return false;
}
return true;
}
}/start_server# Check network configuration
cat BepInEx/config/DeepSynergy/network.json
# Verify STUN servers are accessible
ping stun.l.google.com
# Check firewall settings (allow UDP ports 49152-65535)
# Enable verbose logging
/synergy_debug enable
# Check BepInEx console for errors
# Look for: [Error : DeepSynergy] Failed to initialize WebRTC# Force full resync
/force_sync all
# Check sync status
/sync_check
# Verify Merkle tree integrity
/debug merkle_verify// Manual resync trigger
var stateManager = SynergyCore.GetStateManager();
stateManager.RequestFullSync();
// Check for conflicts
var conflicts = stateManager.GetSyncConflicts();
foreach (var conflict in conflicts)
{
Debug.LogWarning($"Sync conflict: {conflict.Type} at {conflict.Timestamp}");
}// Reduce sync rate in network.json
{
"sync_rate_hz": 10,
"max_latency_ms": 200,
"compression": "lz4"
}# Check peer latency
/peer_info
# Output example:
# Peer 1 (9B2A): 45ms, packet loss 0.2%
# Peer 2 (4C7D): 180ms, packet loss 2.1%/api_narrate# Verify environment variables
echo $OPENAI_API_KEY
echo $CLAUDE_API_KEY
# Check API status in logs
grep "AI Integration" BepInEx/LogOutput.log
# Test API connection directly
/api_test openai
/api_test claude// Conflict resolution is automatic, but you can listen for events
SyncEvents.OnStructureConflict += (conflict) => {
Debug.Log($"Structure conflict resolved: {conflict.WinningPlayerId} " +
$"placed {conflict.StructureType} at {conflict.Position}");
};// Enable migration in synergy_profile.json
{
"enable_host_migration": true,
"migration_timeout_seconds": 10
}// Monitor migration status
SyncEvents.OnMigrationStarted += () => {
Debug.Log("Host migration in progress...");
};
SyncEvents.OnMigrationFailed += (reason) => {
Debug.LogError($"Migration failed: {reason}");
// Optionally save session state locally
SynergyCore.GetStateManager().SaveLocalBackup();
};# Verify shared_blueprints setting
/synergy_info | grep blueprints
# Force blueprint resync
/force_sync blueprints// Manual blueprint sync
var blueprintManager = SynergyCore.GetBlueprintManager();
blueprintManager.SyncAllBlueprints();
// Check blueprint state
var syncedBlueprints = blueprintManager.GetSyncedBlueprints();
Debug.Log($"Synced blueprints: {syncedBlueprints.Count}");