Downloads containing charSoundDL.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Characters Cranky Mutator N/A Download file

File preview

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;
	}
}