Implement a picker for major events, using the new editable string picker.

It's used not only in special node editing (for Event Happens and If Event Happened?)
but also in the Townperson Advanced, Talk Node, and Quest editors.
This commit is contained in:
2025-02-23 10:38:46 -05:00
committed by Celtic Minstrel
parent 97cba0471b
commit 62267261f3
15 changed files with 88 additions and 21 deletions

View File

@@ -336,6 +336,7 @@ the response changes (the first field) to 90, and the Event which prevents the c
second field) to 4. If Fred was killed (i.e. if the Goblin Chief was not killed before Day second field) to 4. If Fred was killed (i.e. if the Goblin Chief was not killed before Day
90), she will respond with the second text field. Otherwise, she will respond with the 90), she will respond with the second text field. Otherwise, she will respond with the
first.</p> first.</p>
<p>You can also use events in quests. An quest can be set to expire if an event hasn't happened by a certain day. This is more or less the same as making dialogue vary.</p>
<p>Finally, you can use the If-Then Special Node Type 150: Special Thing happened?, which <p>Finally, you can use the If-Then Special Node Type 150: Special Thing happened?, which
calls different special nodes depending on whether a day has been reached and whether an calls different special nodes depending on whether a day has been reached and whether an
Event has happened before that day or not.</p> Event has happened before that day or not.</p>

View File

@@ -252,7 +252,7 @@ occurred. See <a href="../Specials.html#time">Passage of Time</a> in the chapter
explanation. explanation.
<dl> <dl>
<dt>Mess1, Mess2:</dt><dd>Standard usage.</dd> <dt>Mess1, Mess2:</dt><dd>Standard usage.</dd>
<dt>Extra 1a:</dt><dd>The number of the special event. (Range 1 ... 20)</dd> <dt>Extra 1a:</dt><dd>The number of the special event.</dd>
<dt>Example:</dt><dd>If the slaying of the goblin king is event 4, when it is killed, call <dt>Example:</dt><dd>If the slaying of the goblin king is event 4, when it is killed, call
this special node with Extra 1a set to 4.</dd></dd> this special node with Extra 1a set to 4.</dd></dd>
@@ -1310,7 +1310,7 @@ reached before a given event happened (see the chapter on the passage of time fo
explanation). explanation).
<dl> <dl>
<dt>Extra 1a:</dt><dd>The day some special thing is supposed to happen.</dd> <dt>Extra 1a:</dt><dd>The day some special thing is supposed to happen.</dd>
<dt>Extra 1b:</dt><dd>The number of an event (0 .. 20). This is the event that would <dt>Extra 1b:</dt><dd>The number of an event. This is the event that would
prevent the special thing from happening (leave at 0 for no event). If the day in 1a has prevent the special thing from happening (leave at 0 for no event). If the day in 1a has
been reached, and this special event did not occur before the day in 1a, then the special been reached, and this special event did not occur before the day in 1a, then the special
in Extra 2b is called.</dd> in Extra 2b is called.</dd>

View File

@@ -10,20 +10,21 @@
<text top='85' left='50' width='118' height='16'>Quest Description:</text> <text top='85' left='50' width='118' height='16'>Quest Description:</text>
<field name='name' top='57' left='205' width='252' height='16'/> <field name='name' top='57' left='205' width='252' height='16'/>
<field name='descr' top='84' left='205' width='252' height='104'/> <field name='descr' top='84' left='205' width='252' height='104'/>
<text top='209' left='50' width='160' height='28'>Must be completed by day:<br/>(-1 for no deadline)</text> <text top='209' left='50' width='170' height='28'>Must be completed by day:<br/>(-1 for no deadline)</text>
<field name='chop' top='208' left='205' width='110' height='16'/> <field name='chop' top='208' left='205' width='110' height='16'/>
<text top='247' left='50' width='150' height='28'>Event to waive deadline:<br/>(-1 for none)</text> <text top='247' left='50' width='170' height='28'>Event to waive deadline:<br/>(-1 for none)</text>
<field name='evt' top='246' left='205' width='100' height='16'/> <field name='evt' top='246' left='205' width='60' height='16'/>
<button name='choose-evt' type='regular' anchor='evt' relative='pos pos-in' top='-4' left='8'>Choose</button>
<text size='large' top='280' left='50' width='220' height='17'>Reward for completing:</text> <text size='large' top='280' left='50' width='220' height='17'>Reward for completing:</text>
<text top='307' left='50' width='150' height='16'>Experience Points:</text> <text top='307' left='50' width='150' height='16'>Experience Points:</text>
<field name='xp' top='306' left='205' width='110' height='16'/> <field name='xp' top='306' left='205' width='110' height='16'/>
<text top='333' left='50' width='150' height='16'>Gold:</text> <text top='333' left='50' width='150' height='16'>Gold:</text>
<field name='gold' top='332' left='205' width='110' height='16'/> <field name='gold' top='332' left='205' width='110' height='16'/>
<led name='rel' wrap='true' top='208' left='330'>Deadline is relative to start day</led> <led name='rel' wrap='true' top='208' left='345'>Deadline is relative to start day</led>
<led name='start' wrap='true' top='246' left='330'>Player is given quest when scenario starts</led> <led name='start' wrap='true' top='246' left='345'>Player is given quest when scenario starts</led>
<led name='inbank' top='285' left='330'>Include in a job bank:</led> <led name='inbank' top='285' left='345'>Include in a job bank:</led>
<field name='bank1' top='306' left='344' width='110' height='16'/> <field name='bank1' top='306' left='359' width='110' height='16'/>
<field name='bank2' top='332' left='344' width='110' height='16'/> <field name='bank2' top='332' left='359' width='110' height='16'/>
<button name='left' type='left' def-key='left' top='358' left='50'/> <button name='left' type='left' def-key='left' top='358' left='50'/>
<button name='right' type='right' def-key='right' top='358' left='115'/> <button name='right' type='right' def-key='right' top='358' left='115'/>
<button name='cancel' type='regular' def-key='esc' top='358' left='322'>Cancel</button> <button name='cancel' type='regular' def-key='esc' top='358' left='322'>Cancel</button>

View File

@@ -57,8 +57,9 @@
A creature which is part of a special group does not exist until it's created by a special encounter A creature which is part of a special group does not exist until it's created by a special encounter
(like special node type One-Time Place Town Encounter). (like special node type One-Time Place Town Encounter).
</text> </text>
<text top='133' left='301' width='222' height='44'> <text top='133' left='366' width='222' height='44'>
For an explanation on how event codes work, For an explanation on how event codes work,
see the chapter in the instructions on the passing of time. see the chapter in the instructions on the passing of time.
</text> </text>
<button name='choose-event' type='regular' top='150' left='296'>Choose</button>
</dialog> </dialog>

View File

@@ -311,6 +311,7 @@ subtags:
* `<sound>` - (max unbounded) Gives a name for a custom sound (ID 100 or greater). These * `<sound>` - (max unbounded) Gives a name for a custom sound (ID 100 or greater). These
names are used when showing the Pick Sound dialog in various places. The required `id` names are used when showing the Pick Sound dialog in various places. The required `id`
attribute specifies which sound it applies to. attribute specifies which sound it applies to.
* `<event>` - (max unbounded) Gives a name to a major event flag. These names are shown (and editable) when showing the Pick Event dialog in various places. The required `id` attribute specifies which event.
Terrain Types Terrain Types
------------- -------------

View File

@@ -364,6 +364,15 @@
</xs:simpleContent> </xs:simpleContent>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="event" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="id" use="required" type="xs:integer"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:element name="scenario"> <xs:element name="scenario">

View File

@@ -312,7 +312,7 @@ Second part of message
Unused Unused
Unused Unused
Unused Unused
Number of Special Event (0 .. 9) Number of Special Event
Unused Unused
Unused Unused
Unused Unused

View File

@@ -947,6 +947,12 @@ void readScenarioFromXml(ticpp::Document&& data, cScenario& scenario) {
if(sndnum >= scenario.snd_names.size()) if(sndnum >= scenario.snd_names.size())
scenario.snd_names.resize(sndnum + 1); scenario.snd_names.resize(sndnum + 1);
edit->GetText(&scenario.snd_names[sndnum], false); edit->GetText(&scenario.snd_names[sndnum], false);
} else if(type == "event") {
int evtnum = 0;
edit->GetAttribute("id", &evtnum);
if(evtnum > scenario.evt_names.size())
scenario.evt_names.resize(evtnum);
edit->GetText(&scenario.evt_names[evtnum - 1], false);
} else if(type == "graphics") { } else if(type == "graphics") {
static const std::set<ePicType> valid_pictypes = { static const std::set<ePicType> valid_pictypes = {
PIC_TER, PIC_TER_ANIM, PIC_TER_MAP, PIC_TER, PIC_TER_ANIM, PIC_TER_MAP,

View File

@@ -202,6 +202,7 @@ void swap(cScenario& lhs, cScenario& rhs) {
swap(lhs.scen_file, rhs.scen_file); swap(lhs.scen_file, rhs.scen_file);
swap(lhs.outdoors, rhs.outdoors); swap(lhs.outdoors, rhs.outdoors);
swap(lhs.towns, rhs.towns); swap(lhs.towns, rhs.towns);
swap(lhs.evt_names, rhs.evt_names);
} }
cScenario::cItemStorage::cItemStorage() : ter_type(-1), property(0) { cScenario::cItemStorage::cItemStorage() : ter_type(-1), property(0) {
@@ -600,4 +601,4 @@ std::string cScenario::get_feature_flag(std::string flag) {
std::map<std::string, std::string>::const_iterator iter = this->feature_flags.find(flag); std::map<std::string, std::string>::const_iterator iter = this->feature_flags.find(flag);
if(iter == this->feature_flags.end()) return ""; if(iter == this->feature_flags.end()) return "";
return iter->second; return iter->second;
} }

View File

@@ -100,6 +100,7 @@ public:
std::vector<std::string> journal_strs; std::vector<std::string> journal_strs;
std::vector<std::string> spec_strs; std::vector<std::string> spec_strs;
std::vector<std::string> snd_names; std::vector<std::string> snd_names;
std::vector<std::string> evt_names;
bool adjust_diff; bool adjust_diff;
bool is_legacy; bool is_legacy;
fs::path scen_file; // transient fs::path scen_file; // transient

View File

@@ -2173,6 +2173,12 @@ bool edit_quest(size_t which_quest) {
cDialog quest_dlg(*ResMgr::dialogs.get("edit-quest")); cDialog quest_dlg(*ResMgr::dialogs.get("edit-quest"));
quest_dlg["cancel"].attachClickHandler(std::bind(&cDialog::toast, _1, false)); quest_dlg["cancel"].attachClickHandler(std::bind(&cDialog::toast, _1, false));
quest_dlg["okay"].attachClickHandler(std::bind(save_quest_from_dlog, _1, std::ref(quest), std::ref(which_quest), true)); quest_dlg["okay"].attachClickHandler(std::bind(save_quest_from_dlog, _1, std::ref(quest), std::ref(which_quest), true));
quest_dlg["choose-evt"].attachClickHandler([](cDialog& me, std::string, eKeyMod) {
int value = me["evt"].getTextAsNum();
value = choose_text_editable(scenario.evt_names, value, &me, "Select an event:");
me["evt"].setTextToNum(value);
return true;
});
quest_dlg["inbank"].attachFocusHandler([](cDialog& me, std::string, bool losing) -> bool { quest_dlg["inbank"].attachFocusHandler([](cDialog& me, std::string, bool losing) -> bool {
if(losing) { if(losing) {
me["bank1"].hide(); me["bank1"].hide();

View File

@@ -394,6 +394,13 @@ void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
data.PushText(scenario.snd_names[i]); data.PushText(scenario.snd_names[i]);
data.CloseElement("sound"); data.CloseElement("sound");
} }
for(int i = 0; i < scenario.evt_names.size(); i++) {
if(scenario.evt_names[i].empty()) continue;
data.OpenElement("event");
data.PushAttribute("id", i + 1);
data.PushText(scenario.evt_names[i]);
data.CloseElement("event");
}
data.CloseElement("editor"); data.CloseElement("editor");
data.CloseElement("scenario"); data.CloseElement("scenario");
} }

View File

@@ -501,6 +501,14 @@ short choose_text(eStrType list, unsigned short cur_choice, cDialog* parent, std
return dlog.show(cur_choice); return dlog.show(cur_choice);
} }
short choose_text_editable(std::vector<std::string>& list, short cur_choice, cDialog* parent, std::string title) {
cStringChoice choice(list, title, parent, true);
auto new_choice = choice.show(cur_choice <= 0 ? 0 : cur_choice - 1);
list = choice.getStrings();
if(choice->accepted()) return new_choice + 1;
return cur_choice;
}
static bool edit_text_event_filter(cDialog& me, std::string item_hit, short& which_str, eStrMode str_mode) { static bool edit_text_event_filter(cDialog& me, std::string item_hit, short& which_str, eStrMode str_mode) {
std::string newVal = me["text"].getText(); std::string newVal = me["text"].getText();
fetch_str(str_mode, which_str) = newVal; fetch_str(str_mode, which_str) = newVal;
@@ -1033,6 +1041,7 @@ static bool edit_spec_enc_value(cDialog& me, std::string item_hit, node_stack_t&
case eSpecPicker::STATUS: store = choose_status_effect(val, false, &me); break; case eSpecPicker::STATUS: store = choose_status_effect(val, false, &me); break;
case eSpecPicker::STATUS_PARTY: store = choose_status_effect(val, true, &me); break; case eSpecPicker::STATUS_PARTY: store = choose_status_effect(val, true, &me); break;
case eSpecPicker::SOUND: store = choose_sound(val, &me); break; case eSpecPicker::SOUND: store = choose_sound(val, &me); break;
case eSpecPicker::EVENT: store = choose_text_editable(scenario.evt_names, val, &me, "Select an event:"); break;
case eSpecPicker::NONE: return false; case eSpecPicker::NONE: return false;
} }
me[field].setTextToNum(store); me[field].setTextToNum(store);

View File

@@ -12,6 +12,7 @@ pic_num_t choose_graphic(short cur_choice,ePicType g_type,cDialog* parent);
short choose_background(short cur_choice, cDialog* parent); short choose_background(short cur_choice, cDialog* parent);
short choose_text_res(std::string res_list,short first_t,short last_t,unsigned short cur_choice,cDialog* parent,const char *title); short choose_text_res(std::string res_list,short first_t,short last_t,unsigned short cur_choice,cDialog* parent,const char *title);
short choose_text(eStrType list, unsigned short cur_choice, cDialog* parent,std::string title); short choose_text(eStrType list, unsigned short cur_choice, cDialog* parent,std::string title);
short choose_text_editable(std::vector<std::string>& list, short cur_choice, cDialog* parent, std::string title);
bool edit_text_str(short which_str,eStrMode mode); bool edit_text_str(short which_str,eStrMode mode);
bool edit_spec_enc(short which_node,short mode,cDialog* parent); bool edit_spec_enc(short which_node,short mode,cDialog* parent);
short get_fresh_spec(short which_mode); short get_fresh_spec(short which_mode);

View File

@@ -126,15 +126,16 @@ static void put_placed_monst_adv_in_dlog(cDialog& me, cTownperson& monst, const
me["num"].setTextToNum(which); me["num"].setTextToNum(which);
me["type"].setText(scenario.scen_monsters[monst.number].m_name); me["type"].setText(scenario.scen_monsters[monst.number].m_name);
int iTime = 0; int iTime = 0;
bool need_event = false;
switch(monst.time_flag) { switch(monst.time_flag) {
case eMonstTime::ALWAYS: iTime = 0; break; case eMonstTime::ALWAYS: iTime = 0; break;
case eMonstTime::APPEAR_ON_DAY: iTime = 1; break; case eMonstTime::APPEAR_ON_DAY: iTime = 1; need_event = true; break;
case eMonstTime::DISAPPEAR_ON_DAY: iTime = 2; break; case eMonstTime::DISAPPEAR_ON_DAY: iTime = 2; need_event = true; break;
case eMonstTime::SOMETIMES_A: iTime = 3; break; case eMonstTime::SOMETIMES_A: iTime = 3; break;
case eMonstTime::SOMETIMES_B: iTime = 4; break; case eMonstTime::SOMETIMES_B: iTime = 4; break;
case eMonstTime::SOMETIMES_C: iTime = 5; break; case eMonstTime::SOMETIMES_C: iTime = 5; break;
case eMonstTime::APPEAR_WHEN_EVENT: iTime = 6; break; case eMonstTime::APPEAR_WHEN_EVENT: iTime = 6; need_event = true; break;
case eMonstTime::DISAPPEAR_WHEN_EVENT: iTime = 7; break; case eMonstTime::DISAPPEAR_WHEN_EVENT: iTime = 7; need_event = true; break;
case eMonstTime::APPEAR_AFTER_CHOP: iTime = 8; break; case eMonstTime::APPEAR_AFTER_CHOP: iTime = 8; break;
} }
dynamic_cast<cLedGroup&>(me["time"]).setSelected("time" + std::to_string(iTime + 1)); dynamic_cast<cLedGroup&>(me["time"]).setSelected("time" + std::to_string(iTime + 1));
@@ -147,6 +148,11 @@ static void put_placed_monst_adv_in_dlog(cDialog& me, cTownperson& monst, const
me["hail"].setTextToNum(monst.special_on_talk); me["hail"].setTextToNum(monst.special_on_talk);
me["sdfx"].setTextToNum(monst.spec1); me["sdfx"].setTextToNum(monst.spec1);
me["sdfy"].setTextToNum(monst.spec2); me["sdfy"].setTextToNum(monst.spec2);
if(need_event) {
me["choose-event"].show();
} else {
me["choose-event"].hide();
}
} }
static bool get_placed_monst_adv_in_dlog(cDialog& me, cTownperson& monst) { static bool get_placed_monst_adv_in_dlog(cDialog& me, cTownperson& monst) {
@@ -196,6 +202,11 @@ static bool edit_placed_monst_adv_time_flag(cDialog& me, std::string, bool losin
int item_hit = time.getSelected()[4] - '1'; int item_hit = time.getSelected()[4] - '1';
me["extra1-lbl"].setText(day_str_1[item_hit]); me["extra1-lbl"].setText(day_str_1[item_hit]);
me["extra2-lbl"].setText(day_str_2[item_hit]); me["extra2-lbl"].setText(day_str_2[item_hit]);
if(item_hit == 1 || item_hit == 2 || item_hit == 6 || item_hit == 7) {
me["choose-event"].show();
} else {
me["choose-event"].hide();
}
return true; return true;
} }
@@ -226,6 +237,12 @@ cTownperson edit_placed_monst_adv(cTownperson initial, short which, cDialog& par
edit["editdeath"].attachClickHandler(std::bind(edit_placed_monst_adv_death, _1)); edit["editdeath"].attachClickHandler(std::bind(edit_placed_monst_adv_death, _1));
edit["edithail"].attachClickHandler(std::bind(edit_placed_monst_adv_hail, _1)); edit["edithail"].attachClickHandler(std::bind(edit_placed_monst_adv_hail, _1));
edit["time"].attachFocusHandler(edit_placed_monst_adv_time_flag); edit["time"].attachFocusHandler(edit_placed_monst_adv_time_flag);
edit["choose-event"].attachClickHandler([](cDialog& me, std::string, eKeyMod) {
int value = me["extra2"].getTextAsNum();
value = choose_text_editable(scenario.evt_names, value, &me, "Select an event:");
me["extra2"].setTextToNum(value);
return true;
});
put_placed_monst_adv_in_dlog(edit,initial,which); put_placed_monst_adv_in_dlog(edit,initial,which);
@@ -1189,7 +1206,7 @@ static void put_talk_node_in_dlog(cDialog& me, std::stack<node_ref_t>& talk_edit
me["str1"].setText(talk_node.str1); me["str1"].setText(talk_node.str1);
me["str2"].setText(talk_node.str2); me["str2"].setText(talk_node.str2);
if(talk_node.type == eTalkNode::SHOP) if(talk_node.type == eTalkNode::SHOP || talk_node.type == eTalkNode::DEP_ON_TIME_AND_EVENT)
me["chooseB"].show(); me["chooseB"].show();
else me["chooseB"].hide(); else me["chooseB"].hide();
me["chooseA-regular"].hide(); me["chooseA-regular"].hide();
@@ -1259,9 +1276,15 @@ static bool select_talk_node_personality(cDialog& me, std::stack<node_ref_t>& ta
static bool select_talk_node_value(cDialog& me, std::string item_hit, const std::stack<node_ref_t>& talk_edit_stack) { static bool select_talk_node_value(cDialog& me, std::string item_hit, const std::stack<node_ref_t>& talk_edit_stack) {
const auto& talk_node = talk_edit_stack.top().second; const auto& talk_node = talk_edit_stack.top().second;
if(item_hit == "chooseB") { if(item_hit == "chooseB") {
int i = me["extra2"].getTextAsNum(); if(talk_node.type == eTalkNode::SHOP) {
i = choose_text(STRT_SHOP,i,&me,"Which shop?"); int i = me["extra2"].getTextAsNum();
me["extra2"].setTextToNum(i); i = choose_text(STRT_SHOP,i,&me,"Which shop?");
me["extra2"].setTextToNum(i);
} else if(talk_node.type == eTalkNode::DEP_ON_TIME_AND_EVENT) {
int value = me["extra2"].getTextAsNum();
value = choose_text_editable(scenario.evt_names, value, &me, "Select an event:");
me["extra2"].setTextToNum(value);
}
} else if(item_hit == "chooseA") { } else if(item_hit == "chooseA") {
int spec = me["extra1"].getTextAsNum(); int spec = me["extra1"].getTextAsNum();
// Create/Edit a quest: // Create/Edit a quest: