Using GameAnalytics (Retention and Monetisation) (Ez mode)

You can add compatibility for it to send error reports to the server. If I added this as an in-built function, would people use it?

  • Yes
  • No

0 voters

1 Like

wow thanks for making this! I’ll use it and I think several of my friends will be interested too! :smiley:

This is how my current one works and it is very helpful for finding bugs. Please do!

Its also important to mention that there can only be 100,000 unique event_ids per day, so nesting all the information into the event_id may not be a good idea.

Just to clarify what he means by this is not that there is a limit of 100k events a day.

For resource events (the only type this tutorial shows how to use which you can affect in terms of event ids) these are two events with separate ids and count as two to your 100k limit

Analytics.RecordTransaction(Player, 500, “Pet:Dragon”)
Analytics.RecordTransaction(Player, 1000000, “Pet:UhhhNotADragon”)

However, if I was to fire the first one twice, they’d have the same id, and count as one towards your 100k limit.

There is no limit to the amount of data you can send to game analytics, and it’s all free!

no clue how they make money plz don’t go out of business

6 Likes

Oh yay game analytics, I love using it. All the custom events are super useful, for example I keep track of average times for each level in my game (ie I know that it takes a user on average 110 min to reach level 2 etc) and then you can graph those, and like do cool things like compare different weeks worth of data to eachother etc. Although I use @ByDefault’s Module as I found it works well enough but digging through yours seems pretty well written as well. One thing I would like to note about anyone putting this in their place, while encoding strings to be sent to the game analytics server, server CPU usage spikes with lots of pending requests. To combat this I modified by sha2_256 file to include some heartbeat waits, this reduces lag spikes significantly to like 5% CPU usage, if you want to see how bad it is watch F9 Server Script Usage, spikes to 50% sometimes causing replication issues depending on whatever your game analytics refresh is set to:


local Bit = require(lockbox.util.bit);
local String = string;
local Math = math;
local Queue = require(lockbox.util.queue);

local CONSTANTS = {
   0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
   0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
   0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
   0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
   0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
   0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
   0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
   0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2  };

local AND = Bit.band;
local OR  = Bit.bor;
local NOT = Bit.bnot;
local XOR = Bit.bxor;
local LROT = Bit.lrotate;
local RROT = Bit.rrotate;
local LSHIFT = Bit.lshift;
local RSHIFT = Bit.rshift;

--SHA2 is big-endian
local bytes2word = function(b0,b1,b2,b3)
	local i = b0; i = LSHIFT(i,8);
	i = OR(i,b1); i = LSHIFT(i,8);
	i = OR(i,b2); i = LSHIFT(i,8);
	i = OR(i,b3);
	return i;
end

local word2bytes = function(word)
	local b0,b1,b2,b3;
	b3 = AND(word,0xFF); word = RSHIFT(word,8);
	b2 = AND(word,0xFF); word = RSHIFT(word,8);
	b1 = AND(word,0xFF); word = RSHIFT(word,8);
	b0 = AND(word,0xFF);
	return b0,b1,b2,b3;
end

local bytes2dword = function(b0,b1,b2,b3,b4,b5,b6,b7)
	local i = bytes2word(b0,b1,b2,b3);
	local j = bytes2word(b4,b5,b6,b7);
	return (i*0x100000000)+j;
end

local dword2bytes = function(i)
	local b4,b5,b6,b7 = word2bytes(i);
	local b0,b1,b2,b3 = word2bytes(Math.floor(i/0x100000000));
	return b0,b1,b2,b3,b4,b5,b6,b7;
end 




local SHA2_256 = function()

	local queue = Queue();

	local h0 = 0x6a09e667;
	local h1 = 0xbb67ae85;
	local h2 = 0x3c6ef372;
	local h3 = 0xa54ff53a;
	local h4 = 0x510e527f;
	local h5 = 0x9b05688c;
	local h6 = 0x1f83d9ab;
	local h7 = 0x5be0cd19;

	local public = {};

	local processBlock = function()
		
		local a = h0;
		local b = h1;
		local c = h2;
		local d = h3;
		local e = h4;
		local f = h5;
		local g = h6;
		local h = h7;
		
		local w = {};
		for i=0,15 do
			w[i] = bytes2word(queue.pop(),queue.pop(),queue.pop(),queue.pop());
		end

		game:GetService("RunService").Heartbeat:Wait()

		for i=16,63 do
			if i%20 == 0 then
				game:GetService("RunService").Heartbeat:Wait()
			end
			local s0 = XOR(RROT(w[i-15],7), XOR(RROT(w[i-15],18), RSHIFT(w[i-15],3)));
			local s1 = XOR(RROT(w[i-2],17), XOR(RROT(w[i-2], 19), RSHIFT(w[i-2],10)));
			w[i] = AND(w[i-16] + s0 + w[i-7] + s1, 0xFFFFFFFF);
		end

		for i=0,63 do
			if i%12 == 0 then
				game:GetService("RunService").Heartbeat:Wait()
			end
			local s1 = XOR(RROT(e,6), XOR(RROT(e,11),RROT(e,25)));
			local ch = XOR(AND(e,f), AND(NOT(e),g));
			local temp1 = h + s1 + ch + CONSTANTS[i+1] + w[i];
			local s0 = XOR(RROT(a,2), XOR(RROT(a,13), RROT(a,22)));
			local maj = XOR(AND(a,b), XOR(AND(a,c), AND(b,c)));
			local temp2 = s0 + maj;

			h = g;
			g = f;
			f = e;
			e = d + temp1;
			d = c;
			c = b;
			b = a;
			a = temp1 + temp2;
		end

		h0 = AND(h0 + a, 0xFFFFFFFF);
		h1 = AND(h1 + b, 0xFFFFFFFF);
		h2 = AND(h2 + c, 0xFFFFFFFF);
		h3 = AND(h3 + d, 0xFFFFFFFF);
		h4 = AND(h4 + e, 0xFFFFFFFF);
		h5 = AND(h5 + f, 0xFFFFFFFF);
		h6 = AND(h6 + g, 0xFFFFFFFF);
		h7 = AND(h7 + h, 0xFFFFFFFF);
		game:GetService("RunService").Heartbeat:Wait()
	end

	public.init = function()
		queue.reset();

		h0 = 0x6a09e667;
		h1 = 0xbb67ae85;
		h2 = 0x3c6ef372;
		h3 = 0xa54ff53a;
		h4 = 0x510e527f;
		h5 = 0x9b05688c;
		h6 = 0x1f83d9ab;
		h7 = 0x5be0cd19;

		return public;
	end

	public.update = function(bytes)
		
		for b in bytes do
			queue.push(b);
			if queue.size() >= 64 then processBlock(); end
		end
		
		return public;
	end

	public.finish = function()
		local bits = queue.getHead() * 8;

		queue.push(0x80);
		while ((queue.size()+7) % 64) < 63 do
			queue.push(0x00);
		end

		local b0,b1,b2,b3,b4,b5,b6,b7 = dword2bytes(bits);

		queue.push(b0);
		queue.push(b1);
		queue.push(b2);
		queue.push(b3);
		queue.push(b4);
		queue.push(b5);
		queue.push(b6);
		queue.push(b7);

		while queue.size() > 0 do
			processBlock();
		end

		return public;
	end
	
	public.asBytes = function()
		local  b0, b1, b2, b3 = word2bytes(h0);
		local  b4, b5, b6, b7 = word2bytes(h1);
		local  b8, b9,b10,b11 = word2bytes(h2);
		local b12,b13,b14,b15 = word2bytes(h3);
		local b16,b17,b18,b19 = word2bytes(h4);
		local b20,b21,b22,b23 = word2bytes(h5);
		local b24,b25,b26,b27 = word2bytes(h6);
		local b28,b29,b30,b31 = word2bytes(h7);


		return {  b0, b1, b2, b3, b4, b5, b6, b7, b8, b9,b10,b11,b12,b13,b14,b15
				,b16,b17,b18,b19,b20,b21,b22,b23,b24,b25,b26,b27,b28,b29,b30,b31};	
	end

	public.asHex = function()
		local  b0, b1, b2, b3 = word2bytes(h0);
		local  b4, b5, b6, b7 = word2bytes(h1);
		local  b8, b9,b10,b11 = word2bytes(h2);
		local b12,b13,b14,b15 = word2bytes(h3);
		local b16,b17,b18,b19 = word2bytes(h4);
		local b20,b21,b22,b23 = word2bytes(h5);
		local b24,b25,b26,b27 = word2bytes(h6);
		local b28,b29,b30,b31 = word2bytes(h7);

		local fmt = "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"		

		return String.format(fmt, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9,b10,b11,b12,b13,b14,b15
				,b16,b17,b18,b19,b20,b21,b22,b23,b24,b25,b26,b27,b28,b29,b30,b31);
	end

	return public;

end

return SHA2_256;

One thing I do also is send any local errors to the server, then the server sends those local errors to game analytics, so I can keep track of local player errors without asking people to open up their F9.

9 Likes

Thanks man.

I’ll add that + bug catching support to my module tomorrow

I really don’t like how the credentials are stored in the same ModuleScript that is given to clients. Why not have those in a separate “Credentials” script that can be placed in ServerStorage, since only the server needs access to them?

4 Likes

I updated my version to do Analytics.ServerInit(GameKey, SecretKey).

Been rushed for time so didn’t publish the changes (my version is different to the public ones). Will do it today however, thanks for reminding me

1 Like

And I did it. Highly recommend you use the new script!

1 Like

What’s this mean? All of my settings for studio-API+HTTP enabled are on

Figured it out. Had to hard-code the keys into your script. Just putting them into the initialization function doesn’t work.

Ya just resolved. Thanks to @NewFissy for pointing the error out and how to fix it.

1 Like

There’s also an issue that I fixed by switching these two lines:

Right now it’s

PlayerData[Player.Name] = Functions.AnnotateEvent:InvokeClient(Player)
PlayerJoinTimes[Player.Name] = os.time()

And your diagram infers that’s right, but I assume that’s not what you mean?

What’s the issue too, please?

1 Like

I kept getting errors when the player left saying that the player never had a PlayerJoinTime.

Also I added a :WaitForChild() between Functions and AnnotateEvent because I was getting race conditions.

Thanks, I implemented both these changes.

1 Like

Still having trouble. I can’t get it to work. I have HTTP enabled and the correct keys where they should be. :confused: The site doesn’t show anything an no errors.

There is a delay on the real time display of 15-45 minutes. Is it printing out the right things?

It’s been about 40 minutes and there’s 0 data. Even the ‘Live feed’ for Latest 10 processed events is blank. The script appears to be running without issue.