1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.replica.replicaisland; 18 19import com.replica.replicaisland.CollisionParameters.HitType; 20import com.replica.replicaisland.GameObject.ActionType; 21import com.replica.replicaisland.GameObject.Team; 22import com.replica.replicaisland.GameObjectFactory.GameObjectType; 23 24/** 25 * A general-purpose component that responds to dynamic collision notifications. This component 26 * may be configured to produce common responses to hit (taking damage, being knocked back, etc), or 27 * it can be derived for entirely different responses. This component must exist on an object for 28 * that object to respond to dynamic collisions. 29 */ 30public class HitReactionComponent extends GameComponent { 31 private static final float ATTACK_PAUSE_DELAY = (1.0f / 60) * 4; 32 private final static float DEFAULT_BOUNCE_MAGNITUDE = 200.0f; 33 private final static float EVENT_SEND_DELAY = 5.0f; 34 35 private boolean mPauseOnAttack; 36 private float mPauseOnAttackTime; 37 private boolean mBounceOnHit; 38 private float mBounceMagnitude; 39 private float mInvincibleAfterHitTime; 40 private float mLastHitTime; 41 private boolean mInvincible; 42 private boolean mDieOnCollect; 43 private boolean mDieOnAttack; 44 private ChangeComponentsComponent mPossessionComponent; 45 private InventoryComponent.UpdateRecord mInventoryUpdate; 46 private LauncherComponent mLauncherComponent; 47 private int mLauncherHitType; 48 private float mInvincibleTime; 49 private int mGameEventHitType; 50 private int mGameEventOnHit; 51 private int mGameEventIndexData; 52 private float mLastGameEventTime; 53 private boolean mForceInvincibility; 54 private SoundSystem.Sound mTakeHitSound; 55 private SoundSystem.Sound mDealHitSound; 56 private int mDealHitSoundHitType; 57 private int mTakeHitSoundHitType; 58 59 private GameObjectFactory.GameObjectType mSpawnOnDealHitObjectType; 60 private int mSpawnOnDealHitHitType; 61 private boolean mAlignDealHitObjectToVictimX; 62 private boolean mAlignDealHitObjectToVictimY; 63 64 65 public HitReactionComponent() { 66 super(); 67 reset(); 68 setPhase(ComponentPhases.PRE_DRAW.ordinal()); 69 } 70 71 @Override 72 public void reset() { 73 mPauseOnAttack = false; 74 mPauseOnAttackTime = ATTACK_PAUSE_DELAY; 75 mBounceOnHit = false; 76 mBounceMagnitude = DEFAULT_BOUNCE_MAGNITUDE; 77 mInvincibleAfterHitTime = 0.0f; 78 mInvincible = false; 79 mDieOnCollect = false; 80 mDieOnAttack = false; 81 mPossessionComponent = null; 82 mInventoryUpdate = null; 83 mLauncherComponent = null; 84 mLauncherHitType = HitType.LAUNCH; 85 mInvincibleTime = 0.0f; 86 mGameEventOnHit = -1; 87 mGameEventIndexData = 0; 88 mLastGameEventTime = -1.0f; 89 mGameEventHitType = CollisionParameters.HitType.INVALID; 90 mForceInvincibility = false; 91 mTakeHitSound = null; 92 mDealHitSound = null; 93 mSpawnOnDealHitObjectType = GameObjectType.INVALID; 94 mSpawnOnDealHitHitType = CollisionParameters.HitType.INVALID; 95 mDealHitSoundHitType = CollisionParameters.HitType.INVALID; 96 mAlignDealHitObjectToVictimX = false; 97 mAlignDealHitObjectToVictimY = false; 98 } 99 100 /** Called when this object attacks another object. */ 101 public void hitVictim(GameObject parent, GameObject victim, int hitType, 102 boolean hitAccepted) { 103 if (hitAccepted) { 104 if (mPauseOnAttack && hitType == CollisionParameters.HitType.HIT) { 105 TimeSystem time = sSystemRegistry.timeSystem; 106 time.freeze(mPauseOnAttackTime); 107 } 108 109 if (mDieOnAttack) { 110 parent.life = 0; 111 } 112 113 if (hitType == mLauncherHitType && mLauncherComponent != null) { 114 mLauncherComponent.prepareToLaunch(victim, parent); 115 } 116 117 if (mDealHitSound != null && 118 (hitType == mDealHitSoundHitType || 119 mDealHitSoundHitType == CollisionParameters.HitType.INVALID)) { 120 SoundSystem sound = sSystemRegistry.soundSystem; 121 if (sound != null) { 122 sound.play(mDealHitSound, false, SoundSystem.PRIORITY_NORMAL); 123 } 124 } 125 126 if (mSpawnOnDealHitObjectType != GameObjectType.INVALID && 127 hitType == mSpawnOnDealHitHitType) { 128 final float x = mAlignDealHitObjectToVictimX ? 129 victim.getPosition().x : parent.getPosition().x; 130 final float y = mAlignDealHitObjectToVictimY ? 131 victim.getPosition().y : parent.getPosition().y; 132 133 GameObjectFactory factory = sSystemRegistry.gameObjectFactory; 134 GameObjectManager manager = sSystemRegistry.gameObjectManager; 135 136 if (factory != null) { 137 GameObject object = factory.spawn(mSpawnOnDealHitObjectType, x, 138 y, parent.facingDirection.x < 0.0f); 139 140 if (object != null && manager != null) { 141 manager.add(object); 142 } 143 } 144 } 145 } 146 } 147 148 /** Called when this object is hit by another object. */ 149 public boolean receivedHit(GameObject parent, GameObject attacker, int hitType) { 150 final TimeSystem time = sSystemRegistry.timeSystem; 151 final float gameTime = time.getGameTime(); 152 153 if (mGameEventHitType == hitType && 154 mGameEventHitType != CollisionParameters.HitType.INVALID ) { 155 if (mLastGameEventTime < 0.0f || gameTime > mLastGameEventTime + EVENT_SEND_DELAY) { 156 LevelSystem level = sSystemRegistry.levelSystem; 157 level.sendGameEvent(mGameEventOnHit, mGameEventIndexData, true); 158 } else { 159 // special case. If we're waiting for a hit type to spawn an event and 160 // another event has just happened, eat this hit so we don't miss 161 // the chance to send the event. 162 hitType = CollisionParameters.HitType.INVALID; 163 } 164 mLastGameEventTime = gameTime; 165 } 166 167 switch(hitType) { 168 case CollisionParameters.HitType.INVALID: 169 break; 170 171 case CollisionParameters.HitType.HIT: 172 // don't hit our friends, if we have friends. 173 final boolean sameTeam = (parent.team == attacker.team && parent.team != Team.NONE); 174 if (!mForceInvincibility && !mInvincible && parent.life > 0 && !sameTeam) { 175 parent.life -= 1; 176 177 if (mBounceOnHit && parent.life > 0) { 178 VectorPool pool = sSystemRegistry.vectorPool; 179 Vector2 newVelocity = pool.allocate(parent.getPosition()); 180 newVelocity.subtract(attacker.getPosition()); 181 newVelocity.set(0.5f * Utils.sign(newVelocity.x), 182 0.5f * Utils.sign(newVelocity.y)); 183 newVelocity.multiply(mBounceMagnitude); 184 parent.setVelocity(newVelocity); 185 parent.getTargetVelocity().zero(); 186 pool.release(newVelocity); 187 } 188 189 if (mInvincibleAfterHitTime > 0.0f) { 190 mInvincible = true; 191 mInvincibleTime = mInvincibleAfterHitTime; 192 } 193 194 } else { 195 // Ignore this hit. 196 hitType = CollisionParameters.HitType.INVALID; 197 } 198 break; 199 case CollisionParameters.HitType.DEATH: 200 // respect teams? 201 parent.life = 0; 202 break; 203 case CollisionParameters.HitType.COLLECT: 204 if (mInventoryUpdate != null && parent.life > 0) { 205 InventoryComponent attackerInventory = attacker.findByClass(InventoryComponent.class); 206 if (attackerInventory != null) { 207 attackerInventory.applyUpdate(mInventoryUpdate); 208 } 209 } 210 if (mDieOnCollect && parent.life > 0) { 211 parent.life = 0; 212 } 213 break; 214 case CollisionParameters.HitType.POSSESS: 215 if (mPossessionComponent != null && parent.life > 0 && attacker.life > 0) { 216 mPossessionComponent.activate(parent); 217 } else { 218 hitType = CollisionParameters.HitType.INVALID; 219 } 220 break; 221 case CollisionParameters.HitType.LAUNCH: 222 break; 223 224 default: 225 break; 226 } 227 228 229 if (hitType != CollisionParameters.HitType.INVALID) { 230 if (mTakeHitSound != null && hitType == mTakeHitSoundHitType) { 231 SoundSystem sound = sSystemRegistry.soundSystem; 232 if (sound != null) { 233 sound.play(mTakeHitSound, false, SoundSystem.PRIORITY_NORMAL); 234 } 235 } 236 mLastHitTime = gameTime; 237 parent.setCurrentAction(ActionType.HIT_REACT); 238 parent.lastReceivedHitType = hitType; 239 240 } 241 242 return hitType != CollisionParameters.HitType.INVALID; 243 } 244 245 @Override 246 public void update(float timeDelta, BaseObject parent) { 247 GameObject parentObject = (GameObject)parent; 248 TimeSystem time = sSystemRegistry.timeSystem; 249 250 final float gameTime = time.getGameTime(); 251 252 if (mInvincible && mInvincibleTime > 0) { 253 if (time.getGameTime() > mLastHitTime + mInvincibleTime) { 254 mInvincible = false; 255 } 256 } 257 258 // This means that the lastReceivedHitType will persist for two frames, giving all systems 259 // a chance to react. 260 if (gameTime - mLastHitTime > timeDelta) { 261 parentObject.lastReceivedHitType = CollisionParameters.HitType.INVALID; 262 } 263 } 264 265 public void setPauseOnAttack(boolean pause) { 266 mPauseOnAttack = pause; 267 } 268 269 public void setPauseOnAttackTime(float seconds) { 270 mPauseOnAttackTime = seconds; 271 } 272 273 public void setBounceOnHit(boolean bounce) { 274 mBounceOnHit = bounce; 275 } 276 277 public void setBounceMagnitude(float magnitude) { 278 mBounceMagnitude = magnitude; 279 } 280 281 public void setInvincibleTime(float time) { 282 mInvincibleAfterHitTime = time; 283 } 284 285 public void setDieWhenCollected(boolean die) { 286 mDieOnCollect = true; 287 } 288 289 public void setDieOnAttack(boolean die) { 290 mDieOnAttack = die; 291 } 292 293 public void setInvincible(boolean invincible) { 294 mInvincible = invincible; 295 } 296 297 public void setPossessionComponent(ChangeComponentsComponent component) { 298 mPossessionComponent = component; 299 } 300 301 public void setInventoryUpdate(InventoryComponent.UpdateRecord update) { 302 mInventoryUpdate = update; 303 } 304 305 public void setLauncherComponent(LauncherComponent component, int launchHitType) { 306 mLauncherComponent = component; 307 mLauncherHitType = launchHitType; 308 } 309 310 public void setSpawnGameEventOnHit(int hitType, int gameFlowEventType, int indexData) { 311 mGameEventHitType = hitType; 312 mGameEventOnHit = gameFlowEventType; 313 mGameEventIndexData = indexData; 314 if (hitType == HitType.INVALID) { 315 // The game event has been cleared, so reset the timer blocking a 316 // subsequent event. 317 mLastGameEventTime = -1.0f; 318 } 319 } 320 321 public final void setForceInvincible(boolean force) { 322 mForceInvincibility = force; 323 } 324 325 public final void setTakeHitSound(int hitType, SoundSystem.Sound sound) { 326 mTakeHitSoundHitType = hitType; 327 mTakeHitSound = sound; 328 } 329 330 public final void setDealHitSound(int hitType, SoundSystem.Sound sound) { 331 mDealHitSound = sound; 332 mDealHitSoundHitType = hitType; 333 } 334 335 public final void setSpawnOnDealHit(int hitType, GameObjectType objectType, boolean alignToVictimX, 336 boolean alignToVicitmY) { 337 mSpawnOnDealHitObjectType = objectType; 338 mSpawnOnDealHitHitType = hitType; 339 mAlignDealHitObjectToVictimX = alignToVictimX; 340 mAlignDealHitObjectToVictimY = alignToVicitmY; 341 } 342 343} 344