/ gui_advplayerlist_mascot_GL4.lua
gui_advplayerlist_mascot_GL4.lua
  1  -- file: utilites_GL4.lua
  2  -- author: chmod777
  3  -- license: GNU GPL, v2 or later
  4  
  5  function widget:GetInfo()
  6  	return {
  7  		name		= 'AdvPlayersList Mascot GL4',
  8  		desc		= 'Shows a mascot sitting on top of the adv-playerlist  (use /mascot to switch)',
  9  		author		= 'Floris, (GL4 by chmod777)',
 10  		date		= '23 may 2015',
 11  		license		= 'GNU GPL, v2 or later',
 12  		layer		= 0,
 13  		enabled		= false,
 14  	}
 15  end
 16  
 17  ---------------------------------------------------------------------------------------------------
 18  --  Config
 19  ---------------------------------------------------------------------------------------------------
 20  
 21  local imageDirectory			= ':l:'..LUAUI_DIRNAME..'Images/advplayerslist_mascot/'
 22  local customImageDirectory		= LUAUI_DIRNAME..'Widgets/chmod777_includes/images/'
 23  
 24  local OPTIONS = {}
 25  OPTIONS.defaults = {	-- these will be loaded when switching style, but the style will overwrite the those values
 26  	name				= 'Defaults',
 27  	imageSize			= 55,
 28  	xOffset				= -1.6,
 29  	yOffset				= -58/5,
 30  	blinkDuration		= 0.12,
 31  	blinkTimeout		= 6,
 32  }
 33  table.insert(OPTIONS, {
 34  	name				= 'Floris Cat',
 35  	body				= imageDirectory..'floriscat_body.png',
 36  	head				= imageDirectory..'floriscat_head.png',
 37  	headblink			= imageDirectory..'floriscat_headblink.png',
 38  	santahat			= imageDirectory..'santahat.png',
 39  	imageSize			= 53,
 40  	xOffset				= -1.6,
 41  	yOffset				= -58/5,
 42  	head_xOffset		= 0,
 43  	head_yOffset		= 0,
 44  })
 45  table.insert(OPTIONS, {
 46  	name				= 'GrumpyCat',
 47  	body				= imageDirectory..'grumpycat_body.png',
 48  	head				= imageDirectory..'grumpycat_head.png',
 49  	headblink			= imageDirectory..'grumpycat_headblink.png',
 50  	santahat			= imageDirectory..'santahat.png',
 51  	imageSize			= 53,
 52  	xOffset				= -1.6,
 53  	yOffset				= -58/5,
 54  	head_xOffset		= 0,
 55  	head_yOffset		= 0,
 56  })
 57  table.insert(OPTIONS, {
 58  	name				= "Teifion's MrBeans",
 59  	body				= imageDirectory..'mrbeans_body.png',
 60  	head				= imageDirectory..'mrbeans_head.png',
 61  	headblink			= imageDirectory..'mrbeans_headblink.png',
 62  	santahat			= imageDirectory..'santahat.png',
 63  	imageSize			= 50,
 64  	xOffset				= -1.6,
 65  	yOffset				= -58/4,
 66  	head_xOffset		= -0.01,
 67  	head_yOffset		= 0.13,
 68  })
 69  
 70  ---------------------------------------------------------------------------------------------------
 71  -- Add custom mascots here
 72  ---------------------------------------------------------------------------------------------------
 73  
 74  table.insert(OPTIONS, {
 75  	name				= 'Doge',
 76  	body				= customImageDirectory..'dogedog.png',
 77  	head				= nil,
 78  	headblink			= nil,
 79  	santahat			= imageDirectory..'santahat.png',
 80  	imageSize			= 50,
 81  	xOffset				= 0,
 82  	yOffset				= -8,
 83  	head_xOffset		= 5/100,
 84  	head_yOffset		= 6/100,
 85  })
 86  
 87  ---------------------------------------------------------------------------------------------------
 88  --  Declarations
 89  ---------------------------------------------------------------------------------------------------
 90  
 91  local currentOption = 1
 92  
 93  local usedImgSize = OPTIONS[currentOption].imageSize
 94  local chobbyInterface
 95  
 96  local xPos = 0
 97  local yPos = 0
 98  
 99  local isBlinking = false
100  local blinkStart = nil
101  local blinkEnd = nil
102  local rot = 0
103  local bob = 0
104  
105  local positionChanged = false
106  local xOffset = 0
107  local yOffset = 0
108  local xHeadOffset = 0
109  local yHeadOffset = 0
110  
111  local mascotChanged = false
112  local bodyTexture = nil
113  local headTexture = nil
114  local head = nil
115  local headblink = nil
116  local hatTexture = nil
117  
118  local mascotVAO = nil
119  local mascotVBO = nil
120  local mascotIndexVBO = nil
121  local mascotInstanceVBO = nil
122  local confettiParticleCount = 60
123  local maxElements = 3  + confettiParticleCount
124  local baseElements = 0
125  local extendedElements = 0
126  
127  local mascotShaderWrapper = nil
128  local mascotShader = nil
129  local shaderConfig = {}
130  
131  local math_isInRect = math.isInRect
132  local pi = math.pi
133  local sin = math.sin
134  local random = math.random
135  local floor = math.floor
136  
137  local gl_Texture = gl.Texture
138  local gl_UseShader = gl.UseShader
139  local gl_Uniform = gl.Uniform
140  
141  local GL_TRIANGLES = GL.TRIANGLES
142  
143  local luaShaderDir = 'LuaUI/Widgets/Include/'
144  local LuaShader = VFS.Include(luaShaderDir..'LuaShader.lua')
145  VFS.Include(luaShaderDir..'instancevbotable.lua')
146  
147  local function shallow_copy(t)
148  	local t2 = {}
149  	for k,v in pairs(t) do
150  		t2[k] = v
151  	end
152  	return t2
153  end
154  
155  local OPTIONS_original = shallow_copy(OPTIONS)
156  OPTIONS_original.defaults = nil
157  
158  ---------------------------------------------------------------------------------------------------
159  -- Santa hats in December
160  ---------------------------------------------------------------------------------------------------
161  
162  local drawSantahat = false
163  if os.date('%m') == '12'  and  os.date('%d') >= '12' then
164  	drawSantahat = true
165  end
166  
167  ---------------------------------------------------------------------------------------------------
168  -- GL Stuff
169  ---------------------------------------------------------------------------------------------------
170  
171  local vsSrc = [[
172  #version 420
173  precision highp int;
174  precision highp float;
175  
176  // vertex attributes
177  layout (location = 0) in vec2 coords;
178  layout (location = 1) in vec2 uv;
179  
180  // instance attributes
181  layout (location = 2) in ivec4 instanceFlags;      // see buildInstanceVBOData
182  layout (location = 3) in vec2 confettiStartPos;
183  layout (location = 4) in float confettiSpeed;
184  layout (location = 5) in float confettiRandomSeed;
185  
186  uniform vec2 viewGeometry; // x, y
187  uniform vec2 screenPos;    // x, y
188  uniform float imgSize;
189  uniform vec4 offsets;      // offset.xy, headOffset.xy
190  uniform vec2 bobRotation;  // x -> bob, y -> rotation
191  uniform float confettiTime;
192  
193  const float CONFETTI_SCALE = 0.05;
194  
195  out DataVS {
196  	vec2 uv;
197  	float drawHead;
198  	float drawHat;
199  	float isConfetti;
200  	float confettiPalletIndex;
201  } vs_out;
202  
203  vec2 rotate(vec2 v, float a) {
204  	float s = sin(a);
205  	float c = cos(a);
206  	mat2 m = mat2(c, s, -s, c);
207  	return m * v;
208  }
209  
210  void main() {
211  	int isConfettiI = instanceFlags.x & 0x10;
212  	bool isConfetti = bool(isConfettiI);
213  
214  	vs_out.uv = uv;
215  	vs_out.drawHead = float(instanceFlags.x & 0x04);
216  	vs_out.drawHat = float(instanceFlags.x & 0x08);
217  	vs_out.isConfetti = float(isConfettiI);
218  	vs_out.confettiPalletIndex = float(instanceFlags.y);
219  
220  	bool useHeadRotation = bool(instanceFlags.x & 0x01);
221  	bool useHeadOffset = bool(instanceFlags.x & 0x02);
222  
223  	vec2 offset = offsets.xy;
224  	vec2 headOffset = offsets.zw;
225  	float bob = bobRotation.x;
226  	float rotation = bobRotation.y;
227  
228  	vec2 imgSize2 = vec2(imgSize);
229  	
230  	vec2 coord = coords.xy;
231  	vec2 translate = screenPos.xy;
232  
233  	if (useHeadOffset) {
234  		translate += fma(headOffset, imgSize2, vec2(0.0, 7));
235  	}
236  	if (useHeadRotation) {
237  		// Center, rotate, then uncenter.
238  		// Note that the quad has coords 0->1.
239  		coord = rotate(coords.xy - 0.5, radians(rotation)) + 0.5;
240  
241  		translate.y += bob;
242  	}
243  
244  
245  	if (isConfetti) {
246  		imgSize2 *= CONFETTI_SCALE;
247  
248  		vec2 travel = vec2(confettiTime) * vec2(confettiRandomSeed, confettiSpeed);
249  		float ground = confettiRandomSeed * 0.1; // so that they don't stack up in a line
250  		if (travel.y < confettiStartPos.y - ground) {
251  			translate += confettiStartPos - vec2(sin(travel.x), travel.y);
252  		} else {
253  			translate += vec2(confettiStartPos.x, ground);
254  		}
255  	}
256  
257  	coord = fma(coord, imgSize2, vec2(translate)) / viewGeometry.xy;
258  	coord = fma(coord, vec2(2.0), vec2(-1.0));
259  
260  	gl_Position = vec4(coord.x, coord.y, 0, 1);
261  }
262  ]]
263  
264  local fsSrc = [[
265  #version 330
266  
267  #extension GL_ARB_uniform_buffer_object : require
268  #extension GL_ARB_shading_language_420pack: require
269  
270  layout (binding = 0) uniform sampler2D body;
271  layout (binding = 1) uniform sampler2D head;
272  layout (binding = 2) uniform sampler2D hat;
273  
274  const vec3 CONFETTI_PALLET[5] = {
275  	vec3(0.6588, 0.3922, 0.9922), // purple
276  	vec3(0.1608, 0.8039, 1.0),    // blue
277  	vec3(0.4706, 1.0,    0.2667), // green
278  	vec3(1.0,    0.4431, 0.5529), // red
279  	vec3(0.9922, 1.0,    0.4157), // yellow
280  };
281  const float CONFETTI_ALPHA = 0.6;
282  
283  in DataVS {
284  	vec2 uv;
285  	float drawHead;
286  	float drawHat;
287  	float isConfetti;
288  	float confettiPalletIndex;
289  } vs_in;
290  
291  out vec4 fragColor;
292  
293  void main() {
294  	bool drawHeadB = vs_in.drawHead > 0.5;
295  	bool drawHatB = vs_in.drawHat > 0.5;
296  	bool isConfettiB = vs_in.isConfetti > 0.5;
297  	int palletIndex = int(vs_in.confettiPalletIndex);
298  
299  	vec4 textureSample = texture(body, vs_in.uv);
300  	if (drawHeadB) {
301  		textureSample = texture(head, vs_in.uv);
302  	}
303  	if (drawHatB) {
304  		textureSample = texture(hat, vs_in.uv);
305  	}
306  
307  	if (isConfettiB) {
308  		fragColor.xyz = CONFETTI_PALLET[palletIndex];
309  		fragColor.a = CONFETTI_ALPHA;
310  	} else {
311  		fragColor = textureSample;
312  	}
313  }
314  ]]
315  
316  local screenPosUniformLoc = nil
317  local drawHeadUniformLoc = nil
318  local imgSizeUniformLoc = nil
319  local offsetsUniformLoc = nil
320  local bobRotationUniformLoc = nil
321  local viewGeometryUniformLoc = nil
322  local confettiTimeUniformLoc = nil
323  
324  function initShader()
325  	mascotShaderWrapper = LuaShader({
326  		vertex = vsSrc:gsub('//__DEFINES__', LuaShader.CreateShaderDefinesString(shaderConfig)),
327  		fragment = fsSrc:gsub('//__DEFINES__', LuaShader.CreateShaderDefinesString(shaderConfig)),
328  		uniformInt = {},
329  		uniformFloat = {}
330  	}, 'mascotShader')
331  	local shaderCompiled = mascotShaderWrapper:Initialize()
332  
333  	if not shaderCompiled then
334  		Spring.Echo('Failed to compile mascotShader GL4')
335  		local shLog = gl.GetShaderLog() or ''
336  		Spring.Echo(shLog)
337  		widgetHandler:RemoveWidget()
338  	end
339  	
340  	mascotShader = mascotShaderWrapper.shaderObj
341  
342  	screenPosUniformLoc = gl.GetUniformLocation(mascotShader, 'screenPos')
343  	imgSizeUniformLoc = gl.GetUniformLocation(mascotShader, 'imgSize')
344  	offsetsUniformLoc = gl.GetUniformLocation(mascotShader, 'offsets')
345  	bobRotationUniformLoc = gl.GetUniformLocation(mascotShader, 'bobRotation')
346  	viewGeometryUniformLoc = gl.GetUniformLocation(mascotShader, 'viewGeometry')
347  	confettiTimeUniformLoc = gl.GetUniformLocation(mascotShader, 'confettiTime')
348  end
349  
350  local function TableConcat(t1,t2)
351  	for i=1,#t2 do
352  		t1[#t1+1] = t2[i]
353  	end
354  	return t1
355  end
356  local function ZeroExtend(t, n)
357  	while #t <= 7 do
358  		t[#t+1] = 0
359  	end
360  	return t
361  end
362  
363  local function buildInstanceVBOData()
364  	-- x flags
365  	--  1 - useHeadRotation
366  	--  2 - useHeadOffset
367  	--  4 - drawHead
368  	--  8 - drawHat
369  	-- 16 - isConfetti
370  	-- y confetti pallet
371  	--  0-purple, 1-blue, 2-green, 3-red, 4-yellow
372  	-- z,w unused
373  	-- confetti starting position x, y
374  	-- confetti random seead
375  	local USE_HEAD_ROTATION = 1
376  	local USE_HEAD_OFFSET = 2
377  	local DRAW_HEAD = 4
378  	local DRAW_HAT = 8
379  	local IS_CONFETTI = 16
380  
381  	local instanceVBOData = {}
382  	local numFields = 8
383  	baseElements = 0
384  
385  	if bodyTexture ~= nil then
386  		local bodyInstanceData = {}
387  		ZeroExtend(bodyInstanceData, numFields)
388  		TableConcat(instanceVBOData, bodyInstanceData)
389  		baseElements = baseElements + 1
390  	end
391  
392  	if headTexture ~= nil then
393  		local headInstanceData = {USE_HEAD_ROTATION+USE_HEAD_OFFSET+DRAW_HEAD}
394  		ZeroExtend(headInstanceData, numFields)
395  		TableConcat(instanceVBOData, headInstanceData)
396  		baseElements = baseElements + 1
397  	end
398  
399  	if drawSantahat and hatTexture ~= nil then
400  		local hatInstanceData
401  		if headTexture == nil then
402  			hatInstanceData = {USE_HEAD_OFFSET+DRAW_HAT}
403  		else
404  			hatInstanceData = {USE_HEAD_ROTATION+USE_HEAD_OFFSET+DRAW_HAT}
405  		end
406  		ZeroExtend(hatInstanceData, numFields)
407  		TableConcat(instanceVBOData, hatInstanceData)
408  		baseElements = baseElements + 1
409  	end
410  
411  	for i = baseElements, baseElements+confettiParticleCount-1 do
412  		local confettiIndex = floor(random(0,4))
413  		local startX = random(0,70)
414  		local startY = random(80,120)
415  		local confettiSpeed = random(50, 80)
416  		local confettiRandomSeed = random(10, 20)
417  		instanceVBOData[i*8+1] = IS_CONFETTI
418  		instanceVBOData[i*8+2] = confettiIndex
419  		instanceVBOData[i*8+3] = 0
420  		instanceVBOData[i*8+4] = 0
421  		instanceVBOData[i*8+5] = startX
422  		instanceVBOData[i*8+6] = startY
423  		instanceVBOData[i*8+7] = confettiSpeed
424  		instanceVBOData[i*8+8] = confettiRandomSeed
425  	end
426  
427  	return instanceVBOData
428  end
429  
430  local function initGLBuffers()
431  	mascotVAO = gl.GetVAO()
432  	if mascotVAO == nil then
433  		Spring.Echo('Mascot GL4: Failed to get VAO')
434  		widgetHandler:RemoveWidget()
435  	end
436  
437  	mascotVBO = gl.GetVBO(GL.ARRAY_BUFFER, false)
438  	if mascotVBO == nil then
439  		Spring.Echo('Mascot GL4: Failed to get ARRAY_BUFFER VBO')
440  		widgetHandler:RemoveWidget()
441  	end
442  	local minX,minY = 0,0
443  	local maxX,maxY = 1,1
444  	local minU,minV = 0,1
445  	local maxU,maxV = 1,0
446  
447  	local rectVBOData = {
448  		minX,minY, minU,minV, --bl
449  		minX,maxY, minU,maxV, --br
450  		maxX,maxY, maxU,maxV, --tr
451  		maxX,minY, maxU,minV, --tl
452  	}
453  	local numVertices = 4
454  	mascotVBO:Define(numVertices, {
455  		{id = 0, name = 'coords', size = 2, type = GL.FLAOT},
456  		{id = 1, name = 'uv', size = 2, type = GL.FLAOT},
457  	})
458  	mascotVBO:Upload(rectVBOData)
459  	mascotVAO:AttachVertexBuffer(mascotVBO)
460  	
461  	mascotIndexVBO = gl.GetVBO(GL.ELEMENT_ARRAY_BUFFER, false)
462  	if mascotVBO == nil then
463  		Spring.Echo('Mascot GL4: Failed to get ELEMENT_ARRAY_BUFFER VBO')
464  		widgetHandler:RemoveWidget()
465  	end
466  	local indexVBOData = {
467  		2, 1, 0,
468  		3, 2, 0,
469  	}
470  	local numIndices = 6
471  	mascotIndexVBO:Define(numIndices)
472  	mascotIndexVBO:Upload(indexVBOData)
473  	mascotVAO:AttachIndexBuffer(mascotIndexVBO)
474  
475  	mascotInstanceVBO = gl.GetVBO(GL.ARRAY_BUFFER, false)
476  	if mascotInstanceVBO == nil then
477  		Spring.Echo('Mascot GL4: Failed to get ARRAY_BUFFER VBO')
478  		widgetHandler:RemoveWidget()
479  	end
480  	local instanceVBOLayout = {
481  		{id = 2, name = 'instanceFlags', size = 4, type = GL.INT},
482  		{id = 3, name = 'confettiStartOffset', size = 2, type = GL.FLAOT},
483  		{id = 4, name = 'confettiSpeed', size = 1, type = GL.FLAOT},
484  		{id = 5, name = 'confettiRandomSeed', size = 1, type = GL.FLAOT},
485  	}
486  	mascotInstanceVBO:Define(maxElements, instanceVBOLayout)
487  	mascotVAO:AttachInstanceBuffer(mascotInstanceVBO)
488  
489  	return true
490  end
491  
492  ---------------------------------------------------------------------------------------------------
493  -- Manage Options
494  ---------------------------------------------------------------------------------------------------
495  
496  local function toggleOptions(option)
497  	if OPTIONS[option] then
498  		currentOption = option
499  	else
500  		currentOption = currentOption + 1
501  		if not OPTIONS[currentOption] then
502  			currentOption = 1
503  		end
504  	end
505  	
506  	loadOption()
507  	updatePosition(true)
508  end
509  
510  function loadOption()
511  	local appliedOption = OPTIONS_original[currentOption]
512  	OPTIONS[currentOption] = shallow_copy(OPTIONS.defaults)
513  
514  	for option, value in pairs(appliedOption) do
515  		OPTIONS[currentOption][option] = value
516  	end
517  
518  	if currentOption > 3 then
519  		local imageFiles = VFS.DirList(customImageDirectory)
520  		local foundBody = false
521  		local bodyPath = OPTIONS[currentOption]['body']
522  		for i=1, #imageFiles do
523  			local windowsPathAsSTD = imageFiles[i]:gsub('\\', '/')
524  			if (bodyPath == imageFiles[i]) or (bodyPath == windowsPathAsSTD) then
525  				foundBody = true
526  				break
527  			end
528  		end
529  		if not foundBody then
530  			Spring.Echo('Missing file: '..OPTIONS[currentOption]['body']..' Going to next mascot.')
531  			toggleOptions()
532  			return
533  		end
534  	end
535  
536  	mascotChanged = true
537  	bodyTexture = OPTIONS[currentOption]['body']
538  	head = OPTIONS[currentOption]['head']
539  	headblink = OPTIONS[currentOption]['headblink']
540  	hatTexture = OPTIONS[currentOption]['santahat']
541  	xOffset = OPTIONS[currentOption]['xOffset']
542  	yOffset = OPTIONS[currentOption]['yOffset']
543  	xHeadOffset = OPTIONS[currentOption]['head_xOffset']
544  	yHeadOffset = OPTIONS[currentOption]['head_yOffset']
545  	if not isBlinking or headBlink == nil then
546  		headTexture = head
547  	else
548  		headTexture = headBlink
549  	end
550  
551  	blinkStart = OPTIONS[currentOption]['blinkTimeout']
552  	blinkEnd = blinkStart + OPTIONS[currentOption]['blinkDuration']
553  
554  	if mascotInstanceVBO then
555  		local instanceVBOData = buildInstanceVBOData()
556  		mascotInstanceVBO:Upload(instanceVBOData)
557  	end
558  end
559  
560  function widget:MousePress(mx, my, mb)
561  	if mb == 1 and math_isInRect(mx, my, xPos, yPos, xPos+usedImgSize, yPos+usedImgSize) then
562  		toggleOptions()
563  	end
564  end
565  
566  function widget:TextCommand(command)
567  	if string.sub(command, 1, 6) == 'mascot' then
568  		toggleOptions(tonumber(string.sub(command, 8)))
569  		Spring.Echo('Playerlist mascot: '..OPTIONS[currentOption].name)
570  	end
571  end
572  
573  function widget:GetConfigData()
574  	return {currentOption = currentOption}
575  end
576  
577  function widget:SetConfigData(data)
578  	if data.currentOption ~= nil and OPTIONS[data.currentOption] ~= nil then
579  		currentOption = data.currentOption or currentOption
580  	end
581  end
582  
583  ---------------------------------------------------------------------------------------------------
584  -- Updates
585  ---------------------------------------------------------------------------------------------------
586  
587  local updateViewGeometry = true
588  function widget:ViewResize()
589  	updatePosition(true)
590  	updateViewGeometry = true
591  end
592  
593  local parentPos = {}
594  function updatePosition(force)
595  	local prevPos = parentPos
596  	if WG['displayinfo'] ~= nil then
597  		parentPos = WG['displayinfo'].GetPosition()
598  	elseif WG['unittotals'] ~= nil then
599  		parentPos = WG['unittotals'].GetPosition()
600  	elseif WG['music'] ~= nil then
601  		parentPos = WG['music'].GetPosition()
602  	elseif WG['advplayerlist_api'] ~= nil then
603  		parentPos = WG['advplayerlist_api'].GetPosition()
604  	else
605  		local scale = (vsy / 880) * (1 + (Spring.GetConfigFloat('ui_scale', 1) - 1) / 1.25)
606  		parentPos = {0,vsx-(220*scale),0,vsx,scale}
607  	end
608  
609  	local parentLeft = parentPos[2]  -- x
610  	local parentTop = parentPos[1]   -- y
611  	local parentScale = parentPos[5]
612  
613  	if (force or
614  		prevPos[1] == nil or
615  		prevPos[1] ~= parentTop or
616  		prevPos[2] ~= parentLeft or
617  		prevPos[5] ~= parentScale)
618  	then
619  		positionChanged = true
620  		usedImgSize = OPTIONS[currentOption]['imageSize'] * parentScale
621  		xPos = parentLeft + (xOffset * parentScale)
622  		yPos = parentTop + (yOffset * parentScale)
623  	end
624  end
625  
626  local totalTime = 0
627  
628  local blinkTimer = 0
629  
630  local animationInterval = 1/30
631  local animationTimer = 0
632  local updateBobRotation = false
633  
634  local updatePositionTimer = 0
635  local updatePositionInterval = 2
636  
637  local doConfettiAnimation = true
638  local confettiTime = 0
639  local confettiInterval = 5
640  
641  function widget:Update(dt)
642  	totalTime=totalTime+dt
643  
644  	animationTimer=animationTimer+dt
645  	if animationTimer > animationInterval then
646  		updateBobRotation = true
647  		bob = 1.5*sin(pi*(totalTime/5.5))
648  		rot = 12 + 6*sin(pi*(totalTime/4))
649  		animationTimer=0
650  	end
651  
652  	blinkTimer=blinkTimer+dt
653  	if (not isBlinking) and (headblink ~= nil) and (blinkTimer > blinkStart) then
654  		isBlinking = true
655  		headTexture = headblink
656  	end
657  	if isBlinking and (blinkTimer > blinkEnd) then
658  		isBlinking = false
659  		headTexture = head
660  		blinkTimer = 0
661  	end
662  
663  	if doConfettiAnimation then
664  		confettiTime=confettiTime+dt
665  		if confettiTime > confettiInterval then
666  			confettiTime = 0
667  			extendedElements = 0
668  			doConfettiAnimation = false
669  		end
670  	end
671  
672  	-- For when other advPlayerList menues are enabled/disabled or resize
673  	updatePositionTimer = updatePositionTimer + dt
674  	if updatePositionTimer > updatePositionInterval then
675  		updatePositionTimer = 0
676  		updatePosition()
677  	end
678  end
679  
680  function widget:UnitDestroyed(unitID, unitDefID, unitTeam)
681  	local name = UnitDefs[unitDefID].name;
682  	if name == 'armdecom' or name == 'coredecom' then
683  		extendedElements = confettiParticleCount
684  		doConfettiAnimation = true
685  	end
686  end
687  
688  function widget:Initialize()
689  	initShader()
690  	initGLBuffers()
691  	loadOption()
692  	updatePosition()
693  end
694  
695  function widget:Shutdown()
696  	if mascotShader ~= nil then
697  		mascotShaderWrapper:Delete()
698  	end
699  	if mascotInstanceVBO ~= nil then
700  		mascotInstanceVBO:Delete()
701  	end
702  	if mascotIndexVBO ~= nil then
703  		mascotIndexVBO:Delete()
704  	end
705  	if mascotVBO ~= nil then
706  		mascotVBO:Delete()
707  	end
708  	if mascotVAO ~= nil then
709  		mascotVAO:Delete()
710  	end
711  end
712  
713  function widget:RecvLuaMsg(msg, playerID)
714  	if msg:sub(1,18) == 'LobbyOverlayActive' then
715  		chobbyInterface = (msg:sub(1,19) == 'LobbyOverlayActive1')
716  	end
717  end
718  
719  function widget:DrawScreen()
720  	if chobbyInterface then return end
721  
722  	if bodyTexture then
723  		gl_Texture(0, bodyTexture)
724  	end
725  	if headTexture then
726  		gl_Texture(1, headTexture)
727  	end
728  	if hatTexture then
729  		gl_Texture(2, hatTexture)
730  	end
731  
732  	gl_UseShader(mascotShader)
733  		if updateBobRotation then
734  			gl_Uniform(bobRotationUniformLoc, bob, rot)
735  			updateBobRotation = false
736  		end
737  
738  		if positionChanged then
739  			gl_Uniform(screenPosUniformLoc, xPos, yPos)
740  			positionChanged = false
741  		end
742  
743  		if mascotChanged then
744  			gl_Uniform(imgSizeUniformLoc, usedImgSize)
745  			gl_Uniform(offsetsUniformLoc, xOffset, yOffset, xHeadOffset, yHeadOffset)
746  			mascotChanged = false
747  		end
748  		
749  		if doConfettiAnimation then
750  			gl_Uniform(confettiTimeUniformLoc, confettiTime)
751  		end
752  
753  		if updateViewGeometry then
754  			local x, y = Spring.GetViewGeometry()
755  			gl_Uniform(viewGeometryUniformLoc, x, y)
756  		end
757  
758  		local usedElements = baseElements + extendedElements
759  		if usedElements > 0 then
760  			mascotVAO:DrawElements(GL_TRIANGLES, 6, 0, usedElements, 0, 0)
761  		end
762  	gl_UseShader(0)
763  
764  	gl_Texture(0, false)
765  	gl_Texture(1, false)
766  	gl_Texture(2, false)
767  end
768