| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Elevator | Cranky | Mutator | N/A | |||||
//--/ @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;
}
}
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.