/* * 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.shared.physics; import aphelion.shared.gameconfig.GameConfig; import aphelion.shared.physics.entities.Actor; import aphelion.shared.physics.entities.ProjectilePublic; import aphelion.shared.physics.entities.ActorPublic; import java.util.Iterator; import java.util.List; import aphelion.shared.physics.valueobjects.PhysicsPositionVector; import org.junit.*; import static org.junit.Assert.*; /** * * @author Joris */ public class SimpleEnvironmentTest extends PhysicsTest { @Test public void testPhysicsPointConstant() { assertTrue(EnvironmentConf.ROTATION_POINTS % 2 == 0); assertTrue(EnvironmentConf.ROTATION_POINTS % 4 == 0); assertTrue(Integer.MAX_VALUE / EnvironmentConf.ROTATION_POINTS >= 2); // Continuum compatibility (not absolutely necessary, however nice to have): assertTrue(EnvironmentConf.ROTATION_POINTS % 40 == 0); // continuum rotation points } @Test public void testActorCreation() { SimpleEnvironment env = (SimpleEnvironment) this.env; env.tick(); assertTrue(env.tick_now == 1); // Should trigger the operation immediately env.actorNew(1, 1, 1234, "Warbird"); assertNotNull(env.getActor(1)); // Should not trigger the operation yet env.actorNew(2, 2, 1234, "Warbird"); env.actorRemove(2, 1); assertNotNull(env.getActor(1)); assertNull(env.getActor(2)); env.tick(); // 2 assertTrue(env.getActor(1).isNonExistent()); assertNotNull(env.getActor(2)); // late operation, should execute immediately env.actorNew(1, 3, 1234, "Warbird"); assertNotNull(env.getActor(3)); // operations with the same tick should execute in the same order they are added env.actorRemove(1, 3); assertTrue(env.getActor(3).isNonExistent()); env.actorNew(3, 4, 1234, "Warbird"); env.actorRemove(3, 4); env.tick(); // 3 assertTrue(env.getActor(4).isNonExistent()); ActorPublic actor2 = env.getActor(2); assertFalse(actor2.isNonExistent()); env.actorRemove(3, 2); assertTrue(actor2.isNonExistent()); } @Test public void testActorMovement() { // todo: break apart test case int rotSpeed = conf.getInteger("ship-rotation-speed").get(); env.tick(); // 0 -> 1 env.tick(); // 2 env.tick(); // 3 env.actorNew(3, 1, 1234, "Warbird"); ActorPublic actor = env.getActor(1); // actor created at tick 3 Actor privateActor = getPrivateActor(actor); env.tick(); // 4 env.actorWarp(4, 1, false, 1000, 90, 100, 15, 0); assertPosition(1000, 90, actor); assertVelocity(100, 15, actor); assertRotation(0, actor); env.tick(); // 5 assertPosition(1100, 105, actor); env.actorMove(5, 1, MOVE_UP); // up assertPosition(1100, 105, actor); // no change in position yet assertVelocity(100, -13, actor); // but the velocity should have been changed env.actorMove(5, 1, MOVE_UP); // duplicate move, should be ignored assertPosition(1100, 105, actor); assertVelocity(100, -13, actor); env.tick(); // 6 assertPosition(1200, 92, actor); // dead reckon assertVelocity(100, -13, actor); // velocity change in the future env.actorMove(7, 1, MOVE_DOWN); // down assertPosition(1200, 92, actor); // no change assertVelocity(100, -13, actor); env.tick(); // 7 assertPosition(1300, 79, actor); // dead reckon using previous velocity assertVelocity(100, 15, actor); // velocity change env.tick(); // 8 assertPosition(1400, 94, actor); // dead reckon using new velocity assertVelocity(100, 15, actor); // test max speed (2624) assertRotation(0, actor); env.actorMove(9, 1, MOVE_UP); env.actorWarp(9, 1, false, -500, -1400, 0, -2500, 0); env.tick(); // 9 // test operation priority (the move should have been ignored since there is a warp for the same tick) assertVelocity(0, -2500, actor); env.actorMove(10, 1, MOVE_UP); env.actorMove(11, 1, MOVE_UP); env.actorMove(12, 1, MOVE_UP); env.actorMove(13, 1, MOVE_UP); env.actorMove(14, 1, MOVE_UP); env.tick(); env.tick(); env.tick(); env.tick(); env.tick(); // 14 // maximum speed is 2624. however this is not corrected until the next tick. // (this correction is done as the very first step when dead reckoning, so // the velocity that is larger than the limit is never really used) assertVelocity(0, -2640, actor); env.tick(); // 15 assertVelocity(0, -2624, actor); env.actorWarp(16, 1, false, 0, 0, 0, 0, EnvironmentConf.ROTATION_1_2TH); // facing down env.actorMove(17, 1, MOVE_UP); env.actorWarp(18, 1, false, 0, 0, 0, 0, EnvironmentConf.ROTATION_1_4TH); // facing right env.actorMove(19, 1, MOVE_UP); env.actorWarp(20, 1, false, 0, 0, 0, 0, EnvironmentConf.ROTATION_3_4TH); // facing left env.actorMove(21, 1, MOVE_UP); env.tick(); // 16 env.tick(); // 17 assertPosition(0, 0, actor); assertVelocity(0, 28, actor); env.tick(); // 18 env.tick(); // 19 assertPosition(0, 0, actor); assertVelocity(28, 0, actor); env.tick(); // 20 env.tick(); // 21 assertPosition(0, 0, actor); assertVelocity(-28, 0, actor); env.actorWarp(21, 1, false, 0, 0, 0, 0, 0); env.actorMove(22, 1, MOVE_RIGHT); env.tick(); // 22 assertRotation(rotSpeed, actor); assertSnappedRotation(0, actor); env.actorMove(23, 1, MOVE_LEFT); env.tick(); // 23 assertRotation(0, actor); assertSnappedRotation(0, actor); env.actorMove(24, 1, MOVE_LEFT); env.tick(); // 24 assertRotation(EnvironmentConf.ROTATION_POINTS - rotSpeed, actor); assertSnappedRotation(0, actor); env.actorMove(25, 1, MOVE_LEFT); env.tick(); // 25 assertSnappedRotation(0, actor); env.actorMove(26, 1, MOVE_LEFT); env.tick(); // 26 assertSnappedRotation(EnvironmentConf.ROTATION_POINTS - EnvironmentConf.ROTATION_POINTS / 40, actor); env.actorMove(27, 1, MOVE_LEFT); env.tick(); // 27 assertSnappedRotation(EnvironmentConf.ROTATION_POINTS - EnvironmentConf.ROTATION_POINTS / 40, actor); env.actorMove(28, 1, MOVE_LEFT); env.tick(); // 28 assertSnappedRotation(EnvironmentConf.ROTATION_POINTS - EnvironmentConf.ROTATION_POINTS / 40, actor); env.actorWarp(30, 1, false, 0, 0, 10, 11, 0); env.tick(); // 29 env.tick(); // 30 for (int a = 0; a < 10; ++a) { env.tick(); } // tick 40 assertVelocity(10, 11, actor); assertPosition(100, 11 * 10, actor); // late move at tick 35. position should use the old velocity up to and including tick 35. // tick 36 and later use the new velocity env.actorMove(35, 1, MOVE_DOWN); assertVelocity(10, 39, actor); assertPosition(100, 11 * 5 + 39 * 5, actor); // another late move at tick 36 env.actorMove(36, 1, MOVE_DOWN); assertVelocity(10, 67, actor); assertPosition(100, 11 * 5 + 39 * 1 + 67 * 4, actor); // a late warp env.actorWarp(37, 1, false, 0, 0, 1000, 1001, 0); assertVelocity(1000, 1001, actor); assertPosition(1000 * 3, 1001 * 3, actor); // a late move on the same tick as the late warp (the move should be ignored) env.actorMove(37, 1, MOVE_DOWN); assertVelocity(1000, 1001, actor); assertPosition(1000 * 3, 1001 * 3, actor); } @Test public void testMoveConsistency1() { SimpleEnvironment env = (SimpleEnvironment) this.env; // Test multiple move's received in the past (but still in order) env.actorNew(0, 1, 1234, "Warbird"); env.actorWarp(0, 1, false, 0, 0, 1000, 1001, EnvironmentConf.ROTATION_1_2TH); ActorPublic actor = env.getActor(1); while (env.tick_now < 20) { env.tick(); } // now at tick 20 env.actorMove(10, 1, MOVE_UP); env.actorMove(15, 1, MOVE_UP); // Ensure a timewarp can happen while (env.tick_now < 15 + env.econfig.TRAILING_STATE_DELAY) { env.tick(); } // now at tick 25 assertVelocity(1000, 1001 + 28 * 2, actor); assertPosition( (15 + env.econfig.TRAILING_STATE_DELAY) * 1000, 10 * 1001 + 5 * (1001+28) + env.econfig.TRAILING_STATE_DELAY * (1001+28*2), actor); } @Test public void testMoveConsistency2() { SimpleEnvironment env = (SimpleEnvironment) this.env; // Test multiple move's received in the past, out of order // This is solved without a timewarp env.actorNew(0, 1, 1234, "Warbird"); // facing down env.actorWarp(0, 1, false, 0, 0, 1000, 1001, EnvironmentConf.ROTATION_1_2TH); ActorPublic actor = env.getActor(1); while (env.tick_now < 20) { env.tick(); } // now at tick 20 env.actorMove(15, 1, MOVE_UP); assertVelocity(1000, 1001 + 28, actor); assertPosition(20 * 1000, 20 * 1001 + 5 * 28, actor); assertPosition(10 * 1000, 10 * 1001, 10, actor); // Re ordered operations. The following operation is ignored. // This can be fixed using a timewarp. env.actorMove(10, 1, MOVE_UP); assertVelocity(1000, 1001 + 28 * 2, actor); assertPosition( 20 * 1000, 20 * 1001 + 10 * 28 + 5 * 28, actor); } @Test public void testInvalidOperationOrder() { SimpleEnvironment env = (SimpleEnvironment) this.env; List<Object> yamlDocuments; try { yamlDocuments = GameConfig.loadYaml( "- ship-spawn-x: 100\n" + " ship-spawn-y: 100\n" + " ship-spawn-radius: 0\n"); } catch (Exception ex) { throw new Error(ex); } env.loadConfig(env.getTick() - env.getConfig().HIGHEST_DELAY + 1, "testInvalidOperationOrder()", yamlDocuments); env.actorWarp(1, 1, false, 1000, 1000, 100, 100, 0); env.actorNew(5, 1, 1234, "Warbird"); while (env.tick_now < 200) { env.tick(); } assertEquals(0, env.getTimewarpCount()); ActorPublic actor = env.getActor(1); assertPosition(100 * PhysicsMap.TILE_PIXELS + PhysicsMap.TILE_PIXELS / 2, 100 * PhysicsMap.TILE_PIXELS + PhysicsMap.TILE_PIXELS / 2, actor); // warp should be ignored } @Test public void testLateActorRemove() { // these tick values are so long ago, they are not part of any trailing state env.actorRemove(-10000, 1); env.actorNew(-11000, 1, 1234, "Warbird"); env.tick(); } @Test public void testWeaponFire() { SimpleEnvironment env = (SimpleEnvironment) this.env; env.actorNew(0, 1, 1234, "Warbird"); env.actorWarp(0, 1, false, 1000, 2000, 0, 10, EnvironmentConf.ROTATION_1_2TH); env.actorWeapon(0, 1, WEAPON_SLOT.GUN, false, 0, 0, 0, 0, 0); int offsetY = conf.getInteger("projectile-offset-y").get(); int fireSpeed = conf.getInteger("projectile-speed").get(); Iterator<ProjectilePublic> it = env.projectileIterator(0); assertTrue(it.hasNext()); PhysicsPositionVector pos = new PhysicsPositionVector(); ProjectilePublic proj = it.next(); assertFalse(it.hasNext()); proj.getPosition(pos); assertEquals(1000, pos.pos.x); assertEquals(2000 + offsetY, pos.pos.y); assertEquals(0, pos.vel.x); assertEquals(fireSpeed + 10, pos.vel.y); it = null; proj = null; env.tick(); it = env.projectileIterator(0); assertTrue(it.hasNext()); proj = it.next(); assertFalse(it.hasNext()); proj.getPosition(pos); assertEquals(1000, pos.pos.x); assertEquals(2000 + offsetY + fireSpeed + 10, pos.pos.y); assertEquals(0, pos.vel.x); assertEquals(fireSpeed + 10, pos.vel.y); } @Test public void testWeaponFireConsistency() { SimpleEnvironment env = (SimpleEnvironment) this.env; env.actorNew(0, 1, 1234, "Warbird"); env.actorWarp(0, 1, false, 1000, 2000, 0, 0, EnvironmentConf.ROTATION_1_2TH); env.actorWeapon(2, 1, WEAPON_SLOT.GUN, false, 0, 0, 0, 0, 0); env.tick(); // 1 env.tick(); // 2 int offsetY = conf.getInteger("projectile-offset-y").get(); int fireSpeed = conf.getInteger("projectile-speed").get(); Iterator<ProjectilePublic> it = env.projectileIterator(0); assertTrue(it.hasNext()); PhysicsPositionVector pos = new PhysicsPositionVector(); ProjectilePublic proj = it.next(); assertFalse(it.hasNext()); proj.getPosition(pos); assertEquals(1000, pos.pos.x); assertEquals(2000 + offsetY, pos.pos.y); assertEquals(0, pos.vel.x); assertEquals(fireSpeed, pos.vel.y); it = null; proj = null; // weapon fire was at tick 2 env.actorMove(1, 1, MOVE_UP); // Ensure a timewarp can happen while (env.tick_now < 2 + env.econfig.TRAILING_STATE_DELAY) { env.tick(); } // now at tick 13 assertEquals(1, env.getTimewarpCount()); assertPosition(1000, 2000 + ((int) env.tick_now - 1) * 28, env.getActor(1)); it = env.projectileIterator(0); assertTrue(it.hasNext()); proj = it.next(); assertFalse(it.hasNext()); proj.getPosition(pos); // 57548 but was: 57296 assertEquals(1000, pos.pos.x); assertEquals(2000 + offsetY + 28 // ship position + (fireSpeed + 28) * env.econfig.TRAILING_STATE_DELAY, // the distance the projectile has traveled pos.pos.y ); assertEquals(0, pos.vel.x); assertEquals(fireSpeed + 28, pos.vel.y); } }