Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
![]() |
Player-to-Player Collision... | froducish | Mutator | N/A | ![]() |
#pragma name "Player-to-Player Collision Detector"
#pragma require "player_collision_detector_header.asc"
#include "player_collision_detector_header.asc"
bool active = false;
array<COLLIDE::HOOK@> onCollides(32);
void onCollide(jjPLAYER@ victim, jjPLAYER@ collider, COLLIDE::Collide collision, uint8 eventID) {
for (uint i = 0; i < onCollides.length; i++) {
COLLIDE::HOOK@ hook = onCollides[i];
if (hook !is null) {
hook(victim, collider, collision, eventID);
}
}
}
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() {
sendOfTypeExcept(PACKET_ON, jjLocalPlayers[0].playerID);
active = true;
}
void stopDetecting() {
sendOfTypeExcept(PACKET_OFF, jjLocalPlayers[0].playerID);
active = false;
}
string getVersion() const {
return "2.0";
}
};
CollisionDetector publicInstance;
CollisionDetector@ onGetPublicInterface() {
return publicInstance;
}
namespace RESPONDER {
abstract class Responder : jjBEHAVIORINTERFACE {
protected jjPLAYER@ victim;
protected jjPLAYER@ collider;
protected jjOBJ@ bullet;
protected jjOBJ@ ob;
void onBehave(jjOBJ@ obj) {
obj.delete();
}
// This is to be used *after* narrowing down a region of collision. Used for collisions that may hurt or pass through a player.
protected bool bestSecondCheck() const final {
uint curAnim = victim.curAnim - jjAnimSets[victim.setID].firstAnim;
return !victim.isEnemy(collider) ||
(bullet !is null && (victim.frozen != 0 /*|| victim.doesCollide(bullet, true)*/)) || // Trade off a little precision by excluding doesCollide to prevent issues with player methods like hurt(...)
victim.blink != 0 ||
((victim.charCurr == CHAR::JAZZ || victim.charCurr == CHAR::SPAZ || victim.charCurr == CHAR::LORI) && (curAnim == RABBIT::HURT || curAnim == RABBIT::DIE)) ||
(victim.charCurr == CHAR::FROG && victim.curAnim == jjAnimSets[ANIM::FROG].firstAnim + 1) ||
((victim.charCurr == CHAR::BIRD || victim.charCurr == CHAR::BIRD2) && (victim.curAnim == jjAnimSets[ANIM::BIRD].firstAnim + 5 || victim.curAnim == jjAnimSets[ANIM::BIRD].firstAnim + 10));
}
protected void spawnObject() final {
@ob = jjObjects[jjAddObject(OBJECT::BEES, -1000, -1000, 0, CREATOR::OBJECT, BEHAVIOR::BEES)];
ob.behavior = this;
ob.counter = 0;
ob.playerHandling = HANDLING::PARTICLE;
ob.deactivates = false;
}
}
class HurtingForce : Responder {
private int force;
private bool bulletDidCollide;
HurtingForce(int force, jjPLAYER@ victim, jjPLAYER@ collider, jjOBJ@ bullet = null) {
this.force = force;
@this.victim = victim;
@this.collider = collider;
@this.bullet = bullet;
spawnObject();
}
void onBehave(jjOBJ@ obj) {
if ((bullet is null && obj.counter >= 70) || (bullet !is null && !bullet.isActive)) {
obj.delete();
}
// Used as a third check instead of a second check lol
bulletDidCollide = bulletDidCollide || (bullet is null ? false : victim.doesCollide(bullet, true));
if (bestSecondCheck()) {
if (bullet is null) {
sendCollision(collider.playerID, victim.playerID, force < 0 ? COLLIDE::SPECIALMOVE : COLLIDE::STOMP, 0);
} else {
float x = victim.xPos - bullet.xPos;
float y = victim.yPos - bullet.yPos;
// If they are enemies then be extra sure it REALLY is the bullet that affected this victim.
if (!victim.isEnemy(collider) || bulletDidCollide || (((x*x)+(y*y)) <= (24 * 24) * 2)) {
sendCollision(collider.playerID, victim.playerID, (bullet.var[6] & (1 << 3)) != 0 ? COLLIDE::BULLETPU : COLLIDE::BULLET, bullet.eventID);
}
}
obj.delete();
}
obj.counter++;
}
}
class SugarRush : Responder {
SugarRush(jjPLAYER@ victim, jjPLAYER@ collider) {
@this.victim = victim;
@this.collider = collider;
spawnObject();
}
void onBehave(jjOBJ@ obj) {
if (bestSecondCheck()) {
sendCollision(collider.playerID, victim.playerID, COLLIDE::SUGARRUSH, 0);
obj.delete();
} else if (obj.counter >= 70) {
obj.delete();
}
obj.counter++;
}
}
class Bump : Responder {
private float xSpeedOld;
private int victimOldDirection;
private int colliderOldDirection;
Bump(jjPLAYER@ collider, jjPLAYER@ victim) {
@this.victim = victim;
@this.collider = collider;
xSpeedOld = collider.xSpeed;
victimOldDirection = victim.direction;
colliderOldDirection = collider.direction;
spawnObject();
}
void onBehave(jjOBJ@ obj) {
if (obj.state == STATE::DONE) {
bumpDone.set(0, collider, false);
obj.delete();
return;
}
if (obj.counter >= 35) {
obj.state = STATE::DONE;
} else if ((victim.direction != victimOldDirection &&!isGlidingBird(victim)) || (collider.direction != colliderOldDirection && !isGlidingBird(collider))) {
obj.state = STATE::WAIT;
} else if (obj.state != STATE::WAIT && collider.idle < 2 && victim.idle < 2 && sgn(collider.xSpeed) != sgn(xSpeedOld) && sgn(collider.xSpeed) != 0){
sendBump(collider.playerID, victim.playerID);
obj.state = STATE::WAIT;
}
obj.counter++;
}
private int sgn(float num) const {
return num < 0 ? -1 : num > 0 ? 1 : 0;
}
private bool isGlidingBird(const jjPLAYER@ player) const {
return (player.charCurr == CHAR::BIRD || player.charCurr == CHAR::BIRD2) && player.curAnim < jjAnimSets[ANIM::BIRD].firstAnim + 4;
}
}
}
class BooleansPerLocal {
private array<uint8> arr;
BooleansPerLocal(uint capacity) {
arr = array<uint8>(capacity, 0);
}
void set(uint idx, const jjPLAYER@ player, bool value) {
uint8 mask = 1 << uint8(player.localPlayerID);
arr[idx] &= ~mask;
if (value) {
arr[idx] |= mask;
}
}
bool get(uint idx, const jjPLAYER@ player) const {
uint8 mask = 1 << uint8(player.localPlayerID);
return arr[idx] & mask != 0;
}
}
BooleansPerLocal bulletDone(jjObjectMax);
BooleansPerLocal hitForceDone(32);
BooleansPerLocal bumpDone(1);
void onPlayer(jjPLAYER@ player) {
if (active) {
for (int i = 0; i < 32; i++) {
jjPLAYER@ collider = jjPlayers[i];
if (collider.isInGame && collider !is player) {
if (playerForces[i] == 0 && hitForceDone.get(i, player)) {
hitForceDone.set(i, player, false);
}
float x = collider.xPos - player.xPos;
float y = collider.yPos - player.yPos;
float radiusSquared = (x*x) + (y*y);
if (!hitForceDone.get(i, player) && playerForces[i] != 0 && radiusSquared <= (32 * 32)) {
hitForceDone.set(i, player, true);
RESPONDER::HurtingForce(playerForces[i], player, collider);
} else if (hasSugarRush[i] && radiusSquared <= (32 * 32)) {
RESPONDER::SugarRush(player, collider);
}
// Actually in this case the player takes responsiblity as the bumper, not the bumpee
if (playerForces[player.playerID] == 0 && playerForces[i] == 0 && (radiusSquared <= (32 * 32)) && (jjGameMode != GAME::CTF || player.isEnemy(collider)) && player.blink == 0 && collider.blink == 0 && !bumpDone.get(0, player)) {
bumpDone.set(0, player, true);
RESPONDER::Bump(player, collider);
}
}
}
for (int i = 1; i < jjObjectCount; i++) {
jjOBJ@ obj = jjObjects[i];
if (!bulletDone.get(i, player) && obj.isActive && (obj.eventID < OBJECT::SMOKERING || obj.eventID == OBJECT::TNT) && obj.creatorType == CREATOR::PLAYER && int(obj.creatorID) != player.playerID) {
float x = player.xPos - obj.xPos;
float y = player.yPos - obj.yPos;
if (((x*x)+(y*y)) <= (20 * 20) * 2) {
bulletDone.set(i, player, true);
RESPONDER::HurtingForce(0, player, jjPlayers[obj.creatorID], obj);
}
} else if (bulletDone.get(i, player) && !obj.isActive) {
bulletDone.set(i, player, false);
}
}
}
}
// Forces are modified to exclude sugar rush from the constant 1. Sugar rush will be checked separately.
array<int> playerForces(32, 0);
void onPlayerInput(jjPLAYER@ player) {
if (active) {
uint curAnim = player.curAnim - jjAnimSets[p.setID].firstAnim;
bool isStomping = (player.charCurr == CHAR::JAZZ || player.charCurr == CHAR::SPAZ || player.charCurr == CHAR::LORI) && (curAnim == RABBIT::FALLBUTTSTOMP || curAnim == RABBIT::BUTTSTOMPLAND);
int force = player.getObjectHitForce() != -1 ? (isStomping ? 1 : 0) : -1;
int8 origin = player.playerID;
// Only if hit force actually changed locally so we don't bombard others with packets.
if (playerForces[origin] != force) {
updateStatus(origin, force);
}
}
}
array<bool> hasSugarRush(32, false);
// Why is this the only hook to use a reference (&) instead of a nullable reference (@)? :S
void onPlayerDraw(jjPLAYERDRAW& playerdraw) {
if (active) {
bool rush = playerdraw.sugarRush;
int8 origin = playerdraw.player.playerID;
if (hasSugarRush[origin] != rush) {
updateStatus(origin, rush);
}
}
}
void onMain() {
if (jjIsServer && active) {
for (int i = 0; i < 32; i++) {
if (!jjPlayers[i].isInGame && (playerForces[i] != 0 || hasSugarRush[i])) {
updateStatus(i, 0);
updateStatus(i, false);
}
}
}
}
enum PacketType {
PACKET_UPDATE,
PACKET_COLLIDE,
PACKET_BUMP,
PACKET_ON,
PACKET_OFF
};
void onReceive(jjSTREAM &in packet, int clientID) {
uint8 type;
packet.pop(type);
if (jjIsServer) {
switch (type) {
case PACKET_UPDATE: {
int8 origin;
packet.pop(origin);
if (jjPlayers[origin].clientID == clientID) {
uint8 code;
packet.pop(code);
if (code == 0) {
int force;
packet.pop(force);
updateStatus(origin, force);
} else {
bool rush;
packet.pop(rush);
updateStatus(origin, rush);
}
}
}
break;
case PACKET_COLLIDE: {
int8 victim;
packet.pop(victim);
if (jjPlayers[victim].clientID == clientID) {
int8 origin;
int collision;
uint8 eventID;
packet.pop(origin);
packet.pop(collision);
packet.pop(eventID);
sendCollision(origin, victim, COLLIDE::Collide(collision), eventID);
}
}
break;
case PACKET_BUMP: {
int8 attack;
packet.pop(attack);
if (jjPlayers[attack].clientID == clientID) {
int8 victim;
packet.pop(victim);
sendBump(attack, victim);
}
} break;
case PACKET_ON: {
int8 playerID;
packet.pop(playerID);
if (jjPlayers[playerID].clientID == clientID) {
active = true;
sendOfTypeExcept(PACKET_ON, playerID);
}
}
break;
case PACKET_OFF: {
int8 playerID;
packet.pop(playerID);
if (jjPlayers[playerID].clientID == clientID) {
active = false;
sendOfTypeExcept(PACKET_OFF, playerID);
}
}
break;
}
} else {
switch (type) {
case PACKET_UPDATE: {
int8 origin;
packet.pop(origin);
uint8 code;
packet.pop(code);
if (code == 0) {
int force;
packet.pop(force);
playerForces[origin] = force;
} else {
bool rush;
packet.pop(rush);
hasSugarRush[origin] = rush;
}
}
break;
case PACKET_COLLIDE: {
int8 victim, origin;
int collision;
uint8 eventID;
packet.pop(victim);
packet.pop(origin);
packet.pop(collision);
packet.pop(eventID);
onCollide(jjPlayers[victim], jjPlayers[origin], COLLIDE::Collide(collision), eventID);
}
break;
case PACKET_BUMP: {
int8 attack, victim;
packet.pop(attack);
packet.pop(victim);
onCollide(jjPlayers[victim], jjPlayers[attack], COLLIDE::BUMP, 0);
} break;
case PACKET_ON: {
active = true;
}
break;
case PACKET_OFF: {
active = false;
}
break;
}
}
}
void updateStatus(int8 playerID, int force) {
playerForces[playerID] = force;
jjSTREAM packet;
packet.push(uint8(PACKET_UPDATE));
packet.push(playerID);
packet.push(uint8(0));
packet.push(force);
jjSendPacket(packet, -jjPlayers[playerID].clientID);
}
void updateStatus(int8 playerID, bool rush) {
hasSugarRush[playerID] = rush;
jjSTREAM packet;
packet.push(uint8(PACKET_UPDATE));
packet.push(playerID);
packet.push(uint8(1));
packet.push(rush);
jjSendPacket(packet, -jjPlayers[playerID].clientID);
}
void sendCollision(int8 attackID, int8 victimID, COLLIDE::Collide collision, uint8 eventID) {
jjSTREAM packet;
packet.push(uint8(PACKET_COLLIDE));
packet.push(victimID);
packet.push(attackID);
packet.push(int(collision));
packet.push(eventID);
jjSendPacket(packet, -jjPlayers[victimID].clientID);
onCollide(jjPlayers[victimID], jjPlayers[attackID], collision, eventID);
}
void sendBump(int8 attackID, int8 victimID) {
jjSTREAM packet;
packet.push(uint8(PACKET_BUMP));
packet.push(attackID);
packet.push(victimID);
jjSendPacket(packet, -jjPlayers[attackID].clientID);
onCollide(jjPlayers[victimID], jjPlayers[attackID], COLLIDE::BUMP, 0);
}
void sendOfTypeExcept(PacketType type, int8 playerID) {
jjSTREAM packet;
packet.push(uint8(type));
packet.push(playerID);
jjSendPacket(packet, -jjPlayers[playerID].clientID);
}
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.