313 | function SpeakerDisplay:createComponents(hudAtlasPath) |
314 | local baseX, baseY = SpeakerDisplay.getBackgroundPosition(1) |
315 | |
316 | local offX, offY = self:scalePixelToScreenVector(SpeakerDisplay.POSITION.SPEAKER_ICON) |
317 | local iconWidth, iconHeight = self:scalePixelToScreenVector(SpeakerDisplay.SIZE.SPEAKER_ICON) |
318 | local overlay = Overlay.new(hudAtlasPath, 0, 0, iconWidth, iconHeight) |
319 | overlay:setUVs(GuiUtils.getUVs(SpeakerDisplay.UV.SPEAKER_ICON)) |
320 | overlay:setColor(unpack(SpeakerDisplay.COLOR.SPEAKER_ICON)) |
321 | self.speakerIconOverlay = overlay |
322 | |
323 | overlay = Overlay.new(hudAtlasPath, 0, 0, iconWidth, iconHeight) |
324 | overlay:setUVs(GuiUtils.getUVs(SpeakerDisplay.UV.SPEAKER_ICON_MUTED)) |
325 | overlay:setColor(unpack(SpeakerDisplay.COLOR.SPEAKER_ICON_MUTED)) |
326 | self.speakerIconOverlayMuted = overlay |
327 | |
328 | overlay = Overlay.new(hudAtlasPath, 0, 0, iconWidth, iconHeight) |
329 | overlay:setUVs(GuiUtils.getUVs(SpeakerDisplay.UV.SPEAKER_ICON_AWAY)) |
330 | overlay:setColor(unpack(SpeakerDisplay.COLOR.SPEAKER_ICON_AWAY)) |
331 | self.speakerIconOverlayAway = overlay |
332 | end |
201 | function SpeakerDisplay:draw() |
202 | if self:getVisible() then--and #self.currentSpeakers > 0 then |
203 | -- make sure we draw on top of everything because this display is so important for PS4 technical requirements: |
204 | new2DLayer() |
205 | |
206 | SpeakerDisplay:superClass().draw(self) -- draw HUD elements |
207 | |
208 | setTextBold(true) |
209 | setTextAlignment(RenderText.ALIGN_RIGHT) |
210 | |
211 | local posX, posY = self:getPosition() |
212 | |
213 | local function drawItem(index, user, isMuted, isAway) |
214 | local name = utf8ToUpper(user:getNickname()) |
215 | |
216 | local lineY = posY + (index - 1) * (self.lineHeight + self.lineSpacing) |
217 | |
218 | -- Draw background rect |
219 | local width = getTextWidth(self.textSize, name) + math.abs(self.textOffsetX) + self.linePadding |
220 | drawFilledRect(posX - width, lineY, width, self.lineHeight, unpack(SpeakerDisplay.COLOR.BACKGROUND)) |
221 | |
222 | local textX = posX + self.textOffsetX |
223 | local textY = lineY + (self.lineHeight - self.textSize) * 0.5 + self.textOffsetY |
224 | |
225 | -- Draw text shadow |
226 | setTextColor(unpack(SpeakerDisplay.COLOR.NAME_SHADOW)) |
227 | renderText(textX + self.shadowOffset, textY - self.shadowOffset, self.textSize, name) |
228 | |
229 | -- Draw text |
230 | setTextColor(unpack(SpeakerDisplay.COLOR.NAME)) |
231 | renderText(textX, textY, self.textSize, name) |
232 | |
233 | if isAway then |
234 | renderOverlay(self.speakerIconOverlayAway.overlayId, posX - self.linePadding - self.iconWidth, lineY + (self.lineHeight - self.iconHeight) * 0.5, self.iconWidth, self.iconHeight) |
235 | elseif isMuted then |
236 | renderOverlay(self.speakerIconOverlayMuted.overlayId, posX - self.linePadding - self.iconWidth, lineY + (self.lineHeight - self.iconHeight) * 0.5, self.iconWidth, self.iconHeight) |
237 | else |
238 | renderOverlay(self.speakerIconOverlay.overlayId, posX - self.linePadding - self.iconWidth, lineY + (self.lineHeight - self.iconHeight) * 0.5, self.iconWidth, self.iconHeight) |
239 | end |
240 | end |
241 | |
242 | -- For each name |
243 | for i = 1, #self.currentSpeakers do |
244 | local user = self.currentSpeakers[i] |
245 | drawItem(i, user, user:getVoiceMuted(), false) |
246 | end |
247 | |
248 | -- Add each away state |
249 | local i = #self.currentSpeakers |
250 | for user, _ in pairs(self.userAway) do |
251 | i = i + 1 |
252 | |
253 | drawItem(i, user, user:getVoiceMuted(), true) |
254 | end |
255 | end |
256 | end |
23 | function SpeakerDisplay.new(hudAtlasPath, ingameMap) |
24 | local backgroundOverlay = SpeakerDisplay.createBackground(hudAtlasPath) |
25 | local self = SpeakerDisplay:superClass().new(backgroundOverlay, nil, SpeakerDisplay_mt) |
26 | |
27 | self.ingameMap = ingameMap |
28 | |
29 | self.maxNumPlayers = g_serverMaxClientCapacity |
30 | self.users = {} |
31 | |
32 | self.isMenuVisible = true |
33 | self.isInfoWindowVisible = false |
34 | self.isSpeedometerVisible = false |
35 | |
36 | self.userSpeaking = {} -- {"full online ID" = bool} |
37 | self.userAway = {} -- {"full online ID" = bool} |
38 | self.userTiming = {} -- {"full online ID" = int} |
39 | self.userVisibility = {} |
40 | self.currentSpeakers = {} -- {i = "full online ID"} |
41 | self.lastVoiceState = {} |
42 | |
43 | self.mapOffsetX, self.mapOffsetY = 0, 0 |
44 | self.lineWidth, self.lineHeight = 0, 0 |
45 | self.textLineOffsetX, self.textLineOffsetY = 0, 0 |
46 | self.textSize = 0 |
47 | self.textOffsetY = 0 |
48 | self.shadowOffset = 0 |
49 | |
50 | self:storeScaledValues() |
51 | self:createComponents(hudAtlasPath) |
52 | |
53 | return self |
54 | end |
282 | function SpeakerDisplay:storeScaledValues() |
283 | self.positionXHUD, self.positionYHUD = self:scalePixelToScreenVector(SpeakerDisplay.POSITION.SELF) |
284 | self.positionXMenu, self.positionYMenu = self:scalePixelToScreenVector(SpeakerDisplay.POSITION.SELF_MENU) |
285 | |
286 | self.lineWidth, self.lineHeight = self:scalePixelToScreenVector(SpeakerDisplay.SIZE.LINE) |
287 | self.lineSpacing = self:scalePixelToScreenHeight(SpeakerDisplay.POSITION.LINE_SPACING[2]) |
288 | self.linePadding = self:scalePixelToScreenWidth(SpeakerDisplay.POSITION.LINE_PADDING[1]) |
289 | |
290 | self.iconWidth, self.iconHeight = self:scalePixelToScreenVector(SpeakerDisplay.SIZE.SPEAKER_ICON) |
291 | |
292 | self.textOffsetX, self.textOffsetY = self:scalePixelToScreenVector(SpeakerDisplay.POSITION.NAME) |
293 | self.textSize = self:scalePixelToScreenHeight(SpeakerDisplay.TEXT_SIZE.NAME) |
294 | self.shadowOffset = SpeakerDisplay.SHADOW_OFFSET_FACTOR * self.textSize |
295 | end |
174 | function SpeakerDisplay:update(dt) |
175 | SpeakerDisplay:superClass().update(self, dt) |
176 | |
177 | self:updateSpeakingState(dt) |
178 | self:updateVisibility() |
179 | |
180 | |
181 | if not self.isMenuVisible then |
182 | if self.isSpeedometerVisible then |
183 | local _, y = g_currentMission.hud.speedMeter:getPosition() |
184 | local h = g_currentMission.hud.speedMeter:getHeight() |
185 | self:setPosition(nil, y + h) |
186 | else |
187 | local h = g_currentMission.hud.infoDisplay:getDisplayHeight() |
188 | self:setPosition(nil, g_safeFrameMajorOffsetY + h) |
189 | end |
190 | else |
191 | self:setPosition(nil, g_safeFrameMajorOffsetY) |
192 | end |
193 | end |
103 | function SpeakerDisplay:updateSpeakingState(dt) |
104 | for _, user in pairs(self.users) do |
105 | local uuid = user:getUniqueUserId() |
106 | local wasSpeakingLastFrame = self.userSpeaking[uuid] |
107 | local isSpeakingNow = VoiceChatUtil.getIsSpeakerActive(uuid) and not user:getIsBlocked() |
108 | |
109 | -- When removing, wait 500ms to hide |
110 | -- When adding, wait 250ms to show |
111 | |
112 | if wasSpeakingLastFrame and not isSpeakingNow then |
113 | self.userTiming[uuid] = 500 |
114 | elseif isSpeakingNow and not wasSpeakingLastFrame then |
115 | self.userTiming[uuid] = 250 |
116 | end |
117 | |
118 | self.userSpeaking[uuid] = isSpeakingNow |
119 | |
120 | if self.userTiming[uuid] ~= nil then |
121 | self.userTiming[uuid] = self.userTiming[uuid] - dt |
122 | |
123 | if self.userTiming[uuid] <= 0 then |
124 | self.userTiming[uuid] = nil |
125 | |
126 | if self.userSpeaking[uuid] and not self.userVisibility[uuid] then |
127 | table.insert(self.currentSpeakers, user) |
128 | self.userVisibility[uuid] = true |
129 | elseif not self.userSpeaking[uuid] and self.userVisibility[uuid] then |
130 | table.removeElement(self.currentSpeakers, user) |
131 | self.userVisibility[uuid] = false |
132 | end |
133 | end |
134 | end |
135 | |
136 | if Platform.isStadia then |
137 | local state = voiceChatGetConnectionStatus(uuid) |
138 | if self.lastVoiceState ~= state then |
139 | if state == VoiceChatConnectionStatus.UNAVAILABLE then |
140 | -- Add on-change |
141 | self.userAway[user] = 2000 |
142 | |
143 | -- Hide from active speakers so player doesnt show twice |
144 | table.removeElement(self.currentSpeakers, user) |
145 | self.userVisibility[uuid] = false |
146 | else |
147 | -- Directly remove when mic was enabled |
148 | self.userAway[user] = nil |
149 | end |
150 | |
151 | self.lastVoiceState[uuid] = state |
152 | end |
153 | |
154 | -- Countdown timer to hide |
155 | if self.userAway[user] ~= nil then |
156 | self.userAway[user] = self.userAway[user] - dt |
157 | |
158 | if self.userAway[user] <= 0 then |
159 | self.userAway[user] = nil |
160 | end |
161 | end |
162 | end |
163 | end |
164 | end |