www.pudn.com > map_editor.rar > eye_candy_window.cpp


/*
TODO: 
 * Get Roja working.
 * New effect options.
 * New effects.
*/

#ifdef EYE_CANDY

extern "C"
{
#include "global.h"
#include "interface.h"
#include "elwindows.h"
#include "shadows.h"
#include "gui.h"
#include "editor.h"
}

#include "../elc/eye_candy/eye_candy.h"
#include 
#include "eye_candy_window.h"

extern "C"
{
int view_eye_candy_window=0;
int last_ec_index = -2;	// None selected.
int eye_candy_window = -1;
int eye_candy_confirmed = 0;
int eye_candy_initialized = 0;
int eye_candy_ready_to_add = 0;
}

static int eye_candy_window_x=15;
static int eye_candy_window_y=50;
static int eye_candy_window_x_len=600;
static int eye_candy_window_y_len=470;
std::vector effects;
EffectDefinition current_effect;

extern "C" void create_eye_candy_window ()
{
  if (eye_candy_window < 0)
  {
    eye_candy_window = create_window ("eye_candy", 0, 0, eye_candy_window_x, eye_candy_window_y, eye_candy_window_x_len, eye_candy_window_y_len, ELW_WIN_DEFAULT & ~ELW_SHOW);

    set_window_handler (eye_candy_window, ELW_HANDLER_DISPLAY, &display_eye_candy_window_handler);
    set_window_handler (eye_candy_window, ELW_HANDLER_CLICK, &check_eye_candy_window_interface);
  }
}

extern "C" void change_eye_candy_effect()
{
  if (!eye_candy_initialized)
    return;
  current_effect.effect = gtk_combo_box_get_active(GTK_COMBO_BOX(gtk_effect_list));
  current_effect.hue = GTK_ADJUSTMENT(gtk_effect_hue_obj)->value;
  current_effect.saturation = GTK_ADJUSTMENT(gtk_effect_saturation_obj)->value;
  current_effect.scale = GTK_ADJUSTMENT(gtk_effect_scale_obj)->value;
  current_effect.density = GTK_ADJUSTMENT(gtk_effect_density_obj)->value;
  current_effect.base_height = atoi(gtk_entry_get_text(GTK_ENTRY(gtk_effect_base_height)));
  if (current_effect.reference)
  {
    ec_recall_effect(current_effect.reference);
    current_effect.reference = NULL;
  }
  switch (current_effect.effect)
  {
    case 0:    // Fire
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_campfire(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, 10, current_effect.scale);
      break;
    case 1:    // Cloud/Fog
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_hide(gtk_effect_scale_box);
      gtk_widget_show(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_cloud(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.density, (current_effect.bounds.elements.size() > 1 ? ¤t_effect.bounds : &initial_bounds), 10);
      break;
    case 2:    // Fireflies
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_show(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_fireflies(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.density, current_effect.scale, (current_effect.bounds.elements.size() > 1 ? ¤t_effect.bounds : &initial_bounds));
      break;
    case 3:    // Fountain
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_show(gtk_effect_base_height_box);
      current_effect.reference = ec_create_fountain(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.base_height, false, current_effect.scale, 10);
      break;
    case 4:    // Lamp/Torch
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_lamp(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.scale, 10);
      break;
    case 5:    // Magic Protection
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_ongoing_magic_protection(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, 10, current_effect.scale);
      break;
    case 6:    // Shield
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_ongoing_shield(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, 10, current_effect.scale);
      break;
    case 7:    // Magic Immunity
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_ongoing_magic_immunity(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, 10, current_effect.scale);
      break;
    case 8:    // Poison
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_ongoing_poison(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, 10, current_effect.scale);
      break;
    case 9:    // Smoke
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_hide(gtk_effect_scale_box);
      gtk_widget_show(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_smoke(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.scale, 10);
      break;
    case 10:  // Teleporter
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_teleporter(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.scale, 10);
      break;
    case 11:  // Leaves
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_show(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
//      std::cout << "1: " << current_effect.hue << " / " << current_effect.saturation << " / " << current_effect.scale << " / " << current_effect.density << std::endl;
      current_effect.reference = ec_create_wind_leaves(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.scale, current_effect.density, (current_effect.bounds.elements.size() > 1 ? ¤t_effect.bounds : &initial_bounds), 1.0, 0.0, 0.0);
      break;
    case 12:  // Flower Petals
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_show(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_wind_petals(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.scale, current_effect.density, (current_effect.bounds.elements.size() > 1 ? ¤t_effect.bounds : &initial_bounds), 1.0, 0.0, 0.0);
      break;
    case 13:  // Waterfall
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_show(gtk_effect_base_height_box);
//      current_effect.reference = ec_create_waterfall(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation);
      break;
    case 14:  // Bees
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_show(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = NULL;
//      current_effect.reference = ec_create_bees(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation);
      break;
    case 15:  // Portal
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = NULL;
//      current_effect.reference = ec_create_bees(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation);
      break;
    case 16:    // Candle
      gtk_widget_show(gtk_effect_hue_box);
      gtk_widget_show(gtk_effect_saturation_box);
      gtk_widget_show(gtk_effect_scale_box);
      gtk_widget_hide(gtk_effect_density_box);
      gtk_widget_hide(gtk_effect_base_height_box);
      current_effect.reference = ec_create_candle(current_effect.position.x, current_effect.position.y, current_effect.position.z, current_effect.hue, current_effect.saturation, current_effect.scale, 10);
      break;
  }
}

extern "C" void remove_current_eye_candy_effect()
{
  if (current_effect.reference)
  {
    ec_recall_effect(current_effect.reference);
    current_effect.reference = NULL;
  }
}

void confirm_eye_candy_effect()
{
  eye_candy_confirmed = 1;
  change_eye_candy_effect();
  gtk_widget_hide(gtk_effect_win);
  switch (current_effect.effect)
  {
    case 1:    // Cloud/Fog
    case 2:    // Fireflies
    case 11:   // Leaves
    case 12:   // Flower Petals
    {
      current_effect.position = ec::Vec3(-1.0, -1.0, 0.0);
      minimap_on = 1;
      break;
    }
  }
  eye_candy_ready_to_add = 1;
}

extern "C" int display_eye_candy_window_handler()
{
  return 1;
}

extern "C" int check_eye_candy_window_interface()
{
  return 1;
}

extern "C" void update_eye_candy_position(float x, float y)
{
  if (!minimap_on)
  {
    switch (current_effect.effect)
    {
      case 1:    // Cloud/Fog
      case 2:    // Fireflies
      case 11:   // Leaves
      case 12:   // Flower Petals
      {
        if (current_effect.bounds.elements.size() > 1)
          return;
      }
    }
    current_effect.position.x = x;
    current_effect.position.y = y;
    if (current_effect.reference)
      ec_set_position(current_effect.reference, x, y, current_effect.position.z);
  }
}

extern "C" void add_eye_candy_point()
{
  int minimap_x_start=window_width/2-128;
  int minimap_y_start;
  float x_map_pos;
  float y_map_pos;
  int scale;
  
  if(window_widthminimap_x_start+256*scale || mouse_y>minimap_y_start+256*scale) return;

  x_map_pos=((float)(mouse_x-minimap_x_start)/(float)scale);
  y_map_pos=256-((mouse_y-minimap_y_start)/(float)scale);
  const float z = -2.2f + tile_map[(int)(y_map_pos)*tile_map_size_x+(int)x_map_pos] * 0.2f;
  x_map_pos = x_map_pos * 3 / (256 / tile_map_size_x);
  y_map_pos = y_map_pos * 3 / (256 / tile_map_size_y);
  
  if (left_click <= 1)
  {
    const bool ret = find_bounds_index(x_map_pos, y_map_pos);
    if (!ret)  // Didn't click on anything; create new.
    {
      if ((current_effect.bounds.elements.size() == 0) && (current_effect.position == ec::Vec3(-1.0, -1.0, 0.0)))
        current_effect.position = ec::Vec3(x_map_pos, y_map_pos, z);
      else if (current_effect.bounds.elements.size() < 13)
        current_effect.bounds.elements.insert(current_effect.bounds.elements.begin() + last_ec_index, angle_to(current_effect.position.x, current_effect.position.y, x_map_pos, y_map_pos));
      else
        ; // Can't add any more; too many already.
    }
  }
  else
  {
    if (last_ec_index == -1)  // Clicked on the center; drag it.
      current_effect.position = ec::Vec3(x_map_pos, y_map_pos, z);
    else if (last_ec_index >= 0)      // Clicked on another point; drag it.
    {
      const ec::SmoothPolygonElement new_angle = angle_to(current_effect.position.x, current_effect.position.y, x_map_pos, y_map_pos);
      current_effect.bounds.elements.erase(current_effect.bounds.elements.begin() + last_ec_index);
      int i;
      for (i = 0; i < (int)current_effect.bounds.elements.size(); i++)
      {
        if (new_angle.angle < current_effect.bounds.elements[i].angle)
          break;
      }
      current_effect.bounds.elements.insert(current_effect.bounds.elements.begin() + i, new_angle);
      last_ec_index = i;
    }
  }
  change_eye_candy_effect();
}

extern "C" void delete_eye_candy_point()
{
  int minimap_x_start=window_width/2-128;
  int minimap_y_start;
  float x_map_pos;
  float y_map_pos;
  int scale;

  if(window_widthminimap_x_start+256*scale || mouse_y>minimap_y_start+256*scale) return;

  x_map_pos=((float)(mouse_x-minimap_x_start)/(float)scale);
  y_map_pos=256-(((mouse_y-minimap_y_start))/(float)scale);
  x_map_pos = x_map_pos * 3 / (256 / tile_map_size_x);
  y_map_pos = y_map_pos * 3 / (256 / tile_map_size_y);

  if (find_bounds_index(x_map_pos, y_map_pos))
  {
    if (last_ec_index >= 0)  // Clicked on a bounds point; delete it
    {
      current_effect.bounds.elements.erase(current_effect.bounds.elements.begin() + last_ec_index);
    }
    else if (last_ec_index == -1)  // Clicked on the center; cancel the effect
    {
      current_effect = EffectDefinition();
      eye_candy_done_adding_effect();
      cur_mode = mode_tile;
      minimap_on = 0;
    }
  }
}

extern "C" void eye_candy_add_effect()
{
  if (eye_candy_initialized && eye_candy_ready_to_add)
  {
    effects.push_back(current_effect);
    current_effect.reference = NULL;
    switch (current_effect.effect)
    {
      case 1:    // Cloud/Fog
      case 2:    // Fireflies
      case 11:   // Leaves
      case 12:   // Flower Petals
      {
        current_effect.effect = 0;
        cur_mode = mode_tile;
        return;
      }
      default:
      {
        change_eye_candy_effect();
        current_effect.bounds = ec::SmoothPolygonBoundingRange();
      }
    }
  }
}

extern "C" void eye_candy_done_adding_effect()
{
  if (eye_candy_initialized)
  {
    eye_candy_ready_to_add = 0;
    if (current_effect.reference)
    {
      switch (current_effect.effect)
      {
        case 1:    // Cloud/Fog
        case 2:    // Fireflies
        case 11:   // Leaves
        case 12:   // Flower Petals
        {
          effects.push_back(current_effect);
          break;
        }
        default:
        {
          remove_current_eye_candy_effect();
        }
      }
      current_effect.reference = NULL;
      current_effect.bounds = ec::SmoothPolygonBoundingRange();
      current_effect.position = ec::Vec3(-1.0, -1.0, 0.0);
    }
  }
}

extern "C" int eye_candy_get_effect()
{
  return current_effect.effect;
}

void eye_candy_adjust_z(float offset)
{
  current_effect.position.z += offset;
  change_eye_candy_effect();
}

extern "C" void draw_bounds_on_minimap()
{
  glDisable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  for (std::vector::iterator iter = effects.begin(); iter != effects.end(); iter++)
    draw_bound(*iter, false);
  if (eye_candy_ready_to_add)
    draw_bound(current_effect, true);
  glDisable(GL_BLEND);
  glEnable(GL_TEXTURE_2D);
}

void draw_bound(EffectDefinition& eff, bool selected)
{
  int minimap_x_start=window_width/2-128;
  int minimap_y_end;
  int scale;

  if(window_width= 2)
  {
    std::vector::const_iterator prev_iter = eff.bounds.elements.begin() + (eff.bounds.elements.size() - 1);
    std::vector::const_iterator next_iter = eff.bounds.elements.begin();
    bool wrapped = false;
    for (float f = 0; f < 2 * ec::PI; f += (2 * ec::PI) / 360.0)
    {
      if ((f > next_iter->angle) && (!wrapped))
      {
        prev_iter = next_iter;
        next_iter++;
        if (next_iter == eff.bounds.elements.end())
        {
          wrapped = true;
          next_iter = eff.bounds.elements.begin();
        }
      }
      float percent;
      if (wrapped)
        percent = (f - prev_iter->angle) / (next_iter->angle + (2 * ec::PI) - prev_iter->angle);
      else if (f > prev_iter->angle)
        percent = (f - prev_iter->angle) / (next_iter->angle - prev_iter->angle);
      else
        percent = (f + 2 * ec::PI - prev_iter->angle) / (next_iter->angle + (2 * ec::PI) - prev_iter->angle);
      const float dist = prev_iter->radius * (1.0 - percent) + next_iter->radius * percent;
      const float temp_x = minimap_x_start + ((eff.position.x - dist * sin(f)) * scale) / 3 * (256 / tile_map_size_x);
      const float temp_y = minimap_y_end - ((eff.position.y + dist * cos(f)) * scale) / 3 * (256 / tile_map_size_y);
      glVertex2f(temp_x, temp_y);
    }
  }
  glEnd();

  if (selected)
  {
    glColor4f(1.0, 1.0, 1.0, 0.7);
    glBegin(GL_QUADS);
    for (std::vector::const_iterator iter = eff.bounds.elements.begin(); iter != eff.bounds.elements.end(); iter++)
    {
      const float temp_x = minimap_x_start + ((eff.position.x - iter->radius * sin(iter->angle)) * scale) / 3 * (256 / tile_map_size_x);
      const float temp_y = minimap_y_end - ((eff.position.y + iter->radius * cos(iter->angle)) * scale) / 3 * (256 / tile_map_size_y);
      glVertex2f(temp_x - 2.0 * scale, temp_y - 2.0 * scale);
      glVertex2f(temp_x - 2.0 * scale, temp_y + 2.0 * scale);
      glVertex2f(temp_x + 2.0 * scale, temp_y + 2.0 * scale);
      glVertex2f(temp_x + 2.0 * scale, temp_y - 2.0 * scale);
    }
    
    glColor4f(1.0, 0.8, 0.6, 1.0);
    const float temp_x = minimap_x_start + (eff.position.x * scale) / 3 * (256 / tile_map_size_x);
    const float temp_y = minimap_y_end - (eff.position.y * scale) / 3 * (256 / tile_map_size_y);
    glVertex2f(temp_x - 2.0 * scale, temp_y - 2.0 * scale);
    glVertex2f(temp_x - 2.0 * scale, temp_y + 2.0 * scale);
    glVertex2f(temp_x + 2.0 * scale, temp_y + 2.0 * scale);
    glVertex2f(temp_x + 2.0 * scale, temp_y - 2.0 * scale);
    glEnd();
  }
}

bool find_bounds_index(float x, float y)
{
  if ((current_effect.position.x == -1.0) && (current_effect.position.y == -1.0))
  {
    last_ec_index = -1;
    return false;
  }

  if ((fabs(x - current_effect.position.x) < 9.0) && (fabs(y - current_effect.position.y) < 9.0))
  {
    last_ec_index = -1;
    return true;
  }

  for (int i = 0; i < (int)current_effect.bounds.elements.size(); i++)
  {
    std::vector::const_iterator iter = current_effect.bounds.elements.begin() + i;
    const float temp_x = current_effect.position.x - iter->radius * sin(iter->angle);
    const float temp_y = current_effect.position.y + iter->radius * cos(iter->angle);
    if ((fabs(x - temp_x) < 9.0) && (fabs(y - temp_y) < 9.0))
    {
      last_ec_index = i;
      return true;
    }
  }

  float cur_angle = angle_to(current_effect.position.x, current_effect.position.y, x, y).angle;
  last_ec_index = current_effect.bounds.elements.size();
  for (int i = 0; i < (int)current_effect.bounds.elements.size(); i++)
  {
    const float angle = current_effect.bounds.elements[i].angle;
    if (cur_angle < angle)
    {
      last_ec_index = i;
      break;
    }
  }

  return false;
}

ec::SmoothPolygonElement angle_to(float start_x, float start_y, float end_x, float end_y)
{
  const float diff_x = -(end_x - start_x);
  const float diff_y = end_y - start_y;
  float angle = atan2(diff_x, diff_y);
  if (angle < 0)
    angle += 2.0 * ec::PI;
  const float dist = sqrt(diff_x * diff_x + diff_y * diff_y);
  return ec::SmoothPolygonElement(angle, dist);
}

void draw_eye_candy_obj_info()
{
  unsigned char str[128];
  int x_menu,y_menu;
  if (cur_mode!=mode_eye_candy || !eye_candy_confirmed)
    return;

  x_menu=0;
  y_menu=window_height-72;
  //draw a black rectangle
  glEnable(GL_BLEND);
  glBlendFunc(GL_ONE,GL_SRC_ALPHA);
  glDisable(GL_TEXTURE_2D);
  glBegin(GL_QUADS);
  glColor4f(0.0f,0.0f,0.0f,0.5f);
  glVertex3i(x_menu,y_menu+70,0);
  glVertex3i(x_menu,y_menu,0);
  glVertex3i(x_menu+600,y_menu,0);
  glVertex3i(x_menu+600,y_menu+70,0);
  glColor3f(1.0f,1.0f,1.0f);
  glEnd();
  glEnable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);

  x_menu+=2;
  y_menu+=2;

  sprintf((char *)str, "X Pos: %03.2f",current_effect.position.x);
  draw_string(x_menu,y_menu,str,1);

  y_menu+=17;
  sprintf((char *)str, "Y Pos: %03.2f",current_effect.position.y);
  draw_string(x_menu,y_menu,str,1);

  y_menu+=17;
  sprintf((char *)str, "Z Pos: %03.2f",current_effect.position.z);
  draw_string(x_menu,y_menu,str,1);
/////////////////////////////////////////////////
  x_menu+=15*12;
  y_menu-=17*2;

  sprintf((char *)str, "Angle   : %03.2f",current_effect.angle);
  draw_string(x_menu,y_menu,str,1);

  y_menu+=17;
  sprintf((char *)str, "Density : %03.2f",current_effect.density);
  draw_string(x_menu,y_menu,str,1);

  y_menu+=17;
  sprintf((char *)str, "Scale   : %03.2f",current_effect.scale);
  draw_string(x_menu,y_menu,str,1);
/////////////////////////////////////////////////
  x_menu+=17*12;
  y_menu-=17*2;

  sprintf((char *)str, "Hue       : %03.2f",current_effect.hue);
  draw_string(x_menu,y_menu,str,1);

  y_menu+=17;
  sprintf((char *)str, "Saturation: %03.2f",current_effect.saturation);
  draw_string(x_menu,y_menu,str,1);

  y_menu+=17;
  sprintf((char *)str, "Height    : %03.2f",current_effect.base_height);
  draw_string(x_menu,y_menu,str,1);
}

void draw_eye_candy_selectors()
{
  glEnable(GL_COLOR);
  glEnable(GL_LIGHTING);
  glDisable(GL_TEXTURE_2D);
  glColor4f(0.5, 0.5, 0.5, 1.0);
  int i=0;
  if (eye_candy_ready_to_add)
    draw_eye_candy_selector(¤t_effect, i);
  i++;
  for (std::vector::const_iterator iter = effects.begin(); iter != effects.end(); iter++, i++)
    draw_eye_candy_selector(&(*iter), i);
  glDisable(GL_COLOR);
  glDisable(GL_LIGHTING);
  glEnable(GL_TEXTURE_2D);
}
    
void draw_eye_candy_selector(const EffectDefinition*const effect, const int i)
{
  glPushMatrix();
  glTranslatef(effect->position.x, effect->position.y, effect->position.z);
  glLoadName (MAX_OBJ_3D + i);
  glBegin(GL_QUADS);
  {
    // Front Face
    glNormal3f(0.0, 0.0, 1.0);
    glVertex3f(-0.15f, -0.15f,  0.15f);
    glVertex3f( 0.15f, -0.15f,  0.15f);
    glVertex3f( 0.15f,  0.15f,  0.15f);
    glVertex3f(-0.15f,  0.15f,  0.15f);
    // Back Face
    glNormal3f(0.0, 0.0, -1.0);
    glVertex3f(-0.15f, -0.15f, -0.15f);
    glVertex3f(-0.15f,  0.15f, -0.15f);
    glVertex3f( 0.15f,  0.15f, -0.15f);
    glVertex3f( 0.15f, -0.15f, -0.15f);
    // Top Face
    glNormal3f(0.0, 1.0, 0.0);
    glVertex3f(-0.15f,  0.15f, -0.15f);
    glVertex3f(-0.15f,  0.15f,  0.15f);
    glVertex3f( 0.15f,  0.15f,  0.15f);
    glVertex3f( 0.15f,  0.15f, -0.15f);
    // Bottom Face
    glNormal3f(0.0, -1.0, 0.0);
    glVertex3f(-0.15f, -0.15f, -0.15f);
    glVertex3f( 0.15f, -0.15f, -0.15f);
    glVertex3f( 0.15f, -0.15f,  0.15f);
    glVertex3f(-0.15f, -0.15f,  0.15f);
    // Right face
    glNormal3f(1.0, 0.0, 0.0);
    glVertex3f( 0.15f, -0.15f, -0.15f);
    glVertex3f( 0.15f,  0.15f, -0.15f);
    glVertex3f( 0.15f,  0.15f,  0.15f);
    glVertex3f( 0.15f, -0.15f,  0.15f);
    // Left Face
    glNormal3f(-1.0, 0.0, 0.0);
    glVertex3f(-0.15f, -0.15f, -0.15f);
    glVertex3f(-0.15f, -0.15f,  0.15f);
    glVertex3f(-0.15f,  0.15f,  0.15f);
    glVertex3f(-0.15f,  0.15f, -0.15f);
  }
  glEnd();
  glPopMatrix();
}

void select_eye_candy_effect(int i)
{
  if (i == MAX_OBJ_3D)	// The current selection
    return;
  
  std::vector::iterator iter = effects.begin() + (i - MAX_OBJ_3D - 1);
  if (current_effect.reference)
  {
    ec_recall_effect(current_effect.reference);
    current_effect.reference = NULL;
  }
  current_effect = *iter;
  effects.erase(iter);
  eye_candy_ready_to_add = 1;
}

void kill_eye_candy_effect()
{
  if (current_effect.reference)
  {
    ec_recall_effect(current_effect.reference);
    current_effect.reference = NULL;
  }
  eye_candy_ready_to_add = 0;
}

int get_eye_candy_count()
{
  return effects.size();
}

void deserialize_eye_candy_effect(particles_io* data)
{
  const unsigned char*const code = (const unsigned char*const)data->file_name + 5;

//  std::cout << "Deserialize" << std::endl;  

  EffectDefinition dest;
  
  unsigned char raw_code[54];
  int i = 0;

  while (i < 18)
  {
    raw_code[i * 3]     = ((code[i * 4 + 0] - ' ') >> 0) | ((code[i * 4 + 1] - ' ') << 6);
    raw_code[i * 3 + 1] = ((code[i * 4 + 1] - ' ') >> 2) | ((code[i * 4 + 2] - ' ') << 4);
    raw_code[i * 3 + 2] = ((code[i * 4 + 2] - ' ') >> 4) | ((code[i * 4 + 3] - ' ') << 2);
    i++;
  }
  
  int bounds_count = raw_code[1];
  if (bounds_count > 19)
    bounds_count = 19;
  for (i = 0; i < bounds_count; i++)
  {
    const float angle = raw_code[i * 2 + 2] * (2 * ec::PI) / 256.0f;
    const float dist = raw_code[i * 2 + 3];
    dest.bounds.elements.push_back(ec::SmoothPolygonElement(angle, dist));
  }

  dest.position.x = data->x_pos;
  dest.position.y = data->y_pos;
  dest.position.z = data->z_pos;
  
  switch (raw_code[0])
  {
    case 0x00:	// Campfire
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x00;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_campfire(dest.position.x, dest.position.y, dest.position.z, hue, saturation, 10, scale);
      break;
    }
    case 0x01:	// Cloud
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float density = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x01;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.density = density;
      dest.reference = ec_create_cloud(dest.position.x, dest.position.y, dest.position.z, hue, saturation, density, (ec_bounds)(&dest.bounds), 10);
      break;
    }
    case 0x02:	// Fireflies
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float density = raw_code[43] + raw_code[44] / 256.0;
      const float scale = raw_code[45] + raw_code[46] / 256.0;
      dest.effect = 0x02;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.density = density;
      dest.reference = ec_create_fireflies(dest.position.x, dest.position.y, dest.position.z, hue, saturation, density, scale, (ec_bounds)(&dest.bounds));
      break;
    }
    case 0x03:	// Fountain
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      const float base_height = raw_code[45] * 8.0 + raw_code[46] / 32.0;
      const int backlit = raw_code[47];
      dest.effect = 0x03;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.base_height = base_height;
      dest.reference = ec_create_fountain(dest.position.x, dest.position.y, dest.position.z, hue, saturation, base_height, backlit, scale, 10);
      break;
    }
    case 0x04:	// Lamp
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x04;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_lamp(dest.position.x, dest.position.y, dest.position.z, hue, saturation, scale, 10);
      break;
    }
    case 0x05:	// Magic protection
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x05;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_ongoing_magic_protection(dest.position.x, dest.position.y, dest.position.z, hue, saturation, 10, scale);
      break;
    }
    case 0x06:	// Shield
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x06;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_ongoing_shield(dest.position.x, dest.position.y, dest.position.z, hue, saturation, 10, scale);
      break;
    }
    case 0x07:	// Magic immunity
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x07;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_ongoing_magic_immunity(dest.position.x, dest.position.y, dest.position.z, hue, saturation, 10, scale);
      break;
    }
    case 0x08:	// Poison
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x08;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_ongoing_poison(dest.position.x, dest.position.y, dest.position.z, hue, saturation, 10, scale);
      break;
    }
    case 0x09:	// Smoke
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float density = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x09;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.density = density;
      dest.reference = ec_create_smoke(dest.position.x, dest.position.y, dest.position.z, hue, saturation, density, 10);
      break;
    }
    case 0x0A:	// Teleporter
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x0A;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_teleporter(dest.position.x, dest.position.y, dest.position.z, hue, saturation, scale, 10);
      break;
    }
    case 0x0B:	// Leaves
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float density = raw_code[43] + raw_code[44] / 256.0;
      const float scale = raw_code[45] + raw_code[46] / 256.0;
      dest.effect = 0x0B;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.density = density;
      dest.reference = ec_create_wind_leaves(dest.position.x, dest.position.y, dest.position.z, hue, saturation, scale, density, (ec_bounds)(&dest.bounds), 1.0, 0.0, 0.0);
      break;
    }
    case 0x0C:	// Petals
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float density = raw_code[43] + raw_code[44] / 256.0;
      const float scale = raw_code[45] + raw_code[46] / 256.0;
      dest.effect = 0x0C;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.density = density;
      dest.reference = ec_create_wind_petals(dest.position.x, dest.position.y, dest.position.z, hue, saturation, scale, density, (ec_bounds)(&dest.bounds), 1.0, 0.0, 0.0);
      break;
    }
    case 0x0D:	// Waterfall
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float density = raw_code[43] + raw_code[44] / 256.0;
      const float base_height = raw_code[45] * 8.0 + raw_code[46] / 32.0;
      const float angle = raw_code[47] * ec::PI / 128.0;
      dest.effect = 0x0D;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.density = density;
      dest.base_height = base_height;
      dest.angle = angle;
//      dest.reference = ec_create_waterfall(dest.position.x, dest.position.y, dest.position.z, hue, saturation);
      break;
    }
    case 0x0E:	// Bees
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float density = raw_code[43] + raw_code[44] / 256.0;
      const float scale = raw_code[45] + raw_code[46] / 256.0;
      dest.effect = 0x0E;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.density = density;
//      dest.reference = ec_create_bees(dest.position.x, dest.position.y, dest.position.z, hue, saturation);
      break;
    }
    case 0x0F:	// Portal
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      const float angle = raw_code[45] * ec::PI / 128.0;
      dest.effect = 0x0F;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.angle = angle;
//      dest.reference = ec_create_portal(dest.position.x, dest.position.y, dest.position.z, hue, saturation);
      break;
    }
    case 0x10:	// Candle
    {
      const float hue = raw_code[41] / 256.0;
      const float saturation = raw_code[42] / 16.0;
      const float scale = raw_code[43] + raw_code[44] / 256.0;
      dest.effect = 0x10;
      dest.hue = hue;
      dest.saturation = saturation;
      dest.scale = scale;
      dest.reference = ec_create_candle(dest.position.x, dest.position.y, dest.position.z, hue, saturation, scale, 10);
      break;
    }
  }
  effects.push_back(dest);
}

void serialize_eye_candy_effect(int index, particles_io* data)
{
//  std::cout << "Serialize" << std::endl;  

  memset((char*)data, 0, sizeof(particles_io));

  std::string unformatted_data(80, '\0');
  unformatted_data[0] = effects[index].effect;
  unformatted_data[1] = effects[index].bounds.elements.size();
  if (unformatted_data[1] > 19)
    unformatted_data[1] = 19;
  for (int i = 0; i < unformatted_data[1]; i++)
  {
    unformatted_data[i * 2 + 2] = (char)((unsigned char)(effects[index].bounds.elements[i].angle / (2 * ec::PI) * 256.0f));
    unformatted_data[i * 2 + 3] = (char)((unsigned char)(effects[index].bounds.elements[i].radius));
  }
  switch (effects[index].effect)
  {
    case 0:    // Fire
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 1:    // Cloud/Fog
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].density));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].density * 256.0) % 256));
      break;
    case 2:    // Fireflies
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].density));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].density * 256.0) % 256));
      unformatted_data[45] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[46] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 3:    // Fountain
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      unformatted_data[47] = 0; 	// Backlit
      break;
    case 4:    // Lamp/Torch
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 5:    // Magic Protection
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 6:    // Shield
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 7:    // Magic Immunity
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 8:    // Poison
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 9:    // Smoke
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].density));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].density * 256.0) % 256));
      break;
    case 10:  // Teleporter
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 11:  // Leaves
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].density));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].density * 256.0) % 256));
      unformatted_data[45] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[46] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 12:  // Flower Petals
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].density));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].density * 256.0) % 256));
      unformatted_data[45] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[46] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 13:  // Waterfall
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].density));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].density * 256.0) % 256));
      unformatted_data[45] = (char)((unsigned char)(effects[index].base_height / 8.0));
      unformatted_data[46] = (char)((unsigned char)((int)(effects[index].base_height * 32.0) % 256));
      unformatted_data[47] = (char)((unsigned char)(effects[index].angle * 256.0));
      break;
    case 14:  // Bees
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].density));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].density * 256.0) % 256));
      unformatted_data[45] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[46] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
    case 15:  // Portal
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      unformatted_data[45] = (char)((unsigned char)(effects[index].angle * 256.0));
      break;
    case 16:    // Candle
      unformatted_data[41] = (char)((unsigned char)(effects[index].hue * 256.0));
      unformatted_data[42] = (char)((unsigned char)(effects[index].saturation * 16.0));
      unformatted_data[43] = (char)((unsigned char)(effects[index].scale));
      unformatted_data[44] = (char)((unsigned char)((int)(effects[index].scale * 256.0) % 256));
      break;
  }

  std::string data_str = "ec://";
  for (int i = 0; i < 18; i++)
  {
    data_str += ' ' + (char)((((unsigned char)unformatted_data[i * 3 + 0] & 0x3F)));
    data_str += ' ' + (char)((((unsigned char)unformatted_data[i * 3 + 0] & 0xC0) >> 6) | (((unsigned char)unformatted_data[i * 3 + 1] & 0x0F) << 2));
    data_str += ' ' + (char)((((unsigned char)unformatted_data[i * 3 + 1] & 0xF0) >> 4) | (((unsigned char)unformatted_data[i * 3 + 2] & 0x03) << 4));
    data_str += ' ' + (char)((((unsigned char)unformatted_data[i * 3 + 2] & 0xFC) >> 2));
  }

  sprintf(data->file_name, data_str.c_str());
  data->x_pos = effects[index].position.x;
  data->y_pos = effects[index].position.y;
  data->z_pos = effects[index].position.z;
}

void destroy_all_eye_candy()
{
  for (std::vector::iterator iter = effects.begin(); iter != effects.end(); iter++)
  {
    if (iter->reference)
    {
      ec_recall_effect(iter->reference);
      iter->reference = NULL;
    }
  }
  effects.clear();
}

#endif // EYE_CANDY