Added RocketAssemblyStation and ReadyRocket
This commit is contained in:
@@ -14,5 +14,4 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
itemId: fish
|
||||
displayName: Fish
|
||||
category: 0
|
||||
worldPrefab: {fileID: 6820144355512337958, guid: 04a6d9d9e28782a4a9f8f71ce4438fc0, type: 3}
|
||||
|
||||
@@ -15,3 +15,4 @@ MonoBehaviour:
|
||||
items:
|
||||
- {fileID: 11400000, guid: dc1f8087c2120614fa0ddcf92cde7f0d, type: 2}
|
||||
- {fileID: 11400000, guid: 28dfa90ac37ac884d84b4f3754b33546, type: 2}
|
||||
- {fileID: 11400000, guid: 5353380be6a58a041bc52d32971d1d9b, type: 2}
|
||||
|
||||
@@ -9,10 +9,9 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f101, type: 3}
|
||||
m_Name: MetalPlate
|
||||
m_EditorClassIdentifier:
|
||||
itemId: metal_plate
|
||||
displayName: Metal Plate
|
||||
category: 1
|
||||
worldPrefab: {fileID: 2888887303011377321, guid: 29519dc5d2cfae441b261f3ca68f2290, type: 3}
|
||||
|
||||
@@ -9,10 +9,9 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f101, type: 3}
|
||||
m_Name: MetalRod
|
||||
m_EditorClassIdentifier:
|
||||
itemId: metal_rod
|
||||
displayName: Metal Rod
|
||||
category: 1
|
||||
worldPrefab: {fileID: 6071798070859371582, guid: 5cf4d9e867c084847ba82c56bddc456b, type: 3}
|
||||
|
||||
@@ -9,11 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Back-Standart-Ready
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-back-standart-ready
|
||||
displayName: Rocket-Back-Standart-Ready
|
||||
category: 2
|
||||
isAssemblyReady: 1
|
||||
partTypeId: rocket-back-standard
|
||||
processingState: 1
|
||||
worldPrefab: {fileID: 5195529150077527847, guid: ea8e439364ce6aa4dab3586bae0edfed, type: 3}
|
||||
|
||||
@@ -9,11 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Base-Avia-Ready
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-base-avia-ready
|
||||
displayName: Rocket-Base-Avia-Ready
|
||||
category: 2
|
||||
isAssemblyReady: 1
|
||||
partTypeId: rocket-base-avia
|
||||
processingState: 1
|
||||
worldPrefab: {fileID: 1256934312238460503, guid: 9712e2459410a6d43b55ea9d3847e29a, type: 3}
|
||||
|
||||
@@ -9,11 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Base-Standart-Ready
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-base-standart-ready
|
||||
displayName: Rocket-Base-Standart-Ready
|
||||
category: 2
|
||||
isAssemblyReady: 1
|
||||
partTypeId: rocket-base-standard
|
||||
processingState: 1
|
||||
worldPrefab: {fileID: 875723161486981202, guid: 6e681ca5358fff648ba85ae9e494fa77, type: 3}
|
||||
|
||||
@@ -9,11 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Cone-Standart-Ready
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-cone-standart-ready
|
||||
displayName: Rocket-Cone-Standart-Ready
|
||||
category: 2
|
||||
isAssemblyReady: 1
|
||||
partTypeId: rocket-cone-standard
|
||||
processingState: 1
|
||||
worldPrefab: {fileID: 7151289907260619783, guid: a31204f69b185d54ab9e12b0a3a1e98b, type: 3}
|
||||
|
||||
@@ -9,10 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Back-Standart
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-back-standart
|
||||
displayName: Rocket-Back-Standart
|
||||
category: 2
|
||||
partTypeId: rocket-back-standard
|
||||
processingState: 0
|
||||
worldPrefab: {fileID: 8591287727625443167, guid: be54182e751ec4b48b7db90e8598dce1, type: 3}
|
||||
|
||||
@@ -9,10 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Base-Avia
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-base-avia
|
||||
displayName: Rocket-Base-Avia
|
||||
category: 2
|
||||
partTypeId: rocket-base-avia
|
||||
processingState: 0
|
||||
worldPrefab: {fileID: 1256934312238460503, guid: 9712e2459410a6d43b55ea9d3847e29a, type: 3}
|
||||
|
||||
@@ -9,10 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Base-Standart
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-base-standart
|
||||
displayName: Rocket-Base-Standart
|
||||
category: 2
|
||||
partTypeId: rocket-base-standard
|
||||
processingState: 0
|
||||
worldPrefab: {fileID: 875723161486981202, guid: 6e681ca5358fff648ba85ae9e494fa77, type: 3}
|
||||
|
||||
@@ -9,10 +9,11 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f102, type: 3}
|
||||
m_Name: Rocket-Cone-Standart
|
||||
m_EditorClassIdentifier:
|
||||
itemId: rocket-cone-standart
|
||||
displayName: Rocket-Cone-Standart
|
||||
category: 2
|
||||
partTypeId: rocket-cone-standard
|
||||
processingState: 0
|
||||
worldPrefab: {fileID: 7151289907260619783, guid: a31204f69b185d54ab9e12b0a3a1e98b, type: 3}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d20a47f4accbbc4a993a1373834d882
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f103, type: 3}
|
||||
m_Name: Ready-Rocket-Standart
|
||||
m_EditorClassIdentifier:
|
||||
itemId: full-rocket-standart
|
||||
displayName: Full-Rocket-Standart
|
||||
worldPrefab: {fileID: 7456648717704822871, guid: 746f04d9ba6c87d4bae8a09287c5d090, type: 3}
|
||||
rocketTypeId: standard
|
||||
criticalImpactSpeed: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5353380be6a58a041bc52d32971d1d9b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -9,10 +9,9 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f101, type: 3}
|
||||
m_Name: Scrap-1
|
||||
m_EditorClassIdentifier:
|
||||
itemId: scrap-1
|
||||
displayName: Scrap-1
|
||||
category: 0
|
||||
worldPrefab: {fileID: 9104915302393667013, guid: 6eee6df5dab550446b41008fa6fff89a, type: 3}
|
||||
|
||||
@@ -9,10 +9,9 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b0c79cebdb4201743be8612545ee180e, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f101, type: 3}
|
||||
m_Name: Scrap-2
|
||||
m_EditorClassIdentifier:
|
||||
itemId: scrap-2
|
||||
displayName: Scrap-2
|
||||
category: 0
|
||||
worldPrefab: {fileID: 3823145821987330810, guid: 8cc9c4d7d9e04d640bbd0616e7926670, type: 3}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2973313a334da044a84ce698504a5751
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 016d686fabe553e4e87d7531e66451ba
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 04f3334793c14ee791f78059d0c2854c, type: 3}
|
||||
m_Name: Ready-Rocket-Standart
|
||||
m_EditorClassIdentifier:
|
||||
recipeId: full-rocket-standart
|
||||
displayName: Full-Rocket-Standart
|
||||
requiredPartsBottomToTop:
|
||||
- {fileID: 11400000, guid: 203427fd9de0c514887bec7c046a6f69, type: 2}
|
||||
- {fileID: 11400000, guid: 218df70f83b5574428eb2159664b9b6e, type: 2}
|
||||
- {fileID: 11400000, guid: a97b695af4781b34fa8cab9a97ba9d9d, type: 2}
|
||||
outputItem: {fileID: 11400000, guid: 5353380be6a58a041bc52d32971d1d9b, type: 2}
|
||||
assemblyDuration: 5
|
||||
blueprintTitle:
|
||||
blueprintImage: {fileID: 21300000, guid: 10f35c691ba84cc43a31218838a402a2, type: 3}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cfa63174ab38cc40bb4aa962831fc25
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -21,16 +21,16 @@ public static class FurnacePipelinePrototypeSetup
|
||||
EnsureFolder(ItemPrefabFolder);
|
||||
EnsureFolder(WorkstationPrefabFolder);
|
||||
|
||||
ItemDefinition scrapDefinition = GetOrCreateAsset<ItemDefinition>(
|
||||
MaterialItemDefinition scrapDefinition = GetOrCreateAsset<MaterialItemDefinition>(
|
||||
$"{ItemDataFolder}/Scrap.asset");
|
||||
ItemDefinition plateDefinition = GetOrCreateAsset<ItemDefinition>(
|
||||
MaterialItemDefinition plateDefinition = GetOrCreateAsset<MaterialItemDefinition>(
|
||||
$"{ItemDataFolder}/MetalPlate.asset");
|
||||
ItemDefinition rodDefinition = GetOrCreateAsset<ItemDefinition>(
|
||||
MaterialItemDefinition rodDefinition = GetOrCreateAsset<MaterialItemDefinition>(
|
||||
$"{ItemDataFolder}/MetalRod.asset");
|
||||
|
||||
ConfigureItemDefinition(scrapDefinition, "scrap", "Scrap", ItemCategory.Scrap);
|
||||
ConfigureItemDefinition(plateDefinition, "metal_plate", "Metal Plate", ItemCategory.SmeltedMaterial);
|
||||
ConfigureItemDefinition(rodDefinition, "metal_rod", "Metal Rod", ItemCategory.SmeltedMaterial);
|
||||
ConfigureItemDefinition(scrapDefinition, "scrap", "Scrap");
|
||||
ConfigureItemDefinition(plateDefinition, "metal_plate", "Metal Plate");
|
||||
ConfigureItemDefinition(rodDefinition, "metal_rod", "Metal Rod");
|
||||
|
||||
GameObject scrapPrefab = CreateNetworkItemPrefab(
|
||||
$"{ItemPrefabFolder}/Scrap.prefab",
|
||||
@@ -195,12 +195,10 @@ public static class FurnacePipelinePrototypeSetup
|
||||
private static void ConfigureItemDefinition(
|
||||
ItemDefinition definition,
|
||||
string itemId,
|
||||
string displayName,
|
||||
ItemCategory category)
|
||||
string displayName)
|
||||
{
|
||||
SetString(definition, "itemId", itemId);
|
||||
SetString(definition, "displayName", displayName);
|
||||
SetInt(definition, "category", (int)category);
|
||||
}
|
||||
|
||||
private static void ConfigureRecipe(
|
||||
|
||||
@@ -0,0 +1,752 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Unity.Netcode;
|
||||
using Unity.Netcode.Components;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public enum ItemCreationDefinitionKind
|
||||
{
|
||||
Generic,
|
||||
Material,
|
||||
Part,
|
||||
Rocket,
|
||||
Usable
|
||||
}
|
||||
|
||||
public enum ItemCreationMovementKind
|
||||
{
|
||||
Carry,
|
||||
Drag,
|
||||
Hand,
|
||||
Static
|
||||
}
|
||||
|
||||
public sealed class ItemCreationWindow : EditorWindow
|
||||
{
|
||||
private const string DefaultDefinitionFolder = "Assets/CONTENT/Game/Data/Items";
|
||||
private const string DefaultPrefabFolder = "Assets/CONTENT/Game/Prefabs/Items";
|
||||
private const string DefaultCatalogPath =
|
||||
"Assets/CONTENT/Game/Data/Items/ItemCatalog.asset";
|
||||
private const string DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset";
|
||||
|
||||
[SerializeField] private string assetName = "NewItem";
|
||||
[SerializeField] private string itemId = "new-item";
|
||||
[SerializeField] private string displayName = "New Item";
|
||||
[SerializeField] private ItemDefinition existingDefinition;
|
||||
[SerializeField] private ItemCreationDefinitionKind definitionKind;
|
||||
[SerializeField] private ItemCreationMovementKind movementKind =
|
||||
ItemCreationMovementKind.Carry;
|
||||
|
||||
[SerializeField] private GameObject existingWorldPrefab;
|
||||
[SerializeField] private GameObject visualPrefab;
|
||||
[SerializeField] private string definitionFolder = DefaultDefinitionFolder;
|
||||
[SerializeField] private string prefabFolder = DefaultPrefabFolder;
|
||||
|
||||
[SerializeField] private string partTypeId = "part";
|
||||
[SerializeField] private PartProcessingState partProcessingState;
|
||||
|
||||
[SerializeField] private string rocketTypeId = "rocket";
|
||||
[SerializeField] private float criticalImpactSpeed = 7f;
|
||||
|
||||
[SerializeField] private float dragWeight = 35f;
|
||||
[SerializeField] private MonoScript handItemBehaviour;
|
||||
|
||||
[SerializeField] private bool addFurnaceMetalSource;
|
||||
[SerializeField] private float furnaceMetalAmount = 25f;
|
||||
|
||||
[SerializeField] private ItemCatalog catalog;
|
||||
[SerializeField] private NetworkPrefabsList networkPrefabsList;
|
||||
|
||||
[MenuItem("FORT-BO/Items/Item Creator")]
|
||||
private static void Open()
|
||||
{
|
||||
ItemCreationWindow window = GetWindow<ItemCreationWindow>("Item Creator");
|
||||
window.minSize = new Vector2(470f, 650f);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (catalog == null)
|
||||
catalog = AssetDatabase.LoadAssetAtPath<ItemCatalog>(DefaultCatalogPath);
|
||||
|
||||
if (networkPrefabsList == null)
|
||||
{
|
||||
networkPrefabsList =
|
||||
AssetDatabase.LoadAssetAtPath<NetworkPrefabsList>(DefaultNetworkPrefabsPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.LabelField("Identity", EditorStyles.boldLabel);
|
||||
existingDefinition = (ItemDefinition)EditorGUILayout.ObjectField(
|
||||
"Existing Definition",
|
||||
existingDefinition,
|
||||
typeof(ItemDefinition),
|
||||
false);
|
||||
using (new EditorGUI.DisabledScope(existingDefinition != null))
|
||||
{
|
||||
assetName = EditorGUILayout.TextField("Asset Name", assetName);
|
||||
itemId = EditorGUILayout.TextField("Item Id", itemId);
|
||||
displayName = EditorGUILayout.TextField("Display Name", displayName);
|
||||
definitionKind = (ItemCreationDefinitionKind)EditorGUILayout.EnumPopup(
|
||||
"Definition Kind",
|
||||
definitionKind);
|
||||
}
|
||||
|
||||
if (existingDefinition != null)
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
"Definition Kind",
|
||||
GetDefinitionKind(existingDefinition).ToString());
|
||||
}
|
||||
|
||||
movementKind = (ItemCreationMovementKind)EditorGUILayout.EnumPopup(
|
||||
"Movement Kind",
|
||||
movementKind);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Prefab", EditorStyles.boldLabel);
|
||||
existingWorldPrefab = (GameObject)EditorGUILayout.ObjectField(
|
||||
"Existing World Prefab",
|
||||
existingWorldPrefab,
|
||||
typeof(GameObject),
|
||||
false);
|
||||
|
||||
bool hasWorldPrefab = ResolveExistingWorldPrefab() != null;
|
||||
using (new EditorGUI.DisabledScope(hasWorldPrefab))
|
||||
{
|
||||
visualPrefab = (GameObject)EditorGUILayout.ObjectField(
|
||||
"Visual Prefab",
|
||||
visualPrefab,
|
||||
typeof(GameObject),
|
||||
false);
|
||||
prefabFolder = EditorGUILayout.TextField("Prefab Folder", prefabFolder);
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
hasWorldPrefab
|
||||
? "The existing prefab is configured in place. Existing components are preserved."
|
||||
: "A new network prefab is created. Visual Prefab is added as a child and may be empty.",
|
||||
MessageType.Info);
|
||||
|
||||
if (existingDefinition == null)
|
||||
DrawDefinitionSpecificFields();
|
||||
DrawMovementSpecificFields();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Optional capabilities", EditorStyles.boldLabel);
|
||||
addFurnaceMetalSource = EditorGUILayout.Toggle(
|
||||
"Furnace Metal Source",
|
||||
addFurnaceMetalSource);
|
||||
if (addFurnaceMetalSource)
|
||||
{
|
||||
furnaceMetalAmount = EditorGUILayout.FloatField(
|
||||
"Metal Amount",
|
||||
furnaceMetalAmount);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Registration", EditorStyles.boldLabel);
|
||||
using (new EditorGUI.DisabledScope(existingDefinition != null))
|
||||
{
|
||||
definitionFolder = EditorGUILayout.TextField(
|
||||
"Definition Folder",
|
||||
definitionFolder);
|
||||
}
|
||||
catalog = (ItemCatalog)EditorGUILayout.ObjectField(
|
||||
"Item Catalog",
|
||||
catalog,
|
||||
typeof(ItemCatalog),
|
||||
false);
|
||||
networkPrefabsList = (NetworkPrefabsList)EditorGUILayout.ObjectField(
|
||||
"Network Prefabs List",
|
||||
networkPrefabsList,
|
||||
typeof(NetworkPrefabsList),
|
||||
false);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.HelpBox(
|
||||
"Definition Kind describes what the item is. Movement Kind describes how its prefab " +
|
||||
"is handled. They are intentionally independent.",
|
||||
MessageType.None);
|
||||
|
||||
using (new EditorGUI.DisabledScope(EditorApplication.isPlayingOrWillChangePlaymode))
|
||||
{
|
||||
string buttonLabel = existingDefinition == null
|
||||
? "Create and Register Item"
|
||||
: "Configure and Register Item";
|
||||
if (GUILayout.Button(buttonLabel, GUILayout.Height(34f)))
|
||||
CreateAndRegisterItem();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDefinitionSpecificFields()
|
||||
{
|
||||
switch (definitionKind)
|
||||
{
|
||||
case ItemCreationDefinitionKind.Part:
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Part", EditorStyles.boldLabel);
|
||||
partTypeId = EditorGUILayout.TextField("Part Type Id", partTypeId);
|
||||
partProcessingState = (PartProcessingState)EditorGUILayout.EnumPopup(
|
||||
"Processing State",
|
||||
partProcessingState);
|
||||
break;
|
||||
|
||||
case ItemCreationDefinitionKind.Rocket:
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Rocket", EditorStyles.boldLabel);
|
||||
rocketTypeId = EditorGUILayout.TextField("Rocket Type Id", rocketTypeId);
|
||||
criticalImpactSpeed = EditorGUILayout.FloatField(
|
||||
"Critical Impact Speed",
|
||||
criticalImpactSpeed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMovementSpecificFields()
|
||||
{
|
||||
switch (movementKind)
|
||||
{
|
||||
case ItemCreationMovementKind.Drag:
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Drag", EditorStyles.boldLabel);
|
||||
dragWeight = EditorGUILayout.FloatField("Weight", dragWeight);
|
||||
break;
|
||||
|
||||
case ItemCreationMovementKind.Hand:
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Hand", EditorStyles.boldLabel);
|
||||
handItemBehaviour = (MonoScript)EditorGUILayout.ObjectField(
|
||||
"Custom Hand Interactable",
|
||||
handItemBehaviour,
|
||||
typeof(MonoScript),
|
||||
false);
|
||||
EditorGUILayout.HelpBox(
|
||||
"Leave empty to use NetworkUsableHandItemInteractable. Add a separate " +
|
||||
"IHandItemUseAction component for the actual effect.",
|
||||
MessageType.Info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateAndRegisterItem()
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateInput();
|
||||
ItemDefinition definition;
|
||||
string definitionPath;
|
||||
string safeAssetName;
|
||||
if (existingDefinition != null)
|
||||
{
|
||||
definition = existingDefinition;
|
||||
definitionPath = AssetDatabase.GetAssetPath(definition);
|
||||
safeAssetName = SanitizeFileName(definition.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureFolder(definitionFolder);
|
||||
safeAssetName = SanitizeFileName(assetName.Trim());
|
||||
definitionPath = $"{definitionFolder}/{safeAssetName}.asset";
|
||||
if (AssetDatabase.LoadMainAssetAtPath(definitionPath) != null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Asset already exists: {definitionPath}");
|
||||
}
|
||||
|
||||
EnsureUniqueItemId(itemId);
|
||||
definition = CreateDefinition(definitionPath);
|
||||
}
|
||||
|
||||
GameObject worldPrefab = ConfigureOrCreateWorldPrefab(safeAssetName, definition);
|
||||
|
||||
SetObjectReference(
|
||||
definition,
|
||||
"worldPrefab",
|
||||
worldPrefab.GetComponent<NetworkObject>());
|
||||
RegisterInCatalog(definition);
|
||||
RegisterNetworkPrefab(worldPrefab);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Selection.activeObject = definition;
|
||||
EditorGUIUtility.PingObject(worldPrefab);
|
||||
Debug.Log(
|
||||
$"Created '{definition.DisplayName}': {definitionPath} + " +
|
||||
$"{AssetDatabase.GetAssetPath(worldPrefab)}.",
|
||||
definition);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
EditorUtility.DisplayDialog("Item creation failed", exception.Message, "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateInput()
|
||||
{
|
||||
if (existingDefinition == null && string.IsNullOrWhiteSpace(assetName))
|
||||
throw new InvalidOperationException("Asset Name is required.");
|
||||
|
||||
if (existingDefinition == null && string.IsNullOrWhiteSpace(itemId))
|
||||
throw new InvalidOperationException("Item Id is required.");
|
||||
|
||||
if (existingDefinition == null &&
|
||||
(string.IsNullOrWhiteSpace(definitionFolder) ||
|
||||
!definitionFolder.StartsWith("Assets", StringComparison.Ordinal)))
|
||||
{
|
||||
throw new InvalidOperationException("Definition Folder must be inside Assets.");
|
||||
}
|
||||
|
||||
GameObject resolvedWorldPrefab = ResolveExistingWorldPrefab();
|
||||
if (resolvedWorldPrefab != null &&
|
||||
!PrefabUtility.IsPartOfPrefabAsset(resolvedWorldPrefab))
|
||||
{
|
||||
throw new InvalidOperationException("Existing World Prefab must be a prefab asset.");
|
||||
}
|
||||
|
||||
if (resolvedWorldPrefab != null)
|
||||
ValidateExistingPrefabMovement(resolvedWorldPrefab);
|
||||
|
||||
if (ResolveExistingWorldPrefab() == null &&
|
||||
(string.IsNullOrWhiteSpace(prefabFolder) ||
|
||||
!prefabFolder.StartsWith("Assets", StringComparison.Ordinal)))
|
||||
{
|
||||
throw new InvalidOperationException("Prefab Folder must be inside Assets.");
|
||||
}
|
||||
|
||||
if (movementKind == ItemCreationMovementKind.Hand)
|
||||
ResolveHandItemBehaviourType();
|
||||
|
||||
if (EffectiveDefinitionKind == ItemCreationDefinitionKind.Rocket &&
|
||||
movementKind is not ItemCreationMovementKind.Carry and
|
||||
not ItemCreationMovementKind.Drag)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A rocket assembled by the workstation must use Carry or Drag movement.");
|
||||
}
|
||||
|
||||
if (ResolveExistingWorldPrefab() == null && visualPrefab != null &&
|
||||
(visualPrefab.GetComponentInChildren<NetworkObject>(true) != null ||
|
||||
visualPrefab.GetComponentInChildren<Rigidbody>(true) != null))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Visual Prefab must contain visuals/colliders only, without NetworkObject or Rigidbody.");
|
||||
}
|
||||
|
||||
if (existingDefinition == null && definitionKind == ItemCreationDefinitionKind.Part &&
|
||||
string.IsNullOrWhiteSpace(partTypeId))
|
||||
{
|
||||
throw new InvalidOperationException("Part Type Id is required for a part.");
|
||||
}
|
||||
|
||||
if (existingDefinition == null && definitionKind == ItemCreationDefinitionKind.Rocket &&
|
||||
string.IsNullOrWhiteSpace(rocketTypeId))
|
||||
{
|
||||
throw new InvalidOperationException("Rocket Type Id is required for a rocket.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateExistingPrefabMovement(GameObject prefabAsset)
|
||||
{
|
||||
string prefabPath = AssetDatabase.GetAssetPath(prefabAsset);
|
||||
GameObject root = PrefabUtility.LoadPrefabContents(prefabPath);
|
||||
try
|
||||
{
|
||||
EnsureSingleMovementBehaviour(root);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject ResolveExistingWorldPrefab()
|
||||
{
|
||||
if (existingWorldPrefab != null)
|
||||
return existingWorldPrefab;
|
||||
|
||||
return existingDefinition != null && existingDefinition.WorldPrefab != null
|
||||
? existingDefinition.WorldPrefab.gameObject
|
||||
: null;
|
||||
}
|
||||
|
||||
private ItemCreationDefinitionKind EffectiveDefinitionKind =>
|
||||
existingDefinition != null ? GetDefinitionKind(existingDefinition) : definitionKind;
|
||||
|
||||
private static ItemCreationDefinitionKind GetDefinitionKind(ItemDefinition definition) =>
|
||||
definition switch
|
||||
{
|
||||
MaterialItemDefinition => ItemCreationDefinitionKind.Material,
|
||||
PartItemDefinition => ItemCreationDefinitionKind.Part,
|
||||
RocketItemDefinition => ItemCreationDefinitionKind.Rocket,
|
||||
UsableItemDefinition => ItemCreationDefinitionKind.Usable,
|
||||
_ => ItemCreationDefinitionKind.Generic
|
||||
};
|
||||
|
||||
private ItemDefinition CreateDefinition(string path)
|
||||
{
|
||||
ItemDefinition definition = definitionKind switch
|
||||
{
|
||||
ItemCreationDefinitionKind.Material =>
|
||||
CreateInstance<MaterialItemDefinition>(),
|
||||
ItemCreationDefinitionKind.Part =>
|
||||
CreateInstance<PartItemDefinition>(),
|
||||
ItemCreationDefinitionKind.Rocket =>
|
||||
CreateInstance<RocketItemDefinition>(),
|
||||
ItemCreationDefinitionKind.Usable =>
|
||||
CreateInstance<UsableItemDefinition>(),
|
||||
_ => CreateInstance<ItemDefinition>()
|
||||
};
|
||||
|
||||
definition.name = assetName.Trim();
|
||||
AssetDatabase.CreateAsset(definition, path);
|
||||
SetString(definition, "itemId", NormalizeId(itemId));
|
||||
SetString(definition, "displayName", displayName?.Trim());
|
||||
|
||||
if (definition is PartItemDefinition)
|
||||
{
|
||||
SetString(definition, "partTypeId", NormalizeId(partTypeId));
|
||||
SetInt(definition, "processingState", (int)partProcessingState);
|
||||
}
|
||||
else if (definition is RocketItemDefinition)
|
||||
{
|
||||
SetString(definition, "rocketTypeId", NormalizeId(rocketTypeId));
|
||||
SetFloat(definition, "criticalImpactSpeed", Mathf.Max(0.1f, criticalImpactSpeed));
|
||||
}
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
private GameObject ConfigureOrCreateWorldPrefab(
|
||||
string safeAssetName,
|
||||
ItemDefinition definition)
|
||||
{
|
||||
string prefabPath;
|
||||
GameObject prefabAsset = ResolveExistingWorldPrefab();
|
||||
if (prefabAsset != null)
|
||||
{
|
||||
prefabPath = AssetDatabase.GetAssetPath(prefabAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureFolder(prefabFolder);
|
||||
prefabPath = $"{prefabFolder}/{safeAssetName}.prefab";
|
||||
if (AssetDatabase.LoadMainAssetAtPath(prefabPath) != null)
|
||||
throw new InvalidOperationException($"Prefab already exists: {prefabPath}");
|
||||
|
||||
CreateInitialPrefab(prefabPath, safeAssetName);
|
||||
}
|
||||
|
||||
GameObject root = PrefabUtility.LoadPrefabContents(prefabPath);
|
||||
try
|
||||
{
|
||||
ConfigurePrefabRoot(root, definition);
|
||||
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
}
|
||||
|
||||
return AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
|
||||
}
|
||||
|
||||
private void CreateInitialPrefab(string prefabPath, string safeAssetName)
|
||||
{
|
||||
GameObject root = new(safeAssetName);
|
||||
try
|
||||
{
|
||||
if (visualPrefab != null)
|
||||
{
|
||||
GameObject visual = PrefabUtility.InstantiatePrefab(visualPrefab) as GameObject;
|
||||
if (visual == null)
|
||||
visual = Instantiate(visualPrefab);
|
||||
|
||||
visual.name = "Visual";
|
||||
visual.transform.SetParent(root.transform, false);
|
||||
}
|
||||
|
||||
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DestroyImmediate(root);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigurePrefabRoot(GameObject root, ItemDefinition definition)
|
||||
{
|
||||
EnsureSingleMovementBehaviour(root);
|
||||
|
||||
NetworkObject networkObject = GetOrAdd<NetworkObject>(root);
|
||||
NetworkTransform networkTransform = GetOrAdd<NetworkTransform>(root);
|
||||
networkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Server;
|
||||
networkTransform.UseUnreliableDeltas = true;
|
||||
|
||||
NetworkItem networkItem = GetOrAdd<NetworkItem>(root);
|
||||
SetObjectReference(networkItem, "definition", definition);
|
||||
|
||||
bool needsPhysics = movementKind != ItemCreationMovementKind.Static ||
|
||||
EffectiveDefinitionKind == ItemCreationDefinitionKind.Rocket;
|
||||
Rigidbody body = needsPhysics ? GetOrAdd<Rigidbody>(root) : root.GetComponent<Rigidbody>();
|
||||
if (body != null)
|
||||
{
|
||||
body.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
body.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||
}
|
||||
|
||||
if (needsPhysics)
|
||||
{
|
||||
NetworkRigidbody networkRigidbody = GetOrAdd<NetworkRigidbody>(root);
|
||||
networkRigidbody.UseRigidBodyForMotion = true;
|
||||
EnsureCollider(root);
|
||||
}
|
||||
|
||||
switch (movementKind)
|
||||
{
|
||||
case ItemCreationMovementKind.Carry:
|
||||
GetOrAdd<NetworkCarryableInteractable>(root);
|
||||
if (body != null)
|
||||
body.mass = 1f;
|
||||
break;
|
||||
|
||||
case ItemCreationMovementKind.Drag:
|
||||
NetworkDraggableInteractable draggable =
|
||||
GetOrAdd<NetworkDraggableInteractable>(root);
|
||||
float safeWeight = Mathf.Max(0.1f, dragWeight);
|
||||
SetFloat(draggable, "weight", safeWeight);
|
||||
if (body != null)
|
||||
body.mass = safeWeight;
|
||||
break;
|
||||
|
||||
case ItemCreationMovementKind.Hand:
|
||||
Type handBehaviourType = ResolveHandItemBehaviourType();
|
||||
if (root.GetComponent(handBehaviourType) == null)
|
||||
root.AddComponent(handBehaviourType);
|
||||
if (body != null)
|
||||
body.mass = 0.5f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (EffectiveDefinitionKind == ItemCreationDefinitionKind.Rocket)
|
||||
{
|
||||
GetOrAdd<NetworkRocketItem>(root);
|
||||
GetOrAdd<RocketItemView>(root);
|
||||
}
|
||||
|
||||
if (addFurnaceMetalSource)
|
||||
{
|
||||
FurnaceMetalSource source = GetOrAdd<FurnaceMetalSource>(root);
|
||||
SetFloat(source, "metalAmount", Mathf.Max(0.01f, furnaceMetalAmount));
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(networkObject);
|
||||
}
|
||||
|
||||
private void EnsureSingleMovementBehaviour(GameObject root)
|
||||
{
|
||||
bool hasCarry = root.GetComponent<NetworkCarryableInteractable>() != null;
|
||||
bool hasDrag = root.GetComponent<NetworkDraggableInteractable>() != null;
|
||||
bool hasHand = root.GetComponent<NetworkHandItemInteractable>() != null;
|
||||
int existingCount = (hasCarry ? 1 : 0) + (hasDrag ? 1 : 0) + (hasHand ? 1 : 0);
|
||||
|
||||
if (existingCount > 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The prefab already contains more than one movement behaviour.");
|
||||
}
|
||||
|
||||
bool compatible = existingCount == 0 ||
|
||||
(movementKind == ItemCreationMovementKind.Carry && hasCarry) ||
|
||||
(movementKind == ItemCreationMovementKind.Drag && hasDrag) ||
|
||||
(movementKind == ItemCreationMovementKind.Hand && hasHand);
|
||||
if (!compatible)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The prefab already has a different movement behaviour. Remove or change it manually first.");
|
||||
}
|
||||
|
||||
if (movementKind == ItemCreationMovementKind.Hand && hasHand)
|
||||
{
|
||||
NetworkHandItemInteractable existingHand =
|
||||
root.GetComponent<NetworkHandItemInteractable>();
|
||||
if (existingHand.GetType() != ResolveHandItemBehaviourType())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"The prefab already uses {existingHand.GetType().Name}, but another hand " +
|
||||
"behaviour was selected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Type ResolveHandItemBehaviourType()
|
||||
{
|
||||
if (handItemBehaviour == null)
|
||||
return typeof(NetworkUsableHandItemInteractable);
|
||||
|
||||
Type type = handItemBehaviour.GetClass();
|
||||
if (type == null || type.IsAbstract ||
|
||||
!typeof(NetworkHandItemInteractable).IsAssignableFrom(type))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Use Behaviour must be a non-abstract NetworkHandItemInteractable script.");
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private void EnsureUniqueItemId(string candidateId)
|
||||
{
|
||||
string normalizedId = NormalizeId(candidateId);
|
||||
foreach (string guid in AssetDatabase.FindAssets("t:ItemDefinition"))
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
ItemDefinition definition = AssetDatabase.LoadAssetAtPath<ItemDefinition>(path);
|
||||
if (definition != null && definition.ItemId == normalizedId)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Item Id '{normalizedId}' is already used by {path}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterInCatalog(ItemDefinition definition)
|
||||
{
|
||||
if (catalog == null)
|
||||
return;
|
||||
|
||||
SerializedObject serializedCatalog = new(catalog);
|
||||
SerializedProperty items = serializedCatalog.FindProperty("items");
|
||||
for (int index = 0; index < items.arraySize; index++)
|
||||
{
|
||||
if (items.GetArrayElementAtIndex(index).objectReferenceValue == definition)
|
||||
return;
|
||||
}
|
||||
|
||||
int newIndex = items.arraySize;
|
||||
items.InsertArrayElementAtIndex(newIndex);
|
||||
items.GetArrayElementAtIndex(newIndex).objectReferenceValue = definition;
|
||||
serializedCatalog.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(catalog);
|
||||
}
|
||||
|
||||
private void RegisterNetworkPrefab(GameObject prefab)
|
||||
{
|
||||
if (networkPrefabsList == null || networkPrefabsList.Contains(prefab))
|
||||
return;
|
||||
|
||||
networkPrefabsList.Add(new NetworkPrefab { Prefab = prefab });
|
||||
EditorUtility.SetDirty(networkPrefabsList);
|
||||
}
|
||||
|
||||
private static T GetOrAdd<T>(GameObject gameObject) where T : Component =>
|
||||
gameObject.TryGetComponent(out T component) ? component : gameObject.AddComponent<T>();
|
||||
|
||||
private static void EnsureCollider(GameObject root)
|
||||
{
|
||||
if (root.GetComponentInChildren<Collider>(true) != null)
|
||||
return;
|
||||
|
||||
BoxCollider collider = root.AddComponent<BoxCollider>();
|
||||
if (!TryCalculateLocalRendererBounds(root, out Bounds bounds))
|
||||
return;
|
||||
|
||||
collider.center = bounds.center;
|
||||
collider.size = bounds.size;
|
||||
}
|
||||
|
||||
private static bool TryCalculateLocalRendererBounds(GameObject root, out Bounds bounds)
|
||||
{
|
||||
Renderer[] renderers = root.GetComponentsInChildren<Renderer>(true);
|
||||
if (renderers.Length == 0)
|
||||
{
|
||||
bounds = new Bounds(Vector3.zero, Vector3.one);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool initialized = false;
|
||||
bounds = default;
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
Bounds worldBounds = renderer.bounds;
|
||||
Vector3 center = root.transform.InverseTransformPoint(worldBounds.center);
|
||||
Vector3 extents = worldBounds.extents;
|
||||
for (int x = -1; x <= 1; x += 2)
|
||||
for (int y = -1; y <= 1; y += 2)
|
||||
for (int z = -1; z <= 1; z += 2)
|
||||
{
|
||||
Vector3 worldCorner = worldBounds.center +
|
||||
Vector3.Scale(extents, new Vector3(x, y, z));
|
||||
Vector3 localCorner = root.transform.InverseTransformPoint(worldCorner);
|
||||
if (!initialized)
|
||||
{
|
||||
bounds = new Bounds(center, Vector3.zero);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
bounds.Encapsulate(localCorner);
|
||||
}
|
||||
}
|
||||
|
||||
return initialized;
|
||||
}
|
||||
|
||||
private static void EnsureFolder(string folderPath)
|
||||
{
|
||||
string normalized = folderPath.Replace('\\', '/').TrimEnd('/');
|
||||
if (AssetDatabase.IsValidFolder(normalized))
|
||||
return;
|
||||
|
||||
string parent = Path.GetDirectoryName(normalized)?.Replace('\\', '/');
|
||||
string folderName = Path.GetFileName(normalized);
|
||||
if (string.IsNullOrEmpty(parent) || string.IsNullOrEmpty(folderName))
|
||||
throw new InvalidOperationException($"Invalid folder path: {folderPath}");
|
||||
|
||||
EnsureFolder(parent);
|
||||
AssetDatabase.CreateFolder(parent, folderName);
|
||||
}
|
||||
|
||||
private static string NormalizeId(string value) =>
|
||||
value.Trim().ToLowerInvariant().Replace(' ', '-');
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
{
|
||||
foreach (char invalidCharacter in Path.GetInvalidFileNameChars())
|
||||
value = value.Replace(invalidCharacter, '-');
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void SetString(UnityEngine.Object target, string propertyName, string value) =>
|
||||
Edit(target, propertyName, property => property.stringValue = value);
|
||||
|
||||
private static void SetFloat(UnityEngine.Object target, string propertyName, float value) =>
|
||||
Edit(target, propertyName, property => property.floatValue = value);
|
||||
|
||||
private static void SetInt(UnityEngine.Object target, string propertyName, int value) =>
|
||||
Edit(target, propertyName, property => property.intValue = value);
|
||||
|
||||
private static void SetObjectReference(
|
||||
UnityEngine.Object target,
|
||||
string propertyName,
|
||||
UnityEngine.Object value) =>
|
||||
Edit(target, propertyName, property => property.objectReferenceValue = value);
|
||||
|
||||
private static void Edit(
|
||||
UnityEngine.Object target,
|
||||
string propertyName,
|
||||
Action<SerializedProperty> edit)
|
||||
{
|
||||
SerializedObject serializedObject = new(target);
|
||||
SerializedProperty property = serializedObject.FindProperty(propertyName);
|
||||
if (property == null)
|
||||
throw new MissingFieldException(target.GetType().Name, propertyName);
|
||||
|
||||
edit(property);
|
||||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f120
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,357 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &2376631388558761094
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1632953841127884940}
|
||||
- component: {fileID: 7456648717704822871}
|
||||
- component: {fileID: 7292341886911989191}
|
||||
- component: {fileID: 8891747045554379344}
|
||||
- component: {fileID: 3133876013448598132}
|
||||
- component: {fileID: 193117245170608707}
|
||||
- component: {fileID: 4269639932524941515}
|
||||
- component: {fileID: 6493104616022895181}
|
||||
- component: {fileID: 3635736001397410476}
|
||||
m_Layer: 0
|
||||
m_Name: Ready-Rocket-Standart
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1632953841127884940
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
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:
|
||||
- {fileID: 1646663282415760833}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &7456648717704822871
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
GlobalObjectIdHash: 3332465624
|
||||
InScenePlacedSourceGlobalObjectIdHash: 0
|
||||
DeferredDespawnTick: 0
|
||||
Ownership: 1
|
||||
AlwaysReplicateAsRoot: 0
|
||||
SynchronizeTransform: 1
|
||||
m_InScenePlaced: 0
|
||||
ActiveSceneSynchronization: 0
|
||||
SceneMigrationSynchronization: 1
|
||||
SpawnWithObservers: 1
|
||||
DontDestroyWithOwner: 0
|
||||
AutoObjectParentSync: 1
|
||||
SyncOwnerTransformWhenParented: 1
|
||||
AllowOwnerToParent: 0
|
||||
--- !u!114 &7292341886911989191
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
NetworkTransformExpanded: 0
|
||||
AutoOwnerAuthorityTickOffset: 1
|
||||
PositionInterpolationType: 0
|
||||
RotationInterpolationType: 0
|
||||
ScaleInterpolationType: 0
|
||||
PositionLerpSmoothing: 1
|
||||
PositionMaxInterpolationTime: 0.1
|
||||
RotationLerpSmoothing: 1
|
||||
RotationMaxInterpolationTime: 0.1
|
||||
ScaleLerpSmoothing: 1
|
||||
ScaleMaxInterpolationTime: 0.1
|
||||
AuthorityMode: 0
|
||||
TickSyncChildren: 0
|
||||
UseUnreliableDeltas: 1
|
||||
SyncPositionX: 1
|
||||
SyncPositionY: 1
|
||||
SyncPositionZ: 1
|
||||
SyncRotAngleX: 1
|
||||
SyncRotAngleY: 1
|
||||
SyncRotAngleZ: 1
|
||||
SyncScaleX: 1
|
||||
SyncScaleY: 1
|
||||
SyncScaleZ: 1
|
||||
PositionThreshold: 0.001
|
||||
RotAngleThreshold: 0.01
|
||||
ScaleThreshold: 0.01
|
||||
UseQuaternionSynchronization: 0
|
||||
UseQuaternionCompression: 0
|
||||
UseHalfFloatPrecision: 0
|
||||
InLocalSpace: 0
|
||||
SwitchTransformSpaceWhenParented: 0
|
||||
Interpolate: 1
|
||||
SlerpPosition: 0
|
||||
--- !u!114 &8891747045554379344
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 323282b7a87fc4344bd33250e88822e8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
definition: {fileID: 11400000, guid: 5353380be6a58a041bc52d32971d1d9b, type: 2}
|
||||
--- !u!54 &3133876013448598132
|
||||
Rigidbody:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
serializedVersion: 4
|
||||
m_Mass: 40
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ImplicitCom: 1
|
||||
m_ImplicitTensor: 1
|
||||
m_UseGravity: 1
|
||||
m_IsKinematic: 0
|
||||
m_Interpolate: 1
|
||||
m_Constraints: 0
|
||||
m_CollisionDetection: 1
|
||||
--- !u!114 &193117245170608707
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f6c0be61502bb534f922ebb746851216, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
NetworkRigidbodyBaseExpanded: 0
|
||||
UseRigidBodyForMotion: 1
|
||||
AutoUpdateKinematicState: 1
|
||||
AutoSetKinematicOnDespawn: 1
|
||||
--- !u!114 &4269639932524941515
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: e5e737931b3bc22468ea0fe857062359, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
interactionLabel: Interact
|
||||
maximumInteractionDistance: 3
|
||||
weight: 40
|
||||
pullStrengthPerPlayer: 50
|
||||
maximumDraggers: 2
|
||||
dragOffset: {x: 0, y: 1, z: 1.8}
|
||||
maximumDragSpeed: 3
|
||||
minimumSpeedMultiplier: 0.35
|
||||
maximumDraggerDistance: 4.5
|
||||
positionSmoothTime: 0.12
|
||||
rotationFollowSpeed: 8
|
||||
rotationOffset: {x: 0, y: 0, z: 0}
|
||||
movementMode: 0
|
||||
groundLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
groundContactPoint: {fileID: 0}
|
||||
groundProbeRadius: 0.2
|
||||
groundProbeHeight: 1.25
|
||||
maximumGroundDrop: 2
|
||||
maximumGroundAngle: 55
|
||||
groundClearance: 0.02
|
||||
maximumGroundVerticalSpeed: 6
|
||||
alignToGround: 1
|
||||
groundAlignmentSpeed: 10
|
||||
physicsBody: {fileID: 3133876013448598132}
|
||||
objectColliders:
|
||||
- {fileID: 5222869138606293483}
|
||||
- {fileID: 6894540284203297686}
|
||||
- {fileID: 1623627698583667245}
|
||||
- {fileID: 7077397027048864733}
|
||||
collisionRefreshInterval: 0.25
|
||||
--- !u!114 &6493104616022895181
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f111, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
networkItem: {fileID: 8891747045554379344}
|
||||
physicsBody: {fileID: 3133876013448598132}
|
||||
--- !u!114 &3635736001397410476
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2376631388558761094}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d1e6d4d81ab34d2e9ee3a1bfa201f112, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
rocket: {fileID: 6493104616022895181}
|
||||
onNormalQualityApplied:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onDefectiveQualityApplied:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onDamagedStateApplied:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onCriticalImpact:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onCriticalImpactPoint:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onCriticalImpactSpeed:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1001 &5717474834163769460
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 1632953841127884940}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2553015719312062053, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_VersionIndex
|
||||
value: 1173
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3949508846642722774, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_VersionIndex
|
||||
value: 3154
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4560519800175472911, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_VersionIndex
|
||||
value: 3693
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8990336511305880825, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: Visual
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
--- !u!64 &1623627698583667245 stripped
|
||||
MeshCollider:
|
||||
m_CorrespondingSourceObject: {fileID: 6471890211788923481, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
m_PrefabInstance: {fileID: 5717474834163769460}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!4 &1646663282415760833 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 6449878790825891253, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
m_PrefabInstance: {fileID: 5717474834163769460}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!136 &5222869138606293483 stripped
|
||||
CapsuleCollider:
|
||||
m_CorrespondingSourceObject: {fileID: 514491035522146719, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
m_PrefabInstance: {fileID: 5717474834163769460}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!64 &6894540284203297686 stripped
|
||||
MeshCollider:
|
||||
m_CorrespondingSourceObject: {fileID: 1222409323731209186, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
m_PrefabInstance: {fileID: 5717474834163769460}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!64 &7077397027048864733 stripped
|
||||
MeshCollider:
|
||||
m_CorrespondingSourceObject: {fileID: 3273969631134882729, guid: 0b36d4cab828ea645bb5ab46d25c2204, type: 3}
|
||||
m_PrefabInstance: {fileID: 5717474834163769460}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 746f04d9ba6c87d4bae8a09287c5d090
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -16,13 +16,12 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
|
||||
[Header("Holding Follow")]
|
||||
[SerializeField, Min(0.1f)] private float maximumFollowSpeed = 12f;
|
||||
[SerializeField, Min(0.1f)] private float followResponsiveness = 10f;
|
||||
[SerializeField, Min(0.1f)] private float maximumFollowAcceleration = 80f;
|
||||
[SerializeField, Min(0.5f)] private float maximumHoldDistance = 4.5f;
|
||||
[SerializeField, Min(0.1f)] private float rotationFollowSpeed = 14f;
|
||||
|
||||
[Header("Local Presentation")]
|
||||
[Tooltip("Removes the server round-trip delay for the player currently carrying the item.")]
|
||||
[SerializeField] private bool correctLocalHolderPresentation = true;
|
||||
[SerializeField, Min(0.1f)] private float maximumLocalCorrectionDistance = 2f;
|
||||
[SerializeField, Min(0.1f)] private float rotationResponsiveness = 10f;
|
||||
[SerializeField, Min(0.1f)] private float maximumAngularAcceleration = 80f;
|
||||
|
||||
[Header("Throw")]
|
||||
[SerializeField, Min(0f)] private float throwForce = 6f;
|
||||
@@ -41,10 +40,10 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
private readonly HashSet<Collider> ignoredPlayerColliders = new();
|
||||
private readonly HashSet<Collider> desiredIgnoredColliders = new();
|
||||
private readonly List<Collider> collidersToRestore = new();
|
||||
private readonly RaycastHit[] localCorrectionHits = new RaycastHit[24];
|
||||
private float nextCollisionRefreshTime;
|
||||
private bool initialUseGravity;
|
||||
private CollisionDetectionMode initialCollisionDetectionMode;
|
||||
private NetworkTransform networkTransform;
|
||||
|
||||
protected abstract Vector3 HoldingOffset { get; }
|
||||
protected abstract string DropPrompt { get; }
|
||||
@@ -80,6 +79,8 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
initialUseGravity = physicsBody.useGravity;
|
||||
initialCollisionDetectionMode = physicsBody.collisionDetectionMode;
|
||||
}
|
||||
|
||||
networkTransform = GetComponent<NetworkTransform>();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
@@ -87,6 +88,12 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
SpawnedHeldObjects.Add(this);
|
||||
holderClientId.OnValueChanged += HandleHolderChanged;
|
||||
isExternallyControlled.OnValueChanged += HandleExternalControlChanged;
|
||||
if (networkTransform != null)
|
||||
{
|
||||
networkTransform.Interpolate = true;
|
||||
networkTransform.UseUnreliableDeltas = true;
|
||||
}
|
||||
|
||||
ApplyHoldingState();
|
||||
}
|
||||
|
||||
@@ -129,28 +136,6 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!correctLocalHolderPresentation || IsServer || !IsSpawned ||
|
||||
!IsHeldByLocalPlayer || IsExternallyControlled || physicsBody == null ||
|
||||
!TryGetPlayerObject(holderClientId.Value, out NetworkObject holder))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Transform anchor = ResolveHoldingAnchor(holder);
|
||||
Vector3 targetPosition = anchor.TransformPoint(HoldingOffset);
|
||||
Vector3 targetDelta = targetPosition - physicsBody.position;
|
||||
if (targetDelta.sqrMagnitude >
|
||||
maximumLocalCorrectionDistance * maximumLocalCorrectionDistance ||
|
||||
HasBlockingColliderForLocalCorrection(targetDelta))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
transform.SetPositionAndRotation(targetPosition, anchor.rotation);
|
||||
}
|
||||
|
||||
public void RequestThrow()
|
||||
{
|
||||
if (IsSpawned && IsHeldByLocalPlayer)
|
||||
@@ -292,10 +277,14 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 targetVelocity = targetDelta / Mathf.Max(Time.fixedDeltaTime, 0.001f);
|
||||
physicsBody.linearVelocity = Vector3.ClampMagnitude(
|
||||
targetVelocity,
|
||||
float fixedDeltaTime = Mathf.Max(Time.fixedDeltaTime, 0.001f);
|
||||
Vector3 targetVelocity = Vector3.ClampMagnitude(
|
||||
targetDelta * followResponsiveness,
|
||||
maximumFollowSpeed);
|
||||
physicsBody.linearVelocity = Vector3.MoveTowards(
|
||||
physicsBody.linearVelocity,
|
||||
targetVelocity,
|
||||
maximumFollowAcceleration * fixedDeltaTime);
|
||||
|
||||
DriveRotation(anchor.rotation);
|
||||
return true;
|
||||
@@ -306,57 +295,25 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
Quaternion rotationDelta = targetRotation * Quaternion.Inverse(physicsBody.rotation);
|
||||
rotationDelta.ToAngleAxis(out float angle, out Vector3 axis);
|
||||
if (axis.sqrMagnitude < 0.0001f || float.IsNaN(axis.x))
|
||||
{
|
||||
physicsBody.angularVelocity = Vector3.MoveTowards(
|
||||
physicsBody.angularVelocity,
|
||||
Vector3.zero,
|
||||
maximumAngularAcceleration * Mathf.Max(Time.fixedDeltaTime, 0.001f));
|
||||
return;
|
||||
}
|
||||
|
||||
if (angle > 180f)
|
||||
angle -= 360f;
|
||||
|
||||
Vector3 targetAngularVelocity = axis.normalized *
|
||||
(angle * Mathf.Deg2Rad / Mathf.Max(Time.fixedDeltaTime, 0.001f));
|
||||
physicsBody.angularVelocity = Vector3.ClampMagnitude(
|
||||
targetAngularVelocity,
|
||||
float fixedDeltaTime = Mathf.Max(Time.fixedDeltaTime, 0.001f);
|
||||
Vector3 targetAngularVelocity = Vector3.ClampMagnitude(
|
||||
axis.normalized * angle * Mathf.Deg2Rad * rotationResponsiveness,
|
||||
rotationFollowSpeed);
|
||||
}
|
||||
|
||||
private bool HasBlockingColliderForLocalCorrection(Vector3 targetDelta)
|
||||
{
|
||||
float distance = targetDelta.magnitude;
|
||||
if (distance <= 0.0001f)
|
||||
return false;
|
||||
|
||||
int hitCount = Physics.RaycastNonAlloc(
|
||||
physicsBody.position,
|
||||
targetDelta / distance,
|
||||
localCorrectionHits,
|
||||
distance,
|
||||
~0,
|
||||
QueryTriggerInteraction.Ignore);
|
||||
|
||||
for (int hitIndex = 0; hitIndex < hitCount; hitIndex++)
|
||||
{
|
||||
Collider hitCollider = localCorrectionHits[hitIndex].collider;
|
||||
if (hitCollider == null ||
|
||||
ignoredPlayerColliders.Contains(hitCollider) ||
|
||||
IsObjectCollider(hitCollider))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsObjectCollider(Collider candidate)
|
||||
{
|
||||
foreach (Collider objectCollider in objectColliders)
|
||||
{
|
||||
if (objectCollider == candidate)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
physicsBody.angularVelocity = Vector3.MoveTowards(
|
||||
physicsBody.angularVelocity,
|
||||
targetAngularVelocity,
|
||||
maximumAngularAcceleration * fixedDeltaTime);
|
||||
}
|
||||
|
||||
private void HandleHolderChanged(ulong _, ulong __) => ApplyHoldingState();
|
||||
@@ -369,6 +326,9 @@ public abstract class NetworkHeldInteractable : NetworkInteractable, IWorkstatio
|
||||
{
|
||||
bool isPhysicallyHeld = IsHeld && !IsExternallyControlled;
|
||||
bool shouldBeKinematic = IsExternallyControlled || !IsServer;
|
||||
physicsBody.interpolation = IsServer
|
||||
? RigidbodyInterpolation.Interpolate
|
||||
: RigidbodyInterpolation.None;
|
||||
|
||||
if (shouldBeKinematic)
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed class ItemCatalog : ScriptableObject
|
||||
return false;
|
||||
}
|
||||
|
||||
return itemsById.TryGetValue(itemId.Trim().ToLowerInvariant(), out definition);
|
||||
return itemsById.TryGetValue(NormalizeId(itemId), out definition);
|
||||
}
|
||||
|
||||
private void RebuildIndex()
|
||||
@@ -39,4 +39,7 @@ public sealed class ItemCatalog : ScriptableObject
|
||||
Debug.LogError($"Duplicate item id '{definition.ItemId}' in {name}.", this);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeId(string value) =>
|
||||
value.Trim().ToLowerInvariant().Replace(' ', '-');
|
||||
}
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
public enum ItemCategory
|
||||
{
|
||||
Scrap,
|
||||
SmeltedMaterial,
|
||||
RocketPart,
|
||||
Tool,
|
||||
Consumable
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "ItemDefinition", menuName = "FORT-BO/Items/Item Definition")]
|
||||
public sealed class ItemDefinition : ScriptableObject
|
||||
public class ItemDefinition : ScriptableObject
|
||||
{
|
||||
[SerializeField] private string itemId;
|
||||
[SerializeField] private string displayName;
|
||||
[SerializeField] private ItemCategory category;
|
||||
[SerializeField] private bool isAssemblyReady;
|
||||
[SerializeField] private NetworkObject worldPrefab;
|
||||
|
||||
public string ItemId => itemId;
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(displayName) ? name : displayName;
|
||||
public ItemCategory Category => category;
|
||||
public bool IsAssemblyReady => isAssemblyReady;
|
||||
public NetworkObject WorldPrefab => worldPrefab;
|
||||
|
||||
private void OnValidate()
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
itemId = itemId?.Trim().ToLowerInvariant();
|
||||
itemId = NormalizeId(itemId);
|
||||
displayName = displayName?.Trim();
|
||||
}
|
||||
|
||||
protected static string NormalizeId(string value) =>
|
||||
value?.Trim().ToLowerInvariant().Replace(' ', '-');
|
||||
}
|
||||
|
||||
@@ -10,5 +10,11 @@ public sealed class NetworkItem : NetworkBehaviour
|
||||
public ItemDefinition Definition => definition;
|
||||
public string ItemId => definition != null ? definition.ItemId : string.Empty;
|
||||
public string DisplayName => definition != null ? definition.DisplayName : name;
|
||||
public ItemCategory Category => definition != null ? definition.Category : default;
|
||||
|
||||
public bool TryGetDefinition<TDefinition>(out TDefinition typedDefinition)
|
||||
where TDefinition : ItemDefinition
|
||||
{
|
||||
typedDefinition = definition as TDefinition;
|
||||
return typedDefinition != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f100
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "MaterialItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Material")]
|
||||
public sealed class MaterialItemDefinition : ItemDefinition
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f101
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
|
||||
public enum PartProcessingState : byte
|
||||
{
|
||||
Raw,
|
||||
Ready
|
||||
}
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "PartItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Part")]
|
||||
public sealed class PartItemDefinition : ItemDefinition
|
||||
{
|
||||
[Tooltip("Data-driven part family id, for example rocket-engine or rocket-cone-standard.")]
|
||||
[SerializeField] private string partTypeId;
|
||||
[SerializeField] private PartProcessingState processingState;
|
||||
|
||||
public string PartTypeId => string.IsNullOrWhiteSpace(partTypeId)
|
||||
? ItemId
|
||||
: partTypeId;
|
||||
public PartProcessingState ProcessingState => processingState;
|
||||
public bool IsReadyForAssembly => processingState == PartProcessingState.Ready;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
partTypeId = NormalizeId(partTypeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f102
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "RocketItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Rocket")]
|
||||
public sealed class RocketItemDefinition : ItemDefinition
|
||||
{
|
||||
[Tooltip("Stable rocket family id used by launchers and orders.")]
|
||||
[SerializeField] private string rocketTypeId;
|
||||
[SerializeField, Min(0.1f)] private float criticalImpactSpeed = 7f;
|
||||
|
||||
public string RocketTypeId => string.IsNullOrWhiteSpace(rocketTypeId)
|
||||
? ItemId
|
||||
: rocketTypeId;
|
||||
public float CriticalImpactSpeed => criticalImpactSpeed;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
rocketTypeId = NormalizeId(rocketTypeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f103
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "UsableItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Usable")]
|
||||
public sealed class UsableItemDefinition : ItemDefinition
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f104
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f110
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
public enum RocketAssemblyQuality : byte
|
||||
{
|
||||
Normal,
|
||||
Defective
|
||||
}
|
||||
|
||||
public struct RocketCriticalImpactState : INetworkSerializable,
|
||||
IEquatable<RocketCriticalImpactState>
|
||||
{
|
||||
public uint Sequence;
|
||||
public Vector3 Point;
|
||||
public float Speed;
|
||||
|
||||
public RocketCriticalImpactState(uint sequence, Vector3 point, float speed)
|
||||
{
|
||||
Sequence = sequence;
|
||||
Point = point;
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||
where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref Sequence);
|
||||
serializer.SerializeValue(ref Point);
|
||||
serializer.SerializeValue(ref Speed);
|
||||
}
|
||||
|
||||
public bool Equals(RocketCriticalImpactState other) =>
|
||||
Sequence == other.Sequence && Point == other.Point && Speed.Equals(other.Speed);
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(NetworkItem), typeof(Rigidbody))]
|
||||
public sealed class NetworkRocketItem : NetworkBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkItem networkItem;
|
||||
[SerializeField] private Rigidbody physicsBody;
|
||||
|
||||
private readonly NetworkVariable<RocketAssemblyQuality> assemblyQuality = new(
|
||||
RocketAssemblyQuality.Normal,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private readonly NetworkVariable<RocketCriticalImpactState> criticalImpactState = new(
|
||||
default,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
public RocketItemDefinition Definition =>
|
||||
networkItem != null && networkItem.TryGetDefinition(out RocketItemDefinition definition)
|
||||
? definition
|
||||
: null;
|
||||
public string RocketTypeId => Definition != null ? Definition.RocketTypeId : string.Empty;
|
||||
public RocketAssemblyQuality AssemblyQuality => assemblyQuality.Value;
|
||||
public bool IsDefective => AssemblyQuality == RocketAssemblyQuality.Defective;
|
||||
public bool IsCriticallyDamaged => criticalImpactState.Value.Sequence > 0;
|
||||
public uint CriticalImpactSequence => criticalImpactState.Value.Sequence;
|
||||
public Vector3 LastCriticalImpactPoint => criticalImpactState.Value.Point;
|
||||
public float LastCriticalImpactSpeed => criticalImpactState.Value.Speed;
|
||||
|
||||
public event Action Changed;
|
||||
public event Action CriticalImpact;
|
||||
|
||||
private void Reset() => ResolveReferences();
|
||||
|
||||
private void Awake() => ResolveReferences();
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
assemblyQuality.OnValueChanged += HandleAssemblyQualityChanged;
|
||||
criticalImpactState.OnValueChanged += HandleCriticalImpactChanged;
|
||||
|
||||
if (Definition == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"{name} has NetworkRocketItem but its NetworkItem does not reference " +
|
||||
"a RocketItemDefinition.",
|
||||
this);
|
||||
}
|
||||
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
assemblyQuality.OnValueChanged -= HandleAssemblyQualityChanged;
|
||||
criticalImpactState.OnValueChanged -= HandleCriticalImpactChanged;
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (!IsServer || !IsSpawned || IsCriticallyDamaged || Definition == null)
|
||||
return;
|
||||
|
||||
float impactSpeed = collision.relativeVelocity.magnitude;
|
||||
if (impactSpeed < Definition.CriticalImpactSpeed)
|
||||
return;
|
||||
|
||||
Vector3 impactPoint = collision.contactCount > 0
|
||||
? collision.GetContact(0).point
|
||||
: transform.position;
|
||||
TryTriggerCriticalImpact(impactPoint, impactSpeed);
|
||||
}
|
||||
|
||||
public bool TryTriggerCriticalImpact(Vector3 impactPoint, float impactSpeed)
|
||||
{
|
||||
if (!IsServer || !IsSpawned || IsCriticallyDamaged || Definition == null ||
|
||||
impactSpeed < Definition.CriticalImpactSpeed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
criticalImpactState.Value = new RocketCriticalImpactState(
|
||||
criticalImpactState.Value.Sequence + 1,
|
||||
impactPoint,
|
||||
impactSpeed);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryInitializeAssemblyQuality(bool isDefectiveResult)
|
||||
{
|
||||
if (!IsServer || !IsSpawned)
|
||||
return false;
|
||||
|
||||
assemblyQuality.Value = isDefectiveResult
|
||||
? RocketAssemblyQuality.Defective
|
||||
: RocketAssemblyQuality.Normal;
|
||||
return true;
|
||||
}
|
||||
|
||||
[ContextMenu("Debug/Trigger Critical Impact (Server Play Mode)")]
|
||||
private void TriggerCriticalImpactFromContextMenu()
|
||||
{
|
||||
if (Application.isPlaying && IsServer && IsSpawned && Definition != null)
|
||||
{
|
||||
TryTriggerCriticalImpact(
|
||||
transform.position,
|
||||
Definition.CriticalImpactSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (networkItem == null)
|
||||
networkItem = GetComponent<NetworkItem>();
|
||||
|
||||
if (physicsBody == null)
|
||||
physicsBody = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
private void HandleAssemblyQualityChanged(
|
||||
RocketAssemblyQuality _,
|
||||
RocketAssemblyQuality __) => Changed?.Invoke();
|
||||
|
||||
private void HandleCriticalImpactChanged(
|
||||
RocketCriticalImpactState previous,
|
||||
RocketCriticalImpactState current)
|
||||
{
|
||||
Changed?.Invoke();
|
||||
if (current.Sequence != previous.Sequence)
|
||||
CriticalImpact?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f111
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class RocketItemView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkRocketItem rocket;
|
||||
|
||||
[Header("State")]
|
||||
[SerializeField] private UnityEvent onNormalQualityApplied;
|
||||
[SerializeField] private UnityEvent onDefectiveQualityApplied;
|
||||
[SerializeField] private UnityEvent onDamagedStateApplied;
|
||||
|
||||
[Header("Critical Impact")]
|
||||
[SerializeField] private UnityEvent onCriticalImpact;
|
||||
[SerializeField] private UnityEvent<Vector3> onCriticalImpactPoint;
|
||||
[SerializeField] private UnityEvent<float> onCriticalImpactSpeed;
|
||||
|
||||
private uint observedImpactSequence;
|
||||
private RocketAssemblyQuality observedQuality;
|
||||
private bool hasAppliedQuality;
|
||||
private bool hasAppliedDamagedState;
|
||||
|
||||
private void Reset() => rocket = GetComponentInParent<NetworkRocketItem>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (rocket == null)
|
||||
rocket = GetComponentInParent<NetworkRocketItem>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (rocket == null)
|
||||
return;
|
||||
|
||||
observedImpactSequence = rocket.CriticalImpactSequence;
|
||||
observedQuality = rocket.AssemblyQuality;
|
||||
hasAppliedQuality = false;
|
||||
hasAppliedDamagedState = false;
|
||||
rocket.Changed += ApplyState;
|
||||
rocket.CriticalImpact += HandleCriticalImpact;
|
||||
ApplyState();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (rocket == null)
|
||||
return;
|
||||
|
||||
rocket.Changed -= ApplyState;
|
||||
rocket.CriticalImpact -= HandleCriticalImpact;
|
||||
}
|
||||
|
||||
[ContextMenu("Preview/Critical Impact")]
|
||||
private void PreviewCriticalImpact()
|
||||
{
|
||||
onCriticalImpactPoint?.Invoke(transform.position);
|
||||
onCriticalImpactSpeed?.Invoke(7f);
|
||||
onCriticalImpact?.Invoke();
|
||||
onDamagedStateApplied?.Invoke();
|
||||
}
|
||||
|
||||
[ContextMenu("Preview/Normal Quality")]
|
||||
private void PreviewNormalQuality() => onNormalQualityApplied?.Invoke();
|
||||
|
||||
[ContextMenu("Preview/Defective Quality")]
|
||||
private void PreviewDefectiveQuality() => onDefectiveQualityApplied?.Invoke();
|
||||
|
||||
private void ApplyState()
|
||||
{
|
||||
if (rocket == null)
|
||||
return;
|
||||
|
||||
if (!hasAppliedQuality || observedQuality != rocket.AssemblyQuality)
|
||||
{
|
||||
observedQuality = rocket.AssemblyQuality;
|
||||
hasAppliedQuality = true;
|
||||
if (observedQuality == RocketAssemblyQuality.Defective)
|
||||
onDefectiveQualityApplied?.Invoke();
|
||||
else
|
||||
onNormalQualityApplied?.Invoke();
|
||||
}
|
||||
|
||||
if (rocket.IsCriticallyDamaged && !hasAppliedDamagedState)
|
||||
{
|
||||
hasAppliedDamagedState = true;
|
||||
onDamagedStateApplied?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCriticalImpact()
|
||||
{
|
||||
if (rocket == null || rocket.CriticalImpactSequence == observedImpactSequence)
|
||||
return;
|
||||
|
||||
observedImpactSequence = rocket.CriticalImpactSequence;
|
||||
onCriticalImpactPoint?.Invoke(rocket.LastCriticalImpactPoint);
|
||||
onCriticalImpactSpeed?.Invoke(rocket.LastCriticalImpactSpeed);
|
||||
onCriticalImpact?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f112
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f130
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
using Unity.Netcode;
|
||||
|
||||
public interface IHandItemUseAction
|
||||
{
|
||||
bool TryUseOnServer(NetworkObject playerObject);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f131
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class NetworkUsableHandItemInteractable : NetworkHandItemInteractable
|
||||
{
|
||||
[Tooltip("Component on this prefab implementing IHandItemUseAction.")]
|
||||
[SerializeField] private MonoBehaviour useActionComponent;
|
||||
|
||||
private IHandItemUseAction UseAction => useActionComponent as IHandItemUseAction;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
ResolveUseAction();
|
||||
}
|
||||
|
||||
private void OnValidate() => ResolveUseAction();
|
||||
|
||||
protected override void UseOnServer(NetworkObject playerObject)
|
||||
{
|
||||
if (UseAction == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"{name} was used but has no IHandItemUseAction component assigned.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
UseAction.TryUseOnServer(playerObject);
|
||||
}
|
||||
|
||||
private void ResolveUseAction()
|
||||
{
|
||||
if (useActionComponent is IHandItemUseAction)
|
||||
return;
|
||||
|
||||
foreach (MonoBehaviour component in GetComponents<MonoBehaviour>())
|
||||
{
|
||||
if (component is IHandItemUseAction)
|
||||
{
|
||||
useActionComponent = component;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
useActionComponent = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f132
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -4,6 +4,7 @@ using UnityEngine;
|
||||
public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSystem
|
||||
{
|
||||
[SerializeField, Range(1f, 89f)] private float verticalLookLimit = 80f;
|
||||
[SerializeField, Min(0.1f)] private float remotePitchFollowSpeed = 20f;
|
||||
|
||||
private readonly NetworkVariable<float> synchronizedPitch = new(
|
||||
0f,
|
||||
@@ -14,6 +15,7 @@ public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSyste
|
||||
private AudioListener audioListener;
|
||||
private MeshRenderer playerRenderer;
|
||||
private float pitch;
|
||||
private float targetPitch;
|
||||
private bool isInitialized;
|
||||
|
||||
public Camera PlayerCamera => playerCamera;
|
||||
@@ -34,7 +36,10 @@ public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSyste
|
||||
playerCamera.gameObject.SetActive(isLocalPlayer);
|
||||
|
||||
if (!isLocalPlayer)
|
||||
ApplyCameraPitch(synchronizedPitch.Value);
|
||||
{
|
||||
targetPitch = synchronizedPitch.Value;
|
||||
ApplyCameraPitch(targetPitch);
|
||||
}
|
||||
|
||||
if (!isLocalPlayer)
|
||||
return;
|
||||
@@ -62,7 +67,10 @@ public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSyste
|
||||
synchronizedPitch.OnValueChanged += HandleSynchronizedPitchChanged;
|
||||
|
||||
if (isInitialized && !IsOwner)
|
||||
ApplyCameraPitch(synchronizedPitch.Value);
|
||||
{
|
||||
targetPitch = synchronizedPitch.Value;
|
||||
ApplyCameraPitch(targetPitch);
|
||||
}
|
||||
|
||||
if (isInitialized && IsOwner)
|
||||
CaptureCursor();
|
||||
@@ -70,7 +78,17 @@ public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSyste
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!isInitialized || !IsOwner || !Application.isFocused)
|
||||
if (!isInitialized)
|
||||
return;
|
||||
|
||||
if (!IsOwner)
|
||||
{
|
||||
float interpolation = 1f - Mathf.Exp(-remotePitchFollowSpeed * Time.deltaTime);
|
||||
ApplyCameraPitch(Mathf.LerpAngle(pitch, targetPitch, interpolation));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Application.isFocused)
|
||||
return;
|
||||
|
||||
if (Cursor.lockState != CursorLockMode.Locked || Cursor.visible)
|
||||
@@ -89,6 +107,7 @@ public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSyste
|
||||
return;
|
||||
|
||||
pitch = Mathf.Clamp(pitch - pitchDelta, -verticalLookLimit, verticalLookLimit);
|
||||
targetPitch = pitch;
|
||||
ApplyCameraPitch(pitch);
|
||||
|
||||
if (IsSpawned)
|
||||
@@ -110,7 +129,7 @@ public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSyste
|
||||
private void HandleSynchronizedPitchChanged(float _, float currentPitch)
|
||||
{
|
||||
if (!IsOwner)
|
||||
ApplyCameraPitch(currentPitch);
|
||||
targetPitch = currentPitch;
|
||||
}
|
||||
|
||||
private void ApplyCameraPitch(float currentPitch)
|
||||
|
||||
+65
-19
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
public enum RocketAssemblyWorkstationState : byte
|
||||
{
|
||||
@@ -28,7 +29,9 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
|
||||
[Header("Interaction")]
|
||||
[SerializeField, Min(0.1f)] private float maximumTakeDistance = 3.5f;
|
||||
[SerializeField, Min(0.05f)] private float reinsertCooldownAfterTake = 0.5f;
|
||||
[FormerlySerializedAs("reinsertCooldownAfterTake")]
|
||||
[SerializeField, Min(0f)] private float minimumReinsertBlockDuration = 0.1f;
|
||||
[SerializeField, Min(0f)] private float reinsertClearance = 0.1f;
|
||||
|
||||
private readonly NetworkVariable<RocketAssemblyWorkstationState> state = new(
|
||||
RocketAssemblyWorkstationState.Loading,
|
||||
@@ -63,7 +66,8 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
private readonly NetworkItem[] slottedItems = new NetworkItem[MaximumSlotCount];
|
||||
private readonly IWorkstationItemControl[] slottedItemControls =
|
||||
new IWorkstationItemControl[MaximumSlotCount];
|
||||
private readonly Dictionary<ulong, double> reinsertBlockedUntil = new();
|
||||
private readonly Dictionary<ulong, ReinsertBlock> reinsertBlocks = new();
|
||||
private readonly List<ulong> clearedReinsertBlockIds = new();
|
||||
|
||||
private NetworkItem currentOutput;
|
||||
|
||||
@@ -170,6 +174,7 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
if (!IsServer || !IsSpawned)
|
||||
return;
|
||||
|
||||
UpdateReinsertBlocks();
|
||||
RemoveMissingSlottedItems();
|
||||
|
||||
if (State == RocketAssemblyWorkstationState.Assembling &&
|
||||
@@ -311,8 +316,11 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
NetworkItem item = slottedItems[slotIndex];
|
||||
if (item != null)
|
||||
{
|
||||
reinsertBlockedUntil[item.NetworkObjectId] =
|
||||
GetNetworkTime() + reinsertCooldownAfterTake;
|
||||
reinsertBlocks[item.NetworkObjectId] = new ReinsertBlock(
|
||||
item,
|
||||
slotIndex,
|
||||
item.GetComponentsInChildren<Collider>(true),
|
||||
GetNetworkTime() + minimumReinsertBlockDuration);
|
||||
}
|
||||
|
||||
ClearSlot(slotIndex);
|
||||
@@ -333,7 +341,8 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
|
||||
Array.Clear(slottedItems, 0, slottedItems.Length);
|
||||
Array.Clear(slottedItemControls, 0, slottedItemControls.Length);
|
||||
reinsertBlockedUntil.Clear();
|
||||
reinsertBlocks.Clear();
|
||||
clearedReinsertBlockIds.Clear();
|
||||
currentOutput = null;
|
||||
selectedRecipeIndex.Value = FindFirstValidRecipeIndex();
|
||||
assemblyStartedAt.Value = 0d;
|
||||
@@ -380,9 +389,7 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
}
|
||||
|
||||
bool defective = assemblyWillBeDefective.Value;
|
||||
ItemDefinition outputDefinition = defective
|
||||
? recipe.DefectiveOutputItem
|
||||
: recipe.OutputItem;
|
||||
RocketItemDefinition outputDefinition = recipe.OutputItem;
|
||||
if (outputDefinition == null || outputDefinition.WorldPrefab == null)
|
||||
{
|
||||
CancelAssemblyOnServer();
|
||||
@@ -408,12 +415,14 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
outputObject.Spawn();
|
||||
|
||||
currentOutput = outputObject.GetComponent<NetworkItem>();
|
||||
if (currentOutput == null ||
|
||||
NetworkRocketItem rocketItem = outputObject.GetComponent<NetworkRocketItem>();
|
||||
if (currentOutput == null || rocketItem == null ||
|
||||
!WorkstationItemControlUtility.TryGetControl(currentOutput, out _))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"{name} cannot produce '{outputDefinition.DisplayName}': the output prefab " +
|
||||
"must contain NetworkItem and a supported Carry or Drag interaction component.",
|
||||
"must contain NetworkItem, NetworkRocketItem and a supported Carry or Drag " +
|
||||
"interaction component.",
|
||||
this);
|
||||
outputObject.Despawn(true);
|
||||
currentOutput = null;
|
||||
@@ -421,6 +430,8 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
return;
|
||||
}
|
||||
|
||||
rocketItem.TryInitializeAssemblyQuality(defective);
|
||||
|
||||
assemblyStartedAt.Value = 0d;
|
||||
assemblyEndsAt.Value = 0d;
|
||||
state.Value = RocketAssemblyWorkstationState.OutputReady;
|
||||
@@ -513,17 +524,51 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
|
||||
private bool IsTemporarilyBlockedFromReinsert(NetworkItem item)
|
||||
{
|
||||
if (item == null ||
|
||||
!reinsertBlockedUntil.TryGetValue(item.NetworkObjectId, out double blockedUntil))
|
||||
return item != null && reinsertBlocks.ContainsKey(item.NetworkObjectId);
|
||||
}
|
||||
|
||||
private void UpdateReinsertBlocks()
|
||||
{
|
||||
if (reinsertBlocks.Count == 0)
|
||||
return;
|
||||
|
||||
double now = GetNetworkTime();
|
||||
clearedReinsertBlockIds.Clear();
|
||||
foreach (KeyValuePair<ulong, ReinsertBlock> pair in reinsertBlocks)
|
||||
{
|
||||
return false;
|
||||
ReinsertBlock block = pair.Value;
|
||||
bool canReleaseBlock = block.Item == null || !block.Item.IsSpawned ||
|
||||
(now >= block.MinimumBlockedUntil &&
|
||||
(!IsValidSlotIndex(block.SourceSlotIndex) ||
|
||||
!slotsBottomToTop[block.SourceSlotIndex].OverlapsAny(
|
||||
block.ItemColliders,
|
||||
reinsertClearance)));
|
||||
if (canReleaseBlock)
|
||||
clearedReinsertBlockIds.Add(pair.Key);
|
||||
}
|
||||
|
||||
if (GetNetworkTime() < blockedUntil)
|
||||
return true;
|
||||
foreach (ulong networkObjectId in clearedReinsertBlockIds)
|
||||
reinsertBlocks.Remove(networkObjectId);
|
||||
}
|
||||
|
||||
reinsertBlockedUntil.Remove(item.NetworkObjectId);
|
||||
return false;
|
||||
private readonly struct ReinsertBlock
|
||||
{
|
||||
public ReinsertBlock(
|
||||
NetworkItem item,
|
||||
int sourceSlotIndex,
|
||||
Collider[] itemColliders,
|
||||
double minimumBlockedUntil)
|
||||
{
|
||||
Item = item;
|
||||
SourceSlotIndex = sourceSlotIndex;
|
||||
ItemColliders = itemColliders;
|
||||
MinimumBlockedUntil = minimumBlockedUntil;
|
||||
}
|
||||
|
||||
public NetworkItem Item { get; }
|
||||
public int SourceSlotIndex { get; }
|
||||
public Collider[] ItemColliders { get; }
|
||||
public double MinimumBlockedUntil { get; }
|
||||
}
|
||||
|
||||
private NetworkItem ResolveSynchronizedItem(int slotIndex)
|
||||
@@ -588,8 +633,9 @@ public sealed class NetworkRocketAssemblyWorkstation : NetworkWorkstationControl
|
||||
slotIndex >= 0 && slotIndex < SlotCount && slotsBottomToTop[slotIndex] != null;
|
||||
|
||||
private static bool IsReadyRocketPart(NetworkItem item) =>
|
||||
item != null && item.IsSpawned && item.Definition != null &&
|
||||
item.Category == ItemCategory.RocketPart && item.Definition.IsAssemblyReady;
|
||||
item != null && item.IsSpawned &&
|
||||
item.TryGetDefinition(out PartItemDefinition definition) &&
|
||||
definition.IsReadyForAssembly;
|
||||
|
||||
private static bool IsRecipeValid(RocketAssemblyRecipe recipe) =>
|
||||
recipe != null && recipe.IsConfigured;
|
||||
|
||||
@@ -8,6 +8,7 @@ public sealed class RocketAssemblyInputSlot : MonoBehaviour, IInteractable
|
||||
[SerializeField] private Transform snapPoint;
|
||||
|
||||
private NetworkRocketAssemblyWorkstation workstation;
|
||||
private Collider triggerCollider;
|
||||
private int slotIndex = -1;
|
||||
|
||||
public Transform SnapPoint => snapPoint != null ? snapPoint : transform;
|
||||
@@ -21,10 +22,17 @@ public sealed class RocketAssemblyInputSlot : MonoBehaviour, IInteractable
|
||||
private void Reset()
|
||||
{
|
||||
snapPoint = transform;
|
||||
GetComponent<Collider>().isTrigger = true;
|
||||
ResolveTriggerCollider();
|
||||
triggerCollider.isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnValidate() => GetComponent<Collider>().isTrigger = true;
|
||||
private void Awake() => ResolveTriggerCollider();
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ResolveTriggerCollider();
|
||||
triggerCollider.isTrigger = true;
|
||||
}
|
||||
|
||||
public void Configure(NetworkRocketAssemblyWorkstation owner, int index)
|
||||
{
|
||||
@@ -51,4 +59,30 @@ public sealed class RocketAssemblyInputSlot : MonoBehaviour, IInteractable
|
||||
if (item != null)
|
||||
workstation.TryInsertItem(slotIndex, item);
|
||||
}
|
||||
|
||||
public bool OverlapsAny(Collider[] itemColliders, float clearance)
|
||||
{
|
||||
ResolveTriggerCollider();
|
||||
if (triggerCollider == null || !triggerCollider.enabled || itemColliders == null)
|
||||
return false;
|
||||
|
||||
Bounds triggerBounds = triggerCollider.bounds;
|
||||
triggerBounds.Expand(Mathf.Max(0f, clearance) * 2f);
|
||||
foreach (Collider itemCollider in itemColliders)
|
||||
{
|
||||
if (itemCollider != null && itemCollider.enabled &&
|
||||
triggerBounds.Intersects(itemCollider.bounds))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ResolveTriggerCollider()
|
||||
{
|
||||
if (triggerCollider == null)
|
||||
triggerCollider = GetComponent<Collider>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@ public sealed class RocketAssemblyRecipe : ScriptableObject
|
||||
[SerializeField] private string recipeId;
|
||||
[SerializeField] private string displayName;
|
||||
[Tooltip("Exact assembly order. Element 0 is the lowest rocket part.")]
|
||||
[SerializeField] private List<ItemDefinition> requiredPartsBottomToTop = new();
|
||||
[SerializeField] private ItemDefinition outputItem;
|
||||
[SerializeField] private ItemDefinition defectiveOutputItem;
|
||||
[SerializeField] private List<PartItemDefinition> requiredPartsBottomToTop = new();
|
||||
[SerializeField] private RocketItemDefinition outputItem;
|
||||
[SerializeField, Min(0.05f)] private float assemblyDuration = 5f;
|
||||
|
||||
[Header("Blueprint")]
|
||||
@@ -23,10 +22,9 @@ public sealed class RocketAssemblyRecipe : ScriptableObject
|
||||
|
||||
public string RecipeId => recipeId;
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(displayName) ? name : displayName;
|
||||
public IReadOnlyList<ItemDefinition> RequiredPartsBottomToTop => requiredPartsBottomToTop;
|
||||
public IReadOnlyList<PartItemDefinition> RequiredPartsBottomToTop => requiredPartsBottomToTop;
|
||||
public int RequiredPartCount => requiredPartsBottomToTop.Count;
|
||||
public ItemDefinition OutputItem => outputItem;
|
||||
public ItemDefinition DefectiveOutputItem => defectiveOutputItem;
|
||||
public RocketItemDefinition OutputItem => outputItem;
|
||||
public float AssemblyDuration => assemblyDuration;
|
||||
public string BlueprintTitle => string.IsNullOrWhiteSpace(blueprintTitle)
|
||||
? DisplayName
|
||||
@@ -36,8 +34,7 @@ public sealed class RocketAssemblyRecipe : ScriptableObject
|
||||
public bool IsConfigured =>
|
||||
requiredPartsBottomToTop.Count is > 0 and <= MaximumPartCount &&
|
||||
requiredPartsBottomToTop.TrueForAll(IsReadyRocketPart) &&
|
||||
IsSpawnable(outputItem) &&
|
||||
IsSpawnable(defectiveOutputItem);
|
||||
IsSpawnable(outputItem);
|
||||
|
||||
public bool Matches(IReadOnlyList<NetworkItem> slottedItems)
|
||||
{
|
||||
@@ -75,14 +72,12 @@ public sealed class RocketAssemblyRecipe : ScriptableObject
|
||||
requiredPartsBottomToTop.Count - MaximumPartCount);
|
||||
}
|
||||
|
||||
private static bool MatchesDefinition(NetworkItem item, ItemDefinition definition) =>
|
||||
private static bool MatchesDefinition(NetworkItem item, PartItemDefinition definition) =>
|
||||
item.Definition == definition ||
|
||||
(!string.IsNullOrEmpty(definition.ItemId) && item.ItemId == definition.ItemId);
|
||||
|
||||
private static bool IsReadyRocketPart(ItemDefinition definition) =>
|
||||
definition != null &&
|
||||
definition.Category == ItemCategory.RocketPart &&
|
||||
definition.IsAssemblyReady;
|
||||
private static bool IsReadyRocketPart(PartItemDefinition definition) =>
|
||||
definition != null && definition.IsReadyForAssembly;
|
||||
|
||||
private static bool IsSpawnable(ItemDefinition definition) =>
|
||||
definition != null && definition.WorldPrefab != null;
|
||||
|
||||
Reference in New Issue
Block a user