1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "emoticon.h"
4
5#include "chat.h"
6
7#include <engine/graphics.h>
8#include <engine/shared/config.h>
9
10#include <generated/protocol.h>
11
12#include <game/client/animstate.h>
13#include <game/client/gameclient.h>
14#include <game/client/ui.h>
15
16CEmoticon::CEmoticon()
17{
18 OnReset();
19}
20
21void CEmoticon::ConKeyEmoticon(IConsole::IResult *pResult, void *pUserData)
22{
23 CEmoticon *pSelf = (CEmoticon *)pUserData;
24
25 if(pSelf->GameClient()->m_Scoreboard.IsActive())
26 return;
27
28 if(!pSelf->GameClient()->m_Snap.m_SpecInfo.m_Active && pSelf->Client()->State() != IClient::STATE_DEMOPLAYBACK)
29 pSelf->m_Active = pResult->GetInteger(Index: 0) != 0;
30}
31
32void CEmoticon::ConEmote(IConsole::IResult *pResult, void *pUserData)
33{
34 ((CEmoticon *)pUserData)->Emote(Emoticon: pResult->GetInteger(Index: 0));
35}
36
37void CEmoticon::OnConsoleInit()
38{
39 Console()->Register(pName: "+emote", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyEmoticon, pUser: this, pHelp: "Open emote selector");
40 Console()->Register(pName: "emote", pParams: "i[emote-id]", Flags: CFGFLAG_CLIENT, pfnFunc: ConEmote, pUser: this, pHelp: "Use emote");
41}
42
43void CEmoticon::OnReset()
44{
45 m_WasActive = false;
46 m_Active = false;
47 m_SelectedEmote = -1;
48 m_SelectedEyeEmote = -1;
49 m_TouchPressedOutside = false;
50}
51
52void CEmoticon::OnRelease()
53{
54 m_Active = false;
55}
56
57bool CEmoticon::OnCursorMove(float x, float y, IInput::ECursorType CursorType)
58{
59 if(!m_Active)
60 return false;
61
62 Ui()->ConvertMouseMove(pX: &x, pY: &y, CursorType);
63 m_SelectorMouse += vec2(x, y);
64 return true;
65}
66
67bool CEmoticon::OnInput(const IInput::CEvent &Event)
68{
69 if(IsActive() && Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
70 {
71 OnRelease();
72 return true;
73 }
74 return false;
75}
76
77void CEmoticon::OnRender()
78{
79 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
80 return;
81
82 if(!m_Active)
83 {
84 if(m_TouchPressedOutside)
85 {
86 m_SelectedEmote = -1;
87 m_SelectedEyeEmote = -1;
88 m_TouchPressedOutside = false;
89 }
90
91 if(m_WasActive && m_SelectedEmote != -1)
92 Emote(Emoticon: m_SelectedEmote);
93 if(m_WasActive && m_SelectedEyeEmote != -1)
94 EyeEmote(EyeEmote: m_SelectedEyeEmote);
95 m_WasActive = false;
96 return;
97 }
98
99 if(GameClient()->m_Snap.m_SpecInfo.m_Active)
100 {
101 m_Active = false;
102 m_WasActive = false;
103 return;
104 }
105
106 m_WasActive = true;
107
108 const CUIRect Screen = *Ui()->Screen();
109
110 const bool WasTouchPressed = m_TouchState.m_AnyPressed;
111 Ui()->UpdateTouchState(State&: m_TouchState);
112 if(m_TouchState.m_AnyPressed)
113 {
114 const vec2 TouchPos = (m_TouchState.m_PrimaryPosition - vec2(0.5f, 0.5f)) * Screen.Size();
115 const float TouchCenterDistance = length(a: TouchPos);
116 if(TouchCenterDistance <= 170.0f)
117 {
118 m_SelectorMouse = TouchPos;
119 }
120 else if(TouchCenterDistance > 190.0f)
121 {
122 m_TouchPressedOutside = true;
123 }
124 }
125 else if(WasTouchPressed)
126 {
127 m_Active = false;
128 return;
129 }
130
131 if(length(a: m_SelectorMouse) > 170.0f)
132 m_SelectorMouse = normalize(v: m_SelectorMouse) * 170.0f;
133
134 float SelectedAngle = angle(a: m_SelectorMouse) + 2 * pi / 24;
135 if(SelectedAngle < 0)
136 SelectedAngle += 2 * pi;
137
138 m_SelectedEmote = -1;
139 m_SelectedEyeEmote = -1;
140 if(length(a: m_SelectorMouse) > 110.0f)
141 m_SelectedEmote = (int)(SelectedAngle / (2 * pi) * NUM_EMOTICONS);
142 else if(length(a: m_SelectorMouse) > 40.0f)
143 m_SelectedEyeEmote = (int)(SelectedAngle / (2 * pi) * NUM_EMOTES);
144
145 const vec2 ScreenCenter = Screen.Center();
146
147 Ui()->MapScreen();
148
149 Graphics()->BlendNormal();
150
151 Graphics()->TextureClear();
152 Graphics()->QuadsBegin();
153 Graphics()->SetColor(r: 0, g: 0, b: 0, a: 0.3f);
154 Graphics()->DrawCircle(CenterX: ScreenCenter.x, CenterY: ScreenCenter.y, Radius: 190.0f, Segments: 64);
155 Graphics()->QuadsEnd();
156
157 Graphics()->WrapClamp();
158 for(int Emote = 0; Emote < NUM_EMOTICONS; Emote++)
159 {
160 float Angle = 2 * pi * Emote / NUM_EMOTICONS;
161 if(Angle > pi)
162 Angle -= 2 * pi;
163
164 Graphics()->TextureSet(Texture: GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[Emote]);
165 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
166 Graphics()->QuadsBegin();
167 const vec2 Nudge = direction(angle: Angle) * 150.0f;
168 const float Size = m_SelectedEmote == Emote ? 80.0f : 50.0f;
169 IGraphics::CQuadItem QuadItem(ScreenCenter.x + Nudge.x, ScreenCenter.y + Nudge.y, Size, Size);
170 Graphics()->QuadsDraw(pArray: &QuadItem, Num: 1);
171 Graphics()->QuadsEnd();
172 }
173 Graphics()->WrapNormal();
174
175 if(GameClient()->m_GameInfo.m_AllowEyeWheel && g_Config.m_ClEyeWheel && GameClient()->m_aLocalIds[g_Config.m_ClDummy] >= 0)
176 {
177 Graphics()->TextureClear();
178 Graphics()->QuadsBegin();
179 Graphics()->SetColor(r: 1.0, g: 1.0, b: 1.0, a: 0.3f);
180 Graphics()->DrawCircle(CenterX: ScreenCenter.x, CenterY: ScreenCenter.y, Radius: 100.0f, Segments: 64);
181 Graphics()->QuadsEnd();
182
183 CTeeRenderInfo TeeInfo = GameClient()->m_aClients[GameClient()->m_aLocalIds[g_Config.m_ClDummy]].m_RenderInfo;
184
185 for(int Emote = 0; Emote < NUM_EMOTES; Emote++)
186 {
187 float Angle = 2 * pi * Emote / NUM_EMOTES;
188 if(Angle > pi)
189 Angle -= 2 * pi;
190
191 const vec2 Nudge = direction(angle: Angle) * 70.0f;
192 TeeInfo.m_Size = m_SelectedEyeEmote == Emote ? 64.0f : 48.0f;
193 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &TeeInfo, Emote, Dir: vec2(-1, 0), Pos: ScreenCenter + Nudge);
194 }
195
196 Graphics()->TextureClear();
197 Graphics()->QuadsBegin();
198 Graphics()->SetColor(r: 0, g: 0, b: 0, a: 0.3f);
199 Graphics()->DrawCircle(CenterX: ScreenCenter.x, CenterY: ScreenCenter.y, Radius: 30.0f, Segments: 64);
200 Graphics()->QuadsEnd();
201 }
202 else
203 m_SelectedEyeEmote = -1;
204
205 RenderTools()->RenderCursor(Center: ScreenCenter + m_SelectorMouse, Size: 24.0f);
206}
207
208void CEmoticon::Emote(int Emoticon)
209{
210 CNetMsg_Cl_Emoticon Msg;
211 Msg.m_Emoticon = Emoticon;
212 Client()->SendPackMsgActive(pMsg: &Msg, Flags: MSGFLAG_VITAL);
213
214 if(g_Config.m_ClDummyCopyMoves)
215 {
216 CMsgPacker MsgDummy(NETMSGTYPE_CL_EMOTICON, false);
217 MsgDummy.AddInt(i: Emoticon);
218 Client()->SendMsg(Conn: !g_Config.m_ClDummy, pMsg: &MsgDummy, Flags: MSGFLAG_VITAL);
219 }
220}
221
222void CEmoticon::EyeEmote(int Emote)
223{
224 char aBuf[32];
225 switch(Emote)
226 {
227 case EMOTE_NORMAL:
228 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote normal %d", g_Config.m_ClEyeDuration);
229 break;
230 case EMOTE_PAIN:
231 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote pain %d", g_Config.m_ClEyeDuration);
232 break;
233 case EMOTE_HAPPY:
234 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote happy %d", g_Config.m_ClEyeDuration);
235 break;
236 case EMOTE_SURPRISE:
237 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote surprise %d", g_Config.m_ClEyeDuration);
238 break;
239 case EMOTE_ANGRY:
240 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote angry %d", g_Config.m_ClEyeDuration);
241 break;
242 case EMOTE_BLINK:
243 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote blink %d", g_Config.m_ClEyeDuration);
244 break;
245 }
246 GameClient()->m_Chat.SendChat(Team: 0, pLine: aBuf);
247}
248