1#include "layer_sounds.h"
2
3#include <game/editor/editor.h>
4#include <game/editor/editor_actions.h>
5#include <game/generated/client_data.h>
6
7static const float s_SourceVisualSize = 32.0f;
8
9CLayerSounds::CLayerSounds(CEditor *pEditor) :
10 CLayer(pEditor)
11{
12 m_Type = LAYERTYPE_SOUNDS;
13 m_aName[0] = '\0';
14 m_Sound = -1;
15}
16
17CLayerSounds::CLayerSounds(const CLayerSounds &Other) :
18 CLayer(Other)
19{
20 m_Sound = Other.m_Sound;
21 m_vSources = Other.m_vSources;
22}
23
24CLayerSounds::~CLayerSounds() = default;
25
26void CLayerSounds::Render(bool Tileset)
27{
28 // TODO: nice texture
29 Graphics()->TextureClear();
30 Graphics()->BlendNormal();
31 Graphics()->QuadsBegin();
32
33 // draw falloff distance
34 Graphics()->SetColor(r: 0.6f, g: 0.8f, b: 1.0f, a: 0.4f);
35 for(const auto &Source : m_vSources)
36 {
37 ColorRGBA Offset = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
38 CEditor::EnvelopeEval(TimeOffsetMillis: Source.m_PosEnvOffset, Env: Source.m_PosEnv, Result&: Offset, Channels: 2, pUser: m_pEditor);
39 const vec2 Position = vec2(fx2f(v: Source.m_Position.x) + Offset.r, fx2f(v: Source.m_Position.y) + Offset.g);
40 const float Falloff = Source.m_Falloff / 255.0f;
41
42 switch(Source.m_Shape.m_Type)
43 {
44 case CSoundShape::SHAPE_CIRCLE:
45 {
46 m_pEditor->Graphics()->DrawCircle(CenterX: Position.x, CenterY: Position.y, Radius: Source.m_Shape.m_Circle.m_Radius, Segments: 32);
47 if(Falloff > 0.0f)
48 {
49 m_pEditor->Graphics()->DrawCircle(CenterX: Position.x, CenterY: Position.y, Radius: Source.m_Shape.m_Circle.m_Radius * Falloff, Segments: 32);
50 }
51 break;
52 }
53 case CSoundShape::SHAPE_RECTANGLE:
54 {
55 const float Width = fx2f(v: Source.m_Shape.m_Rectangle.m_Width);
56 const float Height = fx2f(v: Source.m_Shape.m_Rectangle.m_Height);
57 m_pEditor->Graphics()->DrawRectExt(x: Position.x - Width / 2, y: Position.y - Height / 2, w: Width, h: Height, r: 0.0f, Corners: IGraphics::CORNER_NONE);
58 if(Falloff > 0.0f)
59 {
60 m_pEditor->Graphics()->DrawRectExt(x: Position.x - Falloff * Width / 2, y: Position.y - Falloff * Height / 2, w: Width * Falloff, h: Height * Falloff, r: 0.0f, Corners: IGraphics::CORNER_NONE);
61 }
62 break;
63 }
64 }
65 }
66
67 Graphics()->QuadsEnd();
68
69 // draw handles
70 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_AUDIO_SOURCE].m_Id);
71 Graphics()->QuadsBegin();
72
73 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
74 m_pEditor->RenderTools()->SelectSprite(Id: SPRITE_AUDIO_SOURCE);
75 for(const auto &Source : m_vSources)
76 {
77 ColorRGBA Offset = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
78 CEditor::EnvelopeEval(TimeOffsetMillis: Source.m_PosEnvOffset, Env: Source.m_PosEnv, Result&: Offset, Channels: 2, pUser: m_pEditor);
79 const vec2 Position = vec2(fx2f(v: Source.m_Position.x) + Offset.r, fx2f(v: Source.m_Position.y) + Offset.g);
80 m_pEditor->RenderTools()->DrawSprite(x: Position.x, y: Position.y, Size: m_pEditor->MapView()->ScaleLength(Value: s_SourceVisualSize));
81 }
82
83 Graphics()->QuadsEnd();
84}
85
86CSoundSource *CLayerSounds::NewSource(int x, int y)
87{
88 m_pEditor->m_Map.OnModify();
89
90 m_vSources.emplace_back();
91 CSoundSource *pSource = &m_vSources[m_vSources.size() - 1];
92
93 pSource->m_Position.x = f2fx(v: x);
94 pSource->m_Position.y = f2fx(v: y);
95
96 pSource->m_Loop = 1;
97 pSource->m_Pan = 1;
98 pSource->m_TimeDelay = 0;
99
100 pSource->m_PosEnv = -1;
101 pSource->m_PosEnvOffset = 0;
102 pSource->m_SoundEnv = -1;
103 pSource->m_SoundEnvOffset = 0;
104
105 pSource->m_Falloff = 80;
106
107 pSource->m_Shape.m_Type = CSoundShape::SHAPE_CIRCLE;
108 pSource->m_Shape.m_Circle.m_Radius = 1500;
109
110 return pSource;
111}
112
113void CLayerSounds::BrushSelecting(CUIRect Rect)
114{
115 // draw selection rectangle
116 IGraphics::CLineItem Array[4] = {
117 IGraphics::CLineItem(Rect.x, Rect.y, Rect.x + Rect.w, Rect.y),
118 IGraphics::CLineItem(Rect.x + Rect.w, Rect.y, Rect.x + Rect.w, Rect.y + Rect.h),
119 IGraphics::CLineItem(Rect.x + Rect.w, Rect.y + Rect.h, Rect.x, Rect.y + Rect.h),
120 IGraphics::CLineItem(Rect.x, Rect.y + Rect.h, Rect.x, Rect.y)};
121 Graphics()->TextureClear();
122 Graphics()->LinesBegin();
123 Graphics()->LinesDraw(pArray: Array, Num: 4);
124 Graphics()->LinesEnd();
125}
126
127int CLayerSounds::BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect)
128{
129 // create new layer
130 std::shared_ptr<CLayerSounds> pGrabbed = std::make_shared<CLayerSounds>(args&: m_pEditor);
131 pGrabbed->m_Sound = m_Sound;
132 pBrush->AddLayer(pLayer: pGrabbed);
133
134 for(const auto &Source : m_vSources)
135 {
136 float px = fx2f(v: Source.m_Position.x);
137 float py = fx2f(v: Source.m_Position.y);
138
139 if(px > Rect.x && px < Rect.x + Rect.w && py > Rect.y && py < Rect.y + Rect.h)
140 {
141 CSoundSource n = Source;
142
143 n.m_Position.x -= f2fx(v: Rect.x);
144 n.m_Position.y -= f2fx(v: Rect.y);
145
146 pGrabbed->m_vSources.push_back(x: n);
147 }
148 }
149
150 return pGrabbed->m_vSources.empty() ? 0 : 1;
151}
152
153void CLayerSounds::BrushPlace(std::shared_ptr<CLayer> pBrush, float wx, float wy)
154{
155 std::shared_ptr<CLayerSounds> pSoundLayer = std::static_pointer_cast<CLayerSounds>(r: pBrush);
156 std::vector<CSoundSource> vAddedSources;
157 for(const auto &Source : pSoundLayer->m_vSources)
158 {
159 CSoundSource n = Source;
160
161 n.m_Position.x += f2fx(v: wx);
162 n.m_Position.y += f2fx(v: wy);
163
164 m_vSources.push_back(x: n);
165 vAddedSources.push_back(x: n);
166 }
167 m_pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionSoundPlace>(args&: m_pEditor, args&: m_pEditor->m_SelectedGroup, args&: m_pEditor->m_vSelectedLayers[0], args&: vAddedSources));
168 m_pEditor->m_Map.OnModify();
169}
170
171CUi::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox)
172{
173 CProperty aProps[] = {
174 {.m_pName: "Sound", .m_Value: m_Sound, .m_Type: PROPTYPE_SOUND, .m_Min: -1, .m_Max: 0},
175 {.m_pName: nullptr},
176 };
177
178 static int s_aIds[(int)ELayerSoundsProp::NUM_PROPS] = {0};
179 int NewVal = 0;
180 auto [State, Prop] = m_pEditor->DoPropertiesWithState<ELayerSoundsProp>(pToolbox: pToolBox, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
181 if(Prop != ELayerSoundsProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO))
182 {
183 m_pEditor->m_Map.OnModify();
184 }
185
186 static CLayerSoundsPropTracker s_Tracker(m_pEditor);
187 s_Tracker.Begin(pObject: this, Prop, State);
188
189 if(Prop == ELayerSoundsProp::PROP_SOUND)
190 {
191 if(NewVal >= 0)
192 m_Sound = NewVal % m_pEditor->m_Map.m_vpSounds.size();
193 else
194 m_Sound = -1;
195 }
196
197 s_Tracker.End(Prop, State);
198
199 return CUi::POPUP_KEEP_OPEN;
200}
201
202void CLayerSounds::ModifySoundIndex(FIndexModifyFunction Func)
203{
204 Func(&m_Sound);
205}
206
207void CLayerSounds::ModifyEnvelopeIndex(FIndexModifyFunction Func)
208{
209 for(auto &Source : m_vSources)
210 {
211 Func(&Source.m_SoundEnv);
212 Func(&Source.m_PosEnv);
213 }
214}
215
216std::shared_ptr<CLayer> CLayerSounds::Duplicate() const
217{
218 return std::make_shared<CLayerSounds>(args: *this);
219}
220
221const char *CLayerSounds::TypeName() const
222{
223 return "sounds";
224}
225