Implement a quest system and job board
- The job board is loosely based on Exile III's job board; the dialog is converted from the one contained as a relic of E3 in BoE - Quest system is loosely based on a mix of Exile III jobs and Blades of Avernum quests - Talking to a monster (even a hostile one) can now trigger an arbitrary special node Dialog engine: - LED's now support wrapped labels
This commit is contained in:
@@ -137,12 +137,18 @@ the party is in town A, the response is Text 1. Otherwise, its Text 2.</p>
|
||||
</ol>
|
||||
<p>The possible shop types are as follows:</p>
|
||||
<ol start='0'>
|
||||
<li>Ordinary items shop; B is the number of the first item from the scenario's item list.</li>
|
||||
<li>Mage spells shop; B is the number of the first mage spell (0 - 61, but generally not lower than 30).</li>
|
||||
<li>Priest spells shop; B is the number of the first priest spell (0 - 61, but generally not lower than 30).</li>
|
||||
<li>Ordinary items shop; B is the number of the first item from the scenario's item
|
||||
list.</li>
|
||||
<li>Mage spells shop; B is the number of the first mage spell (0 - 61, but generally not
|
||||
lower than 30).</li>
|
||||
<li>Priest spells shop; B is the number of the first priest spell (0 - 61, but generally
|
||||
not lower than 30).</li>
|
||||
<li>Alchemy shop; B is the number of the first recipe (0 - 19).</li>
|
||||
<li>Healing shop; B and C are ignored.</li>
|
||||
<li>Random Items Shop, type 0. This brings up a shop window where the party can buy up to 10 randomly chosen items. These items are changed every 3000 moves, and are often magical. B and C are ignored for these shops.</li>
|
||||
<li>Random Items Shop, type 0. This brings up a shop window where the party can buy up to
|
||||
10 randomly chosen items. These items are changed every 3000 moves, and are often magical.
|
||||
There are five separate random lists maintained by the game. B and C are ignored for these
|
||||
shops.</li>
|
||||
<li>Random Items Shop, type 1.</li>
|
||||
<li>Random Items Shop, type 2.</li>
|
||||
<li>Random Items Shop, type 3.</li>
|
||||
@@ -154,20 +160,12 @@ is the response, the player gets to shop in a store called Fred's Fish. The pric
|
||||
quite cheap, and the player can buy items 193-207.</p>
|
||||
<p><b>Node Type 8 - Training</b> The training window immediately comes up. Text 1 & 2
|
||||
are ignored.</p>
|
||||
<p><b>Node Type 9 - Mage Spell Shop</b> Shop where the party can buy mage spells. A is the
|
||||
cost adjustment (Range 0 ... 6, see above). B is the number of the first spell sold in the
|
||||
shop (press the Choose button to select). C is the total number of spells sold in the
|
||||
shop, taken from the list of spells in the game, starting with B. Text 1 is the name of
|
||||
the shop.</p>
|
||||
<p><b>Node Type 10 - Priest Spell Shop</b> Exactly like Mage Spell Shop (above) but with
|
||||
Priest spells.</p>
|
||||
<p><b>Node Type 11 - Alchemy Shop </b>Shop where the party can buy alchemy recipes. A is
|
||||
the cost adjustment (Range 0 ... 6, see above. B is the number of the first recipe sold in
|
||||
the shop (press the Choose button to select). C is the total number of recipes sold in the
|
||||
shop, taken from the list of recipes in the game, starting with B. Text 1 is the name of
|
||||
the shop.</p>
|
||||
<p><b>Node Type 12 - Healer </b> Brings up the healing screen. A is the cost adjustment
|
||||
(Range 0 ... 6, see above) and Text 1 is the name of the healer.</p>
|
||||
<p><b>Node Type 9 - Job Board</b> Brings up the job board, where the player can choose to
|
||||
accept a job or quest. This could be something simple like delivering a message or
|
||||
package, or it could be something plot-relevant. A is the number of the job board to
|
||||
show. If the job board is angry at you for failing too many jobs, no job board will be
|
||||
shown and the response will be Text 2. Text 1 is currently ignored, but reserved to be a
|
||||
name for the job board.</p>
|
||||
|
||||
<h3>Item Button Talking Nodes</h3>
|
||||
|
||||
@@ -230,11 +228,13 @@ the number of the special item being sold. If they already have it, they are tol
|
||||
already have that". Otherwise, the cost of the item is B gold. If the party can afford it,
|
||||
they are told Text 1. Otherwise, they are told Text 2.</p>
|
||||
<p>Note: If you set the cost to 0, the party is always given the item.</p>
|
||||
<p><b>Node Type 23 - Special Item Shop</b> This brings up a shop window where the party
|
||||
can buy up to 10 randomly chosen items. These items are changed every 3000 moves, and are
|
||||
often magical. There are 5 different, independently maintained lists of items the shop can
|
||||
give. A is the cost adjustment of the shop (Range 0 ... 6, see above) and B is list of
|
||||
items to sell from (0 .. 4).</p>
|
||||
<p><b>Node Type 23 - Receive Quest</b> If quest A has been completed, the response is Text
|
||||
2 and nothing special happens. Otherwise, the party is given quest A (if they hadn't
|
||||
already received it) and the response is Text 1.</p>
|
||||
<p>Note: This node is not set up for the possibility that the quest somehow failed. If the
|
||||
quest is one that has a deadline or other failure condition, it might be better to instead
|
||||
use node types 29 or 30 to call a special node and check the quest's status before giving
|
||||
it.</p>
|
||||
<p><b>Node Type 24 - Reveal Town Location</b> Charges the party money, and enables them to
|
||||
enter a hidden town. The cost is A gold. If the party can afford it, they are told Text 1,
|
||||
and they will be able to see and enter town/dungeon number B. Otherwise, they are told
|
||||
|
||||
@@ -468,6 +468,26 @@ to cause the game to crash since it won't know which town to get the node from.
|
||||
<dt>Extra 1a:</dt><dd>This specifies the personality to use for the conversation. A
|
||||
personality is defined as part of a town; see the chapter on Dialogue for
|
||||
details.</dd></dd>
|
||||
|
||||
<dt>Type 45: Update Quest</dt><dd>This special node allows you to set the status of one of
|
||||
the scenario's quests or jobs.
|
||||
<dl>
|
||||
<dt>Extra 1a:</dt><dd>The quest to update.</dd>
|
||||
<dt>Extra 1b:</dt><dd>The new status for the quest:
|
||||
<ol start='0'>
|
||||
<li>Marks the quest as not started. This might be useful if the quest is on a job board
|
||||
and you want it to be repeatable. Note however that marking a quest available does not
|
||||
grant automatic rewards.</li>
|
||||
<li>Marks the quest as started. If it wasn't already started, the game automatically fills
|
||||
in the quest's start day, and if Extra 2a is non-negative, the quest is considered to have
|
||||
come from the job board with that number.</li>
|
||||
<li>Marks the quest as complete. If the quest specifies automatic rewards, the game grants
|
||||
these to the party.</li>
|
||||
<li>Marks the quest as failed. If it came from a job board, the game increases the anger
|
||||
level of that job board. You can set Extra 2a if you want to increase the anger level even
|
||||
more. A job board won't offer any jobs if its anger level is 50 or more.</li>
|
||||
</ol>
|
||||
</dd></dd>
|
||||
</dl>
|
||||
|
||||
<h3>One-Shot Specials</h3>
|
||||
@@ -1192,22 +1212,33 @@ is called.
|
||||
<dt>Extra 1a:</dt><dd>If the Stuff Done flag equals this value, call special in Extra
|
||||
1b.</dd></dd>
|
||||
|
||||
<dt>Type 156: If Context?</dt>Result depends on how the special node was called.
|
||||
<dt>Type 156: If Context?</dt><dd>Result depends on how the special node was called.
|
||||
<dl>
|
||||
<dt>Extra 1a:</dt><dd>The context to test for. Click Choose to select one. If you want to check whether the party is in town, in combat, or outdoors, you probably want one of the first three options. To test for Ritual of Sanctification, use the Targeting Spell option and set Extra 1b to 108. Most of these contexts arise from special nodes assigned while editing monsters, items, town details, and other things, rather than special nodes assigned to a terrain space.</dd>
|
||||
<dt>Extra 1b:</dt><dd>The meaning of this field depends on the context. Usually it's not used. For the three movement contexts, 0 means you can enter the space and 1 means you can't. For the targeting spell context, setting this to something other than -1 means that the special in Extra 1c will only be called if the spell that was cast is equal to the spell that has the given number. Add 100 to indicate a priest spell. Item-only spells such as Wrack or Strengthen Target can also be tested for; just enter the same spell ID you would enter for the item ability.
|
||||
<dt>Extra 1b:</dt><dd>The meaning of this field depends on the context. Usually it's not used. For the three movement contexts, 0 means you can enter the space and 1 means you can't. For the targeting spell context, setting this to something other than -1 means that the special in Extra 1c will only be called if the spell that was cast is equal to the spell that has the given number. Add 100 to indicate a priest spell. Item-only spells such as Wrack or Strengthen Target can also be tested for; just enter the same spell ID you would enter for the item ability.</dd></dd>
|
||||
|
||||
<dt>Type 157: If Numeric Response?</dt>Result depends on a number entered by the player.
|
||||
<dt>Type 157: If Numeric Response?</dt><dd>Result depends on a number entered by the player.
|
||||
<!-- TODO: Document this. -->
|
||||
</dd>
|
||||
|
||||
<dt>Type 158: In Boat?</dt>Result depends whether the player is in a boat.
|
||||
<dt>Type 158: In Boat?</dt><dd>Result depends whether the player is in a boat.
|
||||
<dl>
|
||||
<dt>Extra 1b:</dt><dd>If left at -1, the special in Extra 1c is called if the player is in any boat. Otherwise, the special in Extra 1c is only called if the player is in the boat with that number.</dd></dd>
|
||||
<dt>Extra 1b:</dt><dd>If left at -1, the special in Extra 1c is called if the player is in
|
||||
any boat. Otherwise, the special in Extra 1c is only called if the player is in the boat
|
||||
with that number.</dd></dd>
|
||||
|
||||
<dt>Type 158: On Horse?</dt>Result depends whether the player is on horseback.
|
||||
<dt>Type 158: On Horse?</dt><dd>Result depends whether the player is on horseback.
|
||||
<dl>
|
||||
<dt>Extra 1b:</dt><dd>If left at -1, the special in Extra 1c is called if the player is on any horses. Otherwise, the special in Extra 1c is only called if the player is on the horses with that number.</dd></dd>
|
||||
<dt>Extra 1b:</dt><dd>If left at -1, the special in Extra 1c is called if the player is on
|
||||
any horses. Otherwise, the special in Extra 1c is only called if the player is on the
|
||||
horses with that number.</dd></dd>
|
||||
|
||||
<dt>Type 159: Quest Status?</dt><dd>Result depends on the status of a quest.
|
||||
<dl>
|
||||
<dt>Extra 1a:</dt><dd>The quest to check.</dd>
|
||||
<dt>Extra 1b:</dt><dd>If the quest has this status (0 - not started, 1 - started, 2 -
|
||||
completed, 3 - failed), the special in Extra 1c is called.</dd>
|
||||
<dt>Jumpto:</dt><dd>Otherwise, this special is called.</dd></dd>
|
||||
</dl>
|
||||
|
||||
<h3>Town Mode Specials</h3>
|
||||
|
||||
@@ -129,6 +129,15 @@
|
||||
<sector-start x="20" y="20"/>
|
||||
<!-- Definitions of special items, if any -->
|
||||
<specials/>
|
||||
<!-- Define quests here -->
|
||||
<quest start-with='false'>
|
||||
<!-- Quests can have a deadline, and an event that waives the deadline -->
|
||||
<deadline relative='true' waive-if='5'>12</deadline>
|
||||
<!-- Quests can have an automatic reward -->
|
||||
<reward xp='12000' gold='250'/>
|
||||
<name>Sample Quest</name>
|
||||
<description>Your mission, if you choose to accept it, is...!!??</description>
|
||||
</quest>
|
||||
</game>
|
||||
|
||||
<editor>
|
||||
|
||||
@@ -136,6 +136,32 @@
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="quest" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name='deadline' minOccurs='0'>
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base='xs:integer'>
|
||||
<xs:attribute name="relative" type="bool"/>
|
||||
<xs:attribute name='waive-if' type='xs:integer'/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name='reward' minOccurs='0'>
|
||||
<xs:complexType>
|
||||
<xs:attribute name='xp' type='xs:integer'/>
|
||||
<xs:attribute name='gold' type='xs:integer'/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name='bank' type='xs:integer' minOccurs='0' maxOccurs='2'/>
|
||||
<xs:element name="name" type="xs:string"/>
|
||||
<xs:element name="description" type="xs:string"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="start-with" type="bool"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="timer" minOccurs="0" maxOccurs="20">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
|
||||
@@ -111,8 +111,10 @@
|
||||
<xsl:if test='./@state'>
|
||||
background-image: url('img/button/led-<xsl:value-of select='./@state'/>.png');
|
||||
</xsl:if>
|
||||
background-position: left top;
|
||||
left: <xsl:value-of select='./@left'/>px; top: <xsl:value-of select='./@top'/>px;
|
||||
width: <xsl:value-of select='./@width'/>px;
|
||||
height: <xsl:value-of select='./@height'/>px;
|
||||
</xsl:attribute>
|
||||
<xsl:value-of select='.'/>
|
||||
</div>
|
||||
|
||||
30
rsrc/dialogs/edit-quest.xml
Normal file
30
rsrc/dialogs/edit-quest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
|
||||
<dialog defbtn='okay' debug='true'>
|
||||
<pict type='dlog' num='16' top='8' left='8'/>
|
||||
<text size='large' top='8' left='50' width='100' height='17'>Editing Quests</text>
|
||||
<text top='8' left='200' width='100' height='16'>Quest number:</text>
|
||||
<text name='num' top='8' left='300' width='100' height='16'/>
|
||||
<text top='58' left='50' width='132' height='16'>Name of Quest:</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='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>
|
||||
<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>
|
||||
<field name='evt' top='246' left='205' width='100' height='16'/>
|
||||
<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>
|
||||
<field name='xp' top='306' left='205' width='110' height='16'/>
|
||||
<text top='333' left='50' width='150' height='16'>Gold:</text>
|
||||
<field name='gold' top='332' left='205' width='110' height='16'/>
|
||||
<led name='rel' wrap='true' top='208' left='330' width='120' height='28'>Deadline is relative to start day</led>
|
||||
<led name='start' wrap='true' top='246' left='330' width='120' height='28'>Player is given quest when scenario starts</led>
|
||||
<led name='inbank' top='285' left='330' width='120'>Include in a job bank:</led>
|
||||
<field name='bank1' top='306' left='344' width='110' height='16'/>
|
||||
<field name='bank2' top='332' left='344' width='110' height='16'/>
|
||||
<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='cancel' type='regular' def-key='esc' top='358' left='322'>Cancel</button>
|
||||
<button name='okay' type='regular' top='358' left='387'>OK</button>
|
||||
</dialog>
|
||||
18
rsrc/dialogs/job-board.xml
Normal file
18
rsrc/dialogs/job-board.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
|
||||
<dialog defbtn='done' debug='true'>
|
||||
<pict type='dlog' num='3' top='9' left='9'/>
|
||||
<text size='large' top='9' left='54' width='161' height='19'>THE JOB BOARD:</text>
|
||||
<text top='9' left='230' width='92' height='19'>Current day:</text>
|
||||
<text name='day' top='9' left='324' width='92' height='19'/>
|
||||
<text name='feedback' framed='true' top='313' left='19' width='183' height='18'/>
|
||||
<text name='job1' top='38' left='54' width='364' height='60'/>
|
||||
<button name='take1' type='regular' top='78' left='426'>Take</button>
|
||||
<text name='job2' top='104' left='54' width='364' height='60'/>
|
||||
<button name='take2' type='regular' top='144' left='426'>Take</button>
|
||||
<text name='job3' top='170' left='54' width='364' height='60'/>
|
||||
<button name='take3' type='regular' top='210' left='426'>Take</button>
|
||||
<text name='job4' top='236' left='54' width='364' height='60'/>
|
||||
<button name='take4' type='regular' top='276' left='426'>Take</button>
|
||||
<button name='done' type='done' top='305' left='426'/>
|
||||
</dialog>
|
||||
14
rsrc/dialogs/quest-info.xml
Normal file
14
rsrc/dialogs/quest-info.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
|
||||
<dialog defbtn='done' debug='true'>
|
||||
<pict type='dlog' num='21' top='8' left='8'/>
|
||||
<text name='name' framed='true' top='9' left='55' width='257' height='19'/>
|
||||
<text name='descr' framed='true' top='39' left='55' width='257' height='87'/>
|
||||
<text top='136' left='55' width='80' height='16'>Received on:</text>
|
||||
<text name='start' framed='true' top='136' left='140' width='100' height='16'/>
|
||||
<text top='156' left='55' width='80' height='16'>Deadline:</text>
|
||||
<text name='chop' framed='true' top='156' left='140' width='100' height='16'/>
|
||||
<text top='176' left='55' width='80' height='16'>Pay:</text>
|
||||
<text name='pay' framed='true' top='176' left='140' width='100' height='16'/>
|
||||
<button name='done' type='done' top='208' left='251'/>
|
||||
</dialog>
|
||||
@@ -242,6 +242,7 @@
|
||||
</xs:attribute>
|
||||
<xs:attributeGroup ref="rect"/>
|
||||
<xs:attributeGroup ref="font"/>
|
||||
<xs:attribute name="wrap" default="false" type="bool"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -5,7 +5,7 @@ Looking (Outdoors)
|
||||
Looking (Town/Combat)
|
||||
Entering town
|
||||
Leaving town
|
||||
Talking
|
||||
In conversation
|
||||
Using special item
|
||||
Town event timer triggered
|
||||
Scenario event timer triggered
|
||||
@@ -22,3 +22,4 @@ Attacking at melee
|
||||
Being attacked at melee
|
||||
Attacking at range
|
||||
Being attacked at range
|
||||
Initiating conversation
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 234 KiB |
@@ -553,6 +553,14 @@ static void handle_talk(location destination, bool& did_something, bool& need_re
|
||||
if(univ.town.monst[i].on_space(destination)) {
|
||||
did_something = true;
|
||||
need_redraw = true;
|
||||
if(univ.town.monst[i].special_on_talk >= 0) {
|
||||
short s1, s2, s3;
|
||||
run_special(eSpecCtx::HAIL, 2, univ.town.monst[i].special_on_talk, univ.town.monst[i].cur_loc, &s1, &s2, &s3);
|
||||
if(s3 > 0)
|
||||
need_redraw = true;
|
||||
if(s1 > 0)
|
||||
break;
|
||||
}
|
||||
if(univ.town.monst[i].attitude % 2 == 1) {
|
||||
add_string_to_buf(" Creature is hostile.");
|
||||
} else if(univ.town.monst[i].summon_time > 0 || univ.town.monst[i].personality < 0) {
|
||||
@@ -1344,8 +1352,7 @@ bool handle_action(sf::Event event) {
|
||||
set_stat_window(ITEM_WIN_SPECIAL);
|
||||
break;
|
||||
case 7:
|
||||
// TODO: Jobs! Or maybe quests!
|
||||
//set_stat_window(ITEM_WIN_QUESTS);
|
||||
set_stat_window(ITEM_WIN_QUESTS);
|
||||
break;
|
||||
case 8: // help
|
||||
cChoiceDlog("help-inventory").show();
|
||||
@@ -1384,7 +1391,7 @@ bool handle_action(sf::Event event) {
|
||||
if(stat_window == ITEM_WIN_SPECIAL)
|
||||
put_spec_item_info(spec_item_array[item_hit]);
|
||||
else if(stat_window == ITEM_WIN_QUESTS)
|
||||
; // TODO: Implement quests view
|
||||
put_quest_info(spec_item_array[item_hit]);
|
||||
else display_pc_item(stat_window, item_hit,univ.party[stat_window].items[item_hit],0);
|
||||
break;
|
||||
case 5: // sell? That this code was reached indicates that the item was sellable
|
||||
|
||||
@@ -523,6 +523,67 @@ void end_talk_mode() {
|
||||
redraw_screen(REFRESH_TERRAIN | REFRESH_BAR);
|
||||
}
|
||||
|
||||
static void fill_job_bank(cDialog& me, job_bank_t& bank, std::string) {
|
||||
// TODO: Maybe customize the icon?
|
||||
// TODO: Allow custom title?
|
||||
me["day"].setTextToNum(calc_day());
|
||||
for(int i = 0; i < 4; i++) {
|
||||
std::string id = std::to_string(i + 1);
|
||||
if(bank.jobs[i] >= 0 && bank.jobs[i] < univ.scenario.quests.size()) {
|
||||
cQuest& quest = univ.scenario.quests[bank.jobs[i]];
|
||||
std::string description = quest.descr;
|
||||
if(quest.deadline > 0) {
|
||||
if(quest.flags % 10 == 1)
|
||||
description += " Must be completed in " + std::to_string(quest.deadline) + " days.";
|
||||
else description += " Must be completed by day " + std::to_string(quest.deadline) + ".";
|
||||
}
|
||||
description += " Pay is " + std::to_string(quest.gold) + " gold.";
|
||||
me["take" + id].show();
|
||||
me["job" + id].setText(description);
|
||||
} else {
|
||||
me["take" + id].hide();
|
||||
me["job" + id].setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void show_job_bank(int which_bank, std::string title) {
|
||||
cDialog job_dlg("job-bank");
|
||||
job_dlg.attachClickHandlers([&](cDialog& me, std::string hit, eKeyMod) -> bool {
|
||||
int which = hit[4] - '1';
|
||||
me["prompt"].setText("Job accepted.");
|
||||
job_bank_t& bank = univ.party.job_banks[which_bank];
|
||||
univ.party.quest_status[bank.jobs[which]] = eQuestStatus::STARTED;
|
||||
univ.party.quest_source[bank.jobs[which]] = store_personality;
|
||||
univ.party.quest_start[bank.jobs[which]] = calc_day();
|
||||
// Now, if there are spare jobs available, fill in. Otherwise, clear space.
|
||||
if(bank.jobs[4] >= 0)
|
||||
std::swap(bank.jobs[which], bank.jobs[4]);
|
||||
else if(bank.jobs[5] >= 0)
|
||||
std::swap(bank.jobs[which], bank.jobs[5]);
|
||||
fill_job_bank(me, bank, title);
|
||||
return true;
|
||||
}, {"take1", "take2", "take3", "take4"});
|
||||
job_dlg["done"].attachClickHandler(std::bind(&cDialog::toast, &job_dlg, false));
|
||||
|
||||
if(which_bank >= univ.party.job_banks.size())
|
||||
univ.party.job_banks.resize(which_bank + 1);
|
||||
if(!univ.party.job_banks[which_bank].inited)
|
||||
generate_job_bank(which_bank, univ.party.job_banks[which_bank]);
|
||||
fill_job_bank(job_dlg, univ.party.job_banks[which_bank], title);
|
||||
|
||||
int anger = univ.party.job_banks[which_bank].anger;
|
||||
if(anger >= 0 && anger < 10) {
|
||||
job_dlg["prompt"].setText("Dispatcher is neutral towards you.");
|
||||
} else if(anger >= 10 && anger < 20) {
|
||||
job_dlg["prompt"].setText("Dispatcher is a little annoyed at you.");
|
||||
} else if(anger >= 20 && anger < 35) {
|
||||
job_dlg["prompt"].setText("Dispatcher is annoyed at you.");
|
||||
} else job_dlg["prompt"].setText("Dispatcher is rather angry at you.");
|
||||
|
||||
job_dlg.run();
|
||||
}
|
||||
|
||||
void handle_talk_event(location p) {
|
||||
short i,get_pc,s1 = -1,s2 = -1,s3 = -1;
|
||||
char asked[4];
|
||||
@@ -727,6 +788,17 @@ void handle_talk_event(location p) {
|
||||
start_shop_mode(shop,b,b + c - 1,a,save_talk_str1.c_str());
|
||||
strnum1 = -1;
|
||||
return;
|
||||
case eTalkNode::JOB_BANK:
|
||||
if(a < univ.party.job_banks.size() && univ.party.job_banks[a].anger >= 50) {
|
||||
strnum1 = strnum2;
|
||||
save_talk_str1 = save_talk_str2;
|
||||
strnum2 = 0;
|
||||
save_talk_str2 = "";
|
||||
break;
|
||||
} else {
|
||||
show_job_bank(a, save_talk_str1.c_str());
|
||||
return;
|
||||
}
|
||||
case eTalkNode::SELL_WEAPONS:
|
||||
strnum1 = -1;
|
||||
stat_screen_mode = MODE_SELL_WEAP;
|
||||
@@ -853,6 +925,30 @@ void handle_talk_event(location p) {
|
||||
strnum2 = 0;
|
||||
save_talk_str2 = "";
|
||||
break;
|
||||
case eTalkNode::RECEIVE_QUEST:
|
||||
if(a < 0 || a >= univ.scenario.quests.size()) {
|
||||
giveError("Tried to give a nonexistent quest!");
|
||||
return;
|
||||
}
|
||||
switch(univ.party.quest_status[a]) {
|
||||
case eQuestStatus::AVAILABLE:
|
||||
univ.party.quest_status[a] = eQuestStatus::STARTED;
|
||||
univ.party.quest_source[a] = -1;
|
||||
univ.party.quest_start[a] = calc_day();
|
||||
break;
|
||||
case eQuestStatus::STARTED:
|
||||
break;
|
||||
case eQuestStatus::COMPLETED:
|
||||
strnum1 = strnum2;
|
||||
save_talk_str1 = save_talk_str2;
|
||||
break;
|
||||
case eQuestStatus::FAILED:
|
||||
// TODO: How to handle this?
|
||||
return;
|
||||
}
|
||||
strnum2 = 0;
|
||||
save_talk_str2 = "";
|
||||
break;
|
||||
case eTalkNode::BUY_TOWN_LOC:
|
||||
if(univ.party.can_find_town[b]) {
|
||||
// TODO: Uh, is something supposed to happen here?
|
||||
|
||||
@@ -849,6 +849,23 @@ void give_help(short help1, short help2, cDialog& parent) {
|
||||
give_help(help1, help2, &parent);
|
||||
}
|
||||
|
||||
void put_quest_info(short which_i) {
|
||||
cQuest& quest = univ.scenario.quests[which_i];
|
||||
cDialog quest_dlg("quest-info");
|
||||
quest_dlg["name"].setText(quest.name);
|
||||
quest_dlg["descr"].setText(quest.descr);
|
||||
int start = univ.party.quest_start[which_i];
|
||||
quest_dlg["start"].setText("Day " + std::to_string(start));
|
||||
if(quest.deadline > 0)
|
||||
quest_dlg["chop"].setText("Day " + std::to_string(quest.deadline + (quest.flags % 10) * start));
|
||||
else quest_dlg["chop"].setText("None");
|
||||
if(quest.gold > 0)
|
||||
quest_dlg["pay"].setText(std::to_string(quest.gold) + " gold");
|
||||
else quest_dlg["pay"].setText("Unknown");
|
||||
quest_dlg["done"].attachClickHandler(std::bind(&cDialog::toast, &quest_dlg, false));
|
||||
quest_dlg.run();
|
||||
}
|
||||
|
||||
void put_spec_item_info (short which_i) {
|
||||
cStrDlog display_strings(univ.scenario.special_items[which_i].descr,"",
|
||||
univ.scenario.special_items[which_i].name,univ.scenario.intro_pic,PIC_SCEN);
|
||||
|
||||
@@ -24,6 +24,7 @@ void add_to_journal(short event);
|
||||
void give_help(short help1,short help2,class cDialog& parent_num);
|
||||
void give_help(short help1,short help2);
|
||||
void put_spec_item_info (short which_i);
|
||||
void put_quest_info(short which_i);
|
||||
|
||||
// These are defined in pc.editors.cpp since they are also used by the character editor
|
||||
void pick_race_abil(cPlayer *pc,short mode);
|
||||
|
||||
@@ -920,6 +920,20 @@ cItem return_treasure(short loot) {
|
||||
|
||||
}
|
||||
|
||||
void generate_job_bank(int which, job_bank_t& bank) {
|
||||
std::fill(bank.jobs.begin(), bank.jobs.end(), -1);
|
||||
bank.inited = true;
|
||||
size_t iSlot = 0;
|
||||
for(size_t i = 0; iSlot < 4 && i < univ.scenario.quests.size(); i++) {
|
||||
if(univ.scenario.quests[i].bank1 != which && univ.scenario.quests[i].bank2 != which)
|
||||
continue;
|
||||
if(univ.party.quest_status[i] != eQuestStatus::AVAILABLE)
|
||||
continue;
|
||||
if(get_ran(1,1,100) <= 50 - bank.anger)
|
||||
bank.jobs[iSlot++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
void refresh_store_items() {
|
||||
short i,j;
|
||||
short loot_index[10] = {1,1,1,1,2,2,2,3,3,4};
|
||||
@@ -934,6 +948,9 @@ void refresh_store_items() {
|
||||
univ.party.magic_store_items[i][j].ident = true;
|
||||
}
|
||||
|
||||
for(i = 0; i < univ.party.job_banks.size(); i++) {
|
||||
generate_job_bank(i, univ.party.job_banks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ void reset_item_max();
|
||||
short item_val(cItem item);
|
||||
void place_treasure(location where,short level,short loot,short mode);
|
||||
cItem return_treasure(short loot);
|
||||
void generate_job_bank(int which, job_bank_t& bank);
|
||||
void refresh_store_items();
|
||||
std::string get_text_response(std::string prompt = "", pic_num_t pic = 16);
|
||||
short get_num_response(short min, short max, std::string prompt);
|
||||
|
||||
@@ -171,6 +171,13 @@ static void init_party_scen_data() {
|
||||
univ.party.party_event_timers.clear();
|
||||
for(i = 0; i < 50; i++)
|
||||
univ.party.spec_items[i] = univ.scenario.special_items[i].flags >= 10;
|
||||
for(i = 0; i < univ.scenario.quests.size(); i++) {
|
||||
if(univ.scenario.quests[i].flags >= 10) {
|
||||
univ.party.quest_status[i] = eQuestStatus::STARTED;
|
||||
univ.party.quest_start[i] = 1;
|
||||
univ.party.quest_source[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < 200; i++)
|
||||
univ.party.m_killed[i] = 0;
|
||||
|
||||
@@ -1816,6 +1816,45 @@ void special_increase_age(long length, bool queue) {
|
||||
location null_loc; // TODO: Should we pass the party's location here? It doesn't quite make sense to me though...
|
||||
unsigned long age_before = univ.party.age - length;
|
||||
unsigned long current_age = univ.party.age;
|
||||
bool failed_job = false;
|
||||
|
||||
for(auto p : univ.party.quest_status) {
|
||||
if(p.second != eQuestStatus::STARTED)
|
||||
continue;
|
||||
cQuest& quest = univ.scenario.quests[p.first];
|
||||
if(quest.deadline <= 0)
|
||||
continue;
|
||||
bool is_relative = quest.flags % 10;
|
||||
int deadline = quest.deadline + is_relative * univ.party.quest_start[p.first];
|
||||
if(day_reached(deadline + 1, quest.event)) {
|
||||
p.second = eQuestStatus::FAILED;
|
||||
if(univ.party.quest_source[p.first] >= 0) {
|
||||
int bank = univ.party.quest_source[p.first];
|
||||
// Safety valve in case it was given by a special node
|
||||
if(bank >= univ.party.job_banks.size())
|
||||
univ.party.job_banks.resize(bank + 1);
|
||||
int add_anger = 1;
|
||||
if(quest.flags % 10 == 1) {
|
||||
if(quest.deadline < 20)
|
||||
add_anger++;
|
||||
if(quest.deadline < 10)
|
||||
add_anger++;
|
||||
if(quest.deadline < 5)
|
||||
add_anger++;
|
||||
} else if(quest.deadline - univ.party.quest_start[p.first] > 20)
|
||||
add_anger++;
|
||||
univ.party.job_banks[bank].anger += add_anger;
|
||||
}
|
||||
if(!failed_job)
|
||||
add_string_to_buf("The deadline for one of your quests has passed.",2);
|
||||
failed_job = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Angered job boards slowly forgive you
|
||||
if(univ.party.age % 30 == 0)
|
||||
for(i = 0; i < univ.party.job_banks.size(); i++)
|
||||
move_to_zero(univ.party.job_banks[i].anger);
|
||||
|
||||
if(is_town() || (is_combat() && which_combat_type == 1)) {
|
||||
for(i = 0; i < 8; i++)
|
||||
@@ -2399,6 +2438,42 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
|
||||
start_talk_mode(i, spec.ex1a, spec.ex1b, spec.pic);
|
||||
*next_spec = -1;
|
||||
break;
|
||||
case eSpecType::UPDATE_QUEST:
|
||||
if(spec.ex1a < 0 || spec.ex1a >= univ.scenario.quests.size()) {
|
||||
giveError("The scenario tried to update a non-existent quest.");
|
||||
break;
|
||||
}
|
||||
if(spec.ex1b < 0 || spec.ex1b > 3) {
|
||||
giveError("Invalid quest status (range 0 .. 3).");
|
||||
break;
|
||||
}
|
||||
if(spec.ex1b == int(eQuestStatus::STARTED) && univ.party.quest_status[spec.ex1a] != eQuestStatus::STARTED) {
|
||||
univ.party.quest_start[spec.ex1a] = calc_day();
|
||||
univ.party.quest_source[spec.ex1a] = max(-1,spec.ex2a);
|
||||
if(univ.party.quest_source[spec.ex1a] >= univ.party.job_banks.size())
|
||||
univ.party.job_banks.resize(univ.party.quest_source[spec.ex1a] + 1);
|
||||
}
|
||||
univ.party.quest_status[spec.ex1a] = eQuestStatus(spec.ex1b);
|
||||
switch(univ.party.quest_status[spec.ex1a]) {
|
||||
case eQuestStatus::STARTED: add_string_to_buf("You have received a quest."); break;
|
||||
case eQuestStatus::AVAILABLE: break; // TODO: Should this award XP/gold if the quest was previously started?
|
||||
case eQuestStatus::FAILED:
|
||||
add_string_to_buf("You have failed to complete a quest.");
|
||||
if(univ.party.quest_source[spec.ex1a] >= 0 && univ.party.quest_source[spec.ex1a] < univ.party.job_banks.size())
|
||||
univ.party.job_banks[univ.party.quest_source[spec.ex1a]].anger += spec.ex2a < 0 ? 1 : spec.ex2a;
|
||||
break;
|
||||
case eQuestStatus::COMPLETED:
|
||||
add_string_to_buf("You have completed a quest!");
|
||||
if(univ.scenario.quests[spec.ex1a].gold > 0) {
|
||||
int gold = univ.scenario.quests[spec.ex1a].gold;
|
||||
add_string_to_buf(" Received " + std::to_string(gold) + " as a reward.");
|
||||
give_gold(gold, true);
|
||||
}
|
||||
if(univ.scenario.quests[spec.ex1a].xp > 0)
|
||||
award_party_xp(univ.scenario.quests[spec.ex1a].xp);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
giveError("Special node type \"" + (*cur_node.type).name() + "\" is either miscategorized or unimplemented!");
|
||||
break;
|
||||
@@ -3457,6 +3532,18 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
|
||||
if(spec.ex2c == 1 && k > j) *next_spec = spec.ex1b;
|
||||
if(spec.ex2c == 2 && k >= j) *next_spec = spec.ex1b;
|
||||
break;
|
||||
case eSpecType::IF_QUEST:
|
||||
if(spec.ex1a < 0 || spec.ex1a >= univ.scenario.quests.size()) {
|
||||
giveError("The scenario tried to update a non-existent quest.");
|
||||
break;
|
||||
}
|
||||
if(spec.ex1b < 0 || spec.ex1b > 3) {
|
||||
giveError("Invalid quest status (range 0 .. 3).");
|
||||
break;
|
||||
}
|
||||
if(univ.party.quest_status[spec.ex1a] == eQuestStatus(spec.ex1b))
|
||||
*next_spec = spec.ex1c;
|
||||
break;
|
||||
case eSpecType::IF_CONTEXT:
|
||||
// TODO: Test this. In particular, test that the legacy behaviour is correct.
|
||||
j = -1;
|
||||
@@ -3581,6 +3668,10 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
|
||||
if(which_mode == eSpecCtx::ATTACKED_RANGE)
|
||||
*next_spec = spec.ex1c;
|
||||
break;
|
||||
case 25: // Initiating conversation
|
||||
if(which_mode == eSpecCtx::HAIL)
|
||||
*next_spec = spec.ex1c;
|
||||
break;
|
||||
}
|
||||
if(j >= 0) *a = j;
|
||||
break;
|
||||
|
||||
@@ -256,7 +256,19 @@ void put_item_screen(short screen_num,short suppress_buttons) {
|
||||
}
|
||||
break;
|
||||
case ITEM_WIN_QUESTS:
|
||||
// TODO: Implement quest list
|
||||
style.font = FONT_BOLD;
|
||||
style.colour = sf::Color::White;
|
||||
win_draw_string(item_stats_gworld,upper_frame_rect,"Quests/Jobs:",eTextMode::WRAP,style);
|
||||
style.colour = sf::Color::Black;
|
||||
for(i = 0; i < 8; i++) {
|
||||
i_num = i + item_offset;
|
||||
if(spec_item_array[i_num] >= 0) {
|
||||
// 2nd condition above is quite kludgy, in case it gets here with array all 0's
|
||||
win_draw_string(item_stats_gworld,item_buttons[i][0],univ.scenario.quests[spec_item_array[i_num]].name,eTextMode::WRAP,style);
|
||||
|
||||
place_item_button(3,i,4,0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default: // on an items page
|
||||
@@ -517,8 +529,15 @@ void set_stat_window(short new_stat) {
|
||||
item_sbar->setMaximum(array_pos);
|
||||
break;
|
||||
case ITEM_WIN_QUESTS:
|
||||
item_sbar->setMaximum(2);
|
||||
item_sbar->setPageSize(2);
|
||||
for(i = 0; i < 60; i++)
|
||||
spec_item_array[i] = -1;
|
||||
for(i = 0; i < 50; i++)
|
||||
if(univ.party.quest_status[i] == eQuestStatus::STARTED) {
|
||||
spec_item_array[array_pos] = i;
|
||||
array_pos++;
|
||||
}
|
||||
array_pos = max(0,array_pos - 8);
|
||||
item_sbar->setMaximum(array_pos);
|
||||
break;
|
||||
default:
|
||||
item_sbar->setMaximum(16);
|
||||
|
||||
@@ -399,6 +399,7 @@ cTownperson::cTownperson() {
|
||||
monster_time = 0;
|
||||
personality = -1;
|
||||
special_on_kill = -1;
|
||||
special_on_talk = -1;
|
||||
}
|
||||
|
||||
cTownperson::cTownperson(location loc, mon_num_t num, const cMonster& monst) : cTownperson() {
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
short spec1, spec2;
|
||||
short spec_enc_code, time_code;
|
||||
short monster_time, personality;
|
||||
short special_on_kill;
|
||||
short special_on_kill, special_on_talk;
|
||||
pic_num_t facial_pic;
|
||||
|
||||
void append(legacy::creature_start_type old);
|
||||
|
||||
@@ -552,6 +552,8 @@ void cParty::writeTo(std::ostream& file) const {
|
||||
file << "SCENARIO " << scen_name << '\n';
|
||||
file << "WON " << scen_won << '\n';
|
||||
file << "PLAYED " << scen_played << '\n';
|
||||
for(auto p : quest_status)
|
||||
file << "QUEST " << p.first << ' ' << p.second << ' ' << quest_start.at(p.first) << ' ' << quest_source.at(p.first) << '\n';
|
||||
for(auto iter = campaign_flags.begin(); iter != campaign_flags.end(); iter++){
|
||||
std::string campaign_id = maybe_quote_string(iter->first);
|
||||
// Okay, we have the campaign ID in a state such that reading it back in will restore the original ID.
|
||||
@@ -586,6 +588,13 @@ void cParty::writeTo(std::ostream& file) const {
|
||||
file << '\f';
|
||||
}
|
||||
file << '\f';
|
||||
for(int i = 0; i < job_banks.size(); i++) {
|
||||
file << "JOBBANK " << i << ' ' << job_banks[i].anger << '\n';
|
||||
if(!job_banks[i].inited) continue;
|
||||
for(int j = 0; j < 6; j++)
|
||||
file << "JOB " << j << ' ' << job_banks[i].jobs[j] << '\n';
|
||||
}
|
||||
file << '\f';
|
||||
for(int i = 0; i < 10; i++)
|
||||
if(out_c[i].exists){
|
||||
file << "ENCOUNTER " << i << "\n";
|
||||
@@ -756,6 +765,10 @@ void cParty::readFrom(std::istream& file){
|
||||
int i;
|
||||
sin >> i;
|
||||
sin >> m_killed[i];
|
||||
} else if(cur == "QUEST") {
|
||||
int i;
|
||||
sin >> i;
|
||||
sin >> quest_status[i] >> quest_start[i] >> quest_source[i];
|
||||
}else if(cur == "KILLS")
|
||||
sin >> total_m_killed;
|
||||
else if(cur == "DAMAGE")
|
||||
@@ -872,6 +885,27 @@ void cParty::readFrom(std::istream& file){
|
||||
bin >> std::ws;
|
||||
getline(bin, note.the_str1);
|
||||
getline(bin, note.the_str2);
|
||||
} else if(cur == "JOB_BANK") {
|
||||
int i;
|
||||
bin >> i;
|
||||
if(i < 0) continue;
|
||||
if(i >= job_banks.size())
|
||||
job_banks.resize(i + 1);
|
||||
bin >> job_banks[i].anger;
|
||||
job_banks[i].inited = false;
|
||||
while(bin) {
|
||||
getline(bin, cur);
|
||||
std::istringstream sin(cur);
|
||||
sin >> cur;
|
||||
if(cur == "JOB") {
|
||||
job_banks[i].inited = true;
|
||||
int j;
|
||||
sin >> j;
|
||||
if(j < 0 || j >= 6)
|
||||
continue;
|
||||
sin >> job_banks[i].jobs[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
bin.clear();
|
||||
}
|
||||
@@ -1068,3 +1102,32 @@ std::ostream& operator<<(std::ostream& out, ePartyStatus type) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::istream& operator>>(std::istream& in, eQuestStatus& type) {
|
||||
std::string name;
|
||||
in >> name;
|
||||
if(name == "avail") type = eQuestStatus::AVAILABLE;
|
||||
else if(name == "start") type = eQuestStatus::STARTED;
|
||||
else if(name == "done") type = eQuestStatus::COMPLETED;
|
||||
else if(name == "fail") type = eQuestStatus::FAILED;
|
||||
else in.setstate(std::ios_base::failbit);
|
||||
return in;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, eQuestStatus type) {
|
||||
switch(type) {
|
||||
case eQuestStatus::AVAILABLE:
|
||||
out << "avail";
|
||||
break;
|
||||
case eQuestStatus::STARTED:
|
||||
out << "start";
|
||||
break;
|
||||
case eQuestStatus::COMPLETED:
|
||||
out << "done";
|
||||
break;
|
||||
case eQuestStatus::FAILED:
|
||||
out << "fail";
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,14 @@ struct campaign_flag_type{
|
||||
unsigned char idx[25][25];
|
||||
};
|
||||
|
||||
struct job_bank_t {
|
||||
std::array<int,6> jobs;
|
||||
int anger = 0;
|
||||
bool inited = false;
|
||||
};
|
||||
|
||||
enum class eQuestStatus {AVAILABLE, STARTED, COMPLETED, FAILED};
|
||||
|
||||
class cUniverse;
|
||||
|
||||
class cParty : public iLiving {
|
||||
@@ -85,6 +93,7 @@ public:
|
||||
short in_horse;
|
||||
cOutdoors::cCreature out_c[10];
|
||||
std::array<std::array<cItem,10>,5> magic_store_items;
|
||||
std::vector<job_bank_t> job_banks;
|
||||
mon_num_t imprisoned_monst[4]; // Soul Crystal
|
||||
char m_noted[256]; // has the monster been scried?
|
||||
char m_seen[256]; // has the monster ever been seen? (this used to have the above meaning)
|
||||
@@ -92,6 +101,10 @@ public:
|
||||
std::vector<cEncNote> special_notes;
|
||||
std::vector<cConvers> talk_save;
|
||||
std::map<ePartyStatus,short> status;
|
||||
// Quest stuff
|
||||
std::map<int, eQuestStatus> quest_status;
|
||||
std::map<int, int> quest_start; // the day the quest was started; used for quests with relative deadlines
|
||||
std::map<int, int> quest_source; // if gotten from a job board, this is the number of the job board; otherwise -1
|
||||
location left_at;
|
||||
size_t left_in;
|
||||
eDirection direction;
|
||||
@@ -204,5 +217,7 @@ std::istream& operator>>(std::istream& in, eEncNoteType& type);
|
||||
std::ostream& operator<<(std::ostream& out, eEncNoteType type);
|
||||
std::istream& operator>>(std::istream& in, ePartyStatus& type);
|
||||
std::ostream& operator<<(std::ostream& out, ePartyStatus type);
|
||||
std::istream& operator>>(std::istream& in, eQuestStatus& type);
|
||||
std::ostream& operator<<(std::ostream& out, eQuestStatus type);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,6 +34,17 @@ struct scenario_header_flags {
|
||||
unsigned char ver[3],min_run_ver,prog_make_ver[3],num_towns;
|
||||
};
|
||||
|
||||
class cQuest {
|
||||
public:
|
||||
short flags = 0; // 0 - absolute deadline, 1 - relative to when quest started, +10 - start quest when scenario starts
|
||||
short deadline = -1;
|
||||
short event = -1; // if this event occurs before the deadline, then the deadline is waived
|
||||
short xp = 0, gold = 0; // automatically award this much XP and gold to the party when the quest is marked complete
|
||||
short bank1 = -1, bank2 = -1; // which job bank(s) this quest is in; -1 for none
|
||||
std::string name;
|
||||
std::string descr;
|
||||
};
|
||||
|
||||
class cScenario {
|
||||
public:
|
||||
class cItemStorage {
|
||||
@@ -57,7 +68,8 @@ public:
|
||||
short flag_to_add_to_town[10][2];
|
||||
rectangle store_item_rects[3];
|
||||
short store_item_towns[3];
|
||||
cSpecItem special_items[50];
|
||||
std::array<cSpecItem,50> special_items;
|
||||
std::vector<cQuest> quests;
|
||||
short rating,uses_custom_graphics;
|
||||
std::vector<ePicType> custom_graphics;
|
||||
std::array<cMonster,256> scen_monsters;
|
||||
|
||||
@@ -507,7 +507,7 @@ enum class eSpecCtx {
|
||||
TOWN_LOOK = 4,
|
||||
ENTER_TOWN = 5,
|
||||
LEAVE_TOWN = 6,
|
||||
TALK = 7,
|
||||
TALK = 7, // Special called during conversation
|
||||
USE_SPEC_ITEM = 8,
|
||||
TOWN_TIMER = 9,
|
||||
SCEN_TIMER = 10,
|
||||
@@ -525,6 +525,7 @@ enum class eSpecCtx {
|
||||
ATTACKING_RANGE = 22,
|
||||
ATTACKED_MELEE = 23,
|
||||
ATTACKED_RANGE = 24,
|
||||
HAIL = 25, // Special called by trying to initiate conversation
|
||||
};
|
||||
|
||||
enum class eSpecType {
|
||||
@@ -574,6 +575,7 @@ enum class eSpecType {
|
||||
APPEND_TER = 42,
|
||||
PAUSE = 43,
|
||||
START_TALK = 44,
|
||||
UPDATE_QUEST = 45,
|
||||
ONCE_GIVE_ITEM = 50,
|
||||
ONCE_GIVE_SPEC_ITEM = 51,
|
||||
ONCE_NULL = 52,
|
||||
@@ -644,6 +646,7 @@ enum class eSpecType {
|
||||
IF_NUM_RESPONSE = 157,
|
||||
IF_IN_BOAT = 158,
|
||||
IF_ON_HORSE = 159,
|
||||
IF_QUEST = 160,
|
||||
MAKE_TOWN_HOSTILE = 170,
|
||||
TOWN_RUN_MISSILE = 171,
|
||||
TOWN_MONST_ATTACK = 172,
|
||||
@@ -699,13 +702,13 @@ enum class eSpecCat {
|
||||
|
||||
inline eSpecCat getNodeCategory(eSpecType node) {
|
||||
int code = (int) node;
|
||||
if(code >= 0 && code <= 44)
|
||||
if(code >= 0 && code <= 45)
|
||||
return eSpecCat::GENERAL;
|
||||
if(code >= 50 && code <= 63)
|
||||
return eSpecCat::ONCE;
|
||||
if(code >= 80 && code <= 105)
|
||||
return eSpecCat::AFFECT;
|
||||
if(code >= 130 && code <= 159)
|
||||
if(code >= 130 && code <= 160)
|
||||
return eSpecCat::IF_THEN;
|
||||
if(code >= 170 && code <= 199)
|
||||
return eSpecCat::TOWN;
|
||||
@@ -726,6 +729,7 @@ enum class eTalkNode {
|
||||
DEP_ON_TOWN = 6,
|
||||
SHOP = 7,
|
||||
TRAINING = 8,
|
||||
JOB_BANK = 9,
|
||||
SELL_WEAPONS = 13,
|
||||
SELL_ARMOR = 14,
|
||||
SELL_ITEMS = 15,
|
||||
@@ -736,6 +740,7 @@ enum class eTalkNode {
|
||||
BUY_SHIP = 20,
|
||||
BUY_HORSE = 21,
|
||||
BUY_SPEC_ITEM = 22,
|
||||
RECEIVE_QUEST = 23,
|
||||
BUY_TOWN_LOC = 24,
|
||||
END_FORCE = 25,
|
||||
END_FIGHT = 26,
|
||||
|
||||
@@ -80,7 +80,7 @@ void cButton::draw(){
|
||||
style.lineHeight = 8;
|
||||
eTextMode textMode = eTextMode::CENTRE;
|
||||
if(type == BTN_TINY) {
|
||||
textMode = eTextMode::LEFT_TOP;
|
||||
textMode = wrapLabel ? eTextMode::WRAP : eTextMode::LEFT_TOP;
|
||||
to_rect.left += 18;
|
||||
style.colour = textClr;
|
||||
} else if(type == BTN_PUSH) {
|
||||
@@ -236,12 +236,14 @@ bool cLed::triggerClickHandler(cDialog& me, std::string id, eKeyMod mods){
|
||||
void cLed::setFormat(eFormat prop, short val) throw(xUnsupportedProp){
|
||||
if(prop == TXT_FONT) textFont = (eFont) val;
|
||||
else if(prop == TXT_SIZE) textSize = val;
|
||||
else if(prop == TXT_WRAP) wrapLabel = val;
|
||||
else throw xUnsupportedProp(prop);
|
||||
}
|
||||
|
||||
short cLed::getFormat(eFormat prop) throw(xUnsupportedProp){
|
||||
if(prop == TXT_FONT) return textFont;
|
||||
else if(prop == TXT_SIZE) return textSize;
|
||||
else if(prop == TXT_WRAP) return wrapLabel;
|
||||
else throw xUnsupportedProp(prop);
|
||||
}
|
||||
|
||||
@@ -263,7 +265,7 @@ void cLed::draw(){
|
||||
style.colour = textClr;
|
||||
to_rect.right = frame.right;
|
||||
to_rect.left = frame.left + 18; // Possibly could be 20
|
||||
win_draw_string(*inWindow,to_rect,lbl,eTextMode::LEFT_TOP,style);
|
||||
win_draw_string(*inWindow,to_rect,lbl,wrapLabel ? eTextMode::WRAP : eTextMode::LEFT_TOP,style);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,11 +85,12 @@ protected:
|
||||
/// @param t The type of control. Should be either CTRL_LED or CTRL_BTN.
|
||||
cButton(cDialog* parent,eControlType t);
|
||||
private:
|
||||
bool wrapLabel;
|
||||
bool labelWithKey;
|
||||
std::string fromList;
|
||||
static rectangle btnRects[13][2];
|
||||
protected:
|
||||
/// Determines whether the button's label should be word wrapped.
|
||||
bool wrapLabel;
|
||||
/// The button's text colour; only used by LED and tiny buttons
|
||||
sf::Color textClr;
|
||||
/// The index in buttons of the texture for each button type.
|
||||
|
||||
@@ -612,6 +612,12 @@ template<> pair<string,cLed*> cDialog::parse(Element& who /*LED*/){
|
||||
throw xBadVal("led",name,val,attr->Row(),attr->Column(),fname);
|
||||
}
|
||||
p.second->setColour(clr);
|
||||
} else if(name == "wrap") {
|
||||
std::string val;
|
||||
attr->GetValue(&val);
|
||||
if(val == "true")
|
||||
p.second->setFormat(TXT_WRAP, true);
|
||||
else p.second->setFormat(TXT_WRAP, false);
|
||||
}else if(name == "top"){
|
||||
attr->GetValue(&frame.top), foundTop = true;
|
||||
}else if(name == "left"){
|
||||
|
||||
@@ -221,7 +221,10 @@ bool handle_action(location the_point,sf::Event /*event*/) {
|
||||
case 7:
|
||||
start_special_item_editing();
|
||||
break;
|
||||
case 11: // pick out
|
||||
case 8:
|
||||
start_quest_editing();
|
||||
break;
|
||||
case 12: // pick out
|
||||
if(change_made) {
|
||||
if(!save_check("save-section-confirm"))
|
||||
break;
|
||||
@@ -233,12 +236,12 @@ bool handle_action(location the_point,sf::Event /*event*/) {
|
||||
set_up_main_screen();
|
||||
}
|
||||
break;
|
||||
case 12: // edit outdoors
|
||||
case 13: // edit outdoors
|
||||
start_out_edit();
|
||||
mouse_button_held = false;
|
||||
return false;
|
||||
break;
|
||||
case 16: // pick town
|
||||
case 17: // pick town
|
||||
if(change_made) {
|
||||
if(!save_check("save-section-confirm"))
|
||||
break;
|
||||
@@ -250,12 +253,12 @@ bool handle_action(location the_point,sf::Event /*event*/) {
|
||||
set_up_main_screen();
|
||||
}
|
||||
break;
|
||||
case 17: // edit town
|
||||
case 18: // edit town
|
||||
start_town_edit();
|
||||
mouse_button_held = false;
|
||||
return false;
|
||||
break;
|
||||
case 18:
|
||||
case 19:
|
||||
start_dialogue_editing(0);
|
||||
break;
|
||||
|
||||
@@ -382,6 +385,17 @@ bool handle_action(location the_point,sf::Event /*event*/) {
|
||||
else edit_text_str(j,5);
|
||||
start_string_editing(5,1);
|
||||
break;
|
||||
case 16:
|
||||
if(option_hit) {
|
||||
if(j == scenario.quests.size() - 1)
|
||||
scenario.quests.pop_back();
|
||||
else {
|
||||
scenario.quests[j] = cQuest();
|
||||
scenario.quests[j].name = "Unused Quest";
|
||||
}
|
||||
} else edit_quest(j);
|
||||
start_quest_editing();
|
||||
break;
|
||||
}
|
||||
mouse_button_held = false;
|
||||
}
|
||||
@@ -2925,6 +2939,7 @@ void set_up_main_screen() {
|
||||
set_lb(-1,11,"Create New Town",0);
|
||||
set_lb(-1,11,"Edit Scenario Text",0);
|
||||
set_lb(-1,11,"Edit Special Items",0);
|
||||
set_lb(-1,11,"Edit Quests",0);
|
||||
set_lb(-1,1,"",0);
|
||||
set_lb(-1,1,"Outdoors Options",0);
|
||||
sprintf((char *) message," Section x = %d, y = %d",(short) cur_out.x,(short) cur_out.y);
|
||||
@@ -3105,6 +3120,27 @@ void start_special_item_editing() {
|
||||
set_lb(NLS - 3,0,"",1);
|
||||
}
|
||||
|
||||
void start_quest_editing() {
|
||||
int num_options = scenario.quests.size() + 1;
|
||||
if(overall_mode < MODE_MAIN_SCREEN)
|
||||
set_up_main_screen();
|
||||
overall_mode = MODE_MAIN_SCREEN;
|
||||
right_sbar->show();
|
||||
right_sbar->setPosition(0);
|
||||
reset_rb();
|
||||
right_sbar->setMaximum(num_options - NRSONPAGE);
|
||||
for(int i = 0; i < num_options; i++) {
|
||||
std::string title;
|
||||
if(i == scenario.quests.size())
|
||||
title = "Create New Quest";
|
||||
else title = scenario.quests[i].name;
|
||||
title = std::to_string(i) + " - " + title;
|
||||
set_rb(i, 16000 + i, title.c_str(), 0);
|
||||
}
|
||||
redraw_screen();
|
||||
set_lb(NLS - 3, 1, "Command-click or right-click to delete", 1);
|
||||
}
|
||||
|
||||
extern size_t num_strs(short mode); // defined in scen.keydlgs.cpp
|
||||
|
||||
// mode 0 - scen 1 - out 2 - town 3 - journal
|
||||
|
||||
@@ -46,6 +46,7 @@ void start_terrain_editing();
|
||||
void start_monster_editing(short just_redo_text);
|
||||
void start_item_editing(short just_redo_text);
|
||||
void start_special_item_editing();
|
||||
void start_quest_editing();
|
||||
void start_string_editing(short mode,short just_redo_text);
|
||||
void start_special_editing(short mode,short just_redo_text);
|
||||
void town_entry(location spot_hit);
|
||||
|
||||
@@ -23,6 +23,7 @@ bool left_buttons_active = 1,right_buttons_active = 0;
|
||||
extern short left_button_status[NLS]; // 0 - clear, 1 - text, 2 - title text, 3 - tabbed text, +10 - button
|
||||
extern short right_button_status[NRS];
|
||||
extern std::shared_ptr<cScrollbar> right_sbar;
|
||||
// Button status:
|
||||
// 0 - clear
|
||||
// 1000 + x - terrain type x
|
||||
// 2000 + x - monster type x
|
||||
@@ -34,9 +35,12 @@ extern std::shared_ptr<cScrollbar> right_sbar;
|
||||
// 8000 + x - out string x
|
||||
// 9000 + x - town string x
|
||||
// 10000 + x - scen. special item x
|
||||
// 11000 + x - charter intro c
|
||||
// 11000 + x - journal entry x
|
||||
// 12000 + x - dialogue node x
|
||||
// 13000 + x - basic dialogue node x
|
||||
// 14000 + x - outdoor sign x
|
||||
// 15000 + x - town sign x
|
||||
// 16000 + x - quest x
|
||||
|
||||
|
||||
// for following, lb stands for left button(s)
|
||||
|
||||
@@ -1989,6 +1989,105 @@ void edit_spec_item(short which_item) {
|
||||
item_dlg.run();
|
||||
}
|
||||
|
||||
static void put_quest_in_dlog(cDialog& me, const cQuest& quest, size_t which_quest) {
|
||||
me["num"].setText(std::to_string(which_quest) + " of " + std::to_string(scenario.quests.size()));
|
||||
me["name"].setText(quest.name);
|
||||
me["descr"].setText(quest.descr);
|
||||
me["chop"].setTextToNum(quest.deadline);
|
||||
me["evt"].setTextToNum(quest.event);
|
||||
me["xp"].setTextToNum(quest.xp);
|
||||
me["gold"].setTextToNum(quest.gold);
|
||||
me["bank1"].setTextToNum(quest.bank1);
|
||||
me["bank2"].setTextToNum(quest.bank2);
|
||||
|
||||
dynamic_cast<cLed&>(me["rel"]).setState(quest.flags % 10 == 1 ? led_red : led_off);
|
||||
dynamic_cast<cLed&>(me["start"]).setState(quest.flags >= 10 ? led_red : led_off);
|
||||
dynamic_cast<cLed&>(me["inbank"]).setState(quest.bank1 >= 0 || quest.bank2 >= 0 ? led_red : led_off);
|
||||
if(quest.bank1 < 0 && quest.bank2 < 0) {
|
||||
me["bank1"].hide();
|
||||
me["bank2"].hide();
|
||||
} else {
|
||||
me["bank1"].show();
|
||||
me["bank2"].show();
|
||||
}
|
||||
}
|
||||
|
||||
static bool save_quest_from_dlog(cDialog& me, cQuest& quest, size_t which_quest, bool close) {
|
||||
if(!me.toast(true)) return false;
|
||||
|
||||
quest.name = me["name"].getText();
|
||||
quest.descr = me["descr"].getText();
|
||||
quest.deadline = me["chop"].getTextAsNum();
|
||||
quest.event = me["evt"].getTextAsNum();
|
||||
quest.xp = me["xp"].getTextAsNum();
|
||||
quest.gold = me["gold"].getTextAsNum();
|
||||
|
||||
quest.flags = dynamic_cast<cLed&>(me["rel"]).getState() == led_red;
|
||||
if(dynamic_cast<cLed&>(me["start"]).getState() == led_red)
|
||||
quest.flags += 10;
|
||||
if(dynamic_cast<cLed&>(me["inbank"]).getState() == led_red) {
|
||||
quest.bank1 = me["bank1"].getTextAsNum();
|
||||
quest.bank2 = me["bank2"].getTextAsNum();
|
||||
} else quest.bank1 = quest.bank2 = -1;
|
||||
|
||||
scenario.quests[which_quest] = quest;
|
||||
if(!close) me.untoast();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool change_quest_dlog_page(cDialog& me, std::string dir, cQuest& quest, size_t& which_quest) {
|
||||
if(!save_quest_from_dlog(me, quest, which_quest, false))
|
||||
return true;
|
||||
|
||||
if(dir == "left") {
|
||||
if(which_quest == 0)
|
||||
which_quest = scenario.quests.size();
|
||||
which_quest--;
|
||||
} else if(dir == "right") {
|
||||
which_quest++;
|
||||
if(which_quest == scenario.quests.size())
|
||||
which_quest = 0;
|
||||
}
|
||||
|
||||
quest = scenario.quests[which_quest];
|
||||
put_quest_in_dlog(me, quest, which_quest);
|
||||
return true;
|
||||
}
|
||||
|
||||
void edit_quest(size_t which_quest) {
|
||||
using namespace std::placeholders;
|
||||
if(which_quest == scenario.quests.size()) {
|
||||
scenario.quests.resize(which_quest + 1);
|
||||
scenario.quests[which_quest].name = "New Quest";
|
||||
}
|
||||
cQuest quest = scenario.quests[which_quest];
|
||||
|
||||
cDialog quest_dlg("edit-quest");
|
||||
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["inbank"].attachFocusHandler([](cDialog& me, std::string, bool losing) -> bool {
|
||||
if(losing) {
|
||||
me["bank1"].hide();
|
||||
me["bank2"].hide();
|
||||
} else {
|
||||
me["bank1"].show();
|
||||
me["bank2"].show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// TODO: Some focus handlers
|
||||
|
||||
if(scenario.quests.size() == 1) {
|
||||
quest_dlg["left"].hide();
|
||||
quest_dlg["right"].hide();
|
||||
} else {
|
||||
quest_dlg.attachClickHandlers(std::bind(change_quest_dlog_page, _1, _2, std::ref(quest), std::ref(which_quest)), {"left", "right"});
|
||||
}
|
||||
|
||||
put_quest_in_dlog(quest_dlg, quest, which_quest);
|
||||
quest_dlg.run();
|
||||
}
|
||||
|
||||
static void put_save_rects_in_dlog(cDialog& me) {
|
||||
short i;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ cMonster edit_monst_abil(cMonster starting_record,short which_monst,cDialog& par
|
||||
short edit_item_type(short which_item);
|
||||
cItem edit_item_abil(cItem starting_record,short which_item,cDialog& parent);
|
||||
void edit_spec_item(short which_item);
|
||||
void edit_quest(size_t which_quest);
|
||||
void edit_save_rects();
|
||||
void edit_horses();
|
||||
void edit_add_town();
|
||||
|
||||
@@ -189,6 +189,34 @@ static void writeScenarioToXml(ticpp::Printer&& data) {
|
||||
data.CloseElement("item");
|
||||
}
|
||||
data.CloseElement("specials");
|
||||
for(size_t i = 0; i < scenario.quests.size(); i++) {
|
||||
cQuest& quest = scenario.quests[i];
|
||||
data.OpenElement("quest");
|
||||
data.PushAttribute("start-with", boolstr(quest.flags / 10));
|
||||
if(quest.deadline >= 0) {
|
||||
data.OpenElement("deadline");
|
||||
data.PushAttribute("relative", boolstr(quest.flags % 10));
|
||||
if(quest.event >= 0)
|
||||
data.PushAttribute("waive-if", quest.event);
|
||||
data.PushText(quest.deadline);
|
||||
data.CloseElement("deadline");
|
||||
}
|
||||
if(quest.xp > 0 || quest.gold > 0) {
|
||||
data.OpenElement("reward");
|
||||
if(quest.xp > 0)
|
||||
data.PushAttribute("xp", quest.xp);
|
||||
if(quest.gold > 0)
|
||||
data.PushAttribute("gold", quest.gold);
|
||||
data.CloseElement("reward");
|
||||
}
|
||||
if(quest.bank1 >= 0)
|
||||
data.PushElement("bank", quest.bank1);
|
||||
if(quest.bank2 >= 0)
|
||||
data.PushElement("bank", quest.bank2);
|
||||
data.PushElement("name", quest.name);
|
||||
data.PushElement("description", quest.descr);
|
||||
data.CloseElement("quest");
|
||||
}
|
||||
for(int i = 0; i < 20; i++) {
|
||||
if(scenario.scenario_timer_times[i] > 0) {
|
||||
data.OpenElement("timer");
|
||||
|
||||
Reference in New Issue
Block a user