Downloads containing player_collision_detector.mut

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Player-to-Player Collision... froducish Mutator N/A Download file

File preview

#pragma name "Player-to-Player Collision Detector"
#pragma require "player_collision_detector_interface.asc"
#include "player_collision_detector_interface.asc"

array<jjBEHAVIOR> backedWeaponBehaviors(OBJECT::TNT + 1);
array<HANDLING::Player> backedWeaponPlayerHandlings(OBJECT::TNT + 1);

bool active = false;
array<int> playerForces(32, 0);
array<COLLIDE::HOOK@> onCollides(32);

void onCollide(jjPLAYER@ victim, jjPLAYER@ collider, COLLIDE::Collide collision) {
	for (uint i = 0; i < onCollides.length; i++) {
		COLLIDE::HOOK@ hook = onCollides[i];
		if (hook !is null) {
			hook(victim, collider, collision);
		}
	}
}

class CollisionDetector : COLLIDE::DetectorI {
	void setHook(COLLIDE::HOOK@ hook = onCollide, uint moduleID = jjScriptModuleID) {
		if (moduleID >= onCollides.length()) {
			onCollides.resize(moduleID * 2);
		}
		@onCollides[moduleID] = hook;
	}
	
	void unsetHook(uint moduleID = jjScriptModuleID) {
		@onCollides[moduleID] = null;
	}
	
	void startDetecting() {
		affectAllWeapons(makeDetectable);
		sendOfTypeExcept(PACKET_ON, jjLocalPlayers[0].playerID);
		active = true;
	}
	
	void stopDetecting() {
		affectAllWeapons(makeUndetectable);
		sendOfTypeExcept(PACKET_OFF, jjLocalPlayers[0].playerID);
		active = false;
	}
	
	string getVersion() const {
		return "1.1";
	}
};

CollisionDetector publicInstance;
CollisionDetector@ onGetPublicInterface() {
	return publicInstance;
}

enum packetType { 
	PACKET_FORWARD,
	PACKET_COLLIDE,
	PACKET_ON,
	PACKET_OFF
};

class BulletDetector : jjBEHAVIORINTERFACE {
	jjBEHAVIOR base;
	
	BulletDetector(jjBEHAVIOR base) {
		this.base = base;
	}
	
	void onBehave(jjOBJ@ obj) {
		obj.behave(base);
	}
	
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ victim, int force) {
		obj.playerHandling = victim.isEnemy(jjPlayers[obj.creatorID]) ? HANDLING::ENEMYBULLET : HANDLING::PLAYERBULLET;
		if (int(obj.creatorID) != victim.playerID) {
			sendCollision(obj.creatorID, victim.playerID, ((obj.var[6] & (1 << 3)) >>> 3) == 1 ? COLLIDE::BULLETPU : COLLIDE::BULLET);
		}
		return true;
	}
}

void onPlayerInput(jjPLAYER@ player) {
	if (active) {
		forwardPacket(player.playerID, player.getObjectHitForce());
	}
}

void onPlayer(jjPLAYER@ player) {
	if (active) {
		for (int i = 0; i < 32; i++) {
			jjPLAYER@ cand = jjPlayers[i];
			float x = cand.xPos - player.xPos;
			float y = cand.yPos - player.yPos;
			if (cand.isInGame && !cand.isLocal) {
				if (playerForces[i] != 0 && ((x*x)+(y*y)) <= (32 * 32)) {
					COLLIDE::Collide collision = playerForces[i] < 0 ? COLLIDE::SPECIALMOVE : COLLIDE::STOMPORRUSH;
					sendCollision(i, player.playerID, collision);
				} else if (((x*x)+(y*y)) <= (24 * 24)) {
					sendCollision(i, player.playerID, COLLIDE::BUMP);
				}
			}
		}
	}
}

void initDetector(int8 origin, int force) {
	playerForces[origin] = force;
	// Even though Gun8 should be already accounted for when startDetecting() is called, for some strange reason alreadyHandlesCollision on its behavior
	// returns false. Even stranger that it returns true only if Gun8 is in fireball mode. Can't explain that. Below's a quick hack needed to specifically
	// account for it.
	if (!jjAllowsFireball) {
		for (int i = 1; i < jjObjectCount; i++) {
			jjOBJ @obj = jjObjects[i];
			if (obj.isActive && (obj.eventID == OBJECT::BULLET) && obj.creatorType == CREATOR::PLAYER && int(obj.creatorID) == origin && !alreadyHandlesCollision(obj.behavior)) {
				obj.scriptedCollisions = true;
				obj.playerHandling = HANDLING::SPECIAL;
				obj.behavior = BulletDetector(obj.behavior);
			}
		}
	}
}

void onReceive(jjSTREAM &in packet, int clientID) {
	uint8 type;
	packet.pop(type);
	if (jjIsServer) {
		switch (type) {
			case PACKET_FORWARD: {
				int8 origin;
				packet.pop(origin);
				if (jjPlayers[origin].clientID == clientID) {
					int force;
					packet.pop(force);
					forwardPacket(origin, force);
					initDetector(origin, force); // This specifically deals with the non-server player vs server player (this) case.
				}
			}
			break;
			case PACKET_COLLIDE: {
				int8 victim;
				packet.pop(victim);
				if (jjPlayers[victim].clientID == clientID) {
					int8 origin;
					int collision;
					packet.pop(origin);
					packet.pop(collision);
					sendCollision(origin, victim, COLLIDE::Collide(collision));
				}
			}
			break;
			case PACKET_ON: {
				int8 playerID;
				packet.pop(playerID);
				if (jjPlayers[playerID].clientID == clientID) {
					affectAllWeapons(makeDetectable);
					active = true;
					sendOfTypeExcept(PACKET_ON, playerID);
				}
			}
			break;
			case PACKET_OFF: {
				int8 playerID;
				packet.pop(playerID);
				if (jjPlayers[playerID].clientID == clientID) {
					affectAllWeapons(makeUndetectable);
					active = false;
					sendOfTypeExcept(PACKET_OFF, playerID);
				}
			}
			break;
		}
	} else {
		switch (type) {
			case PACKET_FORWARD: {
				int8 origin;
				int force;
				packet.pop(origin);
				packet.pop(force);
				initDetector(origin, force);
			}
			break;
			case PACKET_COLLIDE: {
				int8 victim, origin;
				int collision;
				packet.pop(victim);
				packet.pop(origin);
				packet.pop(collision);
				onCollide(jjPlayers[victim], jjPlayers[origin], COLLIDE::Collide(collision));
			}
			break;
			case PACKET_ON: {
				affectAllWeapons(makeDetectable);
				active = true;
			}
			break;
			case PACKET_OFF: {
				affectAllWeapons(makeUndetectable);
				active = false;
			}
			break;
		}
	}
}

void forwardPacket(int8 playerID, int force) {
	jjSTREAM packet;
	packet.push(uint8(PACKET_FORWARD));
	packet.push(playerID);
	packet.push(force);
	jjSendPacket(packet, -jjPlayers[playerID].clientID);
}

void sendCollision(int8 attackID, int8 victimID, COLLIDE::Collide collision) {
	jjSTREAM packet;
	packet.push(uint8(PACKET_COLLIDE));
	packet.push(victimID);
	packet.push(attackID);
	packet.push(int(collision));
	jjSendPacket(packet, -jjPlayers[victimID].clientID);
	onCollide(jjPlayers[victimID], jjPlayers[attackID], collision);
}

void sendOfTypeExcept(packetType type, int8 playerID) {
	jjSTREAM packet;
	packet.push(uint8(type));
	packet.push(playerID);
	jjSendPacket(packet, -jjPlayers[playerID].clientID);
}

bool alreadyHandlesCollision(jjBEHAVIOR behavior) {
	jjBEHAVIORINTERFACE@ implementsInterface = cast<jjBEHAVIORINTERFACE>(behavior);
	if (implementsInterface !is null) {
		BulletDetector@ handlesCollision = cast<BulletDetector>(implementsInterface);
		return (handlesCollision !is null);
	}
	return false;
}

void makeDetectable(OBJECT::Object weapon) {
	jjBEHAVIOR behavior = jjObjectPresets[weapon].behavior;
	if (!alreadyHandlesCollision(behavior)) {
		backedWeaponBehaviors[weapon] = behavior;
		backedWeaponPlayerHandlings[weapon] = jjObjectPresets[weapon].playerHandling;
		jjObjectPresets[weapon].scriptedCollisions = true;
		jjObjectPresets[weapon].playerHandling = HANDLING::SPECIAL;
		jjObjectPresets[weapon].behavior = BulletDetector(backedWeaponBehaviors[weapon]);
	}
}

void makeUndetectable(OBJECT::Object weapon) {
	jjBEHAVIOR behavior = jjObjectPresets[weapon].behavior;
	if (alreadyHandlesCollision(behavior)) {
		jjObjectPresets[weapon].scriptedCollisions = false;
		jjObjectPresets[weapon].playerHandling = backedWeaponPlayerHandlings[weapon];
		jjObjectPresets[weapon].behavior = backedWeaponBehaviors[weapon];
	}
}

funcdef void VOIDFUNCOBJSCONST(OBJECT::Object);
void affectAllWeapons(VOIDFUNCOBJSCONST@ func) {
	for (int i = 0; i < OBJECT::SMOKERING; i++) {
		func(OBJECT::Object(i));
	}
	func(OBJECT::TNT);
}