DEADSOFTWARE

5d18755c590feb3ebbf9df3071cc96a32da8fa66
[cavedroid.git] / core / src / ru / deadsoftware / cavedroid / game / mobs / Mob.java
1 package ru.deadsoftware.cavedroid.game.mobs;
2
3 import com.badlogic.gdx.Gdx;
4 import com.badlogic.gdx.graphics.Color;
5 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
6 import com.badlogic.gdx.math.MathUtils;
7 import com.badlogic.gdx.math.Rectangle;
8 import com.badlogic.gdx.math.Vector2;
9 import com.badlogic.gdx.utils.Timer;
10 import ru.deadsoftware.cavedroid.game.GameItemsHolder;
11 import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto;
12 import ru.deadsoftware.cavedroid.game.model.item.InventoryItem;
13 import ru.deadsoftware.cavedroid.game.world.GameWorld;
14 import ru.deadsoftware.cavedroid.misc.Saveable;
15
16 import javax.annotation.CheckForNull;
17 import java.util.Collections;
18 import java.util.List;
19
20 /**
21 * Mob class.
22 */
23 public abstract class Mob extends Rectangle implements Saveable {
24
25 private static final float DAMAGE_TINT_TIMEOUT_S = 0.5f;
26 private static final Color DAMAGE_TINT_COLOR = new Color(0xff8080ff);
27
28 private static final float HIT_RANGE = 8f;
29
30 protected static int ANIMATION_SPEED = 360;
31
32 public enum Type {
33 MOB,
34 FALLING_BLOCK
35 }
36
37 public enum Direction {
38
39 LEFT(0, -1),
40 RIGHT(1, 1);
41
42 private final int index;
43 private final int basis;
44
45 /**
46 * Index for this direction (left = 0, right = 1)
47 */
48 public final int getIndex() {
49 return index;
50 }
51
52 /**
53 * Basis for this direction (left = -1, right = 1)
54 */
55 public final int getBasis() {
56 return basis;
57 }
58
59 Direction(int index, int basis) {
60 this.index = index;
61 this.basis = basis;
62 }
63 }
64
65 private class ResetTakeDamageTask extends Timer.Task {
66
67 @Override
68 public void run() {
69 mTakingDamage = false;
70 }
71 }
72
73 protected Vector2 mVelocity;
74 protected Type mType;
75 protected int mAnimDelta = ANIMATION_SPEED;
76 protected float mAnim;
77
78 protected Direction mDirection;
79 protected boolean mDead;
80 protected boolean mCanJump;
81 protected boolean mFlyMode;
82
83 protected int mMaxHealth;
84 protected int mHealth;
85
86 private boolean mTakingDamage = false;
87 @CheckForNull private ResetTakeDamageTask mResetTakeDamageTask = null;
88
89 /**
90 * @param x in pixels
91 * @param y in pixels
92 * @param width in pixels
93 * @param height in pixels
94 * @param mDirection Direction in which mob is looking
95 */
96 protected Mob(float x, float y, float width, float height, Direction mDirection, Type type, int maxHealth) {
97 super(x, y, width, height);
98 mVelocity = new Vector2(0, 0);
99 mCanJump = false;
100 mDead = false;
101 this.mDirection = mDirection;
102 this.mType = type;
103 this.mMaxHealth = maxHealth;
104 this.mHealth = mMaxHealth;
105 }
106
107 protected static Direction randomDir() {
108 return MathUtils.randomBoolean(.5f) ? Direction.LEFT : Direction.RIGHT;
109 }
110
111 private boolean isAnimationIncreasing() {
112 return mAnim > 0 && mAnimDelta > 0 || mAnim < 0 && mAnimDelta < 0;
113 }
114
115 private void checkHealth() {
116 mHealth = MathUtils.clamp(mHealth, 0, mMaxHealth);
117
118 if (mHealth <= 0) {
119 kill();
120 }
121 }
122
123 protected final void updateAnimation(float delta) {
124 final float velocityMultiplier = (Math.abs(getVelocity().x) / getSpeed());
125 final float animMultiplier = (velocityMultiplier == 0f ? 1f : velocityMultiplier) * delta;
126 final float maxAnim = 60f * (velocityMultiplier == 0f ? 1f : velocityMultiplier);
127
128 if (mVelocity.x != 0f || Math.abs(mAnim) > mAnimDelta * animMultiplier) {
129 mAnim += mAnimDelta * animMultiplier;
130 } else {
131 mAnim = 0;
132 }
133
134 if (mAnim > maxAnim) {
135 mAnim = maxAnim;
136 mAnimDelta = -ANIMATION_SPEED;
137 } else if (mAnim < -maxAnim) {
138 mAnim = -maxAnim;
139 mAnimDelta = ANIMATION_SPEED;
140 }
141
142 if (mVelocity.x == 0f && isAnimationIncreasing()) {
143 mAnimDelta = -mAnimDelta;
144 }
145 }
146
147 /**
148 * @return The X coordinate of a mob in blocks
149 */
150 public final int getMapX() {
151 return (int) (x + (getWidth() / 2)) / 16;
152 }
153
154 /**
155 * @return The Y coordinate of mob's upper edge in blocks
156 */
157 public final int getUpperMapY() {
158 return (int) (y / 16);
159 }
160
161 /**
162 * @return The Y coordinate if mob's vertical center in blocks
163 */
164 public final int getMiddleMapY() {
165 return (int) (y + (getHeight() / 2)) / 16;
166 }
167
168 /**
169 * @return The Y coordinate of mob's legs in blocks
170 */
171 public final int getLowerMapY() {
172 return (int) (y + getHeight()) / 16;
173 }
174
175 public final float getWidth() {
176 return width;
177 }
178
179 public final float getHeight() {
180 return height;
181 }
182
183 /**
184 * @return Integer representing a direction in which mob is looking, where 0 is left and 1 is right
185 */
186 public final Direction getDirection() {
187 return mDirection;
188 }
189
190 public final boolean looksLeft() {
191 return mDirection == Direction.LEFT;
192 }
193
194 public final boolean looksRight() {
195 return mDirection == Direction.RIGHT;
196 }
197
198 /**
199 * Switches direction in which mob is looking
200 */
201 protected final void switchDir() {
202 mDirection = looksLeft() ? Direction.RIGHT : Direction.LEFT;
203 }
204
205 public final boolean isDead() {
206 return mDead;
207 }
208
209 public final float getAnim() {
210 return mAnim;
211 }
212
213 /**
214 * Set's mob's dead variable to true and nothing else. It doesn't delete the
215 */
216 public void kill() {
217 mDead = true;
218 }
219
220 public final void move(float delta) {
221 x += mVelocity.x * delta;
222 y += mVelocity.y * delta;
223 }
224
225 public final Vector2 getVelocity() {
226 return mVelocity;
227 }
228
229 protected final void setVelocity(Vector2 velocity) {
230 mVelocity = velocity;
231 }
232
233 public final boolean canJump() {
234 return mCanJump;
235 }
236
237 public final void setCanJump(boolean canJump) {
238 this.mCanJump = canJump;
239 }
240
241 public final boolean isFlyMode() {
242 return mFlyMode;
243 }
244
245 public final void setFlyMode(boolean flyMode) {
246 this.mFlyMode = flyMode;
247 }
248
249 public final Type getType() {
250 return mType;
251 }
252
253 public final void checkWorldBounds(GameWorld gameWorld) {
254 if (x + width / 2 < 0) {
255 x += gameWorld.getWidthPx();
256 }
257 if (x + width / 2 > gameWorld.getWidthPx()) {
258 x -= gameWorld.getWidthPx();
259 }
260 }
261
262 public final int getHealth() {
263 return mHealth;
264 }
265
266 public final int getMaxHealth() {
267 return mMaxHealth;
268 }
269
270 public final void attachToController(MobsController controller) {
271 controller.addMob(this);
272 }
273
274 public void damage(int damage) {
275 if (damage == 0) {
276 return;
277 }
278
279 if (damage < 0) {
280 Gdx.app.error(this.getClass().getSimpleName(), "Damage cant be negative!");
281 return;
282 }
283
284 if (mHealth <= Integer.MIN_VALUE + damage) {
285 mHealth = Integer.MIN_VALUE + damage;
286 }
287
288 mHealth -= damage;
289 checkHealth();
290
291 setTakingDamage(true);
292 }
293
294 public void heal(int heal) {
295 if (heal < 0) {
296 Gdx.app.error(this.getClass().getSimpleName(), "Heal cant be negative!");
297 return;
298 }
299
300 if (mHealth >= Integer.MAX_VALUE - heal) {
301 mHealth = Integer.MAX_VALUE - heal;
302 }
303
304 mHealth += heal;
305 checkHealth();
306 }
307
308 public Rectangle getHitBox() {
309 return new Rectangle(x - HIT_RANGE, y - HIT_RANGE, width + HIT_RANGE, height + HIT_RANGE);
310 }
311
312 public boolean isTakingDamage() {
313 return mTakingDamage;
314 }
315
316 public void setTakingDamage(boolean takingDamage) {
317 mTakingDamage = takingDamage;
318
319 if (takingDamage) {
320 if (mResetTakeDamageTask != null && mResetTakeDamageTask.isScheduled()) {
321 mResetTakeDamageTask.cancel();
322 } else if (mResetTakeDamageTask == null) {
323 mResetTakeDamageTask = new ResetTakeDamageTask();
324 }
325
326 Timer.schedule(mResetTakeDamageTask, DAMAGE_TINT_TIMEOUT_S);
327 }
328 }
329
330 protected Color getTintColor() {
331 return isTakingDamage() ? DAMAGE_TINT_COLOR : Color.WHITE;
332 }
333
334 public List<InventoryItem> getDrop(GameItemsHolder gameItemsHolder) {
335 return Collections.emptyList();
336 }
337
338 public abstract void draw(SpriteBatch spriteBatch, float x, float y, float delta);
339
340 public abstract void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, MobsController mobsController, float delta);
341
342 public abstract void changeDir();
343
344 public abstract float getSpeed();
345
346 public abstract void jump();
347
348 @Override
349 public abstract SaveDataDto.MobSaveDataDto getSaveData();
350
351 public static Mob fromSaveData(SaveDataDto.MobSaveDataDto saveData) {
352 return MobSaveDataMapperKt.fromSaveData(saveData);
353 }
354 }