Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Sub-enemies

DM can now track sub-enemies.  So players see Skeletons as an enemy.
DM can then break that down into Skeleton 1, Skeleton 2, and Skeleton
3, all of which go on the same turn.  Can track name and current/max
health for each sub-enemy.  Can add and delete sub-enemies.
  • Loading branch information...
commit 1df5115286e653dec0a58a94a66e8afd9206917e 1 parent c87dd70
@moneypenny moneypenny authored
View
2  README.md
@@ -20,7 +20,7 @@ View for the Dungeon Master:
![Dungeons and Dragons Leaderboard in Chrome](https://raw.github.com/joekr/D-D-Leaderboard/master/screenshot-2.png "Dungeon Master view in Chrome")
-See [Handlebars](http://handlebarsjs.com/) for the template system. See also [Meteor documentation](http://docs.meteor.com/). A good write up on Meteor [Fundamentals and Best Practices](http://andrewscala.com/meteor/)
+See [Handlebars](http://handlebarsjs.com/) for the template system. See also [Meteor documentation](http://docs.meteor.com/). A good write up on Meteor: [Fundamentals and Best Practices](http://andrewscala.com/meteor/).
## Install Meteor
$ curl install.meteor.com | /bin/sh
View
9 client/dnd.css
@@ -119,6 +119,8 @@ fieldset.dm,
.navbar fieldset.dm {
display: none;
}
+.enemy-breakdown .dm.options .create-new-sub-enemy {
+}
footer {
color: #B4B4B4;
}
@@ -138,6 +140,10 @@ footer {
.icon-sword {
background-image: url('/icon-sword.gif');
}
+input.error {
+ border-color: #B94A48;
+ color: #B94A48;
+}
label[for] {
cursor: pointer;
}
@@ -159,7 +165,8 @@ body.dm .navbar .control-group.dm {
.navbar .control-group.error .radio,
.navbar .control-group.error input,
.navbar .control-group.error select,
-.navbar .control-group.error textarea {
+.navbar .control-group.error textarea,
+input.error {
background-color: #FFDDD7;
}
.navbar fieldset,
View
47 client/dnd.html
@@ -82,8 +82,8 @@
<label for="new-enemy">Enemy?</label>
</div>
<div class="form-actions">
- <input type="button" id="add-button" class="btn btn-primary btn-large" value="Add">
- <input type="reset" id="reset-button" class="btn btn-mini btn-danger" value="Reset">
+ <a href="#save" class="btn btn-success" id="add-button"><i class="icon-ok icon-white"></i></a>
+ <a href="#clear" class="btn btn-danger" id="reset-button"><i class="icon-remove icon-white"></i></a>
</div>
</form>
</template>
@@ -296,21 +296,33 @@
</div>
</template>
-<template name="character_hp">
- <td class="dm">
- {{#if isBloodied}}
- <i class="icon-blood"></i>
- {{/if}}
- <span class="current-and-max-hp">
- <span class="current-hp">{{currentHP}}</span> /
- <span class="max-hp">{{maxHP}}</span>
- </span>
- </td>
+<template name="sub_enemy_row">
+ <tr data-id="{{_id}}" data-char-id="{{charID}}" class="{{#if active}}active{{/if}} enemy-{{isEnemy}} enemy-breakdown">
+ <td class="dm">
+ {{#if isBloodied}}
+ <i class="icon-blood"></i>
+ {{/if}}
+ <span class="current-and-max-hp">
+ <span class="current-hp">{{currentHP}}</span> /
+ <span class="max-hp">{{maxHP}}</span>
+ </span>
+ </td>
+ <td class="dm" colspan="6">
+ <form class="dm form-inline pull-left">
+ <input type="text" class="input-medium" id="sub-enemy-name" placeholder="Name" value="{{name}}">
+ <a href="#save" class="btn btn-success" id="sub-enemy-save"><i class="icon-white icon-ok"></i></a>
+ </form>
+ </td>
+ <td class="dm options">
+ <a href="#delete-sub-enemy" class="delete btn btn-danger"><i class="icon-white icon-trash"></i></a>
+ <a href="#create-new" class="btn btn-info create-new-sub-enemy"><i class="icon-white icon-plus"></i></a>
+ </td>
+ </tr>
</template>
<template name="character">
<tr data-id="{{_id}}" class="{{#if active}}active{{/if}} enemy-{{isEnemy}}">
- <td class="char-name" rowspan="2">
+ <td class="char-name" rowspan="{{rowSpan}}">
{{name}}
</td>
<td class="dm"></td>
@@ -322,12 +334,9 @@
</td>
</tr>
{{#if isEnemy}}
- <tr data-id="{{_id}}" class="{{#if active}}active{{/if}} enemy-{{isEnemy}} enemy-breakdown">
- {{> character_hp}}
- <td class="dm" colspan="7">
-
- </td>
- </tr>
+ {{#each subEnemies}}
+ {{> sub_enemy_row}}
+ {{/each}}
{{else}}
<tr data-id="{{_id}}" class="{{#if active}}active{{/if}} enemy-{{isEnemy}} status-effects">
<td class="dm">
View
153 client/dnd.js
@@ -1,4 +1,15 @@
CharacterList = new Meteor.Collection("characters");
+SubCharacterList = new Meteor.Collection("sub-characters");
+
+var addNewSubEnemy = function(charID, props) {
+ SubCharacterList.insert({
+ charID: charID,
+ name: props.name,
+ currentHP: props.currentHP,
+ maxHP: props.maxHP,
+ isEnemy: true
+ });
+ };
var playCharacterAudio = function(character) {
//console.debug("Going to play character audio " + char.name);
@@ -157,7 +168,8 @@ Template.navbar.events = {
'change #new-enemy': function() {
showEnemyHealthFieldsetIfNecessary();
},
- 'click #add-button': function() {
+ 'click #add-button': function(event) {
+ event.preventDefault();
var id = $('#character-id').val();
var character = CharacterList.findOne({
_id: id
@@ -174,8 +186,10 @@ Template.navbar.events = {
var charRef = parseInt($("#char-ref").val(), 10);
var charWill = parseInt($("#char-will").val(), 10);
var damage = parseInt($("#char-dmg").val(), 10);
- var currentHP = isEnemy ? parseInt($('#enemy-hp').val(), 10) : -1;
- var maxHP = isEnemy ? parseInt($('#enemy-max-hp').val(), 10) : -1;
+ var enemyHPRaw = $('#enemy-hp').val();
+ var maxEnemyHPRaw = $('#enemy-max-hp').val();
+ var currentHP = isEnemy && $.trim(enemyHPRaw) != '' ? parseInt(enemyHPRaw, 10) : 1;
+ var maxHP = isEnemy && $.trim(maxEnemyHPRaw) != '' ? parseInt(maxEnemyHPRaw, 10) : 1;
if (null == character) {
var charProps = {
name: name,
@@ -193,7 +207,10 @@ Template.navbar.events = {
effects: []
};
console.debug("Inserting new character: " + JSON.stringify(charProps));
- CharacterList.insert(charProps);
+ var newCharID = CharacterList.insert(charProps);
+ if (isEnemy) {
+ addNewSubEnemy(newCharID, charProps);
+ }
} else {
console.debug("Updating character #" + id);
CharacterList.update({
@@ -215,15 +232,17 @@ Template.navbar.events = {
}
$('#reset-button').click();
},
- 'click #reset-button': function() {
+ 'click #reset-button': function(event) {
+ event.preventDefault();
$('#new-enemy').attr('checked', false);
- $('#add-button').val('Add');
$("#char-ac").val('');
$("#char-fort").val('');
$("#char-ref").val('');
$("#char-will").val('');
$("#char-dmg").val('');
$('#character-id').val('');
+ $('#enemy-hp').val('');
+ $('#enemy-max-hp').val('');
$('.navbar .control-group.error').removeClass('error');
}
};
@@ -239,7 +258,7 @@ Template.character_list.characters = function() {
});
};
-Template.character.isBloodied = function() {
+Template.sub_enemy_row.isBloodied = function() {
return this.currentHP <= (this.maxHP / 2);
};
@@ -252,6 +271,7 @@ var editCharacter = function(character) {
$("#char-will").val(character.char_will);
$("#char-dmg").val(character.damage);
$('#new-enemy').attr('checked', character.isEnemy);
+ showEnemyHealthFieldsetIfNecessary();
if (character.isEnemy) {
$('#enemy-hp').removeAttr('disabled').val(character.currentHP);
$('#enemy-max-hp').removeAttr('disabled').val(character.maxHP);
@@ -259,32 +279,76 @@ var editCharacter = function(character) {
$('#enemy-hp, #enemy-max-hp').attr('disabled', 'disabled');
}
$('#character-id').val(character._id);
- $('#add-button').val('Edit');
$('html, body').animate({scrollTop: 0}, 500);
};
-Template.character.events = {
- 'click .delete': function() {
- //console.debug('delete' + this);
- if (confirm("Are you sure you want to delete character " + this.name + "?")) {
- CharacterList.remove(this._id);
+Template.character.rowSpan = function() {
+ if (this.isEnemy) {
+ return SubCharacterList.find({
+ charID: this._id
+ }).count() + 1;
+ }
+ return 2;
+};
+
+Template.character.subEnemies = function() {
+ return SubCharacterList.find({
+ charID: this._id
+ }, {
+ sort: {
+ name: 1
+ }
+ });
+};
+
+Template.sub_enemy_row.events = {
+ 'click a[href=#delete-sub-enemy]': function() {
+ if (confirm("Are you sure you want to delete enemy " + this.name + "?")) {
+ SubCharacterList.remove(this._id);
}
return false;
},
- 'click .char-name': function() {
- editCharacter(this);
- },
- 'click a[href=#retire]': function() {
- //console.debug("Retiring " + this.name);
- CharacterList.update({
+ 'click a[href=#save]': function(event) {
+ var link = $(event.currentTarget);
+ var form = link.parent();
+ var nameInput = $('#sub-enemy-name', form);
+ var name = nameInput.val();
+ if ($.trim(name) == '') {
+ nameInput.addClass('error');
+ return false;
+ }
+ nameInput.removeClass('error');
+ SubCharacterList.update({
_id: this._id
}, {
$set: {
- char_in_game: false
+ name: name
}
});
return false;
},
+ 'click a[href=#create-new]': function(event) {
+ var link = $(event.currentTarget);
+ var charID = this.charID;
+ var subCharCount = link.closest('tbody').children('tr[data-char-id=' + charID + ']').length;
+ var name = this.name;
+ var namePrefix;
+ if (name.indexOf(' ') > -1) {
+ namePrefix = name.split(' ')[0];
+ } else if (name.indexOf('#') > -1) {
+ namePrefix = name.split('#')[0];
+ } else {
+ namePrefix = name;
+ }
+ SubCharacterList.insert({
+ charID: charID,
+ name: namePrefix + ' #' + (subCharCount + 1),
+ currentHP: this.maxHP,
+ maxHP: this.maxHP,
+ isEnemy: true
+ });
+ return false;
+ },
'click .current-and-max-hp': function(event) {
var currentAndMaxHPSpan = $(event.currentTarget);
if (currentAndMaxHPSpan.hasClass('editing')) {
@@ -293,23 +357,32 @@ Template.character.events = {
currentAndMaxHPSpan.addClass('editing');
var currentHPSpan = $('.current-hp', currentAndMaxHPSpan);
var currentHP = currentHPSpan.text();
- var input = $('<input type="number">');
+ var input = $('<input type="number" class="current-hp-edit">');
input.val(currentHP);
currentHPSpan.html(input);
var enemy = this;
var updateCurrentHP = function() {
- var newHP = parseInt(input.val(), 10);
- CharacterList.update({
- _id: enemy._id
- }, {
- $set: {
- currentHP: newHP
- }
- });
+ var newHPRaw = input.val();
+ if ($.trim(newHPRaw) != '') {
+ var newHP = parseInt(newHPRaw, 10);
+ SubCharacterList.update({
+ _id: enemy._id
+ }, {
+ $set: {
+ currentHP: newHP
+ }
+ });
+ }
input.remove();
currentHPSpan.text(newHP);
currentAndMaxHPSpan.removeClass('editing');
};
+ /*input.change(function() {
+ var idInEditForm = $('#character-id').val();
+ if (idInEditForm == enemy._id) {
+ $('#enemy-hp').val(input.val());
+ }
+ });*/
input.keypress(function(e) {
if (e.which == 13) { // Enter
updateCurrentHP();
@@ -319,6 +392,28 @@ Template.character.events = {
}
};
+Template.character.events = {
+ 'click .delete': function() {
+ if (confirm("Are you sure you want to delete character " + this.name + "?")) {
+ CharacterList.remove(this._id);
+ }
+ return false;
+ },
+ 'click .char-name': function() {
+ editCharacter(this);
+ },
+ 'click a[href=#retire]': function() {
+ CharacterList.update({
+ _id: this._id
+ }, {
+ $set: {
+ char_in_game: false
+ }
+ });
+ return false;
+ }
+};
+
Template.out_game_character_list.characters = function() {
return CharacterList.find({
char_in_game: false
View
5 server/dnd.js
@@ -1,9 +1,14 @@
CharacterList = new Meteor.Collection("characters");
+SubCharacterList = new Meteor.Collection("sub-characters");
Meteor.publish('characters', function() {
return Lists.find();
});
+Meteor.publish('sub-characters', function() {
+ return Lists.find();
+});
+
Meteor.startup(function () {
// code to run on server at startup
});
Please sign in to comment.
Something went wrong with that request. Please try again.