List of commits:
Subject Hash Author Date (UTC)
Switch scripting to floating-point & code comments eb067496f4edb4420c05a09dc034ad0a25ad1e56 mse 2021-08-31 11:06:01
Add top-level command: GOSUB 62e9520aec0471fe023ead290595f735a30eb806 mse 2021-08-26 09:31:27
Add persistent VN stats 2d1ffc35afd8ae0f477cbd5aac0559fc02571600 mse 2021-08-23 10:44:16
Bribes increase karma 643f0fc6da65316aa799bbe00cea2030e2c1ccd6 mse 2021-08-17 08:16:36
Add command: #RUMBLE <intensity> <milliseconds> 02964b2ecc57a48192522ef369e33c4912d2f0d2 mse 2021-08-16 07:17:40
Reset position in non-mouse menu selection c21eb6a9d238fa1909dbbbc49907ad09bfca52c8 mse 2021-08-16 03:30:49
Add haptic rumble for combat 65ff172308b0ba26b8930e8e83eb41bd36c6b008 mse 2021-08-15 06:11:25
Reduce joystick sensitivity 3804492a52d36c40cd51d42a971fe92f4fdd52ba mse 2021-08-14 22:37:19
Set audio buffer 1024 to reduce latency b1733b33b3342b32f08e1bafb5baf63ed7db419e mse 2021-07-11 02:56:22
Update readme, deps, bump to v0.5.0 2f96bc1fe9af48874750f2ba507eda5d704313b0 mse 2021-07-05 12:14:52
New license c9932a52aaa81e6b136510157bfc3ce3e10ab800 mse 2021-07-04 16:01:57
Fix joystick dead zone 303dc3276d27f2a123e5370b31b79cdf87afc879 mse 2021-06-26 07:09:28
Windows-compatible locale 65244a8360ad6a65fea64f43f115f5c93bf966dd mse 2021-06-11 17:13:26
Detect language using std::locale 60f1680d73e559dc85f6b85e9ca69cb9be717ae6 mse 2021-06-09 16:11:30
Rename data -> base and load game as mod ec3b8aa10ee005d89d897b43c68cecd4b9ea8a75 mse 2021-06-07 12:31:53
Replace function with macro cc34567bf5b5503d90ea989004606fcca240d3cd mse 2021-05-30 20:14:02
Bump version to 0.4.4 4b4eb20f18d047f34a94456b441ee55cc4c6248b mse 2021-05-30 03:48:53
Initial modding support with -m parameters 94429b99dd3c85d67d8a9fe8b2faee06c1e9fcde mse 2021-05-29 22:01:40
Moddable dialogue loading via FileOpen 6183fb4b8dde53f07f8e7bf6835a8fe7ca0a8a8c mse 2021-05-29 10:54:24
Moddable sound file loading via FileOpen ed4d1680cfb17f573fe799908d35e6856c183076 mse 2021-05-29 07:21:32
Commit eb067496f4edb4420c05a09dc034ad0a25ad1e56 - Switch scripting to floating-point & code comments
Author: mse
Author date (UTC): 2021-08-31 11:06
Committer name: mse
Committer date (UTC): 2021-08-31 11:06
Parent(s): 62e9520aec0471fe023ead290595f735a30eb806
Signing key:
Tree: 717df44fa1b20e6bd49411e88b32725c012747dc
File Lines added Lines deleted
confec.cpp 30 46
include/dialogue.h 39 24
File confec.cpp changed (mode: 100644) (index 993a3e3..e7529a4)
... ... int main( int argc, char* argv[] ){
1107 1107 mt = std::mt19937_64( std::time( nullptr ) ); mt = std::mt19937_64( std::time( nullptr ) );
1108 1108
1109 1109 // This callback is executed from dialogue scripts. // This callback is executed from dialogue scripts.
1110 convo.callback = [&]( std::string param ) -> long long {
1110 convo.callback = [&]( std::string param ) -> double {
1111 1111 if( param.length() > 0 && param[0] == '(' ){ if( param.length() > 0 && param[0] == '(' ){
1112 1112 // Call the S-expression interpreter. // Call the S-expression interpreter.
1113 return (long long)SEval( param );
1113 return SEval( param );
1114 1114 } }
1115 1115 if( param == "QUIT" ) if( param == "QUIT" )
1116 1116 Quit(); Quit();
 
... ... int main( int argc, char* argv[] ){
1120 1120 vn_callback_file = vn_callback_file =
1121 1121 cancel_file.substr( cancel_file.find_last_of( '/' ) + 1 ); cancel_file.substr( cancel_file.find_last_of( '/' ) + 1 );
1122 1122 OpenSettings( param.length() > 9 ? param.substr( 9 ) : "" ); OpenSettings( param.length() > 9 ? param.substr( 9 ) : "" );
1123 return 0;
1123 return 0.0;
1124 1124 } }
1125 1125 if( param.length() > 16 if( param.length() > 16
1126 1126 && param.substr( 0, 15 ) == "CHARACTERSELECT" ){ && param.substr( 0, 15 ) == "CHARACTERSELECT" ){
1127 1127 OpenCharacterSelect( param.substr( 16 ) ); OpenCharacterSelect( param.substr( 16 ) );
1128 return 0;
1128 return 0.0;
1129 1129 } }
1130 1130 if( param == "INPUTNAME" if( param == "INPUTNAME"
1131 1131 && world.followEntity >= 0 && world.followEntity >= 0
 
... ... int main( int argc, char* argv[] ){
1142 1142 &world.entities[world.followEntity].name, &world.entities[world.followEntity].name,
1143 1143 "Name your confectioner" "Name your confectioner"
1144 1144 ); );
1145 return 0;
1145 return 0.0;
1146 1146 } }
1147 1147 if( param.length() > 9 if( param.length() > 9
1148 1148 && param.substr( 0, 8 ) == "GIVEITEM" ){ && param.substr( 0, 8 ) == "GIVEITEM" ){
1149 1149 std::string item_name = param.substr( 9 ); std::string item_name = param.substr( 9 );
1150 1150 if( world.items.find( item_name ) == world.items.end() ){ if( world.items.find( item_name ) == world.items.end() ){
1151 1151 // Error. Item not found. // Error. Item not found.
1152 return -1;
1152 return -1.0;
1153 1153 }else if( world.followEntity >= 0 }else if( world.followEntity >= 0
1154 1154 && world.entities.size() > (size_t)world.followEntity ){ && world.entities.size() > (size_t)world.followEntity ){
1155 1155 // The item and the player character exist. // The item and the player character exist.
 
... ... int main( int argc, char* argv[] ){
1164 1164 inventory_slots inventory_slots
1165 1165 ); );
1166 1166 // Success! // Success!
1167 return 0;
1167 return 0.0;
1168 1168 } }
1169 1169 } }
1170 1170 // Error. The item does not fit in the player's inventory. // Error. The item does not fit in the player's inventory.
1171 return -2;
1171 return -2.0;
1172 1172 } }
1173 1173 if( param.length() >= 9 if( param.length() >= 9
1174 1174 && param.substr( 0, 9 ) == "SEQUENCER" ){ && param.substr( 0, 9 ) == "SEQUENCER" ){
1175 1175 BeginSequencer( param.length() > 10 ? param.substr( 10 ) : "" ); BeginSequencer( param.length() > 10 ? param.substr( 10 ) : "" );
1176 return 0;
1176 return 0.0;
1177 1177 } }
1178 1178 if( param == "FINISHSEQUENCER" ){ if( param == "FINISHSEQUENCER" ){
1179 1179 FinishSequencer(); FinishSequencer();
1180 return 0;
1180 return 0.0;
1181 1181 } }
1182 1182 if( param == "TRADE" ){ if( param == "TRADE" ){
1183 1183 BeginTrading(); BeginTrading();
1184 return 0;
1184 return 0.0;
1185 1185 } }
1186 1186 if( param == "FINISHTRADE" ){ if( param == "FINISHTRADE" ){
1187 1187 FinishTrading(); FinishTrading();
1188 return 0;
1188 return 0.0;
1189 1189 } }
1190 1190 if( param == "CAKE" ){ if( param == "CAKE" ){
1191 1191 BeginCake(); BeginCake();
1192 return 0;
1192 return 0.0;
1193 1193 } }
1194 1194 if( param == "FINISHCAKE" ){ if( param == "FINISHCAKE" ){
1195 1195 FinishCake(); FinishCake();
1196 return 0;
1196 return 0.0;
1197 1197 } }
1198 1198 if( param == "DELETEALL" ){ if( param == "DELETEALL" ){
1199 1199 DeleteAllUserFiles(); DeleteAllUserFiles();
1200 1200 convo.variables.clear(); convo.variables.clear();
1201 1201 InitSettings(); InitSettings();
1202 1202 recipes = {}; recipes = {};
1203 return 0;
1203 return 0.0;
1204 1204 } }
1205 1205 if( param == "DISCONNECT" ){ if( param == "DISCONNECT" ){
1206 1206 if( world.followEntity >= 0 if( world.followEntity >= 0
 
... ... int main( int argc, char* argv[] ){
1219 1219 // Reset the main player stats. // Reset the main player stats.
1220 1220 health = 0; health = 0;
1221 1221 money = 0; money = 0;
1222 return 0;
1222 return 0.0;
1223 1223 } }
1224 1224 if( param == "SAVEGAME" ){ if( param == "SAVEGAME" ){
1225 1225 // Open the Save Game menu. // Open the Save Game menu.
 
... ... int main( int argc, char* argv[] ){
1271 1271 s = "[Auto Save] " + s; s = "[Auto Save] " + s;
1272 1272 i++; i++;
1273 1273 } }
1274 return 0;
1274 return 0.0;
1275 1275 } }
1276 1276 // Save the game. // Save the game.
1277 1277 if( param == "SAVE1" ){ if( param == "SAVE1" ){
1278 1278 SaveGame( 1 ); SaveGame( 1 );
1279 return 0;
1279 return 0.0;
1280 1280 }else if( param == "SAVE2" ){ }else if( param == "SAVE2" ){
1281 1281 SaveGame( 2 ); SaveGame( 2 );
1282 return 0;
1282 return 0.0;
1283 1283 }else if( param == "SAVE3" ){ }else if( param == "SAVE3" ){
1284 1284 SaveGame( 3 ); SaveGame( 3 );
1285 return 0;
1285 return 0.0;
1286 1286 } }
1287 1287 // If none of the above commands are used, transport the player. // If none of the above commands are used, transport the player.
1288 1288 Warp( param ); Warp( param );
1289 return 0;
1289 return 0.0;
1290 1290 }; };
1291 1291
1292 1292 #ifndef __LIGHT__ #ifndef __LIGHT__
 
... ... void vnDataDownload( const std::string &data_path ){
2036 2036 // Copy VN stats into the player's bribeItems. // Copy VN stats into the player's bribeItems.
2037 2037 for( auto &v : convo.variables ){ for( auto &v : convo.variables ){
2038 2038 if( v.key.length() > 6 && v.key.substr( 0, 6 ) == "stats." ) if( v.key.length() > 6 && v.key.substr( 0, 6 ) == "stats." )
2039 player.bribeItems.set( v.key.substr( 6 ), v.valueInt );
2039 player.bribeItems.set( v.key.substr( 6 ), v.valueNumber );
2040 2040 } }
2041 2041 } }
2042 2042 // Override the CANCEL ID. // Override the CANCEL ID.
 
... ... void SyncSaveData( size_t slot ){
2771 2771 save_names.resize( slot ); save_names.resize( slot );
2772 2772 save_names[slot - 1] = ent.name; save_names[slot - 1] = ent.name;
2773 2773 convo.setVariable( "health" + n, ent.health ); convo.setVariable( "health" + n, ent.health );
2774 convo.setVariable( "day" + n, ent.age / day_duration + 1.0 );
2774 convo.setVariable( "day" + n, (long long)( ent.age / day_duration + 1.0 ) );
2775 2775 convo.setVariable( "money" + n, ent.money ); convo.setVariable( "money" + n, ent.money );
2776 2776 }else{ }else{
2777 2777 // Works with a fixed number of save slots (usually 3). // Works with a fixed number of save slots (usually 3).
 
... ... void SyncSaveData( size_t slot ){
2784 2784 ); );
2785 2785 save_names.push_back( ent.name ); save_names.push_back( ent.name );
2786 2786 convo.setVariable( "health" + n, ent.health ); convo.setVariable( "health" + n, ent.health );
2787 convo.setVariable( "day" + n, ent.age / day_duration + 1.0 );
2787 convo.setVariable( "day" + n, (long long)( ent.age / day_duration + 1.0 ) );
2788 2788 convo.setVariable( "money" + n, ent.money ); convo.setVariable( "money" + n, ent.money );
2789 2789 } }
2790 2790 } }
 
... ... void LoadMap( std::string file_path, bool autosave ){
3224 3224 // Load a 2D map. // Load a 2D map.
3225 3225 convo.setVariable( "mainmenu", 0 ); convo.setVariable( "mainmenu", 0 );
3226 3226 mainmenu = 0; mainmenu = 0;
3227 /*
3228 // Cancel the player's rest state to avoid bugs.
3229 if( world.followEntity >= 0
3230 && world.entities.size() > (size_t)world.followEntity ){
3231 auto &player = world.entities[world.followEntity];
3232 printf( "hi\n" );
3233 player.task = fworld::TASK_NONE;
3234 player.stun = 0.0;
3235 player.staticCollisions = false;
3236 world.recalculateMapEntities();
3237 player = {};
3238 }
3239 world.mapChanged = true;
3240 world.mapFile = "";
3241 world.unloadMap();
3242 */
3243 ////////////////////////////////////////////////////// TODOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3227 // Former site of non-cancelled player rest state bug.
3244 3228 // Loading it twice doesn't do anything. Why do old entities go on top of new tiles? // Loading it twice doesn't do anything. Why do old entities go on top of new tiles?
3245 3229 world.loadMap( data_path, file_path ); world.loadMap( data_path, file_path );
3246 3230 }else{ }else{
 
... ... void Render(){
4664 4648 std::string &str = fgl::textInputString; std::string &str = fgl::textInputString;
4665 4649 if( str.length() > 6 if( str.length() > 6
4666 4650 && str.substr( 0, 5 ) == "/call" ){ && str.substr( 0, 5 ) == "/call" ){
4667 long long result =
4651 double result =
4668 4652 convo.getVariable( "CALLBACK " + str.substr( 6 ) ); convo.getVariable( "CALLBACK " + str.substr( 6 ) );
4669 4653 if( result ){ if( result ){
4670 4654 Notify( Notify(
4671 4655 &tex_notification, &tex_notification,
4672 4656 "Callback returned:", "Callback returned:",
4673 std::to_string( result )
4657 convo.stringifyNumber( result )
4674 4658 ); );
4675 4659 } }
4676 4660 }else if( str.length() > 5 }else if( str.length() > 5
4677 4661 && str.substr( 0, 4 ) == "/get" ){ && str.substr( 0, 4 ) == "/get" ){
4678 long long result =
4662 double result =
4679 4663 convo.getVariable( str.substr( 5 ) ); convo.getVariable( str.substr( 5 ) );
4680 4664 Notify( Notify(
4681 4665 &tex_notification, &tex_notification,
4682 std::to_string( result ),
4666 convo.stringifyNumber( result ),
4683 4667 "" ""
4684 4668 ); );
4685 4669 }else if( str.length() > 5 }else if( str.length() > 5
 
... ... void Render(){
4709 4693 // Numeric value. // Numeric value.
4710 4694 convo.setVariable( convo.setVariable(
4711 4695 param.substr( 0, space_at ), param.substr( 0, space_at ),
4712 (long long)num
4696 (double)num
4713 4697 ); );
4714 4698 } }
4715 4699 } }
File include/dialogue.h changed (mode: 100755) (index 868d2f2..53c9c2d)
9 9
10 10 #include <cstdlib> #include <cstdlib>
11 11 #include <ctime> #include <ctime>
12 #include <climits>
12 #include <cmath>
13 13
14 14 #include <functional> #include <functional>
15 15 #include <string> #include <string>
 
... ... namespace dialogue {
25 25 // Key/value pairs. // Key/value pairs.
26 26 struct Variable { struct Variable {
27 27 std::string key; std::string key;
28 long long valueInt;
28 double valueNumber;
29 29 }; };
30 30
31 31 // Arithmetic operations on key/value pairs. // Arithmetic operations on key/value pairs.
32 32 struct Operation { struct Operation {
33 33 std::string key; std::string key;
34 34 char op; char op;
35 long long valueInt;
35 double valueNumber;
36 36 std::string valueKey; // Has length > 0 if value is to be derived from variable. std::string valueKey; // Has length > 0 if value is to be derived from variable.
37 37 }; };
38 38
 
... ... class Talk {
55 55 Screen screen; Screen screen;
56 56 std::vector<Screen> screens; std::vector<Screen> screens;
57 57 std::vector<Variable> variables; std::vector<Variable> variables;
58 std::function<long long(std::string)> callback;
58 std::function<double(std::string)> callback;
59 59 void go( std::string id ); void go( std::string id );
60 60 void append( std::string filePath ); void append( std::string filePath );
61 61 bool hasScreen( std::string id ); bool hasScreen( std::string id );
62 62 Screen getScreen( std::string id ); Screen getScreen( std::string id );
63 63 void setScreen( Screen scr ); void setScreen( Screen scr );
64 64 bool hasVariable( std::string key ); bool hasVariable( std::string key );
65 long long getVariable( std::string key );
66 void setVariable( std::string key, long long valueInt );
65 double getVariable( std::string key );
66 void setVariable( std::string key, double valueNumber );
67 67 int parseJSON( std::string &text ); int parseJSON( std::string &text );
68 std::string stringifyNumber( double n );
68 69 Talk( std::string filePath = "" ); Talk( std::string filePath = "" );
69 70 private: private:
70 71 void operate( Operation o ); void operate( Operation o );
 
... ... bool Talk::hasVariable( std::string key ){
177 178 return false; return false;
178 179 } }
179 180
180 long long Talk::getVariable( std::string key ){
181 double Talk::getVariable( std::string key ){
181 182 if( key == "RAND" ){ if( key == "RAND" ){
182 183 // Returns a (pseudo)random number. // Returns a (pseudo)random number.
183 184 return std::rand(); return std::rand();
 
... ... long long Talk::getVariable( std::string key ){
191 192 std::string arg = key.length() >= 7 ? key.substr( 6 ) : ""; std::string arg = key.length() >= 7 ? key.substr( 6 ) : "";
192 193 transformString( arg ); transformString( arg );
193 194 go( arg ); go( arg );
194 return 0;
195 return 0.0;
195 196 }else{ }else{
196 197 // Returns the value of a variable if it exists, otherwise 0. // Returns the value of a variable if it exists, otherwise 0.
197 198 for( Variable &v : variables ){ for( Variable &v : variables ){
198 if( v.key == key ) return v.valueInt;
199 if( v.key == key ) return v.valueNumber;
199 200 } }
200 201 } }
201 return 0;
202 return 0.0;
202 203 } }
203 204
204 void Talk::setVariable( std::string key, long long valueInt ){
205 void Talk::setVariable( std::string key, double valueNumber ){
205 206 for( Variable &v : variables ){ for( Variable &v : variables ){
206 207 if( v.key == key ){ if( v.key == key ){
207 v.valueInt = valueInt;
208 v.valueNumber = valueNumber;
208 209 return; return;
209 210 } }
210 211 } }
211 variables.push_back( { key, valueInt } );
212 variables.push_back( { key, valueNumber } );
212 213 } }
213 214
214 215 int Talk::parseJSON( std::string &text ){ int Talk::parseJSON( std::string &text ){
 
... ... int Talk::parseJSON( std::string &text ){
250 251 exec.push_back( { exec.push_back( {
251 252 key, key,
252 253 op, op,
253 o.value.getInt64(),
254 o.value.getDouble(),
254 255 o.value.isString() ? viewToString( o.value.getString() ) : "" o.value.isString() ? viewToString( o.value.getString() ) : ""
255 256 } ); } );
256 257 } }
 
... ... int Talk::parseJSON( std::string &text ){
284 285 return 0; return 0;
285 286 } }
286 287
288 // If n can be losslessly round-tripped between a double, a long long,
289 // and a double, return a string without a decimal. Otherwise, return a
290 // string with sprintf-style floating-point notation.
291 std::string Talk::stringifyNumber( double n ){
292 long long rounded = std::llround( n );
293 if( (double)rounded == n ){
294 // Number is either an integer or so weird it's an extreme corner case.
295 return std::to_string( rounded );
296 }else{
297 // Number is not an integer.
298 return std::to_string( n );
299 }
300 }
301
287 302 Talk::Talk( std::string filePath ){ Talk::Talk( std::string filePath ){
288 303 callback = []( std::string param ){ callback = []( std::string param ){
289 304 // Suppress unused parameter warnings. // Suppress unused parameter warnings.
290 305 param = ""; param = "";
291 return 0;
306 return 0.0;
292 307 }; };
293 308 if( filePath.length() > 0 ){ if( filePath.length() > 0 ){
294 309 go( filePath ); go( filePath );
 
... ... Talk::Talk( std::string filePath ){
296 311 } }
297 312
298 313 void Talk::operate( Operation o ){ void Talk::operate( Operation o ){
299 long long in = o.valueInt, out = getVariable( o.key );
314 double in = o.valueNumber, out = getVariable( o.key );
300 315 if( o.valueKey.length() > 0 ){ if( o.valueKey.length() > 0 ){
301 316 in = getVariable( o.valueKey ); in = getVariable( o.valueKey );
302 317 } }
303 318 switch( o.op ){ switch( o.op ){
304 319 case ':': out = in; case ':': out = in;
305 320 break; break;
306 case '=': out = ( out == in ) ? 1 : 0;
321 case '=': out = ( out == in ) ? 1.0 : 0.0;
307 322 break; break;
308 case '!': out = ( out != in ) ? 1 : 0;
323 case '!': out = ( out != in ) ? 1.0 : 0.0;
309 324 break; break;
310 case '<': out = ( out < in ) ? 1 : 0;
325 case '<': out = ( out < in ) ? 1.0 : 0.0;
311 326 break; break;
312 case '>': out = ( out > in ) ? 1 : 0;
327 case '>': out = ( out > in ) ? 1.0 : 0.0;
313 328 break; break;
314 case '?': out = ( out != 0 ) ? in : 0;
329 case '?': out = out ? in : 0.0;
315 330 break; break;
316 case '%': out %= in;
331 case '%': out = std::fmod( out, in );
317 332 break; break;
318 333 case '*': out *= in; case '*': out *= in;
319 334 break; break;
320 case '/': out = ( in == 0 ) ? LLONG_MAX : out / in;
335 case '/': out /= in;
321 336 break; break;
322 337 case '+': out += in; case '+': out += in;
323 338 break; break;
 
... ... void Talk::transformString( std::string &str ){
335 350 case '`': case '`':
336 351 if( replacing ){ if( replacing ){
337 352 if( i - start > 1 ){ if( i - start > 1 ){
338 outStr += std::to_string( getVariable( str.substr( start + 1, i - start - 1 ) ) );
353 outStr += stringifyNumber( getVariable( str.substr( start + 1, i - start - 1 ) ) );
339 354 } }
340 355 start = i + 1; start = i + 1;
341 356 }else{ }else{
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/mse/ConfectionerEngine

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/mse/ConfectionerEngine

Clone this repository using git:
git clone git://git.rocketgit.com/user/mse/ConfectionerEngine

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main