Downloads containing Elevator.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Elevator Cranky Mutator N/A Download file

File preview

//--/ @Event 156=Elevator                           |+|Food      |Elevator |Stopper
/***
Elevator commands:
!elevator [up|climb] <id>
!elevator [down|descend] <id>
!elevator [lock|stop] <id> - stops from going down
!elevator fdown / !elevator fup - same as up/down but unlocks first
!elevator speed <id> <speed> - another idea is to add a command to change elevator speed (NOT DONE YET)
!elevator mute - mute elevator warnings (NOT DONE YET)
-would be nice to make this into an .asc script so it can be used on multiple levels and/or mutators
***/
bool autoUpDownEnabled = true;
bool AUTO_UP = true;
bool AUTO_DOWN = true;
bool directionControls = true;
bool DEBUG_ON = false;
const int ELEVATOR_MAX_ID = 16;
const string ELEVATOR_FILE_SUFFIX = "_Elevator.asdat";
const int ELEVATOR_CONTROL_UP_DOWN = 0;
const int ELEVATOR_CONTROL_LEFT_RIGHT = 1;
const int ELEVATOR_CONTROL_ALL = 2;
const int ELEVATOR_TYPE_NORMAL = 0;
const int ELEVATOR_TYPE_INVERSED = 1;
// Elevator speed is stored as quarter-speed units: 1 = 0.25 = 1 pixel/tick, 4 = 1.00 = 4 pixels/tick.
const int ELEVATOR_DEFAULT_SPEED = 4;
const int ELEVATOR_MIN_SPEED = 1;
const int ELEVATOR_MAX_SPEED = 20;
const int ELEVATOR_SPEED_FORMAT_VERSION = 2;
const int ELEVATOR_SEND_PACKET_INTERVAL = 1;
const bool ELEVATOR_PREDICTION_ON = false;
const bool ENABLE_START_STOP_PACKETS = false;
const int ELEVATOR_SYNC_DISTANCE_TILES = 50;
const int ELEVATOR_PREDICTION_MAX_QUEUE_POINTS = 12;
array<int> platformObjects = {OBJECT::PINKPLATFORM, OBJECT::BOLLPLATFORM};
class ElevatorSavedEvent {
	int x;
	int y;
	int eventId;
	int foreground;
	int speed;
	int id;
	int controllable;
	int controlMode;
	int elevatorType;
	int elevatorSpeed;
}
array<ElevatorSavedEvent> ElevatorPlacements;
bool ElevatorPlacementsLoaded = false;
bool ElevatorPlacementsHaveFile = false;
bool ElevatorPlacementsBuiltFromLevel = false;

class elevator{
	int id; //length parameter
	int objectID;
	int tile;
	int speed;
	int foreground; //sync parameter, 0 = solid, 1 = background
	int controllable; //swing parameter: circle = not controllable (automatic), pendulum = controllable (up/down arrows)
	int controlMode;
	int elevatorType;
	int initial_y;
	bool wasBollPlatform = false;
	bool platformAbove = false;
	bool platformLeft = false;
	bool platformRight = false;
	bool platformBellow = false;
}
array<elevator> elevatorList;
array<int> elevatorObjectIds;
array<int> elevatorControl(32);
array<int> upElevatorControl(32);
array<int> upDownControl;
array<int> elevatorIds;
array<array<int>> ElevatorObjectIdsByElevatorId;
array<int> ElevatorGroupState(ELEVATOR_MAX_ID, -9999);
array<int> ElevatorSyncedMoving(ELEVATOR_MAX_ID, 0);
array<float> ElevatorLastSentX(ELEVATOR_MAX_ID, -999999.0);
array<float> ElevatorLastSentY(ELEVATOR_MAX_ID, -999999.0);
class elevatorInfoClass{ //the id/e.id is the key here for this dictionary
	int scale_y; //how tall is the elevator	
	int lowest_x; //to accompany lowest_y. This way we can prevent OBJECT::THING deletions when they're used as simple fruit
	int lowest_y; //how "high" the elevator can go. In JJ2, y=0 is the "highest" and y=jjScreenHeight is the lowest point.
	int start_x = 9999;
	int end_x;
	int bot_y = 9999;
	int top_y;
	int startObjId;
	int botObjId;
	bool controlled; //whether the elevator is currently controlled by a player
	bool locked; //press fire to prevent elevator from going down. Fire again to unlock.
	
	int last_direction_x;
	int last_direction_y;
	int direction_x; //-1 left, 0 standing still, 1 right
	int direction_y; //-1 up, 0 standing still, 1 down
	int playerID = -1;
	int speed = 0;
	int controlMode = ELEVATOR_CONTROL_UP_DOWN;
	int elevatorType = ELEVATOR_TYPE_NORMAL;
}
class elevatorCmdClass{
	int cmd = 0;
	bool cmdBack = false; //All elevator pieces must go up/down, so you can't just make cmd = 0 at the first piece
	//Therefore cmdBack makes cmd = 0 ONLY after the for loop has finished
}
class ElevatorPredictionPoint {
	int id;
	float diff_x;
	float diff_y;
	int tick;
}
dictionary elevatorInfo;
dictionary elevatorCmd; 
array<ElevatorPredictionPoint> ElevatorPredictionQueue;
array<int> ElevatorPredictionActive(ELEVATOR_MAX_ID, 0);
array<int> ElevatorPredictionTicks(ELEVATOR_MAX_ID, 0);
array<int> ElevatorPredictionTargetTick(ELEVATOR_MAX_ID, 0);
array<float> ElevatorPredictionTargetX(ELEVATOR_MAX_ID, 0.0);
array<float> ElevatorPredictionTargetY(ELEVATOR_MAX_ID, 0.0);

float Elevator_Abs(float value)
{
	return value < 0 ? -value : value;
}

float Elevator_MoveToward(float current, float target, float maxStep)
{
	if(current < target)
	{
		float next = current + maxStep;
		return next > target ? target : next;
	}
	if(current > target)
	{
		float next = current - maxStep;
		return next < target ? target : next;
	}
	return target;
}

void Elevator_ClearPrediction(int id)
{
	if(id > 0 && uint(id) < ElevatorPredictionActive.length)
	{
		ElevatorPredictionActive[id] = 0;
		ElevatorPredictionTicks[id] = 0;
		ElevatorPredictionTargetTick[id] = 0;
	}
	for(int q = int(ElevatorPredictionQueue.length) - 1; q >= 0; q--)
	{
		if(ElevatorPredictionQueue[q].id == id) ElevatorPredictionQueue.removeAt(q);
	}
}

array<int> ElevatorFilePlacementX;
array<int> ElevatorFilePlacementY;
void Elevator_RememberFilePlacement(int x, int y)
{
	for(uint i = 0; i < ElevatorFilePlacementX.length && i < ElevatorFilePlacementY.length; i++)
		if(ElevatorFilePlacementX[i] == x && ElevatorFilePlacementY[i] == y) return;
	ElevatorFilePlacementX.insertLast(x);
	ElevatorFilePlacementY.insertLast(y);
}

bool Elevator_FilePlacementExists(int x, int y)
{
	for(uint i = 0; i < ElevatorFilePlacementX.length && i < ElevatorFilePlacementY.length; i++)
		if(ElevatorFilePlacementX[i] == x && ElevatorFilePlacementY[i] == y)
			return true;
	return false;
}


void Elevator_OnLevelLoad() {
	//jjSetWaterLevel(WATERLEVEL, true);
	Elevator_LoadPlacements(true);
}

string Elevator_FileName()
{
	string baseName = jjLevelFileName;
	int dot = int(baseName.findLast("."));
	if(dot > 0) baseName = baseName.substr(0, dot);
	return baseName + ELEVATOR_FILE_SUFFIX;
}

bool Elevator_IsSavedEvent(int eventId)
{
	return platformObjects.find(eventId) >= 0 || eventId == OBJECT::THING;
}

bool Elevator_TileHasSavedEvent(int x, int y)
{
	int eventId = jjEventGet(x, y);
	int id = jjParameterGet(x, y, 8, 4);
	if(id <= 0 || id >= ELEVATOR_MAX_ID) return false;
	if(platformObjects.find(eventId) >= 0 && jjParameterGet(x, y, 2, -6) >= 0) return true;
	return eventId == OBJECT::THING;
}

void Elevator_DeleteObjectsAtTile(int x, int y)
{
	for(int i = 1; i < jjObjectCount; i++)
	{
		jjOBJ@ obj = jjObjects[i];
		if(int(obj.xOrg / 32) != x || int(obj.yOrg / 32) != y) continue;
		if(Elevator_IsSavedEvent(obj.eventID)) obj.delete();
	}
}

int Elevator_ClampControlMode(int controlMode)
{
	return controlMode < ELEVATOR_CONTROL_UP_DOWN || controlMode > ELEVATOR_CONTROL_ALL ? ELEVATOR_CONTROL_UP_DOWN : controlMode;
}

int Elevator_ClampType(int elevatorType)
{
	return elevatorType == ELEVATOR_TYPE_INVERSED ? ELEVATOR_TYPE_INVERSED : ELEVATOR_TYPE_NORMAL;
}

int Elevator_ClampSpeed(int elevatorSpeed)
{
	if(elevatorSpeed < ELEVATOR_MIN_SPEED) return ELEVATOR_MIN_SPEED;
	if(elevatorSpeed > ELEVATOR_MAX_SPEED) return ELEVATOR_MAX_SPEED;
	return elevatorSpeed;
}

string Elevator_SpeedText(int elevatorSpeed)
{
	elevatorSpeed = Elevator_ClampSpeed(elevatorSpeed);
	int whole = elevatorSpeed / 4;
	int fraction = elevatorSpeed % 4;
	if(fraction == 0) return "" + whole;
	if(fraction == 1) return whole + ".25";
	if(fraction == 2) return whole + ".50";
	return whole + ".75";
}

bool Elevator_AllowsVertical(int controlMode)
{
	return controlMode == ELEVATOR_CONTROL_UP_DOWN || controlMode == ELEVATOR_CONTROL_ALL;
}

bool Elevator_AllowsHorizontal(int controlMode)
{
	return controlMode == ELEVATOR_CONTROL_LEFT_RIGHT || controlMode == ELEVATOR_CONTROL_ALL;
}

void Elevator_ApplyPlacement(int x, int y, int eventId, int foreground, int speed, int id, int controllable, int controlMode, int elevatorType, bool spawnObject)
{
	if(x < 0 || y < 0 || x >= jjLayerWidth[4] || y >= jjLayerHeight[4]) return;
	Elevator_DeleteObjectsAtTile(x, y);
	controlMode = Elevator_ClampControlMode(controlMode);
	elevatorType = Elevator_ClampType(elevatorType);
	if(eventId == OBJECT::THING)
	{
		jjEventSet(x, y, 0);
		return;
	}
	jjEventSet(x, y, eventId);
	jjParameterSet(x, y, 0, 2, foreground);
	jjParameterSet(x, y, 2, -6, speed);
	jjParameterSet(x, y, 8, 4, id);
	jjParameterSet(x, y, 12, 1, controllable);
	jjParameterSet(x, y, 13, 2, controlMode);
	jjParameterSet(x, y, 15, 1, elevatorType);
	if(spawnObject) jjAddObject(eventId, x * 32 + 16, y * 32 + 16);
}

int Elevator_FindPlacementIndex(int x, int y)
{
	for(uint i = 0; i < ElevatorPlacements.length; i++)
		if(ElevatorPlacements[i].x == x && ElevatorPlacements[i].y == y) return int(i);
	return -1;
}

void Elevator_SetPlacement(int x, int y, int eventId, int foreground, int speed, int id, int controllable, int controlMode, int elevatorType, int elevatorSpeed)
{
	if(x < 0 || y < 0 || x >= jjLayerWidth[4] || y >= jjLayerHeight[4]) return;
	ElevatorSavedEvent placement;
	placement.x = x;
	placement.y = y;
	placement.eventId = eventId;
	placement.foreground = foreground;
	placement.speed = speed;
	placement.id = id;
	placement.controllable = controllable;
	placement.controlMode = Elevator_ClampControlMode(controlMode);
	placement.elevatorType = Elevator_ClampType(elevatorType);
	placement.elevatorSpeed = Elevator_ClampSpeed(elevatorSpeed);
	int index = Elevator_FindPlacementIndex(x, y);
	if(index >= 0) ElevatorPlacements[index] = placement;
	else ElevatorPlacements.insertLast(placement);
}

bool Elevator_RemovePlacement(int x, int y)
{
	int index = Elevator_FindPlacementIndex(x, y);
	if(index < 0) return false;
	ElevatorPlacements.removeAt(index);
	return true;
}

bool Elevator_RemovePlacementId(int id)
{
	bool removed = false;
	for(int i = int(ElevatorPlacements.length) - 1; i >= 0; i--)
	{
		if(ElevatorPlacements[i].id == id)
		{
			ElevatorPlacements.removeAt(i);
			removed = true;
		}
	}
	return removed;
}

bool Elevator_PlacementIdExists(int id)
{
	for(uint i = 0; i < ElevatorPlacements.length; i++)
		if(ElevatorPlacements[i].id == id) return true;
	return false;
}

void Elevator_BuildPlacementsFromLevel()
{
	ElevatorPlacements.resize(0);
	ElevatorPlacementsBuiltFromLevel = false;
}

void Elevator_MergePlacementsFromLevel()
{
}

void Elevator_ClearSavedEvents()
{
	for(int x = 0; x < jjLayerWidth[4]; x++)
	{
		for(int y = 0; y < jjLayerHeight[4]; y++)
		{
			if(Elevator_TileHasSavedEvent(x, y))
			{
				Elevator_DeleteObjectsAtTile(x, y);
				jjEventSet(x, y, 0);
			}
		}
	}
}

void Elevator_ClearPlacementEvents()
{
	for(uint i = 0; i < ElevatorPlacements.length; i++)
	{
		ElevatorSavedEvent placement = ElevatorPlacements[i];
		if(placement.x < 0 || placement.y < 0 || placement.x >= jjLayerWidth[4] || placement.y >= jjLayerHeight[4]) continue;
		jjEventSet(placement.x, placement.y, 0);
	}
}

void Elevator_LoadPlacements(bool applyEvents)
{
	jjSTREAM load(Elevator_FileName());
	ElevatorPlacements.resize(0);
	ElevatorFilePlacementX.resize(0);
	ElevatorFilePlacementY.resize(0);
	ElevatorPlacementsLoaded = true;
	ElevatorPlacementsHaveFile = !load.isEmpty();
	ElevatorPlacementsBuiltFromLevel = false;
	if(load.isEmpty())
	{
		return;
	}
	else
	{
		string line;
		int currentId = 0;
		int speedFormatVersion = 1;
		while(load.getLine(line))
		{
			array<string> parts = line.split(" ");
			if(parts.length >= 2 && parts[0] == "ElevatorData")
			{
				currentId = parseInt(parts[1]);
				speedFormatVersion = parts.length >= 3 ? parseInt(parts[2]) : 1;
				continue;
			}
			if(parts.length < 7 || parts[0] != "event") continue;
			int x = parseInt(parts[1]);
			int y = parseInt(parts[2]);
			int eventId = parseInt(parts[3]);
			if(!Elevator_IsSavedEvent(eventId)) continue;
			int foreground = parseInt(parts[4]);
			int speed = parseInt(parts[5]);
			if(currentId <= 0 && parts.length < 8) continue;
			int id = currentId > 0 ? currentId : parseInt(parts[6]);
			int controllable = currentId > 0 ? parseInt(parts[6]) : parseInt(parts[7]);
			int controlMode = ELEVATOR_CONTROL_UP_DOWN;
			int elevatorType = ELEVATOR_TYPE_NORMAL;
			int elevatorSpeed = speedFormatVersion < ELEVATOR_SPEED_FORMAT_VERSION ? 1 : ELEVATOR_DEFAULT_SPEED;
			if(currentId > 0)
			{
				if(parts.length >= 8) controlMode = parseInt(parts[7]);
				if(parts.length >= 9) elevatorType = parseInt(parts[8]);
				if(parts.length >= 10) elevatorSpeed = parseInt(parts[9]);
			}
			else
			{
				if(parts.length >= 9) controlMode = parseInt(parts[8]);
				if(parts.length >= 10) elevatorType = parseInt(parts[9]);
				if(parts.length >= 11) elevatorSpeed = parseInt(parts[10]);
			}
			// Files created before quarter-speed support stored 1..5 as whole speeds.
			// Convert those legacy values to quarter-speed units (1.00 becomes 4).
			if(speedFormatVersion < ELEVATOR_SPEED_FORMAT_VERSION) elevatorSpeed *= 4;
			if(id <= 0 || id >= ELEVATOR_MAX_ID)
				continue;
			if(platformObjects.find(eventId) >= 0 && speed < 0)
				continue;
			Elevator_SetPlacement(x, y, eventId, foreground, speed, id, controllable, controlMode, elevatorType, elevatorSpeed);
			Elevator_RememberFilePlacement(x, y);
		}
	}
	if(applyEvents) Elevator_ClearPlacementEvents();
}

void Elevator_LoadEditorPlacements()
{
	if(!ElevatorPlacementsLoaded) Elevator_LoadPlacements(false);
}

void Elevator_WritePlacements()
{
	jjSTREAM save;
	for(int id = 1; id < ELEVATOR_MAX_ID; id++)
	{
		if(!Elevator_PlacementIdExists(id)) continue;
		save.write("ElevatorData " + id + " " + ELEVATOR_SPEED_FORMAT_VERSION + "\n");
		for(uint i = 0; i < ElevatorPlacements.length; i++)
		{
			ElevatorSavedEvent placement = ElevatorPlacements[i];
			if(placement.id != id) continue;
			save.write("event " + placement.x + " " + placement.y + " " + placement.eventId + " " + placement.foreground + " " + placement.speed + " " + placement.controllable + " " + placement.controlMode + " " + placement.elevatorType + " " + placement.elevatorSpeed + "\n");
		}
	}
	save.save(Elevator_FileName());
	ElevatorPlacementsHaveFile = true;
}

void Elevator_SavePlacements()
{
	Elevator_LoadEditorPlacements();
	Elevator_WritePlacements();
}

bool gotElevators = false;
array<int> ElevatorLastPositionSync(ELEVATOR_MAX_ID, -9999);

int Elevator_UpLimitTargetY(int limitY, int bottomY, int initialY)
{
	return limitY * 32 + (initialY - bottomY) * 32;
}

int Elevator_DownLimitTargetY(int limitY, int topY, int initialY)
{
	return limitY * 32 + (initialY - topY) * 32;
}

bool Elevator_CanMoveUp(float yPos, float yOrg, int elevatorType, int limitY, int bottomY, int topY, int initialY)
{
	if(elevatorType == ELEVATOR_TYPE_INVERSED) return yPos > yOrg;
	return yPos > Elevator_UpLimitTargetY(limitY, bottomY, initialY);
}

bool Elevator_CanMoveDown(float yPos, float yOrg, int elevatorType, int limitY, int bottomY, int topY, int initialY)
{
	if(elevatorType == ELEVATOR_TYPE_INVERSED) return yPos < Elevator_DownLimitTargetY(limitY, topY, initialY);
	return yPos < yOrg;
}

int Elevator_ObjectListIndex(int objectID)
{
	if(objectID < 0 || uint(objectID) >= elevatorObjectIds.length) return -1;
	int index = elevatorObjectIds[objectID];
	if(index < 0 || uint(index) >= elevatorList.length) return -1;
	return index;
}

void Elevator_EnsureGroupObjectArray(int id)
{
	if(id < 0) return;
	if(uint(id) >= ElevatorObjectIdsByElevatorId.length)
		ElevatorObjectIdsByElevatorId.resize(uint(id) + 1);
}

void Elevator_RegisterGroupObject(int id, int objectID)
{
	if(id < 0 || objectID <= 0) return;
	Elevator_EnsureGroupObjectArray(id);
	if(ElevatorObjectIdsByElevatorId[id].find(objectID) < 0)
		ElevatorObjectIdsByElevatorId[id].insertLast(objectID);
}

void Elevator_SetGroupState(int id, int state)
{
	if(id < 0 || uint(id) >= ElevatorObjectIdsByElevatorId.length) return;
	if(uint(id) < ElevatorGroupState.length && ElevatorGroupState[id] == state) return;
	for(uint i = 0; i < ElevatorObjectIdsByElevatorId[id].length; i++)
	{
		int objectID = ElevatorObjectIdsByElevatorId[id][i];
		if(objectID <= 0 || objectID >= jjObjectCount) continue;
		jjObjects[objectID].state = state;
	}
	if(uint(id) < ElevatorGroupState.length) ElevatorGroupState[id] = state;
}

float Elevator_AbsDiff(float a, float b)
{
	return a > b ? a - b : b - a;
}

bool Elevator_ObjectVisibleToLocalPlayer(jjOBJ@ obj)
{
	for(int i = 0; i < jjLocalPlayerCount; i++)
	{
		jjPLAYER@ play = jjLocalPlayers[i];
		if(obj.xPos >= play.cameraX - 64 && obj.xPos <= play.cameraX + jjResolutionWidth + 64
		&& obj.yPos >= play.cameraY - 64 && obj.yPos <= play.cameraY + jjResolutionHeight + 64)
			return true;
	}
	return false;
}

bool Elevator_ObjectNearLocalPlayer(jjOBJ@ obj)
{
	for(int i = 0; i < jjLocalPlayerCount; i++)
	{
		jjPLAYER@ play = jjLocalPlayers[i];
		if(play.platform == obj.objectID) return true;
		if(Elevator_AbsDiff(play.xPos, obj.xPos) <= 96 && Elevator_AbsDiff(play.yPos, obj.yPos) <= 96) return true;
	}
	return false;
}

void Elevator_RegisterSpawnedObject(int objectID, ElevatorSavedEvent placement)
{
	if(objectID <= 0 || platformObjects.find(placement.eventId) < 0) return;
	jjOBJ@ obj = jjObjects[objectID];
	obj.behavior = Lift();
	obj.isFreezable = false;
	obj.deactivates = false;
	obj.state = STATE::WAIT;

	elevator e;
	e.id = placement.id;
	e.objectID = objectID;
	e.tile = jjTileGet(4, placement.x, placement.y);
	e.foreground = placement.foreground;
	e.speed = placement.speed;
	e.controllable = placement.controllable;
	e.controlMode = Elevator_ClampControlMode(placement.controlMode);
	e.elevatorType = Elevator_ClampType(placement.elevatorType);
	e.initial_y = placement.y;
	e.wasBollPlatform = placement.eventId == OBJECT::BOLLPLATFORM;
	if(e.foreground == 0)
	{
		if(placement.y > 0) e.platformAbove = Elevator_FilePlacementExists(placement.x, placement.y - 1);
		if(placement.x > 0) e.platformLeft = Elevator_FilePlacementExists(placement.x - 1, placement.y + 1);
		if(placement.y + 1 < jjLayerHeight[4]) e.platformBellow = Elevator_FilePlacementExists(placement.x, placement.y + 1);
		if(placement.x + 1 < jjLayerWidth[4] && placement.y + 1 < jjLayerHeight[4]) e.platformRight = Elevator_FilePlacementExists(placement.x + 1, placement.y + 1);
	}

	elevatorList.insertLast(e);
	if(uint(objectID) >= elevatorObjectIds.length)
	{
		uint oldLength = elevatorObjectIds.length;
		elevatorObjectIds.resize(uint(objectID) + 1);
		for(uint i = oldLength; i < elevatorObjectIds.length; i++) elevatorObjectIds[i] = -1;
	}
	elevatorObjectIds[objectID] = elevatorList.length - 1;
	if(elevatorIds.find(e.id) < 0) elevatorIds.insertLast(e.id);
	Elevator_RegisterGroupObject(e.id, objectID);

	elevatorInfoClass value;
	value.controlled = false;
	value.locked = false;
	elevatorInfo.get(e.id+'', value);
	if(value is null)
	{
		value.lowest_y = placement.y;
		value.start_x = placement.x;
		value.end_x = placement.x;
		value.bot_y = placement.y;
		value.top_y = placement.y;
		value.startObjId = objectID;
		value.botObjId = objectID;
		value.speed = Elevator_ClampSpeed(placement.elevatorSpeed);
		value.controlMode = e.controlMode;
		value.elevatorType = e.elevatorType;
		elevatorInfo.set(e.id+'', value);
	}
	else
	{
		if(value.lowest_y > placement.y) value.lowest_y = placement.y;
		if(value.start_x > placement.x) { value.start_x = placement.x; value.startObjId = objectID; }
		if(value.end_x < placement.x) value.end_x = placement.x;
		if(value.bot_y > placement.y) { value.bot_y = placement.y; value.botObjId = objectID; }
		if(value.top_y < placement.y) value.top_y = placement.y;
		value.speed = Elevator_ClampSpeed(placement.elevatorSpeed);
		value.controlMode = e.controlMode;
		value.elevatorType = e.elevatorType;
		elevatorInfo.set(e.id+'', value);
	}
}

void Elevator_SpawnSavedObjects()
{
	Elevator_LoadEditorPlacements();
	for(uint i = 0; i < ElevatorPlacements.length; i++)
	{
		ElevatorSavedEvent placement = ElevatorPlacements[i];
		if(platformObjects.find(placement.eventId) < 0) continue;
		int spawnEventId = placement.eventId == OBJECT::BOLLPLATFORM ? OBJECT::PINKPLATFORM : placement.eventId;
		int objectID = jjAddObject(spawnEventId, placement.x * 32 + 16, placement.y * 32 + 16, 0, CREATOR::LEVEL, BEHAVIOR::DEFAULT);
		Elevator_RegisterSpawnedObject(objectID, placement);
	}
}

void Elevator_UpdatePrediction()
{
	if(!ELEVATOR_PREDICTION_ON || ELEVATOR_SEND_PACKET_INTERVAL <= 1) return;
	for(int id = 1; id < ELEVATOR_MAX_ID; id++)
	{
		if(Elevator_LocalPlayerControls(id))
		{
			Elevator_ClearPrediction(id);
			continue;
		}
		if(ElevatorPredictionActive[id] == 0)
		{
			for(uint q = 0; q < ElevatorPredictionQueue.length; q++)
			{
				if(ElevatorPredictionQueue[q].id != id) continue;
				ElevatorPredictionTargetX[id] = ElevatorPredictionQueue[q].diff_x;
				ElevatorPredictionTargetY[id] = ElevatorPredictionQueue[q].diff_y;
				ElevatorPredictionTargetTick[id] = ElevatorPredictionQueue[q].tick;
				ElevatorPredictionTicks[id] = ELEVATOR_SEND_PACKET_INTERVAL;
				ElevatorPredictionActive[id] = 1;
				ElevatorPredictionQueue.removeAt(q);
				break;
			}
		}
		if(ElevatorPredictionActive[id] == 0) continue;

		elevatorInfoClass eInfo;
		elevatorInfo.get(id+'', eInfo);
		float currentDiffX = jjObjects[eInfo.startObjId].xPos - jjObjects[eInfo.startObjId].xOrg;
		float currentDiffY = jjObjects[eInfo.botObjId].yPos - jjObjects[eInfo.botObjId].yOrg;
		float distanceX = Elevator_Abs(ElevatorPredictionTargetX[id] - currentDiffX);
		float distanceY = Elevator_Abs(ElevatorPredictionTargetY[id] - currentDiffY);
		if(distanceX < 0.01 && distanceY < 0.01)
		{
			Elevator_ApplySyncedPosition(id, ElevatorPredictionTargetX[id], ElevatorPredictionTargetY[id]);
			ElevatorPredictionActive[id] = 0;
			ElevatorPredictionTicks[id] = 0;
		}
		else
		{
			int ticks = ElevatorPredictionTicks[id] > 0 ? ElevatorPredictionTicks[id] : 1;
			float stepX = distanceX / ticks;
			float stepY = distanceY / ticks;
			if(stepX < 1.0 && distanceX > 0.0) stepX = 1.0;
			if(stepY < 1.0 && distanceY > 0.0) stepY = 1.0;
			float nextDiffX = Elevator_MoveToward(currentDiffX, ElevatorPredictionTargetX[id], stepX);
			float nextDiffY = Elevator_MoveToward(currentDiffY, ElevatorPredictionTargetY[id], stepY);
			Elevator_ApplySyncedPosition(id, nextDiffX, nextDiffY);
			if(ElevatorPredictionTicks[id] > 0) ElevatorPredictionTicks[id]--;
		}
	}
}

void Elevator_OnMain() {
	jjWaterLayer = 9;
	
	if(!gotElevators){
		Elevator_SpawnSavedObjects();
		
		for(uint placementIndex = 0; placementIndex < ElevatorPlacements.length; placementIndex++)
		{
			ElevatorSavedEvent placement = ElevatorPlacements[placementIndex];
			if(platformObjects.find(placement.eventId) < 0) continue;
			jjLayers[4].generateSettableTileArea(placement.x, placement.y, 1, 1);
			jjTileSet(4, placement.x, placement.y, 0);
		}
		
		for(uint index=0; index<elevatorIds.length; index++){
			int i = elevatorIds[index];
			//check where "thing/elevator stopper" is
			elevatorInfoClass eInfo;
			elevatorInfo.get(i+'', eInfo);
			bool ok_found = false;
			if(eInfo.elevatorType == ELEVATOR_TYPE_INVERSED)
			{
				for(int y=eInfo.bot_y; y<jjLayerHeight[4]; y++){
					for(int x=eInfo.start_x; x<=eInfo.end_x; x++){
						if(jjEventGet(x,y) == OBJECT::THING && (jjParameterGet(x, y, 8, 4) == i || jjParameterGet(x, y, 8, 4) == 0)){
							eInfo.lowest_x = x;
							eInfo.lowest_y = y;
							ok_found = true;
							break;
						}
					}
					if(ok_found) break;
				}
			}
			else
			{
				for(int y=eInfo.top_y; y>1; y--){
					for(int x=eInfo.start_x; x<=eInfo.end_x; x++){
						if(jjEventGet(x,y) == OBJECT::THING && (jjParameterGet(x, y, 8, 4) == i || jjParameterGet(x, y, 8, 4) == 0)){
							eInfo.lowest_x = x;
							eInfo.lowest_y = y;
							ok_found = true;
							break;
						}
					}
					if(ok_found) break;
				}
			}
			if(!ok_found) eInfo.lowest_y = eInfo.elevatorType == ELEVATOR_TYPE_INVERSED ? jjLayerHeight[4] - 1 : 1; //0 is too high, no space for the bunny
			for(uint placementIndex = 0; placementIndex < ElevatorPlacements.length; placementIndex++)
			{
				ElevatorSavedEvent placement = ElevatorPlacements[placementIndex];
				if(placement.eventId == OBJECT::THING && placement.id == i)
				{
					if(!ok_found || (eInfo.elevatorType == ELEVATOR_TYPE_INVERSED && placement.y > eInfo.lowest_y) || (eInfo.elevatorType == ELEVATOR_TYPE_NORMAL && placement.y < eInfo.lowest_y))
					{
						eInfo.lowest_x = placement.x;
						eInfo.lowest_y = placement.y;
						ok_found = true;
					}
				}
			}
			elevatorInfo.set(i+'', eInfo);
		}
		
		for(uint i=0; i<32; i++){ elevatorControl[i] = -1; upElevatorControl[i] = -1; }
		upDownControl.resize(ELEVATOR_MAX_ID);
		for(uint i=0; i<upDownControl.length; i++) upDownControl[i] = 1;
		gotElevators = true;
		if(!jjIsServer) requestSync();
	}
}

//void Lift(jjOBJ@ obj) {
class Lift: jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		int elevatorIndex = Elevator_ObjectListIndex(obj.objectID);
		if(elevatorIndex < 0){
			obj.behave(BEHAVIOR::PLATFORM, true);
			//jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL);
			//jjDrawSprite(obj.xPos, obj.yPos, ANIM::PINKPLAT, p.curAnim - jjAnimSets[ANIM::PINKPLAT].firstAnim, obj.frameID, obj.direction, SPRITE::NORMAL, 0, 4, 4, -1);
		}else{
			elevator e = elevatorList[elevatorIndex];
			switch (obj.state) {
				case STATE::WAIT:
					break;
				case STATE::FADEIN:
					Elevator_SetGroupState(e.id, obj.state);
					break;
				case STATE::FADEOUT:
					Elevator_SetGroupState(e.id, obj.state);
					break;
				case STATE::DEACTIVATE:
					obj.deactivate();
					return;
			}
			//if(obj.eventID == OBJECT::BOLLPLATFORM) obj.beSolid();
			
			bool visible = Elevator_ObjectVisibleToLocalPlayer(obj);
			bool nearLocalPlayer = Elevator_ObjectNearLocalPlayer(obj);
			if(visible) jjDrawTile(obj.xPos-16, obj.yPos-15, e.tile);
			if(e.foreground == 0){ 
				if(nearLocalPlayer)
				{
					bool ok = false;
					for(int i=0; i<jjLocalPlayerCount; i++){
						jjPLAYER@ play = jjLocalPlayers[i];
						if(play.platform != obj.objectID && (Elevator_AbsDiff(play.xPos, obj.xPos) > 96 || Elevator_AbsDiff(play.yPos, obj.yPos) > 96)) continue;
						if(obj.yPos-16 < play.yPos && play.yPos < obj.yPos+16 && (e.platformAbove || e.platformBellow || e.platformLeft || e.platformRight)){
							if(obj.xPos < play.xPos && play.xPos < obj.xPos+32){
								if(play.specialMove > 0 && play.direction == -1){
									//play.specialMove = 0;
									play.xSpeed = play.xAcc = 0;
									play.xPos = obj.xPos + 32;
								}else{
									//speed=acc=0 might mean Lori will go in the wall a bit, but she will also be able to continuously do her special move as normal
									if((play.charCurr == CHAR::LORI || play.charCurr == CHAR::BIRD) && play.direction == -1){
										play.xSpeed = play.xAcc = 0;
									}else if(play.charCurr != CHAR::BIRD) play.xSpeed = play.xAcc = 1; //opposite direction helps push out of the wall a bit
									play.xPos = obj.xPos + 32;
								}
								ok = true;
							}else if(obj.xPos - 32 < play.xPos && play.xPos <= obj.xPos){
								if(play.specialMove > 0 && play.direction == 1){
									//play.specialMove = 0;
									play.xSpeed = play.xAcc = 0;
									play.xPos = obj.xPos - 32;
								}else{
									if((play.charCurr == CHAR::LORI || play.charCurr == CHAR::BIRD) && play.direction == 1){
										play.xSpeed = play.xAcc = 0;
									}else if(play.charCurr != CHAR::BIRD) play.xSpeed = play.xAcc = -1; //opposite direction helps push out of the wall a bit
									play.xPos = obj.xPos - 32;
								}
								ok = true;
							}
						}
						if(play.platform == 0){
							myBeSolid(play, obj, obj.xPos-16, obj.yPos-14, 32, 32); //was yPos-16 but is -14 so sidekick can be done well
						}
					}
					if(ok) obj.clearPlatform();
					else obj.bePlatform(obj.xPos, obj.yPos, 0, 0);
				}
			}
			else if(e.foreground == 1){
				if(visible || nearLocalPlayer)
				{
					obj.behave(BEHAVIOR::PICKUP, false);
					obj.scriptedCollisions = true;
					obj.playerHandling = HANDLING::SPECIAL;
				}
			}
			
			//to script collisions - ONLY for e.foreground == 1
			//(platform height is about half a tile, so currently bullets pass through the other half).
			/*bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force){
				return false;
			}*/
		}
	}
}

elevatorInfoClass setControllable(int id, int playerID, bool value){
	if(playerID == -1) value = false;
	elevatorInfoClass eInfo; 
	elevatorInfo.get(id+'', eInfo);
	eInfo.controlled = value;
	eInfo.playerID = value ? playerID : -1;
	elevatorInfo.set(id+'', eInfo);
	//jjAlert("eInfo control "+eInfo.playerID+" "+eInfo.direction_x);
	//++sendElevatorPacket(id, 0);
	return eInfo;
}

elevatorInfoClass setLocked(int id, bool value){
	elevatorInfoClass eInfo; 
	elevatorInfo.get(id+'', eInfo);
	eInfo.locked = value;
	elevatorInfo.set(id+'', eInfo);
	sendElevatorLock(id, value, 0);
	return eInfo;
}

int lastElevatorLock = 0;
void Elevator_OnPlayer(jjPLAYER@ play) {
	array<bool> sentPacketForElevator(ELEVATOR_MAX_ID, false);
	if(gotElevators){
		array<bool> sentElevatorControl(ELEVATOR_MAX_ID, false);
		if(elevatorControl[play.playerID] != -1 && play.keyJump){ //&& elevatorControl[i] != -1){
			elevatorInfoClass eInfo;
			int i = elevatorControl[play.playerID];
			elevatorInfo.get(i+'', eInfo);
			if(play.playerID == eInfo.playerID){
				//elevatorControl[i] = -1; - local
				eInfo.direction_x = 0;
				eInfo.direction_y = 0;
				eInfo.controlled = false;
				eInfo.playerID = -1;
				elevatorInfo.set(i+'', eInfo);
				if(DEBUG_ON) jjAlert("here1? "+elevatorControl[i]);
				sendElevatorPlayer(i, -1, 0, play.playerID);
				sendElevatorPacket(i, 0, 0, 0, false);
				sentPacketForElevator[i] = true;
			}else if(-1 == eInfo.playerID){
				//elevatorControl[i] = -1; - local
				if(DEBUG_ON) jjAlert("here2? "+i+" "+elevatorControl[play.playerID]+" "+eInfo.playerID);
				eInfo.direction_x = 0;
				eInfo.direction_y = 0;
				eInfo.controlled = false;
				eInfo.playerID = -1;
				elevatorInfo.set(i+'', eInfo);
				sendElevatorPlayer(i, -1, 0, play.playerID);
				sendElevatorPacket(i, 0, 0, 0, false);
				sentPacketForElevator[i] = true;
			}
		}
	
		//if (jjIsServer && play.platform == 1 && jjGameTicks % 70 == 0) jjAlert(play.platform+" "+(play.platform == CREATOR::PLAYER));
		if (play.platform != 0) {
			jjOBJ@ obj = jjObjects[play.platform];
			
			int elevatorIndex = Elevator_ObjectListIndex(obj.objectID);
			if (elevatorIndex >= 0 && obj.creatorType != CREATOR::PLAYER && (obj.state == STATE::WAIT || obj.state == STATE::FADEOUT)) {
				elevator e = elevatorList[elevatorIndex];
				obj.state = STATE::FADEIN;
				Elevator_SetGroupState(e.id, obj.state);
				
				if(e.controllable == 1 && !sentElevatorControl[e.id]){
					if(DEBUG_ON) jjAlert("player controlling "+play.playerID);
					elevatorControl[play.playerID] = e.id;
					
					setControllable(e.id, play.playerID, true);
					sendElevatorPlayer(e.id, play.playerID, 0, play.playerID);
					
					send_elevatorControl(e.id, play.playerID, play.playerID, 0, 0);
					sentElevatorControl[e.id] = true;
				}else if(e.controllable == 0 && !sentElevatorControl[e.id] && e.speed != 0){
					send_elevatorControl(e.id, play.playerID, play.playerID, -1, 0);
					sentElevatorControl[e.id] = true;
				}
			}
		}
		else if (play.platform == 0) {
			array<int> releaseElevatorIds;
			if(elevatorControl[play.playerID] != -1) releaseElevatorIds.insertLast(elevatorControl[play.playerID]);
			if(upElevatorControl[play.playerID] != -1 && releaseElevatorIds.find(upElevatorControl[play.playerID]) < 0) releaseElevatorIds.insertLast(upElevatorControl[play.playerID]);
			for(uint releaseIndex = 0; releaseIndex < releaseElevatorIds.length; releaseIndex++) {
				int id = releaseElevatorIds[releaseIndex];
				if(id <= 0 || id >= ELEVATOR_MAX_ID || sentElevatorControl[id]) continue;
				if(uint(id) >= ElevatorObjectIdsByElevatorId.length || ElevatorObjectIdsByElevatorId[id].length == 0) continue;
				int objectID = ElevatorObjectIdsByElevatorId[id][0];
				if(objectID <= 0 || objectID >= jjObjectCount) continue;
				jjOBJ@ obj = jjObjects[objectID];
				if(obj.creatorType == CREATOR::PLAYER || obj.state != STATE::FADEIN) continue;
				Elevator_SetGroupState(id, STATE::FADEOUT);

				elevatorInfoClass eInfo;
				elevatorInfo.get(id+'', eInfo);
				if(play.playerID == eInfo.playerID){
					eInfo.playerID = -1;
					eInfo.direction_x = 0;
					eInfo.direction_y = 0;
					eInfo.controlled = false;
					elevatorInfo.set(id+'', eInfo);
					sendElevatorPlayer(id, -1, 0, play.playerID);
				}
				else setControllable(id, -1, false);

				send_elevatorControl(id, -1, play.playerID, 1, 0);
				sentElevatorControl[id] = true;
			}
		}
		
		int playerElevatorId = elevatorControl[play.playerID];
		if(playerElevatorId != -1 && uint(playerElevatorId) < ElevatorObjectIdsByElevatorId.length)
		{
			for(uint i=0; i<ElevatorObjectIdsByElevatorId[playerElevatorId].length; i++){
				int objectID = ElevatorObjectIdsByElevatorId[playerElevatorId][i];
				int elevatorIndex = Elevator_ObjectListIndex(objectID);
				if(elevatorIndex < 0) continue;
				jjOBJ@ obj = jjObjects[objectID];
				elevator e = elevatorList[elevatorIndex];
				elevatorInfoClass eInfo;
				elevatorInfo.get(e.id+'', eInfo);
				elevatorCmdClass eCmd;
				elevatorCmd.get(e.id+'', eCmd);
				bool ok = false;
				bool speed0PlatformCondition = false;
				if(play.platform != 0){
					if(0 < uint(play.platform) && uint(play.platform) < elevatorObjectIds.length){
						if(elevatorObjectIds[play.platform] >= 0 && uint(elevatorObjectIds[play.platform]) < elevatorList.length){
							elevator e2 = elevatorList[elevatorObjectIds[play.platform]];
							speed0PlatformCondition = (e2.speed == 0);
							ok = e2.controllable == 1;
						}
					}
				}
				if(eCmd.cmd == 0 && elevatorControl[play.playerID] == e.id && ok){// && eInfo.controlled && (play.playerID == eInfo.playerID || -1 == eInfo.playerID)){
					if(obj.state == STATE::FADEIN){
						int dir_x = 0, dir_y = 0; bool entered = false;

						int speed = speed0PlatformCondition ? 0 : eInfo.speed;
						if (Elevator_AllowsVertical(e.controlMode) && Elevator_CanMoveUp(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y) && play.keyUp){
							obj.yPos -= speed;
							entered = true; dir_y = -1;
						}else if (Elevator_AllowsVertical(e.controlMode) && Elevator_CanMoveDown(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y) && play.keyDown){
							obj.yPos += speed;
							entered = true; dir_y = 1;
						}

						if (Elevator_AllowsHorizontal(e.controlMode) && play.keyLeft && !play.keyRun && !play.keyJump){
							play.xSpeed = play.xAcc = 0;
							obj.xPos -= speed;
							entered = true; dir_x = -1;
						}else if (Elevator_AllowsHorizontal(e.controlMode) && play.keyRight && !play.keyRun && !play.keyJump){
							play.xSpeed = play.xAcc = 0;
							obj.xPos += speed;
							entered = true; dir_x = 1;
						}

						if (play.keyFire && !eInfo.locked && lastElevatorLock + 20 < jjGameTicks)
						{ setLocked(e.id, true); eInfo.locked = true; lastElevatorLock = jjGameTicks; jjAlert("locked");}
						else if (play.keyFire && eInfo.locked && lastElevatorLock + 20 < jjGameTicks)
						{ setLocked(e.id, false); eInfo.locked = false; lastElevatorLock = jjGameTicks; jjAlert("unlocked");}

						if(!sentPacketForElevator[e.id]){
							if(entered){
								if(eInfo.playerID == -1) eInfo.playerID = play.playerID;
								eInfo.direction_x = dir_x;
								eInfo.direction_y = dir_y;
								elevatorInfo.set(e.id+'', eInfo);
								sendElevatorPacket(e.id, dir_x, dir_y, 0);
								sentPacketForElevator[e.id] = true;
							}else{
								if(play.playerID == eInfo.playerID){
									int last_dir_x = eInfo.direction_x, last_dir_y = eInfo.direction_y;
									if(!play.keyLeft && !play.keyRight) eInfo.direction_x = dir_x;
									if(!play.keyUp && !play.keyDown) eInfo.direction_y = dir_y;
									if((dir_x == 0 && last_dir_x != 0) || (dir_y == 0 && last_dir_y != 0)){
										//jjAlert("stop "+dir_x+" "+last_dir_x+", "+dir_y+" "+last_dir_y);
										eInfo.playerID = -1;
										elevatorInfo.set(e.id+'', eInfo);
										sendElevatorPlayer(e.id, -1, 0, play.playerID);
										sendElevatorPacket(e.id, dir_x, dir_y, 0);
										sentPacketForElevator[e.id] = true;
									}
									elevatorInfo.set(e.id+'', eInfo);
								}
							}
						}
					}else if(obj.state == STATE::FADEOUT && !eInfo.locked){
						if (e.elevatorType == ELEVATOR_TYPE_INVERSED && obj.yPos > obj.yOrg) obj.yPos -= eInfo.speed;
						else if (e.elevatorType == ELEVATOR_TYPE_NORMAL && obj.yPos < obj.yOrg) obj.yPos += eInfo.speed;
						else obj.state = STATE::WAIT;
					}
				}else{
					if(eCmd.cmd == 0 && play.playerID == eInfo.playerID && !sentPacketForElevator[e.id]){
						elevatorInfo.get(e.id+'', eInfo);
						eInfo.direction_x = 0;
						eInfo.direction_y = 0;
						elevatorInfo.set(e.id+'', eInfo);
						sendElevatorPacket(e.id, 0, 0, 0);
						sentPacketForElevator[e.id] = true;
					}
				}
			}
		}
	}

	/*for(int i = 0; i < 256; i++) { //loop through all the keys
		if(jjKey[i] && !keyPressed[i]) {
			onKeyDown(p, i);
			keyPressed[i] = true;
		} else if(!jjKey[i] && keyPressed[i]) keyPressed[i] = false;
	}
	if(inArea(p, 56, 253, 56, 253))  myArea = 1;
	else if(inArea(p, 7, 253, 7, 253))  myArea = 2;
	else if(inArea(p, 59, 251, 59, 251)) onFunction0();
	else myArea = 0;*/
	
	if(play.localPlayerID == jjLocalPlayers[0].localPlayerID){
		for(uint groupIndex=0; groupIndex<elevatorIds.length; groupIndex++){
			int id = elevatorIds[groupIndex];
			elevatorInfoClass eInfo; 
			elevatorInfo.get(id+'', eInfo);
			elevatorCmdClass eCmd;
			elevatorCmd.get(id+'', eCmd);
			bool remoteDirectionActive = directionControls && eInfo.playerID >= 0 && eInfo.playerID < 32 && !jjPlayers[eInfo.playerID].isLocal;
			bool autoActive = autoUpDownEnabled && !eInfo.controlled && !eInfo.locked;
			if(eCmd.cmd == 0 && !autoActive && !remoteDirectionActive) continue;
			if(uint(id) >= ElevatorObjectIdsByElevatorId.length) continue;
			for(uint groupObjectIndex=0; groupObjectIndex<ElevatorObjectIdsByElevatorId[id].length; groupObjectIndex++){
				int objectID = ElevatorObjectIdsByElevatorId[id][groupObjectIndex];
				int elevatorIndex = Elevator_ObjectListIndex(objectID);
				if(elevatorIndex < 0) continue;
				jjOBJ@ obj = jjObjects[objectID];
				elevator e = elevatorList[elevatorIndex];
				if(eCmd.cmd == 0){ //not controlled through command
					int dir_x = 0, dir_y = 0; bool entered = false;

					bool speed0PlatformCondition = false;
					if(play.platform != 0){
						if(0 < uint(play.platform) && uint(play.platform) < elevatorObjectIds.length){
							if(elevatorObjectIds[play.platform] >= 0 && uint(elevatorObjectIds[play.platform]) < elevatorList.length){
								elevator e2 = elevatorList[elevatorObjectIds[play.platform]];
								speed0PlatformCondition = (e2.speed == 0);
							}
						}
					}

					int speed = speed0PlatformCondition ? 0 : eInfo.speed;
					if(/*jjIsServer &&*/ autoActive && !e.wasBollPlatform){
						if(upDownControl[e.id] == -1 && AUTO_UP){ //&& obj.state == STATE::FADEIN){
							if (e.elevatorType == ELEVATOR_TYPE_INVERSED && Elevator_CanMoveDown(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)){
								obj.yPos += speed;
								entered = true; dir_y = 1;
							}else if (e.elevatorType == ELEVATOR_TYPE_NORMAL && Elevator_CanMoveUp(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)){
								//if(jjIsServer && jjGameTicks%70 == 0) jjAlert("mhm");
								obj.yPos -= speed;
								entered = true; dir_y = -1;
							}
						}else if(upDownControl[e.id] == 1 && AUTO_DOWN){ //&& obj.state == STATE::FADEOUT){
							//if(!sentPacketForElevator[e.id] && e.id == 1 && jjGameTicks%70 == 0) jjAlert("HMM");
							if (e.elevatorType == ELEVATOR_TYPE_INVERSED && Elevator_CanMoveUp(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)){
								obj.yPos -= speed;
								entered = true; dir_y = -1;
							}else if (e.elevatorType == ELEVATOR_TYPE_NORMAL && Elevator_CanMoveDown(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)){
								//if(!sentPacketForElevator[e.id] && e.id == 1 && jjGameTicks%70 == 0) jjAlert("hmm");
								obj.yPos += speed;
								entered = true; dir_y = 1;
							}else obj.state = STATE::WAIT;
						}
					}

					if(remoteDirectionActive){// || entered){ - entered won't be true for another client
						//direction elevator control
						if(eInfo.direction_y == -1){
							if (Elevator_CanMoveUp(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)){
								obj.yPos -= speed; //was 9*46 instead of eInfo.lowest_y
								entered = true; dir_y = -1;
							}
						}else if(eInfo.direction_y == 1){
							if (Elevator_CanMoveDown(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)){
								obj.yPos += speed;
								entered = true; dir_y = 1;
							}
						} 
						if(eInfo.direction_x == -1){
							//..play.xSpeed = play.xAcc = 0;
							obj.xPos -= speed;
							entered = true; dir_x = -1;
						}else if(eInfo.direction_x == 1){
							//jjAlert("how playerID: "+eInfo.playerID+" direction_x: "+eInfo.direction_x);
							//..play.xSpeed = play.xAcc = 0;
							obj.xPos += speed;
							entered = true; dir_x = 1;
						}
					}
					//can't send packets here, too many in this for
					//if(entered && jjGameTicks%70 == 0 && e.id == 1) jjAlert("here "+eInfo.direction_x+" "+eInfo.direction_y);
					//sendElevatorPacket(e.id, dir_x, dir_y, 0);
					if(jjIsServer && autoUpDownEnabled && entered && !sentPacketForElevator[e.id]){
						//if(e.id == 1 && jjGameTicks%70 == 0) jjAlert("autoUpDown "+dir_x+" "+dir_y);
						sendElevatorPacket(e.id, dir_x, dir_y, 0);//, false);
						sentPacketForElevator[e.id] = true;
					}
				}else{
					switch(eCmd.cmd){
						case 1: //up
							if (!eInfo.locked && Elevator_CanMoveUp(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)) obj.yPos -= eInfo.speed;
							else{ eCmd.cmdBack = true; elevatorCmd.set(e.id+'', eCmd); }
							break;
						case 2: //down
							if (!eInfo.locked && Elevator_CanMoveDown(obj.yPos, obj.yOrg, e.elevatorType, eInfo.lowest_y, eInfo.top_y, eInfo.bot_y, e.initial_y)) obj.yPos += eInfo.speed;
							else{ eCmd.cmdBack = true; elevatorCmd.set(e.id+'', eCmd); }
							break;
					}
				}
			}
		}
		for(uint index=0; index<elevatorIds.length; index++){
			int i = elevatorIds[index];
			elevatorInfoClass eInfo; 
			elevatorInfo.get(i+'', eInfo);
			elevatorCmdClass eCmd;
			elevatorCmd.get(i+'', eCmd);
			if(eCmd.cmdBack){
				//jjAlert("back to 0 "+i+" "+eCmd.cmd+" "+eInfo.playerID+" "+eInfo.controlled);
				eCmd.cmd = 0;
				eCmd.cmdBack = false;
				if(eInfo.playerID != -1) eInfo.controlled = true;
				eInfo.last_direction_x = -2;
				eInfo.last_direction_y = -2;
				elevatorInfo.set(i+'', eInfo);
				elevatorCmd.set(i+'', eCmd);
				//++sendElevatorPacket(i, 0);
			}
		}
	}
	if(gotElevators) Elevator_UpdatePrediction();
	
	//hiding player - ONLY when pressing DOWN - very basic
}

void myBeSolid(jjPLAYER@ player, jjOBJ@ obj, float center, uint width, float bottom, float leftmostPlayer, float rightmostPlayer, float sidePlayerTop, float sidePlayerBottom, float xL, float yL) {
	bool pushedPlayer = false;
	if (int(player.ySpeed) <= 1 && player.xPos > leftmostPlayer && player.xPos < rightmostPlayer && player.yPos >= bottom && player.yPos < sidePlayerBottom) {        
		player.yPos = sidePlayerBottom + 1;
		player.ySpeed = player.yAcc = 1;
		pushedPlayer = true;
	} else if (player.yPos > sidePlayerTop && player.yPos < sidePlayerBottom) {
		if (player.xPos > leftmostPlayer && player.xPos <= center) {
			//player.xPos = leftmostPlayer;
			player.xSpeed = player.xAcc = 0;
			pushedPlayer = true;
		} else if (player.xPos < rightmostPlayer && player.xPos >= center) {
			//player.xPos = rightmostPlayer;
			player.xSpeed = player.xAcc = 0;
			pushedPlayer = true;
		}
	} else {
		obj.bePlatform(xL, yL, width);
		pushedPlayer = player.platform == obj.objectID;
	}
	if (pushedPlayer && jjMaskedPixel(int(player.xPos), int(player.yPos))) //squashed into wall
		player.kill();
}
void myBeSolid(jjPLAYER@ player, jjOBJ@ obj, float left, float top, uint width, uint height, float xLast, float yLast) {
	top -= 4;
	const float bottom = top + height + 0; //(!Player::DoubleSized ? 0 : 24); Player::DoubleSized = false for now
	const float right = left + width;
	const int xBuffer = 13;//(!Player::DoubleSized ? 13 : 25);
	myBeSolid(
		player,
		obj,
		left + width/2, width+8, bottom,
		left - xBuffer, right + xBuffer,
		top - 12, bottom + 12,
		xLast, yLast
	);
}
void myBeSolid(jjPLAYER@ player, jjOBJ@ obj, float left, float top, uint width, uint height) {
	myBeSolid(player, obj, left, top, width, height, obj.xPos, obj.yPos);
}

float last_id = 0, last_xPos = -1, last_yPos = -1, last_controlled, last_locked, last_cmd; //arrays for each elevatorId?
int Elevator_DirectionFromDiff(float currentDiff, float receivedDiff)
{
	if(receivedDiff > currentDiff) return 1;
	if(receivedDiff < currentDiff) return -1;
	return 0;
}

void Elevator_ApplySyncedPosition(int id, float diff_x, float diff_y)
{
	if(id < 0 || uint(id) >= ElevatorObjectIdsByElevatorId.length) return;
	for(uint i=0; i<ElevatorObjectIdsByElevatorId[id].length; i++){
		int objectID = ElevatorObjectIdsByElevatorId[id][i];
		if(objectID <= 0 || objectID >= jjObjectCount) continue;
		jjOBJ@ obj = jjObjects[objectID];
		obj.xPos = obj.xOrg + diff_x;
		obj.yPos = obj.yOrg + diff_y;
	}
}

bool Elevator_LocalPlayerControls(int id)
{
	for(int i=0; i<jjLocalPlayerCount; i++)
	{
		if(elevatorControl[jjLocalPlayers[i].playerID] == id) return true;
	}
	return false;
}

bool Elevator_ClientNear(int id, int clientID)
{
	if(clientID <= 0 || clientID >= 32) return true;
	if(!jjPlayers[clientID].isInGame) return false;
	elevatorInfoClass eInfo;
	elevatorInfo.get(id+'', eInfo);
	float elevatorX = jjObjects[eInfo.startObjId].xPos;
	float elevatorY = jjObjects[eInfo.startObjId].yPos;
	float maxDistance = ELEVATOR_SYNC_DISTANCE_TILES * 32;
	float distanceX = jjPlayers[clientID].xPos - elevatorX;
	float distanceY = jjPlayers[clientID].yPos - elevatorY;
	if(distanceX < 0) distanceX = -distanceX;
	if(distanceY < 0) distanceY = -distanceY;
	return distanceX <= maxDistance && distanceY <= maxDistance;
}

bool Elevator_SendPositionPacket(jjSTREAM &in packet, int id, int clientID, bool targetOnly = false)
{
	if(targetOnly && clientID != 0)
	{
		jjSendPacket(packet, clientID);
		return true;
	}
	if(clientID > 0)
	{
		if(Elevator_ClientNear(id, clientID))
		{
			jjSendPacket(packet, clientID);
			return true;
		}
		return false;
	}
	bool sent = false;
	if(clientID < 0)
	{
		int excludedClientID = -clientID;
		for(int i = 1; i < 32; i++)
		{
			if(i == excludedClientID) continue;
			if(Elevator_ClientNear(id, i))
			{
				jjSendPacket(packet, i);
				sent = true;
			}
		}
		return sent;
	}
	for(int i = 1; i < 32; i++)
	{
		if(Elevator_ClientNear(id, i))
		{
			jjSendPacket(packet, i);
			sent = true;
		}
	}
	return sent;
}

int Elevator_PlayerPlatformElevatorId(int platformObjectID)
{
	if(platformObjectID <= 0 || uint(platformObjectID) >= elevatorObjectIds.length) return -1;
	int elevatorIndex = elevatorObjectIds[platformObjectID];
	if(elevatorIndex < 0 || uint(elevatorIndex) >= elevatorList.length) return -1;
	return elevatorList[elevatorIndex].id;
}

void sendElevatorPacket(int id, int dir_x, int dir_y, int clientID, bool localCheck = true, bool targetOnly = false){ //maybe id, command (cmd, locked or position)
	elevatorInfoClass eInfo;
	elevatorInfo.get(id+'', eInfo);
	
	int direction_x = dir_x;//int((last_xPos - jjObjects[eInfo.startObjId].xPos)/abs(last_xPos - jjObjects[eInfo.startObjId].xPos));
	int direction_y = dir_y;//int((last_yPos - jjObjects[eInfo.botObjId].yPos)/abs(last_yPos - jjObjects[eInfo.botObjId].yPos));
	float diff_x = jjObjects[eInfo.startObjId].xPos - jjObjects[eInfo.startObjId].xOrg;
	float diff_y = jjObjects[eInfo.botObjId].yPos - jjObjects[eInfo.botObjId].yOrg;
	bool moving = ENABLE_START_STOP_PACKETS && (direction_x != 0 || direction_y != 0);
	bool directionChanged = eInfo.last_direction_x != direction_x || eInfo.last_direction_y != direction_y;
	bool positionChanged = id <= 0 || uint(id) >= ElevatorLastSentX.length || ElevatorLastSentX[id] != diff_x || ElevatorLastSentY[id] != diff_y;
	bool intervalReady = id <= 0 || uint(id) >= ElevatorLastPositionSync.length || jjGameTicks - ElevatorLastPositionSync[id] >= ELEVATOR_SEND_PACKET_INTERVAL;
	bool shouldSend = ENABLE_START_STOP_PACKETS ? (directionChanged || (moving && positionChanged && intervalReady)) : (positionChanged && intervalReady);
	if(!localCheck || shouldSend){ //ONLY FOR TESTING id == 1
	//if(abs(last_xPos - jjObjects[eInfo.startObjId].xPos) >= 32 || abs(last_yPos - jjObjects[eInfo.botObjId].yPos) >= 32){
		if(DEBUG_ON && clientID == 0) jjAlert("dir: last: "+eInfo.last_direction_x+" "+eInfo.last_direction_y+" "+localCheck);
		eInfo.last_direction_x = direction_x;
		eInfo.last_direction_y = direction_y;
		elevatorInfo.set(id+'', eInfo);
		
		//!!!
		if(DEBUG_ON && clientID == 0) jjAlert("dir: id: "+id+" x: "+direction_x+" y: "+direction_y+" playerID: "+eInfo.playerID+" ctrl: "+eInfo.controlled+" locked: "+eInfo.locked);
		
		last_xPos = diff_x;
		last_yPos = diff_y;
		
		jjSTREAM elevatorPacket;
		elevatorPacket.push("elevatorSend");
		elevatorPacket.push(id);
		elevatorPacket.push(diff_x);
		elevatorPacket.push(diff_y);
		elevatorPacket.push(moving);
		bool sent = false;
		if(targetOnly && clientID != 0) sent = Elevator_SendPositionPacket(elevatorPacket, id, clientID, true);
		else if(clientID == 0) sent = Elevator_SendPositionPacket(elevatorPacket, id, 0);
		else sent = Elevator_SendPositionPacket(elevatorPacket, id, -clientID);
		if(sent && id > 0 && uint(id) < ElevatorLastSentX.length)
		{
			ElevatorLastSentX[id] = diff_x;
			ElevatorLastSentY[id] = diff_y;
			ElevatorLastPositionSync[id] = jjGameTicks;
		}
	}
}

void sendElevatorPlayer(int id, int playerID, int clientID, int actualPlayerID){
	//--if(jjIsServer && playerID == -1) setUpDownControl(actualPlayerID);
	jjSTREAM elevatorPacket;
	elevatorPacket.push("elevatorPlayer");
	elevatorPacket.push(id);
	elevatorPacket.push(playerID);
	elevatorPacket.push(actualPlayerID);
	if(clientID == 0) jjSendPacket(elevatorPacket);
	else jjSendPacket(elevatorPacket, -clientID);
}

void sendElevatorLock(int id, bool locked, int clientID, bool targetOnly = false){
	jjSTREAM elevatorPacket;
	elevatorPacket.push("elevatorLock");
	elevatorPacket.push(id);
	elevatorPacket.push(locked);
	if(targetOnly && clientID != 0) jjSendPacket(elevatorPacket, clientID);
	else if(clientID == 0) jjSendPacket(elevatorPacket);
	else jjSendPacket(elevatorPacket, -clientID);
}

void send_elevatorControl(int id, int playerID, int actualPlayerID, int value, int clientID){
	if(DEBUG_ON) jjAlert("send_: "+value);
	elevatorControl[actualPlayerID] = playerID == -1 ? -1 : id;
	if(value == -1) upElevatorControl[actualPlayerID] = id;
	else upElevatorControl[actualPlayerID] = -1;
	
	if(value != -1){
		setUpDownControl(actualPlayerID);
		
		if(playerID == -1){
			elevatorInfoClass eInfo;
			elevatorInfo.get(id+'', eInfo);
			eInfo.controlled = false;
			elevatorInfo.set(id+'', eInfo);
		}
	}else if(upDownControl[id] == 1 && value == -1){ //no player on the elevator
		upDownControl[id] = -1;
		//>>setUpDownControl(actualPlayerID);
		//<<elevatorControl[actualPlayerID] = -1;
	}
	jjSTREAM elevatorPacket;
	elevatorPacket.push("elevatorControl");
	elevatorPacket.push(id);
	elevatorPacket.push(playerID);
	elevatorPacket.push(actualPlayerID);
	elevatorPacket.push(value);
	if(clientID == 0) jjSendPacket(elevatorPacket);
	else jjSendPacket(elevatorPacket, -clientID);
}

void requestSync(){
	jjSTREAM clientPacket;
	clientPacket.push("reqElevatorSync");
	jjSendPacket(clientPacket);
}

void Elevator_OnReceive(jjSTREAM &in packet, int clientID) {
	string text;
	packet.pop(text);
	if(jjIsServer && jjRegexMatch(text, "reqElevatorSync", true)){
		string upDownControlStr = "", elevatorControlStr = "", upElevatorControlStr = "";
		for(uint i=0; i<upDownControl.length; i++){
			if(elevatorIds.find(int(i)) >= 0)
			{
				sendElevatorPacket(int(i), 0, 0, clientID, false, true);
				elevatorInfoClass eInfo;
				elevatorInfo.get(int(i)+'', eInfo);
				sendElevatorLock(int(i), eInfo.locked, clientID, true);
			}
			upDownControlStr += (upDownControl[i] == -1 ? "2" : ""+upDownControl[i]);
		}
		for(uint i=0; i<32; i++){
			elevatorControlStr += (elevatorControl[i] == -1 ? "2" : ""+elevatorControl[i]);
			upElevatorControlStr += (upElevatorControl[i] == -1 ? "2" : ""+upElevatorControl[i]);
		}
		jjSTREAM clientPacket;
		clientPacket.push("respElevatorSync");
		clientPacket.push(upDownControlStr);
		clientPacket.push(elevatorControlStr);
		clientPacket.push(upElevatorControlStr);
		jjSendPacket(clientPacket, clientID);
	}else if(!jjIsServer && jjRegexMatch(text, "respElevatorSync", true)){
		string upDownControlStr = "", elevatorControlStr = "", upElevatorControlStr = "";
		packet.pop(upDownControlStr);
		packet.pop(elevatorControlStr);
		packet.pop(upElevatorControlStr);
		
		if(DEBUG_ON) jjAlert("str "+upDownControlStr+", "+elevatorControlStr+", "+upElevatorControlStr);
		for(uint i=0; i<upDownControl.length && i < upDownControlStr.length; i++){
			upDownControl[i] = ((upDownControlStr.substr(i, 1) == "2") ? -1 : parseInt(upDownControlStr.substr(i, 1)));
		}
		for(uint i=0; i<32; i++){
			elevatorControl[i] = (elevatorControlStr.substr(i, 1) == "2" ? -1 : parseInt(elevatorControlStr.substr(i, 1)));
			upElevatorControl[i] = (upElevatorControlStr.substr(i, 1) == "2" ? -1 : parseInt(upElevatorControlStr.substr(i, 1)));
		}
		
		if(DEBUG_ON) jjAlert("id1_: "+upDownControl[1]+" players: "+elevatorControl[0]+" "+elevatorControl[1]+" "+elevatorControl[2]
			+" up: "+upElevatorControl[0]+" "+upElevatorControl[1]+" "+upElevatorControl[2]);
	}else if(jjRegexMatch(text, "elevatorSend", true)){
		bool moving;
		int id, direction_x, direction_y;
		float diff_x, diff_y;
		elevatorInfoClass eInfo;

		packet.pop(id);
		packet.pop(diff_x);
		packet.pop(diff_y);
		packet.pop(moving);
		if(jjIsServer && clientID == 0) return;

		elevatorInfo.get(id+'', eInfo);
		float currentDiffX = jjObjects[eInfo.startObjId].xPos - jjObjects[eInfo.startObjId].xOrg;
		float currentDiffY = jjObjects[eInfo.botObjId].yPos - jjObjects[eInfo.botObjId].yOrg;
		if(!moving)
		{
			direction_x = 0;
			direction_y = 0;
		}
		else
		{
			direction_x = Elevator_DirectionFromDiff(currentDiffX, diff_x);
			direction_y = Elevator_DirectionFromDiff(currentDiffY, diff_y);
		}
		if(id > 0 && uint(id) < ElevatorSyncedMoving.length) ElevatorSyncedMoving[id] = (moving && (direction_x != 0 || direction_y != 0)) ? 1 : 0;
		if(jjIsServer || !Elevator_LocalPlayerControls(id))
		{
			if(!jjIsServer && ELEVATOR_PREDICTION_ON && ELEVATOR_SEND_PACKET_INTERVAL > 1)
			{
				float deltaX = Elevator_Abs(diff_x - currentDiffX);
				float deltaY = Elevator_Abs(diff_y - currentDiffY);
				if(deltaX > 0.01 || deltaY > 0.01)
				{
					ElevatorPredictionPoint point;
					point.id = id;
					point.diff_x = diff_x;
					point.diff_y = diff_y;
					point.tick = jjGameTicks;
					ElevatorPredictionQueue.insertLast(point);
					int count = 0;
					for(int q = int(ElevatorPredictionQueue.length) - 1; q >= 0; q--)
					{
						if(ElevatorPredictionQueue[q].id != id) continue;
						count++;
						if(count > ELEVATOR_PREDICTION_MAX_QUEUE_POINTS) ElevatorPredictionQueue.removeAt(q);
					}
				}
			}
			else Elevator_ApplySyncedPosition(id, diff_x, diff_y);
		}
		
		if(jjIsServer){
			jjSTREAM elevatorPacket;
			elevatorPacket.push("elevatorSend");
			elevatorPacket.push(id);
			elevatorPacket.push(diff_x);
			elevatorPacket.push(diff_y);
			elevatorPacket.push(moving);
			Elevator_SendPositionPacket(elevatorPacket, id, -clientID);
		}
	}else if(jjRegexMatch(text, "elevatorLock", true)){
		int id;
		bool locked;
		packet.pop(id);
		packet.pop(locked);

		elevatorInfoClass eInfo;
		elevatorInfo.get(id+'', eInfo);
		eInfo.locked = locked;
		elevatorInfo.set(id+'', eInfo);

		if(jjIsServer) sendElevatorLock(id, locked, clientID);
	}else if(jjRegexMatch(text, "elevatorPlayer", true)){
		int id, playerID, actualPlayerID;
		packet.pop(id);
		packet.pop(playerID);
		packet.pop(actualPlayerID);
		
		elevatorInfoClass eInfo;
		elevatorInfo.get(id+'', eInfo);
		eInfo.direction_x = 0;
		eInfo.direction_y = 0;
		if(playerID == -1){
			/*elevatorControl[actualPlayerID] = -1;
			setUpDownControl();
			if(jjIsServer){
				jjAlert("1? "+actualPlayerID);
			}*/
			eInfo.controlled = false;
			eInfo.playerID = -1;
		}else{
			/*elevatorControl[actualPlayerID] = id;
			setUpDownControl();
			if(jjIsServer){
				jjAlert("2? "+actualPlayerID);
			}*/
			eInfo.controlled = true;
			eInfo.playerID = playerID;
		}
		elevatorInfo.set(id+'', eInfo);
		if(jjIsServer){
			sendElevatorPlayer(id, playerID, clientID, actualPlayerID);
		}
		Elevator_ClearPrediction(id);
	}else if(jjRegexMatch(text, "elevatorControl", true)){
		int id, playerID, actualPlayerID, value;
		packet.pop(id);
		packet.pop(playerID);
		packet.pop(actualPlayerID);
		packet.pop(value);
		
		elevatorControl[actualPlayerID] = playerID == -1 ? -1 : id;
		if(value == -1) upElevatorControl[actualPlayerID] = id;
		else upElevatorControl[actualPlayerID] = -1;
		
		if(value != -1){
			if(playerID == -1){
				elevatorInfoClass eInfo;
				elevatorInfo.get(id+'', eInfo);
				eInfo.controlled = false;
				elevatorInfo.set(id+'', eInfo);
			}
			setUpDownControl();
		}else if(upDownControl[id] == 1 && value == -1){ //no player on the elevator
			upDownControl[id] = -1;
			//>>setUpDownControl(actualPlayerID);
			//<<elevatorControl[actualPlayerID] = -1;
		}
		
		if(jjIsServer) send_elevatorControl(id, playerID, actualPlayerID, value, clientID);
		Elevator_ClearPrediction(id);
	}
}

void setUpDownControl(int actual = -1){ //just for server
	//if(actual != -1) elevatorControl[actual] = -1;
	array<int> upDownControlCount(upDownControl.length, 0);
	elevatorInfoClass eInfo;
	for(uint i=0; i<upDownControl.length; i++) upDownControl[i] = 1; //go down
	for(uint i=0; i<32; i++){
		elevatorControl[i] = jjPlayers[i].isInGame ? elevatorControl[i] : -1;
		if(elevatorControl[i] != -1){
			upDownControl[elevatorControl[i]] = 0;
			upDownControlCount[elevatorControl[i]]++;
			
			/*elevatorInfo.get(i+'', eInfo);
			eInfo.controlled = true;
			eInfo.playerID = i;
			elevatorInfo.set(i+'', eInfo);*/
		}
	}
	for(uint i=0; i<32; i++){
		if(upElevatorControl[i] != -1){
			if(upDownControlCount[elevatorControl[i]] == 1) upDownControl[upElevatorControl[i]] = -1;
		}
	}
	
	int id = 1;
	//elevatorInfoClass eInfo;
	elevatorInfo.get(id+'', eInfo);
	if(DEBUG_ON) jjAlert("id1: "+upDownControl[1]+" players: "+elevatorControl[0]+" "+elevatorControl[1]+" "+elevatorControl[2]
		+" "+eInfo.controlled+" "+eInfo.playerID+" up: "+upElevatorControl[0]+" "+upElevatorControl[1]+" "+upElevatorControl[2]);
}

void Elevator_ResetRuntime()
{
	gotElevators = false;
	elevatorList.resize(0);
	elevatorObjectIds.resize(0);
	ElevatorObjectIdsByElevatorId.resize(ELEVATOR_MAX_ID);
	for(uint i = 0; i < ElevatorObjectIdsByElevatorId.length; i++) ElevatorObjectIdsByElevatorId[i].resize(0);
	ElevatorGroupState.resize(ELEVATOR_MAX_ID);
	elevatorIds.resize(0);
	elevatorControl.resize(32);
	upElevatorControl.resize(32);
	upDownControl.resize(0);
	ElevatorLastPositionSync.resize(ELEVATOR_MAX_ID);
	ElevatorSyncedMoving.resize(ELEVATOR_MAX_ID);
	ElevatorLastSentX.resize(ELEVATOR_MAX_ID);
	ElevatorLastSentY.resize(ELEVATOR_MAX_ID);
	ElevatorPredictionQueue.resize(0);
	ElevatorPredictionActive.resize(ELEVATOR_MAX_ID);
	ElevatorPredictionTicks.resize(ELEVATOR_MAX_ID);
	ElevatorPredictionTargetTick.resize(ELEVATOR_MAX_ID);
	ElevatorPredictionTargetX.resize(ELEVATOR_MAX_ID);
	ElevatorPredictionTargetY.resize(ELEVATOR_MAX_ID);
	elevatorInfo.deleteAll();
	elevatorCmd.deleteAll();
	for(uint i = 0; i < ElevatorLastPositionSync.length; i++) ElevatorLastPositionSync[i] = -9999;
	for(uint i = 0; i < ElevatorGroupState.length; i++) ElevatorGroupState[i] = -9999;
	for(uint i = 0; i < ElevatorSyncedMoving.length; i++) ElevatorSyncedMoving[i] = 0;
	for(uint i = 0; i < ElevatorLastSentX.length; i++)
	{
		ElevatorLastSentX[i] = -999999.0;
		ElevatorLastSentY[i] = -999999.0;
	}
	for(uint i = 0; i < ElevatorPredictionActive.length; i++)
	{
		ElevatorPredictionActive[i] = 0;
		ElevatorPredictionTicks[i] = 0;
		ElevatorPredictionTargetTick[i] = 0;
		ElevatorPredictionTargetX[i] = 0;
		ElevatorPredictionTargetY[i] = 0;
	}
	for(uint i = 0; i < 32; i++)
	{
		elevatorControl[i] = -1;
		upElevatorControl[i] = -1;
	}
}