/* * Copyright (C) 2010 The Android Open Source Project * * 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 com.replica.replicaisland; import com.replica.replicaisland.CollisionParameters.HitType; import com.replica.replicaisland.GameObject.ActionType; import com.replica.replicaisland.GameObject.Team; import com.replica.replicaisland.GameObjectFactory.GameObjectType; /** * A general-purpose component that responds to dynamic collision notifications. This component * may be configured to produce common responses to hit (taking damage, being knocked back, etc), or * it can be derived for entirely different responses. This component must exist on an object for * that object to respond to dynamic collisions. */ public class HitReactionComponent extends GameComponent { private static final float ATTACK_PAUSE_DELAY = (1.0f / 60) * 4; private final static float DEFAULT_BOUNCE_MAGNITUDE = 200.0f; private final static float EVENT_SEND_DELAY = 5.0f; private boolean mPauseOnAttack; private float mPauseOnAttackTime; private boolean mBounceOnHit; private float mBounceMagnitude; private float mInvincibleAfterHitTime; private float mLastHitTime; private boolean mInvincible; private boolean mDieOnCollect; private boolean mDieOnAttack; private ChangeComponentsComponent mPossessionComponent; private InventoryComponent.UpdateRecord mInventoryUpdate; private LauncherComponent mLauncherComponent; private int mLauncherHitType; private float mInvincibleTime; private int mGameEventHitType; private int mGameEventOnHit; private int mGameEventIndexData; private float mLastGameEventTime; private boolean mForceInvincibility; private SoundSystem.Sound mTakeHitSound; private SoundSystem.Sound mDealHitSound; private int mDealHitSoundHitType; private int mTakeHitSoundHitType; private GameObjectFactory.GameObjectType mSpawnOnDealHitObjectType; private int mSpawnOnDealHitHitType; private boolean mAlignDealHitObjectToVictimX; private boolean mAlignDealHitObjectToVictimY; public HitReactionComponent() { super(); reset(); setPhase(ComponentPhases.PRE_DRAW.ordinal()); } @Override public void reset() { mPauseOnAttack = false; mPauseOnAttackTime = ATTACK_PAUSE_DELAY; mBounceOnHit = false; mBounceMagnitude = DEFAULT_BOUNCE_MAGNITUDE; mInvincibleAfterHitTime = 0.0f; mInvincible = false; mDieOnCollect = false; mDieOnAttack = false; mPossessionComponent = null; mInventoryUpdate = null; mLauncherComponent = null; mLauncherHitType = HitType.LAUNCH; mInvincibleTime = 0.0f; mGameEventOnHit = -1; mGameEventIndexData = 0; mLastGameEventTime = -1.0f; mGameEventHitType = CollisionParameters.HitType.INVALID; mForceInvincibility = false; mTakeHitSound = null; mDealHitSound = null; mSpawnOnDealHitObjectType = GameObjectType.INVALID; mSpawnOnDealHitHitType = CollisionParameters.HitType.INVALID; mDealHitSoundHitType = CollisionParameters.HitType.INVALID; mAlignDealHitObjectToVictimX = false; mAlignDealHitObjectToVictimY = false; } /** Called when this object attacks another object. */ public void hitVictim(GameObject parent, GameObject victim, int hitType, boolean hitAccepted) { if (hitAccepted) { if (mPauseOnAttack && hitType == CollisionParameters.HitType.HIT) { TimeSystem time = sSystemRegistry.timeSystem; time.freeze(mPauseOnAttackTime); } if (mDieOnAttack) { parent.life = 0; } if (hitType == mLauncherHitType && mLauncherComponent != null) { mLauncherComponent.prepareToLaunch(victim, parent); } if (mDealHitSound != null && (hitType == mDealHitSoundHitType || mDealHitSoundHitType == CollisionParameters.HitType.INVALID)) { SoundSystem sound = sSystemRegistry.soundSystem; if (sound != null) { sound.play(mDealHitSound, false, SoundSystem.PRIORITY_NORMAL); } } if (mSpawnOnDealHitObjectType != GameObjectType.INVALID && hitType == mSpawnOnDealHitHitType) { final float x = mAlignDealHitObjectToVictimX ? victim.getPosition().x : parent.getPosition().x; final float y = mAlignDealHitObjectToVictimY ? victim.getPosition().y : parent.getPosition().y; GameObjectFactory factory = sSystemRegistry.gameObjectFactory; GameObjectManager manager = sSystemRegistry.gameObjectManager; if (factory != null) { GameObject object = factory.spawn(mSpawnOnDealHitObjectType, x, y, parent.facingDirection.x < 0.0f); if (object != null && manager != null) { manager.add(object); } } } } } /** Called when this object is hit by another object. */ public boolean receivedHit(GameObject parent, GameObject attacker, int hitType) { final TimeSystem time = sSystemRegistry.timeSystem; final float gameTime = time.getGameTime(); if (mGameEventHitType == hitType && mGameEventHitType != CollisionParameters.HitType.INVALID ) { if (mLastGameEventTime < 0.0f || gameTime > mLastGameEventTime + EVENT_SEND_DELAY) { LevelSystem level = sSystemRegistry.levelSystem; level.sendGameEvent(mGameEventOnHit, mGameEventIndexData, true); } else { // special case. If we're waiting for a hit type to spawn an event and // another event has just happened, eat this hit so we don't miss // the chance to send the event. hitType = CollisionParameters.HitType.INVALID; } mLastGameEventTime = gameTime; } switch(hitType) { case CollisionParameters.HitType.INVALID: break; case CollisionParameters.HitType.HIT: // don't hit our friends, if we have friends. final boolean sameTeam = (parent.team == attacker.team && parent.team != Team.NONE); if (!mForceInvincibility && !mInvincible && parent.life > 0 && !sameTeam) { parent.life -= 1; if (mBounceOnHit && parent.life > 0) { VectorPool pool = sSystemRegistry.vectorPool; Vector2 newVelocity = pool.allocate(parent.getPosition()); newVelocity.subtract(attacker.getPosition()); newVelocity.set(0.5f * Utils.sign(newVelocity.x), 0.5f * Utils.sign(newVelocity.y)); newVelocity.multiply(mBounceMagnitude); parent.setVelocity(newVelocity); parent.getTargetVelocity().zero(); pool.release(newVelocity); } if (mInvincibleAfterHitTime > 0.0f) { mInvincible = true; mInvincibleTime = mInvincibleAfterHitTime; } } else { // Ignore this hit. hitType = CollisionParameters.HitType.INVALID; } break; case CollisionParameters.HitType.DEATH: // respect teams? parent.life = 0; break; case CollisionParameters.HitType.COLLECT: if (mInventoryUpdate != null && parent.life > 0) { InventoryComponent attackerInventory = attacker.findByClass(InventoryComponent.class); if (attackerInventory != null) { attackerInventory.applyUpdate(mInventoryUpdate); } } if (mDieOnCollect && parent.life > 0) { parent.life = 0; } break; case CollisionParameters.HitType.POSSESS: if (mPossessionComponent != null && parent.life > 0 && attacker.life > 0) { mPossessionComponent.activate(parent); } else { hitType = CollisionParameters.HitType.INVALID; } break; case CollisionParameters.HitType.LAUNCH: break; default: break; } if (hitType != CollisionParameters.HitType.INVALID) { if (mTakeHitSound != null && hitType == mTakeHitSoundHitType) { SoundSystem sound = sSystemRegistry.soundSystem; if (sound != null) { sound.play(mTakeHitSound, false, SoundSystem.PRIORITY_NORMAL); } } mLastHitTime = gameTime; parent.setCurrentAction(ActionType.HIT_REACT); parent.lastReceivedHitType = hitType; } return hitType != CollisionParameters.HitType.INVALID; } @Override public void update(float timeDelta, BaseObject parent) { GameObject parentObject = (GameObject)parent; TimeSystem time = sSystemRegistry.timeSystem; final float gameTime = time.getGameTime(); if (mInvincible && mInvincibleTime > 0) { if (time.getGameTime() > mLastHitTime + mInvincibleTime) { mInvincible = false; } } // This means that the lastReceivedHitType will persist for two frames, giving all systems // a chance to react. if (gameTime - mLastHitTime > timeDelta) { parentObject.lastReceivedHitType = CollisionParameters.HitType.INVALID; } } public void setPauseOnAttack(boolean pause) { mPauseOnAttack = pause; } public void setPauseOnAttackTime(float seconds) { mPauseOnAttackTime = seconds; } public void setBounceOnHit(boolean bounce) { mBounceOnHit = bounce; } public void setBounceMagnitude(float magnitude) { mBounceMagnitude = magnitude; } public void setInvincibleTime(float time) { mInvincibleAfterHitTime = time; } public void setDieWhenCollected(boolean die) { mDieOnCollect = true; } public void setDieOnAttack(boolean die) { mDieOnAttack = die; } public void setInvincible(boolean invincible) { mInvincible = invincible; } public void setPossessionComponent(ChangeComponentsComponent component) { mPossessionComponent = component; } public void setInventoryUpdate(InventoryComponent.UpdateRecord update) { mInventoryUpdate = update; } public void setLauncherComponent(LauncherComponent component, int launchHitType) { mLauncherComponent = component; mLauncherHitType = launchHitType; } public void setSpawnGameEventOnHit(int hitType, int gameFlowEventType, int indexData) { mGameEventHitType = hitType; mGameEventOnHit = gameFlowEventType; mGameEventIndexData = indexData; if (hitType == HitType.INVALID) { // The game event has been cleared, so reset the timer blocking a // subsequent event. mLastGameEventTime = -1.0f; } } public final void setForceInvincible(boolean force) { mForceInvincibility = force; } public final void setTakeHitSound(int hitType, SoundSystem.Sound sound) { mTakeHitSoundHitType = hitType; mTakeHitSound = sound; } public final void setDealHitSound(int hitType, SoundSystem.Sound sound) { mDealHitSound = sound; mDealHitSoundHitType = hitType; } public final void setSpawnOnDealHit(int hitType, GameObjectType objectType, boolean alignToVictimX, boolean alignToVicitmY) { mSpawnOnDealHitObjectType = objectType; mSpawnOnDealHitHitType = hitType; mAlignDealHitObjectToVictimX = alignToVictimX; mAlignDealHitObjectToVictimY = alignToVicitmY; } }