/* * 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.GCInteger; import aphelion.shared.gameconfig.GameConfig; import static aphelion.shared.physics.SimpleEnvironmentTest.MOVE_UP; import aphelion.shared.physics.entities.ProjectilePublic; import aphelion.shared.physics.events.pub.ActorDiedPublic; import aphelion.shared.physics.events.pub.EventPublic; import aphelion.shared.physics.events.pub.ProjectileExplosionPublic; import aphelion.shared.physics.valueobjects.PhysicsPoint; import java.util.List; import aphelion.shared.physics.valueobjects.PhysicsPositionVector; import org.junit.*; import static org.junit.Assert.*; /** * Test if timewarps are able to execute without error. * @author Joris */ public class TimewarpTest extends PhysicsTest { @Test public void testActorCreation() { SimpleEnvironment env = (SimpleEnvironment) this.env; try { List<Object> yamlDocuments = GameConfig.loadYaml("" + "- selector: {ship: warbird}\n" + " test-actor-creation-test: 1944619\n" + "- selector: {ship: javelin}\n" + " test-actor-creation-test: 391385\n" ); env.loadConfig(env.getTick() - env.econfig.HIGHEST_DELAY, "test", yamlDocuments); } catch (Exception ex) { throw new Error(ex); } // todo also test change env.actorNew(1, ACTOR_FIRST, 1234, "warbird"); env.actorWarp(1, ACTOR_FIRST, false, 1000, 90, 0, 0, 0); env.timewarp(1); env.tick(); // tick 1, it should now create the actor this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 0, false)); GCInteger testGC = env.getActor(ACTOR_FIRST, 0, false).getActorConfigInteger("test-actor-creation-test"); assertEquals(1944619, testGC.get()); assertEquals(1, env.getActorCount(0)); env.timewarp(1); // the actor is not yet present in state 1, it should recreate him in state 0 assertEquals(1, env.getActorCount(0)); env.timewarp(1); assertEquals(1, env.getActorCount(0)); env.timewarp(1); assertEquals(1, env.getActorCount(0)); this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 0, false)); assertEquals(1944619, testGC.get()); env.actorModification(3, ACTOR_FIRST, "javelin"); while(env.getTick() < env.econfig.TRAILING_STATE_DELAY) { env.tick(); } assertEquals(391385, testGC.get()); assertEquals(1, env.getActorCount(0)); env.tick(); // should create the actor at this tick in state 1 this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 0, false)); this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 1, false)); env.timewarp(1); assertEquals(1, env.getActorCount(0)); this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 0, false)); this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 1, false)); env.timewarp(env.econfig.TRAILING_STATES-1); assertEquals(1, env.getActorCount(0)); this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 0, false)); this.assertPosition(1000, 90, env.getActor(ACTOR_FIRST, 1, false)); assertEquals(391385, testGC.get()); } @Test public void testActorDestruction() { SimpleEnvironment env = (SimpleEnvironment) this.env; env.actorNew(1, ACTOR_FIRST, 1234, "warbird"); env.actorRemove(env.econfig.TRAILING_STATE_DELAY + 3, ACTOR_FIRST); env.tick(); assertActorExists(env.getActor(ACTOR_FIRST, 0, false)); assertActorNotExists(env.getActor(ACTOR_FIRST, 1, false)); while(env.getTick() < env.econfig.TRAILING_STATE_DELAY) { env.tick(); } env.tick(); // should create the actor at this tick in state 1 assertActorExists(env.getActor(ACTOR_FIRST, 0, false)); assertActorExists(env.getActor(ACTOR_FIRST, 1, false)); env.tick(); env.tick(); // should remove the actor at this tick in state 0 assertActorNotExists(env.getActor(ACTOR_FIRST, 0, false)); assertNotNull(env.getActor(ACTOR_FIRST, 1, false)); env.timewarp(1); assertActorNotExists(env.getActor(ACTOR_FIRST, 0, false)); assertNotNull(env.getActor(ACTOR_FIRST, 1, false)); env.timewarp(env.econfig.TRAILING_STATES-1); assertActorNotExists(env.getActor(ACTOR_FIRST, 0, false)); assertNotNull(env.getActor(ACTOR_FIRST, 1, false)); // Soft delete assertEquals(1, env.getActorCount(0)); while(env.getTick(env.econfig.TRAILING_STATES-1) < 1 + env.econfig.HIGHEST_DELAY) { env.tick(); } // hard delete assertEquals(0, env.getActorCount(0)); } @Test public void testConfigChange() { SimpleEnvironment env = (SimpleEnvironment) this.env; env.actorNew(1, ACTOR_FIRST, 1234, "warbird"); env.actorWarp(1, ACTOR_FIRST, false, 1000, 90, 0, 0, 0); // Config change try { List<Object> yamlDocuments = GameConfig.loadYaml( "- ship-thrust: 1000\n" // was 28 ); env.loadConfig(3, "test", yamlDocuments); } catch (Exception ex) { throw new Error(ex); } env.actorMove(2, ACTOR_FIRST, MOVE_UP); env.actorMove(3, ACTOR_FIRST, MOVE_UP); env.actorMove(4, ACTOR_FIRST, MOVE_UP); env.actorMove(5, ACTOR_FIRST, MOVE_UP); env.actorMove(6, ACTOR_FIRST, MOVE_UP); env.tick(); // tick 1 env.tick(); // tick 2 env.tick(); // tick 3 env.tick(); // tick 4 env.tick(); // tick 5 env.tick(); // tick 6 assertEquals(1000, env.getGlobalConfigInteger(0, "ship-thrust").get()); assertEquals(28, env.getGlobalConfigInteger(1, "ship-thrust").get()); assertVelocity(0, -3624, env.getActor(ACTOR_FIRST, 0, false)); env.timewarp(1); assertEquals(1000, env.getGlobalConfigInteger(0, "ship-thrust").get()); assertEquals(28, env.getGlobalConfigInteger(1, "ship-thrust").get()); assertVelocity(0, -3624, env.getActor(ACTOR_FIRST, 0, false)); env.timewarp(env.econfig.TRAILING_STATES-1); assertEquals(1000, env.getGlobalConfigInteger(0, "ship-thrust").get()); assertEquals(28, env.getGlobalConfigInteger(1, "ship-thrust").get()); assertVelocity(0, -3624, env.getActor(ACTOR_FIRST, 0, false)); } private void testProjectileCreation_assertFirstProj(int state, int x, int y) { SimpleEnvironment env = (SimpleEnvironment) this.env; for (ProjectilePublic proj : env.projectileIterable(state)) { assertPosition(x, y, proj); return; } } @Test public void testProjectileCreation() { SimpleEnvironment env = (SimpleEnvironment) this.env; try { List<Object> yamlDocuments = GameConfig.loadYaml("" + "- weapon-slot-gun: test-noreload\n" + " weapon-slot-bomb: test-reload\n" + " projectile-expiration-ticks: 100\n" + "- selector: {weapon: test-noreload}\n" + " weapon-switch-delay: 0\n" + "- selector: {weapon: test-reload}\n" + " weapon-switch-delay: 4\n" ); env.loadConfig(env.getTick() - env.econfig.HIGHEST_DELAY, "test", yamlDocuments); } catch (Exception ex) { throw new Error(ex); } env.actorNew(1, ACTOR_FIRST, 1234, "warbird"); env.actorWarp(1, ACTOR_FIRST, false, 1000, 90, 0, 0, 0); env.actorWeapon(2, ACTOR_FIRST, WEAPON_SLOT.GUN, false, 0, 0, 0, 0, 0); env.tick(); // 1 env.tick(); // 2 assertEquals(1, env.calculateProjectileCount(0)); assertEquals(0, env.calculateProjectileCount(1)); testProjectileCreation_assertFirstProj(0, 1000, -14246); ProjectilePublic firstProjectile = env.projectileIterator(0).next(); env.timewarp(1); assertEquals(1, env.calculateProjectileCount(0)); assertEquals(0, env.calculateProjectileCount(1)); testProjectileCreation_assertFirstProj(0, 1000, -14246); assertContains(env.projectileIterator(0), firstProjectile); env.timewarp(env.econfig.TRAILING_STATES-1); assertEquals(1, env.calculateProjectileCount(0)); assertEquals(0, env.calculateProjectileCount(1)); testProjectileCreation_assertFirstProj(0, 1000, -14246); assertContains(env.projectileIterator(0), firstProjectile); while(env.getTick() < env.econfig.TRAILING_STATE_DELAY) { env.tick(); } env.tick(); env.tick(); assertEquals(1, env.calculateProjectileCount(0)); assertEquals(1, env.calculateProjectileCount(1)); testProjectileCreation_assertFirstProj(0, 1000, -178086); testProjectileCreation_assertFirstProj(1, 1000, -14246); assertContains(env.projectileIterator(0), firstProjectile); env.timewarp(1); assertEquals(1, env.calculateProjectileCount(0)); assertEquals(1, env.calculateProjectileCount(1)); testProjectileCreation_assertFirstProj(0, 1000, -178086); testProjectileCreation_assertFirstProj(1, 1000, -14246); assertContains(env.projectileIterator(0), firstProjectile); env.timewarp(env.econfig.TRAILING_STATES-1); assertEquals(1, env.calculateProjectileCount(0)); assertEquals(1, env.calculateProjectileCount(1)); testProjectileCreation_assertFirstProj(0, 1000, -178086); testProjectileCreation_assertFirstProj(1, 1000, -14246); assertContains(env.projectileIterator(0), firstProjectile); assertEquals(4, env.getTimewarpCount()); // 2 weapons, one of them has a weapon switch delay // execute them in the wrong order so that only 1 fires in state 0, // but both fire in state 1 long t = env.getTick(); env.tick(); env.tick(); // make sure they are both late env.actorWeapon(t+2, ACTOR_FIRST, WEAPON_SLOT.BOMB, false, 0, 0, 0, 0, 0); // bomb has switch delay, env.actorWeapon(t+1, ACTOR_FIRST, WEAPON_SLOT.GUN, false, 0, 0, 0, 0, 0); // gun does not // after resolving inconsistencies, both weapons should have executed properly assertEquals(2, env.calculateProjectileCount(0)); assertEquals(4, env.getTimewarpCount()); // should detect the inconsistency and resolve it // (new Projectile() should execute properly) while(env.getTick(1) < t+env.econfig.TRAILING_STATE_DELAY) { env.tick(); } assertEquals(5, env.getTimewarpCount()); env.tick(); env.tick(); assertEquals(5, env.getTimewarpCount()); assertEquals(3, env.calculateProjectileCount(0)); assertContains(env.projectileIterator(0), firstProjectile); // expire the projectiles while(env.getTick(env.econfig.TRAILING_STATES-1) < t + 3 + 100 + env.econfig.HIGHEST_DELAY) { env.tick(); } assertEquals(0, env.calculateProjectileCount(0)); } @Test public void testProjectileCreationCoupled() { SimpleEnvironment env = (SimpleEnvironment) this.env; try { List<Object> yamlDocuments = GameConfig.loadYaml("" + "- weapon-slot-gun: test-noreload\n" + " weapon-slot-bomb: test-reload\n" + " projectile-expiration-ticks: 100\n" + " weapon-projectiles: 50\n" + " projectile-angle: [LINEAR, 14702688] # 735134400 / 50" + "- selector: {weapon: test-noreload}\n" + " weapon-switch-delay: 0\n" + "- selector: {weapon: test-reload}\n" + " weapon-switch-delay: 4\n" ); env.loadConfig(env.getTick() - env.econfig.HIGHEST_DELAY, "test", yamlDocuments); } catch (Exception ex) { throw new Error(ex); } env.actorNew(1, ACTOR_FIRST, 1234, "warbird"); env.actorWarp(1, ACTOR_FIRST, false, 1000, 90, 0, 0, 0); env.actorWeapon(2, ACTOR_FIRST, WEAPON_SLOT.GUN, false, 0, 0, 0 ,0 ,0); env.tick(); // 1 env.tick(); // 2 assertEquals(1 * 50, env.calculateProjectileCount(0)); env.timewarp(1); assertEquals(1 * 50, env.calculateProjectileCount(0)); env.timewarp(env.econfig.TRAILING_STATES-1); assertEquals(1 * 50, env.calculateProjectileCount(0)); while(env.getTick() < env.econfig.TRAILING_STATE_DELAY) { env.tick(); } env.tick(); env.tick(); assertEquals(1 * 50, env.calculateProjectileCount(0)); assertEquals(1 * 50, env.calculateProjectileCount(1)); env.timewarp(1); assertEquals(1 * 50, env.calculateProjectileCount(0)); assertEquals(1 * 50, env.calculateProjectileCount(1)); env.timewarp(env.econfig.TRAILING_STATES-1); assertEquals(1 * 50, env.calculateProjectileCount(0)); assertEquals(1 * 50, env.calculateProjectileCount(1)); assertEquals(4, env.getTimewarpCount()); // 2 weapons, one of them has a weapon switch delay // execute them in the wrong order so that only 1 fires in state 0, // but both fire in state 1 long t = env.getTick(); env.tick(); env.tick(); // make sure they are both late env.actorWeapon(t+2, ACTOR_FIRST, WEAPON_SLOT.BOMB, false, 0, 0, 0, 0, 0); // bomb has switch delay, env.actorWeapon(t+1, ACTOR_FIRST, WEAPON_SLOT.GUN, false, 0, 0, 0, 0, 0); // gun does not // after resolving inconsistencies, both weapons should have executed properly assertEquals(2 * 50, env.calculateProjectileCount(0)); assertEquals(4, env.getTimewarpCount()); // should detect the inconsistency and resolve it // (new Projectile() should execute properly) while(env.getTick(1) < t+env.econfig.TRAILING_STATE_DELAY) { env.tick(); } assertEquals(5, env.getTimewarpCount()); env.tick(); env.tick(); assertEquals(5, env.getTimewarpCount()); assertEquals(3 * 50, env.calculateProjectileCount(0)); // expire the projectiles while(env.getTick(env.econfig.TRAILING_STATES-1) < t + 3 + 100 + env.econfig.HIGHEST_DELAY) { env.tick(); } assertEquals(0 * 50, env.calculateProjectileCount(0)); } private void testExplosionEventShort_assertEvent(int state) { SimpleEnvironment env = (SimpleEnvironment) this.env; int events = 0; for (EventPublic e : env.eventIterable()) { ++events; if (e instanceof ProjectileExplosionPublic) { ProjectileExplosionPublic ev = (ProjectileExplosionPublic) e; assert ev.hasOccurred(state); assertEquals(ACTOR_FIRST, ev.getFireActor(state)); assertEquals(ACTOR_SECOND, ev.getHitActor(state)); assertEquals(4, ev.getOccurredAt(state)); PhysicsPoint pos = new PhysicsPoint(); ev.getPosition(state, pos); assertPointEquals(45664, 90, pos); PhysicsPositionVector posv = new PhysicsPositionVector(); // The projectile is removed at the moment of impact, it no longer has a position assertFalse(ev.getProjectile(state).getHistoricPosition(posv, ev.getOccurredAt(state), true)); assertFalse(ev.getProjectile(state).getHistoricPosition(posv, ev.getOccurredAt(state), false)); assertTrue(ev.getProjectile(state).getHistoricPosition(posv, ev.getOccurredAt(state) - 1, true)); assertPointEquals(35336,90, posv.pos); assertTrue(ev.getProjectile(state).getHistoricPosition(posv, ev.getOccurredAt(state) - 1, false)); assertPointEquals(35336,90, posv.pos); } else if (e instanceof ActorDiedPublic) { ActorDiedPublic ev = (ActorDiedPublic) e; assert ev.hasOccurred(state); assertEquals(4, ev.getOccurredAt(state)); assertEquals(ACTOR_SECOND, ev.getDied(state)); assert ev.getCause(state) instanceof ProjectileExplosionPublic; } else { assert false; } } assertEquals(2, events); } @Test public void testExplosionEventShort() { SimpleEnvironment env = (SimpleEnvironment) this.env; // Short time between fire and explosion (less than TRAILING_STATE_DELAY) try { List<Object> yamlDocuments = GameConfig.loadYaml("" + "- weapon-projectiles: 1\n" + " projectile-hit-ship: true\n" + " projectile-angle-relative: true\n" + " projectile-speed: 20000\n" + " projectile-damage: 2000\n" + " ship-energy: 1500\n" ); env.loadConfig(env.getTick() - env.econfig.HIGHEST_DELAY, "test", yamlDocuments); } catch (Exception ex) { throw new Error(ex); } env.actorNew(1, ACTOR_FIRST, 1234, "warbird"); env.actorWarp(1, ACTOR_FIRST, false, 1000, 90, 0, 0, EnvironmentConf.ROTATION_1_4TH); env.actorNew(1, ACTOR_SECOND, 4321, "warbird"); env.actorWarp(1, ACTOR_SECOND, false, 60000, 90, 0, 0, 0); env.actorWeapon(2, ACTOR_FIRST, WEAPON_SLOT.GUN, false, 0, 0, 0 , 0, 0); env.tick(); // 1 env.tick(); // 2 env.tick(); // 3 env.tick(); // 4, should hit at this tick testExplosionEventShort_assertEvent(0); env.timewarp(1); testExplosionEventShort_assertEvent(0); while (env.getTick() < env.econfig.TRAILING_STATE_DELAY + 4) { env.tick(); } testExplosionEventShort_assertEvent(0); testExplosionEventShort_assertEvent(1); env.timewarp(1); testExplosionEventShort_assertEvent(0); testExplosionEventShort_assertEvent(1); env.timewarp(env.econfig.TRAILING_STATES-1); testExplosionEventShort_assertEvent(0); testExplosionEventShort_assertEvent(1); } private void testExplosionEventLong_assertEvent(int state) { SimpleEnvironment env = (SimpleEnvironment) this.env; int events = 0; for (EventPublic e : env.eventIterable()) { ++events; if (e instanceof ProjectileExplosionPublic) { ProjectileExplosionPublic ev = (ProjectileExplosionPublic) e; assert ev.hasOccurred(state); assertEquals(ACTOR_FIRST, ev.getFireActor(state)); assertEquals(ACTOR_SECOND, ev.getHitActor(state)); assertEquals(21, ev.getOccurredAt(state)); PhysicsPoint pos = new PhysicsPoint(); ev.getPosition(state, pos); assertPointEquals(385664, 90, pos); PhysicsPositionVector posv = new PhysicsPositionVector(); // The projectile is removed at the moment of impact, it no longer has a position assertFalse(ev.getProjectile(state).getHistoricPosition(posv, ev.getOccurredAt(state), true)); assertFalse(ev.getProjectile(state).getHistoricPosition(posv, ev.getOccurredAt(state), false)); assertTrue(ev.getProjectile(state).getHistoricPosition(posv, ev.getOccurredAt(state) - 1, true)); assertPointEquals(375336, 90, posv.pos); } else if (e instanceof ActorDiedPublic) { ActorDiedPublic ev = (ActorDiedPublic) e; assert ev.hasOccurred(state); assertEquals(21, ev.getOccurredAt(state)); assertEquals(ACTOR_SECOND, ev.getDied(state)); assert ev.getCause(state) instanceof ProjectileExplosionPublic; } else { assert false; } } assertEquals(2, events); } @Test public void testExplosionEventLong() { SimpleEnvironment env = (SimpleEnvironment) this.env; // Long time between fire and explosion (more than TRAILING_STATE_DELAY) try { List<Object> yamlDocuments = GameConfig.loadYaml("" + "- weapon-projectiles: 1\n" + " projectile-hit-ship: true\n" + " projectile-angle-relative: true\n" + " projectile-speed: 20000\n" + " projectile-damage: 2000\n" + " ship-energy: 1500\n" ); env.loadConfig(env.getTick() - env.econfig.HIGHEST_DELAY, "test", yamlDocuments); } catch (Exception ex) { throw new Error(ex); } env.actorNew(1, ACTOR_FIRST, 1234, "warbird"); env.actorWarp(1, ACTOR_FIRST, false, 1000, 90, 0, 0, EnvironmentConf.ROTATION_1_4TH); env.actorNew(1, ACTOR_SECOND, 4321, "warbird"); env.actorWarp(1, ACTOR_SECOND, false, 400000, 90, 0, 0, 0); env.actorWeapon(2, ACTOR_FIRST, WEAPON_SLOT.GUN, false, 0, 0, 0 , 0, 0); // modify this test case if TRAILING_STATE_DELAY changes assert env.econfig.TRAILING_STATE_DELAY == 32; while (env.getTick() < 38) { env.tick(); } testExplosionEventLong_assertEvent(0); env.timewarp(1); testExplosionEventLong_assertEvent(0); while (env.getTick() < env.econfig.TRAILING_STATE_DELAY + 38) { env.tick(); } testExplosionEventLong_assertEvent(0); testExplosionEventLong_assertEvent(1); env.timewarp(1); testExplosionEventLong_assertEvent(0); testExplosionEventLong_assertEvent(1); env.timewarp(env.econfig.TRAILING_STATES-1); testExplosionEventLong_assertEvent(0); testExplosionEventLong_assertEvent(1); } }