Initial version of updated consolidated editor.

git-svn-id: https://andors-trail.googlecode.com/svn/trunk@66 08aca716-68be-ccc6-4d58-36f5abd142ac
This commit is contained in:
oskar.wiksten@gmail.com
2011-04-11 18:29:22 +00:00
parent a502ad1f56
commit 40125054cf
11 changed files with 3566 additions and 0 deletions

View File

@@ -0,0 +1,453 @@
function IncludeJavascript(jsFile) {
document.write('<script type="text/javascript" src="' + jsFile + '"></scr' + 'ipt>');
}
IncludeJavascript("FieldList.js");
IncludeJavascript("DataStore.js");
IncludeJavascript("ImageSelector.js");
IncludeJavascript("EditorTabs.js");
IncludeJavascript("inc/jquery.shorten.min.js");
var model;
var imageSelector;
var tabs;
var questlogDialog;
var onHitConditionsDialog;
var equipConditionsDialog;
var droplistItemDialog;
function checkboxHidesElement(checkbox, element, visibleCondition) {
checkbox.change(function () {
if (checkbox.attr("checked")) {
element.fadeIn("slow");
} else {
element.fadeOut("slow");
}
});
var visible = bool(visibleCondition);
checkbox.attr("checked", visible);
element.toggle(visible);
}
function bool(v) {
return v ? true : false;
}
function setInputFieldsToObjectValues(div, obj) {
div.find("input,select,textarea").each(function() {
$(this).val(obj[$(this).attr("id")]);
});
div.find("input:checkbox").each(function() {
//$(this).unbind();
$(this).attr("checked", bool(obj[$(this).attr("id")]));
});
}
function bindInputFieldChangesToObject(div, obj) {
div.find("input,select,textarea").unbind().change(function() {
obj[$(this).attr("id")] = $(this).val();
});
div.find("input:checkbox").unbind("change").change(function() {
obj[$(this).attr("id")] = $(this).attr("checked") ? 1 : 0;
});
}
function applyEditorBindingsForObject(div, obj) {
div.find("input").addClass("ui-widget-content ui-corner-all");
setInputFieldsToObjectValues(div, obj);
bindInputFieldChangesToObject(div, obj);
}
function applyCommonEditorBindings(div, obj, dataStore) {
applyEditorBindingsForObject(div, obj);
div.find("#" + dataStore.nameField).change(function() { dataStore.onNameChanged(obj, $(this).val()); });
}
function createMonsterEditor(obj) {
var div = $( "#templates #editMonster" ).clone();
applyCommonEditorBindings(div, obj, model.monsters);
checkboxHidesElement(div.find('#hasConversation'), div.find('#hasConversationDisplay'), obj.phraseID);
checkboxHidesElement(div.find('#hasCombat'), div.find('#hasCombatDisplay'), obj.attackChance);
checkboxHidesElement(div.find('#hasCritical'), div.find('#hasCriticalDisplay'), obj.criticalChance || obj.criticalMultiplier);
checkboxHidesElement(div.find('#hasHitEffect'), div.find('#hasHitEffectDisplay'), obj.hasHitEffect);
imageSelector.imageify(div.find('#monsterimage'), div.find('#iconID'), 'monsters');
bindFieldToDataStore( $( "#droplistID", div ), model.droplists , function(obj) { return obj.id; } );
var createNewCondition = function() { return { chance: 100, magnitude: 1 }; }
if (!obj.onHit_conditionsSource) obj.onHit_conditionsSource = [];
if (!obj.onHit_conditionsTarget) obj.onHit_conditionsTarget = [];
var setupEditor = function(div) {
bindFieldToDataStore( $( "#condition", div ), model.actorEffects , function(obj) { return obj.id; } );
}
applyTableEditor( $( "#onHit_conditionsSource", div ) , onHitConditionsDialog, obj.onHit_conditionsSource, createNewCondition, setupEditor);
applyTableEditor( $( "#onHit_conditionsTarget", div ) , onHitConditionsDialog, obj.onHit_conditionsTarget, createNewCondition, setupEditor);
return div;
}
function createItemEditor(obj) {
var div = $( "#templates #editItem" ).clone();
applyCommonEditorBindings(div, obj, model.items);
checkboxHidesElement(div.find('#hasEquipEffect'), div.find('#hasEquipEffectDisplay'), obj.hasEquipEffect);
checkboxHidesElement(div.find('#hasUseEffect'), div.find('#hasUseEffectDisplay'), obj.hasUseEffect);
checkboxHidesElement(div.find('#equip_hasCritical'), div.find('#equip_hasCriticalDisplay'), obj.equip_criticalChance || obj.equip_criticalMultiplier);
checkboxHidesElement(div.find('#hasHitEffect'), div.find('#hasHitEffectDisplay'), obj.hasHitEffect);
checkboxHidesElement(div.find('#hasKillEffect'), div.find('#hasKillEffectDisplay'), obj.hasKillEffect);
imageSelector.imageify(div.find('#itemimage'), div.find('#iconID'), 'items');
var createNewCondition = function() { return { chance: 100, magnitude: 1 }; }
if (!obj.equip_conditions) obj.equip_conditions = [];
if (!obj.use_conditionsSource) obj.use_conditionsSource = [];
if (!obj.hit_conditionsSource) obj.hit_conditionsSource = [];
if (!obj.hit_conditionsTarget) obj.hit_conditionsTarget = [];
if (!obj.kill_conditionsSource) obj.kill_conditionsSource = [];
var setupEditor = function(div) {
bindFieldToDataStore( $( "#condition", div ), model.actorEffects , function(obj) { return obj.id; } );
}
applyTableEditor( $( "#equip_conditions", div ) , equipConditionsDialog, obj.equip_conditions, createNewCondition, setupEditor);
applyTableEditor( $( "#use_conditionsSource", div ) , onHitConditionsDialog, obj.use_conditionsSource, createNewCondition, setupEditor);
applyTableEditor( $( "#hit_conditionsSource", div ) , onHitConditionsDialog, obj.hit_conditionsSource, createNewCondition, setupEditor);
applyTableEditor( $( "#hit_conditionsTarget", div ) , onHitConditionsDialog, obj.hit_conditionsTarget, createNewCondition, setupEditor);
applyTableEditor( $( "#kill_conditionsSource", div ) , onHitConditionsDialog, obj.kill_conditionsSource, createNewCondition, setupEditor);
return div;
}
function bindFieldToDataStore(field, dataStore, converter) {
var dataCallback = function(request, response) {
var result = [];
var pattern = new RegExp(request.term, "i");
dataStore.items.forEach(function(obj) {
var name = converter(obj);
if (name.match(pattern)) {
result.push(name);
}
});
response(result);
};
field.autocomplete( "destroy" ).autocomplete({ source: dataCallback, minLength: 0 });
}
function createStatusEffectEditor(obj) {
var div = $( "#templates #editActorEffect" ).clone();
applyCommonEditorBindings(div, obj, model.actorEffects);
checkboxHidesElement(div.find('#hasRoundEffect'), div.find('#hasRoundEffectDisplay'), obj.hasRoundEffect);
checkboxHidesElement(div.find('#hasFullRoundEffect'), div.find('#hasFullRoundEffectDisplay'), obj.hasFullRoundEffect);
checkboxHidesElement(div.find('#hasAbilityEffect'), div.find('#hasAbilityEffectDisplay'), obj.hasAbilityEffect);
checkboxHidesElement(div.find('#hasCritical'), div.find('#hasCriticalDisplay'), obj.criticalChance || obj.criticalMultiplier);
imageSelector.imageify(div.find('#statuseffectimage'), div.find('#iconID'), 'effects');
return div;
}
function applyTableEditor(table, dialog, array, templateFunction, editorSetup) {
var updateRowText = function(row, obj) {
$( "td", row ).each(function() {
var id = $( this ).attr("id");
var val = obj[id];
val = val ? val : "";
$( "td#" + id, row ).text(val).shorten({
width: '200'
}).css('display','');
});
};
var addToList = function(obj) {
var row = $( "<tr>" );
table.find("th").each(function() {
var id = $( this ).attr("id");
row.append( $( "<td>" ).attr("id", id) );
});
updateRowText(row, obj);
table.append(row);
row.click(function() {
applyEditorBindingsForObject( dialog, obj );
if (editorSetup) { editorSetup(dialog); }
dialog.unbind( "dialogclose" ).bind( "dialogclose", function() {
updateRowText(row, obj);
});
dialog.dialog( "open" );
});
return row;
};
table.parent().find("#add").button().click(function() {
var obj = templateFunction();
array.push( obj );
addToList( obj ).click();
});
table.addClass("ui-corner-all");
$( "thead", table ).addClass("ui-widget-header");
array.forEach(addToList);
}
function createQuestEditor(obj) {
var div = $( "#templates #editQuest" ).clone(true);
applyCommonEditorBindings(div, obj, model.quests);
if (!obj.stages) obj.stages = [];
var array = obj.stages;
var createNewStage = function() {
var nextProgress;
if (array.length > 0) { nextProgress = parseInt(array[array.length - 1].progress) + 10; }
if (!nextProgress) { nextProgress = 10; }
return { progress: nextProgress };
};
applyTableEditor( $( "#stages", div ) , questlogDialog, array, createNewStage, function() {});
return div;
}
function createDroplistEditor(obj) {
var div = $( "#templates #editDroplist" ).clone(true);
applyCommonEditorBindings(div, obj, model.droplists);
if (!obj.items) obj.items = [];
var createNewDroplistItem = function() { return { quantity: 1, chance: 100 } };
var setupEditor = function(div) {
bindFieldToDataStore( $( "#itemID", div ), model.items , function(obj) { return obj.searchTag; } );
}
applyTableEditor( $( "#items", div ) , droplistItemDialog, obj.items, createNewDroplistItem, setupEditor);
return div;
}
function openTabForObject(obj, dataStore) {
tabs.openTabForObject(obj, dataStore.objectTypename, obj[dataStore.nameField]);
}
function bindObjectsToItemList(itemListDiv, dataStore) {
itemListDiv.children().remove();
var addToList = function(obj) {
var item = $("<li>" + obj[dataStore.nameField] + "</li>");
item.click(function() { openTabForObject(obj, dataStore); });
itemListDiv.append(item);
item.hide().fadeIn('slow');
};
dataStore.items.forEach(addToList);
dataStore.onAdded = addToList;
dataStore.onDeserialized = function() {
bindObjectsToItemList(itemListDiv, dataStore);
// TODO: Should also close all tabs.
};
dataStore.onNameChanged = function(obj, name) {
$("li:eq(" + dataStore.items.indexOf(obj) + ")", itemListDiv).html(name);
//TODO: Should this really be in the same function?
// (splitting the left part from the tab controls would reduce coupling, which would be a good thing.)
tabs.renameTabForObject(obj, name);
};
}
function exportIfExists(dataStore, div) {
var exists = false;
if (dataStore && dataStore.items.length > 0) exists = true;
div.toggle(exists);
if (!exists) { return; }
var exportData = dataStore.serialize();
$( "#value" , div ).val(exportData);
}
function prepareImport(dataStore, div) {
var importButton = $( "#import", div );
var textarea = $( "#value", div );
importButton.button({ disabled: true }).click(function() {
if (!textarea.val()) return;
dataStore.deserialize(textarea.val());
div.hide('slow');
});
textarea.val("").change(function() {
var disabled = $(this).val() ? false : true;
importButton.button( "option", "disabled", disabled );
});
}
function bindEditorType(dataStore, div, createObjectEditor, newObjectCreator) {
tabs.registerEditorType(dataStore.objectTypename, createObjectEditor);
bindObjectsToItemList( $( "ul", div ), dataStore );
$( "#add", div )
.button()
.click(function() {
var obj = newObjectCreator();
dataStore.add(obj);
openTabForObject( obj, dataStore );
});
}
function startEditor() {
model = {
actorEffects: new DataStore('effect', new FieldList("[id|name|iconID|isStacking|"
+ "hasRoundEffect|round_visualEffectID|round_boostHP_Min|round_boostHP_Max|round_boostAP_Min|round_boostAP_Max|"
+ "hasFullRoundEffect|fullround_visualEffectID|fullround_boostHP_Min|fullround_boostHP_Max|fullround_boostAP_Min|fullround_boostAP_Max|"
+ "hasAbilityEffect|boostMaxHP|boostMaxAP|moveCostPenalty|attackCost|attackChance|criticalChance|criticalMultiplier|attackDamage_Min|attackDamage_Max|blockChance|damageResistance|"
+ "];"))
,quests: new DataStore('quest', new FieldList("[id|name|showInLog|stages[progress|logText|rewardExperience|finishesQuest|]|];"))
,items: new DataStore('item', new FieldList("[iconID|name|searchTag|category|baseMarketCost|"
+ "hasEquipEffect|equip_boostMaxHP|equip_boostMaxAP|equip_moveCostPenalty|equip_attackCost|equip_attackChance|equip_criticalChance|equip_criticalMultiplier|equip_attackDamage_Min|equip_attackDamage_Max|equip_blockChance|equip_damageResistance|equip_conditions[condition|magnitude|]|"
+ "hasUseEffect|use_boostHP_Min|use_boostHP_Max|use_boostAP_Min|use_boostAP_Max|use_conditionsSource[condition|magnitude|duration|chance|]|"
+ "hasHitEffect|hit_boostHP_Min|use_boostHP_Max|hit_boostAP_Min|hit_boostAP_Max|hit_conditionsSource[condition|magnitude|duration|chance|]|hit_conditionsTarget[condition|magnitude|duration|chance|]|"
+ "hasKillEffect|kill_boostHP_Min|kill_boostHP_Max|kill_boostAP_Min|kill_boostAP_Max|kill_conditionsSource[condition|magnitude|duration|chance|]|"
+ "];"))
,droplists: new DataStore('droplist', new FieldList("[id|items[itemID|quantity|chance|]|];"), 'id')
,dialogue: new DataStore('dialogue', new FieldList("[id|name|];"))
,monsters: new DataStore('monster', new FieldList("[iconID|name|tags|size|exp|maxHP|maxAP|moveCost|attackCost|attackChance|criticalChance|criticalMultiplier|attackDamage_Min|attackDamage_Max|blockChance|damageResistance|droplistID|phraseID|"
+ "hasHitEffect|onHit_boostHP_Min|onHit_boostHP_Max|onHit_boostAP_Min|onHit_boostAP_Max|onHit_conditionsSource[condition|magnitude|duration|chance|]|onHit_conditionsTarget[condition|magnitude|duration|chance|]|"
+ "];"))
};
model.actorEffects.add({id: "bless", name: "Bless", iconID: "items_tiles:318", hasAbilityEffect: true, attackChance: 15, blockChance: 5});
model.actorEffects.add({id: "poison_weak", name: "Weak Poison", iconID: "items_tiles:340", hasRoundEffect: true, round_visualEffectID: 2, round_boostHP_Min: -1, round_boostHP_Max: -1});
model.quests.add({id: "testQuest", name: "Test quest", stages: [ { progress: 10, logText: "Stage 10"} , { progress: 20, logText: "Stage 20", finishesQuest: 1 } ] });
model.items.add({iconID: "items_tiles:70", name: "Test item", searchTag: "item0", category: 0, baseMarketCost: 51, hasEquipEffect: 1, equip_attackChance: 10, equip_attackDamage_Min: 2, equip_attackDamage_Max: 4});
model.items.add({iconID: "items_tiles:266", name: "Ring of damage +1", searchTag: "dmg_ring1", category: 7, baseMarketCost: 62, hasEquipEffect: 1, equip_attackDamage_Min: 1, equip_attackDamage_Max: 1});
model.droplists.add({id: "merchant1", items: [ { itemID: 'dmg_ring1', quantity: 5, chance: 100 } , { itemID: 'item0', quantity: 1, chance: 100 } ] } );
model.monsters.add({name: "Small ant", iconID: "monsters_insects:2", maxHP: 30, size: '1x1'});
model.monsters.add({name: "Red ant", iconID: "monsters_insects:3", maxHP: 20, size: '1x1'});
model.monsters.add({name: "Wasp", iconID: "monsters_insects:1", maxHP: 10, size: '1x1'});
$( "#left #tools" ).accordion({ fillSpace: true });
tabs = new EditorTabs( $( "#center #tabs" ) );
bindEditorType(model.actorEffects, $( "#tools #effectlist" ), createStatusEffectEditor, function() {
return {name: "New Effect", id: 'new_effect' };
});
bindEditorType(model.quests, $( "#tools #questlist" ), createQuestEditor, function() {
return {name: "New Quest", id: 'new_quest' };
});
bindEditorType(model.items, $( "#tools #itemlist" ), createItemEditor, function() {
return {name: "New Item", searchTag: "new_item", category: 31 };
});
bindEditorType(model.droplists, $( "#tools #droplist" ), createDroplistEditor, function() {
return {id: "new_droplist" };
});
bindEditorType(model.monsters, $( "#tools #monsterlist" ), createMonsterEditor, function() {
return {name: "New Monster", maxAP: 10, attackCost: 5, moveCost: 5, size: '1x1'};
});
var importExportDialog;
$( "#buttons #import" )
.button()
.click(function() {
importExportDialog.dialog({ title: "Import data" });
$( "div", importExportDialog ).show();
prepareImport(model.actorEffects, $( "#statuseffects", importExportDialog ));
prepareImport(model.quests, $( "#quests", importExportDialog ));
prepareImport(model.items, $( "#items", importExportDialog ));
prepareImport(model.droplists, $( "#droplists", importExportDialog ));
prepareImport(model.dialogue, $( "#dialogue", importExportDialog ));
prepareImport(model.monsters, $( "#monsters", importExportDialog ));
importExportDialog.dialog( "open" );
});
$( "#buttons #export" )
.button()
.click(function() {
importExportDialog.dialog({ title: "Export data" });
exportIfExists(model.actorEffects, $( "#statuseffects", importExportDialog ));
exportIfExists(model.quests, $( "#quests", importExportDialog ));
exportIfExists(model.items, $( "#items", importExportDialog ));
exportIfExists(model.droplists, $( "#droplists", importExportDialog ));
exportIfExists(model.dialogue, $( "#dialogue", importExportDialog ));
exportIfExists(model.monsters, $( "#monsters", importExportDialog ));
$( "#import", importExportDialog ).hide();
importExportDialog.dialog( "open" );
});
var defaultButtons = {
Close: function() { $( this ).dialog( "close" ); }
};
importExportDialog = $( "#templates #dialog-importexport" )
.dialog({
modal: true,
autoOpen: false,
width: 800,
height: 500,
buttons: defaultButtons
});
questlogDialog = $( "#templates #dialog-questlog" )
.dialog({
title: "Quest log item",
modal: true,
autoOpen: false,
width: 450,
buttons: defaultButtons
});
onHitConditionsDialog = $( "#templates #dialog-onHitConditions" )
.dialog({
title: "Actor status conditon",
modal: true,
autoOpen: false,
width: 350,
buttons: defaultButtons
});
equipConditionsDialog = $( "#templates #dialog-equipConditions" )
.dialog({
title: "Actor status conditon",
modal: true,
autoOpen: false,
width: 350,
buttons: defaultButtons
});
droplistItemDialog = $( "#templates #dialog-droplistItem" )
.dialog({
title: "Droplist item",
modal: true,
autoOpen: false,
width: 350,
buttons: defaultButtons
});
imageSelector = new ImageSelector("http://andors-trail.googlecode.com/svn/trunk/AndorsTrail/res/drawable/", $( "#dialog-images" ) );
imageSelector.add(new TilesetImage("items_tiles", {x: 14, y:30}, {x: 34, y:34}, [ 'items', 'effects' ] ));
imageSelector.add(new TilesetImage("monsters_armor1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_demon1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_demon2", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_dogs", {x: 7, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_dragons", {x: 7, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_eye1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_eye2", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_eye3", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_eye4", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_ghost1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_ghost2", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_hydra1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_insects", {x: 6, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_liches", {x: 4, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_mage", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_mage2", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_mage3", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_mage4", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_man1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_men", {x: 9, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_men2", {x: 10, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_misc", {x: 12, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_rats", {x: 5, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_rogue1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_skeleton1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_skeleton2", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_snakes", {x: 6, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_cyclops", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_warrior1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_wraiths", {x: 3, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_zombie1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_zombie2", {x: 1, y:1}, undefined, [ 'monsters' ] ));
imageSelector.add(new TilesetImage("monsters_dragon1", {x: 1, y:1}, undefined, [ 'monsters' ] ));
}

View File

@@ -0,0 +1,141 @@
var DataStore_Data_fieldValue = '[^\\{\\}\\|]*';
var DataStore_Data_arrayObject = '\\{(' + DataStore_Data_fieldValue + '\\|)*\\}';
var DataStore_Data_arrayObjectPattern = new RegExp(DataStore_Data_arrayObject, 'g');
var DataStore_Data_arrayField = '\\{(' + DataStore_Data_arrayObject + ')*\\}';
var DataStore_Data_field = '(' + DataStore_Data_fieldValue + '|' + DataStore_Data_arrayField + ')\\|';
var DataStore_Data_pattern = new RegExp(DataStore_Data_field, 'g');
var DataStore_Data_line = "^(\\{(" + DataStore_Data_field + ")*\\};)$";
var DataStore_Data_linePattern = new RegExp(DataStore_Data_line, 'gm');
var showErrorMessages = true;
var specialEncodings = [
{
decoded: "'", decoded_Regex: /'/gm,
encoded: "\\'", encoded_Regex: /\\'/gm
},
{
decoded: "\n", decoded_Regex: /\n/gm,
encoded: "\\n", encoded_Regex: /\\n/gm
}
];
function DataStore(objectTypename, fieldList, nameField) {
this.objectTypename = objectTypename;
this.fieldList = fieldList;
this.nameField = nameField ? nameField : 'name';
this.items = [];
this.add = function(obj) {
if (this.items.indexOf(obj) < 0) {
this.items.push(obj);
this.onAdded(obj);
}
}
this.get = function(index) { return this.items[index]; }
this.clear = function() { this.items = {}; }
this.onAdded = function(obj) { }
this.onNameChanged = function(obj, name) { }
this.onDeserialized = function() { }
this.deserialize = function(str) {
this.items = deserializeObjectList(this.fieldList, str);
this.onDeserialized();
}
this.serialize = function() {
return serializeObjectList(this.fieldList, this.items);
}
}
function deserializeObject(fieldList, data) {
var match = data.match(DataStore_Data_pattern);
if (!match) return;
if (match.length != fieldList._fields.length) {
if (showErrorMessages) {
alert("Error parsing data object. Expected " + fieldList._fields.length + " fields, but found " + match.length + " fields.\ndata = \"" + data + "\"");
showErrorMessages = false;
}
return;
}
var obj = {};
for (var i = 0; i < fieldList._fields.length; ++i) {
var s = match[i].match(DataStore_Data_field)[1]; // Strip trailing pipe
var f = fieldList._fields[i];
var v = s;
var fieldName = fieldList.getFieldName(i);
if (f instanceof FieldList) {
fieldName = f._name;
v = [];
var objects = s.match(DataStore_Data_arrayObjectPattern);
if (objects) {
for (var j = 0; j < objects.length; ++j) {
v[j] = deserializeObject(f, objects[j]);
}
}
} else {
for(var j = 0; j < specialEncodings.length; ++j) {
var e = specialEncodings[j];
v = v.replace(e.encoded_Regex, e.decoded);
}
}
obj[fieldName] = v;
}
return obj;
}
function deserializeObjectList(fieldList, data) {
var result = [];
if(!data) return result;
var match = data.match(DataStore_Data_linePattern);
if(!match) return result;
for(var i = 0; i < match.length; ++i) {
result[i] = deserializeObject(fieldList, match[i]);
}
return result;
}
function serializeObject(fieldList, obj) {
if (!obj) return "";
var result = "{";
for(var i = 0; i < fieldList._fields.length; ++i) {
var fieldName = fieldList.getFieldName(i);
var f = fieldList._fields[i];
var v = obj[fieldName];
if (f instanceof FieldList) {
if (v && v.length > 0) {
result += "{";
for(var j = 0; j < v.length; ++j) {
result += serializeObject(f, v[j]);
}
result += "}";
}
} else if (v != undefined) {
v = "" + v;
for(var j = 0; j < specialEncodings.length; ++j) {
var e = specialEncodings[j];
v = v.replace(e.decoded_Regex, e.encoded);
}
result += v;
}
result += "|";
}
result += "}";
return result;
}
function serializeObjectList(fieldList, obj) {
var result = fieldList.getHeader() + ";\n";
if(!obj) return result;
for(var i = 0; i < obj.length; ++i) {
result += serializeObject(fieldList, obj[i]) + ";\n";
}
return result;
}

View File

@@ -0,0 +1,84 @@
function EditorTabs(div) {
var mainTabs;
var nextTabID;
var tabInfos = [];
var editorTypes = [];
// =====================================
// Private methods
var makeTabClosable = function(tabID) {
var tab = findTab(tabID);
tab.find( ".ui-icon-close" ).click(function() {
tabInfos[tabID] = null;
var index = findTabIndex(tab);
mainTabs.tabs( "remove", index );
});
}
var findTab = function(tabID) { return mainTabs.find('ul li a[href="#tabs-' + tabID + '"]').parent(); }
var findTabIndex = function(tab) { return $( "li", mainTabs ).index(tab); }
var addTab = function(title, tabInfo) {
var tabID = nextTabID;
tabInfos[tabID] = tabInfo;
mainTabs.tabs( "add", "#tabs-" + tabID, title );
mainTabs.tabs( "select", -1 );
makeTabClosable(tabID);
nextTabID++;
}
var findTabIDOfObject = function(obj) {
for (var i = 1; i < tabInfos.length; ++i) {
if (tabInfos[i] && tabInfos[i].obj == obj) {
return i;
}
}
return -1;
}
var createObjectEditor = function(tabInfo) {
var creator = editorTypes[tabInfo.objectType];
if (!creator) {
alert("unknown objectType: " + tabInfo.objectType);
return;
}
return creator(tabInfo.obj);
}
// =====================================
// Public methods
this.registerEditorType = function(objectType, editorCreator) {
editorTypes[objectType] = editorCreator;
}
this.renameTabForObject = function(obj, name) {
var tabID = findTabIDOfObject(obj);
if (!tabID) return;
findTab(tabID).find("a").text(name);
}
this.openTabForObject = function(obj, objectType, title) {
var tabID = findTabIDOfObject(obj);
if (tabID > 0) {
var index = findTabIndex(findTab(tabID));
mainTabs.tabs( "select", index );
return;
}
addTab(title, {obj: obj, objectType: objectType});
}
mainTabs = div.tabs({
tabTemplate: "<li><a href='#{href}'>#{label}</a> <span class='ui-icon ui-icon-close'>Remove Tab</span></li>",
add: function( event, ui ) {
var editor = createObjectEditor( tabInfos[nextTabID] );
$( ui.panel ).append( editor );
mainTabs.tabs('select', ui.index);
}
});
mainTabs.find( ".ui-tabs-nav" ).sortable({ axis: "x" });
nextTabID = mainTabs.size() + 1;
}

View File

@@ -0,0 +1,62 @@
var FieldList_Header_fieldName = '[^\\[\\]\\|]*';
var FieldList_Header_arrayField = FieldList_Header_fieldName + '\\[(' + FieldList_Header_fieldName + '\\|)*\\]';
var FieldList_Header_arrayFieldName = new RegExp(FieldList_Header_fieldName);
var FieldList_Header_field = '(' + FieldList_Header_fieldName + '|' + FieldList_Header_arrayField + ')\\|';
var FieldList_Header_pattern = new RegExp(FieldList_Header_field, 'g');
var FieldList_Header_line = "^(\\[(" + FieldList_Header_field + ")*\\];)$";
var FieldList_Header_linePattern = new RegExp(FieldList_Header_line, 'm');
function FieldList(header, name) {
this._name = name ? name : "";
this._fields = [];
var match = header.match(FieldList_Header_pattern);
if (!match) return;
for (var i = 0; i < match.length; ++i) {
var s = match[i].match(FieldList_Header_field)[1]; // Strip trailing pipe
var f = s;
if (s.match(FieldList_Header_arrayField)) {
var name = s.match(FieldList_Header_arrayFieldName)[0];
f = new FieldList(s, name);
}
this._fields[i] = f;
}
this.getFieldName = function(i) {
var f = this._fields[i];
if (f instanceof FieldList) {
return f._name;
} else {
return f;
}
}
this.getHeader = function() {
var result = this._name + "[";
for(var i = 0; i < this._fields.length; ++i) {
var f = this._fields[i];
if (f instanceof FieldList) {
result += f.getHeader();
} else {
result += f;
}
result += "|";
}
result += "]";
return result;
}
this.getHeaderLine = function() {
return this.getHeader() + ";";
}
}
function findHeader(str, name) {
var match = str.match(FieldList_Header_linePattern);
if (!match) return;
return new FieldList(match[0]);
}

View File

@@ -0,0 +1,111 @@
function TilesetImage(name, numTiles, tileSize, tags) {
this._name = name;
this._numTiles = numTiles ? numTiles : { x: 1, y: 1 };
this._tileSize = tileSize ? tileSize : { x: 32, y: 32 };
this._tags = tags ? tags : [];
this.localIDToCoords = function(localID) {
return {
x: (localID % this._numTiles.x) * this._tileSize.x,
y: Math.floor(localID / this._numTiles.x) * this._tileSize.y
}
}
this.coordsToLocalID = function(x, y) {
return Math.floor(x / this._tileSize.x)
+ this._numTiles.x * Math.floor(y / this._tileSize.y)
}
}
var defaultimage = {
name: 'defaultimage',
localID: 0,
path: ''
};
function ImageSelector(imagePath, dialog) {
var _tilesets = {};
_tilesets[""] = new TilesetImage(defaultimage.name);
_tilesets[defaultimage.name] = _tilesets[""];
var currentInput;
var get = function(name) { return _tilesets[name]; }
var parseImageID = function(str) {
if (!str || str == "") return defaultimage;
var v = str.split(":");
if (v.length < 1) return defaultimage;
return {
name: v[0],
localID: v[1],
path: imagePath
};
}
var getImageID = function(name, localID) {
if (!name) return "";
return name + ":" + localID;
}
var updateImageFromFormField = function(image, formField) {
var img = parseImageID(formField.val());
var tilesetImage = get(img.name);
if (!tilesetImage) { tilesetImage = get(""); }
var c = tilesetImage.localIDToCoords(img.localID);
image.css({
"background-image": "url(" +img.path + img.name + ".png)",
"background-position": (-c.x)+"px " + (-c.y)+"px",
"width": tilesetImage._tileSize.x + "px",
"height": tilesetImage._tileSize.y + "px",
"cursor": "pointer"
});
}
this.add = function(tileset) {
var name = tileset._name;
_tilesets[name] = tileset;
dialog.append("<img src=\"" + imagePath + name + ".png\" id=\"" + name + "\" style=\"cursor: pointer;\" />");
dialog.find("#" + name).click(function(e) {
var x = e.pageX - $(this).offset().left;
var y = e.pageY - $(this).offset().top;
var localID = tileset.coordsToLocalID(x, y);
currentInput.val(getImageID(name, localID));
currentInput.change(); // Causes the change handler to be run, thus updating the image.
dialog.dialog("close");
});
}
var showImages = function(showTilesetTag) {
jQuery.each(_tilesets, function(idx, t) {
var visible = t._tags.indexOf(showTilesetTag) >= 0;
$( "#" + idx, dialog ).toggle(visible);
});
}
this.imageify = function(img, val, showTilesetTag) {
val.change(function() { updateImageFromFormField(img, val); });
img.click(function() {
currentInput = val;
showImages(showTilesetTag);
dialog.dialog("open");
});
val.change();
}
dialog.dialog({
autoOpen: false,
modal: true,
width: 600,
height: 800,
position: [50,50],
buttons: {
Cancel: function() {
$( this ).dialog( "close" );
}
}
});
}

767
AndorsTrailEdit/editor.html Normal file
View File

@@ -0,0 +1,767 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link rel="shortcut icon" href="http://andors.techby2guys.com/favicon.ico"/>
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/jquery-ui.min.js"></script>
<link rel="stylesheet" type="text/css" href="styles2.css" />
<script type="text/javascript" src="AndorsTrailEditor.js"></script>
<title>Andor's Trail Content Editor</title>
</head>
<body>
<div id="screen">
<div id="top">
<div class="andorsTrailLogo" id="title">
Andor's Trail Content Editor
<span id="version">
v0.6.9dev1
</span>
</div>
<div id="buttons">
<div id="import">Import</div>
<div id="export">Export</div>
</div>
</div>
<div id="left" class="workarea">
<div id="tools">
<h3><a href="#">Actor status effects</a></h3>
<div id="effectlist">
<ul class="itemlist"> </ul>
<div id="add">Add</div>
</div>
<h3><a href="#">Quests</a></h3>
<div id="questlist">
<ul class="itemlist"> </ul>
<div id="add">Add</div>
</div>
<h3><a href="#">Items</a></h3>
<div id="itemlist">
<ul class="itemlist"> </ul>
<div id="add">Add</div>
</div>
<h3><a href="#">Droplists</a></h3>
<div id="droplist">
<ul class="itemlist"> </ul>
<div id="add">Add</div>
</div>
<h3><a href="#">Dialogue</a></h3>
<div id="conversationlist">
<ul class="itemlist">
<li>TODO: Conversation 1</li>
<li>TODO: Conversation 2</li>
<li>TODO: Conversation 3</li>
</ul>
<div id="add">Add</div>
</div>
<h3><a href="#">Monsters &amp; NPCs</a></h3>
<div id="monsterlist">
<ul class="itemlist"> </ul>
<div id="add">Add</div>
</div>
</div>
</div>
<div id="center" class="workarea">
<div id="tabs">
<ul>
<li><a href="#tabs-1">Start</a></li>
</ul>
<div id="tabs-1">
<h3>Welcome to the content editor for Andor's Trail.</h3>
Start by importing some content from the game by pressing the top right "Import" button.<br />
Or you could start by creating new content by selecting something from the left list.<br />
<br />
</div>
</div>
</div>
</div>
<div class="hidden" id="templates">
<!-- ========================================================= -->
<!-- Monster editor -->
<div id="editMonster">
<h3>Monster / NPC</h3>
<fieldset class="fieldSet">
<legend>General</legend>
<div class="fieldWithLabel">
<div id="monsterimage" class="field"><input type="hidden" id="iconID" /></div>
</div>
<div class="fieldWithLabel">
<label for="name" class="label">Display name:</label>
<input class="field" type="text" size="30" id="name" class="fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="tags" class="label">Spawngroup:</label>
<input class="field" type="text" size="30" id="tags" class="fieldInput" title="This is a grouping of monster types. Use these tags on spawn areas to spawn random monsters with the same tag. Several monsters may have the same tag." />
</div>
<div class="fieldWithLabel">
<label for="size" class="label">Size:</label>
<input class="field" type="text" size="5" id="size" class="fieldInput" />
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasConversation" />Has conversation</div>
</div>
<div class="fieldWithLabel" id="hasConversationDisplay">
<label for="phraseID" class="label">Conversation phrase ID:</label>
<input class="field" type="text" size="30" id="phraseID" class="fieldInput" />
</div>
<div class="fieldWithLabel">
<label for="droplistID" class="label">Droplist ID (combat or shop):</label>
<input class="field" type="text" size="30" id="droplistID" class="fieldInput"/>
</div>
</fieldset>
<fieldset class="fieldSet">
<legend>Stats</legend>
<div class="fieldWithLabel">
<label for="maxAP" class="label">Total AP:</label>
<input class="field" type="text" size="5" id="maxAP" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="moveCost" class="label">Movement cost (AP):</label>
<input class="field" type="text" size="5" id="moveCost" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="maxHP" class="label">Max HP:</label>
<input class="field" type="text" size="5" id="maxHP" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasCombat" />Has combat traits</div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasCombatDisplay">
<legend>Combat</legend>
<div class="fieldWithLabel">
<label for="attackCost" class="label">Attack cost (AP):</label>
<input class="field" type="text" size="5" id="attackCost" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="attackChance" class="label">Attack chance:</label>
<div class="field"><input type="text" size="5" id="attackChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="attackDamage" class="label">Attack damage (range):</label>
<div class="field"><input type="text" size="3" id="attackDamage_Min" class="fieldInput integer" /> - <input type="text" size="3" id="attackDamage_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasCritical" />Has critical chance</div>
</div>
<div id="hasCriticalDisplay">
<div class="fieldWithLabel">
<label for="criticalChance" class="label">Critical chance:</label>
<div class="field"><input type="text" size="5" id="criticalChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="criticalMultiplier" class="label">Critical multiplier:</label>
<input class="field" type="text" size="5" id="criticalMultiplier" class="fieldInput integer" />
</div>
</div>
<div class="fieldWithLabel">
<label for="blockChance" class="label">Block chance:</label>
<div class="field"><input type="text" size="5" id="blockChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="damageResistance" class="label">Damage resistance:</label>
<input class="field" type="text" size="5" id="damageResistance" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasHitEffect" />Has effect on each hit</div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasHitEffectDisplay">
<legend>On every successful attack hit</legend>
<div class="fieldWithLabel">
<label for="onHit_boostHP" class="label">Boost current HP (range):</label>
<div class="field"><input type="text" size="3" id="onHit_boostHP_Min" class="fieldInput" /> - <input type="text" size="3" id="onHit_boostHP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="onHit_boostAP" class="label">Boost current AP (range):</label>
<div class="field"><input type="text" size="3" id="onHit_boostAP_Min" class="fieldInput" /> - <input type="text" size="3" id="onHit_boostAP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="onHit_conditionsSource" class="label">Conditions added to source</label>
<table class="field" id="onHit_conditionsSource">
<thead><tr>
<th id="condition">Condition</th>
<th id="magnitude">Magnitude</th>
<th id="duration">Duration</th>
<th id="chance">Chance</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
<div class="fieldWithLabel">
<label for="onHit_conditionsTarget" class="label">Conditions added to target</label>
<table class="field" id="onHit_conditionsTarget">
<thead><tr>
<th id="condition">Condition</th>
<th id="magnitude">Magnitude</th>
<th id="duration">Duration</th>
<th id="chance">Chance</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
</fieldset>
<div class="endSets"> </div>
</div>
<div id="dialog-onHitConditions">
<div class="fieldWithLabel">
<label for="condition" class="label">Actor status effect id:</label>
<input type="text" size="30" id="condition" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="magnitude" class="label">Magnitude:</label>
<input type="text" size="5" id="magnitude" class="field fieldInput integer"/>
</div>
<div class="fieldWithLabel">
<label for="duration" class="label">Duration (number of turns):</label>
<input type="text" size="5" id="duration" class="field fieldInput integer"/>
</div>
<div class="fieldWithLabel">
<label for="chance" class="label">Chance:</label>
<select class="field" id="chance">
<option value="100">Always</option>
<option value="70">Frequent (70%)</option>
<option value="50">Sometimes (50%)</option>
<option value="20">Rare (20%)</option>
<option value="10">Seldom (10%)</option>
<option value="1">Very seldom (1%)</option>
<option value="1/1000">Unique (0.1%)</option>
<option value="1/10000">Legendary (0.01%)</option>
</select>
</div>
</div>
<!-- ========================================================= -->
<!-- Droplist editor -->
<div id="editDroplist">
<h3>Droplist</h3>
<fieldset class="fieldSet">
<legend>General</legend>
<div class="fieldWithLabel">
<label for="id" class="label">Droplist internal ID:</label>
<input type="text" size="30" id="id" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="stages" class="label">Items dropped</label>
<table class="field" id="items">
<thead><tr>
<th id="itemID">Item (id)</th>
<th id="quantity">Quantity</th>
<th id="chance">Chance</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
</fieldset>
<div class="endSets"> </div>
</div>
<div id="dialog-droplistItem">
<div class="fieldWithLabel">
<label for="itemID" class="label">Item (id):</label>
<input type="text" size="30" id="itemID" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="quantity" class="label">Quantity:</label>
<input type="text" size="7" id="quantity" class="field fieldInput integer"/>
</div>
<div class="fieldWithLabel">
<label for="chance" class="label">Chance:</label>
<select class="field" id="chance">
<option value="100">Always</option>
<option value="70">Frequent (70%)</option>
<option value="50">Sometimes (50%)</option>
<option value="20">Rare (20%)</option>
<option value="10">Seldom (10%)</option>
<option value="1">Very seldom (1%)</option>
<option value="1/1000">Unique (0.1%)</option>
</select>
</div>
</div>
<!-- ========================================================= -->
<!-- Item editor -->
<div id="editItem">
<h3>Item</h3>
<fieldset class="fieldSet">
<legend>General</legend>
<div class="fieldWithLabel">
<div id="itemimage" class="field"><input type="hidden" id="iconID" /></div>
</div>
<div class="fieldWithLabel">
<label for="name" class="label">Display name:</label>
<input class="field" type="text" size="30" id="name" class="fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="tags" class="label">Internal ID:</label>
<input class="field" type="text" size="30" id="searchTag" class="fieldInput" />
</div>
<div class="fieldWithLabel">
<label for="category" class="label">Category:</label>
<select class="field" id="category">
<option value="0">Weapon</option>
<option value="1">Shield</option>
<option value="2">Wearable (head)</option>
<option value="3">Wearable (body)</option>
<option value="4">Wearable (hand)</option>
<option value="5">Wearable (feet)</option>
<option value="6">Wearable (neck)</option>
<option value="7">Wearable (ring)</option>
<option value="20">Potion</option>
<option value="21">Food</option>
<option value="30">Money</option>
<option value="31">Other</option>
</select>
</div>
<div class="fieldWithLabel">
<label for="baseMarketCost" class="label">Base market cost (gold):</label>
<input class="field" type="text" size="7" id="baseMarketCost" class="fieldInput integer" title="The actual price is adjusted 15% depending on if the item is being bought or sold. Set to 0 to prohibit selling this item type (for example, for a quest item)." />
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasEquipEffect" />Has equip effect</div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasUseEffect" />Has use effect</div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasHitEffect" />Has effect on every hit</div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasKillEffect" />Has effect on every kill</div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasEquipEffectDisplay">
<legend>When equipped</legend>
<div class="fieldWithLabel">
<label for="equip_boostMaxHP" class="label">Boost max HP:</label>
<input type="text" size="5" id="equip_boostMaxHP" class="field fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="equip_boostMaxAP" class="label">Boost max AP:</label>
<input type="text" size="5" id="equip_boostMaxAP" class="field fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="equip_moveCostPenalty" class="label">Move cost penalty (AP):</label>
<input type="text" size="5" id="equip_moveCostPenalty" class="field fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="equip_attackCost" class="label">Attack cost (AP):</label>
<input class="field" type="text" size="5" id="equip_attackCost" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="equip_attackChance" class="label">Attack chance:</label>
<div class="field"><input type="text" size="5" id="equip_attackChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="equip_attackDamage" class="label">Attack damage (range):</label>
<div class="field"><input type="text" size="3" id="equip_attackDamage_Min" class="fieldInput integer" /> - <input type="text" size="3" id="equip_attackDamage_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="equip_hasCritical" />Has critical chance</div>
</div>
<div id="equip_hasCriticalDisplay">
<div class="fieldWithLabel">
<label for="equip_criticalChance" class="label">Critical chance:</label>
<div class="field"><input type="text" size="5" id="equip_criticalChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="equip_criticalMultiplier" class="label">Critical multiplier:</label>
<input class="field" type="text" size="5" id="equip_criticalMultiplier" class="fieldInput integer" />
</div>
</div>
<div class="fieldWithLabel">
<label for="equip_blockChance" class="label">Block chance:</label>
<div class="field"><input type="text" size="5" id="equip_blockChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="equip_damageResistance" class="label">Damage resistance:</label>
<input class="field" type="text" size="5" id="equip_damageResistance" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="equip_conditions" class="label">Apply status effects</label>
<table class="field" id="equip_conditions">
<thead><tr>
<th id="condition">Condition</th>
<th id="magnitude">Magnitude</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasUseEffectDisplay">
<legend>When used</legend>
<div class="fieldWithLabel">
<label for="use_boostHP" class="label">Boost current HP (range):</label>
<div class="field"><input type="text" size="3" id="use_boostHP_Min" class="fieldInput" /> - <input type="text" size="3" id="use_boostHP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="use_boostAP" class="label">Boost current AP (range):</label>
<div class="field"><input type="text" size="3" id="use_boostAP_Min" class="fieldInput" /> - <input type="text" size="3" id="use_boostAP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="use_conditionsSource" class="label">Apply status effects</label>
<table class="field" id="use_conditionsSource">
<thead><tr>
<th id="condition">Condition</th>
<th id="magnitude">Magnitude</th>
<th id="duration">Duration</th>
<th id="chance">Chance</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasHitEffectDisplay">
<legend>When making a successful hit</legend>
<div class="fieldWithLabel">
<label for="hit_boostHP" class="label">Boost current HP (range):</label>
<div class="field"><input type="text" size="3" id="hit_boostHP_Min" class="fieldInput" /> - <input type="text" size="3" id="hit_boostHP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="hit_boostAP" class="label">Boost current AP (range):</label>
<div class="field"><input type="text" size="3" id="hit_boostAP_Min" class="fieldInput" /> - <input type="text" size="3" id="hit_boostAP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="hit_conditionsSource" class="label">Apply status effects to source</label>
<table class="field" id="hit_conditionsSource">
<thead><tr>
<th id="condition">Condition</th>
<th id="magnitude">Magnitude</th>
<th id="duration">Duration</th>
<th id="chance">Chance</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
<div class="fieldWithLabel">
<label for="hit_conditionsTarget" class="label">Apply status effects to target</label>
<table class="field" id="hit_conditionsTarget">
<thead><tr>
<th id="condition">Condition</th>
<th id="magnitude">Magnitude</th>
<th id="duration">Duration</th>
<th id="chance">Chance</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasKillEffectDisplay">
<legend>On every kill</legend>
<div class="fieldWithLabel">
<label for="kill_boostHP" class="label">Boost current HP (range):</label>
<div class="field"><input type="text" size="3" id="kill_boostHP_Min" class="fieldInput" /> - <input type="text" size="3" id="kill_boostHP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="kill_boostAP" class="label">Boost current AP (range):</label>
<div class="field"><input type="text" size="3" id="kill_boostAP_Min" class="fieldInput" /> - <input type="text" size="3" id="kill_boostAP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="kill_conditionsSource" class="label">Apply status effects</label>
<table class="field" id="kill_conditionsSource">
<thead><tr>
<th id="condition">Condition</th>
<th id="magnitude">Magnitude</th>
<th id="duration">Duration</th>
<th id="chance">Chance</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
</fieldset>
<div class="endSets"> </div>
</div>
<div id="dialog-equipConditions">
<div class="fieldWithLabel">
<label for="condition" class="label">Actor status effect id:</label>
<input type="text" size="30" id="condition" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="magnitude" class="label">Magnitude:</label>
<input type="text" size="5" id="magnitude" class="field fieldInput integer"/>
</div>
</div>
<!-- ========================================================= -->
<!-- Quest editor -->
<div id="editQuest">
<h3>Quest</h3>
<fieldset class="fieldSet">
<legend>General</legend>
<div class="fieldWithLabel">
<label for="id" class="label">Internal ID:</label>
<input type="text" size="30" id="id" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="name" class="label">Display name:</label>
<input type="text" size="30" id="name" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="showInLog" />Show in quest log</div>
</div>
</fieldset>
<fieldset class="fieldSet">
<legend>Stages</legend>
<div class="fieldWithLabel">
<label for="stages" class="label">Stages shown in quest log</label>
<table class="field" id="stages">
<thead><tr>
<th id="progress">Progress</th>
<th id="logText">Logtext</th>
<th id="rewardExperience">Experience</th>
<th id="finishesQuest">Finishes quest</th>
</tr></thead>
<tbody>
</tbody>
</table>
<div id="add">Add</div>
</div>
</fieldset>
<div class="endSets"> </div>
</div>
<div id="dialog-questlog">
<div class="fieldWithLabel">
<label for="progress" class="label">Progress stage (#):</label>
<input type="text" size="4" id="progress" class="field fieldInput integer"/>
</div>
<div class="fieldWithLabel">
<label for="logText" class="label">Logtext displayed in quest log:</label>
<textarea rows="4" cols="40" id="logText"></textarea>
</div>
<div class="fieldWithLabel">
<label for="rewardExperience" class="label">Experience reward for reaching this stage:</label>
<input type="text" size="7" id="rewardExperience" class="field fieldInput integer"/>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="finishesQuest" />Finishes quest</div>
</div>
</div>
<!-- ========================================================= -->
<!-- Actor effects editor -->
<div id="editActorEffect">
<h3>Actor status effect</h3>
<fieldset class="fieldSet">
<legend>General</legend>
<div class="fieldWithLabel">
<div id="statuseffectimage" class="field"><input type="hidden" id="iconID" /></div>
</div>
<div class="fieldWithLabel">
<label for="id" class="label">Internal ID:</label>
<input type="text" size="30" id="id" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<label for="name" class="label">Display name:</label>
<input type="text" size="30" id="name" class="field fieldInput"/>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="isStacking" />Allow stacked effects</div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasRoundEffect" />Has effect every round</div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasFullRoundEffect" />Has effect every full round</div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasAbilityEffect" />Has constant ability effect</div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasRoundEffectDisplay">
<legend>Every round</legend>
<div class="fieldWithLabel">
<label for="round_visualEffectID" class="label">Visual effect:</label>
<select class="field fieldInput" id="round_visualEffectID">
<option value="-1">None</option>
<option value="0">Red splatter</option>
<option value="1">Blue swirl</option>
<option value="2">Green splatter</option>
</select>
</div>
<div class="fieldWithLabel">
<label for="round_boostHP" class="label">Boost current HP (range):</label>
<div class="field"><input type="text" size="3" id="round_boostHP_Min" class="fieldInput" /> - <input type="text" size="3" id="round_boostHP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="round_boostAP" class="label">Boost current AP (range):</label>
<div class="field"><input type="text" size="3" id="round_boostAP_Min" class="fieldInput" /> - <input type="text" size="3" id="round_boostAP_Max" class="fieldInput integer" /></div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasFullRoundEffectDisplay">
<legend>Every full round</legend>
<div class="fieldWithLabel">
<label for="fullround_visualEffectID" class="label">Visual effect:</label>
<select class="field fieldInput" id="fullround_visualEffectID">
<option value="-1">None</option>
<option value="0">Red splatter</option>
<option value="1">Blue swirl</option>
<option value="2">Green splatter</option>
</select>
</div>
<div class="fieldWithLabel">
<label for="fullround_boostHP" class="label">Boost current HP (range):</label>
<div class="field"><input type="text" size="3" id="fullround_boostHP_Min" class="fieldInput" /> - <input type="text" size="3" id="fullround_boostHP_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<label for="fullround_boostAP" class="label">Boost current AP (range):</label>
<div class="field"><input type="text" size="3" id="fullround_boostAP_Min" class="fieldInput" /> - <input type="text" size="3" id="fullround_boostAP_Max" class="fieldInput integer" /></div>
</div>
</fieldset>
<fieldset class="fieldSet" id="hasAbilityEffectDisplay">
<legend>Constant ability effect</legend>
<div class="fieldWithLabel">
<label for="boostMaxHP" class="label">Boost max HP:</label>
<input type="text" size="5" id="boostMaxHP" class="field fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="boostMaxAP" class="label">Boost max AP:</label>
<input type="text" size="5" id="boostMaxAP" class="field fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="moveCostPenalty" class="label">Move cost penalty (AP):</label>
<input type="text" size="5" id="moveCostPenalty" class="field fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="attackCost" class="label">Attack cost (AP):</label>
<input class="field" type="text" size="5" id="attackCost" class="fieldInput integer" />
</div>
<div class="fieldWithLabel">
<label for="attackChance" class="label">Attack chance:</label>
<div class="field"><input type="text" size="5" id="attackChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="attackDamage" class="label">Attack damage (range):</label>
<div class="field"><input type="text" size="3" id="attackDamage_Min" class="fieldInput integer" /> - <input type="text" size="3" id="attackDamage_Max" class="fieldInput integer" /></div>
</div>
<div class="fieldWithLabel">
<div class="label"><input type="checkbox" id="hasCritical" />Has critical chance</div>
</div>
<div id="hasCriticalDisplay">
<div class="fieldWithLabel">
<label for="criticalChance" class="label">Critical chance:</label>
<div class="field"><input type="text" size="5" id="criticalChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="criticalMultiplier" class="label">Critical multiplier:</label>
<input class="field" type="text" size="5" id="criticalMultiplier" class="fieldInput integer" />
</div>
</div>
<div class="fieldWithLabel">
<label for="blockChance" class="label">Block chance:</label>
<div class="field"><input type="text" size="5" id="blockChance" class="fieldInput integer" />%</div>
</div>
<div class="fieldWithLabel">
<label for="damageResistance" class="label">Damage resistance:</label>
<input class="field" type="text" size="5" id="damageResistance" class="fieldInput integer" />
</div>
</fieldset>
<div class="endSets"> </div>
</div>
<!-- ========================================================= -->
<!-- Import / Export dialog form -->
<div id="dialog-importexport">
<div id="statuseffects">
<div class="importexport-header">Actor status effects</div>
<div id="description">This data corresponds to res/values/actorstatuseffects.xml in the source code.</div>
<textarea id="value" rows="6" cols="80"></textarea>
<div id="import">Import</div>
</div>
<div id="quests">
<div class="importexport-header">Quests</div>
<div id="description">This data corresponds to res/values/questlist.xml in the source code.</div>
<textarea id="value" rows="6" cols="80"></textarea>
<div id="import">Import</div>
</div>
<div id="items">
<div class="importexport-header">Items</div>
<div id="description">This data corresponds to res/values/itemlist.xml in the source code.</div>
<textarea id="value" rows="6" cols="80"></textarea>
<div id="import">Import</div>
</div>
<div id="droplists">
<div class="importexport-header">Droplists</div>
<div id="description">This data corresponds to res/values/droplist.xml in the source code.</div>
<textarea id="value" rows="6" cols="80"></textarea>
<div id="import">Import</div>
</div>
<div id="dialogue">
<div class="importexport-header">Dialogue</div>
<div id="description">This data corresponds to res/values/conversationlist.xml in the source code.</div>
<textarea id="value" rows="6" cols="80"></textarea>
<div id="import">Import</div>
</div>
<div id="monsters">
<div class="importexport-header">Monsters</div>
<div id="description">This data corresponds to res/values/monsters.xml in the source code.</div>
<textarea id="value" rows="6" cols="80"></textarea>
<div id="import">Import</div>
</div>
</div>
<!-- ========================================================= -->
<!-- Select images dialog -->
<div id="dialog-images"> </div>
</div>
<script type="text/javascript">
$(document).ready(startEditor);
</script>
</body>
</html>

View File

@@ -0,0 +1,7 @@
/*
* Shorten, a jQuery plugin to automatically shorten text to fit in a block or a pre-set width and configure how the text ends.
* Copyright (C) 2009-2011 Marc Diethelm
* License: (GPL 3, http://www.gnu.org/licenses/gpl-3.0.txt) see license.txt
*/
(function(a){function s(g,c){return c.measureText(g).width}function t(g,c){c.text(g);return c.width()}var q=false,o,j,k;a.fn.shorten=function(){var g={},c=arguments,r=c.callee;if(c.length)if(c[0].constructor==Object)g=c[0];else if(c[0]=="options")return a(this).eq(0).data("shorten-options");else g={width:parseInt(c[0]),tail:c[1]};this.css("visibility","hidden");var h=a.extend({},r.defaults,g);return this.each(function(){var e=a(this),d=e.text(),p=d.length,i,f=a("<span/>").html(h.tail).text(),l={shortened:false, textOverflow:false};i=e.css("float")!="none"?h.width||e.width():h.width||e.parent().width();if(i<0)return true;e.data("shorten-options",h);this.style.display="block";this.style.whiteSpace="nowrap";if(o){var b=a(this),n=document.createElement("canvas");ctx=n.getContext("2d");b.html(n);ctx.font=b.css("font-style")+" "+b.css("font-variant")+" "+b.css("font-weight")+" "+Math.ceil(parseFloat(b.css("font-size")))+"px "+b.css("font-family");j=ctx;k=s}else{b=a('<table style="padding:0; margin:0; border:none; font:inherit;width:auto;zoom:1;position:absolute;"><tr style="padding:0; margin:0; border:none; font:inherit;"><td style="padding:0; margin:0; border:none; font:inherit;white-space:nowrap;"></td></tr></table>'); $td=a("td",b);a(this).html(b);j=$td;k=t}b=k.call(this,d,j);if(b<i){e.text(d);this.style.visibility="visible";e.data("shorten-info",l);return true}h.tooltip&&this.setAttribute("title",d);if(r._native&&!g.width){n=a("<span>"+h.tail+"</span>").text();if(n.length==1&&n.charCodeAt(0)==8230){e.text(d);this.style.overflow="hidden";this.style[r._native]="ellipsis";this.style.visibility="visible";l.shortened=true;l.textOverflow="ellipsis";e.data("shorten-info",l);return true}}f=k.call(this,f,j);i-=f;f=i*1.15; if(b-f>0){f=d.substring(0,Math.ceil(p*(f/b)));if(k.call(this,f,j)>i){d=f;p=d.length}}do{p--;d=d.substring(0,p)}while(k.call(this,d,j)>=i);e.html(a.trim(a("<span/>").text(d).html())+h.tail);this.style.visibility="visible";l.shortened=true;e.data("shorten-info",l);return true})};var m=document.documentElement.style;if("textOverflow"in m)q="textOverflow";else if("OTextOverflow"in m)q="OTextOverflow";if(typeof Modernizr!="undefined"&&Modernizr.canvastext)o=Modernizr.canvastext;else{m=document.createElement("canvas"); o=!!(m.getContext&&m.getContext("2d")&&typeof m.getContext("2d").fillText==="function")}a.fn.shorten._is_canvasTextSupported=o;a.fn.shorten._native=q;a.fn.shorten.defaults={tail:"&hellip;",tooltip:true}})(jQuery);

1442
AndorsTrailEdit/inc/qunit.js Normal file

File diff suppressed because it is too large Load Diff

BIN
AndorsTrailEdit/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,35 @@
html,body { margin:0; padding:0; height: 100%; font-size: 0.9em; }
#screen { height: 100%; background-color: #a0c0ff; color: black; position: relative; font-family: sans-serif; }
#screen #top { height: 50px; color: #eeeeee; }
#screen #top #title { font-size: 2em; float: left; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; }
#screen #top #version { font-size: 10pt; margin-left: 3ex; }
.andorsTrailLogo { background-image: url(logo.png); background-repeat: no-repeat; background-position: 3px 3px; padding:10px 0px 15px 50px; }
#screen #top #buttons { float: right; font-size: 1.2em; margin: 10px; }
#screen .workarea { margin: 5px; border: 1px #c0d0ff solid; background-color: #eeeeee; position: absolute; top: 50px; bottom: 0; }
#screen #left { width: 250px; padding: 0px; font-size: 1em; }
#screen #center { left: 256px; right: 0; }
#screen #center #tabs { border: 0px; padding: 0px; }
.ui-icon-close { float: left; margin: 0.4em 0.2em 0 0; cursor: pointer; }
.hidden { display: none; }
.fieldWithLabel { font-size: 0.9em; }
.fieldWithLabel .label { display: block; margin-top: 1ex; }
.fieldWithLabel .field { display: block; }
.fieldWithLabel .hint { }
.fieldWithLabel .fieldInput { }
.fieldSet { float: left; margin: 1ex; background-color: #f7f7ff; }
.endSets { clear: both; }
#screen #left div { }
.itemlist { border: 1px #bbb solid; list-style: none; padding: 0px; margin: 0px; line-height: 1.3; margin-bottom: 2ex; }
.itemlist li { cursor: pointer; padding: 0.5ex 1ex 0.5ex 2ex; }
.itemlist li:nth-child(odd) { background-color: #eee; border-top: 1px #ddd solid; border-bottom: 1px #ddd solid; }
.itemlist li:hover { background-color: #d7d7ff; }
.importexport-header { font-weight: bold; padding-top: 1ex; }
#dialog-importexport #value { font-family: monospace; font-size: 7pt; }
.fieldWithLabel table { border-collapse: collapse; margin-bottom: 1ex; }
.fieldWithLabel table th { border: 1px #ccc solid; padding: 0.5ex; }
.fieldWithLabel table td { border: 1px #ccc solid; padding: 0.5ex; cursor: pointer; }
.fieldWithLabel table tbody tr:nth-child(even) { background-color: #eee }
.fieldWithLabel table tbody tr:hover { background-color: #d7d7ff; }

View File

@@ -0,0 +1,464 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Andor's Trail editor unit tests</title>
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/jquery-ui.min.js"></script>
<script type="text/javascript" src="AndorsTrailEditor.js"></script>
<script type="text/javascript" src="inc/qunit.js"></script>
<script type="text/javascript">
$(document).ready(function(){
// ========================================================
module("FieldList");
test("Deserialization of header", function() {
var header = "[Id|Name|Spells[SpellID|SpellName|]|Size|]";
var ds = new FieldList(header);
equals(ds._fields.length, 4);
equals(ds._fields[0], "Id");
equals(ds._fields[1], "Name");
equals(ds._fields[2]._name, "Spells");
equals(ds._fields[2]._fields.length, 2);
equals(ds._fields[2]._fields[0], "SpellID");
equals(ds._fields[2]._fields[1], "SpellName");
equals(ds._fields[3], "Size");
});
test("Deserialization of header with multiple lists", function() {
var header = "[Id|Name|Spells[SpellID|SpellName|]|Size|Drops[ItemID|]|]";
var ds = new FieldList(header);
equals(ds._fields.length, 5);
equals(ds._fields[0], "Id");
equals(ds._fields[1], "Name");
equals(ds._fields[2]._name, "Spells");
equals(ds._fields[2]._fields.length, 2);
equals(ds._fields[2]._fields[0], "SpellID");
equals(ds._fields[2]._fields[1], "SpellName");
equals(ds._fields[3], "Size");
equals(ds._fields[4]._name, "Drops");
equals(ds._fields[4]._fields.length, 1);
equals(ds._fields[4]._fields[0], "ItemID");
});
test("Serialization of header", function() {
var header = "[Id|Name|Spells[SpellID|SpellName|]|Size|]";
var ds = new FieldList(header);
equals(ds.getHeader(), header);
equals(ds.getHeaderLine(), header + ";");
});
test("Finding header in text", function() {
var ds = findHeader("[Id|Name|[SpellID|SpellName|]|Size|];");
ok(ds);
equals(ds._fields.length, 4);
ds = findHeader("[Id|Name|[SpellID|SpellName|]|Size|]");
ok(!ds);
ds = findHeader("\n[Id|Name|[SpellID|SpellName|]|Size|];");
ok(ds);
equals(ds._fields.length, 4);
ds = findHeader("\ntest\n[Id|Name|[SpellID|SpellName|]|Size|];");
ok(ds);
equals(ds._fields.length, 4);
ds = findHeader("\ntest\n[Id|Name|[SpellID|SpellName|]|Size|];\ntest2\n");
ok(ds);
equals(ds._fields.length, 4);
ds = findHeader("\ntest\n{Test|Foo|};\n[Id|Name|[SpellID|SpellName|]|Size|];\ntest2\n");
ok(ds);
equals(ds._fields.length, 4);
ds = findHeader("\ntest\n[Id|Name|[SpellID|SpellName|]|Size|];\n{Test|Foo|};\n");
ok(ds);
equals(ds._fields.length, 4);
});
// ========================================================
module("deserializeObject");
test("Simple object deserialization", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var obj = deserializeObject(ds, "{23|TestObject|50|}");
ok(obj);
equals(obj.Id, 23);
equals(obj.Name, "TestObject");
equals(obj.SpellID, 50);
});
test("Hierarcical object deserialization", function() {
var ds = new FieldList("[Id|Name|Spells[SpellID|SpellName|]|Size|]");
var obj = deserializeObject(ds, "{23|TestObject|{{50|Heal|}{51|Curse|}}|Large|}");
ok(obj);
equals(obj.Id, 23);
equals(obj.Name, "TestObject");
ok(obj.Spells);
equals(obj.Spells.length, 2);
equals(obj.Spells[0].SpellID, 50);
equals(obj.Spells[0].SpellName, "Heal");
equals(obj.Spells[1].SpellID, 51);
equals(obj.Spells[1].SpellName, "Curse");
equals(obj.Size, "Large");
});
test("Multiple hierarcical object deserialization", function() {
var ds = new FieldList("[Id|Name|Spells[SpellID|SpellName|]|Size|Drops[ItemID|]|Effects[EffectID|]|]");
var obj = deserializeObject(ds, "{23|TestObject|{{50|Heal|}{51|Curse|}}|Large|{{4|}}||}");
ok(obj);
equals(obj.Id, 23);
equals(obj.Name, "TestObject");
ok(obj.Spells);
equals(obj.Spells.length, 2);
equals(obj.Spells[0].SpellID, 50);
equals(obj.Spells[0].SpellName, "Heal");
equals(obj.Spells[1].SpellID, 51);
equals(obj.Spells[1].SpellName, "Curse");
equals(obj.Size, "Large");
ok(obj.Drops);
equals(obj.Drops.length, 1);
equals(obj.Drops[0].ItemID, 4);
ok(obj.Effects);
equals(obj.Effects.length, 0);
});
// ========================================================
module("serializeObject");
test("Simple object serialization", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var obj = new Object();
obj.Id = 23;
obj.Name = "TestObject";
obj.SpellID = 50;
equals(serializeObject(ds, obj), "{23|TestObject|50|}");
});
test("Hierarcical object serialization", function() {
var ds = new FieldList("[Id|Name|Spells[SpellID|SpellName|]|Size|]");
var obj = new Object();
obj.Id = 23;
obj.Name = "TestObject";
obj.Spells = [];
obj.Spells[0] = new Object();
obj.Spells[0].SpellID = 50;
obj.Spells[0].SpellName = "Heal";
obj.Spells[1] = new Object();
obj.Spells[1].SpellID = 51;
obj.Spells[1].SpellName = "Curse";
obj.Size = "Large";
equals(serializeObject(ds, obj), "{23|TestObject|{{50|Heal|}{51|Curse|}}|Large|}");
});
test("Multiple hierarcical object serialization", function() {
var ds = new FieldList("[Id|Name|Spells[SpellID|SpellName|]|Size|Drops[ItemID|]|Effects[EffectID|]|]");
var obj = new Object();
obj.Id = 23;
obj.Name = "TestObject";
obj.Spells = [];
obj.Spells[0] = new Object();
obj.Spells[0].SpellID = 50;
obj.Spells[0].SpellName = "Heal";
obj.Spells[1] = new Object();
obj.Spells[1].SpellID = 51;
obj.Spells[1].SpellName = "Curse";
obj.Size = "Large";
obj.Drops = [];
obj.Drops[0] = new Object();
obj.Drops[0].ItemID = 4;
obj.Effects = [];
equals(serializeObject(ds, obj), "{23|TestObject|{{50|Heal|}{51|Curse|}}|Large|{{4|}}||}");
});
test("Object serialization with undefined fields", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var obj = new Object();
obj.Name = "TestObject";
obj.SpellID = 50;
equals(serializeObject(ds, obj), "{|TestObject|50|}");
});
// ========================================================
module("serializeObjectList");
test("Simple object serialization", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var list = [];
var obj = new Object();
obj.Id = 23;
obj.Name = "TestObject";
obj.SpellID = 50;
list[0] = obj;
obj = new Object();
obj.Id = 25;
obj.Name = "Foo";
obj.SpellID = 9;
list[1] = obj;
equals(serializeObjectList(ds, list), "[Id|Name|SpellID|];\n{23|TestObject|50|};\n{25|Foo|9|};\n");
});
test("Serialize empty list", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var list = [];
equals(serializeObjectList(ds, list), "[Id|Name|SpellID|];\n");
});
// ========================================================
module("deserializeObjectList");
test("Simple object deserialization", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var list = deserializeObjectList(ds, "{23|TestObject|50|};\n{25|Foo|9|};");
ok(list);
equals(list.length, 2);
var obj = list[0];
ok(obj);
equals(obj.Id, 23);
equals(obj.Name, "TestObject");
equals(obj.SpellID, 50);
var obj = list[1];
ok(obj);
equals(obj.Id, 25);
equals(obj.Name, "Foo");
equals(obj.SpellID, 9);
});
test("Deserialize empty list", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var list = deserializeObjectList(ds, "");
ok(list);
equals(list.length, 0);
list = deserializeObjectList(ds, "test value};");
ok(list);
equals(list.length, 0);
});
test("Deserialize garbage values", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var list = deserializeObjectList(ds, "test value};");
ok(list);
equals(list.length, 0);
list = deserializeObjectList(ds, "{23|TestObject|50|};\nfoo\n\n{25|Foo|9|};\nbaz\n");
ok(list);
equals(list.length, 2);
var obj = list[0];
ok(obj);
equals(obj.Id, 23);
equals(obj.Name, "TestObject");
equals(obj.SpellID, 50);
var obj = list[1];
ok(obj);
equals(obj.Id, 25);
equals(obj.Name, "Foo");
equals(obj.SpellID, 9);
});
// ========================================================
module("Combined serialization");
test("Serialization -> deserialization", function() {
var ds = new FieldList("[Id|Name|Spells[SpellID|SpellName|]|Size|Drops[ItemID|]|Effects[EffectID|]|]");
var obj = new Object();
obj.Id = 23;
obj.Name = "TestObject";
obj.Spells = [];
obj.Spells[0] = new Object();
obj.Spells[0].SpellID = 50;
obj.Spells[0].SpellName = "Heal";
obj.Spells[1] = new Object();
obj.Spells[1].SpellID = 51;
obj.Spells[1].SpellName = "Curse";
obj.Size = "Large";
obj.Drops = [];
obj.Drops[0] = new Object();
obj.Drops[0].ItemID = 4;
obj.Effects = [];
var obj = deserializeObject(ds, serializeObject(ds, obj));
ok(obj);
equals(obj.Id, 23);
equals(obj.Name, "TestObject");
ok(obj.Spells);
equals(obj.Spells.length, 2);
equals(obj.Spells[0].SpellID, 50);
equals(obj.Spells[0].SpellName, "Heal");
equals(obj.Spells[1].SpellID, 51);
equals(obj.Spells[1].SpellName, "Curse");
equals(obj.Size, "Large");
ok(obj.Drops);
equals(obj.Drops.length, 1);
equals(obj.Drops[0].ItemID, 4);
ok(obj.Effects);
equals(obj.Effects.length, 0);
});
// ========================================================
module("Regex test");
test("test1", function() {
var expression = /'/gm;
var str = "Test'value'";
var str2 = str.replace(expression, '_');
equals(str2, "Test_value_");
str2 = str.replace(expression, "\\'");
equals(str2, "Test\\'value\\'");
});
// ========================================================
module("Serialization and deserialization with special characters");
test("Serialization of newline", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var obj = new Object();
obj.Id = 3;
obj.Name = "Test\nObject\nValue";
obj.SpellID = 50;
equals(serializeObject(ds, obj), "{3|Test\\nObject\\nValue|50|}");
});
test("Deserialization of newline", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var obj = deserializeObject(ds, "{3|Test\\nObject\\nValue|50|}");
ok(obj);
equals(obj.Id, 3);
equals(obj.Name, "Test\nObject\nValue");
equals(obj.SpellID, 50);
});
test("Serialization of single quote", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var obj = new Object();
obj.Id = 3;
obj.Name = "Test 'Object'";
obj.SpellID = 50;
equals(serializeObject(ds, obj), "{3|Test \\'Object\\'|50|}");
});
test("Deserialization of single quote", function() {
var ds = new FieldList("[Id|Name|SpellID|]");
var obj = deserializeObject(ds, "{3|Test \\'Object\\'|50|}");
ok(obj);
equals(obj.Id, 3);
equals(obj.Name, "Test 'Object'");
equals(obj.SpellID, 50);
});
// ========================================================
module("DataStore");
test("Getting and putting items", function() {
var ds = new DataStore('item', new FieldList("[iconID|name|];"));
equals(ds.items.length, 0);
ds.add({iconID: 3, name: 'Test'});
equals(ds.items.length, 1);
equals(ds.items[0].iconID, 3);
equals(ds.items[0].name, 'Test');
});
test("Serialization of datastore", function() {
var ds = new DataStore('item', new FieldList("[iconID|name|];"));
ds.add({iconID: 3, name: 'Test'});
equals(ds.serialize(), '[iconID|name|];\n{3|Test|};\n');
});
// ========================================================
module("TilesetImage");
test("Image bounds", function() {
var img = new TilesetImage("defaultimage.png", { x:1, y:1 });
equals(img._tileSize.x, 32);
equals(img._tileSize.y, 32);
var c = img.localIDToCoords(0);
equals(img.coordsToLocalID(c.x, c.y), 0);
equals(c.x, 0);
equals(c.y, 0);
equals(img.coordsToLocalID(0, 0), 0);
equals(img.coordsToLocalID(1, 1), 0);
equals(img.coordsToLocalID(10, 10), 0);
equals(img.coordsToLocalID(31, 31), 0);
img = new TilesetImage("defaultimage.png", { x:10, y:2 });
c = img.localIDToCoords(0);
equals(img.coordsToLocalID(c.x, c.y), 0);
equals(c.x, 0);
equals(c.y, 0);
equals(img.coordsToLocalID(0, 0), 0);
equals(img.coordsToLocalID(1, 1), 0);
equals(img.coordsToLocalID(10, 10), 0);
equals(img.coordsToLocalID(31, 31), 0);
c = img.localIDToCoords(1);
equals(img.coordsToLocalID(c.x, c.y), 1);
equals(c.x, 32);
equals(c.y, 0);
equals(img.coordsToLocalID(32, 0), 1);
equals(img.coordsToLocalID(33, 1), 1);
equals(img.coordsToLocalID(40, 10), 1);
equals(img.coordsToLocalID(63, 31), 1);
c = img.localIDToCoords(10);
equals(img.coordsToLocalID(c.x, c.y), 10);
equals(c.x, 0);
equals(c.y, 32);
equals(img.coordsToLocalID(0, 32), 10);
equals(img.coordsToLocalID(1, 33), 10);
equals(img.coordsToLocalID(10, 40), 10);
equals(img.coordsToLocalID(31, 63), 10);
c = img.localIDToCoords(12);
equals(img.coordsToLocalID(c.x, c.y), 12);
equals(c.x, 64);
equals(c.y, 32);
equals(img.coordsToLocalID(64, 32), 12);
equals(img.coordsToLocalID(65, 33), 12);
equals(img.coordsToLocalID(70, 40), 12);
equals(img.coordsToLocalID(95, 63), 12);
});
});
</script>
</head>
<body>
<h1 id="qunit-header">Andor's Trail editor unit tests</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
</body>
</html>