Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7bc5784
Add landmines
orwenn22 Sep 21, 2025
2335275
Better landmine tool icon
orwenn22 Sep 21, 2025
cec8b54
don't judge landmines when holding a key
orwenn22 Sep 21, 2025
8156b74
Fix typo
orwenn22 Sep 22, 2025
92bbfb2
Fix typo
orwenn22 Sep 22, 2025
0371be4
don't allow visual lanes on landmines
orwenn22 Sep 22, 2025
bdf738f
judge landmines earlier if they are in the timing window of the next …
orwenn22 Sep 23, 2025
c8758b1
Use landmines in stepmania-imported maps
orwenn22 Sep 23, 2025
3b0a7f8
judge landmine when holding a key
orwenn22 Sep 25, 2025
869021a
Change landmine appearance
orwenn22 Oct 1, 2025
b8aa8d1
remove "small" paremeter from landmines
orwenn22 Oct 1, 2025
4207476
add landmine type to charting sidebar's "hit type"
orwenn22 Oct 2, 2025
0ca7bc3
don't include landmines in nps
orwenn22 Oct 2, 2025
646d8e3
Merge branch 'main' into landmines
orwenn22 Oct 2, 2025
4cc39d7
don't give any judgement to landimes if they aren't triggered
orwenn22 Oct 3, 2025
0b1b6c6
fix seeking back maps with landmines
orwenn22 Oct 9, 2025
0441f1c
revert accidental changes
orwenn22 Oct 10, 2025
e49e935
add "No Mines" mod
orwenn22 Oct 10, 2025
d9533a9
nerf performance if No Mines mod is enabled
orwenn22 Oct 10, 2025
5cb80b5
use outlined shapes for the default skins' landmines
orwenn22 Oct 11, 2025
5970bb7
count landmine as miss if holding, even if CheckJudgement isn't user-…
orwenn22 Oct 15, 2025
8fea15f
revert accidental changes
orwenn22 Oct 16, 2025
3062083
add ability to hide landmines
orwenn22 Oct 16, 2025
4601fa9
actual logic to find the next note in DrawableLandmine
orwenn22 Oct 16, 2025
5eac5d7
add dedicated hitwindows for landmines
orwenn22 Oct 21, 2025
20d9467
Merge branch 'main' into landmines
orwenn22 Oct 22, 2025
52a3713
Merge branch 'main' into landmines
orwenn22 Nov 8, 2025
3f59be9
Merge branch 'main' into landmines
orwenn22 Dec 16, 2025
736d2ef
apply anchoring and sizing when getting the landmine
orwenn22 Dec 16, 2025
1f2794e
Merge branch 'main' into landmines
orwenn22 Jan 22, 2026
f7e368b
various landmines related changes
orwenn22 Jan 23, 2026
e0354dd
add ResultType enum for HitResult and score submission
orwenn22 Jan 24, 2026
62a1c29
undo hitobject reverting changed
orwenn22 Jan 24, 2026
a8d0418
Merge branch 'main' into landmines
orwenn22 Mar 16, 2026
1895a0b
not hapenning
orwenn22 Mar 16, 2026
e8b788f
Merge branch 'main' into landmines
orwenn22 Apr 5, 2026
96a659e
mine support for quaver maps
orwenn22 Apr 5, 2026
59cdd3e
override HitWindows in DrawableLandmine
orwenn22 Apr 5, 2026
8f537d8
Merge branch 'main' into landmines
orwenn22 Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions fluXis.Import.Quaver/Map/QuaverMap.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using fluXis.Import.Quaver.Map.Structs;
using fluXis.Map;
using fluXis.Map.Structures;
Expand Down Expand Up @@ -59,13 +60,49 @@ public MapInfo ToMapInfo()

foreach (var o in HitObjects)
{
mapInfo.HitObjects.Add(new HitObject
// we don't support LN mines, so let's just put a bunch of mines every 1/4 beat
if (o.Type == "Mine" && o.IsLongNote)
{
Time = o.StartTime,
Lane = o.Lane,
HoldTime = o.IsLongNote ? o.EndTime - o.StartTime : 0,
Group = o.TimingGroup
});
TimingPoint timingPoint = mapInfo.GetTimingPoint(o.StartTime);

for (float t = o.StartTime; t < o.EndTime; t += timingPoint.MsPerBeat / 4)
{
mapInfo.HitObjects.Add(new HitObject
{
Time = t,
Lane = o.Lane,
Type = 2,
HoldTime = 0,
Group = o.TimingGroup
});

timingPoint = mapInfo.GetTimingPoint(o.StartTime); // in case the bpm changes during the LN mine (idk might happen on some weird maps)
}

// if the endtime is too far from the last mine we placed then put a mine directly at the endtime
if (Math.Abs(mapInfo.HitObjects.Last().Time - o.EndTime) > timingPoint.MsPerBeat / 16)
{
mapInfo.HitObjects.Add(new HitObject
{
Time = o.EndTime,
Lane = o.Lane,
Type = 2,
HoldTime = 0,
Group = o.TimingGroup
});
}
}
else
{
mapInfo.HitObjects.Add(new HitObject
{
Time = o.StartTime,
Lane = o.Lane,
Type = o.Type == "Mine" ? 2 : 0,
HoldTime = o.IsLongNote ? o.EndTime - o.StartTime : 0,
Group = o.TimingGroup
});
}
}

foreach (var t in TimingPoints)
Expand Down
1 change: 1 addition & 0 deletions fluXis.Import.Quaver/Map/Structs/QuaverHitObjectInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class QuaverHitObjectInfo
{
public float StartTime { get; set; }
public int Lane { get; set; }
public string Type { get; set; }
public float EndTime { get; set; }
public string TimingGroup { get; set; }

Expand Down
6 changes: 6 additions & 0 deletions fluXis.Import.Stepmania/Map/StepmaniaFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ public List<MapInfo> ToMapInfos()
break;

case StepNote.Mine:
map.HitObjects.Add(new HitObject
{
Time = (int)Math.Round(time, MidpointRounding.AwayFromZero),
Lane = i + 1,
Type = 2,
});
break;

default:
Expand Down
6 changes: 4 additions & 2 deletions fluXis.Import.Stepmania/StepmaniaImport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ public override List<RealmMapSet> GetMaps()

foreach (var info in infos)
{
var hits = info.HitObjects.Count(x => !x.LongNote);
var lns = info.HitObjects.Count(x => x.LongNote);
var hits = info.HitObjects.Count(x => !x.LongNote && x.Type == 1);
var lns = info.HitObjects.Count(x => x.LongNote && x.Type == 1);
var mines = info.HitObjects.Count(x => x.Landmine);
var length = info.HitObjects.Max(x => x.Time);

var map = new StepManiaRealmMap
Expand All @@ -120,6 +121,7 @@ public override List<RealmMapSet> GetMaps()
BPMMax = info.TimingPoints.Max(x => x.BPM),
NoteCount = hits,
LongNoteCount = lns,
LandmineCount = mines,
NotesPerSecond = (float)((hits + lns) / (length / 1000f))
}
};
Expand Down
10 changes: 9 additions & 1 deletion fluXis/Database/FluXisRealm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public class FluXisRealm : IDisposable
/// 18 - Reset online score IDs
/// 19 - Add `RealmMapUserSettings`
/// 20 - Added `AudioHash` &amp; 'EnableVisualization' to RealmMap
/// 21 - Add `LandmineCount` to `RealmMapFilters`
/// </summary>
private const int schema_version = 20;
private const int schema_version = 21;

private Realm updateRealm;

Expand Down Expand Up @@ -335,6 +336,13 @@ private void migrateTo(Migration migration, ulong targetSchemaVersion)
Logger.Log($"(Schema v20) Migration took {sw.ElapsedMilliseconds}ms for {maps.Count} maps");
break;
}

case 21:
{
var maps = migration.NewRealm.All<RealmMap>().ToList();
maps.ForEach(x => x.Filters.LandmineCount = 0);
break;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions fluXis/Database/Maps/RealmMapFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class RealmMapFilters : RealmObject

public int NoteCount { get; set; }
public int LongNoteCount { get; set; }
public int LandmineCount { get; set; }
public float NotesPerSecond { get; set; }

[Ignored]
Expand Down Expand Up @@ -55,6 +56,7 @@ public void Reset()
BPMMax = 0;
NoteCount = 0;
LongNoteCount = 0;
LandmineCount = 0;
NotesPerSecond = 0;
Effects = 0;
}
Expand Down
7 changes: 7 additions & 0 deletions fluXis/Map/Structures/HitObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,23 @@ public class HitObject : ITimedObject
[JsonProperty("group", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Group { get; set; }

[JsonProperty("hidden")]
public bool Hidden { get; set; }

/// <summary>
/// 0 = Normal / Long
/// 1 = Tick
/// 2 = Landmine
/// </summary>
[JsonProperty("type")]
public int Type { get; set; }

[JsonIgnore]
public bool LongNote => HoldTime > 0 && Type == 0;

[JsonIgnore]
public bool Landmine => Type == 2;

[JsonIgnore]
public double EndTime
{
Expand Down
23 changes: 23 additions & 0 deletions fluXis/Mods/NoMineMod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using fluXis.Graphics.Sprites.Icons;
using fluXis.Map;
using osu.Framework.Graphics.Sprites;

namespace fluXis.Mods;

public class NoMineMod : IMod, IApplicableToMap
{
public string Name => "No Mines";
public string Acronym => "NMN";
public string Description => "Removes all landmines.";
public IconUsage Icon => FontAwesome6.Solid.Flag;
public ModType Type => ModType.Misc;
public float ScoreMultiplier => .8f;
public bool Rankable => true;
public Type[] IncompatibleMods => Array.Empty<Type>();

public void Apply(MapInfo map)
{
map.HitObjects.RemoveAll(hitObject => hitObject.Landmine);
}
}
3 changes: 3 additions & 0 deletions fluXis/Online/API/Models/Maps/APIMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public class APIMap
[JsonProperty("long-notes")]
public int LongNoteCount { get; init; }

[JsonProperty("landmines")]
public int LandmineCount { get; init; }

[JsonProperty("maxcombo")]
public int MaxCombo { get; init; }

Expand Down
5 changes: 3 additions & 2 deletions fluXis/Online/API/Payloads/Scores/ScoreSubmissionPayload.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using fluXis.Replays;
using fluXis.Scoring.Enums;
using Newtonsoft.Json;

namespace fluXis.Online.API.Payloads.Scores;
Expand Down Expand Up @@ -41,7 +42,7 @@ public class Result
[JsonProperty("diff")]
public double Difference { get; set; }

[JsonProperty("end")]
public bool HoldEnd { get; set; }
[JsonProperty("type")]
public ResultType Type { get; set; }
}
}
2 changes: 1 addition & 1 deletion fluXis/Online/API/Requests/Scores/ScoreSubmitRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected override WebRequest CreateWebRequest(string url)
var results = score.HitResults.Select(x => new ScoreSubmissionPayload.Result
{
Difference = x.Difference,
HoldEnd = x.HoldEnd
Type = x.Type
});

var payload = new ScoreSubmissionPayload
Expand Down
1 change: 1 addition & 0 deletions fluXis/Replays/AutoGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ private IEnumerable<IAction> generateActions()
for (int i = 0; i < objects.Count; i++)
{
var currentObject = objects[i];
if (currentObject.Type == 2) continue;

if (currentObject.Time < blockedUntil)
continue;
Expand Down
8 changes: 8 additions & 0 deletions fluXis/Scoring/Enums/ResultType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace fluXis.Scoring.Enums;

public enum ResultType
{
Hit,
HoldEnd,
Landmine
};
6 changes: 3 additions & 3 deletions fluXis/Scoring/HitWindows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public Judgement JudgementFor(double milliseconds)
{
milliseconds = Math.Abs(milliseconds);

for (var result = Judgement.Flawless; result >= Judgement.Miss; --result)
foreach (var timing in Timings)
{
if (milliseconds <= TimingFor(result))
return result;
if (milliseconds <= timing.Milliseconds)
return timing.Judgement;
}

return Judgement.Miss;
Expand Down
17 changes: 17 additions & 0 deletions fluXis/Scoring/LandmineWindows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using fluXis.Scoring.Enums;

namespace fluXis.Scoring;

public class LandmineWindows : HitWindows
{
public LandmineWindows(float difficulty, float rate)
: base(difficulty, rate)
{
}

protected override Timing[] CreateTimings(float difficulty, float multiplier) => new Timing[]
{
new(Judgement.Miss, difficulty, multiplier, 64, 49, 34), //matches "Perfect" judgement from regular HitWindows
new(Judgement.Flawless, difficulty, multiplier, 188, 173, 158)
};
}
2 changes: 2 additions & 0 deletions fluXis/Scoring/Processing/ScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ public static double CalculatePerformance(float rating, float accuracy, int flaw
val *= 0.6;
if (mods.Any(x => x is NoEventMod))
val *= 0.4;
if (mods.Any(x => x is NoMineMod))
val *= 0.6; // TODO: figure out actual value

return val;
}
Expand Down
8 changes: 4 additions & 4 deletions fluXis/Scoring/Structs/HitResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ namespace fluXis.Scoring.Structs;
[JsonProperty("judgement")]
public Judgement Judgement { get; init; }

[JsonProperty("holdend")]
public bool HoldEnd { get; init; }
[JsonProperty("result-type")]
public ResultType Type { get; init; }

public HitResult(double time, double diff, Judgement jud, bool end)
public HitResult(double time, double diff, Judgement jud, ResultType type)
{
Time = time;
Difference = diff;
Judgement = jud;
HoldEnd = end;
Type = type;
}

[JsonConstructor]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Linq;
using fluXis.Screens.Edit.Tabs.Charting.Playfield;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osuTK.Input;

namespace fluXis.Screens.Edit.Tabs.Charting.Blueprints.Placement;

public partial class LandminePlacementBlueprint : NotePlacementBlueprint
{
public override bool AllowPainting => true;
private readonly BlueprintNotePiece piece;

public LandminePlacementBlueprint()
{
RelativeSizeAxes = Axes.Both;
InternalChild = piece = new BlueprintNotePiece
{
Anchor = Anchor.TopLeft,
Origin = Anchor.BottomLeft,
};
//piece.Child.Colour = Theme.Red;

Hit.Type = 2;
}

public override void UpdatePlacement(double time, int lane)
{
base.UpdatePlacement(time, lane);

piece.Width = EditorHitObjectContainer.NOTEWIDTH;
piece.Position = ToLocalSpace(PositionProvider.ScreenSpacePositionAtTime(time, lane));
}

protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button != MouseButton.Left)
return false;

base.OnMouseDown(e);
FinishPlacement(true);
return true;
}

protected override void OnPlacementFinished(bool commit)
{
if (Map.MapInfo.HitObjects.Where(x => x.Lane == Hit.Lane).Any(x => Math.Abs(x.Time - Hit.Time) < 10))
return;

base.OnPlacementFinished(commit);
}
}
5 changes: 3 additions & 2 deletions fluXis/Screens/Edit/Tabs/Charting/ChartingContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public partial class ChartingContainer : EditorTabContainer, IKeyBindingHandler<
new SelectTool(),
new SingleNoteTool(),
new LongNoteTool(),
new TickNoteTool()
new TickNoteTool(),
new LandmineTool()
};

public IReadOnlyList<EffectTool> EffectTools { get; } = new EffectTool[]
Expand Down Expand Up @@ -140,7 +141,7 @@ protected override void LoadComplete()
new()
{
Title = "Tools",
ExtraTitle = "(1-4)",
ExtraTitle = "(1-5)",
Icon = FontAwesome6.Solid.Pen,
Tools = Tools
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ private void add(HitObject info)
case 1:
draw = new EditorTickNote(info);
break;

case 2:
draw = new EditorLandmine(info);
break;
}

if (draw is null)
Expand Down
Loading
Loading