/* * 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.events; import aphelion.shared.gameconfig.GCStringList; import aphelion.shared.physics.SimpleEnvironment; import aphelion.shared.physics.entities.Actor; import aphelion.shared.physics.config.WeaponConfig; import aphelion.shared.physics.entities.Projectile; import aphelion.shared.physics.entities.ProjectilePublic; import aphelion.shared.physics.events.pub.ProjectileExplosionPublic; import aphelion.shared.physics.State; import aphelion.shared.physics.entities.*; import aphelion.shared.physics.valueobjects.PhysicsPoint; import aphelion.shared.physics.valueobjects.PhysicsShipPosition; import aphelion.shared.swissarmyknife.SwissArmyKnife; import java.util.*; import java.util.logging.Logger; /** * A projectile exploded for the final time. * @author Joris */ public class ProjectileExplosion extends Event implements ProjectileExplosionPublic { private static final Logger log = Logger.getLogger("aphelion.shared.physics"); private final History[] history; private final ProjectileFactory chained_factory = new ProjectileFactory(); public ProjectileExplosion(SimpleEnvironment env, Key key) { super(env, key); history = new History[env.econfig.TRAILING_STATES]; for (int a = 0; a < env.econfig.TRAILING_STATES; ++a) { history[a] = new History(); } } @Override public Event cloneWithoutHistory(SimpleEnvironment env) { return new ProjectileExplosion(env, (Key) this.key); } public void execute(long tick, State state, Projectile explodedProjectile, EXPLODE_REASON reason, Actor actorHit, PhysicsPoint tileHit) { super.execute(tick, state); assert state == explodedProjectile.state; boolean doSplash = false; GCStringList chainWeapon; switch(reason) { case EXPIRATION: assert actorHit == null; assert tileHit == null; if (explodedProjectile.cfg(explodedProjectile.config.projectile_expirationExplode, tick)) { doSplash = true; } chainWeapon = explodedProjectile.config.projectile_expirationChainWeapon; break; case PROX_DELAY: assert actorHit == null; assert tileHit == null; doSplash = true; chainWeapon = explodedProjectile.config.projectile_proxChainWeapon; break; case PROX_DIST: assert actorHit == null; assert tileHit == null; doSplash = true; chainWeapon = explodedProjectile.config.projectile_proxChainWeapon; break; case HIT_TILE: assert actorHit == null; assert tileHit != null; assert tileHit.set; doSplash = true; chainWeapon = explodedProjectile.config.projectile_hitTileChainWeapon; break; case HIT_SHIP: assert actorHit != null; assert tileHit == null; doSplash = true; chainWeapon = explodedProjectile.config.projectile_hitShipChainWeapon; break; default: assert false; return; } if (SwissArmyKnife.assertEnabled) { for (History hist : history) { if (hist.projectile == null) { continue; } assert hist.projectile.crossStateList == explodedProjectile.crossStateList; assert hist.projectile.crossStateList[state.id] == explodedProjectile; } } History hist = history[state.id]; hist.set = true; hist.reason = reason; explodedProjectile.softRemove(tick); if (reason == EXPLODE_REASON.HIT_TILE || reason == EXPLODE_REASON.HIT_SHIP) { // it hit an actor or a tile // remove our coupled projectiles for (Projectile coupledProjectile : explodedProjectile.coupled) { if (coupledProjectile == explodedProjectile) { continue; } if (coupledProjectile.isNonExistent(tick)) { continue; } if (reason == EXPLODE_REASON.HIT_SHIP) { if (!explodedProjectile.cfg(explodedProjectile.config.projectile_hitShipCoupled, tick)) { continue; } } else if (reason == EXPLODE_REASON.HIT_TILE) { if (!explodedProjectile.cfg(explodedProjectile.config.projectile_hitTileCoupled, tick)) { continue; } } else { assert false; } coupledProjectile.softRemove(tick); hist.coupledProjectiles.add(coupledProjectile); } } hist.projectile = explodedProjectile; hist.hit_x = explodedProjectile.pos.pos.x; hist.hit_y = explodedProjectile.pos.pos.y; hist.hit_vel_x = explodedProjectile.pos.vel.x; hist.hit_vel_y = explodedProjectile.pos.vel.y; hist.hit_tile = tileHit != null; hist.hit_tile_x = tileHit == null ? 0 : tileHit.x; hist.hit_tile_x = tileHit == null ? 0 : tileHit.y; hist.tick = tick; hist.hit_pid = actorHit == null ? 0 : actorHit.pid; hist.fire_pid = explodedProjectile.owner == null ? 0 : explodedProjectile.owner.pid; WeaponConfig explodedConfig = explodedProjectile.config; int damage = explodedProjectile.cfg(explodedConfig.projectile_damage, tick) * 1024; if (reason == EXPLODE_REASON.HIT_SHIP) { actorHit.energy.addRelativeValue(Actor.ENERGY_SETTER.OTHER.id, tick, -damage); if (actorHit.energy.get(tick) <= 0) { hist.killed_pids.add(actorHit.pid); actorHit.died(tick); } actorHit.applyEmp(tick, explodedProjectile.cfg(explodedConfig.projectile_empTicks, tick)); } if (doSplash) { // apply splash damage on all actors except the one we // hit directly (this is different from continum) explodedProjectile.doSplashDamage(actorHit, tick, hist.killed_pids); explodedProjectile.doSplashEmp(actorHit, tick); } for (Integer killed_pid : hist.killed_pids) { ActorDied.Key diedKey = new ActorDied.Key((Key) this.key, new ActorKey(killed_pid)); ActorDied diedEvent = (ActorDied) env.findEvent(diedKey); if (diedEvent == null) { diedEvent = new ActorDied(env, diedKey); } state.env.registerEvent(diedEvent); diedEvent.execute(tick, state, state.actors.get(new ActorKey(killed_pid)), this, explodedProjectile.owner); } // Fire a chained weapon String weapon = chainWeapon.isSet() ? explodedProjectile.cfg(chainWeapon, tick) : ""; if (!weapon.isEmpty()) { // todo projectile.owner == null WeaponConfig chainConfig = explodedProjectile.owner.config.getWeaponConfig(weapon); int projectile_count = SwissArmyKnife.clip(chainConfig.projectiles.get(), 1, 1024); chained_factory.hintProjectileCount(projectile_count); PhysicsShipPosition actorPos = new PhysicsShipPosition(); actorPos.setPositionVectory(explodedProjectile.pos); actorPos.rot = 0; // todo PhysicsTrig.atan2() based on velocity vector actorPos.rot_snapped = 0; Projectile[] projectiles = chained_factory.constructProjectiles( state, explodedProjectile.owner, tick, chainConfig, projectile_count, explodedProjectile, 0 ); assert projectiles.length == projectile_count; for (int p = 0; p < projectile_count; ++p) { Projectile projectile = projectiles[p]; projectile.initFire(tick, actorPos); projectile.register(); projectile.updatedPosition(tick); // dead reckon current position so that it is no longer late // the position at the tick of this operation should not be dead reckoned, therefor +1 projectile.performDeadReckoning(state.env.getMap(), tick + 1, state.tick_now - tick, true); } } } @Override public boolean isConsistent(State older, State newer) { History histOlder = history[older.id]; History histNewer = history[newer.id]; if (!histNewer.set) { return !histOlder.set; } if (!histOlder.set) { if (histNewer.tick > older.tick_now) { return true; // this event has not had the chance to execute on the older state yet. } } return histNewer.isConsistent(histOlder); } @Override public void resetExecutionHistory(State state, State resetTo, Event resetToEvent) { History histFrom = ((ProjectileExplosion) resetToEvent).history[resetTo.id]; History histTo = history[state.id]; histTo.set(histFrom, state); } @Override public void resetToEmpty(State state) { History histTo = history[state.id]; histTo.set(new History(), state); } @Override public long getOccurredAt(int stateid) { History hist = history[stateid]; if (!hist.set) { return 0; // use hasOccurred first } return hist.tick; } @Override public boolean hasOccurred(int stateid) { History hist = history[stateid]; return hist.set; } @Override public EXPLODE_REASON getReason(int stateid) { History hist = history[stateid]; return hist.reason; } @Override public void getPosition(int stateid, PhysicsPoint pos) { History hist = history[stateid]; if (hist.set) { pos.set(hist.hit_x, hist.hit_y); } else { pos.unset(); } } @Override public void getVelocity(int stateid, PhysicsPoint vel) { History hist = history[stateid]; if (hist.set) { vel.set(hist.hit_vel_x, hist.hit_vel_y); } else { vel.unset(); } } @Override public int getHitActor(int stateid) { History hist = history[stateid]; return hist.hit_pid; } @Override public int getFireActor(int stateid) { History hist = history[stateid]; return hist.fire_pid; } @Override public ProjectilePublic getProjectile(int stateid) { History hist = history[stateid]; return hist.projectile; } @Override public Iterable<Integer> getKilled(int stateid) { History hist = history[stateid]; return Collections.unmodifiableList(hist.killed_pids); } @Override public int getKilledSize(int stateid) { History hist = history[stateid]; return hist.killed_pids.size(); } @Override public void getHitTile(int stateid, PhysicsPoint tile) { History hist = history[stateid]; if (hist.hit_tile) { tile.set(hist.hit_tile_x, hist.hit_tile_y); } else { tile.unset(); } } @Override public Iterable<ProjectilePublic> getCoupledProjectiles(int stateid) { History hist = history[stateid]; return (List<ProjectilePublic>) (Object) hist.coupledProjectiles; } @Override public int getCoupledProjectilesSize(int stateid) { History hist = history[stateid]; return hist.coupledProjectiles.size(); } private static class History { boolean set = false; EXPLODE_REASON reason; int hit_x; int hit_y; int hit_pid; // (direct hit) boolean hit_tile; int hit_tile_x; int hit_tile_y; int fire_pid; long tick; final List<Integer> killed_pids = new ArrayList<>(4); // not part of consistency check: int hit_vel_x; int hit_vel_y; Projectile projectile; // coupled projectiles that were removed uring this event final List<Projectile> coupledProjectiles = new ArrayList<>(4); public boolean isConsistent(History other) { if (this.set != other.set) { return false; } if (this.reason != other.reason) { return false; } if (this.hit_x != other.hit_x) { return false; } if (this.hit_y != other.hit_y) { return false; } if (this.hit_tile != other.hit_tile) { return false; } if (this.hit_tile_x != other.hit_tile_x) { return false; } if (this.hit_tile_x != other.hit_tile_x) { return false; } if (this.hit_pid != other.hit_pid) { return false; } if (this.fire_pid != other.fire_pid) { return false; } if (this.tick != other.tick) { return false; } if (!this.killed_pids.equals(other.killed_pids)) { return false; } return true; } public void set(History other, State myState) { set = other.set; reason = other.reason; hit_x = other.hit_x; hit_y = other.hit_y; hit_vel_x = other.hit_vel_x; hit_vel_y = other.hit_vel_y; hit_tile = other.hit_tile; hit_tile_x = other.hit_tile_x; hit_tile_y = other.hit_tile_y; hit_pid = other.hit_pid; fire_pid = other.fire_pid; tick = other.tick; killed_pids.clear(); killed_pids.addAll(other.killed_pids); projectile = other.projectile == null ? null : other.projectile.findInOtherState(myState); } } public static final class Key implements EventKey { private final ProjectileKey causedBy; public Key(ProjectileKey causedBy) { if (causedBy == null) { throw new IllegalArgumentException(); } this.causedBy = causedBy; } @Override public int hashCode() { int hash = 3; hash = 83 * hash + Objects.hashCode(this.causedBy); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof Key)) { return false; } final Key other = (Key) obj; if (!Objects.equals(this.causedBy, other.causedBy)) { return false; } return true; } @Override public String toString() { return "ProjectileExplosion.Key{" + "causedBy=" + causedBy + '}'; } } }