/*
* 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.event.Deadlock;
import aphelion.shared.physics.entities.*;
import aphelion.shared.physics.operations.*;
import aphelion.shared.event.TickEvent;
import aphelion.shared.gameconfig.*;
import aphelion.shared.net.protobuf.GameOperation;
import aphelion.shared.physics.events.*;
import aphelion.shared.physics.events.pub.EventPublic;
import aphelion.shared.physics.operations.pub.OperationPublic;
import aphelion.shared.physics.valueobjects.PhysicsMovement;
import aphelion.shared.physics.valueobjects.PhysicsShipPosition;
import aphelion.shared.physics.valueobjects.PhysicsWarp;
import aphelion.shared.resource.ResourceDB;
import aphelion.shared.swissarmyknife.LinkedListEntry;
import aphelion.shared.swissarmyknife.LinkedListHead;
import aphelion.shared.swissarmyknife.ThreadSafe;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Physics Engine based on TSS.
*
* All position values are in pixels * 1024. The Top Left corner of the screen is considered 0,0. All time values are in
* ticks (10ms by default) Actor ids (pid) are unique, and should be for a while (if a player leaves, do not immediately
* reuse his pid).
*
* All position values (pos, velocity) must be between -(2^30-1) and 2^30-1;
*
* Trailing State Synchronisation:
*
* + Operations arrive by network / or are generated at the client.
*
* + An operation is
* placed on the pending list for each trailing state (PhysicsState).
*
* + Late operations: These are executed immediately. (operation.tick lte state.tick_now).
*
* + Early operations: These are placed on a todo list (operation.tick gt state.tick_now).
*
* + As an operation executes, it stores the changes it made to the game state. This is stored for each trailing state.
*
* + At some point (there are multiple viable strategies), each trailing state looks at the changes in game state that
* an operation produced. These changes are compared with changed recorded at the directly previous state.
*
* + Very late operations: If an operation is very late (so late that it does not get added to at least 1 state todo
* list, at least 2 on the server). It is ignored. This means lost weapons, etc.
*
* + Very early operations: If an operation is very early (based on a setting). It is ignored, this is to prevent
* cheating.
*
* + Any operation that is older than the last trailing state is removed (HISTORY_DURATION)
*
* + Movement operations consist of the ActorWarp(position,velocity,rotation,time) and the
* ActorMove(up,down,left,right,time) operations. ActorWarp may only be generated by the server. And is sent upon spawn,
* special items and periodically to prevent synchronization mistakes. ActorMove is generated by the client and consists
* of what keys the player pressed for that tick. ActorMove is sent to all clients. Multiple moves on the same tick are
* ignored.
*
*
* + Weapon fire consists of the WeaponFire(position,velocity,weaponid,time) operation. In states where the operation is
* late, it is executed using the position and velocity values. in these states, there is no check for cheating. And the
* "current" position of the projectile is made up to date using dead reckoning. In states where the operation is early,
* position and velocity are ignored. The position and direction of weapon fire are determined by the movement commands
* the player has sent. Things like weapon delay and energy are also checked to prevent cheating.
*
* Operations vs Events:
* + An operation is reexecuted in a timewarp (added back to the todo list). An event is never reexecuted
* unless the original cause occurs again.
* + Both operations and events have a single instance for the entire environment. Operations are placed in a
* todo or history list for each state. Events only in a history list.
* + Both execute consistency checks.
* + Operations always have an external cause (user hits a key, something recieved over the network).
* Events always have an internal cause (previous operation triggered a weapon fire, the event is the
* projectile hitting someone).
*
* @author Joris
*/
public class SimpleEnvironment implements TickEvent, PhysicsEnvironment
{
private static final Logger log = Logger.getLogger("aphelion.shared.physics");
public final EnvironmentConf econfig;
long tick_now;
long tickedAt_nano;
private long lastTimewarp_nano = 0;
private final PhysicsMap map;
final State[] trailingStates; //Delay = index * TRAILING_STATE_DURATION
final LinkedListHead<Event> eventHistoryList = new LinkedListHead<>(); // ordered by the order of appending
final HashMap<EventKey, Event> eventHistory = new HashMap<>();
/** If set, addOperation will use this queue instead of handling the operation directly. */
private final ConcurrentLinkedQueue<Operation> threadedAddOperation;
final AtomicLong polledAddOperationsCount = new AtomicLong(0); // used in test cases
private long nextOpSeq = 1;
private final AtomicLong nextOpSeqSafe = new AtomicLong(1);
private long timewarps = 0;
/** should only be set by test cases, TODO: get rid of me and use doReexecuteDirtyPositionPath instead */
@Deprecated
public boolean testcaseImmediateMove;
public SimpleEnvironment(boolean server, PhysicsMap map)
{
this(new EnvironmentConf(server), map, false);
}
public SimpleEnvironment(EnvironmentConf econfig, PhysicsMap map, boolean threadedAddOperation)
{
this.econfig = econfig;
this.map = map;
trailingStates = new State[econfig.TRAILING_STATES];
for (int s = 0; s < econfig.TRAILING_STATES; s++)
{
trailingStates[s] = new State(
this,
s,
econfig.FIRST_STATE_DELAY + s * econfig.TRAILING_STATE_DELAY,
s < econfig.TRAILING_STATES - 2, // do not allow hints in the last 2 trailing states
s == econfig.TRAILING_STATES - 1 // force late config execution in the last trailing state
);
trailingStates[s].tick_now = this.tick_now - trailingStates[s].delay;
}
if (threadedAddOperation)
{
this.threadedAddOperation = new ConcurrentLinkedQueue<>();
}
else
{
this.threadedAddOperation = null;
}
}
@Override
public EnvironmentConf getConfig()
{
return econfig;
}
/** Use this method to find an event before you construct a new one to
* make sure it has not been created already.
* @param key
* @return
*/
public @Nullable Event findEvent(EventKey key)
{
return this.eventHistory.get(key);
}
/** Register the occurrence of an event.
* This should only be called by physics.
* Call me anytime you execute an event.
* @param event
*/
public void registerEvent(Event event)
{
assert event.env == this;
if (event.inEnvList) { return; }
assert !eventHistory.containsKey(event.key);
event.inEnvList = true;
eventHistoryList.append(event.link);
eventHistory.put(event.key, event);
}
public void unregisterEvent(Event event)
{
assert event.env == this;
event.link.remove();
eventHistory.remove(event.key);
event.inEnvList = false;
}
public @Nullable Event findForeignEvent(Event event)
{
if (event.env == this)
{
return event;
}
else
{
return this.eventHistory.get(event.key);
}
}
@Override
public void skipForward(long tick)
{
if (this.tick_now != 0)
{
throw new IllegalStateException();
}
this.tick_now = tick;
}
@Override
public long getTick()
{
return tick_now;
}
@Override
public long getTickedAt()
{
return tickedAt_nano;
}
public long getTick(int stateid)
{
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
return trailingStates[stateid].tick_now;
}
public int getState(long tick)
{
int ticks_ago = (int) (this.tick_now - tick);
int state_id = ticks_ago / econfig.TRAILING_STATE_DELAY;
if (state_id < 0 || state_id >= econfig.TRAILING_STATES)
{
return -1;
}
return state_id;
}
@Override
public void tick(long eventloop_tick) // interface
{
tick();
}
public static int debug_current_state = -1;
public boolean hasThreadedAddOperation()
{
return threadedAddOperation != null && !threadedAddOperation.isEmpty();
}
public void pollThreadedAddOperation()
{
pollThreadedAddOperation(Deadlock.noopTicker);
}
public void pollThreadedAddOperation(Deadlock.DeadlockTicker deadlockTicker)
{
if (threadedAddOperation != null)
{
Operation op;
while ((op = threadedAddOperation.poll()) != null)
{
assert op.env == this;
polledAddOperationsCount.getAndAdd(1);
for (int s = 0; s < econfig.TRAILING_STATES; s++)
{
trailingStates[s].addOperation(op);
}
deadlockTicker.tickDeadlock();
}
}
}
@Override
public void tick()
{
tick(Deadlock.noopTicker);
}
private boolean firstTick = true;
public void tick(Deadlock.DeadlockTicker deadlockTicker)
{
if (firstTick)
{
firstTick = false;
log.log(Level.INFO, "{0}: First tick for environment", econfig.logString);
econfig.log();
}
pollThreadedAddOperation(deadlockTicker);
doReexecuteDirtyPositionPath();
++tick_now;
tickedAt_nano = System.nanoTime();
for (int s = 0; s < econfig.TRAILING_STATES; s++)
{
debug_current_state = s;
long tick = this.tick_now - this.trailingStates[s].delay;
this.trailingStates[s].tick(tick);
}
consistencyCheck(deadlockTicker);
removeOldHistory();
}
public void doReexecuteDirtyPositionPath()
{
for (int s = 0; s < econfig.TRAILING_STATES; s++)
{
debug_current_state = s;
this.trailingStates[s].doReexecuteDirtyPositionPath();
}
}
public boolean consistencyCheck()
{
return consistencyCheck(Deadlock.noopTicker);
}
public boolean consistencyCheck(Deadlock.DeadlockTicker deadlockTicker)
{
for (int s = econfig.TRAILING_STATES - 1; s > 0; --s)
{
State older = this.trailingStates[s];
State newer = this.trailingStates[s - 1];
assert older.env == this;
assert newer.env == this;
if (!older.needTimewarpToThisState)
{
older.needTimewarpToThisState = !areStatesConsistent(older, newer);
}
if (older.needTimewarpToThisState)
{
if (this.lastTimewarp_nano == 0
|| this.tickedAt_nano - this.lastTimewarp_nano >= econfig.TIMEWARP_EVERY_NANO)
{
// state is not consistent with the older state
// fix all higher states
timewarp(s);
return false;
}
}
}
return true;
}
/** Reset all states to the given state and re-simulate.
* @param stateid The state to reset lower (more recent) states to
*/
public void timewarp(int stateid)
{
timewarp(stateid, Deadlock.noopTicker);
}
/** Reset all states to the given state and re-simulate.
* @param stateid The state to reset lower (more recent) states to
* @param deadlockTicker
*/
public void timewarp(int stateid, Deadlock.DeadlockTicker deadlockTicker)
{
long start = System.nanoTime();
++timewarps;
for (int s = stateid; s > 0; --s)
{
State older = this.trailingStates[s];
State newer = this.trailingStates[s - 1];
older.needTimewarpToThisState = false;
newer.timewarp(older, deadlockTicker);
}
long end = System.nanoTime();
log.log(Level.WARNING, "Time Warp {0}: to state {1} in {2}ms. Tick {3} to {4}.", new Object[] {
econfig.logString,
stateid,
(end - start) / 1_000_000.0,
this.trailingStates[0].tick_now,
this.trailingStates[stateid].tick_now,
});
// At the end of execution, so that if timewarps become long running,
// they will not cause a timewarp right again.
lastTimewarp_nano = System.nanoTime();
}
/** The number of timewarps performed since the start of environment.
* @return */
public long getTimewarpCount()
{
return timewarps;
}
private void removeOldHistory() // (or recycle)
{
LinkedListEntry<Operation> linkOp, linkOpNext;
LinkedListEntry<Event> linkEv, linkEvNext;
Operation op;
State oldestState;
// If an operation has been executed in the oldest state,
// it can be removed.
oldestState = this.trailingStates[econfig.TRAILING_STATES - 1];
linkOp = oldestState.history.first;
while (linkOp != null)
{
linkOpNext = linkOp.next;
op = linkOp.data;
assert op.env == this;
if (op.tick < this.tick_now - econfig.KEEP_OPERATIONS_FOR_TICKS)
{
// remove the operation everywhere
for (int s = 0; s < econfig.TRAILING_STATES; ++s)
{
trailingStates[s].operations.remove(op.key);
op.link[s].remove();
}
}
else
{
// do not remove operations which were just added to the history in the oldest state
// (op.tick == oldestState.tick_now)
// remove it next tick
break;
}
linkOp = linkOpNext;
}
linkEv = this.eventHistoryList.first;
while (linkEv != null)
{
linkEvNext = linkEv.next;
Event event = linkEv.data;
assert event.env == this;
if (event.isOld(this.tick_now - econfig.KEEP_EVENTS_FOR_TICKS))
{
event.remove();
}
linkEv = linkEvNext;
}
}
/**
* Full consistency check for a state
*/
private boolean areStatesConsistent(State older, State newer)
{
assert newer.id == older.id - 1;
// Go through the actors of the newer state
// There is no need to verify if older state has actors we do not have;
// The operation history check will take care of this.
PhysicsShipPosition newerPosition = new PhysicsShipPosition();
PhysicsShipPosition olderPosition = new PhysicsShipPosition();
for (int i = 0; i < newer.actorsList.size(); ++i)
{
Actor actorNewer = newer.actorsList.get(i);
if (actorNewer.createdAt_tick <= older.tick_now)
{
// this works because histories overlap
actorNewer.getHistoricPosition(newerPosition, older.tick_now, false);
actorNewer.getHistoricPosition(olderPosition, older.tick_now, true);
// note if this method is not called every tick, all the tick since
// the last call of areStatesConsistent() will have to be checked,
// instead of only the current
if (!olderPosition.equals(newerPosition))
{
log.log(Level.WARNING, "{0}: Inconsistency in position/velocity/rotation, actor {1}", new Object[]{
econfig.logString,
actorNewer.pid
});
return false;
}
if (actorNewer.getHistoricEnergy(older.tick_now, false) !=
actorNewer.getHistoricEnergy(older.tick_now, true))
{
log.log(Level.WARNING, "{0}: Inconsistency in energy, actor {1}", new Object[]{
econfig.logString,
actorNewer.pid
});
return false;
}
}
}
// Go through the history of the newer state
for (LinkedListEntry<Operation> linkOp = newer.history.first; linkOp != null; linkOp = linkOp.next)
{
assert linkOp.head == newer.history;
if (!linkOp.data.isConsistent(older, newer))
{
log.log(Level.WARNING, "{0}: Inconsistency in operation {1}", new Object[]{
econfig.logString,
linkOp.data.getClass().getName()
});
return false;
}
}
// go through the events of the newer state
for (LinkedListEntry<Event> linkEv = eventHistoryList.first; linkEv != null; linkEv = linkEv.next)
{
if (!linkEv.data.isConsistent(older, newer))
{
log.log(Level.WARNING, "{0}: Inconsistency in event {1}", new Object[]{
econfig.logString,
linkEv.data.getClass().getName()
});
return false;
}
}
return true;
}
/** .
*
* @return false if the operation was too old and has been ignored
*/
@ThreadSafe
boolean addOperation(Operation operation)
{
final long now = this.tick_now;
assert operation.env == this;
if (operation.ignorable && operation.tick <= now - econfig.HIGHEST_DELAY)
{
log.log(Level.WARNING, "Operation about actor {1} at {2} dropped, too old ({0}). now = {3}", new Object[] {
econfig.logString,
operation.getPid(),
operation.getTick(),
now
});
return false;
}
if (threadedAddOperation == null)
{
for (int s = 0; s < econfig.TRAILING_STATES; s++)
{
trailingStates[s].addOperation(operation);
}
}
else
{
threadedAddOperation.add(operation);
}
return true;
}
@Override
public ActorPublic getActor(int pid)
{
return getActor(pid, false);
}
@Override
public ActorPublic getActor(int pid, boolean nofail)
{
return getActor(pid, 0, nofail);
}
/**
* Look up an actor by pid. The returned value is a wrapper (PhysicsActor) around the real actor class
* (PhysicsActorPrivate). This wrapper is unaffected by things such as temporary removal of the actor.
*
* @param pid
* @param nofail if set, an actor wrapper is always returned, even if the actor does not exist at the moment.
* @param stateid A state id, 0 for the most current state. TRAILING_STATES-1 for the oldest.
* This lets you get a wrapper before the actual actor creation operation has been executed (which may take a
* while, depending on the timestamp).
* @return
*/
public ActorPublic getActor(int pid, int stateid, boolean nofail)
{
Actor actor;
State state;
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
if (pid == 0)
{
// pid 0 is a special value that is never assigned
// always return null even if nofail is set
return null;
}
state = trailingStates[stateid];
actor = state.actors.get(new ActorKey(pid));
if (actor == null)
{
if (nofail)
{
return new ActorPublicImpl(pid, state);
}
else
{
return null;
}
}
return actor.publicWrapper;
}
@Override
public Iterator<ActorPublic> actorIterator()
{
return actorIterator(0);
}
/** Loop over all the known actors in a given state using an iterator.
* It is OK to reference PhysicsActorPublic for long periods of time.
* @param stateid A state id, 0 for the most current state. TRAILING_STATES-1 for the oldest.
* @return A read only iterator. Only use this iterator on the main thread, do not store the iterator.
*/
public Iterator<ActorPublic> actorIterator(int stateid)
{
State state;
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
state = trailingStates[stateid];
return new ActorIterator(state.actorsList.iterator(), state);
}
/** Loop over all the known actors in a given state using an iterator.
* It is OK to reference PhysicsActorPublic for long periods of time.
* @return A read only iterator. Only use this iterator on the main thread, do not store the iterator.
*/
@Override
public Iterable<ActorPublic> actorIterable()
{
return actorIterable(0);
}
/** Loop over all the known actors in a given state using an iterator.
* It is OK to reference PhysicsActorPublic for long periods of time.
* @param stateid A state id, 0 for the most current state. TRAILING_STATES-1 for the oldest.
* @return A read only iterator. Only use this iterator on the main thread, do not store the iterator.
*/
public Iterable<ActorPublic> actorIterable(int stateid)
{
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
final State state = trailingStates[stateid];
return new Iterable<ActorPublic>()
{
@Override
public Iterator<ActorPublic> iterator()
{
return new ActorIterator(state.actorsList.iterator(), state);
}
};
}
@Override
public int getActorCount()
{
return getActorCount(0);
}
/** Return the actor count, including those who have been soft deleted.
* This is equal to the number of actors iterated by actorIterator()
* @param stateid
* @return
*/
public int getActorCount(int stateid)
{
State state;
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
state = trailingStates[stateid];
assert state.actors.size() == state.actorsList.size();
return state.actors.size();
}
@Override
public Iterator<ProjectilePublic> projectileIterator()
{
return projectileIterator(0);
}
@SuppressWarnings("unchecked")
public Iterator<ProjectilePublic> projectileIterator(int stateid)
{
State state;
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
state = trailingStates[stateid];
return (Iterator<ProjectilePublic>) (Object) state.projectilesList.iteratorReadOnly();
}
@Override
public Iterable<ProjectilePublic> projectileIterable()
{
return projectileIterable(0);
}
@SuppressWarnings("unchecked")
public Iterable<ProjectilePublic> projectileIterable(int stateid)
{
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
final State state = trailingStates[stateid];
return new Iterable<ProjectilePublic>()
{
@Override
public Iterator<ProjectilePublic> iterator()
{
return (Iterator<ProjectilePublic>) (Object) state.projectilesList.iteratorReadOnly();
}
};
}
@Override
public int calculateProjectileCount()
{
return calculateProjectileCount(0);
}
/** Return the projectile count, including those who have been soft deleted.
* This is equal to the number of projectiles iterated by projectileIterator()
* This method is O(n) time and primarily intended for test cases.
* @param stateid
* @return
*/
public int calculateProjectileCount(int stateid)
{
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
final State state = trailingStates[stateid];
return state.projectilesList.calculateSize();
}
/** Loop over all the operations in the todo list of a state.
* @return A read only iterator. Only use this iterator on the main thread, do not
* store the iterator or any of its values after iterating.
*/
public Iterator<OperationPublic> todoListIterator()
{
return todoListIterator(0);
}
/** Loop over all the operations in the todo list of a state.
* @param stateid A state id, 0 for the most current state. TRAILING_STATES-1 for the oldest.
* @return A read only iterator. Only use this iterator on the main thread, do not
* store the iterator or any of its values after iterating.
*/
@SuppressWarnings("unchecked")
public Iterator<OperationPublic> todoListIterator(int stateid)
{
State state;
if (stateid < 0 || stateid >= econfig.TRAILING_STATES)
{
throw new IllegalArgumentException("Invalid state");
}
state = trailingStates[stateid];
return (Iterator<OperationPublic>) (Object) state.todo.iteratorReadOnly();
}
@Override
public ConfigSelection newConfigSelection()
{
return trailingStates[0].config.newSelection();
}
private OperationKey getNextOperationKey()
{
if (threadedAddOperation == null)
{
return new OperationKey(nextOpSeq++);
}
else
{
return new OperationKey(nextOpSeqSafe.getAndIncrement());
}
}
@Override
@ThreadSafe
public void loadConfig(long tick, String fileIdentifier, List yamlDocuments)
{
LoadConfig op = new LoadConfig(this, getNextOperationKey());
op.tick = tick;
op.fileIdentifier = fileIdentifier;
op.yamlDocuments = yamlDocuments;
boolean ret = addOperation(op); // no fail
assert ret;
}
@Override
@ThreadSafe
public void unloadConfig(long tick, String fileIdentifier)
{
UnloadConfig op = new UnloadConfig(this, getNextOperationKey());
op.tick = tick;
op.fileIdentifier = fileIdentifier;
boolean ret = addOperation(op); // no fail
assert ret;
}
@Override
@ThreadSafe
public void actorNew(long tick, int pid, long seed, String ship)
{
ActorNew op = new ActorNew(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
op.seed = seed;
op.ship = ship;
boolean ret = addOperation(op);
assert ret;
}
@Override
@ThreadSafe
public void actorSync(GameOperation.ActorSync sync)
{
assert !this.econfig.server;
ActorSync op = new ActorSync(this, getNextOperationKey());
op.tick = sync.getTick();
op.pid = sync.getPid();
op.sync = sync;
boolean ret = addOperation(op);
assert ret; // atleast the oldest state must accept the sync
}
@Override
@ThreadSafe
public void actorModification(long tick, int pid, String ship)
{
ActorModification op = new ActorModification(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
op.ship = ship;
boolean ret = addOperation(op);
assert ret;
}
@Override
@ThreadSafe
public void actorRemove(long tick, int pid)
{
ActorRemove op = new ActorRemove(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
boolean ret = addOperation(op);
assert ret;
}
@Override
@ThreadSafe
public boolean actorWarp(long tick, int pid, boolean hint, int x, int y, int x_vel, int y_vel, int rotation)
{
ActorWarp op = new ActorWarp(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
op.hint = hint;
op.warp = new PhysicsWarp(x, y, x_vel, y_vel, rotation);
return addOperation(op);
}
@Override
@ThreadSafe
public boolean actorWarp(
long tick, int pid, boolean hint, int x, int y, int x_vel, int y_vel, int rotation, boolean has_x, boolean has_y, boolean has_x_vel, boolean has_y_vel, boolean has_rotation)
{
ActorWarp op = new ActorWarp(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
op.hint = hint;
op.warp = new PhysicsWarp(
has_x ? x : null,
has_y ? y : null,
has_x_vel ? x_vel : null,
has_y_vel ? y_vel : null,
has_rotation ? rotation : null);
return addOperation(op);
}
@Override
@ThreadSafe
public boolean actorMove(long tick, int pid, PhysicsMovement move)
{
ActorMove op = new ActorMove(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
op.move = move;
if (move == null)
{
throw new IllegalArgumentException();
}
try
{
return addOperation(op);
}
finally
{
if (this.testcaseImmediateMove)
{
doReexecuteDirtyPositionPath();
}
}
}
@Override
@ThreadSafe
public boolean actorWeapon(
long tick, int pid, WEAPON_SLOT weapon_slot,
boolean hint_set,
int hint_x, int hint_y,
int hint_x_vel, int hint_y_vel,
int hint_snapped_rotation)
{
ActorWeaponFire op = new ActorWeaponFire(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
op.weapon_slot = weapon_slot;
if (weapon_slot == null)
{
throw new IllegalArgumentException();
}
if (hint_set)
{
op.hint_set = hint_set;
op.hint_x = hint_x;
op.hint_y = hint_y;
op.hint_x_vel = hint_x_vel;
op.hint_y_vel = hint_y_vel;
op.hint_snapped_rot = hint_snapped_rotation;
}
return addOperation(op);
}
@Override
@ThreadSafe
public boolean actorWeapon(long tick, int pid, WEAPON_SLOT weapon_slot)
{
ActorWeaponFire op = new ActorWeaponFire(this, getNextOperationKey());
op.tick = tick;
op.pid = pid;
op.weapon_slot = weapon_slot;
if (weapon_slot == null)
{
throw new IllegalArgumentException();
}
return addOperation(op);
}
@Override
@ThreadSafe
public boolean weaponSync(long tick,
int owner_pid,
String weaponKey,
GameOperation.WeaponSync.Projectile[] projectiles,
long syncKey)
{
assert !econfig.server;
// do not modify "projectiles" after calling this method
WeaponSync op = new WeaponSync(this, getNextOperationKey());
op.tick = tick;
op.pid = owner_pid;
op.weaponKey = weaponKey;
op.syncProjectiles = projectiles;
op.syncKey = syncKey;
return addOperation(op);
}
@Override
public PhysicsMap getMap()
{
return map;
}
@Override
public Iterator<EventPublic> eventIterator()
{
return (Iterator<EventPublic>) (Object) this.eventHistoryList.iteratorReadOnly();
}
@Override
public Iterable<EventPublic> eventIterable()
{
return new Iterable<EventPublic>()
{
@Override
public Iterator<EventPublic> iterator()
{
return (Iterator<EventPublic>) (Object) eventHistoryList.iteratorReadOnly();
}
};
}
@Override
public GCInteger getGlobalConfigInteger(String name)
{
return trailingStates[0].globalConfig.getInteger(name);
}
@Override
public GCString getGlobalConfigString(String name)
{
return trailingStates[0].globalConfig.getString(name);
}
@Override
public GCBoolean getGlobalConfigBoolean(String name)
{
return trailingStates[0].globalConfig.getBoolean(name);
}
@Override
public GCIntegerList getGlobalConfigIntegerList(String name)
{
return trailingStates[0].globalConfig.getIntegerList(name);
}
@Override
public GCStringList getGlobalConfigStringList(String name)
{
return trailingStates[0].globalConfig.getStringList(name);
}
@Override
public GCBooleanList getGlobalConfigBooleanList(String name)
{
return trailingStates[0].globalConfig.getBooleanList(name);
}
@Override
public GCImage getGlobalConfigImage(String name, ResourceDB db)
{
return trailingStates[0].globalConfig.getImage(name, db);
}
public GCInteger getGlobalConfigInteger(int state, String name)
{
return trailingStates[state].globalConfig.getInteger(name);
}
public GCString getGlobalConfigString(int state, String name)
{
return trailingStates[state].globalConfig.getString(name);
}
public GCBoolean getGlobalConfigBoolean(int state, String name)
{
return trailingStates[state].globalConfig.getBoolean(name);
}
public GCIntegerList getGlobalConfigIntegerList(int state, String name)
{
return trailingStates[state].globalConfig.getIntegerList(name);
}
public GCStringList getGlobalConfigStringList(int state, String name)
{
return trailingStates[state].globalConfig.getStringList(name);
}
public GCBooleanList getGlobalConfigBooleanList(int state, String name)
{
return trailingStates[state].globalConfig.getBooleanList(name);
}
public GCImage getGlobalConfigImage(int state, String name, ResourceDB db)
{
return trailingStates[state].globalConfig.getImage(name, db);
}
}