/*
* Aphelion
* Copyright (c) 2013 Joris van der Wel
*
* This file is part of Aphelion
*
* Aphelion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* Aphelion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Aphelion. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, the following supplemental terms apply, based on section 7 of
* the GNU Affero General Public License (version 3):
* a) Preservation of all legal notices and author attributions
* b) Prohibition of misrepresentation of the origin of this material, and
* modified versions are required to be marked in reasonable ways as
* different from the original version (for example by appending a copyright notice).
*
* Linking this library statically or dynamically with other modules is making a
* combined work based on this library. Thus, the terms and conditions of the
* GNU Affero General Public License cover the whole combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules,
* and to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent module,
* the terms and conditions of the license of that module. An independent
* module is a module which is not derived from or based on this library.
*/
package aphelion.client.graphics.world.event;
import aphelion.client.RENDER_LAYER;
import aphelion.client.graphics.world.ActorShip;
import aphelion.client.graphics.world.GCImageAnimation;
import aphelion.client.graphics.world.MapEntities;
import aphelion.client.graphics.world.Projectile;
import aphelion.shared.gameconfig.GCImage;
import aphelion.shared.physics.EnvironmentConf;
import aphelion.shared.physics.PhysicsEnvironment;
import aphelion.shared.physics.PhysicsMap;
import aphelion.shared.physics.entities.ProjectilePublic;
import aphelion.shared.physics.events.pub.ProjectileExplosionPublic;
import aphelion.shared.physics.events.pub.ProjectileExplosionPublic.EXPLODE_REASON;
import static aphelion.shared.physics.events.pub.ProjectileExplosionPublic.EXPLODE_REASON.EXPIRATION;
import static aphelion.shared.physics.events.pub.ProjectileExplosionPublic.EXPLODE_REASON.HIT_SHIP;
import static aphelion.shared.physics.events.pub.ProjectileExplosionPublic.EXPLODE_REASON.HIT_TILE;
import static aphelion.shared.physics.events.pub.ProjectileExplosionPublic.EXPLODE_REASON.PROX_DELAY;
import static aphelion.shared.physics.events.pub.ProjectileExplosionPublic.EXPLODE_REASON.PROX_DIST;
import aphelion.shared.physics.valueobjects.PhysicsPoint;
import aphelion.shared.physics.valueobjects.PhysicsPositionVector;
import aphelion.shared.resource.ResourceDB;
import java.util.ArrayList;
import javax.annotation.Nonnull;
/**
*
* @author Joris
*/
public class ProjectileExplosionTracker implements EventTracker
{
private final ResourceDB resourceDB;
private final PhysicsEnvironment physicsEnv;
private final MapEntities mapEntities;
private boolean firstRun = true;
private ProjectileExplosionPublic event;
private long renderDelay;
private ArrayList<GCImageAnimation> projectileAnimations;
private final PhysicsPoint lastEventPosition = new PhysicsPoint();
/** Used to track old animations incase a new animation is spawned somewhere else. */
private int spawnID = 0;
private ActorShip hitActor;
// Todo: setting?:
private static final long TIMEWARP_RESPAWN_ANIM_DISTSQ = 5 * PhysicsMap.TILE_PIXELS;
private static final long TIMEWARP_EXTRA_ANIM_DISTSQ = 5 * PhysicsMap.TILE_PIXELS;
private static final float TIMEWARP_ALPHA_VELOCITY = 0.025f;
public ProjectileExplosionTracker(@Nonnull ResourceDB resourceDB, @Nonnull PhysicsEnvironment physicsEnv, @Nonnull MapEntities mapEntities)
{
this.resourceDB = resourceDB;
this.physicsEnv = physicsEnv;
this.mapEntities = mapEntities;
}
public void update(@Nonnull ProjectileExplosionPublic event)
{
if (this.event == null)
{
this.event = event;
}
assert this.event == event;
// might be null
ProjectilePublic physicsProjectile_state0 = event.getProjectile(0);
if (firstRun)
{
if (physicsProjectile_state0 == null)
{
return;
}
Projectile projectile = mapEntities.physicsProjectileToGraphics(physicsProjectile_state0);
// do not update the render delay after it has been set
renderDelay = projectile.renderDelay_current;
if (renderDelay >= physicsEnv.getConfig().HIGHEST_DELAY)
{
return; // too old
}
}
hitActor = mapEntities.getActorShip(event.getHitActor(0));
firstRun = false;
if (projectileAnimations != null && lastEventPosition.set)
{
PhysicsPoint pos = new PhysicsPoint();
event.getPosition(0, pos);
if (lastEventPosition.distanceSquared(pos) >= TIMEWARP_RESPAWN_ANIM_DISTSQ)
{
// Spawn new animations, but do not remove the old ones (they are fade out)
projectileAnimations = null;
}
}
if (projectileAnimations == null)
{
// using the render delay of the projectile
event.getPosition(0, lastEventPosition);
if (physicsProjectile_state0 != null &&
event.hasOccurred(0) &&
event.getOccurredAt(0) <= physicsEnv.getTick() - renderDelay)
{
spawnAnimations();
}
}
}
private void spawnAnimations()
{
++spawnID;
ProjectilePublic physicsProjectile_state0 = event.getProjectile(0);
projectileAnimations = new ArrayList<>(1 + event.getCoupledProjectilesSize(0));
PhysicsPoint tileHit = new PhysicsPoint();
event.getHitTile(0, tileHit);
GCImage hitImage;
EXPLODE_REASON reason = event.getReason(0);
switch (reason)
{
case EXPIRATION:
hitImage = physicsProjectile_state0.getWeaponConfigImage(
"projectile-expiration-animation",
resourceDB);
break;
case PROX_DELAY:
hitImage = physicsProjectile_state0.getWeaponConfigImage(
"projectile-prox-animation",
resourceDB);
break;
case PROX_DIST:
hitImage = physicsProjectile_state0.getWeaponConfigImage(
"projectile-prox-animation",
resourceDB);
break;
case HIT_TILE:
hitImage = physicsProjectile_state0.getWeaponConfigImage(
"projectile-hit-tile-animation",
resourceDB);
break;
case HIT_SHIP:
hitImage = physicsProjectile_state0.getWeaponConfigImage(
"projectile-hit-ship-animation",
resourceDB);
break;
default:
assert false;
return;
}
if (hitImage == null)
{
return;
}
spawnAnimation(hitImage, physicsProjectile_state0);
for (ProjectilePublic coupledProjectile : event.getCoupledProjectiles(0))
{
spawnAnimation(hitImage, coupledProjectile);
}
}
private void spawnAnimation(@Nonnull GCImage hitImage, @Nonnull ProjectilePublic physicsProj)
{
Projectile proj = mapEntities.physicsProjectileToGraphics(physicsProj);
PhysicsPoint pos = new PhysicsPoint();
event.getPosition(0, pos);
boolean hitLocal = hitActor != null && hitActor.isLocalPlayer();
if (pos.set)
{
GCImageAnimation anim = new MyAnimation(spawnID, resourceDB, hitImage);
if (!hitLocal) // inherit the alpha of the projectile, unless it hit the local player
{
anim.setAlpha(proj.getAlpha());
}
anim.setPositionFromPhysics(pos);
mapEntities.addAnimation(RENDER_LAYER.AFTER_LOCAL_SHIP, anim, null);
projectileAnimations.add(anim);
}
// If the last rendered position of the projectile is very different,
// display 2 animations.
if (proj.physicsPos.set &&
(!pos.set || pos.distanceSquared(proj.physicsPos) > TIMEWARP_EXTRA_ANIM_DISTSQ)
)
{
GCImageAnimation anim = new MyAnimation(spawnID, resourceDB, hitImage);
if (!hitLocal) // inherit the alpha of the projectile
{
anim.setAlpha(proj.getAlpha());
}
anim.setPositionFromPhysics(proj.physicsPos);
mapEntities.addAnimation(RENDER_LAYER.AFTER_LOCAL_SHIP, anim, null);
projectileAnimations.add(anim);
}
}
private class MyAnimation extends GCImageAnimation
{
private final int mySpawnID;
MyAnimation(int spawnID, ResourceDB db, GCImage image)
{
super(db, image);
this.mySpawnID = spawnID;
}
@Override
public void tick(long tick)
{
super.tick(tick);
// TODO: perhaps limit the alpha to the alpha of the projectile?
// (or inherit the velocity?)
if (spawnID == this.mySpawnID && event.hasOccurred(0))
{
this.setAlphaVelocity(TIMEWARP_ALPHA_VELOCITY);
}
else
{
// fade out if the event was timewarped (or: fade it back in)
this.setAlphaVelocity(-TIMEWARP_ALPHA_VELOCITY);
}
}
}
}