bugfixes and improvements
* fixed inconsistent player order * minor visual adjustments * refactoring
This commit is contained in:
parent
851c3a3451
commit
2484dec68a
@ -4,26 +4,29 @@ import lombok.experimental.UtilityClass;
|
|||||||
|
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class AnimationTimings {
|
public class AnimationTimings {
|
||||||
public static final float JUGGLE = 0.25f;
|
private static final float FACTOR = 1;
|
||||||
|
|
||||||
public static final float STACK_EXPAND = 0.25f;
|
public static final float JUGGLE = 0.25f * FACTOR;
|
||||||
public static final float STACK_COLLAPSE = 0.25f;
|
|
||||||
|
|
||||||
public static final float STACK_FINISH_MOVE = 0.25f;
|
public static final float STACK_EXPAND = 0.25f * FACTOR;
|
||||||
public static final float STACK_FINISH_ROTATE = 0.1f;
|
public static final float STACK_COLLAPSE = 0.25f * FACTOR;
|
||||||
public static final float STACK_FINISH_FADE = 0.5f;
|
|
||||||
public static final float STACK_HOLD = 0.5f;
|
|
||||||
|
|
||||||
public static final float PAD_OF_TRUTH_EXPAND = 0.25f;
|
public static final float STACK_FINISH_MOVE = 0.25f * FACTOR;
|
||||||
public static final float PAD_OF_TRUTH_COLLAPSE = 0.25f;
|
public static final float STACK_FINISH_ROTATE = 0.1f * FACTOR;
|
||||||
|
public static final float STACK_FINISH_FADE = 0.5f * FACTOR;
|
||||||
|
public static final float STACK_HOLD = 0.5f * FACTOR;
|
||||||
|
|
||||||
public static final float HAND_LAYOUT = 0.15f;
|
public static final float WEREWOLF_SWAP = 0.25f * FACTOR;
|
||||||
|
|
||||||
public static final float OVERLAY_HOLD = 3f;
|
public static final float PAD_OF_TRUTH_EXPAND = 0.25f * FACTOR;
|
||||||
public static final float OVERLAY_FADE = 0.1f;
|
public static final float PAD_OF_TRUTH_COLLAPSE = 0.25f * FACTOR;
|
||||||
|
|
||||||
public static final float OVERLAY_TRUMP = .3f;
|
public static final float HAND_LAYOUT = 0.15f * FACTOR;
|
||||||
|
|
||||||
public static final float MESSAGE_HOLD = 1.5f;
|
public static final float OVERLAY_HOLD = 3f * FACTOR;
|
||||||
public static final float MESSAGE_FADE = 0.5f;
|
public static final float OVERLAY_FADE = 0.1f * FACTOR;
|
||||||
|
public static final float OVERLAY_SHARED_ELEMENT = .3f * FACTOR;
|
||||||
|
|
||||||
|
public static final float MESSAGE_HOLD = 1.5f * FACTOR;
|
||||||
|
public static final float MESSAGE_FADE = 0.5f * FACTOR;
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,7 @@ public class CardActor extends Actor {
|
|||||||
setWidth(PREF_WIDTH);
|
setWidth(PREF_WIDTH);
|
||||||
setHeight(PREF_HEIGHT);
|
setHeight(PREF_HEIGHT);
|
||||||
setColor(0.8f, 0.8f, 0.8f, 1.0f);
|
setColor(0.8f, 0.8f, 0.8f, 1.0f);
|
||||||
|
setOrigin(PREF_WIDTH / 2, PREF_HEIGHT / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardActor(Card card, TextureAtlas atlas) {
|
public CardActor(Card card, TextureAtlas atlas) {
|
||||||
|
@ -41,8 +41,6 @@ public class CardsGroup extends WidgetGroup {
|
|||||||
private CardActor dragTarget;
|
private CardActor dragTarget;
|
||||||
private float dragStartX;
|
private float dragStartX;
|
||||||
|
|
||||||
@Getter
|
|
||||||
private SelectMode selectMode = SelectMode.NONE;
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
private Card selected;
|
private Card selected;
|
||||||
@ -130,8 +128,7 @@ public class CardsGroup extends WidgetGroup {
|
|||||||
for (var child : getChildren()) {
|
for (var child : getChildren()) {
|
||||||
if (child instanceof CardActor card) {
|
if (child instanceof CardActor card) {
|
||||||
float height = card.getHeight();
|
float height = card.getHeight();
|
||||||
if (selectMode == SelectMode.NONE && (child == dragTarget || dragTarget == null && child == target)
|
if ((child == dragTarget || dragTarget == null && child == target) || card.getCard() == selected) {
|
||||||
|| selectMode == SelectMode.SINGLE && card.getCard() == selected) {
|
|
||||||
height += speed * delta;
|
height += speed * delta;
|
||||||
} else {
|
} else {
|
||||||
height -= speed * delta;
|
height -= speed * delta;
|
||||||
@ -207,19 +204,8 @@ public class CardsGroup extends WidgetGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CardActor remove(Card card) {
|
public CardActor remove(Card card) {
|
||||||
// find actor
|
var actor = find(card);
|
||||||
CardActor actor = null;
|
if (actor == null) return null;
|
||||||
var children = getChildren();
|
|
||||||
for (int i = 0; i < children.size; i++) {
|
|
||||||
if (children.get(i) instanceof CardActor cardActor && cardActor.getCard() == card) {
|
|
||||||
actor = cardActor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actor == null) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjust actor
|
// adjust actor
|
||||||
actor.setY(getY() + actor.getHeight() - getHeight());
|
actor.setY(getY() + actor.getHeight() - getHeight());
|
||||||
@ -237,20 +223,22 @@ public class CardsGroup extends WidgetGroup {
|
|||||||
return actor;
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CardActor find(Card card) {
|
||||||
|
var children = getChildren();
|
||||||
|
for (int i = 0; i < children.size; i++) {
|
||||||
|
if (children.get(i) instanceof CardActor cardActor && cardActor.getCard() == card) {
|
||||||
|
return cardActor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getMinHeight() {
|
public float getMinHeight() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectMode(SelectMode selectMode) {
|
|
||||||
this.selectMode = Objects.requireNonNull(selectMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnClickListener(Consumer<CardActor> onClickListener) {
|
public void setOnClickListener(Consumer<CardActor> onClickListener) {
|
||||||
this.onClickListener = onClickListener;
|
this.onClickListener = onClickListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SelectMode {
|
|
||||||
SINGLE, NONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,20 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table;
|
|||||||
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
|
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
|
||||||
import com.badlogic.gdx.utils.Align;
|
import com.badlogic.gdx.utils.Align;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.AnimationTimings;
|
import eu.jonahbauer.wizard.client.libgdx.AnimationTimings;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
public class PadOfTruth extends Table {
|
public class PadOfTruth extends Table {
|
||||||
private static final float EXTENDED_WIDTH = 636;
|
public static final float EXTENDED_WIDTH = 636;
|
||||||
private static final float EXTENDED_HEIGHT = 824;
|
public static final float EXTENDED_HEIGHT = 824;
|
||||||
private static final float COLLAPSED_SCALE = CardActor.PREF_HEIGHT / EXTENDED_HEIGHT;
|
public static final float COLLAPSED_SCALE = CardActor.PREF_HEIGHT / EXTENDED_HEIGHT;
|
||||||
|
|
||||||
private final Label[] names = new Label[6];
|
private final Label[] names = new Label[6];
|
||||||
private final Label[][] predictions = new Label[20][];
|
private final Label[][] predictions = new Label[20][];
|
||||||
private final Label[][] scores = new Label[20][];
|
private final Label[][] scores = new Label[20][];
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
public PadOfTruth(Skin skin, Drawable background) {
|
public PadOfTruth(Skin skin, Drawable background) {
|
||||||
super(skin);
|
super(skin);
|
||||||
setTouchable(Touchable.enabled);
|
setTouchable(Touchable.enabled);
|
||||||
@ -32,7 +36,6 @@ public class PadOfTruth extends Table {
|
|||||||
setHeight(EXTENDED_HEIGHT);
|
setHeight(EXTENDED_HEIGHT);
|
||||||
|
|
||||||
setTransform(true);
|
setTransform(true);
|
||||||
setOrigin(0, 0);
|
|
||||||
setScale(COLLAPSED_SCALE);
|
setScale(COLLAPSED_SCALE);
|
||||||
|
|
||||||
addListener(new InputListener() {
|
addListener(new InputListener() {
|
||||||
@ -40,6 +43,7 @@ public class PadOfTruth extends Table {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
|
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
|
||||||
|
if (!enabled) return;
|
||||||
if (fromActor != null && isAscendantOf(fromActor)) return;
|
if (fromActor != null && isAscendantOf(fromActor)) return;
|
||||||
if (action != null) removeAction(action);
|
if (action != null) removeAction(action);
|
||||||
action = Actions.scaleTo(1, 1, AnimationTimings.PAD_OF_TRUTH_EXPAND);
|
action = Actions.scaleTo(1, 1, AnimationTimings.PAD_OF_TRUTH_EXPAND);
|
||||||
@ -48,6 +52,7 @@ public class PadOfTruth extends Table {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
|
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
|
||||||
|
if (!enabled) return;
|
||||||
if (toActor != null && isAscendantOf(toActor)) return;
|
if (toActor != null && isAscendantOf(toActor)) return;
|
||||||
if (action != null) removeAction(action);
|
if (action != null) removeAction(action);
|
||||||
action = Actions.scaleTo(COLLAPSED_SCALE, COLLAPSED_SCALE, AnimationTimings.PAD_OF_TRUTH_COLLAPSE);
|
action = Actions.scaleTo(COLLAPSED_SCALE, COLLAPSED_SCALE, AnimationTimings.PAD_OF_TRUTH_COLLAPSE);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
|
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
|
||||||
|
|
||||||
public interface InteractionOverlay {
|
public interface InteractionOverlay {
|
||||||
|
void close();
|
||||||
}
|
}
|
||||||
|
@ -36,9 +36,10 @@ public class MakePredictionOverlay extends Overlay implements InteractionOverlay
|
|||||||
var listener = new ChangeListener() {
|
var listener = new ChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void changed(ChangeEvent event, Actor actor) {
|
public void changed(ChangeEvent event, Actor actor) {
|
||||||
|
if (isClosing()) return;
|
||||||
for (int i = 0; i < values.length; i++) {
|
for (int i = 0; i < values.length; i++) {
|
||||||
if (actor == buttons[i]) {
|
if (actor == buttons[i]) {
|
||||||
screen.send(new PredictMessage(values[i]));
|
screen.onPredictionMade(values[i]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ import eu.jonahbauer.wizard.client.libgdx.AnimationTimings;
|
|||||||
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
|
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
||||||
|
|
||||||
@ -23,7 +25,9 @@ public abstract class Overlay extends Action {
|
|||||||
|
|
||||||
private Container<?> root;
|
private Container<?> root;
|
||||||
private boolean started;
|
private boolean started;
|
||||||
private boolean finished;
|
@Getter(AccessLevel.PROTECTED)
|
||||||
|
private boolean closing;
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
public Overlay(GameScreen gameScreen, long timeout) {
|
public Overlay(GameScreen gameScreen, long timeout) {
|
||||||
this.screen = gameScreen;
|
this.screen = gameScreen;
|
||||||
@ -44,11 +48,12 @@ public abstract class Overlay extends Action {
|
|||||||
show((Group) getActor());
|
show((Group) getActor());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.currentTimeMillis() > timeout) {
|
if (System.currentTimeMillis() > timeout && !closing) {
|
||||||
finishInternal();
|
closing = true;
|
||||||
|
onClosing();
|
||||||
}
|
}
|
||||||
|
|
||||||
return finished;
|
return closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Actor createContent();
|
protected abstract Actor createContent();
|
||||||
@ -68,21 +73,20 @@ public abstract class Overlay extends Action {
|
|||||||
parent.addActor(getRoot());
|
parent.addActor(getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void finishInternal() {
|
protected final void onClosed() {
|
||||||
if (!finished) {
|
if (closed) return;
|
||||||
finished = true;
|
closed = true;
|
||||||
getRoot().remove();
|
getRoot().remove();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void finish() {
|
protected void onClosing() {
|
||||||
getRoot().addAction(sequence(
|
getRoot().addAction(sequence(
|
||||||
targeting(root, alpha(0.0f, AnimationTimings.OVERLAY_FADE, Interpolation.pow2Out)),
|
targeting(root, alpha(0.0f, AnimationTimings.OVERLAY_FADE, Interpolation.pow2Out)),
|
||||||
run(this::finishInternal)
|
run(this::onClosed)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void timeout() {
|
public void close() {
|
||||||
timeout = 0;
|
timeout = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
|||||||
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
|
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||||
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
|
|
||||||
import eu.jonahbauer.wizard.common.model.Card;
|
import eu.jonahbauer.wizard.common.model.Card;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
@ -44,10 +43,11 @@ public class PickTrumpOverlay extends Overlay implements InteractionOverlay {
|
|||||||
cardGroup.addListener(new ClickListener() {
|
cardGroup.addListener(new ClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void clicked(InputEvent event, float x, float y) {
|
public void clicked(InputEvent event, float x, float y) {
|
||||||
|
if (isClosing()) return;
|
||||||
var target = event.getTarget();
|
var target = event.getTarget();
|
||||||
for (Card.Suit suit : Card.Suit.values()) {
|
for (Card.Suit suit : Card.Suit.values()) {
|
||||||
if (cards.get(suit) == target) {
|
if (cards.get(suit) == target) {
|
||||||
screen.send(new PickTrumpMessage(suit));
|
screen.onSuitClicked(suit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
|
|||||||
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
|
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||||
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
|
|
||||||
import eu.jonahbauer.wizard.common.model.Card;
|
import eu.jonahbauer.wizard.common.model.Card;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
@ -51,10 +50,11 @@ public class PlayColoredCardOverlay extends Overlay implements InteractionOverla
|
|||||||
cardGroup.addListener(new ClickListener() {
|
cardGroup.addListener(new ClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void clicked(InputEvent event, float x, float y) {
|
public void clicked(InputEvent event, float x, float y) {
|
||||||
|
if (isClosing()) return;
|
||||||
var target = event.getTarget();
|
var target = event.getTarget();
|
||||||
for (Card.Suit suit : Card.Suit.values()) {
|
for (Card.Suit suit : Card.Suit.values()) {
|
||||||
if (actors.get(suit) == target) {
|
if (actors.get(suit) == target) {
|
||||||
screen.send(new PlayCardMessage(cards.get(suit)));
|
screen.onCardClicked(cards.get(suit));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ public class PlayColoredCardOverlay extends Overlay implements InteractionOverla
|
|||||||
cancel.addListener(new ChangeListener() {
|
cancel.addListener(new ChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void changed(ChangeEvent event, Actor actor) {
|
public void changed(ChangeEvent event, Actor actor) {
|
||||||
finishInternal();
|
close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
root.addActor(cancel);
|
root.addActor(cancel);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
|
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Interpolation;
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.AnimationTimings;
|
import eu.jonahbauer.wizard.client.libgdx.AnimationTimings;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.actions.MyActions;
|
|
||||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||||
|
|
||||||
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
|
||||||
@ -38,13 +36,7 @@ public class StartRoundOverlay extends Overlay {
|
|||||||
var root = getRoot();
|
var root = getRoot();
|
||||||
root.addAction(sequence(
|
root.addAction(sequence(
|
||||||
delay(AnimationTimings.OVERLAY_HOLD),
|
delay(AnimationTimings.OVERLAY_HOLD),
|
||||||
targeting(root, alpha(0.0f, AnimationTimings.OVERLAY_FADE, Interpolation.pow2Out)),
|
run(this::close)
|
||||||
run(this::finishInternal)
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
MyActions.finish(getRoot().getActions().get(0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
|
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Interpolation;
|
import com.badlogic.gdx.math.Interpolation;
|
||||||
import com.badlogic.gdx.scenes.scene2d.Action;
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||||
import com.badlogic.gdx.scenes.scene2d.Group;
|
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||||
import com.badlogic.gdx.scenes.scene2d.actions.ParallelAction;
|
import com.badlogic.gdx.scenes.scene2d.actions.ParallelAction;
|
||||||
@ -161,7 +160,7 @@ public class TrumpOverlay extends Overlay {
|
|||||||
cardAnimation.addAction(sequence(
|
cardAnimation.addAction(sequence(
|
||||||
targeting(trumpCardActor, removeActorSilently()),
|
targeting(trumpCardActor, removeActorSilently()),
|
||||||
targeting(trumpCardActor, changeParent(parent)),
|
targeting(trumpCardActor, changeParent(parent)),
|
||||||
targeting(trumpCardActor, moveTo(10, 10, AnimationTimings.OVERLAY_TRUMP))
|
targeting(trumpCardActor, moveTo(10, 10, AnimationTimings.OVERLAY_SHARED_ELEMENT))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,9 +170,9 @@ public class TrumpOverlay extends Overlay {
|
|||||||
targeting(trumpSuitActor, changeParent(parent)),
|
targeting(trumpSuitActor, changeParent(parent)),
|
||||||
run(trumpCardActor::toFront),
|
run(trumpCardActor::toFront),
|
||||||
parallel(
|
parallel(
|
||||||
targeting(trumpSuitActor, rotateTo(-90, AnimationTimings.OVERLAY_TRUMP)),
|
targeting(trumpSuitActor, rotateTo(-90, AnimationTimings.OVERLAY_SHARED_ELEMENT)),
|
||||||
targeting(trumpSuitActor,
|
targeting(trumpSuitActor,
|
||||||
moveTo(10, 10 + (trumpSuitActor.getHeight() + trumpSuitActor.getWidth()) / 2, AnimationTimings.OVERLAY_TRUMP)
|
moveTo(10, 10 + (trumpSuitActor.getHeight() + trumpSuitActor.getWidth()) / 2, AnimationTimings.OVERLAY_SHARED_ELEMENT)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
@ -186,12 +185,14 @@ public class TrumpOverlay extends Overlay {
|
|||||||
targeting(root, alpha(0.0f, AnimationTimings.OVERLAY_FADE, Interpolation.pow2Out)),
|
targeting(root, alpha(0.0f, AnimationTimings.OVERLAY_FADE, Interpolation.pow2Out)),
|
||||||
cardAnimation
|
cardAnimation
|
||||||
),
|
),
|
||||||
run(this::finishInternal)
|
run(this::close)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finish() {
|
protected void onClosing() {
|
||||||
MyActions.finish(getRoot().getActions().get(0));
|
var actions = getRoot().getActions();
|
||||||
|
if (actions.size > 0) MyActions.finish(actions.get(0));
|
||||||
|
onClosed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,22 +22,17 @@ import eu.jonahbauer.wizard.client.libgdx.actors.game.CardsGroup;
|
|||||||
import eu.jonahbauer.wizard.client.libgdx.actors.game.PadOfTruth;
|
import eu.jonahbauer.wizard.client.libgdx.actors.game.PadOfTruth;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.actors.game.overlay.*;
|
import eu.jonahbauer.wizard.client.libgdx.actors.game.overlay.*;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.state.Game;
|
import eu.jonahbauer.wizard.client.libgdx.state.Game;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.util.Triple;
|
|
||||||
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
|
|
||||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||||
import eu.jonahbauer.wizard.common.messages.player.ContinueMessage;
|
|
||||||
import eu.jonahbauer.wizard.common.messages.player.JuggleMessage;
|
|
||||||
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
|
|
||||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
|
||||||
import eu.jonahbauer.wizard.common.model.Card;
|
import eu.jonahbauer.wizard.common.model.Card;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.jetbrains.annotations.Nls;
|
import org.jetbrains.annotations.Nls;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*;
|
import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*;
|
||||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.*;
|
|
||||||
|
|
||||||
public class GameScreen extends MenuScreen {
|
public class GameScreen extends MenuScreen {
|
||||||
@Getter
|
@Getter
|
||||||
@ -46,11 +41,9 @@ public class GameScreen extends MenuScreen {
|
|||||||
private Label.LabelStyle labelStyleDefault;
|
private Label.LabelStyle labelStyleDefault;
|
||||||
private Label.LabelStyle labelStyleActive;
|
private Label.LabelStyle labelStyleActive;
|
||||||
|
|
||||||
private final Game state;
|
private final UUID self;
|
||||||
|
private final LinkedHashMap<UUID, String> players;
|
||||||
private final List<UUID> players;
|
private final List<UUID> orderedPlayers;
|
||||||
|
|
||||||
private Triple<UUID, UserInputMessage.Action, Long> activePlayer;
|
|
||||||
|
|
||||||
private CardsGroup handCards;
|
private CardsGroup handCards;
|
||||||
private CardStack cardStack;
|
private CardStack cardStack;
|
||||||
@ -70,18 +63,16 @@ public class GameScreen extends MenuScreen {
|
|||||||
private final Map<UUID, Seat> seats = new HashMap<>();
|
private final Map<UUID, Seat> seats = new HashMap<>();
|
||||||
private final Map<UUID, Label> nameLabels = new HashMap<>();
|
private final Map<UUID, Label> nameLabels = new HashMap<>();
|
||||||
|
|
||||||
private final AtomicBoolean sending = new AtomicBoolean();
|
|
||||||
private final AtomicBoolean pendingSync = new AtomicBoolean();
|
private final AtomicBoolean pendingSync = new AtomicBoolean();
|
||||||
|
|
||||||
private boolean juggling;
|
public GameScreen(WizardGame game, @NotNull UUID self, @NotNull LinkedHashMap<UUID, String> players) {
|
||||||
private Card juggledCard;
|
|
||||||
|
|
||||||
public GameScreen(WizardGame game) {
|
|
||||||
super(game);
|
super(game);
|
||||||
this.state = (Game) game.getClient().getState();
|
this.self = self;
|
||||||
this.players = new ArrayList<>(state.getPlayers().keySet());
|
this.players = players;
|
||||||
|
this.orderedPlayers = players.keySet().stream().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="Layout">
|
||||||
@Override
|
@Override
|
||||||
public void show() {
|
public void show() {
|
||||||
super.show();
|
super.show();
|
||||||
@ -92,10 +83,9 @@ public class GameScreen extends MenuScreen {
|
|||||||
labelStyleActive.fontColor = Color.RED;
|
labelStyleActive.fontColor = Color.RED;
|
||||||
|
|
||||||
seat();
|
seat();
|
||||||
prepareLabels();
|
|
||||||
|
|
||||||
handCards = new CardsGroup(Collections.emptyList(), atlas);
|
handCards = new CardsGroup(Collections.emptyList(), atlas);
|
||||||
handCards.setOnClickListener(this::onCardClicked);
|
handCards.setOnClickListener(actor -> onCardClicked(actor.getCard()));
|
||||||
var container = new Container<>(handCards);
|
var container = new Container<>(handCards);
|
||||||
container.setPosition(360, 75);
|
container.setPosition(360, 75);
|
||||||
container.setSize(1200, CardActor.PREF_HEIGHT);
|
container.setSize(1200, CardActor.PREF_HEIGHT);
|
||||||
@ -107,13 +97,13 @@ public class GameScreen extends MenuScreen {
|
|||||||
messages.setTouchable(Touchable.disabled);
|
messages.setTouchable(Touchable.disabled);
|
||||||
|
|
||||||
padOfTruth = new PadOfTruth(game.data.skin, new TextureRegionDrawable(atlas.findRegion(GameAtlas.PAD_OF_TRUTH)));
|
padOfTruth = new PadOfTruth(game.data.skin, new TextureRegionDrawable(atlas.findRegion(GameAtlas.PAD_OF_TRUTH)));
|
||||||
padOfTruth.setPosition(1910 - 636f, 10);
|
padOfTruth.setPosition(WizardGame.WIDTH - 10 - PadOfTruth.EXTENDED_WIDTH, 10);
|
||||||
padOfTruth.setOrigin(636f, 0);
|
padOfTruth.setOrigin(PadOfTruth.EXTENDED_WIDTH, 0);
|
||||||
|
|
||||||
cardStack = new CardStack();
|
cardStack = new CardStack();
|
||||||
cardStack.setHoverBounds(0.5f * WizardGame.WIDTH - 50, 0.5f * WizardGame.HEIGHT - 50, 100, 100);
|
cardStack.setHoverBounds(0.5f * WizardGame.WIDTH - 50, 0.5f * WizardGame.HEIGHT - 50, 100, 100);
|
||||||
|
|
||||||
setNames();
|
addLabels();
|
||||||
|
|
||||||
Gdx.input.setInputProcessor(game.data.stage);
|
Gdx.input.setInputProcessor(game.data.stage);
|
||||||
game.data.stage.addActor(container);
|
game.data.stage.addActor(container);
|
||||||
@ -122,14 +112,53 @@ public class GameScreen extends MenuScreen {
|
|||||||
game.data.stage.addActor(messages);
|
game.data.stage.addActor(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void seat() {
|
||||||
|
var count = players.size();
|
||||||
|
Seat[] seats = switch (count) {
|
||||||
|
case 3 -> new Seat[] {Seat.BOTTOM, Seat.TOP_LEFT, Seat.TOP_RIGHT};
|
||||||
|
case 4 -> new Seat[] {Seat.BOTTOM, Seat.LEFT, Seat.TOP, Seat.RIGHT};
|
||||||
|
case 5 -> new Seat[] {Seat.BOTTOM, Seat.LEFT, Seat.TOP_LEFT, Seat.TOP_RIGHT, Seat.RIGHT};
|
||||||
|
case 6 -> new Seat[] {Seat.BOTTOM, Seat.LEFT, Seat.TOP_LEFT, Seat.TOP, Seat.TOP_RIGHT, Seat.RIGHT};
|
||||||
|
default -> throw new AssertionError();
|
||||||
|
};
|
||||||
|
|
||||||
|
int index = orderedPlayers.indexOf(self);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
var player = orderedPlayers.get((index + i) % count);
|
||||||
|
var seat = seats[i];
|
||||||
|
this.seats.put(player, seat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLabels() {
|
||||||
|
int i = 0;
|
||||||
|
for (var entry : players.entrySet()) {
|
||||||
|
UUID uuid = entry.getKey();
|
||||||
|
String name = entry.getValue();
|
||||||
|
|
||||||
|
padOfTruth.setName(i++, name);
|
||||||
|
|
||||||
|
if (isSelf(uuid)) continue;
|
||||||
|
var label = new Label("", game.data.skin);
|
||||||
|
var seat = seats.get(uuid);
|
||||||
|
label.setX(seat.getLabelX());
|
||||||
|
label.setY(seat.getLabelY());
|
||||||
|
label.setAlignment(seat.getLabelAlign());
|
||||||
|
label.setText(name);
|
||||||
|
nameLabels.put(uuid, label);
|
||||||
|
game.data.stage.addActor(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderBackground(float delta) {
|
protected void renderBackground(float delta) {
|
||||||
float scale = Math.max(
|
float scale = Math.max(
|
||||||
game.data.extendViewport.getWorldWidth() / WizardGame.WIDTH,
|
game.data.extendViewport.getWorldWidth() / WizardGame.WIDTH,
|
||||||
game.data.extendViewport.getWorldHeight() / WizardGame.HEIGHT
|
game.data.extendViewport.getWorldHeight() / WizardGame.HEIGHT
|
||||||
);
|
);
|
||||||
game.batch.setColor(1, 1, 1, 0.25f);
|
|
||||||
game.batch.draw(game.data.background, 0,0, scale * WizardGame.WIDTH, scale * WizardGame.HEIGHT);
|
game.batch.draw(game.data.background, 0,0, scale * WizardGame.WIDTH, scale * WizardGame.HEIGHT);
|
||||||
|
game.batch.setColor(1, 1, 1, 0.25f);
|
||||||
game.batch.draw(
|
game.batch.draw(
|
||||||
game.data.title,
|
game.data.title,
|
||||||
(game.data.extendViewport.getWorldWidth() - game.data.title.getRegionWidth() * 0.75f) / 2f,
|
(game.data.extendViewport.getWorldWidth() - game.data.title.getRegionWidth() * 0.75f) / 2f,
|
||||||
@ -158,7 +187,7 @@ public class GameScreen extends MenuScreen {
|
|||||||
game.data.stage.addAction(currentAction);
|
game.data.stage.addAction(currentAction);
|
||||||
}
|
}
|
||||||
} else if (pendingSync.getAndSet(false)) {
|
} else if (pendingSync.getAndSet(false)) {
|
||||||
send(new ContinueMessage());
|
game.getClient().execute(Game.class, Game::sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,90 +199,25 @@ public class GameScreen extends MenuScreen {
|
|||||||
pendingActions.add(run(runnable));
|
pendingActions.add(run(runnable));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seat() {
|
|
||||||
var count = players.size();
|
|
||||||
Seat[] seats = switch (count) {
|
|
||||||
case 3 -> new Seat[] {Seat.TOP_LEFT, Seat.TOP_RIGHT};
|
|
||||||
case 4 -> new Seat[] {Seat.LEFT, Seat.TOP, Seat.RIGHT};
|
|
||||||
case 5 -> new Seat[] {Seat.LEFT, Seat.TOP_LEFT, Seat.TOP_RIGHT, Seat.RIGHT};
|
|
||||||
case 6 -> new Seat[] {Seat.LEFT, Seat.TOP_LEFT, Seat.TOP, Seat.TOP_RIGHT, Seat.RIGHT};
|
|
||||||
default -> throw new AssertionError();
|
|
||||||
};
|
|
||||||
int index = players.indexOf(state.getSelf());
|
|
||||||
for (int i = 1; i < count; i++) {
|
|
||||||
var player = players.get((index + i) % count);
|
|
||||||
var seat = seats[i - 1];
|
|
||||||
this.seats.put(player, seat);
|
|
||||||
}
|
|
||||||
this.seats.put(state.getSelf(), Seat.BOTTOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareLabels() {
|
|
||||||
for (UUID player : players) {
|
|
||||||
if (isSelf(player)) continue;
|
|
||||||
var label = new Label("", game.data.skin);
|
|
||||||
var seat = seats.get(player);
|
|
||||||
label.setX(seat.getLabelX());
|
|
||||||
label.setY(seat.getLabelY());
|
|
||||||
label.setAlignment(seat.getLabelAlign());
|
|
||||||
this.nameLabels.put(player, label);
|
|
||||||
game.data.stage.addActor(label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNames() {
|
|
||||||
for (int i = 0; i < players.size(); i++) {
|
|
||||||
var player = players.get(i);
|
|
||||||
var name = state.getPlayers().get(player);
|
|
||||||
padOfTruth.setName(i, name);
|
|
||||||
if (!isSelf(player)) {
|
|
||||||
nameLabels.get(player).setText(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCardClicked(CardActor actor) {
|
|
||||||
var card = actor.getCard();
|
|
||||||
|
|
||||||
if (checkAction(PLAY_CARD)) {
|
|
||||||
var timeout = activePlayer.third();
|
|
||||||
if (card == Card.CLOUD) {
|
|
||||||
execute(new PlayColoredCardOverlay(this,
|
|
||||||
timeout,
|
|
||||||
Card.CLOUD,
|
|
||||||
Card.CLOUD_RED,
|
|
||||||
Card.CLOUD_GREEN,
|
|
||||||
Card.CLOUD_BLUE,
|
|
||||||
Card.CLOUD_YELLOW
|
|
||||||
));
|
|
||||||
} else if (card == Card.JUGGLER) {
|
|
||||||
execute(new PlayColoredCardOverlay(this,
|
|
||||||
timeout,
|
|
||||||
Card.JUGGLER,
|
|
||||||
Card.JUGGLER_RED,
|
|
||||||
Card.JUGGLER_GREEN,
|
|
||||||
Card.JUGGLER_BLUE,
|
|
||||||
Card.JUGGLER_YELLOW
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
send(new PlayCardMessage(card));
|
|
||||||
}
|
|
||||||
} else if (checkAction(JUGGLE_CARD)) {
|
|
||||||
juggledCard = card;
|
|
||||||
send(new JuggleMessage(card));
|
|
||||||
} else {
|
|
||||||
addMessage("You cannot do that right now.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkAction(UserInputMessage.Action action) {
|
|
||||||
return activePlayer != null && (activePlayer.first() == null || isSelf(activePlayer.first())) && activePlayer.second() == action;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSelf(UUID uuid) {
|
private boolean isSelf(UUID uuid) {
|
||||||
return state.getSelf().equals(uuid);
|
return self.equals(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onCardClicked(Card card) {
|
||||||
|
game.getClient().execute(Game.class, (s, c) -> s.onCardClicked(c, card));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSuitClicked(Card.Suit suit) {
|
||||||
|
game.getClient().execute(Game.class, (s, c) -> s.onSuitClicked(c, suit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPredictionMade(int prediction) {
|
||||||
|
game.getClient().execute(Game.class, (s, c) -> s.onPredictionMade(c, prediction));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all round-scoped and shows an {@linkplain StartRoundOverlay overlay}.
|
||||||
|
*/
|
||||||
public void startRound(int round) {
|
public void startRound(int round) {
|
||||||
execute(parallel(
|
execute(parallel(
|
||||||
run(() -> {
|
run(() -> {
|
||||||
@ -277,24 +241,8 @@ public class GameScreen extends MenuScreen {
|
|||||||
execute(() -> cardStack.clearChildren());
|
execute(() -> cardStack.clearChildren());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void finishTrick(UUID player) {
|
||||||
* Indicates that the next call to {@link #setHand(UUID, List)} should be animated.
|
var seat = seats.getOrDefault(player, Seat.FALLBACK);
|
||||||
*/
|
|
||||||
public void setJuggling(boolean juggling) {
|
|
||||||
execute(() -> {
|
|
||||||
this.juggling = juggling;
|
|
||||||
if (juggling) {
|
|
||||||
handCards.setSelectMode(CardsGroup.SelectMode.SINGLE);
|
|
||||||
} else {
|
|
||||||
handCards.setSelectMode(CardsGroup.SelectMode.NONE);
|
|
||||||
juggledCard = null;
|
|
||||||
handCards.setSelected(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void finishTrick(UUID player, List<Card> cards) {
|
|
||||||
var seat = seats.get(player);
|
|
||||||
|
|
||||||
var action = parallel();
|
var action = parallel();
|
||||||
execute(sequence(
|
execute(sequence(
|
||||||
@ -321,15 +269,17 @@ public class GameScreen extends MenuScreen {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHand(UUID player, List<Card> cards) {
|
/**
|
||||||
|
* Updates the given players hand cards.
|
||||||
|
*/
|
||||||
|
public void setHand(UUID player, List<Card> cards, boolean juggle) {
|
||||||
if (isSelf(player)) {
|
if (isSelf(player)) {
|
||||||
var sequence = sequence();
|
var sequence = sequence();
|
||||||
sequence.addAction(run(() -> {
|
sequence.addAction(run(() -> {
|
||||||
var changes = handCards.update(cards);
|
var changes = handCards.update(cards);
|
||||||
|
|
||||||
// animate card changes
|
// animate card changes
|
||||||
if (juggling) {
|
if (juggle) {
|
||||||
setJuggling(false);
|
|
||||||
sequence.addAction(animateJuggle(changes.first(), changes.second()));
|
sequence.addAction(animateJuggle(changes.first(), changes.second()));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -337,62 +287,39 @@ public class GameScreen extends MenuScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTrump(Card trumpCard, Card.Suit trumpSuit) {
|
/**
|
||||||
if (trumpCardActor == null) {
|
* Adds the prediction for the given round and player to the {@linkplain #padOfTruth pad of truth} and
|
||||||
trumpCardActor = new CardActor(Card.HIDDEN, atlas);
|
* shows a corresponding message.
|
||||||
}
|
*/
|
||||||
|
public void addPrediction(int round, UUID player, int prediction, boolean changed) {
|
||||||
if (trumpSuitActor == null) {
|
|
||||||
trumpSuitActor = new CardActor(Card.HIDDEN, atlas);
|
|
||||||
}
|
|
||||||
|
|
||||||
String player = null;
|
|
||||||
if (activePlayer != null && activePlayer.second() == PICK_TRUMP) {
|
|
||||||
player = state.getPlayers().get(activePlayer.first());
|
|
||||||
clearActivePlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(new TrumpOverlay(this, player, trumpCard, trumpSuit));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPrediction(int round, UUID player, int prediction) {
|
|
||||||
boolean changed = false;
|
|
||||||
|
|
||||||
if (activePlayer != null && activePlayer.first().equals(player) && (activePlayer.second() == CHANGE_PREDICTION || activePlayer.second() == MAKE_PREDICTION)) {
|
|
||||||
changed = activePlayer.second() == CHANGE_PREDICTION;
|
|
||||||
clearActivePlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSelf(player)) {
|
if (isSelf(player)) {
|
||||||
addMessage(game.messages.format("game.action." + (changed ? "change" : "make") + "_prediction.self", prediction));
|
addMessage(game.messages.format("game.action." + (changed ? "change" : "make") + "_prediction.self", prediction));
|
||||||
} else {
|
} else {
|
||||||
var name = state.getPlayers().get(player);
|
var name = players.get(player);
|
||||||
addMessage(game.messages.format("game.action." + (changed ? "change" : "make") + "_prediction.other", name, prediction));
|
addMessage(game.messages.format("game.action." + (changed ? "change" : "make") + "_prediction.other", name, prediction));
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(() -> padOfTruth.setPrediction(players.indexOf(player), round, prediction));
|
execute(() -> padOfTruth.setPrediction(orderedPlayers.indexOf(player), round, prediction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the card from the players hand and puts it into the {@linkplain #cardStack stack}.
|
||||||
|
*/
|
||||||
public void playCard(UUID player, Card card) {
|
public void playCard(UUID player, Card card) {
|
||||||
if (activePlayer != null && activePlayer.first().equals(player) && activePlayer.second() == PLAY_CARD) {
|
|
||||||
clearActivePlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSelf(player)) {
|
if (isSelf(player)) {
|
||||||
addMessage(game.messages.get("game.action.play_card.self"));
|
addMessage(game.messages.get("game.action.play_card.self"));
|
||||||
} else {
|
} else {
|
||||||
var name = state.getPlayers().get(player);
|
var name = players.get(player);
|
||||||
addMessage(game.messages.format("game.action.play_card.other", name));
|
addMessage(game.messages.format("game.action.play_card.other", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
Seat seat = seats.get(player);
|
Seat seat = seats.getOrDefault(player, Seat.FALLBACK);
|
||||||
|
|
||||||
var sequence = sequence();
|
var sequence = sequence();
|
||||||
sequence.addAction(run(() -> {
|
sequence.addAction(run(() -> {
|
||||||
CardActor actor = null;
|
CardActor actor = null;
|
||||||
if (isSelf(player)) {
|
if (isSelf(player)) {
|
||||||
actor = handCards.remove(card);
|
actor = handCards.remove(card);
|
||||||
actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor == null) {
|
if (actor == null) {
|
||||||
@ -406,55 +333,35 @@ public class GameScreen extends MenuScreen {
|
|||||||
execute(sequence);
|
execute(sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the scores for a round to the corresponding row of the {@linkplain #padOfTruth pad of truth}.
|
||||||
|
*/
|
||||||
public void addScores(int round, Map<UUID, Integer> scores) {
|
public void addScores(int round, Map<UUID, Integer> scores) {
|
||||||
execute(() -> {
|
execute(() -> {
|
||||||
for (int i = 0; i < players.size(); i++) {
|
for (int i = 0; i < orderedPlayers.size(); i++) {
|
||||||
UUID player = players.get(i);
|
UUID player = orderedPlayers.get(i);
|
||||||
padOfTruth.setScore(i, round, scores.get(player));
|
padOfTruth.setScore(i, round, scores.get(player));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all visual changes done by {@link #setActivePlayer(UUID, UserInputMessage.Action, long)}.
|
||||||
|
*/
|
||||||
public void clearActivePlayer() {
|
public void clearActivePlayer() {
|
||||||
setActivePlayer(null, null, 0);
|
setActivePlayer(null, null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlights the given players label and sets the {@linkplain #setPersistentMessage(String) persistent message}
|
||||||
|
* accordingly.
|
||||||
|
*/
|
||||||
public void setActivePlayer(UUID player, UserInputMessage.Action action, long timeout) {
|
public void setActivePlayer(UUID player, UserInputMessage.Action action, long timeout) {
|
||||||
if (action == SYNC) throw new IllegalArgumentException();
|
execute(() -> nameLabels.forEach((p, l) -> l.setStyle(p.equals(player) ? labelStyleActive : labelStyleDefault)));
|
||||||
|
|
||||||
// reset label color
|
if (isSelf(player) || player == null && action == null) {
|
||||||
if (activePlayer != null && nameLabels.containsKey(activePlayer.first())) {
|
|
||||||
var label = nameLabels.get(activePlayer.first());
|
|
||||||
execute(() -> label.setStyle(labelStyleDefault));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player == null && action == null) {
|
|
||||||
activePlayer = null;
|
|
||||||
setPersistentMessage(null);
|
setPersistentMessage(null);
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
activePlayer = Triple.of(player, action, timeout);
|
|
||||||
// set label color
|
|
||||||
if (nameLabels.containsKey(player)) {
|
|
||||||
var label = nameLabels.get(player);
|
|
||||||
execute(() -> label.setStyle(labelStyleActive));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isSelf = state.getSelf().equals(player);
|
|
||||||
if (isSelf || player == null) {
|
|
||||||
// show interface
|
|
||||||
setPersistentMessage(null);
|
|
||||||
switch (action) {
|
|
||||||
case PICK_TRUMP -> execute(new PickTrumpOverlay(this, timeout, false));
|
|
||||||
case MAKE_PREDICTION -> execute(new MakePredictionOverlay(this, timeout, state.getRound()));
|
|
||||||
case CHANGE_PREDICTION -> execute(new ChangePredictionOverlay(this, timeout, state.getRound(), state.getPredictions().get(state.getSelf())));
|
|
||||||
case PLAY_CARD -> setPersistentMessage(game.messages.get("game.message.play_card.self"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isSelf) {
|
|
||||||
// show message
|
|
||||||
var key = switch (action) {
|
var key = switch (action) {
|
||||||
case CHANGE_PREDICTION -> "game.message.change_prediction.";
|
case CHANGE_PREDICTION -> "game.message.change_prediction.";
|
||||||
case JUGGLE_CARD -> "game.message.juggle_card.";
|
case JUGGLE_CARD -> "game.message.juggle_card.";
|
||||||
@ -464,15 +371,141 @@ public class GameScreen extends MenuScreen {
|
|||||||
default -> throw new AssertionError();
|
default -> throw new AssertionError();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (player == null) {
|
if (player != null) {
|
||||||
setPersistentMessage(game.messages.get(key + "all"));
|
setPersistentMessage(game.messages.format(key + "other", players.get(player)));
|
||||||
} else {
|
} else {
|
||||||
var name = state.getPlayers().get(player);
|
setPersistentMessage(game.messages.get(key + "all"));
|
||||||
setPersistentMessage(game.messages.format(key + "other", name));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visually highlights the given card on the players hand.
|
||||||
|
*/
|
||||||
|
public void setSelectedCard(@Nullable Card card) {
|
||||||
|
handCards.setSelected(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the current {@linkplain #trumpCardActor trump card} (if present) with a {@linkplain Card#WEREWOLF werewolf}
|
||||||
|
* card on the given players hand.
|
||||||
|
*/
|
||||||
|
public void swapTrumpCard(UUID player) {
|
||||||
|
var seat = seats.getOrDefault(player, Seat.FALLBACK);
|
||||||
|
|
||||||
|
var sequence = sequence();
|
||||||
|
sequence.addAction(run(() -> {
|
||||||
|
if (trumpCardActor == null || !trumpCardActor.hasParent()) return;
|
||||||
|
|
||||||
|
if (isSelf(player)) {
|
||||||
|
var handCard = handCards.find(Card.WEREWOLF);
|
||||||
|
if (handCard == null) return;
|
||||||
|
|
||||||
|
handCard.setCard(trumpCardActor.getCard());
|
||||||
|
trumpCardActor.setCard(Card.WEREWOLF);
|
||||||
|
|
||||||
|
float localHandX = handCard.getX(), localHandY = handCard.getY();
|
||||||
|
float stageTrumpX = 10, stageTrumpY = 10;
|
||||||
|
float localTrumpX, localTrumpY;
|
||||||
|
float stageHandX, stageHandY;
|
||||||
|
|
||||||
|
var pos = new Vector2(handCard.getX(), handCard.getY());
|
||||||
|
handCards.localToStageCoordinates(pos);
|
||||||
|
stageHandX = pos.x;
|
||||||
|
stageHandY = pos.y;
|
||||||
|
|
||||||
|
pos.set(stageTrumpX, stageTrumpY);
|
||||||
|
handCards.stageToLocalCoordinates(pos);
|
||||||
|
localTrumpX = pos.x;
|
||||||
|
localTrumpY = pos.y;
|
||||||
|
|
||||||
|
trumpCardActor.setPosition(stageHandX, stageHandY);
|
||||||
|
trumpCardActor.setRotation(0);
|
||||||
|
|
||||||
|
handCard.setPosition(localTrumpX, localTrumpY);
|
||||||
|
handCard.setRotation(0);
|
||||||
|
|
||||||
|
sequence.addAction(parallel(
|
||||||
|
targeting(trumpCardActor, moveTo(stageTrumpX, stageTrumpY, AnimationTimings.WEREWOLF_SWAP)),
|
||||||
|
targeting(handCard, moveTo(localHandX, localHandY, AnimationTimings.WEREWOLF_SWAP))
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
var handCard = new CardActor(trumpCardActor.getCard(), atlas);
|
||||||
|
handCard.setPosition(10, 10);
|
||||||
|
trumpCardActor.setCard(Card.WEREWOLF);
|
||||||
|
game.data.stage.addActor(handCard);
|
||||||
|
|
||||||
|
sequence.addAction(seat.moveToHand(trumpCardActor, 0));
|
||||||
|
sequence.addAction(parallel(
|
||||||
|
targeting(trumpCardActor, moveTo(10,10, AnimationTimings.WEREWOLF_SWAP)),
|
||||||
|
targeting(trumpCardActor, rotateTo(0, AnimationTimings.WEREWOLF_SWAP)),
|
||||||
|
seat.moveToHand(handCard, AnimationTimings.WEREWOLF_SWAP)
|
||||||
|
));
|
||||||
|
sequence.addAction(removeActor(handCard));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
execute(sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="Overlays" defaultstate="collapsed">
|
||||||
|
public void showTrumpOverlay(UUID player, Card trumpCard, Card.Suit trumpSuit) {
|
||||||
|
if (trumpCardActor == null) {
|
||||||
|
trumpCardActor = new CardActor(Card.HIDDEN, atlas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trumpSuitActor == null) {
|
||||||
|
trumpSuitActor = new CardActor(Card.HIDDEN, atlas);
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(new TrumpOverlay(this, players.get(player), trumpCard, trumpSuit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showColoredCardOverlay(Card card, long timeout) {
|
||||||
|
if (card == Card.JUGGLER) {
|
||||||
|
execute(new PlayColoredCardOverlay(this, timeout, card, Card.JUGGLER_RED, Card.JUGGLER_GREEN, Card.JUGGLER_BLUE, Card.JUGGLER_YELLOW));
|
||||||
|
} else if (card == Card.CLOUD) {
|
||||||
|
execute(new PlayColoredCardOverlay(this, timeout, card, Card.CLOUD_RED, Card.CLOUD_GREEN, Card.CLOUD_BLUE, Card.CLOUD_YELLOW));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InteractionOverlay showPickTrumpOverlay(long timeout, boolean allowNone) {
|
||||||
|
var overlay = new PickTrumpOverlay(this, timeout, allowNone);
|
||||||
|
execute(overlay);
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showScoreOverlay() {
|
||||||
|
execute(sequence(
|
||||||
|
run(() -> padOfTruth.setEnabled(false)),
|
||||||
|
parallel(
|
||||||
|
targeting(padOfTruth, scaleTo(1, 1, AnimationTimings.OVERLAY_SHARED_ELEMENT)),
|
||||||
|
targeting(padOfTruth, moveTo((WizardGame.WIDTH - padOfTruth.getWidth()) / 2, (WizardGame.HEIGHT - padOfTruth.getHeight()) / 2, AnimationTimings.OVERLAY_SHARED_ELEMENT))
|
||||||
|
),
|
||||||
|
delay(AnimationTimings.OVERLAY_HOLD),
|
||||||
|
parallel(
|
||||||
|
targeting(padOfTruth, scaleTo(PadOfTruth.COLLAPSED_SCALE, PadOfTruth.COLLAPSED_SCALE, AnimationTimings.OVERLAY_SHARED_ELEMENT)),
|
||||||
|
targeting(padOfTruth, moveTo(WizardGame.WIDTH - 10 - PadOfTruth.EXTENDED_WIDTH, 10))
|
||||||
|
),
|
||||||
|
run(() -> padOfTruth.setEnabled(true))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public InteractionOverlay showMakePredictionOverlay(int round, long timeout) {
|
||||||
|
var overlay = new MakePredictionOverlay(this, timeout, round);
|
||||||
|
execute(overlay);
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InteractionOverlay showChangePredictionOverlay(int round, int oldPrediction, long timeout) {
|
||||||
|
var overlay = new ChangePredictionOverlay(this, timeout, round, oldPrediction);
|
||||||
|
execute(overlay);
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
//<editor-fold desc="Messages" defaultstate="collapsed">
|
||||||
public void addMessage(@Nls String text) {
|
public void addMessage(@Nls String text) {
|
||||||
addMessage(text, false);
|
addMessage(text, false);
|
||||||
}
|
}
|
||||||
@ -512,44 +545,33 @@ public class GameScreen extends MenuScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
persistentMessage = new Label(text, getData().skin);
|
persistentMessage = new Label(text, game.data.skin);
|
||||||
messages.addActor(persistentMessage);
|
messages.addActor(persistentMessage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//</editor-fold>
|
||||||
public void send(PlayerMessage message) {
|
|
||||||
if (!sending.getAndSet(true) || (message instanceof ContinueMessage)) {
|
|
||||||
game.getClient().send(new InteractionMessage(message));
|
|
||||||
} else {
|
|
||||||
addMessage("Please slow down.", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ready(boolean success) {
|
public void ready(boolean success) {
|
||||||
if (!pendingSync.get()) {
|
if (success) closeInteractionOverlay();
|
||||||
sending.set(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success && currentAction instanceof Overlay overlay && overlay instanceof InteractionOverlay) {
|
|
||||||
overlay.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkAction(JUGGLE_CARD) && juggledCard != null) {
|
|
||||||
handCards.setSelected(juggledCard);
|
|
||||||
juggledCard = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void timeout() {
|
public void timeout() {
|
||||||
addMessage(game.messages.get("game.message.timeout"));
|
addMessage(game.messages.get("game.message.timeout"));
|
||||||
ready(true);
|
closeInteractionOverlay();
|
||||||
clearActivePlayer();
|
clearActivePlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sync() {
|
public void sync() {
|
||||||
pendingSync.set(true);
|
pendingSync.set(true);
|
||||||
sending.set(true);
|
}
|
||||||
|
|
||||||
|
public void closeInteractionOverlay() {
|
||||||
|
execute(() -> {
|
||||||
|
if (currentAction instanceof Overlay overlay && overlay instanceof InteractionOverlay) {
|
||||||
|
overlay.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -579,8 +601,8 @@ public class GameScreen extends MenuScreen {
|
|||||||
|
|
||||||
var animation = parallel();
|
var animation = parallel();
|
||||||
removed.forEach(actor -> {
|
removed.forEach(actor -> {
|
||||||
getData().stage.addActor(actor);
|
game.data.stage.addActor(actor);
|
||||||
animation.addAction(targeting(actor, left.moveToHand(AnimationTimings.JUGGLE)));
|
animation.addAction(left.moveToHand(actor, AnimationTimings.JUGGLE));
|
||||||
});
|
});
|
||||||
|
|
||||||
added.forEach(actor -> {
|
added.forEach(actor -> {
|
||||||
@ -600,7 +622,6 @@ public class GameScreen extends MenuScreen {
|
|||||||
// apply start values
|
// apply start values
|
||||||
var startPos = new Vector2(right.getHandX(), right.getHandY());
|
var startPos = new Vector2(right.getHandX(), right.getHandY());
|
||||||
handCards.stageToLocalCoordinates(startPos);
|
handCards.stageToLocalCoordinates(startPos);
|
||||||
actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
|
|
||||||
animation.addAction(targeting(actor, moveTo(startPos.x, startPos.y)));
|
animation.addAction(targeting(actor, moveTo(startPos.x, startPos.y)));
|
||||||
animation.addAction(targeting(actor, rotateTo(right.getHandAngle())));
|
animation.addAction(targeting(actor, rotateTo(right.getHandAngle())));
|
||||||
|
|
||||||
@ -617,6 +638,7 @@ public class GameScreen extends MenuScreen {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public enum Seat {
|
public enum Seat {
|
||||||
|
FALLBACK(WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT * 0.5f, 0, 0, 0, WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT * 0.5f),
|
||||||
BOTTOM(WizardGame.WIDTH * 0.5f, 0, 0, 0, Align.bottom, WizardGame.WIDTH * 0.5f, 300),
|
BOTTOM(WizardGame.WIDTH * 0.5f, 0, 0, 0, Align.bottom, WizardGame.WIDTH * 0.5f, 300),
|
||||||
LEFT(0, WizardGame.HEIGHT * 0.5f, 50, WizardGame.HEIGHT * 0.5f + 110f, Align.bottomLeft, 117.5f, WizardGame.HEIGHT * 0.5f),
|
LEFT(0, WizardGame.HEIGHT * 0.5f, 50, WizardGame.HEIGHT * 0.5f + 110f, Align.bottomLeft, 117.5f, WizardGame.HEIGHT * 0.5f),
|
||||||
TOP_LEFT(WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT - 200),
|
TOP_LEFT(WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT - 200),
|
||||||
@ -655,14 +677,13 @@ public class GameScreen extends MenuScreen {
|
|||||||
var actor = new CardActor(card, atlas);
|
var actor = new CardActor(card, atlas);
|
||||||
actor.setPosition(getHandX() - actor.getWidth() / 2, getHandY() - actor.getHeight() / 2);
|
actor.setPosition(getHandX() - actor.getWidth() / 2, getHandY() - actor.getHeight() / 2);
|
||||||
actor.setRotation(getHandAngle());
|
actor.setRotation(getHandAngle());
|
||||||
actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
|
|
||||||
return actor;
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action moveToHand(float duration) {
|
public Action moveToHand(CardActor actor, float duration) {
|
||||||
return parallel(
|
return parallel(
|
||||||
moveTo(handX, handY, duration),
|
targeting(actor, moveTo(handX, handY, duration)),
|
||||||
rotateTo(handAngle, duration)
|
targeting(actor, rotateTo(handAngle, duration))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,32 @@
|
|||||||
package eu.jonahbauer.wizard.client.libgdx.state;
|
package eu.jonahbauer.wizard.client.libgdx.state;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.client.libgdx.Client;
|
import eu.jonahbauer.wizard.client.libgdx.Client;
|
||||||
|
import eu.jonahbauer.wizard.client.libgdx.actors.game.overlay.InteractionOverlay;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
|
||||||
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
|
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
|
||||||
|
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
|
||||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||||
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
import eu.jonahbauer.wizard.common.messages.data.SessionData;
|
||||||
import eu.jonahbauer.wizard.common.messages.observer.*;
|
import eu.jonahbauer.wizard.common.messages.observer.*;
|
||||||
|
import eu.jonahbauer.wizard.common.messages.player.*;
|
||||||
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
|
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
|
||||||
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
|
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
|
||||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||||
import eu.jonahbauer.wizard.common.model.Card;
|
import eu.jonahbauer.wizard.common.model.Card;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.*;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@Getter
|
@Getter
|
||||||
@ -27,7 +36,7 @@ public final class Game extends BaseState {
|
|||||||
private final String sessionName;
|
private final String sessionName;
|
||||||
private final String secret;
|
private final String secret;
|
||||||
|
|
||||||
private final Map<UUID, String> players;
|
private final LinkedHashMap<UUID, String> players;
|
||||||
private final Map<UUID, Integer> scores = new HashMap<>();
|
private final Map<UUID, Integer> scores = new HashMap<>();
|
||||||
|
|
||||||
private int round = -1;
|
private int round = -1;
|
||||||
@ -37,13 +46,20 @@ public final class Game extends BaseState {
|
|||||||
|
|
||||||
private int trick = -1;
|
private int trick = -1;
|
||||||
private final List<Pair<UUID, Card>> stack = new ArrayList<>();
|
private final List<Pair<UUID, Card>> stack = new ArrayList<>();
|
||||||
|
private Interaction currentInteraction;
|
||||||
|
private int pendingClearActivePlayer = 0;
|
||||||
|
|
||||||
private Card trumpCard;
|
private Card trumpCard;
|
||||||
private Card.Suit trumpSuit;
|
private Card.Suit trumpSuit;
|
||||||
|
|
||||||
private GameScreen gameScreen;
|
private GameScreen gameScreen;
|
||||||
|
private final AtomicBoolean sending = new AtomicBoolean(false);
|
||||||
|
|
||||||
public Game(UUID self, UUID session, String sessionName, String secret, Map<UUID, String> players) {
|
private boolean juggling;
|
||||||
|
private Card juggleCard;
|
||||||
|
private boolean werewolf;
|
||||||
|
|
||||||
|
public Game(UUID self, UUID session, String sessionName, String secret, LinkedHashMap<UUID, String> players) {
|
||||||
this.self = self;
|
this.self = self;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.sessionName = sessionName;
|
this.sessionName = sessionName;
|
||||||
@ -53,20 +69,71 @@ public final class Game extends BaseState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ClientState> onEnter(Client client) {
|
public Optional<ClientState> onEnter(Client client) {
|
||||||
gameScreen = new GameScreen(client.getGame());
|
gameScreen = new GameScreen(client.getGame(), self, players);
|
||||||
client.getGame().setScreen(gameScreen);
|
client.getGame().setScreen(gameScreen);
|
||||||
return super.onEnter(client);
|
return super.onEnter(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="onMessage">
|
||||||
@Override
|
@Override
|
||||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||||
log(message);
|
try {
|
||||||
|
|
||||||
if (message instanceof GameMessage game) {
|
if (message instanceof GameMessage game) {
|
||||||
var observerMessage = game.getObserverMessage();
|
var observerMessage = game.getObserverMessage();
|
||||||
if (observerMessage instanceof StateMessage state) {
|
if (observerMessage instanceof StateMessage state) {
|
||||||
switch (state.getState()) {
|
switch (state.getState()) {
|
||||||
case "starting_round" -> {
|
case "starting_round" -> onStartRound();
|
||||||
|
case "starting_trick" -> onStartTrick();
|
||||||
|
case "juggling" -> onJuggle();
|
||||||
|
case "finished" -> {
|
||||||
|
onFinished();
|
||||||
|
return returnToSession();
|
||||||
|
}
|
||||||
|
case "error" -> {
|
||||||
|
onError();
|
||||||
|
return returnToSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (observerMessage instanceof HandMessage hand) {
|
||||||
|
onHandMessage(hand.getPlayer(), hand.getHand());
|
||||||
|
} else if (observerMessage instanceof PredictionMessage prediction) {
|
||||||
|
onPredictionMessage(prediction.getPlayer(), prediction.getPrediction());
|
||||||
|
} else if (observerMessage instanceof TrumpMessage trump) {
|
||||||
|
onTrumpMessage(trump.getCard(), trump.getSuit());
|
||||||
|
} else if (observerMessage instanceof TrickMessage trick) {
|
||||||
|
onTrickMessage(trick.getPlayer(), trick.getCards());
|
||||||
|
} else if (observerMessage instanceof CardMessage card) {
|
||||||
|
onCardMessage(card.getPlayer(), card.getCard());
|
||||||
|
} else if (observerMessage instanceof ScoreMessage score) {
|
||||||
|
onScoreMessage(score.getPoints());
|
||||||
|
} else if (observerMessage instanceof UserInputMessage input) {
|
||||||
|
onUserInputMessage(input.getPlayer(), input.getAction(), input.getTimeout());
|
||||||
|
} else if (observerMessage instanceof TimeoutMessage) {
|
||||||
|
onTimeoutMessage();
|
||||||
|
} else {
|
||||||
|
log.fatal("Unknown observer message type {}.", observerMessage.getClass());
|
||||||
|
// TODO user feedback
|
||||||
|
return Optional.of(new Menu());
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
} else if (message instanceof NackMessage nack) {
|
||||||
|
return onNackMessage(client, message, nack);
|
||||||
|
} else if (message instanceof AckMessage) {
|
||||||
|
onAckMessage();
|
||||||
|
return Optional.empty();
|
||||||
|
} else {
|
||||||
|
return unexpectedMessage(client, message);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (pendingClearActivePlayer > 0 && --pendingClearActivePlayer == 0) {
|
||||||
|
finishInteraction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStartRound() {
|
||||||
|
log.info("Round {} is starting...", round + 1);
|
||||||
|
|
||||||
round ++;
|
round ++;
|
||||||
predictions.clear();
|
predictions.clear();
|
||||||
tricks.clear();
|
tricks.clear();
|
||||||
@ -76,80 +143,232 @@ public final class Game extends BaseState {
|
|||||||
trick = -1;
|
trick = -1;
|
||||||
gameScreen.startRound(round);
|
gameScreen.startRound(round);
|
||||||
}
|
}
|
||||||
case "starting_trick" -> {
|
|
||||||
|
private void onStartTrick() {
|
||||||
|
log.info("Trick {} is starting...", trick + 1);
|
||||||
trick ++;
|
trick ++;
|
||||||
stack.clear();
|
stack.clear();
|
||||||
|
finishInteraction();
|
||||||
gameScreen.startTrick();
|
gameScreen.startTrick();
|
||||||
}
|
}
|
||||||
case "finished", "error" -> {
|
|
||||||
return returnToSession();
|
|
||||||
}
|
|
||||||
case "juggling" -> gameScreen.setJuggling(true);
|
|
||||||
}
|
|
||||||
} else if (observerMessage instanceof HandMessage hand) {
|
|
||||||
hands.put(hand.getPlayer(), hand.getHand());
|
|
||||||
gameScreen.setHand(hand.getPlayer(), hand.getHand());
|
|
||||||
} else if (observerMessage instanceof PredictionMessage prediction) {
|
|
||||||
predictions.put(prediction.getPlayer(), prediction.getPrediction());
|
|
||||||
gameScreen.addPrediction(round, prediction.getPlayer(), prediction.getPrediction());
|
|
||||||
} else if (observerMessage instanceof TrumpMessage trump) {
|
|
||||||
trumpCard = trump.getCard();
|
|
||||||
trumpSuit = trump.getSuit();
|
|
||||||
gameScreen.setTrump(trumpCard, trumpSuit);
|
|
||||||
} else if (observerMessage instanceof TrickMessage trick) {
|
|
||||||
this.stack.clear();
|
|
||||||
this.tricks.computeIfAbsent(trick.getPlayer(), player -> new ArrayList<>())
|
|
||||||
.add(trick.getCards());
|
|
||||||
gameScreen.finishTrick(trick.getPlayer(), trick.getCards());
|
|
||||||
} else if (observerMessage instanceof CardMessage card) {
|
|
||||||
this.stack.add(Pair.of(card.getPlayer(), card.getCard()));
|
|
||||||
|
|
||||||
var handCard = switch (card.getCard()) {
|
private void onJuggle() {
|
||||||
|
juggling = true;
|
||||||
|
juggleCard = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFinished() {
|
||||||
|
log.info("The game has finished.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onError() {
|
||||||
|
log.error("The game has finished with an error.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHandMessage(UUID player, List<Card> hand) {
|
||||||
|
checkPlayer(player);
|
||||||
|
log.info("{} hand cards are: {}", nameOf(player, true, true), hand);
|
||||||
|
|
||||||
|
if (juggling) checkActivePlayer(player, JUGGLE_CARD);
|
||||||
|
finishInteraction();
|
||||||
|
|
||||||
|
hands.put(player, hand);
|
||||||
|
gameScreen.setSelectedCard(null);
|
||||||
|
gameScreen.setHand(player, hand, juggling);
|
||||||
|
juggling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPredictionMessage(UUID player, int prediction) {
|
||||||
|
checkPlayer(player);
|
||||||
|
checkActivePlayer(player, MAKE_PREDICTION, CHANGE_PREDICTION);
|
||||||
|
log.info("{} predicted: {}", nameOf(player, true, false), prediction);
|
||||||
|
|
||||||
|
boolean changed = currentInteraction != null && currentInteraction.action() == CHANGE_PREDICTION;
|
||||||
|
finishInteraction();
|
||||||
|
|
||||||
|
predictions.put(player, prediction);
|
||||||
|
gameScreen.addPrediction(round, player, prediction, changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTrumpMessage(Card trumpCard, Card.Suit trumpSuit) {
|
||||||
|
if (trumpCard == null) {
|
||||||
|
log.info("There is no trump in this round.");
|
||||||
|
} else {
|
||||||
|
log.info("The trump suit is {} ({}).", trumpSuit != null ? trumpSuit : "yet to be determined.", trumpCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
finishInteraction();
|
||||||
|
|
||||||
|
this.trumpCard = trumpCard;
|
||||||
|
this.trumpSuit = trumpSuit;
|
||||||
|
if (trumpCard == Card.WEREWOLF && trumpSuit == null) {
|
||||||
|
werewolf = true;
|
||||||
|
} else {
|
||||||
|
werewolf = false;
|
||||||
|
gameScreen.showTrumpOverlay(null, trumpCard, trumpSuit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTrickMessage(UUID player, List<Card> cards) {
|
||||||
|
checkPlayer(player);
|
||||||
|
log.info("This trick {} goes to {}.", cards, nameOf(player));
|
||||||
|
|
||||||
|
this.stack.clear();
|
||||||
|
this.tricks.computeIfAbsent(player, p -> new ArrayList<>())
|
||||||
|
.add(cards);
|
||||||
|
gameScreen.finishTrick(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCardMessage(UUID player, Card card) {
|
||||||
|
checkPlayer(player);
|
||||||
|
checkActivePlayer(player, PLAY_CARD);
|
||||||
|
log.info("{} played {}.", nameOf(player, true, false), card);
|
||||||
|
finishInteraction();
|
||||||
|
|
||||||
|
this.stack.add(Pair.of(player, card));
|
||||||
|
|
||||||
|
var handCard = switch (card) {
|
||||||
case CHANGELING_JESTER, CHANGELING_WIZARD -> Card.CHANGELING;
|
case CHANGELING_JESTER, CHANGELING_WIZARD -> Card.CHANGELING;
|
||||||
case JUGGLER_BLUE, JUGGLER_GREEN, JUGGLER_RED, JUGGLER_YELLOW -> Card.JUGGLER;
|
case JUGGLER_BLUE, JUGGLER_GREEN, JUGGLER_RED, JUGGLER_YELLOW -> Card.JUGGLER;
|
||||||
case CLOUD_BLUE, CLOUD_GREEN, CLOUD_RED, CLOUD_YELLOW -> Card.CLOUD;
|
case CLOUD_BLUE, CLOUD_GREEN, CLOUD_RED, CLOUD_YELLOW -> Card.CLOUD;
|
||||||
default -> card.getCard();
|
default -> card;
|
||||||
};
|
};
|
||||||
|
|
||||||
var hand = this.hands.get(card.getPlayer());
|
var hand = this.hands.get(player);
|
||||||
if (hand != null) {
|
if (hand != null) {
|
||||||
hand.remove(handCard);
|
hand.remove(handCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
gameScreen.playCard(card.getPlayer(), handCard);
|
gameScreen.playCard(player, handCard);
|
||||||
} else if (observerMessage instanceof ScoreMessage score) {
|
}
|
||||||
score.getPoints().forEach((player, points) -> scores.merge(player, points, Integer::sum));
|
|
||||||
gameScreen.addScores(round, score.getPoints());
|
private void onScoreMessage(Map<UUID, Integer> points) {
|
||||||
} else if (observerMessage instanceof UserInputMessage input) {
|
log.info("The scores are as follows: " + points);
|
||||||
if (input.getAction() == UserInputMessage.Action.SYNC) {
|
points.forEach((player, p) -> scores.merge(player, p, Integer::sum));
|
||||||
|
gameScreen.addScores(round, points);
|
||||||
|
gameScreen.showScoreOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUserInputMessage(UUID player, UserInputMessage.Action action, long timeout) {
|
||||||
|
checkPlayer(player);
|
||||||
|
log.info(
|
||||||
|
"Waiting for input {} from {}. (times out at {})",
|
||||||
|
action,
|
||||||
|
nameOf(player),
|
||||||
|
LocalDateTime.ofInstant(Instant.ofEpochMilli(timeout), ZoneId.systemDefault())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (action == UserInputMessage.Action.SYNC) {
|
||||||
gameScreen.sync();
|
gameScreen.sync();
|
||||||
} else {
|
} else {
|
||||||
gameScreen.setActivePlayer(input.getPlayer(), input.getAction(), input.getTimeout());
|
currentInteraction = new Interaction(player, action, timeout);
|
||||||
|
gameScreen.setActivePlayer(player, action, timeout);
|
||||||
|
|
||||||
|
if (werewolf && action == PICK_TRUMP) {
|
||||||
|
gameScreen.swapTrumpCard(player);
|
||||||
}
|
}
|
||||||
} else if (observerMessage instanceof TimeoutMessage) {
|
|
||||||
|
if (isActive()) {
|
||||||
|
switch (action) {
|
||||||
|
case PICK_TRUMP -> currentInteraction.overlay(gameScreen.showPickTrumpOverlay(timeout, werewolf));
|
||||||
|
case MAKE_PREDICTION -> currentInteraction.overlay(gameScreen.showMakePredictionOverlay(round, timeout));
|
||||||
|
case CHANGE_PREDICTION -> currentInteraction.overlay(gameScreen.showChangePredictionOverlay(round, predictions.get(player), timeout));
|
||||||
|
case PLAY_CARD -> gameScreen.setPersistentMessage(gameScreen.getMessages().get("game.message.play_card.self"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (werewolf && action == PICK_TRUMP) {
|
||||||
|
werewolf = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTimeoutMessage() {
|
||||||
|
log.info("The previous interaction timed out.");
|
||||||
|
delayedFinishInteraction();
|
||||||
gameScreen.timeout();
|
gameScreen.timeout();
|
||||||
} else {
|
|
||||||
// TODO user feedback
|
|
||||||
return Optional.of(new Menu());
|
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
|
||||||
} else if (message instanceof NackMessage nack) {
|
private Optional<ClientState> onNackMessage(Client client, ServerMessage message, NackMessage nack) {
|
||||||
|
sending.set(false);
|
||||||
|
|
||||||
|
if (isActive() && currentInteraction.action() == JUGGLE_CARD && juggleCard != null) {
|
||||||
|
juggleCard = null;
|
||||||
|
}
|
||||||
|
|
||||||
int code = nack.getCode();
|
int code = nack.getCode();
|
||||||
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
|
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
|
||||||
|
log.error(nack.getMessage());
|
||||||
gameScreen.addMessage(nack.getMessage(), true);
|
gameScreen.addMessage(nack.getMessage(), true);
|
||||||
gameScreen.ready(false);
|
gameScreen.ready(false);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
} else {
|
} else {
|
||||||
return unexpectedMessage(client, message);
|
return unexpectedMessage(client, message);
|
||||||
}
|
}
|
||||||
} else if (message instanceof AckMessage) {
|
}
|
||||||
|
|
||||||
|
private void onAckMessage() {
|
||||||
|
log.info("OK");
|
||||||
|
sending.set(false);
|
||||||
|
|
||||||
|
if (isActive() && currentInteraction.action() == JUGGLE_CARD && juggleCard != null) {
|
||||||
|
gameScreen.setSelectedCard(juggleCard);
|
||||||
|
juggleCard = null;
|
||||||
|
}
|
||||||
|
|
||||||
gameScreen.ready(true);
|
gameScreen.ready(true);
|
||||||
return Optional.empty();
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
//<editor-fold desc="Screen Callbacks" defaultstate="collapsed">
|
||||||
|
public Optional<ClientState> onCardClicked(Client client, Card card) {
|
||||||
|
if (isActive()) {
|
||||||
|
if (currentInteraction.action() == PLAY_CARD) {
|
||||||
|
if (card == Card.CLOUD || card == Card.JUGGLER) {
|
||||||
|
gameScreen.showColoredCardOverlay(card, currentInteraction.timeout());
|
||||||
} else {
|
} else {
|
||||||
return unexpectedMessage(client, message);
|
send(client, new PlayCardMessage(card));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
} else if (currentInteraction.action() == JUGGLE_CARD) {
|
||||||
|
if (send(client, new JuggleMessage(juggleCard))) {
|
||||||
|
juggleCard = card;
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gameScreen.addMessage("You cannot do that right now.", true);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ClientState> onSuitClicked(Client client, Card.Suit suit) {
|
||||||
|
if (isActive() && currentInteraction.action() == PICK_TRUMP) {
|
||||||
|
send(client, new PickTrumpMessage(suit));
|
||||||
|
} else {
|
||||||
|
gameScreen.addMessage("You cannot do that right now.", true);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ClientState> onPredictionMade(Client client, int prediction) {
|
||||||
|
if (isActive()) {
|
||||||
|
if (currentInteraction.action() == MAKE_PREDICTION || currentInteraction.action() == CHANGE_PREDICTION) {
|
||||||
|
send(client, new PredictMessage(prediction));
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameScreen.addMessage("You cannot do that right now.", true);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ClientState> sync(Client client) {
|
||||||
|
send(client, new ContinueMessage());
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
private Optional<ClientState> returnToSession() {
|
private Optional<ClientState> returnToSession() {
|
||||||
return Optional.of(new Session(
|
return Optional.of(new Session(
|
||||||
new SessionData(session, sessionName, -1, null),
|
new SessionData(session, sessionName, -1, null),
|
||||||
@ -160,88 +379,115 @@ public final class Game extends BaseState {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void log(ServerMessage message) {
|
/**
|
||||||
if (message instanceof GameMessage gameMessage) {
|
* Sends a message. Only one message may be sent at a time until without receiving an answer.
|
||||||
var observerMessage = gameMessage.getObserverMessage();
|
* @return {@code true} iff the message was sent
|
||||||
if (observerMessage instanceof StateMessage state) {
|
*/
|
||||||
switch (state.getState()) {
|
private boolean send(Client client, PlayerMessage message) {
|
||||||
case "starting_round" -> log.info("Round {} is starting...", round + 1);
|
if (message instanceof ContinueMessage || !sending.getAndSet(true)) {
|
||||||
case "starting_trick" -> log.info("Trick {} is starting...", trick + 1);
|
client.send(new InteractionMessage(message));
|
||||||
case "finished" -> log.info("The game has finished.");
|
return true;
|
||||||
case "error" -> log.info("The game has finished with an error.");
|
|
||||||
}
|
|
||||||
} else if (observerMessage instanceof HandMessage hand) {
|
|
||||||
if (hand.getPlayer().equals(self)) {
|
|
||||||
log.info("Your hand cards are: {}", hand.getHand());
|
|
||||||
} else {
|
} else {
|
||||||
log.info("{}'s hand cards are: {}", nameOf(hand.getPlayer()), hand.getHand());
|
gameScreen.addMessage("Please slow down.", true);
|
||||||
}
|
return false;
|
||||||
} else if (observerMessage instanceof PredictionMessage prediction) {
|
|
||||||
if (prediction.getPlayer().equals(self)) {
|
|
||||||
log.info("You predicted: {}%n", prediction.getPrediction());
|
|
||||||
} else {
|
|
||||||
log.info("{} predicted: {}%n", nameOf(prediction.getPlayer()), prediction.getPrediction());
|
|
||||||
}
|
|
||||||
} else if (observerMessage instanceof TrumpMessage trump) {
|
|
||||||
trumpCard = trump.getCard();
|
|
||||||
trumpSuit = trump.getSuit();
|
|
||||||
if (trumpCard == null) {
|
|
||||||
log.info("There is no trump in this round.");
|
|
||||||
} else {
|
|
||||||
log.info("The trump suit is {} ({}).", trumpSuit, trumpCard);
|
|
||||||
}
|
|
||||||
} else if (observerMessage instanceof TrickMessage trick) {
|
|
||||||
log.info("This trick {} goes to {}.", trick.getCards(), nameOf(trick.getPlayer()));
|
|
||||||
} else if (observerMessage instanceof CardMessage card) {
|
|
||||||
if (card.getPlayer().equals(self)) {
|
|
||||||
log.info("You played {}.", card.getCard());
|
|
||||||
} else {
|
|
||||||
log.info("{} played {}.", nameOf(card.getPlayer()), card.getCard());
|
|
||||||
}
|
|
||||||
} else if (observerMessage instanceof ScoreMessage score) {
|
|
||||||
log.info("The scores are as follows: " + score.getPoints());
|
|
||||||
} else if (observerMessage instanceof UserInputMessage input) {
|
|
||||||
if (input.getAction() != UserInputMessage.Action.SYNC) {
|
|
||||||
if (self.equals(input.getPlayer())) {
|
|
||||||
log.info("It is your turn to {}. You have time until {}.", switch (input.getAction()) {
|
|
||||||
case CHANGE_PREDICTION -> "change your prediction";
|
|
||||||
case JUGGLE_CARD -> "juggle a card";
|
|
||||||
case PLAY_CARD -> "play a card";
|
|
||||||
case PICK_TRUMP -> "pick the trump suit";
|
|
||||||
case MAKE_PREDICTION -> "make a prediction";
|
|
||||||
default -> throw new AssertionError();
|
|
||||||
}, LocalDateTime.ofInstant(Instant.ofEpochMilli(input.getTimeout()), ZoneId.systemDefault()));
|
|
||||||
} else {
|
|
||||||
log.info(
|
|
||||||
"Waiting for input {} from {}. (times out at {})",
|
|
||||||
input.getAction(),
|
|
||||||
nameOf(input.getPlayer()),
|
|
||||||
LocalDateTime.ofInstant(Instant.ofEpochMilli(input.getTimeout()), ZoneId.systemDefault())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (observerMessage instanceof TimeoutMessage) {
|
|
||||||
log.info("The previous interaction timed out.");
|
|
||||||
} else {
|
|
||||||
log.fatal("Unknown observer message type {}.", observerMessage.getClass());
|
|
||||||
}
|
|
||||||
} else if (message instanceof NackMessage nack) {
|
|
||||||
int code = nack.getCode();
|
|
||||||
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
|
|
||||||
log.error(nack.getMessage());
|
|
||||||
} else {
|
|
||||||
log.fatal("Unexpected message: {}", message);
|
|
||||||
}
|
|
||||||
} else if (message instanceof AckMessage) {
|
|
||||||
log.info("OK");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether some action from the player is expected.
|
||||||
|
*/
|
||||||
|
private boolean isActive() {
|
||||||
|
return currentInteraction != null && (currentInteraction.player() == null || self.equals(currentInteraction.player()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the interaction overlay associated with the current interaction and reset the current interaction.
|
||||||
|
*/
|
||||||
|
private void finishInteraction() {
|
||||||
|
if (currentInteraction != null) {
|
||||||
|
var overlay = currentInteraction.overlay();
|
||||||
|
if (overlay != null) overlay.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentInteraction = null;
|
||||||
|
gameScreen.clearActivePlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI-wise equivalent to {@link #finishInteraction()} but information about the interaction is kept until after the
|
||||||
|
* next message.
|
||||||
|
*/
|
||||||
|
private void delayedFinishInteraction() {
|
||||||
|
pendingClearActivePlayer = 2;
|
||||||
|
if (currentInteraction != null) {
|
||||||
|
var overlay = currentInteraction.overlay();
|
||||||
|
if (overlay != null) overlay.close();
|
||||||
|
}
|
||||||
|
gameScreen.clearActivePlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="Logging" defaultState="collapsed">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given player is known and logs errors.
|
||||||
|
*/
|
||||||
|
private void checkPlayer(UUID player) {
|
||||||
|
if (player != null && !players.containsKey(player)) {
|
||||||
|
log.error("Unknown player {}.", player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether one of the given actions is currently expected from the given player and logs errors.
|
||||||
|
*/
|
||||||
|
private void checkActivePlayer(UUID player, UserInputMessage.Action...actions) {
|
||||||
|
if (currentInteraction != null && (currentInteraction.player() == null || currentInteraction.player().equals(player))) {
|
||||||
|
for (var action : actions) {
|
||||||
|
if (currentInteraction.action() == action) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("Received message does not match the previous user input message.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the given player.
|
||||||
|
*/
|
||||||
private String nameOf(UUID player) {
|
private String nameOf(UUID player) {
|
||||||
|
return nameOf(player, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the given player, optionally with a capitalized first letter and/or a possessive suffix ("'s")
|
||||||
|
*/
|
||||||
|
private String nameOf(UUID player, boolean capitalize, boolean possessive) {
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
return "all players";
|
return (capitalize ? "A" : "a") + "ll players" + (possessive ? "'" : "");
|
||||||
|
} else if (self.equals(player)) {
|
||||||
|
return (capitalize ? "Y" : "y") + "ou" + (possessive ? "r" : "");
|
||||||
} else {
|
} else {
|
||||||
return players.get(player);
|
return players.get(player) + (possessive ? "'s" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(fluent = true)
|
||||||
|
public static final class Interaction {
|
||||||
|
private final UUID player;
|
||||||
|
private final UserInputMessage.Action action;
|
||||||
|
private final long timeout;
|
||||||
|
private WeakReference<InteractionOverlay> overlay;
|
||||||
|
|
||||||
|
public void overlay(InteractionOverlay overlay) {
|
||||||
|
this.overlay = new WeakReference<>(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InteractionOverlay overlay() {
|
||||||
|
if (overlay == null) return null;
|
||||||
|
else return overlay.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import lombok.Getter;
|
|||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@Getter
|
@Getter
|
||||||
@ -74,10 +73,9 @@ public final class Session extends BaseState {
|
|||||||
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
} else if (message instanceof StartingGameMessage) {
|
} else if (message instanceof StartingGameMessage) {
|
||||||
return Optional.of(new Game(
|
var players = new LinkedHashMap<UUID, String>();
|
||||||
self, session, sessionName, secret,
|
this.players.forEach(player -> players.put(player.getUuid(), player.getName()));
|
||||||
players.stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName))
|
return Optional.of(new Game(self, session, sessionName, secret, players));
|
||||||
));
|
|
||||||
} else if (sending && message instanceof NackMessage nack) {
|
} else if (sending && message instanceof NackMessage nack) {
|
||||||
// TODO display error
|
// TODO display error
|
||||||
log.error(nack.getMessage());
|
log.error(nack.getMessage());
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package eu.jonahbauer.wizard.client.libgdx.util;
|
|
||||||
|
|
||||||
public record Triple<F,S,T>(F first, S second, T third) {
|
|
||||||
public static <F,S,T> Triple<F,S,T> of(F first, S second, T third) {
|
|
||||||
return new Triple<>(first, second, third);
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 237 B |
Binary file not shown.
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.2 MiB |
@ -1,6 +1,7 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine;
|
package eu.jonahbauer.wizard.core.machine;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.common.machine.TimeoutState;
|
import eu.jonahbauer.wizard.common.machine.TimeoutState;
|
||||||
|
import eu.jonahbauer.wizard.common.messages.player.ContinueMessage;
|
||||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.states.SyncState;
|
import eu.jonahbauer.wizard.core.machine.states.SyncState;
|
||||||
@ -57,6 +58,8 @@ public abstract class GameState implements TimeoutState<GameState, Game> {
|
|||||||
//</editor-fold>
|
//</editor-fold>
|
||||||
|
|
||||||
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
||||||
|
if (message instanceof ContinueMessage) return Optional.empty();
|
||||||
|
|
||||||
throw new IllegalStateException("You cannot do that right now.");
|
throw new IllegalStateException("You cannot do that right now.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ public class Session implements Observer {
|
|||||||
private final Configuration configuration;
|
private final Configuration configuration;
|
||||||
|
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private final Map<UUID, SessionPlayer> players = new HashMap<>();
|
private final Map<UUID, SessionPlayer> players = new LinkedHashMap<>();
|
||||||
|
|
||||||
private Game game;
|
private Game game;
|
||||||
private final List<Pair<UUID, ServerMessage>> messages = new ArrayList<>();
|
private final List<Pair<UUID, ServerMessage>> messages = new ArrayList<>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user