package com.fteams.siftrain.objects; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.fteams.siftrain.assets.Assets; import com.fteams.siftrain.assets.GlobalConfiguration; import com.fteams.siftrain.assets.Results; import com.fteams.siftrain.entities.SimpleNotesInfo; import com.fteams.siftrain.util.SongUtils; public class CircleMark implements Comparable<CircleMark> { public float getSize() { return size; } public int getEffectMask() { return effect; } public SimpleNotesInfo getNote() { return note; } public Vector2 getHoldReleasePosition() { return holdReleasePosition; } @Override public int compareTo(CircleMark o) { if (this.spawnTime != o.spawnTime) { return Float.compare(spawnTime, o.spawnTime); } return SongUtils.compare(destination, o.destination); } public enum Accuracy { NONE, MISS, BAD, GOOD, GREAT, PERFECT } public boolean visible; public boolean holdBeamVisible; public boolean waiting; public boolean holding; public boolean miss; public boolean endVisible; public boolean waitingStart; public boolean waitingEnd; public boolean processed; public boolean hold; public boolean soundPlayed; public boolean sound2Played; public boolean left; Vector2 origin = new Vector2(); Vector2 position = new Vector2(); Vector2 holdReleasePosition = new Vector2(); Vector2 velocity = new Vector2(); public Integer notePosition; public Integer destination = 0; private Double speed; SimpleNotesInfo note; private float spawnTime; private float despawnTime; private float startWaitTime; private float endWaitTime; private float holdEndSpawnTime; private float holdEndDespawnTime; private float holdEndStartWaitTime; private float holdEndEndWaitTime; public float alpha = 1f; public float alpha2 = 1f; public Integer effect; // only for holds private float size; private float size2; public CircleMark(float x, float y, SimpleNotesInfo note, Double noteSpeed, float delay) { float timing = (float) (delay + note.timing_sec * 1f + GlobalConfiguration.offset * 1f / 1000f); notePosition = note.position; this.origin.x = x; this.origin.y = y; this.position.x = x; this.position.y = y; this.holdReleasePosition.x = x; this.holdReleasePosition.y = y; this.note = note; this.effect = note.effect; this.hold = (note.effect & SongUtils.NOTE_TYPE_HOLD) != 0; // position goes 9-8-...-2-1 this.destination = note.position - 1; this.speed = noteSpeed; this.spawnTime = (float) (timing - speed); this.startWaitTime = (float) (timing - SongUtils.overallDiffBad[GlobalConfiguration.overallDifficulty] / 1000f); this.endWaitTime = (float) (timing + SongUtils.overallDiffBad[GlobalConfiguration.overallDifficulty] / 1000f); this.despawnTime = timing * 1.0f; this.size = 0.1f; this.size2 = 0f; if (hold) { this.holdEndSpawnTime = (float) (timing + note.effect_value - speed); this.holdEndStartWaitTime = (float) (timing + note.effect_value - SongUtils.overallDiffBad[GlobalConfiguration.overallDifficulty] / 1000f); this.holdEndEndWaitTime = (float) (timing + note.effect_value + SongUtils.overallDiffBad[GlobalConfiguration.overallDifficulty] / 1000f); this.holdEndDespawnTime = (float) (timing + note.effect_value); } previousTime = 0f; previousSystemTime = 0L; initializeVelocity(); initializeStates(); } private boolean firstHit; private boolean secondHit; public Accuracy accuracyStart; public Accuracy accuracyEnd; public Float accuracyHitStartTime; public Float accuracyHitEndTime; public void updateDestination(int newDestination) { this.destination = newDestination; this.notePosition = newDestination + 1; // reset the velocity vectors; initializeVelocity(); } private void initializeStates() { visible = false; holdBeamVisible = false; waiting = false; miss = false; holding = false; endVisible = false; waitingStart = false; waitingEnd = false; processed = false; soundPlayed = false; sound2Played = false; alpha = 1f; alpha2 = 1f; } public void reset() { this.accuracyStart = null; this.accuracyEnd = null; this.accuracyHitStartTime = null; this.accuracyHitEndTime = null; this.position.x = this.origin.x; this.position.y = this.origin.y; this.holdReleasePosition.x = this.origin.x; this.holdReleasePosition.y = this.origin.y; this.size = 0.1f; this.size2 = 0f; firstHit = false; secondHit = false; initializeStates(); } private void initializeVelocity() { float xVel = (float) Math.cos((destination) * Math.PI / 8); float yVel = -(float) Math.sin((destination) * Math.PI / 8); velocity.x = (float) (xVel * 249 / speed); velocity.y = (float) (yVel * 249 / speed); } public boolean isDone() { return miss || (firstHit && secondHit); } float previousTime; long previousSystemTime; public void update(float time) { if (miss || (firstHit && secondHit)) { if (visible) { visible = false; holdBeamVisible = false; } if (endVisible) { endVisible = false; } return; } updateFirst(time); if (hold) { updateSecond(time); } processMiss(time); previousTime = time; previousSystemTime = System.currentTimeMillis(); } private void updateFirst(float time) { if (spawnTime <= time && despawnTime > time && !visible) { visible = true; holdBeamVisible = true; } if (spawnTime >= time && visible) { visible = false; holdBeamVisible = false; } if (visible && despawnTime <= time) { if (GlobalConfiguration.playHintSounds && !soundPlayed) { Assets.perfectSound.play(GlobalConfiguration.feedbackVolume / 200f); soundPlayed = true; } if (holding) { alpha = 1f; } else { alpha = MathUtils.clamp((endWaitTime - time) / (endWaitTime - despawnTime), 0f, 1f); if (alpha == 0f) { visible = false; holdBeamVisible = false; } } } if (visible) { float scl = time - spawnTime; if (holding) { scl = speed.floatValue(); updateSize(Math.max(0f, despawnTime - time)); } else updateSize(despawnTime - time); position.set(origin.cpy().add(velocity.cpy().scl(scl))); } if (startWaitTime <= time && endWaitTime > time && !waiting) { waiting = true; waitingStart = true; } } private void updateSecond(float time) { if (holdEndSpawnTime <= time && holdEndDespawnTime > time && !endVisible) { endVisible = true; waitingEnd = true; } if (holdEndSpawnTime >= time && endVisible) endVisible = false; if (endVisible && holdEndDespawnTime <= time) { if (GlobalConfiguration.playHintSounds && !sound2Played) { Assets.perfectSound.play(GlobalConfiguration.feedbackVolume / 200f); sound2Played = true; } alpha2 = MathUtils.clamp((holdEndEndWaitTime - time) / (holdEndEndWaitTime - holdEndDespawnTime), 0f, 1f); if (alpha2 == 0f) endVisible = false; } if (endVisible) { updateSize2(holdEndDespawnTime - time); holdReleasePosition.add(velocity.cpy().scl(time - previousTime)); } if (holdEndStartWaitTime <= time && holdEndEndWaitTime > time && !waitingEnd && waiting) { waitingEnd = true; } } private void processMiss(float time) { // miss if we miss the first note if (hold && !firstHit && !holding && endWaitTime <= time && waitingStart && !miss) { waiting = false; endVisible = false; miss = true; accuracyStart = Accuracy.MISS; accuracyEnd = Accuracy.MISS; // System.out.println("MISS-001: didn't hit the note"); } else if (!hold && endWaitTime <= time && waitingStart && !miss) { waiting = false; endVisible = false; miss = true; accuracyStart = Accuracy.MISS; //System.out.println("MISS-002: didn't hit the note"); } if (hold && !miss) { // miss if we hold for too long if (holdEndEndWaitTime <= time && waitingEnd && !miss) { //System.out.println("MISS-003: held for too long"); miss = true; waitingEnd = false; holding = false; endVisible = false; waiting = false; accuracyEnd = Accuracy.MISS; } // miss if we release before we start waiting if (firstHit && holdEndSpawnTime > time && !holding && !miss) { accuracyEnd = Accuracy.MISS; waiting = false; endVisible = false; miss = true; //System.out.println("MISS-004: released hold too early"); } } } public float getSize2() { return this.size2; } public Accuracy hit() { float delta = (System.currentTimeMillis() - previousSystemTime) / 1000f; float hit = previousTime + delta - despawnTime - GlobalConfiguration.inputOffset / 1000f; Accuracy accuracy = Results.getAccuracyFor(hit); // If the note was tapped too early, we ignore the tap if (despawnTime > previousTime && accuracy == Accuracy.MISS) { return Accuracy.NONE; } accuracyHitStartTime = hit; if (hold) { holding = true; accuracyStart = accuracy; firstHit = true; } else { accuracyStart = accuracy; accuracyEnd = Accuracy.NONE; firstHit = true; secondHit = true; processed = true; waiting = false; visible = false; holdBeamVisible = false; } waitingStart = false; // calculate hit accuracy return accuracyStart; } public Accuracy release() { if (!firstHit) { return Accuracy.NONE; } secondHit = true; float delta = (System.currentTimeMillis() - previousSystemTime) / 1000f; float hit = previousTime + delta - holdEndDespawnTime - GlobalConfiguration.inputOffset / 1000f; waitingEnd = false; holding = false; endVisible = false; holdBeamVisible = false; waiting = false; accuracyEnd = Results.getAccuracyFor(hit); if (accuracyEnd == Accuracy.MISS) { processed = true; //System.out.println("MISS-005: Released hold too early"); } else { accuracyHitEndTime = hit; } return accuracyEnd; } private void updateSize(float despawnTime) { float progress = (float) ((speed - despawnTime) / speed); this.size = 0.1f + progress * 0.9f; } private void updateSize2(float despawnTime) { float progress = (float) ((speed - despawnTime) / speed); this.size2 = progress; } public Vector2 getPosition() { return position; } }