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 || !GameClient()->m_Snap.m_pLocalCharacter)
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) * (float)NUM_EMOTICONS);
142 else if(length(a: m_SelectorMouse) > 40.0f)
143 m_SelectedEyeEmote = (int)(SelectedAngle / (2 * pi) * (float)NUM_EMOTES);
144
145 const vec2 ScreenCenter = Screen.Center();
146
147 Ui()->MapScreen();
148
149 Graphics()->TextureClear();
150 Graphics()->QuadsBegin();
151 Graphics()->SetColor(r: 0, g: 0, b: 0, a: 0.3f);
152 Graphics()->DrawCircle(CenterX: ScreenCenter.x, CenterY: ScreenCenter.y, Radius: 190.0f, Segments: 64);
153 Graphics()->QuadsEnd();
154
155 Graphics()->WrapClamp();
156 for(int Emote = 0; Emote < NUM_EMOTICONS; Emote++)
157 {
158 float Angle = 2 * pi * Emote / (float)NUM_EMOTICONS;
159 if(Angle > pi)
160 Angle -= 2 * pi;
161
162 Graphics()->TextureSet(Texture: GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[Emote]);
163 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
164 Graphics()->QuadsBegin();
165 const vec2 Nudge = direction(angle: Angle) * 150.0f;
166 const float Size = m_SelectedEmote == Emote ? 80.0f : 50.0f;
167 IGraphics::CQuadItem QuadItem(ScreenCenter.x + Nudge.x, ScreenCenter.y + Nudge.y, Size, Size);
168 Graphics()->QuadsDraw(pArray: &QuadItem, Num: 1);
169 Graphics()->QuadsEnd();
170 }
171 Graphics()->WrapNormal();
172
173 if(GameClient()->m_GameInfo.m_AllowEyeWheel && g_Config.m_ClEyeWheel && GameClient()->m_aLocalIds[g_Config.m_ClDummy] >= 0)
174 {
175 Graphics()->TextureClear();
176 Graphics()->QuadsBegin();
177 Graphics()->SetColor(r: 1.0, g: 1.0, b: 1.0, a: 0.3f);
178 Graphics()->DrawCircle(CenterX: ScreenCenter.x, CenterY: ScreenCenter.y, Radius: 100.0f, Segments: 64);
179 Graphics()->QuadsEnd();
180
181 CTeeRenderInfo TeeInfo = GameClient()->m_aClients[GameClient()->m_aLocalIds[g_Config.m_ClDummy]].m_RenderInfo;
182
183 for(int Emote = 0; Emote < NUM_EMOTES; Emote++)
184 {
185 float Angle = 2 * pi * Emote / (float)NUM_EMOTES;
186 if(Angle > pi)
187 Angle -= 2 * pi;
188
189 const vec2 Nudge = direction(angle: Angle) * 70.0f;
190 TeeInfo.m_Size = m_SelectedEyeEmote == Emote ? 64.0f : 48.0f;
191 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &TeeInfo, Emote, Dir: vec2(-1, 0), Pos: ScreenCenter + Nudge);
192 }
193
194 Graphics()->TextureClear();
195 Graphics()->QuadsBegin();
196 Graphics()->SetColor(r: 0, g: 0, b: 0, a: 0.3f);
197 Graphics()->DrawCircle(CenterX: ScreenCenter.x, CenterY: ScreenCenter.y, Radius: 30.0f, Segments: 64);
198 Graphics()->QuadsEnd();
199 }
200 else
201 m_SelectedEyeEmote = -1;
202
203 RenderTools()->RenderCursor(Center: ScreenCenter + m_SelectorMouse, Size: 24.0f);
204}
205
206void CEmoticon::Emote(int Emoticon)
207{
208 CNetMsg_Cl_Emoticon Msg;
209 Msg.m_Emoticon = Emoticon;
210 Client()->SendPackMsgActive(pMsg: &Msg, Flags: MSGFLAG_VITAL);
211
212 if(g_Config.m_ClDummyCopyMoves)
213 {
214 CMsgPacker MsgDummy(NETMSGTYPE_CL_EMOTICON, false);
215 MsgDummy.AddInt(i: Emoticon);
216 Client()->SendMsg(Conn: !g_Config.m_ClDummy, pMsg: &MsgDummy, Flags: MSGFLAG_VITAL);
217 }
218}
219
220void CEmoticon::EyeEmote(int Emote)
221{
222 char aBuf[32];
223 switch(Emote)
224 {
225 case EMOTE_NORMAL:
226 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote normal %d", g_Config.m_ClEyeDuration);
227 break;
228 case EMOTE_PAIN:
229 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote pain %d", g_Config.m_ClEyeDuration);
230 break;
231 case EMOTE_HAPPY:
232 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote happy %d", g_Config.m_ClEyeDuration);
233 break;
234 case EMOTE_SURPRISE:
235 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote surprise %d", g_Config.m_ClEyeDuration);
236 break;
237 case EMOTE_ANGRY:
238 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote angry %d", g_Config.m_ClEyeDuration);
239 break;
240 case EMOTE_BLINK:
241 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "/emote blink %d", g_Config.m_ClEyeDuration);
242 break;
243 }
244 GameClient()->m_Chat.SendChat(Team: 0, pLine: aBuf);
245}
246