Downloads containing BoxSync.asc

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

File preview

bool DEBUG_ON = false;
bool DEBUG_ALERTS_ON = false;
bool ONLY_COOP_MODES = true; //set to false if you want crates to be solid in all modes (not only SP and COOP)
bool checkGamemode = true;
bool doOnce = false;
bool ok_solid = false;
array<jjOBJ@> solids;
array<int> solidDirection;
array<int> pushLeftPlayers(32, -1);
array<int> pushRightPlayers(32, -1);
array<bool> playerSynced(32, false);
array<int> latchedSolid(32, -1);
array<int> latchedDir(32, 0);
array<float> lastStreamX;
array<float> lastStreamY;
int kk = 0;

const int BOXSYNC_POSITION_SEND_INTERVAL = 1;
const float BOX_PUSH_SPEED = 0.5f;
const int BORDER_THICKNESS_SMALL_OBJECT = 32;
const int BORDER_THICKNESS_BIG_OBJECT = 32;
const int epsilon = 3;
const int BODY_BLOCK_THICKNESS = 8;
const int PUSH_CONTACT_GAP = 2;
const float BOXSYNC_POSITION_EPSILON = 0.05f;

bool isSolidBoxObject(jjOBJ@ obj){
	return obj.behavior == BEHAVIOR::AMMO15 || obj.behavior == BEHAVIOR::BIGOBJECT || obj.behavior == BEHAVIOR::CRATE || obj.behavior == BEHAVIOR::MONITOR || obj.eventID == OBJECT::BIGBOX || obj.eventID == OBJECT::BIGROCK || obj.eventID == OBJECT::FIRESHIELD || obj.eventID == OBJECT::WATERSHIELD || obj.eventID == OBJECT::LIGHTNINGSHIELD || obj.eventID == OBJECT::MORPH || obj.eventID == OBJECT::BIRDMORPH || obj.eventID == OBJECT::BLASTERPOWERUP || obj.eventID == OBJECT::BOUNCERPOWERUP || obj.eventID == OBJECT::ICEPOWERUP || obj.eventID == OBJECT::SEEKERPOWERUP || obj.eventID == OBJECT::RFPOWERUP || obj.eventID == OBJECT::TOASTERPOWERUP || obj.eventID == OBJECT::TNTPOWERUP || obj.eventID == OBJECT::GUN8POWERUP || obj.eventID == OBJECT::GUN9POWERUP;
}

bool isBigBoxObject(jjOBJ@ obj){
	return obj.behavior == BEHAVIOR::BIGOBJECT || obj.eventID == OBJECT::BIGBOX || obj.eventID == OBJECT::BIGROCK;
}

void normalizeBigBoxObject(jjOBJ@ obj){
	if(obj.eventID == OBJECT::BIGBOX || obj.eventID == OBJECT::BIGROCK) obj.behavior = BEHAVIOR::BIGOBJECT;
}

bool hasBoxObjectAtTile(int eventID, int x, int y){
	for(int i = 1; i < jjObjectCount; i++){
		jjOBJ@ obj = jjObjects[i];
		if(!obj.isActive || int(obj.eventID) != eventID) continue;
		if(int(obj.xOrg / 32) == x && int(obj.yOrg / 32) == y) return true;
	}
	return false;
}

void spawnMissingBigBoxObjects(){
	for(int y = 0; y < jjLayerHeight[4]; y++){
		for(int x = 0; x < jjLayerWidth[4]; x++){
			int eventID = jjEventGet(x, y);
			if(eventID != OBJECT::BIGBOX && eventID != OBJECT::BIGROCK) continue;
			if(hasBoxObjectAtTile(eventID, x, y)) continue;
			int objectID = jjAddObject(eventID, x * 32, y * 32, 0, CREATOR::LEVEL, BEHAVIOR::DEFAULT);
			if(objectID > 0 && objectID < jjObjectCount) normalizeBigBoxObject(jjObjects[objectID]);
		}
	}
}

int findSolidIndexByOrigin(int eventID, float xOrg, float yOrg){
	for(uint i = 0; i < solids.length; i++){
		jjOBJ@ obj = solids[i];
		if(obj is null || !obj.isActive) continue;
		if(int(obj.eventID) == eventID && abs(obj.xOrg - xOrg) < 8 && abs(obj.yOrg - yOrg) < 8) return int(i);
	}
	return -1;
}

void ensureSolidSetup(){
	if(!doOnce){
		spawnMissingBigBoxObjects();
		for(int i=0; i<jjObjectCount; i++){
			jjOBJ@ obj = jjObjects[i];
			normalizeBigBoxObject(obj);
			if(isSolidBoxObject(obj)){ 
				obj.clearPlatform();
			}
		}
		doOnce = true;
	}

	if(!ok_solid){
		kk = 0;
		solids.resize(0);
	
		for (int i = 1; i < jjObjectCount; i++) {
			jjOBJ@ obj = jjObjects[i];
			normalizeBigBoxObject(obj);
			if((jjGameMode == GAME::SP || jjGameMode == GAME::COOP) && isSolidBoxObject(obj)){ 
				solids.resize(kk+1);
				@solids[kk] = obj;
				kk++;
			}
		}
		solidDirection.resize(solids.length);
		lastStreamX.resize(solids.length);
		lastStreamY.resize(solids.length);
		for(uint i=0; i<solidDirection.length; i++){
			solidDirection[i] = 0; //making sure
			if(solids[i] !is null){
				lastStreamX[i] = solids[i].xPos;
				lastStreamY[i] = solids[i].yPos;
			}else{
				lastStreamX[i] = 0;
				lastStreamY[i] = 0;
			}
		}
		ok_solid = true;
	}
}

void BoxSync_OnLevelLoad()
{
	checkGamemode = true;
	doOnce = false;
	ok_solid = false;
	solids.resize(0);
	solidDirection.resize(0);
	lastStreamX.resize(0);
	lastStreamY.resize(0);
	pushLeftPlayers.resize(32);
	pushRightPlayers.resize(32);
	for(uint i=0; i<32; i++)
	{
		pushLeftPlayers[i] = -1;
		pushRightPlayers[i] = -1;
		playerSynced[i] = false;
		latchedSolid[i] = -1;
		latchedDir[i] = 0;
	}
	kk = 0;
}

void BoxSync_OnMain()
{
	ensureSolidSetup();

	if(jjIsServer){
		syncNewClients();
	}

	syncMovedSolidPositions();
}

void syncNewClients(){
	for(uint p = 0; p < 32; p++){
		if(jjPlayers[p].isInGame){
			if(!playerSynced[p]){
				playerSynced[p] = true;

				int targetClientID = jjPlayers[p].clientID;

				// clientID 0 is the server/local player. Do not use the normal
				// broadcast path for join sync, because this is meant only for
				// remote clients who just entered.
				if(targetClientID > 0){
					// Send all current object positions to this specific player.
					// IMPORTANT: this must use a positive clientID. The old code used
					// send_solidPosPacket(i, targetClientID), but that function sends
					// to -clientID, meaning "everyone except this client". That is why
					// the joining client never received the sync.
					for(uint i = 0; i < solids.length; i++){
						send_solidPosPacketToClient(i, targetClientID);
					}

					// Optional: also send current push state to this one client.
					for(uint q = 0; q < 32; q++){
						if(pushLeftPlayers[q] != -1) send_solidPacketToClient(pushLeftPlayers[q], q, -1, targetClientID);
						if(pushRightPlayers[q] != -1) send_solidPacketToClient(pushRightPlayers[q], q, 1, targetClientID);
					}
				}
			}
		}else{
			playerSynced[p] = false;
			pushLeftPlayers[p] = -1;
			pushRightPlayers[p] = -1;
			latchedSolid[p] = -1;
			latchedDir[p] = 0;
		}
	}
	setSolidControl(-1, 0);
}

void BoxSync_OnDrawLayer4(jjPLAYER@ play, jjCANVAS@ canvas)
{
}

void BoxSync_OnPlayer(jjPLAYER@ play)
{
	if(!play.isLocal) return;
	solidCode(play);
}

bool BoxSync_OnReceive(jjSTREAM &in packet, int clientID)
{
	string text;
	packet.pop(text);
	
	if(jjRegexMatch(text, "solidSend", true)){
		ensureSolidSetup();
		
		int id, playerID, dir;
		packet.pop(id);
		packet.pop(playerID);
		packet.pop(dir);
		
		if(id < 0 || id >= int(solids.length) || playerID < 0 || playerID >= 32) return true;
		
		int newSolidPlayer = dir == 0 ? -1 : id;
		pushLeftPlayers[playerID] = dir == -1 ? newSolidPlayer : -1;
		pushRightPlayers[playerID] = dir == 1 ? newSolidPlayer : -1;
		setSolidControl(id, dir);
		
		if(jjIsServer){
			jjSTREAM solidPacket;
			solidPacket.push("solidSend");
			solidPacket.push(id);
			solidPacket.push(playerID);
			solidPacket.push(dir);
			jjSendPacket(solidPacket, -clientID);
		}
		return true;
	}
	else if(jjRegexMatch(text, "solidPos", true)){
		ensureSolidSetup();
		
		int id, eventID;
		float xOrg, yOrg, xPos, yPos;
		packet.pop(id);
		packet.pop(eventID);
		packet.pop(xOrg);
		packet.pop(yOrg);
		packet.pop(xPos);
		packet.pop(yPos);
		
		if(id < 0 || id >= int(solids.length)) id = findSolidIndexByOrigin(eventID, xOrg, yOrg);
		if(id < 0 || id >= int(solids.length)) return true;
		
		solids[id].xPos = xPos;
		solids[id].yPos = yPos;
		rememberStreamPosition(id);
		
		if(jjIsServer){
			jjSTREAM solidPacket;
			solidPacket.push("solidPos");
			solidPacket.push(id);
			solidPacket.push(eventID);
			solidPacket.push(xOrg);
			solidPacket.push(yOrg);
			solidPacket.push(xPos);
			solidPacket.push(yPos);
			jjSendPacket(solidPacket, -clientID);
		}
		return true;
	}
	
	return false;
}

void solidCode(jjPLAYER@ play){
	ensureSolidSetup();

	//crate pushing code
	if(jjGameMode == GAME::SP || jjGameMode == GAME::COOP || !ONLY_COOP_MODES){
		checkGamemode = true;
		
		for (uint i = 0; i < solids.length; i++) {
			jjOBJ@ obj = solids[i];
			
			if(obj is null) continue;
			if(!obj.isActive)
			{ 
				obj.clearPlatform();
				continue;
			}
			
			if(obj.isActive && isSolidBoxObject(obj))
			{ 
				if(obj.state == STATE::KILL)
				{
					obj.clearPlatform();
				}
				else
				{
					jjANIMFRAME@ frame = @::jjAnimFrames[obj.curFrame];
					int width = frame.width;
					int height = frame.height;
					int hotX = frame.hotSpotX;
					int hotY = frame.hotSpotY;
					obj.beSolid();
					
					int isSolid = getPushDirection(play, obj, width, hotX, hotY, height);
					
					//removing this IF will cause the players to pass through the object
					if(isSolid != 0 && solidDirection[i] != 2)
					{
						//collision
						if(!wouldHitWall(obj, isSolid, width, hotX, hotY, height)){
							obj.xPos += isSolid * BOX_PUSH_SPEED;
							send_solidPacket(i, play.playerID, isSolid, play.clientID);
						}else{
							send_solidPacket(i, play.playerID, 0, play.clientID);
						}
					}
					else
					{
						bool col = false;
						bool descend = true;
						for (uint j = 0; j < solids.length; j++) {
							jjOBJ@ obj2 = solids[j];
							if(i != j && obj.objectID != obj2.objectID && obj2.state == STATE::SLEEP){ //state of crate
								jjANIMFRAME@ frame2 = @::jjAnimFrames[obj2.curFrame];
								int width2 = frame2.width;
								int height2 = frame2.height;
								int hotX2 = frame2.hotSpotX;
								int hotY2 = frame2.hotSpotY;
								if(abs(obj.yPos - obj2.yPos) <= 16 && abs(obj.xPos - obj2.xPos) <= 100){
									if((abs((obj.xPos + hotX) - (obj2.xPos + hotX2 + width2)) <= 2 && isSolid == -1) || (abs((obj.xPos + hotX + width) - (obj2.xPos + hotX2)) <= 2 && isSolid == 1)){
										col = true;
										break;
									}
								}else if(abs(obj.xPos - obj2.xPos) <= 32 && obj2.yPos - obj.yPos >= 0 && obj2.yPos - obj.yPos <= height/2 + height2/2){
									descend = false;
								}
							}
						}
						bool obj_moved = false;
						//can push object
						int localDirection = isSolid; //if you use obj.beSolid() multiple times here, then pushing animation won't go into effect
						bool keyCondition = (play.keyRight && localDirection == 1) || (play.keyLeft && localDirection == -1);
						bool keyCondition2 = (!play.keyRight && localDirection == 1 && pushRightPlayers[play.playerID] == int(i)) || (!play.keyLeft && localDirection == -1 && pushLeftPlayers[play.playerID] == int(i));
						
						float pDis = findNearestPlayerToObject(obj, play.playerID);//abs(jjPlayers[pID].xPos - obj.xPos);
						bool pFoundCond = false;
						pFoundCond = jjIsServer && pDis < (isBigBoxObject(obj) ? 64 : 32); //= obj.doesCollide(jjPlayers[pID]);
						
						if(col == false && solidDirection[i] != 2 && !pFoundCond && !wouldHitWall(obj, localDirection, width, hotX, hotY, height)){ //&& keyCondition){
							obj.xPos += localDirection * BOX_PUSH_SPEED;
							obj_moved = localDirection != 0;
						}else if(pFoundCond){
							localDirection = 0;
							obj.xPos += localDirection * 0.5f;
							//obj.direction = 0;
							//obj.clearPlatform();
							//obj_moved = true;
						}
						//if(i == 0 && jjGameTicks%70 == 0) jjAlert("hmm "+localDirection+" "+obj.state);
						
						if(jjMaskedHLine(int(obj.xPos)+hotX+1, width-1, int(obj.yPos)+hotY+height) == false && descend == true){
							obj.yPos += 2;
							obj_moved = true;
						}
						
						//if this wasn't moved locally
						send_solidPacket(i, play.playerID, localDirection, 0, pDis);						
						if(!pFoundCond){
							// Do not predict remote pushes here. The active mover streams xPos/yPos,
							// and everyone else only applies solidPos packets. This prevents stale
							// non-pusher positions from fighting the pusher.
							obj.xPos += 0;
						}
					}
				}
			}
		}

		syncMovedSolidPositions();
	}else{
		if(checkGamemode){
			for(int i=0; i<32; i++){
				if(jjPlayers[i].isInGame){
					if(jjPlayers[i].platform != 0){
						jjOBJ@ obj = jjObjects[jjPlayers[i].platform];
						obj.clearPlatform();
						normalizeBigBoxObject(obj);
						if(obj.isActive && isSolidBoxObject(obj)){ 
							jjPlayers[i].platform = 0;
						}
					}
				}
			}
			checkGamemode = false;
			doOnce = false;
		}
	}
}
void send_solidPacket(int id, int playerID, int dir, int clientID, float pDis = -1){
	int newSolidPlayer = dir == 0 ? -1 : id;
	bool enterCondition = ((dir == -1 || dir == 0) && pushLeftPlayers[playerID] != newSolidPlayer) || ((dir == 1 || dir == 0) && pushRightPlayers[playerID] != newSolidPlayer);
	if(solidDirection[id] != dir && enterCondition){
		if(DEBUG_ALERTS_ON) jjAlert("dir "+solidDirection[id]+" "+dir+" "+pDis);
		//solidDirection[id] = dir;
		pushLeftPlayers[playerID] = dir == -1 ? newSolidPlayer : -1;
		pushRightPlayers[playerID] = dir == 1 ? newSolidPlayer : -1;
		setSolidControl(id, dir);
		
		//send control packet only; position is handled by solidPos streaming
		jjSTREAM solidPacket;
		solidPacket.push("solidSend");
		solidPacket.push(id);
		solidPacket.push(playerID);
		solidPacket.push(dir);
		if(clientID == 0) jjSendPacket(solidPacket);
		else jjSendPacket(solidPacket, -clientID);
	}
}

void send_solidPacketToClient(int id, int playerID, int dir, int targetClientID){
	if(id < 0 || id >= int(solids.length)) return;
	if(playerID < 0 || playerID >= 32) return;
	if(targetClientID <= 0) return;

	jjSTREAM solidPacket;
	solidPacket.push("solidSend");
	solidPacket.push(id);
	solidPacket.push(playerID);
	solidPacket.push(dir);
	jjSendPacket(solidPacket, targetClientID);
}

void send_solidPosPacketToClient(int id, int targetClientID){
	if(id < 0 || id >= int(solids.length)) return;
	if(targetClientID <= 0) return;

	jjOBJ@ obj = solids[id];
	if(obj is null || !obj.isActive) return;

	jjSTREAM solidPacket;
	solidPacket.push("solidPos");
	solidPacket.push(id);
	solidPacket.push(int(obj.eventID));
	solidPacket.push(obj.xOrg);
	solidPacket.push(obj.yOrg);
	solidPacket.push(obj.xPos);
	solidPacket.push(obj.yPos);
	jjSendPacket(solidPacket, targetClientID);
	rememberStreamPosition(id);
}

void send_solidPosPacket(int id, int clientID){
	if(id < 0 || id >= int(solids.length)) return;
	jjOBJ@ obj = solids[id];
	if(obj is null || !obj.isActive) return;

	jjSTREAM solidPacket;
	solidPacket.push("solidPos");
	solidPacket.push(id);
	solidPacket.push(int(obj.eventID));
	solidPacket.push(obj.xOrg);
	solidPacket.push(obj.yOrg);
	solidPacket.push(obj.xPos);
	solidPacket.push(obj.yPos);
	if(clientID == 0) jjSendPacket(solidPacket);
	else jjSendPacket(solidPacket, -clientID);
	rememberStreamPosition(id);
}


void rememberStreamPosition(int id){
	if(id < 0 || id >= int(solids.length)) return;
	if(id >= int(lastStreamX.length)) return;
	jjOBJ@ obj = solids[id];
	if(obj is null) return;
	lastStreamX[id] = obj.xPos;
	lastStreamY[id] = obj.yPos;
}

bool solidPositionChangedSinceStream(int id){
	if(id < 0 || id >= int(solids.length)) return false;
	if(id >= int(lastStreamX.length)) return false;
	jjOBJ@ obj = solids[id];
	if(obj is null || !obj.isActive) return false;
	return abs(obj.xPos - lastStreamX[id]) > BOXSYNC_POSITION_EPSILON || abs(obj.yPos - lastStreamY[id]) > BOXSYNC_POSITION_EPSILON;
}

void syncMovedSolidPositions(){
	if(!ok_solid) return;
	for(uint i = 0; i < solids.length; i++){
		if(solidPositionChangedSinceStream(int(i))){
			send_solidPosPacket(int(i), 0);
			rememberStreamPosition(int(i));
		}
	}
}

void setSolidControl(int id, int dir){
	for(uint i=0; i<solidDirection.length; i++) solidDirection[i] = 0;
	for(uint i=0; i<32; i++){
		int localSolidDirection = 0;
		pushLeftPlayers[i] = jjPlayers[i].isInGame ? pushLeftPlayers[i] : -1;
		pushRightPlayers[i] = jjPlayers[i].isInGame ? pushRightPlayers[i] : -1;
		if(pushLeftPlayers[i] != -1 && 0 <= pushLeftPlayers[i] && pushLeftPlayers[i] < int(solidDirection.length)){
			if(solidDirection[pushLeftPlayers[i]] == 1){
				solidDirection[pushLeftPlayers[i]] = 2; //pushing from both directions
				//jjAlert("2to: "+i);
			}else solidDirection[pushLeftPlayers[i]] = -1; //for solidDirection[pushLeftPlayers[i]] = -1 or 0
		}else if(pushRightPlayers[i] != -1 && 0 <= pushRightPlayers[i] && pushRightPlayers[i] < int(solidDirection.length)){
			if(solidDirection[pushRightPlayers[i]] == -1){
				solidDirection[pushRightPlayers[i]] = 2; //pushing from both directions
				//jjAlert("2to: "+i);
			}else solidDirection[pushRightPlayers[i]] = 1; //for solidDirection[pushRightPlayers[i]] = -1 or 0
		}
	}
	if(DEBUG_ALERTS_ON) jjAlert("solid direction set: "+id+" "+solidDirection[id]+", "+
		pushLeftPlayers[0]+" "+pushLeftPlayers[1]+" "+pushLeftPlayers[2]+", "+
		pushRightPlayers[0]+" "+pushRightPlayers[1]+" "+pushRightPlayers[2]);
}

float findNearestPlayerToObject(jjOBJ@ obj, int skipPlayerID){
	float min = 200; int p = -1;
	for(uint i=0; i<32; i++){
		if (int(i) == skipPlayerID) continue;
		jjPLAYER@ play = jjPlayers[i]; 
		//+++if(play.isInGame && !play.isLocal)
		if(play.isInGame)
		{
			if(pushLeftPlayers[i] == -1 && pushRightPlayers[i] == -1 && abs(obj.yPos-play.yPos) < 32){
				float value = abs(obj.xPos-play.xPos); //*(obj.xPos-play.xPos) + (obj.yPos-play.yPos)*(obj.yPos-play.yPos);
				if(value < min){
					min = value;
					p = i;
					//if(!jjIsServer) jjAlert("min "+p+" "+min);
				}
			}
		}
	}
	return min;
}


/// <summary>
/// getPushDirection is a replacement for obj.beSolid()
/// as that is unreliable because it can say that the player is pushing for -1/1 frame
/// then for the next frame the player may not be perfectly aligned with the border
/// and the result is "not pushing" 0. So it will be 0,1,0,1...
/// Using these border thickness variables is an attempt to increase the area in which
/// the player is considered to be pushing the object
/// <summary>
int getPushDirection(jjPLAYER@ play, jjOBJ@ obj, int width, int hotX, int hotY, int height) {
	int thickness = isBigBoxObject(obj) ? BORDER_THICKNESS_BIG_OBJECT : BORDER_THICKNESS_SMALL_OBJECT;
	
	float leftBorderXMin = obj.xPos + hotX - thickness;
	float leftBorderXMax = obj.xPos + hotX + epsilon;
	
	float rightBorderXMin = obj.xPos + hotX + width - epsilon;
	float rightBorderXMax = obj.xPos + hotX + width + thickness;
	
	float objTop = obj.yPos + hotY;
	float objBottom = obj.yPos + hotY + height;

	float playerXCenter = play.xPos;
	bool yOK = playerOverlapsY(objTop, objBottom, play);

	int result = 0;

	// Local player is on left side, pushing object right
	if (
		playerXCenter >= leftBorderXMin &&
		playerXCenter <= leftBorderXMax &&
		yOK &&
		play.keyRight
	) {
		result = 1;
	}

	// Local player is on right side, pushing object left
	if (
		playerXCenter >= rightBorderXMin &&
		playerXCenter <= rightBorderXMax &&
		yOK &&
		play.keyLeft
	) {
		result = -1;
	}

	// Bodyblock check:
	// If another player is standing in the opposite side area,
	// cancel the push before any movement happens.
	if(result != 0) {
		for(uint i = 0; i < 32; i++) {
			if(!jjPlayers[i].isInGame || int(i) == play.playerID) continue;

			jjPLAYER@ other = jjPlayers[i];

			float otherXCenter = other.xPos;
			bool otherYOK = playerOverlapsY(objTop, objBottom, other);

			if(!otherYOK) continue;

			bool otherOnLeft = otherXCenter >= leftBorderXMin && otherXCenter <= leftBorderXMax;
			bool otherOnRight = otherXCenter >= rightBorderXMin && otherXCenter <= rightBorderXMax;

			// pushing right, blocked by someone standing on right side
			if(result == 1 && otherOnRight) {
				result = 0;
				break;
			}

			// pushing left, blocked by someone standing on left side
			if(result == -1 && otherOnLeft) {
				result = 0;
				break;
			}
		}
	}

	if(DEBUG_ON) {
		jjDrawRectangle(
			int(leftBorderXMin),
			int(obj.yPos + hotY),
			int(leftBorderXMax - leftBorderXMin),
			height,
			result == 1 ? 24 : 12,
			SPRITE::NORMAL,
			0
		);
		
		jjDrawRectangle(
			int(rightBorderXMin),
			int(obj.yPos + hotY),
			int(rightBorderXMax - rightBorderXMin),
			height,
			result == -1 ? 25 : 43,
			SPRITE::NORMAL,
			0
		);
		
		jjDrawRectangle(
			int(playerXCenter),
			int(play.yPos),
			3,
			32,
			2,
			SPRITE::NORMAL,
			0
		);
	}

	return result;
}


bool wouldHitWall(jjOBJ@ obj, int dir, int width, int hotX, int hotY, int height){
	if(dir == 0) return false;

	float nextX = obj.xPos + dir * BOX_PUSH_SPEED;
	int checkX = dir > 0 ? int(nextX + hotX + width + 1) : int(nextX + hotX - 1);
	int checkY = int(obj.yPos + hotY + 1);
	int checkHeight = height - 2;
	if(checkHeight < 1) checkHeight = height;

	return jjMaskedVLine(checkX, checkY, checkHeight);
}

bool playerOverlapsY(float objTop, float objBottom, jjPLAYER@ play) {
	float playerTop = play.yPos - 16;
	float playerBottom = play.yPos + 16;
	return playerBottom >= objTop && playerTop <= objBottom;
}