/******************************************************************
  
  Module:  surfacetype.cpp
  
  Author: Borut Pfeifer
  
  Description: support for different surface types that the player
               can hit in game
  
  Copyright 2006 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

#include "surfacetype.h"
#include "SyWaterTile.h"
#include "gameobj.h"
#include "titan.h"
#include "SyScene.h"
#include "physics.h"
#include "stats.h"
#include "registry.h"
#include "debugoverlay.h"
#include "graphic.h"
#include "gameerror.h"
#include "database.h"
#include "soundmanager.h"

static SyResourceID s_characterSoundSurfaceIDs[][cImpactSoundSet::MAX_PERMUTATIONS] =
{
  { SyHashResourceID("CharacterFlesh1"), SyHashResourceID("CharacterFlesh2"), SyHashResourceID("CharacterFlesh3") }, 
  { SyHashResourceID("CharacterLeather1"), SyHashResourceID("CharacterLeather2"), SyHashResourceID("CharacterLeather3") },
  { SyHashResourceID("CharacterMetal1"), SyHashResourceID("CharacterMetal2"), SyHashResourceID("CharacterMetal3") },
  { SyHashResourceID("CharacterStone1"), SyHashResourceID("CharacterStone2"), SyHashResourceID("CharacterStone3") },
  { SyHashResourceID("CharacterMagic1"), SyHashResourceID("CharacterMagic2"), SyHashResourceID("CharacterMagic3") },
};

static SyResourceID s_characterBlockSoundSurfaceIDs[][cImpactSoundSet::MAX_PERMUTATIONS] =
{
  { SyHashResourceID("CharacterFleshBlock1"), SyHashResourceID("CharacterFleshBlock2"), SyHashResourceID("CharacterFleshBlock3") }, 
  { SyHashResourceID("CharacterLeatherBlock1"), SyHashResourceID("CharacterLeatherBlock2"), SyHashResourceID("CharacterLeatherBlock3") },
  { SyHashResourceID("CharacterMetalBlock1"), SyHashResourceID("CharacterMetalBlock2"), SyHashResourceID("CharacterMetalBlock3") },
  { SyHashResourceID("CharacterStoneBlock1"), SyHashResourceID("CharacterStoneBlock2"), SyHashResourceID("CharacterStoneBlock3") },
  { SyHashResourceID("CharacterMagicBlock1"), SyHashResourceID("CharacterMagicBlock2"), SyHashResourceID("CharacterMagicBlock3") },
};

static SyResourceID s_worldSoundSurfaceIDs[][cImpactSoundSet::MAX_PERMUTATIONS] =
{
  { SyHashResourceID("SurfaceDefault1"), SyHashResourceID("SurfaceDefault2"), SyHashResourceID("SurfaceDefault3") }, 
  { SyHashResourceID("SurfaceEarth1"), SyHashResourceID("SurfaceEarth2"), SyHashResourceID("SurfaceEarth3") }, 
  { SyHashResourceID("SurfaceWood1"), SyHashResourceID("SurfaceWood2"), SyHashResourceID("SurfaceWood3") }, 
  { SyHashResourceID("SurfaceRock1"), SyHashResourceID("SurfaceRock2"), SyHashResourceID("SurfaceRock3") }, 
  { SyHashResourceID("SurfaceMetal1"), SyHashResourceID("SurfaceMetal2"), SyHashResourceID("SurfaceMetal3") }, 
  { SyHashResourceID("SurfaceSand1"), SyHashResourceID("SurfaceSand2"), SyHashResourceID("SurfaceSand3") }, 
  { SyHashResourceID("SurfaceSnow1"), SyHashResourceID("SurfaceSnow2"), SyHashResourceID("SurfaceSnow3") }, 
  { SyHashResourceID("SurfaceIce1"), SyHashResourceID("SurfaceIce2"), SyHashResourceID("SurfaceIce3") }, 
  { SyHashResourceID("SurfaceDungeonWater1"), SyHashResourceID("SurfaceDungeonWater2"), SyHashResourceID("SurfaceDungeonWater3") }, 
  { SyHashResourceID("SurfaceSewerWater1"), SyHashResourceID("SurfaceSewerWater2"), SyHashResourceID("SurfaceSewerWater3") }, 
  { SyHashResourceID("SurfacePondWater1"), SyHashResourceID("SurfacePondWater2"), SyHashResourceID("SurfacePondWater3") }, 
  { SyHashResourceID("SurfaceMud1"), SyHashResourceID("SurfaceMud2"), SyHashResourceID("SurfaceMud3") }, 
  { SyHashResourceID("SurfaceLava1"), SyHashResourceID("SurfaceLava2"), SyHashResourceID("SurfaceLava3") }, 
};

cImpactFXSet::cImpactFXSet()
: mPropFXID(ID_NONE),
  mDebrisFXID(ID_NONE)
{
  for(int i=0; i<GAME_SURFACE_MAX_TYPES; ++i)
  {
    mSurfaceFXIDs[i]=ID_NONE;
  }
}

tGameID cImpactFXSet::GetEffectForCollisionSurfaceType(SyCollSurfType collision) const
{
  GAME_ASSERT(ERROR_CODE, SYCOLLSURFTYPE_NONE == collision ||
                          SYCOLLSURFTYPE_EARTH == collision ||
                          SYCOLLSURFTYPE_WOOD == collision ||
                          SYCOLLSURFTYPE_ROCK == collision ||
                          SYCOLLSURFTYPE_METAL == collision ||
                          SYCOLLSURFTYPE_SAND == collision ||
                          SYCOLLSURFTYPE_SNOW == collision ||
                          SYCOLLSURFTYPE_ICE == collision, "Invalid surface type");

  if (SYCOLLSURFTYPE_NONE == collision ||
      SYCOLLSURFTYPE_EARTH == collision ||
      SYCOLLSURFTYPE_WOOD == collision ||
      SYCOLLSURFTYPE_ROCK == collision ||
      SYCOLLSURFTYPE_METAL == collision ||
      SYCOLLSURFTYPE_SAND == collision ||
      SYCOLLSURFTYPE_SNOW == collision ||
      SYCOLLSURFTYPE_ICE == collision)
  {
    return mSurfaceFXIDs[collision];
  }

  return ID_NONE;
}

tGameID cImpactFXSet::GetEffectForWaterType(int water) const
{
  if (SyWaterTile::FluidProfileUsesDungeonWaterEffect(water))
  {
    return mSurfaceFXIDs[GAME_SURFACE_DUNGEON_WATER];
  }
  else if (SyWaterTile::FluidProfileUsesSewerWaterEffect(water))
  {
    return mSurfaceFXIDs[GAME_SURFACE_SEWER_WATER];
  }
  else if (SyWaterTile::FluidProfileUsesPondWaterEffect(water))
  {
    return mSurfaceFXIDs[GAME_SURFACE_POND_WATER];
  }
  else if (SyWaterTile::FluidProfileUsesMudEffect(water))
  {
    return mSurfaceFXIDs[GAME_SURFACE_MUD];
  }
  else if (SyWaterTile::FluidProfileUsesSnowEffect(water))
  {
    return mSurfaceFXIDs[GAME_SURFACE_SNOW];
  }
  else if (SyWaterTile::FluidProfileUsesLavaEffect(water))
  {
    return mSurfaceFXIDs[GAME_SURFACE_LAVA];
  }
  else
  {
    return ID_NONE;
  }
}

int32 cImpactFXSet::GetCharacterFXHandle(const char* effectName, cGameObject* pObj)
{
  GAME_ASSERT(ERROR_CODE, pObj->GetType() == cGameObject::OBJ_NPC || pObj->GetType() == cGameObject::OBJ_PLAYER, "Bad object type in GetCharacterEffectID");
  
  if (pObj->GetType() != cGameObject::OBJ_NPC && pObj->GetType() != cGameObject::OBJ_PLAYER)
  {
    return -1;
  }

  if (effectName == NULL)
  {
    return -1;
  }

  int32 scriptHandle = -1;
  bool bFoundFX = false;
  int effectNameLen = strlen(effectName);
  SyScene* pScene = pObj->GetTitan()->GetScene();

  if (effectNameLen > 0)
  {
    if (SyStr::Strnicmp(effectName, "fx_", 3) == 0)
    {
      bFoundFX = pScene->GetDictionary()->FindTyped(SyHashResourceID(effectName), SYRESOURCETYPE_FXSCRIPT, scriptHandle) != 0;
    }
    else
    {
      // use naming convention to find effect for this type of character
      const char* modelName = pObj->GetStats()->GetFileName();

      if (modelName && modelName[0] != 0)
      {
        int modelLen = (int)strlen(modelName);
        int modelPrefixStart = modelLen;
        int modelPrefixEnd = 0;

        while (modelPrefixStart > 0)
        {
          if (modelName[modelPrefixStart-1] == '\\' || modelName[modelPrefixStart-1] == '/')
          {
            break;
          }

          --modelPrefixStart;
        }

        modelPrefixEnd = modelPrefixStart;
        while (modelPrefixEnd < modelLen)
        {
          if (modelName[modelPrefixEnd] == '_' || modelName[modelPrefixEnd] == '.')
          {
            break;
          }

          ++modelPrefixEnd;
        }

        // protect possible buffer overrun
        if (11 + SY_MAX(6, (modelPrefixEnd-modelPrefixStart+1)) + effectNameLen + 1 < 128)
        {
          char fxName[128];
          strcpy(fxName, "fx_surface_");
          strncat(fxName, modelName+modelPrefixStart, modelPrefixEnd-modelPrefixStart);
          strcat(fxName, "_");
          strcat(fxName, effectName);
          bFoundFX = pScene->GetDictionary()->FindTyped(SyHashResourceID(fxName), SYRESOURCETYPE_FXSCRIPT, scriptHandle) != 0;

          if (!bFoundFX)
          {
            if (pObj->GetType() == cGameObject::OBJ_NPC)
            {
              strcpy(fxName, "fx_surface_npc_");
              strcat(fxName, effectName);
              bFoundFX = pScene->GetDictionary()->FindTyped(SyHashResourceID(fxName), SYRESOURCETYPE_FXSCRIPT, scriptHandle) != 0;
            }
            else
            {
              strcpy(fxName, "fx_surface_player_");
              strcat(fxName, effectName);
              bFoundFX = pScene->GetDictionary()->FindTyped(SyHashResourceID(fxName), SYRESOURCETYPE_FXSCRIPT, scriptHandle) != 0;
            }

            if (!bFoundFX)
            {
              strcpy(fxName, "fx_surface_character_");
              strcat(fxName, effectName);
              bFoundFX = pScene->GetDictionary()->FindTyped(SyHashResourceID(fxName), SYRESOURCETYPE_FXSCRIPT, scriptHandle) != 0;
            }
          }
        }
        else
        {
          GAME_ASSERT(ERROR_CODE, false, "effect name buffer overrun: %s", effectName);
        }
      }
    }
  }

  GAME_ASSERT(ERROR_ART, bFoundFX, "Could not find character impact effect '%s'", effectName);
  return scriptHandle;
}

void cImpactFXSet::PlayImpact(cGameObject* pObj, bool bAttach) const
{
  if (!pObj || pObj->GetStats()->IsDead())
  {
    return;
  }

  Titan* pTitan = pObj->GetTitan();

  bool bFoundFX = false;
  int32 scriptHandle = -1;

  if (pObj->GetType() == cGameObject::OBJ_NPC || pObj->GetType() == cGameObject::OBJ_PLAYER)
  {
    cGraphicCharacter* pGraphic = static_cast<cGraphicCharacter*>(pObj->GetGraphic());
    SyString effectName;

    if (pGraphic && pGraphic->GetAnimController()->IsBlocking())
    {
      effectName = mCharacterBlockFXName;
    }
    else 
    {
      effectName = mCharacterFXName;
    }

    if (effectName.Length() > 0 && pGraphic && !pGraphic->GetAnimController()->IsGettingUp())
    {
      scriptHandle = GetCharacterFXHandle(effectName.AsChar(), pObj);
      bFoundFX = -1 != scriptHandle;
      GAME_ASSERT(ERROR_ART, bFoundFX, "Could not find character effect for impact effect set '%s'", mID.GetName());
    }
  }
  else if (ID_NONE != mPropFXID && pObj->GetType() == cGameObject::OBJ_PROP)
  {
    bFoundFX = pTitan->GetScene()->GetDictionary()->FindTyped(mPropFXID, SYRESOURCETYPE_FXSCRIPT, scriptHandle) != 0;
    GAME_ASSERT(ERROR_ART, bFoundFX, "Could not find prop effect for impact effect set '%s'", mID.GetName());
  }

  if (bFoundFX)
  {
    SyActorHandle handle = pObj->GetGraphic()->GetActorHandle();
    if (bAttach && SyActorNull != handle)
    {
      pTitan->GetScene()->GetFXScriptSystem()->PlayScript(scriptHandle, 1, &handle);
    }
    else
    {
      cGraphicActor* pGraphic = prop_cast<cGraphicCharacter*>(pObj->GetGraphic());
      SyVect3 chestNode;
      if (pGraphic && pGraphic->GetIdentNodeLocation(CHAR_NODE_CHEST, &chestNode))
      {
        pTitan->GetScene()->GetFXScriptSystem()->PlayScriptAtLocation(scriptHandle, chestNode, pObj->GetHPR(), 1.0f);
      }
      else
      {
        pTitan->GetScene()->GetFXScriptSystem()->PlayScriptAtLocation(scriptHandle, pObj->GetLocation(), pObj->GetHPR(), 1.0f);
      }
    }
  }
}

void cImpactFXSet::PlayImpact(Titan* pTitan, SyCollRay& ray, SySceneFilter& filter, int waterType) const
{
  if (filter.GetActor() != SyActorNull)
  {
    cGameObject* pHit = pTitan->GetRegistry()->FetchByActorHandle(filter.GetActor());
    if (pHit)
    {
      PlayImpact(pHit, true);
      return;
    }
  }

  tGameID effectID = GetEffectForWaterType(waterType);

  if (effectID == ID_NONE)
  {
    effectID = GetEffectForCollisionSurfaceType(ray.GetHitSurfaceType());
  }

  if (effectID != ID_NONE)
  {
    int32 scriptHandle;
    SyVect3 normalHPR, upHPR, up(0.0f, 1.0f, 0.0f);
    up.HPR(&upHPR.X, &upHPR.Y, &upHPR.Z);
    ray.GetHitNormal().HPR(&normalHPR.X, &normalHPR.Y, &normalHPR.Z);

    if(pTitan->GetScene()->GetDictionary()->FindTyped(effectID, SYRESOURCETYPE_FXSCRIPT, scriptHandle))
    {
      pTitan->GetScene()->GetFXScriptSystem()->PlayScriptAtLocation(scriptHandle, ray.GetHitPoint(), normalHPR-upHPR, 1.0f);
    }
    else
    {
      GAME_ASSERT(ERROR_ART, false, "Could not find effect for impact effect set '%s'", mID.GetName());
    }
  }
}

cImpactSoundSet::cImpactSoundSet()
{
}

cImpactSoundSet::~cImpactSoundSet()
{
  mSounds.Clear();
}

void cImpactSoundSet::PlayHitSound(cGameObject* pObj) const
{
  if (!pObj)
  {
    return;
  }

  const cStatsModelData* pModelData = NULL;
  bool bBlocking = false;

  if (pObj->GetType() == cGameObject::OBJ_NPC || pObj->GetType() == cGameObject::OBJ_PLAYER)
  {
    cStatsCharacter* pCharStats = static_cast<cStatsCharacter*>(pObj->GetStats());
    pModelData = pObj->GetTitan()->GetDatabaseSys()->GetModelData(pCharStats->GetMaster()->mModelDataID);

    if (static_cast<cGraphicCharacter*>(pObj->GetGraphic())->GetAnimController()->IsBlocking())
    {
      bBlocking = true;
    }
  }
//  else if (pObj->GetType() == cGameObject::OBJ_PROP)
//  {
//    cStatsProp* pPropStats = static_cast<cStatsProp*>(pObj->GetStats());
//    pModelData = pObj->GetTitan()->GetDatabaseSys()->GetModelData(pPropStats->GetMaster()->mModelDataID);
//  }

  SyAssertf((sizeof(s_characterSoundSurfaceIDs) / sizeof (s_characterSoundSurfaceIDs[0]))==CHARACTER_SOUND_SURFACE_MAX_TYPES, "Missing character sound surface type name");
  SyAssertf((sizeof(s_characterBlockSoundSurfaceIDs) / sizeof (s_characterBlockSoundSurfaceIDs[0]))==CHARACTER_SOUND_SURFACE_MAX_TYPES, "Missing character sound surface type name");

  if (pModelData)
  {
    SyScene* pScene = pObj->GetTitan()->GetScene();
    int foundID;
    int numPerms = 0;

    int32 soundHandles[MAX_PERMUTATIONS]; 

    for (int iPerm=0; iPerm<MAX_PERMUTATIONS; ++iPerm)
    {
      soundHandles[iPerm] = -1;

      if (bBlocking)
      {
        foundID = mSounds.Find(s_characterBlockSoundSurfaceIDs[(int)pModelData->mSoundSurfaceType][iPerm]);
      }
      else
      {
        foundID = mSounds.Find(s_characterSoundSurfaceIDs[(int)pModelData->mSoundSurfaceType][iPerm]);
      }

      if (foundID != mSounds.End())
      {
        if (pScene->GetDictionary()->FindTyped(mSounds(foundID), SYRESOURCETYPE_SOUNDSPRITE, soundHandles[iPerm]))
        {
          ++numPerms;
        }
        else
        {
//          GAME_ASSERT(ERROR_ART, false, "Could not find character hit sound in impact sound set '%s'", mID.GetName());
        }
      }
    }

    if (numPerms > 0)
    {
      int randPerm = 0;

      if (numPerms > 1)
      {
        randPerm = pObj->GetTitan()->Random(0, numPerms-1);
        numPerms = 0;
        for (int iPerm=0; iPerm<MAX_PERMUTATIONS; ++iPerm)
        {
          if (numPerms >= randPerm)
          {
            randPerm = iPerm;
            break;
          }

          if (soundHandles[iPerm] != -1)
          {
            ++numPerms;
          }
        }
      }

      pObj->GetTitan()->GetSoundMgr()->PlayImpact(soundHandles[randPerm], pObj->GetLocation());
    }
  }
}

void cImpactSoundSet::PlayHitSound(Titan* pTitan, SyCollRay& ray, SySceneFilter& filter, int water) const
{
  if (filter.GetActor() != SyActorNull)
  {
    cGameObject* pHit = pTitan->GetRegistry()->FetchByActorHandle(filter.GetActor());
    if (pHit)
    {
      PlayHitSound(pHit);
      return;
    }
  }

  SyAssertf((sizeof(s_worldSoundSurfaceIDs) / sizeof (s_worldSoundSurfaceIDs[0]))==GAME_SURFACE_MAX_TYPES, "Missing world sound surface type name");

  GameSurfaceType surfType = GAME_SURFACE_DEFAULT;

  if (SyWaterTile::FluidProfileUsesDungeonWaterEffect(water))
  {
    surfType = GAME_SURFACE_DUNGEON_WATER;
  }
  else if (SyWaterTile::FluidProfileUsesSewerWaterEffect(water))
  {
    surfType = GAME_SURFACE_SEWER_WATER;
  }
  else if (SyWaterTile::FluidProfileUsesPondWaterEffect(water))
  {
    surfType = GAME_SURFACE_POND_WATER;
  }
  else if (SyWaterTile::FluidProfileUsesMudEffect(water))
  {
    surfType = GAME_SURFACE_MUD;
  }
  else if (SyWaterTile::FluidProfileUsesSnowEffect(water))
  {
    surfType = GAME_SURFACE_SNOW;
  }
  else if (SyWaterTile::FluidProfileUsesLavaEffect(water))
  {
    surfType = GAME_SURFACE_LAVA;
  }
  else
  {
    SyCollSurfType collType = ray.GetHitSurfaceType();
    GAME_ASSERT(ERROR_CODE, SYCOLLSURFTYPE_NONE == collType ||
                            SYCOLLSURFTYPE_EARTH == collType ||
                            SYCOLLSURFTYPE_WOOD == collType ||
                            SYCOLLSURFTYPE_ROCK == collType ||
                            SYCOLLSURFTYPE_METAL == collType ||
                            SYCOLLSURFTYPE_SAND == collType ||
                            SYCOLLSURFTYPE_SNOW == collType ||
                            SYCOLLSURFTYPE_ICE == collType, "Invalid surface type");

    if (SYCOLLSURFTYPE_NONE == collType ||
        SYCOLLSURFTYPE_EARTH == collType ||
        SYCOLLSURFTYPE_WOOD == collType ||
        SYCOLLSURFTYPE_ROCK == collType ||
        SYCOLLSURFTYPE_METAL == collType ||
        SYCOLLSURFTYPE_SAND == collType ||
        SYCOLLSURFTYPE_SNOW == collType ||
        SYCOLLSURFTYPE_ICE == collType)
    {
      surfType = (GameSurfaceType)collType;
    }
  }

  SyScene* pScene = pTitan->GetScene();
  int foundID;
  int numPerms = 0;

  int32 soundHandles[MAX_PERMUTATIONS]; 

  for (int iPerm=0; iPerm<MAX_PERMUTATIONS; ++iPerm)
  {
    soundHandles[iPerm] = -1;
    foundID = mSounds.Find(s_worldSoundSurfaceIDs[surfType][iPerm]);
    if (foundID != mSounds.End())
    {
      if (pScene->GetDictionary()->FindTyped(mSounds(foundID), SYRESOURCETYPE_SOUNDSPRITE, soundHandles[iPerm]))
      {
        ++numPerms;
      }
      else
      {
//        GAME_ASSERT(ERROR_ART, false, "Could not find world surface hit sound in impact sound set '%s'", mID.GetName());
      }
    }
  }

  if (numPerms > 0)
  {
    int randPerm = 0;

    if (numPerms > 1)
    {
      randPerm = pTitan->Random(0, numPerms-1);
      numPerms = 0;
      for (int iPerm=0; iPerm<MAX_PERMUTATIONS; ++iPerm)
      {
        if (numPerms >= randPerm)
        {
          randPerm = iPerm;
          break;
        }

        if (soundHandles[iPerm] != -1)
        {
          ++numPerms;
        }
      }
    }

    pTitan->GetSoundMgr()->PlayImpact(soundHandles[randPerm], ray.GetHitPoint());
  }
}

