diff --git a/html/components/pbt-input.js b/html/components/pbt-input.js
index 8257b7b1..cd1417bd 100644
--- a/html/components/pbt-input.js
+++ b/html/components/pbt-input.js
@@ -20,6 +20,15 @@ const TimerControl = {
Reset: "Reset"
}
+const Colors = {
+ Paused: "white",
+ Alert: "crimson",
+ Stop: "crimson",
+ Inactive: "dimgrey",
+ Active: "white",
+ Start: "limegreen"
+}
+
function isTrue(value) {
'use strict';
if (typeof value === 'boolean') {
@@ -295,39 +304,98 @@ function setupCallbacks() {
);
WS.Register(
- 'ScoreBoard.CurrentGame.BoxClock(*).Time',
+ [
+ 'ScoreBoard.CurrentGame.BoxClock(*).Time',
+ 'ScoreBoard.CurrentGame.Team(*).BoxSeat(*).Started',
+ 'ScoreBoard.CurrentGame.Clock(Jam).Running',
+ ],
function (k, v) {
- var running = isTrue(WS.state['ScoreBoard.CurrentGame.BoxClock(' + k.BoxClock + ').Running']);
- if(running) {
+ var running = isTrue(WS.state['ScoreBoard.CurrentGame.BoxClock(' + k.BoxClock + ').Running']);
+ if(running) {
if(v <= 10000) {
- $('.Time.'+k.BoxClock).css('color', 'crimson');
+ $('.Time.'+k.BoxClock).css('color', Colors.Alert);
} else {
- $('.Time.'+k.BoxClock).css('color', 'white');
+ $('.Time.'+k.BoxClock).css('color', Colors.Active);
+ }
+ } else if(k.BoxClock) {
+ var jamrunning = isTrue(WS.state['ScoreBoard.CurrentGame.Clock(Jam).Running']);
+ var team = k.BoxClock.startsWith('Team2') ? 2 : 1;
+ var pos = '';
+ for(const p of ["Jammer", "Blocker1", "Blocker2", "Blocker3"]) {
+ if(k.BoxClock.endsWith(p)) {
+ pos = p;
+ }
+ }
+
+ var started = isTrue(WS.state['ScoreBoard.CurrentGame.Team('+team+').BoxSeat('+ pos+').Started']);
+ if(!jamrunning && started) {
+ // indicate a clock that is paused between jams so it's clearer it'll start back up on next jam
+ $('.Time.'+k.BoxClock).css('color', Colors.Paused);
+ }
+ } else if(k.BoxSeat) {
+ if(v) {
+ $('.Time.Team' + k.Team + k.BoxSeat).css('color', Colors.Paused);
+ var jamrunning = isTrue(WS.state['ScoreBoard.CurrentGame.Clock(Jam).Running']);
+ if(!jamrunning) {
+ $('.BoxTimerPage button.Team'+k.Team + k.BoxSeat+'.Start').prop("disabled", true);
+ }
+ } else {
+ $('.Time.Team' + k.Team + k.BoxSeat).css('color', Colors.Inactive);
+ $('.BoxTimerPage button.Team'+k.Team + k.BoxSeat+'.Start').prop("disabled", false);
}
- } else {
- $('.Time.'+k.BoxClock).css('color', 'gray');
}
}
);
WS.Register(
- 'ScoreBoard.CurrentGame.BoxClock(*).Running',
+ [
+ 'ScoreBoard.CurrentGame.BoxClock(*).Running',
+ 'ScoreBoard.CurrentGame.Clock(Jam).Running',
+ 'ScoreBoard.CurrentGame.Team(*).BoxSeat(*).Started',
+ ],
function (k, v) {
- if(isTrue(v)) {
+ var running = isTrue(WS.state['ScoreBoard.CurrentGame.BoxClock(' + k.BoxClock + ').Running']);
+ if(k.BoxClock == null) {
+ return;
+ }
+ if(running) {
$('.BoxTimerPage button.'+k.BoxClock+'.Stop').show();
$('.BoxTimerPage button.'+k.BoxClock+'.Start').hide();
$('.BoxTimerPage button.'+k.BoxClock+'.Reset').prop("disabled", true);
- $('.Time.'+k.BoxClock).css('color', 'white');
+ $('.Time.'+k.BoxClock).css('color', Colors.Active);
} else {
$('.BoxTimerPage button.'+k.BoxClock+'.Stop').hide();
$('.BoxTimerPage button.'+k.BoxClock+'.Start').show();
+ $('.BoxTimerPage button.'+k.BoxClock+'.Start').prop("disabled", false);
$('.BoxTimerPage button.'+k.BoxClock+'.Reset').prop("disabled", false);
- $('.Time.'+k.BoxClock).css('color', 'dimgray');
- }
+
+ var jamrunning = isTrue(WS.state['ScoreBoard.CurrentGame.Clock(Jam).Running']);
+ var team = k.BoxClock.startsWith('Team2') ? 2 : 1;
+ var pos = '';
+
+ for(const p of ["Jammer", "Blocker1", "Blocker2", "Blocker3"]) {
+ if(k.BoxClock.endsWith(p)) {
+ pos = p;
+ }
+ }
+ var started = isTrue(WS.state['ScoreBoard.CurrentGame.Team('+team+').BoxSeat('+pos+').Started']);
+ if(started) {
+ $('.Time.'+k.BoxClock).css('color', Colors.Paused);
+ if(!jamrunning) {
+ $('.BoxTimerPage button.'+k.BoxClock+'.Start').prop("disabled", true);
+ }
+ } else {
+ $('.Time.'+k.BoxClock).css('color', Colors.Inactive);
+ $('.BoxTimerPage button.'+k.BoxClock+'.Start').prop("disabled", false);
+ }
+ }
}
);
- WS.Register('ScoreBoard.CurrentGame.Team(*).Skater(*).Role',
+ WS.Register(
+ [
+ 'ScoreBoard.CurrentGame.Team(*).Skater(*).Role',
+ ],
function (k, v) {
if(v == Position.Jammer) {
var selectId = $('#PenaltyBoxJammersTeam'+k.Team+'DisplayJammer,#PenaltyBoxBothTeamsTeam'+k.Team+'DisplayJammer');
@@ -341,7 +409,7 @@ function setupCallbacks() {
WS.Register(
[
'ScoreBoard.CurrentGame.Team(*).BoxSeat(*).BoxSkaterPenalties',
- ],
+ ],
function (k, v) {
var sel = $('.Team'+k.Team+'.'+k.BoxSeat+'.PenaltyCount');
if(sel.length) {
@@ -360,7 +428,7 @@ WS.Register([
function(k, v) {
// Edit the popup that allows adding/removing time to match penalty duration rule
var sel = $('.editTime');
- if(sel.length) {
+ if(sel.length && v) {
// Convert from format like 0:30 or 1:00 to seconds,
// which appears in menu like '+30', '-30' or '+60', '-60'
@@ -437,12 +505,12 @@ WS.Register(
}
function setupButtonsStyle() {
- $('.BoxTimerPage button.Start').css('background-color', 'limegreen');
- $('.BoxTimerPage button.Start').css('color', 'white');
- $('.BoxTimerPage button.Stop').css('background-color', 'crimson');
- $('.BoxTimerPage button.Stop').css('color', 'white');
- $('.BoxTimerPage button.Reset').css('background-color', 'dimgray');
- $('.BoxTimerPage button.Reset').css('color', 'white');
+ $('.BoxTimerPage button.Start').css('background-color', Colors.Start);
+ $('.BoxTimerPage button.Start').css('color', Colors.Active);
+ $('.BoxTimerPage button.Stop').css('background-color', Colors.Stop);
+ $('.BoxTimerPage button.Stop').css('color', Colors.Active);
+ $('.BoxTimerPage button.Reset').css('background-color', Colors.Inactive);
+ $('.BoxTimerPage button.Reset').css('color', Colors.Active);
}
function setupEditButton(pageId, teamId, pos, seatId) {
diff --git a/src/com/carolinarollergirls/scoreboard/core/game/BoxSeatImpl.java b/src/com/carolinarollergirls/scoreboard/core/game/BoxSeatImpl.java
index f91ace0c..3b830650 100644
--- a/src/com/carolinarollergirls/scoreboard/core/game/BoxSeatImpl.java
+++ b/src/com/carolinarollergirls/scoreboard/core/game/BoxSeatImpl.java
@@ -75,7 +75,11 @@ public void setSkater(Skater s) {
@Override
public void startBox() {
+ Clock jc = team.getGame().getClock(Clock.ID_JAM);
if(started()) {
+ if(!jc.isRunning()) {
+ return;
+ }
// Resume a clock that was paused.
synchronized (coreLock) {
Clock pc = getBoxClock();
@@ -109,6 +113,92 @@ public void startBox() {
}
}
restartBox();
+ if(!jc.isRunning()) {
+ stopBox(); // If jam not running, pause right away
+ getBoxClock().resetTime();
+ }
+ }
+ }
+
+ private void doJammerLogic(Clock pc) {
+ if(getFloorPosition() != FloorPosition.JAMMER) {
+ return;
+ }
+ Team otherTeam = (team.getProviderId() == Team.ID_1) ? team.getGame().getTeam(Team.ID_2) : team.getGame().getTeam(Team.ID_1);
+ for(BoxSeat bs : otherTeam.getAll(Team.BOX_SEAT)) {
+ if(bs.getFloorPosition() == FloorPosition.JAMMER) {
+ Clock otherClock = bs.getBoxClock();
+ if(otherClock != null && (otherClock.isRunning() || bs.started())) {
+ long penaltyDuration = team.getGame().getLong(Rule.PENALTIES_DURATION);
+ long otherMaximum = otherClock.getMaximumTime();
+ long thisMaximum = pc.getMaximumTime();
+ long otherTime = otherClock.getTime();
+ long thisTime = pc.getTime();
+ long otherElapsed2 = otherMaximum - otherTime;
+ long epsilon = 800;
+ if(otherElapsed2 < epsilon) {
+ // This value can end up being not quite zero or even a bit negative, which is undesirable
+ // because then it'll be ignored when we try to set the clock value, so round to 0 when approximately 0.
+ // But, use a small number of milliseconds here to represent zero, because
+ // that will trigger the UI to notice that the clock has changed; it'll still show as 0 seconds.
+ //otherElapsed2 = 1;
+ }
+ long otherNewTime = otherTime;
+ long thisNewTime = thisMaximum;
+
+ if(otherNewTime >= penaltyDuration || thisNewTime >= penaltyDuration) {
+ // A jammer has multiple penalties.
+ // Cancel extra penalties as far as we can, taking into account how much the other jammer has already served.
+ // Since these times aren't exact, check within some small amount of penalty length.
+ while(otherNewTime >= (penaltyDuration - epsilon) && thisNewTime >= (penaltyDuration - epsilon)) {
+ otherNewTime -= penaltyDuration;
+ thisNewTime -= penaltyDuration;
+ otherMaximum -= penaltyDuration;
+ thisMaximum -= penaltyDuration;
+ }
+ }
+
+ if(otherMaximum == 0 || thisMaximum == 0) {
+ // If a max reduced to zero above, the values for thisNewTime and otherNewTime are already good
+ } else if(otherMaximum == thisMaximum) {
+ // Simple, common case.
+ // Sitting jammer is released,
+ // and arriving jammer serves as much as sitting jammer did.
+ thisNewTime = otherElapsed2;
+ otherNewTime = 1;
+ } else if(otherMaximum > penaltyDuration && thisMaximum == penaltyDuration) {
+ // This and next case are similar to the simple one above except the max times differ,
+ // so need to account for that in the elapsed time.
+ thisNewTime = otherElapsed2 - (otherMaximum - thisMaximum);
+ otherNewTime = 1;
+ } else if(otherMaximum == penaltyDuration && thisMaximum > penaltyDuration) {
+ thisNewTime = otherElapsed2 + (thisMaximum - otherMaximum);
+ otherNewTime = 1;
+ } else {
+ // The nightmare scenario!
+ // A jammer has returned to the box while
+ // sitting jammer is already serving a reduced single penalty,
+ // so can't reduce that any more.
+ // Sitting jammer must finish their penalty,
+ // and arriving jammer gets regular penalty.
+ otherNewTime = otherTime;
+ thisNewTime = penaltyDuration;
+ }
+
+ // jigger refresh of clock
+ if(thisNewTime == 0) {
+ thisNewTime = epsilon;
+ }
+ if(thisNewTime == thisTime) {
+ thisNewTime += 1;
+ }
+ pc.setMaximumTime(thisNewTime);
+ pc.setTime(thisNewTime);
+ otherClock.setTime(otherNewTime);
+ pc.restart();
+ }
+ break;
+ }
}
}
@@ -119,68 +209,11 @@ private void restartBox() {
if(pc == null) {
return;
}
- pc.setMaximumTime(team.getGame().getLong(Rule.PENALTIES_DURATION));
pc.setCountDirectionDown(true);
pc.restart();
setWallStartTime(ScoreBoardClock.getInstance().getCurrentWalltime());
if(hasFloorPosition()) {
- if(getFloorPosition() == FloorPosition.JAMMER) {
- // Jammer logic
- Team otherTeam = (team.getProviderId() == Team.ID_1) ? team.getGame().getTeam(Team.ID_2) : team.getGame().getTeam(Team.ID_1);
- for(BoxSeat bs : otherTeam.getAll(Team.BOX_SEAT)) {
- if(bs.getFloorPosition() == FloorPosition.JAMMER) {
- Clock otherClock = bs.getBoxClock();
- if(otherClock != null && otherClock.isRunning()) {
- long penaltyDuration = team.getGame().getLong(Rule.PENALTIES_DURATION);
- long otherMaximum = otherClock.getMaximumTime();
- long otherTime = otherClock.getTime();
- long otherElapsed2 = otherMaximum - otherTime;
- long otherNewTime = 0;
- long thisNewTime = 0;
- if(otherMaximum == penaltyDuration) {
- // Simple, common case.
- // Sitting jammer is released,
- // and arriving jammer serves as much as sitting jammer did.
- thisNewTime = otherElapsed2;
- otherNewTime = 0;
- } else if(otherMaximum > penaltyDuration) {
- if(otherTime > penaltyDuration) {
- // In this case sitting jammer has multiple penalties.
- // Turn new jammer away,
- // and reduce sitting jammer by one penalty.
- // If sitting jammer happens to have even more
- // than two penalties, other jammer can come and go
- // reducing sitting jammer by one penalty each time
- // until we arrive at the simple case.
- thisNewTime = 1000; // to cause zero to appear
- otherNewTime = otherTime - penaltyDuration;
- } else {
- // Reverts to simple case
- // Sitting jammer leaves,
- // New jammer sits for the amount that
- // other jammer served of their second penalty.
- thisNewTime = otherElapsed2 - penaltyDuration;
- otherNewTime = 0;
- }
- } else {
- // The nightmare scenario!
- // A jammer has returned to the box while
- // sitting jammer is already serving a reduced single penalty,
- // so can't reduce that any more.
- // Sitting jammer must finish their penalty,
- // and arriving jammer gets regular penalty.
- otherNewTime = otherTime;
- thisNewTime = penaltyDuration;
- }
- pc.setMaximumTime(thisNewTime);
- pc.setTime(thisNewTime);
- pc.restart();
- otherClock.setTime(otherNewTime);
- }
- break;
- }
- }
- }
+ doJammerLogic(pc);
Fielding f = team.getPosition(getFloorPosition()).getCurrentFielding();
if( f != null ) {
BoxTrip bt = f.getCurrentBoxTrip();
@@ -201,7 +234,7 @@ public void resetBox() {
long penaltyDuration = team.getGame().getLong(Rule.PENALTIES_DURATION);
pc.setMaximumTime(penaltyDuration);
}
- wallStartTime = 0;
+ setWallStartTime(0);
resetSkater();
}
@@ -256,10 +289,11 @@ public void endBox() {
s.setPenaltyCount(2, penaltyCountP2 + getCurNumPenalties());
}
}
- //curTrip.end();
fpValid = false;
numPenalties = 1;
- f.execute(Fielding.END_BOX_TRIP, Source.OTHER);
+ if(f.getCurrentBoxTrip() != null) {
+ f.getCurrentBoxTrip().end();
+ }
}
}
}
@@ -291,7 +325,9 @@ public void setBoxSkater(String number) {
if(cur_s != null) {
Fielding cur_f = cur_s.getCurrentFielding();
if(cur_f != null) {
- cur_f.getCurrentBoxTrip().delete();
+ if(cur_f.getCurrentBoxTrip() != null) {
+ cur_f.getCurrentBoxTrip().delete();
+ }
}
}
}
@@ -368,6 +404,7 @@ public FloorPosition getFloorPosition() {
private void setWallStartTime(long w) {
wallStartTime = w;
+ set(STARTED, wallStartTime != 0);
}
private long getWallStartTime() {
diff --git a/src/com/carolinarollergirls/scoreboard/core/game/FieldingImpl.java b/src/com/carolinarollergirls/scoreboard/core/game/FieldingImpl.java
index 0b71870e..53aae756 100644
--- a/src/com/carolinarollergirls/scoreboard/core/game/FieldingImpl.java
+++ b/src/com/carolinarollergirls/scoreboard/core/game/FieldingImpl.java
@@ -124,10 +124,6 @@ public void execute(Command prop, Source source) {
if (prop == UNEND_BOX_TRIP && getCurrentBoxTrip() != null && !getCurrentBoxTrip().isCurrent()) {
getCurrentBoxTrip().unend();
}
- if (prop == END_BOX_TRIP && getCurrentBoxTrip() != null) {
- getCurrentBoxTrip().end();
- itemRemoved(BOX_TRIP, getCurrentBoxTrip(), source);
- }
}
@Override
diff --git a/src/com/carolinarollergirls/scoreboard/core/interfaces/BoxSeat.java b/src/com/carolinarollergirls/scoreboard/core/interfaces/BoxSeat.java
index d254c4f9..2ddb2644 100644
--- a/src/com/carolinarollergirls/scoreboard/core/interfaces/BoxSeat.java
+++ b/src/com/carolinarollergirls/scoreboard/core/interfaces/BoxSeat.java
@@ -27,6 +27,7 @@ public interface BoxSeat extends ScoreBoardEventProvider {
public static final Value BOX_SKATER = new Value<>(String.class, "BoxSkater", "", props);
public static final Value BOX_TIME_CHANGE = new Value<>(Integer.class, "BoxTimeChange", 0, props);
public static final Value BOX_SKATER_PENALTIES = new Value<>(Integer.class, "BoxSkaterPenalties", 0, props);
+ public static final Value STARTED = new Value<>(Boolean.class, "Started", false, props);
public static final Command START_BOX = new Command("StartBox", props);
public static final Command STOP_BOX = new Command("StopBox", props);
diff --git a/src/com/carolinarollergirls/scoreboard/core/interfaces/Fielding.java b/src/com/carolinarollergirls/scoreboard/core/interfaces/Fielding.java
index e86c9ec5..c47c8074 100644
--- a/src/com/carolinarollergirls/scoreboard/core/interfaces/Fielding.java
+++ b/src/com/carolinarollergirls/scoreboard/core/interfaces/Fielding.java
@@ -44,5 +44,4 @@ public interface Fielding extends ParentOrderedScoreBoardEventProvider
public static final Command ADD_BOX_TRIP = new Command("AddBoxTrip", props);
public static final Command UNEND_BOX_TRIP = new Command("UnendBoxTrip", props);
- public static final Command END_BOX_TRIP = new Command("EndBoxTrip", props);
}