/* * 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.Graph; import aphelion.client.RENDER_LAYER; import aphelion.shared.resource.ResourceDB; import aphelion.client.graphics.screen.Camera; import aphelion.client.graphics.AnimatedColour; import aphelion.shared.event.TickEvent; import aphelion.shared.gameconfig.*; import aphelion.shared.net.game.NetworkedActor; import aphelion.shared.physics.EnvironmentConf; import aphelion.shared.physics.entities.ActorPublic; import aphelion.shared.physics.PhysicsMath; import aphelion.shared.physics.valueobjects.PhysicsMoveable; import aphelion.shared.physics.valueobjects.PhysicsMovement; import aphelion.shared.swissarmyknife.Point; import de.lessvoid.nifty.tools.Color; import javax.annotation.Nonnull; import org.newdawn.slick.Image; import org.newdawn.slick.SpriteSheetCounted; /** * * @author Joris */ public class ActorShip extends MapEntity implements TickEvent, WrappedValueAbstract.ChangeListener { public final int pid; private NetworkedActor netActor; private final ActorPublic actor; private final Animator animator; private int physRotation; private int spriteTile = 0; public float distSqToLocal; /** The position without smoothing. */ public final Point realPosition = new Point(0, 0); /** The position without delay (with dead reckoning). */ public final Point shadowPosition = new Point(0, 0); // RenderDelay public final RenderDelayValue renderDelay_value = new RenderDelayValue(0); public long renderDelay_current; public long renderDelay_mostRecentMove; public long renderDelay_mostRecentMoveLatency; public long renderingAt_tick; private ReusableAnimationList<ExhaustAnimation> exhaustAnimations; private ReusableAnimationList<GCImageAnimation> empedAnimations; private long lastExhaust_nanos; private long lastEmp_nanos; private boolean physicsInitialized = false; public GCInteger maxEnergy; public GCImage shipImage; public GCColour radarColour; public GCInteger shipRadius; public GCImage exhaust_image; public GCInteger exhaust_delay; public GCBoolean exhaust_remote; public GCImage emped_image; public GCInteger emped_delay; public AnimatedColour radarAnim; public MapAnimation activeDeathAnimation; private Color playerColor = new Color(1f, 1f, 0f, 1f); private Color lowEnergyColor = new Color(1f, 0f, 0f, 1f); public static final float SPAWN_ALPHA_VELOCITY = 0.025f; public ActorShip(@Nonnull ResourceDB db, @Nonnull NetworkedActor netActor, @Nonnull ActorPublic actor, @Nonnull Animator animator) { super(db); assert netActor != null; assert actor != null; this.netActor = netActor; this.actor = actor; this.pid = actor.getPid(); this.animator = animator; exhaustAnimations = new ReusableAnimationList<>(db, animator, new ReusableAnimationList.Factory<ExhaustAnimation>() { @Override public ExhaustAnimation create(ResourceDB db) { return new ExhaustAnimation(db, exhaust_image); } }); empedAnimations = new ReusableAnimationList<>(db, animator, new ReusableAnimationList.Factory<GCImageAnimation>() { @Override public GCImageAnimation create(ResourceDB db) { return new GCImageAnimation(db, emped_image); } }); lastExhaust_nanos = Graph.nanoTime(); lastEmp_nanos = Graph.nanoTime(); tryInitPhysics(); } public void setRotationFromPhysics(int physRotation) { this.physRotation = physRotation; if (shipImage != null) { int pointsPerTile = EnvironmentConf.ROTATION_POINTS / (shipImage.getTilesHorizontal() * shipImage.getTilesVertical()); if (pointsPerTile <= 0) { spriteTile = 0; } else { spriteTile = physRotation / pointsPerTile; } } } private final Point lastCameraPosition = new Point(); public void getCameraPosition(@Nonnull Point pos) { if (this.actor.isDead()) { if (activeDeathAnimation != null) { pos.set(this.activeDeathAnimation.pos); } else { // player is dead and the animation is done. // keep the camera where it was pos.set(lastCameraPosition); } } else { pos.set(this.pos); } lastCameraPosition.set(pos); } public void setShadowPositionFromPhysics(int x, int y) { shadowPosition.x = x / 1024f; shadowPosition.y = y / 1024f; } /** Without smoothing. */ public void setRealPositionFromPhysics(int x, int y) { realPosition.x = x / 1024f; realPosition.y = y / 1024f; } public void updateDistanceToLocal(@Nonnull Point localPos) { Point dist = new Point(pos); dist.sub(localPos); this.distSqToLocal = dist.lengthSquared(); } public ActorPublic getActor() { return actor; } public int getEnergy(boolean precise) { if (precise) { return actor.getEnergy(); } else { return actor.getEnergy() / 1024; } } public int getMaxEnergy() { int nrg = maxEnergy.get(); if (nrg < 1) { nrg = 1; } return nrg; } public final void tryInitPhysics() { if (physicsInitialized) { return; } if (actor.isNonExistent()) { return; } physicsInitialized = true; // any get config should not fail if isDeleted returns false maxEnergy = actor.getActorConfigInteger("ship-energy"); shipImage = actor.getActorConfigImage("ship-image", db); if (this.isLocalPlayer()) { radarColour = actor.getActorConfigColour("ship-local-radar-colour"); } else { radarColour = actor.getActorConfigColour("ship-radar-colour"); } shipRadius = actor.getActorConfigInteger("ship-radius"); exhaust_image = actor.getActorConfigImage("ship-exhaust-image", db); exhaust_delay = actor.getActorConfigInteger("ship-exhaust-delay"); exhaust_remote = actor.getActorConfigBoolean("ship-exhaust-remote-players"); emped_image = actor.getActorConfigImage("ship-emped-image", db); emped_delay = actor.getActorConfigInteger("ship-emped-delay"); lastExhaust_nanos = Graph.nanoTime(); lastEmp_nanos = Graph.nanoTime(); setRotationFromPhysics(physRotation); // update tile } @Override public boolean render(@Nonnull Camera camera, int iteration) { if (!isExists()) { return false; } if (iteration > 0) { return false; } long now = Graph.nanoTime(); Point screenPos = new Point(); camera.mapToScreenPosition(pos, screenPos); float x = screenPos.x; float y = screenPos.y; float w = 1; float h = 1; SpriteSheetCounted shipSprite = shipImage.getSpriteSheet(); Image image = null; if (shipSprite != null) { image = shipSprite.getSubImage(spriteTile); x -= image.getWidth() / 2f * camera.zoom; y -= image.getHeight() / 2f * camera.zoom; w = image.getWidth() * camera.zoom; h = image.getHeight() * camera.zoom; } if (camera.radarRendering) { if (this.radarColour.isSet()) { if (radarAnim == null) { radarAnim = radarColour.getAnimation(); } Graph.g.setColor(radarAnim.get()); Graph.g.fillRect(x-1, y-1, 2, 2); } return false; } if (this.exhaust_image.isSet() && (this.isLocalPlayer() || exhaust_remote.get())) { if (now - this.lastExhaust_nanos > this.exhaust_delay.get() * 1_000_000L) { this.lastExhaust_nanos = now; PhysicsMoveable moveable = actor.getHistoricMovement(this.renderingAt_tick, true); PhysicsMovement move = moveable instanceof PhysicsMovement ? (PhysicsMovement) moveable : null; if (move != null && (move.up || move.down)) { ExhaustAnimation left = exhaustAnimations.register(RENDER_LAYER.AFTER_TILES, camera); ExhaustAnimation right = exhaustAnimations.register(RENDER_LAYER.AFTER_TILES, camera); left.setPosition(this); PhysicsMath.rotationToPoint(left.physicsPos, this.physRotation + EnvironmentConf.ROTATION_1_2TH, this.shipRadius.get() + 1024 * 5); left.setPositionFromPhysics(); right.setPosition(left); PhysicsMath.rotationToPoint(left.physicsPos, this.physRotation - EnvironmentConf.ROTATION_1_4TH, 1024 * 5); left.setPositionFromPhysics(); PhysicsMath.rotationToPoint(right.physicsPos, this.physRotation + EnvironmentConf.ROTATION_1_4TH, 1024 * 5); right.setPositionFromPhysics(); } } } else { this.lastExhaust_nanos = now; } if (this.emped_image.isSet()) { if (now - this.lastEmp_nanos > this.emped_delay.get() * 1_000_000L) { this.lastEmp_nanos = now; if (actor.isEmped()) { GCImageAnimation anim = empedAnimations.register(RENDER_LAYER.AFTER_TILES, camera); anim.setPosition(this); anim.setVelocityFromPhysics(0, 0); } } } else { this.lastEmp_nanos = now; } if (image != null) { // Draw the position without smoothing if (Client.showDebug) { Point testScreen = new Point(); camera.mapToScreenPosition(realPosition, testScreen); testScreen.x -= image.getWidth() / 2f * camera.zoom; testScreen.y -= image.getHeight() / 2f * camera.zoom; image.draw(testScreen.x, testScreen.y, w, h, org.newdawn.slick.Color.magenta); } image.draw(x, y, w, h, this.alphaFilter); if (netActor.name != null) { camera.renderPlayerText( netActor.name + (Client.showDebug ? " [" + renderDelay_current + "]" : ""), x + image.getWidth(), y + image.getHeight() / 2f, playerColor); } int energy = this.actor.getEnergy(this.renderingAt_tick) / 1024; if (energy < this.maxEnergy.get() / 2) { camera.renderPlayerText(energy + "", x + image.getWidth(), y + image.getHeight() / 2f + 15, energy < this.maxEnergy.get() / 4 ? lowEnergyColor : playerColor); } } /*int r = 14; Graph.g.setColor(Color.green); Graph.g.drawLine(screenPos.x, screenPos.y, screenPos.x, screenPos.y); Graph.g.drawRect(screenPos.x-r, screenPos.y-r, r+r, r+r);*/ return false; } @Override public void tick(long tick) { super.tick(tick); tryInitPhysics(); renderDelay_value.tick(tick); if (activeDeathAnimation != null && !activeDeathAnimation.isAnimating() && !this.actor.isDead(this.renderingAt_tick)) { // have to unset it because it might mess up the camera position // in case death animations _just_ got disabled. activeDeathAnimation = null; } } public boolean isLocalPlayer() { return netActor.local; } @Override public void gameConfigValueChanged(@Nonnull WrappedValueAbstract val) { if (val == this.radarColour) { radarAnim = null; } } }