/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

// Golgatha Generic game object class
//
// The g1_object_class is the base type for all non-building/ground objects in the game.


#ifndef G1_OBJECT_HH
#define G1_OBJECT_HH

#include "arch.hh"
#include "math/num_type.hh"
#include "g1_limits.hh"
#include "math/transform.hh"
#include "image/context.hh"

#include "obj3d.hh"
#include "reference.hh"
#include "draw_context.hh"
#include "error/error.hh"
#include "player_type.hh"
#include "objs/model_id.hh"
#include "objs/model_draw.hh"
#include "range.hh"
#include "global_id.hh"


class g1_loader_class;
class g1_saver_class;
class g1_poly_list;
class g1_saver_class;
class g1_loader_class;
class i4_graphical_style_class;
class i4_event_handler_class;         // defined in device/device.hh
class li_object;
class li_symbol;
class li_environment;
class li_class;
class g1_object_definition_class;
class g1_screen_box;
struct g1_object_defaults_struct;     // objs/defaults.hh
class g1_damage_map_struct;            // objs/defaults.hh
struct g1_team_icon_ref;
class g1_player_piece_class;

//g1_mini_object stuff
class g1_mini_object
{
public:
  void draw(g1_draw_context_class *context, 
            i4_transform_class *transform,
            g1_screen_box *bound_box,
            g1_player_type player,
            i4_transform_class *use_this_transform=0, //if you want to supply a local space transform
            i4_bool pass_world_space_transform=i4_T,  // if you don't want lighting pass i4_F
            i4_bool use_lod_model=i4_F);
            
  i4_3d_vector offset;       // tells where the center of mini_object model is (where to rotate)
  i4_3d_vector loffset;

  i4_float x,y;              // location in game map in game coordinates
  i4_float lx,ly;            // last tick position (use to interpolate frames)
	
  i4_float h;                // height above (or below ground)
  i4_float lh;               // last tick height

  i4_3d_vector rotation;     // contains rotation information
  i4_3d_vector lrotation;
  
  g1_model_id_type defmodeltype; //model used by this mini object. can be overridden with draw()
  g1_model_id_type lod_model;

  w16 frame;          // current animation frame
  w16 animation;      // current animation number in model
  
  void calc_transform(i4_float ratio, i4_transform_class *trans);
  // calculates transform to parent system

  void position(const i4_3d_vector &pos) { lx=x=pos.x; ly=y=pos.y; lh=h=pos.z; }
  
  void grab_old()
  //be sure this is called by any parent object grab_old()'s
  {
    lx=x;
    ly=y;
    lh=h;
    lrotation = rotation;    
    loffset = offset;
  }
};



class g1_object_chain_class
{
public:
  g1_object_class       *object;
  g1_object_chain_class *next;
  w16 offset;

  inline g1_object_chain_class *next_solid();  // defined after g1_object_class
  g1_object_chain_class *next_non_solid() { return next; }  
};


enum g1_radar_type
{
  G1_RADAR_NONE,
  G1_RADAR_VEHICLE,
  G1_RADAR_STANK,
  G1_RADAR_WEAPON,
  G1_RADAR_BUILDING,
  G1_RADAR_PATH_OBJECT
};


typedef sw16 g1_object_type;

class g1_object_class
{
protected:
  friend class g1_reference_class;
  friend class g1_map_class;
  friend class g1_map_cell_class;
  friend class g1_remove_manager_class;

  void load_v2(g1_loader_class *fp);
  void load_v3(g1_loader_class *fp);
  void load_v4(g1_loader_class *fp);
  void load_v5(g1_loader_class *fp);
  void load_v6(g1_loader_class *fp);
  void load_v7(g1_loader_class *fp);
  void load_v8(g1_loader_class *fp);
  void load_v9(g1_loader_class *fp);

  // a list of all objects that reference us, so we can remove the reference when we delete ourself
  i4_isl_list ref_list;
  
  // list of squares this object is on (max of 4), if value is 0xffff then it is not a reference
  // the last one holds the center of the object, if necessary (ie, the object takes 
  // up more than 1 square and his corners are far enough apart)
  i4_array occupied_squares;

  g1_object_chain_class *new_occupied_square()
  {
    g1_object_chain_class *ret = occupied_squares.add();
    ret->object = this; 
    return ret;
  }

  i4_bool occupy_location_model(const g1_model_draw_parameters& draw_params);
  i4_bool occupy_location_corners();
  i4_bool occupy_location_center();     // only adds the center of the object
  i4_bool occupy_location_model_extents(const g1_model_draw_parameters &draw_params);
public:

  // generic type flags for run time object caster, defaults to can't cast
  enum
  {
    TO_MAP_PIECE      = (1<<0),
    TO_PLAYER_PIECE   = (1<<1),
    TO_DYNAMIC_OBJECT = (1<<2),
    TO_PATH_OBJECT    = (1<<3),
    TO_DECO_OBJECT    = (1<<4),
  };

  virtual w32 private_safe_to_cast() const { return 0; }

  w32 global_id;                // unique to the object, used to reference via networking

  g1_object_type id;            // this is the object's 'type' id

  i4_float x,y;                 // location in game map in game coordinates
  i4_float lx,ly;               // last tick position (use to interpolate frames)

  i4_float h;                   // height above (or below ground)
  i4_float lh;                  // last tick height

  i4_float theta;               // facing direction in the x-y game plane
  i4_float ltheta;              // last tick theta (yaw)

  i4_float pitch;               // pitch (y-z)
  i4_float lpitch;              // last tick pitch

  i4_float roll;                // roll (x-z)
  i4_float lroll;               // last tick roll

  sw16 health;                  // the health its got left

  li_class *vars;               // lisp variables used for dialog's.  These load and save automatically.

  g1_player_type player_num;
  g1_radar_type radar_type;
  g1_team_icon_ref *radar_image;


  virtual void change_player_num(int new_player_num);  // don't change player_num directly
  virtual g1_team_type get_team() const;
  
  enum flag_type
  {
    SELECTED      =1<<0,   // if object has been selected by player
    THINKING      =1<<1,   // if object is in a level's think list
 
    SHADOWED      =1<<2,   // if object should have shadows drawn for it
    MAP_INVISIBLE =1<<3,   // if object is invisible
    DELETED       =1<<4,   // if object has been removed, but remove has not been processed yet
    IN_TUNNEL     =1<<5,   // if the object is in a tunnel

    SELECTABLE    =1<<6,   // if the user can select the object in the game
    TARGETABLE    =1<<7,   // if object can be attacked

    GROUND        =1<<8,
    UNDERWATER    =1<<9,
    AERIAL        =1<<10,

    HIT_GROUND    =1<<11,
    HIT_UNDERWATER=1<<12,
    HIT_AERIAL    =1<<13,  // can attack objects in the air

    BLOCKING      =1<<14,  // if object blocks passage of other objects

    SCRATCH_BIT   =1<<15,  // use this for temporary calculations, but set it back to 0 when done
    MAP_OCCUPIED  =1<<16,  // makes sure you don't call occupy_location/unoccupy_location twice
    
    DANGEROUS     =1<<17,  // object should be killed

    RADAR_REMOVE  =1<<18,  // object needs to be removed from radar
    CAN_DRIVE_ON  =1<<19,  // if objects can drive over / stand on you
    ON_WATER      =1<<20,  // if objects is on water
  };

  enum { SAVE_FLAGS= SELECTED | THINKING };

  w32 flags;
  int get_flag(int x) const { return flags & x; }
  void set_flag(int x, int value) { if (value) flags|=x; else flags&=(~x); }  
  i4_bool selected() const { return flags & SELECTED; }
  i4_bool out_of_bounds(i4_float x, i4_float y) const;
  i4_bool moved() const { return (lx!=x || ly!=y); }
  i4_bool valid() const { return get_flag(MAP_OCCUPIED)!=0; }

  g1_object_definition_class *get_type();     // inlined below

  virtual void mark_as_selected();  
  virtual void mark_as_unselected();

  virtual i4_float occupancy_radius() const;

  // called when the object is on a map cell that is about to be drawn
  virtual void predraw() {}

  // called when the object is on a map cell that is drawn
  virtual void draw(g1_draw_context_class *context);

  // called for each object after everything else has been drawn and in editor mode
  virtual void editor_draw(g1_draw_context_class *context);

  virtual void note_stank_near(g1_player_piece_class *s) { ; }

  // called every game tick
  virtual void think() = 0;
  virtual void post_think() {;}

  g1_object_class(g1_object_type id, g1_loader_class *fp);
  virtual void validate() {}
  virtual void save(g1_saver_class *fp);

  void request_think();
  virtual void request_remove();

  // call to add object to a map (adds to cell x,y where object is standing)
  virtual i4_bool occupy_location();

  // call to remove an object from a map
  virtual void unoccupy_location();

  virtual i4_bool deploy_to(float x, float y) { return i4_F; }

  virtual void damage(g1_object_class *who_is_hurting,
                      int how_much_hurt, i4_3d_vector damage_dir);

  virtual i4_bool check_collision(const i4_3d_vector &start, 
                                  i4_3d_vector &ray);

  enum {NOTIFY_DAMAGE_KILLED=1};

  virtual void notify_damage(g1_object_class *obj, sw32 hp) {}
  
  virtual void calc_world_transform(i4_float ratio, i4_transform_class *transform=0);
  // calculates the transform from object coordinates to the world and stores it in 
  //   'transform'.  if transform is null, it stores it into the object's internal storage

  i4_transform_class *world_transform; //calculated and linearly allocated at draw time

  g1_model_draw_parameters draw_params;
  g1_mini_object *mini_objects;
  w16 num_mini_objects;

  void allocate_mini_objects(int num,char *reason)
  {
    mini_objects = (g1_mini_object *)i4_malloc(sizeof(g1_mini_object)*num,reason);
    memset(mini_objects,0,sizeof(g1_mini_object) * num);
    num_mini_objects = num;
  }

  virtual void grab_old();     // grab info about the current tick for interpolation

  //  to be consistant, every g1_object should have an init
  virtual void init()  {}

  // show in editor if mouse cursor stays still on object
  virtual i4_str *get_context_string();  

  virtual ~g1_object_class();

  virtual i4_bool can_attack(g1_object_class *who) const { return i4_F; }

  virtual void stop_thinking();

  float height_above_ground();      // calls map->terrain_height

  const char *name() const;
  
  virtual li_object *message(li_symbol *message_name,
                             li_object *message_params, 
                             li_environment *env) { return 0; }

  // called for every object when you click 'ok' in the vars edit dialog for an object
  virtual void object_changed_by_editor(g1_object_class *who, li_class *old_values) {}
  virtual int get_chunk_names(char **&list) { return 0; }
};


inline g1_object_chain_class *g1_object_chain_class::next_solid()
{
  if (!next || !next->object->get_flag(g1_object_class::BLOCKING))
    return 0;
  else return next;
}


enum eBuildError
{
  G1_BUILD_OK=0,                       // successful build
  G1_BUILD_WAIT,                       // needs more time to build
  G1_BUILD_TOO_EXPENSIVE,              // needs more money to build
  G1_BUILD_OCCUPIED,                   // space to build is occupied
  G1_BUILD_NO_SPACE,                   // no space to build
  G1_BUILD_INVALID_OBJECT,             // invalid object or object parameters
  G1_BUILD_ALREADY_EXISTS,             // unique object already exists
  G1_BUILD_PLAYBACK,                   // playback in progress, can't build manually
};

class g1_object_definition_class;

// this is the call to add a new object_type to the game.
g1_object_type g1_add_object_type(g1_object_definition_class *def);

// this is the call to find the object_type of a specified object
g1_object_type g1_get_object_type(const char *name);
g1_object_type g1_get_object_type(li_symbol *name);

// remove probably doesn't need to used during a normal game, but can be useful for
// adding and removing a dll during a running session instead of restarting the game
void g1_remove_object_type(g1_object_type type);


// this class defines an object to golgotha, primarly how to create such an object and
// load it from disk.  The object then has virtual functions that allow it do things like
// think, save, and draw.  Objects can be created at anytime in the game, and should be
// able to linked in through dlls.
class g1_object_definition_class
{
protected:
  typedef void (*function_type)(void);
  char *_name;
  function_type init_function, uninit_function;
  
  g1_damage_map_struct          *damage;     // loaded from scheme/balance.scm

public:
  enum 
  {
    EDITOR_SELECTABLE = (1<<0),      // object shows up in editor
    DELETE_WITH_LEVEL = (1<<1),      // delete this type with the map
    MOVABLE           = (1<<2),      // objects
    TO_MAP_PIECE      = (1<<3),
    TO_PLAYER_PIECE   = (1<<4),
    TO_DYNAMIC_OBJECT = (1<<5),
    TO_PATH_OBJECT    = (1<<6),
    TO_DECO_OBJECT    = (1<<7),
    HAS_ALPHA         = (1<<8)      // if object has alpha polys it will draw after non-alpha objects
  };

  
  w32                        var_class;   // class type to create for object's vars
  li_class                   *vars;       // variables specific to the type
  g1_object_defaults_struct  *defaults;   // loaded from scheme/balance.scm
  
  g1_damage_map_struct        *get_damage_map();
  
  w32 flags;
  int get_flag(int x) const { return flags & x; }
  void set_flag(int x, int value) { if (value) flags|=x; else flags&=(~x); }  

  g1_object_type type;

  g1_object_definition_class(char *_name,
                             w32 type_flags = EDITOR_SELECTABLE,
                             function_type _init = 0,
                             function_type _uninit = 0);

  virtual ~g1_object_definition_class() { g1_remove_object_type(type); }

  // create_object should return a new initialized instance of an object, if fp is null then
  // default values should be supplied, otherwise the object should load itself from the file
  virtual g1_object_class *create_object(g1_object_type id,
                                         g1_loader_class *fp) = 0;

  // the name on an object should be unique, so you might want to be creative,  the name
  // is used to match up the object types in save files since object type id's are dynamically
  // created
  const char *name() { return _name; }

  i4_bool editor_selectable() { return flags & EDITOR_SELECTABLE; }

  virtual void init();
  virtual void uninit() { if (uninit_function) (*uninit_function)();}
  
  virtual void save(g1_saver_class *fp) { ; }  // save info about type
  virtual void load(g1_loader_class *fp) { ; } // load info about type

  // this is called when the object is right-clicked, the dialog is added to a draggable frame
  virtual i4_window_class *create_edit_dialog();

};

void g1_apply_damage(g1_object_class *kinda_gun_being_used,
                     g1_object_class *who_pulled_the_trigger,
                     g1_object_class *whos_on_the_wrong_side_of_the_gun,
                     const i4_3d_vector &direction_hurten_is_commin_from);

// increase this number if you change the load/save structure of g1_object_class
#define G1_OBJECT_FORMAT_VERSION 1 


// this table has an array of pointers to object definitions
// this is used by the border frame to find object with build info so it 
// can add buttons for them
extern g1_object_definition_class *g1_object_type_array[G1_MAX_OBJECT_TYPES];
extern g1_object_type g1_last_object_type;  // largest object number assigned

// this is prefered way to create new object in the game
inline g1_object_class *g1_create_object(g1_object_type type)
{ 
  if (g1_object_type_array[type])
    return g1_object_type_array[type]->create_object(type, 0);
  else 
    return 0;
}

inline g1_object_definition_class *g1_object_class::get_type() { return g1_object_type_array[id]; }

void g1_initialize_loaded_objects(); 
void g1_uninitialize_loaded_objects();

#endif