var fs = require('fs');
// Constants
var con =
{
allowable_connections:
[
['dialogue.Text', 'dialogue.Text'],
['dialogue.Text', 'dialogue.Node'],
['dialogue.Text', 'dialogue.Choice'],
['dialogue.Text', 'dialogue.Set'],
['dialogue.Text', 'dialogue.Branch'],
['dialogue.Text', 'dialogue.Function'],
['dialogue.Text', 'dialogue.Tree'],
['dialogue.Text', 'dialogue.GoToLabel'],
['dialogue.Text', 'dialogue.ConditionalBranch'],
['dialogue.Node', 'dialogue.Text'],
['dialogue.Node', 'dialogue.Node'],
['dialogue.Node', 'dialogue.Choice'],
['dialogue.Node', 'dialogue.Set'],
['dialogue.Node', 'dialogue.Branch'],
['dialogue.Node', 'dialogue.Function'],
['dialogue.Node', 'dialogue.Tree'],
['dialogue.Node', 'dialogue.GoToLabel'],
['dialogue.Node', 'dialogue.ConditionalBranch'],
['dialogue.Tree', 'dialogue.Node'],
['dialogue.Tree', 'dialogue.Text'],
['dialogue.Tree', 'dialogue.Choice'],
['dialogue.Tree', 'dialogue.Set'],
['dialogue.Tree', 'dialogue.Branch'],
['dialogue.Tree', 'dialogue.Function'],
['dialogue.Tree', 'dialogue.Tree'],
['dialogue.Tree', 'dialogue.GoToLabel'],
['dialogue.Tree', 'dialogue.ConditionalBranch'],
['dialogue.StartNode', 'dialogue.Text'],
['dialogue.StartNode', 'dialogue.Node'],
['dialogue.StartNode', 'dialogue.Choice'],
['dialogue.StartNode', 'dialogue.Set'],
['dialogue.StartNode', 'dialogue.Branch'],
['dialogue.StartNode', 'dialogue.Function'],
['dialogue.StartNode', 'dialogue.Tree'],
['dialogue.StartNode', 'dialogue.GoToLabel'],
['dialogue.StartNode', 'dialogue.ConditionalBranch'],
['dialogue.Choice', 'dialogue.Text'],
['dialogue.Choice', 'dialogue.Node'],
['dialogue.Choice', 'dialogue.Set'],
['dialogue.Choice', 'dialogue.Branch'],
['dialogue.Choice', 'dialogue.Function'],
['dialogue.Choice', 'dialogue.Tree'],
['dialogue.Choice', 'dialogue.GoToLabel'],
['dialogue.Choice', 'dialogue.ConditionalBranch'],
['dialogue.Set', 'dialogue.Text'],
['dialogue.Set', 'dialogue.Node'],
['dialogue.Set', 'dialogue.Set'],
['dialogue.Set', 'dialogue.Branch'],
['dialogue.Set', 'dialogue.Function'],
['dialogue.Set', 'dialogue.Tree'],
['dialogue.Set', 'dialogue.GoToLabel'],
['dialogue.Set', 'dialogue.ConditionalBranch'],
['dialogue.Function', 'dialogue.Text'],
['dialogue.Function', 'dialogue.Node'],
['dialogue.Function', 'dialogue.Set'],
['dialogue.Function', 'dialogue.Branch'],
['dialogue.Function', 'dialogue.Function'],
['dialogue.Function', 'dialogue.Tree'],
['dialogue.Function', 'dialogue.GoToLabel'],
['dialogue.Function', 'dialogue.ConditionalBranch'],
['dialogue.Branch', 'dialogue.Text'],
['dialogue.Branch', 'dialogue.Node'],
['dialogue.Branch', 'dialogue.Set'],
['dialogue.Branch', 'dialogue.Branch'],
['dialogue.Branch', 'dialogue.Function'],
['dialogue.Branch', 'dialogue.Tree'],
['dialogue.Branch', 'dialogue.GoToLabel'],
['dialogue.Branch', 'dialogue.ConditionalBranch'],
['dialogue.ConditionalBranch', 'dialogue.Text'],
['dialogue.ConditionalBranch', 'dialogue.Node'],
['dialogue.ConditionalBranch', 'dialogue.Set'],
['dialogue.ConditionalBranch', 'dialogue.ConditionalBranch'],
['dialogue.ConditionalBranch', 'dialogue.Branch'],
['dialogue.ConditionalBranch', 'dialogue.Function'],
['dialogue.ConditionalBranch', 'dialogue.Tree'],
['dialogue.ConditionalBranch', 'dialogue.GoToLabel'],
],
default_link: new joint.dia.Link(
{
attrs:
{
'.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', },
'.link-tools .tool-remove circle, .marker-vertex': { r: 8 },
},
}),
};
con.default_link.set('smooth', true);
// State
var state =
{
graph: new joint.dia.Graph(),
paper: null,
filepath: null,
panning: false,
mouse_position: { x: 0, y: 0 },
context_position: { x: 0, y: 0 },
menu: null,
};
// Models
joint.shapes.dialogue = {};
joint.shapes.dialogue.Base = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Base',
size: { width: 100, height: 64 },
name: '',
attrs:
{
rect: { stroke: 'none', 'fill-opacity': 0 },
text: { display: 'none' },
},
},
joint.shapes.devs.Model.prototype.defaults
),
});
joint.shapes.dialogue.BaseView = joint.shapes.devs.ModelView.extend(
{
template:
[
'
',
'',
'',
'',
'
',
].join(''),
initialize: function()
{
_.bindAll(this, 'updateBox');
joint.shapes.devs.ModelView.prototype.initialize.apply(this, arguments);
this.$box = $(_.template(this.template)());
// Prevent paper from handling pointerdown.
this.$box.find('textarea, input').on('mousedown click', function(evt) { evt.stopPropagation(); });
// This is an example of reacting on the input change and storing the input data in the cell model.
this.$box.find('textarea.name, input.name').on('change', _.bind(function(evt)
{
this.model.set('name', $(evt.target).val());
}, this));
this.$box.find('.delete').on('click', _.bind(this.model.remove, this.model));
// Update the box position whenever the underlying model changes.
this.model.on('change', this.updateBox, this);
// Remove the box when the model gets removed from the graph.
this.model.on('remove', this.removeBox, this);
this.updateBox();
},
render: function()
{
joint.shapes.devs.ModelView.prototype.render.apply(this, arguments);
this.paper.$el.prepend(this.$box);
this.updateBox();
return this;
},
updateBox: function()
{
// Set the position and dimension of the box so that it covers the JointJS element.
var bbox = this.model.getBBox();
// Example of updating the HTML with a data stored in the cell model.
var nameField = this.$box.find('textarea.name, input.name');
if (!nameField.is(':focus'))
nameField.val(this.model.get('name'));
var label = this.$box.find('.label');
var type = this.model.get('type').slice('dialogue.'.length);
label.text(type);
label.attr('class', 'label ' + type);
this.$box[0].classList.add(type);
this.$box.css({ width: bbox.width, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' });
},
removeBox: function(evt)
{
this.$box.remove();
},
});
joint.shapes.dialogue.Node = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Node',
size: { width: 100, height: 65, },
inPorts: ['input'],
outPorts: ['output'],
attrs:
{
'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
},
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.NodeView = joint.shapes.dialogue.BaseView;
joint.shapes.dialogue.Tree = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Tree',
size: { width: 150, height: 65, },
inPorts: ['input'],
outPorts: ['output'],
attrs:
{
'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
},
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.TreeView = joint.shapes.dialogue.BaseView;
joint.shapes.dialogue.Text = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Text',
size: { width: 350 },
inPorts: ['input'],
outPorts: ['output'],
attrs:
{
'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
},
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.TextView = joint.shapes.dialogue.BaseView;
joint.shapes.dialogue.Function = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Function',
size: { width: 200, height: 65, },
inPorts: ['input'],
outPorts: ['output'],
attrs:
{
'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
},
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.FunctionView = joint.shapes.dialogue.BaseView;
joint.shapes.dialogue.GoToLabel = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.GoToLabel',
size: { width: 125, height: 65, },
inPorts: ['input'],
outPorts: ['output'],
attrs:
{
'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
},
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.GoToLabelView = joint.shapes.dialogue.BaseView;
joint.shapes.dialogue.Branch = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Branch',
size: { width: 150, height: 95, },
inPorts: ['input'],
outPorts: ['output0'],
values: [],
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.BranchView = joint.shapes.dialogue.BaseView.extend(
{
template:
[
'',
'',
'',
'',
'',
'',
'',
'
',
].join(''),
initialize: function()
{
joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
this.$box.find('.add').on('click', _.bind(this.addPort, this));
this.$box.find('.remove').on('click', _.bind(this.removePort, this));
},
removePort: function()
{
if (this.model.get('outPorts').length > 1)
{
var outPorts = this.model.get('outPorts').slice(0);
outPorts.pop();
this.model.set('outPorts', outPorts);
var values = this.model.get('values').slice(0);
values.pop();
this.model.set('values', values);
this.updateSize();
}
},
addPort: function()
{
var outPorts = this.model.get('outPorts').slice(0);
outPorts.push('output' + outPorts.length.toString());
this.model.set('outPorts', outPorts);
var values = this.model.get('values').slice(0);
values.push(null);
this.model.set('values', values);
this.updateSize();
},
updateBox: function()
{
joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
var values = this.model.get('values');
var valueFields = this.$box.find('input.value');
// Add value fields if necessary
for (var i = valueFields.length; i < values.length; i++)
{
// Prevent paper from handling pointerdown.
var $field = $('');
$field.attr('placeholder', 'Value ' + (i + 1).toString());
$field.attr('index', i);
this.$box.append($field);
$field.on('mousedown click', function(evt) { evt.stopPropagation(); });
// This is an example of reacting on the input change and storing the input data in the cell model.
$field.on('change', _.bind(function(evt)
{
var values = this.model.get('values').slice(0);
values[$(evt.target).attr('index')] = $(evt.target).val();
this.model.set('values', values);
}, this));
}
// Remove value fields if necessary
for (var i = values.length; i < valueFields.length; i++)
$(valueFields[i]).remove();
// Update value fields
valueFields = this.$box.find('input.value');
for (var i = 0; i < valueFields.length; i++)
{
var field = $(valueFields[i]);
if (!field.is(':focus'))
field.val(values[i]);
}
},
updateSize: function()
{
var textField = this.$box.find('input.name');
var height = textField.outerHeight(true);
this.model.set('size', { width: 150, height: 95 + Math.max(0, (this.model.get('outPorts').length - 1) * height) });
},
});
joint.shapes.dialogue.ConditionalBranch = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.ConditionalBranch',
size: { width: 200, height: 55, },
inPorts: ['input'],
outPorts: ['output0'],
values: [],
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.ConditionalBranchView = joint.shapes.dialogue.BaseView.extend(
{
template:
[
'',
'',
'',
'',
'',
'',
'
',
].join(''),
initialize: function()
{
joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
this.$box.find('.add').on('click', _.bind(this.addPort, this));
this.$box.find('.remove').on('click', _.bind(this.removePort, this));
},
removePort: function()
{
if (this.model.get('outPorts').length > 1)
{
var outPorts = this.model.get('outPorts').slice(0);
outPorts.pop();
this.model.set('outPorts', outPorts);
var values = this.model.get('values').slice(0);
values.pop();
this.model.set('values', values);
this.updateSize();
}
},
addPort: function()
{
var outPorts = this.model.get('outPorts').slice(0);
outPorts.push('output' + outPorts.length.toString());
this.model.set('outPorts', outPorts);
var values = this.model.get('values').slice(0);
values.push(null);
this.model.set('values', values);
this.updateSize();
},
updateBox: function()
{
joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
var values = this.model.get('values');
var valueFields = this.$box.find('input.value');
// Add value fields if necessary
for (var i = valueFields.length; i < values.length; i++)
{
// Prevent paper from handling pointerdown.
var $field = $('');
$field.attr('placeholder', 'Condition ' + (i + 1).toString());
$field.attr('index', i);
this.$box.append($field);
$field.on('mousedown click', function(evt) { evt.stopPropagation(); });
// This is an example of reacting on the input change and storing the input data in the cell model.
$field.on('change', _.bind(function(evt)
{
var values = this.model.get('values').slice(0);
values[$(evt.target).attr('index')] = $(evt.target).val();
this.model.set('values', values);
}, this));
}
// Remove value fields if necessary
for (var i = values.length; i < valueFields.length; i++)
$(valueFields[i]).remove();
// Update value fields
valueFields = this.$box.find('input.value');
for (var i = 0; i < valueFields.length; i++)
{
var field = $(valueFields[i]);
if (!field.is(':focus'))
field.val(values[i]);
}
},
updateSize: function()
{
var textField = this.$box.find('input');
var height = textField.outerHeight(true);
this.model.set('size', { width: 200, height: 95 + Math.max(0, (this.model.get('outPorts').length - 1) * height) });
},
});
joint.shapes.dialogue.StartNode = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.StartNode',
inPorts: [],
outPorts: ['output'],
size: { width: 100, height: 32, },
value: 'DialogueNode.START',
attrs:
{
'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
},
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.StartNodeView = joint.shapes.dialogue.BaseView.extend(
{
template:
[
'',
'',
'',
'
',
].join(''),
initialize: function()
{
joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
//this.$box.find('input.value').on('change', _.bind(function(evt)
//{
// this.model.set('value', $(evt.target).val());
//}, this));
},
updateBox: function()
{
joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
//var field = this.$box.find('input.value');
//if (!field.is(':focus'))
// field.val(this.model.get('value'));
},
});
joint.shapes.dialogue.Choice = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Choice',
inPorts: ['input'],
outPorts: ['output'],
size: { width: 175, height: 93, },
value: '',
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.ChoiceView = joint.shapes.dialogue.BaseView.extend(
{
template:
[
'',
'',
'',
'',
'',
'
',
].join(''),
initialize: function()
{
joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
this.$box.find('input.value').on('change', _.bind(function(evt)
{
this.model.set('value', $(evt.target).val());
}, this));
},
updateBox: function()
{
joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
var field = this.$box.find('input.value');
if (!field.is(':focus'))
field.val(this.model.get('value'));
},
});
joint.shapes.dialogue.Set = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.Set',
inPorts: ['input'],
outPorts: ['output'],
size: { width: 125, height: 90, },
value: '',
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.SetView = joint.shapes.dialogue.BaseView.extend(
{
template:
[
'',
'',
'',
'',
'',
'
',
].join(''),
initialize: function()
{
joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
this.$box.find('input.value').on('change', _.bind(function(evt)
{
this.model.set('value', $(evt.target).val());
}, this));
},
updateBox: function()
{
joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
var field = this.$box.find('input.value');
if (!field.is(':focus'))
field.val(this.model.get('value'));
},
});
/**
*
* AI Hook
*
*/
joint.shapes.dialogue.AIHook = joint.shapes.devs.Model.extend(
{
defaults: joint.util.deepSupplement
(
{
type: 'dialogue.AIHook',
inPorts: [],
outPorts: [],
size: { width: 300, height: 126, },
value: '',
},
joint.shapes.dialogue.Base.prototype.defaults
),
});
joint.shapes.dialogue.AIHookView = joint.shapes.dialogue.BaseView.extend(
{
template:
[
'',
'',
'',
'',
'',
'',
'
',
].join(''),
initialize: function()
{
joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
this.$box.find('input.conditions').on('change', _.bind(function(evt)
{
this.model.set('conditions', $(evt.target).val());
}, this));
this.$box.find('select.hooktype').on('change', _.bind(function(evt)
{
this.model.set('hooktype', $(evt.target).val());
}, this));
this.$box.find('input.identifier').on('change', _.bind(function(evt)
{
this.model.set('identifier', $(evt.target).val());
}, this));
},
updateBox: function()
{
joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
var field = this.$box.find('select.hooktype');
if (!field.is(':focus'))
field.val(this.model.get('hooktype'));
var field = this.$box.find('input.conditions');
if (!field.is(':focus'))
field.val(this.model.get('conditions'));
var field = this.$box.find('input.identifier');
if (!field.is(':focus'))
field.val(this.model.get('identifier'));
},
});
// Functions
var func = {};
func.validate_connection = function(cellViewS, magnetS, cellViewT, magnetT, end, linkView)
{
// Prevent loop linking
if (magnetS === magnetT)
return false;
if (cellViewS === cellViewT)
return false;
if ($(magnetT).parents('.outPorts').length > 0) // Can't connect to an output port
return false;
var sourceType = cellViewS.model.attributes.type;
var targetType = cellViewT.model.attributes.type;
var valid = false;
for (var i = 0; i < con.allowable_connections.length; i++)
{
var rule = con.allowable_connections[i];
if (sourceType === rule[0] && targetType === rule[1])
{
valid = true;
break;
}
}
if (!valid)
return false;
var links = state.graph.getConnectedLinks(cellViewS.model);
for (var i = 0; i < links.length; i++)
{
var link = links[i];
if (link.attributes.source.id === cellViewS.model.id && link.attributes.source.port === magnetS.attributes.port.nodeValue && link.attributes.target.id)
{
var targetCell = state.graph.getCell(link.attributes.target.id);
if (targetCell.attributes.type !== targetType)
return false; // We can only connect to multiple targets of the same type
if (targetCell === cellViewT.model)
return false; // Already connected
}
}
return true;
};
func.validate_magnet = function(cellView, magnet)
{
if ($(magnet).parents('.outPorts').length === 0)
return false; // we only want output ports
// If unlimited connections attribute is null, we can only ever connect to one object
// If it is not null, it is an array of type strings which are allowed to have unlimited connections
var unlimitedConnections = magnet.getAttribute('unlimitedConnections');
var links = state.graph.getConnectedLinks(cellView.model);
for (var i = 0; i < links.length; i++)
{
var link = links[i];
if (link.attributes.source.id === cellView.model.id && link.attributes.source.port === magnet.attributes.port.nodeValue)
{
// This port already has a connection
if (unlimitedConnections && link.attributes.target.id)
{
var targetCell = state.graph.getCell(link.attributes.target.id);
if (unlimitedConnections.indexOf(targetCell.attributes.type) !== -1)
return true; // It's okay because this target type has unlimited connections
}
return false;
}
}
return true;
};
func.optimized_data = function()
{
var cells = state.graph.toJSON().cells;
var nodesByID = {};
var cellsByID = {};
var nodes = [];
for (var i = 0; i < cells.length; i++)
{
var cell = cells[i];
if (cell.type != 'link')
{
var node =
{
type: cell.type.slice('dialogue.'.length),
id: cell.id,
};
if (node.type === 'Branch')
{
node.variable = cell.name;
node.branches = {};
for (var j = 0; j < cell.values.length; j++)
{
var branch = cell.values[j];
node.branches[branch] = null;
}
}
else if (node.type === 'ConditionalBranch')
{
node.branches = {};
for (var j = 0; j < cell.values.length; j++)
{
var branch = cell.values[j];
node.branches[branch] = null;
}
}
else if (node.type === 'Set')
{
node.variable = cell.name;
node.value = cell.value;
node.next = null;
}
else if (node.type === 'AIHook')
{
node.hooktype = cell.hooktype;
node.identifier = cell.identifier;
node.conditions = cell.conditions;
node.next = null;
}
else
{
node.name = cell.name;
node.next = null;
}
if (node.type === "Choice") {
node.value = cell.value;
}
nodes.push(node);
nodesByID[cell.id] = node;
cellsByID[cell.id] = cell;
}
}
for (var i = 0; i < cells.length; i++)
{
var cell = cells[i];
if (cell.type === 'link')
{
var source = nodesByID[cell.source.id];
var target = cell.target ? nodesByID[cell.target.id] : null;
if (source)
{
if (source.type === 'Branch')
{
var portNumber = parseInt(cell.source.port.slice('output'.length));
var value;
if (portNumber === 0)
value = '_default';
else
{
var sourceCell = cellsByID[source.id];
value = sourceCell.values[portNumber - 1];
}
source.branches[value] = target ? target.id : null;
}
else if ((source.type === 'Text' || source.type === 'Node' || source.type === 'StartNode') && target && target.type === 'Choice')
{
if (!source.choices)
{
source.choices = [];
delete source.next;
}
source.choices.push(target.id);
}
else
source.next = target ? target.id : null;
}
}
}
return nodes;
};
// Menu actions
func.flash = function(text)
{
var $flash = $('#flash');
$flash.text(text);
$flash.stop(true, true);
$flash.show();
$flash.css('opacity', 1.0);
$flash.fadeOut({ duration: 1500 });
};
func.apply_fields = function()
{
$('input[type=text], select').blur();
};
func.save = function()
{
func.apply_fields();
if (!state.filepath)
func.show_save_dialog();
else
func.do_save();
};
func.optimized_filename = function(f)
{
return f.substring(0, f.length - 2) + 'dlz';
};
func.perfect_typescript = function(f)
{
return f.substring(0, f.length - 2) + 'dl.ts';
};
func.just_name = function (f) {
return f.substring(0, f.length - 3);
};
func.do_save = function()
{
if (state.filepath)
{
fs.writeFileSync(state.filepath, JSON.stringify(state.graph), 'utf8');
//fs.writeFileSync(func.optimized_filename(state.filepath), JSON.stringify(func.optimized_data()), 'utf8');
let name = func.just_name(func.filename_from_filepath(state.filepath));
let cleants = "// File created automatically by custom Dialogger on " + new Date().toLocaleString() + "\n";
cleants += "// Do not tamper with this file.\n// It will be replaced automatically by Dialogger and all changes will be lost.\n// Instead change " + name + ".dl.";
cleants += "\nmodule DialogueTrees {\n export let " + name + " = (function () {\n";
cleants += func.ts_data(func.optimized_data(), name).replace(/^/gm, ' ') + "\n return tree;\n })();\n}";
fs.writeFileSync(func.perfect_typescript(state.filepath), cleants, 'utf8');
func.flash('Saved ' + state.filepath);
}
};
func.filename_from_filepath = function(f)
{
return f.replace(/^.*[\\\/]/, '');
};
func.show_open_dialog = function()
{
$('#file_open').click();
};
func.show_save_dialog = function()
{
$('#file_save').click();
};
func.add_node = function(constructor)
{
return function()
{
var container = $('#container')[0];
var element = new constructor(
{
position: { x: state.context_position.x + container.scrollLeft, y: state.context_position.y + container.scrollTop },
});
state.graph.addCells([element]);
};
};
func.clear = function()
{
state.graph.clear();
state.filepath = null;
document.title = 'Dialogger';
};
func.handle_open_files = function(files)
{
state.filepath = files[0].path;
var data = fs.readFileSync(state.filepath);
document.title = func.filename_from_filepath(state.filepath);
state.graph.clear();
state.graph.fromJSON(JSON.parse(data));
};
func.handle_save_files = function(files)
{
state.filepath = files[0].path;
func.do_save();
};
func.ts_data = function(nodes, name) {
let declarations = [];
let addDeclaration = (dec) => {
if (declarations.indexOf(dec) == -1) {
declarations.push(dec);
}
}
addDeclaration("let tree : DialogueTree = new DialogueTree(" + JSON.stringify(name) + ");\n");
let finalString = "";
for (let i = 0; i < nodes.length; i++) {
finalString += "\n\n";
let node = nodes[i];
if (node.type == "Node") {
finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
finalString += "node.setName(" + JSON.stringify(node.name) + ");\n";
if (node.next != null && node.next != undefined) {
finalString += "node.setNext(" + JSON.stringify(node.next) + ");\n";
}
if (node.choices != undefined && node.choices != null) {
finalString += "node.setChoices(" + JSON.stringify(node.choices) + ");\n";
}
finalString += "tree.addNode(node);\n";
addDeclaration("let node : DialogueNode;\n");
} else if (node.type == "GoToLabel") {
finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
finalString += "node.setNext(" + JSON.stringify(node.name) + ");\n";
finalString += "tree.addNode(node);\n";
addDeclaration("let node : DialogueNode;\n");
} else if (node.type == "Tree") {
finalString += "nodetree = new DialogueNodeTree(" + JSON.stringify(node.id) + ");\n";
finalString += "nodetree.setTree(() => { return " + node.name + "; });\n";
if (node.next != null && node.next != undefined) {
finalString += "nodetree.setNext(" + JSON.stringify(node.next) + ");\n";
}
if (node.choices != undefined && node.choices != null) {
finalString += "nodetree.setChoices(" + JSON.stringify(node.choices) + ");\n";
}
finalString += "tree.addNode(nodetree);\n";
addDeclaration("let nodetree : DialogueNodeTree;\n");
} else if (node.type == "StartNode") {
finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
if (node.next != null && node.next != undefined) {
finalString += "node.setNext(" + JSON.stringify(node.next) + ");\n";
}
if (node.choices != undefined && node.choices != null) {
finalString += "node.setChoices(" + JSON.stringify(node.choices) + ");\n";
}
finalString += "tree.addStartNode(node);\n";
addDeclaration("let node : DialogueNode;\n");
} else if (node.type == "Text") {
finalString += "text = new DialogueText(" + JSON.stringify(node.id) + ");\n";
//finalString += "node.setSay(" + node.name + ");\n"; // this way all code we placed will be added right away!
// SetSay needs to be a function, otherwise data might not be updated when printing!
finalString += "text.setSay(() => { return new Say(" + node.name + ");});\n";
if (node.next != null && node.next != undefined) {
finalString += "text.setNext(" + JSON.stringify(node.next) + ");\n";
}
if (node.choices != undefined && node.choices != null) {
finalString += "text.setChoices(" + JSON.stringify(node.choices) + ");\n";
}
finalString += "tree.addNode(text);\n";
addDeclaration("let text : DialogueText;\n");
} else if (node.type == "Choice") {
finalString += "choice = new DialogueChoice(" + JSON.stringify(node.id) + ");\n";
//finalString += "node.setSay(" + node.name + ");\n"; // this way all code we placed will be added right away!
// SetSay needs to be a function, otherwise data might not be updated when printing!
finalString += "choice.setSay(() => { return new Say(" + node.name + ");});\n";
if (node.value != undefined && node.value != null && node.value != "") {
finalString += "choice.setConditions(() => { return " + node.value + ";});\n"
}
if (node.next != null && node.next != undefined) {
finalString += "choice.setNext(" + JSON.stringify(node.next) + ");\n";
}
if (node.choices != undefined && node.choices != null) {
finalString += "choice.setChoices(" + JSON.stringify(node.choices) + ");\n";
}
finalString += "tree.addNode(choice);\n";
addDeclaration("let choice : DialogueChoice;\n");
} else if (node.type == "Branch") {
finalString += "branch = new DialogueBranch(" + JSON.stringify(node.id) + ");\n";
finalString += "branch.setVariable(() => { return " + node.variable + ";});\n";
for (let key in node.branches) {
let branchCondition = key;
let branchTarget = node.branches[key];
if (branchCondition == "_default") {
finalString += "branch.setNext(" + JSON.stringify(branchTarget) + ");\n";
} else {
finalString += "branch.addBranch(" + JSON.stringify(branchTarget) + ", () => { return " + branchCondition + ";});\n";
}
}
finalString += "tree.addNode(branch);\n";
addDeclaration("let branch : DialogueBranch;\n");
} else if (node.type == "ConditionalBranch") {
finalString += "branch = new DialogueBranch(" + JSON.stringify(node.id) + ");\n";
//finalString += "branch.setVariable(() => { return " + node.variable + ";});\n";
for (let key in node.branches) {
let branchCondition = key;
let branchTarget = node.branches[key];
if (branchCondition == "_default") {
finalString += "branch.setNext(" + JSON.stringify(branchTarget) + ");\n";
} else {
finalString += "branch.addBranch(" + JSON.stringify(branchTarget) + ", () => { return " + branchCondition + ";});\n";
}
}
finalString += "tree.addNode(branch);\n";
addDeclaration("let branch : DialogueBranch;\n");
} else if (node.type == "Set" || node.type == "Function") {
finalString += "set = new DialogueSet(" + JSON.stringify(node.id) + ");\n";
if (node.type == "Function") {
finalString += "set.setFunction(() => {\n" + node.name + "\n});\n";
} else {
finalString += "set.setFunction(() => {\n " + node.variable + " = " + node.value + ";\n});\n";
}
if (node.next != null && node.next != undefined) {
finalString += "set.setNext(" + JSON.stringify(node.next) + ");\n";
}
if (node.choices != undefined && node.choices != null) {
finalString += "set.setChoices(" + JSON.stringify(node.choices) + ");\n";
}
finalString += "tree.addNode(set);\n";
addDeclaration("let set : DialogueSet;\n");
} else if (node.type == "AIHook") {
// hooktype, identifier, conditions
// AskAbout/Circumstance/Informative/Critical
node.hooktype = node.hooktype != undefined ? node.hooktype : "";
node.identifier = node.identifier != undefined ? node.identifier : "";
node.conditions = node.conditions != undefined ? node.conditions : "";
let hookType = node.hooktype.toLowerCase().trim();
finalString += " AI." + (hookType == "askabout" ? "investigateRules" : "talktoRules") + ".createAndAddRule({\n" +
" name : " + JSON.stringify("AIHook for " + name) + ",\n";
finalString += " conditions : (runner : RulebookRunner) => {\n" +
" // @ts-ignore\n" +
" let player = WorldState.player, greeter = runner.noun.greeter, answerer = runner.noun.answerer;\n" +
"\n" +
" return (" + node.conditions + ");\n" +
" },\n";
if (hookType == "critical" || hookType == "informative") {
finalString += " priority : Rule.PRIORITY_HIGHEST,\n" +
" firstPriority : Rule.PRIORITY_HIGHEST,\n";
} else {
finalString += " priority : Rule.PRIORITY_MEDIUM,\n" +
" firstPriority : Rule.PRIORITY_MEDIUM,\n"
}
if (hookType == "informative") {
finalString += " code : (runner : RulebookRunner) => {\n" +
" runner.noun.runFirst.push(tree);\n" +
" },\n";
} else if (hookType == "critical") {
finalString += " code : (runner : RulebookRunner) => {\n" +
" runner.noun.runAndStop.push(tree);\n" +
" },\n";
} else {
finalString += " code : (runner : RulebookRunner) => {\n" +
" runner.noun.options.push({\n" +
" tree: tree,\n" +
" text : new Say(" + node.identifier + ")\n" +
" })\n" +
" },\n";
}
finalString += "});\n\n";
}
}
return declarations.join("") + finalString;
};
// Initialize
(function()
{
state.paper = new joint.dia.Paper(
{
el: $('#paper'),
width: 32000,
height: 16000,
model: state.graph,
gridSize: 1,
defaultLink: con.default_link,
validateConnection: func.validate_connection,
validateMagnet: func.validate_magnet,
snapLinks: { radius: 75 }, // enable link snapping within 75px lookup radius
markAvailable: true,
});
state.paper.on('blank:pointerdown', function(e, x, y)
{
if (e.button === 0 || e.button === 1)
{
state.panning = true;
state.mouse_position.x = e.pageX;
state.mouse_position.y = e.pageY;
$('body').css('cursor', 'move');
func.apply_fields();
}
});
state.paper.on('cell:pointerdown', function(e, x, y)
{
func.apply_fields();
});
$('#container').mousemove(function(e)
{
if (state.panning)
{
var $this = $(this);
$this.scrollLeft($this.scrollLeft() + state.mouse_position.x - e.pageX);
$this.scrollTop($this.scrollTop() + state.mouse_position.y - e.pageY);
state.mouse_position.x = e.pageX;
state.mouse_position.y = e.pageY;
}
});
$('#container').mouseup(function(e)
{
state.panning = false;
$('body').css('cursor', 'default');
});
$('#file_open').on('change', function()
{
if (this.files)
func.handle_open_files(this.files);
// clear files from this input
var $this = $(this);
$this.wrap('