function rand(max) {
  return Math.floor(max*Math.random());
}

function lim(x,low,high) {
  if (low != null && x<=low) return low;
  if (high != null && x>=high) return high;
  return x;
}

function calc_next_level_xp(lev) {
  return 4+Math.ceil(Math.pow(2.3,lev));
}

//a is attacker
//d is defender
function calc_melee(a,d) {
  var ret={ miss: true, damage: 0 };

  //calculate armour class
  var ac=d.ac;
  for (var i in d.worn) {
    var o=d.worn[i];
    if (o.ac) {
      ac += o.ac;
      if (o.state.mod) ac += o.state.mod;
    }
  }

  var miss= lim(ac*2.5,0,60);  //max 60% chance of miss
  miss+=lim((d.dexterity - a.dexterity) * 3, -2, 20); //dexterity adds another max +-20% chance of miss, 60% total

  if (d.state.invisible) {
    //invisibility adds 20% chance of miss;
  }
  
  if (rand(100) >= miss) {
    ret.miss=false;
  } else {
    ret.miss=true;
    return ret;
  }
  
  var dam;
  if (a.worn.right_hand) {
    //use damage from weapon, including modifier
    dam=broll(a.worn.right_hand.dc);
    dam.add += a.worn.right_hand.state.mod;
  } else {
    //intrinsic (usually bare handed) combat
    dam=broll(a.dc);
  }
  if (a.mod && a.mod.plus_melee)
    dam.add += a.mod.plus_melee;

  ret.dam=dam;

  var damage=roll(dam);

  //strength over 15 will increase damage by up to 30%
  //TODO this is especially effective with bashing weapons
  damage *= 1+ ((lim((a.strength - 15),0,10)/10) * 0.3);
  damage=Math.ceil(damage);

  //ac will reduce damage by a certain amount
  damage -= Math.floor(rand(lim(ac-10,0,40))/3);

  damage=lim(damage,0,null);

  //damage can get reduced by other modifiers as well
  ret.damage=damage;
  return ret;
}


function broll(s) {
  var re = /^([0-9]+)[dD]([0-9]+)([+-]?[0-9]+)?(x([0-9]+))?$/;
  var m=re.exec(s);
  if (m==null) return null;
  
  var sum=0;
  for(var i in m) {
    var d=parseInt(m[i]);
    if (!isNaN(d)) { m[i]=d; } else { m[i]=0; }
  }
  if (!m[5] || m[5]==0) m[5]=1;

  return { times: m[1], max: m[2], add: m[3], mul: m[5] };
}

function avg_roll(s) {
  var x=s;

  if (typeof s=="string") {
    x=broll(s);
  } else if (typeof s=="number") {
    return s;
  }

  if (x==null) return 0;
  var sum=(x.max/2) * x.times;
  if (x.add) sum += x.add;
  if (x.mul) sum *= x.mul;

  return sum;
}

function roll(s) {
  var x=s;
  if (typeof s=="string") {
    x=broll(s);
  }

  if (x==null) return 0;

  var sum=0;
  for (var i=0;i<x.times;i++) {
    sum += Math.floor(x.max*Math.random())+1;
  }
  if (x.add) sum += x.add;
  if (x.mul) sum *= x.mul;
  return sum;
}

function dplot(x,y) {
  //current field of view
  di.fov["z" + x + "_" + y]=1;

  //known
  g.gm.fov["z" + x + "_" + y]=1;
  
  var o=g.gm.topTile(x,y);

  if (o==null) return true;
  if (o.sub_type=='wall') return false;

  //can see through it
  return true;
} 

function in_fov(x,y) {
  if (di.fov) {
    if (di.fov["z" + x + "_" + y])
      return true;
    
    return false;
  }
  return true;
}  

function calc_fov(xm,ym,xoffset,yoffset) {
  //  if (!di.fov) di.fov=new Object();

  di.fov=new Object();

  var x_high=xm+xoffset+2;
  var x_low=xm-(xoffset+2);


  var y_high=ym+(yoffset+2);
  var y_low=ym-(yoffset+2);
  /*
  var x_high=lim(xm+xoffset+2,0,g.gm.ar_width);
  var y_high=lim(ym+yoffset+2,0,g.gm.ar_height);

  var x_low=lim(xm-xoffset,0,g.gm.ar_width);
  var y_low=lim(ym-yoffset,0,g.gm.ar_height);
  */
	 
  for (var x= x_low; x <= x_high; x++) {
    dline2(xm,ym,x,y_low);
    dline2(xm,ym,x,y_high);

    dline2(xm,ym,x,y_low+1);
    dline2(xm,ym,x,y_high-1);
  }

  for (var y= y_low; y <= y_high; y++) {
    dline2(xm,ym,x_low,y);
    dline2(xm,ym,x_high,y);
    dline2(xm,ym,x_low+1,y);
    dline2(xm,ym,x_high-1,y);
  }
}

function dline2( x1,
	        y1,
	        x2,
	        y2)
{
  var delta_x = Math.abs(x2 - x1) << 1;
  var delta_y = Math.abs(y2 - y1) << 1;

  // if x1 == x2 or y1 == y2, then it does not matter what we set here
  var ix = (x2 > x1  ? 1 : -1);
  var iy = (y2 > y1  ? 1 : -1) ;

  if (!dplot(x1, y1)) return;

  if (delta_x >= delta_y)
    {
      // error may go below zero
      var error = delta_y - (delta_x >> 1);

      while (x1 != x2) {
	  if (error >= 0)
            {
	      if (error > 0 || (ix > 0))
                {
		  y1 += iy;
		  error -= delta_x;
                }
	      // else do nothing
            }
	  // else do nothing

	  x1 += ix;
	  error += delta_y;
	  if (!dplot(x1, y1)) return;
        }
    }
  else
    {
      // error may go below zero
      var error = delta_x - (delta_y >> 2);

      while (y1 != y2)
        {
	  if (error >= 0)
            {
	      if (error > 0 || (iy > 0))
                {
		  x1 += ix;
		  error -= delta_y;
                }
	      // else do nothing
            }
	  // else do nothing

	  y1 += iy;
	  error += delta_x;

	  if (!dplot(x1, y1)) return;
        }
    }
}

function plot(x,y) {
  print(x + "," + y);
  return true;
}

function dline(x0, y0,x1,y1) {
  var steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
  if (steep){
    // swap
    var tmp=x0;x0=y0;y0=tmp;
    tmp=x1;x1=y1;y1=tmp;
  }
  if (x0 > x1){
    // swap
    var tmp=x0;x0=x1;x1=tmp;
    tmp=y0;y0=y1;y1=tmp;
  }
     
  var deltax = x1 - x0;
  var deltay = Math.abs(y1 - y0);
  var error = 0;
  var ystep;
  var y = y0;
  if (y0<y1) ystep = 1; else ystep = -1;
  for (x=x0;x<=x1;x++){
    if (steep) {
      if (!dplot(y,x)) return;
    }  else { 
      if (!dplot(x,y)) return;
    }

    error = error + deltay;
    if (2*error >= deltax){
      y = y + ystep;
      error = error - deltax;
    }
  }
}


