/* * 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.List; import java.util.Optional; import java.util.Random; import com.shootoff.camera.Shot; import com.shootoff.targets.Hit; import com.shootoff.targets.Target; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Bounds; import javafx.geometry.Dimension2D; import javafx.geometry.HPos; import javafx.geometry.Point2D; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.util.Duration; public class BouncingTargets extends ProjectorTrainingExerciseBase implements TrainingExercise { private int shootCount = 4; private int dontShootCount = 1; private static int maxVelocity = 10; private boolean removeShootTargets = false; private static final List<BouncingTarget> shootTargets = new ArrayList<>(); private static final List<BouncingTarget> dontShootTargets = new ArrayList<>(); private static ProjectorTrainingExerciseBase thisSuper; private Timeline targetAnimation; private int score = 0; public BouncingTargets() {} public BouncingTargets(List<Target> targets) { super(targets); setThisSuper(super.getInstance()); } @Override public void targetUpdate(Target target, TargetChange change) {} // For testing protected void init(int shootCount, int dontShootCount, int maxVelocity) { this.shootCount = shootCount; this.dontShootCount = dontShootCount; setMaxVelocity(maxVelocity); shootTargets.clear(); dontShootTargets.clear(); startExercise(); } private static void setThisSuper(ProjectorTrainingExerciseBase thisSuper) { BouncingTargets.thisSuper = thisSuper; } private static void setMaxVelocity(int maxVelocity) { BouncingTargets.maxVelocity = maxVelocity; } @Override public void init() { addSettingControls(); startExercise(); } private void startExercise() { super.showTextOnFeed("Score: 0"); addTargets(shootTargets, "targets/shoot_dont_shoot/shoot.target", shootCount); addTargets(dontShootTargets, "targets/shoot_dont_shoot/dont_shoot.target", dontShootCount); targetAnimation = new Timeline(new KeyFrame(Duration.millis(20), e -> updateTargets())); targetAnimation.setCycleCount(Timeline.INDEFINITE); targetAnimation.play(); } private void addSettingControls() { final GridPane bouncingTargetsPane = new GridPane(); final ColumnConstraints cc = new ColumnConstraints(100); cc.setHalignment(HPos.CENTER); bouncingTargetsPane.getColumnConstraints().addAll(new ColumnConstraints(), cc); final int MAX_TARGETS = 10; final int MAX_VELOCITY = 30; final int SHOOT_DEFAULT_COUNT = 4 - 1; final int DONT_SHOOT_DEFAULT_COUNT = 1 - 1; final int DEFAULT_MAX_VELOCITY = 10; final ObservableList<String> targetCounts = FXCollections.observableArrayList(); for (int i = 1; i <= MAX_TARGETS; i++) targetCounts.add(Integer.toString(i)); final ComboBox<String> shootTargetsComboBox = new ComboBox<>(targetCounts); shootTargetsComboBox.getSelectionModel().select(SHOOT_DEFAULT_COUNT); shootTargetsComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { shootCount = Integer.parseInt(newValue); stopExercise(); startExercise(); }); bouncingTargetsPane.add(new Label("Shoot Targets:"), 0, 0); bouncingTargetsPane.add(shootTargetsComboBox, 1, 0); final ComboBox<String> dontShootTargetsComboBox = new ComboBox<>(targetCounts); dontShootTargetsComboBox.getSelectionModel().select(DONT_SHOOT_DEFAULT_COUNT); dontShootTargetsComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { dontShootCount = Integer.parseInt(newValue); stopExercise(); startExercise(); }); bouncingTargetsPane.add(new Label("Don't Shoot Targets:"), 0, 1); bouncingTargetsPane.add(dontShootTargetsComboBox, 1, 1); final ObservableList<String> maxVelocity = FXCollections.observableArrayList(); for (int i = 1; i <= MAX_VELOCITY; i++) maxVelocity.add(Integer.toString(i)); final ComboBox<String> maxVelocityComboBox = new ComboBox<>(maxVelocity); maxVelocityComboBox.getSelectionModel().select(DEFAULT_MAX_VELOCITY - 1); maxVelocityComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { BouncingTargets.maxVelocity = Integer.parseInt(newValue); stopExercise(); startExercise(); }); bouncingTargetsPane.add(new Label("Max Target Speed:"), 0, 2); bouncingTargetsPane.add(maxVelocityComboBox, 1, 2); final CheckBox removeTargets = new CheckBox(); removeTargets.setOnAction((event) -> removeShootTargets = removeTargets.isSelected()); bouncingTargetsPane.add(new Label("Remove Hit Shoot Targets:"), 0, 3); bouncingTargetsPane.add(removeTargets, 1, 3); super.addExercisePane(bouncingTargetsPane); } protected List<BouncingTarget> getShootTargets() { return shootTargets; } protected List<BouncingTarget> getDontShootTargets() { return dontShootTargets; } private void updateTargets() { for (final BouncingTarget b : shootTargets) b.moveTarget(); for (final BouncingTarget b : dontShootTargets) b.moveTarget(); } protected static class BouncingTarget { private final Target target; private double dx; private double dy; public BouncingTarget(Target target) { this.target = target; final Random r = new Random(); dx = r.nextInt(maxVelocity + 1) + 1; dy = r.nextInt(maxVelocity + 1) + 1; if (r.nextBoolean()) dx *= -1; if (r.nextBoolean()) dy *= -1; } public Target getTarget() { return target; } private enum CollisionType { NONE, COLLISION_X, COLLISION_Y, COLLISION_BOTH; } private CollisionType checkCollision() { final Bounds targetBounds = target.getBoundsInParent(); List<BouncingTarget> collisionList; if (shootTargets.contains(this)) { collisionList = shootTargets; } else { collisionList = dontShootTargets; } for (final BouncingTarget b : collisionList) { if (b.getTarget().equals(target)) continue; final Bounds bBounds = b.getTarget().getBoundsInParent(); if (targetBounds.intersects(bBounds)) { final boolean atRight = targetBounds.getMaxX() > bBounds.getMinX() && targetBounds.getMaxX() - bBounds.getMinX() < maxVelocity * 2; final boolean atLeft = bBounds.getMaxX() > bBounds.getMinX() && bBounds.getMaxX() - bBounds.getMinX() < maxVelocity * 2; final boolean atBottom = targetBounds.getMaxY() > bBounds.getMinY() && targetBounds.getMaxY() - bBounds.getMinY() < maxVelocity * 2; final boolean atTop = bBounds.getMaxY() > targetBounds.getMinY() && bBounds.getMaxY() - targetBounds.getMinY() < maxVelocity * 2; if ((atRight || atLeft) && (atBottom || atTop)) { return CollisionType.COLLISION_BOTH; } else if (atRight || atLeft) { return CollisionType.COLLISION_X; } else if (atBottom || atTop) { return CollisionType.COLLISION_Y; } } } return CollisionType.NONE; } public void moveTarget() { if (maxVelocity == 0) return; final Bounds b = target.getBoundsInParent(); final Point2D p = target.getPosition(); final Dimension2D d = target.getDimension(); final CollisionType ct = checkCollision(); if (b.getMinX() <= 1 || b.getMinX() + d.getWidth() > thisSuper.getArenaWidth() || ct == CollisionType.COLLISION_X || ct == CollisionType.COLLISION_BOTH) { dx *= -1; } if (b.getMinY() <= 1 || b.getMinY() + d.getHeight() > thisSuper.getArenaHeight() || ct == CollisionType.COLLISION_X || ct == CollisionType.COLLISION_BOTH) { dy *= -1; } target.setPosition(p.getX() + dx, p.getY() + dy); } } private void addTargets(List<BouncingTarget> targets, String target, int count) { for (int i = 0; i < count; i++) { final Optional<Target> newTarget = super.addTarget(new File(target), 0, 0); if (newTarget.isPresent()) { // Randomly place the target final int maxX = (int) (super.getArenaWidth() - newTarget.get().getDimension().getWidth() - 50); final int x = new Random().nextInt(maxX + 1) + 1; final int maxY = (int) (super.getArenaHeight() - newTarget.get().getDimension().getHeight() - 50); final int y = new Random().nextInt(maxY + 1) + 1; newTarget.get().setPosition(x, y); targets.add(new BouncingTarget(newTarget.get())); } } } @Override public ExerciseMetadata getInfo() { return new ExerciseMetadata("Bouncing Targets", "1.0", "phrack", "This exercise randomly moves shoot (gray ring) and don't shoot (red ring) targets" + " around the arena. All targets bounce off the arena bounds and targets of the " + "same type can bounce off of each other. Don't shoot targets are always able to " + "overlap shoot targets. This exercise is scored. Your score is the tally of how " + "many shoot targets you have hit since shooting your last don't shoot target."); } @Override public void shotListener(Shot shot, Optional<Hit> hit) { if (hit.isPresent()) { if (hit.get().getHitRegion().tagExists("subtarget")) { switch (hit.get().getHitRegion().getTag("subtarget")) { case "shoot": { score++; super.showTextOnFeed(String.format("Score: %d", score)); if (removeShootTargets) { super.removeTarget(hit.get().getTarget()); if (score == shootTargets.size()) { super.playSound("sounds/beep.wav"); TextToSpeech.say(String.format("Your score was %d", score)); stopExercise(); startExercise(); } } } break; case "dont_shoot": { super.playSound("sounds/beep.wav"); TextToSpeech.say(String.format("Your score was %d", score)); if (removeShootTargets) { stopExercise(); startExercise(); } else { score = 0; super.showTextOnFeed("Score: 0"); } } break; } } } } private void stopExercise() { targetAnimation.stop(); for (final BouncingTarget b : shootTargets) super.removeTarget(b.getTarget()); shootTargets.clear(); for (final BouncingTarget b : dontShootTargets) super.removeTarget(b.getTarget()); dontShootTargets.clear(); score = 0; super.showTextOnFeed("Score: 0"); } @Override public void reset(List<Target> targets) { stopExercise(); addTargets(shootTargets, "targets/shoot_dont_shoot/shoot.target", shootCount); addTargets(dontShootTargets, "targets/shoot_dont_shoot/dont_shoot.target", dontShootCount); targetAnimation.play(); } }