package com.arretadogames.pilot.entities;
import android.graphics.Color;
import android.opengl.GLES11;
import com.arretadogames.pilot.config.GameSettings;
import com.arretadogames.pilot.entities.effects.EffectDescriptor;
import com.arretadogames.pilot.entities.effects.EffectManager;
import com.arretadogames.pilot.game.Game;
import com.arretadogames.pilot.game.GameState;
import com.arretadogames.pilot.items.Item;
import com.arretadogames.pilot.physics.PhysicalWorld;
import com.arretadogames.pilot.render.AnimationSwitcher;
import com.arretadogames.pilot.render.PhysicsRect;
import com.arretadogames.pilot.render.opengl.GLCanvas;
import com.arretadogames.pilot.world.GameWorld;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.Filter;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.contacts.Contact;
import java.util.Collection;
import java.util.HashSet;
import javax.microedition.khronos.opengles.GL10;
public abstract class Player extends DashableEntity {
protected static final int GHOST_MODE_TRANSPARENCY_COLOR = Color.argb(80, 255, 255, 255);
private static float TIME_BEFORE_DIE = 3f; // This will actually depend on the animation
private float timeToDie;
private float maxJumpVelocity = 5;
private float maxRunVelocity = 3;
private int maxDoubleJumps = 0;
private float jumpAceleration = 3;
private float runAceleration = 5;
private PlayerNumber playerNumber;
private boolean hasFinished; /* Player has finished level */
protected boolean jumpActive;
protected boolean actActive;
private float timeFinished;
protected AnimationSwitcher sprite;
protected int contJump;
protected Fixture bodyFixture;
protected Fixture footFixture;
protected Collection<Body> bodiesContact;
private boolean ghostModeActive;
private boolean forceStop;
private float stunDuration;
private float paralysisDuration;
private boolean isEnabled;
protected int categoryBits;
protected int maskBits;
private Vec2 stopImpulse = new Vec2(-1f, 0);
private Item item;
private float initialRunAceleration;
protected boolean shouldLimitVelocity;
public Player(float x, float y, PlayerNumber playerNumber,
float timeWaitingForAct, float dashDuration) {
super(x, y, timeWaitingForAct, dashDuration);
this.playerNumber = playerNumber;
hasFinished = false;
jumpActive = false;
actActive = false;
timeToDie = 0;
bodiesContact = new HashSet<Body>();
ghostModeActive = false;
forceStop = false;
isEnabled = true;
shouldLimitVelocity = true;
}
public void setMaskAndCategoryBits() {
Filter filter = new Filter();
filter.categoryBits = categoryBits;
filter.maskBits = maskBits;
Fixture f = body.getFixtureList();
while (f != null) {
f.setFilterData(filter);
f = f.getNext();
}
}
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean isEnabled) {
this.isEnabled = isEnabled;
}
public void setGhostMode(boolean ghostModeActive) {
if (this.ghostModeActive != ghostModeActive) {
Filter filter = new Filter();
if (ghostModeActive) {
filter.categoryBits = CollisionFlag.GROUP_NON_COLLIDABLE.getValue() ;
filter.maskBits = CollisionFlag.GROUP_NON_COLLIDABLE.getValue() ;
body.setGravityScale(0);
} else {
filter.categoryBits = categoryBits;
filter.maskBits = maskBits;
body.setGravityScale(1);
}
bodyFixture.setFilterData(filter);
this.ghostModeActive = ghostModeActive;
}
}
public void stun(float stunDuration) {
if (!isStunned()) {
this.stunDuration = stunDuration;
EffectDescriptor descriptor = new EffectDescriptor();
descriptor.position = body.getPosition();
descriptor.repeat = true;
descriptor.type = "stun";
descriptor.pRect = new PhysicsRect(physRect.width() / 1.5f, physRect.width() / 3.5f);
descriptor.position.y += physRect.height() / 1.5f;
descriptor.duration = stunDuration;
EffectManager.getInstance().addEffect(descriptor);
}
}
public void paralyze(float duration) {
if (!isParalyzed()) {
this.paralysisDuration = duration;
EffectDescriptor descriptor = new EffectDescriptor();
descriptor.position = body.getPosition();
descriptor.repeat = true;
descriptor.type = "stun";
descriptor.pRect = new PhysicsRect(physRect.width(), physRect.width() / 2);
descriptor.position.y += physRect.height() / 2;
descriptor.duration = duration;
EffectManager.getInstance().addEffect(descriptor);
}
}
public boolean isStunned() {
return stunDuration > 0;
}
public boolean isParalyzed() {
return paralysisDuration > 0;
}
public boolean shouldStop() {
return isForceStopped() || isDead() || hasFinished() || isStunned();
}
public boolean shouldAct() {
return !isParalyzed();
}
public void setForceStop(boolean forceStop) {
this.forceStop = forceStop;
}
@Override
public final void step(float timeElapsed){
super.step(timeElapsed);
if (state == State.DYING) {
timeToDie -= timeElapsed;
if (timeToDie <= 0) {
state = State.DEAD;
PhysicalWorld.getInstance().addDeadEntity(this);
}
}
if (getItem() != null) {
getItem().step(timeElapsed);
}
if (stunDuration > 0) {
stunDuration -= timeElapsed;
}
if (paralysisDuration > 0) {
paralysisDuration -= timeElapsed;
}
if (isEnabled()) {
applyConstants();
if (shouldStop() || !shouldAct()) {
if (shouldStop()) {
stopAction();
}
return;
}
if (jumpActive) {
jump();
jumpActive = false;
}
if(actActive){
callAct();
}
if(contJump > 0) {
contJump--;
}
run();
playerStep(timeElapsed);
if (Math.abs(body.getLinearVelocity().x) > getMaxRunVelocity() && shouldLimitVelocity) {
body.getLinearVelocity().x = getMaxRunVelocity() *
(body.getLinearVelocity().x / Math.abs(body.getLinearVelocity().x));
}
}
}
public abstract void jump();
public abstract void run();
public abstract void applyConstants();
public PlayerNumber getNumber() {
return playerNumber;
}
public void setFinished(boolean hasFinished) {
if (!hasFinished() && hasFinished) // Was not finished before, now it is
timeFinished = ((GameWorld)Game.getInstance().getScreen(GameState.RUNNING_GAME)).getTotalElapsedTime();
this.hasFinished = hasFinished;
}
public boolean hasFinished() {
return hasFinished;
}
@Override
public EntityType getType() {
return EntityType.PLAYER;
}
public void setJumping(boolean isJumping) {
this.jumpActive = isJumping;
}
public void setAct(boolean isAct) {
this.actActive = isAct;
}
public boolean isGhostMode() {
return ghostModeActive;
}
public boolean isForceStopped() {
return forceStop;
}
@Override
public void kill() {
if (isAlive()) {
state = State.DYING;
timeToDie = TIME_BEFORE_DIE;
}
}
public float getTimeFinished() {
if (hasFinished())
return timeFinished;
return 0;
}
protected void stopAction() {
if (body.getLinearVelocity().x != 0) {
if (body.getLinearVelocity().x > 0) {
body.applyLinearImpulse(stopImpulse.mul(body.getMass()/ 10f), body.getPosition(), true);
} else {
body.setLinearVelocity(new Vec2(0, 0)); // Just done once
}
}
}
public float getMaxJumpVelocity() {
return maxJumpVelocity;
}
public void setMaxJumpVelocity(float maxJumpVelocity) {
this.maxJumpVelocity = maxJumpVelocity;
}
public float getMaxRunVelocity() {
return maxRunVelocity;
}
public void setMaxRunVelocity(float maxRunVelocity) {
this.maxRunVelocity = maxRunVelocity;
}
public float getJumpAceleration() {
return jumpAceleration;
}
public void setJumpAceleration(float jumpAceleration) {
this.jumpAceleration = jumpAceleration;
}
public float getRunAceleration() {
return body.getLinearVelocity().x < GameSettings.INITIAL_RUN_THRESHOLD ?
initialRunAceleration: runAceleration;
}
public void setRunAceleration(float runAceleration, float initialRunAceleration) {
this.runAceleration = runAceleration;
this.initialRunAceleration = initialRunAceleration;
}
public abstract int getStatusImg();
public int getMaxDoubleJumps() {
return maxDoubleJumps;
}
public void setMaxDoubleJumps(int maxDoubleJumps) {
this.maxDoubleJumps = maxDoubleJumps;
}
public void beginContact(Entity e, Contact contact) {
if( (contact.m_fixtureA.equals(footFixture)
&& (!contact.m_fixtureB.isSensor() || e.getType() == EntityType.FLUID)) ||
(contact.m_fixtureB.equals(footFixture)
&&(!contact.m_fixtureA.isSensor() || e.getType() == EntityType.FLUID)) ){
bodiesContact.add(e.body);
sprite.setAnimationState("default");
}
}
public void endContact(Entity e , Contact contact) {
if(contact.m_fixtureA.equals(footFixture) || contact.m_fixtureB.equals(footFixture)){
if(bodiesContact.contains(e.body)){
bodiesContact.remove(e.body);
if(bodiesContact.size()==0){
sprite.setAnimationState("jump");
}
}
}
}
public void setSprite(AnimationSwitcher sprite){
this.sprite = sprite;
}
protected double getAngle(){
double angle = 0;
if(body.getLinearVelocity().length() > 1){
double cos = Vec2.dot(body.getLinearVelocity(), new Vec2(1,0)) / (body.getLinearVelocity().length());
cos = Math.abs(cos);
angle = Math.acos(cos);
if( body.getLinearVelocity().y < 0 ) angle = angle * -1;
angle = Math.min(Math.PI/6,angle);
angle = Math.max(-Math.PI/6,angle);
}
return angle;
}
protected void applyReturn(Vec2 impulse){
int quant = bodiesContact.size() * 3;
for(Body b : bodiesContact){
b.applyLinearImpulse(impulse.mul(-1/quant), b.getWorldCenter(), true);
b.applyLinearImpulse(impulse.mul(-1/quant), body.getWorldPoint(new Vec2(-0.5f,-0.6f)), true);
b.applyLinearImpulse(impulse.mul(-1/quant), body.getWorldPoint(new Vec2(0.5f,-0.6f)), true);
}
}
public void setItem(Item item) {
if (item != null && this.item != null) {
return;
}
this.item = item;
}
public Item getItem() {
return this.item;
}
@Override
public final void render(GLCanvas canvas, float timeElapsed) {
sprite.setAnimationRateMultiplier(body.getLinearVelocity().x / 2f);
if (isGhostMode()) {
GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
canvas.setColor(GHOST_MODE_TRANSPARENCY_COLOR);
}
playerRender(canvas, timeElapsed);
if (isGhostMode()) {
canvas.setColor(Color.WHITE);
GLES11.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
}
}
protected abstract void playerRender(GLCanvas canvas, float timeElapsed);
protected abstract void playerStep(float timeElapsed);
}