// = = = = = = = = = = = = = 
// Oddomatoid add-on
// (c) Bludgeonsoft 2019
// = = = = = = = = = = = = = 


//^_ TODO
//^_   tasks analyze
//^_ 
//^_ 
//^_ 


(function(global){ 

Addons.oddoVersion = 4;


//= = = = = = = = = = = = = = = = = = = = = = = = = = = 
///App.registerSpriteLater = function(ctor, layer, category, _registry){
///  defer(function(){
///
///    // register sprite late
///    if(App._spritesToRegister=='done'){
///      App.sprites.layer[layer].push(ctor);
///      App.sprites.categories[category||layer].push(ctor.typeName||ctor.name);
///
///      Sprite.register(ctor, function(){
///        trace("Sprite registered: "+name);
///      }, _registry);
///    }
///    else{
///      // register sprite on app start
///      App.registerSprite(ctor, layer, category);
///    }
///
///  },0);
///};
//= = = = = = = = = = = = = = = = = = = = = = = = = = = 


//^* bubble-like: (mostly clear)
//^* [0x449b6d,0x49a075,0x9eb54c,0x49b27f,0x5bdd8e,0x5bc1ea]
//^* [0x5e968c,0x60998e,0x6bbc72,0x68ada0,0x84d8c6,0xaab5e2]

///var PALETTE = [0x007ef4,0xf16bc8,0x90a5f7,0xff7edc,0xffa8d9,0xdec849];
///var PALETTE = [0xff27f0,0xff3cff,0xa89dff,0xff5bff,0xffafff,0xff9c56];
///var PALETTE = [0xccf818e8,0xfa34e5,0xa5a0ff,0xf5ff5cef,0xffafff,0xffa05b];
// v1
//var PALETTE = [0xaaf818e8,0xfa34e5,0xa5a0ff,0x99ff5cef,0xffafff,0xffa05b];

var PALETTE = [0xaaf818e8,0xfa34e5,0xa5a0ff,0x99ff5cef,0xffafff,0xffa05b];



//var BREAK_PALETTE = [0x0, 0xffffff, 0xff5cef];
var BREAK_PALETTE = [0xee00cc, 0xffafff, 0xff5cef];
var IMAGE = "~~~~~~~~~~~~~~~~~~~~~~~~00000000~~~~~~0000~~~~~~~~~~~~~~~~~~000000000000~~~~~~~~0~~~~~~~~~~~~~~00000~~~~~~~~~~~~0~~~~~~~~~~~~~~0|~~~~~~0000~~~~~~~~~~~00011111111~~~~00111100~~~~~~~~~~~~~000111111111111000~~~~~1000~~~~~~~~00011111000~~~~~~~~~1000~~~~~~~~0001|~~~~00111100~~~~~~~0011122222222~~~0112222110~~~~~~~~~~0011122222222222211100~~~2111000~~0001112222211100~~~~~~~2111000~~0001112|~~~0112222110~~~~~01122233333232~~012233232210~~~~~~~~011222233222323333222110~~22221110011122222333222110~~~~~~2222111001112222|~~012233322240~~~012233333222222~~012333222240~~~~~~~01222232222222222333332240~323322211222332322222332210~~~~~3233222112223323|~~012333222240~~~012333322222222~01233322222240~~~~~012333225222222222223333240~2233332222333322222222223210~~~~2233332222333322|~01233325222240~0123333222222222~01233322222240~~~~0123333222222222222222333324022233333333332222225222222240~~~2223333333333222|~01233252525240~0123322222222222~01233222222240~~~012333332222222222222222233240222223333332222222222222222240~~2222233333322222|~01232225222240~01222222222522220123322222222240~~012333322222222225222222222240222222222222252222222222222240~~2222222222222522|~01222222232240~01232222225252220123322222222240~01223332222222222555222222232402225222222222222222222222222240~2222222222222222|~~012252232240~~~0122222222522220123222252522240~012222222222522222522222222240~2222222222222222222522222222240~2222222222222222|~~012222222240~~~0122222222222220123222225222240~012322222225252222222222222240~2222222442222222225552222222240~2222222222222222|~~~0442222440~~~~~014222222222220123222252522240012222522222252222222222222440~~222244400444222222252222252222402225222225222222|~~~~00444400~~~~~~~0044422222222012222222222224001232222222222222222222244400~~~2444000~~000444222222222222222402222222255522222|~~~~~~0000~~~~~~~~~~~000444444440123222222222240012322222222222244444444000~~~~~4000~~~~~~~~000422222222222222402222222225222222|~~~~~~~~~~~~~~~~~~~~~~~~000000000122222222222240012222222222222200000000~~~~~~~~0~~~~~~~~~~~~~~022222222222222402222222222222222|01222222222222400122222222222222012232222222224001223222222222222222222222222240222222222222222222222222222222402222222222222222|01222222222222400122222222222222~01222222222240~~0122222222222222222222222222240222222222222222222222222222222402222222222222222|01222222222222400123222222222222~01233225222240~~0123322522222222222222222222240222222222222222222222222522222402222222222222222|01222222252222400123325222222222~01233322222240~~0123332222222222252222222222240222222522222222222222222222222402222222522222222|0123222255522240~012332222252222~~012332222240~~~~01233222222222252522222252240~222225252222222222222222222222402222222222222222|0123222225222240~012333222222222~~012333222240~~~~01233322222522225222222222240~222222522222222222222222222222402222222222222222|0123322222222240~012333222222222~~012233222240~~~~01223322225252222222222222240~2222222222225222222222222223240~2222222222222222|0123322222222240~~01233222222222~~~0123322240~~~~~~012332222252222222222223240~~2222222222222222222225252222240~2222222222222222|~01233222222240~~~01222222222222~~~0123322240~~~~~~012332222222222222222233240~~2222222222222222222222522223240~2222222252222222|~01233322222240~~~~0122322225222~~012233222240~~~~012233222222222222222233240~~~2252222222222222222225252223240~2222222525222222|~01233322222240~~~~~012222222222~~012333222240~~~~01233322222222222252233240~~~~222222222222222222222222222222402222222252222222|~~012333222240~~~~~~~04222222222~~012332222240~~~~0123322222222222222222240~~~~~222222244222222222222222222222402222222222225222|~~012233222240~~~~~~~~0442222222~01233322252240~~0123332222252222222222440~~~~~~222244400444222222222222522222402252222222222222|~~~0442222440~~~~~~~~~~004442222~01233222222240~~012332222222222222244400~~~~~~~2444000~~000444222222222222222402222222222222222|~~~~00444400~~~~~~~~~~~~~0004444~01222222222240~~0122222222222224444000~~~~~~~~~4000~~~~~~~~000422222222222222402222222222222222|~~~~~~0000~~~~~~~~~~~~~~~~~~0000012232222222224001223222222222220000~~~~~~~~~~~~0~~~~~~~~~~~~~~022222222222222402222222222222222|~~~~~000000~~~~~~~~~~~~~~~~~~~00~~~~~~~~~~~~~~~~~~~~~~~~0000000000~~~~~~~~~~~~~~000000~~~~00000000000000~~~~~~~~000000~~~~000000|~~~0011111100~~~~~~~~~~~~~000011~~~~~~~00~~~~~~~~~~~~~0011111111110000~~~~~~~~~~11111100001111111111111100~~~~~~1111110000111111|~~011222222110~~~~~~~~0000111122~~~~~001100~~~~~~~~~0011222222222211110000~~~~~~2222221111222222222222221100~~~~2222221111222222|~01223333222240~~~~~001111222222~~~~01122110~~~~~~~0112222323332222222111100~~~~32333322223333232233233222110~~~3233332222333323|~01233333222240~~~~0112222333232~~~0122232210~~~~~012233322222222323332222110~~~222333333333322222222222322240~~2223333333333222|0123333222222240~~01223333222222~~~0123332240~~~~~0123333222522222222233332210~~222223333332222222252222222240~~2222233333322222|0123332522222240~~01233322222222~~012333322240~~~01233333222222222222222333210~~2222222222222222222222222222240~2222222222222222|0123325252222240~012233322225222~~012333222240~~~012333322222222222522223332210~2222222222222522222222222222240~2222222222222522|0123322522252240~012333222252522~~012322222240~~0122333222222222225552222333210~222222222222222222222222222222402222222222222222|0122222222222240~~01222222225222~~012322222240~~012222222225222222252222222210~~222222222222222222225222222222402222222222222222|0122222222232240~~01222222222222~01232222222240~012322222252522222222222222210~~222522222222222222255522222222402222222222222222|~01222522232240~~~~0142222222222~01232252522240~01222222222522222222222222410~~~222222222222222222225222222222402225222225222222|~01222222222240~~~~~004444222222~01232225222240~0123252222222222222222444400~~~~222222222222222222222222225222402222222255522222|~~044222222440~~~~~~~~0000444422~01222252522240~01232222222222222244440000~~~~~~222222444422222222222222222222402222222225222222|~~~0044444400~~~~~~~~~~~~~00004401223222222222400123222222222222440000~~~~~~~~~~444444000044444422222222222222402222222222222222|~~~~~000000~~~~~~~~~~~~~~~~~~~000122222222222240012222222222222200~~~~~~~~~~~~~~000000~~~~00000022222222222222402222222222222222|01222222222222400122222222222222012322222222224001232222222222222222222222222240222222222222222222222222222222402222222222222222|012222222222224001222222222222220122222222222240012222222222222222222222222222402222222222222222222222222222240~2222222222222222|~01222225222240~01232222222222220123222522222240012322252222222222222222222222402222222222222222222222252222240~2222222222222222|~01232252522240~01232522222222220123322222222240012332222222222222252222222222402222222222222222222222222222240~2222222222222222|~01232225222240~0123322222222222012332222222224001233222222522222252522222222240222222222222222222222222222240~~2222222522222222|~01233222222240~0123332222522222012333222222224001233322225252222225222222252240222222252222222222222222223240~~2222222222222222|~~012322222240~~0123332222222222~01233222222240~~0123322222522222222222222222240222222525222222222222222232240~~2222222222222222|~~012332222240~~0123332222222222~01233222222240~~012332222222222222222222222224022222225222222222225252222240~~~2222222522222222|~~012332222240~~~012332222222222~01233222222240~~012332222222222222222222223240~22222222222252222222522223240~~~2222225252222222|~~012333222240~~~012222222222222~01233222222240~~012332222222222222222222233240~222222222222222222252522232240~~2222222522222222|~~~0123322240~~~~~012232222252220123332222222240012333222222222222222222233240~~222522222222222222222222223240~~2222222222225222|~~~0122322240~~~~~012222222222220123322222222240012332222222222222222522332240~~222222222222222222222222222240~~2225222222222222|~~~~04422440~~~~~~~0442222222222012332222252224001233222222522222222222222440~~~2222222222222222222222222222240~2222222222222222|~~~~~004400~~~~~~~~~00442222222201232222222222400123222222222222222222224400~~~~2222224444222222222222252222240~2222222222222222|~~~~~~~00~~~~~~~~~~~~~0044444444012222222222224001222222222222224444444400~~~~~~4444440000444444222222222222240~2222222222222222|~~~~~~~~~~~~~~~~~~~~~~~~000000000123222222222240012322222222222200000000~~~~~~~~000000~~~~00000022222222222222402222222222222222|[0x000000,0xdddddd,0x999999,0xbbbbbb,0x666666,0x444444]";



//
//
//
//   * * * *  * * *    * * *    * * * *  
//   *     *  *    *   *    *   *     *  
//   *     *  *     *  *     *  *     *  
//   *     *  *    *   *    *   *     *  
//   * * * *  * * *    * * *    * * * *  
//
//
//

function Oddo(data){
  data = data||{};

  if(!data.rules || data.rules=="random"){
    var mask = "";
    for(var i=0, len=18; i<len; i++){
      mask += rnd(2);
    }
    data.rules = Oddo.rulesFromMask(mask);
  }

  this.rules = Oddo.rulesFromString(data.rules || Oddo.RULES_DEFAULT);

  ///this.currentMatches = data.matches;

  // hue and sat
  ///this.hue = Oddo.getHue(this.rules);
  ///if(this.hue){
  ///  var pal = Game.adjustPalette(PALETTE, Color.shift, this.hue);
  ///  var palPop = Game.adjustPalette(BREAK_PALETTE, Color.shift, this.hue);
  ///}
  ///this.sat = Oddo.getSat(this.rules);
  ///if(this.sat){
  ///  var pal = Game.adjustPalette(pal||PALETTE, Color.saturate, this.sat);
  ///  var palPop = Game.adjustPalette(palPop||BREAK_PALETTE, Color.saturate, this.sat);
  ///}
  ///pal = pal||PALETTE
  ///this.popPalette = palPop;
  // hue only
  this.hue = Oddo.getHueFull(this.rules);
  if(this.hue){
    this.palette = Game.adjustPalette(PALETTE, Color.shift, this.hue);
    this.popPalette = Game.adjustPalette(BREAK_PALETTE, Color.shift, this.hue);
  }
  else{
    this.palette = PALETTE;
    this.popPalette = BREAK_PALETTE;
  }



  var CANVAS = ImageCache.useBitmap("OddoAddOnBitmap", IMAGE, this.palette);
  data = setDefaults(data, {
    width:16, height:16,
    palette:this.palette,
    itemType:"none",
    animations:{
      main:{
        image:CANVAS,
        width:16, height:16, length:2,
        rate:.25,
      },
    }
  });

  Oddo._super.constructor.call(this, data);

  this.edgeType = true;
  this.floorType = null;

  if(Oddo.SNAP_TO_COLOR){
    this.tileset = "oddo";
  }
  else{
    this.tileset = "oddo_"+this.rules.string;
  }

  this.tilesets = [this.tileset];

  this.analyzable = this;
  this.edible = true;
  this.bitable = true;
  this.showsMotion = true;

  ///this.pushable = true;
  this.movable = true;
  this.heavy = false;
  this.veryHeavy = false;

  this.enemy = true;
  this.bubble = true;
  this.hearts = new Quantity(0,Oddo.HEARTS).toMax();
  this.breakable = true;

  // starting rate
  this.metabolicRate = (D(3)+2)*60;

  // zomb stuff
  if(App.zombies){
    if(!Bubble.created){
      Bubble.created = [this];
    }
    else if(D(100)<Oddo.BUBBLECHANCE){
      Bubble.created.push(this);
    }
    ///App.zombies.aggression = Math.max(
    ///  App.zombies.aggression,
    ///  Math.min(10, Bubble.created.length)
    ///);
    if(Oddo.HORDE_AGGRESSIVE){
      App.zombies.aggression++;
    }
  }

  this.noise = new Noisy(this, {
    "pip":{path:"sounds/pip2.wav", gain:1},
    "pop":{path:"sounds/pop.wav", gain:.7},//1
  });

  this.hitNoise = "fist";

  this.poop = data.poop;

  // DEBUG
  ///if(Game.time) Oddo.MADE = (Oddo.MADE||0)+1;
  ///trace("Oddo.MADE create "+Oddo.MADE,"Oddo","cyan");

}
extend(Oddo, Wall);
///extend(Oddo, Creature); // not tile sprite!

App.registerSpriteLater(Oddo, "ground");


//== == == == == == == == == == == == == == == == == == 
// * pretty good:
// s23b234 
// s3b1234567
// * alt2
// s1b12

Oddo.RULES_DEFAULT = "s12b123"; //"s1b12"; //"s2b234";// "s1b2345"; //"s345b2"; // s1b2

Oddo.BUBBLECHANCE = 100;

Oddo.HORDE_AGGRESSIVE = false;
//                           000001110000100110
// 98528 is for masks.full = 011000000011100000, rule s12b123
Oddo.STARTING_HUE_FULL = 98528; 

Oddo.STARTING_HUE = 147;
Oddo.STARTING_SAT = 44;
Oddo.MUTATION_CHANCE = 4; // 10 0

Oddo.RATE_MAX =  20 *60; // 20 10
Oddo.RATE_MIN =   5 *60; //  5  1
Oddo.RATE_RANGE =  Oddo.RATE_MAX - Oddo.RATE_MIN;

Oddo.ACTION_DELAY = 60; // 30 60 10

Oddo.SPECIESDIFF = 0; // 1

Oddo.HEARTS = 1; 

Oddo.SNAP_TO_COLOR = false;

Oddo.ATTACK_ENEMIES = !true;
Oddo.OFFGASSING = .2;

//== == == == == == == == == == == == == == == == == == 
Oddo.toBubbles = function(_tries){
  _tries = _tries||0;
  var MAX = 20;
  var created = Array.clone( Oddo.created );
  for(var i=0, len=created.length; i<len; i++){
    var m = created[i];
    if(m && !m.destroyed){
      m.selfDestruct(rnd(MAX));//
    }
  }
  Game.alarm(MAX,function(){
    if(Oddo.created.length && _tries<=5){
      Oddo.toBubbles(_tries+1);
    }
  });
  App.shake(2);
};
    ///var loc = m.getLocation();
    ///m.destroy();
    ///defer(function(loc){
    ///  Oddo.makeBubble(loc);
    ///}.bind(this, loc), (i%10)*33 );

///  while(created.length){
///    var m = created[0];
///    ///if(m.isInView()) 
///    defer(function(){
///    }.bind(this),0);
///
///    //^m.makeBubble(true);
///    //^m.destroy();
///  }

  ///Oddo.created.forEach(function(m){
  ///  //m.makeBubble(true);
  ///  //m.destroy();
  ///  if(m.isInView()){
  ///    m.onHit(App.player, {}, 100);
  ///  }
  ///});




// Conway's default rules: s23b3
// Any live cell with fewer than two live neighbours dies,
//   as if by underpopulation.
// Any live cell with two or three live neighbours lives 
//   on to the next generation.
// Any live cell with more than three live neighbours dies, 
//   as if by overpopulation.
// Any dead cell with exactly three live neighbours becomes 
//   a live cell, as if by reproduction.



//== == == == == == == == == == == == == == == == == == 
Oddo.rulesFromString = function(rules){
  if(typeof rules != "string"){
    return rules;
  }

  var parsed = rules.match(/s(\d*)b(\d*)/i);

  var rules = {
    safe:parsed[1].split("").join(","),
    birth:parsed[2].split("").join(","),
    string:rules,
  }

  rules.masks = Oddo.rulesToMask(rules);

  return rules;
};

//== == == == == == == == == == == == == == == == == == 
Oddo.rulesToString = function(rules){
  if(rules.string) return rules.string;
  return "s"+ (rules.safe.split(",").join("") ) +
         "b"+ (rules.birth.split(",").join("") );
};
///Oddo.rulesToMask = function(rules){
///  rules = Oddo.rulesFromString(rules);
///  var S = rules.safe.split(",");
///  var B = rules.birth.split(",");
///  var s = [0,0,0,0,0,0,0,0,0];
///  var b = [0,0,0,0,0,0,0,0,0];
///  for(var i=0, len=S.length; i<len; i++){
///    var index = S[i];
///    s[index] = 1;
///  }
///  for(var i=0, len=B.length; i<len; i++){
///    var index = B[i];
///    b[index] = 1;
///  }
///  return s + b;
///};
//== == == == == == == == == == == == == == == == == == 
Oddo.rulesToMask = function(rules){
  //////rules = Oddo.rulesFromString(rules);
  /** /
  var S = rules.safe.split(",").reverse(); // REVERSE
  var B = rules.birth.split(",").reverse();
  /**/
  var S = rules.safe.split(",");
  var B = rules.birth.split(",");
  /**/

  var s = [0,0,0,0,0,0,0,0,0];
  var b = [0,0,0,0,0,0,0,0,0];
  for(var i=0, len=S.length; i<len; i++){
    var index = S[i];
    s[index] = 1;
  }
  for(var i=0, len=B.length; i<len; i++){
    var index = B[i];
    b[index] = 1;
  }
  ///var safe = s.join("").split("").reverse().join("");
  var safe = s.join("");
  var birth = b.join("");
  var full = safe+birth;
  var bin = parseInt(full,2);
  return { safe:safe, birth:birth, full:full, bin:bin,
    safePercent: (parseInt(safe,2)/511),
    birthPercent: (parseInt(birth,2)/511),
  };
};
//== == == == == == == == == == == == == == == == == == 
Oddo.rulesFromMask = function(fullMask){
  ///fullMask = fullMask.split("").reverse().join(""); // REVERSE

  var rulesS = "s", rulesB = "b";
  var len=fullMask.length;
  var half=len/2;
  for(var i=0; i<len; i++){
    ///if(i<8){
    if(i<half){
      if(fullMask.charAt(i)==1) rulesS += i;
    }
    else{
      if(fullMask.charAt(i)==1) rulesB += i-half;
    }
  }
  return rulesS+rulesB;
};


//== == == == == == == == == == == == == == == == == == 
//  { safe: "1,2",
//    birth: "1,2,3",
//    string: "s12b123"
//  }
Oddo.getHueFull = function(rules){
  ///var masks = rules.masks;
  ///var h = Math.round(((masks.safePercent+masks.birthPercent)/2)*360);

  ///var h = parseInt(rules.masks.full,2) - Oddo.STARTING_HUE_FULL;
  ///h = h %360;

  var a = parseInt(rules.masks.full,2);
  if(a == Oddo.STARTING_HUE_FULL){
    return 0;
  }
  var diff = NUM.hammingDistance(a, Oddo.STARTING_HUE_FULL);
  // max diff is 18
  //var h = _R((diff/18)*360);
  // much bigger visible change
  var h = _R((diff/6)*360) % 360;

  return h;
};

Oddo.getHue = function(rules){
  var masks = rules.masks;
  var h = Math.round(masks.safePercent*360);
  return h - Oddo.STARTING_HUE;
};
Oddo.getSat = function(rules){
  var masks = rules.masks;
  var s = Math.round(masks.birthPercent*100);
  return s - Oddo.STARTING_SAT;
};
///Oddo.getHue = function(rules){
///  var masks = rules.masks;
///  var h = Math.round(((masks.safePercent+masks.birthPercent)/2)*360);
///  return h - Oddo.STARTING_HUE;
///};
// not noticeable 
///Oddo.getHue = function(rules){
///  rules = Oddo.rulesFromString(rules);
///  var S = (rules.safe+","+rules.birth).split(",");
///  var colorS = 0;
///  var len = S.length;
///  for(var i=0; i<len; i++){
///    colorS += parseInt(S[i]);
///  }
///  return ( (colorS + len) * 4 ) - Oddo.STARTING_HUE; // <-56
///};

//----------------------------------------------------- 
Oddo.prototype.serialize = function(){
  var data = Oddo._super.serialize.call(this);
  data.rules = this.rules.string;
  data.poop = this.poop;
  ///data.matches = this.currentMatches;
  return data;
};

// .................................................... setter
Object.defineProperty(Oddo.prototype, "poop", {
  get: function(){
    return this._poop;
  },
  set: function(value){
    this._poop = value;
    ///if(this._poop){
    ///  var p = Array.clone(this.palette);
    ///  p[1] = 0xbbffffff;
    ///  this.setTint(p)
    ///}
  },
});

//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.onAdded = function(){
  Oddo._super.onAdded.call(this);
  var foliage = App.getFoliageAt( this.gridLoc );
  if(foliage instanceof Fungus) this.poop = foliage.getSpore();

  var terrain = App.getTerrainAt( this.gridLoc );
  if(terrain instanceof Dirt){
    App.placeDirt(this.gridLoc, 1, true);
  }

  this.nextCycle(D(this.metabolicRate)+30);

  //this.noise.play("pip");
};
//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.nextCycle = function(delay){
  var nutrients = Math.min(100, this.getTerrain().nutrients);
  var nutrX = (100-nutrients)/100;
  this.metabolicRate = _R( (nutrX * Oddo.RATE_RANGE) + Oddo.RATE_MIN );

  this.lastCycleTime = Game.time;
  
  if(delay==null) delay = this.metabolicRate;

  this.alarm(delay, function(){
    this.metabolize();
  }.bind(this));
};

/////== == == == == == == == == == == == == == == == == == 
///Oddo.prototype.update = function(){
///
///  if(Game.time % 60 == 0){
///    this.metabolize();
///  }
///
///  Oddo._super.update.call(this);
///};

//== == == == == == == == == == == == == == == == == == metabolize
Oddo.prototype.metabolize = function(){
  // count neighbors
  var neighbors = this.countNeighbors();
  var count = neighbors.count+"";
  var rules = this.rules;

  // birth
  if(count.isIn(rules.birth)){
    this.reproduce(neighbors.mask);
  }

  // safe
  if(count.isIn(rules.safe)){
    // noop
  }
  else{
    this.die();
    return;
  }

  this.nextCycle();
};




//== == == == == == == == == == == == == == == == == == 
Oddo.isSameSpecies = function(a,b){
  if(a instanceof Oddo && b instanceof Oddo){
    var diff = NUM.hammingDistance(a.rules.masks.bin, b.rules.masks.bin);
    if(diff<=Oddo.SPECIESDIFF) return true;
  }
  return false;
};




//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.countNeighbors = function(){
  var compass = DIRS;
  var max = compass.length;
  var count=0, dirs=[];
  var loc = this.gridLoc;
  var mask = "";
  for(var i=0; i<max; i++){
    var dir = compass[i];
    var neighbor = this.layer.getSpriteAt(loc, dir);

    // match only same species
    var sameSpecies = Oddo.isSameSpecies(this,neighbor);

    ///var sameSpecies = false;
    ///if(neighbor instanceof Oddo){
    ///  var diff = NUM.hammingDistance(this.rules.masks.bin, neighbor.rules.masks.bin);
    ///  if(diff<2) sameSpecies = true;
    ///}

    if(sameSpecies){
      // spores flow through whole blob
      if(!this.poop){
        neighbor.poop;
      }
      else if(!neighbor.poop){
        neighbor.poop = this.poop;
      }

      count++;
      dirs.push(dir);
      mask+="1";
    }
    else{
      mask+="0";
    }

    ///// match only same species
    ///if(neighbor instanceof Oddo && neighbor.tileset == this.tileset){
    ///// match any species
    //////if(neighbor instanceof Oddo){
    ///  count++;
    ///  dirs.push(dir);
    ///  mask+="1";
    ///}
    ///else{
    ///  mask+="0";
    ///}
  }
  return {
    count:count, dirs:dirs, mask:mask
  };
};

//== == == == == == == == == == == == == == == == == == checkMasks
//^* == == == == == == == == == == == == == == == == == 
//^*  A . B   0 1 2  i.e. C (5) = 0x10100000
//^*  . * .   7 X 3  
//^*  . C .   6 5 4  
///Oddo.MASKS = {
///  "00010100":"NW",
///  "00001010":"north",
///  "00000101":"NE",
///  "10000010":"east",
///  "01000001":"SE",
///  "10100000":"south",
///  "01010000":"SW",
///  "00101000":"west",
///}
var MASKS = [
  ///BIN("00010100"),
  ///BIN("00001010"),
  ///BIN("00000101"),
  ///BIN("10000010"),
  ///BIN("01000001"),
  ///BIN("10100000"),
  ///BIN("01010000"),
  ///BIN("00101000"),

  // diag opposite
  ///BIN("00001000"), // NW
  ///BIN("00000100"), // north
  ///BIN("00000010"), // NE
  ///BIN("00000001"), // east
  ///BIN("10000000"), // SE
  ///BIN("01000000"), // south
  ///BIN("00100000"), // SW
  ///BIN("00010000"), // west

  // CCW neighbor
  // s12b12 lasts and clumps
  // s1b12  lasts and clumps and sparser
  BIN("00000001"), // NW
  BIN("10000000"), // north
  BIN("01000000"), // NE
  BIN("00100000"), // east
  BIN("00010000"), // SE
  BIN("00001000"), // south
  BIN("00000100"), // SW
  BIN("00000010"), // west
];
///var DIRS = [ "NW", "north", "NE", "east", "SE", "south", "SW", "west" ];
///var DIRS = [ "north","east","south","west" ];

//var DIRS = GridLayer.compass;
var DIRS = GridLayer.fullCompass;


//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.checkMasks = function(mask){
  var MASK = BIN(mask);
  var matches = [];

  for(var i=0, len = DIRS.length; i<len; i++){
    if(MASKS[i] == (MASK & MASKS[i])){
      matches.push( DIRS[i] );
    }
  }

  return matches;
};

//== == == == == == == == == == == == == == == == == == mutate
Oddo.prototype.mutate = function(){
  // no mutation
  if(D(100) > Oddo.MUTATION_CHANCE){
    return this.rules.string;
  }
  var mask = this.rules.masks.full;
  ///var index = rnd(mask.length);
  // skip index 0
  var index = rnd(mask.length-1)+1;
  var bit = mask.charAt(index)=="0"?"1":"0";
  // keep 0 as 0
  if(mask.charAt(0)=="1") mask.setCharAt(0, 0);
  mask = mask.setCharAt(index, bit);
  return Oddo.rulesFromMask(mask);
};
///Oddo.prototype.mutate = function(){
///  var mask = this.rules.masks.full;
///  var bit = rnd(2);
///  var index = rnd(mask.length);
///  mask = mask.setCharAt(index, bit);
///  return Oddo.rulesFromMask(mask);
///  ///return Array.choose(["s12b123","s1b12","s2b234","s1b2345","s345b2","s1b2"]);
///};

//== == == == == == == == == == == == == == == == == == ~reproduce
Oddo.prototype.reproduce = function(mask){
  if(this.reproducing){
    return;
  }
  this.reproducing = 0;

  var matches = this.checkMasks(mask);

  for(var i=0, len=matches.length; i<len; i++){
    var dir = matches[i];
    var loc = this.layer.locFromDirection(this.gridLoc, dir);
    if(loc){
      var groundAt = App.getGroundAt(loc);
      var isLand = !( App.getTerrainAt(loc) instanceof Water );
      var freeSpace = groundAt==null;
      ///if(!freeSpace){
      ///  if(groundAt instanceof Zombie){
      ///    var direct = this.layer.getDirection(this.gridLoc, loc);
      ///    if(direct){
      ///      groundAt.knockBack(this);
      ///      this.alarm(1,function(){
      ///        this.reproduce(mask);
      ///      }.bind(this));
      ///    }
      ///    return;
      ///  }
      ///}


      ///if( groundAt==null && 
      ///    !( App.getTerrainAt(loc) instanceof Water ) ){
      if( isLand && freeSpace){
        this.reproducing++;
        var rules = this.mutate();

        // DELAYEDACTIONS DEBUG
        if(Oddo.ACTION_DELAY>-1){
          this.alarm(Oddo.ACTION_DELAY,function(LOC){
            if(!this.destroyed){
              var groundAt = App.getGroundAt(LOC);
              if( groundAt==null && 
                  !( App.getTerrainAt(LOC) instanceof Water )
              ){
                App.add(LOC, {type:"Oddo",rules:rules});
                this.reproducing--;
              }
            }
          }.bind(this,loc));
        }
        else{
          App.add(loc, {type:"Oddo",rules:rules});
          this.reproducing--;
        }
      }
      // attack enemies
      else if(Oddo.ATTACK_ENEMIES && this.isEnemy(groundAt)){
        var damage = 10;
        if(groundAt instanceof Oddo) damage = .5;
        else if(groundAt instanceof Zombie){
          damage = D(6)==1 ? damage : 0;
        }
        var acid = {type:"Acid", attributes:{damage:damage}};
        ///App.explosionPop(groundAt.getLocation(), this.popPalette );
        App.explosionFlash(groundAt.getLocation() , this.popPalette);
        App.attack(groundAt, acid, this);

        // repro
        ///if(groundAt.destroyed){
        ///  var rules = this.mutate();
        ///  App.add(loc, {type:"Oddo",rules:rules});
        ///}

      ///  if(groundAt instanceof Zombie) trace("Oddo.ATTACK Zombie",0,"#f00");
      ///  else  trace("Oddo.ATTACK "+groundAt.name,0,"#ff0");
      
      }
    }
  }

  this.currentMatches = matches;
};

//== == == == == == == == == == == == == == == == == == ~die 
Oddo.prototype.selfDestruct = function(delay){
  if(this.selfDestructing) return;
  this.selfDestructing = true;
  this.reproducing = true;
  ///this.die(delay);
  this.alarm(delay||0,function(){
    if(this.destroyed) return;
    this.makeBubble(true, true);
    App.explosionPop(this.getLocation(), this.popPalette );
    this.noise.play("pip");
    this.destroy();
  }.bind(this));
};

//== == == == == == == == == == == == == == == == == == ~die
Oddo.prototype.die = function(delay){
  if(delay==null) delay = Oddo.ACTION_DELAY;

  var gridLoc = this.gridLoc;
  var spore = this.poop;

  // DELAYEDACTIONS
  if(delay>-1){
    this.alarm(delay,function(){
      this.noise.play("pip");
      this.makeBubble();
      App.explosionPop(this.getLocation(), this.popPalette );
      if(!this.destroyed) this.destroy();
    }.bind(this));
  }
  else{
    this.noise.play("pip");
    this.makeBubble();
    App.explosionPop(this.getLocation(), this.popPalette );
    if(!this.destroyed) this.destroy();
  }

  if(spore){
    Game.alarm(Anim.POOPDELAY+delay, function(){
      App.addFungus(gridLoc, spore);
    });
  }

};
//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.getGridLoc = function(){
  return this.gridLoc;
};
//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.destroy = function(){
  if(App.zombies){
    Array.removeItem(Bubble.created, this);
    ///if(App.zombies.aggression>0) App.zombies.aggression--;
  }
  ///Oddo.MADE--;
  ///trace("Oddo.MADE destroy "+Oddo.MADE,"Oddo","cyan");


  ///if(Oddo.created.length<1){
  ///  App.zombies.aggression = 0;
  ///}

  return Oddo._super.destroy.call(this);
};


//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.eaten = function(other, eaterPxSize, firstBite){
  this.die(0);
  return {
    nutrients:eaterPxSize*10,
    hydration:eaterPxSize,
    poop:null,
  }

};



//== == == == == == == == == == == == == == == == == == 
Oddo.prototype.isEnemy = function(other){
  if(other instanceof Zombie) return true;
  if(other instanceof Oddo && !Oddo.isSameSpecies(this,other)){
    return true;
  }
  ///if(other instanceof Oddo){
  ///  return true;
  ///}
  return false;
};

//----------------------------------------------------- 
Oddo.prototype.onHit = function(attacker, item, damage, onDemolished){
  if(attacker instanceof Zombie){
///    ///attacker.knockBack(this);
///
///    attacker.stunned = true;
///    attacker.setAggro("low");
///    attacker.target = null;
///    attacker.alarm( 60*2, function(){
///      attacker.calm = Zombie.CALM_DURATION_LONG;
///      attacker.stunned = false;
///      attacker.target = null;
///    });

  }

  this.makeBubble(true);

  ///var loc = this.getLocation();
  ///var bub = App.add({x:-100,y:-100},"Bubble");
  ///bub.setLocation(loc.x,loc.y);

  var destroyed = Oddo._super.onHit.call(this,attacker, item, damage, onDemolished);
  if(destroyed){
    this.noise.play("pop");
    if(attacker instanceof Zombie){
      App.zombies.aggression -= ZombieHorde.BUBBLE_CALMING;
    }
  }
  return null;
};

//----------------------------------------------------- 
Oddo.prototype.makeBubble = function(force, _silent){
  ///var stability = Math.max(this.rules.masks.safePercent, this.rules.masks.birthPercent);
  ///stability = .75 + (.25 * stability);
  ///stability = 1 - stability;
  if(force || Math.random() <= Oddo.OFFGASSING){
    var loc = this.getLocation();
    ///var bub = App.add({x:-100,y:-100},"Bubble");
    ///bub.setLocation(loc.x,loc.y);
    Oddo.makeBubble(loc, _silent);
  }
};
//= = = = = = = = = = = = = = = = = = = = = = = = = = = 
Oddo.makeBubble = function(loc, _silent){
  var bub = App.add({x:-100,y:-100},"Bubble");
  bub.addSilently = _silent;
  bub.setLocation(loc.x,loc.y);
  bub.getCurrentAnimation().rate = Oddo.MAKEBUBBLEGROWRATE;
};
Oddo.MAKEBUBBLEGROWRATE = .5;

//----------------------------------------------------- 
Oddo.prototype.getPreview = function(options){
  if(typeof options != "object") options = {scale:options||this.scale};
  if(!options.crop) options.crop = [0,0,1,1];
  options.fromPalette = this.palette;
  if(!this._previewPalette){
    this._previewPalette = Game.adjustPalette(this.palette, Color.lighten, 8);
    this._previewPalette = Game.adjustPalette(this._previewPalette, Color.setAlpha, 255);
  }
  options.palette = this._previewPalette;
  ///options.palette = this._previewPalette = this._previewPalette||
  ///  Game.adjustPalette(this.palette, Color.lighten, 8);
  return Oddo._super.getPreview.call(this,options);
};
//----------------------------------------------------- ~analyze
//^#  title (name)
//^#  description
//^#  stats (bars)
//^#  info (dict)
//^#  dna
//^#  brain
//^#  dev
//^#  backgroundColor
//^#  foregroundColor
//^#  preview
Oddo.prototype.analyze = function(){

  var genes = {
    safe:this.rules.masks.safePercent,
    birth:this.rules.masks.birthPercent,
  };

  ///var description = this.rules.masks.full.mask;

  var neighb = this.countNeighbors();
  ///var dirs = neighb.dirs.join(", ");
  ///dirs = dirs.replace(/([a-z])[a-z][^,]+/g,"$1").toUpperCase();
  ///var matches = neighb.count+": "+dirs;
  //////var matches = this.currentMatches||"...";
  var matches = neighb.count

  var cycle = (Game.time - (this.lastCycleTime||0)) / this.metabolicRate;
  cycle = _R(cycle * 100);

  var data = {
    //palette:,
    title: this.makeSpeciesName(),//TEXT("Oddo"),
    description: this.getSpeciesDescription(),
    stats: dict([ // percentage bars
      "cycle", (new Quantity(0,100,cycle)),
    ]),
    info: dict([ // name value pairs
      "rules",   this.rules.string,
      "nutrients", this.getTerrain().nutrients,
      "matches", matches,
      "metabolic rate",this.metabolicRate,
      "binary",this.rules.masks.full,
    ]),
    ///// dna
    ///dna:dict([
    ///  "safe",  {name:"safo",  value:genes.safe, abbrev:"sa"},
    ///  "birth", {name:"biro",  value:genes.birth, abbrev:"bi"},
    ///]),
    //backgroundColor:this.palette[3],
    backgroundColor:Color(this.palette[3]).saturation(100).lightness(70),
    ///foregroundColor:this.palette[4],
    ///foregroundColor:this.palette[0],
    exactColor:true,
  }
  return data;
};

//----------------------------------------------------- 
Oddo.prototype.makeSpeciesName = function(){
  return this.speciesName;///this.speciesName = Oddo.makeSpeciesName(this.rules);
};
//----------------------------------------------------- 
Oddo.prototype.getSpeciesName = function(){
  return this.speciesName;
};
// .................................................... setter
Object.defineProperty(Oddo.prototype, "speciesName", {
  get: function(){
    if(this._speciesName){
      return this._speciesName;
    }
    else{
      return this._speciesName = Oddo.makeSpeciesName(this.rules);
    }
  },
});

//----------------------------------------------------- 
Oddo.prototype.getSpeciesDescription = function(){
  ///return this.rules.masks.full;
  var survives = "1,3,5";
  var births = "1";
  return TEXT(Oddo.DESCRIPTION_TEMPLATE).sub(survives, births);

};
// "%s is a very stable and fairly invasive species of oddomatoid."
Oddo.DESCRIPTION_TEMPLATE = "This oddomatoid will survive to the next metabolic cycle if it has %s neighbors, and will give birth if it has %s neighbors.";


/////----------------------------------------------------- ~makeSpeciesName
///Oddo.prototype.getSpeciesDescription = function(){
///  return this.makeSpeciesName();
///};


//== == == == == == == == == == == == == == == == == == 
///Oddo.NAME_PARTS = "zero,one,two,three,four,five,six,seven,eight";
///Oddo.NAME_PARTS = "zo,o,to,ti,fo,fi,si,so,e";
Oddo.NAME_PARTS = "zo,xo,to,ti,fo,fi,si,so,xe";
Oddo.NAME_PARTS = Oddo.NAME_PARTS.split(",");

///Oddo.DECLENSION = "a ae ae ae am arum as er i ibus ii is is ius o or orum os ua um um um us us us uum".split(" ");
///Oddo.DECLENSION = "da dae dam dum das der di dus dii dis dius do dor dos dua dus duum".split(" ");
Oddo.DECLENSION = "dda ddae ddam ddum ddas dder ddi ddus ddii ddis ddius ddo ddor ddos ddua ddus dduum".split(" ");
///Oddo.DECLENSION = "xae xam xum xas xer xi xus xii xis xius xo xor xos xua xus xuum".split(" ");
///Oddo.DECLENSION = "xak xam xum x xer xi xus xap xxo xius xoo xor xoss xiuk xus xuum".split(" ");

Oddo.makeSpeciesName = function(rules){

  var S = rules.safe.split(",");
  var B = rules.birth.split(",");

  var a = parseInt(rules.masks.safe,2) % Oddo.DECLENSION.length;
  var suffixA = Oddo.DECLENSION[a];
  var b = parseInt(rules.masks.birth,2) % Oddo.DECLENSION.length;
  var suffixB = Oddo.DECLENSION[b];

  var name = "";

  for(var i=0, len=S.length; i<len; i++){
    var index = S[i];
    name += Oddo.NAME_PARTS[index].substr(0,2);
  }
  name = name.capitalize();
  name += suffixA + " ";
  for(var i=0, len=B.length; i<len; i++){
    var index = B[i];
    name += Oddo.NAME_PARTS[index].substr(0,2);
  }
  name += suffixB;
  return name;
};

























//   * * *  * * *   * * *    * * *  * * *   * * *  * * * *  * * *   
//   *   *  *    *  *    *   *   *  *    *  *   *  *  *  *  *    *  
//   *   *  *     * *     *  *   *  * * *   *   *  *  *  *  * * *   
//   *   *  *    *  *    *   *   *  *    *  *   *  *  *  *  *    *  
//   * * *  * * *   * * *    * * *  * * *   * * *  *     *  * * *   



/////----------------------------------------------------- 
///Oddo.prototype.getDescription = function(raw){
///  if(!this._description){
///    this._rawDescription = TEXT("Oddo<p>This is a oddo living automaton.</p>");
///    this._description = this._rawDescription.replace(/.*<p>(.+)<\/p>.*/,"$1");
///    ///if(!this._description) this._description = this._rawDescription;
///  }
///  return raw?this._rawDescription:this._description;
///};



var BOMBPALETTE = [0xb924ea,0xca68fe,0xf075ff,0xacc49e,0x7ef782,0x93ceff];
  ///[0xb924ea,0xca68fe,0xf075ff,0xacc49e,0xfebcff,0xceddc4]


// OddoBomb
//~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  
function OddoBomb(data){
  data = setDefaults(data, {
    item:"OddoBomb",
    palette:BOMBPALETTE,
    width:16, height:16,
    animations:{
      main:{ image:"images/waterbomb.png", offset:0, length:1, },
    },
  });
  this.shadowWidth = 6;
  this.floorLevel = 2;


  OddoBomb._super.constructor.call(this, data);
}
extend(OddoBomb, ItemSprite);
App.registerSpriteLater(OddoBomb, "items");

OddoBomb.rarity = "extremelyrare";

//~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  
function OddoBombItem(_data){
  var data = { type:"OddoBomb",
    actions:[ "Detonate>Use", "Drop"],
    tooltip:TEXT("Oddo bomb<p>Use with caution.</p>"),
  };
  if(_data) data = setDefaults(_data, data);
  InventoryItem.call(this, data);
}
extend(OddoBombItem, InventoryItem);
InventoryItem.register("OddoBomb", OddoBombItem);
//----------------------------------------------------- 
OddoBombItem.prototype.getIcon = function(){
 return { images:["images/waterbomb.png"], 
          attributes:{reach:0},
          palette:BOMBPALETTE }
};

App.onEvent("interact",function(item, action, other, loc, mode){
  if(mode!="bump" && item.type == "OddoBomb"){
    App.player.explodeItem(item);
    OddoBomb.explode(loc, 4); // 3
  }
});
//----------------------------------------------------- ~island
App.explode = function(centerLoc, size, fn){
  centerLoc = centerLoc||App.player.gridLoc;
  size = size||5;
  var i = 0, T = 8;
  // ~ ~ ~ spiral
  GridLayer.spiralSearch(centerLoc, function(thisLoc){
    var dist = Game.distance(centerLoc, thisLoc);
    if(dist>size) return;


    var t = dist*T;
    Game.alarm(t, function(centerLoc, thisLoc){

      fn(centerLoc, thisLoc, dist, size);

    }.bind(this, centerLoc, thisLoc));


  }, size);
};
//~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  
OddoBomb.explode = function(centerLoc, size){
  var rules = [];
  for(var n=0; n<1; n++){
    var mask = "";
    for(var i=0, len=18; i<len; i++){
      mask += rnd(2);
    }
    rules.push(Oddo.rulesFromMask(mask));
  }

  App.explode(centerLoc, size, function(centerLoc, thisLoc, dist, size){
    var strength = ((size-dist) / size) * 100;
    if(D(100)<=strength){
      App.add(thisLoc, {type:"Oddo",rules:Array.choose(rules) });
      var LOC = Layer.fromGrid(thisLoc);
      if(D(3)==1) Oddo.makeBubble(LOC);

      App.explosion(LOC, App.palette.waterBombSplash);
    }
  });
};


///if(Recipe.collection){
///  Recipe.collection.basic.push(
///    "OddoBomb       = WaterBottle + WaterPile + RockPile + Tire",
///  );
///}
///else{
///  Recipe.book.push(
///    "OddoBomb       = WaterBottle + WaterPile + RockPile + Tire",
///  );
///}

Recipe.addRecipes([
  "-oddomatoid> OddoBomb = BubbleBottle + Tire + Tire + RockPile",
]);


if(App.ui && App.ui._buildDialog){
  App.ui._buildDialog = App.ui._buildDialog.destroy();
}
















// 
// 
// 
// 
// 
// 
// Controller
//~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  

/// blue screen 
/// pink screen [0x4a6b68,0xf15258,0xe492ff,0xc7d4d3]

window.Controller = MakeItem({
  item:"Controller",
  palette: App.addPalette("controller",[0x4a6b68,0xf15258,0x30cbe9,0xc7d4d3]),
  animations:{
    main:{ image:"images/controller.png", offset:0, length:1, },
    take:{ image:"images/controller.png" },
  },
  canPauseOffscreen: true,
  shadowWidth: 6,
  shadowHeight:2,
  shadowLeft: 5,
  shadowTop: 4,
  floorLevel: 3,
  static:{USE_DELAY:20},
  ///proto:{},
  inventoryItem:{
    type:"Controller",
    rarity:"extremelyrare",
    actions:["Use","Drop"],
    icon:{ images:["images/controller.png?crop=[0,0,16]"],
           palette:App.palette.controller },
    tooltip:TEXT("Controller<p>Its warranty has expired.</p>"),
    stackable:false,
    recipe:"-oddomatoid> Controller = BubbleBottle + Device|Recorder|Talkie + Recorder|Device|Talkie + RockPile",
    onUse:function(item, loc, mode, other){
      trace("Controller onUse");
      if(this.inUse){
        App.noise.play("deedee");
      }
      else{
        this.inUse = true;
        App.noise.play("deedee");
        App.player.jump();
        Game.alarm(Controller.USE_DELAY, function(){
          Oddo.toBubbles();
          this.inUse = false;
        }.bind(this));
      }
    }
  }
});






///
///// 
///// 
///// 
///// 
///// 
///// 
///// Controller
/////~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  
///function Controller(data){
///  var image = (data&&data.image)?data.image:"images/controller.png";
///  data = setDefaults(data, {
///    item:"Controller",
///    palette:App.palette.controller,
///    image:image,
///    animations:{
///      // resing on floor
///      main:{
///        image:image,
///        type:Animation.MANUAL
///      },
///      // tossed in the air
///      take:{
///        image:image, offset:9, length:8,
///        rate:.25, type:Animation.LOOP
///      },
///      digup:{
///        image:image, offset:9, length:8,
///        rate:.25, type:Animation.LOOP
///      },
///      land:{
///        image:image, offset:9, length:2,
///        rate:.25, type:Animation.ONCE
///      },
///      use:{
///        image:image, offset:1, length:8, type:Animation.ONCE,
///        //rate:1, delays: [1,1,1,1,1,1,1,4]
///        rate:Controller.rate, delays: Controller.delays
///      },
///      consume:null,
///    }
///  });
///  this.junk = false;
///  Controller._super.constructor.call(this, data);
///  //this.useOffset = point(-6,-2);
///  this.useOffset = Controller.useOffset;
///}
///Controller.delays=[5,1,1,1,1,1,1,15];
///Controller.rate=.5;
///Controller.useOffset=point(-8,0);
///Controller.bubbleOffset=point(-9,-4);
///Controller.makeBubbleDelay = 40;
///
///extend(Controller, ItemSprite);
///App.registerSprite(Controller, 'items');
/////----------------------------------------------------- 
///Controller.prototype.chooseFrame = function(){
///  this.setFrame( 0 );
///};
/////~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  
///InventoryItem.build({ 
///  type:"Controller",
///  actions:[ "Use", "Drop"],
///  icon:{ images:["images/controller.png"],
///         palette:App.palette.controller },
///  canUsePaused:false, stackable:false,
///  tooltip:TEXT("Controller<p>Its warranty has expired.</p>"),
///});
/////----------------------------------------------------- 
///Controller.bubblePulse = function(loc){
///  if(this.inUse){
///    App.noise.play("deedee");
///  }
///  else{
///    this.inUse = true;
///    Game.alarm(60, function(){
///      Oddo.toBubbles();
///      this.inUse = false;
///    }.bind(this));
///  }
///};
///
///App.addPalettes({
///  controller:[0x4a6b68,0xf15258,0x30cbe9,0xc7d4d3]
///});
///
///


































global.Oddo = Oddo;
//^_ == == == == == == == == == == == == == == == == ==
trace("Oddomatoid LOADED!","addon"); }(window));

