Merge branch 'master' into 'master'

[Mobile] Add Player Avatar Animation

See merge request core-developers/forge!5388
This commit is contained in:
Michael Kamensky
2021-09-19 12:22:33 +00:00
15 changed files with 322 additions and 53 deletions

View File

@@ -781,6 +781,8 @@ public class Game {
}
public void onPlayerLost(Player p) {
//set for Avatar
p.setHasLost(true);
// Rule 800.4 Losing a Multiplayer game
CardCollectionView cards = this.getCardsInGame();
boolean planarControllerLost = false;

View File

@@ -1306,6 +1306,7 @@ public class GameAction {
orderedNoRegCreats = true;
}
for (Card c : noRegCreats) {
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
}
}
@@ -1444,6 +1445,7 @@ public class GameAction {
// cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
checkAgain = true;
}
@@ -1596,6 +1598,8 @@ public class GameAction {
for (Card c : list) {
if (c.getCounters(CounterEnumType.LOYALTY) <= 0) {
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
// Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed());
@@ -1659,6 +1663,8 @@ public class GameAction {
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
for (Card c: cc) {
if (c != toKeep) {
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
}
}
@@ -1693,6 +1699,8 @@ public class GameAction {
}
for (Card c : worlds) {
//for animation
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
game.fireEvent(new GameEventCardDestroyed());
}
@@ -1707,6 +1715,7 @@ public class GameAction {
c.getController().addSacrificedThisTurn(c, source);
c.updateWasDestroyed(true);
return sacrificeDestroy(c, source, table, params);
}
@@ -1733,6 +1742,8 @@ public class GameAction {
activator = sa.getActivatingPlayer();
}
//for animation
c.updateWasDestroyed(true);
// Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed());

View File

@@ -3029,6 +3029,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
view.updateTokenCard(this);
}
public void updateWasDestroyed(boolean value) {
view.updateWasDestroyed(value);
}
public final Card getCopiedPermanent() {
return copiedPermanent;
}

View File

@@ -411,6 +411,15 @@ public class CardView extends GameEntityView {
set(TrackableProperty.CurrentRoom, c.getCurrentRoom());
}
public boolean wasDestroyed() {
if (get(TrackableProperty.WasDestroyed) == null)
return false;
return get(TrackableProperty.WasDestroyed);
}
void updateWasDestroyed(boolean value) {
set(TrackableProperty.WasDestroyed, value);
}
public int getClassLevel() {
return get(TrackableProperty.ClassLevel);
}

View File

@@ -559,6 +559,8 @@ public class Player extends GameEntity implements Comparable<Player> {
return 0;
}
life -= toLose;
//for Avatar animation
view.setAvatarWasDamaged(true);
view.updateLife(this);
lifeLost = toLose;
if (manaBurn) {
@@ -2636,6 +2638,8 @@ public class Player extends GameEntity implements Comparable<Player> {
public void updateAvatar() {
view.updateAvatarIndex(this);
view.updateAvatarCardImageKey(this);
view.setAvatarWasDamaged(false);
view.setHasLost(false);
}
public void updateSleeve() {
@@ -2848,6 +2852,14 @@ public class Player extends GameEntity implements Comparable<Player> {
view.setIsExtraTurn(b);
}
public void setHasLost(boolean b) {
view.setHasLost(b);
}
public void setAvatarWasDamaged(boolean val) {
view.setAvatarWasDamaged(val);
}
public int getExtraTurnCount() {
return view.getExtraTurnCount();
}

View File

@@ -214,6 +214,26 @@ public class PlayerView extends GameEntityView {
set(TrackableProperty.IsExtraTurn, val);
}
public boolean getHasLost() {
if (get(TrackableProperty.HasLost) == null)
return false;
return get(TrackableProperty.HasLost);
}
public void setHasLost(final boolean val) {
set(TrackableProperty.HasLost, val);
}
public boolean getAvatarWasDamaged() {
if (get(TrackableProperty.WasAvatarDamaged) == null)
return false;
return get(TrackableProperty.WasAvatarDamaged);
}
public void setAvatarWasDamaged(final boolean val) {
set(TrackableProperty.WasAvatarDamaged, val);
}
public int getExtraTurnCount() {
return get(TrackableProperty.ExtraTurnCount);
}

View File

@@ -78,6 +78,7 @@ public enum TrackableProperty {
GainControlTargets(TrackableTypes.CardViewCollectionType),
CloneOrigin(TrackableTypes.CardViewType),
ExiledWith(TrackableTypes.CardViewType),
WasDestroyed(TrackableTypes.BooleanType),
ImprintedCards(TrackableTypes.CardViewCollectionType),
HauntedBy(TrackableTypes.CardViewCollectionType),
@@ -199,6 +200,8 @@ public enum TrackableProperty {
ExtraTurnCount(TrackableTypes.IntegerType),
HasPriority(TrackableTypes.BooleanType),
HasDelirium(TrackableTypes.BooleanType),
WasAvatarDamaged(TrackableTypes.BooleanType),
HasLost(TrackableTypes.BooleanType),
//SpellAbility
HostCard(TrackableTypes.CardViewType),

View File

@@ -39,7 +39,7 @@ public class Graphics {
private int failedClipCount;
private float alphaComposite = 1;
private int transformCount = 0;
private String sVertex = "uniform mat4 u_projTrans;\n" +
private final String sVertex = "uniform mat4 u_projTrans;\n" +
"\n" +
"attribute vec4 a_position;\n" +
"attribute vec2 a_texCoord0;\n" +
@@ -55,7 +55,7 @@ public class Graphics {
" v_texCoord = a_texCoord0;\n" +
" v_color = a_color;\n" +
"}";
private String sFragment = "#ifdef GL_ES\n" +
private final String sFragment = "#ifdef GL_ES\n" +
"precision mediump float;\n" +
"precision mediump int;\n" +
"#endif\n" +
@@ -103,8 +103,38 @@ public class Graphics {
"\n" +
" gl_FragColor = vec4(u_color,alpha);\n" +
"}";
private final String vertexShaderGray = "attribute vec4 a_position;\n" +
"attribute vec4 a_color;\n" +
"attribute vec2 a_texCoord0;\n" +
"\n" +
"uniform mat4 u_projTrans;\n" +
"\n" +
"varying vec4 v_color;\n" +
"varying vec2 v_texCoords;\n" +
"\n" +
"void main() {\n" +
" v_color = a_color;\n" +
" v_texCoords = a_texCoord0;\n" +
" gl_Position = u_projTrans * a_position;\n" +
"}";
private final String fragmentShaderGray = "#ifdef GL_ES\n" +
" precision mediump float;\n" +
"#endif\n" +
"\n" +
"varying vec4 v_color;\n" +
"varying vec2 v_texCoords;\n" +
"uniform sampler2D u_texture;\n" +
"uniform float u_grayness;\n" +
"\n" +
"void main() {\n" +
" vec4 c = v_color * texture2D(u_texture, v_texCoords);\n" +
" float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) );\n" +
" vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness);\n" +
" gl_FragColor = vec4(blendedColor.rgb, c.a);\n" +
"}";
private final ShaderProgram shaderOutline = new ShaderProgram(sVertex, sFragment);
private final ShaderProgram shaderGrayscale = new ShaderProgram(vertexShaderGray, fragmentShaderGray);
public Graphics() {
ShaderProgram.pedantic = false;
@@ -632,6 +662,16 @@ public class Graphics {
shapeRenderer.end();
}
public void setColorRGBA(float r, float g, float b, float alphaComposite0) {
alphaComposite = alphaComposite0;
batch.setColor(new Color(r, g, b, alphaComposite));
}
public void resetColorRGBA(float alphaComposite0) {
alphaComposite = alphaComposite0;
batch.setColor(Color.WHITE);
}
public void setAlphaComposite(float alphaComposite0) {
alphaComposite = alphaComposite0;
batch.setColor(new Color(1, 1, 1, alphaComposite));
@@ -662,6 +702,76 @@ public class Graphics {
image.draw(this, x, y, w, h);
fillRoundRect(borderColor, x+1, y+1, w-1.5f, h-1.5f, (h-w)/10);//used by zoom let some edges show...
}
public void drawAvatarImage(FImage image, float x, float y, float w, float h, boolean drawGrayscale) {
if (!drawGrayscale) {
image.draw(this, x, y, w, h);
} else {
batch.end();
shaderGrayscale.bind();
shaderGrayscale.setUniformf("u_grayness", 1f);
batch.setShader(shaderGrayscale);
batch.begin();
//draw gray
image.draw(this, x, y, w, h);
//reset
batch.end();
batch.setShader(null);
batch.begin();
}
}
public void drawCardImage(FImage image, float x, float y, float w, float h, boolean drawGrayscale) {
if (!drawGrayscale) {
image.draw(this, x, y, w, h);
} else {
batch.end();
shaderGrayscale.bind();
shaderGrayscale.setUniformf("u_grayness", 1f);
batch.setShader(shaderGrayscale);
batch.begin();
//draw gray
image.draw(this, x, y, w, h);
//reset
batch.end();
batch.setShader(null);
batch.begin();
}
}
public void drawCardImage(Texture image, float x, float y, float w, float h, boolean drawGrayscale) {
if (!drawGrayscale) {
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
} else {
batch.end();
shaderGrayscale.bind();
shaderGrayscale.setUniformf("u_grayness", 1f);
batch.setShader(shaderGrayscale);
batch.begin();
//draw gray
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
//reset
batch.end();
batch.setShader(null);
batch.begin();
}
}
public void drawCardImage(TextureRegion image, float x, float y, float w, float h, boolean drawGrayscale) {
if (image != null) {
if (!drawGrayscale) {
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
} else {
batch.end();
shaderGrayscale.bind();
shaderGrayscale.setUniformf("u_grayness", 1f);
batch.setShader(shaderGrayscale);
batch.begin();
//draw gray
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
//reset
batch.end();
batch.setShader(null);
batch.begin();
}
}
}
public void drawImage(FImage image, float x, float y, float w, float h) {
drawImage(image, x, y, w, h, false);
}

View File

@@ -39,6 +39,7 @@ public class FSkin {
private static boolean loaded = false;
public static Texture hdLogo = null;
public static Texture overlay_alpha = null;
public static Texture scratch = null;
public static void changeSkin(final String skinName) {
final ForgePreferences prefs = FModel.getPreferences();
@@ -142,6 +143,14 @@ public class FSkin {
} else {
overlay_alpha = null;
}
final FileHandle scratch_overlay = getDefaultSkinFile("scratch.png");
if (scratch_overlay.exists()) {
Texture txScratch = new Texture(scratch_overlay, true);
txScratch.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
scratch = txScratch;
} else {
scratch = null;
}
if (splashScreen != null) {
final FileHandle f = getSkinFile("bg_splash.png");

View File

@@ -590,9 +590,9 @@ public class CardRenderer {
g.setAlphaComposite(oldAlpha);
} else if (showsleeves) {
if (!card.isForeTold())
g.drawImage(sleeves, x, y, w, h);
g.drawCardImage(sleeves, x, y, w, h, card.wasDestroyed());
else
g.drawImage(image, x, y, w, h);
g.drawCardImage(image, x, y, w, h, card.wasDestroyed());
} else {
if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON)
&& (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane()) && rotate){
@@ -610,19 +610,19 @@ public class CardRenderer {
} else {
if (Forge.enableUIMask.equals("Full") && canshow) {
if (ImageCache.isBorderlessCardArt(image))
g.drawImage(image, x, y, w, h);
g.drawCardImage(image, x, y, w, h, card.wasDestroyed());
else {
boolean t = (card.getCurrentState().getOriginalColors() != card.getCurrentState().getColors()) || card.getCurrentState().hasChangeColors();
g.drawBorderImage(ImageCache.getBorderImage(image.toString(), canshow), ImageCache.borderColor(image), ImageCache.getTint(card, image), x, y, w, h, t); //tint check for changed colors
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea);
g.drawCardImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea, card.wasDestroyed());
}
} else if (Forge.enableUIMask.equals("Crop") && canshow) {
g.drawImage(ImageCache.croppedBorderImage(image), x, y, w, h);
g.drawCardImage(ImageCache.croppedBorderImage(image), x, y, w, h, card.wasDestroyed());
} else {
if (canshow)
g.drawImage(image, x, y, w, h);
g.drawCardImage(image, x, y, w, h, card.wasDestroyed());
else // draw card back sleeves
g.drawImage(sleeves, x, y, w, h);
g.drawCardImage(sleeves, x, y, w, h, card.wasDestroyed());
}
}
}

View File

@@ -1,12 +1,15 @@
package forge.screens.match.views;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Align;
import forge.Forge;
import forge.Graphics;
import forge.animation.ForgeAnimation;
import forge.assets.FImage;
import forge.assets.FSkin;
import forge.assets.FSkinFont;
import forge.game.card.CounterEnumType;
import forge.game.player.PlayerView;
@@ -21,19 +24,61 @@ public class VAvatar extends FDisplayObject {
private final PlayerView player;
private final FImage image;
private AvatarAnimation avatarAnimation;
public VAvatar(PlayerView player0) {
player = player0;
image = MatchController.getPlayerAvatar(player);
setSize(WIDTH, HEIGHT);
avatarAnimation = new AvatarAnimation();
}
public VAvatar(PlayerView player0, float size) {
player = player0;
image = MatchController.getPlayerAvatar(player);
setSize(size, size);
avatarAnimation = new AvatarAnimation();
}
private class AvatarAnimation extends ForgeAnimation {
private static final float DURATION = 0.6f;
private float progress = 0;
private boolean finished;
Texture scratch = FSkin.scratch;
private void drawAvatar(Graphics g, FImage image, float x, float y, float w, float h) {
float percentage = progress / DURATION;
if (percentage < 0) {
percentage = 0;
} else if (percentage > 1) {
percentage = 1;
//animation finished clear avatar red overlay
player.setAvatarWasDamaged(false);
}
float mod = w/2f;
if (scratch == null) {
g.setColorRGBA(1, percentage, percentage, g.getfloatAlphaComposite());
g.drawAvatarImage(image, x, y, w, h, player.getHasLost());
g.resetColorRGBA(g.getfloatAlphaComposite());
} else {
g.drawAvatarImage(image, x, y, w, h, player.getHasLost());
g.setAlphaComposite(1-(percentage*1));
g.drawImage(scratch, x-mod/2, y-mod/2, w+mod, h+mod);
g.resetAlphaComposite();
}
}
@Override
protected boolean advance(float dt) {
progress += dt;
return progress < DURATION;
}
@Override
protected void onEnd(boolean endingAll) {
finished = true;
}
}
@Override
public boolean tap(float x, float y, int count) {
ThreadUtil.invokeInGameThread(new Runnable() { //must invoke in game thread in case a dialog needs to be shown
@@ -68,7 +113,19 @@ public class VAvatar extends FDisplayObject {
public void draw(Graphics g) {
float w = getWidth();
float h = getHeight();
g.drawImage(image, 0, 0, w, h);
if (avatarAnimation != null && !MatchController.instance.getGameView().isMatchOver()) {
if (player.getAvatarWasDamaged() && avatarAnimation.progress < 1) {
avatarAnimation.start();
avatarAnimation.drawAvatar(g, image, 0, 0, w, h);
} else {
avatarAnimation.progress = 0;
g.drawAvatarImage(image, 0, 0, w, h, player.getHasLost());
}
} else {
g.drawAvatarImage(image, 0, 0, w, h, player.getHasLost());
}
if (Forge.altPlayerLayout && !Forge.altZoneTabs && Forge.isLandscapeMode())
return;

View File

@@ -1,8 +1,10 @@
package forge.toolbox;
import com.badlogic.gdx.graphics.Texture;
import forge.Forge;
import forge.Graphics;
import forge.animation.ForgeAnimation;
import forge.assets.FSkin;
import forge.card.CardRenderer;
import forge.card.CardRenderer.CardStackPosition;
import forge.game.card.CardView;
@@ -22,6 +24,7 @@ public class FCardPanel extends FDisplayObject {
private boolean wasTapped;
CardTapAnimation tapAnimation;
CardUnTapAnimation untapAnimation;
CardDestroyedAnimation cardDestroyedAnimation;
public FCardPanel() {
this(null);
@@ -30,6 +33,7 @@ public class FCardPanel extends FDisplayObject {
card = card0;
tapAnimation = new CardTapAnimation();
untapAnimation = new CardUnTapAnimation();
cardDestroyedAnimation = new CardDestroyedAnimation();
}
public CardView getCard() {
@@ -84,7 +88,46 @@ public class FCardPanel extends FDisplayObject {
protected CardStackPosition getStackPosition() {
return CardStackPosition.Top;
}
private class CardDestroyedAnimation extends ForgeAnimation {
private static final float DURATION = 0.6f;
private float progress = 0;
private boolean finished;
private Texture scratch = FSkin.scratch;
private void drawCard(Graphics g, CardView card, float x, float y, float w, float h, float edgeOffset) {
float percentage = progress / DURATION;
if (percentage < 0) {
percentage = 0;
} else if (percentage > 1) {
percentage = 1;
}
float mod = (w/3f)*percentage;
float oldAlpha = g.getfloatAlphaComposite();
if (tapped) {
g.startRotateTransform(x + edgeOffset, y + h - edgeOffset, getTappedAngle());
}
CardRenderer.drawCardWithOverlays(g, card, x-mod/2, y-mod/2, w+mod, h+mod, getStackPosition());
if (scratch != null) {
g.setAlphaComposite(0.6f);
g.drawCardImage(scratch, x-mod/2, y-mod/2, w+mod, h+mod, true);
g.setAlphaComposite(oldAlpha);
}
if (tapped) {
g.endTransform();
}
}
@Override
protected boolean advance(float dt) {
progress += dt;
return progress < DURATION;
}
@Override
protected void onEnd(boolean endingAll) {
finished = true;
}
}
private class CardUnTapAnimation extends ForgeAnimation {
private static final float DURATION = 0.14f;
private float progress = 0;
@@ -163,8 +206,21 @@ public class FCardPanel extends FDisplayObject {
w = h / ASPECT_RATIO;
}
float edgeOffset = w / 2f;
if (isGameFast) {
//don't animate if fast
if (card.wasDestroyed()) {
if (cardDestroyedAnimation != null) {
if (cardDestroyedAnimation.progress < 1) {
cardDestroyedAnimation.start();
cardDestroyedAnimation.drawCard(g, card, x, y, w, h, edgeOffset);
} else {
cardDestroyedAnimation.progress = 0;
}
}
return;
}
if (isGameFast || MatchController.instance.getGameView().isMatchOver()) {
//don't animate if game is fast or match is over
if (tapped) {
g.startRotateTransform(x + edgeOffset, y + h - edgeOffset, getTappedAngle());
}
@@ -198,12 +254,14 @@ public class FCardPanel extends FDisplayObject {
tapAnimation.progress = 0;
}
//draw untapped
if (untapAnimation.progress < 1 && animate) {
untapAnimation.start();
untapAnimation.drawCard(g, card, x, y, w, h, edgeOffset);
} else {
wasTapped = false;
CardRenderer.drawCardWithOverlays(g, card, x, y, w, h, getStackPosition());
if (untapAnimation != null) {
if (untapAnimation.progress < 1 && animate) {
untapAnimation.start();
untapAnimation.drawCard(g, card, x, y, w, h, edgeOffset);
} else {
wasTapped = false;
CardRenderer.drawCardWithOverlays(g, card, x, y, w, h, getStackPosition());
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

View File

@@ -306,6 +306,7 @@ public class HostedMatch {
game = null;
for (final PlayerControllerHuman humanController : humanControllers) {
humanController.getGui().setGameSpeed(false);
if (FModel.getPreferences().getPref(FPref.UI_AUTO_YIELD_MODE).equals(ForgeConstants.AUTO_YIELD_PER_CARD) || isMatchOver()) {
// when autoyielding per card, we need to clear auto yields between games since card IDs change
humanController.getGui().clearAutoYields();

View File

@@ -13,41 +13,7 @@ import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardView;
import forge.game.event.GameEvent;
import forge.game.event.GameEventAnteCardsSelected;
import forge.game.event.GameEventAttackersDeclared;
import forge.game.event.GameEventBlockersDeclared;
import forge.game.event.GameEventCardAttachment;
import forge.game.event.GameEventCardChangeZone;
import forge.game.event.GameEventCardCounters;
import forge.game.event.GameEventCardDamaged;
import forge.game.event.GameEventCardPhased;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.event.GameEventCardTapped;
import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventCombatEnded;
import forge.game.event.GameEventCombatUpdate;
import forge.game.event.GameEventGameFinished;
import forge.game.event.GameEventGameOutcome;
import forge.game.event.GameEventLandPlayed;
import forge.game.event.GameEventManaBurn;
import forge.game.event.GameEventManaPool;
import forge.game.event.GameEventPlayerControl;
import forge.game.event.GameEventPlayerCounters;
import forge.game.event.GameEventPlayerLivesChanged;
import forge.game.event.GameEventPlayerPoisoned;
import forge.game.event.GameEventPlayerPriority;
import forge.game.event.GameEventPlayerStatsChanged;
import forge.game.event.GameEventShuffle;
import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellRemovedFromStack;
import forge.game.event.GameEventSpellResolved;
import forge.game.event.GameEventSubgameEnd;
import forge.game.event.GameEventTokenStateUpdate;
import forge.game.event.GameEventTurnBegan;
import forge.game.event.GameEventTurnPhase;
import forge.game.event.GameEventZone;
import forge.game.event.IGameEventVisitor;
import forge.game.event.*;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.zone.Zone;
@@ -506,6 +472,13 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
return processPlayer(event.receiver, livesUpdate);
}
@Override
public Void visit(final GameEventPlayerDamaged event) {
//for avatar animation
event.target.setAvatarWasDamaged(event.amount > 0);
return processEvent();
}
@Override
public Void visit(final GameEventPlayerCounters event) {
return processPlayer(event.receiver, livesUpdate);