/* * 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; import aphelion.client.Client; import aphelion.client.graphics.AnimatedColour; import aphelion.client.graphics.screen.Camera; import aphelion.shared.event.TickEvent; import aphelion.shared.gameconfig.GCBoolean; import aphelion.shared.gameconfig.GCColour; import aphelion.shared.gameconfig.GCImage; import aphelion.shared.gameconfig.WrappedValueAbstract; import aphelion.shared.physics.EnvironmentConf; import aphelion.shared.physics.PhysicsEnvironment; import aphelion.shared.physics.entities.ProjectilePublic; import aphelion.shared.physics.valueobjects.PhysicsPoint; import aphelion.shared.physics.valueobjects.PhysicsPositionVector; import aphelion.shared.resource.ResourceDB; import aphelion.shared.swissarmyknife.Point; import aphelion.shared.swissarmyknife.SwissArmyKnife; import de.lessvoid.nifty.tools.Color; import java.lang.ref.WeakReference; import javax.annotation.Nonnull; import org.lwjgl.opengl.GL11; import org.newdawn.slick.Animation; import org.newdawn.slick.Image; import org.newdawn.slick.SpriteSheetCounted; import org.newdawn.slick.opengl.TextureImpl; import org.newdawn.slick.opengl.renderer.SGL; /** * The graphic counter part of physics.Projectile. * @author Joris */ public class Projectile extends MapEntity implements WrappedValueAbstract.ChangeListener, TickEvent { final ProjectilePublic physicsProjectile; public final Point shadowPosition = new Point(0, 0); public final Point shadowPosition_prev = new Point(0, 0); // RenderDelay: public final RenderDelayValue renderDelay_value = new RenderDelayValue(0); public long renderDelay_current; public WeakReference<ActorShip> renderDelay_basedOn; public long renderingAt_tick; private final GCImage imageNoBounce; private final GCImage imageBounces; private final GCImage imageInactive; private final GCImage imageTrail; private final GCBoolean imageTrailRandomized; private final GCColour colourRadar; private Animation animNoBounce; private Animation animBounces; private Animation animInactive; private AnimatedColour animRadar; public static final float TIMEWARP_ALPHA_VELOCITY_MIN = 0.003f; // 10 seconds to fully fade back in public static final float TIMEWARP_ALPHA_VELOCITY_MAX = 0.1f; public static final float TIMEWARP_ALPHA_VELOCITY_LOCAL_DIST_SMOOTHING = 500; // in pixels public Projectile(@Nonnull ResourceDB db, @Nonnull ProjectilePublic physicsProjectile) { super(db); this.physicsProjectile = physicsProjectile; imageNoBounce = physicsProjectile.getWeaponConfigImage("projectile-image", db); imageBounces = physicsProjectile.getWeaponConfigImage("projectile-image-bounces", db); imageInactive = physicsProjectile.getWeaponConfigImage("projectile-image-inactive", db); imageTrail = physicsProjectile.getWeaponConfigImage("projectile-image-trail", db); imageTrailRandomized = physicsProjectile.getWeaponConfigBoolean("projectile-image-trail-randomized"); colourRadar = physicsProjectile.getWeaponConfigColour("projectile-radar-colour"); } public @Nonnull ProjectilePublic getPhysicsProjectile() { return physicsProjectile; } public void calculateRenderAtTick(@Nonnull PhysicsEnvironment physicsEnv) { renderDelay_current = SwissArmyKnife.clip( renderDelay_value.get(), 0, physicsEnv.getConfig().HIGHEST_DELAY); this.renderingAt_tick = physicsEnv.getTick() - renderDelay_current; } public void setShadowPositionFromPhysics(int x, int y) { shadowPosition_prev.set(shadowPosition); shadowPosition.x = x / 1024f; shadowPosition.y = y / 1024f; } @Override public void tick(long tick) { super.tick(tick); renderDelay_value.tick(tick); } private void updateAnimObjects() { if (animNoBounce == null) { animNoBounce = imageNoBounce.newAnimation(); if (animNoBounce != null) { animNoBounce.setLooping(true); } } if (animBounces == null) { animBounces = imageBounces.newAnimation(); if (animBounces != null) { animBounces.setLooping(true); } } if (animInactive == null) { animInactive = imageInactive.newAnimation(); if (animInactive != null) { animInactive.setLooping(true); } } if (animRadar == null) { animRadar = colourRadar.getAnimation(); } } @Override public void gameConfigValueChanged(@Nonnull WrappedValueAbstract val) { if (val == this.imageNoBounce) { this.animNoBounce = null; } if (val == this.imageBounces) { this.animBounces = null; } if (val == this.imageInactive) { this.animInactive = null; } if (val == this.colourRadar) { this.animRadar = null; } } @Override public boolean render(@Nonnull Camera camera, int iteration) { if (!isExists()) { return false; } if (iteration > 1) { return false; } updateAnimObjects(); Point screenPos = new Point(); camera.mapToScreenPosition(pos, screenPos); if (camera.radarRendering) { if (iteration == 0 && this.animRadar != null) { org.newdawn.slick.Color color = this.animRadar.get(); TextureImpl.bindNone(); GL11.glColor4f(color.r, color.g, color.b, color.a * this.alpha); float x = screenPos.x - 0.5f; float y = screenPos.y - 0.5f; GL11.glBegin(SGL.GL_QUADS); GL11.glVertex2f(x, y); GL11.glVertex2f(x + 1, y); GL11.glVertex2f(x + 1, y + 1); GL11.glVertex2f(x, y + 1); GL11.glEnd(); } return false; } if (iteration == 0) { // render the trail SpriteSheetCounted trail = this.imageTrail.getSpriteSheet(); if (trail != null) { Point trailPos = new Point(); PhysicsPositionVector phyPos = new PhysicsPositionVector(); long rand = imageTrailRandomized.get() ? SwissArmyKnife.fastRandomIsh() : 0; long tick = this.renderingAt_tick - (rand & 0b11); // 0, 1, 2, 3 rand >>= 2; for (int tile = 0; tile < trail.getTilesCount(); tile += (rand & 0b1) + 1, rand >>= 1) { physicsProjectile.getHistoricPosition( phyPos, tick - 2 * tile, true); if (phyPos.pos.set) { trailPos.set(phyPos.pos); trailPos.divide(1024); camera.mapToScreenPosition(trailPos, trailPos); Image img = trail.getSubImage(tile); img.draw(trailPos.x - img.getWidth()/2, trailPos.y - img.getHeight()/2, this.alphaFilter); } } } } else if (iteration == 1) { Animation anim; if (imageInactive.isSet() && !this.physicsProjectile.isActive()) { anim = animInactive; } else if (imageBounces.isSet() && this.physicsProjectile.getBouncesLeft() != 0) { anim = animBounces; } else { anim = animNoBounce; // also fallback } if (anim != null) { anim.draw( screenPos.x - anim.getWidth() / 2 * camera.zoom, screenPos.y - anim.getHeight() / 2 * camera.zoom, anim.getWidth() * camera.zoom, anim.getHeight() * camera.zoom, this.alphaFilter); } if (Client.showDebug) { camera.renderPlayerText(this.renderDelay_current + "", screenPos.x + 5, screenPos.y + 5, Color.WHITE); } } return iteration < 1; } }