/*
* ShootOFF - Software for Laser Dry Fire Training
* Copyright (C) 2016 phrack
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.shootoff.plugins;
import java.io.File;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.shootoff.camera.Shot;
import com.shootoff.gui.DelayedStartListener;
import com.shootoff.targets.Hit;
import com.shootoff.targets.Target;
import com.shootoff.util.NamedThreadFactory;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
public class TimedHolsterDrill extends TrainingExerciseBase implements TrainingExercise, DelayedStartListener {
private final static String LENGTH_COL_NAME = "Length";
private final static int LENGTH_COL_WIDTH = 60;
private final static int START_DELAY = 10; // s
private final static int RESUME_DELAY = 5; // s
private static final int CORE_POOL_SIZE = 2;
private static final String PAUSE = "Pause";
private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(CORE_POOL_SIZE,
new NamedThreadFactory("TimedHolsterDrillExercise"));
private int delayMin = 4;
private int delayMax = 8;
private boolean repeatExercise = true;
private long beepTime = 0;
private boolean hadShot = false;
private boolean coloredRows = false;
private Button pauseResumeButton;
public TimedHolsterDrill() {}
public TimedHolsterDrill(List<Target> targets) {
super(targets);
}
@Override
public void targetUpdate(Target target, TargetChange change) {}
@Override
public ExerciseMetadata getInfo() {
return new ExerciseMetadata("Timed Holster Drill", "1.0", "phrack",
"This exercise does not require a target, but one may be used "
+ "to give the shooter something to shoot at. When the exercise "
+ "is started you are asked to enter a range for randomly "
+ "delayed starts. You are then given 10 seconds to position "
+ "yourself. After a random wait (within the entered range) a "
+ "beep tells you to draw their pistol from it's holster, "
+ "fire at your target, and finally re-holster. This process is "
+ "repeated as long as this exercise is on.");
}
@Override
public void init() {
initUI();
initService();
}
@Override
public void shotListener(Shot shot, Optional<Hit> hi) {
if (repeatExercise) {
hadShot = true;
setLength();
}
}
protected void setLength() {
final float drawShotLength = (float) (System.currentTimeMillis() - beepTime) / (float) 1000; // s
setShotTimerColumnText(LENGTH_COL_NAME, String.format("%.2f", drawShotLength));
}
@Override
public void reset(List<Target> targets) {
repeatExercise = false;
pauseShotDetection(true);
executorService.shutdownNow();
pauseResumeButton.setText(PAUSE);
resetValues();
repeatExercise = true;
executorService = Executors.newScheduledThreadPool(CORE_POOL_SIZE,
new NamedThreadFactory("TimedHolsterDrillExercise"));
executorService.schedule(new SetupWait(), START_DELAY, TimeUnit.SECONDS);
}
@Override
public void destroy() {
repeatExercise = false;
executorService.shutdownNow();
super.destroy();
}
protected class SetupWait implements Runnable {
@Override
public void run() {
if (!repeatExercise) return;
pauseShotDetection(true);
playSound(new File("sounds/voice/shootoff-makeready.wav"));
final int randomDelay = new Random().nextInt((delayMax - delayMin) + 1) + delayMin;
if (repeatExercise) executorService.schedule(new Round(), randomDelay, TimeUnit.SECONDS);
return;
}
}
protected class Round implements Runnable {
@Override
public void run() {
if (repeatExercise) {
doRound();
executorService.schedule(new Round(), setupRound(), TimeUnit.SECONDS);
}
return;
}
}
protected void initUI() {
pauseResumeButton = addShootOFFButton(PAUSE, (event) -> {
final Button pauseResumeButton = (Button) event.getSource();
if (PAUSE.equals(pauseResumeButton.getText())) {
pauseResumeButton.setText("Resume");
repeatExercise = false;
pauseShotDetection(true);
} else {
pauseResumeButton.setText(PAUSE);
repeatExercise = true;
executorService.schedule(new SetupWait(), RESUME_DELAY, TimeUnit.SECONDS);
}
});
addShootOFFButton("Clear Shots", (event) -> super.clearShots());
addShotTimerColumn(LENGTH_COL_NAME, LENGTH_COL_WIDTH);
}
protected void initService() {
pauseShotDetection(true);
resetValues();
executorService.schedule(new SetupWait(), START_DELAY, TimeUnit.SECONDS);
}
protected int setupRound() {
// Only toggle the color if there was a shot in the last round
// otherwise the colors get out of sync if the user misses a
// round (thus you can have a string of shots that is all gray
// or white even though they were different rounds)
if (hadShot) {
coloredRows = !coloredRows;
hadShot = false;
}
if (coloredRows) {
setShotTimerRowColor(Color.LIGHTGRAY);
} else {
setShotTimerRowColor(null);
}
final int randomDelay = new Random().nextInt((delayMax - delayMin) + 1) + delayMin;
return randomDelay;
}
protected void doRound() {
playSound("sounds/beep.wav");
pauseShotDetection(false);
startRoundTimer();
}
protected void startRoundTimer() {
beepTime = System.currentTimeMillis();
}
@Override
public void updatedDelayedStartInterval(int min, int max) {
delayMin = min;
delayMax = max;
}
protected void resetValues() {
getDelayedStartInterval(this);
}
}