#include "timage.hpp"
#include "objects.hpp"
#include "chars.hpp"

#include "game.hpp"
#include "intsect.hpp"
#include "ability.hpp"
#include "lisp.hpp"
#include "jrand.hpp"
#include "light.hpp"
#include "dprint.hpp"
#include "clisp.hpp"
#include "lisp_gc.hpp"
#include "profile.hpp"

#ifdef SCADALISP
#define LCAR(x)		CAR(x)
#define LCDR(x)		CDR(x)
#else
#define LCAR(x)		(x)->car
#define LCDR(x)		(x)->cdr
#endif /* SCADALISP */

char **object_names;
int total_objects;
game_object *current_object;
view *current_view;

game_object *game_object::copy()
{
  game_object *o=create(otype,x,y);
  o->state=state;
  int i=0;
  for (;i<TOTAL_OBJECT_VARS;i++)
    o->set_var(i,get_var(i));
  memcpy(o->lvars,lvars,4*figures[otype]->tv);
  for (i=0;i<total_objects();i++)
    o->add_object(get_object(i));
  for (i=0;i<total_lights();i++)
    o->add_light(get_light(i));
  return o;
}

int simple_object::total_vars() { return TOTAL_OBJECT_VARS; }


obj_desc object_descriptions[TOTAL_OBJECT_VARS]={
				{"fade_dir",      RC_C },
				{"frame_dir",     RC_C },
				{"direction",     RC_C },
				{"gravity_on",    RC_C },
				{"fade_count",    RC_C },

				{"fade_max",      RC_C },
				{"active",        RC_C },
				{"flags",         RC_C },
				{"aitype",        RC_C },
				{"xvel",          RC_L },

				{"fxvel",         RC_C },
				{"yvel",          RC_L },
				{"fyvel",         RC_C },
				{"xacel",         RC_L },
				{"fxacel",        RC_C },

				{"yacel",         RC_L },
				{"fyacel",        RC_C },
				{"x",             RC_L },
				{"fx",            RC_C },
				{"y",             RC_L },

				{"fy",            RC_C },
				{"hp",            RC_S },
				{"mp",            RC_S },
				{"fmp",           RC_S },
				{"cur_frame",     RC_S },

				{"aistate",       RC_S },
				{"aistate_time",  RC_S },
				{"targetable",    RC_C }

			      };
  
long game_object::get_var_by_name(char *name, int &error)
{
  error=0;
  int i=0;
  for (;i<TOTAL_OBJECT_VARS;i++)
  {
    if (!strcmp(object_descriptions[i].name,name))
      return get_var(i);
  }

  for (i=0;i<figures[otype]->tiv;i++)
  {
    if (!strcmp(lstring_value(symbol_name(figures[otype]->vars[i])),name))
    {
      return lvars[figures[otype]->var_index[i]];
/*      lisp_object_var *cobj=(lisp_object_var *)symbol_value(figures[otype]->vars[i]);
      character_type *t=figures[otype];
      int number=cobj->number;
      if (t->tiv<=number || !t->vars[number])
      {
	lbreak("access : variable does not exsist for this class\n");
	return 0;
      }
      return lvars[t->var_index[number]];*/
    }
  }
  error=1;
  return 0;
}

int game_object::set_var_by_name(char *name, long value)
{
  int i=0;
  for (;i<TOTAL_OBJECT_VARS;i++)
  {
    if (!strcmp(object_descriptions[i].name,name))
    {
      set_var(i,value);
      return 1;
    }
  }
  for (i=0;i<figures[otype]->tiv;i++)
    if (!strcmp(lstring_value(symbol_name(figures[otype]->vars[i])),name))
    {
      lvars[figures[otype]->var_index[i]]=value;
      return 1;
    }
  return 0;
}


char *simple_object::var_name(int x)
{
  return object_descriptions[x].name;
}

int simple_object::var_type(int x)
{
  return object_descriptions[x].type;
}


void simple_object::set_var(int xx, ulong v)
{
  switch (xx)
  {
    case 0 : set_fade_dir(v); break;
    case 1 : set_frame_dir(v); break;
    case 2 : direction=v; break;
    case 3 : set_gravity(v); break;
    case 4 : set_fade_count(v); break;
    case 5 : set_fade_max(v); break;
    case 6 : active=v;break;
    case 7 : set_flags(v); break;
    case 8 : set_aitype(v); break;
    case 9 : set_xvel(v); break;

    case 10 : set_fxvel(v); break;
    case 11 : set_yvel(v); break;
    case 12 : set_fyvel(v); break;
    case 13 : set_xacel(v); break;
    case 14 : set_fxacel(v); break;

    case 15 : set_yacel(v); break;
    case 16 : set_fyacel(v); break;
    case 17 : x=v; break;
    case 18 : set_fx(v); break;
    case 19 : y=v; break;

    case 20 : set_fy(v); break;
    case 21 : set_hp(v); break;
    case 22 : set_mp(v); break;
    case 23 : set_fmp(v); break;

    case 24 : current_frame=v;  break;
    case 25 : set_aistate(v); break;

    case 26 : set_aistate_time(v); break;
    case 27 : set_targetable(v); break;
  }
}

long simple_object::get_var(int xx)
{
  switch (xx)
  {
    case 0 : return fade_dir(); break;
    case 1 : return frame_dir(); break;
    case 2 : return direction; break;
    case 3 : return gravity(); break;
    case 4 : return fade_count(); break;
    case 5 : return fade_max(); break;
    case 6 : return active; break;
    case 7 : return flags(); break;
    case 8 : return aitype(); break;
    case 9 : return xvel(); break;
    case 10 : return fxvel(); break;

    case 11 : return yvel(); break;
    case 12 : return fyvel(); break;

    case 13 : return xacel(); break;
    case 14 : return fxacel(); break;

    case 15 : return yacel(); break;
    case 16 : return fyacel(); break;

    case 17 : return x; break;
    case 18 : return fx(); break;

    case 19 : return y; break;
    case 20 : return fy(); break;

    case 21 : return hp(); break;
    case 22 : return mp(); break;
    case 23 : return fmp(); break;

    case 24 : return current_frame;  break;
    case 25 : return aistate(); break;
    case 26 : return aistate_time(); break;
    case 27 : return targetable();
  }
  return 0;
}




int RC_type_size(int type)
{
  switch (type)
  {
    case RC_C : 
    { return 1; } break;
    case RC_S : 
    { return 2; } break;
    case RC_L : 
    { return 4; } break;
  }		
  CHECK(0);
  return 1;
} 

void game_object::reload_notify()
{
  void *ns=figures[otype]->get_fun(OFUN_RELOAD);  
  if (ns)
  {
    game_object *o=current_object;
    current_object=this;

    void *m=mark_heap(TMP_SPACE);
    eval_function((lisp_symbol *)ns,NULL);
    restore_heap(m,TMP_SPACE);

    current_object=o;
  }
}

void game_object::next_sequence()
{
  void *ns=figures[otype]->get_fun(OFUN_NEXT_STATE);
  if (ns)
  {  
    current_object=this;
    void *m=mark_heap(TMP_SPACE);
    void *ret=eval_function((lisp_symbol *)ns,NULL);
    restore_heap(m,TMP_SPACE);
  } else
  {
    switch (state)
    {
      case dieing : 
      { set_state(dead); } break;

      case end_run_jump :
      {
	set_state(running);
      } break;
      case dead :
      case run_jump :
      case run_jump_fall :
      case running :
      { set_state(state); } break;
      case start_run_jump :
      { 
	set_yvel(get_ability(type(),jump_yvel));
	if (xvel()>0)
        set_xvel(get_ability(type(),jump_xvel));
	else if (xvel()<0)
        set_xvel(-get_ability(type(),jump_xvel));
	set_xacel(0);
	set_fxacel(0);
	set_gravity(1);      
	set_state(run_jump);
      } break;

      case flinch_up :
      case flinch_down :
      {
	if (gravity())
	{
	  if (has_sequence(end_run_jump))
	    set_state(end_run_jump);
	  else set_state(stopped);
	} else set_state(stopped);
      } break;

      
      default :
      { set_state(stopped); } break;
    }
  }

}



game_object::~game_object()
{
  if (lvars) jfree(lvars);
  clean_up();
}

void game_object::add_power(int amount)  
{ 
  int max_power=lnumber_value(symbol_value(l_max_power));  
  int n=mp()+amount;
  if (n<0) n=0;
  if (n>max_power) n=max_power;
  set_mp(n);
}

void game_object::add_hp(int amount)
{
  if (controller() && controller()->god) return ;
  int max_hp=lnumber_value(symbol_value(l_max_hp));  
  int n=hp()+amount;
  if (n<0) n=0;
  if (n>max_hp)
    n=max_hp;
  set_hp(n);
}

int game_object::can_morph_into(int type)
{
  if (type!=otype && mp()>=figures[type]->morph_power)
    return 1;
  else return 0;
}

void game_object::morph_into(int type, void (*stat_fun)(int), int anneal, int frames)
{
  set_morph_status(new morph_char(this,type,stat_fun,anneal,frames));
  otype=type;
  set_state(stopped);
}

void game_object::draw_above(view *v)
{
  long x1,y1,x2,y2,sy1,sy2,sx,i;
  picture_space(x1,y1,x2,y2);    

  the_game->game_to_mouse(x1,y1,v,sx,sy2);
  if (sy2>=v->cy1)
  {
    long draw_to=y1-(sy2-v->cy1),tmp=x;
    current_level->foreground_intersect(x,y1,tmp,draw_to);     
    the_game->game_to_mouse(x1,draw_to,v,i,sy1);     // calculate sy1

    sy1=max(v->cy1,sy1);
    sy2=min(v->cy2,sy2);
    trans_image *p=picture();
    
    for (i=sy1;i<=sy2;i++)
      p->put_scan_line(screen,sx,i,0);  
  }  
}

int game_object::push_range()
{
  return get_ability(otype,push_xrange);
}

int game_object::decide()
{
  if (figures[otype]->get_fun(OFUN_AI))
  {
    int old_aistate;
    old_aistate=aistate();

    current_object=this;
    void *m=mark_heap(TMP_SPACE);

    time_marker *prof1;
    if (profiling())
      prof1=new time_marker;

    Cell *ret=(Cell *)eval_function((lisp_symbol *)figures[otype]->get_fun(OFUN_AI),NULL);
    if (profiling())
    {
      time_marker now;
      profile_add_time(this->otype,now.diff_time(prof1));
      delete prof1;
    }

    restore_heap(m,TMP_SPACE);

    if (keep_ai_info())
    {
      if (aistate()!=old_aistate)
      set_aistate_time(0);
      else set_aistate_time(aistate_time()+1);    
    }
    if (!NILP(ret)) return 1;
    else return 0;
  }
  else move(0,0,0);
  return 1;
}

int game_object::can_hurt(game_object *who)     // collision checking will ask first to see if you
{
  int is_attacker=current_level->is_attacker(this);  
  // it's you against them!  Damage only if it you are attacking or they are attacking you
  // I.E. don't let them hurt themselves, this can change if you over-ride this virtual function

  if (who->hurtable() && (is_attacker || current_level->is_attacker(who) || hurt_all()))
    return 1;
  else return 0;  
}

void game_object::note_attack(game_object *whom)
{
  return ;   // nevermind
}

void game_object::do_flinch(game_object *from)
{
  if (jrandom(2) && has_sequence(flinch_down))
    set_state(flinch_down);
  else if (has_sequence(flinch_up))
    set_state(flinch_up);
}

   
void game_object::do_damage(int amount, game_object *from, long hitx, long hity, 
			    long push_xvel, long push_yvel) 
{

  void *d=figures[otype]->get_fun(OFUN_DAMAGE);  
  if (d)
  {
    void	*am, *frm, *hx, *hy, *px, *py;  
    game_object *o=current_object;
    current_object=this;

    void *m=mark_heap(TMP_SPACE);

    am=new_cons_cell();
    l_ptr_stack.push(&am);

    ((cons_cell *)am)->car=new_lisp_number(amount);   

    frm=new_cons_cell();
    l_ptr_stack.push(&frm);

    ((cons_cell *)frm)->car=new_lisp_pointer(from);

    hx=new_cons_cell();
    l_ptr_stack.push(&hx);

    ((cons_cell *)hx)->car=new_lisp_number(hitx);

    hy=new_cons_cell();
    l_ptr_stack.push(&hy);
    ((cons_cell *)hy)->car=new_lisp_number(hity);

    px=new_cons_cell();
    l_ptr_stack.push(&px);
    ((cons_cell *)px)->car=new_lisp_number(push_xvel);

    py=new_cons_cell();
    l_ptr_stack.push(&py);
    ((cons_cell *)py)->car=new_lisp_number(push_yvel);


    ((cons_cell *)am)->cdr=frm;
    ((cons_cell *)frm)->cdr=hx;
    ((cons_cell *)hx)->cdr=hy;
    ((cons_cell *)hy)->cdr=px;
    ((cons_cell *)px)->cdr=py;

    time_marker *prof1;
    if (profiling())
      prof1=new time_marker;

    eval_user_fun((lisp_symbol *)d,am);
    if (profiling())
    {
      time_marker now;
      profile_add_time(this->otype,now.diff_time(prof1));
      delete prof1;
    }


    l_ptr_stack.pop(6);
   
    restore_heap(m,TMP_SPACE);

    current_object=o;
  } else damage_fun(amount,from,hitx,hity,push_xvel,push_yvel);
#ifdef SCADALISP
  ENDLOCAL();
#endif
}

void game_object::damage_fun(int amount, game_object *from, long hitx, long hity, 
			    long push_xvel, long push_yvel) 
{ 
  if (!hurtable() || !alive()) return ;

  add_hp(-amount);
  set_flags(flags()|FLAG_JUST_HIT);
  do_flinch(from);

  
  set_xvel(xvel()+push_xvel);
  if (push_yvel<0 && !gravity())
    set_gravity(1);
  set_yvel(yvel()+push_yvel);

  view *c=controller();
  if (c && hp()<=0)
  {
    view *v=from->controller();
    if (v) v->kills++;                       // attack from another player?
    else if (from->total_objects()>0)
    {
      v=from->get_object(0)->controller();   // weapon from another player?
      if (v && v!=c) v->kills++;
      else
      {
	v=c;                                 // sucide
	if (v) v->kills--;
      }
    }
  }
}
     


int game_object::facing_attacker(int attackerx)
{ 
  return ((attackerx<x && direction<0) || (attackerx>=x && direction>0)); 

}


void game_object::picture_space(long &x1, long &y1,long &x2, long &y2)
{
  int xc=x_center(),w=picture()->width(),h=picture()->height();  
  if (direction>0)
    x1=x-xc;  
  else x1=x-(w-xc-1);  
  x2=x1+w-1;
  y1=y-h+1;
  y2=y;  
}


int game_object::next_picture()
{
  int ret=1;
  if (frame_dir()>0)
  {
    if (!current_sequence()->next_frame(current_frame))
    {
      next_sequence();
      ret=0;
    }
  }
  else 
  {
    if (!current_sequence()->last_frame(current_frame))
    {
      next_sequence();
      ret=0;
    }
  }
  frame_advance();
  return ret;
}


long game_object::x_center()
{
  return current_sequence()->x_center(current_frame);   
}


void game_object::draw()
{
  if (figures[otype]->get_fun(OFUN_DRAW))
  {
    current_object=this;

    void *m=mark_heap(TMP_SPACE);
    time_marker *prof1;
    if (profiling())
      prof1=new time_marker;

    eval_function((lisp_symbol *)figures[otype]->get_fun(OFUN_DRAW),NULL);
    if (profiling())
    {
      time_marker now;
      profile_add_time(this->otype,now.diff_time(prof1));
      delete prof1;
    }



    restore_heap(m,TMP_SPACE);

  } else drawer();
}


void game_object::map_draw()
{
  if (figures[otype]->get_fun(OFUN_MAP_DRAW))
  {
    current_object=this;

    void *m=mark_heap(TMP_SPACE);
    time_marker *prof1;
    if (profiling())
      prof1=new time_marker;

    eval_function((lisp_symbol *)figures[otype]->get_fun(OFUN_MAP_DRAW),NULL);
    if (profiling())
    {
      time_marker now;
      profile_add_time(this->otype,now.diff_time(prof1));
      delete prof1;
    }

    restore_heap(m,TMP_SPACE);

  }
}

void game_object::draw_trans(int count, int max)
{
  trans_image *cpict=picture();
  cpict->put_fade(screen,
		  (direction<0 ? x-(cpict->width()-x_center()-1) : x-x_center())-current_vxadd,
		  y-cpict->height()+1-current_vyadd,
		  count,max,
		  color_table,the_game->current_palette());
}


void game_object::draw_tint(int tint_id)
{
  trans_image *cpict=picture();
  if (fade_count())      
    cpict->put_fade_tint(screen,
		       (direction<0 ? x-(cpict->width()-x_center()-1) : x-x_center())-current_vxadd,
		       y-cpict->height()+1-current_vyadd,
		       fade_count(),fade_max(),
		       cash.ctint(tint_id)->data,
		       color_table,the_game->current_palette());


  else
    cpict->put_remaped(screen,
		       (direction<0 ? x-(cpict->width()-x_center()-1) : x-x_center())-current_vxadd,
		       y-cpict->height()+1-current_vyadd,
		       cash.ctint(tint_id)->data);
}


void game_object::draw_double_tint(int tint_id, int tint2)
{
  trans_image *cpict=picture();
  if (fade_count())      
    cpict->put_fade_tint(screen,
		       (direction<0 ? x-(cpict->width()-x_center()-1) : x-x_center())-current_vxadd,
		       y-cpict->height()+1-current_vyadd,
		       fade_count(),fade_max(),
		       cash.ctint(tint_id)->data,
		       color_table,the_game->current_palette());


  else
    cpict->put_double_remaped(screen,
		       (direction<0 ? x-(cpict->width()-x_center()-1) : x-x_center())-current_vxadd,
		       y-cpict->height()+1-current_vyadd,
		       cash.ctint(tint_id)->data,
		       cash.ctint(tint2)->data);
}



void game_object::draw_predator()
{
  trans_image *cpict=picture();
  cpict->put_predator(screen,
		     (direction<0 ? x-(cpict->width()-x_center()-1) : x-x_center())-current_vxadd,
		     y-cpict->height()+1-current_vyadd);
		    
}

void game_object::drawer()
{
  trans_image *cpict;

  if (morph_status())
  {
    morph_status()->draw(this,current_view);
    if (morph_status()->frames_left()<1)
      set_morph_status(NULL);
  }
  else
  {
    view *v=controller();

    if (fade_count())      
      draw_trans(fade_count(),fade_max());
    else
    {
      trans_image *cpict=picture();
      cpict->put_image(screen,
		       (direction<0 ? x-(cpict->width()-x_center()-1) : x-x_center())-current_vxadd,
		       y-cpict->height()+1-current_vyadd);
    }
  }
}

game_object *game_object::try_move(long x, long y, long &xv, long &yv, int checks)
{
  if (xv || yv)  // make sure they are suggesting movement
  {    
    game_object *who1=NULL,*who2=NULL;      // who did we intersect?  
    long x2,y2,h;  

    if (checks&1)
    {      
      x2=x+xv;
      y2=y+yv;    
      if (xv==0)
        current_level->vforeground_intersect(x,y,y2);
      else
        current_level->foreground_intersect(x,y,x2,y2);
      if (!stoppable())
        who1=current_level->boundary_setback(this,x,y,x2,y2);
      else
        who1=current_level->all_boundary_setback(this,x,y,x2,y2);  
      xv=x2-x;
      yv=y2-y;
    }
    

    if (checks&2)
    {      
      h=picture()->height();    
      x2=x+xv;    
      y2=y-h+1+yv; 
      if (xv==0)
        current_level->vforeground_intersect(x,y-h+1,y2);
      else
        current_level->foreground_intersect(x,y-h+1,x2,y2);
      if (!stoppable())
        who2=current_level->all_boundary_setback(this,x,y-h+1,x2,y2);    
      else
        who2=current_level->boundary_setback(this,x,y-h+1,x2,y2);
      xv=x2-x;
      yv=y2-y+h-1;  
    }
        
    if (who2) return who2;
    else return who1;
  }
  else return NULL;  
}

void *game_object::float_tick()  // returns 1 if you hit something, 0 otherwise
{
  long ret=0;
  if (hp()<=0)
  {
    if (state!=dead)
    {
      set_xacel(0);
      set_fxacel(0);

      if (has_sequence(dieing))
      {      
	if (state!=dieing)
	{
	  set_state(dieing);
	  set_xvel(0);
	}
      } else 
      { set_xvel(0); 
	set_fxvel(0);
	if (has_sequence(dead))
          set_state(dead); 
	else return 0;
      }
    } 
  }

  long fxv=sfxvel()+sfxacel(),fyv=sfyvel()+sfyacel();
  long xv=xvel()+xacel()+(fxv>>8),yv=yvel()+yacel()+(fyv>>8);

  if (xv!=xvel() || yv!=yvel())   // only store vel's if changed so we don't increase object size
  {
    set_xvel(xv);
    set_yvel(yv);
  }

  if (fxv!=sfxvel() || fyv!=sfyvel())
  {
    set_fxvel(fxv&0xff);
    set_fyvel(fyv&0xff);
  }

 
  if (fxv || fyv || xv || yv)   // don't even try if there is no velocity
  {
    long nx=x,ny=y;
    long ffx=fx()+sfxvel(),ffy=fy()+sfyvel();
    long nxv=xvel()+(ffx>>8);
    long nyv=yvel()+(ffy>>8);
    set_fx(ffx&0xff);
    set_fy(ffy&0xff);
    
    long old_nxv=nxv,old_nyv=nyv;
    game_object *hit_object=try_move(x,y,nxv,nyv,3);   // now find out what velocity is safe to use
    
/*    if (get_cflag(CFLAG_STOPPABLE))
    {
      game_object *r=current_level->boundary_setback(exclude,x,y,nxv,nyv,1);
      if (r) hit_object=r;
    }*/

    x+=nxv;
    y+=nyv;
    if (old_nxv!=nxv || old_nyv!=nyv)
    {
      long lx=last_tile_hit_x,ly=last_tile_hit_y;
      stop();
      if (old_nxv==0)
      {
	if (old_nyv>0) ret|=BLOCKED_DOWN;
	else if (old_nyv<0) ret|=BLOCKED_UP;
      } else if (old_nyv==0)
      {
	if (old_nxv>0) ret|=BLOCKED_RIGHT;
	else if (old_nxv<0) ret|=BLOCKED_LEFT;
      } else
      {
	long tx=(old_nxv>0 ? 1 : -1),ty=0;
	try_move(x,y,tx,ty,3);
	if (!tx) 	
	  ret|=(old_nxv>0 ? BLOCKED_RIGHT : BLOCKED_LEFT);	
	else tx=0;

	ty=(old_nyv>0 ? 1 : -1);
	try_move(x,y,tx,ty,3);
	if (!ty) 	
	  ret|=(old_nyv>0 ? BLOCKED_DOWN : BLOCKED_UP);
	
	if (!ret)
	  ret|=(old_nyv>0 ? BLOCKED_DOWN : BLOCKED_UP) | (old_nxv>0 ? BLOCKED_RIGHT : BLOCKED_LEFT);

      }

      void *rlist=NULL;   // return list
      p_ref r1(rlist);

      if (hit_object)
      {
	push_onto_list(new_lisp_pointer(hit_object),rlist);
	push_onto_list(l_object,rlist);
      } else
      {
	push_onto_list(new_lisp_number(ly),rlist);
	push_onto_list(new_lisp_number(lx),rlist);
	push_onto_list(l_tile,rlist);
      }
      push_onto_list(new_lisp_number(ret),rlist);

      return rlist;
    } else return true_symbol;
  }
  return true_symbol;
}

int game_object::tick()      // returns blocked status
{
  int blocked=0;

  /*  long xt=0,yt=2;
  try_move(x,y-2,xt,yt,1);    // make sure we are not falling through the floor
  y=y-2+yt; */
  
  if (flags()&FLAG_JUST_BLOCKED)
    set_flags(flags()-FLAG_JUST_BLOCKED);

  if (gravity() && !floating())
  {
    int fya;
    if (yacel()>=0)
      fya=sfyacel()+200;
    else
      fya=sfyacel()-200;

    set_yacel(yacel()+(fya>>8));
    set_fyacel(fya&255);
  }
  
  // first let's move the guy acording to his physics
  long xa=xacel(),ya=yacel(),fxa=sfxacel(),fya=sfyacel();
  if (xa || ya || fxa || fya)
  {
    int fxv=sfxvel(),fyv=sfyvel();
    fxv+=fxa;  fyv+=fya;    
    long xv=xvel()+xa+(fxv>>8); 
    set_xvel(xvel()+xa+(fxv>>8));
    set_yvel(yvel()+ya+(fyv>>8));
    set_fxvel(fxv&0xff);
    set_fyvel(fyv&0xff);
  }
  
  // check to see if this advancement causes him to collide with objects
  long old_vy=yvel(),old_vx=xvel();  // save the correct veloicties

  if (old_vx || old_vy)
  {
    int up=0;
    if (yvel()<=0)  // if we are going up or a strait across check up and down
    up=2;
    long xv=xvel(),yv=yvel();
    game_object *h=try_move(x,y,xv,yv,1|up);       // now find out what velocity is safe to use
    set_xvel(xv);
    set_yvel(yv);
    x+=xv;
    y+=yv;

    if (h && stoppable()) return BLOCKED_LEFT|BLOCKED_RIGHT;

    if (xv!=old_vx || yv!=old_vy)     // he collided with something
    {          
      if (gravity())                         // was he going up or down?
      {	                       
	long fall_xv=0,old_fall_vy,fall_vy;
	old_fall_vy=fall_vy=old_vy-yvel();             // make sure he gets all of his yvel
	try_move(x,y,fall_xv,fall_vy,1|up);
	if (old_vy>0 && fall_vy<old_fall_vy)       // he was trying to fall, but he hit the ground
	{
	  if (old_vy>0)	
	  {
	    blocked|=BLOCKED_DOWN;
	    
	    if (!xvel() && has_sequence(end_run_jump))	  
	    {
	      set_xvel(old_vx);
	      set_state(end_run_jump);
	    }
	    else set_state(stopped);	  
	  }
	  else blocked|=BLOCKED_UP;


	  if (state==run_jump_fall)
	  {
	    if (has_sequence(running))
	    set_state(running);
	    else
	    {
	      stop_x();
	      set_state(stopped);
	    }
	  }
	  else
	  {
	    set_yacel(0);
	    set_fyacel(0);
	    set_yvel(0);
	    set_fyvel(0);
	    set_gravity(0);
	  }

	} else 
	{
	  if (old_vy!=0)
	  {
	    long testx=old_vx<0 ? -1 : 1,testy=0;    // see if we were stopped left/right
                                                     // or just up down
	    try_move(x,y,testx,testy,1|up);
	    if (testx==0)                           // blocked left/right, set flag
	    {
	      if (old_vx<0)
	        blocked|=BLOCKED_LEFT;
	      else
	        blocked|=BLOCKED_RIGHT;
	    }
	    else if (old_vy<0)
	      blocked|=BLOCKED_UP;
	    else if (old_vy>0)
	      blocked|=BLOCKED_DOWN;

	  } else if (old_vx<0)   // we can skip left/right test because player wasn't moving up/down
            blocked|=BLOCKED_LEFT;
	  else
            blocked|=BLOCKED_RIGHT;

	  set_xvel(0);
	  set_fxvel(0);
	  if (old_vy<0 && fall_vy>0)        
	  {
	    set_yvel(yvel()+fall_vy);
	  } else set_yvel(yvel()+fall_vy);
	}      
	y+=fall_vy;            
      }    
      else                  // see if we can make him 'climb' the hill
      {
	long ox=x,oy=y;       // rember orginal position in case climb doesn't work

	long climb_xvel=0,climb_yvel=-5;	    // try to move up one pixel to step over the 
	try_move(x,y,climb_xvel,climb_yvel,3);  // jutting polygon line
	y+=climb_yvel;

	climb_xvel=old_vx-xvel();
	climb_yvel=-(abs(climb_xvel));	    // now try 45 degree slope
	try_move(x,y,climb_xvel,climb_yvel,3);

	if (abs(climb_xvel)>0)  // see if he got further by climbing
	{
	  blocked=blocked&(~(BLOCKED_LEFT|BLOCKED_RIGHT));
	  x+=climb_xvel;
	  y+=climb_yvel;

	  set_xvel(xvel()+climb_xvel);
	  set_yvel(0);          
	  set_fyvel(0);
	  
	  // now put him back on the ground          
	  climb_yvel=abs(climb_xvel)+5;               // plus one to put him back on the ground
	  climb_xvel=0;
	  try_move(x,y,climb_xvel,climb_yvel,1);
	  if (climb_yvel)
          y+=climb_yvel;
	}
	else	         
	{	
	  if (old_vx<0)
          blocked|=BLOCKED_LEFT;
	  else
          blocked|=BLOCKED_RIGHT;
	  set_state(stopped);      // nope, musta hit a wall, stop the poor fella
	  x=ox;
	  y=oy;	
	}
	
      }
      
      if (xvel()!=old_vx && state!=run_jump_fall && state!=end_run_jump)
      {
	set_xacel(0); 
	set_fxacel(0);
      }
    }
  }
     
  if (yacel()==0 && !gravity())       // he is not falling, make sure he can't
  {
    long nvx=0,nvy=yvel()+12;  // check three pixels below for ground
    try_move(x,y,nvx,nvy,1); 
    if (nvy>11)                    // if he falls more than 2 pixels, then he falls
    {
      if (state!=run_jump_fall)      // make him fall
      {       
        if (has_sequence(run_jump_fall))
          set_state(run_jump_fall);
        set_gravity(1);
      }
    } else if (nvy)   // if he fells less than 3 pixels then let him descend 'hills'
    {      
      y+=nvy;
      blocked|=BLOCKED_DOWN;      
    }    
  }
  return blocked;
}

void game_object::defaults()
{
  set_state(state);
  if (otype!=0xffff)
  {
    int s=get_ability(otype,start_hp);
    if (s!=default_simple.hp())
      set_hp(s);
  }
}

void game_object::frame_advance()
{
  int ad=current_sequence()->get_advance(current_frame);
  if (ad && current_level)
  {
    long xv;
    if (direction>0) xv=ad; else xv=-ad;
    long yv=0;
    try_move(x,y,xv,yv,3);
    x+=xv;
  }   
}

void game_object::set_state(character_state s, int frame_direction)
{
  if (has_sequence(s))
    state=s;
  else state=stopped;

  current_frame=0;  
  if (frame_direction!=1)
    set_frame_dir(frame_direction); 

  frame_advance();
}


game_object *create(int type, long x, long y, int skip_constructor, int aitype)
{
  game_object *g=new game_object(type,skip_constructor);
  g->x=x; g->y=y; g->last_x=x; g->last_y=y;
  if (aitype)
    g->set_aitype(aitype);
  if (figures[type]->get_fun(OFUN_CONSTRUCTOR) && !skip_constructor)
  {
    game_object *o=current_object;
    current_object=g;    

    void *m=mark_heap(TMP_SPACE);

    time_marker *prof1;
    if (profiling())
      prof1=new time_marker;

    eval_function((lisp_symbol *)figures[type]->get_fun(OFUN_CONSTRUCTOR),NULL);
    if (profiling())
    {
      time_marker now;
      profile_add_time(type,now.diff_time(prof1));
      delete prof1;
    }



    restore_heap(m,TMP_SPACE);

    current_object=o;
  }
  return g;
}

int base_size()
{
  return 1+  
         1*8+
	 2*5+
	 4*7;
}

int game_object::size()
{
  return base_size();
}



int game_object::move(int cx, int cy, int button)
{  
  int ret=0;

  if (figures[otype]->get_fun(OFUN_MOVER))      // is a lisp move function defined?
  {
    void *lcx,*lcy,*lb;

    game_object *o=current_object;
    current_object=this;


    // make a list of the parameters, and call the lisp function
    lcx=new_cons_cell();
    l_ptr_stack.push(&lcx);
    ((cons_cell *)lcx)->car=new_lisp_number(cx);

    lcy=new_cons_cell();
    l_ptr_stack.push(&lcy);
    ((cons_cell *)lcy)->car=new_lisp_number(cy);

    lb=new_cons_cell();
    l_ptr_stack.push(&lb);
    ((cons_cell *)lb)->car=new_lisp_number(button);


    ((cons_cell *)lcx)->cdr=lcy;
    ((cons_cell *)lcy)->cdr=lb;

    void *m=mark_heap(TMP_SPACE);

    time_marker *prof1;
    if (profiling())
      prof1=new time_marker;

    void *r=eval_function((lisp_symbol *)figures[otype]->get_fun(OFUN_MOVER),
			  (void *)lcx);
    if (profiling())
    {
      time_marker now;
      profile_add_time(this->otype,now.diff_time(prof1));
      delete prof1;
    }

    restore_heap(m,TMP_SPACE);

    l_ptr_stack.pop(3);
    if (item_type(r)!=L_NUMBER)
    {
      lprint(r);
      lbreak("Object %s did not return a number from it's mover function!\n"
	     "It should return a number to indicate it's blocked status to the\n"
	     "ai function.",object_names[otype]);	
    }
    ret|=lnumber_value(r);
    current_object=o;
  }
  else ret|=mover(cx,cy,button);    

  return ret;
}

int game_object::mover(int cx, int cy, int button)  // return false if the route is blocked
{
  if (hp()<=0) 
    return tick();

  if (flinch_state(state))                    // flinching? don't move
    cx=cy=button=0;
    
 
  if (cx)          // x movement suggested?
  {      
    if (state==stopped)   // see if started moving
    {   
      if (has_sequence(running))
      {
	if (cx>0)
	{
	  direction=1;
          set_xvel(get_ability(type(),run_top_speed));
	}
	else
	{
	  direction=-1;
	  set_xacel(-get_ability(type(),run_top_speed));
	}
	set_state(running);
      }
    } else if (state==run_jump || state==run_jump_fall)
    {
      if (cx>0)
      {
	direction=1;
	set_xacel(get_ability(type(),start_accel));           // set the appropriate accel
      } else
      {
	direction=-1;
	set_xacel(-get_ability(type(),start_accel));           // set the appropriate accel
      }	  
    }
    else 
    {
      // turn him around if he pressed the other way, he is not walking so a fast turn
      // is needed, don't go through the turn sequence
      if ((cx>0  && direction<0) || (cx<0 && direction>0))
        direction=-direction;      
    } 
  }         // not pressing left or right, so slow down or stop
  else if (!gravity() && state!=start_run_jump)
  {    
    long stop_acel;
    if (xvel()<0)                                    // he was going left
    {
      stop_acel=get_ability(type(),stop_accel);    // find out how fast he can slow down
      if (xvel()+stop_acel>=0)                       // if this acceleration is enough to stop him
      {
	stop_x();
	if (!gravity())	
	  set_state(stopped);      
      } else { set_xacel(stop_acel); }
    } else if (xvel()>0)
    {
      stop_acel=-get_ability(type(),stop_accel);
      if (xvel()+stop_acel<=0)
      {
	stop_x();
	if (!gravity())
	  set_state(stopped);
      } else set_xacel(stop_acel);
    } else if (!gravity())                // don't stop in the air
    { 
      set_xacel(0);
      set_fxacel(0);
      // Check to see if we should go to stop state 
      if (state==running)
        set_state(stopped);
    }   
  }    
  

/*  if (state==still_jump || state==still_jump_fall || state==end_still_jump)
  {
    set_xacel(0);
    set_fxacel(0);
    if (xvel()>0) set_xvel(get_ability(type(),jump_top_speed));
    else if (xvel()<0) set_xvel(-get_ability(type(),jump_top_speed));
  } else if (state==run_jump || state==run_jump_fall || state==end_run_jump)
  {
    set_xacel(0);
    set_fxacel(0);
    if (xvel()>0) set_xvel(get_ability(type(),jump_top_speed));
    else if (xvel()<0) set_xvel(-get_ability(type(),jump_top_speed));
  } */
  
  // see if the user said to jump
  if (cy<0 && !floating() && !gravity())
  {
    set_gravity(1);      
    set_yvel(get_ability(type(),jump_yvel));
//    if (cx && has_sequence(run_jump))
      set_state(run_jump);
    if (xvel()!=0)
      if (direction>0)
        set_xvel(get_ability(type(),jump_top_speed));
      else
        set_xvel(-get_ability(type(),jump_top_speed));
    set_xacel(0);


/*    if (state==stopped)  
    {
      if (cx && has_sequence(start_run_jump))
        set_state(start_run_jump);
      else if (has_sequence(start_still_jump))
        set_state(start_still_jump);                
      else 
      {

      }
    }
    else if (state==running && has_sequence(run_jump))
    {
      set_state(run_jump);
      set_yvel(get_ability(type(),jump_yvel));
      set_gravity(1);      
    }    
    else if (state==walking || state==turn_around)  // if walking check to see if has a
    {
      if (has_sequence(start_still_jump))
        set_state(start_still_jump);    
      else
      {
        set_yvel(get_ability(type(),jump_yvel));
        set_gravity(1);      
        if (has_sequence(run_jump))
          set_state(run_jump);
      }
    }    */
  }



  if (state==run_jump && yvel()>0)
    set_state(run_jump_fall);
    


//  if (state!=end_still_jump && state!=end_run_jump)
  {    
    if (cx>0)
      set_xacel(get_ability(type(),start_accel));
    else if (cx<0)
      set_xacel(-get_ability(type(),start_accel));
  }  
  
  // make sure they are not going faster than their maximum speed
  int top_speed;
  if (state==stopped || state==end_run_jump)
    top_speed=get_ability(type(),walk_top_speed);
  else if (state==running)
    top_speed=get_ability(type(),run_top_speed);    
  else if (state==run_jump || state==run_jump_fall || state==start_run_jump)
  {
    top_speed=get_ability(type(),jump_top_speed);      
    if (!cx) top_speed=0;
  }
  else top_speed=1000;
    
      
  if (abs(xvel()+xacel())>top_speed)
  {    
    if (xacel()<0) set_xacel(-top_speed-xvel());
    else set_xacel(top_speed-xvel());
  }  
    
  character_state old_state=state;
  int old_frame=current_frame;    
  int tick_stat=tick();

  // if he started to jump and slammed into a wall then make sure he stays in this state
  // so he can finish the jump
  if (!tick_stat && (old_state==start_run_jump))
  {
    set_state(old_state);
    current_frame=old_frame;
    next_picture();    
  }
    
  return tick_stat;
    
}




game_object *game_object::bmove(int &whit, game_object *exclude)
{

  // first let's move the guy acording to his physics
  long xa=xacel(),ya=yacel(),fxa=sfxacel(),fya=sfyacel();
  if (xa || ya || fxa || fya)
  {
    int fxv=sfxvel(),fyv=sfyvel();
    fxv+=fxa;  fyv+=fya;    
    long xv=xvel()+xa+(fxv>>8); 
    set_xvel(xvel()+xa+(fxv>>8));
    set_yvel(yvel()+ya+(fyv>>8));
    set_fxvel(fxv&0xff);
    set_fyvel(fyv&0xff);
  }
  
  long ox2,oy2;

  long nx=x+xvel(),nfx=fx()+fxvel(),ny=y+yvel(),nfy=fy()+fyvel();
  nx+=nfx>>8;
  ny+=nfy>>8;
 

  // check to see if this advancement causes him to collide with objects
  ox2=nx;
  oy2=ny;  // save the correct veloicties
  
  if (nx==x)
    current_level->vforeground_intersect(x,y,ny); 
  else
    current_level->foreground_intersect(x,y,nx,ny);  // first see how far we can travel
  game_object *ret=current_level->all_boundary_setback(exclude,x,y,nx,ny);
  x=nx;
  y=ny;
  set_fx(nfx&0xff);
  set_fy(nfy&0xff);
  if (ret)
  {
    if (!ret->hurtable())   // object is not hurtable, return as if hit wall.
    { whit=1;
      return NULL;
    } else
    return ret;
  }
  else
  {
    whit=(nx!=ox2 || ny!=oy2);
    return NULL;
  }
}



int object_to_number_in_list(game_object *who, object_node *list)
{
  int x=1;
  while (list) 
  { 
    if (who==list->me) return x; 
    else list=list->next;
    x++;
  }
  return 0;
}

game_object *number_to_object_in_list(long x, object_node *list)
{
  if (!x) return NULL; x--;
  while (x && list) { list=list->next; x--; }
  if (list) return list->me;
  else return NULL;
}


void delete_object_list(object_node *first)
{
  while (first)
  {
    object_node *n=first;
    first=first->next;
    delete n;
  }
}


long object_list_length(object_node *list)
{
  long x=0;
  while (list) { list=list->next; x++; }
  return x;
  
}



game_object::game_object(int Type, int load) 
{ 
  if (Type<0xffff)
  {
    int t=figures[Type]->tv;
    if (t)
    {
      lvars=(long *)jmalloc(t*4,"object vars");
      memset(lvars,0,t*4);
    }
    else lvars=NULL;
  } else lvars=NULL;

  otype=Type; 
  if (!load) defaults(); 
}


int game_object::reduced_state()
{
  long x=0;
  for (int i=0;i<figures[otype]->ts;i++)
  {
    if (i==state) return x;
      else
    if (figures[otype]->seq[i]) x++;
  }
  return 0;
}


void game_object::change_aitype(int new_type)
{
  set_aitype(new_type);
  if (otype<0xffff)
  {
    void *f=figures[otype]->get_fun(OFUN_CHANGE_TYPE);  
    if (f) 
    {
      game_object *o=current_object;
      current_object=(game_object *)this;

      time_marker *prof1;
      if (profiling())
        prof1=new time_marker;

      eval_user_fun((lisp_symbol *)f,NULL);

      if (profiling())
      {
	time_marker now;
	profile_add_time(this->otype,now.diff_time(prof1));
	delete prof1;
      }


      current_object=o;
    }
  }
}


void game_object::change_type(int new_type)
{
  if (lvars) jfree(lvars);     // free old variable

  if (otype<0xffff)
  {
    int t=figures[new_type]->tv;
    if (t)
    {
      lvars=(long *)jmalloc(t*4,"object vars");
      memset(lvars,0,t*4);
    }
    else lvars=NULL;
  } else return;
  otype=new_type;

  if (figures[new_type]->get_fun(OFUN_CONSTRUCTOR))
  {
    game_object *o=current_object;
    current_object=this;    

    void *m=mark_heap(TMP_SPACE);

    time_marker *prof1;
    if (profiling())
      prof1=new time_marker;

    eval_function((lisp_symbol *)figures[new_type]->get_fun(OFUN_CONSTRUCTOR),NULL);
    if (profiling())
    {
      time_marker now;
      profile_add_time(otype,now.diff_time(prof1));
      delete prof1;
    }


    restore_heap(m,TMP_SPACE);

    current_object=o;
  }  
}
