diff --git a/engine/openbor.c b/engine/openbor.c index 5ad67de3b..36774f6e0 100644 --- a/engine/openbor.c +++ b/engine/openbor.c @@ -855,6 +855,7 @@ Script updated_script; //execute when ingame update finished Script loading_script; // in loading screen Script input_script_all; //keyscript for all players Script key_script_all; //keyscript for all players +Script score_script_all; //score listener for all players Script timetick_script; //time tick script. //player script @@ -1182,6 +1183,7 @@ void init_scripts() Script_Init(&endlevel_script, "endlevel", NULL, 1); Script_Init(&input_script_all, "inputall", NULL, 1); Script_Init(&key_script_all, "keyall", NULL, 1); + Script_Init(&score_script_all, "scoreall", NULL, 1); Script_Init(&timetick_script, "timetick", NULL, 1); Script_Init(&loading_script, "loading", NULL, 1); for(i = 0; i < MAX_PLAYERS; i++) @@ -1238,6 +1240,10 @@ void load_scripts() { Script_Clear(&key_script_all, 2); } + if(!load_script(&score_script_all, "data/scripts/scoreall.c")) + { + Script_Clear(&score_script_all, 2); + } if(!load_script(&timetick_script, "data/scripts/timetick.c")) { Script_Clear(&timetick_script, 2); @@ -1332,6 +1338,7 @@ void load_scripts() Script_Compile(&endlevel_script); Script_Compile(&input_script_all); Script_Compile(&key_script_all); + Script_Compile(&score_script_all); Script_Compile(&timetick_script); Script_Compile(&loading_script); for(i = 0; i < MAX_PLAYERS; i++) @@ -1381,6 +1388,7 @@ void clear_scripts() Script_Clear(&endlevel_script, 2); Script_Clear(&input_script_all, 2); Script_Clear(&key_script_all, 2); + Script_Clear(&score_script_all, 2); Script_Clear(&timetick_script, 2); Script_Clear(&loading_script, 2); for(i = 0; i < MAX_PLAYERS; i++) @@ -2422,7 +2430,8 @@ void execute_spawn_script(s_spawn_entry *p, entity *e) tempvar.dblVal = (DOUBLE)p->position.z; Script_Set_Local_Variant(cs, "spawnz", &tempvar); tempvar.dblVal = (DOUBLE)p->position.y; - Script_Set_Local_Variant(cs, "spawna", &tempvar); + Script_Set_Local_Variant(cs, "spawna", &tempvar); // Legacy alias for spawny. + Script_Set_Local_Variant(cs, "spawny", &tempvar); ScriptVariant_ChangeType(&tempvar, VT_INTEGER); tempvar.lVal = (LONG)p->at; Script_Set_Local_Variant(cs, "spawnat", &tempvar); @@ -2435,6 +2444,7 @@ void execute_spawn_script(s_spawn_entry *p, entity *e) Script_Set_Local_Variant(cs, "spawnx", &tempvar); Script_Set_Local_Variant(cs, "spawnz", &tempvar); Script_Set_Local_Variant(cs, "spawna", &tempvar); + Script_Set_Local_Variant(cs, "spawny", &tempvar); Script_Set_Local_Variant(cs, "spawnat", &tempvar); } } @@ -2502,6 +2512,37 @@ void execute_key_script_all(int player) } } +/* +* Caskey, Damon V. +* 2026-08-20 +* +* Execute the shared score listener with the zero-based +* player index and signed 64-bit score adjustment. +*/ +void execute_score_script_all(int playerindex, int64_t score) +{ + ScriptVariant tempvar; + Script *cs = &score_script_all; + + if(Script_IsInitialized(cs)) + { + ScriptVariant_Init(&tempvar); + ScriptVariant_ChangeType(&tempvar, VT_INTEGER); + tempvar.lVal = (LONG)playerindex; + Script_Set_Local_Variant(cs, "player", &tempvar); + + ScriptVariant_ChangeType(&tempvar, VT_INTEGER64); + tempvar.llVal = score; + Script_Set_Local_Variant(cs, "score", &tempvar); + + Script_Execute(cs); + + ScriptVariant_Clear(&tempvar); + Script_Set_Local_Variant(cs, "player", &tempvar); + Script_Set_Local_Variant(cs, "score", &tempvar); + } +} + void execute_timetick_script(int time, int gotime) { ScriptVariant tempvar; @@ -4084,6 +4125,25 @@ static bool command_token_get_uint64(const s_command_token* token, uint64_t* res return true; } +/* +* Caskey, Damon V. +* 2026-08-20 +* +* Convert a null-terminated command argument to a +* signed 64-bit integer using the bounded token parser. +*/ +static bool command_argument_get_int64(const char* argument, int64_t* result) { + s_command_token token; + + assert(argument); + assert(result); + + token.text = argument; + token.length = strlen(argument); + + return command_token_get_int64(&token, result); +} + /* * Caskey, Damon V. * 2026-08-06 @@ -15743,7 +15803,16 @@ s_model *load_cached_model(char *name, char *owner, char unload) } break; case CMD_MODEL_SCORE: - newchar->score = GET_INT_ARG(1); + if(!command_argument_get_int64(GET_ARG(1), &newchar->score)) + { + borShutdown( + 1, + "Invalid signed 64-bit score '%s' in %s, line %zu.\n", + GET_ARG(1), + filename, + line + ); + } newchar->multiple = GET_INT_ARG(2); // New var multiple for force/scoring break; case CMD_MODEL_SMARTBOMB: @@ -24582,10 +24651,15 @@ void load_level(char *filename) break; case CMD_LEVEL_SCORE: // So score can be overriden in the levels .txt file - next.score = GET_INT_ARG(1); - if(next.score == -1) + if(!command_argument_get_int64(GET_ARG(1), &next.score)) { - next.score = 0; // So negative values cannot be added + borShutdown( + 1, + "Invalid signed 64-bit score '%s' in %s, line %d.\n", + GET_ARG(1), + filename, + line + ); } next.multiple = GET_INT_ARG(2); if(next.multiple == -1) @@ -25387,7 +25461,9 @@ void updatestatus() { } if(set->continuescore == 2) { - player[i].score = player[i].score + 1; + if(player[i].score < UINT64_MAX) { + player[i].score++; + } } } } @@ -26212,13 +26288,13 @@ void predrawstatus() tmp = player[i].score; //work around issue on 64bit where sizeof(long) != sizeof(int) if(!pscore[i][2] && !pscore[i][3] && !pscore[i][4] && !pscore[i][5]) { - font_printf(videomodes.shiftpos[i] + pscore[i][0], savedata.windowpos + pscore[i][1], pscore[i][6], 0, (scoreformat ? "%s - %09lu" : "%s - %lu"), (char *)(player[i].ent->name), tmp); + font_printf(videomodes.shiftpos[i] + pscore[i][0], savedata.windowpos + pscore[i][1], pscore[i][6], 0, (scoreformat ? "%s - %09" PRIu64 : "%s - %" PRIu64), (char *)(player[i].ent->name), tmp); } else { font_printf(videomodes.shiftpos[i] + pscore[i][0], savedata.windowpos + pscore[i][1], pscore[i][6], 0, "%s", player[i].ent->name); font_printf(videomodes.shiftpos[i] + pscore[i][2], savedata.windowpos + pscore[i][3], pscore[i][6], 0, "-"); - font_printf(videomodes.shiftpos[i] + pscore[i][4], savedata.windowpos + pscore[i][5], pscore[i][6], 0, (scoreformat ? "%09lu" : "%lu"), tmp); + font_printf(videomodes.shiftpos[i] + pscore[i][4], savedata.windowpos + pscore[i][5], pscore[i][6], 0, (scoreformat ? "%09" PRIu64 : "%" PRIu64), tmp); } if(player[i].ent->energy_state.health_current <= 0) @@ -26617,10 +26693,21 @@ void update_loading(s_loadingbar *s, int value, int max) } } -void addscore(int playerindex, int add) +/* +* Caskey, Damon V. +* 2026-08-20 +* +* Apply a signed 64-bit score adjustment to an unsigned +* player score. Saturate at zero or UINT64_MAX, award +* crossed life thresholds without an unbounded loop, and +* execute the player's score script. +*/ +void addscore(int playerindex, int64_t add) { + uint64_t add_magnitude = 0; + uint64_t life_awards = 0; + uint64_t old_score = 0; uint64_t s = 0; - uint64_t next1up = 0; ScriptVariant var; // used for execute script Script *cs; @@ -26631,28 +26718,56 @@ void addscore(int playerindex, int add) playerindex &= 3; - s = player[playerindex].score; + old_score = player[playerindex].score; + s = old_score; cs = score_script + playerindex; - if (lifescore > 0) next1up = ((s / lifescore) + 1) * lifescore; - else lifescore = 0; + if(add >= 0) + { + add_magnitude = (uint64_t)add; - s += add; - if(s > 999999999) + if(add_magnitude > UINT64_MAX - s) + { + s = UINT64_MAX; + } + else + { + s += add_magnitude; + } + } + else { - s = 999999999; + add_magnitude = add == INT64_MIN + ? (uint64_t)INT64_MAX + UINT64_C(1) + : (uint64_t)-add; + + s = add_magnitude > s ? 0 : s - add_magnitude; } - while(s > next1up) + /* + * Preserve the legacy strict threshold comparison: a + * life is awarded only after score exceeds the boundary. + */ + if(lifescore > 0 && s > old_score) { + life_awards = ((s - 1) / lifescore) - (old_score / lifescore); + } + if(life_awards > 0) + { if(global_sample_list.one_up >= 0) { sound_play_sample(global_sample_list.one_up, 0, savedata.effectvol, savedata.effectvol, 100); } - player[playerindex].lives++; - next1up += lifescore; + if(life_awards > UINT64_MAX - player[playerindex].lives) + { + player[playerindex].lives = UINT64_MAX; + } + else + { + player[playerindex].lives += life_awards; + } } player[playerindex].score = s; @@ -26661,13 +26776,15 @@ void addscore(int playerindex, int add) if(Script_IsInitialized(cs)) { ScriptVariant_Init(&var); - ScriptVariant_ChangeType(&var, VT_INTEGER); - var.lVal = (LONG)add; + ScriptVariant_ChangeType(&var, VT_INTEGER64); + var.llVal = add; Script_Set_Local_Variant(cs, "score", &var); Script_Execute(cs); ScriptVariant_Clear(&var); Script_Set_Local_Variant(cs, "score", &var); } + + execute_score_script_all(playerindex, add); } @@ -52118,7 +52235,7 @@ void hallfame(int addtoscore) { int done = 0; int topten[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - u32 score; + uint64_t score; char name[MAX_NAME_LEN + 1]; int i, p, y; char tmpBuff[MAX_BUFFER_LEN] = {""}; @@ -52181,7 +52298,7 @@ void hallfame(int addtoscore) for(i = 0; i < 10; i++) { font_printf(_colx(topten[i], col1), y + videomodes.vShift, topten[i], 0, "%2i. %s", i + 1, savescore.hscoren[i]); - font_printf(_colx(topten[i], col2), y + videomodes.vShift, topten[i], 0, (scoreformat ? "%09lu" : "%u"), savescore.highsc[i]); + font_printf(_colx(topten[i], col2), y + videomodes.vShift, topten[i], 0, (scoreformat ? "%09" PRIu64 : "%" PRIu64), savescore.highsc[i]); y += (videomodes.vRes - videomodes.vShift - 56 - 32) / 10; //font_heights[topten[i]] + 6; } @@ -52283,7 +52400,7 @@ void showcomplete(int num) font_printf(videomodes.hShift + tscore[0], videomodes.vShift + tscore[1], 0, 0, Tr("Total Score")); for(i = 0, j = 2, k = 3; i < levelsets[current_set].maxplayers; i++, j = j + 2, k = k + 2) if(player[i].lives > 0) { - font_printf(videomodes.hShift + tscore[j], videomodes.vShift + tscore[k], 0, 0, (scoreformat ? "%09lu" : "%lu"), player[i].score); + font_printf(videomodes.hShift + tscore[j], videomodes.vShift + tscore[k], 0, 0, (scoreformat ? "%09" PRIu64 : "%" PRIu64), player[i].score); } while(_time > nexttime) diff --git a/engine/openbor.h b/engine/openbor.h index ff629c737..cf46ee4ed 100644 --- a/engine/openbor.h +++ b/engine/openbor.h @@ -75,8 +75,8 @@ typedef uint64_t key_mask_t; "Special thanks to SEGA and SNK.\n\n" #define COMPATIBLEVERSION 0x00033749 -#define CV_SAVED_GAME 0x00033748 -#define CV_HIGH_SCORE 0x00033747 +#define CV_SAVED_GAME 0x00033750 +#define CV_HIGH_SCORE 0x00033751 #define GAME_SPEED_DEFAULT 200 #define THINK_SPEED 2 #define COUNTER_SPEED_DEFAULT (GAME_SPEED_DEFAULT*2) @@ -3605,7 +3605,7 @@ typedef struct int index; // Assign on model read. ~~ char *name; // Model name and default entity name. ~~ char *path; // Path, so scripts can dynamically get files, sprites, sounds, etc. ~~ - unsigned score; // Points given to player when defeated or collected as item. ~~ + int64_t score; // Points added to or deducted from the player when defeated or collected as item. ~~ int health; // Starting and maximum hit points. ~~ hp float scroll; // Autoscroll like panel entity. ~~ unsigned offscreenkill; // Distance allowed out of screen until killed. ~~ @@ -4104,7 +4104,7 @@ typedef struct char alias[MAX_NAME_LEN]; int health[MAX_PLAYERS]; int mp; // mp's variable for mpbar by tails - unsigned score; // So score can be overridden for enemies/obstacles + int64_t score; // So score can be overridden for enemies/obstacles int multiple; // So score can be overridden for enemies/obstacles s_axis_principal_float position; //x, y, z location. unsigned credit; @@ -4748,7 +4748,8 @@ unsigned char *model_get_colourmap(s_model *model, unsigned which); void ent_set_colourmap(entity *ent, uint64_t which); void predrawstatus(); void drawstatus(); -void addscore(int playerindex, int add); +void addscore(int playerindex, int64_t add); +void execute_score_script_all(int playerindex, int64_t score); void free_ent(entity *e); void free_ents(); int alloc_ents(); @@ -5048,7 +5049,7 @@ typedef struct unsigned stage; // Stage unsigned pLives[MAX_PLAYERS]; // Player Lives Left unsigned pCredits[MAX_PLAYERS]; // Player Credits Left - unsigned pScores[MAX_PLAYERS]; // Player Scores + uint64_t pScores[MAX_PLAYERS]; // Player Scores unsigned credits; // Number Of Credits unsigned times_completed; unsigned which_set; @@ -5071,7 +5072,7 @@ typedef struct typedef struct { unsigned compatibleversion; - unsigned highsc[10]; + uint64_t highsc[10]; char hscoren[10][MAX_NAME_LEN]; } s_savescore; diff --git a/engine/source/openborscript/model.c b/engine/source/openborscript/model.c index 368b32acd..ae0eca983 100644 --- a/engine/source/openborscript/model.c +++ b/engine/source/openborscript/model.c @@ -312,7 +312,7 @@ static const model_property_info model_properties[] = { .id_string = "MODEL_PROPERTY_SCORE", .config_flags = PROPERTY_ACCESS_CONFIG_MACRO_DEFAULT, .offset = PROPERTY_MEMBER_OFFSET(s_model, score), - .type = VT_INTEGER }, + .type = VT_INTEGER64 }, {.property = MODEL_PROPERTY_SCROLL, .id_string = "MODEL_PROPERTY_SCROLL", @@ -555,5 +555,3 @@ HRESULT openbor_set_model_property(ScriptVariant** varlist, ScriptVariant** cons } - -