Signed-off-by: TRAfoer <lhf190@outlook.com>
This commit is contained in:
@@ -9,6 +9,7 @@ using TMPro;
|
||||
using System.Reflection;
|
||||
using System.Linq.Expressions;
|
||||
using Sirenix.Utilities;
|
||||
using System.Collections;
|
||||
|
||||
//又在写大粪 ——神币
|
||||
namespace Ichni.Editor
|
||||
@@ -268,7 +269,7 @@ namespace Ichni.Editor
|
||||
.Reference(typeof(Vector3))
|
||||
.Reference(typeof(Vector2));//这是AI给的东西?
|
||||
foreach (MethodInfo i in typeof(EditorConsoleMethods).GetMethods().
|
||||
ToList().Where(i => i.IsStatic && i.IsPublic && i.ReturnType == typeof(void)))
|
||||
ToList().Where(i => i.IsStatic && i.IsPublic && (i.ReturnType == typeof(void))))
|
||||
{
|
||||
var parameters = i.GetParameters().Select(p => p.ParameterType).ToArray();
|
||||
var delegateType = Expression.GetDelegateType(parameters.Concat(new[] { i.ReturnType }).ToArray());
|
||||
|
||||
@@ -229,26 +229,26 @@ namespace Ichni.Editor
|
||||
switch (action)
|
||||
{
|
||||
case "Tap":
|
||||
Tap a = Tap.GenerateElement("New Tap", Guid.NewGuid(), new List<string>(), false, findTrack(id), timestamp);
|
||||
Tap a = Tap.GenerateElement("New Tap", Guid.NewGuid(), new List<string>(), true, findTrack(id), timestamp);
|
||||
((TransformSubmodule)a.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0);
|
||||
a.noteVisual.SetEditorSubmodules(); // 设置selset
|
||||
a.Refresh();
|
||||
break;
|
||||
case "Stay":
|
||||
|
||||
Stay b = Stay.GenerateElement("New Stay", Guid.NewGuid(), new List<string>(), false, findTrack(id), timestamp);
|
||||
Stay b = Stay.GenerateElement("New Stay", Guid.NewGuid(), new List<string>(), true, findTrack(id), timestamp);
|
||||
((TransformSubmodule)b.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0);
|
||||
b.noteVisual.SetEditorSubmodules(); // 设置selset
|
||||
b.Refresh();
|
||||
break;
|
||||
case "Hold":
|
||||
Hold c = Hold.GenerateElement("New Hold", Guid.NewGuid(), new List<string>(), false, findTrack(id), timestamp, timestamp + holdDuration);
|
||||
Hold c = Hold.GenerateElement("New Hold", Guid.NewGuid(), new List<string>(), true, findTrack(id), timestamp, timestamp + holdDuration);
|
||||
((TransformSubmodule)c.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0);
|
||||
c.noteVisual.SetEditorSubmodules(); // 设置selset
|
||||
c.Refresh();
|
||||
break;
|
||||
case "Flick":
|
||||
Flick d = Flick.GenerateElement("New Flick", Guid.NewGuid(), new List<string>(), false, findTrack(id), timestamp, new List<Vector2>());
|
||||
Flick d = Flick.GenerateElement("New Flick", Guid.NewGuid(), new List<string>(), true, findTrack(id), timestamp, new List<Vector2>());
|
||||
((TransformSubmodule)d.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0);
|
||||
d.noteVisual.SetEditorSubmodules(); // 设置selset
|
||||
d.Refresh();
|
||||
@@ -398,5 +398,61 @@ namespace Ichni.Editor
|
||||
anim.startValue = -anim.startValue;
|
||||
}
|
||||
}
|
||||
public static void AttachNoteInNearestTrail()
|
||||
{
|
||||
Track track = inspector.connectedGameElement as Track;
|
||||
if (track == null)
|
||||
{
|
||||
LogWindow.Log("Please select a Track first!", Color.red);
|
||||
return;
|
||||
}
|
||||
List<NoteBase> noteBases = track.childElementList.OfType<NoteBase>().ToList();
|
||||
List<IHaveTrail> trails = track.GetAllGameElementsFromThis().OfType<IHaveTrail>().ToList();
|
||||
if (trails.Count == 0)
|
||||
{
|
||||
LogWindow.Log("The Track has no Trail!", Color.red);
|
||||
return;
|
||||
}
|
||||
foreach (var note in noteBases)
|
||||
{
|
||||
IHaveTrail nearestTrail = null;
|
||||
Vector3 FinalPos = Vector3.positiveInfinity;
|
||||
foreach (var trail in trails)
|
||||
{
|
||||
if (trail is IHaveTransformSubmodule haveTransform && trail is GameElement gameElement)
|
||||
{
|
||||
Vector3 pos = haveTransform.transformSubmodule.originalPosition;
|
||||
GameElement gameElement1 = gameElement;
|
||||
while (gameElement1 != track)
|
||||
{
|
||||
if (gameElement1 is not IHaveTransformSubmodule)
|
||||
{
|
||||
gameElement1 = gameElement1.parentElement;
|
||||
continue;
|
||||
}
|
||||
List<Displacement> animationBases = gameElement1.childElementList.OfType<Displacement>().ToList();
|
||||
|
||||
foreach (var displacement in animationBases)
|
||||
{
|
||||
pos += displacement.getValue(note.exactJudgeTime);
|
||||
}
|
||||
gameElement1 = gameElement1.parentElement;
|
||||
}
|
||||
if (Vector3.Distance(pos, (note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition) <=
|
||||
Vector3.Distance(FinalPos, (note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition))
|
||||
{
|
||||
nearestTrail = trail;
|
||||
FinalPos = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestTrail != null)
|
||||
{
|
||||
(note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition = FinalPos;
|
||||
note.Refresh();
|
||||
(note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).Refresh();//捏妈妈滴为什么notevisual的TransformSubmodule不刷新
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace Ichni.Editor
|
||||
public Button addFolderButton;
|
||||
public List<HierarchyTab> tabList;
|
||||
public Button expandButtom;
|
||||
public bool NeedExecute = false;
|
||||
private void Awake()
|
||||
{
|
||||
tabList = new List<HierarchyTab>();
|
||||
@@ -158,8 +159,6 @@ namespace Ichni.Editor
|
||||
}
|
||||
void getTabPos(HierarchyTab finalTab)
|
||||
{
|
||||
|
||||
|
||||
// 修正定位算法
|
||||
|
||||
RectTransform tabRect = finalTab.GetComponent<RectTransform>();
|
||||
@@ -185,5 +184,21 @@ namespace Ichni.Editor
|
||||
finalTab.SelectGameElement();
|
||||
|
||||
}
|
||||
public GameElement upLoadElement = null;
|
||||
public IEnumerator TryGetElement()
|
||||
{
|
||||
NeedExecute = true;
|
||||
// 等待直到upLoadElement被赋值
|
||||
if (upLoadElement == null)
|
||||
{
|
||||
yield return new WaitUntil(() => upLoadElement != null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
NeedExecute = false;
|
||||
upLoadElement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,17 @@ namespace Ichni.Editor
|
||||
|
||||
return c;
|
||||
}
|
||||
public void ExecuteOrSelect()
|
||||
{
|
||||
if (EditorManager.instance.uiManager.hierarchy.NeedExecute)
|
||||
{
|
||||
EditorManager.instance.uiManager.hierarchy.upLoadElement = connectedGameElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectGameElement();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectGameElement()
|
||||
{
|
||||
|
||||
@@ -43,7 +43,10 @@ namespace Ichni.RhythmGame
|
||||
UpdateAnimation(EditorManager.instance.songInformation.songTime);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Vector3 getValue(float time)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
/// <summary>
|
||||
/// 施加时间偏移,即移动所有Flexible参数的时间
|
||||
/// </summary>
|
||||
|
||||
@@ -66,7 +66,13 @@ namespace Ichni.RhythmGame
|
||||
animationReturnType = FlexibleReturnType.MiddleInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector3 getValue(float time)
|
||||
{
|
||||
float x = positionX.GetValue(time);
|
||||
float y = positionY.GetValue(time);
|
||||
float z = positionZ.GetValue(time);
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
public override void ApplyTimeOffset(float offset)
|
||||
{
|
||||
base.ApplyTimeOffset(offset);
|
||||
|
||||
@@ -66,7 +66,13 @@ namespace Ichni.RhythmGame
|
||||
animationReturnType = FlexibleReturnType.MiddleInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector3 getValue(float time)
|
||||
{
|
||||
float x = scaleX.GetValue(time);
|
||||
float y = scaleY.GetValue(time);
|
||||
float z = scaleZ.GetValue(time);
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
public override void ApplyTimeOffset(float offset)
|
||||
{
|
||||
base.ApplyTimeOffset(offset);
|
||||
|
||||
@@ -65,7 +65,13 @@ namespace Ichni.RhythmGame
|
||||
animationReturnType = FlexibleReturnType.MiddleInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector3 getValue(float time)
|
||||
{
|
||||
float x = eulerAngleX.GetValue(time);
|
||||
float y = eulerAngleY.GetValue(time);
|
||||
float z = eulerAngleZ.GetValue(time);
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
public override void ApplyTimeOffset(float offset)
|
||||
{
|
||||
base.ApplyTimeOffset(offset);
|
||||
|
||||
@@ -184,7 +184,13 @@ namespace Ichni.RhythmGame
|
||||
currentAnimationIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
public float GetValue(float songtime)
|
||||
{
|
||||
UpdateFlexibleFloat(songtime);
|
||||
float a = value;
|
||||
UpdateFlexibleFloat(EditorManager.instance.songInformation.songTime);
|
||||
return a;
|
||||
}
|
||||
/// <summary>
|
||||
/// 转换为Beatmap存档类型
|
||||
/// </summary>
|
||||
|
||||
@@ -20,13 +20,9 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
Flick flick = Instantiate(EditorManager.instance.basePrefabs.flickNote, parentElement.transform)
|
||||
.GetComponent<Flick>();
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
{
|
||||
isFirstGenerated = false;
|
||||
}
|
||||
|
||||
flick.Initialize(elementName, id, tags, isFirstGenerated, parentElement);
|
||||
|
||||
|
||||
flick.Initialize(elementName, id, tags, EditorManager.instance.useNotePrefab ? false : isFirstGenerated, parentElement);
|
||||
flick.exactJudgeTime = exactJudgeTime;
|
||||
flick.availableFlickDirections = directions;
|
||||
|
||||
@@ -50,10 +46,10 @@ namespace Ichni.RhythmGame
|
||||
flick.track = null;
|
||||
flick.isOnTrack = false;
|
||||
}
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
|
||||
if (EditorManager.instance.useNotePrefab && isFirstGenerated)
|
||||
{
|
||||
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(flick,GetNoteTypeName(flick) + "_Prefab");
|
||||
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(flick, GetNoteTypeName(flick) + "_Prefab");
|
||||
}
|
||||
|
||||
return flick;
|
||||
@@ -65,19 +61,19 @@ namespace Ichni.RhythmGame
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
base.SetDefaultSubmodules();
|
||||
noteAudioSubmodule = new NoteAudioSubmodule(this, "DefaultStay");
|
||||
noteAudioSubmodule ??= new NoteAudioSubmodule(this, "DefaultStay");
|
||||
}
|
||||
|
||||
public override void SaveBM()
|
||||
{
|
||||
matchedBM = new Flick_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
|
||||
matchedBM = new Flick_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
|
||||
exactJudgeTime, availableFlickDirections);
|
||||
}
|
||||
|
||||
|
||||
public override void SetUpInspector()
|
||||
{
|
||||
base.SetUpInspector();
|
||||
|
||||
|
||||
IHaveInspection inspector = EditorManager.instance.uiManager.inspector;
|
||||
|
||||
var flickSpecial = inspector.GenerateContainer("Flick Special");
|
||||
@@ -117,7 +113,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
public override GameElement DuplicateBM(GameElement parent)
|
||||
{
|
||||
return Flick.GenerateElement(elementName, Guid.NewGuid(), tags, false, parent,
|
||||
return Flick.GenerateElement(elementName, Guid.NewGuid(), tags, false, parent,
|
||||
exactJudgeTime, availableFlickDirections);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,9 @@ namespace Ichni.RhythmGame
|
||||
Hold hold = Instantiate(EditorManager.instance.basePrefabs.holdNote, parentElement.transform)
|
||||
.GetComponent<Hold>();
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
{
|
||||
isFirstGenerated = false;
|
||||
}
|
||||
|
||||
hold.Initialize(elementName, id, tags, isFirstGenerated, parentElement);
|
||||
|
||||
hold.Initialize(elementName, id, tags, EditorManager.instance.useNotePrefab ? false : isFirstGenerated, parentElement);
|
||||
hold.exactJudgeTime = exactJudgeTime;
|
||||
hold.holdEndTime = holdEndTime;
|
||||
hold.holdingTime = 0;
|
||||
@@ -58,7 +55,7 @@ namespace Ichni.RhythmGame
|
||||
hold.isOnTrack = false;
|
||||
}
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
if (EditorManager.instance.useNotePrefab && isFirstGenerated)
|
||||
{
|
||||
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(hold, GetNoteTypeName(hold) + "_Prefab");
|
||||
}
|
||||
@@ -97,7 +94,7 @@ namespace Ichni.RhythmGame
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
base.SetDefaultSubmodules();
|
||||
noteAudioSubmodule = new NoteAudioSubmodule(this, "DefaultTap");
|
||||
noteAudioSubmodule ??= new NoteAudioSubmodule(this, "DefaultTap");
|
||||
}
|
||||
|
||||
public override void SaveBM()
|
||||
@@ -138,13 +135,13 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
if (Keyboard.current.hKey.wasPressedThisFrame)
|
||||
{
|
||||
foreach (KeyValuePair<string,List<EffectBase>> effect in noteVisual.effectSubmodule.effectCollection)
|
||||
foreach (KeyValuePair<string, List<EffectBase>> effect in noteVisual.effectSubmodule.effectCollection)
|
||||
{
|
||||
effect.Value.ForEach(x => x.Disrupt());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (holdEndTime < exactJudgeTime)
|
||||
{
|
||||
LogWindow.Log("Hold end time is earlier than exact judge time.", Color.red);
|
||||
@@ -206,7 +203,7 @@ namespace Ichni.RhythmGame
|
||||
noteVisual.effectSubmodule.effectCollection["StartHold"].ForEach(e => e.UpdateEffect(exactJudgeTime));
|
||||
noteVisual.effectSubmodule.effectCollection["Holding"].ForEach(e => e.UpdateEffect(exactJudgeTime));
|
||||
|
||||
|
||||
|
||||
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(holdEndTime));
|
||||
switch (EditorManager.instance.currentJudgeType)
|
||||
{
|
||||
@@ -223,7 +220,7 @@ namespace Ichni.RhythmGame
|
||||
noteVisual.effectSubmodule.effectCollection["Miss"].ForEach(e => e.UpdateEffect(holdEndTime));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(holdEndTime));
|
||||
|
||||
if (EditorManager.instance.cameraManager.haveGameCamera)
|
||||
|
||||
@@ -61,8 +61,8 @@ namespace Ichni.RhythmGame
|
||||
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
timeDurationSubmodule = new TimeDurationSubmodule(this);
|
||||
noteJudgeSubmodule = new NoteJudgeSubmodule(this);
|
||||
timeDurationSubmodule ??= new TimeDurationSubmodule(this);
|
||||
noteJudgeSubmodule ??= new NoteJudgeSubmodule(this);
|
||||
}
|
||||
public override void Refresh()
|
||||
{
|
||||
@@ -74,6 +74,8 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
noteVisual.Refresh();
|
||||
}
|
||||
|
||||
foreach (SampleWindow i in SampleWindow.instances.Where(i => i.gameElement)) i.OnceSpawnNote();
|
||||
}
|
||||
protected virtual void Update()
|
||||
{
|
||||
|
||||
@@ -16,15 +16,12 @@ namespace Ichni.RhythmGame
|
||||
GameElement parentElement, float exactJudgeTime)
|
||||
{
|
||||
Stay stay = Instantiate(EditorManager.instance.basePrefabs.stayNote, parentElement.transform).GetComponent<Stay>();
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
{
|
||||
isFirstGenerated = false;
|
||||
}
|
||||
|
||||
stay.Initialize(elementName, id, tags, isFirstGenerated, parentElement);
|
||||
|
||||
|
||||
|
||||
stay.Initialize(elementName, id, tags, EditorManager.instance.useNotePrefab ? false : isFirstGenerated, parentElement);
|
||||
stay.exactJudgeTime = exactJudgeTime;
|
||||
|
||||
|
||||
if (parentElement.TryGetComponent(out Track track))
|
||||
{
|
||||
if (track.trackTimeSubmodule != null)
|
||||
@@ -45,22 +42,22 @@ namespace Ichni.RhythmGame
|
||||
stay.track = null;
|
||||
stay.isOnTrack = false;
|
||||
}
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
|
||||
if (EditorManager.instance.useNotePrefab && isFirstGenerated)
|
||||
{
|
||||
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(stay,GetNoteTypeName(stay) + "_Prefab");
|
||||
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(stay, GetNoteTypeName(stay) + "_Prefab");
|
||||
}
|
||||
|
||||
return stay;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public partial class Stay
|
||||
{
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
base.SetDefaultSubmodules();
|
||||
noteAudioSubmodule = new NoteAudioSubmodule(this, "DefaultStay");
|
||||
noteAudioSubmodule ??= new NoteAudioSubmodule(this, "DefaultStay");
|
||||
}
|
||||
|
||||
public override void SaveBM()
|
||||
@@ -68,25 +65,25 @@ namespace Ichni.RhythmGame
|
||||
matchedBM = new Stay_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM, exactJudgeTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace Beatmap
|
||||
{
|
||||
public class Stay_BM : NoteBase_BM
|
||||
{
|
||||
public Stay_BM()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
public Stay_BM(string elementName, Guid elementGuid, List<string> tags, GameElement_BM attachedElement, float exactJudgeTime)
|
||||
|
||||
public Stay_BM(string elementName, Guid elementGuid, List<string> tags, GameElement_BM attachedElement, float exactJudgeTime)
|
||||
: base(elementName, elementGuid, tags, attachedElement, exactJudgeTime)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override void ExecuteBM()
|
||||
{
|
||||
matchedElement = Stay.GenerateElement(elementName, elementGuid, tags, false,
|
||||
matchedElement = Stay.GenerateElement(elementName, elementGuid, tags, false,
|
||||
GetElement(attachedElementGuid), exactJudgeTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,8 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
Tap tap = Instantiate(EditorManager.instance.basePrefabs.tapNote, parentElement.transform)
|
||||
.GetComponent<Tap>();
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
{
|
||||
isFirstGenerated = false;
|
||||
}
|
||||
|
||||
tap.Initialize(elementName, id, tags, isFirstGenerated, parentElement);
|
||||
|
||||
tap.Initialize(elementName, id, tags, EditorManager.instance.useNotePrefab ? false : isFirstGenerated, parentElement);
|
||||
tap.exactJudgeTime = exactJudgeTime;
|
||||
|
||||
if (parentElement.TryGetComponent(out Track track))
|
||||
@@ -46,10 +41,10 @@ namespace Ichni.RhythmGame
|
||||
tap.track = null;
|
||||
tap.isOnTrack = false;
|
||||
}
|
||||
|
||||
if (EditorManager.instance.useNotePrefab)
|
||||
|
||||
if (EditorManager.instance.useNotePrefab && isFirstGenerated)
|
||||
{
|
||||
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(tap,GetNoteTypeName(tap) + "_Prefab");
|
||||
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(tap, GetNoteTypeName(tap) + "_Prefab");
|
||||
}
|
||||
|
||||
return tap;
|
||||
@@ -61,7 +56,7 @@ namespace Ichni.RhythmGame
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
base.SetDefaultSubmodules();
|
||||
noteAudioSubmodule = new NoteAudioSubmodule(this, "DefaultTap");
|
||||
noteAudioSubmodule ??= new NoteAudioSubmodule(this, "DefaultTap");
|
||||
}
|
||||
|
||||
public override void SaveBM()
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
public partial class Track
|
||||
{
|
||||
public TrackNoteEditor trackNoteEditor;
|
||||
/// <summary>
|
||||
/// 快速复制粘贴Track
|
||||
/// </summary>
|
||||
@@ -86,7 +87,7 @@ namespace Ichni.RhythmGame
|
||||
pn.transformSubmodule.Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Track整体旋转
|
||||
/// </summary>
|
||||
@@ -100,7 +101,7 @@ namespace Ichni.RhythmGame
|
||||
LogWindow.Log("Axis direction cannot be zero!", Color.red);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
trackPathSubmodule.pathNodeList.ForEach(pn =>
|
||||
{
|
||||
Vector3 originalPosition = pn.transformSubmodule.originalPosition;
|
||||
@@ -109,7 +110,7 @@ namespace Ichni.RhythmGame
|
||||
pn.transformSubmodule.Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Track整体翻转
|
||||
/// </summary>
|
||||
@@ -125,7 +126,7 @@ namespace Ichni.RhythmGame
|
||||
pn.transformSubmodule.Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 仅开启起点和终点的PathNode的Sphere显示,中间的PathNode不显示
|
||||
/// </summary>
|
||||
@@ -136,7 +137,7 @@ namespace Ichni.RhythmGame
|
||||
LogWindow.Log("PathNode amount is less than 2!", Color.red);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
trackPathSubmodule.pathNodeList[0].SetPathNodeSphere(true);
|
||||
trackPathSubmodule.pathNodeList[^1].SetPathNodeSphere(true);
|
||||
for (int i = 1; i < trackPathSubmodule.pathNodeList.Count - 1; i++)
|
||||
|
||||
@@ -2,18 +2,21 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni;
|
||||
using Ichni.RhythmGame;
|
||||
using Michsky.MUIP;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class NotefabContoler : MonoBehaviour
|
||||
{
|
||||
public SampleWindow sampleWindow;
|
||||
public NoteBase noteBase;
|
||||
public RawImage ifHold;
|
||||
public void Initialize(NoteBase note, float timePerBeat, int beatDeviver)
|
||||
public void Initialize(NoteBase note, float timePerBeat, int beatDeviver, float posX)
|
||||
{
|
||||
noteBase = note;
|
||||
Image color = GetComponent<Image>();
|
||||
transform.localPosition = new Vector3(0, note.exactJudgeTime / timePerBeat * beatDeviver, 0);
|
||||
transform.localPosition = new Vector3(posX, note.exactJudgeTime / timePerBeat * beatDeviver, 0);
|
||||
switch (note)
|
||||
{
|
||||
case Hold hold:
|
||||
@@ -36,9 +39,42 @@ public class NotefabContoler : MonoBehaviour
|
||||
color.color = new Color(1, 0.2f, 0, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
public void Onclick()
|
||||
public void Update()
|
||||
{
|
||||
EditorManager.instance.uiManager.hierarchy.FindTab(noteBase);
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(this.GetComponent<RectTransform>(), Mouse.current.position.ReadValue()))
|
||||
{
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
StartCoroutine(Moving());
|
||||
if (EditorManager.instance.uiManager.inspector.connectedGameElement != noteBase) EditorManager.instance.uiManager.hierarchy.FindTab(noteBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
public IEnumerator Moving()
|
||||
{
|
||||
sampleWindow.GetComponent<WindowDragger>().Lock = true;
|
||||
float startX = transform.localPosition.x;
|
||||
while (Mouse.current.leftButton.isPressed)
|
||||
{
|
||||
Vector2 localMousePosition = GetComponent<RectTransform>().InverseTransformPoint(Mouse.current.position.ReadValue());
|
||||
// if (Mathf.Abs(localMousePosition.x - startX) > GetComponent<RectTransform>().sizeDelta.x / 2)
|
||||
{
|
||||
transform.localPosition += new Vector3(Mouse.current.delta.ReadValue().x, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
noteBase.noteVisual.transformSubmodule.originalPosition = new Vector3(
|
||||
transform.localPosition.x / sampleWindow.XWidth,
|
||||
noteBase.noteVisual.transformSubmodule.originalPosition.y,
|
||||
noteBase.noteVisual.transformSubmodule.originalPosition.z
|
||||
);
|
||||
noteBase.noteVisual.transformSubmodule.Refresh();
|
||||
|
||||
sampleWindow.GetComponent<WindowDragger>().Lock = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class SampleWindow : MovableWindow//该window高度为300,横的要在100和500之间切换
|
||||
public class SampleWindow : MovableWindow//该window高度为300,横的要XWidth0和500之间切换
|
||||
{
|
||||
public static List<SampleWindow> instances = new List<SampleWindow>();
|
||||
public TMP_InputField DeviverInputField;
|
||||
@@ -27,6 +27,7 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
public bool isFocus = false;
|
||||
public bool isExpand = false;
|
||||
public int beatDeviver = 100;
|
||||
public int XWidth = 10;
|
||||
public int Xdevide = 1;
|
||||
public float realDevider;
|
||||
public GameObject beatLinePrefabv;
|
||||
@@ -88,7 +89,7 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
v.transform.localPosition = new Vector3(0, i * beatDeviver + (beatDeviver / Xdevide * j), 0);
|
||||
RawImage g = v.GetComponent<RawImage>();
|
||||
g.color = new Color(0, g.color.g, g.color.b, 0.2f);
|
||||
if (v.transform.localPosition.y > 600)
|
||||
if (v.transform.localPosition.y > 1200)
|
||||
{
|
||||
Destroy(v);
|
||||
break;
|
||||
@@ -117,13 +118,14 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
}
|
||||
foreach (var i in noteBases)
|
||||
{
|
||||
SpawnNote(i);
|
||||
SpawnNote(i, i.noteVisual.transformSubmodule.originalPosition.x * XWidth);
|
||||
}
|
||||
}
|
||||
private void SpawnNote(NoteBase i, float posx = 0)
|
||||
{
|
||||
GameObject u = Instantiate(NotePrefab, NoteMovepoint);
|
||||
u.GetComponent<NotefabContoler>().Initialize(i, timePerBeat, beatDeviver);
|
||||
u.GetComponent<NotefabContoler>().Initialize(i, timePerBeat, beatDeviver, posx);
|
||||
u.GetComponent<NotefabContoler>().sampleWindow = this;
|
||||
}
|
||||
|
||||
public GameObject selectedGameObject;
|
||||
@@ -132,6 +134,16 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
selectedGameObject = EventSystem.current.currentSelectedGameObject;
|
||||
LineMovepoint.localPosition = new(0, -beatDeviver * (songBeat - (int)songBeat), 0);
|
||||
NoteMovepoint.localPosition = new(0, -beatDeviver * songBeat, 0);
|
||||
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(windowRect, Mouse.current.position.ReadValue()))
|
||||
{
|
||||
DetectNote();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
void LateUpdate()
|
||||
{
|
||||
if (isFocus && gameElement is Track track)
|
||||
{
|
||||
if (track.trackTimeSubmodule is TrackTimeSubmoduleMovable trackTimeSubmoduleMovable)
|
||||
@@ -147,12 +159,6 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
TransformChanged();
|
||||
windowRect.GetComponent<CanvasGroup>().alpha = track.timeDurationSubmodule.CheckTimeInDuration(songTime) ? 1f : 0.2f;
|
||||
}
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(windowRect, Mouse.current.position.ReadValue()))
|
||||
{
|
||||
DetectNote();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
void TransformChanged()
|
||||
{
|
||||
@@ -193,6 +199,11 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
windowRect.sizeDelta = new Vector2(500, windowRect.sizeDelta.y);
|
||||
}
|
||||
}
|
||||
public void ChangeXWidth(string change)
|
||||
{
|
||||
XWidth = int.Parse(change);
|
||||
OnceSpawnNote();
|
||||
}
|
||||
public void DetectNote()
|
||||
{
|
||||
if (Keyboard.current.digit1Key.wasPressedThisFrame)
|
||||
@@ -206,6 +217,11 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
}
|
||||
public void AddNote(int NoteCode)
|
||||
{
|
||||
if (!EditorManager.instance.useNotePrefab)
|
||||
{
|
||||
LogWindow.Log("Please enable \"Note Prefab\" in EditorManager", Color.red);
|
||||
return;
|
||||
}
|
||||
// 获取鼠标在 NoteMovepoint 中的相对位置
|
||||
Vector2 localMousePosition = NoteMovepoint.InverseTransformPoint(Mouse.current.position.ReadValue());
|
||||
Debug.Log(localMousePosition);
|
||||
@@ -218,32 +234,41 @@ public class SampleWindow : MovableWindow//该window高度为300,横的要在1
|
||||
far -= 1f / Xdevide;
|
||||
float time = far * timePerBeat;
|
||||
|
||||
if (!isExpand)//movable
|
||||
;
|
||||
switch (NoteCode)
|
||||
{
|
||||
switch (NoteCode)
|
||||
{
|
||||
case 0:
|
||||
Tap a = Tap.GenerateElement("New Tap", Guid.NewGuid(), new List<string>(), false, gameElement, time);
|
||||
noteBases.Add(a);
|
||||
SpawnNote(a);
|
||||
break;
|
||||
case 3:
|
||||
Hold b = Hold.GenerateElement("New Hold", Guid.NewGuid(), new List<string>(), false, gameElement, time, time + 0.5f);
|
||||
noteBases.Add(b);
|
||||
SpawnNote(b);
|
||||
break;
|
||||
case 1:
|
||||
Stay c = Stay.GenerateElement("New Stay", Guid.NewGuid(), new List<string>(), false, gameElement, time);
|
||||
noteBases.Add(c);
|
||||
SpawnNote(c);
|
||||
break;
|
||||
case 2:
|
||||
Flick d = Flick.GenerateElement("New Flick", Guid.NewGuid(), new List<string>(), false, gameElement, time, new List<Vector2>());
|
||||
noteBases.Add(d);
|
||||
SpawnNote(d);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0:
|
||||
Tap a = Tap.GenerateElement("New Tap", Guid.NewGuid(), new List<string>(), true, gameElement, time);
|
||||
noteBases.Add(a);
|
||||
a.noteVisual.transformSubmodule.originalPosition = new Vector3(localMousePosition.x / XWidth, 0f, 0f);
|
||||
a.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(a, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
case 3:
|
||||
Hold b = Hold.GenerateElement("New Hold", Guid.NewGuid(), new List<string>(), true, gameElement, time, time + 0.5f);
|
||||
noteBases.Add(b);
|
||||
b.noteVisual.transformSubmodule.originalPosition = new Vector3(localMousePosition.x / XWidth, 0f, 0f);
|
||||
b.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(b, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
case 1:
|
||||
Stay c = Stay.GenerateElement("New Stay", Guid.NewGuid(), new List<string>(), true, gameElement, time);
|
||||
noteBases.Add(c);
|
||||
c.noteVisual.transformSubmodule.originalPosition = new Vector3(localMousePosition.x / XWidth, 0f, 0f);
|
||||
c.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(c, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
case 2:
|
||||
Flick d = Flick.GenerateElement("New Flick", Guid.NewGuid(), new List<string>(), true, gameElement, time, new List<Vector2>());
|
||||
noteBases.Add(d);
|
||||
d.noteVisual.transformSubmodule.originalPosition = new Vector3(localMousePosition.x / XWidth, 0f, 0f);
|
||||
d.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(d, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/Scripts/Graphical Tools/TrackNoteEditor.meta
Normal file
8
Assets/Scripts/Graphical Tools/TrackNoteEditor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f613492402ff5f2449535cf8161474ef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Dreamteck.Splines;
|
||||
using Ichni.RhythmGame;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
|
||||
public class TrackNoteEditor : MonoBehaviour
|
||||
{
|
||||
public SplineRenderer splineRenderer;
|
||||
public SplineComputer splineComputer;
|
||||
public int BeatDevider = 4; // 节拍分割数
|
||||
|
||||
public Track Track;
|
||||
public void Initialize(Track track)
|
||||
{
|
||||
Track = track;
|
||||
Track.trackNoteEditor = this;
|
||||
// 初始化其他相关组件或数据
|
||||
UnityEditorInternal.ComponentUtility.CopyComponent(Track.trackPathSubmodule.path);
|
||||
UnityEditorInternal.ComponentUtility.PasteComponentAsNew(gameObject);
|
||||
splineComputer = GetComponent<SplineComputer>();
|
||||
if (Track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c643f60e3c1d85543a8835265334bd58
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -209,12 +209,12 @@ namespace Ichni
|
||||
|
||||
LogWindow.Log("Save Clip Complete", Color.green);
|
||||
}
|
||||
|
||||
|
||||
public void LoadClip(string clipName)
|
||||
{
|
||||
LogWindow.Log("Start Loading Clip...");
|
||||
|
||||
if(!ES3.FileExists(Application.streamingAssetsPath + "/Clips/" + clipName + ".json"))
|
||||
|
||||
if (!ES3.FileExists(Application.streamingAssetsPath + "/Clips/" + clipName + ".json"))
|
||||
{
|
||||
LogWindow.Log("Clip not found", Color.red);
|
||||
return;
|
||||
@@ -225,16 +225,16 @@ namespace Ichni
|
||||
LogWindow.Log("Please select only one Game Element to load the beatmap clip.", Color.red);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
GameElement selectedElement = EditorManager.instance.operationManager.currentSelectedElements[0];
|
||||
|
||||
|
||||
if (selectedElement is null)
|
||||
{
|
||||
LogWindow.Log("Please select a Game Element to load the beatmap clip.", Color.red);
|
||||
return;
|
||||
}
|
||||
Debug.Log(selectedElement.elementName + " " + selectedElement.elementGuid);
|
||||
|
||||
|
||||
_LoadClip(selectedElement, clipName);
|
||||
|
||||
LogWindow.Log("Load Clip Complete", Color.green);
|
||||
@@ -263,18 +263,18 @@ namespace Ichni
|
||||
{
|
||||
string filePath = Application.streamingAssetsPath + "/Clips/" + clipName + ".json";
|
||||
List<BaseElement_BM> clip = ES3.Load<List<BaseElement_BM>>("Clip", filePath, ProjectManager.SaveSettings);
|
||||
|
||||
|
||||
//对于第一个元素,需要特殊处理,将它放入目标物体的子物体列表中
|
||||
GameElement_BM first = clip[0] as GameElement_BM;
|
||||
List<BaseElement_BM> firstAttaches = GameElement_BM.GetAllAttachedBaseElements(first, clip);
|
||||
first.elementGuid = Guid.NewGuid();
|
||||
GameElement_BM.identifier.TryAdd(first.elementGuid, first);
|
||||
firstAttaches.ForEach(e => { e.attachedElementGuid = first.elementGuid; });
|
||||
|
||||
|
||||
//将目标物体(临时)存入读存档的Dictionary中
|
||||
target.SaveBM();
|
||||
GameElement_BM.identifier.TryAdd(target.elementGuid, target.matchedBM as GameElement_BM);
|
||||
(target.matchedBM as GameElement_BM).matchedElement = target;
|
||||
(target.matchedBM as GameElement_BM).matchedElement = target;
|
||||
first.attachedElementGuid = target.elementGuid;
|
||||
|
||||
for (var index = 1; index < clip.Count; index++)
|
||||
@@ -290,7 +290,7 @@ namespace Ichni
|
||||
}
|
||||
|
||||
first.ExecuteBM();
|
||||
|
||||
|
||||
for (var index = 1; index < clip.Count; index++)
|
||||
{
|
||||
clip[index].ExecuteBM();
|
||||
@@ -304,7 +304,7 @@ namespace Ichni
|
||||
{
|
||||
string mergePath = Application.streamingAssetsPath + "/Merges/" + mergeName + ".json";
|
||||
BeatmapContainer_BM merge = ES3.Load<BeatmapContainer_BM>("Beatmap", mergePath, ProjectManager.SaveSettings);
|
||||
|
||||
|
||||
merge.elementList.ForEach(element =>
|
||||
{
|
||||
if (element == null)
|
||||
@@ -312,12 +312,12 @@ namespace Ichni
|
||||
Debug.LogError("Null element detected in elementList. Skipping execution.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (BeatmapContainer_BM.LowPriorityGameElementTypes.Contains(element.GetType()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (element is GameElement_BM gameElement)
|
||||
{
|
||||
GameElement_BM.identifier.Add(gameElement.elementGuid, gameElement);
|
||||
@@ -371,12 +371,16 @@ namespace Ichni
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
ES3.Save("Note", clip, GetNotePrefabPath(noteName), ProjectManager.SaveSettings);
|
||||
}
|
||||
|
||||
public void LoadNotePrefab(NoteBase target, string noteName)
|
||||
{
|
||||
if (target.noteVisual != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<BaseElement_BM> clip = ES3.Load<List<BaseElement_BM>>("Note", GetNotePrefabPath(noteName), ProjectManager.SaveSettings);
|
||||
|
||||
if (clip == null || clip.Count == 0)
|
||||
@@ -384,17 +388,17 @@ namespace Ichni
|
||||
LogWindow.Log("Note prefab not found", Color.red);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
target.SaveBM();
|
||||
GameElement_BM.identifier.TryAdd(target.elementGuid, target.matchedBM as GameElement_BM);
|
||||
(target.matchedBM as GameElement_BM).matchedElement = target;
|
||||
|
||||
(target.matchedBM as GameElement_BM).matchedElement = target;
|
||||
|
||||
GameElement_BM first = clip[0] as GameElement_BM;
|
||||
List<BaseElement_BM> firstAttaches = GameElement_BM.GetAllAttachedBaseElements(first, clip);
|
||||
first.elementGuid = target.elementGuid;
|
||||
GameElement_BM.identifier.TryAdd(first.elementGuid, first);
|
||||
firstAttaches.ForEach(e => { e.attachedElementGuid = first.elementGuid; });
|
||||
|
||||
|
||||
for (var index = 1; index < clip.Count; index++)
|
||||
{
|
||||
var element = clip[index];
|
||||
@@ -406,11 +410,12 @@ namespace Ichni
|
||||
attachedElements.ForEach(e => { e.attachedElementGuid = gameElement.elementGuid; });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (var index = 1; index < clip.Count; index++)
|
||||
{
|
||||
clip[index].ExecuteBM();
|
||||
}
|
||||
target.SetDefaultSubmodules();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +426,7 @@ namespace Ichni
|
||||
private string GetAutoSavePath(string autoSaveName) => autoSavePath + "/" + autoSaveName + ".json";
|
||||
private float autoSaveInterval => EditorManager.instance.editorSettings.autoSaveInterval;
|
||||
private int maximumAutoSaveCount => EditorManager.instance.editorSettings.maximumAutoSaveCount;
|
||||
|
||||
|
||||
public float autoSaveTimer;
|
||||
|
||||
public AutoSaveManager()
|
||||
@@ -466,21 +471,21 @@ namespace Ichni
|
||||
string newestSavePath = GetAutoSavePath("AutoSave_0");
|
||||
SaveBeatMap(newestSavePath);
|
||||
}
|
||||
|
||||
|
||||
private void SaveBeatMap(string autoSavePath)
|
||||
{
|
||||
EditorManager.instance.beatmapContainer.SaveBM();
|
||||
ES3.Save("BeatMap", EditorManager.instance.beatmapContainer.matchedBM as BeatmapContainer_BM,
|
||||
autoSavePath, ProjectManager.SaveSettings);
|
||||
}
|
||||
|
||||
|
||||
private List<string> GetSortedSaveFiles()
|
||||
{
|
||||
if(!ES3.DirectoryExists(autoSavePath))
|
||||
if (!ES3.DirectoryExists(autoSavePath))
|
||||
{
|
||||
Directory.CreateDirectory(autoSavePath);
|
||||
}
|
||||
|
||||
|
||||
List<string> saveFiles = new List<string>(Directory.GetFiles(autoSavePath, "AutoSave_*.es3"));
|
||||
saveFiles.Sort(string.Compare);
|
||||
return saveFiles;
|
||||
|
||||
Reference in New Issue
Block a user