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