打开主菜单

少前百科GFwiki β

MediaWiki:Gadget-chibiAnimation.js

莹lolo讨论 | 贡献2021年5月22日 (六) 01:01的版本 (测试)

注意:在保存之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Internet Explorer:按住Ctrl的同时单击刷新,或按Ctrl-F5
  • Opera:前往菜单 → 设置(Mac为Opera → Preferences),然后隐私和安全 → 清除浏览数据 → 缓存的图片和文件
RLQ.push(function () {
    $(document).ready(function() {
      // Maybe if the user switched the animations off, skip init?
      if (mw.user.options.get("gadget-chibiAnimation") != 1) {
        console.log("Property 'gadget-chibiAnimation != 1'.");
        return;
      }
      
      var chibiDivs = $(".tdoll_chibi,.chibiAnimationContainer");
      
      console.log("Booting up Chibi Animation Gadget. Containers found: ", chibiDivs.length);
      
      // if this page doesn't contain a chibi animation we skip the init
      if (chibiDivs.length < 1) {
        return;
      }
      
      chibiDivs.addClass("loading");
      mw.loader.using(['ext.gadget.md5hasher', 'ext.gadget.pixiLoader']).then(function() {
          window.animations.PIXILoader.init().then(function() {
              initGirl();
          }, function(x,y,z) {
              console.error("Loading Spine failed");
              chibiDivs.removeClass("loading");
          });
      }, function(x,y,z) {
          console.error("Loading needed libraries failed");
          chibiDivs.removeClass("loading");
      });
    });
  });
  
  
  var chibiDivs = $(".tdoll_chibi,.chibiAnimationContainer");
  chibiDivs.addClass("loading");
  function loaderload() {   
      mw.loader.using(['ext.gadget.md5hasher']).then(function() {
        initGirl();
      }, function() {
        console.error("Loading needed libraries failed");
      });
  };
  
  function initGirl() {
      var chibiDivs = $(".tdoll_chibi,.chibiAnimationContainer");
      console.log("Init animation...");
      chibiDivs.each(function() {
          var chibiAnimationBaseview = new BaseView(900, 900);if($(this).hasClass('bigScale')){var chibiAnimationBaseview = new BaseView(1500, 1500)};
          chibiAnimationBaseview.start();
          var currentDiv = $(this);
          currentDiv.data('baseview', chibiAnimationBaseview);
  
          var girlLoadedHandler = function () {
              currentDiv.removeClass("loading");
          };
          
          var requestedScale = currentDiv.data("chibiScale");
          
          var renderView = $(chibiAnimationBaseview.getRenderer().view);
          renderView.addClass("chibiAnimation");
  
          renderView.on('costume_changed', function(event, costumeSuffix ,tdollname) {console.log(event,'Spine Changed')
              var loadingDivs = $(event.target).closest('.tdoll_chibi,.chibiAnimationContainer');
              loadingDivs.addClass("loading");
              if(tdollname!=null){var tdollId = tdollname.replace(" ", "_")}else{var tdollId = currentDiv.data("tdollId").replace(" ", "_");};
              loadingDivs.attr('data-tdoll-costume', costumeSuffix);console.log(tdollId);
              var dormVariant = loadingDivs.find('.chibiDormSwitcher input').is(':checked');
              loadGirl(chibiAnimationBaseview, tdollId, costumeSuffix, dormVariant, girlLoadedHandler, requestedScale, changeSelect);
          });
  
          var clickArea = $("<div></div>");
          clickArea.addClass("chibiAnimationClickArea");
          clickArea.click(function(e){
              e.stopPropagation();
              chibiAnimationBaseview.nextAnimation($(this).parent().children("select.chibiAnimationSelect"));
          });
  
          var rePlayButton = $("<div></div>");
          rePlayButton.addClass("chibiAnimationRePlay");
          rePlayButton.click(function(){
              chibiAnimationBaseview.replay();
          });
          
          var changeSelect = $("<select></select>");
          changeSelect.addClass("chibiAnimationSelect");
          if(currentDiv.hasClass('selectControl')) changeSelect.css("display","unset");
          else changeSelect.css("display","none");
          changeSelect.change(function(e){
              chibiAnimationBaseview.selectAnimation(this.value);
          });
            
          var dormSliderButton = $("<div></div>");
          dormSliderButton.addClass("chibiDormButton");
          dormSliderButton.addClass("stateoff");
          dormSliderButton.click( function() {
              var currentDormButton = $(this);
              var futureEnabledState = currentDormButton.hasClass('stateoff');
              if(futureEnabledState) {
                  currentDormButton.addClass('stateon');
                  currentDormButton.removeClass('stateoff');
              } else {
                  currentDormButton.removeClass('stateon');
                  currentDormButton.addClass('stateoff');
              }
                  
              var tdollChibiDiv = currentDormButton.closest('.tdoll_chibi,.chibiAnimationContainer');
              tdollChibiDiv.addClass("loading");
              var tdollChibiDivCostume = tdollChibiDiv.attr('data-tdoll-costume') || "";
              console.log(currentDiv,"switchDormButton");
              var tdollId = currentDiv.data("tdollId").replace(" ", "_");
              loadGirl(chibiAnimationBaseview, tdollId, tdollChibiDivCostume, futureEnabledState, girlLoadedHandler, requestedScale, changeSelect);
          });
              
          var downloadButton = $("<div></div>");
          downloadButton.addClass("chibiAnimationDownload");
          downloadButton.click(function(){
              $(this).parent(".tdoll_chibi").append("<div class=\"chibiAnimationBlock\"></div>");
              chibiAnimationBaseview.download($(this).parent().children("canvas.chibiAnimation"));
          });
         
          var animationInfo = $("<div></div>");
          animationInfo.addClass("chibiAnimationInfo");
          if(currentDiv.hasClass('selectControl')) animationInfo.css("display","unset");
          else animationInfo.css("display","none");
          animationInfo.click(function(){
              var newInfoDiv = $("<div></div>");
              newInfoDiv.addClass("chibiAnimationInfoShow");
              newInfoDiv.css({"left":(String(window.innerWidth / 2 - 150) + "px")});
  
              newInfoDiv.append("<div class=\"chibiAnimationInfoShowLine\"><div>动画名称<\/div><div>持续时间/s<\/div><div>TimeLine<\/div><\/div>");
              for(var i = 0; i < 20; i++){
                  if(!$(this).attr("data-name" + String(i))) break;
                  var html_text = "<div class=\"chibiAnimationInfoShowLine\"><div>" + $(this).attr("data-name" + String(i));
                  html_text += "<\/div><div>" + Number($(this).attr("data-duration" + String(i))).toFixed(5);
                  html_text += "<\/div><div>" + $(this).attr("data-frame" + String(i)) + "<\/div><\/div>";
                  newInfoDiv.append(html_text);
              }
  
              var newInfoDivClose = $("<div></div>");
              newInfoDivClose.addClass("chibiAnimationInfoShowClose");
              newInfoDivClose.html("×");
              newInfoDivClose.click(function(e){
                $(this).parent().parent(".tdoll_chibi").children(".chibiAnimationInfoShow").remove();
              });
  
              newInfoDiv.append(newInfoDivClose);
              animationInfo.parent().append(newInfoDiv);
          });
  
  
          currentDiv.empty();
          currentDiv.append(clickArea);
          if (currentDiv.attr("data-tdoll-hidedormbutton") != 'true') currentDiv.append(dormSliderButton);
          currentDiv.append(rePlayButton)
          currentDiv.append(animationInfo);
          currentDiv.append(downloadButton);
          currentDiv.append(changeSelect);
          currentDiv.append(renderView[0]);
          
          var screencapLink = $('<a></a>');
          screencapLink.addClass('chibiScreenshotButton');
          screencapLink.prop("href", "javascript:void(0);");
          screencapLink.prop("target", "_blank");
          screencapLink.prop("rel", "noopener");
          screencapLink.prop("download", "chibi_screenshot.png");
          screencapLink.on('click', function(e) {
            var canv = currentDiv.find('canvas')[0];
            var dataURL = canv.toDataURL('image/png');
            e.target.href = dataURL;
          });
          currentDiv.append(screencapLink);
  
          if(!currentDiv.data('tdollId')) {
              var rendererDiv = $('#cancan');
              var selectedHead = $('.commander-sim-option.head option:selected').val();
              var selectedBody = $('.commander-sim-option.body option:selected').val();
              var selectedFeet = $('.commander-sim-option.feet option:selected').val();
              var baseview = rendererDiv.data('baseview');
              window.loadCommander(rendererDiv, baseview, selectedHead, selectedBody, selectedFeet);
          }
              var tdollId = currentDiv.data("tdollId").replace(" ", "_");
          loadGirl(chibiAnimationBaseview, tdollId, "", false, girlLoadedHandler, requestedScale, changeSelect);
      });
  }
  
  function loadGirl(view, id, costumeSuffix, dormVersion, callback, requestedScale, changeSelect){
      var costumeName = id;
      costumeName = costumeName + costumeSuffix
      console.log("Loading", costumeName);
      view.clean();
      
      var sl = new SkeletonLoader("/images/", costumeName);
      
      var successHandler = function(p){
          if (p === null) {
              return;
          }
          
          view.clean();
          view.addSpinePlayer(p, requestedScale);
      
          callback();
      };
      
      var failureHandler = function(p){
          var textSample = new PIXI.Text('模型未载入', {
            "fill": "#f00000",
            "font": "20px \"Courier New\", Courier, monospace"
          });
          textSample['anchor'].set(0.5, 0.5);
          textSample.x = view.getRenderer().width / 2;
          textSample.y = view.getRenderer().height / 2;
          
          view.clean();
          view.addSprite(textSample);
          
          console.log("Failed loading T-Doll Chibi", view, id, costumeSuffix, dormVersion, p);
          
          callback();
      };
  
      sl.load(costumeName, dormVersion, successHandler, failureHandler, changeSelect);
  }
  
  window.defineVisibleParts = function(skeleton, startId, endId) {
      var startReached = false;
      var endReached = endId === null;
      for (var i=skeleton.slots.length-1; i>=0; i--) {
          var shouldBeVisible = endReached && !startReached;
          
          skeleton.slots[i].a = shouldBeVisible ? 1 : 0;
          
          if (skeleton.slots[i].name == endId) {
              endReached = true;
          }
          if (skeleton.slots[i].name == startId) {
              startReached = true;
          }
      }
  };
  
  window.parseCommanderString = function(rawData) {
      var files = rawData.split(";");
      
      var cpp = window.gfUtils.createWikiPathPart;  
      var basePath = "http://www.gfwiki.org/images/";
  
      var ret = {
          baseId: files[2],
          skel: basePath + cpp(files[0]) + files[0], 
          atlas: basePath + cpp(files[1]) + files[1], 
          png: basePath + cpp(files[2]) + files[2]
      };
      return ret;
  };
  
  window.loadCommander = function(commanderDiv, view, headRaw, bodyRaw, feetRaw){
      commanderDiv.addClass('loading');
      if (typeof view == "undefined"){}else
      {view.clean();}
      
      var sl = new SkeletonLoader(".");
      
      var headGenus = parseCommanderString(headRaw);
      
      var failureHandler = function(p){
          commanderDiv.removeClass('loading');
          
          var textSample = new PIXI.Text('模型未载入', {
            "fill": "#f00000",
            "font": "20px \"Courier New\", Courier, monospace"
          });
          textSample.anchor.set(0.5, 0.5);
          textSample.x = view.getRenderer().width / 2;
          textSample.y = view.getRenderer().height / 2;
          
          view.clean();
          view.addSprite(textSample);
          
          console.log("Failed loading Commander Chibi", view, headRaw, bodyRaw, feetRaw, p);
      };
      
      sl.load(headGenus, false, function(headSkeleton){
          if (headSkeleton == null) {
              return;
          }
          
          var feetGenus = parseCommanderString(feetRaw);
          
          sl.load(feetGenus, false, function(feetSkeleton){
              if (feetSkeleton == null) {
                  return;
              }
              
              var bodyGenus = parseCommanderString(bodyRaw);
              
              sl.load(bodyGenus, false, function(bodySkeleton){
                  if (bodySkeleton == null) {
                      return;
                  }
                  
                  defineVisibleParts(headSkeleton, null, 'FaceLayer');
                  view.addSpinePlayer(headSkeleton);
                  
                  defineVisibleParts(bodySkeleton, null, 'BodyLayer');
                  view.addSpinePlayer(bodySkeleton);
                  
                  defineVisibleParts(feetSkeleton, null, 'FootLayer');
                  view.addSpinePlayer(feetSkeleton);
                  
                  defineVisibleParts(feetSkeleton, 'FootLayer', null);
                  view.addSpinePlayer(feetSkeleton);
                  
                  defineVisibleParts(bodySkeleton, 'BodyLayer', 'RHandLayer');
                  view.addSpinePlayer(bodySkeleton);
                  
                  defineVisibleParts(headSkeleton, 'FaceLayer', null);
                  view.addSpinePlayer(headSkeleton);
                  
                  defineVisibleParts(bodySkeleton, 'RHandLayer', null);
                  view.addSpinePlayer(bodySkeleton);
                  
                  // Everything done here
                  commanderDiv.removeClass("loading");
              }, failureHandler);
          }, failureHandler);
      }, failureHandler);
  };
  
  function SkeletonBinary() {
      this.data = null;
      this.scale = 1;
      this.json = {};
      this.nextNum = 0;
      this.chars = null;
  }
  
  SkeletonBinary.prototype = {
      BlendMode : ["normal", "additive", "multiply", "screen"],
      AttachmentType : ["region", "boundingbox", "mesh", "skinnedmesh"],
  
      readByte : function(){
          return this.nextNum < this.data.length ? this.data[this.nextNum++] : null;
      },
      readBoolean : function(){
          return this.readByte() != 0;
      },
      readShort : function(){
          return (this.readByte() << 8) | this.readByte();
      },
      readInt : function(optimizePositive){
          if(typeof optimizePositive === 'undefined'){
              return (this.readByte() << 24) | (this.readByte() << 16) | (this.readByte() << 8) | this.readByte();
          }
          var b = this.readByte();
          var result = b & 0x7f;
          if ((b & 0x80) != 0){
              b = this.readByte();
              result |= (b & 0x7F) << 7;
              if ((b & 0x80) != 0){
                  b = this.readByte();
                  result |= (b & 0x7F) << 14;
                  if ((b & 0x80) != 0){
                      b = this.readByte();
                      result |= (b & 0x7F) << 21;
                      if ((b & 0x80) != 0){
                          b = this.readByte();
                          result |= (b & 0x7F) << 28;
                      }
                  }
              }
          }
          return optimizePositive ? result : ((result >> 1) ^ -(result & 1));
      },
      bytes2Float32 : function(bytes){
          var sign = (bytes & 0x80000000) ? -1 : 1;
          var exponent = ((bytes >> 23) & 0xFF) - 127;
          var significand = (bytes & ~(-1 << 23));
  
          if (exponent == 128)
              return sign * ((significand) ? Number.NaN : Number.POSITIVE_INFINITY);
  
          if (exponent == -127) {
              if (significand == 0) return sign * 0.0;
              exponent = -126;
              significand /= (1 << 22);
          } else significand = (significand | (1 << 23)) / (1 << 23);
  
          return sign * significand * Math.pow(2, exponent);
      },
      readFloat : function(){
          return this.bytes2Float32((this.readByte()<<24) + (this.readByte()<<16) + (this.readByte()<<8) + (this.readByte()<<0));
      },
      readFloatArray : function(){
          var n = this.readInt(true);
          var array = new Array(n);
          if(this.scale == 1){
              for(var i = 0; i < n; i++){
                  array[i] = this.readFloat();
              }
          }else{
              for(var i = 0; i < n; i++){
                  array[i] = this.readFloat() * this.scale;
              }
          }
          return array;
      },
      readShortArray : function(){
          var n = this.readInt(true);
          var array = new Array(n);
          for(var i = 0; i < n; i++){
              array[i] = this.readShort();
          }
          return array;
      },
      readIntArray : function(){
          var n = this.readInt(true);
          var array = new Array(n);
          for(var i = 0; i < n; i++)
              array[i] = this.readInt(true);
          return array;
      },
      readHex : function(){
          var hex = this.readByte().toString(16);
          return hex.length == 2 ? hex : '0' + hex;
      },
      readColor : function(){
          return this.readHex() + this.readHex() + this.readHex() + this.readHex();
      },
      readUtf8_slow : function(charCount, charIndex, b){
          while(true){
              switch (b >> 4){
              case 0:
              case 1:
              case 2:
              case 3:
              case 4:
              case 5:
              case 6:
              case 7:
                  this.chars += String.fromCharCode(b);
                  break;
              case 12:
              case 13:
                  this.chars += String.fromCharCode((b & 0x1F) << 6 | this.readByte() & 0x3F);
                  break;
              case 14:
                  this.chars += String.fromCharCode((b & 0x0F) << 12 | (this.readByte() & 0x3F) << 6 | this.readByte() & 0x3F);
                  break;
              }
              if (++charIndex >= charCount) break;
              b = this.readByte() & 0xFF;
          }
      },
      readString : function(){
          var charCount = this.readInt(this, true);
          switch(charCount){
          case 0:
              return null;
          case 1:
              return "";
          }
          charCount--;
          this.chars = "";
          var b = 0;
          var charIndex = 0;
          while (charIndex < charCount){
              b = this.readByte();
              if (b > 127)
                  break;
              this.chars += String.fromCharCode(b);
              charIndex++;
          }
          if (charIndex < charCount)
              this.readUtf8_slow(charCount, charIndex, b);
          return this.chars;
      },
      initJson : function(){
          this.json.skeleton = {};
          var skeleton = this.json.skeleton;
          skeleton.hash = this.readString();
          if(skeleton.hash.length == 0)
              skeleton.hash = null;
          skeleton.spine = this.readString();
          if(skeleton.spine.length == 0)
              skeleton.spine = null;
          skeleton.width = this.readFloat();
          skeleton.height = this.readFloat();
          var nonessential = this.readBoolean();
          if(nonessential){
              skeleton.images = this.readString();
              if(skeleton.images.length == 0)
                  skeleton.images = null;
          }
  
          //Bones.
          this.json.bones = new Array(this.readInt(true));
          var bones = this.json.bones;
          for(var i = 0; i < bones.length; i++){
              var boneData = {};
              boneData.name = this.readString();
              boneData.parent = null;
              var parentIndex = this.readInt(true) - 1;
              if(parentIndex != -1)
                  boneData.parent = bones[parentIndex].name;
              boneData.x = this.readFloat() * this.scale;
              boneData.y = this.readFloat() * this.scale;
              boneData.scaleX = this.readFloat();
              boneData.scaleY = this.readFloat();
              boneData.rotation = this.readFloat();
              boneData.length = this.readFloat() * this.scale;
              boneData.flipX = this.readBoolean();
              boneData.flipY = this.readBoolean();
              boneData.inheritScale = this.readBoolean();
              boneData.inheritRotation = this.readBoolean();
  
              if(nonessential){
                  boneData.color = this.readColor();
              }
              bones[i] = boneData;
          }
  
          // IK constraints.
          var ikIndex = this.readInt(true);
          if(ikIndex > 0){
              this.json.ik = new Array(this.readInt(true));
              var ik = this.json.ik;
              for(var i = 0; i < ikIndex; i++){
                  var ikConstraints = {};
                  ikConstraints.name = this.readString();
                  ikConstraints.bones = new Array(this.readInt(true));
                  for(var j = 0; j < ikConstraints.bones.length; j++){
                      ikConstraints.bones[j] = this.json.bones[this.readInt(true)].name;
                  }
                  ikConstraints.target = this.json.bones[this.readInt(true)].name;
                  ikConstraints.mix = this.readFloat();
                  ikConstraints.bendPositive = this.readBoolean();
                  // Maybe use ReadSByte();
                  ik[i] = ikConstraints;
              }
          }
  
          // Slots.
          this.json.slots = new Array(this.readInt(true));
          var slots = this.json.slots;
          for(var i = 0; i < slots.length; i++){
              var slotData = {};
              slotData.name = this.readString();
              var boneData = this.json.bones[this.readInt(true)];
              slotData.bone = boneData.name;
              slotData.color = this.readColor();
              slotData.attachment = this.readString();
              slotData.blend = this.BlendMode[this.readInt(true)];
              slots[i] = slotData;
          }
  
          // Default skin.
          this.json.skins = {};
          this.json.skinsName = new Array();
          var skins = this.json.skins;
          var defaultSkin = this.readSkin("default", nonessential);
          if(defaultSkin != null){
              skins["default"] = defaultSkin;
              this.json.skinsName.push("default");
          }
  
          // Skin.
          for(var i = 0, n = this.readInt(true); i < n; i++){
              var skinName = this.readString();
              var skin = this.readSkin(skinName, nonessential);
              skins[skinName] = skin;
              this.json.skinsName.push("skinName");
          }
  
          // Events.
          this.json.events = [];
          this.json.eventsName = [];
          var events = this.json.events;
          for(var i = 0, n = this.readInt(true); i < n; i++){
              var eventName = this.readString();
              var event = {};
              event.int = this.readInt(false);
              event.float = this.readFloat();
              event.string = this.readString();
              events[eventName] = event;
              this.json.eventsName[i] = eventName;
          }
  
          // Animations.
          this.json.animations = {};
          var animations = this.json.animations;
          for(var i = 0, n = this.readInt(true); i < n; i++){
              var animationName = this.readString();
              var animation = this.readAnimation(animationName);
              animations[animationName] = animation;
          }
  
      },
      readSkin : function(skinName, nonessential){
          var slotCount = this.readInt(true);
          if(slotCount == 0)
              return null;
          var skin = {};
          for(var i = 0; i < slotCount; i++){
              var slotIndex = this.readInt(true);
              var slot = {};
              for(var j = 0, n = this.readInt(true); j < n; j++){
                  var name = this.readString();
                  var attachment = this.readAttachment(name, nonessential);
                  slot[name] = attachment;
              }
              skin[this.json.slots[slotIndex].name] = slot;
          }
          return skin;
      },
      readAttachment : function(attachmentName, nonessential){
          var name = this.readString();
          if(name == null)
              name = attachmentName;
          switch(this.AttachmentType[this.readByte()]){
          case "region":
              var path = this.readString();
              if(path == null)
                  path = name;
              var region = {};
              region.type = "region";
              region.name = name;
              region.path = path;
              region.x = this.readFloat() * this.scale;
              region.y = this.readFloat() * this.scale;
              region.scaleX = this.readFloat();
              region.scaleY = this.readFloat();
              region.rotation = this.readFloat();
              region.width = this.readFloat() * this.scale;
              region.height = this.readFloat() * this.scale;
              region.color = this.readColor();
              // Maybe need UpdateOffset()
              return region;
          case "boundingbox":
              var box = {};
              box.type = "boundingbox";
              box.name = name;
              box.vertices = this.readFloatArray();
              // Maybe need vertexCount or color
              return box;
          case "mesh":
              var path = this.readString();
              if(path == null)
                  path = name;
              var mesh = {};
              mesh.type = "mesh";
              mesh.name = name;
              mesh.path = path;
              mesh.uvs = this.readFloatArray();
              // Maybe need updateUVs()
              mesh.triangles = this.readShortArray();
              mesh.vertices = this.readFloatArray();
              mesh.color = this.readColor();
              mesh.hull = this.readInt(true);
              // Maybe need * 2
              if(nonessential){
                  mesh.edges = this.readIntArray();
                  mesh.width = this.readFloat();
                  mesh.height = this.readFloat();
                  // Maybe need * scale
              }
              return mesh;
          case "skinnedmesh":
              var path = this.readString();
              if(path == null)
                  path = name;
              var skinnedmesh = {};
              skinnedmesh.type = "skinnedmesh";
              skinnedmesh.name = name;
              skinnedmesh.path = path;
              skinnedmesh.uvs = this.readFloatArray();
              skinnedmesh.triangles = this.readShortArray();
  
              skinnedmesh.vertices = new Array();
              var vertexCount = this.readInt(true);
              for(var i = 0; i < vertexCount;){
                  var boneCount = Math.floor(this.readFloat());
                  skinnedmesh.vertices[i++] = boneCount;
                  for(var nn = i + boneCount * 4; i < nn; i += 4){
                      skinnedmesh.vertices[i] = Math.floor(this.readFloat());
                      skinnedmesh.vertices[i + 1] = this.readFloat();
                      skinnedmesh.vertices[i + 2] = this.readFloat();
                      skinnedmesh.vertices[i + 3] = this.readFloat();
                  }
              }
              skinnedmesh.color = this.readColor();
              skinnedmesh.hull = this.readInt(true);
              // Maybe need * 2
              if(nonessential){
                  skinnedmesh.edges = this.readIntArray();
                  skinnedmesh.width = this.readFloat();
                  skinnedmesh.height = this.readFloat();
                  // Maybe need * scale
              }
              return skinnedmesh;
          }
          return null;
      },
      readCurve : function(frameIndex, timeline){
          switch(this.readByte()){
          case 1: //CURVE_STEPPED
              timeline[frameIndex].curve = "stepped";
              break;
          case 2: //CURVE_BEZIER
              var cx1 = this.readFloat();
              var cy1 = this.readFloat();
              var cx2 = this.readFloat();
              var cy2 = this.readFloat();
              timeline[frameIndex].curve = [cx1, cy1, cx2, cy2];
          }
      },
      readAnimation : function(name){
          var animation = {};
          var scale = this.scale;
          var duration = 0;
  
          // Slot timelines.
          var slots = {};
          for(var i = 0, n = this.readInt(true); i < n; i++){
              var slotIndex = this.readInt(true);
              var slotMap = {};
              var timeCount = this.readInt(true);
              for(var ii = 0; ii < timeCount; ii++){
                  var timelineType = this.readByte();
                  var frameCount = this.readInt(true);
                  switch(timelineType){
                  case 4: //TIMELINE_COLOR
                      var timeline = new Array(frameCount);
                      for(var frameIndex = 0; frameIndex < frameCount; frameIndex++){
                          var time = this.readFloat();
                          var color = this.readColor();
                          timeline[frameIndex] = {};
                          timeline[frameIndex].time = time;
                          timeline[frameIndex].color = color;
                          if(frameIndex < frameCount - 1){
                              var str = this.readCurve(frameIndex, timeline);
                          }
                      }
                      slotMap.color = timeline;
                      duration = Math.max(duration, timeline[frameCount - 1].time);
                      break;
                  case 3: //TIMELINE_ATTACHMENT
                      var timeline = new Array(frameCount);
                      for(var frameIndex = 0; frameIndex < frameCount; frameIndex++){
                          var time = this.readFloat();
                          var attachmentName = this.readString();
                          timeline[frameIndex] = {};
                          timeline[frameIndex].time = time;
                          timeline[frameIndex].name = attachmentName;
                      }
                      slotMap.attachment = timeline;
                      duration = Math.max(duration, timeline[frameCount - 1].time);
                      break;
                  }
              }
              slots[this.json.slots[slotIndex].name] = slotMap;
          }
          animation.slots = slots;
  
          //// Bone timelines.
          var bones = {};
          for(var i = 0, n = this.readInt(true); i < n; i++){
              var boneIndex = this.readInt(true);
              var boneMap = {};
              for(var ii = 0, nn = this.readInt(true); ii < nn; ii++){
                  var timelineType = this.readByte();
                  var frameCount = this.readInt(true);
                  switch(timelineType){
                  case 1: //TIMELINE_ROTATE
                      var timeline = new Array(frameCount);
                      for(var frameIndex = 0; frameIndex < frameCount; frameIndex++){
                          var tltime = this.readFloat();
                          var tlangle = this.readFloat();
                          timeline[frameIndex] = {};
                          timeline[frameIndex].time = tltime;
                          timeline[frameIndex].angle = tlangle;
                          if(frameIndex < frameCount - 1){
                              this.readCurve(frameIndex, timeline);
                          }
                      }
                      boneMap.rotate = timeline;
                      duration = Math.max(duration, timeline[frameCount - 1].time);
                      break;
                  case 2: //TIMELINE_TRANSLATE
                  case 0: //TIMELINE_SCALE
                      var timeline = new Array(frameCount);
                      var timelineScale = 1;
                      if(timelineType == 2){
                          timelineScale = scale;
                      }
                      for(var frameIndex = 0; frameIndex < frameCount; frameIndex++){
                          var tltime = this.readFloat();
                          var tlx = this.readFloat();
                          var tly = this.readFloat();
                          timeline[frameIndex] = {};
                          timeline[frameIndex].time = tltime;
                          timeline[frameIndex].x = tlx;
                          timeline[frameIndex].y = tly;
                          if(frameIndex < frameCount - 1){
                              this.readCurve(frameIndex, timeline);
                          }
                      }
                      if(timelineType == 0){
                          boneMap.scale = timeline;
                      }else{
                          boneMap.translate = timeline;
                      }
                      duration = Math.max(duration, timeline[frameCount - 1].time);
                      break;
                  case 5: //TIMELINE_FLIPX
                  case 6: //TIMELINE_FLIPY
                      var timeline = new Array(frameCount);
                      for(var frameIndex = 0; frameIndex < frameCount; frameIndex++){
                          var tltime = this.readFloat();
                          var tlflip = this.readBoolean();
                          timeline[frameIndex] = {};
                          timeline[frameIndex].time = tltime;
                          if(timelineType == 5)
                              timeline[frameIndex].x = tlflip;
                          else
                              timeline[frameIndex].y = tlflip;
                      }
                      if(timelineType == 5)
                          boneMap.flipX = timeline;
                      else
                          boneMap.flipY = timeline;
                      duration = Math.max(duration, timeline[frameCount - 1].time);
                      break;
                  }
              }
              bones[this.json.bones[boneIndex].name] = boneMap;
          }
          animation.bones = bones;
  
          // IK timelines.
          var ik = {};
          for(var i = 0, n = this.readInt(true); i < n; i++){
              var ikIndex = this.readInt(true);
              var frameCount = this.readInt(true);
              var timeline = new Array(frameCount);
              for(var frameIndex = 0; frameIndex < frameCount; frameIndex++){
                  var time = this.readFloat();
                  var mix = this.readFloat();
                  var bendPositive = this.readBoolean();
                  // Maybe use ReadSByte()
                  timeline[frameIndex].time = time;
                  timeline[frameIndex].mix = mix;
                  timeline[frameIndex].bendPositive = bendPositive;
                  if(frameIndex < frameCount - 1)
                      this.readCurve(frameIndex, timeline);
              }
              ik[this.json.ik[ikIndex]] = timeline;
          }
          animation.ik = ik;
  
          // FFD timelines.
          var ffd = {};
          for(var i = 0, n = this.readInt(true); i < n; i++){
              var skinIndex = this.readInt(true);
              var slotMap = {};
              for(var ii = 0, nn = this.readInt(true); ii < nn; ii++){
                  var slotIndex = this.readInt(true);
                  var meshMap = {};
                  for(var iii = 0, nnn = this.readInt(true); iii < nnn; iii++){
                      var meshName = this.readString();
                      var frameCount = this.readInt(true);
                      var attachment;
                      var attachments = this.json.skins[this.json.skinsName[skinIndex]][this.json.slots[slotIndex].name];
                      for(var attachmentName in attachments){
                          if(attachments[attachmentName].name == meshName)
                              attachment = attachments[attachmentName];
                      }
                      
                      if(!attachment)
                          console.log("FFD attachment not found: " + meshName);
                      
                      var timeline = new Array(frameCount);
                      for(var frameIndex = 0; frameIndex < frameCount; frameIndex++){
                          var time = this.readFloat();
                          var vertexCount;
                          if(attachment.type == "mesh"){
                              vertexCount = attachment.vertices.length;
                          }else{
                              vertexCount = attachment.uvs.length * 3 * 3;
                              // This maybe wrong
                          }
                          
                          var vertices = new Array(vertexCount);
                          for (var verticeIdx = 0; verticeIdx < vertexCount; verticeIdx++){
                            vertices[verticeIdx] = 0.0;
                          }
                          
                          // ToDo: I have no idea why we need this
                          var bugFixMultiplicator = 0.1;
                          
                          var end = this.readInt(true);
                          if (end == 0) {
                            if (attachment.type == "mesh") {
                              for (var verticeIdx = 0; verticeIdx < vertexCount; verticeIdx++){
                                vertices[verticeIdx] += attachment.vertices[verticeIdx] * bugFixMultiplicator;
                              }
                            }
                          } else {
                              var start = this.readInt(true);
                              end += start;
                              
                              for(var v = start; v < end; v++){
                                  vertices[v] = this.readFloat() * scale;
                              }
                              
                              if(attachment.type == "mesh"){
                                  var meshVertices = attachment.vertices;
                                  for(var v = 0, vn = vertices.length; v < vn; v++){
                                      vertices[v] += meshVertices[v] * bugFixMultiplicator;
                                  }
                              }
                          }
                          timeline[frameIndex] = {};
                          timeline[frameIndex].time = time;
                          timeline[frameIndex].vertices = vertices;
                          
                          if(frameIndex < frameCount - 1)
                              this.readCurve(frameIndex, timeline);
                      }
                      meshMap[meshName] = timeline;
                      duration = Math.max(duration, timeline[frameCount - 1].time);
                  }
                  slotMap[this.json.slots[slotIndex].name] = meshMap;
              }
              ffd[this.json.skinsName[skinIndex]] = slotMap;
          }
          animation.ffd = ffd;
  
          // Draw order timeline.
          var drawOrderCount = this.readInt(true);
          if(drawOrderCount > 0){
              var drawOrders = new Array(drawOrderCount);
              // var timeline = new Array(drawOrderCount);
              var slotCount = this.json.slots.length;
              for(var i = 0; i < drawOrderCount; i++){
                  var drawOrderMap = {};
                  var offsetCount = this.readInt(true);
                  // var drawOrder = new Array(slotCount);
                  // for(var ii = slotCount - 1; ii >= 0; ii--){
                  //     drawOrder[ii] = -1;
                  // }
                  // var unchanged = new Array(slotCount - offsetCount);
                  // var originalIndex = 0, unchangedIndex = 0;
                  var offsets = new Array(offsetCount);
                  for(var ii = 0; ii < offsetCount; ii++){
                      var offsetMap = {};
                      var slotIndex = this.readInt(true);
                      offsetMap.slot = this.json.slots[slotIndex].name;
                      // while (originalIndex != slotIndex)
                      //     unchanged[unchangedIndex++] = originalIndex++;
                      var dooffset = this.readInt(true);
                      offsetMap.offset = dooffset;
                      // drawOrder[originalIndex + dooffset] = originalIndex++;
                      offsets[ii] = offsetMap;
                  }
                  drawOrderMap.offsets = offsets;
  
                  // while(originalIndex < slotCount)
                  //     unchanged[unchangedIndex++] = originalIndex++;
                  // for (var ii = slotCount - 1; ii >= 0; ii--){
                  //     if (drawOrder[ii] == -1)
                  //         drawOrder[ii] = unchanged[--unchangedIndex];
                  var tltime = this.readFloat();
                  drawOrderMap.time = tltime;
                  drawOrders[i] = drawOrderMap;
              }
              duration = Math.max(duration, drawOrders[drawOrderCount - 1].time);
              animation.drawOrder = drawOrders;
          }
  
          // Event timeline.
          var eventCount = this.readInt(true);
          if(eventCount > 0){
              var events = new Array(eventCount);
              for(var i = 0; i < eventCount; i++){
                  var time = this.readFloat();
                  var name = this.json.eventsName[this.readInt(true)];
                  var eventData = this.json.events[name];
                  var e = {};
                  e.name = name;
                  e.int = this.readInt(true);
                  e.float = this.readFloat();
                  e.string = this.readBoolean() ? this.readString() : eventData.string;
                  e.time = time;
                  events[i] = e;
              }
              duration = Math.max(duration, events[eventCount - 1].time);
              animation.events = events;
          }
          return animation;
      }
  }
  
  
  function SkeletonLoader(path){
      var path = path;
      var resources = window.chibiAnimationCache = window.chibiAnimationCache || {};
      var loader = new PIXI.loaders.Loader(path);
      var RES_PATH = ['skel', 'json', 'atlas', 'png'];
      
      return {
          log : function(l){
              console.log(l + " : SkeletonLoader");
          },
          getfullpath : function(genus, type, dormMode, useDormSpritemap){
              var full_path = '';
              var linkGen = window.gfUtils.createWikiPathPart;
                          var dormAddition = "";
                          if (dormMode) {
                            dormAddition = "dorm_";
                          }
                          var dormSpritesAddition = useDormSpritemap ? dormAddition : "";
              
              if (type == "png")
                  full_path = linkGen(genus + "_chibi_" + dormSpritesAddition + "spritemap.png") + genus + "_chibi_" + dormSpritesAddition + "spritemap.png"; 
              else if (type == "atlas")
                  full_path = linkGen(genus + "_chibi_" + dormSpritesAddition + "atlas.txt") + genus + "_chibi_" + dormSpritesAddition + "atlas.txt";
              else if (type == "skel")
                  full_path = linkGen(genus + "_chibi_" + dormAddition + "skel.skel") + genus + "_chibi_" + dormAddition + "skel.skel";
              else if (type == "json")
                  full_path = linkGen(genus + "_chibi_" + dormAddition + "skel.txt") + genus + "_chibi_" + dormAddition + "skel.txt";
              return  full_path;
          },
          load : function(genus, dormMode, successCallback, failureCallback, changeSelect){
              var res_name;
              if (typeof genus === "object") {
                  var regexp = /^(?:.+\/)?(.*)_chibi_.*$/ig;
                  var match = regexp.exec(genus.png);
                  res_name = match[1];
              } else {
                  res_name = genus;
              }
              
              var res_cache_name = res_name;
              if (dormMode) res_cache_name += "-dorm";
              
              var precheckHandler = function(skeletonLoaderReference, useDormSpritemap) {
                  try {
                      if(!genus){
                          this.log("????");
                          return ;
                      }
                      
                      var res_paths = {};
                      
                      if (typeof genus === "object") {
                          res_paths.json = genus.json;
                          res_paths.skel = genus.skel;
                          res_paths.atlas = genus.atlas;
                          res_paths.png = genus.png;
                      } else {
                          for(var i = 0; i < RES_PATH.length; i++){
                              res_paths[RES_PATH[i]] = skeletonLoaderReference.getfullpath(genus, RES_PATH[i], dormMode, useDormSpritemap);
                          }
                      }
                      
                      if (resources.hasOwnProperty(res_cache_name)) {
                          successCallback(resources[res_cache_name]);
                          animationControl(changeSelect, resources[res_cache_name]);
                          return;
                      }
          
                      if (genus == "Destroyer") loader.add(res_cache_name + '-json', res_paths.json, { 'xhrTypr' : 'text' }); // Why is she the only one?!
                      if (!resources.hasOwnProperty(res_cache_name+'-skel') && genus !== "Destroyer") loader.add(res_cache_name + '-skel', res_paths.skel, { 'xhrType' : 'arraybuffer' });
                      if (!resources.hasOwnProperty(res_cache_name+'-atlas')) loader.add(res_cache_name + '-atlas', res_paths.atlas, { 'xhrTypr' : 'text' });
                      if (!resources.hasOwnProperty(res_cache_name+'-png')) loader.add(res_cache_name + '-png', res_paths.png, { 'xhrTypr' : 'png' });
                      loader.load(function(loader, paramResources) {
                        try {
                          var rawSkeletonData;
                          var rawAtlasData = resources[res_cache_name + '-atlas'] ? resources[res_cache_name + '-atlas'].data : paramResources[res_cache_name + '-atlas'].data;
                          var rawPngData = resources[res_cache_name + '-png'] ? resources[res_cache_name + '-png'].data : paramResources[res_cache_name + '-png'].data;
                          
                          if (!resources.hasOwnProperty(res_cache_name)) {
                              if(paramResources[res_cache_name + '-json']){
                                  rawSkeletonData = JSON.parse(paramResources[res_cache_name + '-json'].data);
                              } else {
                                  var skelDataBinaryToUse = resources[res_cache_name + '-skel'] ? resources[res_cache_name + '-skel'].data : paramResources[res_cache_name + '-skel'].data;
                                  resources[res_cache_name + '-skel'] = skelDataBinaryToUse;
                                  var rawdata = skelDataBinaryToUse;
                                  if (rawdata != null && typeof(rawdata) === 'string' && rawdata.charAt(0) === '{') {
                                      // Dirty quickfix if data is json format :)
                                      rawSkeletonData = JSON.parse(rawdata);
                                  } else {
                                      var skel_bin = new SkeletonBinary();
                                      skel_bin.data = new Uint8Array(rawdata);
                                      skel_bin.initJson();
                                      rawSkeletonData = skel_bin.json;
                                  }
                              }
                                
                              var spineAtlas = new PIXI.spine.SpineRuntime.Atlas(rawAtlasData, function(line, callback, pngData) {
                                  if (!(pngData)) {
                                    pngData = rawPngData;
                                  }
                                  callback(new PIXI.BaseTexture(pngData));
                              });
                              var spineAtlasParser = new PIXI.spine.SpineRuntime.AtlasAttachmentParser(spineAtlas);
                              var spineJsonParser = new PIXI.spine.SpineRuntime.SkeletonJsonParser(spineAtlasParser);
                              var skeletonData = spineJsonParser.readSkeletonData(rawSkeletonData, name);
                              
                                animationControl(changeSelect, skeletonData);
                                
                              resources[res_cache_name + '-atlas'] = rawAtlasData;
                              resources[res_cache_name + '-png'] = rawPngData;
                              resources[res_cache_name] = skeletonData;
                          }
          
                          successCallback(resources[res_cache_name]);
  
                    } catch(err) {
                      failureCallback(err);
                    }
                  });
                } catch(err) {
                  failureCallback(err);
                }
              };
                      
              var skeletonLoaderReference = this;
              if (typeof genus === "object") {
                  precheckHandler(skeletonLoaderReference, false);
              } else {
                  var urlToCheck = path + window.gfUtils.createWikiPathPart(res_name + "_chibi_dorm_atlas.txt") + res_name + "_chibi_dorm_atlas.txt";
                  $.ajax({
                      url: urlToCheck,
                      type:'HEAD',
                      error: function() { precheckHandler(skeletonLoaderReference, false); },
                      success: function() { precheckHandler(skeletonLoaderReference, true); }
                  });
              }
          }
      };
  };
  
  
  function animationControl(changeSelect, skeletonData){
      var infoDiv = $(changeSelect.parent(".tdoll_chibi,.chibiAnimationContainer").children(".chibiAnimationInfo"));
      for(var i = 1; i < 20; i ++){
          if(!infoDiv.attr("data-name" + String(i))) break;
          infoDiv.removeAttr("data-name" + String(i));
          infoDiv.removeAttr("data-duration" + String(i));
          infoDiv.removeAttr("data-frame" + String(i));
      }
  
      changeSelect.children("option").remove();
      for(i in skeletonData.animations){
          infoDiv.attr("data-name" + String(i), skeletonData.animations[i].name);
          infoDiv.attr("data-duration" + String(i), skeletonData.animations[i].duration);
          infoDiv.attr("data-frame" + String(i), skeletonData.animations[i].timelines.length);
          
          var op = document.createElement("OPTION");
          if(skeletonData.animations[i].name == "victoryloop") continue;
          op.value = skeletonData.animations[i].name;
          op.innerHTML = skeletonData.animations[i].name;
          changeSelect.append(op);
      }
      changeSelect.val("wait");
  }
  
  function BaseView(w, h){
      var width = w || 400;
      var height = h || 400;
      var last_time = 0;
      var now_time = 0;
      var isUpdate = true;
      var animationframe = null;
      var animations = new Array();
      var player = new Array();
      var stage = new PIXI.Container;
      var renderer = PIXI.autoDetectRenderer(width, height, { transparent : true, preserveDrawingBuffer: true });
      var self = this;
      var animate = function(t) {
              animationframe = window.requestAnimationFrame(function(time) { animate(time); });
              last_time = now_time;
              now_time = t;
              var time_diff = now_time - last_time;
              if(isUpdate){
                  for(var i = 0; i < player.length; i++){
                      if(player[i].update && player[i].isupdate){
                          player[i].update(time_diff / 1000);
                      }
                  }
              }
              
              renderer.render(stage);
          };
      
      return {
          clean : function(){
              stage.removeChildren();
              player = new Array();
          },
          getRenderer : function(){
              return renderer;
          },
          addSprite : function(element){
              stage.addChild(element);
          },
          addSpinePlayer : function(skeletonData, requestedScale){
              var spineplayer = new PIXI.spine.Spine(skeletonData);
              var animations = spineplayer.spineData.animations;
              spineplayer.position.set(width / 2, height * 0.60);
              
              var scaleToUse = 1;
              if (requestedScale) {
                  var tmpParse = parseFloat(requestedScale);
                  if (!isNaN(tmpParse)) {
                      scaleToUse = tmpParse;
                  }
              }
              spineplayer.scale.set(scaleToUse);
              
              spineplayer.animation_num = animations.length-1;
              spineplayer.state.setAnimationByName(0, animations[spineplayer.animation_num].name, true);
              spineplayer.skeleton.setToSetupPose();
              spineplayer.autoUpdate = false;
              spineplayer.update(0);
              spineplayer.isupdate = true;
              var num;
              num = player.push(spineplayer);
              stage.addChild(spineplayer);
              renderer.render(stage);
              return num;
          },
          nextAnimation : function(theChangeSelect){
              var jumpAnimation = false;
              
              for (var playerIdx = 0; playerIdx < player.length; playerIdx++) {
                  var currentPlayer = player[playerIdx];
                  if(currentPlayer && currentPlayer.spineData && currentPlayer.spineData.animations){
                      var animations = currentPlayer.spineData.animations;
                      currentPlayer.animation_num = (currentPlayer.animation_num + 1) % animations.length;
                      
                      var animationName = animations[currentPlayer.animation_num].name;
                      var hasVictoryLoop = this.hasVictoryLoop(animations);
                      var isVictoryAnimation = animationName == "victory";
                      
                      // Change the animation select options, if it doesn't exist, then continue;
                      var isExist = false;  
                      var count = theChangeSelect.find('option').length; 
                      for(var i = 0; i < count; i++){
                          if(theChangeSelect.get(0).options[i].value == animationName){ isExist = true; break; } 
                      }
                      if(isExist) theChangeSelect.val(animationName);
                      
                      // Generally it should be repeatet
                      var repeat = true;
                      repeat &= animationName != "die"; // except on "die" animations
                      repeat &= !isVictoryAnimation || isVictoryAnimation && !hasVictoryLoop; // victory should repeat except if it has victoryloop
                      
                      currentPlayer.state.setAnimationByName(0, animationName, repeat);
                      
                      // if there is a victoryloop animation and victory is now showing, chain them up
                      if (isVictoryAnimation && hasVictoryLoop) {
                          currentPlayer.state.addAnimationByName(0, "victoryloop", true, 0);
                      }
                      
                      currentPlayer.update(0);
                      
                      // We skip on animations without movement and on victoryloop (which is already chained to another animation)
                      if (animations[currentPlayer.animation_num].duration == 0.0 || (hasVictoryLoop && animationName == "victoryloop")) {
                          jumpAnimation = true;
                      }
                  }
              }
              
              if (jumpAnimation) {
                  this.nextAnimation(theChangeSelect);
              }
          },
          selectAnimation : function(targetName){            
              for (var playerIdx = 0; playerIdx < player.length; playerIdx++) {
                  var currentPlayer = player[playerIdx];
                  if(currentPlayer && currentPlayer.spineData && currentPlayer.spineData.animations){
                      var animations = currentPlayer.spineData.animations;
                      
                        for(var num = 0; num < animations.length; num ++){
                            if(animations[num].name == targetName){
                              currentPlayer.animation_num = num;
                              break;
                            }
                        }
                        
                      var animationName = targetName;
                      var hasVictoryLoop = this.hasVictoryLoop(animations);
                      var isVictoryAnimation = animationName == "victory";
                      
                      // Generally it should be repeatet
                      var repeat = true;
                      repeat &= animationName != "die"; // except on "die" animations
                      repeat &= !isVictoryAnimation || isVictoryAnimation && !hasVictoryLoop; // victory should repeat except if it has victoryloop
                      
                      currentPlayer.state.setAnimationByName(0, animationName, repeat);
                      
                      // if there is a victoryloop animation and victory is now showing, chain them up
                      if (isVictoryAnimation && hasVictoryLoop) {
                          currentPlayer.state.addAnimationByName(0, "victoryloop", true, 0);
                      }
                      
                      currentPlayer.update(0);
                  }
              }
          },
          hasVictoryLoop: function(anims) {
              for (var idx = 0; idx < anims.length; idx++) {
                  if (anims[idx].name == "victoryloop") {
                      return true;
                  }
              }
              return false;
          },
          changeupdate : function(num){
              player[num].isupdate = !player[num].isupdate;
              return player[num].isupdate;
          },
          upplayer : function(num){
              if(num <= 0){
                  return null;
              }
              var now = stage.getChildIndex(player[num]);
              var next = stage.getChildIndex(player[num - 1]);
              var p = player[num];
              player[num] = player[num - 1];
              player[num  - 1] = p;
              stage.addChildAt(player[num], now);
              stage.addChildAt(player[num - 1], next);
              return num - 1;
          },
          downplayer : function(num){
              if(num >= player.length - 1){
                  return null;
              }
              var now = stage.getChildIndex(player[num]);
              var next = stage.getChildIndex(player[num + 1]);
              var p = player[num];
              player[num] = player[num + 1];
              player[num  + 1] = p;
              stage.addChildAt(player[num], now);
              stage.addChildAt(player[num + 1], next);
              return num + 1;
          },
          addImagePlayer : function(texture){
              var imageplayer = new PIXI.Sprite(texture);
              imageplayer.anchor.set(0.5);
              imageplayer.position.set(width / 2, height / 2);
              player.push(imageplayer);
              stage.addChild(imageplayer);
              renderer.render(stage);
          },
          start : function(){
              animationframe = window.requestAnimationFrame(function(time) { animate(time); });
          },
          replay : function(){
              for (var playerIdx = 0; playerIdx < player.length; playerIdx++) {
                  var currentPlayer = player[playerIdx];
                  if(currentPlayer && currentPlayer.spineData && currentPlayer.spineData.animations){
                      var animations = currentPlayer.spineData.animations;
                      
                      var animationName = animations[currentPlayer.animation_num].name;
                      var hasVictoryLoop = this.hasVictoryLoop(animations);
                      var isVictoryAnimation = animationName == "victory";
                      
                      // Generally it should be repeatet
                      var repeat = true;
                      repeat &= animationName != "die"; // except on "die" animations
                      repeat &= !isVictoryAnimation || isVictoryAnimation && !hasVictoryLoop; // victory should repeat except if it has victoryloop
                      
                      currentPlayer.state.setAnimationByName(0, animationName, repeat);
                      
                      // if there is a victoryloop animation and victory is now showing, chain them up
                      if (isVictoryAnimation && hasVictoryLoop) {
                          currentPlayer.state.addAnimationByName(0, "victoryloop", true, 0);
                      }
                      
                      currentPlayer.update(0);
                  }
              }
          },
          download : function(canvas){
              for (var playerIdx = 0; playerIdx < player.length; playerIdx++) {
                  var data = [];
                  var currentPlayer = player[playerIdx];
  
                  if(currentPlayer && currentPlayer.spineData && currentPlayer.spineData.animations){
                      var animations = currentPlayer.spineData.animations;
                      var animationName = animations[currentPlayer.animation_num].name;
                      var recordTime = animations[currentPlayer.animation_num].duration * 1000;
                      console.log(animations)
                      console.log(recordTime)
                      if(animationName == "victory"){
                          for(animNumber in animations)
                              if(animations[animNumber].name == "victoryloop") recordTime += animations[animNumber].duration * 1000 * 4;
                      }
                      
                      var stream = canvas[0].captureStream();
                      var recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
                      
                      console.log("Recording start");
                      recorder.ondataavailable = function (event) {
                          if (event.data && event.data.size) {
                              data.push(event.data);
                          }
                      };
  
                      recorder.onstop = function(){
                          // download webm when is over
                          var url = URL.createObjectURL(new Blob(data, { type: 'video/webm' }));
                          var element = document.createElement('a');
                          var name_dorm = (canvas.parent(".tdoll_chibi").children(".chibiDormButton").hasClass("stateoff")) ? "dorm_" : "";
                          var name_all = canvas.parent(".tdoll_chibi").attr("data-tdoll-id") + name_dorm + animations[currentPlayer.animation_num].name;
                          element.setAttribute('href', url);
                          element.setAttribute('download', name_all);
                          element.style.display = 'none';
                          document.body.appendChild(element);
                          element.click();
                          document.body.removeChild(element);
                      };
                      
                      // copy from nextAnimation
                      var hasVictoryLoop = this.hasVictoryLoop(animations);
                      var isVictoryAnimation = animationName == "victory";
                      var repeat = true;
                      repeat &= animationName != "die";
                      repeat &= !isVictoryAnimation || isVictoryAnimation && !hasVictoryLoop;
                      currentPlayer.state.setAnimationByName(0, animationName, repeat);
                      if (isVictoryAnimation && hasVictoryLoop) {
                          currentPlayer.state.addAnimationByName(0, "victoryloop", true, 0);
                      }
                      
                      // start offically
                      currentPlayer.update(0);
                      recorder.start();
                      
                      setTimeout(function(){
                          recorder.stop();
                          console.log("Recording end");
                          canvas.parent(".tdoll_chibi").children(".chibiAnimationBlock").remove();
                      }, recordTime);
                  }
              }
          }
      };
  };