/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// soundman.cpp: Sound Manager
//

#include "g_local.h"
#include "debuglines.h"
#include "entity.h"
#include "trigger.h"
#include "player.h"
#include "soundman.h"

SoundManager SoundMan;

/******************************************************************************

  Sound Manager

******************************************************************************/

Event EV_SoundManager_AddSpeaker
(
    "addspeaker", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Add a new sound where the player is standing.",
    EV_NORMAL
);
Event EV_SoundManager_AddRandomSpeaker
(
    "addrandomspeaker", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Add a new sound where the player is standing.",
    EV_NORMAL
);
Event EV_SoundManager_AddMusicTrigger
(
    "addmusictrigger", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Add a new music trigger where the player is standing.",
    EV_NORMAL
);
Event EV_SoundManager_AddReverbTrigger
(
    "addreverbtrigger", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Add a new reverb trigger where the player is standing.",
    EV_NORMAL
);
Event EV_SoundManager_Replace
( 
    "replace", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Replace the current sound position with the player's.",
    EV_NORMAL
);
Event EV_SoundManager_Delete
( 
    "delete", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Delete the current sound.",
    EV_NORMAL
);
Event EV_SoundManager_MovePlayer
( 
    "moveplayer", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Move the player to the current sound position.",
    EV_NORMAL
);
Event EV_SoundManager_Next
( 
    "next", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Go to the next sound.",
    EV_NORMAL
);
Event EV_SoundManager_Previous
( 
    "prev", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Go to the previous sound.",
    EV_NORMAL
);

Event EV_SoundManager_Show
( 
    "show", 
    EV_CONSOLE,
   "E",
   "path",
   "Show all the sounds.",
    EV_NORMAL
);

Event EV_SoundManager_ShowingSounds
( 
    "_showing_sounds", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Internal event for showing the sounds.",
    EV_NORMAL
);
Event EV_SoundManager_Hide
( 
    "hide", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Hides the sounds.",
    EV_NORMAL
);

Event EV_SoundManager_Save
( 
    "save", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Saves the sounds.",
    EV_NORMAL
);
Event EV_SoundManager_UpdateInput
( 
    "updateinput", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Updates the current sound with user interface values.",
    EV_NORMAL
);

Event EV_SoundManager_Reset
( 
    "reset", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Resets the state of all sounds and triggers.",
    EV_NORMAL
);

Event EV_SoundManager_GlobalTranslate
( 
    "globaltranslate", 
    EV_CONSOLE,
    "v",
    "translate_amount",
    "Translates all sounds and triggers by specified amount.",
    EV_NORMAL
);

Event EV_SoundManager_SwitchFacet
( 
    "switchfacet", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Switch the current facet that we are editing.",
    EV_NORMAL
);

Event EV_SoundManager_PreviewReverb
( 
    "previewreverb", 
    EV_CONSOLE,
    NULL,
    NULL,
    "Test out the current reverb settings.",
    EV_NORMAL
);

Event EV_SoundManager_ResetReverb
( 
    "resetreverb", 
    EV_CONSOLE,
    NULL,
    NULL,
    "reset the reverb settings to a normal.",
    EV_NORMAL
);

CLASS_DECLARATION(Listener, SoundManager, NULL) {
    {&EV_SoundManager_AddSpeaker,       &SoundManager::AddSpeaker          },
    {&EV_SoundManager_AddRandomSpeaker, &SoundManager::AddRandomSpeaker    },
    {&EV_SoundManager_AddMusicTrigger,  &SoundManager::AddMusicTrigger     },
    {&EV_SoundManager_AddReverbTrigger, &SoundManager::AddReverbTrigger    },
    {&EV_SoundManager_Replace,          &SoundManager::Replace             },
    {&EV_SoundManager_Delete,           &SoundManager::Delete              },
    {&EV_SoundManager_MovePlayer,       &SoundManager::MovePlayer          },
    {&EV_SoundManager_Next,             &SoundManager::Next                },
    {&EV_SoundManager_Previous,         &SoundManager::Previous            },
    {&EV_SoundManager_Show,             &SoundManager::Show                },
    {&EV_SoundManager_ShowingSounds,    &SoundManager::ShowingSounds       },
    {&EV_SoundManager_Hide,             &SoundManager::Hide                },
    {&EV_SoundManager_Save,             &SoundManager::Save                },
    {&EV_SoundManager_UpdateInput,      &SoundManager::UpdateEvent         },
    {&EV_SoundManager_Reset,            &SoundManager::ResetEvent          },
    {&EV_SoundManager_GlobalTranslate,  &SoundManager::GlobalTranslateEvent},
    {&EV_SoundManager_SwitchFacet,      &SoundManager::SwitchFacetEvent    },
    {&EV_SoundManager_PreviewReverb,    &SoundManager::PreviewReverbEvent  },
    {&EV_SoundManager_ResetReverb,      &SoundManager::ResetReverbEvent    },
    {NULL,                              NULL                               }
};

Player *SoundManager_GetPlayer(void)
{
    assert(g_entities[0].entity && g_entities[0].entity->isSubclassOf(Player));
    if (!g_entities[0].entity || !(g_entities[0].entity->isSubclassOf(Player))) {
        gi.Printf("No player found.\n");
        return NULL;
    }

    return (Player *)g_entities[0].entity;
}

SoundManager::SoundManager()
{
    currentFacet = 0;
    Reset();
}

void SoundManager::Reset(void)
{
    current = NULL;
    soundList.ClearObjectList();
}

void SoundManager::UpdateUI(void)
{
    if (current) {
        gi.cvar_set("snd_multifaceted", "");
        gi.cvar_set("snd_currentfacet", "");
        gi.cvar_set("snd_onetime", "0");
        gi.cvar_set("snd_useangles", "0");
        gi.cvar_set("snd_yaw", "0");

        gi.cvar_set("snd_origin", va("%.2f %.2f %.2f", current->origin[0], current->origin[1], current->origin[2]));
        gi.cvar_set("snd_targetname", current->targetname.c_str());
        if (current->isSubclassOf(TriggerSpeaker)) {
            TriggerSpeaker *speaker;

            speaker = (TriggerSpeaker *)current;
            if (speaker->volume != DEFAULT_VOL) {
                gi.cvar_set("snd_volume", va("%.1f", speaker->volume));
            } else {
                gi.cvar_set("snd_volume", "Default");
            }
            if (speaker->min_dist != DEFAULT_MIN_DIST) {
                gi.cvar_set("snd_mindist", va("%.1f", speaker->min_dist));
            } else {
                gi.cvar_set("snd_mindist", "Default");
            }

            // setup the sound
            gi.cvar_set("ui_pickedsound", speaker->Noise().c_str());

            if (current->isSubclassOf(RandomSpeaker)) {
                RandomSpeaker *random;

                random = (RandomSpeaker *)current;
                gi.cvar_set("snd_mindelay", va("%.1f", random->mindelay));
                gi.cvar_set("snd_maxdelay", va("%.1f", random->maxdelay));
                gi.cvar_set("snd_channel", va("%d", random->channel));
                gi.cvar_set("snd_chance", va("%.1f", random->chance));
                gi.cvar_set("snd_type", "RandomSpeaker");
            } else {
                gi.cvar_set("snd_type", "Speaker");
            }
        } else if (current->isSubclassOf(TriggerMusic) || current->isSubclassOf(TriggerReverb)) {
            int      multiFaceted;
            Trigger *trigger;

            trigger = (Trigger *)current;
            gi.cvar_set("snd_width", va("%.0f", trigger->maxs[0]));
            gi.cvar_set("snd_length", va("%.0f", trigger->maxs[1]));
            gi.cvar_set("snd_height", va("%.0f", trigger->maxs[2]));
            if (trigger->UsingTriggerDir()) {
                gi.cvar_set("snd_useangles", "1");
            } else {
                gi.cvar_set("snd_useangles", "0");
            }
            gi.cvar_set("snd_yaw", va("%.0f", trigger->angles[1]));
            multiFaceted = trigger->GetMultiFaceted();
            if (multiFaceted) {
                if (multiFaceted == 1) {
                    gi.cvar_set("snd_multifaceted", "North/South");
                    if (currentFacet) {
                        gi.cvar_set("snd_currentfacet", "South");
                    } else {
                        gi.cvar_set("snd_currentfacet", "North");
                    }
                } else {
                    gi.cvar_set("snd_multifaceted", "East/West");
                    if (currentFacet) {
                        gi.cvar_set("snd_currentfacet", "West");
                    } else {
                        gi.cvar_set("snd_currentfacet", "East");
                    }
                }
            } else {
                gi.cvar_set("snd_multifaceted", "Not");
            }

            if (current->isSubclassOf(TriggerMusic)) {
                TriggerMusic *music;

                music = (TriggerMusic *)trigger;

                gi.cvar_set("snd_type", "MusicTrigger");
                if (music->oneshot) {
                    gi.cvar_set("snd_onetime", "1");
                } else {
                    gi.cvar_set("snd_onetime", "0");
                }

                if (!currentFacet) {
                    gi.cvar_set("snd_currentmood", Director.GetString(music->current).c_str());
                    gi.cvar_set("snd_fallbackmood", Director.GetString(music->fallback).c_str());
                } else {
                    gi.cvar_set("snd_currentmood", Director.GetString(music->altcurrent).c_str());
                    gi.cvar_set("snd_fallbackmood", Director.GetString(music->altfallback).c_str());
                }
            } else if (current->isSubclassOf(TriggerReverb)) {
                TriggerReverb *reverb;

                reverb = (TriggerReverb *)trigger;

                gi.cvar_set("snd_type", "ReverbTrigger");
                if (reverb->oneshot) {
                    gi.cvar_set("snd_onetime", "1");
                } else {
                    gi.cvar_set("snd_onetime", "0");
                }

                if (!currentFacet) {
                    gi.cvar_set("snd_reverbtypedisplay", EAXMode_NumToName(reverb->reverbtype));
                    gi.cvar_set("snd_reverbtype", va("%d", reverb->reverbtype));
                    gi.cvar_set("snd_reverblevel", va("%.2f", reverb->reverblevel));
                } else {
                    gi.cvar_set("snd_reverbtypedisplay", EAXMode_NumToName(reverb->altreverbtype));
                    gi.cvar_set("snd_reverbtype", va("%d", reverb->altreverbtype));
                    gi.cvar_set("snd_reverblevel", va("%.2f", reverb->altreverblevel));
                }
            }
        }
        if (EventPending(EV_SoundManager_ShowingSounds)) {
            gi.cvar_set("snd_hiddenstate", "visible");
        } else {
            gi.cvar_set("snd_hiddenstate", "hidden");
        }
        gi.cvar_set("snd_speakernum", va("%d", soundList.IndexOfObject(current) - 1));
    }
}

void SoundManager::UpdateRandomSpeaker(RandomSpeaker *speaker)
{
    cvar_t *cvar;
    Vector  tempvec;

    if (!speaker) {
        return;
    }

    // get origin
    cvar = gi.Cvar_Get("snd_origin", "", 0);
    sscanf(cvar->string, "%f %f %f", &tempvec[0], &tempvec[1], &tempvec[2]);
    speaker->setOrigin(tempvec);

    // get targetname
    cvar = gi.Cvar_Get("snd_targetname", "", 0);
    speaker->SetTargetName(cvar->string);

    // get volume
    cvar = gi.Cvar_Get("snd_volume", "", 0);
    if (str(cvar->string) == "Default") {
        speaker->SetVolume(DEFAULT_VOL);
    } else {
        speaker->SetVolume(cvar->value);
    }

    // get min_dist
    cvar = gi.Cvar_Get("snd_mindist", "", 0);
    if (str(cvar->string) == "Default") {
        speaker->SetMinDist(DEFAULT_MIN_DIST);
    } else {
        speaker->SetMinDist(cvar->value);
    }

    // get mindelay
    cvar = gi.Cvar_Get("snd_mindelay", "", 0);
    speaker->SetMinDelay(cvar->value);

    // get maxdelay
    cvar = gi.Cvar_Get("snd_maxdelay", "", 0);
    speaker->SetMaxDelay(cvar->value);

    // get chance
    cvar = gi.Cvar_Get("snd_chance", "", 0);
    speaker->SetChance(cvar->value);

    // get sound
    cvar = gi.Cvar_Get("ui_pickedsound", "", 0);
    if (cvar->string[0]) {
        speaker->SetNoise(cvar->string);
        speaker->ScheduleSound();
    }
}

void SoundManager::UpdateSpeaker(TriggerSpeaker *speaker)
{
    cvar_t *cvar;
    Vector  tempvec;

    if (!speaker) {
        return;
    }

    //
    // make sure the speaker is an ambient speaker
    //
    speaker->ambient = true;

    // get origin
    cvar = gi.Cvar_Get("snd_origin", "", 0);
    sscanf(cvar->string, "%f %f %f", &tempvec[0], &tempvec[1], &tempvec[2]);
    speaker->setOrigin(tempvec);

    // get targetname
    cvar = gi.Cvar_Get("snd_targetname", "", 0);
    speaker->SetTargetName(cvar->string);

    // get volume
    cvar = gi.Cvar_Get("snd_volume", "", 0);
    if (str(cvar->string) == "Default") {
        speaker->SetVolume(DEFAULT_VOL);
    } else {
        speaker->SetVolume(cvar->value);
    }

    // get min_dist
    cvar = gi.Cvar_Get("snd_mindist", "", 0);
    if (str(cvar->string) == "Default") {
        speaker->SetMinDist(DEFAULT_MIN_DIST);
    } else {
        speaker->SetMinDist(cvar->value);
    }

    // get sound
    cvar = gi.Cvar_Get("ui_pickedsound", "", 0);
    if (cvar->string[0]) {
        speaker->SetNoise(cvar->string);
        speaker->StartSound();
    }
}

void SoundManager::UpdateTriggerMusic(TriggerMusic *music)
{
    cvar_t *cvar;
    str     current;
    str     fallback;
    str     faceted;
    Vector  tempvec;

    if (!music) {
        return;
    }

    //
    // go to a known state
    //

    music->SetOneShot(false);
    // no angle use
    music->useTriggerDir = false;
    // make sure it doesn't trigger
    music->triggerable = false;

    // get origin
    cvar = gi.Cvar_Get("snd_origin", "", 0);
    sscanf(cvar->string, "%f %f %f", &tempvec[0], &tempvec[1], &tempvec[2]);
    music->setOrigin(tempvec);

    // get targetname
    cvar = gi.Cvar_Get("snd_targetname", "", 0);
    music->SetTargetName(cvar->string);

    // get width
    cvar           = gi.Cvar_Get("snd_width", "", 0);
    music->mins[0] = -cvar->value;
    music->maxs[0] = cvar->value;

    // get length
    cvar           = gi.Cvar_Get("snd_length", "", 0);
    music->mins[1] = -cvar->value;
    music->maxs[1] = cvar->value;

    // get height
    cvar           = gi.Cvar_Get("snd_height", "", 0);
    music->mins[2] = 0;
    music->maxs[2] = cvar->value;

    music->setSize(music->mins, music->maxs);

    // get current multi faceted ness
    cvar    = gi.Cvar_Get("snd_multifaceted", "", 0);
    faceted = cvar->string;

    if (faceted == "North/South") {
        music->SetMultiFaceted(1);
    } else if (faceted == "East/West") {
        music->SetMultiFaceted(2);
    } else {
        music->SetMultiFaceted(0);
    }

    // get current mood
    cvar    = gi.Cvar_Get("snd_currentmood", "", 0);
    current = cvar->string;

    // get fallback mood
    cvar     = gi.Cvar_Get("snd_fallbackmood", "", 0);
    fallback = cvar->string;

    if (music->multiFaceted && currentFacet) {
        music->SetAltMood(current, fallback);
    } else {
        music->SetMood(current, fallback);
    }

    // get onetime
    cvar = gi.Cvar_Get("snd_onetime", "", 0);
    if (cvar->integer) {
        music->SetOneShot(true);
    }

    // get yaw
    cvar               = gi.Cvar_Get("snd_yaw", "", 0);
    music->angles[YAW] = cvar->value;
    music->setAngles();

    // get useangles
    cvar = gi.Cvar_Get("snd_useangles", "", 0);
    if (cvar->integer) {
        music->SetTriggerDir(music->angles[YAW]);
    }
    UpdateUI();
}

void SoundManager::UpdateTriggerReverb(TriggerReverb *reverb)
{
    cvar_t *cvar;
    int     reverbtype;
    float   reverblevel;
    str     faceted;
    Vector  tempvec;

    if (!reverb) {
        return;
    }

    //
    // go to a known state
    //

    reverb->SetOneShot(false);
    // no angle use
    reverb->useTriggerDir = false;
    // make sure it doesn't trigger
    reverb->triggerable = false;

    // get origin
    cvar = gi.Cvar_Get("snd_origin", "", 0);
    sscanf(cvar->string, "%f %f %f", &tempvec[0], &tempvec[1], &tempvec[2]);
    reverb->setOrigin(tempvec);

    // get targetname
    cvar = gi.Cvar_Get("snd_targetname", "", 0);
    reverb->SetTargetName(cvar->string);

    // get width
    cvar            = gi.Cvar_Get("snd_width", "", 0);
    reverb->mins[0] = -cvar->value;
    reverb->maxs[0] = cvar->value;

    // get length
    cvar            = gi.Cvar_Get("snd_length", "", 0);
    reverb->mins[1] = -cvar->value;
    reverb->maxs[1] = cvar->value;

    // get height
    cvar            = gi.Cvar_Get("snd_height", "", 0);
    reverb->mins[2] = 0;
    reverb->maxs[2] = cvar->value;

    reverb->setSize(reverb->mins, reverb->maxs);

    // get current multi faceted ness
    cvar    = gi.Cvar_Get("snd_multifaceted", "", 0);
    faceted = cvar->string;

    if (faceted == "North/South") {
        reverb->SetMultiFaceted(1);
    } else if (faceted == "East/West") {
        reverb->SetMultiFaceted(2);
    } else {
        reverb->SetMultiFaceted(0);
    }

    // get reverb type
    cvar       = gi.Cvar_Get("snd_reverbtype", "", 0);
    reverbtype = cvar->integer;

    // get reverb level
    cvar        = gi.Cvar_Get("snd_reverblevel", "", 0);
    reverblevel = cvar->value;

    if (reverb->multiFaceted && currentFacet) {
        reverb->SetAltReverb(reverbtype, reverblevel);
    } else {
        reverb->SetReverb(reverbtype, reverblevel);
    }

    // get onetime
    cvar = gi.Cvar_Get("snd_onetime", "", 0);
    if (cvar->integer) {
        reverb->SetOneShot(true);
    }

    // get yaw
    cvar                = gi.Cvar_Get("snd_yaw", "", 0);
    reverb->angles[YAW] = cvar->value;
    reverb->setAngles();

    // get useangles
    cvar = gi.Cvar_Get("snd_useangles", "", 0);
    if (cvar->integer) {
        reverb->SetTriggerDir(reverb->angles[YAW]);
    }
    UpdateUI();
}

void SoundManager::UpdateEvent(Event *ev)
{
    if (!current) {
        return;
    }
    if (current->isSubclassOf(RandomSpeaker)) {
        UpdateRandomSpeaker((RandomSpeaker *)current);
    } else if (current->isSubclassOf(TriggerSpeaker)) {
        UpdateSpeaker((TriggerSpeaker *)current);
    } else if (current->isSubclassOf(TriggerMusic)) {
        UpdateTriggerMusic((TriggerMusic *)current);
    } else if (current->isSubclassOf(TriggerReverb)) {
        UpdateTriggerReverb((TriggerReverb *)current);
    }
}

void SoundManager::AddSpeaker(Event *ev)
{
    Player *player;
    Vector  ang;
    Vector  pos;

    player = SoundManager_GetPlayer();
    if (player) {
        player->GetPlayerView(&pos, &ang);

        current = new TriggerSpeaker;
        current->setOrigin(pos);
        current->setAngles(ang);

        soundList.AddUniqueObject(current);

        Show();
    }
    UpdateUI();
}

void SoundManager::AddRandomSpeaker(Event *ev)
{
    Player *player;
    Vector  ang;
    Vector  pos;

    player = SoundManager_GetPlayer();
    if (player) {
        player->GetPlayerView(&pos, &ang);

        current = new RandomSpeaker;
        current->setOrigin(pos);
        current->setAngles(ang);

        soundList.AddUniqueObject(current);

        Show();
    }
    UpdateUI();
}

void SoundManager::AddMusicTrigger(Event *ev)
{
    Player *player;
    Vector  ang;

    player = SoundManager_GetPlayer();
    if (player) {
        player->GetPlayerView(NULL, &ang);

        current = new TriggerMusic;
        // we grab the origin from the feet of the player
        current->setOrigin(player->origin);
        current->setAngles(ang);
        current->setSize(Vector("-16 -16 0"), Vector("16 16 64"));
        // make sure it doesn't trigger
        ((TriggerMusic *)current)->triggerable = false;

        soundList.AddUniqueObject(current);

        Show();
    }
    UpdateUI();
}

void SoundManager::AddReverbTrigger(Event *ev)
{
    Player *player;
    Vector  ang;

    player = SoundManager_GetPlayer();
    if (player) {
        player->GetPlayerView(NULL, &ang);

        current = new TriggerReverb;
        // we grab the origin from the feet of the player
        current->setOrigin(player->origin);
        current->setAngles(ang);
        current->setSize(Vector("-16 -16 0"), Vector("16 16 64"));
        // make sure it doesn't trigger
        ((TriggerReverb *)current)->triggerable = false;

        soundList.AddUniqueObject(current);

        Show();
    }
    UpdateUI();
}

void SoundManager::Replace(Event *ev)
{
    Player *player;
    Vector  ang;
    Vector  pos;

    player = SoundManager_GetPlayer();
    if (current && player) {
        player->GetPlayerView(&pos, &ang);

        if (current->isSubclassOf(TriggerMusic) || current->isSubclassOf(TriggerReverb)) {
            current->setOrigin(player->origin);
        } else {
            current->setOrigin(pos);
        }
        current->setAngles(ang);
    }
    UpdateUI();
}

void SoundManager::Delete(Event *ev)
{
    int index = 0;

    if (!current) {
        return;
    }

    if (soundList.ObjectInList(current)) {
        index = soundList.IndexOfObject(current);
        // remove the speaker
        soundList.RemoveObject(current);
    }
    current->PostEvent(EV_Remove, 0);

    if ((index > 0) && (index < soundList.NumObjects())) {
        current = soundList.ObjectAt(index);
        CurrentGainsFocus();
    } else {
        current = NULL;
    }

    UpdateUI();
}

void SoundManager::MovePlayer(Event *ev)
{
    Player *player;
    Vector  pos;

    player = SoundManager_GetPlayer();
    if (current && player) {
        player->GetPlayerView(&pos, NULL);

        if (current->isSubclassOf(TriggerMusic) || current->isSubclassOf(TriggerReverb)) {
            player->setOrigin(current->origin);
        } else {
            player->setOrigin(current->origin - pos + player->origin);
        }
        player->SetViewAngles(current->angles);
    }
}

void SoundManager::CurrentLostFocus(void)
{
    if (current) {
        if (current->isSubclassOf(TriggerMusic) || current->isSubclassOf(TriggerReverb)) {
            current->PostEvent(EV_Trigger_SetTriggerable, 0.1f);
        }
    }
}

void SoundManager::CurrentGainsFocus(void)
{
    if (current) {
        if (current->isSubclassOf(TriggerMusic) || current->isSubclassOf(TriggerReverb)) {
            current->ProcessEvent(EV_Trigger_SetNotTriggerable);
        }
    }
}

void SoundManager::Next(Event *ev)
{
    int index;

    currentFacet = 0;

    if (current) {
        //
        // find current sound in container of sounds
        //
        index = soundList.IndexOfObject(current);
        if (index < soundList.NumObjects()) {
            index++;
        } else {
            index = 1;
        }
        CurrentLostFocus();
    } else {
        index = 1;
    }

    if (index <= soundList.NumObjects()) {
        current = soundList.ObjectAt(index);
        CurrentGainsFocus();
        UpdateUI();
    }
}

void SoundManager::Previous(Event *ev)
{
    int index;

    currentFacet = 0;

    if (current) {
        //
        // find current sound in container of sounds
        //
        index = soundList.IndexOfObject(current);
        if (index > 1) {
            index--;
        } else {
            index = soundList.NumObjects();
        }
        CurrentLostFocus();
    } else {
        index = 1;
    }

    if (index <= soundList.NumObjects()) {
        current = soundList.ObjectAt(index);
        CurrentGainsFocus();
        UpdateUI();
    }
}

void SoundManager::ResetEvent(Event *ev)
{
    int     i;
    Entity *ent;

    for (i = 1; i <= soundList.NumObjects(); i++) {
        ent = soundList.ObjectAt(i);
        if (ent->isSubclassOf(TriggerSpeaker)) {
            // nothing to reset
        } else if (ent->isSubclassOf(TriggerMusic)) {
            TriggerMusic *music;

            music = (TriggerMusic *)ent;
            music->SetOneShot(music->oneshot);
        } else if (ent->isSubclassOf(TriggerReverb)) {
            TriggerReverb *reverb;

            reverb = (TriggerReverb *)ent;
            reverb->SetOneShot(reverb->oneshot);
        }
    }
}

void SoundManager::GlobalTranslateEvent(Event *ev)
{
    int     i;
    Entity *ent;
    Vector  amount;

    amount = ev->GetVector(1);

    for (i = 1; i <= soundList.NumObjects(); i++) {
        ent = soundList.ObjectAt(i);
        ent->addOrigin(amount);
    }
}

void SoundManager::SwitchFacetEvent(Event *ev)
{
    if (current && current->isSubclassOf(Trigger)) {
        Trigger *trigger;

        trigger = (Trigger *)current;
        if (trigger->GetMultiFaceted() && !currentFacet) {
            currentFacet = 1;
        } else {
            currentFacet = 0;
        }
    }
    UpdateUI();
}

void SoundManager::PreviewReverbEvent(Event *ev)
{
    cvar_t *cvar;
    int     reverbtype;
    float   reverblevel;

    // get reverb type
    cvar       = gi.Cvar_Get("snd_reverbtype", "", 0);
    reverbtype = cvar->integer;

    // get reverb level
    cvar        = gi.Cvar_Get("snd_reverblevel", "", 0);
    reverblevel = cvar->value;

    if (g_entities[0].inuse && g_entities[0].client) {
        Player *client;

        client = (Player *)g_entities[0].entity;
        client->SetReverb(reverbtype, reverblevel);
    }
}

void SoundManager::ResetReverbEvent(Event *ev)
{
    if (g_entities[0].inuse && g_entities[0].client) {
        Player *client;

        client = (Player *)g_entities[0].entity;
        client->SetReverb("Generic", 0);
    }
}

void SoundManager::ShowingSounds(Event *ev)
{
    int     i;
    Entity *ent;

    for (i = 1; i <= soundList.NumObjects(); i++) {
        ent = soundList.ObjectAt(i);
        if (ent->isSubclassOf(TriggerSpeaker)) {
            TriggerSpeaker *speaker;

            speaker = (TriggerSpeaker *)ent;
            if (current == ent) {
                if (speaker->volume != DEFAULT_VOL) {
                    G_DrawDebugNumber(speaker->origin + Vector(0, 0, 10), speaker->volume, 0.5, 0, 1, 0, 2);
                }
                if (speaker->min_dist != DEFAULT_MIN_DIST) {
                    G_DrawDebugNumber(speaker->origin + Vector(0, 0, 20), speaker->min_dist, 0.5, 0, 0, 1, 0);
                }

                // falloff circles
                if (speaker->min_dist != DEFAULT_VOL) {
                    G_DebugCircle(speaker->origin, speaker->min_dist, 0, 1, 1, 1, true);
                    G_DebugCircle(speaker->origin, 2 * speaker->min_dist, 0, 0.5, 0.5, 0.5, true);
                    G_DebugCircle(speaker->origin, 4 * speaker->min_dist, 0, 0.25, 0.25, 0.25, true);
                }

                if (speaker->isSubclassOf(RandomSpeaker)) {
                    RandomSpeaker *random;

                    G_DebugPyramid(speaker->origin, 24, 1, 1, 0.5, 1);
                    random = (RandomSpeaker *)ent;

                    G_DrawDebugNumber(random->origin + Vector(0, 0, 30), random->mindelay, 0.5, 0.25, 0, 1, 1);
                    G_DrawDebugNumber(random->origin + Vector(0, 0, 38), random->maxdelay, 0.5, 0.25, 0, 1, 1);
                    G_DrawDebugNumber(random->origin + Vector(0, 0, 46), random->chance, 0.5, 0.25, 0, 1, 2);
                } else {
                    G_DebugPyramid(speaker->origin, 24, 1, 1, 0, 1);
                }
            } else {
                if (speaker->isSubclassOf(RandomSpeaker)) {
                    G_DebugPyramid(speaker->origin, 24, 1, 0, 1, 1);
                } else {
                    G_DebugPyramid(speaker->origin, 24, 1, 0, 0, 1);
                }
            }
        } else if (ent->isSubclassOf(TriggerMusic)) {
            int           facet;
            TriggerMusic *music;

            music = (TriggerMusic *)ent;

            if (current == ent) {
                if (music->oneshot) {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 0, 1, 1, 1);
                } else {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 1, 1, 0, 1);
                }
            } else {
                if (music->oneshot) {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 0, 0.5, 0.5, 1);
                } else {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 1, 0, 0, 1);
                }
            }

            if (music->useTriggerDir) {
                Vector org;

                org = ent->origin;
                org[2] += 0.5f * ent->maxs[2];

                G_DebugArrow(org, music->GetTriggerDir(), 48, 0.5, 1, 1, 1);
            }
            facet = music->GetMultiFaceted();
            if (facet) {
                if (current == ent) {
                    G_DebugHighlightFacet(
                        ent->origin, ent->mins, ent->maxs, (facet_t)(((facet - 1) << 1) + currentFacet), 1, 1, 1, 1
                    );
                } else {
                    G_DebugHighlightFacet(
                        ent->origin, ent->mins, ent->maxs, (facet_t)(((facet - 1) << 1) + 0), 0.6f, 0.3f, 0.2f, 0.1f
                    );
                    G_DebugHighlightFacet(
                        ent->origin, ent->mins, ent->maxs, (facet_t)(((facet - 1) << 1) + 1), 0.6f, 0.3f, 0.2f, 0.1f
                    );
                }
            }
        } else if (ent->isSubclassOf(TriggerReverb)) {
            int            facet;
            TriggerReverb *reverb;

            reverb = (TriggerReverb *)ent;

            if (current == ent) {
                if (reverb->oneshot) {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 0.5, 0.5, 1, 1);
                } else {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 0, 0.5, 1, 1);
                }
            } else {
                if (reverb->oneshot) {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 0.5, 0, 1, 1);
                } else {
                    G_DebugBBox(ent->origin, ent->mins, ent->maxs, 0, 0, 1, 1);
                }
            }

            if (reverb->useTriggerDir) {
                Vector org;

                org = ent->origin;
                org[2] += 0.5f * ent->maxs[2];

                G_DebugArrow(org, reverb->GetTriggerDir(), 48, 0.5, 1, 1, 1);
            }
            facet = reverb->GetMultiFaceted();
            if (facet) {
                if (current == ent) {
                    G_DebugHighlightFacet(
                        ent->origin, ent->mins, ent->maxs, (facet_t)(((facet - 1) << 1) + currentFacet), 1, 1, 1, 1
                    );
                } else {
                    G_DebugHighlightFacet(
                        ent->origin, ent->mins, ent->maxs, (facet_t)(((facet - 1) << 1) + 0), 0.3f, 0.3f, 0.6f, 0.1f
                    );
                    G_DebugHighlightFacet(
                        ent->origin, ent->mins, ent->maxs, (facet_t)(((facet - 1) << 1) + 1), 0.3f, 0.3f, 0.6f, 0.1f
                    );
                }
            }
        }
    }
    PostEvent(EV_SoundManager_ShowingSounds, FRAMETIME);
}

void SoundManager::Show(void)
{
    CurrentGainsFocus();
    CancelEventsOfType(EV_SoundManager_ShowingSounds);
    PostEvent(EV_SoundManager_ShowingSounds, FRAMETIME);
    UpdateUI();
}

void SoundManager::Show(Event *ev)
{
    Show();
}

void SoundManager::Hide(Event *ev)
{
    CurrentLostFocus();
    CancelEventsOfType(EV_SoundManager_ShowingSounds);
    UpdateUI();
}

void SoundManager::Save(void)
{
    Entity *ent;
    str     buf;
    str     filename;
    size_t  i;

    // get the name of the sound file from the world
    filename = "maps/";
    filename += level.mapname;
    for (i = filename.length() - 1; i >= 0; i--) {
        if (filename[i] == '.') {
            filename[i] = 0;
            break;
        }
    }

    filename += ".snd";

    gi.Printf("Saving soundmanager file to '%s'...\n", filename.c_str());

    buf = "";
    buf += va("//\n");
    buf += va("// Sound Manager File \"%s\", %d Sound Entities.\n", filename.c_str(), soundList.NumObjects());
    buf += va("//\n");

    //
    // save out normal TriggerSpeakers
    //
    buf += va("// TriggerSpeakers\n");
    for (i = 1; i <= soundList.NumObjects(); i++) {
        TriggerSpeaker *speaker;

        ent = soundList.ObjectAt(i);
        if (!ent->isSubclassOf(TriggerSpeaker) || ent->isSubclassOf(RandomSpeaker)) {
            continue;
        }

        speaker = (TriggerSpeaker *)ent;
        //
        // start off the spawn command
        //
        buf += "spawn TriggerSpeaker";
        //
        // set the targetname
        //
        if (ent->targetname != "") {
            buf += va(" targetname %s", ent->targetname.c_str());
        }
        //
        // set the origin
        //
        buf += va(" origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z);
        //
        // make the speaker ambient and on
        //
        buf += " spawnflags 1";
        //
        // set the volume
        //
        if (speaker->volume != DEFAULT_VOL) {
            buf += va(" volume %.2f", speaker->volume);
        }
        //
        // set the mindist
        //
        if (speaker->min_dist != DEFAULT_MIN_DIST) {
            buf += va(" min_dist %.1f", speaker->min_dist);
        }
        //
        // save the sound
        //
        buf += va(" sound \"%s\"", speaker->Noise().c_str());
        //
        // make sure it gets re-added to the sound manager
        //
        buf += " _addtosoundmanager 0";
        buf += va("\n");
    }
    //
    // save out RandomSpeakers
    //
    buf += va("// RandomSpeakers\n");
    for (i = 1; i <= soundList.NumObjects(); i++) {
        RandomSpeaker *speaker;

        ent = soundList.ObjectAt(i);
        if (!ent->isSubclassOf(TriggerSpeaker) || !ent->isSubclassOf(RandomSpeaker)) {
            continue;
        }

        speaker = (RandomSpeaker *)ent;
        //
        // start off the spawn command
        //
        buf += "spawn RandomSpeaker";
        //
        // set the targetname
        //
        if (ent->targetname != "") {
            buf += va(" targetname %s", ent->targetname.c_str());
        }
        //
        // set the origin
        //
        buf += va(" origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z);
        //
        // set the volume
        //
        if (speaker->volume != DEFAULT_VOL) {
            buf += va(" volume %.2f", speaker->volume);
        }
        //
        // set the mindist
        //
        if (speaker->min_dist != DEFAULT_MIN_DIST) {
            buf += va(" min_dist %.1f", speaker->min_dist);
        }
        //
        // set the channel
        //
        buf += va(" channel %d", speaker->channel);
        //
        // set the mindelay
        //
        buf += va(" mindelay %.2f", speaker->mindelay);
        //
        // set the maxdelay
        //
        buf += va(" maxdelay %.2f", speaker->maxdelay);
        //
        // set the chance
        //
        buf += va(" chance %.2f", speaker->chance);
        //
        // save the sound
        //
        buf += va(" sound \"%s\"", speaker->Noise().c_str());
        //
        // make sure it gets re-added to the sound manager
        //
        buf += " _addtosoundmanager 0";
        buf += va("\n");
    }
    //
    // save out TriggerMusic
    //
    buf += va("// TriggerMusics\n");
    for (i = 1; i <= soundList.NumObjects(); i++) {
        TriggerMusic *music;

        ent = soundList.ObjectAt(i);
        if (!ent->isSubclassOf(TriggerMusic)) {
            continue;
        }

        music = (TriggerMusic *)ent;
        //
        // start off the spawn command
        //
        buf += "spawn TriggerMusic";
        //
        // set the targetname
        //
        if (ent->targetname != "") {
            buf += va(" targetname %s", ent->targetname.c_str());
        }
        //
        // set the origin
        //
        buf += va(" origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z);
        //
        // set the angle
        //
        if (music->useTriggerDir) {
            buf += va(" angle %.1f", AngleMod(ent->angles.y));
        }
        //
        // set the oneshot
        //
        if (music->oneshot) {
            buf += " oneshot 0";
        }
        //
        // set current
        //
        buf += va(" current %s", Director.GetString(music->current).c_str());

        //
        // set fallback
        //
        buf += va(" fallback %s", Director.GetString(music->fallback).c_str());

        if (music->GetMultiFaceted()) {
            //
            // save out multi faceted
            //
            buf += va(" multifaceted %d", music->GetMultiFaceted());

            //
            // set alt current
            //
            buf += va(" altcurrent %s", Director.GetString(music->altcurrent).c_str());

            //
            // set alt fallback
            //
            buf += va(" altfallback %s", Director.GetString(music->altfallback).c_str());
        }

        //
        // set mins
        //
        buf += va(" _setmins \"%.2f %.2f %.2f\"", ent->mins.x, ent->mins.y, ent->mins.z);

        //
        // set maxs
        //
        buf += va(" _setmaxs \"%.2f %.2f %.2f\"", ent->maxs.x, ent->maxs.y, ent->maxs.z);
        //
        // make sure it gets re-added to the sound manager
        //
        buf += " _addtosoundmanager 0";
        buf += va("\n");
    }

    //
    // save out TriggerReverb
    //
    buf += va("// TriggerReverbs\n");
    for (i = 1; i <= soundList.NumObjects(); i++) {
        TriggerReverb *reverb;

        ent = soundList.ObjectAt(i);
        if (!ent->isSubclassOf(TriggerReverb)) {
            continue;
        }

        reverb = (TriggerReverb *)ent;
        //
        // start off the spawn command
        //
        buf += "spawn TriggerReverb";
        //
        // set the targetname
        //
        if (ent->targetname != "") {
            buf += va(" targetname %s", ent->targetname.c_str());
        }
        //
        // set the origin
        //
        buf += va(" origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z);
        //
        // set the angle
        //
        if (reverb->useTriggerDir) {
            buf += va(" angle %.1f", AngleMod(ent->angles.y));
        }
        //
        // set the oneshot
        //
        if (reverb->oneshot) {
            buf += " oneshot 0";
        }
        //
        // set reverb type
        //
        buf += va(" reverbtype %d", reverb->reverbtype);

        //
        // set reverb level
        //
        buf += va(" reverblevel %.2f", reverb->reverblevel);

        if (reverb->GetMultiFaceted()) {
            //
            // save out multi faceted
            //
            buf += va(" multifaceted %d", reverb->GetMultiFaceted());

            //
            // set alt reverb type
            //
            buf += va(" altreverbtype %d", reverb->altreverbtype);

            //
            // set alt reverb level
            //
            buf += va(" altreverblevel %.2f", reverb->altreverblevel);
        }

        //
        // set mins
        //
        buf += va(" _setmins \"%.2f %.2f %.2f\"", ent->mins.x, ent->mins.y, ent->mins.z);

        //
        // set maxs
        //
        buf += va(" _setmaxs \"%.2f %.2f %.2f\"", ent->maxs.x, ent->maxs.y, ent->maxs.z);
        //
        // make sure it gets re-added to the sound manager
        //
        buf += " _addtosoundmanager 0";
        buf += va("\n");
    }

    buf += "end\n";

    gi.FS_WriteFile(filename.c_str(), (void *)buf.c_str(), buf.length() + 1);
    gi.Printf("done.\n");
}

void SoundManager::Save(Event *ev)
{
    Save();
}

void SoundManager::Load(void)
{
    str      filename;
    intptr_t i;

    // get the name of the sound file from the world
    filename = "maps/";
    filename += level.mapname;
    for (i = filename.length() - 1; i >= 0; i--) {
        if (filename[i] == '.') {
            filename[i] = 0;
            break;
        }
    }

    filename += ".snd";

    // If there isn't a script with the same name as the map, then don't try to load script
    if (gi.FS_ReadFile(filename.c_str(), NULL, true) != -1) {
        Reset();

        Director.ExecuteThread(filename, "");
    }
}

void SoundManager::AddEntity(Entity *ent)
{
    if (ent) {
        soundList.AddUniqueObject(ent);
    }
}
