| 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 | |
| 8 | static const float s_SourceVisualSize = 32.0f; |
| 9 | |
| 10 | CLayerSounds::CLayerSounds(CEditorMap *pMap) : |
| 11 | CLayer(pMap, LAYERTYPE_SOUNDS) |
| 12 | { |
| 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 | Editor()->EnvelopeEval(TimeOffsetMillis: Source.m_PosEnvOffset, Env: Source.m_PosEnv, Result&: Offset, Channels: 2); |
| 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 | 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 | 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 | 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 | 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 | Graphics()->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 | Editor()->EnvelopeEval(TimeOffsetMillis: Source.m_PosEnvOffset, Env: Source.m_PosEnv, Result&: Offset, Channels: 2); |
| 79 | const vec2 Position = vec2(fx2f(v: Source.m_Position.x) + Offset.r, fx2f(v: Source.m_Position.y) + Offset.g); |
| 80 | Graphics()->DrawSprite(x: Position.x, y: Position.y, Size: Editor()->MapView()->ScaleLength(Value: s_SourceVisualSize)); |
| 81 | } |
| 82 | |
| 83 | Graphics()->QuadsEnd(); |
| 84 | } |
| 85 | |
| 86 | CSoundSource *CLayerSounds::NewSource(int x, int y) |
| 87 | { |
| 88 | 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 | Rect.DrawOutline(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); |
| 116 | } |
| 117 | |
| 118 | int CLayerSounds::BrushGrab(CLayerGroup *pBrush, CUIRect Rect) |
| 119 | { |
| 120 | // create new layer |
| 121 | std::shared_ptr<CLayerSounds> pGrabbed = std::make_shared<CLayerSounds>(args: pBrush->Map()); |
| 122 | pGrabbed->m_Sound = m_Sound; |
| 123 | pBrush->AddLayer(pLayer: pGrabbed); |
| 124 | |
| 125 | for(const auto &Source : m_vSources) |
| 126 | { |
| 127 | float SourceX = fx2f(v: Source.m_Position.x); |
| 128 | float SourceY = fx2f(v: Source.m_Position.y); |
| 129 | |
| 130 | if(SourceX > Rect.x && SourceX < Rect.x + Rect.w && SourceY > Rect.y && SourceY < Rect.y + Rect.h) |
| 131 | { |
| 132 | CSoundSource NewSource = Source; |
| 133 | NewSource.m_Position.x -= f2fx(v: Rect.x); |
| 134 | NewSource.m_Position.y -= f2fx(v: Rect.y); |
| 135 | |
| 136 | pGrabbed->m_vSources.push_back(x: NewSource); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | return pGrabbed->m_vSources.empty() ? 0 : 1; |
| 141 | } |
| 142 | |
| 143 | void CLayerSounds::BrushPlace(CLayer *pBrush, vec2 WorldPos) |
| 144 | { |
| 145 | CLayerSounds *pSoundLayer = static_cast<CLayerSounds *>(pBrush); |
| 146 | std::vector<CSoundSource> vAddedSources; |
| 147 | for(const auto &Source : pSoundLayer->m_vSources) |
| 148 | { |
| 149 | CSoundSource NewSource = Source; |
| 150 | NewSource.m_Position.x += f2fx(v: WorldPos.x); |
| 151 | NewSource.m_Position.y += f2fx(v: WorldPos.y); |
| 152 | |
| 153 | m_vSources.push_back(x: NewSource); |
| 154 | vAddedSources.push_back(x: NewSource); |
| 155 | } |
| 156 | Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionSoundPlace>(args: Map(), args&: Editor()->m_SelectedGroup, args&: Editor()->m_vSelectedLayers[0], args&: vAddedSources)); |
| 157 | Map()->OnModify(); |
| 158 | } |
| 159 | |
| 160 | CUi::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox) |
| 161 | { |
| 162 | CProperty aProps[] = { |
| 163 | {"Sound" , m_Sound, PROPTYPE_SOUND, -1, 0}, |
| 164 | {nullptr}, |
| 165 | }; |
| 166 | |
| 167 | static int s_aIds[(int)ELayerSoundsProp::NUM_PROPS] = {0}; |
| 168 | int NewVal = 0; |
| 169 | auto [State, Prop] = Editor()->DoPropertiesWithState<ELayerSoundsProp>(pToolbox: pToolBox, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal); |
| 170 | if(Prop != ELayerSoundsProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO)) |
| 171 | { |
| 172 | Map()->OnModify(); |
| 173 | } |
| 174 | |
| 175 | Map()->m_LayerSoundsPropTracker.Begin(pObject: this, Prop, State); |
| 176 | |
| 177 | if(Prop == ELayerSoundsProp::PROP_SOUND) |
| 178 | { |
| 179 | if(NewVal >= 0) |
| 180 | m_Sound = NewVal % Map()->m_vpSounds.size(); |
| 181 | else |
| 182 | m_Sound = -1; |
| 183 | } |
| 184 | |
| 185 | Map()->m_LayerSoundsPropTracker.End(Prop, State); |
| 186 | |
| 187 | return CUi::POPUP_KEEP_OPEN; |
| 188 | } |
| 189 | |
| 190 | void CLayerSounds::ModifySoundIndex(const FIndexModifyFunction &IndexModifyFunction) |
| 191 | { |
| 192 | IndexModifyFunction(&m_Sound); |
| 193 | } |
| 194 | |
| 195 | void CLayerSounds::ModifyEnvelopeIndex(const FIndexModifyFunction &IndexModifyFunction) |
| 196 | { |
| 197 | for(auto &Source : m_vSources) |
| 198 | { |
| 199 | IndexModifyFunction(&Source.m_SoundEnv); |
| 200 | IndexModifyFunction(&Source.m_PosEnv); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | std::shared_ptr<CLayer> CLayerSounds::Duplicate() const |
| 205 | { |
| 206 | return std::make_shared<CLayerSounds>(args: *this); |
| 207 | } |
| 208 | |
| 209 | const char *CLayerSounds::TypeName() const |
| 210 | { |
| 211 | return "sounds" ; |
| 212 | } |
| 213 | |