/*
* Copyright 2015 Daniel Dittmar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package dan.dit.whatsthat.riddle.games;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import dan.dit.whatsthat.R;
import dan.dit.whatsthat.achievement.AchievementProperties;
import dan.dit.whatsthat.image.Image;
import dan.dit.whatsthat.riddle.Riddle;
import dan.dit.whatsthat.riddle.RiddleConfig;
import dan.dit.whatsthat.riddle.achievement.holders.AchievementJumper;
import dan.dit.whatsthat.riddle.control.RiddleGame;
import dan.dit.whatsthat.riddle.control.RiddleScore;
import dan.dit.whatsthat.riddle.types.TypesHolder;
import dan.dit.whatsthat.testsubject.TestSubject;
import dan.dit.whatsthat.testsubject.shopping.sortiment.SortimentHolder;
import dan.dit.whatsthat.util.compaction.CompactedDataCorruptException;
import dan.dit.whatsthat.util.compaction.Compacter;
import dan.dit.whatsthat.util.flatworld.collision.GeneralHitboxCollider;
import dan.dit.whatsthat.util.flatworld.collision.HitboxRect;
import dan.dit.whatsthat.util.flatworld.effects.WorldEffectMoved;
import dan.dit.whatsthat.util.flatworld.look.BitmapLook;
import dan.dit.whatsthat.util.flatworld.look.Frames;
import dan.dit.whatsthat.util.flatworld.look.FramesOneshot;
import dan.dit.whatsthat.util.flatworld.look.LayerFrames;
import dan.dit.whatsthat.util.flatworld.look.Look;
import dan.dit.whatsthat.util.flatworld.mover.HitboxJumpMover;
import dan.dit.whatsthat.util.flatworld.mover.HitboxNewtonMover;
import dan.dit.whatsthat.util.flatworld.world.Actor;
import dan.dit.whatsthat.util.flatworld.world.FlatRectWorld;
import dan.dit.whatsthat.util.flatworld.world.FlatWorldCallback;
import dan.dit.whatsthat.util.general.PercentProgressListener;
import dan.dit.whatsthat.util.image.BitmapUtil;
import dan.dit.whatsthat.util.image.ImageUtil;
/**
* Created by daniel on 10.05.15.
*/
public class RiddleJumper extends RiddleGame implements FlatWorldCallback {
//fixed constants
private static final float RELATIVE_HEIGHT_BASELINE = 1.f;
private static final long ONE_SECOND = 1000L;
private static final long PSEUDO_RUN_SPEED = 1;
//variable constants
private static final long BACKGROUND_SLIDE_DURATION = 30000L; //ms
private static final long FRAME_DURATION_RUNNER = 90L; //ms
private static final long FRAME_DURATION = 125L; //ms
private static final float RUNNER_LEFT_OFFSET = 10.f; // pixel
private static final float JUMP_RELATIVE_HEIGHT_DELTA = 1.25f;
private static final float DOUBLE_JUMP_RELATIVE_HEIGHT_DELTA = 1.8f;
private static final long JUMP_DURATION = 500L; //ms, time required to perform the normal jump: reaching the peak and landing again.
private static final long DOUBLE_JUMP_REST_DURATION = (long) ((2 * DOUBLE_JUMP_RELATIVE_HEIGHT_DELTA - JUMP_RELATIVE_HEIGHT_DELTA) * JUMP_DURATION / 2); //ms, time required from normal jump peak to full peak and landing again
private static final float OBSTACLE_RELATIVE_HEIGHT_SMALL = 0.7f;
private static final float OBSTACLE_RELATIVE_HEIGHT_FLYING = 0.4f;
private static final float OBSTACLE_RELATIVE_HEIGHT_BIG = 1.1f;
private static final long OBSTACLES_RIGHT_LEFT_DURATION = 1100L; //ms, describes the speed the obstacles travel from right to left
private static final long FLYER_FALL_DURATION = OBSTACLES_RIGHT_LEFT_DURATION; //ms, describes the speed the flying falls to bottom
private static final long FIRST_OBSTACLE_DURATION = 3000L; //ms, delay until the first obstacle appears
private static final int DIFFICULTIES = 4;
public static final int DIFFICULTY_EASY = 0;
public static final int DIFFICULTY_MEDIUM = 1;
public static final int DIFFICULTY_HARD = 2;
public static final int DIFFICULTY_ULTRA = 3;
private static final float DISTANCE_RUN_START_FURTHER_FEATURE = meterToDistanceRun(200);
private static final int[] DISTANCE_RUN_THRESHOLDS = new int[] {0, (int) (meterToDistanceRun(150)), (int) (meterToDistanceRun(400)), (int) (meterToDistanceRun(800))};
private static final long[] NEXT_OBSTACLE_MIN_TIME_SMALL = new long[] {930L, 740L, 580L, 570L, 540L};
private static final long[] NEXT_OBSTACLE_MIN_TIME_BIG = new long[] {1000L, 1200L, 1075L,
1000L, 900L};
private static final long[] NEXT_OBSTACLE_MAX_TIME = new long[] {2000L, 1700L, 1400L, 1150L,
1050L}; //ms, maximum time until the next obstacle appears
private static final long[] NEXT_OBSTACLE_MIN_TIME_SMALL_WIDTH = new long[] {1200L, 1100L, 1100L, 950L, 860L};
private static final double NEXT_OBSTACLE_PREVIOUS_MIN_TIME_WEIGHT = 0.7;
private static final int[] DIFFICULTY_COLORS = new int[] {Color.GREEN, Color.YELLOW, Color.RED, Color.WHITE, Color.CYAN};
private static final int EASY_SMALL_OBSTACLES = 6;
private static final int MEDIUM_SMALL_OBSTACLES = 4;
private static final int HARD_BIG_OBSTACLES = 4;
private static final int ULTRA_OBSTACLES = 3;
private static final float BUBBLE_SCALE = 0.8f;
private static final float MAX_SOLUTION_SCALE = 0.5f;
private static final float BUBBLE_CENTER_Y_ESTIMATE = 0.765f * 0.5f;
private static final float DISTANCE_RUN_PENALTY_ON_SAVE_FRACTION = 0.75f; // mainly required so that it is not worth closing the riddle (or app) when you know you are going to collide
private static final float[] CLEAR_MIND_SIZE_FRACTION = new float[] {0.15f, 0.2f, 0.35f, 0.7f};
private static final int FOGGED_MIND_COLOR = Color.DKGRAY;
private static final int[] MIND_CLEARED_EVERY_K_OBSTACLES = new int[] {2, 3, 4, 6};
private static final int MAX_COLLISIONS_FOR_SCORE_BONUS = 3;
private static final int MAX_COLLISIONS_FOR_SCORE_BIG_BONUS = 1;
private static final String CACHE_BIG_BEAM = TypesHolder.Jumper.NAME + "BigBeam";
private static final String CACHE_BACKGROUND = TypesHolder.Jumper.NAME + "Background";
private Bitmap mThoughtbubble;
private Bitmap mSolutionBackground;
private Canvas mSolutionBackgroundCanvas;
private Paint mClearPaint;
private int mSolutionBackgroundHeight;
private FlatRectWorld mWorld;
private Runner mRunner;
private int mRunnerHeight;
private Bitmap mRunnerBackground;
private Canvas mRunnerBackgroundCanvas;
private Rect mRunnerBackgroundRect;
private Rect mRunnerBackgroundRectDest;
private Bitmap mBackgroundImage;
private int mRunnerBackgroundSeparatorX;
private long mBackgroundSlideCounter;
private List<Obstacle> mCurrentObstacles;
private Bitmap mForeground;
private Canvas mForegroundCanvas;
private long mNextObstacleCounter;
private Random mRand;
private boolean mCollisionBreak;
private int mObstaclesPassed;
private Obstacle mNextObstacle;
private float mDistanceRun;
private boolean mValidDistanceRun;
private Paint mDistanceTextPaint;
private List<List<Obstacle>> mObstacles;
private int mDifficulty;
private long mDifficultyMinTimeExtra;
private boolean mStateMotionIsDown;
private boolean mFlagDoSuperJump;
private float mObstaclesSpeed;
private Obstacle mBoss;
private Bitmap mClearMindBackground;
private Canvas mClearMindCanvas;
private Bitmap[] mClearMind;
private List<Float> mClearMindX;
private List<Float> mClearMindY;
private List<Integer> mClearMindType;
private Paint mSolutionPaint;
private Paint mClearMindPaint;
private Bitmap mBitmapScaled;
private Paint mCollisionBreakTextPaint;
private String[] mCollisionBreakTexts;
private String mNewHighscoreText;
private String mOldHighscoreText;
private String mCurrentDistanceRunMeterText;
private int mLastDrawnDistanceRun;
private float mDistanceRunStart;
private Bitmap mBigBeam;
private WorldEffectMoved mClearMindEffect;
private BitmapLook mClearMindLook;
private boolean mClearMindAttemptHitBubble;
public RiddleJumper(Riddle riddle, Image image, Bitmap bitmap, Resources res, RiddleConfig config, PercentProgressListener listener) {
super(riddle, image, bitmap, res, config, listener);
}
@Override
protected void initAchievementData() {
mConfig.mAchievementGameData.increment(AchievementJumper.KEY_GAME_RUN_STARTED, 1L, 0L);
}
@Override
protected void addBonusReward(@NonNull RiddleScore.Rewardable rewardable) {
int collisions = mConfig.mAchievementGameData != null ? mConfig.mAchievementGameData.getValue(AchievementJumper.KEY_GAME_COLLISION_COUNT, 0L).intValue() : 0;
int bonus = (collisions <= MAX_COLLISIONS_FOR_SCORE_BIG_BONUS ? TypesHolder.SCORE_HARD:
(collisions <= MAX_COLLISIONS_FOR_SCORE_BONUS ? TypesHolder.SCORE_MEDIUM : 0));
rewardable.addBonus(bonus);
}
@Override
public void draw(Canvas canvas) {
canvas.drawBitmap(mRunnerBackground, 0, 0, null);
canvas.drawBitmap(mSolutionBackground, 0, 0, null);
canvas.drawBitmap(mForeground, 0, 0, null);
int currDistanceRun = distanceRunToMeters(mDistanceRun);
// this optimization is only done to reduce amount of times the strings are concatenated since this allocates a StringBuilder
if (currDistanceRun != mLastDrawnDistanceRun || mCurrentDistanceRunMeterText == null) {
mLastDrawnDistanceRun = currDistanceRun;
mCurrentDistanceRunMeterText = Integer.toString(currDistanceRun) + "m";
}
drawTextCenteredX(canvas, mCurrentDistanceRunMeterText, canvas.getWidth() / 2.f, 35.f, mDummyRect, mDistanceTextPaint);
}
private Rect mDummyRect;
private static void drawTextCenteredX(Canvas canvas, String text, float x, float y, Rect dummyRect, Paint paint) {
paint.getTextBounds(text, 0, text.length(), dummyRect);
canvas.drawText(text, x - dummyRect.exactCenterX(), y, paint);
}
private static void setFittingTextSizeX(String text, int maxWidth, Rect dummyRect, Paint paint) {
paint.getTextBounds(text, 0, text.length(), dummyRect);
float currWidth = dummyRect.width();
float newTextSize = paint.getTextSize() * maxWidth / currWidth; // assume linear correlation between text size and width
paint.setTextSize(newTextSize);
}
public static int distanceRunToMeters(float distanceRun) {
return (int) (distanceRun / ONE_SECOND);
}
public static float meterToDistanceRun(int meter) {
return meter * ONE_SECOND;
}
@Override
public void onClose() {
super.onClose();
ImageUtil.CACHE.freeImage(CACHE_BIG_BEAM, mBigBeam);
ImageUtil.CACHE.freeImage(CACHE_BACKGROUND, mBackgroundImage);
}
@Override
protected void initBitmap(Resources res, PercentProgressListener listener) {
mClearMindAttemptHitBubble = TestSubject.isInitialized() && TestSubject.getInstance()
.hasFeature(SortimentHolder.ARTICLE_KEY_JUMPER_BETTERS_IDEAS);
mDummyRect = new Rect();
mRand = new Random();
mBitmapScaled = BitmapUtil.attemptBitmapScaling(mBitmap, (int) (mBitmap.getWidth() * MAX_SOLUTION_SCALE), (int) (mBitmap.getHeight() * MAX_SOLUTION_SCALE), false);
mClearPaint = new Paint();
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mDistanceTextPaint = new Paint();
mDistanceTextPaint.setAntiAlias(true);
mDistanceTextPaint.setTextSize(ImageUtil.convertDpToPixel(22, mConfig.mScreenDensity));
mCollisionBreakTextPaint = new Paint();
mCollisionBreakTextPaint.setFakeBoldText(true);
mCollisionBreakTextPaint.setAntiAlias(true);
mCollisionBreakTexts = res.getStringArray(R.array.riddle_jumper_collision_break);
mNewHighscoreText = res.getString(R.string.riddle_jumper_new_highscore);
mOldHighscoreText = res.getString(R.string.riddle_jumper_old_highscore);
mClearMindLook = new BitmapLook(ImageUtil.loadBitmap(res, R
.drawable.jumper_mindblown, 0, 0, BitmapUtil.MODE_FIT_EXACT));
mClearMindEffect = new WorldEffectMoved(mClearMindLook,
0f, 0f, new HitboxNewtonMover(0f, -40f));
mSolutionBackgroundHeight = (int) (mConfig.mHeight / TypesHolder.Jumper.BITMAP_ASPECT_RATIO);
listener.onProgressUpdate(20);
mObstaclesSpeed = - mConfig.mWidth / ((float) OBSTACLES_RIGHT_LEFT_DURATION / ONE_SECOND);
listener.onProgressUpdate(30);
Compacter cmp = getCurrentState();
initDistanceRun(cmp);
if (cmp != null && cmp.getSize() > 0) {
try {
mBackgroundSlideCounter = cmp.getLong(0);
} catch (CompactedDataCorruptException e) {
mBackgroundSlideCounter = 0L; // not very important data
}
}
listener.onProgressUpdate(40);
mForeground = Bitmap.createBitmap(mConfig.mWidth, mConfig.mHeight, Bitmap.Config.ARGB_8888);
mWorld = new FlatRectWorld(new RectF(0, 0, mForeground.getWidth(), mForeground.getHeight()), new GeneralHitboxCollider(), this);
mForegroundCanvas = new Canvas(mForeground);
listener.onProgressUpdate(60);
initRunner(res);
listener.onProgressUpdate(65);
initObstacles(res, cmp, listener);
listener.onProgressUpdate(90);
initSolution(res, cmp);
listener.onProgressUpdate(95);
drawForeground();
listener.onProgressUpdate(100);
}
private void initObstacles(Resources res, Compacter data, PercentProgressListener listener) {
mObstaclesPassed = 0;
if (data != null && data.getSize() > 1) {
try {
mObstaclesPassed = data.getInt(1);
} catch (CompactedDataCorruptException e) {
// default data is already set
}
}
mCurrentObstacles = new ArrayList<>();
mObstacles = new ArrayList<>();
for (int i = 0; i < DIFFICULTIES; i++) {
mObstacles.add(new ArrayList<Obstacle>());
}
listener.onProgressUpdate(70);
initEasy(res);
listener.onProgressUpdate(75);
initMedium(res);
listener.onProgressUpdate(80);
initHard(res);
listener.onProgressUpdate(85);
initUltra(res);
mNextObstacleCounter = FIRST_OBSTACLE_DURATION;
}
private void initEasy(Resources res) {
int difficulty = DIFFICULTY_EASY;
int width = mConfig.mWidth;
int heightSmall = (int) (OBSTACLE_RELATIVE_HEIGHT_SMALL * mRunnerHeight);
Bitmap[] monsterFeuer = new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.monsterfeuer1, width, heightSmall, false),
ImageUtil.loadBitmap(res, R.drawable.monsterfeuer2, width, heightSmall, false),
ImageUtil.loadBitmap(res, R.drawable.monsterfeuer3, width, heightSmall, false),
ImageUtil.loadBitmap(res, R.drawable.monsterfeuer4, width, heightSmall, false),
ImageUtil.loadBitmap(res, R.drawable.monsterfeuer5, width, heightSmall, false)};
Bitmap[] monsterTeufel = new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.monsterteufel1, width, heightSmall, false),
ImageUtil.loadBitmap(res, R.drawable.monsterteufel2, width, heightSmall, false),
ImageUtil.loadBitmap(res, R.drawable.monsterteufel3, width, heightSmall, false),
null};
monsterTeufel[3] = monsterTeufel[1];
List<Obstacle> obstacles = mObstacles.get(difficulty);
int startCount = obstacles.size();
while (obstacles.size() - startCount < EASY_SMALL_OBSTACLES) {
FramesOneshot feuerFrames = new FramesOneshot(monsterFeuer, (long) ((0.6 + mRand.nextDouble() * 0.7) * OBSTACLES_RIGHT_LEFT_DURATION) );
feuerFrames.setBlendFrames(true, Frames.BLEND_MODE_LINEAR);
obstacles.add(Obstacle.makeObstacle(feuerFrames, 0.9f, 0.85f, mConfig.mWidth,
getTopForRelativeHeight(OBSTACLE_RELATIVE_HEIGHT_SMALL), NEXT_OBSTACLE_MIN_TIME_SMALL, mWorld, mObstaclesSpeed, 0));
Look teufelFrames = new Frames(monsterTeufel, FRAME_DURATION);
obstacles.add(Obstacle.makeObstacle(teufelFrames, 0.75f, 0.95f, mConfig.mWidth,
getTopForRelativeHeight(OBSTACLE_RELATIVE_HEIGHT_SMALL), NEXT_OBSTACLE_MIN_TIME_SMALL, mWorld, mObstaclesSpeed, 0));
}
}
private void initMedium(Resources res) {
int difficulty = DIFFICULTY_MEDIUM;
int width = mConfig.mWidth;
int heightSmall = (int) (OBSTACLE_RELATIVE_HEIGHT_SMALL * mRunnerHeight);
int heightFlying = (int) (OBSTACLE_RELATIVE_HEIGHT_FLYING * mRunnerHeight);
Bitmap[] monsterUfoMedium = new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.monsterufomedium1, width, heightSmall, false)};
Bitmap[] flieger = new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.monsterflying1, width, heightFlying, false),
ImageUtil.loadBitmap(res, R.drawable.monsterflying2, width, heightFlying, false)};
float fliegerTop = getTopForRelativeHeight(OBSTACLE_RELATIVE_HEIGHT_FLYING) - 1.2f * mRunnerHeight;
float fliegerFallSpeed = -(fliegerTop - getTopForRelativeHeight(0) + flieger[0].getHeight()) / ((float) FLYER_FALL_DURATION / ONE_SECOND);
List<Obstacle > obstacles = mObstacles.get(difficulty);
obstacles.addAll(mObstacles.get(DIFFICULTY_EASY));
int startCount = obstacles.size();
while (obstacles.size() - startCount < MEDIUM_SMALL_OBSTACLES) {
Look ufoMediumFrames = new Frames(monsterUfoMedium, FRAME_DURATION);
obstacles.add(Obstacle.makeObstacle(ufoMediumFrames, 0.7f, 0.9f, mConfig.mWidth,
getTopForRelativeHeight(OBSTACLE_RELATIVE_HEIGHT_SMALL), NEXT_OBSTACLE_MIN_TIME_SMALL, mWorld, mObstaclesSpeed, 0));
Look fliegerLook = new Frames(flieger, FRAME_DURATION);
obstacles.add(Obstacle.makeObstacle(fliegerLook, 0.8f, 0.9f, mConfig.mWidth,
fliegerTop, NEXT_OBSTACLE_MIN_TIME_SMALL, mWorld, mObstaclesSpeed, fliegerFallSpeed));
}
Look fliegerLook = new Frames(flieger, FRAME_DURATION);
obstacles.add(Obstacle.makeObstacle(fliegerLook, 0.8f, 0.9f, mConfig.mWidth,
fliegerTop, NEXT_OBSTACLE_MIN_TIME_SMALL, mWorld, mObstaclesSpeed, 0));
}
private void initHard(Resources res) {
int difficulty = DIFFICULTY_HARD;
int width = mConfig.mWidth;
int heightBig = (int) (OBSTACLE_RELATIVE_HEIGHT_BIG * mRunnerHeight);
Bitmap[] monsterWind = new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.monsterwind1, width, heightBig, false),
ImageUtil.loadBitmap(res, R.drawable.monsterwind2, width, heightBig, false)};
mObstacles.get(difficulty).addAll(mObstacles.get(DIFFICULTY_MEDIUM));
List<Obstacle> obstacles = mObstacles.get(difficulty);
int startCount = obstacles.size();
while (obstacles.size() - startCount < HARD_BIG_OBSTACLES) {
Look windFrames = new Frames(monsterWind, FRAME_DURATION);
obstacles.add(Obstacle.makeObstacle(windFrames, 0.8f, 0.85f,
mConfig.mWidth, getTopForRelativeHeight(OBSTACLE_RELATIVE_HEIGHT_BIG), NEXT_OBSTACLE_MIN_TIME_BIG, mWorld, mObstaclesSpeed, 0));
}
}
private void initUltra(Resources res) {
int difficulty = DIFFICULTY_ULTRA;
int width = mConfig.mWidth;
int heightSmall = (int) (OBSTACLE_RELATIVE_HEIGHT_SMALL * mRunnerHeight);
Bitmap[] monsterUfoHard = new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.monsterufohard1, width, heightSmall, false)};
int heightBig = (int) (OBSTACLE_RELATIVE_HEIGHT_BIG * mRunnerHeight);
Bitmap[] crazyBig = new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.monstercrazy1, width, heightBig, false),
ImageUtil.loadBitmap(res, R.drawable.monstercrazy2, width, heightBig, false),
ImageUtil.loadBitmap(res, R.drawable.monstercrazy3, width, heightBig, false),
ImageUtil.loadBitmap(res, R.drawable.monstercrazy4, width, heightBig, false),
ImageUtil.loadBitmap(res, R.drawable.monstercrazy5, width, heightBig, false)};
mBigBeam = ImageUtil.CACHE.obtainImage(CACHE_BIG_BEAM, res, R.drawable.monstercrazy6, width, mForeground.getHeight(), false);
mObstacles.get(difficulty).addAll(mObstacles.get(DIFFICULTY_HARD));
List<Obstacle> obstacles = mObstacles.get(difficulty);
int startCount = obstacles.size();
while (obstacles.size() - startCount < ULTRA_OBSTACLES - 1) {
Look ufoHardFrames = new Frames(monsterUfoHard, FRAME_DURATION);
obstacles.add(Obstacle.makeObstacle(ufoHardFrames, 0.9f, 0.7f,
mConfig.mWidth, getTopForRelativeHeight(OBSTACLE_RELATIVE_HEIGHT_SMALL), NEXT_OBSTACLE_MIN_TIME_SMALL_WIDTH, mWorld, mObstaclesSpeed, 0));
}
LayerFrames crazyFrames = new LayerFrames(crazyBig, OBSTACLES_RIGHT_LEFT_DURATION / (crazyBig.length - 1), 1);
crazyFrames.setBackgroundLayerBitmap(4, 0, mBigBeam);
mBoss = Obstacle.makeObstacle(crazyFrames, 0.7f, 0.85f,
mConfig.mWidth, getTopForRelativeHeight(OBSTACLE_RELATIVE_HEIGHT_BIG), NEXT_OBSTACLE_MIN_TIME_BIG, mWorld, mObstaclesSpeed, 0);
obstacles.add(mBoss);
}
private void initRunner(Resources res) {
mRunnerHeight = mConfig.mHeight - mSolutionBackgroundHeight;
Bitmap fallingImage = ImageUtil.loadBitmap(res, R.drawable.schritt1, mConfig.mWidth, mRunnerHeight, false);
Frames runnerFramesRun = new Frames(new Bitmap[] {
fallingImage,
ImageUtil.loadBitmap(res, R.drawable.schritt2, mConfig.mWidth, mRunnerHeight, false),
ImageUtil.loadBitmap(res, R.drawable.schritt3, mConfig.mWidth, mRunnerHeight, false),
ImageUtil.loadBitmap(res, R.drawable.schritt4, mConfig.mWidth, mRunnerHeight, false),
ImageUtil.loadBitmap(res, R.drawable.schritt5, mConfig.mWidth, mRunnerHeight, false),
ImageUtil.loadBitmap(res, R.drawable.schritt6, mConfig.mWidth, mRunnerHeight, false),
ImageUtil.loadBitmap(res, R.drawable.schritt7, mConfig.mWidth, mRunnerHeight, false),
ImageUtil.loadBitmap(res, R.drawable.schritt8, mConfig.mWidth, mRunnerHeight, false)}, FRAME_DURATION_RUNNER);
runnerFramesRun.setBlendFrames(true, Frames.BLEND_MODE_QUADRATIC);
Look runnerFramesJumpUp = new Frames(new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.schrittjump, mConfig.mWidth, mRunnerHeight, false)}, FRAME_DURATION_RUNNER);
Look runnerFramesJumpDown = new Frames(new Bitmap[] {
fallingImage}, FRAME_DURATION_RUNNER);
Look runnerFramesCollision = new Frames(new Bitmap[] {
ImageUtil.loadBitmap(res, R.drawable.schrittko, mConfig.mWidth, mRunnerHeight, false)}, FRAME_DURATION_RUNNER);
mRunner = Runner.makeRunner(runnerFramesRun, 0.5f, 0.8f, RUNNER_LEFT_OFFSET, getTopForRelativeHeight(RELATIVE_HEIGHT_BASELINE), mWorld);
mRunner.putStateFrames(HitboxJumpMover.STATE_JUMP_ASCENDING, runnerFramesJumpUp);
mRunner.putStateFrames(HitboxJumpMover.STATE_JUMP_FALLING, runnerFramesJumpDown);
mRunner.putStateFrames(HitboxJumpMover.STATE_NOT_MOVING, runnerFramesRun);
mRunner.putStateFrames(HitboxJumpMover.STATE_LANDED, runnerFramesRun);
mRunner.putStateFrames(Runner.STATE_COLLISION, runnerFramesCollision);
//background related
mRunnerBackground = Bitmap.createBitmap(mConfig.mWidth, mConfig.mHeight, mBitmap.getConfig());
mBackgroundImage = ImageUtil.CACHE.obtainImage(CACHE_BACKGROUND, res, R.drawable.skyline, mRunnerBackground.getWidth(), mRunnerBackground.getHeight(), true);
mRunnerBackgroundCanvas = new Canvas(mRunnerBackground);
mRunnerBackgroundRect = new Rect();
mRunnerBackgroundRectDest = new Rect();
mRunnerBackgroundSeparatorX = 0;
updateRunnerBackground();
}
private void updateRunnerBackground() {
if (mRunnerBackgroundSeparatorX < mBackgroundImage.getWidth()) {
mRunnerBackgroundRect.set(mRunnerBackgroundSeparatorX, 0, mBackgroundImage.getWidth(), mBackgroundImage.getHeight());
mRunnerBackgroundRectDest.set(0, 0, mBackgroundImage.getWidth() - mRunnerBackgroundSeparatorX, mBackgroundImage.getHeight());
mRunnerBackgroundCanvas.drawBitmap(mBackgroundImage, mRunnerBackgroundRect, mRunnerBackgroundRectDest, null);
}
int missingWidth = mRunnerBackgroundSeparatorX;
mRunnerBackgroundRect.set(0, 0, missingWidth, mBackgroundImage.getHeight());
mRunnerBackgroundRectDest.set(mBackgroundImage.getWidth() - missingWidth, 0, mBackgroundImage.getWidth(), mBackgroundImage.getHeight());
mRunnerBackgroundCanvas.drawBitmap(mBackgroundImage, mRunnerBackgroundRect, mRunnerBackgroundRectDest, null);
}
private boolean onBackgroundUpdate(long updateTime) {
mBackgroundSlideCounter += updateTime;
if (mBackgroundSlideCounter >= BACKGROUND_SLIDE_DURATION) {
mBackgroundSlideCounter -= BACKGROUND_SLIDE_DURATION;
}
int oldX = mRunnerBackgroundSeparatorX;
mRunnerBackgroundSeparatorX = (int) (mBackgroundImage.getWidth() * (mBackgroundSlideCounter / (float) BACKGROUND_SLIDE_DURATION));
boolean drawBackground = oldX != mRunnerBackgroundSeparatorX;
if (drawBackground) {
updateRunnerBackground();
}
return drawBackground;
}
private void drawForeground() {
mForegroundCanvas.drawPaint(mClearPaint);
mWorld.draw(mForegroundCanvas, null);
if (mCollisionBreak) {
Canvas canvas = mForegroundCanvas;
if (mDifficulty < mCollisionBreakTexts.length) {
drawTextCenteredX(canvas, mCollisionBreakTexts[mDifficulty], canvas.getWidth() / 2.f, mSolutionBackground.getHeight() / 2.f, mDummyRect, mCollisionBreakTextPaint);
}
long currentHighscore = mConfig.mAchievementTypeData.getValue(AchievementJumper.KEY_TYPE_TOTAL_RUN_HIGHSCORE, 0L);
if (mDistanceRun >= currentHighscore) {
// new highscore, set it directly so that the threshold is not displayed and no confusion appears
updateHighscore((long) mDistanceRun, AchievementProperties.UPDATE_POLICY_ALWAYS);
Paint paint = mDistanceTextPaint;
int oldColor = paint.getColor();
paint.setColor(0xffdc9912);
drawTextCenteredX(canvas, String.format(mNewHighscoreText, distanceRunToMeters(mDistanceRun)), canvas.getWidth() / 2.f, mSolutionBackground.getHeight() / 2.f + 2 * mDummyRect.height(), mDummyRect, paint);
paint.setColor(oldColor);
} else {
drawTextCenteredX(canvas, String.format(mOldHighscoreText, distanceRunToMeters(currentHighscore)), canvas.getWidth() / 2.f, mSolutionBackground.getHeight() / 2.f + mDummyRect.height(), mDummyRect, mDistanceTextPaint);
}
}
}
private float getTopForRelativeHeight(float relativeHeight) {
return mConfig.mHeight - mRunnerHeight * relativeHeight;
}
private void initSolution(Resources res, Compacter cmp) {
mSolutionPaint = new Paint();
mSolutionPaint.setAntiAlias(true);
mSolutionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
mSolutionBackground = Bitmap.createBitmap(mConfig.mWidth, mSolutionBackgroundHeight, Bitmap.Config.ARGB_8888);
mSolutionBackgroundCanvas = new Canvas(mSolutionBackground);
mThoughtbubble = ImageUtil.loadBitmap(res, R.drawable.gedankenblase, (int) (BUBBLE_SCALE * mConfig.mWidth), (int) (BUBBLE_SCALE * mSolutionBackgroundHeight), true);
mClearMind = new Bitmap[CLEAR_MIND_SIZE_FRACTION.length];
for (int i = 0; i < CLEAR_MIND_SIZE_FRACTION.length; i++) {
float fraction = CLEAR_MIND_SIZE_FRACTION[i];
int clearMindSize = (int) (Math.min(mSolutionBackground.getWidth(), mSolutionBackground.getHeight()) * fraction);
mClearMind[i] = ImageUtil.loadBitmap(res, R.drawable.explosion1, clearMindSize, clearMindSize, false);
}
mClearMindX = new LinkedList<>();
mClearMindY = new LinkedList<>();
mClearMindType = new LinkedList<>();
mClearMindBackground = Bitmap.createBitmap(mSolutionBackground.getWidth(), mSolutionBackground.getHeight(), mSolutionBackground.getConfig());
mClearMindCanvas = new Canvas(mClearMindBackground);
mClearMindCanvas.drawColor(FOGGED_MIND_COLOR);
mMindRect = new Rect();
mSourceRect = new Rect();
mClearMindPaint = new Paint();
mClearMindPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mSolutionBackgroundCanvas.drawPaint(mClearPaint);
mSolutionBackgroundCanvas.drawBitmap(mThoughtbubble, mSolutionBackground.getWidth() / 2 - mThoughtbubble.getWidth() / 2, mSolutionBackground.getHeight() / 2 - mThoughtbubble.getHeight() / 2, null);
mSolutionBackgroundCanvas.drawBitmap(mClearMindBackground, 0, 0, mSolutionPaint);
if (cmp != null && cmp.getSize() > 5) {
Compacter xData = new Compacter(cmp.getData(3));
Compacter yData = new Compacter(cmp.getData(4));
Compacter typeData = new Compacter(cmp.getData(5));
try {
int count = Math.min(xData.getSize(), Math.min(yData.getSize(), typeData.getSize()));
for (int i = 0; i < count; i++) {
clearMind(xData.getFloat(i), yData.getFloat(i), typeData.getInt(i));
}
} catch (CompactedDataCorruptException e) {
Log.e("Riddle", "Clear mind data corrupt for RiddleJumper: " + e);
}
}
}
private Rect mSourceRect;
private Rect mMindRect;
private void clearMind(float x, float y, int type) {
mClearMindX.add(x);
mClearMindY.add(y);
type = Math.min(mClearMind.length - 1, type);
mClearMindType.add(type);
// update the mind canvas by exploding the part out
Bitmap clearMind = mClearMind[type];
mMindRect.set((int) x, (int) y, (int) (x + clearMind.getWidth() + 0.5f), (int) (y + clearMind.getHeight() + 0.5f));
mClearMindCanvas.drawBitmap(clearMind, x, y, mClearMindPaint);
// redraw relevant part of the thought bubble
mSourceRect.set(mMindRect);
mSourceRect.offset(-mSolutionBackground.getWidth() / 2 + mThoughtbubble.getWidth() / 2, -mSolutionBackground.getHeight() / 2 + mThoughtbubble.getHeight() / 2);
mSolutionBackgroundCanvas.drawBitmap(mThoughtbubble, mSourceRect, mMindRect, null);
// redraw relevant part of the original bitmap
mSourceRect.set(mMindRect);
mSourceRect.offset(-mSolutionBackground.getWidth() / 2 + mBitmapScaled.getWidth() / 2, (int) (-mSolutionBackground.getHeight() * BUBBLE_CENTER_Y_ESTIMATE + mBitmapScaled.getHeight() / 2));
mSolutionBackgroundCanvas.drawBitmap(mBitmapScaled, mSourceRect, mMindRect, mSolutionPaint);
// overdraw relevant part of solution background with mind canvas
mSolutionBackgroundCanvas.drawBitmap(mClearMindBackground, mMindRect, mMindRect, mSolutionPaint);
}
private void onObstaclePassed(Actor obstacle) {
obstacle.setActive(false);
mValidDistanceRun = true; // after loading or first start, to prevent cheating by closing (which is still kinda possible to prevent getting hit but this is punished by decreasing distance)
mObstaclesPassed++;
mDifficultyMinTimeExtra++;
//noinspection SuspiciousMethodCalls
mCurrentObstacles.remove(obstacle); // not suspicious
if (mObstaclesPassed % MIND_CLEARED_EVERY_K_OBSTACLES[mDifficulty] == 0) {
int type = mDifficulty;
int tries = mClearMindAttemptHitBubble ? 10 : 1;
float x;
float y;
int thoughtX;
int thoughtY;
boolean validThoughtPoint;
do {
x = mRand.nextFloat() * (mClearMindBackground.getWidth() - mClearMind[type].getWidth());
y = mRand.nextFloat() * (mClearMindBackground.getHeight() * 2 * BUBBLE_CENTER_Y_ESTIMATE);
thoughtX = (int) (x);
thoughtY = (int) (y);
validThoughtPoint = thoughtX >= 0 && thoughtY >= 0
&& thoughtX < mThoughtbubble.getWidth() && thoughtY < mThoughtbubble
.getHeight();
tries--;
} while ((validThoughtPoint && tries > 0
&& Color.alpha(mThoughtbubble.getPixel(thoughtX, thoughtY)) == 0)
|| (tries > 0 && !validThoughtPoint));
clearMind(x, y, type);
mClearMindEffect.setCenter(mMindRect.centerX(), mMindRect.centerY());
mWorld.addEffect(mClearMindEffect, 1000L, 700L, 255, 0, true);
}
mConfig.mAchievementGameData.increment(AchievementJumper.KEY_GAME_OBSTACLE_DODGED_COUNT, 1L, 0L);
}
private void initDistanceRun(Compacter data) {
if (TestSubject.getInstance().hasFeature(SortimentHolder.ARTICLE_KEY_JUMPER_START_FURTHER_FEATURE)) {
mDistanceRunStart = DISTANCE_RUN_START_FURTHER_FEATURE;
} else {
mDistanceRunStart = 0.f;
}
mDistanceRun = mDistanceRunStart;
if (data != null && data.getSize() > 2) {
try {
mDistanceRun = data.getFloat(2);
} catch (CompactedDataCorruptException e) {
Log.e("Riddle", "Error reading distance run data: " + e);
}
}
mConfig.mAchievementGameData.putValue(AchievementJumper.KEY_GAME_CURRENT_DISTANCE_RUN, (long) mDistanceRun, AchievementProperties.UPDATE_POLICY_ALWAYS);
updateDifficulty();
updateCollisionBreakPaint();
}
private void onReleaseCollision() {
mDistanceRun = mDistanceRunStart;
mConfig.mAchievementGameData.putValue(AchievementJumper.KEY_GAME_CURRENT_DISTANCE_RUN, (long) mDistanceRun, AchievementProperties.UPDATE_POLICY_ALWAYS);
updateDifficulty();
mCollisionBreak = false;
mConfig.mAchievementGameData.increment(AchievementJumper.KEY_GAME_RUN_STARTED, 1L, 0L);
mRunner.setStateFrames(HitboxJumpMover.STATE_NOT_MOVING);
}
private void updateHighscore(Long distanceRun, Long threshold) {
mConfig.mAchievementGameData.putValue(AchievementJumper.KEY_GAME_RUN_HIGHSCORE, distanceRun, threshold);
mConfig.mAchievementTypeData.putValue(AchievementJumper.KEY_TYPE_TOTAL_RUN_HIGHSCORE, distanceRun, threshold);
}
private boolean onDistanceRun(long updateTime) {
if (mValidDistanceRun) {
mDistanceRun += PSEUDO_RUN_SPEED * ONE_SECOND / updateTime;
mConfig.mAchievementGameData.putValue(AchievementJumper.KEY_GAME_CURRENT_DISTANCE_RUN, (long) mDistanceRun, AchievementProperties.UPDATE_POLICY_ALWAYS);
updateHighscore((long) mDistanceRun, AchievementJumper.DISTANCE_RUN_THRESHOLD);
updateDifficulty();
return true;
}
return false;
}
private void updateDifficulty() {
int oldDifficulty = mDifficulty;
mDifficulty = 0;
for (int i = DIFFICULTIES - 1; i >= 0; i--) {
if (mDistanceRun >= DISTANCE_RUN_THRESHOLDS[i]) {
mDifficulty = i;
break;
}
}
if (oldDifficulty != mDifficulty) {
mDifficultyMinTimeExtra = 0L;
if (mDifficulty != DIFFICULTY_EASY && mBoss != null) {
mNextObstacle = mBoss;
}
updateCollisionBreakPaint();
}
mConfig.mAchievementGameData.putValue(AchievementJumper.KEY_GAME_CURRENT_DIFFICULTY, (long) mDifficulty, AchievementProperties.UPDATE_POLICY_ALWAYS);
mDistanceTextPaint.setColor(DIFFICULTY_COLORS[mDifficulty]);
}
private void updateCollisionBreakPaint() {
mCollisionBreakTextPaint.setColor(DIFFICULTY_COLORS[mDifficulty]);
if (mDifficulty < mCollisionBreakTexts.length) {
setFittingTextSizeX(mCollisionBreakTexts[mDifficulty], mConfig.mWidth - 30, mDummyRect, mCollisionBreakTextPaint);
}
}
@Override
public boolean requiresPeriodicEvent() {
return true;
}
@Override
public void onPeriodicEvent(long updateTime) {
if (!mCollisionBreak) {
mWorld.update(updateTime);
drawForeground();
onDistanceRun(updateTime);
onBackgroundUpdate(updateTime);
checkNextObstacle(updateTime);
}
}
private void onNextObstacle() {
Obstacle o = mNextObstacle;
if (o != null && !mCurrentObstacles.contains(o)) {
mCurrentObstacles.add(o);
o.joinTheFight();
setNextObstacleRandomly();
if (mNextObstacle != null) {
double minTime = mNextObstacle.getMinSpawnTime(o, mDifficulty, mDifficultyMinTimeExtra);
mNextObstacleCounter = (long) (minTime + mRand.nextDouble() * (NEXT_OBSTACLE_MAX_TIME[mDifficulty] - minTime));
}
} else {
setNextObstacleRandomly();
}
}
private void setNextObstacleRandomly() {
List<Obstacle> obstacles = mObstacles.get(mDifficulty);
Collections.shuffle(obstacles, mRand);
for (int i = 0; i < obstacles.size(); i++) {
Obstacle obstacle = obstacles.get(i);
if (!obstacle.isActive()) {
mNextObstacle = obstacle;
break;
}
}
}
private void checkNextObstacle(long updateTime) {
mNextObstacleCounter -= updateTime;
if (mNextObstacleCounter <= 0L) {
onNextObstacle();
}
}
@Override
public boolean onMotionEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mStateMotionIsDown = true;
if (mCollisionBreak) {
onReleaseCollision();
} else {
mFlagDoSuperJump = !mRunner.nextJump();
if (!mFlagDoSuperJump) {
mConfig.mAchievementTypeData.increment(AchievementJumper.KEY_TYPE_JUMP_COUNT, 1L, 0L);
}
}
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
mStateMotionIsDown = false;
}
return false;
}
@NonNull
@Override
protected String compactCurrentState() {
Compacter cmp = new Compacter(5);
cmp.appendData(mBackgroundSlideCounter)
.appendData(mObstaclesPassed);
if (mCollisionBreak) {
cmp.appendData(0.f); // the value is still set but not valid anymore during a break
} else {
cmp.appendData(mDistanceRun * DISTANCE_RUN_PENALTY_ON_SAVE_FRACTION);
}
Compacter clearMindX = new Compacter(mClearMindX.size());
Compacter clearMindY = new Compacter(mClearMindY.size());
Compacter clearType = new Compacter(mClearMindType.size());
for (Float xData : mClearMindX) {
clearMindX.appendData(xData);
}
for (Float yData : mClearMindY) {
clearMindY.appendData(yData);
}
for (Integer type : mClearMindType) {
clearType.appendData(type);
}
cmp.appendData(clearMindX.compact());
cmp.appendData(clearMindY.compact());
cmp.appendData(clearType.compact());
return cmp.compact();
}
@Override
public void onReachedEndOfWorld(Actor columbus, float x, float y, int borderFlags) {
}
@Override
public void onLeftWorld(Actor jesus, int borderFlags) {
if ((borderFlags & FlatRectWorld.BORDER_FLAG_LEFT) != 0) {
onObstaclePassed(jesus);
}
}
@Override
public void onCollision(Actor colliding1, Actor colliding2) {
if (!mCollisionBreak && (colliding1 == mRunner || colliding2 == mRunner)) {
mCollisionBreak = true;
for (Obstacle obstacle : mCurrentObstacles) {
obstacle.setActive(false);
}
mCurrentObstacles.clear();
mNextObstacle = null;
mRunner.clearJump(getTopForRelativeHeight(RELATIVE_HEIGHT_BASELINE));
mRunner.setStateFrames(Runner.STATE_COLLISION);
drawForeground();
mConfig.mAchievementGameData.increment(AchievementJumper.KEY_GAME_COLLISION_COUNT, 1L, 0L);
if (colliding1 == mBoss || colliding2 == mBoss) {
mConfig.mAchievementGameData.increment(AchievementJumper.KEY_GAME_COLLIDED_WITH_KNUFFBUFF, 1L, 0L);
}
}
}
@Override
public void onMoverStateChange(Actor actor) {
if (actor == mRunner && mRunner.isFalling() && (mStateMotionIsDown || mFlagDoSuperJump)) {
if (mRunner.nextSuperJump()) {
mConfig.mAchievementGameData.increment(AchievementJumper.KEY_GAME_DOUBLE_JUMP_COUNT, 1L, 0L);
}
mFlagDoSuperJump = false;
}
}
private static class Obstacle extends Actor {
private long[] mMinTimes;
private float mStartLeft;
private float mStartTop;
private HitboxNewtonMover mMover;
private float mSpeedX;
private float mSpeedY;
public Obstacle(HitboxRect hitbox, HitboxNewtonMover mover, Look look, long[] minTimes) {
super(hitbox, mover, look);
mMinTimes = minTimes;
mStartLeft = hitbox.getBoundingRect().left;
mStartTop = hitbox.getBoundingRect().top;
mMover = mover;
mSpeedX = mover.getSpeedX();
mSpeedY = mover.getSpeedY();
setActive(false);
}
public static Obstacle makeObstacle(Look look, float hitboxWidthFraction, float hitboxHeightFraction, float frameLeft, float frameTop, long[] minTimes, FlatRectWorld world, float speedX, float speedY) {
HitboxRect hitbox = HitboxRect.makeHitbox(look, hitboxWidthFraction, hitboxHeightFraction, frameLeft, frameTop);
HitboxNewtonMover mover = new HitboxNewtonMover();
mover.setSpeed(speedX, speedY);
Obstacle obstacle = new Obstacle(hitbox, mover, look, minTimes);
world.addActor(obstacle);
return obstacle;
}
public double getMinSpawnTime(Obstacle previousObstacle, int difficulty, long minTimeDelta) {
double minTime = NEXT_OBSTACLE_PREVIOUS_MIN_TIME_WEIGHT * previousObstacle.mMinTimes[difficulty]
+ (1 - NEXT_OBSTACLE_PREVIOUS_MIN_TIME_WEIGHT) * mMinTimes[difficulty];
minTime -= minTimeDelta;
return Math.max(mMinTimes[difficulty + 1], minTime);
}
public void joinTheFight() {
getHitbox().setLeft(mStartLeft);
getHitbox().setTop(mStartTop);
mMover.setSpeed(mSpeedX, mSpeedY);
setActive(true);
resetCurrentFrames();
}
}
private static class Runner extends Actor {
public static final int STATE_COLLISION = -1;
private HitboxJumpMover mJump;
private boolean mSuperJumping;
private float mFramesOffsetX;
private float mFramesOffsetY;
public Runner(HitboxRect hitbox, HitboxJumpMover mover, Look defaultLook) {
super(hitbox, mover, defaultLook);
mJump = mover;
setActive(true);
}
@Override
protected void onUpdateChangedMoverState() {
setStateFramesByMoverState();
}
public static Runner makeRunner(Look look, float hitboxWidthFraction, float hitboxHeightFraction, float frameLeft, float frameTop, FlatRectWorld world) {
HitboxRect hitbox = HitboxRect.makeHitbox(look, hitboxWidthFraction, hitboxHeightFraction, frameLeft, frameTop);
Runner runner = new Runner(hitbox, new HitboxJumpMover(), look);
world.addActor(runner);
runner.mFramesOffsetX = look.getOffsetX();
runner.mFramesOffsetY = look.getOffsetY();
return runner;
}
public boolean isFalling() {
return mJump.getState() == HitboxJumpMover.STATE_JUMP_FALLING;
}
@Override
public void putStateFrames(int state, Look look) {
look.setOffset(mFramesOffsetX, mFramesOffsetY);
super.putStateFrames(state, look);
}
private boolean nextJump() {
if (mJump.getState() == HitboxJumpMover.STATE_LANDED || mJump.getState() == HitboxJumpMover.STATE_NOT_MOVING) {
mSuperJumping = false;
float deltaHeight = JUMP_RELATIVE_HEIGHT_DELTA * mCurrentLook.getHeight();
mJump.initJump(deltaHeight, deltaHeight, JUMP_DURATION);
setStateFrames(mJump.getState());
return true;
}
return false;
}
private boolean nextSuperJump() {
if (!mSuperJumping) {
mSuperJumping = true;
mJump.initJump((DOUBLE_JUMP_RELATIVE_HEIGHT_DELTA - JUMP_RELATIVE_HEIGHT_DELTA) * mCurrentLook.getHeight(), DOUBLE_JUMP_RELATIVE_HEIGHT_DELTA * mCurrentLook.getHeight(),
DOUBLE_JUMP_REST_DURATION);
setStateFrames(HitboxJumpMover.STATE_JUMP_ASCENDING);
return true;
}
return false;
}
public void clearJump(float frameTop) {
mJump.stop();
getHitbox().setTop(frameTop - mFramesOffsetY);
}
}
@Override
public Bitmap makeSnapshot() {
int width = SNAPSHOT_DIMENSION.getWidthForDensity(mConfig.mScreenDensity);
int height = SNAPSHOT_DIMENSION.getHeightForDensity(mConfig.mScreenDensity);
Bitmap snapshot = Bitmap.createScaledBitmap(mRunnerBackground, width, height, false);
Canvas canvas = new Canvas(snapshot);
Bitmap overlay = Bitmap.createScaledBitmap(mSolutionBackground, width, height, false);
canvas.drawBitmap(overlay, 0, 0, null);
overlay = Bitmap.createScaledBitmap(mForeground, width, height, false);
canvas.drawBitmap(overlay, 0, 0, null);
return snapshot;
}
}