| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Characters | Cranky | Mutator | N/A | |||||
#pragma require "Resize v10.asc"
#include "Resize v10.asc"
#pragma require "nyancat.bmp"
namespace CharsNyanRush
{
// Per-player preference and live visual state. Neither uses jjPLAYER::invisibility.
array<bool> enabled(32, false);
array<bool> rushing(32, false);
array<bool> visualActive(32, false);
ANIM::Set nyanCatAnim;
bool spriteReady = false;
bool requestedInitialState = false;
bool IsEnabled(int playerID)
{
return playerID >= 0 && playerID < int(enabled.length) && enabled[playerID];
}
bool IsActive(int playerID)
{
return playerID >= 0 && playerID < int(visualActive.length) && visualActive[playerID];
}
// Character, monster/object, hat, and ledge renderers use this guard.
bool ShouldSuppressVisuals(int playerID)
{
return IsActive(playerID);
}
// Sugar Rush is exposed directly by jjPLAYERDRAW::sugarRush.
// Do not infer it from getObjectHitForce() or animation IDs: those values
// also occur during ordinary movement/landing transitions.
void UpdateServerRushState(jjPLAYERDRAW &inout draw)
{
if(!jjIsServer) return;
int playerID = draw.player.playerID;
if(playerID < 0 || playerID >= 32) return;
if(!draw.player.isActive || !draw.player.isInGame)
{
bool hadState = rushing[playerID] || visualActive[playerID];
rushing[playerID] = false;
visualActive[playerID] = false;
if(hadState)
SendState(playerID);
return;
}
// This is the actual engine Sugar Rush state, not an approximation.
bool newRushing = draw.sugarRush;
bool newVisualActive = enabled[playerID] && newRushing;
rushing[playerID] = newRushing;
if(newVisualActive != visualActive[playerID])
{
visualActive[playerID] = newVisualActive;
SendState(playerID);
}
}
void LoadSprite()
{
TrueColor::ProcessPalette();
TrueColor::EnableCaching();
TrueColor::Bitmap spriteSheet("nyancat.bmp");
jjPALCOLOR background = spriteSheet.pixels[0][0];
jjPALCOLOR transparent = jjPALCOLOR(0, 0, 0);
for(uint x = 0; x < spriteSheet.width; x++)
{
for(uint y = 0; y < spriteSheet.height; y++)
{
if(spriteSheet.pixels[x][y] == background)
spriteSheet.pixels[x][y] = transparent;
}
}
array<array<TrueColor::Coordinates>> frames = {
{
TrueColor::Coordinates(2, 12, 37, 22, -15, -10),
TrueColor::Coordinates(48, 10, 40, 24, -15, -10),
TrueColor::Coordinates(96, 11, 36, 22, -15, -10),
TrueColor::Coordinates(138, 11, 37, 23, -15, -10),
TrueColor::Coordinates(181, 11, 36, 22, -15, -10),
TrueColor::Coordinates(220, 11, 36, 22, -15, -10)
}
};
nyanCatAnim = TrueColor::FindCustomAnim();
Resize::AllocateAndResizeTrueColorSpriteSheet(nyanCatAnim, spriteSheet, frames, 2);
spriteReady = true;
}
void onLevelLoad()
{
spriteReady = false;
requestedInitialState = false;
LoadSprite();
}
void ApplyState(int playerID, bool newEnabled, bool newVisualActive)
{
if(playerID < 0 || playerID >= 32) return;
enabled[playerID] = newEnabled;
visualActive[playerID] = newVisualActive;
}
void SendState(int playerID, int toClientID = 0, bool joiningSnapshot = false)
{
if(playerID < 0 || playerID >= 32) return;
jjSTREAM packet;
packet.push((joiningSnapshot ? "!charsNyanRushStateBegin " : "!charsNyanRushState ")
+ playerID + " " + (enabled[playerID] ? 1 : 0)
+ " " + (visualActive[playerID] ? 1 : 0));
if(toClientID == 0)
jjSendPacket(packet);
else
jjSendPacket(packet, toClientID);
}
void SendAllStatesToClient(int clientID)
{
if(!jjIsServer || clientID == 0) return;
for(int i = 0; i < 32; i++)
{
if(jjPlayers[i].isActive)
SendState(i, clientID, true);
}
}
void SetEnabledOnServer(int playerID, bool value)
{
if(!jjIsServer || playerID < 0 || playerID >= 32) return;
enabled[playerID] = value;
visualActive[playerID] = value && rushing[playerID];
SendState(playerID);
}
bool SenderOwnsPlayer(int playerID, int fromClientID)
{
if(playerID < 0 || playerID >= 32 || !jjPlayers[playerID].isActive)
return false;
if(fromClientID == 0)
return jjPlayers[playerID].isLocal;
return jjPlayers[playerID].clientID == fromClientID;
}
void RequestEnabled(int playerID, bool value)
{
jjSTREAM packet;
packet.push("!charsNyanRushRequest " + playerID + " " + (value ? 1 : 0));
jjSendPacket(packet);
}
void RequestAllStates()
{
if(jjIsServer) return;
jjSTREAM packet;
packet.push("!charsNyanRushAll");
jjSendPacket(packet);
}
void onReceive(jjSTREAM &in packet, int fromClientID)
{
string line = "";
if(!packet.pop(line)) return;
array<string> parts = line.split(" ");
if(parts.length == 0) return;
if((line == "!all" || line == "!charsNyanRushAll") && jjIsServer)
{
SendAllStatesToClient(fromClientID);
return;
}
if(parts[0] == "!charsNyanRushRequest" && parts.length >= 3 && jjIsServer)
{
int playerID = parseInt(parts[1]);
bool value = parseInt(parts[2]) != 0;
if(SenderOwnsPlayer(playerID, fromClientID))
SetEnabledOnServer(playerID, value);
return;
}
if((parts[0] == "!charsNyanRushState" || parts[0] == "!charsNyanRushStateBegin")
&& parts.length >= 4)
{
int playerID = parseInt(parts[1]);
if(playerID >= 0 && playerID < 32)
ApplyState(playerID, parseInt(parts[2]) != 0, parseInt(parts[3]) != 0);
}
}
bool onLocalChat(string &in text, CHAT::Type)
{
array<string> parts = text.split(" ");
if(parts.length == 0 || !jjRegexMatch(parts[0], "^!nyanrush$", true))
return false;
if(parts.length != 2 || !(jjRegexMatch(parts[1], "^(on|off|1|0)$", true)))
{
jjAlert("Usage: !nyanrush ON or !nyanrush OFF", false);
return true;
}
bool value = jjRegexMatch(parts[1], "^(on|1)$", true);
for(int i = 0; i < jjLocalPlayerCount; i++)
{
int playerID = jjLocalPlayers[i].playerID;
if(jjIsServer)
SetEnabledOnServer(playerID, value);
else
RequestEnabled(playerID, value);
}
jjAlert("Nyan Rush: " + (value ? "ON" : "OFF"), false);
return true;
}
void onMainUpdate()
{
if(!jjIsServer)
{
if(!requestedInitialState && jjGameTicks >= 2)
{
requestedInitialState = true;
RequestAllStates();
}
return;
}
for(int i = 0; i < 32; i++)
{
if(!jjPlayers[i].isActive)
{
bool hadState = enabled[i] || rushing[i] || visualActive[i];
enabled[i] = false;
rushing[i] = false;
visualActive[i] = false;
if(hadState)
SendState(i);
}
}
}
void DrawPlayerName(jjPLAYER@ play)
{
jjTEXTAPPEARANCE style(STRING::NORMAL);
style.align = STRING::CENTER;
jjDrawString(
play.xPos + 2,
play.yPos - 27,
"\xa71" + play.name,
STRING::SMALL,
style,
0,
SPRITE::NORMAL,
0,
1
);
}
void AddTrailParticle(jjPLAYER@ player, int yOffset, int color)
{
jjPARTICLE@ part = jjAddParticle(PARTICLE::STAR);
int direction = player.direction < 0 ? 1 : -1;
part.xPos = player.xPos + 15 * direction;
part.yPos = player.yPos + yOffset + (jjSin((jjGameTicks % 35) * (1024 / 35)) - 0.5) * 5;
part.xSpeed = direction * 5;
part.star.size = 12;
part.star.color = color;
}
void DrawTrail(jjPLAYER@ player)
{
// Two stars every second tick, instead of the original six every tick.
// This retains the rainbow trail without multiplying particle load when
// several opted-in players rush at once.
if((jjGameTicks & 1) != 0) return;
int phase = (jjGameTicks / 2 + player.playerID) % 3;
if(phase == 0)
{
AddTrailParticle(player, -18, 24);
AddTrailParticle(player, 4, 19);
}
else if(phase == 1)
{
AddTrailParticle(player, -11, 43);
AddTrailParticle(player, 11, 35);
}
else
{
AddTrailParticle(player, -4, 40);
AddTrailParticle(player, 18, 93);
}
}
void onMainDraw()
{
if(!spriteReady) return;
for(int i = 0; i < 32; i++)
{
if(!IsActive(i) || !jjPlayers[i].isActive || !jjPlayers[i].isInGame)
continue;
jjPLAYER@ play = jjPlayers[i];
DrawPlayerName(play);
TrueColor::DrawSprite(
play.xPos,
play.yPos,
nyanCatAnim,
0,
(jjGameTicks >> 3) * TrueColor::NumberOfFramesPerImage,
play.direction
);
DrawTrail(play);
}
}
void onPlayerDraw(jjPLAYERDRAW &inout draw)
{
UpdateServerRushState(draw);
if(!ShouldSuppressVisuals(draw.player.playerID)) return;
draw.sprite = false;
draw.name = false;
}
}
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.