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 | |
7 | static const float s_SourceVisualSize = 32.0f; |
8 | |
9 | CLayerSounds::CLayerSounds(CEditor *pEditor) : |
10 | CLayer(pEditor) |
11 | { |
12 | m_Type = LAYERTYPE_SOUNDS; |
13 | m_aName[0] = '\0'; |
14 | m_Sound = -1; |
15 | } |
16 | |
17 | CLayerSounds::CLayerSounds(const CLayerSounds &Other) : |
18 | CLayer(Other) |
19 | { |
20 | m_Sound = Other.m_Sound; |
21 | m_vSources = Other.m_vSources; |
22 | } |
23 | |
24 | CLayerSounds::~CLayerSounds() = default; |
25 | |
26 | void 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 | |
86 | CSoundSource *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 | |
113 | void 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 | |
127 | int 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 | |
153 | void 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 | |
171 | CUi::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 | |
202 | void CLayerSounds::ModifySoundIndex(FIndexModifyFunction Func) |
203 | { |
204 | Func(&m_Sound); |
205 | } |
206 | |
207 | void 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 | |
216 | std::shared_ptr<CLayer> CLayerSounds::Duplicate() const |
217 | { |
218 | return std::make_shared<CLayerSounds>(args: *this); |
219 | } |
220 | |
221 | const char *CLayerSounds::TypeName() const |
222 | { |
223 | return "sounds" ; |
224 | } |
225 | |