Adding Gold Manager with initial network setup

This commit is contained in:
Matt F 2026-04-23 22:44:45 -07:00
parent 048e618f1e
commit 9369763df5
15 changed files with 488 additions and 100 deletions

6
.vsconfig Normal file
View file

@ -0,0 +1,6 @@
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Workload.ManagedGame"
]
}

View file

@ -38,12 +38,12 @@ RenderSettings:
m_ReflectionIntensity: 1 m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0} m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0} m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1}
m_UseRadianceAmbientProbe: 0 m_UseRadianceAmbientProbe: 0
--- !u!157 &3 --- !u!157 &3
LightmapSettings: LightmapSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 12 serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings: m_GISettings:
serializedVersion: 2 serializedVersion: 2
m_BounceScale: 1 m_BounceScale: 1
@ -119,6 +119,78 @@ NavMeshSettings:
debug: debug:
m_Flags: 0 m_Flags: 0
m_NavMeshData: {fileID: 0} m_NavMeshData: {fileID: 0}
--- !u!1 &239104687
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 239104690}
- component: {fileID: 239104688}
- component: {fileID: 239104689}
m_Layer: 0
m_Name: GoldManager
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &239104688
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 239104687}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
GlobalObjectIdHash: 3224213014
InScenePlacedSourceGlobalObjectIdHash: 0
DeferredDespawnTick: 0
Ownership: 1
AlwaysReplicateAsRoot: 0
SynchronizeTransform: 1
ActiveSceneSynchronization: 0
SceneMigrationSynchronization: 0
SpawnWithObservers: 1
DontDestroyWithOwner: 0
AutoObjectParentSync: 1
SyncOwnerTransformWhenParented: 1
AllowOwnerToParent: 0
--- !u!114 &239104689
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 239104687}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d44ebdd0b2fc4144c8f8a181a714b738, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.GoldManager
ShowTopMostFoldoutHeaderGroup: 1
startingGold: 100
--- !u!4 &239104690
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 239104687}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &330585543 --- !u!1 &330585543
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -248,14 +320,14 @@ MonoBehaviour:
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
m_RequiresDepthTexture: 0 m_RequiresDepthTexture: 0
m_RequiresColorTexture: 0 m_RequiresColorTexture: 0
m_Version: 2
m_TaaSettings: m_TaaSettings:
quality: 3 m_Quality: 3
frameInfluence: 0.1 m_FrameInfluence: 0.1
jitterScale: 1 m_JitterScale: 1
mipBias: 0 m_MipBias: 0
varianceClampScale: 0.9 m_VarianceClampScale: 0.9
contrastAdaptiveSharpening: 0 m_ContrastAdaptiveSharpening: 0
m_Version: 2
--- !u!1 &410087039 --- !u!1 &410087039
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -282,14 +354,14 @@ Light:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 410087039} m_GameObject: {fileID: 410087039}
m_Enabled: 1 m_Enabled: 1
serializedVersion: 11 serializedVersion: 13
m_Type: 1 m_Type: 1
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Intensity: 2 m_Intensity: 2
m_Range: 10 m_Range: 10
m_SpotAngle: 30 m_SpotAngle: 30
m_InnerSpotAngle: 21.80208 m_InnerSpotAngle: 21.80208
m_CookieSize: 10 m_CookieSize2D: {x: 10, y: 10}
m_Shadows: m_Shadows:
m_Type: 2 m_Type: 2
m_Resolution: -1 m_Resolution: -1
@ -334,8 +406,11 @@ Light:
m_UseBoundingSphereOverride: 0 m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1 m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0 m_ForceVisible: 0
m_ShadowRadius: 0 m_ShapeRadius: 0
m_ShadowAngle: 0 m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!4 &410087041 --- !u!4 &410087041
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -363,17 +438,23 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_Version: 3
m_UsePipelineSettings: 1 m_UsePipelineSettings: 1
m_AdditionalLightsShadowResolutionTier: 2 m_AdditionalLightsShadowResolutionTier: 2
m_LightLayerMask: 1
m_RenderingLayers: 1
m_CustomShadowLayers: 0 m_CustomShadowLayers: 0
m_ShadowLayerMask: 1
m_ShadowRenderingLayers: 1
m_LightCookieSize: {x: 1, y: 1} m_LightCookieSize: {x: 1, y: 1}
m_LightCookieOffset: {x: 0, y: 0} m_LightCookieOffset: {x: 0, y: 0}
m_SoftShadowQuality: 1 m_SoftShadowQuality: 1
m_RenderingLayersMask:
serializedVersion: 0
m_Bits: 1
m_ShadowRenderingLayersMask:
serializedVersion: 0
m_Bits: 1
m_Version: 4
m_LightLayerMask: 1
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
--- !u!1 &832575517 --- !u!1 &832575517
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -423,6 +504,112 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1682341399
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1682341402}
- component: {fileID: 1682341401}
- component: {fileID: 1682341400}
m_Layer: 0
m_Name: NetworkManager
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1682341400
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1682341399}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Transports.UTP.UnityTransport
m_ProtocolType: 0
m_UseWebSockets: 0
m_UseEncryption: 0
m_MaxPacketQueueSize: 128
m_MaxPayloadSize: 6144
m_HeartbeatTimeoutMS: 500
m_ConnectTimeoutMS: 1000
m_MaxConnectAttempts: 60
m_DisconnectTimeoutMS: 30000
ConnectionData:
Address: 127.0.0.1
Port: 7777
WebSocketPath: /
ServerListenAddress: 127.0.0.1
ClientBindPort: 0
DebugSimulator:
PacketDelayMS: 0
PacketJitterMS: 0
PacketDropRate: 0
--- !u!114 &1682341401
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1682341399}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkManager
NetworkManagerExpanded: 0
NetworkConfig:
ProtocolVersion: 0
NetworkTransport: {fileID: 1682341400}
PlayerPrefab: {fileID: 0}
Prefabs:
NetworkPrefabsLists:
- {fileID: 11400000, guid: 481ab1d7456efd044bc3e349aacd92ae, type: 2}
TickRate: 30
ClientConnectionBufferTimeout: 10
ConnectionApproval: 0
ConnectionData:
EnableTimeResync: 0
TimeResyncInterval: 30
EnsureNetworkVariableLengthSafety: 0
EnableSceneManagement: 1
ForceSamePrefabs: 1
RecycleNetworkIds: 1
NetworkIdRecycleDelay: 120
RpcHashSize: 0
LoadSceneTimeOut: 120
SpawnTimeout: 10
EnableNetworkLogs: 1
NetworkTopology: 0
UseCMBService: 0
AutoSpawnPlayerPrefabClientSide: 1
NetworkProfilingMetrics: 1
OldPrefabList: []
RunInBackground: 1
LogLevel: 1
--- !u!4 &1682341402
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1682341399}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1660057539 &9223372036854775807 --- !u!1660057539 &9223372036854775807
SceneRoots: SceneRoots:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -430,3 +617,5 @@ SceneRoots:
- {fileID: 330585546} - {fileID: 330585546}
- {fileID: 410087041} - {fileID: 410087041}
- {fileID: 832575519} - {fileID: 832575519}
- {fileID: 1682341402}
- {fileID: 239104690}

View file

@ -1,3 +0,0 @@
{
"name": "TD.Core"
}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: f62380d35dc31494f957f6b5559755f5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,152 @@
using Unity.Netcode;
using UnityEngine;
namespace TD.Gameplay
{
/// <summary>
/// GoldManager — canonical server-authoritative template for this project.
///
/// Every gameplay system (towers, enemies, waves, damage) should follow
/// the same three-beat pattern demonstrated here:
/// 1. State lives in NetworkVariables, which only the server can write.
/// 2. Clients REQUEST changes via [Rpc(SendTo.Server, ...)] methods.
/// They never change state directly.
/// 3. The server VALIDATES the request before applying it.
/// Never trust the client.
///
/// Cosmetic-only reactions (sounds, VFX, UI popups) can use
/// [Rpc(SendTo.ClientsAndHost)] or [Rpc(SendTo.NotServer)] to broadcast.
/// </summary>
public class GoldManager : NetworkBehaviour
{
private void Awake()
{
Debug.Log("[GoldManager] Awake ran!");
}
// --- Tunables (editable in Inspector) -----------------------------
[Tooltip("How much gold every player starts with when the game begins.")]
[SerializeField] private int startingGold = 100;
// --- Networked state ----------------------------------------------
// A NetworkVariable<T> automatically syncs from server to clients.
// readPerm = Everyone: all clients can read the current value.
// writePerm = Server: only the server can change it.
private readonly NetworkVariable<int> currentGold = new NetworkVariable<int>(
value: 0,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
// Public read-only accessor for other scripts (UI, tower placement).
public int CurrentGold => currentGold.Value;
// --- Lifecycle ----------------------------------------------------
/// <summary>
/// OnNetworkSpawn runs on every peer (server + all clients) when this
/// NetworkBehaviour becomes active on the network. Replaces Start()
/// for networked setup.
/// </summary>
public override void OnNetworkSpawn()
{
Debug.Log($"[GoldManager] OnNetworkSpawn ran. IsServer={IsServer}, IsClient={IsClient}, IsHost={IsHost}");
currentGold.OnValueChanged += HandleGoldChanged;
Debug.Log($"[GoldManager] Subscribed to OnValueChanged. Current value before init: {currentGold.Value}");
if (IsServer)
{
currentGold.Value = startingGold;
Debug.Log($"[GoldManager] Server initialized gold. Current value after set: {currentGold.Value}");
}
}
public override void OnNetworkDespawn()
{
// Always unsubscribe to avoid callback leaks.
currentGold.OnValueChanged -= HandleGoldChanged;
}
private void HandleGoldChanged(int previous, int current)
{
// Fires on every peer whenever the value syncs. Use Log here so
// you can see syncing in the Console during development.
Debug.Log($"[GoldManager] Gold changed: {previous} -> {current}");
}
// --- Public API (called by client-side code) ----------------------
/// <summary>
/// Client-side entry point for spending gold. Called by gameplay code
/// like TowerPlacement when the local player clicks "build tower."
///
/// The actual spending happens on the server via the Rpc.
/// </summary>
public void RequestSpendGold(int amount)
{
SpendGoldRpc(amount);
}
/// <summary>
/// Server-side entry point for awarding gold (wave clear, enemy kill).
/// Not Rpc-wrapped — this is called directly by server game logic in
/// response to server-authoritative events.
/// </summary>
public void AwardGold(int amount)
{
if (!IsServer)
{
Debug.LogError("[GoldManager] AwardGold called on a client! " +
"Only server code should call this directly.");
return;
}
if (amount <= 0) return;
currentGold.Value += amount;
}
// --- Server-side RPC ----------------------------------------------
// [Rpc(SendTo.Server, ...)] means: a client calls this locally, but
// NGO routes the call and executes the method on the server.
//
// RequireOwnership = false lets any client call it (correct for a
// shared GoldManager). For per-player NetworkObjects you'd usually
// leave the default ownership requirement in place.
//
// Naming convention: methods with [Rpc] attributes must end with "Rpc".
// The source generator relies on this suffix.
[Rpc(SendTo.Server, RequireOwnership = false)]
private void SpendGoldRpc(int amount, RpcParams rpcParams = default)
{
// This method body runs on the server only.
// Validate everything — do not trust the client.
// Validation 1: reject non-positive amounts. A negative amount
// would let a malicious client GAIN gold if we just subtracted.
if (amount <= 0)
{
Debug.LogWarning($"[GoldManager] Rejected spend of {amount} " +
$"from client {rpcParams.Receive.SenderClientId}: " +
$"amount must be positive.");
return;
}
// Validation 2: can't spend more than current balance.
if (currentGold.Value < amount)
{
Debug.LogWarning($"[GoldManager] Rejected spend of {amount} " +
$"from client {rpcParams.Receive.SenderClientId}: " +
$"insufficient funds (have {currentGold.Value}).");
return;
}
// Server applies the change. NetworkVariable syncs to clients
// automatically at the next network tick.
currentGold.Value -= amount;
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d44ebdd0b2fc4144c8f8a181a714b738

View file

@ -1,16 +0,0 @@
{
"name": "TD.Gameplay",
"rootNamespace": "",
"references": [
"GUID:f62380d35dc31494f957f6b5559755f5"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 027bbe8f3a58f024fa5c3fd977f0371b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,18 +0,0 @@
{
"name": "TD.Networking",
"rootNamespace": "",
"references": [
"GUID:f62380d35dc31494f957f6b5559755f5",
"GUID:027bbe8f3a58f024fa5c3fd977f0371b",
"GUID:1491147abca9d7d4bb7105af628b223e"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 824f8ff8ddfcf9a4da4b9c38a264d73e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,17 +0,0 @@
{
"name": "TD.UI",
"rootNamespace": "",
"references": [
"GUID:027bbe8f3a58f024fa5c3fd977f0371b",
"GUID:f62380d35dc31494f957f6b5559755f5"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: dccb24d2a739b9d46aa3fc7d50c9511e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -6,7 +6,7 @@ EditorBuildSettings:
serializedVersion: 2 serializedVersion: 2
m_Scenes: m_Scenes:
- enabled: 1 - enabled: 1
path: Assets/_Project/Scenes/SampleScene.unity path: Assets/_Project/Scenes/Levels/Main.unity
guid: 99c9720ab356a0642a771bea13969a05 guid: 99c9720ab356a0642a771bea13969a05
m_configObjects: m_configObjects:
com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3} com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3}

View file

@ -0,0 +1,121 @@
{
"templatePinStates": [],
"dependencyTypeInfos": [
{
"userAdded": false,
"type": "UnityEngine.AnimationClip",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.Animations.AnimatorController",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.AnimatorOverrideController",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.Audio.AudioMixerController",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.ComputeShader",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.Cubemap",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.GameObject",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.LightingDataAsset",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.LightingSettings",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Material",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.MonoScript",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.PhysicsMaterial",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.PhysicsMaterial2D",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Rendering.PostProcessing.PostProcessResources",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Rendering.VolumeProfile",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.SceneAsset",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.Shader",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.ShaderVariantCollection",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.Texture",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Texture2D",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Timeline.TimelineAsset",
"defaultInstantiationMode": 0
}
],
"defaultDependencyTypeInfo": {
"userAdded": false,
"type": "<default_scene_template_dependencies>",
"defaultInstantiationMode": 1
},
"newSceneOverride": 0
}