/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.shootoff.camera.Shot;
import com.shootoff.targets.Hit;
import com.shootoff.targets.Target;
import com.shootoff.targets.TargetRegion;
import com.shootoff.util.NamedThreadFactory;
public class DuelingTree extends ProjectorTrainingExerciseBase implements TrainingExercise {
private final static String HIT_COL_NAME = "Hit By";
private final static int HIT_COL_WIDTH = 60;
private static final int NEW_ROUND_DELAY = 5; // s
private static final int CORE_POOL_SIZE = 2;
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(CORE_POOL_SIZE,
new NamedThreadFactory("DuelingTreeExercise"));
private TrainingExerciseBase thisSuper;
private boolean continueExercise = true;
private boolean isResetting = false;
private int leftScore = 0;
private int rightScore = 0;
private final List<TargetRegion> paddlesOnLeft = new ArrayList<>();
private final List<TargetRegion> paddlesOnRight = new ArrayList<>();
public DuelingTree() {}
public DuelingTree(List<Target> targets) {
super(targets);
thisSuper = super.getInstance();
findTargets(targets);
}
@Override
public void init() {
// We need to make sure we start with a clean slate because the position
// of the plates matter
super.reset();
super.addShotTimerColumn(HIT_COL_NAME, HIT_COL_WIDTH);
super.showTextOnFeed("left score: 0\nright score: 0");
super.pauseShotDetection(true);
executorService.schedule(() -> {
super.pauseShotDetection(false);
TrainingExerciseBase.playSound("sounds/beep.wav");
}, NEW_ROUND_DELAY, TimeUnit.SECONDS);
}
private boolean findTargets(List<Target> targets) {
boolean foundTarget = false;
// Find the first target with directional subtargets and gets its
// regions
for (final Target target : targets) {
if (foundTarget) break;
for (final TargetRegion region : target.getRegions()) {
if (isLeftPaddle(region)) {
paddlesOnLeft.add(region);
foundTarget = true;
} else if (isRightPaddle(region)) {
paddlesOnRight.add(region);
foundTarget = true;
}
}
}
if (!foundTarget) {
TrainingExerciseBase.playSound(new File("sounds/voice/shootoff-duelingtree-warning.wav"));
continueExercise = false;
}
return foundTarget;
}
private boolean isLeftPaddle(TargetRegion region) {
if (!region.tagExists("subtarget")) return false;
return region.getTag("subtarget").startsWith("left_paddle");
}
private boolean isRightPaddle(TargetRegion region) {
if (!region.tagExists("subtarget")) return false;
return region.getTag("subtarget").startsWith("right_paddle");
}
@Override
public void targetUpdate(Target target, TargetChange change) {
if (TargetChange.REMOVED.equals(change)) return;
for (final TargetRegion r : target.getRegions()) {
if (isLeftPaddle(r) || isRightPaddle(r)) {
continueExercise = findTargets(Arrays.asList(target));
break;
}
}
}
@Override
public ExerciseMetadata getInfo() {
return new ExerciseMetadata("Dueling Tree", "1.0", "phrack",
"This exercise works with the dueling tree target. Challenge "
+ "a friend, assign a side (left or right) to each participant, "
+ "and try to shoot the plates from your side to your friend's "
+ "side. A round ends when all plates are on one person's side.");
}
@Override
public void shotListener(Shot shot, Optional<Hit> hit) {
if (!continueExercise) return;
if (hit.isPresent()) {
final TargetRegion r = hit.get().getHitRegion();
if (r.tagExists("subtarget") && (r.getTag("subtarget").startsWith("left_paddle")
|| r.getTag("subtarget").startsWith("right_paddle"))) {
String hitBy = "";
if (paddlesOnLeft.contains(r)) {
paddlesOnLeft.remove(r);
paddlesOnRight.add(r);
hitBy = "left";
} else if (paddlesOnRight.contains(r)) {
paddlesOnLeft.add(r);
paddlesOnRight.remove(r);
hitBy = "right";
}
super.setShotTimerColumnText(HIT_COL_NAME, hitBy);
if (paddlesOnLeft.size() == 6) {
rightScore++;
roundOver();
}
if (paddlesOnRight.size() == 6) {
leftScore++;
roundOver();
}
}
}
}
private void roundOver() {
if (continueExercise) {
TrainingExerciseBase.playSound("sounds/beep.wav");
thisSuper.showTextOnFeed(String.format("left score: %d%nright score: %d", leftScore, rightScore));
super.pauseShotDetection(true);
isResetting = true;
thisSuper.reset();
isResetting = false;
executorService.schedule(new NewRound(), NEW_ROUND_DELAY, TimeUnit.SECONDS);
}
}
private class NewRound implements Runnable {
@Override
public void run() {
TrainingExerciseBase.playSound("sounds/beep.wav");
thisSuper.pauseShotDetection(false);
}
}
@Override
public void reset(List<Target> targets) {
if (!isResetting) {
leftScore = 0;
rightScore = 0;
super.showTextOnFeed(String.format("left score: 0%nright score: 0"));
}
paddlesOnLeft.clear();
paddlesOnRight.clear();
findTargets(targets);
}
@Override
public void destroy() {
continueExercise = false;
executorService.shutdownNow();
super.destroy();
}
}