using RhythmBase.Components;
using RhythmBase.Components.Easing;
using RhythmBase.Events;
using RhythmBase.Exceptions;
using RhythmBase.Extensions;
using System.Text.RegularExpressions;
namespace RhythmBase.Extensions
{
public static partial class Extensions
{
///
/// Get the pulse sound effect of row beat event.
///
/// The sound effect of row beat event.
public static RDAudio BeatSound(this BaseBeat e)
{
SetBeatSound? setBeatSound = e.Parent?.LastOrDefault((SetBeatSound i) => i.Beat < e.Beat && i.Active);
return (setBeatSound?.Sound) ?? e.Parent?.Sound ?? throw new NotImplementedException();
}
///
/// Getting controlled events.
///
public static IEnumerable> ControllingEvents(this TagAction e) => e.Beat.BaseLevel?.GetTaggedEvents(e.ActionTag, e.Action.HasFlag(TagAction.Actions.All)) ?? [];
///
/// Creates a new subordinate to at the specified beat. The new event created will be attempted to be added to the 's source level.
///
/// RDLevel
/// Specified beat.
public static AdvanceText CreateAdvanceText(this FloatingText e, RDBeat beat)
{
AdvanceText A = new()
{
Parent = e,
Beat = beat.WithoutBinding()
};
e.Children.Add(A);
return A;
}
///
/// Get the end beat of the duration.
///
///
///
///
///
public static RDBeat DurationOffset(this RDBeat beat, float duration)
{
SetBeatsPerMinute setBPM = beat.BaseLevel?.First((SetBeatsPerMinute i) => i.Beat > beat) ?? throw new InvalidRDBeatException();
RDBeat DurationOffset =
beat.BarBeat.bar == setBPM.Beat.BarBeat.bar
? beat + duration
: beat + TimeSpan.FromMinutes(duration / beat.BPM);
return DurationOffset;
}
///
/// Returns the pulse beat of the specified 0-based index.
///
/// THIS IS 7TH BEAT GAMES!
public static RDBeat GetBeat(this AddClassicBeat e, byte index)
{
SetRowXs x = e.Parent?.LastOrDefault((SetRowXs i) => i.Active && e.IsBehind(i)) ?? new();
float Synco = 0 <= x.SyncoBeat && x.SyncoBeat < (sbyte)index ? (float)((x.SyncoSwing == 0f) ? 0.5 : ((double)x.SyncoSwing)) : 0f;
if (index >= 7)
throw new RhythmBaseException("THIS IS 7TH BEAT GAMES!");
return e.Beat.DurationOffset(e.Tick * ((float)index - Synco));
}
///
/// Converts Xs patterns to string form.
///
public static string GetPatternString(this SetRowXs e) => Utils.Utils.GetPatternString(e.Pattern);
///
/// Get the sequence of belonging to this , return all of the from the time the pulse was created to the time it was removed or hit.
///
public static IEnumerable GetPulses(this AddFreeTimeBeat e)
{
List Result = [];
byte pulse = e.Pulse;
if (e.Parent == null)
yield break;
foreach (PulseFreeTimeBeat item in e.Parent.Where(i => i.Active && e.IsInFrontOf(i)))
{
switch (item.Action)
{
case PulseFreeTimeBeat.ActionType.Increment:
pulse += 1;
yield return item;
break;
case PulseFreeTimeBeat.ActionType.Decrement:
pulse = (byte)((pulse > 0b1) ? (pulse - 0b1) : 0b1);
yield return item;
break;
case PulseFreeTimeBeat.ActionType.Custom:
pulse = (byte)item.CustomPulse;
yield return item;
break;
case PulseFreeTimeBeat.ActionType.Remove:
yield return item;
break;
}
if (pulse == 6)
break;
}
}
///
/// Get the hit sound effect of row beat event.
///
/// The sound effect of row beat event.
public static RDAudio HitSound(this BaseBeat e)
{
RDAudio DefaultAudio = new()
{
Filename = "sndClapHit",
Offset = TimeSpan.Zero,
Pan = 100,
Pitch = 100,
Volume = 100
};
RDAudio? HitSound;
switch (e.Player())
{
case PlayerType.P1:
{
SetClapSounds? setClapSounds = e.Beat.BaseLevel?.LastOrDefault((SetClapSounds i) => i.Active && i.P1Sound != null);
HitSound = (setClapSounds?.P1Sound) ?? DefaultAudio;
break;
}
case PlayerType.P2:
{
SetClapSounds? setClapSounds2 = e.Beat.BaseLevel?.LastOrDefault((SetClapSounds i) => i.Active && i.P2Sound != null);
HitSound = (setClapSounds2?.P2Sound) ?? DefaultAudio;
break;
}
case PlayerType.CPU:
{
SetClapSounds? setClapSounds3 = e.Beat.BaseLevel?.LastOrDefault((SetClapSounds i) => i.Active && i.CpuSound != null);
HitSound = (setClapSounds3?.CpuSound) ?? DefaultAudio;
break;
}
default:
HitSound = DefaultAudio;
break;
}
return HitSound;
}
///
/// Get all hits.
///
public static IEnumerable HitTimes(this AddClassicBeat e) =>
new List { new(e, e.GetBeat(6), e.Hold) }
.AsEnumerable();
///
/// Get all hits.
///
public static IEnumerable HitTimes(this AddOneshotBeat e)
{
e._beat.IfNullThrowException();
List L = [];
uint loops = e.Loops;
for (uint i = 0U; i <= loops; i += 1U)
{
sbyte b = (sbyte)(e.Subdivisions - 1);
for (sbyte j = 0; j <= b; j += 1)
L.Add(new RDHit(e, new RDBeat(e._beat._calculator, e._beat.BeatOnly + i * e.Interval + e.Tick + e.Delay + (float)j * (e.Tick / (float)e.Subdivisions)), 0f));
}
return L.AsEnumerable();
}
///
/// Get all hits.
///
public static IEnumerable HitTimes(this AddFreeTimeBeat e) =>
e.Pulse == 6
? [new(e, e.Beat, e.Hold)]
: ([]);
///
/// Get all hits.
///
public static IEnumerable HitTimes(this PulseFreeTimeBeat e) =>
e.IsHitable()
? [new(e, e.Beat, e.Hold)]
: ([]);
///
/// Get all hits.
///
public static IEnumerable HitTimes(this BaseBeat e) => e.Type switch
{
EventType.AddClassicBeat => ((AddClassicBeat)e).HitTimes(),
EventType.AddFreeTimeBeat => ((AddFreeTimeBeat)e).HitTimes(),
EventType.AddOneshotBeat => ((AddOneshotBeat)e).HitTimes(),
_ => e.Type != EventType.PulseFreeTimeBeat
? Array.Empty().AsEnumerable()
: ((PulseFreeTimeBeat)e).HitTimes(),
};
///
/// Determine if is after
///
///
/// - If is after ,
/// - Else,
///
public static bool IsBehind(this OrderedEventCollection e, IBaseEvent item1, IBaseEvent item2) => (item1.Beat > item2.Beat || (item1.Beat.BeatOnly == item2.Beat.BeatOnly && (e.eventsBeatOrder[item1.Beat].BeforeThan(item2, item1))));
///
/// Check if another event is after itself, including events of the same beat but executed after itself.
///
public static bool IsBehind(this IBaseEvent e, IBaseEvent item) => e.Beat.BaseLevel?.IsBehind(e, item) ?? throw new InvalidRDBeatException();
///
/// Check if it can be hit by player.
///
public static bool IsHitable(this PulseFreeTimeBeat e)
{
int PulseIndexMin = 6;
int PulseIndexMax = 6;
if (e.Parent is null)
return false;
foreach (BaseBeat item in ((IEnumerable)e.Parent
.Where(e.IsBehind, new RDRange(e.Beat, null)))
.Reverse())
{
EventType type = item.Type;
switch (type)
{
case EventType.AddFreeTimeBeat:
{
AddFreeTimeBeat Temp2 = (AddFreeTimeBeat)item;
if (PulseIndexMin <= (int)Temp2.Pulse & (int)Temp2.Pulse <= PulseIndexMax)
return true;
break;
}
case EventType.PulseFreeTimeBeat:
{
PulseFreeTimeBeat Temp = (PulseFreeTimeBeat)item;
switch (Temp.Action)
{
case PulseFreeTimeBeat.ActionType.Increment:
if (PulseIndexMin > 0)
PulseIndexMin--;
if (!(PulseIndexMax > 0))
return false;
PulseIndexMax--;
break;
case PulseFreeTimeBeat.ActionType.Decrement:
if (PulseIndexMin > 0)
PulseIndexMin++;
if (!(PulseIndexMax < 6))
return false;
PulseIndexMax++;
break;
case PulseFreeTimeBeat.ActionType.Custom:
if (!(PulseIndexMin <= Temp.CustomPulse & Temp.CustomPulse <= PulseIndexMax))
return false;
PulseIndexMin = 0;
PulseIndexMax = 5;
break;
case PulseFreeTimeBeat.ActionType.Remove:
return false;
}
if (PulseIndexMin > PulseIndexMax)
return false;
break;
}
}
}
return false;
}
///
/// Check if it can be hit by player.
///
public static bool IsHitable(this AddFreeTimeBeat e) => e.Pulse == 6;
///
/// Check if it can be hit by player.
///
public static bool IsHitable(this BaseBeat e) => e.Type switch
{
EventType.AddClassicBeat or EventType.AddOneshotBeat => true,
EventType.AddFreeTimeBeat => ((AddFreeTimeBeat)e).IsHitable(),
_ => e.Type == EventType.PulseFreeTimeBeat && ((PulseFreeTimeBeat)e).IsHitable(),
};
///
/// Determine if is in front of
///
///
/// - If is in front of ,
/// - Else,
///
public static bool IsInFrontOf(this OrderedEventCollection e, IBaseEvent item1, IBaseEvent item2) => (item1.Beat < item2.Beat || (item1.Beat.BeatOnly == item2.Beat.BeatOnly && (e.eventsBeatOrder[item1.Beat].BeforeThan(item1, item2))));
///
/// Check if another event is in front of itself, including events of the same beat but executed before itself.
///
public static bool IsInFrontOf(this IBaseEvent e, IBaseEvent item) => e.Beat.BaseLevel?.IsInFrontOf(e, item) ?? throw new InvalidRDBeatException();
///
/// Get the total length of the oneshot.
///
///
public static float Length(this AddOneshotBeat e) => e.Tick * e.Loops + e.Interval * e.Loops - 1f;
///
/// Get the total length of the classic beat.
///
///
public static float Length(this AddClassicBeat e)
{
float SyncoSwing = e.Parent?.LastOrDefault((SetRowXs i) => i.Active && e.IsBehind(i))?.SyncoSwing ?? 0;
return (float)((double)(e.Tick * 6f) - ((SyncoSwing == 0f) ? 0.5 : ((double)SyncoSwing)) * (double)e.Tick);
}
///
/// Specifies the position of the image. This method changes both the pivot and the angle to keep the image visually in its original position.
///
/// Sprite size.
/// RDLevel
/// Specified position.
public static void MovePositionMaintainVisual(this Move e, RDSizeE spriteSize, RDPointE target)
{
if (e.Position != null && e.Pivot != null && e.Angle != null && e.Angle.Value.IsNumeric)
{
e.Position = new RDPointE?(target);
e.Pivot = new RDPointE?((e.VisualPosition(spriteSize) - new RDSizeE(target)).Rotate(e.Angle.Value.NumericValue));
}
}
///
/// Specifies the position of the image. This method changes both the pivot and the angle to keep the image visually in its original position.
///
/// RDLevel
/// Specified position.
public static void MovePositionMaintainVisual(this MoveRoom e, RDSizeE target)
{
if (e.RoomPosition != null && e.Pivot != null && e.Angle != null && e.Angle.Value.IsNumeric)
{
e.RoomPosition = new RDPointE?((RDPointE)target);
e.Pivot = new RDPointE?((e.VisualPosition() - new RDSizeE((RDPointE)target)).Rotate(e.Angle.Value.NumericValue));
}
}
///
/// Convert beat pattern to string.
///
/// The pattern string.
public static string Pattern(this AddClassicBeat e) => Utils.Utils.GetPatternString(e.RowXs());
///
/// Get current player of the beat event.
///
///
///
public static PlayerType Player(this BaseBeat e)
{
ChangePlayersRows? changePlayersRows = e.Beat.BaseLevel?.LastOrDefault((ChangePlayersRows i) => i.Active && i.Players[e.Index] != PlayerType.NoChange);
return (changePlayersRows != null)
? changePlayersRows.Players[e.Index]
: (PlayerType)(e.Parent?.Player ?? throw new NotImplementedException());
}
///
/// Get the actual beat pattern.
///
/// The actual beat pattern.
public static Patterns[] RowXs(this AddClassicBeat e)
{
if (e.SetXs == null)
{
SetRowXs X = e.Parent?.LastOrDefault((SetRowXs i) => i.Active && e.IsBehind(i)) ?? new();
return X.Pattern;
}
else
{
Patterns[] T = new Patterns[6];
AddClassicBeat.ClassicBeatPatterns? setXs = e.SetXs;
int? num = (setXs != null) ? new int?((int)setXs.GetValueOrDefault()) : null;
if (((num != null) ? new bool?(num.GetValueOrDefault() == 0) : null).GetValueOrDefault())
{
T[1] = Patterns.X;
T[2] = Patterns.X;
T[4] = Patterns.X;
T[5] = Patterns.X;
}
else
{
num = (setXs != null) ? new int?((int)setXs.GetValueOrDefault()) : null;
if (!(((num != null) ? new bool?(num.GetValueOrDefault() == 1) : null).GetValueOrDefault()))
{
throw new RhythmBaseException("How?");
}
T[1] = Patterns.X;
T[3] = Patterns.X;
T[5] = Patterns.X;
}
return T;
}
}
///
/// Get the special tag of the tag event.
///
/// special tags.
public static TagAction.SpecialTag[] SpetialTags(this TagAction e) => (TagAction.SpecialTag[])(from i in Enum.GetValues()
where e.ActionTag.Contains(string.Format("[{0}]", i))
select i);
///
/// Generate split event instances.
///
public static IEnumerable Split(this SayReadyGetSetGo e) => e.PhraseToSay switch
{
SayReadyGetSetGo.Words.SayReaDyGetSetGoNew => [
e.SplitCopy(0f, SayReadyGetSetGo.Words.JustSayRea),
e.SplitCopy(e.Tick, SayReadyGetSetGo.Words.JustSayDy),
e.SplitCopy(e.Tick * 2f, SayReadyGetSetGo.Words.JustSayGet),
e.SplitCopy(e.Tick * 3f, SayReadyGetSetGo.Words.JustSaySet),
e.SplitCopy(e.Tick * 4f, SayReadyGetSetGo.Words.JustSayGo)
],
SayReadyGetSetGo.Words.SayGetSetGo => [
e.SplitCopy(0f, SayReadyGetSetGo.Words.JustSayGet),
e.SplitCopy(e.Tick, SayReadyGetSetGo.Words.JustSaySet),
e.SplitCopy(e.Tick * 2f, SayReadyGetSetGo.Words.JustSayGo)
],
SayReadyGetSetGo.Words.SayReaDyGetSetOne => [
e.SplitCopy(0f, SayReadyGetSetGo.Words.JustSayRea),
e.SplitCopy(e.Tick, SayReadyGetSetGo.Words.JustSayDy),
e.SplitCopy(e.Tick * 2f, SayReadyGetSetGo.Words.JustSayGet),
e.SplitCopy(e.Tick * 3f, SayReadyGetSetGo.Words.JustSaySet),
e.SplitCopy(e.Tick * 4f, SayReadyGetSetGo.Words.Count1)
],
SayReadyGetSetGo.Words.SayGetSetOne => [
e.SplitCopy(0f, SayReadyGetSetGo.Words.JustSayGet),
e.SplitCopy(e.Tick, SayReadyGetSetGo.Words.JustSaySet),
e.SplitCopy(e.Tick * 2f, SayReadyGetSetGo.Words.Count1)
],
SayReadyGetSetGo.Words.SayReadyGetSetGo => [
e.SplitCopy(0f, SayReadyGetSetGo.Words.JustSayReady),
e.SplitCopy(e.Tick * 2f, SayReadyGetSetGo.Words.JustSayGet),
e.SplitCopy(e.Tick * 3f, SayReadyGetSetGo.Words.JustSaySet),
e.SplitCopy(e.Tick * 4f, SayReadyGetSetGo.Words.JustSayGo)
],
_ => [e],
};
///
/// Generate split event instances.
///
public static IEnumerable Split(this AddOneshotBeat e)
{
e._beat.IfNullThrowException();
List L = [];
uint loops = e.Loops;
for (uint i = 0U; i <= loops; i += 1U)
{
AddOneshotBeat T = e.MemberwiseClone();
T.Loops = 0U;
T.Interval = 0f;
T.Beat = new RDBeat(e._beat._calculator, unchecked(e.Beat.BeatOnly + i * e.Interval));
L.Add(T);
}
return L.AsEnumerable();
}
///
/// Generate split event instances. Follow the most recently activated Xs.
///
public static IEnumerable Split(this AddClassicBeat e)
{
SetRowXs x = e.Parent?.LastOrDefault((SetRowXs i) => i.Active && e.IsBehind(i)) ?? new();
return e.Split(x);
}
///
/// Generate split event instances.
///
public static IEnumerable Split(this AddClassicBeat e, SetRowXs Xs)
{
List L = [];
AddFreeTimeBeat Head = e.Clone();
Head.Pulse = 0;
Head.Hold = e.Hold;
L.Add(Head);
int i = 1;
do
{
if (!(i < 6 && Xs.Pattern[i] == Patterns.X))
{
PulseFreeTimeBeat Pulse = e.Clone();
PulseFreeTimeBeat pulseFreeTimeBeat;
(pulseFreeTimeBeat = Pulse).Beat = pulseFreeTimeBeat.Beat + e.Tick * (float)i;
if (i >= (int)Xs.SyncoBeat)
(pulseFreeTimeBeat = Pulse).Beat = pulseFreeTimeBeat.Beat - Xs.SyncoSwing;
if (i % 2 == 1)
(pulseFreeTimeBeat = Pulse).Beat = pulseFreeTimeBeat.Beat + (e.Tick - ((e.Swing == 0f) ? e.Tick : e.Swing));
Pulse.Hold = e.Hold;
Pulse.Action = PulseFreeTimeBeat.ActionType.Increment;
L.Add(Pulse);
}
i++;
}
while (i <= 6);
return L.AsEnumerable();
}
///
/// Calculates the duration of the VFX effect for the given preset.
///
/// The SetVFXPreset event.
/// An RDRange representing the duration of the VFX effect.
public static RDRange VFXDuration(this SetVFXPreset e)
{
if (e.Preset != SetVFXPreset.Presets.DisableAll && e.Enable)
{
SetVFXPreset? close = e.After().FirstOrDefault(i =>
i.Rooms.Contains(e.Rooms) && (
i.Preset == e.Preset ||
i.Preset == SetVFXPreset.Presets.DisableAll
));
return new(e.Beat, close?.Beat);
}
return new(e.Beat, e.Beat);
}
///
/// Remove auxiliary symbols.
///
public static string TextOnly(this ShowDialogue e)
{
string result = e.Text;
foreach (string item in new string[]
{
"shake",
"shakeRadius=\\d+",
"wave",
"waveHeight=\\d+",
"waveSpeed=\\d+",
"swirl",
"swirlRadius=\\d+",
"swirlSpeed=\\d+",
"static"
})
result = Regex.Replace(result, string.Format("\\[{0}\\]", item), "");
return result;
}
///
/// The visual position of the lower left corner of the image.
///
public static RDPointE VisualPosition(this Move e, RDSizeE spriteSize)
{
RDPointE VisualPosition = default;
if (e.Position != null && e.Pivot != null && e.Angle != null && e.Angle.Value.IsNumeric && e.Scale != null)
{
RDPointE previousPosition = e.Position.Value;
RDExpression? x = e.Pivot?.X * (e.Scale?.Width) * spriteSize.Width / 100f;
RDPointE previousPivot = new(x, e.Pivot?.Y * (e.Scale?.Height) * spriteSize.Height / 100f);
VisualPosition = previousPosition + new RDSizeE(previousPivot.Rotate(e.Angle.Value.NumericValue));
}
return VisualPosition;
}
///
/// The visual position of the lower left corner of the image.
///
public static RDPointE VisualPosition(this MoveRoom e)
{
RDPointE VisualPosition = default;
if (e.RoomPosition != null && e.Pivot != null && e.Angle != null)
{
RDPointE previousPosition = e.RoomPosition.Value;
RDPointE previousPivot = new((e.Pivot?.X) * (e.Scale?.Width), (e.Pivot?.Y) * (e.Scale?.Height));
VisualPosition = previousPosition + new RDSizeE(previousPivot.Rotate(e.Angle.Value.NumericValue));
}
return VisualPosition;
}
///
/// Creates a rotated rectangle for the MoveCamera event.
///
/// The MoveCamera event.
/// A rotated rectangle representing the camera's position, zoom, and angle.
public static RDRotatedRectE RotatedRect(this MoveCamera e) => new(e.CameraPosition, new(e.Zoom, e.Zoom), null, e.Angle);
///
/// Creates a rotated rectangle for the MoveRow event.
///
/// The MoveRow event.
/// A rotated rectangle representing the row's position, scale, pivot, and angle.
public static RDRotatedRectE RotatedRect(this MoveRow e) => new(e.RowPosition, e.Scale, new(e.Pivot, e.Pivot), e.Angle);
///
/// Creates a rotated rectangle for the MoveRoom event.
///
/// The MoveRoom event.
/// A rotated rectangle representing the room's position, scale, pivot, and angle.
public static RDRotatedRectE RotatedRect(this MoveRoom e) => new(e.RoomPosition, e.Scale, e.Pivot, e.Angle);
///
/// Creates a rotated rectangle for the Move event.
///
/// The Move event.
/// A rotated rectangle representing the position, scale, pivot, and angle.
public static RDRotatedRectE RotatedRect(this Move e) => new(e.Position, e.Scale, e.Pivot, e.Angle);
private static SayReadyGetSetGo SplitCopy(this SayReadyGetSetGo e, float extraBeat, SayReadyGetSetGo.Words word)
{
SayReadyGetSetGo Temp = e.Clone();
Temp.Beat += extraBeat;
Temp.PhraseToSay = word;
Temp.Volume = e.Volume;
return Temp;
}
}
}