Added RocketAssemblyStation and ReadyRocket
This commit is contained in:
@@ -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:
|
||||
Reference in New Issue
Block a user