init
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 079fed2826f14a7da75cd8d7d84b67f6
|
||||
timeCreated: 1759334623
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e35c80ecac124ccfa0a1124809dd8dde
|
||||
timeCreated: 1762190736
|
||||
@@ -0,0 +1,136 @@
|
||||
#sd-header {
|
||||
border-top-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
border-left-color: rgb(26, 26, 26);
|
||||
border-right-color: rgb(26, 26, 26);
|
||||
border-top-color: rgb(26, 26, 26);
|
||||
border-bottom-color: rgb(26, 26, 26);
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
background-color: rgb(51, 51, 51);
|
||||
-unity-text-align: upper-center;
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
#sd-header > #unity-dragline-anchor {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#sd-header > #unity-dragline-anchor > #unity-dragline {
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#sd-header Label {
|
||||
padding-top: 2px;
|
||||
padding-right: 2px;
|
||||
padding-bottom: 2px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
#sd-add-row {
|
||||
border-top-width: 0;
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 0;
|
||||
border-left-width: 1px;
|
||||
border-left-color: rgb(26, 26, 26);
|
||||
border-right-color: rgb(26, 26, 26);
|
||||
border-top-color: rgb(26, 26, 26);
|
||||
border-bottom-color: rgb(26, 26, 26);
|
||||
background-color: rgb(70, 70, 70);
|
||||
}
|
||||
|
||||
#sd-add-row:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keys-disabled #sd-add-row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sd-add-row > PropertyField {
|
||||
}
|
||||
|
||||
#sd-add-row > Label {
|
||||
-unity-text-align: middle-center;
|
||||
}
|
||||
|
||||
#sd-list > ScrollView {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#sd-list #unity-list-view__reorderable-handle {
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#sd-list #unity-list-view__reorderable-item__container {
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.keys-disabled #sd-list #unity-list-view__footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sd-list #unity-list-view__footer:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex-direction: row;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.row-left {
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
padding-top: 1px;
|
||||
padding-right: 2px;
|
||||
padding-bottom: 1px;
|
||||
padding-left: 17px;
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
border-right-color: rgb(26, 26, 26);
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.row-right {
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
padding-top: 1px;
|
||||
padding-right: 2px;
|
||||
padding-bottom: 1px;
|
||||
padding-left: 2px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
border-left-width: 1px;
|
||||
border-left-color: rgb(26, 26, 26);
|
||||
flex-shrink: 1;
|
||||
min-width: 180px;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 215036bf11e09364a9da52f92c9534f0
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@@ -0,0 +1,14 @@
|
||||
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
|
||||
<Style src="project://database/Assets/TheVVaS/RPGCoreCommon/Helpers/Editor/Drawers/SerializableDictionary.uss?fileID=7433441132597879392&guid=215036bf11e09364a9da52f92c9534f0&type=3#SerializableDictionary" />
|
||||
<ui:Foldout text="Label">
|
||||
<RPGCoreCommon.Helpers.UIElements.TwoPaneSplitView name="sd-header" fixed-pane-initial-dimension="150" class="row">
|
||||
<ui:Label text="KEY" class="row-left" />
|
||||
<ui:Label text="VALUE" class="row-right" />
|
||||
</RPGCoreCommon.Helpers.UIElements.TwoPaneSplitView>
|
||||
<ui:VisualElement name="sd-add-row" class="row">
|
||||
<uie:PropertyField class="row-left" />
|
||||
<ui:Label text="<-- select key before add" class="row-right" />
|
||||
</ui:VisualElement>
|
||||
<ui:ListView show-add-remove-footer="true" name="sd-list" reorderable="true" show-alternating-row-backgrounds="All" selection-type="Multiple" show-border="true" virtualization-method="DynamicHeight" reorder-mode="Animated" show-bound-collection-size="false" />
|
||||
</ui:Foldout>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f99f6caeffbe77e4c9b1062ec393fbc8
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using RPGCoreCommon.Helpers.CustomTypes;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.Drawers
|
||||
{
|
||||
[Serializable]
|
||||
[CustomPropertyDrawer(typeof(SerializableDictionary<,>), true)]
|
||||
public class SerializableDictionaryDrawer : PropertyDrawer
|
||||
{
|
||||
[SerializeField] private VisualTreeAsset _visualTreeAsset;
|
||||
|
||||
private SerializedProperty _property;
|
||||
private SerializedProperty _tempKeyProperty;
|
||||
private SerializedProperty _keysProperty;
|
||||
private SerializedProperty _valuesProperty;
|
||||
|
||||
private SerializableDictionaryAttribute _sdAttribute;
|
||||
private Type _keyType;
|
||||
private Type _valueType;
|
||||
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
_property = property;
|
||||
_sdAttribute = fieldInfo.GetCustomAttribute<SerializableDictionaryAttribute>() ?? new SerializableDictionaryAttribute();
|
||||
_tempKeyProperty = property.FindPropertyRelative("_tempKey");
|
||||
_keysProperty = property.FindPropertyRelative("_keys");
|
||||
_valuesProperty = property.FindPropertyRelative("_values");
|
||||
|
||||
if (_keysProperty == null || _valuesProperty == null)
|
||||
return new HelpBox("Keys and Values in SerializableDictionary must be serializable!", HelpBoxMessageType.Error);
|
||||
|
||||
var genericTypes = property.boxedValue.GetType().FindGenericDefinitionType(typeof(SerializableDictionary<,>)).GetGenericArguments();
|
||||
_keyType = genericTypes[0];
|
||||
_valueType = genericTypes[1];
|
||||
|
||||
var root = _visualTreeAsset.CloneTree();
|
||||
root.Q<Label>().text = preferredLabel;
|
||||
root.DisableDefaultAligning();
|
||||
|
||||
if (string.IsNullOrEmpty(preferredLabel))
|
||||
{
|
||||
var foldout = root.Q<Foldout>();
|
||||
foldout.Q<Toggle>().style.display = DisplayStyle.None;
|
||||
foldout.Q<Toggle>().value = true;
|
||||
foldout.Q(name: "unity-content").style.marginLeft = 0;
|
||||
}
|
||||
|
||||
var list = root.Q<ListView>("sd-list");
|
||||
list.BindProperty(_valuesProperty);
|
||||
list.makeItem = CreateRow;
|
||||
list.bindItem = BindRow;
|
||||
list.onAdd = OnAdd;
|
||||
list.onRemove = OnRemove;
|
||||
list.itemIndexChanged += OnSwap;
|
||||
|
||||
if (_sdAttribute is not null)
|
||||
{
|
||||
root.Q("sd-header").Q<Label>(className: "row-left").text = _sdAttribute.keyName;
|
||||
root.Q("sd-header").Q<Label>(className: "row-right").text = _sdAttribute.valueName;
|
||||
root.EnableInClassList("keys-disabled", !_sdAttribute.isKeyEditable);
|
||||
}
|
||||
|
||||
var dragline = root.Q("sd-header").Q("unity-dragline-anchor");
|
||||
dragline.RegisterCallback<GeometryChangedEvent>(ev =>
|
||||
{
|
||||
var parentWidth = dragline.parent.worldBound.width;
|
||||
var leftWidth = dragline.style.left.value.value;
|
||||
leftWidth = Mathf.Clamp(leftWidth, 150f, parentWidth-180f);
|
||||
|
||||
dragline.style.left = leftWidth;
|
||||
root.Query(className: "row-left").ForEach(element => element.style.width = leftWidth);
|
||||
});
|
||||
|
||||
var tempKeyField = root.Q("sd-add-row").Q<PropertyField>();
|
||||
tempKeyField.BindProperty(_tempKeyProperty);
|
||||
tempKeyField.HideFoldoutAndLabel();
|
||||
|
||||
// This additional key property should be always empty on beginning to not confuse user.
|
||||
// All because we are using "fake" serialized property to make unity draw field for key before even adding it to dictionary.
|
||||
_tempKeyProperty.boxedValue = _keyType.IsValueType ? Activator.CreateInstance(_keyType) : null;
|
||||
_property.serializedObject.ApplyModifiedProperties();
|
||||
_property.serializedObject.Update();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void OnSwap(int index1, int index2)
|
||||
{
|
||||
_keysProperty.MoveArrayElement(index1, index2);
|
||||
_property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void OnAdd(BaseListView list)
|
||||
{
|
||||
var tempKey = _tempKeyProperty.boxedValue;
|
||||
|
||||
if (tempKey == null)
|
||||
{
|
||||
var helpBox = new HelpBox("Key is not selected!", HelpBoxMessageType.Error);
|
||||
helpBox.FadeOut(3000, 2000, true);
|
||||
list.parent.parent.Insert(0, helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
var keyExists = Enumerable.Range(0, _keysProperty.arraySize)
|
||||
.Select(i => _keysProperty.GetArrayElementAtIndex(i))
|
||||
.Any(prop => prop.boxedValue.Equals(tempKey));
|
||||
|
||||
if (keyExists)
|
||||
{
|
||||
var helpBox = new HelpBox("Key already exists!", HelpBoxMessageType.Error);
|
||||
helpBox.FadeOut(3000, 2000, true);
|
||||
list.parent.parent.Insert(0, helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset temporary key to default
|
||||
_tempKeyProperty.boxedValue = _keyType.IsValueType ? Activator.CreateInstance(_keyType) : null;
|
||||
|
||||
// New Key
|
||||
_keysProperty.arraySize++;
|
||||
_keysProperty.GetArrayElementAtIndex(_keysProperty.arraySize - 1).boxedValue = tempKey;
|
||||
|
||||
// New Value
|
||||
_valuesProperty.arraySize++;
|
||||
_valuesProperty.GetArrayElementAtIndex(_valuesProperty.arraySize - 1).boxedValue =
|
||||
_valueType.IsValueType ? Activator.CreateInstance(_valueType) : null;
|
||||
|
||||
// Update list and property
|
||||
_property.serializedObject.ApplyModifiedProperties();
|
||||
_property.serializedObject.Update();
|
||||
list.RefreshItems();
|
||||
}
|
||||
|
||||
private void OnRemove(BaseListView list)
|
||||
{
|
||||
var ids = list.selectedIds.SortDescending().ToList();
|
||||
if (ids.Count < 0) return;
|
||||
|
||||
ids.ForEach(i =>
|
||||
{
|
||||
_keysProperty.DeleteArrayElementAtIndex(i);
|
||||
_valuesProperty.DeleteArrayElementAtIndex(i);
|
||||
});
|
||||
|
||||
// Update list and property
|
||||
_property.serializedObject.ApplyModifiedProperties();
|
||||
_property.serializedObject.Update();
|
||||
list.RefreshItems();
|
||||
}
|
||||
|
||||
private VisualElement CreateRow()
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("row");
|
||||
|
||||
var keyField = new PropertyField();
|
||||
keyField.DisableDefaultAligning();
|
||||
keyField.AddToClassList("row-left");
|
||||
keyField.label = "";
|
||||
keyField.SetEnabled(_sdAttribute.isKeyEditable);
|
||||
row.Add(keyField);
|
||||
|
||||
var valueField = new PropertyField();
|
||||
valueField.HideFoldoutAndLabel();
|
||||
valueField.SetContextAligningHere();
|
||||
valueField.AddToClassList("row-right");
|
||||
valueField.label = "";
|
||||
row.Add(valueField);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private void BindRow(VisualElement row, int i)
|
||||
{
|
||||
if (i >= _keysProperty.arraySize) return;
|
||||
row.Q<PropertyField>(className: "row-left").BindProperty(_keysProperty.GetArrayElementAtIndex(i));
|
||||
row.Q<PropertyField>(className: "row-right").BindProperty(_valuesProperty.GetArrayElementAtIndex(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ea25817e38041b7a6865551cf7cf6f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _visualTreeAsset: {fileID: 9197481963319205126, guid: f99f6caeffbe77e4c9b1062ec393fbc8, type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using RPGCoreCommon.Helpers.CustomTypes;
|
||||
using RPGCoreCommon.Helpers.Editor.UIElements;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.Drawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SerializableType))]
|
||||
public class SerializableTypeDrawer : PropertyDrawer
|
||||
{
|
||||
private const string NoTypeSelected = "No Type Selected";
|
||||
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
var typeField = new FakeObjectField(preferredLabel);
|
||||
typeField.TrackPropertyValue(property, _ => UpdateTypeField(property, typeField));
|
||||
typeField.RegisterCallback<ClickEvent>(_ => ShowPopup(property, typeField), TrickleDown.TrickleDown);
|
||||
|
||||
UpdateTypeField(property, typeField);
|
||||
|
||||
return typeField;
|
||||
}
|
||||
|
||||
private void UpdateTypeField(SerializedProperty property, FakeObjectField field)
|
||||
{
|
||||
var typeName = ((SerializableType)property.boxedValue).type?.Name ?? NoTypeSelected;
|
||||
field.Set(typeName, EditorGUIUtility.FindTexture("cs Script Icon"));
|
||||
}
|
||||
|
||||
private void ShowPopup(SerializedProperty property, FakeObjectField field)
|
||||
{
|
||||
new SearchFieldPopup(field, GetOptions(property)).Show();
|
||||
}
|
||||
|
||||
private Dictionary<string, Action> GetOptions(SerializedProperty property)
|
||||
{
|
||||
var attr = fieldInfo.GetCustomAttribute<SerializableTypeAttribute>();
|
||||
var derivedFrom = attr?.derivedFrom ?? typeof(object);
|
||||
var limitedTo = attr?.limitedTo;
|
||||
|
||||
if (limitedTo?.Length > 0)
|
||||
{
|
||||
return limitedTo
|
||||
.GroupBy(type => type.FullName)
|
||||
.Select(group => group.First())
|
||||
.ToDictionary(type => type.FullName, type => new Action(() => SelectType(property, type)));
|
||||
}
|
||||
|
||||
return TypeCache.GetTypesDerivedFrom(derivedFrom)
|
||||
.AppendIf(derivedFrom, attr?.includeSelf ?? false)
|
||||
.Where(type => type.IsClass && type.IsPublic && !type.IsGenericType)
|
||||
.Where(type => (attr?.allowAbstract ?? false) || !type.IsAbstract)
|
||||
.GroupBy(type => type.FullName)
|
||||
.Select(group => group.First())
|
||||
.ToDictionary(type => type.FullName, type => new Action(() => SelectType(property, type)));
|
||||
}
|
||||
|
||||
private void SelectType(SerializedProperty property, Type type)
|
||||
{
|
||||
property.boxedValue = new SerializableType(type);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71afe6e8d45f4770b749f120130fa18a
|
||||
timeCreated: 1762517198
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39ab4f14276441b6afcbfcdf67f90a28
|
||||
timeCreated: 1762713444
|
||||
@@ -0,0 +1,21 @@
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(LayerAttribute))]
|
||||
public class LayerDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
if (property.boxedValue is not int)
|
||||
return new Label($"Field '{property.displayName}' is not INTEGER. {nameof(LayerAttribute)} can be used only on <b>INTEGER</b> field.");
|
||||
|
||||
var layerField = new LayerField(property.displayName);
|
||||
layerField.BindProperty(property);
|
||||
return layerField;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a3af142730740288c3078e34cd62718
|
||||
timeCreated: 1762713459
|
||||
@@ -0,0 +1,21 @@
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(LayerMaskAttribute))]
|
||||
public class LayerMaskDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
if (property.boxedValue is not int)
|
||||
return new Label($"Field '{property.displayName}' is not INTEGER. {nameof(LayerMaskAttribute)} can be used only on <b>INTEGER</b> field.");
|
||||
|
||||
var layerMaskField = new LayerMaskField(property.displayName);
|
||||
layerMaskField.BindProperty(property);
|
||||
return layerMaskField;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1a4f0b89e344d8a9aefc5767fdb860c
|
||||
timeCreated: 1762714412
|
||||
@@ -0,0 +1,19 @@
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
var propertyField = new PropertyField(property);
|
||||
propertyField.name = "ReadOnly";
|
||||
propertyField.RegisterCallback<GeometryChangedEvent>(_ => propertyField.SetEnabled(false));
|
||||
return propertyField;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6de1b490a8549d3a67957f5a2a8a03e
|
||||
timeCreated: 1764603191
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SerializeReferenceHelperAttribute))]
|
||||
public class SerializeReferenceHelperDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ffb1bb18c6d4a7a889c32c17d78a39c
|
||||
timeCreated: 1773956371
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.Helpers.Editor",
|
||||
"rootNamespace": "RPGCoreCommon.Helpers.Editor",
|
||||
"references": [
|
||||
"RPGCoreCommon.Helpers"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4506747573fa44a9877e5bf69f5bb4a8
|
||||
timeCreated: 1759335131
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc2af81cae08451abf2593fd5fb18538
|
||||
timeCreated: 1761410197
|
||||
@@ -0,0 +1,66 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.UIElements
|
||||
{
|
||||
[UxmlElement]
|
||||
public partial class FakeObjectField : VisualElement
|
||||
{
|
||||
private Image _objectImage;
|
||||
private Label _objectLabel;
|
||||
|
||||
public FakeObjectField() : this(null) { }
|
||||
|
||||
public FakeObjectField(string labelText = "")
|
||||
{
|
||||
var field = new VisualElement();
|
||||
field.AddToClassList("unity-object-field");
|
||||
field.AddToClassList("unity-base-field");
|
||||
field.AddToClassList("unity-base-field__aligned");
|
||||
field.style.marginLeft = field.style.marginRight = 3;
|
||||
|
||||
if (!string.IsNullOrEmpty(labelText))
|
||||
{
|
||||
var label = new Label(labelText);
|
||||
label.AddToClassList("unity-label");
|
||||
label.AddToClassList("unity-base-field__label");
|
||||
field.Add(label);
|
||||
}
|
||||
|
||||
var fieldInput = new VisualElement();
|
||||
fieldInput.AddToClassList("unity-base-field__input");
|
||||
fieldInput.AddToClassList("unity-object-field__input");
|
||||
field.Add(fieldInput);
|
||||
|
||||
var fieldObject = new VisualElement();
|
||||
fieldObject.AddToClassList("unity-object-field__object");
|
||||
fieldInput.Add(fieldObject);
|
||||
|
||||
_objectImage = new Image();
|
||||
_objectImage.AddToClassList("unity-object-field-display__icon");
|
||||
fieldObject.Add(_objectImage);
|
||||
|
||||
_objectLabel = new Label();
|
||||
_objectLabel.AddToClassList("unity-object-field-display__label");
|
||||
fieldObject.Add(_objectLabel);
|
||||
|
||||
var fieldSelector = new VisualElement();
|
||||
fieldSelector.AddToClassList("unity-object-field__selector");
|
||||
fieldInput.Add(fieldSelector);
|
||||
|
||||
Add(field);
|
||||
}
|
||||
|
||||
public void Set(string text, Texture image)
|
||||
{
|
||||
_objectLabel.text = text;
|
||||
_objectImage.image = image;
|
||||
}
|
||||
|
||||
public void Unset()
|
||||
{
|
||||
_objectLabel.text = null;
|
||||
_objectImage.image = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42c0552c878947b394d70f8745d0ec3b
|
||||
timeCreated: 1762538499
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.UIElements
|
||||
{
|
||||
[UxmlElement]
|
||||
public partial class InspectorElement : UnityEditor.UIElements.InspectorElement
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a2d499847854192a17c936d66e234b7
|
||||
timeCreated: 1761410271
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.UIElements
|
||||
{
|
||||
public class SearchFieldPopup : PopupWindowContent
|
||||
{
|
||||
// Elements
|
||||
private VisualElement _root;
|
||||
private ToolbarSearchField _searchField;
|
||||
private ScrollView _scrollView;
|
||||
|
||||
// Runtime
|
||||
private VisualElement _boundTo;
|
||||
private Dictionary<string, Action> _menuItems;
|
||||
|
||||
public SearchFieldPopup(VisualElement boundTo, Dictionary<string, Action> menuItems)
|
||||
{
|
||||
_boundTo = boundTo;
|
||||
_menuItems = menuItems;
|
||||
}
|
||||
|
||||
public override VisualElement CreateGUI()
|
||||
{
|
||||
_root = new VisualElement();
|
||||
_root.style.maxHeight = 500;
|
||||
_root.style.maxWidth = _boundTo.worldBound.width;
|
||||
|
||||
_searchField = new ToolbarSearchField();
|
||||
_searchField.style.left = _searchField.style.right = 0;
|
||||
_searchField.style.width = StyleKeyword.Auto;
|
||||
|
||||
_root.Add(_searchField);
|
||||
|
||||
_scrollView = new ScrollView();
|
||||
_root.Add(_scrollView);
|
||||
|
||||
// Show all options
|
||||
_menuItems.DictSelect((name, action) => new Button(() =>
|
||||
{
|
||||
action();
|
||||
Close();
|
||||
}) { text = name }).ForEach(_scrollView.Add);
|
||||
|
||||
// Do auto focus when appear
|
||||
_root.RegisterCallback<AttachToPanelEvent>(_ => _searchField.Focus());
|
||||
|
||||
// ESC - close
|
||||
// ENTER - select first visible
|
||||
_root.RegisterCallback<KeyDownEvent>(ev =>
|
||||
{
|
||||
if (ev.keyCode == KeyCode.Escape) Close();
|
||||
if (ev.keyCode == KeyCode.Return) _scrollView.Query<Button>().Visible().First().Click();
|
||||
}, TrickleDown.TrickleDown);
|
||||
|
||||
return _root;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
UnityEditor.PopupWindow.Show(_boundTo.worldBound, this);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
editorWindow.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adea6e28eb7a41d691381ba9405fdb74
|
||||
timeCreated: 1762522548
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dc28cb462b84ae1a7779750a9fae9a6
|
||||
timeCreated: 1759334627
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d99760e987ce41308cdba70a87f65816
|
||||
timeCreated: 1762188549
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.CustomTypes
|
||||
{
|
||||
[Serializable]
|
||||
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField] internal TKey _tempKey;
|
||||
[SerializeField] internal List<TKey> _keys = new();
|
||||
[SerializeReference] internal List<TValue> _values = new();
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
_keys = Keys.ToList();
|
||||
_values = Values.ToList();
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
Clear();
|
||||
for (var i = 0; i < _keys.Count; i++)
|
||||
{
|
||||
if (_keys[i] != null) Add(_keys[i], _values[i]);
|
||||
}
|
||||
|
||||
_keys.Clear();
|
||||
_values.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24246e0e86ad4108b4d1b8515aeca9e9
|
||||
timeCreated: 1762188557
|
||||
@@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.CustomTypes
|
||||
{
|
||||
public class SerializableDictionaryAttribute : PropertyAttribute
|
||||
{
|
||||
public string keyName { get; private set; } = "KEY";
|
||||
public string valueName { get; private set; } = "VALUE";
|
||||
public bool isKeyEditable { get; private set; } = true;
|
||||
|
||||
public SerializableDictionaryAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SerializableDictionaryAttribute(string keyName, string valueName, bool isKeyEditable = true)
|
||||
{
|
||||
this.keyName = keyName;
|
||||
this.valueName = valueName;
|
||||
this.isKeyEditable = isKeyEditable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4615aa55725644e48fc7cf85bd9c4ba0
|
||||
timeCreated: 1762345454
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.CustomTypes
|
||||
{
|
||||
[Serializable]
|
||||
public class SerializableType : ISerializationCallbackReceiver, ICloneable
|
||||
{
|
||||
public Type type { get; internal set; }
|
||||
[SerializeField] internal string assemblyQualifiedName;
|
||||
|
||||
public SerializableType(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
assemblyQualifiedName = type?.AssemblyQualifiedName;
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
assemblyQualifiedName = type?.AssemblyQualifiedName ?? assemblyQualifiedName;
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
if (string.IsNullOrEmpty(assemblyQualifiedName) == false)
|
||||
type = Type.GetType(assemblyQualifiedName, false);
|
||||
}
|
||||
|
||||
public SerializableType Clone() => new(type);
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
public static implicit operator Type(SerializableType serializableType) => serializableType.type;
|
||||
|
||||
public static implicit operator SerializableType(Type type) => new(type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e60b347531c46aaa17029cd96c78bf4
|
||||
timeCreated: 1762516011
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.CustomTypes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class SerializableTypeAttribute : PropertyAttribute
|
||||
{
|
||||
public bool includeSelf { get; private set; }
|
||||
public bool allowAbstract { get; private set; }
|
||||
public Type derivedFrom { get; private set; }
|
||||
public Type[] limitedTo { get; private set; }
|
||||
|
||||
public SerializableTypeAttribute(Type derivedFrom, bool includeSelf = true, bool allowAbstract = false)
|
||||
{
|
||||
this.derivedFrom = derivedFrom;
|
||||
this.includeSelf = includeSelf;
|
||||
this.allowAbstract = allowAbstract;
|
||||
}
|
||||
|
||||
public SerializableTypeAttribute(Type[] limitedTo)
|
||||
{
|
||||
this.limitedTo = limitedTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea3922244e274799a4113964b9da4f83
|
||||
timeCreated: 1762517259
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1554117a8b14cbdb719f40f40ea71f2
|
||||
timeCreated: 1759334728
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Exceptions
|
||||
{
|
||||
public class MemberNotFoundException : Exception
|
||||
{
|
||||
public readonly string path;
|
||||
public readonly Type objType;
|
||||
|
||||
public MemberNotFoundException(string path, Type objType)
|
||||
: base($"Property '{path}' not found in '{objType}'")
|
||||
{
|
||||
this.path = path;
|
||||
this.objType = objType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 418add21a24b4cee9309453d1ada1530
|
||||
timeCreated: 1757322594
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Exceptions
|
||||
{
|
||||
public class MemberValueWrongTypeException : Exception
|
||||
{
|
||||
public readonly string path;
|
||||
public readonly Type objType;
|
||||
public readonly Type expectedType;
|
||||
public readonly Type wrongType;
|
||||
|
||||
public MemberValueWrongTypeException(string path, Type objType, Type expectedType, Type wrongType)
|
||||
: base($"Property '{path}' in {objType} is of type '{wrongType}', expected type '{expectedType}'")
|
||||
{
|
||||
this.path = path;
|
||||
this.objType = objType;
|
||||
this.expectedType = expectedType;
|
||||
this.wrongType = wrongType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7594d527fc654804a2c564b71fe756bd
|
||||
timeCreated: 1757322677
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Exceptions
|
||||
{
|
||||
public class NotEnoughElementsForRandomPickException : Exception
|
||||
{
|
||||
public NotEnoughElementsForRandomPickException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25fedc34477644f8b9df4ddcf4c0b701
|
||||
timeCreated: 1756991204
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("RPGCoreCommon.Helpers.Editor")]
|
||||
|
||||
namespace RPGCoreCommon.Helpers
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fab5a3ff08dd4b35b7d68f496c013e8a
|
||||
timeCreated: 1762190797
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace RPGCoreCommon.Helpers
|
||||
{
|
||||
public static class LinqExtensions
|
||||
{
|
||||
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
foreach (var element in source) action.Invoke(element);
|
||||
return source;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<int, T> action)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var element in source) action(index++, element);
|
||||
return source;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Sort<T>(this IEnumerable<T> source, Comparison<T> comparison)
|
||||
{
|
||||
var list = source.ToList();
|
||||
list.Sort(comparison);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Sort<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return source.OrderBy(x => x);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> SortDescending<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return source.OrderByDescending(x => x);
|
||||
}
|
||||
|
||||
public static IEnumerable<TResult> DictSelect<TKey, TValue, TResult>(this IDictionary<TKey, TValue> source, Func<TKey, TValue, TResult> func)
|
||||
{
|
||||
foreach (var pair in source) yield return func(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
public static IDictionary<TKey,TValue> DictForEach<TKey, TValue>(this IDictionary<TKey,TValue> source, Action<TKey, TValue> action)
|
||||
{
|
||||
foreach (var pair in source) action(pair.Key, pair.Value);
|
||||
return source;
|
||||
}
|
||||
|
||||
public static string StringJoin(this IEnumerable<string> source, string separator)
|
||||
{
|
||||
return string.Join(separator, source);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> AppendIf<T>(this IEnumerable<T> source, T element, bool condition)
|
||||
{
|
||||
return condition ? source.Append(element) : source;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ac97972c1444fa0aaebfdb837a58fc2
|
||||
timeCreated: 1759338756
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be7897ed56a743f98832cf13a5f9f866
|
||||
timeCreated: 1762713378
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.PropertyAttributeDrawers
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class LayerAttribute : PropertyAttribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3349d2cb583b4a56805895c7eb808a72
|
||||
timeCreated: 1762713403
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.PropertyAttributeDrawers
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class LayerMaskAttribute : PropertyAttribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfd2f0c410cf43b38901fd4bc70bb606
|
||||
timeCreated: 1762714485
|
||||
@@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.PropertyAttributeDrawers
|
||||
{
|
||||
public class ReadOnlyAttribute : PropertyAttribute
|
||||
{
|
||||
public ReadOnlyAttribute(bool applyToCollection = false) : base(applyToCollection)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 544dc97fbe8f4c258ef3a72e450297eb
|
||||
timeCreated: 1764603113
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.PropertyAttributeDrawers
|
||||
{
|
||||
public class SerializeReferenceHelperAttribute : PropertyAttribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67561284ae71463fbd4a1ec370d0fafd
|
||||
timeCreated: 1773956312
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.Helpers",
|
||||
"rootNamespace": "RPGCoreCommon.Helpers",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfaf89ec9b1449debf236030e69884d6
|
||||
timeCreated: 1759335092
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RPGCoreCommon.Helpers.Exceptions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// This utility class basses on Unity's <see cref="UnityEngine.Random"/> which uses:
|
||||
/// <ul>
|
||||
/// <li><see cref="UnityEngine.Random.InitState"/> to manage seed</li>
|
||||
/// <li><see cref="UnityEngine.Random.value"/> to get random value</li>
|
||||
/// </ul>
|
||||
/// </summary>
|
||||
public static class RandomHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="UnityEngine.Random.InitState"/>
|
||||
/// Alias to Unity's <see cref="UnityEngine.Random.InitState"/>.
|
||||
/// </summary>
|
||||
/// <param name="seed"><inheritdoc cref="UnityEngine.Random.InitState" path="/param[@name='seed']"/></param>
|
||||
public static void InitState(int seed)
|
||||
{
|
||||
UnityEngine.Random.InitState(seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <b>float</b> between min (inclusive) and max (inclusive).
|
||||
/// </summary>
|
||||
public static float RandomFloat(float min = 0f, float max = 1f)
|
||||
{
|
||||
return UnityEngine.Random.value * (max-min) + min;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns integer between minimum (inclusive) and maximum (inclusive).
|
||||
/// </summary>
|
||||
public static int RandomInt(int minInclusive = 0, int maxInclusive = 100)
|
||||
{
|
||||
return Mathf.RoundToInt(UnityEngine.Random.value * (maxInclusive-minInclusive)) + minInclusive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random key from given list.
|
||||
/// </summary>
|
||||
public static int RandomKey<T>(IEnumerable<T> enumerable)
|
||||
{
|
||||
var list = enumerable.ToList();
|
||||
if (!list.Any()) throw new NotEnoughElementsForRandomPickException("List need at least one element.");
|
||||
|
||||
return RandomInt(0, list.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random element from given list.
|
||||
/// </summary>
|
||||
public static T RandomElement<T>(IEnumerable<T> enumerable)
|
||||
{
|
||||
return RandomElements(enumerable, 1).First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns new list with randomized elements.<br/>
|
||||
/// List needs to have at least equal or more elements that given count! If repeatable then list needs to have at least 1 element.<br/>
|
||||
/// </summary>
|
||||
/// <exception cref="NotEnoughElementsForRandomPickException">When given list does not have enough elements to choose from.</exception>
|
||||
public static List<T> RandomElements<T>(IEnumerable<T> enumerable, int count, bool repeatable = true)
|
||||
{
|
||||
var list = enumerable.ToList();
|
||||
var neededCount = repeatable ? 1 : list.Count;
|
||||
if (list.Count < neededCount) throw new NotEnoughElementsForRandomPickException($"Not enough elements in list. Need={neededCount}, Provided={list.Count}");
|
||||
|
||||
if (!repeatable) return list.OrderBy(_ => RandomFloat()).Take(count).ToList();
|
||||
|
||||
return new T[count].Select(_ => list[RandomInt(0, list.Count - 1)]).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns keys from given list by its weight.
|
||||
/// </summary>
|
||||
/// <exception cref="NotEnoughElementsForRandomPickException">When given list does not have any elements to choose from.</exception>
|
||||
public static int RandomKeyByWeight<T>(IEnumerable<T> enumerable, Func<T, float> weightGetter)
|
||||
{
|
||||
var list = enumerable.ToList();
|
||||
if (!list.Any()) throw new NotEnoughElementsForRandomPickException("List need at least one element.");
|
||||
var maxWeight = list.Sum(weightGetter.Invoke);
|
||||
var randomWeight = RandomFloat(0, maxWeight);
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
randomWeight -= weightGetter.Invoke(list[i]);
|
||||
if (randomWeight <= 0) return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns element from given list by its weight.
|
||||
/// </summary>
|
||||
/// <exception cref="NotEnoughElementsForRandomPickException">When given list does not have any elements to choose from.</exception>
|
||||
public static T RandomElementByWeight<T>(IEnumerable<T> enumerable, Func<T, float> weightGetter)
|
||||
{
|
||||
return RandomElementsByWeight(enumerable, weightGetter, 1).First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns elements from given list by its weight.
|
||||
/// </summary>
|
||||
/// <exception cref="NotEnoughElementsForRandomPickException">When given list does not have enough elements to choose from.</exception>
|
||||
public static List<T> RandomElementsByWeight<T>(IEnumerable<T> enumerable, Func<T, float> weightGetter, int count, bool repeatable = true)
|
||||
{
|
||||
var list = enumerable.ToList();
|
||||
var listResult = new List<T>();
|
||||
var neededCount = repeatable ? 1 : list.Count;
|
||||
if (list.Count < neededCount) throw new NotEnoughElementsForRandomPickException($"Not enough elements in list. Need={neededCount}, Provided={list.Count}");
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var randomKey = RandomKeyByWeight(list, weightGetter);
|
||||
listResult.Add(list[randomKey]);
|
||||
if (!repeatable) list.RemoveAt(randomKey);
|
||||
}
|
||||
|
||||
return listResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Percentage chance by given float. Simply checks if random float is smaller than given one.
|
||||
/// </summary>
|
||||
/// <param name="chance">Chance to get true. <b>0.0f</b> = 0%, <b>1.0f</b> = 100%</param>
|
||||
public static bool Chance(float chance)
|
||||
{
|
||||
return RandomFloat() < Mathf.Clamp01(chance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd6d508bd1616174fb1350b8e4d3aebe
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using RPGCoreCommon.Helpers.Exceptions;
|
||||
|
||||
namespace RPGCoreCommon.Helpers
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
public const BindingFlags DefaultBindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
|
||||
|
||||
/// <summary>Works with any member (type, field, property, method)</summary>
|
||||
/// <returns>type of this member's value</returns>
|
||||
public static Type GetMemberValueType(this MemberInfo memberInfo)
|
||||
{
|
||||
return memberInfo switch
|
||||
{
|
||||
Type type => type,
|
||||
FieldInfo fieldInfo => fieldInfo.FieldType,
|
||||
PropertyInfo propertyInfo => propertyInfo.PropertyType,
|
||||
MethodInfo methodInfo => methodInfo.ReturnType,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Works with any member (type, field, property, method)</summary>
|
||||
/// <param name="memberInfo">this member</param>
|
||||
/// <param name="obj">Object to use for this reflection</param>
|
||||
/// <returns>member's value</returns>
|
||||
public static object GetMemberValue(this MemberInfo memberInfo, object obj)
|
||||
{
|
||||
return memberInfo switch
|
||||
{
|
||||
Type => obj,
|
||||
FieldInfo fi => fi.GetValue(obj),
|
||||
PropertyInfo pi => pi.GetValue(obj, null),
|
||||
MethodInfo mi => mi.Invoke(obj, null),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Works with any member (type, field, property, method)</summary>
|
||||
/// <param name="memberInfo">this member</param>
|
||||
/// <param name="path">path from current member with dot "." as delimiter</param>
|
||||
/// <param name="bindingFlags">Custom binding flags to use</param>
|
||||
/// <returns>TYPE of member's value from given path</returns>
|
||||
/// <exception cref="MemberNotFoundException">when member is not found</exception>
|
||||
public static Type GetMemberValueTypeByPath(this MemberInfo memberInfo, string path, BindingFlags bindingFlags = DefaultBindingFlags)
|
||||
{
|
||||
var pathParts = path.Split('.');
|
||||
var memberType = memberInfo.GetMemberValueType();
|
||||
|
||||
foreach (var pathPart in pathParts)
|
||||
{
|
||||
var member = memberType.GetMember(pathPart, bindingFlags).FirstOrDefault();
|
||||
if (member == null) throw new MemberNotFoundException(path, memberType.GetMemberValueType());
|
||||
memberType = member.GetMemberValueType();
|
||||
}
|
||||
|
||||
return memberType;
|
||||
}
|
||||
|
||||
/// <summary>This function supports all members: type, property, field, method</summary>
|
||||
/// <param name="memberInfo">Type (or any memberInfo) that will be used to get result value. When not provided memberInfo will be taken from obj type</param>
|
||||
/// <param name="obj">Object from which finding will be performed</param>
|
||||
/// <param name="path">path from current member with dot "." as delimiter</param>
|
||||
/// <param name="bindingFlags">Custom binding flags to use</param>
|
||||
/// <returns>member's value from given path</returns>
|
||||
/// <exception cref="MemberNotFoundException">When property at given path doesn't exist</exception>
|
||||
public static object GetMemberValueByPath(this MemberInfo memberInfo, object obj, string path, BindingFlags bindingFlags = DefaultBindingFlags)
|
||||
{
|
||||
var pathParts = path.Split('.');
|
||||
var memberType = memberInfo.GetMemberValueType();
|
||||
var memberObj = obj;
|
||||
|
||||
foreach (var pathPart in pathParts)
|
||||
{
|
||||
var member = memberType.GetMember(pathPart, bindingFlags).FirstOrDefault();
|
||||
if (member == null) throw new MemberNotFoundException(path, memberType.GetMemberValueType());
|
||||
memberType = member.GetMemberValueType();
|
||||
memberObj = memberType.GetMemberValue(memberObj);
|
||||
}
|
||||
|
||||
return memberObj;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetMemberValueByPath(MemberInfo, object, string, BindingFlags)"/>
|
||||
/// <exception cref="MemberValueWrongTypeException">When result is not desired type</exception>
|
||||
/// <typeparam name="T">Result type</typeparam>
|
||||
public static T GetMemberValueByPath<T>(this MemberInfo memberInfo, object obj, string path, BindingFlags bindingFlags = DefaultBindingFlags)
|
||||
{
|
||||
var result = GetMemberValueByPath(memberInfo, obj, path, bindingFlags);
|
||||
|
||||
if (result is T typedResult) return typedResult;
|
||||
|
||||
throw new MemberValueWrongTypeException(path, obj.GetType(), typeof(T), result.GetType());
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetMemberValueByPath(MemberInfo, object, string, BindingFlags)"/>
|
||||
public static object GetMemberValueByPath(object obj, string path, BindingFlags bindingFlags = DefaultBindingFlags)
|
||||
{
|
||||
return GetMemberValueByPath(obj.GetType(), obj, path, bindingFlags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find specific generic type in currently implementation.
|
||||
/// </summary>
|
||||
/// <param name="type">This type</param>
|
||||
/// <param name="genericTypeDefinition">Generic type to find</param>
|
||||
/// <returns>Found generic type with parameters</returns>
|
||||
/// <exception cref="Exception">Generic type to find isn't generic.</exception>
|
||||
public static Type FindGenericDefinitionType(this Type type, Type genericTypeDefinition)
|
||||
{
|
||||
if (!genericTypeDefinition.IsGenericTypeDefinition) throw new Exception("The generic argument must be a generic type definition.");
|
||||
|
||||
var tempType = type;
|
||||
while (tempType != null && tempType != typeof(object))
|
||||
{
|
||||
if (tempType.IsGenericType && tempType.GetGenericTypeDefinition() == genericTypeDefinition) return tempType;
|
||||
tempType = tempType.BaseType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if specific generic type in currently implementation has type as argument at index.
|
||||
/// </summary>
|
||||
/// <param name="type">this type</param>
|
||||
/// <param name="genericTypeDefinition">Generic type to find</param>
|
||||
/// <param name="argIndex">index to check</param>
|
||||
/// <param name="argType">type in generic implementation</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsArgTypeInGenericDefinitionType(this Type type, Type genericTypeDefinition, int argIndex, Type argType)
|
||||
{
|
||||
return type.FindGenericDefinitionType(genericTypeDefinition).GenericTypeArguments.ElementAtOrDefault(argIndex) == argType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this member has public read accessor.
|
||||
/// </summary>
|
||||
/// <param name="memberInfo">this member</param>
|
||||
/// <returns>TRUE if member exists and is public</returns>
|
||||
public static bool IsMemberReadPublic(this MemberInfo memberInfo)
|
||||
{
|
||||
return memberInfo switch
|
||||
{
|
||||
Type type => type.IsPublic,
|
||||
PropertyInfo pi => pi.GetGetMethod() != null,
|
||||
MethodInfo mi => mi.IsPublic,
|
||||
FieldInfo fi => fi.IsPublic,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetAllTypes(this Type forType, bool withObject = false)
|
||||
{
|
||||
var tempType = forType;
|
||||
while (tempType is not null)
|
||||
{
|
||||
yield return tempType;
|
||||
tempType = tempType.BaseType;
|
||||
if (!withObject && tempType == typeof(object)) yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8692e0796364209851d145aefa07e1c
|
||||
timeCreated: 1759334645
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5365711d18c94e37abd7c37755b59188
|
||||
timeCreated: 1761335420
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.UIElements
|
||||
{
|
||||
[UxmlElement]
|
||||
public partial class HelpBox : UnityEngine.UIElements.HelpBox
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f50a07c4f534d81a419229e92605e27
|
||||
timeCreated: 1761340341
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.UIElements
|
||||
{
|
||||
[UxmlElement]
|
||||
public partial class TwoPaneSplitView : UnityEngine.UIElements.TwoPaneSplitView
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c45b936017280542bca4e473d23d685
|
||||
@@ -0,0 +1,78 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.Helpers
|
||||
{
|
||||
public static class VectorExtensions
|
||||
{
|
||||
public static Vector3 Scale(this Vector3 vector, float scale)
|
||||
{
|
||||
return new Vector3(vector.x * scale, vector.y * scale, vector.z * scale);
|
||||
}
|
||||
|
||||
public static Vector3 Scale(this Vector3 vector, float x, float y, float z)
|
||||
{
|
||||
return new Vector3(vector.x * x, vector.y * y, vector.z * z);
|
||||
}
|
||||
|
||||
public static Vector3 Add(this Vector3 vector, float x, float y, float z)
|
||||
{
|
||||
return vector.AddX(x).AddY(y).AddZ(z);
|
||||
}
|
||||
|
||||
public static Vector3 AddX(this Vector3 vector, float x)
|
||||
{
|
||||
vector.x += x;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3 AddY(this Vector3 vector, float y)
|
||||
{
|
||||
vector.y += y;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3 AddZ(this Vector3 vector, float z)
|
||||
{
|
||||
vector.z += z;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3 Set(this Vector3 vector, float x, float y, float z)
|
||||
{
|
||||
return vector.SetX(x).SetY(y).SetZ(z);
|
||||
}
|
||||
|
||||
public static Vector3 SetX(this Vector3 vector, float x)
|
||||
{
|
||||
vector.x = x;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3 SetY(this Vector3 vector, float y)
|
||||
{
|
||||
vector.y = y;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3 SetZ(this Vector3 vector, float z)
|
||||
{
|
||||
vector.z = z;
|
||||
return vector;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static Vector3 ToVector3_XZ(this Vector2 vector)
|
||||
{
|
||||
return new Vector3(vector.x, 0, vector.y);
|
||||
}
|
||||
|
||||
public static Vector3 ToVector3_XY(this Vector2 vector)
|
||||
{
|
||||
return new Vector3(vector.x, vector.y, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b93c4334a254d6a9efa9ade68eb4572
|
||||
timeCreated: 1766008109
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers
|
||||
{
|
||||
public static class VisualElementExtensions
|
||||
{
|
||||
public static void Click(this VisualElement visualElement)
|
||||
{
|
||||
using var clickEvent = ClickEvent.GetPooled();
|
||||
clickEvent.target = visualElement;
|
||||
visualElement.panel.visualTree.SendEvent(clickEvent);
|
||||
if (visualElement is not Button button) return;
|
||||
using var ev = new NavigationSubmitEvent();
|
||||
ev.target = button;
|
||||
button.SendEvent(ev);
|
||||
}
|
||||
|
||||
public static void DisableDefaultAligning(this VisualElement visualElement)
|
||||
{
|
||||
const string class1 = "unity-base-field__inspector-field";
|
||||
const string class2 = "unity-base-field__aligned";
|
||||
visualElement.RegisterCallback<GeometryChangedEvent>(_ =>
|
||||
{
|
||||
visualElement.Query(className: class1).ForEach(el => el.RemoveFromClassList(class1));
|
||||
visualElement.Query(className: class2).ForEach(el => el.RemoveFromClassList(class2));
|
||||
}, TrickleDown.TrickleDown);
|
||||
}
|
||||
|
||||
public static void SetContextAligningHere(this VisualElement visualElement)
|
||||
{
|
||||
visualElement.AddToClassList("unity-inspector-element");
|
||||
visualElement.AddToClassList("unity-inspector-main-container");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only for <see cref="PropertyField"/> that has array element as <see cref="SerializedProperty"/>.
|
||||
/// By default, those <see cref="PropertyField"/>s always draw inside <see cref="Foldout"/>.
|
||||
/// When this function is called only content of foldout will be displayed (foldout will be hidden).
|
||||
/// </summary>
|
||||
/// <param name="propertyField">Property field that will draw array element</param>
|
||||
public static void HideFoldoutAndLabel(this PropertyField propertyField)
|
||||
{
|
||||
propertyField.RegisterCallback<GeometryChangedEvent>(_ =>
|
||||
{
|
||||
propertyField.label = "";
|
||||
if (propertyField.Q<Foldout>(name: "unity-foldout-" + propertyField.bindingPath) is {} foldout2)
|
||||
{
|
||||
foldout2.Q<Toggle>().style.display = DisplayStyle.None;
|
||||
foldout2.Q<Toggle>().value = true;
|
||||
foldout2.Q(name: "unity-content").style.marginLeft = 0;
|
||||
}
|
||||
else if (propertyField.Q<Label>(name: "unity-input-" + propertyField.bindingPath) is {} label)
|
||||
{
|
||||
label.style.display = DisplayStyle.None;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void RemoveFoldoutAndLabel(this PropertyField propertyField)
|
||||
{
|
||||
propertyField.RegisterCallback<GeometryChangedEvent>(_ =>
|
||||
{
|
||||
if (propertyField.Q<Foldout>(name: "unity-foldout-" + propertyField.bindingPath) is {} foldout)
|
||||
{
|
||||
var children = foldout.Q(name: "unity-content").Children().ToList();
|
||||
children.ForEach(propertyField.Add);
|
||||
foldout.RemoveFromHierarchy();
|
||||
}
|
||||
else if (propertyField.Q<Label>(name: "unity-input-" + propertyField.bindingPath) is {} label)
|
||||
{
|
||||
label.RemoveFromHierarchy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void FadeOut(this VisualElement visualElement, float delay, float duration, bool remove)
|
||||
{
|
||||
if (visualElement.panel != null)
|
||||
FadeOutPrivate(visualElement, delay, duration, remove);
|
||||
else
|
||||
visualElement.RegisterCallback<AttachToPanelEvent>(_ => FadeOutPrivate(visualElement, delay, duration, remove));
|
||||
|
||||
}
|
||||
|
||||
private static void FadeOutPrivate(VisualElement visualElement, float delayMs, float durationMs, bool remove)
|
||||
{
|
||||
const int waitMs = 50;
|
||||
visualElement.style.opacity = 1f;
|
||||
visualElement.schedule
|
||||
.Execute(_ =>
|
||||
{
|
||||
visualElement.style.opacity = visualElement.style.opacity.value - waitMs / durationMs;
|
||||
if (visualElement.style.opacity.value > 0f) return;
|
||||
visualElement.style.opacity = 0f;
|
||||
if (remove) visualElement.RemoveFromHierarchy();
|
||||
})
|
||||
.Every(waitMs)
|
||||
.Until(() => visualElement.style.opacity.value <= 0f)
|
||||
.ExecuteLater(Convert.ToInt64(delayMs));
|
||||
}
|
||||
|
||||
public static void RemoveOnClick(this VisualElement visualElement, float delayMs = 0f, float durationMs = 250f)
|
||||
{
|
||||
visualElement.RegisterCallback<ClickEvent>(_ => FadeOutPrivate(visualElement, delayMs, durationMs, true));
|
||||
}
|
||||
|
||||
public static void InsertBefore(this VisualElement thisElement, VisualElement otherElement)
|
||||
{
|
||||
var index = thisElement.parent.IndexOf(thisElement);
|
||||
thisElement.parent.Insert(index, otherElement);
|
||||
}
|
||||
|
||||
public static void InsertAfter(this VisualElement thisElement, VisualElement otherElement)
|
||||
{
|
||||
var index = thisElement.parent.IndexOf(thisElement);
|
||||
if (index == thisElement.parent.childCount-1)
|
||||
thisElement.parent.Add(otherElement);
|
||||
else
|
||||
thisElement.parent.Insert(index, otherElement);
|
||||
}
|
||||
|
||||
public static bool IsAncestorOf(this VisualElement thisElement, VisualElement otherElement)
|
||||
{
|
||||
if (thisElement == null || otherElement == null) return false;
|
||||
|
||||
var ancestor = otherElement.parent;
|
||||
while (ancestor != null)
|
||||
{
|
||||
if (ancestor == thisElement) return true;
|
||||
ancestor = ancestor.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsDescendantOf(this VisualElement thisElement, VisualElement otherElement)
|
||||
{
|
||||
return otherElement.IsAncestorOf(thisElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b3f87689a16482f8bcb2e1869729299
|
||||
timeCreated: 1761508983
|
||||
Reference in New Issue
Block a user