9 constexpr
unsigned int ConversationEnd = 99999999;
10 constexpr
unsigned int MaxLen = 24;
19 << (node.
message().size() > MaxLen ? node.
message().substr(0, MaxLen - 3) +
"..." :
28 using namespace bl::gui;
32 const SelectCb& selectCb, Box::Ptr& container,
33 const std::vector<core::file::Conversation::Node>* nodes)
42 "Pass:", regenTree, createNodeCb, std::bind(&
ConversationNode::syncJumps, this),
43 [this](unsigned int nn) {
50 "Fail:", regenTree, createNodeCb, std::bind(&ConversationNode::syncJumps,
this),
51 [
this](
unsigned int nn) {
52 current.nextOnReject() = nn;
58 "Next:", regenTree, createNodeCb, std::bind(&ConversationNode::syncJumps,
this),
59 [
this](
unsigned int nn) {
66 [
this](
const std::string& s) {
68 scriptLabel->setText(s);
71 [
this]() { focusCb(
true); }) {
72 nodeTitle = Label::create(
"");
73 nodeTitle->setCharacterSize(32);
74 nodeTitle->setColor(sf::Color::Cyan, sf::Color::Transparent);
75 container->pack(nodeTitle,
true,
false);
77 promptRow = Box::create(LinePacker::create(LinePacker::Horizontal, 6.f));
78 promptEntry = TextEntry::create(3,
true);
79 promptEntry->getSignal(Event::ValueChanged).willAlwaysCall([
this](
const Event&, Element*) {
80 current.message() = promptEntry->getInput();
83 promptRow->pack(Label::create(
"Say:"),
false,
true);
84 promptRow->pack(promptEntry,
true,
true);
87 Box::create(LinePacker::create(LinePacker::Horizontal, 4.f, LinePacker::Uniform));
88 Box::Ptr subrow = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
89 subrow->pack(Label::create(
"Node Type:"),
false,
true);
90 typeEntry = ComboBox::create();
91 for (
unsigned int i = 0; i < 10; ++i) {
95 typeEntry->getSignal(Event::ValueChanged)
96 .willAlwaysCall(std::bind(&ConversationNode::onTypeChange,
this));
97 subrow->pack(typeEntry,
false,
true);
98 Button::Ptr but = Button::create(
"Delete Node");
99 but->getSignal(Event::LeftClicked).willAlwaysCall([
this](
const Event&, Element*) {
102 but->setColor(sf::Color::Red, sf::Color::Black);
103 row->pack(subrow,
false,
true);
104 row->pack(but,
false,
true);
105 container->pack(row,
true,
false);
107 choiceArea = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
108 choiceScroll = ScrollArea::create(LinePacker::create(LinePacker::Vertical, 4.f));
109 choiceScroll->setColor(sf::Color::Transparent, sf::Color::Black);
110 choiceScroll->setOutlineThickness(2.f);
111 choiceScroll->setMaxSize({1000.f, 400.f});
112 choiceScroll->includeScrollbarsInRequisition(
true);
113 but = Button::create(
"Add Choice");
114 but->getSignal(Event::LeftClicked).willAlwaysCall([
this](
const Event&, Element*) {
115 current.choices().emplace_back(
"", ConversationEnd);
116 auto it = choices.emplace(choices.end(),
120 std::bind(&ConversationNode::onChoiceChange,
122 std::placeholders::_1,
123 std::placeholders::_2,
124 std::placeholders::_3),
125 std::bind(&ConversationNode::onChoiceDelete,
127 std::placeholders::_1,
128 std::placeholders::_2),
131 std::bind(&ConversationNode::syncJumps,
this),
135 choiceScroll->pack(it->content(),
true,
false);
139 choiceArea->pack(choiceScroll,
true,
true);
140 choiceArea->pack(but,
false,
false);
142 itemRow = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
145 row = Box::create(LinePacker::create(LinePacker::Horizontal, 2.f));
146 row->pack(Label::create(
"Item:"),
false,
true);
147 row->pack(itemSelector,
false,
true);
148 itemBeforeCheck = CheckButton::create(
"Prompt Before");
149 itemBeforeCheck->getSignal(Event::ValueChanged)
150 .willAlwaysCall(std::bind(&ConversationNode::onItemChange,
this));
151 itemAfterCheck = CheckButton::create(
"Display Result");
152 itemAfterCheck->getSignal(Event::ValueChanged)
153 .willAlwaysCall(std::bind(&ConversationNode::onItemChange,
this));
154 itemRow->pack(row,
true,
false);
155 itemRow->pack(itemBeforeCheck);
156 itemRow->pack(itemAfterCheck);
158 moneyRow = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
159 moneyEntry = TextEntry::create();
160 moneyEntry->setMode(TextEntry::Mode::Integer);
161 moneyEntry->getSignal(Event::ValueChanged)
162 .willAlwaysCall(std::bind(&ConversationNode::onMoneyChange,
this));
163 moneyRow->pack(Label::create(
"Money Amount:"),
false,
true);
164 moneyRow->pack(moneyEntry,
true,
true);
166 flagRow = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
167 flagEntry = TextEntry::create();
168 flagEntry->getSignal(Event::ValueChanged).willAlwaysCall([
this](
const Event&, Element*) {
169 current.saveFlag() = flagEntry->getInput();
172 flagRow->pack(Label::create(
"Flag Name:"),
false,
true);
173 flagRow->pack(flagEntry,
true,
true);
175 editArea = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
176 container->pack(editArea,
true,
true);
178 scriptRow = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
179 ScrollArea::Ptr scroll = ScrollArea::create(LinePacker::create());
180 scroll->setMaxSize({450.f, 550.f});
181 scroll->setColor(sf::Color(30, 180, 240, 130), sf::Color::Blue);
182 scriptLabel = Label::create(
"<no script selected>");
183 scriptLabel->setColor(sf::Color(30, 180, 60), sf::Color::Transparent);
184 scroll->pack(scriptLabel);
185 scriptRow->pack(Label::create(
"Script:"));
186 scriptRow->pack(scroll,
true,
false);
187 but = Button::create(
"Edit Script");
188 but->getSignal(Event::LeftClicked).willAlwaysCall([
this](
const Event&, Element*) {
190 scriptSelector.open(parent, current.script());
192 scriptRow->pack(but);
201 nodeTitle->setText(nodeToString(index, node));
202 editArea->clearChildren(
true);
206 typeEntry->setSelectedOption(
static_cast<int>(node.
getType()),
false);
207 nextNode.setSelected(node.
next());
212 case Conversation::Node::Type::Talk:
213 promptEntry->setInput(node.
message());
214 editArea->pack(promptRow,
true,
false);
215 editArea->pack(nextNode.content(),
true,
false);
218 case Conversation::Node::Type::Prompt:
219 promptEntry->setInput(node.
message());
220 editArea->pack(promptRow,
true,
false);
221 choiceScroll->clearChildren(
true);
223 for (
unsigned int i = 0; i < node.
choices().size(); ++i) {
224 const auto& c = node.
choices()[i];
225 auto it = choices.emplace(choices.end(),
229 std::bind(&ConversationNode::onChoiceChange,
231 std::placeholders::_1,
232 std::placeholders::_2,
233 std::placeholders::_3),
234 std::bind(&ConversationNode::onChoiceDelete,
236 std::placeholders::_1,
237 std::placeholders::_2),
240 std::bind(&ConversationNode::syncJumps,
this),
244 choiceScroll->pack(choices.back().content(),
true,
false);
246 editArea->pack(choiceArea,
true,
true);
250 editArea->pack(itemRow,
true,
false);
251 itemSelector->setItem(node.
item().
id);
252 editArea->pack(nextNode.content(),
true,
false);
258 editArea->pack(itemRow,
true,
false);
259 itemSelector->setItem(node.
item().
id);
262 editArea->pack(passNext.content(),
true,
false);
263 editArea->pack(failNext.content(),
true,
false);
267 editArea->pack(moneyRow,
true,
false);
268 moneyEntry->setInput(std::to_string(node.
money()));
269 editArea->pack(nextNode.content(),
true,
false);
273 editArea->pack(moneyRow,
true,
false);
274 moneyEntry->setInput(std::to_string(node.
money()));
275 editArea->pack(passNext.content(),
true,
false);
276 editArea->pack(failNext.content(),
true,
false);
280 editArea->pack(flagRow,
true,
false);
281 flagEntry->setInput(node.
saveFlag());
282 editArea->pack(nextNode.content(),
true,
false);
286 editArea->pack(flagRow,
true,
false);
287 flagEntry->setInput(node.
saveFlag());
288 editArea->pack(passNext.content(),
true,
false);
289 editArea->pack(failNext.content(),
true,
false);
293 editArea->pack(passNext.content(),
true,
false);
294 editArea->pack(failNext.content(),
true,
false);
298 editArea->pack(scriptRow,
true,
false);
299 scriptLabel->setText(node.
script().empty() ?
"<no script selected>" : node.
script());
300 editArea->pack(nextNode.content(),
true,
false);
304 BL_LOG_ERROR <<
"Unimplemented node type: " << node.
getType();
311 void ConversationNode::onItemChange() {
312 current.
item().
id = itemSelector->currentItem();
318 void ConversationNode::onMoneyChange() {
319 const std::string& val = moneyEntry->getInput();
320 current.
money() = val.empty() ? 0 : std::atoi(val.c_str());
324 void ConversationNode::onChoiceChange(
unsigned int j,
const std::string& t,
unsigned int n) {
325 current.
choices()[j].first = t;
326 current.
choices()[j].second = n;
331 void ConversationNode::onTypeChange() {
335 Conversation::Node val(t);
336 val.next() = current.
next();
353 current.
item().
id = itemSelector->currentItem();
359 void ConversationNode::onChoiceDelete(
unsigned int j, std::list<Choice>::iterator it) {
360 it->content()->remove();
364 for (
auto& choice : choices) {
372 void ConversationNode::syncJumps() {
376 for (
auto& c : choices) { c.syncJump(); }
379 ConversationNode::NodeConnector::NodeConnector(
380 const std::string& prompt,
const NotifyCb& regenTree,
const CreateNode& createNode,
381 const NotifyCb& syncJumpCb,
const ChangeCb& changeCb,
const SelectCb& selectCb,
382 const std::vector<core::file::Conversation::Node>* nodes)
384 row = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
385 row->pack(Label::create(prompt),
false,
true);
386 entry = ComboBox::create();
387 entry->setMaxHeight(150.f);
388 entry->getSignal(Event::ValueChanged).willAlwaysCall([
this, changeCb](
const Event&, Element*) {
389 changeCb(getSelected());
391 row->pack(entry,
true,
true);
392 Button::Ptr cb = Button::create(
"Create");
393 cb->setTooltip(
"Create a new node and jump to it");
394 cb->getSignal(Event::LeftClicked)
396 [
this, createNode, regenTree, syncJumpCb, changeCb](
const Event&, Element*) {
397 const unsigned int nn = createNode(getSelected());
403 row->pack(cb,
false,
true);
404 cb = Button::create(
"Goto");
405 cb->setTooltip(
"Select the node this points to");
406 cb->getSignal(Event::LeftClicked).willAlwaysCall([
this, selectCb](
const Event&, Element*) {
407 selectCb(getSelected());
409 row->pack(cb,
false,
true);
413 void ConversationNode::NodeConnector::sync() {
414 const unsigned int oldSel = entry->getSelectedOption() == entry->optionCount() - 1 ?
416 entry->getSelectedOption();
417 entry->clearOptions();
418 for (
unsigned int i = 0; i < nodes->size(); ++i) {
419 entry->addOption(nodeToString(i, nodes->at(i)));
421 entry->addOption(
"End Conversation");
425 void ConversationNode::NodeConnector::setSelected(
unsigned int i) {
426 entry->setSelectedOption(i >= nodes->size() ? nodes->size() : i,
false);
429 unsigned int ConversationNode::NodeConnector::getSelected()
const {
430 const unsigned int s =
static_cast<unsigned int>(entry->getSelectedOption());
431 return s < nodes->size() ? s : ConversationEnd;
434 Box::Ptr& ConversationNode::NodeConnector::content() {
return row; }
436 ConversationNode::Choice::Choice(
unsigned int i,
const std::string& c,
unsigned int jp,
437 const OnChange& changeCb,
const OnDelete& delCb,
438 const NotifyCb& regenTree,
const CreateNode& createNode,
439 const NotifyCb& syncJumpCb,
const SelectCb& selectCb,
440 const std::vector<core::file::Conversation::Node>* nodes)
445 "Next:", regenTree, createNode, syncJumpCb,
446 [this](unsigned int nj) { onChange(index, entry->getInput(), nj); }, selectCb, nodes) {
447 jump.setSelected(jp);
449 box = Box::create(LinePacker::create(LinePacker::Vertical));
450 box->setColor(sf::Color::Transparent, sf::Color::Blue);
451 box->setOutlineThickness(1.f);
453 Box::Ptr row = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
454 row->pack(Label::create(
"Text:"),
false,
true);
455 entry = TextEntry::create();
457 entry->getSignal(Event::ValueChanged).willAlwaysCall([
this](
const Event&, Element*) {
458 onChange(index, entry->getInput(), jump.getSelected());
460 row->pack(entry,
true,
true);
461 Button::Ptr but = Button::create(
"Remove");
462 but->setColor(sf::Color::Red, sf::Color::Black);
463 but->getSignal(Event::LeftClicked).willAlwaysCall([
this](
const Event&, Element*) {
466 row->pack(but,
false,
true);
468 box->pack(row,
true,
false);
469 box->pack(jump.content(),
true,
false);
472 void ConversationNode::Choice::setIndex(
unsigned int i) { index = i; }
474 void ConversationNode::Choice::setIterator(std::list<Choice>::iterator i) { it = i; }
476 Box::Ptr& ConversationNode::Choice::content() {
return box; }
478 void ConversationNode::Choice::syncJump() { jump.sync(); }
All classes and functionality used for implementing the game editor.
Stores a conversation that an NPC or trainer can have with the player.
Building block of conversations. A conversation is a tree of nodes and each node is an action that ha...
std::string & script()
Returns the script file of this node.
static std::string typeToString(Type type)
Converts a node type to a human readable string.
std::string & message()
Returns the message or prompt for this node.
std::uint32_t & nextOnPass()
Returns the next node if a check passes (ie take item or money)
std::uint32_t & nextOnReject()
Returns the next node if a check is rejected (ie not taking money or item)
std::uint32_t & money()
Returns the money requested or given, undefined behavior if not a money node.
Type getType() const
Returns the type of this node.
std::string & saveFlag()
Returns the save flag of this node.
Item & item()
Returns the item to give or take. Undefined behavior if not an item node.
std::uint32_t & next()
Returns the index of the next node in the case of a Talk, Give, and Script nodes.
std::vector< std::pair< std::string, std::uint32_t > > & choices()
Returns the choices if this is a prompt node. Undefined behavior if not.
Type
Represents the different effects that nodes can have.
@ TakeMoney
This will ask the player for money. They may refuse or not have enough.
@ RunScript
This will run a script. Must be a file.
@ CheckInteracted
This will check if interacted with the talking NPC and jump accordingly.
@ TakeItem
This will ask the player for an item. They may refuse or not have it.
@ GiveItem
This unconditionally gives the player an item.
@ SetSaveFlag
This will check if a flag exists and jump to the next node accordingly.
@ CheckSaveFlag
This will check if a flag exists and jump to the next node accordingly.
@ GiveMoney
This will unconditionally give money to the player.
Helper component for editing individual conversation nodes.
const core::file::Conversation::Node & getValue() const
Get the current node value.
std::function< unsigned int(unsigned int)> CreateNode
Called when a new node should be created.
void update(unsigned int i, const core::file::Conversation::Node &node)
Update the node editor content with the given node value.
std::function< void(unsigned int)> SelectCb
Called when a node is jumped to.
std::function< void(bool)> FocusCb
std::function< void()> NotifyCb
Called when the node value is modified.
void setParent(bl::gui::GUI *parent)
Set the parent GUI object.
ConversationNode(const FocusCb &focusCb, const NotifyCb &editCb, const NotifyCb &deleteCb, const NotifyCb ®enTree, const CreateNode &createNode, const SelectCb &selectCb, bl::gui::Box::Ptr &container, const std::vector< core::file::Conversation::Node > *nodes)
Construct a new Conversation Node editor.
static Ptr create(const ChangeCb &cb=[](core::item::Id) {})
Creates a new ItemSelector.