diff --git a/proj/vs2013/Common/Common.vcxproj b/proj/vs2013/Common/Common.vcxproj index 115b9c136..1522c3ec1 100644 --- a/proj/vs2013/Common/Common.vcxproj +++ b/proj/vs2013/Common/Common.vcxproj @@ -247,6 +247,13 @@ + + + + + + + diff --git a/proj/vs2013/Common/Common.vcxproj.filters b/proj/vs2013/Common/Common.vcxproj.filters index 1dbe77c9e..fd3da8467 100644 --- a/proj/vs2013/Common/Common.vcxproj.filters +++ b/proj/vs2013/Common/Common.vcxproj.filters @@ -647,6 +647,27 @@ Scenario + + Scenario + + + Scenario + + + Scenario + + + Scenario + + + Scenario + + + Scenario + + + Scenario + Scenario diff --git a/proj/vs2017/Common/Common.vcxproj b/proj/vs2017/Common/Common.vcxproj index 58d25c2b8..1acbc41d5 100644 --- a/proj/vs2017/Common/Common.vcxproj +++ b/proj/vs2017/Common/Common.vcxproj @@ -403,6 +403,13 @@ + + + + + + + diff --git a/proj/vs2017/Common/Common.vcxproj.filters b/proj/vs2017/Common/Common.vcxproj.filters index dd0429385..17b2b7fc6 100644 --- a/proj/vs2017/Common/Common.vcxproj.filters +++ b/proj/vs2017/Common/Common.vcxproj.filters @@ -668,6 +668,27 @@ Scenario + + Scenario + + + Scenario + + + Scenario + + + Scenario + + + Scenario + + + Scenario + + + Scenario + Scenario diff --git a/proj/xc12/BoE.xcodeproj/project.pbxproj b/proj/xc12/BoE.xcodeproj/project.pbxproj index dc5ae8c0f..f42563210 100755 --- a/proj/xc12/BoE.xcodeproj/project.pbxproj +++ b/proj/xc12/BoE.xcodeproj/project.pbxproj @@ -157,6 +157,13 @@ 919B13A21BBCDF14009905A4 /* monst_legacy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919B13A11BBCDE18009905A4 /* monst_legacy.cpp */; }; 919B13A41BBD8854009905A4 /* item_legacy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919B13A31BBD8849009905A4 /* item_legacy.cpp */; }; 919B13A61BBDE986009905A4 /* spec_legacy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919B13A51BBDE985009905A4 /* spec_legacy.cpp */; }; + 919BE86B2D658BC6000C64C6 /* special-general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919BE86A2D658BBC000C64C6 /* special-general.cpp */; }; + 919BE8722D66CD1C000C64C6 /* special-oneshot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919BE8712D66CD1C000C64C6 /* special-oneshot.cpp */; }; + 919BE88D2D66F6E3000C64C6 /* special-affect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919BE88C2D66F6E2000C64C6 /* special-affect.cpp */; }; + 919BE8942D676567000C64C6 /* special-condition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919BE8932D676567000C64C6 /* special-condition.cpp */; }; + 919BE8A02D676DDE000C64C6 /* special-town.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919BE89F2D676DDE000C64C6 /* special-town.cpp */; }; + 919BE8AC2D677699000C64C6 /* special-rect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919BE8AB2D677699000C64C6 /* special-rect.cpp */; }; + 919BE8B32D6776A8000C64C6 /* special-outdoor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919BE8B22D6776A8000C64C6 /* special-outdoor.cpp */; }; 919CC2481B3772F300273FDA /* population.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91AC620A0FA2853700EEAE67 /* population.cpp */; }; 919CC2491B3772FB00273FDA /* creature.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914698FE1A747C4500F20F5E /* creature.cpp */; }; 919CC24B1B37730300273FDA /* item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91279D3D0F9D1D6A007B0D52 /* item.cpp */; }; @@ -766,6 +773,13 @@ 919B13A51BBDE985009905A4 /* spec_legacy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = spec_legacy.cpp; sourceTree = ""; }; 919B13A71BBE297B009905A4 /* scrollpane.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = scrollpane.hpp; sourceTree = ""; }; 919B13A81BBE2B54009905A4 /* scrollpane.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = scrollpane.cpp; sourceTree = ""; }; + 919BE86A2D658BBC000C64C6 /* special-general.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "special-general.cpp"; sourceTree = ""; }; + 919BE8712D66CD1C000C64C6 /* special-oneshot.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "special-oneshot.cpp"; sourceTree = ""; }; + 919BE88C2D66F6E2000C64C6 /* special-affect.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "special-affect.cpp"; sourceTree = ""; wrapsLines = 1; }; + 919BE8932D676567000C64C6 /* special-condition.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "special-condition.cpp"; sourceTree = ""; }; + 919BE89F2D676DDE000C64C6 /* special-town.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "special-town.cpp"; sourceTree = ""; }; + 919BE8AB2D677699000C64C6 /* special-rect.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "special-rect.cpp"; sourceTree = ""; }; + 919BE8B22D6776A8000C64C6 /* special-outdoor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "special-outdoor.cpp"; sourceTree = ""; }; 919DDBFA19006CC9003E7FED /* libboost_filesystem-mt.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libboost_filesystem-mt.dylib"; path = "/opt/local/libexec/boost/1.76/lib/libboost_filesystem-mt.dylib"; sourceTree = ""; }; 919DDBFB19006CC9003E7FED /* libboost_system-mt.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libboost_system-mt.dylib"; path = "/opt/local/libexec/boost/1.76/lib/libboost_system-mt.dylib"; sourceTree = ""; }; 919DDC091900750D003E7FED /* freetype.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = freetype.framework; path = /Library/Frameworks/freetype.framework; sourceTree = ""; }; @@ -1248,6 +1262,13 @@ 91279CC10F9D19DA007B0D52 /* monster.cpp */, 91E5C79D0F9F60FA00C21460 /* outdoors.cpp */, 91279C580F9D1253007B0D52 /* scenario.cpp */, + 919BE88C2D66F6E2000C64C6 /* special-affect.cpp */, + 919BE8932D676567000C64C6 /* special-condition.cpp */, + 919BE86A2D658BBC000C64C6 /* special-general.cpp */, + 919BE8712D66CD1C000C64C6 /* special-oneshot.cpp */, + 919BE8B22D6776A8000C64C6 /* special-outdoor.cpp */, + 919BE8AB2D677699000C64C6 /* special-rect.cpp */, + 919BE89F2D676DDE000C64C6 /* special-town.cpp */, 91FDB5791A4E774E00DE5983 /* shop.cpp */, 91279CC60F9D1A02007B0D52 /* special.cpp */, 91E5C7B70F9F619D00C21460 /* talking.cpp */, @@ -2068,6 +2089,7 @@ files = ( 919CC24B1B37730300273FDA /* item.cpp in Sources */, 919CC24D1B37730E00273FDA /* location.cpp in Sources */, + 919BE8722D66CD1C000C64C6 /* special-oneshot.cpp in Sources */, 919CC24E1B37731400273FDA /* monster.cpp in Sources */, 919CC24F1B37731800273FDA /* outdoors.cpp in Sources */, 919CC2531B37732C00273FDA /* scenario.cpp in Sources */, @@ -2079,6 +2101,7 @@ 919CC25A1B37735100273FDA /* terrain.cpp in Sources */, 919CC25C1B37735C00273FDA /* town.cpp in Sources */, 919CC25F1B37736E00273FDA /* vehicle.cpp in Sources */, + 919BE88D2D66F6E3000C64C6 /* special-affect.cpp in Sources */, 919CC2601B37737200273FDA /* estreams.cpp in Sources */, 919CC2611B37738100273FDA /* gzstream.cpp in Sources */, 919CC2621B37738A00273FDA /* ticpp.cpp in Sources */, @@ -2099,6 +2122,7 @@ 415EEEB02D5534A500B47408 /* prefs.cpp in Sources */, 919CC2701B3773EC00273FDA /* scrollbar.cpp in Sources */, 919CC2711B3773F300273FDA /* cursors.mac.mm in Sources */, + 919BE86B2D658BC6000C64C6 /* special-general.cpp in Sources */, 919CC2721B3773F800273FDA /* fileio.cpp in Sources */, 919CC2741B37740200273FDA /* fileio_scen.cpp in Sources */, 919CC2751B37740A00273FDA /* render_image.cpp in Sources */, @@ -2108,6 +2132,7 @@ 919CC2791B37742200273FDA /* prefs.mac.mm in Sources */, 919CC27A1B37742800273FDA /* qdpict.mac.cpp in Sources */, 919CC27B1B37742D00273FDA /* sounds.cpp in Sources */, + 919BE8AC2D677699000C64C6 /* special-rect.cpp in Sources */, 919CC27C1B37743200273FDA /* special_parse.cpp in Sources */, 919CC27D1B37743700273FDA /* tarball.cpp in Sources */, 919CC27E1B37743B00273FDA /* undo.cpp in Sources */, @@ -2116,9 +2141,11 @@ 915473CF2C800AB000EB1C94 /* enchant.cpp in Sources */, 915AF9E81BBF8B5C008AEF49 /* scrollpane.cpp in Sources */, 91E128E71BC1E6DD00C8BE1D /* basicbtns.cpp in Sources */, + 919BE8942D676567000C64C6 /* special-condition.cpp in Sources */, 91E128ED1BC2076B00C8BE1D /* 3choice.cpp in Sources */, 91E128EE1BC2076B00C8BE1D /* choicedlog.cpp in Sources */, 9143044A2970EDC1003A3967 /* keymods.cpp in Sources */, + 919BE8B32D6776A8000C64C6 /* special-outdoor.cpp in Sources */, 91E128EF1BC2076B00C8BE1D /* pictchoice.cpp in Sources */, 91E128F01BC2076B00C8BE1D /* strchoice.cpp in Sources */, 91E128F11BC2076B00C8BE1D /* strdlog.cpp in Sources */, @@ -2128,6 +2155,7 @@ 91A2480E2969CFD200B8D90F /* res_dialog.cpp in Sources */, 91CE24921EA12ABD005BDCE4 /* gfxsheets.cpp in Sources */, 91CE24931EA12AC9005BDCE4 /* tiling.cpp in Sources */, + 919BE8A02D676DDE000C64C6 /* special-town.cpp in Sources */, 91EC1F0423DDFF9D00271891 /* res_cursor.cpp in Sources */, 91EC1F0523DDFF9D00271891 /* res_font.cpp in Sources */, 91EC1F0623DDFF9D00271891 /* res_image.cpp in Sources */, diff --git a/src/scenario/special-affect.cpp b/src/scenario/special-affect.cpp new file mode 100644 index 000000000..6b257638b --- /dev/null +++ b/src/scenario/special-affect.cpp @@ -0,0 +1,93 @@ +// +// special-affect.cpp +// Common +// +// Created by Celtic Minstrel on 2025-02-20. +// + +#include "special.hpp" + +// Note: If adding a new node type below, be sure to adjust the end point here too. +node_category_info_t CAT_AFFECT{eSpecType::SELECT_TARGET, eSpecType::UNSTORE_PC}; + +namespace { + node_properties_t S_SELECT = node_builder_t(eSpecType::SELECT_TARGET) + .msg(); + node_properties_t S_DAMAGE = node_builder_t(eSpecType::DAMAGE) + .msg() + .ex2b(eSpecPicker::DAMAGE_TYPE) + .ex2c(eSpecPicker::SOUND); + node_properties_t S_HEALTH = node_builder_t(eSpecType::AFFECT_HP) + .msg(); + node_properties_t S_MANA = node_builder_t(eSpecType::AFFECT_SP) + .msg(); + node_properties_t S_EXP = node_builder_t(eSpecType::AFFECT_XP) + .msg(); + node_properties_t S_SKILLPT = node_builder_t(eSpecType::AFFECT_SKILL_PTS) + .msg(); + node_properties_t S_KILL = node_builder_t(eSpecType::AFFECT_DEADNESS) + .msg() + .ex1a(STRT_STATUS); + node_properties_t S_STATUS = node_builder_t(eSpecType::AFFECT_STATUS) + .msg() + .ex1c(eSpecPicker::STATUS); + node_properties_t S_TRAIT = node_builder_t(eSpecType::AFFECT_TRAITS) + .msg() + .ex1a(STRT_TRAIT); + node_properties_t S_ACTIONS = node_builder_t(eSpecType::AFFECT_AP) + .msg(); + node_properties_t S_NAME = node_builder_t(eSpecType::AFFECT_NAME) + .msg() + .msg3(eSpecPicker::MSG_SINGLE); + node_properties_t S_LEVEL = node_builder_t(eSpecType::AFFECT_LEVEL) + .msg(); + node_properties_t S_MORALE = node_builder_t(eSpecType::AFFECT_MORALE) + .msg(); + node_properties_t S_CRYSTAL = node_builder_t(eSpecType::AFFECT_SOUL_CRYSTAL) + .msg(); + node_properties_t S_EQUIP = node_builder_t(eSpecType::GIVE_ITEM) + .msg() + .pict(eSpecPicker::NODE) + .ex1a(STRT_ITEM) + .ex1b(STRT_ENCHANT); + node_properties_t S_TARGET = node_builder_t(eSpecType::AFFECT_MONST_TARG) + .msg(); + node_properties_t S_ATTACK = node_builder_t(eSpecType::AFFECT_MONST_ATT) + .msg(); + node_properties_t S_STAT_M = node_builder_t(eSpecType::AFFECT_MONST_STAT) + .msg() + .ex2a(STRT_MONST_STAT); + node_properties_t S_STAT_P = node_builder_t(eSpecType::AFFECT_STAT) + .msg() + .ex2a(STRT_SKILL); + node_properties_t S_MAGE = node_builder_t(eSpecType::AFFECT_MAGE_SPELL) + .msg() + .ex1a(STRT_MAGE); + node_properties_t S_PRIEST = node_builder_t(eSpecType::AFFECT_PRIEST_SPELL) + .msg() + .ex1a(STRT_PRIEST); + node_properties_t S_GOLD = node_builder_t(eSpecType::AFFECT_GOLD) + .msg(); + node_properties_t S_FOOD = node_builder_t(eSpecType::AFFECT_FOOD) + .msg(); + node_properties_t S_ALCHEMY = node_builder_t(eSpecType::AFFECT_ALCHEMY) + .msg() + .ex1a(STRT_ALCHEMY); + node_properties_t S_STATUS_PARTY = node_builder_t(eSpecType::AFFECT_PARTY_STATUS) + .msg() + .ex1c(eSpecPicker::STATUS_PARTY); + node_properties_t S_NEWPC = node_builder_t(eSpecType::CREATE_NEW_PC) + .sdf() + .msg() + .msg3(eSpecPicker::MSG_SINGLE) + .pict(PIC_PC) + .ptyp(eSpecPicker::NODE) + .ex1c(STRT_RACE); + node_properties_t S_STOREPC = node_builder_t(eSpecType::STORE_PC) + .sdf() + .msg(); + node_properties_t S_UNSTOREPC = node_builder_t(eSpecType::UNSTORE_PC) + .sdf() + .msg() + .ex1b(eSpecPicker::NODE); +} diff --git a/src/scenario/special-condition.cpp b/src/scenario/special-condition.cpp new file mode 100644 index 000000000..303636a42 --- /dev/null +++ b/src/scenario/special-condition.cpp @@ -0,0 +1,112 @@ +// +// special-condition.cpp +// Common +// +// Created by Celtic Minstrel on 2025-02-20. +// + +#include "special.hpp" + +// Note: If adding a new node type below, be sure to adjust the end point here too. +node_category_info_t CAT_COND{eSpecType::IF_SDF, eSpecType::IF_QUEST}; + +namespace { + node_properties_t S_SDF = node_builder_t(eSpecType::IF_SDF) + .sdf() + .ex1b(eSpecPicker::NODE) + .ex2b(eSpecPicker::NODE); + node_properties_t S_TOWN = node_builder_t(eSpecType::IF_TOWN_NUM) + .ex1a(STRT_TOWN) + .ex1b(eSpecPicker::NODE); + node_properties_t S_RANDOM = node_builder_t(eSpecType::IF_RANDOM) + .ex1b(eSpecPicker::NODE); + node_properties_t S_SPECITEM = node_builder_t(eSpecType::IF_HAVE_SPECIAL_ITEM) + .ex1a(STRT_SPEC_ITEM) + .ex1b(eSpecPicker::NODE); + node_properties_t S_SDFCMP = node_builder_t(eSpecType::IF_SDF_COMPARE) + .sdf() + .sdf(eSpecField::EX1A, eSpecField::EX1B) + .ex2b(eSpecPicker::NODE); + node_properties_t S_TERRAIN = node_builder_t(eSpecType::IF_TER_TYPE) + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_TER) + .ex2b(eSpecPicker::NODE); + node_properties_t S_ALIVE = node_builder_t(eSpecType::IF_ALIVE) + .ex1a(STRT_STATUS) + .ex1b(eSpecPicker::NODE); + node_properties_t S_GOLD = node_builder_t(eSpecType::IF_HAS_GOLD) + .ex1b(eSpecPicker::NODE); + node_properties_t S_FOOD = node_builder_t(eSpecType::IF_HAS_FOOD) + .ex1b(eSpecPicker::NODE); + node_properties_t S_ITEM_THERE = node_builder_t(eSpecType::IF_ITEM_CLASS_ON_SPACE) + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2b(eSpecPicker::NODE); + node_properties_t S_ITEM_OWNED = node_builder_t(eSpecType::IF_HAVE_ITEM_CLASS) + .ex1b(eSpecPicker::NODE); + node_properties_t S_ITEM_EQUIP = node_builder_t(eSpecType::IF_EQUIP_ITEM_CLASS) + .ex1b(eSpecPicker::NODE); + node_properties_t S_MAGE = node_builder_t(eSpecType::IF_MAGE_SPELL) + .ex1a(STRT_MAGE) + .ex1b(eSpecPicker::NODE); + node_properties_t S_PRIEST = node_builder_t(eSpecType::IF_PRIEST_SPELL) + .ex1a(STRT_PRIEST) + .ex1b(eSpecPicker::NODE); + node_properties_t S_ALCHEMY = node_builder_t(eSpecType::IF_RECIPE) + .ex1a(STRT_ALCHEMY) + .ex1b(eSpecPicker::NODE); + node_properties_t S_STATUS = node_builder_t(eSpecType::IF_STATUS) + .ex1a(eSpecPicker::STATUS) + .ex1b(eSpecPicker::NODE) + .ex2b(STRT_ACCUM) + .ex2c(STRT_CMP); + node_properties_t S_LOOK = node_builder_t(eSpecType::IF_LOOKING) + .ex1c(eSpecPicker::NODE); + node_properties_t S_DAY = node_builder_t(eSpecType::IF_DAY_REACHED) + .ex1b(eSpecPicker::NODE); + node_properties_t S_FIELDS = node_builder_t(eSpecType::IF_FIELDS) + .rect() + .msg1(eSpecPicker::FIELD) + .msg2(eSpecPicker::NODE); + node_properties_t S_PARTY_SIZE = node_builder_t(eSpecType::IF_PARTY_SIZE) + .ex1b(eSpecPicker::NODE); + node_properties_t S_EVENT = node_builder_t(eSpecType::IF_EVENT_OCCURRED) + .ex2b(eSpecPicker::NODE); + node_properties_t S_RACE = node_builder_t(eSpecType::IF_SPECIES) + .ex1a(STRT_RACE) + .ex1b(eSpecPicker::NODE) + .ex2b(STRT_CMP); + node_properties_t S_TRAIT = node_builder_t(eSpecType::IF_TRAIT) + .ex1a(STRT_TRAIT) + .ex1b(eSpecPicker::NODE) + .ex2b(STRT_CMP); + node_properties_t S_STAT = node_builder_t(eSpecType::IF_STATISTIC) + .ex1b(eSpecPicker::NODE) + .ex2a(+STRT_SKILL) + .ex2b(STRT_ACCUM); + node_properties_t S_TEXT = node_builder_t(eSpecType::IF_TEXT_RESPONSE) + .msg1(+eSpecPicker::MSG_SINGLE) + .ex1a(+eSpecPicker::MSG_SINGLE) + .ex1b(eSpecPicker::NODE) + .ex2a(+eSpecPicker::MSG_SINGLE) + .ex2b(eSpecPicker::NODE); + node_properties_t S_SDFEQ = node_builder_t(eSpecType::IF_SDF_EQ) + .sdf() + .ex1b(eSpecPicker::NODE); + node_properties_t S_CONTEXT = node_builder_t(eSpecType::IF_CONTEXT) + .ex1a(STRT_CONTEXT) + .ex1c(eSpecPicker::NODE); + node_properties_t S_NUM = node_builder_t(eSpecType::IF_NUM_RESPONSE) + .msg1(+eSpecPicker::MSG_SINGLE) + .ex1b(STRT_CMP) + .ex1c(eSpecPicker::NODE) + .ex2b(STRT_CMP) + .ex2c(eSpecPicker::NODE); + node_properties_t S_BOAT = node_builder_t(eSpecType::IF_IN_BOAT) + .ex1c(eSpecPicker::NODE); + node_properties_t S_HORSE = node_builder_t(eSpecType::IF_ON_HORSE) + .ex1c(eSpecPicker::NODE); + node_properties_t S_QUEST = node_builder_t(eSpecType::IF_QUEST) + .ex1a(STRT_QUEST) + .ex1b(STRT_QUEST_STATUS) + .ex1c(eSpecPicker::NODE); +} diff --git a/src/scenario/special-general.cpp b/src/scenario/special-general.cpp new file mode 100644 index 000000000..ae94a0bbe --- /dev/null +++ b/src/scenario/special-general.cpp @@ -0,0 +1,139 @@ +// +// special-general.cpp +// BoE +// +// Created by Celtic Minstrel on 2025-02-18. +// + +#include "special.hpp" + +// Note: If adding a new node type below, be sure to adjust the end point here too. +node_category_info_t CAT_GENERAL{eSpecType::NONE, eSpecType::STR_BUF_TO_SIGN}; + +namespace{ + node_properties_t S_NONE = node_builder_t(eSpecType::NONE); + node_properties_t S_SETFLAG = node_builder_t(eSpecType::SET_SDF) + .sdf() + .msg(); + node_properties_t S_INCFLAG = node_builder_t(eSpecType::INC_SDF) + .sdf() + .msg(); + node_properties_t S_MSG = node_builder_t(eSpecType::DISPLAY_MSG) + .msg(); + node_properties_t S_SHOP = node_builder_t(eSpecType::ENTER_SHOP) + .msg1(eSpecPicker::MSG_SINGLE) + .ex1a(STRT_SHOP) + .ex1b(STRT_COST_ADJ) + .jump(eSpecPicker::NONE); + node_properties_t S_MSG_SM = node_builder_t(eSpecType::DISPLAY_SM_MSG) + .msg(); + node_properties_t S_FLIPFLAG = node_builder_t(eSpecType::FLIP_SDF) + .sdf() + .msg(); + node_properties_t S_RANDFLAG = node_builder_t(eSpecType::SDF_RANDOM) + .sdf() + .msg(); + node_properties_t S_ADDFLAG = node_builder_t(eSpecType::SDF_ADD) + .sdf() + .msg(); + node_properties_t S_SUBFLAG = node_builder_t(eSpecType::SDF_DIFF) + .sdf() + .msg(); + node_properties_t S_STORY = node_builder_t(eSpecType::STORY_DIALOG) + .msg1(eSpecPicker::MSG_SINGLE) + .pic(); + node_properties_t S_PREVENT = node_builder_t(eSpecType::CANT_ENTER) + .msg(); + node_properties_t S_TIME = node_builder_t(eSpecType::CHANGE_TIME) + .msg(); + node_properties_t S_TIMER = node_builder_t(eSpecType::SCEN_TIMER_START) + .msg() + .ex1b(+eSpecPicker::NODE); + node_properties_t S_SND = node_builder_t(eSpecType::PLAY_SOUND) + .ex1a(eSpecPicker::SOUND); + node_properties_t S_HORSE_OWN = node_builder_t(eSpecType::CHANGE_HORSE_OWNER) + .msg(); + node_properties_t S_BOAT_OWN = node_builder_t(eSpecType::CHANGE_BOAT_OWNER) + .msg(); + node_properties_t S_TOWN_VIS = node_builder_t(eSpecType::SET_TOWN_VISIBILITY) + .msg() + .ex1a(STRT_TOWN); + node_properties_t S_EVENT = node_builder_t(eSpecType::MAJOR_EVENT_OCCURRED) + .msg(); + node_properties_t S_FORCEGIVE = node_builder_t(eSpecType::FORCED_GIVE) + .msg() + .ex1a(STRT_ITEM) + .ex1b(eSpecPicker::NODE); + node_properties_t S_BUYTYPE = node_builder_t(eSpecType::BUY_ITEMS_OF_TYPE) + .msg() + .ex1b(eSpecPicker::NODE); + node_properties_t S_GLOBAL = node_builder_t(eSpecType::CALL_GLOBAL) + .jump(+eSpecPicker::NODE); + node_properties_t S_SETROW = node_builder_t(eSpecType::SET_SDF_ROW); + node_properties_t S_COPYFLAG = node_builder_t(eSpecType::COPY_SDF) + .sdf() + .sdf(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_PICTURE = node_builder_t(eSpecType::DISPLAY_PICTURE) + .msg1(eSpecPicker::MSG_SINGLE) + .ex1a(PIC_FULL); + node_properties_t S_REST = node_builder_t(eSpecType::REST) + .msg(); + node_properties_t S_MSG_TITLE = node_builder_t(eSpecType::TITLED_MSG) + .msg() + .msg3(eSpecPicker::MSG_SINGLE) + .pic(); + node_properties_t S_END_SCEN = node_builder_t(eSpecType::END_SCENARIO); + node_properties_t S_SETPTR = node_builder_t(eSpecType::SET_POINTER) + .sdf(); + node_properties_t S_CAMPFLAG = node_builder_t(eSpecType::SET_CAMP_FLAG) + .sdf() + .msg1(+eSpecPicker::MSG_SINGLE); + node_properties_t S_DEBUG = node_builder_t(eSpecType::PRINT_NUMS) + .sdf(); + node_properties_t S_MULFLAG = node_builder_t(eSpecType::SDF_TIMES) + .sdf() + .msg(); + node_properties_t S_DIVFLAG = node_builder_t(eSpecType::SDF_DIVIDE) + .sdf() + .sdf(eSpecField::EX1C, eSpecField::EX2C) + .msg(); + node_properties_t S_EXPFLAG = node_builder_t(eSpecType::SDF_POWER) + .sdf() + .msg(); + node_properties_t S_TERCHANGE = node_builder_t(eSpecType::CHANGE_TER) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_TER); + node_properties_t S_TERSWAP = node_builder_t(eSpecType::SWAP_TER) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_TER) + .ex2b(STRT_TER); + node_properties_t S_TERTRANS = node_builder_t(eSpecType::TRANS_TER) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_TER); + node_properties_t S_BUF_CLEAR = node_builder_t(eSpecType::CLEAR_BUF); + node_properties_t S_BUF_ADDSTR = node_builder_t(eSpecType::APPEND_STRING) + .ex1a(eSpecPicker::MSG_SINGLE); + node_properties_t S_BUF_ADDNUM = node_builder_t(eSpecType::APPEND_NUM); + node_properties_t S_BUF_ADDMONST = node_builder_t(eSpecType::APPEND_MONST) + .ex1a(STRT_MONST); + node_properties_t S_BUF_ADDITEM = node_builder_t(eSpecType::APPEND_ITEM) + .ex1a(STRT_ITEM); + node_properties_t S_BUF_ADDTER = node_builder_t(eSpecType::APPEND_TER) + .ex1a(STRT_TER); + node_properties_t S_PAUSE = node_builder_t(eSpecType::PAUSE); + node_properties_t S_TALK = node_builder_t(eSpecType::START_TALK) + .pict(PIC_TALK) + .ex1b(STRT_MONST) + .jump(eSpecPicker::NONE); + node_properties_t S_QUEST = node_builder_t(eSpecType::UPDATE_QUEST) + .msg() + .ex1a(STRT_QUEST) + .ex1b(STRT_QUEST_STATUS); + node_properties_t S_BUF_SWAP = node_builder_t(eSpecType::SWAP_STR_BUF) + .msg(); + node_properties_t S_ALTER_SIGN = node_builder_t(eSpecType::STR_BUF_TO_SIGN) + .msg(); +} diff --git a/src/scenario/special-oneshot.cpp b/src/scenario/special-oneshot.cpp new file mode 100644 index 000000000..273029f0e --- /dev/null +++ b/src/scenario/special-oneshot.cpp @@ -0,0 +1,57 @@ +// +// special-oneshot.cpp +// Common +// +// Created by Celtic Minstrel on 2025-02-19. +// + +#include "special.hpp" + +// Note: If adding a new node type below, be sure to adjust the end point here too. +node_category_info_t CAT_ONCE{eSpecType::ONCE_GIVE_ITEM, eSpecType::ONCE_TRAP}; + +namespace { + node_properties_t S_GIVE_ITEM = node_builder_t(eSpecType::ONCE_GIVE_ITEM) + .sdf() + .msg() + .ex1a(STRT_ITEM) + .ex2b(eSpecPicker::NODE); + node_properties_t S_GIVE_SPECITEM = node_builder_t(eSpecType::ONCE_GIVE_SPEC_ITEM) + .sdf() + .msg() + .ex1a(STRT_SPEC_ITEM); + node_properties_t S_NONE = node_builder_t(eSpecType::ONCE_NULL) + .sdf(); + node_properties_t S_SETSDF = node_builder_t(eSpecType::ONCE_SET_SDF) + .sdf(); + node_properties_t S_MSG = node_builder_t(eSpecType::ONCE_DISPLAY_MSG) + .sdf() + .msg(); + node_properties_t S_DIALOG = node_builder_t(eSpecType::ONCE_DIALOG) + .sdf() + .msg1(eSpecPicker::MSG_SEQUENCE) + .pic() + .ex1a(STRT_BUTTON) + .ex2a(STRT_BUTTON) + .ex1b(eSpecPicker::NODE) + .ex2b(eSpecPicker::NODE); + node_properties_t S_ITEM_DIALOG = node_builder_t(eSpecType::ONCE_GIVE_ITEM_DIALOG) + .sdf() + .msg() + .msg3(STRT_SPEC_ITEM) + .pic() + .ex1a(STRT_ITEM) + .ex2b(eSpecPicker::NODE); + node_properties_t S_OUTENC = node_builder_t(eSpecType::ONCE_OUT_ENCOUNTER) + .sdf() + .msg(); + node_properties_t S_TOWNENV = node_builder_t(eSpecType::ONCE_TOWN_ENCOUNTER) + .sdf() + .msg(); + node_properties_t S_TRAP = node_builder_t(eSpecType::ONCE_TRAP) + .sdf() + .msg() + .pic() + .ex1a(STRT_TRAP) + .ex2b(+eSpecPicker::NODE); +} diff --git a/src/scenario/special-outdoor.cpp b/src/scenario/special-outdoor.cpp new file mode 100644 index 000000000..97e487308 --- /dev/null +++ b/src/scenario/special-outdoor.cpp @@ -0,0 +1,24 @@ +// +// special-outdoor.cpp +// Common +// +// Created by Celtic Minstrel on 2025-02-20. +// + +#include "special.hpp" + +// Note: If adding a new node type below, be sure to adjust the end point here too. +node_category_info_t CAT_OUTD{eSpecType::OUT_MAKE_WANDER, eSpecType::OUT_MOVE_PARTY}; + +namespace { + node_properties_t S_WANDER = node_builder_t(eSpecType::OUT_MAKE_WANDER); + node_properties_t S_TOWN = node_builder_t(eSpecType::OUT_FORCE_TOWN) + .msg() + .ex1a(STRT_TOWN) + .ex1b(STRT_DIR); + node_properties_t S_ENCOUNTER = node_builder_t(eSpecType::OUT_PLACE_ENCOUNTER) + .msg(); + node_properties_t S_TELEPORT = node_builder_t(eSpecType::OUT_MOVE_PARTY) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B); +} diff --git a/src/scenario/special-rect.cpp b/src/scenario/special-rect.cpp new file mode 100644 index 000000000..627117685 --- /dev/null +++ b/src/scenario/special-rect.cpp @@ -0,0 +1,46 @@ +// +// special-rect.cpp +// Common +// +// Created by Celtic Minstrel on 2025-02-20. +// + +#include "special.hpp" + +// Note: If adding a new node type below, be sure to adjust the end point here too. +node_category_info_t CAT_RECT{eSpecType::RECT_PLACE_FIELD, eSpecType::RECT_UNLOCK}; + +namespace { + node_properties_t S_FIELDS = node_builder_t(eSpecType::RECT_PLACE_FIELD) + .msg() + .rect() + .sdf2(+eSpecPicker::FIELD); + node_properties_t S_EXPLORE = node_builder_t(eSpecType::RECT_SET_EXPLORED) + .msg() + .rect(); + node_properties_t S_MOVE = node_builder_t(eSpecType::RECT_MOVE_ITEMS) + .msg() + .rect() + .loc(eSpecField::SDF1, eSpecField::SDF2); + node_properties_t S_DESTROY = node_builder_t(eSpecType::RECT_DESTROY_ITEMS) + .msg() + .rect(); + node_properties_t S_CHANGE = node_builder_t(eSpecType::RECT_CHANGE_TER) + .msg() + .rect() + .sdf1(STRT_TER); + node_properties_t S_SWAP = node_builder_t(eSpecType::RECT_SWAP_TER) + .msg() + .rect() + .sdf1(STRT_TER) + .sdf2(STRT_TER); + node_properties_t S_TRANS = node_builder_t(eSpecType::RECT_TRANS_TER) + .msg() + .rect(); + node_properties_t S_LOCK = node_builder_t(eSpecType::RECT_LOCK) + .msg() + .rect(); + node_properties_t S_UNLOCK = node_builder_t(eSpecType::RECT_UNLOCK) + .msg() + .rect(); +} diff --git a/src/scenario/special-town.cpp b/src/scenario/special-town.cpp new file mode 100644 index 000000000..51f17cec1 --- /dev/null +++ b/src/scenario/special-town.cpp @@ -0,0 +1,136 @@ +// +// special-town.cpp +// Common +// +// Created by Celtic Minstrel on 2025-02-20. +// + +#include "special.hpp" + +// Note: If adding a new node type below, be sure to adjust the end point here too. +node_category_info_t CAT_TOWN{eSpecType::MAKE_TOWN_HOSTILE, eSpecType::TOWN_PLACE_LABEL}; + +namespace { + node_properties_t S_ATTITUDE = node_builder_t(eSpecType::MAKE_TOWN_HOSTILE) + .msg() + .ex2a(STRT_ATTITUDE); + node_properties_t S_MISSILE = node_builder_t(eSpecType::TOWN_RUN_MISSILE) + .msg() + .pict(PIC_MISSILE) + .loc(eSpecField::EX1A, eSpecField::EX1B) + .loc(eSpecField::EX2A, eSpecField::EX2B) + .ex2c(eSpecPicker::SOUND); + node_properties_t S_ATTACK = node_builder_t(eSpecType::TOWN_MONST_ATTACK) + .msg(); + node_properties_t S_BOOM = node_builder_t(eSpecType::TOWN_BOOM_SPACE) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(PIC_BOOM) + .ex2c(eSpecPicker::SOUND); + node_properties_t S_TELEPORT = node_builder_t(eSpecType::TOWN_MOVE_PARTY) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_HIT = node_builder_t(eSpecType::TOWN_HIT_SPACE) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2b(eSpecPicker::DAMAGE_TYPE); + node_properties_t S_EXPLODE = node_builder_t(eSpecType::TOWN_EXPLODE_SPACE) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2b(eSpecPicker::DAMAGE_TYPE); + node_properties_t S_LOCK = node_builder_t(eSpecType::TOWN_LOCK_SPACE) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_UNLOCK = node_builder_t(eSpecType::TOWN_UNLOCK_SPACE) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_BURST = node_builder_t(eSpecType::TOWN_SFX_BURST) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(eSpecPicker::EXPLOSION) + .ex2c(eSpecPicker::SOUND); + node_properties_t S_WANDER = node_builder_t(eSpecType::TOWN_CREATE_WANDERING) + .msg(); + node_properties_t S_SPAWN = node_builder_t(eSpecType::TOWN_PLACE_MONST) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_MONST); + node_properties_t S_KILL = node_builder_t(eSpecType::TOWN_DESTROY_MONST) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_NUKE = node_builder_t(eSpecType::TOWN_NUKE_MONSTS) + .msg() + .ex1a(STRT_MONST); + node_properties_t S_LEVER_G = node_builder_t(eSpecType::TOWN_GENERIC_LEVER) + .ex1b(eSpecPicker::NODE); + node_properties_t S_PORTAL_G = node_builder_t(eSpecType::TOWN_GENERIC_PORTAL) + .loc(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_BUTTON_G = node_builder_t(eSpecType::TOWN_GENERIC_BUTTON) + .ex1b(eSpecPicker::NODE); + node_properties_t S_STAIR_G = node_builder_t(eSpecType::TOWN_GENERIC_STAIR) + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_TOWN) + .ex2b(STRT_STAIR) + .ex2c(STRT_STAIR_MODE) + .jump(eSpecPicker::NONE); + node_properties_t S_LEVER = node_builder_t(eSpecType::TOWN_LEVER) + .msg1(eSpecPicker::MSG_SEQUENCE) + .pic() + .ex1b(eSpecPicker::NODE); + node_properties_t S_PORTAL = node_builder_t(eSpecType::TOWN_PORTAL) + .msg1(eSpecPicker::MSG_SEQUENCE) + .pic() + .loc(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_STAIR = node_builder_t(eSpecType::TOWN_STAIR) + .msg1(eSpecPicker::MSG_SEQUENCE) + .pic() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_TOWN) + .ex2c(STRT_STAIR_MODE) + .jump(eSpecPicker::NONE); + node_properties_t S_OUTDOOR = node_builder_t(eSpecType::TOWN_RELOCATE) + .msg() + .loc(eSpecField::EX2A, eSpecField::EX2B); + node_properties_t S_ITEM = node_builder_t(eSpecType::TOWN_PLACE_ITEM) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2a(STRT_ITEM); + node_properties_t S_SPLIT = node_builder_t(eSpecType::TOWN_SPLIT_PARTY) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B); + node_properties_t S_REUNITE = node_builder_t(eSpecType::TOWN_REUNITE_PARTY) + .msg(); + node_properties_t S_TIMER = node_builder_t(eSpecType::TOWN_TIMER_START) + .msg() + .ex1b(eSpecPicker::NODE); + node_properties_t S_LIGHT = node_builder_t(eSpecType::TOWN_CHANGE_LIGHTING) + .msg() + .ex1a(STRT_LIGHT); + node_properties_t S_CHARM = node_builder_t(eSpecType::TOWN_SET_ATTITUDE) + .msg() + .ex1b(STRT_ATTITUDE); + node_properties_t S_CAMERA = node_builder_t(eSpecType::TOWN_SET_CENTER) + .msg(); + node_properties_t S_FOG = node_builder_t(eSpecType::TOWN_LIFT_FOG) + .msg(); + node_properties_t S_TARGET = node_builder_t(eSpecType::TOWN_START_TARGETING) + .msg() + .ex1a(STRT_SPELL_PAT); + node_properties_t S_FIELDS = node_builder_t(eSpecType::TOWN_SPELL_PAT_FIELD) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex1c(+STRT_SPELL_PAT) + .ex2a(+eSpecPicker::FIELD); + node_properties_t S_PATTERN = node_builder_t(eSpecType::TOWN_SPELL_PAT_BOOM) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex1c(+STRT_SPELL_PAT) + .ex2a(eSpecPicker::DAMAGE_TYPE); + node_properties_t S_WARP = node_builder_t(eSpecType::TOWN_RELOCATE_CREATURE) + .msg() + .loc(eSpecField::EX1A, eSpecField::EX1B) + .ex2b(STRT_POS_MODE); + node_properties_t S_LABEL = node_builder_t(eSpecType::TOWN_PLACE_LABEL) + .msg1(eSpecPicker::MSG_SINGLE) + .loc(eSpecField::EX1A, eSpecField::EX1B); +} diff --git a/src/scenario/special.cpp b/src/scenario/special.cpp index 2b8b9cbd5..4c88079ce 100644 --- a/src/scenario/special.cpp +++ b/src/scenario/special.cpp @@ -528,173 +528,36 @@ void cSpecial::import_legacy(legacy::special_node_type& old){ } } -// Key: -// space - no button -// m - Create/Edit button to edit message pair (covers msg1 and msg2 together) -// M - Create/Edit button to edit single message -// $ - As above, but always a scenario message -// d - Create/Edit button to edit dialog message sequence (covers msg1 and msg2 together) -// b - Choose button to select a button label -// p - Choose button to select a picture (uses pictype for type) -// ? - Choose button to select a picture type -// s - Create/Edit button to edit special node -// S - As above, but always a scenario node -// x - Choose button to select a sound -// X - Choose button to select a trap type -// T - Choose button to select a town -// i - Choose button to select an item -// I - Choose button to select a special item -// t - Choose button to select a terrain type -// c - Choose button to select a monster type -// C - Choose button to select a monster statistic -// a - Choose button to select an alchemy recipe -// A - Choose button to select a mage spell -// P - Choose button to select a priest spell -// k - Choose button to select a skill -// K - As above, but add the special pseudo-skills for the if-statistic node -// f - Choose button to select a field type -// F - As above, but also include Dispel and Smash -// q - Choose button to select a trait -// Q - Choose button to select a species -// = - Choose button to select a comparison method (<=, <, =, >, >=) -// + - Choose button to select stat cumulation mode -// * - Choose button to select a special node context -// @ - Choose button to select a monster attitude -// D - Choose button to select a damage type -// ! - Choose button to select an explosion animation type -// / - Choose button to select generic stairway text -// : - Choose stairway trigger conditions -// L - Choose button to select a town lighting type -// & - Choose button to select a shop -// % - Choose button to select shop cost adjustment -// { - Choose button to select a spell pattern -// } - As above, but allows you to select which version of the rotateable field -// ^ - Choose button to select a positioning mode -// e - Choose button to select a status effect -// E - Choose button to select a party status effect -// w - Choose button to select main party status effect -// j - Choose button to select a quest -// J - Choose button to select a quest status -// < - Choose button to select a cardinal direction -// ~ - Choose button to select a weapon enchantment -// _ - Choose button to select a full sheet -// 0..9 - Choose button to select a specific type of picture -// (terrain, monster, dialog, talk, item, pc, field, boom, missile, status) -static const char*const button_dict[7][11] = { - { // general nodes - " mmmMmmmmmMmmm mmmmmm Mmm $ mmmmmm mmm", // msg1 - " ", // msg2 - " M ", // msg3 - " p p 3 ", // pic - " ? ? ", // pictype - " & x T i _ M cit j ", // ex1a - " % S ss cJ ", // ex1b - " ", // ex1c - " tt ", // ex2a - " t ", // ex2b - " ", // ex2c - }, { // one-shot nodes - "mm md d mmm", // msg1 - " ", // msg2 - " III ", // msg3 - " p p p", // pic - " ? ? ?", // pictype - "iI b i X", // ex1a - " s ", // ex1b - " ", // ex1c - " b ", // ex2a - "s s s S", // ex2b - " ", // ex2c - }, { // affect pc nodes - "mmmmmmmmmmmmmmmmmmmmmmmmmmmm", // msg1 - " ", // msg2 - " M M ", // msg3 - " s 5 ", // pic - " s ", // pictype - " w q i AP a ", // ex1a - " ~ s", // ex1b - " e Q ", // ex1c - " CK E ", // ex2a - " D ", // ex2b - " x ", // ex2c - }, { // if-then nodes - " f $ $ ", // msg1 - " s ", // msg2 - " ", // msg3 - " ", // pic - " ", // pictype - " T I w APae Qq $ * j", // ex1a - "ssss sss ssssss s s sssss = J", // ex1b - " s sssss", // ex1c - " t K$ ", // ex2a - "s ss s + s==+s = ", // ex2b - " = s ", // ex2c - }, { // town nodes - "mmmmmmmmmmmmmm dddmmmmmmmmmmmmmM", // msg1 - " ", // msg2 - " ", // msg3 - " 8 ppp ", // pic - " ??? ", // pictype - " c L { ", // ex1a - " s s s s @ ", // ex1b - " }} ", // ex1c - "@ 7 ! c T T i FD ", // ex2a - " DD / ^ ", // ex2b - " x x x : : ", // ex2c - }, { // rectangle nodes - "mmmmmmmmm", // msg1 - " ", // msg2 - " ", // msg3 - " ", // pic - " ", // pictype - " tt ", // sdf1 - " ", // unused - " ", // ex1c - "F t ", // sdf2 - " ", // unused - " ", // ex2c - }, { // outdoors nodes - " mmm", // msg1 - " ", // msg2 - " ", // msg3 - " ", // pic - " ", // pictype - " T ", // ex1a - " < ", // ex1b - " ", // ex1c - " ", // ex2a - " ", // ex2b - " ", // ex2c +static eSpecCat getNodeCategory(eSpecType node) { + for(int i = 0; i <= int(eSpecCat::OUTDOOR); i++) { + eSpecCat cat = eSpecCat(i); + if((*cat).contains(node)) return cat; } -}; + return eSpecCat::INVALID; +} -static int offsets[] = { - int(eSpecType::NONE), - int(eSpecType::ONCE_GIVE_ITEM), - int(eSpecType::SELECT_TARGET), - int(eSpecType::IF_SDF), - int(eSpecType::MAKE_TOWN_HOSTILE), - int(eSpecType::RECT_PLACE_FIELD), - int(eSpecType::OUT_MAKE_WANDER), -}; +// Note: While it might seem like it would be simpler to define these in a map, +// having each one as a separate extern variable serves an important purpose: +// it prevents the special node definitions from being optimized away by the linker. +const node_category_info_t& operator* (eSpecCat t) { + extern node_category_info_t CAT_GENERAL, CAT_ONCE, CAT_AFFECT, CAT_COND, CAT_TOWN, CAT_RECT, CAT_OUTD; + static node_category_info_t CAT_INVALID; + switch(t) { + case eSpecCat::GENERAL: return CAT_GENERAL; + case eSpecCat::ONCE: return CAT_ONCE; + case eSpecCat::AFFECT: return CAT_AFFECT; + case eSpecCat::IF_THEN: return CAT_COND; + case eSpecCat::TOWN: return CAT_TOWN; + case eSpecCat::RECT: return CAT_RECT; + case eSpecCat::OUTDOOR: return CAT_OUTD; + case eSpecCat::INVALID: return CAT_INVALID; + } + return CAT_INVALID; +} -static eSpecCat getNodeCategory(eSpecType node) { - int code = (int) node; - if(code >= 0 && code <= 47) - return eSpecCat::GENERAL; - if(code >= 50 && code <= 63) - return eSpecCat::ONCE; - if(code >= 80 && code <= 107) - return eSpecCat::AFFECT; - if(code >= 130 && code <= 160) - return eSpecCat::IF_THEN; - if(code >= 170 && code <= 204) - return eSpecCat::TOWN; - if(code >= 210 && code <= 218) - return eSpecCat::RECT; - if(code >= 225 && code <= 228) - return eSpecCat::OUTDOOR; - return eSpecCat::INVALID; +bool node_category_info_t::contains(eSpecType spec) const { + int code = (int) spec; + return code >= int(first) && code <= int(last); } static std::map& nodeProps() { @@ -702,64 +565,15 @@ static std::map& nodeProps() { return props; } -void node_properties_t::load() { - std::map& allNodeProps = nodeProps(); - // These are the node types that should not have a Create/Edit button on the JumpTo - static std::set dead_ends = {eSpecType::ENTER_SHOP, eSpecType::START_TALK, eSpecType::TOWN_GENERIC_STAIR, eSpecType::TOWN_STAIR}; - // There's really no need to check all the way to the max of the underlying type. - // It's unlikely we'd go above 255, so unsigned char would be fine, but just in case, - // let's use unsigned short. - // Could change the actual enum's underlying type instead though? - using underlying = unsigned short;//std::underlying_type::type; - for(underlying i = 0; i < std::numeric_limits::max(); i++) { - eSpecType check = (eSpecType) i; - eSpecCat category = getNodeCategory(check); - if(category == eSpecCat::INVALID) continue; - node_properties_t props; - props.self = check; - props.cat = category; - int j = int(category), k = i - offsets[j]; - props.f_m1 = button_dict[j][0][k]; - props.f_m2 = button_dict[j][1][k]; - props.f_m3 = button_dict[j][2][k]; - props.f_pic = button_dict[j][3][k]; - props.f_pt = button_dict[j][4][k]; - if(category != eSpecCat::RECT) { - props.f_sd1 = ' '; - props.f_x1a = button_dict[j][5][k]; - props.f_x1b = button_dict[j][6][k]; - } else props.f_sd1 = button_dict[j][5][k]; - props.f_x1c = button_dict[j][7][k]; - if(category != eSpecCat::RECT) { - props.f_sd2 = ' '; - props.f_x2a = button_dict[j][8][k]; - props.f_x2b = button_dict[j][9][k]; - } else props.f_sd2 = button_dict[j][8][k]; - props.f_x2c = button_dict[j][10][k]; - if(category == eSpecCat::RECT) { - props.f_x1a = props.f_x2a = ' '; - props.f_x1b = props.f_x2b = ' '; - } - if(dead_ends.count(check)) { - props.f_jmp = ' '; - } else { - props.f_jmp = check == eSpecType::CALL_GLOBAL ? 'S' : 's'; - } - props.f_sd1.lbl_idx = 1; props.f_sd2.lbl_idx = 2; - props.f_m1.lbl_idx = 3; props.f_m2.lbl_idx = 4; props.f_m3.lbl_idx = 5; - props.f_pic.lbl_idx = 6; props.f_pt.lbl_idx = 7; - props.f_x1a.lbl_idx = 8; props.f_x1b.lbl_idx = 9; props.f_x1c.lbl_idx = 10; - props.f_x2a.lbl_idx = 11; props.f_x2b.lbl_idx = 12; props.f_x2c.lbl_idx = 13; - props.f_jmp.lbl_idx = 14; - props.f_sd1.self = props.f_sd2.self = props.f_m1.self = props.f_m2.self = props.f_m3.self = props.f_pic.self = props.f_pt.self = props.f_x1a.self = props.f_x1b.self = props.f_x1c.self = props.f_x2a.self = props.f_x2b.self = props.f_x2c.self = props.f_jmp.self = props.self; - allNodeProps[check] = props; - } -} +node_properties_t::node_properties_t(eSpecType type) + : self(type) + , cat(getNodeCategory(type)) + , f_jmp(eSpecPicker::NODE) +{} const node_properties_t& operator* (eSpecType t) { static node_properties_t invalid; std::map& allNodeProps = nodeProps(); - if(allNodeProps.empty()) node_properties_t::load(); auto iter = allNodeProps.find(t); return iter == allNodeProps.end() ? invalid : iter->second; } @@ -771,8 +585,8 @@ std::string node_properties_t::opcode() const { static std::string get_node_string(std::string base, eSpecType type, int which) { eSpecCat cat = getNodeCategory(type); - int i = int(cat), j = int(type); - int strnum = (j - offsets[i]) * 17 + which + 1; + int offset = int((*cat).first), i = int(type); + int strnum = (i - offset) * 17 + which + 1; switch(cat) { case eSpecCat::GENERAL: return get_str(base + "-general", strnum); @@ -804,68 +618,47 @@ std::string node_properties_t::descr() const { node_function_t::node_function_t() {} -node_function_t::node_function_t(char c) { - switch(c) { - case ' ': button = eSpecPicker::NONE; break; - case 'm': button = eSpecPicker::MSG_PAIR; force_global = false; break; - case 'M': button = eSpecPicker::MSG_SINGLE; force_global = false; break; - case '$': button = eSpecPicker::MSG_SINGLE; force_global = true; break; - case 'd': button = eSpecPicker::MSG_SEQUENCE; force_global = false; break; - case 'b': button = eSpecPicker::STRING; str_type = STRT_BUTTON; break; - case 'p': button = eSpecPicker::PICTURE; pic_type = PIC_NONE; break; - case '?': button = eSpecPicker::STRING; str_type = STRT_PICT; adjust = -1; break; - case 's': button = eSpecPicker::NODE; force_global = false; break; - case 'S': button = eSpecPicker::NODE; force_global = true; break; - case 'x': button = eSpecPicker::SOUND; break; - case 'X': button = eSpecPicker::STRING; str_type = STRT_TRAP; break; - case 't': button = eSpecPicker::STRING; str_type = STRT_TER; break; - case 'T': button = eSpecPicker::STRING; str_type = STRT_TOWN; break; - case 'i': button = eSpecPicker::STRING; str_type = STRT_ITEM; break; - case 'I': button = eSpecPicker::STRING; str_type = STRT_SPEC_ITEM; break; - case 'c': button = eSpecPicker::STRING; str_type = STRT_MONST; adjust = -1; break; - case 'C': button = eSpecPicker::STRING; str_type = STRT_MONST_STAT; break; - case 'a': button = eSpecPicker::STRING; str_type = STRT_ALCHEMY; break; - case 'A': button = eSpecPicker::STRING; str_type = STRT_MAGE; break; - case 'P': button = eSpecPicker::STRING; str_type = STRT_PRIEST; break; - case 'k': button = eSpecPicker::STRING; str_type = STRT_SKILL; augmented = false; break; - case 'K': button = eSpecPicker::STRING; str_type = STRT_SKILL; augmented = true; break; - case 'f': button = eSpecPicker::FIELD; augmented = false; break; - case 'F': button = eSpecPicker::FIELD; augmented = true; break; - case 'q': button = eSpecPicker::STRING; str_type = STRT_TRAIT; break; - case 'Q': button = eSpecPicker::STRING; str_type = STRT_RACE; break; - case '=': button = eSpecPicker::STRING; str_type = STRT_CMP; adjust = 2; break; - case '+': button = eSpecPicker::STRING; str_type = STRT_ACCUM; adjust = 1; break; - case '*': button = eSpecPicker::STRING; str_type = STRT_CONTEXT; break; - case '@': button = eSpecPicker::STRING; str_type = STRT_ATTITUDE; break; - case 'D': button = eSpecPicker::DAMAGE_TYPE; break; - case '!': button = eSpecPicker::EXPLOSION; break; - case '/': button = eSpecPicker::STRING; str_type = STRT_STAIR; break; - case ':': button = eSpecPicker::STRING; str_type = STRT_STAIR_MODE; break; - case 'L': button = eSpecPicker::STRING; str_type = STRT_LIGHT; break; - case '&': button = eSpecPicker::STRING; str_type = STRT_SHOP; break; - case '%': button = eSpecPicker::STRING; str_type = STRT_COST_ADJ; break; - case '{': button = eSpecPicker::STRING; str_type = STRT_SPELL_PAT; augmented = false; break; - case '}': button = eSpecPicker::STRING; str_type = STRT_SPELL_PAT; augmented = true; break; - case '^': button = eSpecPicker::STRING; str_type = STRT_POS_MODE; break; - case 'e': button = eSpecPicker::STATUS; break; - case 'E': button = eSpecPicker::STATUS_PARTY; break; - case 'w': button = eSpecPicker::STRING; str_type = STRT_STATUS; adjust = 1; break; - case 'j': button = eSpecPicker::STRING; str_type = STRT_QUEST; break; - case 'J': button = eSpecPicker::STRING; str_type = STRT_QUEST_STATUS; break; - case '<': button = eSpecPicker::STRING; str_type = STRT_DIR; break; - case '~': button = eSpecPicker::STRING; str_type = STRT_ENCHANT; break; - case '_': button = eSpecPicker::PICTURE; pic_type = PIC_FULL; break; - case '0': button = eSpecPicker::PICTURE; pic_type = PIC_TER; break; - case '1': button = eSpecPicker::PICTURE; pic_type = PIC_MONST; break; - case '2': button = eSpecPicker::PICTURE; pic_type = PIC_DLOG; break; - case '3': button = eSpecPicker::PICTURE; pic_type = PIC_TALK; break; - case '4': button = eSpecPicker::PICTURE; pic_type = PIC_ITEM; break; - case '5': button = eSpecPicker::PICTURE; pic_type = PIC_PC; break; - case '6': button = eSpecPicker::PICTURE; pic_type = PIC_FIELD; break; - case '7': button = eSpecPicker::PICTURE; pic_type = PIC_BOOM; break; - case '8': button = eSpecPicker::PICTURE; pic_type = PIC_MISSILE; break; - case '9': button = eSpecPicker::PICTURE; pic_type = PIC_STATUS; break; +node_function_t::node_function_t(eSpecPicker button) + : button(button) +{} + +node_function_t::node_function_t(eStrType str) + : button(eSpecPicker::STRING) + , str_type(str) +{ + // Some string types require an adjustment to the index. + switch(str) { + case STRT_PICT: adjust = -1; break; + case STRT_MONST: adjust = -1; break; + case STRT_CMP: adjust = 2; break; + case STRT_ACCUM: adjust = 1; break; + case STRT_STATUS: adjust = 1; break; + default: break; + } +} + +node_function_t::node_function_t(ePicType pic) + : button(eSpecPicker::PICTURE) + , pic_type(pic) +{} + +node_function_t operator+(eSpecPicker picker) { + node_function_t n(picker); + if(picker == eSpecPicker::NODE || picker == eSpecPicker::MSG_PAIR || picker == eSpecPicker::MSG_SINGLE || picker == eSpecPicker::MSG_SEQUENCE) { + n.force_global = true; + } + if(picker == eSpecPicker::FIELD) { + n.augmented = true; + } + return n; +} + +node_function_t operator+(eStrType str) { + node_function_t n(str); + if(str == STRT_SPELL_PAT || str == STRT_SKILL) { + n.augmented = true; } + return n; } std::string node_function_t::label() const { @@ -927,3 +720,144 @@ node_function_t node_properties_t::ex2c(const cSpecial&) const { node_function_t node_properties_t::jump(const cSpecial&) const { return f_jmp; } + +struct field_map { + std::map map = { + {eSpecField::SDF1, &node_properties_t::f_sd1}, + {eSpecField::SDF2, &node_properties_t::f_sd2}, + {eSpecField::MSG1, &node_properties_t::f_m1}, + {eSpecField::MSG2, &node_properties_t::f_m2}, + {eSpecField::MSG3, &node_properties_t::f_m3}, + {eSpecField::PICT, &node_properties_t::f_pic}, + {eSpecField::PTYP, &node_properties_t::f_pt}, + {eSpecField::EX1A, &node_properties_t::f_x1a}, + {eSpecField::EX1B, &node_properties_t::f_x1b}, + {eSpecField::EX1C, &node_properties_t::f_x1c}, + {eSpecField::EX2A, &node_properties_t::f_x2a}, + {eSpecField::EX2B, &node_properties_t::f_x2b}, + {eSpecField::EX2C, &node_properties_t::f_x2c}, + {eSpecField::JUMP, &node_properties_t::f_jmp}, + }; +}; + +static field_map& fields() { + static field_map map; + return map; +} + +node_builder_t& node_builder_t::sdf() { + // The intent is to specify that sdf1,sdf2 is a stuff done flag. + // But specifying that two fields are a stuff done flag isn't implemented yet. + return sdf(eSpecField::SDF1, eSpecField::SDF2); +} + +node_builder_t& node_builder_t::msg() { + return msg1(eSpecPicker::MSG_PAIR).msg2(eSpecPicker::NONE); +}; + +node_builder_t& node_builder_t::rect() { + // The intent is to specify that ex1a,ex1b, and ex2a,ex2b are locations. + // But specifying that two fields are a location isn't implemented yet. + return loc(eSpecField::EX1A, eSpecField::EX1B).loc(eSpecField::EX2A, eSpecField::EX2B); +}; + +node_builder_t& node_builder_t::pic() { + return pict(eSpecPicker::PICTURE).ptyp(STRT_PICT); +} + +node_builder_t& node_builder_t::sdf1(node_function_t picker) { + return field(eSpecField::SDF1, picker); +} + +node_builder_t& node_builder_t::sdf2(node_function_t picker) { + return field(eSpecField::SDF2, picker); +} + +node_builder_t& node_builder_t::jump(node_function_t picker) { + return field(eSpecField::JUMP, picker); +} + +node_builder_t& node_builder_t::msg1(node_function_t picker) { + return field(eSpecField::MSG1, picker); +} + +node_builder_t& node_builder_t::msg2(node_function_t picker) { + return field(eSpecField::MSG2, picker); +} + +node_builder_t& node_builder_t::msg3(node_function_t picker) { + return field(eSpecField::MSG3, picker); +} + +node_builder_t& node_builder_t::pict(node_function_t picker) { + return field(eSpecField::PICT, picker); +} + +node_builder_t& node_builder_t::ptyp(node_function_t picker) { + return field(eSpecField::PTYP, picker); +} + +node_builder_t& node_builder_t::ex1a(node_function_t picker) { + return field(eSpecField::EX1A, picker); +} + +node_builder_t& node_builder_t::ex1b(node_function_t picker) { + return field(eSpecField::EX1B, picker); +} + +node_builder_t& node_builder_t::ex1c(node_function_t picker) { + return field(eSpecField::EX1C, picker); +} + +node_builder_t& node_builder_t::ex2a(node_function_t picker) { + return field(eSpecField::EX2A, picker); +} + +node_builder_t& node_builder_t::ex2b(node_function_t picker) { + return field(eSpecField::EX2B, picker); +} + +node_builder_t& node_builder_t::ex2c(node_function_t picker) { + return field(eSpecField::EX2C, picker); +} + +node_builder_t& node_builder_t::field(eSpecField field, node_function_t picker) { + picker.self = node.self; + node.*fields().map[field] = picker; + return *this; +} + +node_builder_t& node_builder_t::sdf(eSpecField a, eSpecField b) { + // A stuff done flag picker isn't implemented yet. + return *this; +} + +node_builder_t& node_builder_t::loc(eSpecField a, eSpecField b) { + // A location picker isn't implemented yet + return *this; +} + +node_builder_t::operator node_properties_t() { + node.set_label_indices(); + std::map& allNodeProps = nodeProps(); + allNodeProps.emplace(node.self, node); + return node; +} + +void node_properties_t::set_label_indices() { + f_sd1.lbl_idx = 1; + f_sd2.lbl_idx = 2; + f_m1.lbl_idx = 3; + f_m2.lbl_idx = 4; + f_m3.lbl_idx = 5; + f_pic.lbl_idx = 6; + f_pt.lbl_idx = 7; + f_x1a.lbl_idx = 8; + f_x1b.lbl_idx = 9; + f_x1c.lbl_idx = 10; + f_x2a.lbl_idx = 11; + f_x2b.lbl_idx = 12; + f_x2c.lbl_idx = 13; + f_jmp.lbl_idx = 14; + f_sd1.self = f_sd2.self = f_jmp.self = f_m1.self = f_m2.self = f_m3.self = f_pic.self = f_pt.self = f_x1a.self = f_x1b.self = f_x1c.self = f_x2a.self = f_x2b.self = f_x2c.self = self; +} diff --git a/src/scenario/special.hpp b/src/scenario/special.hpp index 8cd12a398..d47c5fc7b 100644 --- a/src/scenario/special.hpp +++ b/src/scenario/special.hpp @@ -126,6 +126,15 @@ enum class eSpecCat { GENERAL, ONCE, AFFECT, IF_THEN, TOWN, RECT, OUTDOOR }; +struct node_category_info_t { + eSpecType first = eSpecType::NONE, last = eSpecType::NONE; + node_category_info_t() = default; + node_category_info_t(eSpecType a, eSpecType b) : first(a), last(b) {} + bool contains(eSpecType spec) const; +}; + +enum class eSpecField { SDF1, SDF2, MSG1, MSG2, MSG3, PICT, PTYP, EX1A, EX1B, EX1C, EX2A, EX2B, EX2C, JUMP }; + enum eStrType { STRT_MONST, STRT_ITEM, STRT_TER, STRT_BUTTON, STRT_SPEC_ITEM, STRT_MAGE, STRT_PRIEST, STRT_ALCHEMY, @@ -147,7 +156,7 @@ enum class eSpecPicker { }; struct node_function_t { - eSpecPicker button; + eSpecPicker button = eSpecPicker::NONE; union { eStrType str_type; // for eSpecPicker::STRING only ePicType pic_type; // for eSpecPicker::PICTURE only; PIC_NONE = use pictype field from node @@ -159,15 +168,21 @@ struct node_function_t { std::string label() const; std::string help() const; // maybe we don't need this though? I guess it would be for a hypothetical help button next to each field to give addition info on how that one field works. node_function_t(); - node_function_t(char c); + node_function_t(eSpecPicker button); + node_function_t(eStrType str); + node_function_t(ePicType pic); private: eSpecType self = eSpecType::NONE; std::string lbl; int lbl_idx = 0, sub_idx = 0; bool needs_split = false; + friend struct node_builder_t; friend struct node_properties_t; }; +node_function_t operator+(eSpecPicker); +node_function_t operator+(eStrType); + struct node_properties_t { eSpecType self; eSpecCat cat; @@ -178,13 +193,75 @@ struct node_properties_t { node_function_t pic(const cSpecial&) const, pictype(const cSpecial&) const; node_function_t ex1a(const cSpecial&) const, ex1b(const cSpecial&) const, ex1c(const cSpecial&) const; node_function_t ex2a(const cSpecial&) const, ex2b(const cSpecial&) const, ex2c(const cSpecial&) const; - node_properties_t() : self(eSpecType::INVALID), cat(eSpecCat::INVALID) {} - node_properties_t(std::initializer_list>); - static void load(); + node_properties_t() : self(eSpecType::INVALID), cat(eSpecCat::INVALID), f_jmp(eSpecPicker::NODE) { + set_label_indices(); + } private: + node_properties_t(eSpecType type); node_function_t f_sd1, f_sd2, f_jmp, f_m1, f_m2, f_m3, f_pic, f_pt, f_x1a, f_x1b, f_x1c, f_x2a, f_x2b, f_x2c; + void set_label_indices(); + friend struct node_builder_t; + friend struct field_map; }; const node_properties_t& operator* (eSpecType t); +const node_category_info_t& operator* (eSpecCat t); + +// Builds the information needed to display the correct buttons when editing a special node. +struct node_builder_t { + node_builder_t(eSpecType type) : node(type) {} + // Specifies that a particular field should use a specified picker. + node_builder_t& field(eSpecField field, node_function_t picker); + // Quick overloads for each possible field. + node_builder_t& sdf1(node_function_t picker); + node_builder_t& sdf2(node_function_t picker); + node_builder_t& jump(node_function_t picker); + node_builder_t& msg1(node_function_t picker); + node_builder_t& msg2(node_function_t picker); + node_builder_t& msg3(node_function_t picker); + node_builder_t& pict(node_function_t picker); + node_builder_t& ptyp(node_function_t picker); + node_builder_t& ex1a(node_function_t picker); + node_builder_t& ex1b(node_function_t picker); + node_builder_t& ex1c(node_function_t picker); + node_builder_t& ex2a(node_function_t picker); + node_builder_t& ex2b(node_function_t picker); + node_builder_t& ex2c(node_function_t picker); + // Specifies that sdf1 and sdf2 combine to define an SDF + node_builder_t& sdf(); + // Specifies that msg1 and msg2 specify a pair of local strings + node_builder_t& msg(); + // Specifies that pict and pictype have their typical meaning of defining a picture + node_builder_t& pic(); + // Specifies that ex1a,ex1b and ex2a,ex2b define opposing corners of a rectangle. + // DO NOT use for other cases where there are two points, for example defining a path! + // Also DO NOT use for cases where an outdoor sector is identified by its coordinates. + node_builder_t& rect(); + // Specifies that the indicated two fields combine to define an SDF. + node_builder_t& sdf(eSpecField a, eSpecField b); + // Specifies that the indicated two fields combine to define a location. + node_builder_t& loc(eSpecField a, eSpecField b); + operator node_properties_t(); +private: + node_properties_t node; +}; + +// An overview of how the builder works. +// Most of the time, the node_function_t will be constructed implicitly using +// either one of its implicit constructors or one of the overloaded unary + operators. + +// An eStrType implies a string picker, ie eSpecPicker::STRING. +// However, passing eSpecPicker::STRING directly doesn't do anything useful. + +// An ePicType implies a graphic picker, ie eSpecPicker::PICTURE. +// Passing eSpecPicker::PICTURE directly means that another field (usually pictype) will determine the type. + +// The prefix + has a useful meaning only in a few special cases: +// +eSpecPicker::NODE means always edit a scenario node, even if the caller is a town or outdoor node. +// +eSpecPicker::MSG_* is similar – always edit a scenario string, never a town or outdoor string. +// +eSpecPicker::FIELD adds the pseudo-fields Move Mountains and Dispel to the list. +// +STRT_SKILL adds read-only pseudo-skills such as "current HP" to the list +// +STRT_SPELL_PAT expands the "rotateable wall" into all its possible orientations +// More cases may be added. #endif diff --git a/src/scenedit/scen.keydlgs.cpp b/src/scenedit/scen.keydlgs.cpp index 57a1be27c..ea31dfcac 100644 --- a/src/scenedit/scen.keydlgs.cpp +++ b/src/scenedit/scen.keydlgs.cpp @@ -661,15 +661,8 @@ static bool edit_spec_enc_type(cDialog& me, std::string item_hit, node_stack_t& else if(item_hit == "town") category = eSpecCat::TOWN; else if(item_hit == "out") category = eSpecCat::OUTDOOR; else if(item_hit == "rect") category = eSpecCat::RECT; - int start = -1, finish = -1, current = int(edit_stack.top().node.type); - for(int i = 0; i < std::numeric_limits::max(); i++) { - eSpecCat check = (*eSpecType(i)).cat; - if(start >= 0 && check == eSpecCat::INVALID) { - finish = i - 1; - break; - } else if(check == category && start < 0) - start = i; - } + auto bounds = *category; + int start = int(bounds.first), finish = int(bounds.last), current = int(edit_stack.top().node.type); if(start < 0 || finish < 0) return true; std::vector choices; for(int i = start; i <= finish; i++) {