Downloads containing deathcircle.mut

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Death CircleFeatured Download SmokeNC Mutator 9 Download file

File preview

/* Death Circle mutator for LRS, written by Smoke[NC] and VioletCLM DM april 2023 */
/* VERSION 1.0 */

/* CONFIG VARIABLES */
/* Speed of moving the center of the circle */
const float cSpeed = 1;
/* Minimum radius that the circle reaches after defaultCompletionTime */
const float rMin = 300;
/* Time it takes to reach minimum radius, in seconds */
const float defaultCompletionTime = 120;

const uint8 ChromaKeyIndex = 25; 
int LayerZ;

void onLevelLoad()
{
	auto layers = jjLayerOrderGet();
	LayerZ = 4 - layers.findByRef(jjLayers[4]) + 1;
	jjLAYER storm(1,1);
	storm.spriteMode = SPRITE::CHROMAKEY;
	storm.spriteParam = ChromaKeyIndex;
	storm.textureSurface = SURFACE::FULLSCREEN;
	storm.xSpeed = storm.ySpeed = 1.1;
	jjPIXELMAP stormImage(TEXTURE::DESOLATION);
	array<uint8> recolor(256);
	for (uint i = 0; i < 32; ++i)
		recolor[176 + i] = ChromaKeyIndex + uint8(i / 5.2);
	stormImage.recolor(recolor).makeTexture(storm);
	storm.textureStyle = TEXTURE::WAVE;
	storm.tileWidth = storm.tileHeight = true;
	layers.insertAt(0, storm);
	jjLayerOrderSet(layers);
	
	//mess with these numbers and see what looks good
	storm.wave.amplitudeX = 0.125;
	storm.wave.wavelengthX = 127;
	storm.wave.amplitudeY = 0.25;
	storm.wave.wavelengthY = 255;
	storm.wave.waveSpeed = 3;
}
	
float max(float x, float y)
{
	return x > y? x : y;
}

class Point
{
	float x;
	float y;
}

class DeathCircle
{
	float r; /* Radius */
	Point c; /* Center */
    float rSpeed; 
	float rOrig;

	/* Death circle will always move to center of mass of the players */
	Point CalcCenterOfMass()
	{
		Point cNew;
		cNew.x = 0;
		cNew.y = 0;
		
		int numPlayersInGame = 0;
		for(int iP = 0; iP < 32; iP++)
		{
			if(jjPlayers[iP].isInGame && !jjPlayers[iP].isZombie)
			{
				numPlayersInGame++;
				cNew.x += jjPlayers[iP].xPos;
				cNew.y += jjPlayers[iP].yPos;
			}
		}
		
		if(numPlayersInGame != 0)
		{
			cNew.x /= numPlayersInGame;
			cNew.y /= numPlayersInGame;
		}

		return cNew;
	}
	
	void UpdateSpeed(float completionTime)
	{
		if(completionTime > 0)
		{
			rSpeed = (r - rMin) / (completionTime * 70);
		}
		else /* Special effect, expand the circle!*/
		{
			rSpeed = (rOrig - rMin) / (completionTime * 70);
		}
	}
	
	DeathCircle()
	{	
		r = 32 * sqrt(jjLayerWidth[4] * jjLayerWidth[4] + jjLayerHeight[4] * jjLayerHeight[4]) / 2 ;
		rOrig = r;
		
		UpdateSpeed(defaultCompletionTime);
		
		c.x = 32 * jjLayerWidth[4] / 2;
		c.y = 32 * jjLayerHeight[4] / 2;
	}
	
	bool GoTo(float objx1, float objy1, float & out objx, float & out objy, float Startx, float Starty, float Endx, float Endy, float Speed, float Behind)
	{
	    float angle = atan2((Endx - Startx), (Endy - Starty));
	    objx1 += sin(angle) * Speed;
	    objy1 += cos(angle) * Speed;
	    objx = objx1;
	    objy = objy1;
        if (((Endx + Behind * sin(angle) - objx1) * (Endx + Behind * sin(angle) - objx1) 
		+ (Endy + Behind * cos(angle) - objy1) * (Endy + Behind * cos(angle) - objy1))
		< (Speed * 1.1 + 3) * (Speed * 1.1 + 3))
        return true; //arrived to destination
        else return false; //not arrived to destination
    }

	void UpdateCircle()
	{
	    Point cNew = CalcCenterOfMass();

		Point cMaybeNew;
		bool arrived = GoTo(c.x, c.y, cMaybeNew.x, cMaybeNew.y, c.x, c.y, cNew.x, cNew.y, cSpeed, 0);
		/* To avoid circle jitter */
		if(!arrived)
		{
			c = cMaybeNew;
		}
		r = max(rMin, r - rSpeed);
	}

	bool OutsideCircle(float x, float y)
	{
		return (x - c.x) * (x - c.x) + (y - c.y) * (y - c.y) > r * r;
	}
}
	
DeathCircle deathCircle;

void onPlayer(jjPLAYER@ player)
{
    /* DRAW DEATH CIRCLE*/
	
	int height = jjResolutionHeight + 5;
	int width = jjResolutionWidth + 5;
	float r = deathCircle.r;
	Point c = deathCircle.c;
	
	/* Draw circle using horizontal rectangles, 
	we draw 600 horizontal rectangles instead of drawing 600 * 800 pixels, 
	which is much faster! */
	for(int iY = 0; iY < height; iY++)
	{
		float recWidth;
		float y = player.cameraY + iY;
		float x0 = 0, x1 = 0;
		bool drawRec0, drawRec1;
		
		/* Circle equation:
		   (x-xC)^2 + (y-yC)^2 = r^2;
		   Attempt to solve for x0,x1:
		   x0,1 = xC +- sqrt(r^2 - (y-yC)^2) */
		   
		float dRYSq = r * r - (y - c.y) * (y - c.y);
		
		/* If this horizontal line is outside the circle, fill the entire width */
		if(dRYSq < 0)
		{
			x0 = player.cameraX + width;
			drawRec0 = true;
			drawRec1 = false;
		}
		else /*This horizontal line intersects the circle in 2 points */
		{
			float dRY = sqrt(dRYSq);
			x0 = c.x - dRY;
			x1 = c.x + dRY;
			
			drawRec0 = true;
			drawRec1 = true;
		}
		
		/* Draw the rectangles:
		   Left-Camera-Edge|---------------x0                      x1-----------------|Right-Camera-Edge */
		if(drawRec0)
		{
			if(deathCircle.OutsideCircle(player.cameraX + jjResolutionWidth / 2, player.cameraY + jjResolutionHeight / 2))
			jjDrawRectangle(player.cameraX, y, int(x0 - player.cameraX), 1, ChromaKeyIndex, SPRITE::TRANSLUCENT, 0, -10);
			else
			jjDrawRectangle(player.cameraX, y, int(x0 - player.cameraX), 1, ChromaKeyIndex, SPRITE::SINGLECOLOR, ChromaKeyIndex, LayerZ);
		}
		
		if(drawRec1)
		{
			if(deathCircle.OutsideCircle(player.cameraX + jjResolutionWidth / 2, player.cameraY + jjResolutionHeight / 2))
			jjDrawRectangle(x1, y, int(player.cameraX + width - x1), 1, ChromaKeyIndex, SPRITE::TRANSLUCENT, 0 , -10);
			else
			jjDrawRectangle(x1, y, int(player.cameraX + width - x1), 1, ChromaKeyIndex, SPRITE::SINGLECOLOR, ChromaKeyIndex, LayerZ);
		}
	}
	
	
	/*HURT IF OUTSIDE CIRCLE*/
	
	if(deathCircle.OutsideCircle(player.xPos, player.yPos) && (jjGameState == GAME::STARTED || jjGameState == GAME::OVERTIME))
	{
		player.hurt();
	}
}

enum packetCmd
{	
	COMMAND_NONE,
	COMMAND_CLIENT_REQUEST_UPDATE_FROM_SERVER,
	COMMAND_PLAYER_WANTS_BROADCAST
}

void PopCircleStats(jjSTREAM &in packet)
{
	packet.pop(deathCircle.c.y);
	packet.pop(deathCircle.c.x);
	packet.pop(deathCircle.r);
	packet.pop(deathCircle.rSpeed);
}

void PushCircleStats(jjSTREAM &inout packet)
{
	packet.push(deathCircle.c.y);
	packet.push(deathCircle.c.x);
	packet.push(deathCircle.r);
	packet.push(deathCircle.rSpeed);
}

void BroadcastCircleStats()
{
	jjSTREAM packet;
	if(jjIsServer)
	{
		packet.push(COMMAND_NONE);
	}
	else
	{
		packet.push(COMMAND_PLAYER_WANTS_BROADCAST);
	}
	PushCircleStats(packet);
	jjSendPacket(packet);
}

void ClientRequestCircleStats()
{
	jjSTREAM packet;
	packet.push(COMMAND_CLIENT_REQUEST_UPDATE_FROM_SERVER);
	jjSendPacket(packet);
}

void onReceive(jjSTREAM &in packet, int clientID)
 {
	int cmd;
	packet.pop(cmd);
	switch (cmd) 
	{
		case COMMAND_CLIENT_REQUEST_UPDATE_FROM_SERVER:
			if (jjIsServer) 
			{
				BroadcastCircleStats();
			}
			break;
		case COMMAND_PLAYER_WANTS_BROADCAST:
			if (jjIsServer) 
			{
				PopCircleStats(packet);
				BroadcastCircleStats();
			}
			break;
		case COMMAND_NONE:
			if (!jjIsServer) 
			{
				PopCircleStats(packet);
			}
			break;
		default: 
			jjAlert("Error in packet command!"); 
			break;
	}
}

bool onLocalChat(string &in stringReceived, CHAT::Type chatType) 
{
	array<string> results;
	
	if (jjRegexIsValid(stringReceived) && (jjIsAdmin || jjIsServer)) 
	{
		if (jjRegexMatch(stringReceived, "!deathcircle\\s+(.+)", results, true)) 
		{
			float circleCompletionTime = parseFloat(results[1]);
			if(circleCompletionTime != 0.f)
			{
				jjAlert("DeathCircle timelimit has been set to " + circleCompletionTime + " minutes", true);
				deathCircle.UpdateSpeed(circleCompletionTime * 60);
				BroadcastCircleStats();
				return true;
			}
		}
	}

	return false;
}

void onLevelBegin()
{
	jjAlert("ADMINS: type !deathcircle <time_m> to change DeathCircle timelimit for this match, default is 2");
	deathCircle = DeathCircle();
	if(!jjIsServer)
	{
		ClientRequestCircleStats();
	}
}

void onMain()
{
	if(jjGameState == GAME::STARTED || jjGameState == GAME::OVERTIME)
	{
		deathCircle.UpdateCircle();
	}
}