| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Characters | Cranky | Mutator | N/A | |||||
namespace CharsSoundDownload
{
const int PACKET_MAGIC = 91017;
const int PACKET_REQUEST = 1;
const int PACKET_BEGIN = 2;
const int PACKET_DATA = 3;
const int PACKET_DONE = 4;
const int PACKET_ERROR = 5;
const int PACKET_CANCEL = 6;
const int ACTION_DOWNLOAD = 91001;
const int ACTION_CANCEL = 91002;
const int ACTION_CLOSE = 91003;
const int ACTION_FORCE_DOWNLOAD = 91004;
const int SEND_BYTES_PER_TICK = 768;
const string PROGRESS_FILE = "charsSoundDownloadProgress.asdat";
const array<string> SoundFiles = {
"smbjumpsuper.wav",
"smbjumpsmall.wav",
"sm64marioyahoo.wav",
"sm64marioyippee.wav",
"smbpipe.wav",
"sm64mariodoh.wav",
"sm64mariohaha.wav",
"sm64mariohoo.wav",
"sm64mariohoohoo.wav",
"smbpowerupappears.wav",
"smbpowerup.wav",
"sm64mariowaha.wav",
"sm64mariookeydokey.wav",
"smbmariodie.wav",
"sfx_38_0.wav",
"sfx_38_1.wav",
"sfx_38_2.wav",
"sfx_38_3.wav",
"sfx_22_62.wav",
"sfx_22_114.wav",
"sfx_92_0.wav",
"sfx_92_1.wav",
"sfx_92_2.wav",
"sfx_92_3.wav",
"sfx_92_4.wav",
"sfx_92_5.wav",
"sfx_92_6.wav",
"sfx_92_7.wav",
"sfx_92_8.wav",
"sfx_92_9.wav",
"sfx_92_10.wav",
"sfx_92_11.wav",
"sfx_92_12.wav",
"sfx_92_13.wav",
"sfx_92_14.wav",
"sfx_92_15.wav",
"sfx_92_16.wav",
"sfx_92_17.wav",
"sfx_92_18.wav",
"sfx_63_8_silent.wav",
"sfx_63_10_silent.wav",
"sfx_63_12_silent.wav",
"sfx_63_13_silent.wav",
"sfx_63_0.wav",
"sfx_63_1.wav",
"sfx_63_2.wav",
"sfx_63_3.wav",
"sfx_63_4.wav",
"sfx_63_5.wav",
"sfx_63_6.wav",
"sfx_63_7.wav",
"sfx_63_8.wav",
"sfx_63_9.wav",
"sfx_63_10.wav",
"sfx_63_11.wav",
"sfx_63_12.wav",
"sfx_63_13.wav",
"sfx_63_14.wav",
"sfx_63_15.wav",
"sfx_63_16.wav",
"sfx_63_17.wav",
"sfx_63_18.wav",
"Silent.wav",
"sonic_bumper.wav",
"sonic_chaos_emerald.wav",
"sonic_checkpoint.wav",
"sonic_extra_life.wav",
"sonic_jump.wav",
"sonic_modern_jump.wav",
"sonic_ring.wav",
"sonic_rings_drop.wav",
"sonic_sega.wav",
"sonic_slow.wav",
"sonic_spindash.wav",
"sonic_spring.wav",
"sonic_turbo_spin.wav",
"sonic_woo.wav",
"YOSHI_AUTOSCH1 [Hurt].wav",
"YOSHI_AUTOSCH2 [Hurt].wav",
"YOSHI_EATBIRD [MLEM].wav",
"YOSHI_HAHAHA.wav",
"YOSHI_HAHAHA2.wav",
"YOSHI_HAPPY [SuperCarrot].wav",
"YOSHI_HOHOHO1 [Hippo xD].wav",
"YOSHI_JUMP.wav",
"YOSHI_Jump_Another.wav",
"YOSHI_KARATE7 [FlutterJump].wav",
"YOSHI_KARATE8 [Angry].wav",
"YOSHI_OHOH [Happy Yoshi].wav",
"YOSHI_OOOH [Shield Hurt].wav",
"YOSHI_Owowowowow Death.wav",
"YOSHI_WOOHOO [Wow].wav",
"YOSHI_YAHOO [ButtStomp].wav",
"YOSHI_YAHOO2 [Spring].wav",
"TUNE_Spaz.wav",
"TUNE_Lori.wav",
"TUNE_SuperMario.wav",
"TUNE_SonicHedgehog.wav",
"TUNE_Devan.wav",
"TUNE_Yoshisaur.wav"
};
DbWindow window;
DbResult result;
bool menuOpen = false;
bool downloading = false;
bool shownHint = false;
int downloadedCount = 0;
int currentIndex = -1;
int currentSize = 0;
int receivedBytes = 0;
string statusText = "Idle";
jjSTREAM@ receiveBuffer = null;
array<bool> downloadedFiles(SoundFiles.length, false);
bool progressLoaded = false;
class ServerTransfer
{
bool active = false;
int clientID = 0;
int index = -1;
int size = 0;
int progress = 0;
jjSTREAM@ stream = null;
}
array<ServerTransfer> transfers(32);
void onLevelLoad()
{
menuOpen = false;
downloading = false;
shownHint = false;
downloadedCount = CountDownloaded();
currentIndex = -1;
currentSize = 0;
receivedBytes = 0;
statusText = "Idle";
@receiveBuffer = @jjSTREAM();
progressLoaded = false;
ClearProgressInMemory();
downloadedCount = 0;
if(jjIsServer)
for(uint i = 0; i < transfers.length; i++)
transfers[i].active = false;
}
void onLevelBegin()
{
if(!shownHint)
{
shownHint = true;
jjAlert("To download the characters sound files write |||!charSoundsDownload", false);
}
}
bool onLocalChat(string &in text, CHAT::Type)
{
if(!jjRegexMatch(text, "^!charSoundsDownload$", true))
return false;
menuOpen = true;
EnsureProgressLoaded();
downloadedCount = CountDownloaded();
statusText = downloadedCount >= int(SoundFiles.length) ? "All sound files are already downloaded" : "Idle";
return true;
}
void onMain()
{
if(jjIsServer)
ProcessServerTransfers();
}
int SoundFileIndex(const string &in filename)
{
for(uint i = 0; i < SoundFiles.length; i++)
if(SoundFiles[i] == filename) return int(i);
return -1;
}
void EnsureProgressLoaded()
{
if(progressLoaded) return;
// Do not open a missing local progress file on clients. JJ2 treats that
// as a built-in server file request and can kick with "Server denied you".
progressLoaded = true;
}
void ClearProgressInMemory()
{
downloadedFiles.resize(SoundFiles.length);
for(uint i = 0; i < downloadedFiles.length; i++)
downloadedFiles[i] = false;
}
void SaveProgress()
{
jjSTREAM save;
for(uint i = 0; i < SoundFiles.length; i++)
if(downloadedFiles[i])
save.write(SoundFiles[i] + "\n");
save.save(PROGRESS_FILE);
}
void MarkDownloaded(int index)
{
if(!IsAllowedIndex(index)) return;
downloadedFiles[index] = true;
SaveProgress();
}
int CountDownloaded()
{
int count = 0;
for(uint i = 0; i < SoundFiles.length; i++)
if(downloadedFiles[i]) count++;
return count;
}
int FirstMissingIndex()
{
for(uint i = 0; i < SoundFiles.length; i++)
if(!downloadedFiles[i]) return int(i);
return -1;
}
bool IsAllowedIndex(int index)
{
return index >= 0 && index < int(SoundFiles.length);
}
void StartDownload()
{
if(downloading) return;
EnsureProgressLoaded();
downloadedCount = CountDownloaded();
int index = FirstMissingIndex();
if(index < 0)
{
statusText = "All sound files are already downloaded";
return;
}
downloading = true;
RequestFile(index);
}
void ForceDownload()
{
if(downloading) CancelDownload();
ClearProgressInMemory();
progressLoaded = true;
SaveProgress();
downloadedCount = 0;
statusText = "Starting forced download";
downloading = true;
RequestFile(0);
}
void CancelDownload()
{
if(downloading && !jjIsServer)
SendCancel();
else if(downloading && jjIsServer)
HandleCancel(0);
downloading = false;
currentIndex = -1;
currentSize = 0;
receivedBytes = 0;
@receiveBuffer = @jjSTREAM();
downloadedCount = CountDownloaded();
statusText = "Cancelled";
}
void RequestFile(int index)
{
if(!IsAllowedIndex(index))
{
downloading = false;
statusText = "Download complete";
return;
}
currentIndex = index;
currentSize = 0;
receivedBytes = 0;
@receiveBuffer = @jjSTREAM();
statusText = "Requesting " + SoundFiles[index];
jjSTREAM packet;
packet.push(PACKET_MAGIC);
packet.push(PACKET_REQUEST);
packet.push(index);
if(jjIsServer)
onReceive(packet, 0);
else
jjSendPacket(packet);
}
void RequestNextMissing()
{
downloadedCount = CountDownloaded();
int index = FirstMissingIndex();
if(index < 0)
{
downloading = false;
currentIndex = -1;
statusText = "Download complete";
jjAlert("Character sound files downloaded.", false);
return;
}
RequestFile(index);
}
void SendCancel()
{
jjSTREAM packet;
packet.push(PACKET_MAGIC);
packet.push(PACKET_CANCEL);
jjSendPacket(packet);
}
int TransferIndexForClient(int clientID)
{
for(uint i = 0; i < transfers.length; i++)
if(transfers[i].active && transfers[i].clientID == clientID)
return int(i);
for(uint j = 0; j < transfers.length; j++)
if(!transfers[j].active)
return int(j);
return -1;
}
void HandleRequest(jjSTREAM &in packet, int clientID)
{
if(!jjIsServer) return;
int index = -1;
packet.pop(index);
if(!IsAllowedIndex(index))
{
SendError(clientID, "Unknown sound file request");
return;
}
jjSTREAM@ file;
@file = @jjSTREAM(SoundFiles[index]);
int size = int(file.getSize());
if(size <= 0)
{
SendError(clientID, "Server is missing " + SoundFiles[index]);
return;
}
int slot = TransferIndexForClient(clientID);
if(slot < 0)
{
SendError(clientID, "Too many active sound downloads");
return;
}
transfers[slot].active = true;
transfers[slot].clientID = clientID;
transfers[slot].index = index;
transfers[slot].size = size;
transfers[slot].progress = 0;
@transfers[slot].stream = @file;
jjSTREAM begin;
begin.push(PACKET_MAGIC);
begin.push(PACKET_BEGIN);
begin.push(index);
begin.push(size);
SendToClient(begin, clientID);
}
void SendToClient(jjSTREAM &in packet, int clientID)
{
if(clientID <= 0)
onReceive(packet, 0);
else
jjSendPacket(packet, clientID);
}
void SendError(int clientID, const string &in message)
{
jjSTREAM packet;
packet.push(PACKET_MAGIC);
packet.push(PACKET_ERROR);
packet.push(message);
SendToClient(packet, clientID);
}
void ProcessServerTransfers()
{
for(uint i = 0; i < transfers.length; i++)
{
if(!transfers[i].active) continue;
if(transfers[i].clientID > 0)
{
int playerID = PlayerIDByClientID(transfers[i].clientID);
if(playerID < 0 || !jjPlayers[playerID].isActive)
{
transfers[i].active = false;
continue;
}
}
jjSTREAM packet;
packet.push(PACKET_MAGIC);
packet.push(PACKET_DATA);
packet.push(transfers[i].index);
int sent = 0;
uint8 byte = 0;
while(sent < SEND_BYTES_PER_TICK && transfers[i].progress < transfers[i].size && transfers[i].stream.pop(byte))
{
packet.push(byte);
transfers[i].progress++;
sent++;
}
SendToClient(packet, transfers[i].clientID);
if(transfers[i].progress >= transfers[i].size)
{
jjSTREAM done;
done.push(PACKET_MAGIC);
done.push(PACKET_DONE);
done.push(transfers[i].index);
SendToClient(done, transfers[i].clientID);
transfers[i].active = false;
}
}
}
int PlayerIDByClientID(int clientID)
{
for(int i = 0; i < 32; i++)
if(jjPlayers[i].isActive && jjPlayers[i].clientID == clientID)
return i;
return -1;
}
bool onReceive(jjSTREAM &in packet, int fromClientID)
{
int magic = 0;
if(!packet.pop(magic) || magic != PACKET_MAGIC) return false;
int packetType = 0;
if(!packet.pop(packetType)) return true;
if(packetType == PACKET_REQUEST)
HandleRequest(packet, fromClientID);
else if(packetType == PACKET_BEGIN)
HandleBegin(packet);
else if(packetType == PACKET_DATA)
HandleData(packet);
else if(packetType == PACKET_DONE)
HandleDone(packet);
else if(packetType == PACKET_ERROR)
HandleError(packet);
else if(packetType == PACKET_CANCEL && jjIsServer)
HandleCancel(fromClientID);
return true;
}
void HandleBegin(jjSTREAM &in packet)
{
int index = -1, size = 0;
packet.pop(index);
packet.pop(size);
if(!downloading || !IsAllowedIndex(index) || index != currentIndex || size <= 0) return;
currentSize = size;
receivedBytes = 0;
@receiveBuffer = @jjSTREAM();
statusText = "Downloading " + SoundFiles[index];
}
void HandleData(jjSTREAM &in packet)
{
int index = -1;
packet.pop(index);
if(!downloading || !IsAllowedIndex(index) || index != currentIndex) return;
uint8 byte = 0;
while(packet.pop(byte))
{
receiveBuffer.push(byte);
receivedBytes++;
}
}
void HandleDone(jjSTREAM &in packet)
{
int index = -1;
packet.pop(index);
if(!downloading || !IsAllowedIndex(index) || index != currentIndex) return;
if(currentSize > 0 && receivedBytes >= currentSize)
{
receiveBuffer.save(SoundFiles[index]);
MarkDownloaded(index);
downloadedCount = CountDownloaded();
RequestNextMissing();
}
else
{
downloading = false;
statusText = "Download failed for " + SoundFiles[index];
}
}
void HandleError(jjSTREAM &in packet)
{
string message = "Download failed";
packet.pop(message);
downloading = false;
currentIndex = -1;
statusText = message;
jjAlert(message, false);
}
void HandleCancel(int clientID)
{
for(uint i = 0; i < transfers.length; i++)
if(transfers[i].active && transfers[i].clientID == clientID)
transfers[i].active = false;
}
bool onDrawScore(jjPLAYER@ player, jjCANVAS@ canvas)
{
if(!menuOpen || !player.isLocal || player.localPlayerID != 0) return false;
window.preferencesFile = "charsSoundDownload.asdat";
DbBegin(@window, "charsSoundDownload", "Character Sounds", jjResolutionWidth / 2 - 190, 70, 380, 122);
window.closeButton = true;
window.draggable = true;
DbDrawFrame(canvas, @window);
int x, y, w, h;
DbContentRect(@window, x, y, w, h);
if(!downloading)
{
EnsureProgressLoaded();
downloadedCount = CountDownloaded();
}
canvas.drawString(x, y, "Downloaded: " + downloadedCount + " / " + int(SoundFiles.length), STRING::SMALL);
y += 14;
if(statusText == "All sound files are already downloaded")
{
canvas.drawString(x, y, "All sound files are", STRING::SMALL);
y += 12;
canvas.drawString(x, y, "already downloaded", STRING::SMALL);
}
else
{
canvas.drawString(x, y, statusText, STRING::SMALL);
}
y += 16;
int totalW = w - 4;
canvas.drawRectangle(x, y, totalW, 10, 16, SPRITE::NORMAL);
int progress = int((float(downloadedCount) / float(SoundFiles.length)) * float(totalW));
if(downloading && currentSize > 0 && currentIndex >= 0)
{
float fileProgress = float(receivedBytes) / float(currentSize);
progress = int(((float(downloadedCount) + fileProgress) / float(SoundFiles.length)) * float(totalW));
}
if(progress > totalW) progress = totalW;
canvas.drawRectangle(x, y, progress, 10, 72, SPRITE::NORMAL);
y += 28;
DbDrawButton(canvas, @window, x, y, 108, 16, ACTION_DOWNLOAD, 0, 0, "Download", !downloading && downloadedCount < int(SoundFiles.length));
DbDrawButton(canvas, @window, x + 122, y, 142, 16, ACTION_FORCE_DOWNLOAD, 0, 0, "Force Download", !downloading);
DbDrawButton(canvas, @window, x + 278, y, 78, 16, ACTION_CANCEL, 0, 0, downloading ? "Cancel" : "Close", true);
DbResult drawResult;
DbProcessMouse(@window, @drawResult);
if(drawResult.closeRequested || drawResult.action == ACTION_CLOSE)
{
CancelDownload();
menuOpen = false;
}
else if(drawResult.activated && drawResult.action == ACTION_DOWNLOAD)
{
StartDownload();
}
else if(drawResult.activated && drawResult.action == ACTION_FORCE_DOWNLOAD)
{
ForceDownload();
}
else if(drawResult.activated && drawResult.action == ACTION_CANCEL)
{
if(downloading) CancelDownload();
else menuOpen = false;
}
return 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.