public LanePlayfield(LanedHitLane type) { bool isAirLane = type == LanedHitLane.Air; lane = type; Name = $"{(isAirLane ? "Air" : "Ground")} Playfield"; Padding = new MarginPadding { Left = RushPlayfield.HIT_TARGET_OFFSET }; Anchor = Origin = isAirLane ? Anchor.TopCentre : Anchor.BottomCentre; RelativeSizeAxes = Axes.Both; Size = new Vector2(1, 0); AddRangeInternal(new Drawable[] { new Container { Name = "Hit Target", Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, Size = new Vector2(RushPlayfield.HIT_TARGET_SIZE), Child = new SkinnableDrawable(new RushSkinComponent(isAirLane ? RushSkinComponents.AirHitTarget : RushSkinComponents.GroundHitTarget), _ => new HitTarget { RelativeSizeAxes = Axes.Both, }, confineMode: ConfineMode.ScaleToFit), }, effectsContainer = new Container(), judgementContainer = new JudgementContainer <DrawableJudgement>(), HitObjectContainer, }); }
protected override void OnApply() { base.OnApply(); Lane = HitObject.Lane; Anchor = LaneAnchor; AccentColour.Value = LaneAccentColour; }
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { DrawableRushHitObject rushJudgedObject = (DrawableRushHitObject)judgedObject; RushJudgementResult rushResult = (RushJudgementResult)result; PlayerSprite.HandleResult(rushJudgedObject, result); // Display hit explosions for objects that allow it. if (result.IsHit && rushJudgedObject.DisplayExplosion) { Drawable explosion = rushJudgedObject switch { DrawableStarSheetHead head => sheetExplosionPool.Get(s => s.Apply(head)), DrawableStarSheetTail tail => sheetExplosionPool.Get(s => s.Apply(tail)), DrawableHeart heart => heartExplosionPool.Get(h => h.Apply(heart)), _ => explosionPool.Get(h => h.Apply(rushJudgedObject)), }; if (rushJudgedObject is IDrawableLanedHit laned) { playfieldForLane(laned.Lane).AddExplosion(explosion); } } // Display health point difference if the judgement result implies it. var pointDifference = rushResult.Judgement.HealthPointIncreaseFor(rushResult); if (pointDifference != 0) { overPlayerEffectsContainer.Add(healthTextPool.Get(h => h.Apply(pointDifference))); } // Display judgement results in a drawable for objects that allow it. if (rushJudgedObject.DisplayResult) { DrawableRushJudgement judgementDrawable = judgementPool.Get(j => j.Apply(result, judgedObject)); LanedHitLane judgementLane = LanedHitLane.Air; // TODO: showing judgements based on the judged object suggests that // this may want to be inside the object class as well. switch (rushJudgedObject.HitObject) { case Sawblade sawblade: judgementLane = sawblade.Lane.Opposite(); break; case LanedHit lanedHit: judgementLane = lanedHit.Lane; break; case MiniBoss _: break; } playfieldForLane(judgementLane).AddJudgement(judgementDrawable); } }
public DrawableLanedObjectPool(LanedHitLane lane, int initialSize, int?maximumSize) : base(initialSize, maximumSize) { this.lane = lane; }
private bool typeForObject(HitObject hitObject, out HitObjectType hitObjectType, out LanedHitLane lane, out MinionSize minionSize) { const float vertical_left = 170f; const float vertical_right = 340f; const float horizontal_top = 160f; const float horizontal_middle = 192f; const float horizontal_bottom = 224f; bool hasClap() => hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP); bool hasFinish() => hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); bool hasWhistle() => hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE); hitObjectType = HitObjectType.Minion; lane = LanedHitLane.Air; minionSize = MinionSize.Small; // this should never happen, honestly if (!(hitObject is IHasPosition position)) { return(false); } if (hitObject is IHasDuration && !(hitObject is IHasDistance)) { hitObjectType = HitObjectType.MiniBoss; return(true); } if (position.X < vertical_left) { if (position.Y >= horizontal_top && position.Y < horizontal_bottom) { hitObjectType = hitObject is IHasDuration ? HitObjectType.DualStarSheet : HitObjectType.DualHit; return(true); } lane = position.Y < horizontal_top ? LanedHitLane.Air : LanedHitLane.Ground; if (hitObject is IHasDuration) { hitObjectType = HitObjectType.StarSheet; } else { hitObjectType = HitObjectType.Minion; if (hasWhistle()) { minionSize = MinionSize.Medium; } else if (hasClap()) { minionSize = MinionSize.Large; } } return(true); } lane = position.Y < horizontal_middle ? LanedHitLane.Air : LanedHitLane.Ground; if (position.X >= vertical_right) { hitObjectType = HitObjectType.Hammer; return(true); } hitObjectType = hasFinish() ? HitObjectType.Heart : HitObjectType.Sawblade; return(true); }
private LanePlayfield playfieldForLane(LanedHitLane lane) => lane == LanedHitLane.Air ? airLane : groundLane;
protected override IEnumerable <RushHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { void updatePrevious(LanedHitLane?newLane) { previousLane = newLane; previousSourceTime = original.GetEndTime(); previousSourcePosition = (original as IHasPosition)?.Position; } // if it's definitely a spinner, return a miniboss if (original is IHasDuration && !(original is IHasDistance)) { yield return(createMiniBoss(original)); updatePrevious(null); yield break; } // otherwise do some flag magic Random random = new Random((int)original.StartTime); HitObjectFlags flags = flagsForHitObject(original); // if no flags, completely skip this object if (flags == HitObjectFlags.None) { updatePrevious(previousLane); yield break; } LanedHitLane?lane = null; var kiaiMultiplier = original.Kiai ? kiai_multiplier : 1; // try to get a lane from the force flags if (flags.HasFlagFast(HitObjectFlags.ForceSameLane) || flags.HasFlagFast(HitObjectFlags.SuggestSameLane) && random.NextDouble() < suggest_probability) { lane = previousLane; } else if (flags.HasFlagFast(HitObjectFlags.ForceNotSameLane) || flags.HasFlagFast(HitObjectFlags.SuggestNotSameLane) && random.NextDouble() < suggest_probability) { lane = previousLane?.Opposite(); } // get the lane from the object lane ??= laneForHitObject(original); // if we should end a sheet, try to if (currentStarSheets.Count > 0 && (flags.HasFlagFast(HitObjectFlags.ForceEndStarSheet) || flags.HasFlagFast(HitObjectFlags.SuggestEndStarSheet) && random.NextDouble() < starsheet_end_probability)) { // TODO: for now we'll end both sheets where they are and ignore snapping logic currentStarSheets.Clear(); } // if we should start a starsheet... if (flags.HasFlagFast(HitObjectFlags.ForceStartStarSheet) || flags.HasFlagFast(HitObjectFlags.SuggestStartStarSheet) && random.NextDouble() < starsheet_start_probability) { // TODO: for now, end all existing sheets currentStarSheets.Clear(); // use the suggested lane or randomly select one LanedHitLane sheetLane = lane ?? (random.NextDouble() < 0.5 ? LanedHitLane.Ground : LanedHitLane.Air); // create a sheet StarSheet sheet = currentStarSheets[sheetLane] = createStarSheet(original, sheetLane, original.Samples); LanedHitLane otherLane = sheetLane.Opposite(); // FIXME: surely this is bad, altering the hit object after it's been returned??? if (sheet != null) { yield return(sheet); } // for sliders with repeats, add extra objects to the lane without a sheet if (original is IHasRepeats hasRepeats && hasRepeats.RepeatCount > 0) { var duration = original.GetEndTime() - original.StartTime; var repeatDuration = duration / hasRepeats.SpanCount(); var skip = 1; // Currently an issue where an odd number of repeats (span count) will skip // the final minion if repeats are too short. Not sure what to do here since // it doesn't make rhythmic sense to add an extra hit object. // Examples: // *-*-*-*-* becomes *---*---* (good) // *-*-*-* becomes *---*-- (looks bad) instead of *---*-* (rhythmically worse) while (repeatDuration < min_repeat_time) { repeatDuration *= 2; skip *= 2; } var repeatCurrent = original.StartTime; var index = -1; foreach (var nodeSample in hasRepeats.NodeSamples) { index++; if (index % skip != 0) { continue; } yield return(createNormalHit(original, otherLane, nodeSample, repeatCurrent)); repeatCurrent += repeatDuration; } } // otherwise we have a chance to make a dual sheet else if (random.NextDouble() < starsheet_dual_probability) { currentStarSheets[otherLane] = createStarSheet(original, otherLane, null); yield return(currentStarSheets[otherLane]); } updatePrevious(sheetLane); yield break; }
public static LanedHitLane Opposite(this LanedHitLane lane) => lane == LanedHitLane.Air ? LanedHitLane.Ground : LanedHitLane.Air;
private float judgementPositionForLane(LanedHitLane lane) => lane == LanedHitLane.Air ? -JUDGEMENT_OFFSET : judgementContainer.DrawHeight - JUDGEMENT_OFFSET;