blob: 58485b4c6434342a8bca4da5617c26f558ed47bf [file] [log] [blame]
/*
* 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;
}
}