From 753f4e7a9c1bbde8cee98ac36d73973b6d9beb93 Mon Sep 17 00:00:00 2001
From: exane <raco0n@gmx.de>
Date: Sun, 21 Jun 2015 16:50:50 +0200
Subject: [PATCH] lot of stuff

---
 .gitignore                     |   3 +-
 assets/data/abilities.js       | 147 ++++++++-
 assets/data/cards.js           |  57 ++++
 assets/data/deck.js            |  15 +-
 client/index.html              |  25 +-
 client/js/client.js            |  92 ++++--
 client/scss/main.scss          |  16 +-
 gulpfile.js                    |   1 +
 public/index.html              |  25 +-
 server/Battle.js               |  32 +-
 server/Battleside.js           | 151 ++++++++-
 server/Card.js                 |  17 +-
 server/Field.js                |  54 +++-
 server/Hand.js                 |   6 +-
 site/public/assets/css/app.css | 568 ++++++++++++++++++++++++++++++++-
 site/server/composer.lock      |   2 +-
 test/src/CardSpec.js           |  13 +
 test/src/PubSubSpec.js         |  32 ++
 test/src/mainSpec.js           |   1 +
 19 files changed, 1155 insertions(+), 102 deletions(-)
 create mode 100644 test/src/CardSpec.js

diff --git a/.gitignore b/.gitignore
index 18a59ae..6c5d384 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,5 @@ test/spec
 /site/.idea
 /site/server/vendor
 /site/server/.env
-/site/client/node_modules
\ No newline at end of file
+/site/client/node_modules
+/site/public/assets/js/bundle.js
\ No newline at end of file
diff --git a/assets/data/abilities.js b/assets/data/abilities.js
index 1f8a717..354e7a3 100644
--- a/assets/data/abilities.js
+++ b/assets/data/abilities.js
@@ -97,6 +97,7 @@ module.exports = {
     }
   },
   "weather_fog": {
+    weather: 1/*,
     onEachTurn: function(card){
       var targetRow = card.constructor.TYPE.RANGED;
       var forcedPower = 1;
@@ -122,10 +123,11 @@ module.exports = {
         if(_card.getRawAbility() == "hero") return;
         _card.setForcedPower(forcedPower);
       });
-    }
+    }*/
   },
   "weather_rain": {
-    onEachTurn: function(card){
+    weather: 2
+    /*onEachTurn: function(card){
       var targetRow = card.constructor.TYPE.SIEGE;
       var forcedPower = 1;
       var field1 = this.field[targetRow].get();
@@ -137,12 +139,9 @@ module.exports = {
         if(_card.getRawAbility() == "hero") return;
         _card.setForcedPower(forcedPower);
       });
-
-    }
-  },
-  "weather_frost": {
-    onEachTurn: function(card){
-      var targetRow = card.constructor.TYPE.CLOSE_COMBAT;
+    },
+    onEachCardPlace: function(card){
+      var targetRow = card.constructor.TYPE.SIEGE;
       var forcedPower = 1;
       var field1 = this.field[targetRow].get();
       var field2 = this.foe.field[targetRow].get();
@@ -153,20 +152,116 @@ module.exports = {
         if(_card.getRawAbility() == "hero") return;
         _card.setForcedPower(forcedPower);
       });
-
-    }
+    }*/
   },
-  "clear_weather": {
-    onAfterPlace: function(card){
-      var targetRow = card.constructor.TYPE.WEATHER;
-      var field = this.field[targetRow].get();
+  "weather_frost": {
+    weather: 0
+    /*
+      onEachTurn: function(card){
+        var targetRow = card.constructor.TYPE.CLOSE_COMBAT;
+        var forcedPower = 1;
+        var field1 = this.field[targetRow].get();
+        var field2 = this.foe.field[targetRow].get();
 
-      //todo: remove weather cards
-    }
+        var field = field1.concat(field2);
+
+        field.forEach(function(_card){
+          if(_card.getRawAbility() == "hero") return;
+          _card.setForcedPower(forcedPower);
+        });
+      },
+      onEachCardPlace: function(card){
+        var targetRow = card.constructor.TYPE.CLOSE_COMBAT;
+        var forcedPower = 1;
+        var field1 = this.field[targetRow].get();
+        var field2 = this.foe.field[targetRow].get();
+
+        var field = field1.concat(field2);
+
+        field.forEach(function(_card){
+          if(_card.getRawAbility() == "hero") return;
+          _card.setForcedPower(forcedPower);
+        });
+      }*/
+  },
+  "weather_clear": {
+    weather: 5
+    /*onAfterPlace: function(card){
+      var targetRow = card.constructor.TYPE.WEATHER;
+      var field = this.field[targetRow];
+      field.removeAll();
+
+      for(var i = card.constructor.TYPE.CLOSE_COMBAT; i < card.constructor.TYPE.SIEGE; i++) {
+        var _field1, _field2, _field;
+        _field1 = this.field[i].get();
+        _field2 = this.foe.field[i].get();
+        _field = _field1.concat(_field2);
+
+        _field.forEach(function(_card){
+          if(_card.getRawAbility() == "hero") return;
+          _card.setForcedPower(-1);
+        });
+      }
+
+    }*/
   },
   "decoy": {
     replaceWith: true
   },
+  "commanders_horn": {
+    commandersHorn: true/*,
+    onEachCardPlace: function(card){
+      var field = this.field[card.getType()];
+      var id = "commanders_horn";
+
+      if(!field.isOnField(card)){
+        field.get().forEach(function(_card){
+          if(_card.getID() == id) return;
+          if(_card.getType() != card.getType()) return;
+          if(_card.hasAbility("hero")) return;
+          _card.setBoost(id, 0);
+        })
+        this.off("EachCardPlace", card.getUidEvents("EachCardPlace"));
+        return;
+      }
+
+      field.get().forEach(function(_card){
+        if(_card.getID() == id) return;
+        if(_card.getType() != card.getType()) return;
+        if(_card.hasAbility("hero")) return;
+        _card.setBoost(id, 0);
+        _card.setBoost(id, _card.getPower());
+      })
+    }*/
+  },
+  "commanders_horn_card": {
+    cancelPlacement: true,
+    commandersHorn: true,
+    isCommandersHornCard: true/*,
+    onEachCardPlace: function(card){
+      var field = this.field[card.getType()];
+      var id = "commanders_horn";
+
+      if(!field.isOnField(card)){
+        field.get().forEach(function(_card){
+          if(_card.getID() == id) return;
+          if(_card.getType() != card.getType()) return;
+          if(_card.hasAbility("hero")) return;
+          _card.setBoost(id, 0);
+        })
+        this.off("EachCardPlace", card.getUidEvents("EachCardPlace"));
+        return;
+      }
+
+      field.get().forEach(function(_card){
+        if(_card.getID() == id) return;
+        if(_card.getType() != card.getType()) return;
+        if(_card.hasAbility("hero")) return;
+        _card.setBoost(id, 0);
+        _card.setBoost(id, _card.getPower());
+      })
+    }*/
+  },
   "foltest_leader1": {
     onActivate: function(){
       var cards = this.deck.find("key", "impenetrable_fog")
@@ -175,6 +270,26 @@ module.exports = {
       this.placeCard(card);
     }
   },
+  "foltest_leader2": {
+    onActivate: function(){
+      this.setWeather(5);
+    }
+  },
+  "foltest_leader3": {
+    onActivate: function(){
+      var siegeCards = this.field[2].get();
+
+      //todo: unless there is commanders horn active
+      siegeCards.forEach(function(card){
+        card.setBoost("foltest_leader3", card.getPower());
+      })
+    }
+  },
+  "foltest_leader4": {
+    onActivate: function(){
+
+    }
+  },
   "francesca_leader1": {
     onActivate: function(){
     }
diff --git a/assets/data/cards.js b/assets/data/cards.js
index 7b8f04f..229a8bf 100644
--- a/assets/data/cards.js
+++ b/assets/data/cards.js
@@ -186,6 +186,30 @@ module.exports = {
     faction: "Northern Realm",
     type: 3
   },
+  "foltest_lord_commander": {
+    name: "Foltest: Lord Commander",
+    power: -1,
+    ability: "foltest_leader2",
+    img: "foltest_commander",
+    faction: "Northern Realm",
+    type: 3
+  },
+  "foltest_siegemaster": {
+    name: "Foltest: The Siegemaster",
+    power: -1,
+    ability: "foltest_leader3",
+    img: "foltest_siege",
+    faction: "Northern Realm",
+    type: 3
+  },
+  "foltest_forged": {
+    name: "Foltest: The Steel-Forged",
+    power: -1,
+    ability: "foltest_leader4",
+    img: "foltest_forged",
+    faction: "Northern Realm",
+    type: 3
+  },
   "decoy": {
     name: "Decoy",
     power: -1,
@@ -194,6 +218,15 @@ module.exports = {
     faction: null,
     type: 4
   },
+  "commanders_horn": {
+    name: "Commander's Horn",
+    power: -1,
+    ability: "commanders_horn_card",
+    img: "horn",
+    faction: null,
+    type: 4
+  },
+
   "impenetrable_fog": {
     name: "Impenetrable Fog",
     power: -1,
@@ -202,6 +235,30 @@ module.exports = {
     faction: null,
     type: 5
   },
+  "biting_frost": {
+    name: "Biting Frost",
+    power: -1,
+    ability: "weather_frost",
+    img: "frost",
+    faction: null,
+    type: 5
+  },
+  "torrential_rain": {
+    name: "Torrential Rain",
+    power: -1,
+    ability: "weather_rain",
+    img: "rain",
+    faction: null,
+    type: 5
+  },
+  "clear_weather": {
+    name: "Clear Weather",
+    power: -1,
+    ability: "weather_clear",
+    img: "clear",
+    faction: null,
+    type: 5
+  },
 
 
   "francesca_pureblood_elf": {
diff --git a/assets/data/deck.js b/assets/data/deck.js
index fe0e7bc..827c91a 100644
--- a/assets/data/deck.js
+++ b/assets/data/deck.js
@@ -24,9 +24,12 @@ module.exports = {
     "ballista",
     "trebuchet",
     "thaler",
-    "foltest_king_of_temeria",
-    "decoy",
-    "impenetrable_fog"
+    "foltest_siegemaster",
+    "biting_frost",
+    "torrential_rain",
+    "clear_weather",
+    "impenetrable_fog",
+    "decoy"
   ],
 
   "scoiatael": [
@@ -40,6 +43,9 @@ module.exports = {
     "toruviel",
     "decoy",
     "decoy",
+    "biting_frost",
+    "torrential_rain",
+    "clear_weather",
     "impenetrable_fog",
     "elven_skirmisher",
     "elven_skirmisher",
@@ -78,6 +84,9 @@ module.exports = {
     "gargoyle",
     "cockatrice",
     "harpy",
+    "biting_frost",
+    "torrential_rain",
+    "clear_weather",
     "impenetrable_fog",
     "endrega",
     "vampire_bruxa",
diff --git a/client/index.html b/client/index.html
index eaf3ad9..f8811de 100644
--- a/client/index.html
+++ b/client/index.html
@@ -76,7 +76,6 @@
         </div>
         <div class="row">
           <div class="col-xs-12 gwent-lives">
-            <!--<i class="ruby"></i>-->
             {{#health lives}}{{/health}}
           </div>
         </div>
@@ -92,30 +91,42 @@
   <div class="col-xs-6">
     <div class="row">
       <div class="col-xs-12 battleside battleside-foe foe">
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-siege">
+        </div>
+        <div class="col-xs-10 field">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-siege"></div>
         </div>
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-range">
+        </div>
+        <div class="col-xs-10 field">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-range"></div>
         </div>
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-close">
+        </div>
+        <div class="col-xs-10 field">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-close"></div>
         </div>
       </div>
       <div class="col-xs-12 mid-line"></div>
       <div class="col-xs-12 battleside battleside-player player">
-        <div class="col-xs-12 field{{#if agile}} active{{/if}}">
+        <div class="col-xs-2 field-horn field-horn-close">
+        </div>
+        <div class="col-xs-10 field{{#if active.close}} active{{/if}}">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-close"></div>
         </div>
-        <div class="col-xs-12 field{{#if agile}} active{{/if}}">
+        <div class="col-xs-2 field-horn field-horn-range">
+        </div>
+        <div class="col-xs-10 field{{#if active.range}} active{{/if}}">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-range"></div>
         </div>
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-siege">
+        </div>
+        <div class="col-xs-10 field{{#if active.range}} active{{/if}}">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-siege"></div>
         </div>
diff --git a/client/js/client.js b/client/js/client.js
index 7adf62b..65a1a3a 100644
--- a/client/js/client.js
+++ b/client/js/client.js
@@ -21,16 +21,6 @@ Handlebars.registerHelper("health", function(lives, options){
   return out;
 });
 
-/*
-var Config = {};
-
-Config.Server = {
-  "hostname": "localhost",
-  "port": 16918,
-  secure: false
-}
-*/
-
 var App = Backbone.Router.extend({
   routes: {
     "lobby": "lobbyRoute",
@@ -137,7 +127,9 @@ var SideView = Backbone.View.extend({
     this.$info.find(".score").html(d.score);
     this.$info.find(".hand-card").html(d.hand);
     this.$info.find(".gwent-lives").html(this.lives(d.lives));
-    this.$info.find(".field-leader").html(this.template(l))
+    if(l._key){
+      this.$info.find(".field-leader").html(this.template(l))
+    }
 
     if(this.app.user.get("waiting") && this.side === ".player"){
       this.$info.addClass("removeBackground");
@@ -153,47 +145,61 @@ var SideView = Backbone.View.extend({
     if(!this.field.close) return;
     this.$fields = this.$el.find(".battleside" + this.side);
     var $field = this.$fields.find(".field-close").parent();
-    var cards = this.field.close._cards;
-    var score = this.field.close._score;
+    var cards = this.field.close.cards;
+    var score = this.field.close.score;
+    var horn = this.field.close.horn;
 
 
     var html = this.templateCards(cards);
 
     $field.find(".field-close").html(html)
     $field.find(".large-field-counter").html(score)
+    if(horn){
+      this.$fields.find(".field-horn-close").html(this.template(horn));
+    }
 
-    calculateCardMargin($field.find(".card"), 433, 70, cards.length);
+    calculateCardMargin($field.find(".card"), 351, 70, cards.length);
   },
   renderRangeField: function(){
     if(!this.field.ranged) return;
     this.$fields = this.$el.find(".battleside" + this.side);
     var $field = this.$fields.find(".field-range").parent();
-    var cards = this.field.ranged._cards;
-    var score = this.field.ranged._score;
+    var cards = this.field.ranged.cards;
+    var score = this.field.ranged.score;
+    var horn = this.field.ranged.horn;
 
     var html = this.templateCards(cards);
 
     $field.find(".field-range").html(html)
     $field.find(".large-field-counter").html(score)
-    calculateCardMargin($field.find(".card"), 433, 70, cards.length);
+    if(horn){
+      this.$fields.find(".field-horn-range").html(this.template(horn));
+    }
+
+    calculateCardMargin($field.find(".card"), 351, 70, cards.length);
   },
   renderSiegeField: function(){
     if(!this.field.siege) return;
     this.$fields = this.$el.find(".battleside" + this.side);
     var $field = this.$fields.find(".field-siege").parent();
-    var cards = this.field.siege._cards;
-    var score = this.field.siege._score;
+    var cards = this.field.siege.cards;
+    var score = this.field.siege.score;
+    var horn = this.field.siege.horn;
 
     var html = this.templateCards(cards);
 
     $field.find(".field-siege").html(html)
     $field.find(".large-field-counter").html(score)
-    calculateCardMargin($field.find(".card"), 433, 70, cards.length);
+    if(horn){
+      this.$fields.find(".field-horn-siege").html(this.template(horn));
+    }
+
+    calculateCardMargin($field.find(".card"), 351, 70, cards.length);
   },
   renderWeatherField: function(){
     if(!this.field.weather) return;
     var $weatherField = this.$el.find(".field-weather");
-    var cards = this.field.weather._cards;
+    var cards = this.field.weather.cards;
     $weatherField.html(this.templateCards(cards));
 
     return this;
@@ -214,7 +220,7 @@ var SideView = Backbone.View.extend({
 var calculateCardMargin = function($selector, totalWidth, cardWidth, n){
   var w = totalWidth, c = cardWidth;
   var res;
-  if(n < 7)
+  if(n < 6)
     res = 0;
   else {
     res = -((w - c) / (n - 1) - c) + 1
@@ -240,6 +246,7 @@ var BattleView = Backbone.View.extend({
     this.listenTo(user, "change:passing", this.render);
     this.listenTo(user, "change:openDiscard", this.render);
     this.listenTo(user, "change:setAgile", this.render);
+    this.listenTo(user, "change:setHorn", this.render);
 
     this.$hand = this.$el.find(".field-hand");
     this.$preview = this.$el.find(".card-preview");
@@ -290,6 +297,14 @@ var BattleView = Backbone.View.extend({
       }
       return;
     }
+    if(!!this.user.get("setHorn")){
+      if(id === this.user.get("setHorn")){
+        this.user.set("setHorn", false);
+        this.app.send("cancel:horn");
+        this.render();
+      }
+      return;
+    }
     if(!!this.user.get("waitForDecoy")){
       if(id === this.user.get("waitForDecoy")){
         this.user.set("waitForDecoy", false);
@@ -313,6 +328,9 @@ var BattleView = Backbone.View.extend({
     if(this.user.get("waitForDecoy")){
       var $card = $(e.target).closest(".card");
       var _id = $card.data("id");
+
+      if($card.parent().hasClass("field-horn")) return;
+
       this.app.send("decoy:replaceWith", {
         cardID: _id
       })
@@ -328,6 +346,16 @@ var BattleView = Backbone.View.extend({
       });
       this.user.set("setAgile", false);
     }
+    if(this.user.get("setHorn")){
+      var $field = $(e.target).closest(".field.active").find(".field-close, .field-range, .field-siege");
+
+      console.log($field);
+      var target = $field.hasClass("field-close") ? 0 : ($field.hasClass("field-range") ? 1 : 2);
+      this.app.send("horn:field", {
+        field: target
+      });
+      this.user.set("setHorn", false);
+    }
   },
   onMouseover: function(e){
     var target = $(e.target).closest(".card");
@@ -355,13 +383,20 @@ var BattleView = Backbone.View.extend({
     var self = this;
     this.$el.html(this.template({
       cards: self.handCards,
-      agile: self.user.get("setAgile")
+      active: {
+        close: self.user.get("setAgile") || self.user.get("setHorn"),
+        range: self.user.get("setAgile") || self.user.get("setHorn"),
+        siege: self.user.get("setHorn")
+      }
     }));
     if(!(this.otherSide && this.yourSide)) return;
     this.otherSide.render();
     this.yourSide.render();
 
-    calculateCardMargin(this.$el.find(".field-hand .card"), 538, 70, this.handCards.length);
+
+    if(this.handCards){
+      calculateCardMargin(this.$el.find(".field-hand .card"), 538, 70, this.handCards.length);
+    }
 
     if(this.user.get("openDiscard")){
       var modal = new Modal({model: this.user});
@@ -375,6 +410,10 @@ var BattleView = Backbone.View.extend({
       var id = this.user.get("setAgile");
       this.$el.find("[data-id='" + id + "']").addClass("activeCard");
     }
+    if(this.user.get("setHorn")){
+      var id = this.user.get("setHorn");
+      this.$el.find("[data-id='" + id + "']").addClass("activeCard");
+    }
     if(this.user.get("waitForDecoy")){
       var id = this.user.get("waitForDecoy");
       this.$el.find("[data-id='" + id + "']").addClass("activeCard");
@@ -529,6 +568,11 @@ var User = Backbone.Model.extend({
       self.set("setAgile", data.cardID);
     })
 
+    app.receive("played:horn", function(data){
+      console.log("played horn");
+      self.set("setHorn", data.cardID);
+    })
+
     app.on("createRoom", this.createRoom, this);
     app.on("joinRoom", this.joinRoom, this);
     app.on("setName", this.setName, this);
diff --git a/client/scss/main.scss b/client/scss/main.scss
index e0c5499..a5bcdd0 100644
--- a/client/scss/main.scss
+++ b/client/scss/main.scss
@@ -17,7 +17,7 @@ $game-height: 800px;
 }
 
 .field {
-  width: 100%;
+  //width: 100%;
   height: 100px;
   border: 1px solid black;/*
   overflow: hidden;
@@ -35,6 +35,14 @@ $game-height: 800px;
   }
 }
 
+.field-horn {/*
+  border: 1px solid green;*/
+  /*clear: both;*/
+  margin: 0px;
+  padding: 0px;
+
+}
+
 .field:hover, .field-single:hover {
   box-shadow: 0px 0px 10px #000;
 }
@@ -104,11 +112,11 @@ $game-height: 800px;
   span {
     color: #00b000;
     font-weight: bold;
-    font-size: 24px;
+    font-size: 20px;
     position: absolute;
     text-shadow: 1px 1px #101010;
-    margin-top: 55px;
-    margin-left: 30px;
+    margin-top: 62px;
+    margin-left: 0px;
     cursor: default;
   }
 
diff --git a/gulpfile.js b/gulpfile.js
index ba9d3e6..dbd8527 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -58,6 +58,7 @@ gulp.task("unit tests", function(){
 gulp.task("watch", function(){
   gulp.watch("./client/js/*", ["browserify"]);
   gulp.watch("./client/scss/*", ["sass"]);
+  gulp.watch("./client/*.html", ["index"]);
   gulp.watch("./test/src/*", ["unit tests"]);
 })
 
diff --git a/public/index.html b/public/index.html
index eaf3ad9..f8811de 100644
--- a/public/index.html
+++ b/public/index.html
@@ -76,7 +76,6 @@
         </div>
         <div class="row">
           <div class="col-xs-12 gwent-lives">
-            <!--<i class="ruby"></i>-->
             {{#health lives}}{{/health}}
           </div>
         </div>
@@ -92,30 +91,42 @@
   <div class="col-xs-6">
     <div class="row">
       <div class="col-xs-12 battleside battleside-foe foe">
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-siege">
+        </div>
+        <div class="col-xs-10 field">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-siege"></div>
         </div>
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-range">
+        </div>
+        <div class="col-xs-10 field">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-range"></div>
         </div>
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-close">
+        </div>
+        <div class="col-xs-10 field">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-close"></div>
         </div>
       </div>
       <div class="col-xs-12 mid-line"></div>
       <div class="col-xs-12 battleside battleside-player player">
-        <div class="col-xs-12 field{{#if agile}} active{{/if}}">
+        <div class="col-xs-2 field-horn field-horn-close">
+        </div>
+        <div class="col-xs-10 field{{#if active.close}} active{{/if}}">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-close"></div>
         </div>
-        <div class="col-xs-12 field{{#if agile}} active{{/if}}">
+        <div class="col-xs-2 field-horn field-horn-range">
+        </div>
+        <div class="col-xs-10 field{{#if active.range}} active{{/if}}">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-range"></div>
         </div>
-        <div class="col-xs-12 field">
+        <div class="col-xs-2 field-horn field-horn-siege">
+        </div>
+        <div class="col-xs-10 field{{#if active.range}} active{{/if}}">
           <div class="col-xs-1 large-field-counter">0</div>
           <div class="col-xs-11 field-siege"></div>
         </div>
diff --git a/server/Battle.js b/server/Battle.js
index 2d48979..1280498 100644
--- a/server/Battle.js
+++ b/server/Battle.js
@@ -61,11 +61,17 @@ var Battle = (function(){
     this.p2.setLeadercard();
     this.p1.draw(10);
     this.p2.draw(10);
-    this.p1.hand.add(Card("blue_stripes_commando"));
-    this.p2.hand.add(Card("blue_stripes_commando"));
-    this.p1.hand.add(Card("blue_stripes_commando"));
-    this.p2.hand.add(Card("blue_stripes_commando"));
-/*
+    this.p1.hand.add(Card("commanders_horn"));
+    this.p2.hand.add(Card("commanders_horn"));
+    this.p1.hand.add(Card("commanders_horn"));
+    this.p2.hand.add(Card("commanders_horn"));
+    /*
+    this.p1.hand.add(Card("biting_frost"));
+    this.p2.hand.add(Card("biting_frost"));
+    this.p1.hand.add(Card("torrential_rain"));
+    this.p2.hand.add(Card("torrential_rain"));
+    this.p1.hand.add(Card("clear_weather"));
+    this.p2.hand.add(Card("clear_weather"));*//*
     this.p1.hand.add(Card("kaedweni_siege_expert"));
     this.p2.hand.add(Card("kaedweni_siege_expert"));
     this.p1.hand.add(Card("ballista"));
@@ -77,8 +83,8 @@ var Battle = (function(){
     this.p1.hand.add(Card("ballista"));
     this.p2.hand.add(Card("ballista"));
     this.p1.hand.add(Card("ballista"));
-    this.p2.hand.add(Card("ballista"));
-    this.p1.hand.add(Card("decoy"));
+    this.p2.hand.add(Card("ballista"));*/
+    /*this.p1.hand.add(Card("decoy"));
     this.p2.hand.add(Card("decoy"));*/
     /*
     this.p1.hand.add(Card("dun_banner_medic"));
@@ -86,8 +92,8 @@ var Battle = (function(){
     this.p1.hand.add(Card("isengrim_faoiltiarnah"));
     this.p2.hand.add(Card("isengrim_faoiltiarnah"));*/
 
-    this.p1.addToDiscard([Card("kaedweni_siege_expert")]);
-    this.p2.addToDiscard([Card("kaedweni_siege_expert")]);
+    /*this.p1.addToDiscard([Card("kaedweni_siege_expert")]);
+    this.p2.addToDiscard([Card("kaedweni_siege_expert")]);*/
     /*
         this.p1.hand.add(Card("decoy"));
         this.p1.hand.add(Card("impenetrable_fog"));
@@ -155,10 +161,10 @@ var Battle = (function(){
       cards: JSON.stringify(p.hand.getCards())
     });
     p.send("update:fields", {
-      close: p.field[Card.TYPE.CLOSE_COMBAT],
-      ranged: p.field[Card.TYPE.RANGED],
-      siege: p.field[Card.TYPE.SIEGE],
-      weather: p.field[Card.TYPE.WEATHER]
+      close: p.field[Card.TYPE.CLOSE_COMBAT].getInfo(),
+      ranged: p.field[Card.TYPE.RANGED].getInfo(),
+      siege: p.field[Card.TYPE.SIEGE].getInfo(),
+      weather: p.field[Card.TYPE.WEATHER].getInfo()
     })
   }
 
diff --git a/server/Battleside.js b/server/Battleside.js
index fb9a9f5..af0970a 100644
--- a/server/Battleside.js
+++ b/server/Battleside.js
@@ -21,10 +21,15 @@ Battleside = (function(){
     this._isWaiting = true;
     this.socket = user.socket;
     this.field = {};
-    this.field[Card.TYPE.LEADER] = Field(Card.TYPE.LEADER);
-    this.field[Card.TYPE.CLOSE_COMBAT] = Field(Card.TYPE.CLOSE_COMBAT);
-    this.field[Card.TYPE.RANGED] = Field(Card.TYPE.RANGED);
-    this.field[Card.TYPE.SIEGE] = Field(Card.TYPE.SIEGE);
+    this.field[Card.TYPE.LEADER] = Field(this);
+    this.field[Card.TYPE.CLOSE_COMBAT] = Field(this, true);
+    this.field[Card.TYPE.RANGED] = Field(this, true);
+    this.field[Card.TYPE.SIEGE] = Field(this, true);
+    /*this.field[Card.TYPE.HORN] = {
+      close: Field(this),
+      range: Field(this),
+      siege: Field(this)
+    };*/
     this.n = n ? "p2" : "p1";
     this._name = name;
     this.battle = battle;
@@ -88,14 +93,24 @@ Battleside = (function(){
 
       self.playCard(card);
     })
-    this.receive("agile:field", function(data) {
+    this.receive("agile:field", function(data){
       var fieldType = data.field;
+      if(!(fieldType in [0, 1])) throw new Error("set field agile: false fieldtype " + fieldType);
       self.runEvent("agile:setField", null, [fieldType]);
       self.runEvent("NextTurn", null, [self.foe]);
     })
     this.receive("cancel:agile", function(){
       self.off("agile:setField");
     })
+    this.receive("horn:field", function(data){
+      var fieldType = data.field;
+      if(!(fieldType in [0, 1, 2])) throw new Error("set field horn: false fieldtype " + fieldType);
+      self.runEvent("horn:setField", null, [fieldType]);
+      self.runEvent("NextTurn", null, [self.foe]);
+    })
+    this.receive("cancel:horn", function(){
+      self.off("horn:setField");
+    })
 
 
     this.on("Turn" + this.getID(), this.onTurnStart, this);
@@ -129,7 +144,7 @@ Battleside = (function(){
   }
 
   r.setUpWeatherFieldWith = function(p2){
-    this.field[Card.TYPE.WEATHER] = p2.field[Card.TYPE.WEATHER] = Field(Card.TYPE.WEATHER);
+    this.field[Card.TYPE.WEATHER] = p2.field[Card.TYPE.WEATHER] = Field(this);
   }
 
   r.findCardOnFieldByID = function(id){
@@ -279,8 +294,16 @@ Battleside = (function(){
     this.checkAbilities(card, obj);
     if(obj._cancelPlacement) return 0;
 
-    var field = obj.targetSide.field[card.getType()];
-    field.add(card);
+
+    var field;
+    if(typeof obj.isHorn !== "undefined"){
+      field = obj.targetSide.field[obj.isHorn];
+      field.add(card, true);
+    }
+    else {
+      field = obj.targetSide.field[card.getType()];
+      field.add(card);
+    }
 
 
     this.runEvent("EachCardPlace");
@@ -298,6 +321,49 @@ Battleside = (function(){
     return 1;
   }
 
+  r.setHorn = function(card) {
+    var self = this;
+    this.send("played:horn", {cardID: card.getID()}, true)
+    this.on("horn:setField", function(type){
+      self.off("horn:setField");
+      card.changeType(type);
+      self.placeCard(card, {
+        isHorn: type,
+        disabled: true
+      });
+      self.hand.remove(card);
+    })
+  }
+
+  r.commanderHornAbility = function(card) {
+    var field = this.field[card.getType()];
+    var id = "commanders_horn";
+
+    if(typeof field === "undefined") {
+      //console.log("field unknown | %s", card.getName());
+      return;
+    }
+
+    if(!field.isOnField(card)){
+      field.get().forEach(function(_card){
+        if(_card.getID() == id) return;
+        if(_card.getType() != card.getType()) return;
+        if(_card.hasAbility("hero")) return;
+        _card.setBoost(id, 0);
+      })
+      this.off("EachCardPlace", card.getUidEvents("EachCardPlace"));
+      return;
+    }
+
+    field.get().forEach(function(_card){
+      if(_card.getID() == id) return;
+      if(_card.getType() != card.getType()) return;
+      if(_card.hasAbility("hero")) return;
+      _card.setBoost(id, 0);
+      _card.setBoost(id, _card.getPower());
+    })
+  }
+
   r.checkAbilities = function(card, obj, __flag){
     var self = this;
     obj.targetSide = this;
@@ -316,10 +382,17 @@ Battleside = (function(){
     }
 
     if(ability && !Array.isArray(ability)){
-      if(ability.onBeforePlace) {
+      if(ability.onBeforePlace){
         ability.onBeforePlace.apply(this, [card]);
       }
-      if(ability.cancelPlacement) {
+      if(ability.isCommandersHornCard) {
+        this.setHorn(card);
+      }
+      if(ability.commandersHorn) {
+        ability.onEachCardPlace = this.commanderHornAbility;
+        ability.onWeatherChange = this.commanderHornAbility;
+      }
+      if(ability.cancelPlacement){
         obj._cancelPlacement = true;
       }
       if(ability.waitResponse){
@@ -327,10 +400,11 @@ Battleside = (function(){
       }
       if(ability.changeSide){
         obj.targetSide = this.foe;
-      }/*
-      if(ability.onReset){
-        this.on("Reset", ability.onReset, this, [card])
-      }*/
+      }
+      if(typeof ability.weather !== "undefined"){
+        ability.onEachTurn = this.setWeather.bind(this, ability.weather);
+        ability.onEachCardPlace = this.setWeather.bind(this, ability.weather);
+      }
       if(ability.replaceWith){
         obj._cancelPlacement = true;
         this.on("Decoy:replaceWith", function(replaceCard){
@@ -362,6 +436,10 @@ Battleside = (function(){
         var uid = this.on("EachCardPlace", ability.onEachCardPlace, this, [card]);
         card._uidEvents["EachCardPlace"] = uid;
       }
+      if(ability.onWeatherChange){
+        var uid = this.on("WeatherChange", ability.onWeatherChange, this, [card]);
+        card._uidEvents["WeatherChange"] = uid;
+      }
 
       this.update();
     }
@@ -380,6 +458,49 @@ Battleside = (function(){
     }
   }
 
+  r.setWeather = function(weather){
+    var targetRow = weather;
+    var field;
+    if(typeof targetRow === "undefined") return;
+
+
+    //console.log(this.field[Card.TYPE.WEATHER]);
+    if(targetRow === Card.TYPE.WEATHER){
+      field = this.field[targetRow];
+      field.removeAll();
+
+      for(var i = Card.TYPE.CLOSE_COMBAT; i <= Card.TYPE.SIEGE; i++) {
+        var _field1, _field2, _field;
+        _field1 = this.field[i].get();
+        _field2 = this.foe.field[i].get();
+        _field = _field1.concat(_field2);
+
+        _field.forEach(function(_card){
+          if(_card.hasAbility("hero")) return;
+          _card.setForcedPower(-1);
+        });
+      }
+      this.runEvent("WeatherChange");
+      return;
+    }
+    var forcedPower = 1;
+
+    if(typeof targetRow === "undefined"){
+      console.trace(this);
+    }
+    var field1 = this.field[targetRow].get();
+    var field2 = this.foe.field[targetRow].get();
+
+    field = field1.concat(field2);
+
+    field.forEach(function(_card){
+      if(_card.hasAbility("hero")) return;
+      _card.setForcedPower(forcedPower);
+    });
+    this.runEvent("WeatherChange");
+    this.update();
+  }
+
   r.clearMainFields = function(){
     var cards1 = this.field[Card.TYPE.CLOSE_COMBAT].removeAll();
     var cards2 = this.field[Card.TYPE.RANGED].removeAll();
@@ -432,7 +553,7 @@ Battleside = (function(){
         if(_.isArray(property)){
           var _f = false;
           for(var i = 0; i < property.length; i++) {
-            if(property[i] === val) {
+            if(property[i] === val){
               _f = true;
               break;
             }
diff --git a/server/Card.js b/server/Card.js
index 46a6481..9628582 100644
--- a/server/Card.js
+++ b/server/Card.js
@@ -17,7 +17,6 @@ var Card = (function(){
     this._boost = {};
     this._forcedPower = -1;
     this._init();
-
   };
   var r = Card.prototype;
   /**
@@ -68,6 +67,7 @@ var Card = (function(){
   }
   r.setForcedPower = function(nr){
     this._forcedPower = nr;
+    /*this.getBoost(); *///recalculate
   }
   r.getRawAbility = function(){
     return this._data.ability;
@@ -82,6 +82,16 @@ var Card = (function(){
     }
     return AbilityData[this._data.ability];
   }
+  r.hasAbility = function(ability) {
+    var a = this.getRawAbility();
+    if(Array.isArray(a)) {
+      for(var i=0; i<a.length; i++) {
+        var _a = a[i];
+        if(_a === ability) return true;
+      }
+    }
+    return a === ability;
+  }
   r.getImage = function(){
     return "../assets/cards/" + this._data.img + ".png";
   }
@@ -105,11 +115,6 @@ var Card = (function(){
     return this._id;
   }
 
-  /*r.boost = function(nr){
-    this.getPower(); //to recalculate this._power;
-    this._boost += nr;
-  }*/
-
   r.getBoost = function() {
     var res = 0;
     for(var key in this._boost) {
diff --git a/server/Field.js b/server/Field.js
index 43ffefd..e78f3db 100644
--- a/server/Field.js
+++ b/server/Field.js
@@ -1,13 +1,15 @@
 var Field = (function(){
-  var Field = function(){
+  var Field = function(side, hasHornField){
     if(!(this instanceof Field)){
-      return (new Field());
+      return (new Field(side, hasHornField));
     }
     /**
      * constructor here
      */
 
+    this._hasHornField = hasHornField || false;
     this._cards = [];
+    this.side = side;
   };
   var r = Field.prototype;
   /**
@@ -18,8 +20,19 @@ var Field = (function(){
 
   r._cards = null;
   r._score = 0;
+  r._hasHornField = null;
+  r._hornCard = null;
+  r.side = null;
 
-  r.add = function(card){
+  r.add = function(card, isHorn){
+    /*if(card.hasAbility("commanders_horn")) {
+      this.setHorn(card);
+      return;
+    }*/
+    if(isHorn && this._hasHornField) {
+      this.setHorn(card);
+      return;
+    }
     this._cards.push(card);
     this.updateScore();
   }
@@ -49,6 +62,9 @@ var Field = (function(){
   }
 
   r.isOnField = function(card){
+    if(this._hasHornField && this.getHorn() && card.getID() === this.getHorn().getID()){
+      return true;
+    }
     return this.getPosition(card) >= 0;
   }
 
@@ -69,13 +85,45 @@ var Field = (function(){
 
   r.removeAll = function(){
     var tmp = this._cards.slice();
+    var self = this;
     tmp.forEach(function(card){
       card.reset();
+      for(var event in card._uidEvents) {
+        self.side.off(event, card.getUidEvents(event));
+      }
     })
     this._cards = [];
+    if(this.getHorn()) {
+      var card = this.getHorn();
+      card.reset();
+      this.setHorn(null);
+      for(var event in card._uidEvents) {
+        self.side.off(event, card.getUidEvents(event));
+      }
+      tmp.push(card);
+    }
     return tmp;
   }
 
+  r.getInfo = function() {
+    var self = this;
+    return {
+      cards: self._cards,
+      horn: self.getHorn(),
+      score: self._score
+    }
+  }
+
+  r.getHorn = function() {
+    if(!this._hasHornField) return null;
+    return this._hornCard;
+  }
+
+  r.setHorn = function(card) {
+    if(!this._hasHornField) return null;
+    this._hornCard = card;
+  }
+
   return Field;
 })();
 
diff --git a/server/Hand.js b/server/Hand.js
index fd59edf..a001c10 100644
--- a/server/Hand.js
+++ b/server/Hand.js
@@ -23,7 +23,8 @@ var Hand = (function(){
    */
   r._hand = null;
 
-  r.add = function(card){
+  r.add = function(card){/*
+    console.log(card.getID(), card.getName());*/
     this._hand.push(card);
   }
 
@@ -45,6 +46,8 @@ var Hand = (function(){
     //console.trace(id);
     id = id instanceof Card ? id.getID() : id;
 
+    if(!n) return -1;
+
     for(var i = 0; i < n; i++) {
       if(!this._hand[i]) {
         console.trace(this._hand[i]);
@@ -59,6 +62,7 @@ var Hand = (function(){
 
   r.getRandomCard = function(){
     var rnd = (Math.random() * this._hand.length) | 0;
+    if(!this._hand.length) return -1;
     return this._hand[rnd];
   }
 
diff --git a/site/public/assets/css/app.css b/site/public/assets/css/app.css
index 4b9901c..5898569 100644
--- a/site/public/assets/css/app.css
+++ b/site/public/assets/css/app.css
@@ -1 +1,567 @@
-/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:'Titillium Web';font-style:normal;font-weight:300;src:local("Titillium WebLight"),local("TitilliumWeb-Light"),url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wpr6YfJ4wTnNoNUCmOpdh16Tg.woff2) format("woff2"),url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wpr1uKlGE8-OjkUKWan_M3D6s.woff) format("woff")}@font-face{font-family:'Titillium Web';font-style:normal;font-weight:400;src:local("Titillium Web"),local("TitilliumWeb-Regular"),url(http://fonts.gstatic.com/s/titilliumweb/v4/7XUFZ5tgS-tD6QamInJTceHuglUR2dhBxWD-q_ehMME.woff2) format("woff2"),url(http://fonts.gstatic.com/s/titilliumweb/v4/7XUFZ5tgS-tD6QamInJTcZ_o9VAbKgK36i-4snuAuCM.woff) format("woff")}@font-face{font-family:'Titillium Web';font-style:normal;font-weight:600;src:local("Titillium WebSemiBold"),local("TitilliumWeb-SemiBold"),url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wpr6d1JQt-lS5nD-1TJX2NNl0.woff2) format("woff2"),url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wprx3QmhlKDgQgT1VN2Ed1WFo.woff) format("woff")}*{padding:0;box-sizing:border-box}body,html{height:100%}body{width:100%;font-family:'Titillium Web',sans-serif}::-moz-selection{background:rgba(217,111,31,.99);color:#fff;text-shadow:none}::selection{background:rgba(217,111,31,.99);color:#fff;text-shadow:none}input,textarea{font-family:'Titillium Web',sans-serif;outline:0}a{text-decoration:none}ul{list-style:none}.wrap-modal{max-width:600px;margin:0 auto}.modal{position:fixed;width:100%;height:100%;left:0;top:0;right:0;bottom:0;background:rgba(14,27,43,.8);z-index:10;opacity:0;visibility:hidden}.modal.active{visibility:visible;opacity:1;transition:all .3s ease-in-out 0s}.modal-banner{z-index:20;background:rgba(6,13,22,.95);width:100%;color:#fff;padding:50px 0;margin:15% 0 0;float:left}.icon-load{background:url(../img/load.gif) no-repeat #ca5a07;width:16px;height:16px;float:right;margin:5px -7px 0 7px;display:none}.form-session{float:left}.field-session{float:left;padding:10px 20px;border:0;color:#8798ac;font-size:16px;margin:0 10px 0 0;height:48px;width:220px;background:#243141;background:linear-gradient(to right,#243141 0,#334152 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#243141', endColorstr='#334152', GradientType=1)}.wrap-btn-action{float:left;padding:10px 20px;cursor:pointer;height:48px;background:#da7020;background:linear-gradient(to right,#da7020 0,#ca5907 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#da7020', endColorstr='#ca5907', GradientType=1)}.btn-action{color:#fff;font-size:16px;font-weight:400;border:0;background:0 0;cursor:pointer;float:left;text-transform:uppercase}.btn-none,.btn-second{color:#fff;font-size:16px;font-weight:400;border:0;padding:10px 20px;float:left;position:relative;cursor:pointer;height:48px;text-transform:uppercase;text-decoration:none;background:#243141;background:linear-gradient(to right,#243141 0,#334152 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#243141', endColorstr='#334152', GradientType=1)}.btn-none{background:0 0;color:#8798ac;transition:color .3s ease-in-out 0s}.btn-none:hover{color:#fff}body.landing{background:url(../img/landing-bg.jpg) center top no-repeat;background-size:cover}.wrap-landing{max-width:900px;margin:0 auto}.logo-big{margin:0 auto;display:block}.container-landing{float:left;width:100%;margin:12% 0 0}.teaser-landing{text-align:center;font-size:21px;font-weight:300;text-shadow:0 0 10px rgba(255,255,255,.6);color:#8798ac;line-height:29pt;cursor:default}.choose{float:left;color:#8798ac;margin:10px 30px;cursor:default}.container-form-landing{margin:40px 0;opacity:0}.container-form-landing.active{transition:all .4s ease-in-out .2s;margin:60px 0;opacity:1}
\ No newline at end of file
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ *    user zoom.
+ */
+html {
+  font-family: sans-serif;
+  /* 1 */
+  -ms-text-size-adjust: 100%;
+  /* 2 */
+  -webkit-text-size-adjust: 100%;
+  /* 2 */ }
+
+/**
+ * Remove default margin.
+ */
+body {
+  margin: 0; }
+
+/* HTML5 display definitions
+   ========================================================================== */
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block; }
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+audio,
+canvas,
+progress,
+video {
+  display: inline-block;
+  /* 1 */
+  vertical-align: baseline;
+  /* 2 */ }
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+audio:not([controls]) {
+  display: none;
+  height: 0; }
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
+ */
+[hidden],
+template {
+  display: none; }
+
+/* Links
+   ========================================================================== */
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+a {
+  background-color: transparent; }
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+a:active,
+a:hover {
+  outline: 0; }
+
+/* Text-level semantics
+   ========================================================================== */
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+abbr[title] {
+  border-bottom: 1px dotted; }
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+b,
+strong {
+  font-weight: bold; }
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+dfn {
+  font-style: italic; }
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0; }
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+mark {
+  background: #ff0;
+  color: #000; }
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+small {
+  font-size: 80%; }
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline; }
+
+sup {
+  top: -0.5em; }
+
+sub {
+  bottom: -0.25em; }
+
+/* Embedded content
+   ========================================================================== */
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+img {
+  border: 0; }
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+svg:not(:root) {
+  overflow: hidden; }
+
+/* Grouping content
+   ========================================================================== */
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+figure {
+  margin: 1em 40px; }
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+hr {
+  box-sizing: content-box;
+  height: 0; }
+
+/**
+ * Contain overflow in all browsers.
+ */
+pre {
+  overflow: auto; }
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em; }
+
+/* Forms
+   ========================================================================== */
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+/**
+ * 1. Correct color not being inherited.
+ *    Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+button,
+input,
+optgroup,
+select,
+textarea {
+  color: inherit;
+  /* 1 */
+  font: inherit;
+  /* 2 */
+  margin: 0;
+  /* 3 */ }
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+button {
+  overflow: visible; }
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+button,
+select {
+  text-transform: none; }
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ */
+button,
+html input[type="button"], input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  /* 2 */
+  cursor: pointer;
+  /* 3 */ }
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+button[disabled],
+html input[disabled] {
+  cursor: default; }
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0; }
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+input {
+  line-height: normal; }
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+input[type="checkbox"],
+input[type="radio"] {
+  box-sizing: border-box;
+  /* 1 */
+  padding: 0;
+  /* 2 */ }
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto; }
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
+ *    (include `-moz` to future-proof).
+ */
+input[type="search"] {
+  -webkit-appearance: textfield;
+  /* 1 */
+  /* 2 */
+  box-sizing: content-box; }
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none; }
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em; }
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+legend {
+  border: 0;
+  /* 1 */
+  padding: 0;
+  /* 2 */ }
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+textarea {
+  overflow: auto; }
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+optgroup {
+  font-weight: bold; }
+
+/* Tables
+   ========================================================================== */
+/**
+ * Remove most spacing between table cells.
+ */
+table {
+  border-collapse: collapse;
+  border-spacing: 0; }
+
+td,
+th {
+  padding: 0; }
+
+@font-face {
+  font-family: 'Titillium Web';
+  font-style: normal;
+  font-weight: 300;
+  src: local("Titillium WebLight"), local("TitilliumWeb-Light"), url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wpr6YfJ4wTnNoNUCmOpdh16Tg.woff2) format("woff2"), url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wpr1uKlGE8-OjkUKWan_M3D6s.woff) format("woff"); }
+
+@font-face {
+  font-family: 'Titillium Web';
+  font-style: normal;
+  font-weight: 400;
+  src: local("Titillium Web"), local("TitilliumWeb-Regular"), url(http://fonts.gstatic.com/s/titilliumweb/v4/7XUFZ5tgS-tD6QamInJTceHuglUR2dhBxWD-q_ehMME.woff2) format("woff2"), url(http://fonts.gstatic.com/s/titilliumweb/v4/7XUFZ5tgS-tD6QamInJTcZ_o9VAbKgK36i-4snuAuCM.woff) format("woff"); }
+
+@font-face {
+  font-family: 'Titillium Web';
+  font-style: normal;
+  font-weight: 600;
+  src: local("Titillium WebSemiBold"), local("TitilliumWeb-SemiBold"), url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wpr6d1JQt-lS5nD-1TJX2NNl0.woff2) format("woff2"), url(http://fonts.gstatic.com/s/titilliumweb/v4/anMUvcNT0H1YN4FII8wprx3QmhlKDgQgT1VN2Ed1WFo.woff) format("woff"); }
+
+* {
+  padding: 0;
+  box-sizing: border-box; }
+
+html,
+body {
+  height: 100%; }
+
+body {
+  width: 100%;
+  font-family: 'Titillium Web', sans-serif; }
+
+body.inner {
+  background: url(../img/inner-bg.jpg) center top no-repeat;
+  background-size: cover; }
+
+::-moz-selection {
+  background: rgba(217, 111, 31, 0.99);
+  color: #fff;
+  text-shadow: none; }
+
+::selection {
+  background: rgba(217, 111, 31, 0.99);
+  color: #fff;
+  text-shadow: none; }
+
+input, textarea {
+  font-family: 'Titillium Web', sans-serif;
+  outline: 0; }
+
+a {
+  text-decoration: none; }
+
+ul {
+  list-style: none; }
+
+.wrap-modal {
+  max-width: 600px;
+  margin: 0 auto; }
+
+.modal {
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(14, 27, 43, 0.8);
+  z-index: 10;
+  opacity: 0;
+  visibility: hidden; }
+  .modal.active {
+    visibility: visible;
+    opacity: 1;
+    transition: all 0.3s ease-in-out 0s; }
+
+.modal-banner {
+  z-index: 20;
+  background: rgba(6, 13, 22, 0.95);
+  width: 100%;
+  color: #fff;
+  padding: 50px 0;
+  margin: 15% 0 0 0;
+  float: left; }
+
+.icon-load {
+  background: url(../img/load.gif) #ca5a07 no-repeat;
+  width: 16px;
+  height: 16px;
+  float: right;
+  margin: 5px -7px 0 7px;
+  display: none; }
+
+.form-session {
+  float: left; }
+
+.field-session {
+  float: left;
+  padding: 10px 20px;
+  border: 0;
+  color: #8798ac;
+  font-size: 16px;
+  margin: 0 10px 0 0;
+  height: 48px;
+  width: 220px;
+  background: #243141;
+  background: linear-gradient(to right, #243141 0%, #334152 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#243141', endColorstr='#334152',GradientType=1 ); }
+
+.wrap-btn-action {
+  float: left;
+  padding: 10px 20px;
+  cursor: pointer;
+  height: 48px;
+  background: #da7020;
+  background: linear-gradient(to right, #da7020 0%, #ca5907 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#da7020', endColorstr='#ca5907',GradientType=1 );
+  transition: box-shadow 0.3s ease-in-out 0s; }
+  .wrap-btn-action:hover {
+    box-shadow: 0 0 10px 0 rgba(217, 111, 31, 0.8); }
+
+.btn-action {
+  color: #fff;
+  font-size: 16px;
+  font-weight: 400;
+  border: 0;
+  background: transparent;
+  cursor: pointer;
+  float: left;
+  text-transform: uppercase; }
+
+.btn-second,
+.btn-none {
+  color: #fff;
+  font-size: 16px;
+  font-weight: 400;
+  border: 0;
+  padding: 10px 20px;
+  float: left;
+  position: relative;
+  cursor: pointer;
+  height: 48px;
+  text-transform: uppercase;
+  text-decoration: none;
+  background: #243141;
+  background: linear-gradient(to right, #243141 0%, #334152 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#243141', endColorstr='#334152',GradientType=1 );
+  transition: all 0.3s ease-in-out 0s; }
+
+.btn-second:hover {
+  box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.1); }
+
+.btn-none {
+  background: transparent;
+  color: #8798ac; }
+  .btn-none:hover {
+    color: #fff; }
+
+::-webkit-input-placeholder {
+  color: #697e97; }
+
+:-moz-placeholder {
+  color: #697e97;
+  opacity: 1; }
+
+::-moz-placeholder {
+  color: #697e97;
+  opacity: 1; }
+
+:-ms-input-placeholder {
+  color: #697e97; }
+
+body.landing {
+  background: url(../img/landing-bg.jpg) center top no-repeat;
+  background-size: cover; }
+
+.wrap-landing {
+  max-width: 900px;
+  margin: 0 auto; }
+
+.logo-big {
+  margin: 0 auto;
+  display: block; }
+
+.container-landing {
+  float: left;
+  width: 100%;
+  margin: 12% 0 0 0; }
+  @media (max-width: 1450px) {
+    .container-landing {
+      margin: 9% 0 0 0; } }
+
+.teaser-landing {
+  text-align: center;
+  font-size: 21px;
+  font-weight: 300;
+  text-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
+  color: #8798ac;
+  line-height: 29pt;
+  cursor: default; }
+
+.choose {
+  float: left;
+  color: #8798ac;
+  margin: 10px 30px;
+  cursor: default; }
+
+.container-form-landing {
+  margin: 40px 0;
+  opacity: 0; }
+  .container-form-landing.active {
+    transition: all 0.4s ease-in-out 0s;
+    margin: 60px 0;
+    opacity: 1; }
diff --git a/site/server/composer.lock b/site/server/composer.lock
index 01a2762..dfe1a7c 100644
--- a/site/server/composer.lock
+++ b/site/server/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "5c6026b96e6fa11a641b51a6ba976f5e",
+    "hash": "541ee30ee4a0e782a2473d5a9ab09eb0",
     "packages": [
         {
             "name": "classpreloader/classpreloader",
diff --git a/test/src/CardSpec.js b/test/src/CardSpec.js
new file mode 100644
index 0000000..8a0fa21
--- /dev/null
+++ b/test/src/CardSpec.js
@@ -0,0 +1,13 @@
+var Card = require("../../server/Card");
+
+describe("cards", function(){
+  var card;
+
+  beforeEach(function() {
+    card = Card("john_natalis");
+  });
+
+  it("should have hero ability", function() {
+    expect(card.hasAbility("hero")).toBe(true);
+  })
+})
\ No newline at end of file
diff --git a/test/src/PubSubSpec.js b/test/src/PubSubSpec.js
index 1b9951d..217b412 100644
--- a/test/src/PubSubSpec.js
+++ b/test/src/PubSubSpec.js
@@ -94,5 +94,37 @@ describe("pubsub", function(){
     expect(battle.events).toEqual({});*/
   })
 
+  it("should give binded ctx", function() {
+    var obj = {}, otherCtx = { key: "test"};
+    var card = Card("biting_frost");
+    var ability = card.getAbility();
+
+    obj.setWeather = function(weatherType) {
+      expect(weatherType).toEqual(0);
+      expect(this).toBe(otherCtx);
+    }
+
+    spyOn(obj, "setWeather").and.callThrough();
+
+    expect(ability.weather).toBeDefined();
+
+    ability.onEachTurn = obj.setWeather.bind(otherCtx, ability.weather);
+    ability.onEachCardPlace = obj.setWeather.bind(otherCtx, ability.weather);
+
+    if(ability.onEachTurn){
+      var uid = battle.on("EachTurn", ability.onEachTurn, battle, [card])
+      card._uidEvents["EachTurn"] = uid;
+    }
+    if(ability.onEachCardPlace){
+      var uid = battle.on("EachCardPlace", ability.onEachCardPlace, battle, [card]);
+      card._uidEvents["EachCardPlace"] = uid;
+    }
+
+    battle.runEvent("EachCardPlace");
+    battle.runEvent("EachTurn");
+
+    expect(obj.setWeather).toHaveBeenCalled();
+  })
+
 
 });
diff --git a/test/src/mainSpec.js b/test/src/mainSpec.js
index fbe8f19..677153e 100644
--- a/test/src/mainSpec.js
+++ b/test/src/mainSpec.js
@@ -1,5 +1,6 @@
 require("./filterSpec");
 require("./PubSubSpec");
+require("./CardSpec");
 
 (function main(){